excel

문서 › 규칙

규칙 형식 레퍼런스 (rules.json)

브라우저와 서버가 함께 쓰는 순수 JSON 규칙: 최상위 키와 기본값, 열 규칙, 형식, 날짜, 행 간 규칙, 빈 행·합계 행, 수식 인젝션, 개인정보, 메시지, 해시, JSON Schema.

규칙은 순수 JSON입니다. 정규식은 문자열로 쓰고 함수는 넣지 않습니다. 그래서 해시를 계산하고, 서버와 공유하고, 다른 언어로 옮길 수 있습니다. columns 가 배열이면 0.2 규칙이고, 없거나 숫자면 0.1 규칙(headers, 열 수)입니다. headers 와 columns[] 를 함께 주면 82excel: use either headers or columns[] 로 throw합니다.

{
  "$schema": "https://82excel.com/schema/rules-1.json",
  "id": "orders-upload", "version": "3",
  "columns": [
    { "key": "orderNo", "header": "주문번호", "aliases": ["주문 번호", "OrderNo"],
      "nullable": false, "unique": true, "pattern": "^[A-Z]{2}\\d{6}$",
      "message": "주문번호는 AB123456 형식입니다" },
    { "key": "kind",   "header": "구분",       "enum": ["개인", "법인"] },
    { "key": "crn",    "header": "법인등록번호", "type": "kr.crn", "required": false },
    { "key": "phone",  "header": "전화번호",    "type": "kr.phone", "phone": { "kind": "mobile" } },
    { "key": "amount", "header": "금액",        "type": "kr.amount", "min": 0 },
    { "key": "start",  "header": "시작일",      "type": "date" },
    { "key": "end",    "header": "종료일",      "type": "date" }
  ],
  "extraColumns": "warn",
  "rows": { "min": 1, "max": 10000, "blank": "skip", "totals": "warn" },
  "rowRules": [
    { "when": { "col": "kind", "eq": "법인" }, "require": ["crn"] },
    { "compare": ["start", "<=", "end"] },
    { "uniqueWith": ["start", "orderNo"] }
  ],
  "formulaInjection": "warn",
  "pii": { "detect": ["rrn", "card", "phone", "email"], "mask": true }
}

defineRules(json) 가 형식을 검사하고 기본값을 채운 뒤 얼린(frozen) 객체를 돌려줍니다. 모르는 키, 잘못된 type, 컴파일되지 않는 정규식, min > max 같은 모순, rowRules 가 없는 열을 가리키는 경우는 82excel: … 로 throw합니다. check, readRows, validate, makeTemplate 은 JSON을 그대로 받아도 되고 defineRules() 결과를 받아도 됩니다.

import { defineRules, check, validate, rulesHash } from '82excel';

const rules = defineRules(await (await fetch('/rules.json')).json()); // throws "82excel: …" on a bad rules file
const r = await check(file, { ...rules, readRows: true });            // browser: the file
const v = validate(r.rows, rules);                                     // server: the cleaned rows
await rulesHash(rules);                                                // 'sha256:…', the same as r.report.schemaHash

최상위 키와 기본값

키기본값설명
$schema, id, version—규칙 이름표. id·version 은 리포트와 업로드 본문에 들어갑니다
columns(필수)열 규칙 배열(아래 표)
columnsOrder"any""strict" 면 찾은 열이 규칙 순서여야 함(HEADER_ORDER)
extraColumns"ignore"규칙에 없는 열: ignore / warn / error (COLUMN_EXTRA)
rows.min, rows.max—결과 행 수(빈 행·제외한 합계 행 빼고). check() 와 validate() 모두 검사(ROWS_TOO_FEW/ROWS_TOO_MANY)
rows.blank"skip"중간 빈 행: skip / warn / error / stop
rows.totals"warn"마지막 합계 행: warn / ignore / error
rowRules[]행 간 규칙
formulaInjection"warn"warn / error / escape / off
pii.detect["rrn"]자유 텍스트에서 찾을 개인정보: rrn, card, phone, email
pii.masktrue메시지·리포트의 개인정보를 가림
normalize.trim / .invisible / .fullwidthDigitstruetext 열의 정리 스위치
messages—코드별 문구 덮어쓰기 { "CELL_TYPE": "…" }
sheet, headerRow첫 시트, 10.1과 같음
accept, maxSize, maxUncompressedSize, maxCells, delimiter0.1과 같음파일 단위 옵션
failOn[]오류로 올릴 경고 코드. check() 와 validate() 가 같게 적용
readRows, previewRows, locale, telemetry—한 번의 실행만 바꾸는 키. 해시에서 빠집니다

