UNPKG

md2hwp

Version:

Convert Markdown to HWP (Hangul Word Processor) format

307 lines (306 loc) 25.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HwpxGenerator = void 0; const jszip_1 = __importDefault(require("jszip")); class HwpxGenerator { constructor(options = {}) { this.paragraphId = 0; this.options = { title: options.title || 'Document', author: options.author || 'md2hwp', pageWidth: options.pageWidth || 59528, pageHeight: options.pageHeight || 84188, marginLeft: options.marginLeft || 8504, marginRight: options.marginRight || 8504, marginTop: options.marginTop || 5668, marginBottom: options.marginBottom || 4252, }; } async generate(contents) { const zip = new jszip_1.default(); // mimetype MUST be uncompressed and first zip.file('mimetype', 'application/hwp+zip', { compression: 'STORE', compressionOptions: { level: 0 } }); zip.file('version.xml', this.generateVersionXml()); zip.file('settings.xml', this.generateSettingsXml()); // META-INF directory const metaInf = zip.folder('META-INF'); if (metaInf) { metaInf.file('manifest.xml', this.generateManifestXml()); metaInf.file('container.xml', this.generateContainerXml()); metaInf.file('container.rdf', this.generateContainerRdf()); } // Contents directory const contentFolder = zip.folder('Contents'); if (contentFolder) { contentFolder.file('content.hpf', this.generateContentHpf()); contentFolder.file('header.xml', this.generateHeaderXml()); contentFolder.file('section0.xml', this.generateSection(contents)); } // Preview directory const previewFolder = zip.folder('Preview'); if (previewFolder) { const previewText = this.generatePreviewText(contents); previewFolder.file('PrvText.txt', previewText); } const buffer = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 9 } }); return buffer; } generateVersionXml() { return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" tagetApplication="WORDPROCESSOR" major="5" minor="1" micro="1" buildNumber="0" os="10" xmlVersion="1.5" application="md2hwp" appVersion="1.0.0"/>`; } generateSettingsXml() { return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><ha:HWPApplicationSetting xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"><ha:CaretPosition listIDRef="0" paraIDRef="0" pos="0"/></ha:HWPApplicationSetting>`; } generateManifestXml() { return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><odf:manifest xmlns:odf="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"/>`; } generateContainerXml() { return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"><ocf:rootfiles><ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/><ocf:rootfile full-path="Preview/PrvText.txt" media-type="text/plain"/></ocf:rootfiles></ocf:container>`; } generateContainerRdf() { return `<?xml version="1.0" encoding="UTF-8"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"></rdf:RDF>`; } generateContentHpf() { const now = new Date().toISOString(); return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><opf:package xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf/" version="" unique-identifier="" id=""><opf:metadata><opf:title>${this.escapeXml(this.options.title)}</opf:title><opf:language>ko</opf:language><opf:meta name="creator" content="text">${this.escapeXml(this.options.author)}</opf:meta><opf:meta name="CreatedDate" content="text">${now}</opf:meta><opf:meta name="ModifiedDate" content="text">${now}</opf:meta></opf:metadata><opf:manifest><opf:item id="header" href="Contents/header.xml" media-type="application/xml"/><opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/><opf:item id="settings" href="settings.xml" media-type="application/xml"/></opf:manifest><opf:spine><opf:itemref idref="header" linear="yes"/><opf:itemref idref="section0" linear="yes"/></opf:spine></opf:package>`; } generateCharPr(id, height, bold = false) { const baseAttrs = `id="${id}" height="${height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"`; const fontRefVals = bold ? 'hangul="0" latin="0" hanja="1" japanese="1" other="1" symbol="1" user="1"' : 'hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"'; const boldTag = bold ? `<hh:bold/>` : ''; return `<hh:charPr ${baseAttrs}><hh:fontRef ${fontRefVals}/><hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>${boldTag}<hh:underline type="NONE" shape="SOLID" color="#000000"/><hh:strikeout shape="NONE" color="#000000"/><hh:outline type="NONE"/><hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/></hh:charPr>`; } generateParaPr(id, lineSpacing, leftMargin = '0', prevMargin = '0') { return `<hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0"><hh:align horizontal="LEFT" vertical="BASELINE"/><hh:heading type="NONE" idRef="0" level="0"/><hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/><hh:autoSpacing eAsianEng="0" eAsianNum="0"/><hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="${leftMargin}" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="${prevMargin}" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin><hh:lineSpacing type="PERCENT" value="${lineSpacing}" unit="HWPUNIT"/><hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/></hh:paraPr>`; } generateHeaderXml() { const nsDecl = `xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core" xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"`; return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><hh:head ${nsDecl} version="1.5" secCnt="1"><hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/><hh:refList><hh:fontfaces itemCnt="2"><hh:fontface lang="HANGUL" fontCnt="1"><hh:font id="0" face="맑은 고딕" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="LATIN" fontCnt="1"><hh:font id="0" face="Arial" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="HANJA" fontCnt="1"><hh:font id="0" face="맑은 고딕" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="JAPANESE" fontCnt="1"><hh:font id="0" face="맑은 고딕" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="OTHER" fontCnt="1"><hh:font id="0" face="Arial" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="SYMBOL" fontCnt="1"><hh:font id="0" face="Arial" type="TTF" isEmbedded="0"/></hh:fontface><hh:fontface lang="USER" fontCnt="1"><hh:font id="0" face="Arial" type="TTF" isEmbedded="0"/></hh:fontface></hh:fontfaces><hh:borderFills itemCnt="2"><hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0"><hh:slash type="NONE" Crooked="0" isCounter="0"/><hh:backSlash type="NONE" Crooked="0" isCounter="0"/><hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/><hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/><hh:topBorder type="NONE" width="0.1 mm" color="#000000"/><hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/><hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/></hh:borderFill><hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0"><hh:slash type="NONE" Crooked="0" isCounter="0"/><hh:backSlash type="NONE" Crooked="0" isCounter="0"/><hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/><hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/><hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/><hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/><hh:diagonal type="SOLID" width="0.12 mm" color="#000000"/></hh:borderFill></hh:borderFills><hh:charProperties itemCnt="6">${this.generateCharPr('0', '1000', false)}${this.generateCharPr('1', '1000', true)}${this.generateCharPr('2', '1400', true)}${this.generateCharPr('3', '1300', true)}${this.generateCharPr('4', '1200', true)}${this.generateCharPr('5', '1100', true)}</hh:charProperties><hh:tabProperties itemCnt="1"><hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/></hh:tabProperties><hh:paraProperties itemCnt="14">${this.generateParaPr('0', '140', '0', '0')}${this.generateParaPr('1', '150', '0', '0')}${this.generateParaPr('2', '150', '800', '0')}${this.generateParaPr('3', '150', '1600', '0')}${this.generateParaPr('4', '150', '2400', '0')}${this.generateParaPr('5', '150', '3200', '0')}${this.generateParaPr('6', '150', '4000', '0')}${this.generateParaPr('7', '150', '4800', '0')}${this.generateParaPr('8', '150', '5600', '0')}${this.generateParaPr('9', '150', '6400', '0')}${this.generateParaPr('10', '150', '7200', '0')}${this.generateParaPr('20', '160', '0', '0')}${this.generateParaPr('21', '160', '0', '400')}</hh:paraProperties><hh:styles itemCnt="1"><hh:style id="0" name="바탕글" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0"/></hh:styles></hh:refList></hh:head>`; } generateSection(contents) { const nsDecl = `xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core" xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"`; let vertPos = 0; const paragraphs = []; let isFirst = true; let previousType = null; for (const content of contents) { const p = this.generateContent(content, vertPos, isFirst, previousType); paragraphs.push(p.xml); vertPos = p.nextVertPos; isFirst = false; previousType = content.type; } return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><hs:sec ${nsDecl}>${paragraphs.join('')}</hs:sec>`; } generateContent(content, vertPos, isFirst, previousType) { switch (content.type) { case 'heading': return this.generateHeading(content, vertPos, isFirst, previousType); case 'paragraph': return this.generateParagraph(content, vertPos, isFirst, previousType); case 'list': return this.generateList(content, vertPos, isFirst, previousType, 0); case 'table': return this.generateTable(content, vertPos, isFirst, previousType); case 'image': return this.generateImage(content, vertPos, isFirst, previousType); default: return this.generateParagraph(content, vertPos, isFirst, previousType); } } generateHeading(content, vertPos, isFirst, previousType) { const text = this.escapeXml(content.content || ''); const level = content.level || 1; // Add extra spacing if previous content was not a heading if (previousType && previousType !== 'heading') { vertPos += 300; // Extra gap before heading } // Map heading level to charPr ID and properties // H1: charPr 2, height 1400 // H2: charPr 3, height 1300 // H3: charPr 4, height 1200 // H4: charPr 5, height 1100 // H5/H6: charPr 0, height 1000 (normal size) let charPrIDRef; let charHeight; let baseline; let spacing; switch (level) { case 1: // H1 charPrIDRef = '2'; charHeight = 1400; baseline = 1190; spacing = 840; break; case 2: // H2 charPrIDRef = '3'; charHeight = 1300; baseline = 1105; spacing = 780; break; case 3: // H3 charPrIDRef = '4'; charHeight = 1200; baseline = 1020; spacing = 720; break; case 4: // H4 charPrIDRef = '5'; charHeight = 1100; baseline = 935; spacing = 660; break; default: // H5, H6 and others charPrIDRef = '0'; charHeight = 1000; baseline = 850; spacing = 600; break; } const lineHeight = charHeight; const secPr = isFirst ? this.generateSecPr() : ''; // Use paraPrIDRef="0" for headings (140% line spacing) const xml = `<hp:p id="${this.paragraphId++}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}<hp:run charPrIDRef="${charPrIDRef}"><hp:t>${text}</hp:t></hp:run><hp:linesegarray><hp:lineseg textpos="0" vertpos="${vertPos}" vertsize="${lineHeight}" textheight="${charHeight}" baseline="${baseline}" spacing="${spacing}" horzpos="0" horzsize="42520" flags="393216"/></hp:linesegarray></hp:p>`; return { xml, nextVertPos: vertPos + lineHeight + spacing }; } generateParagraph(content, vertPos, isFirst, previousType) { let text = ''; let charPrIDRef = '0'; // Use paraPr id="21" (with top margin) when following a heading // Otherwise use paraPr id="20" (normal spacing) const paraPrIDRef = previousType === 'heading' ? '21' : '20'; if (content.children && content.children.length > 0) { const runs = content.children.map(child => { const t = this.escapeXml(child.content || ''); const cid = child.style?.bold ? '1' : '0'; return `<hp:run charPrIDRef="${cid}"><hp:t>${t}</hp:t></hp:run>`; }).join(''); const secPr = isFirst ? this.generateSecPr() : ''; const xml = `<hp:p id="${this.paragraphId++}" paraPrIDRef="${paraPrIDRef}" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}${runs}</hp:p>`; return { xml, nextVertPos: vertPos + 1600 }; } text = this.escapeXml(content.content || ''); const secPr = isFirst ? this.generateSecPr() : ''; const xml = `<hp:p id="${this.paragraphId++}" paraPrIDRef="${paraPrIDRef}" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}<hp:run charPrIDRef="0"><hp:t>${text}</hp:t></hp:run></hp:p>`; return { xml, nextVertPos: vertPos + 1600 }; } generateList(content, vertPos, isFirst, previousType, level = 0) { if (!content.children) return { xml: '', nextVertPos: vertPos }; // Add extra spacing before list (only for top-level lists) if (level === 0 && previousType && previousType !== 'list') { vertPos += 300; } const paragraphs = []; let currentVertPos = vertPos; let first = isFirst; // Calculate paragraph property ID based on level // level 0 → id="1" (no indent) // level 1 → id="2" (800 HWPUNIT indent) // level 2 → id="3" (1600 HWPUNIT indent), etc. const paraPrId = Math.min(level + 1, 10); for (const item of content.children) { // Handle nested lists if (item.type === 'list') { const nestedResult = this.generateList(item, currentVertPos, first, 'list', level + 1); paragraphs.push(nestedResult.xml); currentVertPos = nestedResult.nextVertPos; first = false; continue; } const secPr = first ? this.generateSecPr() : ''; let runs = ''; // Handle list items with mixed content (bold + normal text) if (item.children && item.children.length > 0) { const textRuns = item.children.map(child => { const t = this.escapeXml(child.content || ''); const cid = child.style?.bold ? '1' : '0'; return `<hp:run charPrIDRef="${cid}"><hp:t>${t}</hp:t></hp:run>`; }).join(''); runs = `<hp:run charPrIDRef="0"><hp:t>• </hp:t></hp:run>${textRuns}`; } else { // Simple text content const text = this.escapeXml(item.content || ''); runs = `<hp:run charPrIDRef="0"><hp:t>• ${text}</hp:t></hp:run>`; } // Use pre-defined paragraph property with indentation const xml = `<hp:p id="${this.paragraphId++}" paraPrIDRef="${paraPrId}" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}${runs}</hp:p>`; paragraphs.push(xml); currentVertPos += 1600; first = false; } // Add extra spacing after list (only for top-level lists) const extraSpacing = level === 0 ? 300 : 0; return { xml: paragraphs.join(''), nextVertPos: currentVertPos + extraSpacing }; } generateTable(content, vertPos, isFirst, previousType) { if (!content.rows || content.rows.length === 0) { return { xml: '', nextVertPos: vertPos }; } // Add extra spacing before table if (previousType && previousType !== 'table') { vertPos += 400; } const rowCnt = content.rows.length; const colCnt = Math.max(...content.rows.map(row => row.cells.length)); const tableWidth = 41954; // Table width const cellWidth = Math.floor(tableWidth / colCnt); const cellHeight = 700; // Increased cell height for better readability const tableHeight = rowCnt * (cellHeight + 1200); // Generate table rows const rows = content.rows.map((row, rowIndex) => { const cells = row.cells.map((cell, colIndex) => { const text = this.escapeXml(cell.content); return `<hp:tc name="" header="0" hasMargin="1" protect="0" editable="0" dirty="0" borderFillIDRef="2"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="${cellWidth - 1020}" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="0"><hp:t>${text}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="${colIndex}" rowAddr="${rowIndex}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellWidth}" height="${cellHeight}"/><hp:cellMargin left="510" right="510" top="200" bottom="200"/></hp:tc>`; }).join(''); return `<hp:tr>${cells}</hp:tr>`; }).join(''); const secPr = isFirst ? this.generateSecPr() : ''; const tableId = Math.floor(Math.random() * 2000000000); // Table paragraph const tablePara = `<hp:p id="${this.paragraphId++}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}<hp:run charPrIDRef="0"><hp:tbl id="${tableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="1" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="1" noAdjust="0"><hp:sz width="${tableWidth}" widthRelTo="ABSOLUTE" height="${tableHeight}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="0" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="283" right="283" top="400" bottom="600"/><hp:inMargin left="510" right="510" top="200" bottom="200"/>${rows}</hp:tbl><hp:t/></hp:run><hp:linesegarray><hp:lineseg textpos="0" vertpos="${vertPos}" vertsize="1000" textheight="1000" baseline="850" spacing="600" horzpos="0" horzsize="0" flags="393216"/></hp:linesegarray></hp:p>`; // Add empty paragraph after table for spacing const emptyPara = `<hp:p id="${this.paragraphId++}" paraPrIDRef="20" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="0"><hp:t> </hp:t></hp:run></hp:p>`; const xml = tablePara + emptyPara; // Add extra spacing after table (increased from 600 to 2800) return { xml, nextVertPos: vertPos + tableHeight + 2800 }; } generateImage(content, vertPos, isFirst, previousType) { const alt = content.alt || content.src || 'Image'; const text = `[이미지: ${this.escapeXml(alt)}]`; const secPr = isFirst ? this.generateSecPr() : ''; const xml = `<hp:p id="${this.paragraphId++}" paraPrIDRef="20" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">${secPr}<hp:run charPrIDRef="0"><hp:t>${text}</hp:t></hp:run></hp:p>`; return { xml, nextVertPos: vertPos + 1600 }; } generateSecPr() { return `<hp:run charPrIDRef="0"><hp:secPr id="" textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" tabStopVal="4000" tabStopUnit="HWPUNIT" outlineShapeIDRef="1" 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:lineNumberShape restartType="0" countBy="0" distance="0" startNumber="0"/><hp:pagePr landscape="WIDELY" width="${this.options.pageWidth}" height="${this.options.pageHeight}" gutterType="LEFT_ONLY"><hp:margin header="4252" footer="4252" gutter="0" left="${this.options.marginLeft}" right="${this.options.marginRight}" top="${this.options.marginTop}" bottom="${this.options.marginBottom}"/></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:pageBorderFill type="BOTH" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="EVEN" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="ODD" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill></hp:secPr><hp:ctrl><hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/></hp:ctrl></hp:run>`; } generatePreviewText(contents) { return contents.map(content => { if (content.children) { return content.children.map(c => c.content || '').join(''); } return content.content || ''; }).join('\n'); } escapeXml(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } } exports.HwpxGenerator = HwpxGenerator;