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.
package/dist/index.d.cts CHANGED
@@ -234,20 +234,14 @@ declare function diffBlocks(blocksA: IRBlock[], blocksB: IRBlock[]): DiffResult;
234
234
  declare function extractFormFields(blocks: IRBlock[]): FormResult;
235
235
 
236
236
  /**
237
- * Markdown → HWPX 역변환 (MVP)
237
+ * Markdown → HWPX 역변환
238
238
  *
239
- * 지원: 단락, 헤딩, 테이블 (텍스트+구조만, 스타일 없음)
239
+ * 지원: 헤딩(h1~h6), 단락, 볼드, 이탤릭, 인라인코드, 코드블록,
240
+ * 순서/비순서 리스트, 수평선, 인용문, 테이블
240
241
  * jszip으로 HWPX ZIP 패키징.
241
242
  */
242
243
  /**
243
244
  * 마크다운 텍스트를 HWPX (ArrayBuffer)로 변환.
244
- *
245
- * @example
246
- * ```ts
247
- * import { markdownToHwpx } from "kordoc"
248
- * const hwpxBuffer = await markdownToHwpx("# 제목\n\n본문 텍스트")
249
- * writeFileSync("output.hwpx", Buffer.from(hwpxBuffer))
250
- * ```
251
245
  */
252
246
  declare function markdownToHwpx(markdown: string): Promise<ArrayBuffer>;
253
247
 
package/dist/index.d.ts CHANGED
@@ -234,20 +234,14 @@ declare function diffBlocks(blocksA: IRBlock[], blocksB: IRBlock[]): DiffResult;
234
234
  declare function extractFormFields(blocks: IRBlock[]): FormResult;
235
235
 
236
236
  /**
237
- * Markdown → HWPX 역변환 (MVP)
237
+ * Markdown → HWPX 역변환
238
238
  *
239
- * 지원: 단락, 헤딩, 테이블 (텍스트+구조만, 스타일 없음)
239
+ * 지원: 헤딩(h1~h6), 단락, 볼드, 이탤릭, 인라인코드, 코드블록,
240
+ * 순서/비순서 리스트, 수평선, 인용문, 테이블
240
241
  * jszip으로 HWPX ZIP 패키징.
241
242
  */
242
243
  /**
243
244
  * 마크다운 텍스트를 HWPX (ArrayBuffer)로 변환.
244
- *
245
- * @example
246
- * ```ts
247
- * import { markdownToHwpx } from "kordoc"
248
- * const hwpxBuffer = await markdownToHwpx("# 제목\n\n본문 텍스트")
249
- * writeFileSync("output.hwpx", Buffer.from(hwpxBuffer))
250
- * ```
251
245
  */
252
246
  declare function markdownToHwpx(markdown: string): Promise<ArrayBuffer>;
253
247
 
package/dist/index.js CHANGED
@@ -139,7 +139,7 @@ import { inflateRawSync } from "zlib";
139
139
  import { DOMParser } from "@xmldom/xmldom";
140
140
 
141
141
  // src/utils.ts
142
- var VERSION = true ? "2.2.1" : "0.0.0-dev";
142
+ var VERSION = true ? "2.2.3" : "0.0.0-dev";
143
143
  function toArrayBuffer(buf) {
144
144
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
145
145
  return buf.buffer;
@@ -457,9 +457,47 @@ function blocksToMarkdown(blocks) {
457
457
  }
458
458
  return lines.join("\n").trim();
459
459
  }