열 규칙

키기본값설명
key(필수)결과 행 객체의 키. $ 로 시작할 수 없고($row 예약), __proto__·constructor·prototype 금지
headerkey머리글. 공백·대소문자·NFKC 차이를 무시하고 비교
aliases[]같은 열로 볼 다른 머리글
requiredtrue열이 파일에 있어야 함(COLUMN_MISSING). false 면 없을 때 값은 null
nullabletrue빈칸 허용. false 면 빈칸이 CELL_REQUIRED
type"text"아래 형식 표
uniquefalse열 안에서 중복 금지(CELL_UNIQUE, 두 번째부터)
enum—허용 값 목록. 정리된 값과 엄격 비교(1 ≠ "1")
pattern—ECMAScript 정규식 문자열, u 플래그로 컴파일. 정리된 문자열 값에 test()(고정하려면 ^…$)
min, max—숫자 값의 범위(CELL_RANGE)
minLength, maxLength—, 32767문자열 길이(UTF-16 단위). maxLength 를 넘는 값에는 정규식을 돌리지 않음
date{ formats: 기본 목록, pivot: 50 }날짜 형식(아래)
phone.kind"any"mobile / landline / any
boolean{ true: ["Y","O","예","TRUE"], false: ["N","X","아니오","FALSE"] }대소문자 무시. 양쪽에 있으면 true
rrn.checksum"warn"off 면 RRN_CHECKSUM 경고를 끔
crn.checksum"off"warn 이면 CRN_CHECKSUM 경고를 켬
severity"error"이 열의 규칙 위반 등급: error / warning
pii—allow: 이 열에서 개인정보 감지 안 함 / forbid: 감지되면 오류(PII_FORBIDDEN)
message—이 열의 칸 오류 문구

한 칸은 빈칸 → 형식 → enum → pattern → 범위 → 길이 → 중복 순서로 검사합니다. 형식이 틀리면 그 칸의 나머지 검사는 하지 않습니다. pattern·길이는 문자열 값에, min·max는 숫자 값에만 적용합니다.

형식과 정규형

정리된 값(정규형)은 다시 넣어도 항상 통과하고, 정리는 여러 번 해도 같습니다(멱등). 서버는 정리된 값을 받습니다.

type받는 값정규형오류 코드
text아무 문자열. 숫자 칸은 문자열로(00000 서식이면 앞자리 0 포함), 불리언은 TRUE/FALSE문자열CELL_TYPE
number숫자, 1,234.5, 1., 1e3, 전각 숫자. 0,123·1천 은 거부JS 숫자CELL_TYPE
integernumber 중 정수, ±2^53−1 이내JS 숫자CELL_TYPE
dateYYYY-MM-DD, 기본 형식 목록, 엑셀 날짜 칸YYYY-MM-DDCELL_DATE
booleanboolean 표의 값, 불리언 칸true / falseCELL_TYPE
emaila@b.c 모양(대소문자 유지)문자열CELL_TYPE
kr.phone, kr.brn, kr.crn, kr.rrn, kr.zip, kr.amount한국 업무용 형식. 한국형 검증기010-1234-5678, 123-45-67890 …KR_* 등
  • 빈칸은 null, 빈 문자열, 정리 후 빈 문자열입니다. 숫자 0 과 false 는 빈칸이 아닙니다. 결과 행에서 빈칸은 null 입니다.
  • 형식이 틀린 칸은 결과 행에 원래 값을, 형식은 맞고 규칙(enum 등)이 틀린 칸은 정리된 값을 둡니다.
  • integer·kr.amount 는 ±2^53−1(Number.MAX_SAFE_INTEGER)을 넘으면 거부합니다.

