2016-12-05 47 views
8

Mam problem podczas próby odkodowania danych XML zwróconych przez wystąpienie MS SQL Server 2014 do aplikacji napisanej w D7. (wersja Indy jest tą, która do niej przyszła, 9.00.10).Sposób dekodowania pola blobu XML w D7

Aktualizacja Kiedy pierwotnie napisał ten q, byłem pod wrażeniem, że zawartość pola blob musiała być dekodowane Base64, ale wydaje się, że to było złe. Idąc za sugestią Remy Lebeau, strumień blob zawiera rozpoznawalny tekst w nazwach pól i wartości pól przed dekodowaniem, ale nie później.

W poniższym kodzie SQL w AdoQuery jest po prostu

select * from Autorzy gdzie au_lname = 'White' For XML Auto

autorzy tabeli stanowiącej jeden w demo baza danych pubów. Dodałem klauzulę "Gdzie", aby ograniczyć rozmiar zestawu wyników, aby można było wyświetlić zrzut szesnastkowy zwróconego obiektu typu blob.

Zgodnie z SQL Server OLH, domyślny typ zwracanych danych, gdy określono "Dla XML Auto" jest "binarny format base64". Typem danych pojedynczego pola AdoQuery jest ftBlob, jeśli pozwolę, aby IDE utworzyło to pole.

Wykonanie poniższego kodu generuje wyjątek "Nierówny rozmiar w DecodeToStream". Przy wołaniu do IdDecoderMIME.DecodeToString(S), długość ciągu S wynosi 3514, a 3514 mod 4 to 2, a nie 0, jak powinno być, stąd wyjątek. Potwierdziłem, że liczba bajtów w wartości pola wynosi 3514, więc nie ma różnicy między rozmiarem wariantu a długością łańcucha, tj. Nic nie przeszło pomiędzy.

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; 

Używam tego kodu:

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; 

Heks zrzutu pliku Blob.Txt wygląda to

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..... 

Jak widać, niektóre z nich są czytelne (nazwy pól i zawartość), niektóre z nich nie. Czy ktokolwiek rozpoznaje ten format i wie, jak go wyczyścić do zwykłego tekstu uzyskanego podczas wykonywania tego samego zapytania w SS Management Studio, tj. W jaki sposób mogę wydobyć XML z zestawu wyników?

Btw, otrzymuję ten sam wynik (łącznie z zawartością pliku Blob.Txt) przy użyciu zarówno dostawcy MS OLE DB dla serwera Sql, jak i dostawcy programu Sql Server Native Client 11, i używania Delphi Seattle zamiast D7.

Biorąc pod uwagę, że kod uzyskuje dostęp do zewnętrznej bazy danych, ten kod jest najbliższy do MCVE.

Aktualizacja # 2 Problem znika, jeśli dekodowanie zmienić zapytanie SQL

select Convert(Text, 
(select * from authors where au_lname = 'White' for xml AUTO 
)) 

co daje wynik (w SS) z

<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"/> 

ale ja nadal jestem zainteresowany wiedzieć, jak zmusić to do pracy bez konieczności używania funkcji Convert(). Zauważyłem, że jeśli usunę klauzulę Where z Sql, zwracany jest nie poprawnie sformatowany XML - zawiera on serię węzłów, po jednym na wiersz danych, ale nie ma żadnego otaczającego węzła głównego.

Również zdajemy sobie sprawę, że mogę uniknąć tego problemu, nie używając "Dla XML Auto", interesuje mnie tylko jak to zrobić poprawnie. Nie potrzebuję też żadnej pomocy podczas analizowania pliku XML, gdy już go wyodrębnię.

+0

Czy base64 zawiera znaki wypełniające? Powinno to uczynić z niego nawet wielokrotność liczby 4. Jeśli nie, możesz napisać własne dopełnienie znaków na końcu 'TStringStream', aby jego' Rozmiar' był równym wielokrotnym (chociaż naprawdę powinieneś używać 'TDataSet.CreateBlobStream() 'zamiast' TBlobField.SaveToStream() ', ale wtedy nie można dodać dopełnienia ręcznie). Ewentualnie rozważ uaktualnienie do Indy 10, ponieważ jego 'TIdDecoderMIME' nie odrzuca danych wejściowych, które nie są wielokrotnością 4. –

+0

@RemyLebeau: Thanks. Otrzymuję ten sam wynik za pomocą 'TDataSet.CreateBlobStream'. Po wypróbowaniu ręcznego wypełnienia, które zasugerowałeś, wydaje się, że dekodowanie Base64 nie jest w rzeczywistości konieczne, ponieważ przekształca częściowo czytelny tekst na nieczytelny - patrz zaktualizowany tekst q. – MartynA

Odpowiedz

4

Dodaj TYPE Directive, aby określić, że chcesz zwrócić XML.

+0

Dzięki. Niestety dodanie ", type" do zapytania powoduje, że obiekt AdoQuery1 nie otrzymuje żadnych obiektów pól utworzonych podczas wywołania funkcji 'Open()', więc nie można w ogóle pobrać żadnych danych. Moje obejście przy użyciu 'Konwertuj()' w kwerendzie działa dobrze. – MartynA

+1

@MartynA to robi, jeśli używasz 'SQLOLEDB.1'. Jeśli używasz 'SQLNCLI11.1' musisz ustawić' DataTypeCompatibility = 80' w ciągu połączenia. –

+0

Tak jest! Wynik wciąż nie jest dobrze sformułowanym XML, ale mogę sobie z tym poradzić. – MartynA

2

Nie można po prostu dekodować binarnego obiektu blob na format XML.

Można użyć TADOCommand i kieruje jego strumień wyjściowy do dokumentu XML, np .: obiekt

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; 

Wynika to dobrze uformowane XML z załączając węzeł główny RootNode.