Modern .NET library for working with COBOL Copybook–based data
用於處理以 COBOL Copybook 為基礎資料的現代 .NET 類別庫
讀懂你 COBOL 的明白
- .NET 8.0 或更新版本
- C# 12 或相容版本(.NET 8 預設)
- COBOL Copybook (
.cpy) 純文字檔案 - ASCII / CP950 編碼
透過簡單的文字
X與數字9 / S9,我們建構出長達百年的金融體系。
TL;DR
COBOL 的 PICTURE 子句,以極少的符號,精確地描述出資料的型態、長度、符號位、顯示格式與儲存語意。
這套設計方式歷經數十年的實務驗證,支撐了銀行、保險、政府與大型企業的核心系統,至今仍在持續運作。
然而,在現代語言(例如C#、Java、TypeScript、Rust)中,這些語意往往被隱含、分散或遺失:
string與number無法完整表達 定長、補零、符號位置、顯示與儲存差異- 解析邏輯常以 ad-hoc 的
TryParse、正則或硬編碼規則存在 - PIC 與現代型別之間缺乏可驗證、可測試、可組合的轉換模型
本專案的目的,是將 COBOL PICTURE 子句視為一種 明確的資料規格(Data Specification),並:
- 明確區分 顯示格式(DISPLAY) 與 實際數值語意
- 將
9 / S9 / V / X / A等元素拆解為結構化資訊 - 建立可對應至現代語言型別(
int / long / decimal / string等)的判斷依據
- 以 Fluent / Builder 風格描述解析上下文
- 將「字串 → 型別」與「型別 → 字串」視為對等的一階公民
- 讓轉換過程可被單元測試、驗證與重構
- 避免重複撰寫易出錯的解析邏輯
- 提供一致、可預期的行為邊界(精度、符號)
- 作為資料轉換、系統汰換、或雙軌運行的一部分
- 核心系統資料轉出
- COBOL 與現代服務的資料交換層
- 舊系統重構或漸進式汰換
- 對 PIC 規格進行靜態分析或測試驗證
Copybook 是 COBOL 中用來定義資料結構的重用檔案,透過 COPY 指令引入,常用於描述檔案格式、資料欄位配置與記憶體布局。在大型主機與金融系統中,Copybook 是資料交換與系統整合的核心。
Copybook 通常包含:
- 欄位階層(Level Number)
- 資料型別與長度(PIC 子句)
- 儲存格式(如 DISPLAY、COMP、COMP-3)
由於 Copybook 直接對應到位元與位元組配置,它不僅是程式碼的一部分,更是系統間共用的資料規格說明書。
Copybook Wrapper 是一個 Raw Buffer 層級的存取工具。提供欄位級別抽象存取,不需要傳統的 DTO(Data Transfer Object,資料傳輸物件)或序列化/反序列化過程。
Wrapper vs Serialization(序列化)
| 功能 | Wrapper | Serialization |
|---|---|---|
| Raw ↔ 物件 | 不需要 DTO,直接欄位級存取 | Yes, 一次性 DTO |
| 欄位抽象化 | Yes,靠 CbAddress + indexer/屬性 |
No / 需要 mapping |
| Memory 複製 | 幾乎零複製,Span 直接操作 Raw | 全部複製 |
| 動態欄位讀寫 | 內建 indexer 或強型別屬性 | 一般不方便 |
| 物件圖 / 狀態管理 | No,Raw 是唯一來源 | Yes |
資料物件需繼承核心物件 CbWrapper,根據 Copybook 定義,透過 CbAddress 設定每個欄位的起始位置、長度及格式。
- 可透過 indexer 或 強型別屬性存取欄位
- 支援即時驗證 Raw Buffer 長度是否符合欄位配置
程式碼範例:櫃買中心 T30 漲跌幅度資料
const string s = "11011 00106600000096950000087300020251219000000 0台泥一永 000000000000000000000 0 ";
byte[] raw = cp950.GetBytes(s);
var T30 = new T30_t(raw);
Console.WriteLine(T30.StockNo); // "11011"
Console.WriteLine(T30.StockName); // "台泥一永"
Console.WriteLine(T30.LastMthDate); // "2025-12-19"T30_t
public class T30_t(byte[] raw) : CbWrapper(raw)
{
// ----------------------------
// Copybook Address Map
// ----------------------------
protected override Dictionary<string, CbAddress> AddressMap { get; } = new Dictionary<string, CbAddress>
{
["STOCK-NO"] = new CbAddress( 1, 6, "X(6)"),
["BULL-PRICE"] = new CbAddress( 7, 9, "9(5)V9(4)"),
["LDC-PRICE"] = new CbAddress(16, 9, "9(5)V9(4)"),
["BEAR-PRICE"] = new CbAddress(25, 9, "9(5)V9(4)"),
["LAST-MTH-DATE"] = new CbAddress(34, 8, "9(8)", PicSemantic.GregorianDate), // 用語意方式轉換
["SETTYPE"] = new CbAddress(42, 1, "X(01)"),
["MARK-W"] = new CbAddress(43, 1, "X(01)"),
["MARK-P"] = new CbAddress(44, 1, "X(01)"),
["MARK-L"] = new CbAddress(45, 1, "X(01)"),
["IND-CODE"] = new CbAddress(46, 2, "X(02)"),
["IND-SUB-CODE"] = new CbAddress(48, 2, "X(02)"),
["MARK-M"] = new CbAddress(50, 1, "X(01)"),
["STOCK-NAME"] = new CbAddress(51,16, "X(16)"),
// MARK-W
["MATCH-INTERVAL"] = new CbAddress(67, 3, "9(03)"),
["ORDER-LIMIT"] = new CbAddress(70, 6, "9(06)"),
["ORDERS-LIMIT"] = new CbAddress(76, 6, "9(06)"),
["PREPAY-RATE"] = new CbAddress(82, 3, "9(03)"),
["MARK-S"] = new CbAddress(85, 1, "X(01)"),
["STK-MARK"] = new CbAddress(86, 1, "X(01)"),
["MARK-F"] = new CbAddress(87, 1, "X(01)"),
["MARK-DAY-TRADE"]= new CbAddress(88, 1, "X(01)"),
["STK-CTGCD"] = new CbAddress(89, 1, "X(01)"),
["FILLER"] = new CbAddress(90,11, "X(11)"),
};
// ----------------------------
// 強型別屬性
// ----------------------------
public string StockNo
{
get => this["STOCK-NO"].Get<string>();
set => this["STOCK-NO"].Set(value);
}
public decimal BullPrice
{
get => this["BULL-PRICE"].Get<decimal>();
set => this["BULL-PRICE"].Set(value);
}
public decimal LdcPrice
{
get => this["LDC-PRICE"].Get<decimal>();
set => this["LDC-PRICE"].Set(value);
}
public decimal BearPrice
{
get => this["BEAR-PRICE"].Get<decimal>();
set => this["BEAR-PRICE"].Set(value);
}
public DateOnly LastMthDate
{
get => this["LAST-MTH-DATE"].Get<DateOnly>();
set => this["LAST-MTH-DATE"].Set(value);
}
// (略...)
}📖 更多關於 Copybook Compiler ...
📖 更多關於 Copybook Resolver ...
📖 更多關於 Sub-Class Generator : Forge ...
COBOL 程式有一套固定的欄位規則,尤其在 固定格式(Fixed Format) 下很重要。主要分為 Sequence Area, Indicator Area, Area A, Area B 等區域。
|...+.*..1....+....2....+....3....+....4....+....5....+....6....+....7..
01 ORDER-RECORD.
05 ORDER-ID PIC 9(6).
05 ORDER-DATE PIC 9(8).
05 ORDER-AMOUNT PIC S9(7)V99 COMP-3.| 位置 (Column) | 說明 |
|---|---|
| 1–6 | Sequence Number(序號欄,可選):用於列印或版本控制。 |
| 7 | Indicator Area(指示欄): - *:註解- /:換頁- -:延續上一行 |
| 8–11 | Area A:段落名稱、Section 名稱、DIVISION 關鍵字等。 |
| 12–72 | Area B:語句、指令、變數宣告、程式碼本體。 |
| 73–80 | Identification Area(識別欄,可選):通常用於序號或其他控制用途。 |
現代 COBOL
(Free Format)已經不限制欄位,但固定格式仍常用於舊系統。
ℹ️ "Elementary Data Item" and "Group Item"
| 面向 | Elementary Data Item | Group Item |
|---|---|---|
| 定義角色 | 最小資料單位(leaf) | 結構性容器(composite) |
| 是否可包含子項目 | ❌ 不可 | ✅ 可 |
是否有 PIC 子句 |
✅ 必須有 | ❌ 不可有 |
| 是否直接描述資料型態 | ✅ 是(數值、字元、COMP、COMP-3…) | ❌ 否(由子項目間接決定) |
| 是否可直接被 MOVE / COMPUTE | ✅ 可 | |
| 記憶體佔用 | 由 PIC 決定 |
為所有子項目記憶體的總和 |
可否有 OCCURS |
✅ 可 | ✅ 可 |
可否有 REDEFINES |
✅ 可 | ✅ 可 |
可否有 VALUE |
✅ 可 | ❌(標準上 group 不定義 VALUE) |
| 是否為樹的葉節點 | ✅ 是 | ❌ 否 |
| COBOL 規格名稱 | Elementary data item | Group item |
📖 更多關於 Elementary Data Item ...
用於描述程式中所有資料的結構、型態與儲存方式。
Format 1
<level-number> <data-name-1>
[REDEFINES <data-name-2>]
[PICTURE <character-string>]
[USAGE <usage-type>]
[OCCURS <n> TIMES]
[VALUE <literal-1>].
Format 2
66 <data-name-1> RENAMES <data-name-2> THRU <data-name-3>.
Format 3
88 <condition-name-1> VALUE <literal-1> [THRU <literal-2>].
| Format | 語法用途 | 支援狀態 | 說明 |
|---|---|---|---|
| Format 1 | 一般資料項目(Group / Elementary Item) | ✅ 支援 | 用於描述資料結構、型別、PIC、USAGE、OCCURS 等,是目前解析與生成的核心格式。 |
| Format 2 | 66 RENAMES |
屬於語意別名(Alias)的定義,不影響實際的資料儲存結構;相關別名可由 Wrapper 於應用層自行進行二次定義。 | |
| Format 3 | 88 LEVEL 條件名稱 |
❌ 未支援 | 為條件常數定義(Condition Name),本身不佔用任何實體儲存空間。 當與 OCCURS 子句混合使用時,條件判斷的呼叫與對應關係在實作上較為複雜,易影響可讀性與使用一致性,建議直接呼叫 Wrapper 內的屬性來處理。 |
COBOL 使用 Level Number(層級號) 來描述資料結構,主要有:
| Level | 用途 | 說明 |
|---|---|---|
| 01 | 主結構 | 定義檔案或記錄的頂層結構 |
| 02 … 49 | 子結構 | 01 之下的子群組或欄位,形成巢狀結構 |
| 66 | RENAMES | 將已有欄位重新命名或形成別名區段 |
| 77 | 單一變數 | 不屬於群組,獨立使用 |
| 88 | Condition Name | 定義邏輯條件(True/False) |
⚠️ Level number 越小層級越高,01 是最外層。
📖 更多關於特殊層級 ...
Level 66 — RENAMES
Level 77 — Standalone Variable (單一變數)
Level 88 — Condition Name
| RENAMES | REDEFINES | |
|---|---|---|
| 影響 storage | ❌ | ✅ |
| 改變 offset | ❌ | ✅(對齊另一個) |
| 本體是 | 邏輯群組 | GroupItem |
| 最終表現 | View / Property | View / Property |
在 IBM 提供的 REDEFINES clause 文件中,整理出幾種 REDEFINES 可能的使用與法規則:
CASE 1:Group REDEFINES Elementary Data Item
```cobol
05 A PICTURE X(6).
05 B REDEFINES A.
10 B-1 PICTURE X(2).
10 B-2 PICTURE 9(4).
05 C PICTURE 99V99.
```
CASE 2:01-level + GLOBAL
```cobol
01 A1 PICTURE X(6).
01 B1 REDEFINES A1 GLOBAL PICTURE X(4).
```
CASE 3:多個 REDEFINES 指向同一 target
```cobol
05 A PICTURE 9999.
05 B REDEFINES A PICTURE 9V999.
05 C REDEFINES A PICTURE 99V99.
```
CASE 4:REDEFINES 鏈
```cobol
05 A PICTURE 9999.
05 B REDEFINES A PICTURE 9V999.
05 C REDEFINES B PICTURE 99V99.
```
| Case | 用法說明 | 支援狀態 | 說明 |
|---|---|---|---|
| CASE 1 | Group REDEFINES Elementary Data Item | ✅ 支援 | 最常見且結構單純的用法。Group 僅作為 Elementary Item 的另一種結構化視角,不引入額外 storage。 |
| CASE 2 | 01-level REDEFINES + GLOBAL | ❌ 不支援 | 涉及 01-level overlay 與 GLOBAL 可視範圍,在高階語言中難以安全對應。 |
| CASE 3 | 多個 REDEFINES 指向同一 target | 會形成多重 storage alias,容易造成資料覆寫與語意不明確。 | |
| CASE 4 | REDEFINES 鏈(REDEFINES 已 REDEFINES 的 item) | 需解析並正規化多層 alias 關係,實作與維護成本過高。 |
再根據這篇 Redefined data items and OCCURS clauses 的說明,裡面提到:
According to Standard
COBOL 2002, the data item being redefined cannot contain an OCCURS clause.
所以本專案亦不支援過於複雜的 REDEFINES 運作行為。
支援的 character-string (Symbols) 語法
| Alphabetic | Alphanumeric | Numeric | Numeric (With Sign) |
|---|---|---|---|
| A.. A(n) |
X.. X(n) |
9... 9(n) 9...V9... 9(n)V9(m) 9(n)V9... |
S9... S9(n) S9...V9... S9(n)V9(m) S9(n)V9... |
📖 更多關於 PICTURE Clause Codec ...
USAGE 定義欄位在記憶體中的儲存方式,影響資料的物理編碼與運算行為。
- DISPLAY(預設):以可讀字元存放,每個數字或字母對應一個 byte,便於輸入輸出與檢視。
- COMPUTATIONAL:用電腦原生格式儲存,只用於
Numeric欄位。- COMP-3(Packed Decimal):將兩個數字壓縮在一個 nibble,最後一個 nibble 用於符號,節省空間且方便算術運算。
- COMP-4(Binary)/ COMP-5(Native Binary):以二進位形式存放,運算效率高 (對 COBOL 而言),但不可直接讀取文字。
- COMP-6(Unsigned Packed Decimal):非標準 COBOL 定義。與 COMP-3 方式一樣,但是沒有 sign nibble。
USAGE 項目的適用範圍:
| Class | Category/Semantic | Usage |
|---|---|---|
| Alphabetic | Alphabetic | DISPLAY |
| Alphanumeric | Alphanumeric | DISPLAY |
| Numeric | Numeric | DISPLAY COMP (Binary) COMP-3 (Packed Decimal) COMP-4 (Binary) COMP-5 (Native Binary) COMP-6 (Unsigned Packed Decimal) |
| Date-Time | Date Time Timestamp |
DISPLAY |
📖 更多關於 COMPUTATIONAL 轉換規則 ...
執行指令:
dotnet run -c Release --project GetThePicture.Benchmarks\GetThePicture.Benchmarks.csproj -f {TargetSdkVersion} --filter * dotnet run -c Release --project GetThePicture.Benchmarks\GetThePicture.Benchmarks.csproj -f {TargetSdkVersion} --anyCategories {BenchmarkCategory} - TargetSdkVersion :
net8.0net10.0
- 根據櫃買中心 (OTC) 規格改寫的
T30.CPY(包含註解):DataItem 24 個 - 部分櫃買中心 (OTC) 的
T30.DAT:漲跌幅度資料 55 筆
Rocket Software ACUCOBOL-GT extend (V10.5.0) : USAGE Clause
IBM Enterprise COBOL for z/OS (6.5.0) : USAGE clause
IBM Enterprise COBOL for z/OS (6.5.0) : RECORD KEY clause
IBM COBOL for Linux on x86 (1.2.0) : Classes and categories of data