값 정리

  • 형식이 있는 열(number, integer, date, boolean, email, kr.*)은 normalize 설정과 관계없이 항상 앞뒤 공백, 보이지 않는 문자(제로폭 문자, BOM, NBSP), 전각 숫자·기호를 처리한 뒤 해석합니다.
  • normalize 스위치는 text 열의 결과에만 적용됩니다. trim(앞뒤 공백, U+3000 포함), invisible(제로폭 문자·BOM 제거, NBSP → 공백).
  • text 열은 전각 숫자를 그대로 둡니다. 사용자가 입력한 글(1호점)을 바꾸지 않기 위해서입니다. 그래서 fullwidthDigits 스위치는 현재 결과를 바꾸지 않습니다(규칙 호환을 위해 받기만 합니다). 한글에는 NFKC를 쓰지 않습니다.

날짜

  • 결과는 항상 YYYY-MM-DD. 정규형은 formats 와 관계없이 항상 받습니다. 연도는 0001–9999, 윤년은 그레고리력 규칙입니다.
  • 토큰. YYYY(4자리), YY(2자리, pivot 미만이면 20YY, 아니면 19YY; 기본 50), MM·DD(정확히 2자리), M·D(1–2자리). 쓸 수 있는 글자는 . - / 공백 년 월 일. 연·월·일 토큰이 하나씩 있어야 합니다.
  • 기본 형식: YYYY-M-D, YYYY.M.D, YYYY. M. D., YYYY.M.D., YYYY/M/D, YY-M-D, YYYYMMDD, YYYY년 M월 D일, YYYY년M월D일.
  • 엑셀 날짜 칸. 날짜 서식이 있는 숫자 칸은 엑셀 시리얼로 바꿉니다(1899-12-30 기준, 1900-02-29 버그 처리, date1904 반영). 시각은 버립니다. 파일을 읽을 때 바꾸므로 서버는 시리얼을 받지 않습니다.
  • 날짜 서식이 없는 숫자(그리고 JSON 숫자)는 숫자 글자로 읽습니다. 20240105 → 2024-01-05.

열 찾기

  • 머리글 행에서 header 와 aliases 를 공백·대소문자·NFKC 차이 없이 비교합니다. 결과 행의 키는 머리글 원문이 아니라 key 입니다.
  • required: true 인 열이 없으면 COLUMN_MISSING(오류), 같은 열이 두 번 있으면 COLUMN_DUPLICATE(첫 번째를 씀), 규칙에 없는 열은 extraColumns 에 따라 COLUMN_EXTRA 입니다.
  • check() 결과의 columns 는 [{ key, source, col }](찾은 머리글 원문과 열 글자)입니다.

행 간 규칙

규칙뜻코드 (붙는 열)
{ "when": { "col": "kind", "eq": "법인" }, "require": ["crn"] }조건이 맞으면 필수ROW_REQUIRE (필수 열)
{ "when": { … }, "forbid": ["x"] }조건이 맞으면 비워야 함ROW_FORBID (금지 열)
{ "compare": ["start", "<=", "end"] }두 열 비교 < <= = != >= >(날짜·숫자)ROW_COMPARE (왼쪽 열)
{ "uniqueWith": ["date", "orderNo"] }열 조합 중복 금지ROW_UNIQUE_WITH (첫 열)
  • when 조건은 eq, neq, in, empty: true, notEmpty: true 중 하나입니다. 정리된 값과 엄격 비교하며, empty 말고는 빈칸과 맞지 않습니다.
  • compare 는 두 칸 중 하나라도 비었거나 형식이 틀리면 건너뜁니다. uniqueWith 는 빈칸이 있는 행을 건너뜁니다.
  • 각 규칙에 severity, message 를 줄 수 있습니다. 규칙이 가리키는 열은 columns 에 있어야 합니다(오타 방지).
  • JS 훅: validate(rows, rules, { rowRules: [(row) => [...issues]] }). 규칙 JSON에 들어가지 않으므로 서버에서도 같은 훅을 직접 구현해야 합니다.

