通过 ADO 访问结构化文本文件

供新手入门和老手参考的教程和相关资料,包括中文帮助

Moderators: tmplinshi, arcticir

Post Reply
User avatar
amnesiac
Posts: 186
Joined: 22 Nov 2013, 03:08
Location: Egret Island, China
Contact:

通过 ADO 访问结构化文本文件

Post by amnesiac » 20 Aug 2014, 21:42

注:本文改写自微软知识库文章,待找到源网址后补上。

导言:本文将讲述如何使用 ADO 访问定界和等宽的文本文件,以及为什么这么做会更好。

何谓定界文件
定界文件是指包含由标准的字符分隔开的每个值的文本文件。例如包含下面这样姓、名和中间的首字母组成的文本:
LastName,FirstName,MiddleInitial
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 文件中包含的每个字段的值:

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 查询,那么会明白这里输入文件名的位置一般是输入表名的。
注意了上面这几点后,其他就与一般的 ADO 脚本类似了。接下来使用循环来操作相应字段的值。
注意:您也许注意到这里只讨论读取文本文件而没有说明写入文本文件,我们这里使用的 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
    }
  }
}
这种方法应该是把 CSV 文本文件解析成每个字段的最简单的方法了,问题在哪里呢?在一些简单的情况下它工作的很好。但在要求比较高的情况下这种方法就不行了,这里与前面使用 ADO 的方法进行简单的比较:
  • 过滤困难。在 ADO 中有一个很好用的功能是在查询时进行过滤非常方便,通过字段名进行列过滤,通过字段值进行行过滤。例如“Select * From Logfile Where Comment = 'Application started'”,这样可以直接获取 Comment 等于 “Application started” 的那些记录。如果使用内部命令实现相同的功能,那么只能在循环中每次都检查 Comment 字段的值,这样代码看起来就繁琐多了。
  • 排序、聚合困难。与过滤类似,这些也是在 SQL 查询中可以很方便实现的功能,使用内部命令会困难的多。
代码中的字符串解析循环也许您会替换成 StringSplit?为什么不呢?有两个原因:
  • 第一,如果 CSV 文件的某些字段内部包含逗号时使用 StringSplit 会产生问题,并且它无法智能处理每个字段周围的引号。这些问题不能绕弯解决吗?可以,然而使用 ADO 会更好。
  • 第二,使用 ADO 具有更大的灵活性。假设我们只需要提取其中的个别字段的值,使用 StringSplit 可以完成,但需要不少的代码来排除我们不需要的字段。而数据库查询提取想要的值和提取所有值一样简单。
因此,ADO 的好处是可以平滑处理 CSV 文件每个字段周围的逗号和括在每个值周围的引号,并且可以通过 SQL 查询很方便地对文本文件的信息进行过滤和统计等。

何谓标题行
标题行的含义是文本文件的第一行是字段名列表,后面的行包含了真正的数据。例如:
LastName,FirstName,MiddleInitial
Myer,Ken,W
Poe, Deborah,L
大部分 CSV 文件含有标题行,但不是所有都这样。例如:
Myer,Ken,W
Poe, Deborah,L
这样就出现一个问题,如何指定这些没有名称的字段呢?对于记录集中的字段值可以通过索引引用,但 SQL 查询时应该如何?
最简单的方法是创建 Schema.ini 文件,在其中使用类似下面的语法指定字段名:
Col1=DateTime Text Col2=PID Long Col3=Comment Text
注意,您需要在字段名后面指定数据类型(这里的 Text 是指字符串类型)。在 Schema.ini 文件中可以指定的数据类型包括:
  • Short
  • Long
  • Currency
  • Single
  • Double
  • DateTime
  • Memo
  • Text
创建了 Schema.ini 文件后,需要把它放在与目标文本文件相同的目录。并在脚本中告诉 ADO 目标文本文件不包含标题行(即设置 HDR=No):

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 支持下列分隔符:

Code: Select all

格式	Schema.ini 语法	说明
CSV 分隔	Format = CSVDelimited	文件中各字段通过逗号分隔(注意在逗号和下一个字段名或值的开始处不应该含有空格)。
Tab 分隔	Format = TabDelimited	文件中各个字段通过 Tab 分隔。
自定义分隔符	Format = Delimited (x) 其中 x 为分隔符。如果您使用星号作为分隔符,则使用:Format = Delimited(*) 	文件中的各个字段是通过逗号或 tab 外的其他字符分隔的(有一个例外是您不能使用双引号作为分隔符)。
等宽	Format=FixedLength	文件中的各个字段占用特定数目的字符。如果某个值过长,则多余的字符会被截去,如果过短,则附加空格到相应的长度。
那么具体如何使用 ADO 读取使用其他分隔符的文本文件呢?有两种选择:使用 Schema.ini 文件,或在临时修改注册表。

创建 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( )
这个例子中,为第三个文件指定的分隔符是空格(之前您也许会觉得忘记输入什么了)。
Schema.ini 文件会覆盖注册表设置,所以如果使用这个文件,则不需要关心注册表的设置如何。

修改注册表
如果 Schema.ini 文件不存在,则 ADO 会使用下列注册表设置来解析文本文件:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text]
"Format"="CSVDelimited"
Format 的值默认为 CSVDelimited,这是为什么在本文开始的脚本中没有设置可以直接处理 CSV 文件的原因。
如果要处理 tab 分隔的文本文件,那么只需把 Format 的值改为 TabDelimited,这时 ADO 就会把文本文件视为 tab 分隔的文本文件进行处理了。使用其他分隔符时请参照前面的表对这个值进行相应的修改,需要注意,如果分隔符设置不正确则处理时将出现错误。
手动修改注册表的过程比较麻烦,容易出错,所以建议在脚本中进行。例如假设需要保持注册表中的默认值 CSVDelimited,但在某个脚本中需要读取 tab 分隔的文件,那么可以在操作前加上修改注册表的代码:

Code: Select all

RegWrite, REG_SZ, HKLM, SOFTWARE\Microsoft\Jet\4.0\Engines\Text, Format, TabDelimited
当文件处理完成后,执行下列代码把 Format 恢复为 CSVDelimited:

Code: Select all

RegWrite, REG_SZ, HKLM, SOFTWARE\Microsoft\Jet\4.0\Engines\Text, Format, CSVDelimited
很简单,是吧?需要说明,只有在处理定界文本文件时才能通过修改注册表实现,对于等宽文本文件,只能使用 Schema.ini 文件。
AutoHotkey 学习指南(Beauty of AutoHotkey)
I do not make codes, and only a porter of AutoHotkey: from official to Chinese, from other languages to AutoHotkey, and show AutoHotkey to ordinary users sometimes.

d-xn
Posts: 1
Joined: 26 Jul 2020, 16:46

Re: 通过 ADO 访问结构化文本文件

Post by d-xn » 27 Jul 2020, 16:49

感谢楼主分享,尝试了一下代码,连接数据库的时候会有80004005未指定错误。
所以感觉不是那么简单,有机会Connection的Provider字符串还得再研究一下。

Post Reply

Return to “教程资料”