본문으로 건너뛰기

빈 칸과 없음 — -\-

문서 목록으로

상태: 구현됨

셀이 비어 있는 것과 셀이 값이 없다고 적은 것을 구별하는 표기의 설계입니다.

지금은 그 둘이 같습니다. 옵셔널 컬럼의 빈 칸이 곧 「없음」이므로, string? 컬럼에는 빈 문자열을 적을 방법이 없습니다 — 빈 칸은 언제나 없음이 되어 JSON에 null로 나갑니다. bool?도 같아서, 「적지 않았다」와 「거짓이다」가 한 칸을 나눠 쓰고 있습니다.


1. 문제 — 빈 칸 하나가 두 가지를 뜻하는 것

현재 각 타입이 빈 칸을 읽는 방법입니다.

타입필수 컬럼의 빈 칸옵셔널 컬럼(?)의 빈 칸
string"" — 값입니다없음 (HasValue = false)
boolfalse — 값입니다없음
T[]빈 배열 — 값입니다없음
int bigint float double오류없음
bitset datetime timespan uuid enum 라벨오류없음
참조없음으로 읽고 검증이 거부없음

앞 세 줄에서 같은 표기가 컬럼의 선언에 따라 다른 것을 뜻합니다. string 컬럼의 빈 칸은 값이고, string? 컬럼의 빈 칸은 없음입니다. 그래서 컬럼을 옵셔널로 바꾸는 순간 그 컬럼에 이미 적혀 있던 빈 칸 전부의 뜻이 조용히 바뀝니다.

표현할 수 없는 값도 생깁니다. string?의 빈 문자열과 bool?의 거짓이 그것입니다. int?에서는 0을 적으면 되지만, 문자열에는 「빈 문자열을 적는 다른 방법」이 없습니다.

없음을 적는 표기가 없다는 것이 원인입니다. 없음이 「적지 않음」으로만 표현되므로, 적지 않을 수 있는 다른 뜻들과 자리를 다투게 됩니다.

2. 결정 — 셀 문법 3개

셀에 적힌 것
빈 칸그 타입이 빈 칸을 읽는 규칙 그대로입니다. "" · false · 빈 배열 · 숫자 컬럼이면 오류. 값이 있는 셀입니다
-없음입니다. HasValue = false이고, 값 자리에는 타입의 빈 값이 들어갑니다
\-문자열 - 한 글자입니다

빈 칸의 뜻은 하나도 바뀌지 않습니다. 지워지는 것은 「옵셔널 컬럼의 빈 칸은 없음이다」는 규칙 한 줄이고, 없음은 -가 가져갑니다.

?의 뜻이 명확해집니다. 지금 ?는 「빈 칸을 허용한다」이고, 그래서 string?bool?에 붙인 ?의도 표기일 뿐이었습니다. 앞으로 ?「이 컬럼에 -를 적을 수 있다」 입니다. 타입에 관계없이 같은 뜻이고, 모든 언어의 Has{필드} 액세서가 나타내는 것과 정확히 같은 것을 나타냅니다.

-는 옵셔널 컬럼에서만 뜻을 가집니다. int 컬럼의 -는 오류이고, int? 컬럼의 -만 없음입니다. 없음을 표현할 수 있다고 컬럼이 선언한 자리에서만 없음을 적을 수 있습니다.

특별하게 읽히는 표기 2개

- 문자를 이스케이프하는 체계가 아닙니다. 셀 전체가 정확히 - 또는 \-일 때만 특별하고, 그 밖의 자리에서 -\는 적힌 그대로입니다.

읽히는 값
-없음
\--
-5-5 — 숫자이든 문자열이든 그대로입니다
A-1A-1
----
\-a\-a\가 그대로 남습니다
\\-\\-

판정은 앞뒤 공백을 제거한 텍스트로 합니다. 스프레드시트에서 뒤따르는 공백은 보이지 않으므로, - 를 없음이 아니라고 판정하면 작성자가 원인을 찾을 수 없는 차이가 생깁니다. 값으로 읽히는 문자열의 공백은 지금처럼 보존합니다 — 제거한 텍스트는 이 판정에만 씁니다.

표현할 수 없는 값 1개

문자열 \- 입니다. \\-를 그 표기로 삼으면 낱말 2개를 특별하게 두는 것에서 이스케이프 체계로 넘어가고, 그러면 값에 이미 들어 있는 \의 뜻이 전부 바뀝니다.

