如何解码 D7 中的 XML Blob 字段
How to Decode XML Blob field in D7
我在尝试解码由 MS SQL Server 2014 实例返回到用 D7 编写的应用程序的 XML 数据时遇到问题。(Indy 的版本是它附带的那个,9.00.10)。
Update 最初写这个q的时候,我的印象是blob字段的内容需要进行Base64解码,但现在看来是错误的。遵循 Remy Lebeau 的建议,blob 流在解码前的字段名称和字段值中包含可识别的文本,但解码后则不然。
在下面的代码中,AdoQuery 中的 SQL 就是
Select * from Authors where au_lname = 'White' For XML Auto
作者 table 是演示 'pubs' 数据库中的作者。我添加了 "Where" 子句来限制结果集的大小,这样我就可以显示返回的 blob 的十六进制转储。
根据Sql服务器OLH,指定'For XML Auto'时返回数据的默认类型是'binary base64-encoded format'。 AdoQuery的单个字段的数据类型是ftBlob,如果我让IDE创建这个字段。
执行下面的代码会产生异常"Uneven size in DecodeToStream"。在调用 IdDecoderMIME.DecodeToString(S)
时,字符串 S 的长度是 3514,而 3514 mod 4 是 2,而不是它显然应该是的 0,因此出现异常。我已经确认字段值中的字节数是 3514,因此变体的大小和字符串的长度之间没有区别,即两者之间没有任何问题。
procedure TForm1.FormCreate(Sender: TObject);
var
SS : TStringStream;
Output : String;
S : String;
IdDecoderMIME : TIdDecoderMIME;
begin
SS := TStringStream.Create('');
IdDecoderMIME := TIdDecoderMIME.Create(Nil);
try
AdoQuery1.Open;
TBlobField(AdoQuery1.Fields[0]).SaveToStream(SS);
S := SS.DataString;
IdDecoderMIME.FillChar := #0;
Output := IdDecoderMIME.DecodeToString(S);
Memo1.Lines.Text := S;
finally
SS.Free;
IdDecoderMIME.Free;
end;
end;
我正在使用此代码:
procedure TForm1.FormCreate(Sender: TObject);
var
SS : TStringStream;
MS : TMemoryStream;
Output : String;
begin
SS := TStringStream.Create('');
MS := TMemoryStream.Create;
try
AdoQuery1.Open;
TBlobField(AdoQuery1.Fields[0]).SaveToStream(SS);
SS.WriteString(#13#10);
Output := SS.DataString;
SS.Position := 0;
MS.CopyFrom(SS, SS.Size);
MS.SaveToFile(ExtractFilePath(Application.ExeName) + 'Blob.txt');
finally
SS.Free;
MS.Free;
end;
end;
Blob.Txt 文件的十六进制转储如下所示
00000000 44 05 61 00 75 00 5F 00 69 00 64 00 44 08 61 00 D.a.u._.i.d.D.a.
00000010 75 00 5F 00 6C 00 6E 00 61 00 6D 00 65 00 44 08 u._.l.n.a.m.e.D.
00000020 61 00 75 00 5F 00 66 00 6E 00 61 00 6D 00 65 00 a.u._.f.n.a.m.e.
00000030 44 05 70 00 68 00 6F 00 6E 00 65 00 44 07 61 00 D.p.h.o.n.e.D.a.
00000040 64 00 64 00 72 00 65 00 73 00 73 00 44 04 63 00 d.d.r.e.s.s.D.c.
00000050 69 00 74 00 79 00 44 05 73 00 74 00 61 00 74 00 i.t.y.D.s.t.a.t.
00000060 65 00 44 03 7A 00 69 00 70 00 44 08 63 00 6F 00 e.D.z.i.p.D.c.o.
00000070 6E 00 74 00 72 00 61 00 63 00 74 00 44 07 61 00 n.t.r.a.c.t.D.a.
00000080 75 00 74 00 68 00 6F 00 72 00 73 00 01 0A 02 01 u.t.h.o.r.s.....
00000090 10 E4 04 00 00 0B 00 31 37 32 2D 33 32 2D 31 31 .......172-32-11
000000A0 37 36 02 02 10 E4 04 00 00 05 00 57 68 69 74 65 76.........White
000000B0 02 03 10 E4 04 00 00 07 00 4A 6F 68 6E 73 6F 6E .........Johnson
000000C0 02 04 0D E4 04 00 00 0C 00 34 30 38 20 34 39 36 .........408 496
000000D0 2D 37 32 32 33 02 05 10 E4 04 00 00 0F 00 31 30 -7223.........10
000000E0 39 33 32 20 42 69 67 67 65 20 52 64 2E 02 06 10 932 Bigge Rd....
000000F0 E4 04 00 00 0A 00 4D 65 6E 6C 6F 20 50 61 72 6B ......Menlo Park
00000100 02 07 0D E4 04 00 00 02 00 43 41 02 08 0D E4 04 .........CA.....
如您所见,其中一些是清晰可辨的(字段名称和内容),一些则不是。有谁知道这种格式并知道如何将其清理成我在 SS Management Studio 中执行相同查询时得到的纯文本,即如何从结果集中成功提取 XML?
顺便说一句,我使用 Sql 服务器的 MS OLE DB 提供程序和 Sql 服务器本机客户端 11 得到了相同的结果(包括 Blob.Txt 文件的内容)提供商,并使用 Delphi 西雅图代替 D7。
鉴于代码访问外部数据库,此代码是我能获得的最接近 MCVE 的代码。
更新 #2 如果我将 Sql 查询更改为
,解码问题就会消失
select Convert(Text,
(select * from authors where au_lname = 'White' for xml AUTO
))
给出
的结果(在SS
中)
<authors au_id="172-32-1176" au_lname="White" au_fname="Johnson" phone="408 496-7223" address="10932 Bigge Rd." city="Menlo Park" state="CA" zip="94025" contract="1"/>
但我仍然很想知道如何在不需要 Convert() 的情况下让它工作。我注意到,如果我从 Sql 中删除 Where 子句,则返回的内容格式不正确 XML - 它包含一系列节点,每个数据行一个,但没有封闭根节点。
此外,我意识到我可以通过不使用 "For XML Auto" 来避免这个问题,我只是想知道如何正确地做到这一点。此外,一旦我成功提取 XML,我就不需要任何帮助来解析它。
添加 TYPE Directive 以指定您希望返回 XML。
select *
from Authors
where au_lname = 'White'
for xml auto, type
您不能简单地将二进制 blob 解码为 XML。
您可以使用 TADOCommand
并将其输出流定向到 XML 文档对象,例如:
const
adExecuteStream = 1024;
var
xmlDoc, RecordsAffected: OleVariant;
cmd: TADOCommand;
xmlDoc := CreateOleObject('MSXML2.DOMDocument.3.0'); // or CoDomDocument30.Create;
xmlDoc.async := False;
cmd := TADOCommand.Create(nil);
// specify your connection string
cmd.ConnectionString := 'Provider=SQLOLEDB;Data Source=(local);...';
cmd.CommandType := cmdText;
cmd.CommandText := 'select top 1 * from items for xml auto';
cmd.Properties['Output Stream'].Value := xmlDoc;
cmd.Properties['XML Root'].Value := 'RootNode';
cmd.CommandObject.Execute(RecordsAffected, EmptyParam, adExecuteStream);
xmlDoc.save('d:\test.xml');
cmd.Free;
这会产生一个格式正确的 XML,其中包含根节点 RootNode
。
我在尝试解码由 MS SQL Server 2014 实例返回到用 D7 编写的应用程序的 XML 数据时遇到问题。(Indy 的版本是它附带的那个,9.00.10)。
Update 最初写这个q的时候,我的印象是blob字段的内容需要进行Base64解码,但现在看来是错误的。遵循 Remy Lebeau 的建议,blob 流在解码前的字段名称和字段值中包含可识别的文本,但解码后则不然。
在下面的代码中,AdoQuery 中的 SQL 就是
Select * from Authors where au_lname = 'White' For XML Auto
作者 table 是演示 'pubs' 数据库中的作者。我添加了 "Where" 子句来限制结果集的大小,这样我就可以显示返回的 blob 的十六进制转储。
根据Sql服务器OLH,指定'For XML Auto'时返回数据的默认类型是'binary base64-encoded format'。 AdoQuery的单个字段的数据类型是ftBlob,如果我让IDE创建这个字段。
执行下面的代码会产生异常"Uneven size in DecodeToStream"。在调用 IdDecoderMIME.DecodeToString(S)
时,字符串 S 的长度是 3514,而 3514 mod 4 是 2,而不是它显然应该是的 0,因此出现异常。我已经确认字段值中的字节数是 3514,因此变体的大小和字符串的长度之间没有区别,即两者之间没有任何问题。
procedure TForm1.FormCreate(Sender: TObject);
var
SS : TStringStream;
Output : String;
S : String;
IdDecoderMIME : TIdDecoderMIME;
begin
SS := TStringStream.Create('');
IdDecoderMIME := TIdDecoderMIME.Create(Nil);
try
AdoQuery1.Open;
TBlobField(AdoQuery1.Fields[0]).SaveToStream(SS);
S := SS.DataString;
IdDecoderMIME.FillChar := #0;
Output := IdDecoderMIME.DecodeToString(S);
Memo1.Lines.Text := S;
finally
SS.Free;
IdDecoderMIME.Free;
end;
end;
我正在使用此代码:
procedure TForm1.FormCreate(Sender: TObject);
var
SS : TStringStream;
MS : TMemoryStream;
Output : String;
begin
SS := TStringStream.Create('');
MS := TMemoryStream.Create;
try
AdoQuery1.Open;
TBlobField(AdoQuery1.Fields[0]).SaveToStream(SS);
SS.WriteString(#13#10);
Output := SS.DataString;
SS.Position := 0;
MS.CopyFrom(SS, SS.Size);
MS.SaveToFile(ExtractFilePath(Application.ExeName) + 'Blob.txt');
finally
SS.Free;
MS.Free;
end;
end;
Blob.Txt 文件的十六进制转储如下所示
00000000 44 05 61 00 75 00 5F 00 69 00 64 00 44 08 61 00 D.a.u._.i.d.D.a.
00000010 75 00 5F 00 6C 00 6E 00 61 00 6D 00 65 00 44 08 u._.l.n.a.m.e.D.
00000020 61 00 75 00 5F 00 66 00 6E 00 61 00 6D 00 65 00 a.u._.f.n.a.m.e.
00000030 44 05 70 00 68 00 6F 00 6E 00 65 00 44 07 61 00 D.p.h.o.n.e.D.a.
00000040 64 00 64 00 72 00 65 00 73 00 73 00 44 04 63 00 d.d.r.e.s.s.D.c.
00000050 69 00 74 00 79 00 44 05 73 00 74 00 61 00 74 00 i.t.y.D.s.t.a.t.
00000060 65 00 44 03 7A 00 69 00 70 00 44 08 63 00 6F 00 e.D.z.i.p.D.c.o.
00000070 6E 00 74 00 72 00 61 00 63 00 74 00 44 07 61 00 n.t.r.a.c.t.D.a.
00000080 75 00 74 00 68 00 6F 00 72 00 73 00 01 0A 02 01 u.t.h.o.r.s.....
00000090 10 E4 04 00 00 0B 00 31 37 32 2D 33 32 2D 31 31 .......172-32-11
000000A0 37 36 02 02 10 E4 04 00 00 05 00 57 68 69 74 65 76.........White
000000B0 02 03 10 E4 04 00 00 07 00 4A 6F 68 6E 73 6F 6E .........Johnson
000000C0 02 04 0D E4 04 00 00 0C 00 34 30 38 20 34 39 36 .........408 496
000000D0 2D 37 32 32 33 02 05 10 E4 04 00 00 0F 00 31 30 -7223.........10
000000E0 39 33 32 20 42 69 67 67 65 20 52 64 2E 02 06 10 932 Bigge Rd....
000000F0 E4 04 00 00 0A 00 4D 65 6E 6C 6F 20 50 61 72 6B ......Menlo Park
00000100 02 07 0D E4 04 00 00 02 00 43 41 02 08 0D E4 04 .........CA.....
如您所见,其中一些是清晰可辨的(字段名称和内容),一些则不是。有谁知道这种格式并知道如何将其清理成我在 SS Management Studio 中执行相同查询时得到的纯文本,即如何从结果集中成功提取 XML?
顺便说一句,我使用 Sql 服务器的 MS OLE DB 提供程序和 Sql 服务器本机客户端 11 得到了相同的结果(包括 Blob.Txt 文件的内容)提供商,并使用 Delphi 西雅图代替 D7。
鉴于代码访问外部数据库,此代码是我能获得的最接近 MCVE 的代码。
更新 #2 如果我将 Sql 查询更改为
,解码问题就会消失select Convert(Text,
(select * from authors where au_lname = 'White' for xml AUTO
))
给出
的结果(在SS
中)
<authors au_id="172-32-1176" au_lname="White" au_fname="Johnson" phone="408 496-7223" address="10932 Bigge Rd." city="Menlo Park" state="CA" zip="94025" contract="1"/>
但我仍然很想知道如何在不需要 Convert() 的情况下让它工作。我注意到,如果我从 Sql 中删除 Where 子句,则返回的内容格式不正确 XML - 它包含一系列节点,每个数据行一个,但没有封闭根节点。
此外,我意识到我可以通过不使用 "For XML Auto" 来避免这个问题,我只是想知道如何正确地做到这一点。此外,一旦我成功提取 XML,我就不需要任何帮助来解析它。
添加 TYPE Directive 以指定您希望返回 XML。
select *
from Authors
where au_lname = 'White'
for xml auto, type
您不能简单地将二进制 blob 解码为 XML。
您可以使用 TADOCommand
并将其输出流定向到 XML 文档对象,例如:
const
adExecuteStream = 1024;
var
xmlDoc, RecordsAffected: OleVariant;
cmd: TADOCommand;
xmlDoc := CreateOleObject('MSXML2.DOMDocument.3.0'); // or CoDomDocument30.Create;
xmlDoc.async := False;
cmd := TADOCommand.Create(nil);
// specify your connection string
cmd.ConnectionString := 'Provider=SQLOLEDB;Data Source=(local);...';
cmd.CommandType := cmdText;
cmd.CommandText := 'select top 1 * from items for xml auto';
cmd.Properties['Output Stream'].Value := xmlDoc;
cmd.Properties['XML Root'].Value := 'RootNode';
cmd.CommandObject.Execute(RecordsAffected, EmptyParam, adExecuteStream);
xmlDoc.save('d:\test.xml');
cmd.Free;
这会产生一个格式正确的 XML,其中包含根节点 RootNode
。