在几个月前的某个项目中,需要收集、验证和处理来自各种格式的数据文件。由于数据文件的验证/处理需求会随着时间频繁变化,并且需要标准化处理格式,决定使用.NET兼容的XML格式,并随后使用XSLT来处理/重新格式化数据。一些文件格式(如Excel电子表格和Microsoft Access数据库)可以自动转换为这种文件格式,但许多其他文件格式(如制表符分隔、逗号分隔、管道分隔的文本文件)需要重新处理为.NET兼容的XML格式。本文介绍的主要类和这个工具就是这项工作的结果。
AnyDataFileToXmlConverter工具使用起来快速且简单——只需打开/加载文件,文件就会自动处理为.NETXML格式。对于Excel电子表格,工作簿中的工作表会被枚举,并且必须选择一个工作表进行处理为XML。在Microsoft Access数据库中,必须在处理之前指定要转换为XML的特定数据的查询。
工具提供了几个示例数据文件,展示了可以重新处理为XML的各种常见数据文件格式。还可以处理额外的文件格式(如管道分隔文件),并且类可以轻松修改以处理额外的格式。XML文件可以加载到工具中,但由于它们已经是最终结果格式,因此不执行任何处理。
还有一个可选的数据清理功能,可以自动删除处理包含空行或清除行的Excel电子表格时通常创建的任何“垃圾”XML节点。还可以将输出/结果显示从XML更改为网格,以便于查看和排序。
逗号分隔文件、制表符分隔文件、Excel电子表格、Microsoft Access数据库。
AnyDataFileToXmlConverter工具的主要引擎/处理器是RawFileConverter类,它使用加载文件的文件扩展名来确定使用哪个处理功能来重新处理文件。对于大多数文件格式(文本文件除外),使用Microsoft.Jet.OLEDB提供程序自动将文件加载到DataSet中,然后从DataSet中检索XML。
对于文本文件,文件内容被评估以找到数据列之间的可能字符分隔符,然后根据字符分隔符将文件的每一行分割成列。创建一个DataTable,然后加载文件中的所有数据,文件中的每一行被转换为DataRow,每个字段加载到该DataRow的列中。一旦整个文件被处理到DataTable中,就从DataTable的DataSet对象中检索XML。
private static XmlDocument ConvertTextFile(string sFilePath)
{
XmlDocument xmlRaw = null;
StreamReader oSR = null;
try
{
DataSet dsTextFile = new DataSet();
DataTable dtTextFile = new DataTable();
DataRow drRows = null;
// check and pre-process the text file if it's a non-standard text file
sFilePath = PreprocessNonStandardFiles(sFilePath);
// find the correct delimiter for the file
// (some files have multiple delimiting chars, but only one is correct)
char chrDelimiter = GetDelimiterCharacter(sFilePath);
// Open the file and go to the top of the file
oSR = new StreamReader(sFilePath);
oSR.BaseStream.Seek(0, SeekOrigin.Begin);
// read the first line
string sFirstLine = oSR.ReadLine();
bool bHeaderIsDataRow = false;
// init the columns if the file has a valid, parsible header
string[] sColumns = sFirstLine.Split(chrDelimiter);
if (sColumns.Length > MINIMUM_NUMBER_CSV_COLUMNS) {
bHeaderIsDataRow = InitializeTableColumns(sColumns, ref dtTextFile, true);
if (bHeaderIsDataRow == true) {
oSR.BaseStream.Seek(0, SeekOrigin.Begin);
oSR.Close();
oSR = new StreamReader(sFilePath);
oSR.BaseStream.Seek(0, SeekOrigin.Begin);
}
}
// add in the Rows for the datatable/file
dsTextFile.DataSetName = "NewDataSet";
dsTextFile.Tables.Clear();
dtTextFile.TableName = "Table";
dsTextFile.Tables.Add(dtTextFile);
// iterate thru the file and process each line
while (oSR.Peek() > -1) {
int iFieldIndex = 0;
string sLine = oSR.ReadLine();
string sLineTrimmed = sLine.Trim();
string[] sLineFields = sLine.Split(chrDelimiter);
if ((sLineFields.Length <= 0) || (sLineTrimmed.Length < MINIMUM_NUMBER_CSV_COLUMNS)) {
continue;
}
// if the number of fields is less that the minimum, skip the field
if (sLineFields.Length <= MINIMUM_NUMBER_CSV_COLUMNS) {
continue;
}
// if we suddenly have more fields than columns,
// we're in a header or something, so re-init the columns
if ((sLineFields.Length > dtTextFile.Columns.Count) && (sLineFields.Length > MINIMUM_NUMBER_CSV_COLUMNS)) {
// note: bad data?! - header/inconsistent delimiting problems?
if (dtTextFile.Rows.Count <= 0) {
InitializeTableColumns(sLineFields, ref dtTextFile, false);
}
}
drRows = dtTextFile.NewRow();
foreach (string strField in sLineFields) {
string sField = strField.Trim();
sField = sField.Replace("\"", "");
sField = sField.Replace("'", "");
sField = sField.Replace("$", "");
sField = sField.Replace("%", "");
sField = sField.Replace("-0-", "0");
sField = sField.Replace("&", "and");
// header/inconsistent file delimiting problems?
if (dtTextFile.Columns.Count <= iFieldIndex)
break;
drRows[iFieldIndex] = sField;
iFieldIndex = iFieldIndex + 1;
}
dtTextFile.Rows.Add(drRows);
}
// load the dataset to an xmldocument
xmlRaw = CleanupRawXml(dsTextFile.GetXml());
}
catch (Exception ex) {
throw new Exception("Error: ConvertTextFile", ex);
}
finally {
oSR.Close();
}
return xmlRaw;
}