실측입니다. 이전 대규모 코퍼스의 문자열 값에는 \ 문자가 198개 들어 있고, 전부 \n 두 글자로 게임의 텍스트 렌더러가 읽는 줄바꿈 표기입니다. 일반 이스케이프 체계를 도입하면 그 198개가 전부 다른 값이 됩니다. 표현하지 못하는 값 1개와 뜻이 바뀌는 값 198개 중 앞을 고릅니다.

3. 타입별 읽기

타입빈 칸- (옵셔널 컬럼)- (필수 컬럼)\-
string""없음오류-
boolfalse없음오류오류 — bool이 아닙니다
int bigint float double오류 — 4절없음오류오류
bitset오류 — 4절없음오류오류
datetime timespan uuid오류 — 4절없음오류오류
enum 라벨오류 — 4절없음오류오류
참조오류 — 5절없음오류오류
T[]빈 배열없음 (배열 자체가 없습니다)오류아래 원소 규칙

필수 컬럼의 -는 거부합니다. 그것이 ?를 뜻있게 만드는 자리입니다. 메시지는 셀 위치와 함께 두 가지 고침을 냅니다 — 값을 적거나, 컬럼 타입을 int?로 적거나.

`Needed.Hp` has no value, and the sheet declares the column required.
Write one, or type the column `int?` so that a row may say it has none.
at no-value-refused.xlsx : Bad : C10

판정하는 자리는 검증입니다. 읽는 쪽은 -를 없음으로 읽어 HasValue를 거짓으로 두고, 그것이 허용되는지는 컬럼의 선언을 보는 검증이 판정합니다 — 참조의 「없음」이 같은 이유로 옮겨 둔 자리이고, 그래야 워크북 전체의 위반이 한 번에 보고됩니다.

빈 값이 없는 타입은 옵셔널이 될 수 없습니다. 지금 EmptyValueOf가 거부하는 것들이고, 이 변경이 그 목록을 늘리거나 줄이지 않습니다.

배열의 원소

배열 셀은 구분자로 나눈 원소마다 같은 판정을 받습니다.

배열 셀읽히는 값
빈 칸빈 배열 — 값입니다
-없음. T[]?일 때만 허용합니다
a;\-;b["a", "-", "b"]
a;-;b오류 — 원소는 없음을 가지지 않습니다

원소가 없음을 가지지 않는 것은 옵셔널 필드에서 이미 정한 것입니다. 한 셀에 든 목록의 원소는 전부 있거나 전부 없고, 그래서 ?는 배열 괄호 뒤에만 붙습니다. a;-;b의 거부는 그 결정을 셀에서도 그대로 적용하는 것입니다.

그 결정이 바뀌면 이 거부가 허용으로 바뀝니다. 원소가 없을 수 있는 배열T?[]를 정의하고, 거기서 a;-;b는 가운데 원소가 없는 배열입니다. 표기는 이 문서의 것 그대로이고 — 없음은 -, \-는 문자 - — 바뀌는 것은 그것을 적을 수 있는 자리뿐입니다.

4. 빈 칸 정책 — OnBlankCell

3절에서 빈 칸이 오류인 타입들이 있습니다. 숫자·날짜·기간·uuid·enum이고, 기본은 그대로 오류입니다. 그것을 타입의 빈 값으로 읽겠다고 적을 수 있는 소스 항목의 설정을 둡니다.

OnBlankCell빈 칸
error (기본)3절 그대로입니다. 빈 값이 없는 타입의 빈 칸은 어느 셀인지 함께 거부합니다
empty타입의 빈 값으로 읽습니다 — 0 · false · 0001-01-01 · Guid.Empty · enum의 0
{
"Sources": {
"Xlsx": [
{ "Path": "sheets", "OnBlankCell": "empty" }
]
}
}

empty로 읽은 셀도 값이 있는 셀입니다. HasValue는 참이고, 와이어의 presence 비트도 켜집니다. 없음을 적는 방법은 - 하나뿐이라는 것이 이 설정으로 흔들리지 않습니다 — 이 설정이 정하는 것은 「빈 칸이 오류인가 0인가」이지 「빈 칸이 없음인가」가 아닙니다.

기본이 엄격한 이유. 숫자 컬럼의 빈 칸은 대부분 적다 만 것입니다. 그것을 0으로 읽으면 데이터에 0이 들어가고, 그 0은 작성자가 적은 0과 구별되지 않습니다 — 이 도구가 검출해야 할 휴먼 오류가 그대로 통과하는 자리입니다. keep-firstempty가 그렇듯, 중복 인덱스수식 오류의 완화와 같은 성격의 설정입니다.