460
+ function hasMergedCells(table) {
461
+ for (const row of table.cells) {
462
+ for (const cell of row) {
463
+ if (cell.colSpan > 1 || cell.rowSpan > 1) return true;
464
+ }
465
+ }
466
+ return false;
467
+ }
468
+ function tableToHtml(table) {
469
+ const { cells, rows: numRows, cols: numCols } = table;
470
+ const skip = /* @__PURE__ */ new Set();
471
+ const lines = ["<table>"];
472
+ for (let r = 0; r < numRows; r++) {
473
+ const tag = r === 0 ? "th" : "td";
474
+ const rowHtml = [];
475
+ for (let c = 0; c < numCols; c++) {
476
+ if (skip.has(`${r},${c}`)) continue;
477
+ const cell = cells[r]?.[c];
478
+ if (!cell) continue;
479
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
480
+ for (let dc = 0; dc < cell.colSpan; dc++) {
481
+ if (dr === 0 && dc === 0) continue;
482
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
483
+ }
484
+ }
485
+ const text = sanitizeText(cell.text).replace(/\n/g, "<br>");
486
+ const attrs = [];
487
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
488
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
489
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
490
+ rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`);
491
+ }
492
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
493
+ }
494
+ lines.push("</table>");
495
+ return lines.join("\n");
496
+ }
460
497
  function tableToMarkdown(table) {
461
498
  if (table.rows === 0 || table.cols === 0) return "";
462
499
  const { cells, rows: numRows, cols: numCols } = table;
500
+ if (hasMergedCells(table)) return tableToHtml(table);
463
501
  if (numRows === 1 && numCols === 1) {
464
502
  const content = sanitizeText(cells[0][0].text);
465
503
  if (!content) return "";
@@ -6261,6 +6299,23 @@ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
6261
6299
  var NS_OPF = "http://www.idpf.org/2007/opf/";
6262
6300
  var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
6263
6301
  var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
6302
+ var CHAR_NORMAL = 0;
6303
+ var CHAR_BOLD = 1;
6304
+ var CHAR_ITALIC = 2;
6305
+ var CHAR_BOLD_ITALIC = 3;
6306
+ var CHAR_CODE = 4;
6307
+ var CHAR_H1 = 5;
6308
+ var CHAR_H2 = 6;
6309
+ var CHAR_H3 = 7;
6310
+ var CHAR_H4 = 8;
6311
+ var PARA_NORMAL = 0;
6312
+ var PARA_H1 = 1;
6313
+ var PARA_H2 = 2;
6314
+ var PARA_H3 = 3;
6315
+ var PARA_H4 = 4;
6316
+ var PARA_CODE = 5;
6317
+ var PARA_QUOTE = 6;
6318
+ var PARA_LIST = 7;
6264
6319
  async function markdownToHwpx(markdown) {
6265
6320
  const blocks = parseMarkdownToBlocks(markdown);
6266
6321
  const sectionXml = blocksToSectionXml(blocks);
@@ -6282,6 +6337,25 @@ function parseMarkdownToBlocks(md) {
6282
6337
  i++;
6283
6338
  continue;
6284
6339
  }
6340
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
6341
+ if (fenceMatch) {
6342
+ const fence = fenceMatch[1];
6343
+ const lang = fenceMatch[2].trim();
6344
+ const codeLines = [];
6345
+ i++;
6346
+ while (i < lines.length && !lines[i].startsWith(fence)) {
6347
+ codeLines.push(lines[i]);
6348
+ i++;
6349
+ }
6350
+ if (i < lines.length) i++;
6351
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
6352
+ continue;
6353
+ }
6354
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
6355
+ blocks.push({ type: "hr" });
6356
+ i++;
6357
+ continue;
6358
+ }
6285
6359
  const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
6286
6360
  if (headingMatch) {
6287
6361
  blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
@@ -6300,19 +6374,101 @@ function parseMarkdownToBlocks(md) {
6300
6374
  if (cells.length > 0) tableRows.push(cells);
6301
6375
  i++;
6302
6376
  }
6303
- if (tableRows.length > 0) {
6304
- blocks.push({ type: "table", rows: tableRows });
6377
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
6378
+ continue;
6379
+ }
6380
+ if (line.trimStart().startsWith("> ")) {
6381
+ const quoteLines = [];
6382
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
6383
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
6384
+ i++;
6385
+ }
6386
+ for (const ql of quoteLines) {
6387
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
6305
6388
  }
6306
6389
  continue;
6307
6390
  }
6391
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
6392
+ if (listMatch) {
6393
+ const indent = Math.floor(listMatch[1].length / 2);
6394
+ const ordered = /\d/.test(listMatch[2]);
6395
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
6396
+ i++;
6397
+ continue;
6398
+ }
6308
6399
  blocks.push({ type: "paragraph", text: line.trim() });
6309
6400
  i++;
6310
6401
  }
6311
6402
  return blocks;
6312
6403
  }
6404
+ function parseInlineMarkdown(text) {
6405
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
6406
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
6407
+ text = text.replace(/~~([^~]+)~~/g, "$1");
6408
+ const spans = [];
6409
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
6410
+ let lastIdx = 0;
6411
+ for (const match of text.matchAll(regex)) {
6412
+ const idx = match.index;
6413
+ if (idx > lastIdx) {
6414
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
6415
+ }
6416
+ const raw = match[0];
6417
+ if (raw.startsWith("`")) {
6418
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
6419
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
6420
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
6421
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
6422
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
6423
+ } else {
6424
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
6425
+ }
6426
+ lastIdx = idx + raw.length;
6427
+ }
6428
+ if (lastIdx < text.length) {
6429
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
6430
+ }
6431
+ if (spans.length === 0) {
6432
+ spans.push({ text, bold: false, italic: false, code: false });
6433
+ }
6434
+ return spans;
6435
+ }
6436
+ function spanToCharPrId(span) {
6437
+ if (span.code) return CHAR_CODE;
6438
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
6439
+ if (span.bold) return CHAR_BOLD;
6440
+ if (span.italic) return CHAR_ITALIC;
6441
+ return CHAR_NORMAL;
6442
+ }
6313
6443
  function escapeXml(text) {
6314
6444
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6315
6445
  }
