如何在D7中解码XML Blob字段

8
我在尝试将MS SQL Server 2014的XML数据解码并返回给D7应用时遇到了问题。 更新: 当我最初撰写这个问题时,我误以为blob字段的内容需要Base64解码,但事实并非如此。按照Remy Lebeau的建议,解码之前blob流中包含可识别的字段名称和字段值文本,但解码后没有。在下面的代码中,AdoQuery中的SQL只是"Select * from Authors where au_lname = 'White' For XML Auto",Authors表是演示数据库中的一个表。我添加了"Where"子句来限制结果集的大小,以便可以显示返回的blob的十六进制转储。执行以下代码会生成异常"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?
顺便说一下,我使用MS OLE DB Provider for Sql Server和Sql Server Native Client 11 provider,以及使用Delphi Seattle代替D7,都得到了相同的结果(包括Blob.Txt文件的内容)。
考虑到该代码访问外部数据库,这段代码是我能够提供的最小可复现示例(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后解析它的任何帮助。


Base64是否包含填充字符?应该包含,以使其成为4的倍数。如果没有,您可以将自己的填充字符写入到TStringStream的末尾,使其Size成为偶数倍(虽然您真的应该使用TDataSet.CreateBlobStream()而不是TBlobField.SaveToStream(),但这样您就无法手动添加填充)。或者,考虑升级到Indy 10,因为它的TIdDecoderMIME不会拒绝输入,即使它不是4的倍数。 - Remy Lebeau
@RemyLebeau:谢谢。我使用TDataSet.CreateBlobStream也得到了相同的结果。尝试了您建议的手动填充后,似乎Base64解码实际上并不是必要的,因为它将部分可读文本转换为不可读文本-请参见更新的问题文本。 - MartynA
2个回答

4
TYPE指令添加到查询中,以指定返回XML格式的结果。
select * 
from Authors 
where au_lname = 'White' 
for xml auto, type

很遗憾,将“,type”添加到查询中会导致AdoQuery1对象在调用Open()时不会创建任何字段对象,因此无法检索任何数据。我的解决方法是在查询中使用Convert(),这个方法运行良好。 - MartynA
1
@MartynA 如果你使用 SQLOLEDB.1,它就可以了。如果你使用 SQLNCLI11.1,你必须在连接字符串中设置 DataTypeCompatibility=80 - Mikael Eriksson
就是这样!结果仍然不是格式良好的 XML,但我可以处理它。 - MartynA

2

您不能简单地将二进制数据解码为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;

这将生成一个带有封闭根节点RootNode的格式良好的XML文档。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接