그래서 다른 팀이 유지하는 워크북을 위한 것입니다. 고칠 권한이 없는 시트에서 빈 칸 하나 때문에 워크북 전체의 변환이 멈추는 것은 읽는 쪽이 답할 수 없는 질문입니다. empty로 읽은 셀은 컬럼당 한 번 경고하므로, 몇 개를 그렇게 읽었는지가 실행 기록에 남아 시트 소유자에게 전달됩니다.

소스 항목의 정식 프로퍼티로 둡니다. 어느 레이아웃에나 해당하는 설정이므로 LayoutOptions의 자유 키가 아니라 SheetSourceRecipe의 프로퍼티입니다 — DefaultDelimiter · OnDuplicateIndex · OnFormulaError가 있는 자리이고, 어느 레이아웃인지 몰라도 찾을 수 있어야 하기 때문입니다.

-에는 적용되지 않습니다. 필수 컬럼의 -OnBlankCell이 무엇이든 오류입니다. 이 설정은 빈 칸에 대한 것이고, -는 빈 칸이 아닙니다.

참조도 따르지 않습니다. 참조 컬럼의 빈 칸은 empty로도 통과하지 않고 5절의 메시지로 거부됩니다. 빈 값으로 읽으면 키 0이 되는데, 그것은 「가리키는 것이 없다」라고 적은 셀과 같은 값입니다 — 이 설정이 완화하려는 것은 미완성 셀을 만난 변환이 멈추는 것이지, 미완성 셀이 뜻을 갖게 되는 것이 아닙니다.

5. 없음을 적을 수 있는 자리

자리-근거
옵셔널 컬럼허용이 문서가 정하는 것
필수 컬럼거부3절
인덱스 컬럼거부로우를 식별하는 값이라 없음이 뜻할 것이 없습니다. * 접두사로 붙인 두 번째 인덱스도 같습니다
참조 컬럼옵셔널이면 허용참조의 「없음」이 정한 규칙 그대로이고, 표기만 빈 칸에서 -로 바뀝니다
레코드 멤버멤버가 옵셔널이면 허용레코드 멤버별 옵셔널

참조 컬럼의 빈 칸은 거부로 바뀝니다. 지금은 필수든 아니든 없음으로 읽고 검증이 판정하는데, 그 이유가 「빈 칸이 이 레이아웃의 없음 표기」였습니다. 표기가 -가 되었으므로 옵셔널 참조의 빈 칸도 거부합니다. 판정하는 자리는 그대로 검증이고, 메시지가 -를 안내하도록 바뀝니다.

`Drop.RewardItem`은 `Item`을 가리키고, 셀이 비어 있습니다.
대상의 키를 적거나, 가리키는 것이 없다는 뜻이면 `-`를 적습니다.
at Drop.xlsx : Drop : F12

6. 바뀌는 동작

무엇지금앞으로작성자가 받는 신호
int 컬럼의 빈 칸오류오류 — 변화 없음기존 메시지에 ?OnBlankCell 안내를 더합니다
int? 컬럼의 빈 칸없음오류셀 위치와 - 안내
string? 컬럼의 빈 칸없음""7절의 한시적 경고
bool? 컬럼의 빈 칸없음false7절의 한시적 경고
T[]? 컬럼의 빈 칸없음빈 배열7절의 한시적 경고
참조 컬럼의 빈 칸없음오류5절의 메시지
필수 컬럼의 -문자열이면 값 -, 나머지는 오류오류3절의 메시지
뒤쪽 원소 자르기빈 칸으로 잘립니다-로 잘립니다7절

산출물이 바뀌는 범위 — 실측

대상값이 정확히 -인 문자열
samples/sprout/out/json0개
samples/canopy/out/json0개
test/fixtures/golden 전체0개

필수 컬럼에 -를 적어 둔 셀은 어느 프로젝트에도 없습니다. 그래서 위 표 일곱 번째 줄의 거부는 지금 통과하는 워크북을 하나도 멈추지 않습니다. 이전 대규모 코퍼스는 이미 -를 없음으로 읽고 있으므로 당연하고, 이전 소규모 코퍼스는 -를 문자열로 읽는데도 그렇게 적힌 셀이 없습니다.

골든 — 실측

