Files
Aislo/A00_Common/spreadsheet/spreadsheet_fill.ts
T
eomsangdonandClaude Sonnet 5 b93e03e505 feat(spreadsheet): fill.ts를 A 엔진 실 구현으로 잇기 · numfmt 실무 서식 대조 고침
fill.ts가 A의 실제 moveFormula·toA1을 쓰도록 정리(식 채우기 시험 통과).
실무 구조도 xlsx 세 벌 실측 형식 코드 66개 대조 — `_x` trailing 공백을 trim이 삼키던 버그,
글 값에 순수 수 형식이 적용되던 버그, `General"단위"` 접미 버릇, `[$통화-로캘]` 태그 미지원을 고침.
분수 형식(`0/0`)은 1단계 형식 부분집합 밖이라 남겨둠. 회귀 시험 추가.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RrQ65VtGZbae2VKgYVMhmc
2026-09-27 20:39:03 +09:00

91 lines
4.0 KiB
TypeScript

/* =============================================================================
* spreadsheet_fill.ts (주인 B)
* 채우기 핸들 — 원본 범위를 대상 범위로 늘림: 수 하나 = 복사 · 수 둘 이상 = 등차 연속 ·
* 글 끝 번호(`구-01` → `구-02`) · 식 = 상대 참조 옮김(A `moveFormula`) · 서식 같이.
* 결과는 `{ 종류: "칸" }` 명령에 넣을 칸 뭉치.
* ========================================================================== */
import type { Cell, CellRange, FillDirection, Workbook } from "./spreadsheet_types";
import { moveFormula } from "./spreadsheet_refshift";
import { toA1 } from "./spreadsheet_address";
function detectDirection(source: CellRange, target: CellRange): FillDirection {
if (target.r1 > source.r1 && target.c0 === source.c0 && target.c1 === source.c1) return "down";
if (target.r0 < source.r0 && target.c0 === source.c0 && target.c1 === source.c1) return "up";
if (target.c1 > source.c1 && target.r0 === source.r0 && target.r1 === source.r1) return "right";
return "left";
}
/** 글 끝 숫자를 옮김(`구-01`,step 1 → `구-02`) · 끝 숫자가 없으면 그대로 */
function incrementTrailingNumber(text: string, step: number): string {
const m = /^(.*?)(\d+)$/.exec(text);
if (!m) return text;
const [, prefix, digits] = m;
const next = BigInt(digits) + BigInt(step);
const clamped = next < 0n ? 0n : next;
return prefix + clamped.toString().padStart(digits.length, "0");
}
/** `target` 은 `source` 를 한 방향으로 늘린 범위(방향은 둘을 견줘 앎) · A1 → 칸(null = 비움) */
export function fillCells(
book: Workbook,
sheet: string,
source: CellRange,
target: CellRange,
): Record<string, Cell | null> {
const sheetObj = book.시트.find((s) => s.id === sheet);
const out: Record<string, Cell | null> = {};
if (!sheetObj) return out;
const direction = detectDirection(source, target);
const vertical = direction === "down" || direction === "up";
const lineCount = vertical ? source.c1 - source.c0 + 1 : source.r1 - source.r0 + 1;
for (let li = 0; li < lineCount; li++) {
const fixed = vertical ? source.c0 + li : source.r0 + li;
const sourceStart = vertical ? source.r0 : source.c0;
const sourceEnd = vertical ? source.r1 : source.c1;
const targetStart = vertical ? target.r0 : target.c0;
const targetEnd = vertical ? target.r1 : target.c1;
const lineLen = sourceEnd - sourceStart + 1;
const key = (pos: number): string => (vertical ? toA1(pos, fixed) : toA1(fixed, pos));
const sourceCells: (Cell | undefined)[] = [];
for (let p = sourceStart; p <= sourceEnd; p++) sourceCells.push(sheetObj.칸[key(p)]);
const allNumeric = sourceCells.every(
(c) => c !== undefined && c.식 === undefined && typeof c.값 === "number",
);
const nums = allNumeric ? sourceCells.map((c) => c!.값 as number) : [];
const step = nums.length > 1 ? (nums[nums.length - 1] - nums[0]) / (nums.length - 1) : 0;
for (let p = targetStart; p <= targetEnd; p++) {
if (p >= sourceStart && p <= sourceEnd) continue;
const rel = p - sourceStart;
const slot = ((rel % lineLen) + lineLen) % lineLen;
const period = (rel - slot) / lineLen;
const template = sourceCells[slot];
const outKey = key(p);
if (template === undefined) {
out[outKey] = null;
continue;
}
const cell: Cell = {};
if (template.서식 !== undefined) cell.서식 = template.서식;
if (allNumeric) {
cell.값 = nums[0] + step * rel;
} else if (template.식 !== undefined) {
const dr = vertical ? period * lineLen : 0;
const dc = vertical ? 0 : period * lineLen;
cell.식 = moveFormula(template.식, dr, dc);
} else if (typeof template.값 === "string" && lineLen === 1) {
cell.값 = incrementTrailingNumber(template.값, rel);
} else if (template.값 !== undefined) {
cell.값 = template.값;
}
out[outKey] = cell;
}
}
return out;
}