ISO 2709(資訊與文獻—書目資訊交換格式)是圖書館界廣泛使用的標準格式,又稱 MARC (MAchine-Readable Cataloging) 通用格式。本文深入解析一個 C# 類別 Nosi.Library.Marc,展示如何將原始位元組流切割為三大區段(Leader、Directory、Variable Fields),並提供欄位/子欄位層級的查詢 API。雖然現代圖書館系統多改用 MARCXML 或 JSON-LD,但理解二進位格式對資料遷移、舊系統整合仍具關鍵價值。
+----------------+----------------+---------------------+ | Leader (24 B) | Directory | Variable Fields | | 固定長度 | 可變長度 | 可變長度 | | | 每條 12 字元 | 以 0x1E 分隔欄位 | | | 以 0x1E 結束 | 子欄位以 0x1F 開頭 | | | | 記錄以 0x1D 結束 | +----------------+----------------+---------------------+
FLDEND = 0x1E (30) 欄位結束、RECEND = 0x1D (29) 記錄結束、SUBFLD = 0x1F (31) 子欄位指示符。namespace Nosi.Library
{
public class Marc
{
// 常數
public const char FLDEND = (char)0x1E;
public const char RECEND = (char)0x1D;
public const char SUBFLD = (char)0x1F;
public const int FLDNAME_LEN = 3;
public const int MAX_MARCREC_LEN = 100_000;
private string _marcRecord = "";
// 屬性:原始記錄字串
public string MarcRecord { get => _marcRecord; set => _marcRecord = value; }
// 三大區段存取
private string GetLeader() => _marcRecord.Substring(0, 24);
private string GetDirectory() { /* 從 24 讀到第一個 FLDEND */ }
private string GetVariableFields() { /* 依 Directory 長度切割 */ }
// 公開查詢 API
public int GetField(string tag, int occurrence, out string field) { ... }
public int GetSubField(string field, string subCode, int occurrence, out string subField) { ... }
public int GetSubField(string fieldGroup, string subCode, out string subField) { ... }
public int GetFieldGroup(string field, int occurrence, out string group) { ... }
// 輔助:雙位元組字元長度計算 (Big5/GB 環境)
private int GetCharLength(char ch) => (ch > 127 || ch < 0) ? 2 : 1;
}
}
private int GetFieldPos(string tag, int index, out string fieldPos)
{
string dir = GetDirectory();
if (dir == null || (dir.Length - 1) % 12 != 0) return -1; // 格式錯誤
int count = 0;
for (int i = 0; i < dir.Length; i += 12)
{
if (dir.Substring(i, 3) == tag) count++;
if (count == index)
{
fieldPos = dir.Substring(i, 12); // Tag(3) + Len(4) + Pos(5)
return 1;
}
}
return 0; // 未找到
}
回傳碼:-1 錯誤、0 找不到、1 成功。
int length = int.Parse(fieldPos.Substring(3, 4));
int startPos = int.Parse(fieldPos.Substring(7, 5));
char[] data = GetVariableFields().ToCharArray();
int bytePos = 0, charIndex = 0;
while (bytePos < data.Length)
{
charIndex += GetCharLength(data[bytePos]);
if (charIndex >= startPos && charIndex <= startPos + length)
field += data[bytePos];
bytePos++;
}
關鍵點:Directory 記錄的是**字元位移**而非位元組位移,故須逐字元累加 GetCharLength 以正確定位中文字元(雙位元組)。
public int GetSubField(string field, string subCode, int occurrence, out string subField)
{
if (subCode.Length != 1) return -1;
char[] arr = field.ToCharArray();
int found = 0;
bool inTarget = false;
for (int i = 0; i < arr.Length; i++)
{
if (arr[i] == SUBFLD && i+1 < arr.Length && arr[i+1].ToString() == subCode)
found++;
if (found == occurrence && !inTarget)
{
inTarget = true;
if (arr[i] == SUBFLD) { inTarget = false; break; } // 下一子欄位
subField += arr[i];
}
}
return subField == null ? 0 : 1;
}
邏輯:掃描 SUBFLD + code 計數,進入目標子欄位後累加字元,遇到下一個 SUBFLD 停止。
以 $a 為分隔符,將同一 Tag 多次出現的資料切分為多個「群組」(常見於 7XX 责任者欄位)。
// 假設 rawMarc 為讀取的 ISO 2709 原始字串
var marc = new Marc { MarcRecord = rawMarc };
// 取得 200 欄位 (標題與責任) 第 1 次出現
if (marc.GetField("200", 1, out string f200) == 1)
{
Console.WriteLine($"200 欄位完整內容: {f200}");
// 取得 $a (標題)
if (marc.GetSubField(f200, "a", 1, out string title) == 1)
Console.WriteLine($"標題: {title}");
// 取得 $f (責任者)
if (marc.GetSubField(f200, "f", 1, out string author) == 1)
Console.WriteLine($"作者: {author}");
}
// 遍歷所有 600 欄位 (主題)
for (int i = 1; ; i++)
{
if (marc.GetField("600", i, out string f600) != 1) break;
if (marc.GetSubField(f600, "a", 1, out string subject) == 1)
Console.WriteLine($"主題: {subject}");
}
Span<byte> / ReadOnlySpan<byte>:避免字串分配,直接在位元組層級解析,支援 UTF-8/MARC-8 自動偵測。IEnumerable<MarcField>:提供 LINQ 風格查詢 marc.Fields.Where(f => f.Tag == "200")。async IAsyncEnumerable<MarcRecord> 處理大型 MARC 檔案(百萬筆)。MarcRecord、MarcField、MarcSubField 類別,取代字串操作。Nosi.Library.Marc 雖然以字串為基礎、程式碼風格屬於 .NET 1.x 時代,但完整實作了 ISO 2709 的核心解析邏輯:Leader/Directory/Variable Fields 三層切割、字元位移定位、子欄位代碼掃描。理解這個實作,有助於開發 MARC 轉換工具(MARC → MARCXML/Dublin Core/BIBFRAME)、批次匯入系統、書目資料品質檢核等圖書館資訊系統核心功能。