바뀐 골든은 text 하나입니다. 빈 칸이 없음을 뜻하던 픽스처들 — optional, record-trim, record-ref, record-ref-trim, reference-optional — 의 해당 셀에 -를 적었고, 그 다섯 시나리오의 골든은 한 바이트도 바뀌지 않았습니다. 없음을 적는 방법만 바뀌었지 없음의 뜻은 바뀌지 않았다는 것이 그 결과입니다.

text는 의도한 변경입니다. Quest.Hint(text?)의 두 빈 칸 중 하나를 -로 바꾸고 하나를 빈 칸으로 두었으므로, 그 하나가 null에서 ""가 됩니다. 두 뜻이 한 골든에 나란히 남습니다.

row-sets/json-named/Narrow_alt.jsonnull 2개는 빈 칸에서 온 것이 아니라 그 벌에 없는 컬럼에서 온 것이므로 그대로입니다.

뒤쪽 원소 자르기

가변 길이 레코드 배열의 자르기는 Cell.HasValue를 읽으므로 규칙 자체는 바뀌지 않습니다. 바뀌는 것은 시트에 무엇을 적어야 잘리는가입니다.

Slot1Slot2Slot3지금앞으로
빈 칸길이 2길이 3 — 빈 칸이 값입니다
-(문자열이면 값 -)길이 2

문자열 멤버를 가진 레코드에서 특히 분명합니다 — 지금은 마지막 원소의 문자열 칸을 비우면 그 원소가 사라지는데, 그것은 「빈 이름을 적었다」와 구별되지 않습니다.

7. 이행

표기 자체는 옵션으로 켜지 않습니다. 이 변경의 내용이 「한 표기가 한 가지만 뜻하게 하는 것」인데, -를 읽을지 말지를 recipe가 정하면 표기의 뜻이 워크북마다 갈립니다. 4절의 OnBlankCell이 정하는 것은 그것과 다릅니다 — 빈 칸이 오류인지 0인지이지, -가 없음인지가 아닙니다.

대신 한 릴리스 동안 경고합니다. 조용히 바뀌는 것은 string? · bool? · T[]? 컬럼의 빈 칸뿐이고 — 나머지는 전부 오류로 드러납니다 — 그 셋만 경고 대상입니다.

`Cell.Text` holds blank cells, which are now the type's empty value rather than
"no value". Write `-` in the rows that have none. (34 cells)
at blank-and-null.xlsx : Cells : D11
  • 컬럼당 한 번, 개수와 첫 셀 위치를 함께 보고합니다. 셀마다 내면 빈 칸이 많은 시트에서 다른 진단이 묻힙니다. 집계는 모든 시트를 읽은 뒤에 나오므로 개수가 실제 총계입니다.
  • 다음 릴리스에서 이 경고를 제거합니다. 경고가 남아 있는 동안에는 빈 칸이 값이라는 사실이 이미 적용되어 있으므로, 경고의 유무가 동작을 바꾸지 않습니다.

레이아웃은 빈 칸에 대한 자기 규칙을 유지합니다. 「빈 칸은 이 레이아웃에서 실수이므로 경고하고 없음으로 읽는다」처럼 셀 텍스트를 먼저 해석하는 레이아웃이 있고, 그것은 그 레이아웃 파일 안의 결정입니다. 코어가 정하는 것은 -\-의 뜻이고, 레이아웃은 그 둘을 다시 정의할 수 없습니다.

8. 와이어와 생성 코드 — 변경 없음

  • presence 비트맵 그대로입니다. TCB v103이 로우당 1비트로 담는 것이 HasValue이고, 이 문서는 그 비트를 무엇으로 적는가만 바꿉니다. 형식 버전을 올리지 않습니다.
  • Has{필드} 액세서 그대로입니다. 모든 언어의 생성 코드가 바뀌지 않습니다.
  • JSON은 -null로 냅니다. 지금 없음을 null로 내는 경로 그대로이고, 달라지는 것은 빈 칸이 더 이상 그 경로로 가지 않는다는 것뿐입니다.
  • string?의 빈 문자열이 처음으로 표현됩니다. JSON에 ""로 나가고, 생성 코드에서는 HasTitle이 참이면서 값이 빈 문자열입니다.

9. 구현 자리