빈 행과 합계 행

  • rows.blank: skip(기본, 결과에서 뺌), warn·error(BLANK_ROW), stop(첫 빈 행에서 멈추고 그 뒤 행은 읽지 않음). 어느 정책이든 빈 행은 결과 행이 아니며, 마지막 데이터 행 뒤의 빈 행은 조용히 버립니다.
  • rows.totals: 마지막 데이터 행의 첫 값이 합계·소계·총계·Total 로 시작하거나 모든 칸이 수식이면 합계 행입니다. warn(기본) / error(TOTALS_ROW) / ignore(결과에서 뺌). 파일이 있어야 알 수 있으므로 브라우저(check, readRows)에서만 검사합니다.

수식 인젝션

  • text·email 열에서 = + - @, 탭, CR로 시작하는 문자열입니다. xlsx의 텍스트 열에 있는 실제 수식 칸도 대상입니다(브라우저만).
  • 오탐 방지. 숫자·전화번호·금액으로 읽히는 값(-1000, +82 10-1234-5678, -₩5,000)과 이미 ' 로 시작하는 값은 제외합니다.
  • warn(기본, 경고) / error(오류) / escape(결과 값 앞에 ' 를 붙임. '=… 는 정규형이라 서버에서도 통과하고, 중복·행 규칙은 ' 없이 비교) / off.
  • HYPERLINK(, DDE(, cmd|, IMPORTXML(, IMPORTDATA( 가 들어 있으면 설정과 관계없이(off 라도) 오류입니다.

개인정보

  • 감지(pii.detect, text 열만): rrn(형식+생년월일, 체크섬 없이 — 2020년 이후 번호도 잡음), card(13–19자리 Luhn), phone(휴대폰), email. 기본은 ["rrn"] 입니다. 열마다 PERSONAL_DATA 경고 하나(첫 행에, 개수와 행 목록 포함).
  • 열 설정: pii: "allow" 는 그 열에서 감지하지 않고, pii: "forbid" 는 감지되면 오류 PII_FORBIDDEN 입니다. kr.rrn 처럼 그 정보를 받기로 한 형식의 열은 감지 대상이 아닙니다.
  • 마스킹(pii.mask: true 기본): 주민번호 900101-1******, 카드 1234-****-****-5678, 전화 010-****-5678, 이메일 a***@b.com. 오류 메시지, issue의 raw·value, summary 예시, result.report, CSV·xlsx 오류 리포트, 컴포넌트의 미리보기를 가립니다.
  • result.rows 는 가리지 않습니다. 서버로 보낼 데이터이기 때문입니다.
  • 감지는 칸 길이에 비례하는 시간(선형)에 끝납니다. maxLength 를 넘는 값은 이미 CELL_LENGTH 이므로 감지하지 않습니다.

메시지

칸 오류 코드는 고정된 대문자 상수입니다(오류 코드표). 영어가 기본이고 locale: 'ko' 로 한국어가 됩니다. 열의 message 는 그 열의 칸 오류에만, messages: { CELL_TYPE: '…' }(규칙 또는 옵션)는 모든 0.2 코드에 적용되며, 열의 message 가 우선합니다. 문구 안의 {row}, {col}, {header}, {raw}, {min} 등은 값으로 바뀝니다.

해시

rulesHash(rules) 는 키를 정렬한 정규화 JSON(공백 없음)의 SHA-256, sha256:<hex> 입니다. defineRules() 결과는 기본값을 채우기 전의 원래 JSON으로 계산합니다. readRows, previewRows, locale, telemetry 는 해시에서 뺍니다. 그래서 브라우저 리포트의 schemaHash 와 서버가 자기 rules.json 으로 계산한 값이 같습니다.

JSON Schema와 편집기 자동완성

규칙 파일의 JSON Schema(draft 2020-12)를 https://82excel.com/schema/rules-1.json 에 둡니다(application/json, CORS 허용). 규칙 파일 첫 줄에 $schema 를 적으면 VS Code 같은 편집기가 키와 값을 자동완성하고 틀린 키를 표시합니다. npm 패키지에도 82excel/rules-1.schema.json 으로 같은 파일이 있습니다.

{
  "$schema": "https://82excel.com/schema/rules-1.json",
  "columns": [ … ]
}