导言:本文将讲述如何使用 ADO 访问定界和等宽的文本文件,以及为什么这么做会更好。
何谓定界文件
定界文件是指包含由标准的字符分隔开的每个值的文本文件。例如包含下面这样姓、名和中间的首字母组成的文本:
LastName,FirstName,MiddleInitial
Myer,Ken,W
Poe,Deborah,L
如果值里面包含分隔符,则必须把整个值括在引号中,例如:
Myer,Ken,W
Poe,Deborah,L
105,"cn=Ken Myer,ou=Accounting,ou=North America,dc=fabrikam,dc=com","Fiscal Specialist"
这里第二个值由逗号分隔的多个部分组成,其中包含的逗号应视为值的一部分而不是分隔符。在实际使用时建议每个值都括在双引号中,不论它们是否含有逗号,例如:
"Myer","Ken","W"
刚才所介绍的使用逗号作为分隔符的文件被称为CSV 文件(逗号分隔值文件),是常见的定界文件类型,在一些时候还会遇到使用 tab 或其他字符作为分隔符的文本文件。处理 CSV 文件
现在假设要处理包含下列结构的 CSV 文件:
"DateTime","PID","Comment"
"5/28/2010 13:56:12","2956","Application started"
"5/28/2010 13:59:02","2956","Waiting for input"
"5/28/2010 14:12:45","3104","Application started"
通过下面的代码即可以显示出这个 CSV 文件中包含的每个字段的值:
"5/28/2010 13:56:12","2956","Application started"
"5/28/2010 13:59:02","2956","Waiting for input"
"5/28/2010 14:12:45","3104","Application started"
Code: Select all
adOpenStatic := 3
adLockOptimistic := 3
adCmdText := 0x0001
objConnection := ComObjCreate("ADODB.Connection")
objRecordSet := ComObjCreate("ADODB.Recordset")
strPathtoTextFile := "C:\Databases\"
objConnection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" . strPathtoTextFile . ";Extended Properties=""text;HDR=YES;FMT=Delimited""")
objRecordset.Open("SELECT * FROM PrscessEvent.csv", objConnection, adOpenStatic, adLockOptimistic, adCmdText)
while, !objRecordset.EOF
{
MsgBox, % "DateTime: " . objRecordset.Fields.Item("DateTime").Value
. "PID: " objRecordset.Fields.Item("PID").Value
. "Comment: " . objRecordset.Fields.Item("Comment").Value
objRecordset.MoveNext
}
- 首先,在脚本的开始处除了 adOpenStatic 和 adLockOptimistic 外,这里还定义了 adCmdText 常量。它是仅在操作文本文件时使用的常量。
- 接着,在连接字符串的 Data Source 参数中指定保存文本文件的路径的名称,注意必须以反斜线结尾,在这个例子中为 C:\Databases\。
- 再次,在连接字符串中增加 Extended Properties 参数,Extended Properties=""text;HDR=YES;FMT=Delimited"" 中首先指明了目标是文本文件,接着说明文件含有标题行(即首行为标题行,这些标题之后可以作为字段名来引用字段),最后告诉 ADO 这是定界文件。根据待处理文件的性质,这里可能需要进行细微的修改。
- 最后,在 SQL 查询中指定文本文件的名称,记住这里不需要包含路径(之前在数据源中已经指定了)。如果您熟悉 SQL 查询,那么会明白这里输入文件名的位置一般是输入表名的。
注意:您也许注意到这里只讨论读取文本文件而没有说明写入文本文件,我们这里使用的 ODBC Text Driver 只能从文件读取而无法写入,如果需要写入则可以使用 FileAppend 或类似的方法。
为什么不使用内部命令
如果您在脚本中操作过文本文件,那么很可能使用的是内部命令,例如 FileRead/FileAppend/文件读取循环。那么在上面的操作中为什么不使用内部命令呢?如果使用内部命令,那么可以写出类似下面的代码:
Code: Select all
Loop, Read, C:\Databases\PrscessEvent.csv
{
Loop, Parse, A_LoopReadLine, CSV
{
If (A_Index = 1)
sRecord := "DateTime: " . A_LoopField
If (A_Index = 2)
sRecord .= "PID: " A_LoopField
If (A_Index = 3)
{
sRecord .= "Comment: " . A_LoopField
MsgBox, % sRecord
}
}
}
- 过滤困难。在 ADO 中有一个很好用的功能是在查询时进行过滤非常方便,通过字段名进行列过滤,通过字段值进行行过滤。例如“Select * From Logfile Where Comment = 'Application started'”,这样可以直接获取 Comment 等于 “Application started” 的那些记录。如果使用内部命令实现相同的功能,那么只能在循环中每次都检查 Comment 字段的值,这样代码看起来就繁琐多了。
- 排序、聚合困难。与过滤类似,这些也是在 SQL 查询中可以很方便实现的功能,使用内部命令会困难的多。
- 第一,如果 CSV 文件的某些字段内部包含逗号时使用 StringSplit 会产生问题,并且它无法智能处理每个字段周围的引号。这些问题不能绕弯解决吗?可以,然而使用 ADO 会更好。
- 第二,使用 ADO 具有更大的灵活性。假设我们只需要提取其中的个别字段的值,使用 StringSplit 可以完成,但需要不少的代码来排除我们不需要的字段。而数据库查询提取想要的值和提取所有值一样简单。
何谓标题行
标题行的含义是文本文件的第一行是字段名列表,后面的行包含了真正的数据。例如:
LastName,FirstName,MiddleInitial
Myer,Ken,W
Poe, Deborah,L
大部分 CSV 文件含有标题行,但不是所有都这样。例如:
Myer,Ken,W
Poe, Deborah,L
Myer,Ken,W
Poe, Deborah,L
这样就出现一个问题,如何指定这些没有名称的字段呢?对于记录集中的字段值可以通过索引引用,但 SQL 查询时应该如何?Poe, Deborah,L
最简单的方法是创建 Schema.ini 文件,在其中使用类似下面的语法指定字段名:
Col1=DateTime Text Col2=PID Long Col3=Comment Text
注意,您需要在字段名后面指定数据类型(这里的 Text 是指字符串类型)。在 Schema.ini 文件中可以指定的数据类型包括:
- Short
- Long
- Currency
- Single
- Double
- DateTime
- Memo
- Text
Code: Select all
objConnection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" . strPathtoTextFile . ";Extended Properties=""text;HDR=No;FMT=Delimited""")
尽管逗号是最常用的分隔符,但不是唯一的一个。许多文件使用 tab 分隔各个字段,而有些文件使用空格分隔各个字段。下面这个例子中使用空格作为分隔符:
2004-01-05 04:37:38 W3SVC802775961 192.168.195.28 GET /personal/index.html
2004-01-05 04:37:38 W3SVC802775961 192.168.195.28 GET /personal/navcond.js
ADO 可以处理吗?实际上 ODBC Text Driver 支持下列分隔符:
2004-01-05 04:37:38 W3SVC802775961 192.168.195.28 GET /personal/navcond.js
Code: Select all
格式 Schema.ini 语法 说明
CSV 分隔 Format = CSVDelimited 文件中各字段通过逗号分隔(注意在逗号和下一个字段名或值的开始处不应该含有空格)。
Tab 分隔 Format = TabDelimited 文件中各个字段通过 Tab 分隔。
自定义分隔符 Format = Delimited (x) 其中 x 为分隔符。如果您使用星号作为分隔符,则使用:Format = Delimited(*) 文件中的各个字段是通过逗号或 tab 外的其他字符分隔的(有一个例外是您不能使用双引号作为分隔符)。
等宽 Format=FixedLength 文件中的各个字段占用特定数目的字符。如果某个值过长,则多余的字符会被截去,如果过短,则附加空格到相应的长度。
创建 Schema.ini 文件
Schema.ini 是用来告诉 ADO 如何处理您的数据文件的文本文件。对于处理定界文件,这个文件只是告诉 ADO 它的分隔符是什么。假设您有一个日志文件 MyLog.txt,这个文件的各个字段间使用 tab 分隔,那么您只需要在 Schema.ini 文件中包含下列内容 ADO 就知道如何处理这个文件了:
[MyLog.txt] Format=TabDelimited
没错,只是括在方括号中的文件名和格式类型。注意这里只是指定了文件名而不是文件的绝对路径,因为 Schema.ini 文件必须与目标文本文件放在相同的目录。这样就产生另一个问题:如果在同一个目录中含有两个或更多需要处理的文本文件呢?没关系,我们可以把多个需要处理的文本文件放在一个目录中,此时我们唯一需要做的是在 Schema.ini 文件中指定每个文本文件的信息。例如下面是为同一个目录中三个使用不同分隔符的文本文件的 Schema.ini 文件内容:
[File_1.txt] Format=CSVDelimited
[File_2.txt] Format=TabDelimited
[File_3.txt] Format=Delimited( )
这个例子中,为第三个文件指定的分隔符是空格(之前您也许会觉得忘记输入什么了)。[File_2.txt] Format=TabDelimited
[File_3.txt] Format=Delimited( )
Schema.ini 文件会覆盖注册表设置,所以如果使用这个文件,则不需要关心注册表的设置如何。
修改注册表
如果 Schema.ini 文件不存在,则 ADO 会使用下列注册表设置来解析文本文件:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text]
"Format"="CSVDelimited"
Format 的值默认为 CSVDelimited,这是为什么在本文开始的脚本中没有设置可以直接处理 CSV 文件的原因。"Format"="CSVDelimited"
如果要处理 tab 分隔的文本文件,那么只需把 Format 的值改为 TabDelimited,这时 ADO 就会把文本文件视为 tab 分隔的文本文件进行处理了。使用其他分隔符时请参照前面的表对这个值进行相应的修改,需要注意,如果分隔符设置不正确则处理时将出现错误。
手动修改注册表的过程比较麻烦,容易出错,所以建议在脚本中进行。例如假设需要保持注册表中的默认值 CSVDelimited,但在某个脚本中需要读取 tab 分隔的文件,那么可以在操作前加上修改注册表的代码:
Code: Select all
RegWrite, REG_SZ, HKLM, SOFTWARE\Microsoft\Jet\4.0\Engines\Text, Format, TabDelimited
Code: Select all
RegWrite, REG_SZ, HKLM, SOFTWARE\Microsoft\Jet\4.0\Engines\Text, Format, CSVDelimited