파일하는 일
src/Cooking/CookingContext.cs셀 텍스트를 「없음 · 값」으로 판정하는 함수 1개와, 값일 때의 \- 해제. 배열은 원소마다 같은 판정. 빈 칸에는 OnBlankCell을 적용
src/Recipe/SheetSourceRecipe.csOnBlankCell 프로퍼티. 기본 error
src/Cooking/Layouts/TabbitLayoutParser.csblankIsAbsence를 제거하고 판정을 코어 함수로 넘깁니다
src/Cooking/Layouts/NamedRangeLayoutParser.cstext == "-" 비교를 코어 판정으로 대체합니다. 빈 칸에 대한 이 레이아웃의 경고는 유지합니다
src/Cooking/ModelCooker.Validation.cs필수 컬럼·인덱스 컬럼의 - 거부, 참조 빈 칸의 새 메시지

판정 함수가 한 곳인 것이 요건입니다. 지금 -의 뜻은 한 레이아웃 파일에만 있고, 그래서 다른 레이아웃에서는 같은 셀이 다른 것을 뜻합니다. 표기를 코어로 올리는 이유가 그것이므로, 비교가 두 곳으로 늘어나면 얻는 것이 없습니다.

10. 채택하지 않은 안

일반 백슬래시 이스케이프. \를 이스케이프 문자로 정의하고 \\를 리터럴 \로 읽는 안입니다. 2절의 실측대로 값에 이미 들어 있는 \n 198개의 뜻이 바뀌고, 그 변화는 조용합니다 — \n이 개행 문자가 되어도 오류가 나지 않기 때문입니다.

null 키워드. - 대신 null 또는 <null>을 적는 안입니다. null을 값으로 가지는 문자열 컬럼(상태 이름·enum 라벨을 문자열로 적은 컬럼)에서 같은 충돌이 생기고, -보다 길어서 없음이 많은 시트에서 읽기 비용이 상승합니다. -는 이미 한 레이아웃의 표기이므로 검증된 선택이기도 합니다.

빈 칸을 없음으로 두고 -를 추가로 허용. 두 표기가 같은 것을 뜻하게 되므로 1절의 문제가 그대로 남습니다. string?의 빈 문자열은 여전히 표현되지 않습니다.

OnBlankCell을 컬럼마다. 시트에서 컬럼별로 적는 안입니다. 이 설정의 성격이 「이 워크북을 우리가 고칠 수 있는가」이고 그 답은 워크북 전체에 같으므로, 컬럼마다 적을 것이 아닙니다. 특정 컬럼에서 빈 칸에 뜻을 주고 싶다면 그것은 옵셔널이고 -입니다.

타입별로 다르게 적용. 「문자열만 -를 읽고 숫자는 빈 칸을 그대로 없음으로 둔다」는 안입니다. 시트를 적는 쪽에서 컬럼 타입을 보고 표기를 고르게 되고, 그 규칙은 외울 수 있는 것이 아닙니다.

11. 검증

BlankAndNullCellTests가 규칙 전체를 봅니다. 픽스처는 4개입니다.

픽스처무엇
blank-and-null3절의 타입별 읽기. 타입마다 값·빈 칸·-·\-, 그리고 -5·A-1·--. JSON·바이너리·C# 골든
no-value-refused필수 컬럼의 -와 인덱스의 -. 둘 다 검증이 보고하므로 한 워크북에서 두 메시지
no-value-element배열 원소 자리의 -. 읽는 중에 거부되므로 워크북이 따로 있습니다
blank-cell같은 워크북을 recipe 2개로 — blank-cell-strict는 거부, blank-cell-empty는 빈 값과 HasValue = true, 그리고 경고
그 밖어떻게
참조의 빈 칸과 -reference-required-blank- 로우를 더해 두 메시지를 각각
자르기record-trim·record-ref-trim의 빈 칸을 -로. 골든이 그대로여야 합니다
기존 골든6절 — 다섯 시나리오가 한 바이트도 바뀌지 않았고 text만 의도대로 바뀌었습니다
샘플샘플 재생성

12. 문서 변경

문서바뀌는 것
시트 작성옵셔널 절 전체, bitset 절의 빈 칸 한 줄, 자르기 절의 예시 표
recipeOnBlankCell 항목, 자르기 한 줄
옵셔널 필드1절의 「빈 칸」을 「없음 표기」로. 2단계 설계는 그대로
참조의 「없음」2절의 「빈 칸」 판정을 -
가변 길이 레코드 배열「값이 없다」의 정의 표

doc/sheets.mdbitset 절에는 「빈 칸은 플래그가 하나도 없는 것(0)」이라고 적혀 있는데, 코드는 필수 bitset의 빈 칸을 거부하고 bitset?을 적으라고 안내합니다. 이 문서와 무관하게 문서 쪽이 틀린 자리이고, 3절의 표가 코드와 같습니다.