2016 年 3 月
第 31 卷,第 3 期
本文章是由機器翻譯。
現代化應用程式 - 在 UWP App 中剖析 CSV 檔案
剖析逗號分隔值 (CSV) 檔案聽起來很容易足夠剛開始的時候。速度快,不過,工作就變得越來越複雜為一目了然的 CSV 檔案的難題。如果您不熟悉的格式,CSV 檔案會以純文字儲存資料。檔案中的每一行會構成一筆記錄。每個記錄具有其欄位通常是以逗號分隔,因此分隔名稱。
開發人員立即享有資料交換格式之間的標準。CSV 檔案 「 格式 」 harkens 到軟體產業 JSON、 XML 之前先在先前的時間。CSV 檔案的要求建議 (RFC) 時 (bit.ly/1NsQlvw),它並不喜歡官方的狀態。此外,它被建立在 2005 年之久之後開始出現在 1970 年代 CSV 檔案中。如此一來,有相當多的 CSV 檔案的變化,而且這些規則的有點模糊不清。例如,CSV 檔案可能有索引標籤、 分號或任何字元分隔的欄位。
實際上,CSV 匯入和匯出的 Excel 實作已成為遂標準,而且會顯示最常在業界,甚至是外部的 Microsoft 生態系統。因此,假設我在此文件中有關構成 「 正確 」 剖析和格式化會根據 Excel 匯入/匯出的 CSV 檔案的方式。雖然大部分的 CSV 檔案將會與 Excel 實作落,將不是每個檔案。此資料行的尾端,我將介紹的策略來處理這類不確定性。
公平的問題是,「 為什麼甚至寫入幾十年歷史的擬格式的剖析器在相當新的平台? 」 答案很簡單: 許多組織都有舊版資料系統。由於檔案格式的壽命長,幾乎所有這些舊版資料系統可以匯出至 CSV。此外,花費的時間和精力來將資料匯出至 CSV 極少的詞彙。因此,有許多的 CSV 格式的檔案中大型企業和政府資料集。
設計全方位 CSV 剖析器
儘管官方標準缺乏,CSV 檔案通常會共用一些共同的特徵。
一般而言,CSV 檔案 ︰ 是純文字,包含每一行記錄、 分隔符號隔開每一行中有記錄,則有一個字元的分隔符號和存在的欄位順序相同。
這些一般特性簡述一般的演算法,就會含有三個步驟 ︰
- 分割沿著行分隔符號的字串。
- 分割欄位分隔符號的每一行。
- 將每個欄位的值指派給變數。
這是很容易實作。中的程式碼 [圖 1 CSV 輸入的字串剖析 < 字典 < 字串、 字串 >> 的清單。
[圖 1 CSV 輸入字串剖析成清單 < 字典 < 字串、 字串 >>
var parsedResult = new List<Dictionary<string, string>>();
var records = RawText.Split(this.LineDelimiter);
foreach (var record in records)
{
var fields = record.Split(this.Delimiter);
var recordItem = new Dictionary<string, string>();
var i = 0;
foreach (var field in fields)
{
recordItem.Add(i.ToString(), field);
i++;
}
parsedResult.Add(recordItem);
}
這個方法的效果很好的使用範例如下列 office 部門和其銷售數字 ︰
East, 73, 8300
South, 42, 3000
West, 35, 4250
Mid-West, 18, 1200
若要從字串擷取值,您會逐一查看清單,並拉出使用以零為起始的欄位索引字典中的值。擷取 office 部門欄位,比方說,會像這個一樣簡單 ︰
foreach (var record in parsedData)
{
string fieldOffice = record["0"];
}
雖然可以運作,程式碼並不盡的讀取。
更好的字典
許多的 CSV 檔案包含欄位名稱的標頭資料列。剖析器會使用欄位名稱做為字典的索引鍵,如果開發人員更容易。做為任何給定的 CSV 檔案沒有標頭的資料列,您應該將屬性新增至傳達這項資訊 ︰
public bool HasHeaderRow { get; set; }
例如,範例 CSV 檔案的標頭資料列可能看起來像這樣 ︰
Office Division, Employees, Unit Sales
East, 73, 8300
South, 42, 3000
West, 35, 4250
Mid-West, 18, 1200
在理想情況下,CSV 剖析器能夠充分利用此種中繼資料。這會使程式碼更容易閱讀。擷取 office 部門欄位看起來像這樣 ︰
foreach (var record in parsedData)
{
string fieldOffice = record["Office Division"];
}
空白欄位
空白欄位常出現在資料集。CSV 檔案中的空白欄位都會有一筆記錄中的空白欄位。仍然需要分隔符號。例如,如果沒有東 office 沒有員工資料,記錄將如下所示 ︰
East,,8300
如果沒有任何銷售量資料,以及任何員工資料,記錄看起來會像這樣 ︰
East,,
每個組織都有它自己的資料品質標準。一些可能會選擇將空白欄位,讓更易讀的 CSV 檔案中的預設值。預設值通常會是 0 或 NULL 的數字和"」,則為 NULL 的字串。
保持彈性
周圍的 CSV 檔案格式的語意模糊,程式碼不做任何假設。並不保證欄位分隔符號會逗號,則分隔符號會在新行不能保證。
因此,同時將 CSVParser 類別的屬性 ︰
public char Delimiter { get; set; }
public char LineDelimiter { get; set; }
若要方便開發人員使用此元件,您會想要在大部分情況下進行預設設定,會套用 ︰
private const char DEFAULT_DELIMITER = ',';
private const char DEFAULT_LINE_DELIMITER = '\n';
如果有人想要變更預設的分隔符號字元,程式碼是相當簡單 ︰
CsvParser csvParser = new CsvParser();
csvParser.Delimiter = '\t';
逸出的字元
如果欄位本身包含分隔符號字元,像是逗號,會發生什麼事? 比方說,而不是參考依區域銷售時,如果資料有城市與縣市? 一般而言,CSV 檔案解決此問題由 encasing 整個欄位以引號括住,就像這樣 ︰
Office Division, Employees, Unit Sales
"New York, NY", 73, 8300
"Richmond, VA", 42, 3000
"San Jose, CA", 35, 4250
"Chicago, IL", 18, 1200
此演算法會開啟一個欄位的值 「 紐約 」 成兩個不連續的欄位值分割沿著逗號,"New York"和"NY"。
在此情況下,區隔的城市和州值可能不方便,但是有仍額外引號字元而污染的資料。輕鬆地移除這段期間,可能不容易清理更複雜的資料。
現在變得複雜
這個方法的逸出逗號欄位導入了另一個逸出字元 ︰ 將引號字元。如果,比方說,括號中沒有原始資料中所示 [圖 2嗎?
[圖 2 使用引號括住的原始資料
Office 部門 | Employees | 單位銷售量 | Office 座右銘 |
紐約州紐約市 | 73 | 8300 | 「 我們兜售很棒的產品 」 |
裡奇蒙,VA | 42 | 3000 | 「 試用看看您會想要購買 」 |
加州聖荷西市 | 35 | 4250 | 「 加強矽谷 ! 」 |
芝加哥,伊利諾州 | 18 | 1200 | 「 最大值很棒的產品 」 |
CSV 檔案本身中未經處理的文字會看起來像這樣 ︰
Office Division, Employees, Unit Sales, Office Motto
"New York, NY",73,8300,"""We sell great products"""
"Richmond, VA",42,3000,"""Try it and you'll want to buy it"""
"San Jose, CA",35,4250,"""Powering Silicon Valley!"""
"Chicago, IL",18,1200,"""Great products at great value"""
一個引號 (") 取得逸出為三個引號 ("""),這樣會將提供有趣的論點加入演算法。當然,第一個合理的問題是 ︰ 為什麼沒有一個引號開啟分為下列三個? 如同 Office 部門] 欄位中,欄位的內容取得以引號括住。若要逸出內容一部分的引號字元,它們被加倍。因此,「 成為 」 」。
另一個範例 ([圖 3) 可能會更清楚地示範程序。
[圖 3 報價資料
引述 |
唯一我們不必擔心 」 擔心本身。-總裁 Roosevelt |
「 邏輯,可讓您從 A 到 b 想像力將您所有位置。"-愛因斯坦 |
中的資料 [圖 3 csv 檔案中呈現為這個 ︰
引述
"""The only thing we have to fear is fear itself."" -President Roosevelt"
"""Logic will get you from A to B. Imagination will take you everywhere."" -Albert Einstein"
現在,此欄位會包裝在引號和欄位的內容中個別的引號會加倍,則可能是更清楚。
極端案例
如我開啟一節所述,並非所有的檔案會遵守 CSV 的 Excel 實作。CSV,則為 true 規格缺乏難撰寫一個剖析器來處理每個 CSV 檔案中存在。一定會存在的邊緣案例,這表示程式碼會將門開啟解譯和自訂。
反向的救星來控制
提供您標準的 CSV 格式,並不實際寫入所有想像的情況下的完整剖析器。可能是更理想撰寫的剖析器,以符合應用程式的特定需求。使用控制項反轉,可讓您自訂剖析引擎,為特定的需求。
為了達成此目的,我將建立概述剖析的兩個核心功能的介面 ︰ 擷取記錄並擷取欄位。我決定進行非同步 IParserEngine 介面。這可確保任何應用程式使用此元件將保持回應無論 CSV 檔案的大小 ︰
public interface IParserEngine
{
IAsyncOperation<IList<string>> ExtractRecords(char lineDelimiter, string csvText);
IAsyncOperation<IList<string>> ExtractFields(char delimiter, char quote,
string csvLine);
}
然後我 CSVParser 類別新增下列屬性 ︰
public IParserEngine ParserEngine { get; private set; }
我再讓開發人員選擇 ︰ 使用預設剖析器,或插入其本身。為求簡便,我會多載建構函式 ︰
public CsvParser()
{
InitializeFields();
this.ParserEngine = new ParserEngines.DefaultParserEngine();
}
public CsvParser(IParserEngine parserEngine)
{
InitializeFields();
this.ParserEngine = parserEngine;
}
CSVParser 類別現在提供基本的基礎結構,但實際的剖析邏輯內含 IParserEngine 介面。為開發人員方便起見,我建立了 DefaultParserEngine,而其可處理大部分的 CSV 檔案。我作業開發人員將會遇到最可能的情況下需要列入考量。
讀取器的挑戰
我已列入考量的開發人員會使用 CSV 檔案時遇到的案例。不過,無限期的 CSV 格式性質讓您建立通用的剖析器的所有情況下並不實用。將所有的變化和邊緣案例可能會增加顯著的成本和複雜度的成本以及影響效能。
我確信會 」 在現實世界 」 CSV 檔案 DefaultParserEngine 不能處理。這就是相依性插入模式理想。如果開發人員需要剖析器可以處理極端的邊緣案例,或撰寫的程式碼更好的效能,它們確實是歡迎使用執行這項操作。剖析器引擎無法空出,而不需變更要使用的程式碼。
此專案的程式碼位於 bit.ly/1To1IVI。
總結
CSV 檔案第 41 屆天過去了,而且 XML 和 JSON 的努力,仍然是一種常用的資料交換格式。CSV 檔案缺少共同規格或標準,和通常具有下列共同特性時不一定要在任何指定的檔案中的位置。這使得剖析 CSV 檔案耗費練習。
提供的選擇,大部分的開發人員可能排除 CSV 檔案從他們的解決方案。不過,它們在舊版的企業和政府資料集的普遍存在可能杜絕,在許多情況下的選項。
直接放入,就需要通用 Windows 平台 (UWP) 應用程式的 CSV 剖析器和真實世界 CSV 剖析器必須是具彈性且完善。過程中,我示範了這裡提供彈性的相依性插入的實際用途。雖然這個資料行和其相關聯的程式碼的目標 UWP 應用程式,概念和程式碼適用於其他平台能夠執行 C# 中,例如 Microsoft Azure 或 Windows 桌面開發。
Frank La Vigne是 Microsoft 技術和民事參與小組的技術推廣者,可協助使用者充分利用技術,以便建立更好的社群。定期在他的部落格 FranksWorld.com 和已製作成 YouTube 頻道呼叫的 Frank 世界電視 (youtube.com/FranksWorldTV)。
感謝以下技術專家對本文的審閱: Rachel Appel