// 근거(메모) DifineMemo 의 WPTools 글 형식(V=560, zlib 안) 읽기·쓰기 — 근거자료보기 창(TfrmGunguEdit · spec_sort_memo §3)이 // 고친 글을 원본 형식으로 되돌려 담는다. 읽기: 문단(
)·표(
)·그림 개체( 원문 그대로)와 // 글자 굵게(문자 서식 cs 의 CharStyleON 1번 비트). 쓰기: 굴림체(영문 이름 GulimChe) 보통·굵게 두 서식, 표는 테두리 있는 기본 꼴, // 그림은 읽은 원문 그대로, ASCII 밖 글자는 &#코드; — cp949 변환 없이 원본이 읽는 꼴. 문단 서식(간격·들여쓰기·글꼴 크기)은 버린다. const EstMemoWpt = (() => { const decode = (text) => text .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))) .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/ /g, " ") .replace(/&/g, "&"); const encode = (text) => [...String(text)] .map((ch) => { if (ch === "&") return "&"; if (ch === "<") return "<"; if (ch === ">") return ">"; const code = ch.codePointAt(0); return code < 32 || code > 126 ? `&#${code};` : ch; }) .join(""); // source → 마디 목록: { p: [{ text, bold } | { object }] } · { table: [[ [마디…] ]] } function read(source) { const objects = []; const text = source.replace( //g, (m) => ``, ); const bold = new Map(); for (const m of text.matchAll(//g)) { const on = /CharStyleON:(\d+)/.exec(m[2]); bold.set(m[1], Boolean(on && Number(on[1]) & 1)); } const root = [], stack = [{ list: root }]; let para = null, current = false; const top = () => stack.at(-1); const push = (run) => { if (!para) { para = { p: [] }; top().list.push(para); } const last = para.p.at(-1); if (run.text != null && last?.text != null && last.bold === run.bold) last.text += run.text; else para.p.push(run); }; for (const part of text.split(/(<[^>]*>)/)) { if (!part) continue; if (!part.startsWith("<")) { const t = decode(part.replace(/\r?\n/g, "")); if (t && para) push({ text: t, bold: current }); continue; } const tag = /^<\/?([a-zA-Z]+)/.exec(part)?.[1]?.toLowerCase(), closing = part.startsWith(" s.table)?.table.table.push(row); stack.push({ row }); } else if (tag === "td" && !closing) { const cell = []; stack.findLast((s) => s.row)?.row.push(cell); stack.push({ list: cell }); // 칸 = 문단 하나(글이 td 바로 안에) — 안에 div 가 더 있으면 이어지는 문단 para = { p: [] }; cell.push(para); const cs = /\bcs=(\d+)/.exec(part); current = cs ? bold.get(cs[1]) || false : false; } else if (["table", "tr", "td"].includes(tag) && closing) { const key = { table: "table", tr: "row", td: "list" }[tag]; while (stack.length > 1 && !stack.at(-1)[key]) stack.pop(); if (stack.length > 1) stack.pop(); para = null; } } return root; } function write(nodes) { const font = "CharFont:'GulimChe';CharFontSize:1000;"; const runs = (p) => p.map((r) => (r.object ? r.object : `${encode(r.text)}`)).join(""); const list = (items) => items .map((n) => n.table ? `${n.table .map( (row) => `${row.map((cell) => ``).join("")}`, ) .join("")}
${runs(cell[0]?.p || [])}${list(cell.slice(1))}
\r\n` : `
${runs(n.p)}
\r\n`, ) .join(""); return ( "\r\n" + '' + "\r\n" + "\r\n" + `` + list(nodes) ); } // 편집 창용 html(그림은 고칠 수 없는 자리표 — data-obj 번호로 원문을 되찾음) function html(nodes, objects = []) { const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]); const runs = (p) => p .map((r) => r.object ? `[그림]` : r.bold ? `${esc(r.text)}` : esc(r.text), ) .join("") || "
"; const list = (items) => items .map((n) => n.table ? `${n.table.map((row) => `${row.map((cell) => ``).join("")}`).join("")}
${list(cell)}
` : `
${runs(n.p)}
`, ) .join(""); return list(nodes); } return { read, write, html, encode, decode }; })(); if (typeof module !== "undefined" && module.exports) module.exports = EstMemoWpt;