Files
Aislo/common_util/common_util_spreadsheet_node.ts
T
eomsangdonandClaude Opus 5.5 f20aea412a feat(534-2): 내장 엑셀 내보내기 — 인쇄 HTML(Node print) · xlsx · SVG→PNG · PDF
- spreadsheet_print_html: 통합문서 → 인쇄 HTML(DOM 없음 · 화면 엔진 값 · formatValue · 병합 · colgroup 폭 · 쪽 나눔 덩이 · thead 머리행 반복 · 그림 img · 시트마다 @page)
- 서식 → CSS 를 클립보드에서 떼어 공용(medium · thick · double 굵기 반영)
- 서버 Node 진입 prints · 파이썬 print_htmls · workbook_pdf(공용 PDF 엔진 page_size=None)
- common_util_workbook_xlsx: openpyxl 로 값 · 식 · 서식 · 병합 · 폭 · 높이 · 틀 고정 · 인쇄 설정 · 쪽 나눔 · 그림(두 칸 닻)
- common_util_svg_png: Playwright 로 SVG → PNG(PyMuPDF 안 씀)
- 시험 test_spreadsheet_print(인쇄 HTML · xlsx 되읽기 · PNG 크기 · PDF 쪽 수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:33:57 +09:00

89 lines
4.2 KiB
TypeScript

/* =============================================================================
* common_util_spreadsheet_node.ts
* 산출근거 스프레드시트 풀이를 **서버가** 돌리는 진입점 — [저장] 때 정본 `계산값`.
*
* 풀이는 화면이 쓰는 `A00_Common/spreadsheet/spreadsheet_recalc.ts` 그대로 — 여기에는 계산이 없음(CLAUDE.md 5장 ②).
* 파이썬 껍데기 = `common_util_spreadsheet.py` · 번들 = `npm run build:spreadsheet`.
*
* 실행: node <번들> <입력.json> <출력.json>
* 입력 { books?: Workbook[], tables?: TableAsk[] }
* 출력 { results: ({ ok: true, 계산값 } | { ok: false, 까닭 })[],
* tables: ({ ok: true, 표, 시트, 범위, 값 } | { ok: false, 까닭 })[] } — 입력 차례 그대로 · 하나가 죽어도 나머지는 풂
* 표(41-1) = 면적산출 시트 · 범위 → CAD 표 객체(TableEntity 꼴) — 풀이는 `A00_Common/spreadsheet/spreadsheet_area_table.ts`
* (화면 도면 패널의 저장 전 실시간 표 값과 같은 코드 · 112-1) · 저장 안 함.
* 인쇄(534-2 · 요구 347-5) = 입력 `prints: {book, figures?}[]` → 출력 `prints: ({ ok: true, html } | { ok: false, 까닭 })[]` —
* `A00_Common/spreadsheet/spreadsheet_print_html.ts` 한 벌 · figures = 그림 열쇠 → data: 주소 · PDF 는 파이썬이 만듦.
* 끝 코드: 0 성공 / 2 인자 오류
* ⚠ `A00_Common/spreadsheet/` 밖에 둠 — node:fs 가 브라우저 번들로 새지 않게.
* ========================================================================== */
import { readFileSync, writeFileSync } from "node:fs";
import { parseRange } from "../A00_Common/spreadsheet/spreadsheet_address";
import { areaTable, type TableAsk } from "../A00_Common/spreadsheet/spreadsheet_area_table";
import { printHtml } from "../A00_Common/spreadsheet/spreadsheet_print_html";
import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc";
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet_types";
/** 면적 시트에 보낸 범위 이름을 더하거나(같은 이름 있으면) 범위를 갱신(104-1 · 도면 [저장] 때 새 연결 줄마다) */
interface NameAsk {
book: Workbook;
시트: string;
범위: string;
이름: string;
}
/** 이름정의를 더하거나(있으면) 범위만 바꿈 — 책 사본을 돌려줌(저장은 python 쪽) */
function defineAreaName(ask: NameAsk): { book: Workbook } {
const book = ask.book;
const sheet =
book.시트.find((s) => s.id === ask.시트) ?? book.시트.find((s) => s.이름 === ask.시트);
if (!sheet) throw new Error(`없는 시트 「${ask.시트}」`);
const range = parseRange(ask.범위);
if (!range) throw new Error(`범위 꼴 아님 「${ask.범위}」`);
const kept = (sheet.이름정의 ?? []).filter((n) => n.이름 !== ask.이름);
sheet.이름정의 = [...kept, { 이름: ask.이름, 범위: range }];
return { book };
}
const [inputPath, outputPath] = process.argv.slice(2);
if (!inputPath || !outputPath) {
console.error("사용법: node <번들> <입력.json> <출력.json>");
process.exit(2);
}
const input = JSON.parse(readFileSync(inputPath, "utf8")) as {
books?: Workbook[];
tables?: TableAsk[];
names?: NameAsk[];
prints?: { book: Workbook; figures?: Record<string, string> }[];
};
const results = (input.books ?? []).map((book) => {
try {
return { ok: true, 계산값: recalcWorkbook(book) };
} catch (error) {
return { ok: false, 까닭: error instanceof Error ? error.message : String(error) };
}
});
const tables = (input.tables ?? []).map((ask) => {
try {
return { ok: true, ...areaTable(ask) };
} catch (error) {
return { ok: false, 까닭: error instanceof Error ? error.message : String(error) };
}
});
const names = (input.names ?? []).map((ask) => {
try {
return { ok: true, ...defineAreaName(ask) };
} catch (error) {
return { ok: false, 까닭: error instanceof Error ? error.message : String(error) };
}
});
const prints = (input.prints ?? []).map((ask) => {
try {
return { ok: true, html: printHtml(ask.book, ask.figures) };
} catch (error) {
return { ok: false, 까닭: error instanceof Error ? error.message : String(error) };
}
});
writeFileSync(outputPath, JSON.stringify({ results, tables, names, prints }));