Files
Aislo/A00_Common/spreadsheet/spreadsheet_fill.ts
T
eomsangdonandClaude Sonnet 5 7725b39ecb feat(spreadsheet): 스프레드시트 B(함수 · 숫자 형식 · 채우기) 1단계 구현
수학 26 · 논리/글/찾기/세기 22 함수 · 엑셀 숫자 형식 코드 해석기 · 채우기 핸들(복사 · 등차 연속 · 글 끝번호 · 식 상대이동) 구현.
자기 시험 69건(resources/tester/test_spreadsheet_b.py, vite ssr 번들 → node) 전부 통과.

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

106 lines
4.3 KiB
TypeScript

/* =============================================================================
* spreadsheet_fill.ts (주인 B)
* 채우기 핸들 — 원본 범위를 대상 범위로 늘림: 수 하나 = 복사 · 수 둘 이상 = 등차 연속 ·
* 글 끝 번호(`구-01` → `구-02`) · 식 = 상대 참조 옮김(A `moveFormula`) · 서식 같이.
* 결과는 `{ 종류: "칸" }` 명령에 넣을 칸 뭉치.
* ========================================================================== */
import type { Cell, CellRange, FillDirection, Workbook } from "./spreadsheet_types";
import { moveFormula } from "./spreadsheet_refshift";
/** 0 → `A` · 27 → `AB`(엔진 A 의 `spreadsheet_address.ts` 와 같은 규칙 · 채우기는 자기 것을 씀) */
function colLetters(c: number): string {
let n = c + 1;
let s = "";
while (n > 0) {
const rem = (n - 1) % 26;
s = String.fromCharCode(65 + rem) + s;
n = Math.floor((n - 1) / 26);
}
return s;
}
function a1(r: number, c: number): string {
return `${colLetters(c)}${r + 1}`;
}
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 ? a1(pos, fixed) : a1(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;
}