문서 › 규칙
규칙 형식 레퍼런스 (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.mask | true | 메시지·리포트의 개인정보를 가림 |
normalize.trim / .invisible / .fullwidthDigits | true | text 열의 정리 스위치 |
messages | — | 코드별 문구 덮어쓰기 { "CELL_TYPE": "…" } |
sheet, headerRow | 첫 시트, 1 | 0.1과 같음 |
accept, maxSize, maxUncompressedSize, maxCells, delimiter | 0.1과 같음 | 파일 단위 옵션 |
failOn | [] | 오류로 올릴 경고 코드. check() 와 validate() 가 같게 적용 |
readRows, previewRows, locale, telemetry | — | 한 번의 실행만 바꾸는 키. 해시에서 빠집니다 |
열 규칙
| 키 | 기본값 | 설명 |
|---|---|---|
key | (필수) | 결과 행 객체의 키. $ 로 시작할 수 없고($row 예약), __proto__·constructor·prototype 금지 |
header | key | 머리글. 공백·대소문자·NFKC 차이를 무시하고 비교 |
aliases | [] | 같은 열로 볼 다른 머리글 |
required | true | 열이 파일에 있어야 함(COLUMN_MISSING). false 면 없을 때 값은 null |
nullable | true | 빈칸 허용. false 면 빈칸이 CELL_REQUIRED |
type | "text" | 아래 형식 표 |
unique | false | 열 안에서 중복 금지(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 |
integer | number 중 정수, ±2^53−1 이내 | JS 숫자 | CELL_TYPE |
date | YYYY-MM-DD, 기본 형식 목록, 엑셀 날짜 칸 | YYYY-MM-DD | CELL_DATE |
boolean | boolean 표의 값, 불리언 칸 | true / false | CELL_TYPE |
email | a@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": [ … ]
}