6446
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
6447
+ const spans = parseInlineMarkdown(text);
6448
+ return spans.map((span) => {
6449
+ const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
6450
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
6451
+ }).join("");
6452
+ }
6453
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
6454
+ if (paraPrId === PARA_CODE) {
6455
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
6456
+ }
6457
+ const runs = generateRuns(text, charPrId);
6458
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
6459
+ }
6460
+ function headingParaPrId(level) {
6461
+ if (level === 1) return PARA_H1;
6462
+ if (level === 2) return PARA_H2;
6463
+ if (level === 3) return PARA_H3;
6464
+ return PARA_H4;
6465
+ }
6466
+ function headingCharPrId(level) {
6467
+ if (level === 1) return CHAR_H1;
6468
+ if (level === 2) return CHAR_H2;
6469
+ if (level === 3) return CHAR_H3;
6470
+ return CHAR_H4;
6471
+ }
6316
6472
  function generateContainerXml() {
6317
6473
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6318
6474
  <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
@@ -6334,21 +6490,50 @@ function generateManifest() {
6334
6490
  </opf:spine>
6335
6491
  </opf:package>`;
6336
6492
  }
6493
+ function charPr(id, height, bold, italic, fontId = 0) {
6494
+ const boldAttr = bold ? ` bold="1"` : "";
6495
+ const italicAttr = italic ? ` italic="1"` : "";
6496
+ return ` <hh:charPr id="${id}" height="${height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
6497
+ <hh:fontRef hangul="${fontId}" latin="${fontId}" hanja="${fontId}" japanese="${fontId}" other="${fontId}" symbol="${fontId}" user="${fontId}"/>
6498
+ <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6499
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6500
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6501
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6502
+ </hh:charPr>`;
6503
+ }
6504
+ function paraPr(id, opts = {}) {
6505
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
6506
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
6507
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
6508
+ <hh:heading type="NONE" idRef="0" level="0"/>
6509
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
6510
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
6511
+ <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
6512
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
6513
+ <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
6514
+ </hh:paraPr>`;
6515
+ }
6337
6516
  function generateHeaderXml() {
6338
6517
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6339
6518
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
6340
6519
  <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
6341
6520
  <hh:refList>
6342
6521
  <hh:fontfaces itemCnt="7">
6343
- <hh:fontface lang="HANGUL" fontCnt="1">
6522
+ <hh:fontface lang="HANGUL" fontCnt="2">
6344
6523
  <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
6345
6524
  <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
6346
6525
  </hh:font>
6526
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
6527
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
6528
+ </hh:font>
6347
6529
  </hh:fontface>
6348
- <hh:fontface lang="LATIN" fontCnt="1">
6530
+ <hh:fontface lang="LATIN" fontCnt="2">
6349
6531
  <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
6350
6532
  <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
6351
6533
  </hh:font>
6534
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
6535
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
6536
+ </hh:font>
6352
6537
  </hh:fontface>
6353
6538
  <hh:fontface lang="HANJA" fontCnt="1">
6354
6539
  <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
@@ -6380,34 +6565,37 @@ function generateHeaderXml() {
6380
6565
  <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
6381
6566
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
6382
6567
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
6383
- <hh:leftBorder type="NONE" width="0.1mm" color="0"/>
6384
- <hh:rightBorder type="NONE" width="0.1mm" color="0"/>
6385
- <hh:topBorder type="NONE" width="0.1mm" color="0"/>
6386
- <hh:bottomBorder type="NONE" width="0.1mm" color="0"/>
6387
- <hh:diagonal type="NONE" width="0.1mm" color="0"/>
6568
+ <hh:leftBorder type="NONE" width="0.1mm" color="#000000"/>
6569
+ <hh:rightBorder type="NONE" width="0.1mm" color="#000000"/>
6570
+ <hh:topBorder type="NONE" width="0.1mm" color="#000000"/>
6571
+ <hh:bottomBorder type="NONE" width="0.1mm" color="#000000"/>
6572
+ <hh:diagonal type="NONE" width="0.1mm" color="#000000"/>
6388
6573
  <hh:fillInfo/>
6389
6574
  </hh:borderFill>
6390
6575
  </hh:borderFills>
6391
- <hh:charProperties itemCnt="1">
6392
- <hh:charPr id="0" height="1000" textColor="0" shadeColor="-1" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0">
6393
- <hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6394
- <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6395
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6396
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6397
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6398
- </hh:charPr>
6576
+ <hh:charProperties itemCnt="9">
6577
+ ${charPr(0, 1e3, false, false)}
6578
+ ${charPr(1, 1e3, true, false)}
6579
+ ${charPr(2, 1e3, false, true)}
6580
+ ${charPr(3, 1e3, true, true)}
6581
+ ${charPr(4, 900, false, false, 1)}
6582
+ ${charPr(5, 1800, true, false, 1)}
6583
+ ${charPr(6, 1400, true, false, 1)}
6584
+ ${charPr(7, 1200, true, false, 1)}
6585
+ ${charPr(8, 1100, true, false, 1)}
6399
6586
  </hh:charProperties>
6400
6587
  <hh:tabProperties itemCnt="0"/>
6401
6588
  <hh:numberings itemCnt="0"/>
6402
6589
  <hh:bullets itemCnt="0"/>
6403
- <hh:paraProperties itemCnt="1">
6404
- <hh:paraPr id="0" tabIDRef="0" condense="0" fontLineHeight="0" snapToGrid="0" suppressOverlap="0" checked="0">
6405
- <hh:parLineBreak lineBreak="BREAK_LINE" wordBreak="BREAK_WORD" breakLatinWord="BREAK_WORD" breakNonLatinWord="BREAK_WORD"/>
6406
- <hh:parMargin left="0" right="0" prev="0" next="0" indent="0"/>
6407
- <hh:parBorder borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
6408
- <hh:parShade borderFillIDRef="0"/>
6409
- <hh:parTabList/>
6410
- </hh:paraPr>
6590
+ <hh:paraProperties itemCnt="8">
6591
+ ${paraPr(0)}
6592
+ ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
6593
+ ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
6594
+ ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
6595
+ ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
6596
+ ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
6597
+ ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
6598
+ ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
6411
6599
  </hh:paraProperties>
6412
6600
  <hh:styles itemCnt="1">
6413
6601
  <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
@@ -6416,34 +6604,78 @@ function generateHeaderXml() {
6416
6604
  <hh:compatibleDocument targetProgram="HWP2018"/>
6417
6605
  </hh:head>`;
6418
6606
  }
6419
- function generateParagraph(text) {
6420
- return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
6607
+ function generateSecPr() {
6608
+ 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>`;
6421
6609
  }
6422
6610
  function generateTable(rows) {
6423
6611
  const trElements = rows.map((row) => {
6424
- const tdElements = row.map(
6425
- (cell) => `<hp:tc><hp:cellSpan colSpan="1" rowSpan="1"/>${generateParagraph(cell)}</hp:tc>`
6426
- ).join("");
6612
+ const tdElements = row.map((cell) => {
6613
+ const runs = generateRuns(cell);
6614
+ return `<hp:tc><hp:cellSpan colSpan="1" rowSpan="1"/><hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p></hp:tc>`;
6615
+ }).join("");
6427
6616
  return `<hp:tr>${tdElements}</hp:tr>`;
6428
6617
  }).join("");
6429
6618
  return `<hp:tbl>${trElements}</hp:tbl>`;
6430
6619
  }
6431
6620
  function blocksToSectionXml(blocks) {
6432
- const body = blocks.map((block) => {
6621
+ const paraXmls = [];
6622
+ let isFirst = true;
6623
+ for (const block of blocks) {
6624
+ let xml = "";
6433
6625
  switch (block.type) {
6434
- case "heading":
6435
- return generateParagraph(block.text || "");
6436
- case "table":
6437
- return block.rows ? generateTable(block.rows) : "";
6626
+ case "heading": {
6627
+ const pId = headingParaPrId(block.level || 1);
6628
+ const cId = headingCharPrId(block.level || 1);
6629
+ xml = generateParagraph(block.text || "", pId, cId);
6630
+ break;
6631
+ }
6438
6632
  case "paragraph":
6439
- return generateParagraph(block.text || "");
6440
- default:
6441
- return "";
6633
+ xml = generateParagraph(block.text || "");
6634
+ break;
6635
+ case "code_block": {
6636
+ const codeLines = (block.text || "").split("\n");
6637
+ xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
6638
+ break;
6639
+ }
6640
+ case "blockquote":
6641
+ xml = generateParagraph(block.text || "", PARA_QUOTE);
6642
+ break;
6643
+ case "list_item": {
6644
+ const marker = block.ordered ? `${(block.indent || 0) + 1}. ` : "\xB7 ";
6645
+ const indentPrefix = " ".repeat(block.indent || 0);
6646
+ xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
6647
+ break;
6648
+ }
6649
+ case "hr":
6650
+ 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>`;
6651
+ break;
6652
+ case "table":
6653
+ if (block.rows) {
6654
+ if (isFirst) {
6655
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
6656
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
6657
+ isFirst = false;
6658
+ }
6659
+ xml = generateTable(block.rows);
6660
+ }
6661
+ break;
6662
+ }
6663
+ if (!xml) continue;
6664
+ if (isFirst && block.type !== "table") {
6665
+ xml = xml.replace(
6666
+ /<hp:run charPrIDRef="(\d+)">/,
6667
+ `<hp:run charPrIDRef="$1">${generateSecPr()}`
6668
+ );
6669
+ isFirst = false;
6442
6670
  }
6443
- }).join("\n ");
6671
+ paraXmls.push(xml);
6672
+ }
6673
+ if (paraXmls.length === 0) {
6674
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
6675
+ }
6444
6676
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6445
6677
  <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
6446
- ${body}
6678
+ ${paraXmls.join("\n ")}
6447
6679
  </hs:sec>`;
6448
6680
  }
6449
6681