123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2019 by the Free Pascal development team
- SQLDB REST bridge : ADO-styled XML input/output
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit sqldbrestado;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, DateUtils, db,fpjson, dom, XMLRead, XMLWrite,sqldbrestschema,sqldbrestio, sqldbrestbridge;
- Type
- { TADOInputStreamer }
- TADOInputStreamer = Class(TRestInputStreamer)
- private
- FDataName: UTF8String;
- FRowName: UTF8String;
- FXML: TXMLDocument;
- FPacket : TDOMElement;
- FData : TDOMElement; // Equals FPacket
- FRow : TDOMElement;
- Protected
- function GetNodeText(N: TDOmNode): UnicodeString;
- Public
- Destructor Destroy; override;
- Class Function GetContentType: String; override;
- Function SelectObject(aIndex : Integer) : Boolean; override;
- function GetContentField(aName: UTF8string): TJSONData; override;
- procedure InitStreaming; override;
- Property XML : TXMLDocument Read FXML;
- Property Packet : TDOMElement Read FPacket;
- Property Data : TDOMElement Read FData;
- Property Row : TDOMElement Read FRow;
- Property DataName : UTF8String Read FDataName Write FDataName;
- Property RowName : UTF8String Read FRowName Write FRowName;
- end;
- { TADOOutputStreamer }
- TADOOutputStreamer = Class(TRestOutputStreamer)
- Private
- FDataName: UTF8String;
- FRowName: UTF8String;
- FXML: TXMLDocument;
- FData : TDOMElement; // Equals FRoot
- FRow: TDOMElement;
- FRoot: TDomElement;
- function CreateXSD: TDomElement;
- Public
- procedure EndData; override;
- procedure EndRow; override;
- procedure FinalizeOutput; override;
- procedure StartData; override;
- procedure StartRow; override;
- // Return Nil for null field.
- function FieldToXML(aPair: TRestFieldPair): TDOMElement; virtual;
- procedure WriteField(aPair: TRestFieldPair); override;
- procedure WriteMetadata(aFieldList: TRestFieldPairArray); override;
- Procedure CreateErrorContent(aCode : Integer; Const aMessage: String); override;
- Property XML : TXMLDocument Read FXML;
- Property Data : TDOMelement Read FData;
- Property Row : TDOMelement Read FRow;
- Public
- Destructor Destroy; override;
- Class Function GetContentType: String; override;
- function RequireMetadata : Boolean; override;
- procedure InitStreaming; override;
- Property DataName : UTF8String Read FDataName Write FDataName;
- Property RowName : UTF8String Read FRowName Write FRowName;
- end;
- implementation
- uses sqldbrestconst;
- { TADOInputStreamer }
- destructor TADOInputStreamer.Destroy;
- begin
- FreeAndNil(FXML);
- inherited Destroy;
- end;
- class function TADOInputStreamer.GetContentType: String;
- begin
- Result:='text/xml';
- end;
- function TADOInputStreamer.SelectObject(aIndex: Integer): Boolean;
- Var
- N : TDomNode;
- NN : UnicodeString;
- begin
- Result:=False;
- NN:=UTF8Decode(RowName);
- N:=FData.FindNode(NN);
- While (aIndex>0) and (N<>Nil) and (N.NodeName<>NN) and (N.NodeType<>ELEMENT_NODE) do
- begin
- N:=N.NextSibling;
- Dec(aIndex);
- end;
- Result:=(aIndex=0) and (N<>Nil);
- If Result then
- FRow:=N as TDomElement
- else
- FRow:=Nil;
- end;
- function TADOInputStreamer.GetNodeText(N: TDOmNode): UnicodeString;
- Var
- V : TDomNode;
- begin
- Result:='';
- V:=N.FirstChild;
- While (V<>Nil) and (V.NodeType<>TEXT_NODE) do
- V:=V.NextSibling;
- If Assigned(V) then
- Result:=V.NodeValue;
- end;
- function TADOInputStreamer.GetContentField(aName: UTF8string): TJSONData;
- Var
- NN : UnicodeString;
- N : TDomNode;
- begin
- NN:=UTF8Decode(aName);
- N:=FRow.FindNode(NN);
- if Assigned(N) and (N.NodeType=ELEMENT_NODE) then
- Result:=TJSONString.Create(UTF8Encode(GetNodeText(N)));
- end;
- procedure TADOInputStreamer.InitStreaming;
- Var
- Msg : String;
- NN : UnicodeString;
- begin
- if DataName='' then
- DataName:='Data';
- if RowName='' then
- RowName:='Row';
- FreeAndNil(FXML);
- if Stream.Size<=0 then
- exit;
- try
- ReadXMLFile(FXML,Stream);
- except
- On E : Exception do
- begin
- Msg:=E.Message;
- FXML:=Nil;
- end;
- end;
- if (FXML=Nil) then
- Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[Msg]);
- FPacket:=FXML.DocumentElement;
- NN:=UTF8Decode(DataName);
- if FPacket.NodeName<>NN then
- Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsInvalidContent),SErrInvalidXMLInput,[SErrMissingDocumentRoot]);
- FData:=FPacket;
- end;
- { TADOOutputStreamer }
- procedure TADOOutputStreamer.EndData;
- begin
- FData:=Nil;
- end;
- procedure TADOOutputStreamer.EndRow;
- begin
- FRow:=Nil;
- end;
- procedure TADOOutputStreamer.FinalizeOutput;
- begin
- {$IFNDEF VER3_0}
- if Not (ooHumanReadable in OutputOptions) then
- begin
- With TDOMWriter.Create(Stream,FXML) do
- try
- LineBreak:='';
- IndentSize:=0;
- WriteNode(FXML);
- finally
- Free;
- end;
- end
- else
- {$ENDIF}
- xmlwrite.WriteXML(FXML,Stream);
- FreeAndNil(FXML);
- end;
- procedure TADOOutputStreamer.StartData;
- begin
- // Rows are straight under the Data packet
- FData:=FRoot;
- end;
- procedure TADOOutputStreamer.StartRow;
- begin
- if (FRow<>Nil) then
- Raise ESQLDBRest.Create(Statuses.GetStatusCode(rsError),SErrDoubleRowStart);
- FRow:=FXML.CreateElement(UTF8Decode(RowName));
- FData.AppendChild(FRow);
- end;
- function TADOOutputStreamer.FieldToXML(aPair: TRestFieldPair): TDOMElement;
- Var
- F : TField;
- S : UTF8String;
- begin
- Result:=Nil;
- F:=aPair.DBField;;
- If (aPair.RestField.FieldType=rftUnknown) then
- raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrUnsupportedRestFieldType, [aPair.RestField.PublicName]);
- If (F.IsNull) then
- Exit;
- S:=FieldToString(aPair.RestField.FieldType,F);
- Result:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
- Result.AppendChild(FXML.CreateTextNode(UTF8Decode(S)));
- end;
- procedure TADOOutputStreamer.WriteField(aPair: TRestFieldPair);
- Var
- D : TDOMElement;
- N : UTF8String;
- begin
- N:=aPair.RestField.PublicName;
- if FRow=Nil then
- Raise ESQLDBRest.CreateFmt(Statuses.GetStatusCode(rsError),SErrFieldWithoutRow,[N]);
- D:=FieldToXML(aPair);
- if (D=Nil) and (not HasOption(ooSparse)) then
- D:=FXML.CreateElement(UTF8Decode(aPair.RestField.PublicName));
- if D<>Nil then
- FRow.AppendChild(D);
- end;
- function TADOOutputStreamer.CreateXSD: TDomElement;
- // Create XSD and append to root. Return element to which field list must be appended.
- Var
- SN,N,E,TLN : TDomElement;
- begin
- SN:=FXML.CreateElement('xs:schema');
- SN['id']:=Utf8Decode(DataName);
- SN['xmlns']:='';
- SN['xmlns:xs']:='http://www.w3.org/2001/XMLSchema';
- SN['xmlns:msdata']:= 'urn:schemas-microsoft-com:xml-msdata';
- FRoot.AppendChild(SN);
- // Add table list with 1 table.
- // Element
- N:=FXML.CreateElement('xs:element');
- SN.AppendChild(N);
- N['name']:=UTF8Decode(DataName);
- N['msdata:IsDataSet']:='true';
- N['msdata:UseCurrentLocale']:='true';
- // element is a complex type
- TLN:=FXML.CreateElement('xs:complexType');
- N.AppendChild(TLN);
- // Complex type is a choice (0..Unbounded] of records
- N:=FXML.CreateElement('xs:choice');
- TLN.AppendChild(N);
- N['minOccurs']:='0';
- N['maxOccurs']:='unbounded';
- // Each record is an element
- E:=FXML.CreateElement('xs:element');
- N.AppendChild(E);
- E['name']:=Utf8Decode(RowName);
- // Record is a complex type of fields
- N:=FXML.CreateElement('xs:complexType');
- E.AppendChild(N);
- // Fields are a sequence. To this sequence, the fields may be appended.
- Result:=FXML.CreateElement('xs:sequence');
- N.AppendChild(Result);
- end;
- Const
- XMLPropTypeNames : Array [TRestFieldType] of string = (
- 'unknown', { rtfUnknown }
- 'xs:int', { rftInteger }
- 'xs:int', { rftLargeInt}
- 'xs:double', { rftFloat }
- 'xs:dateTime', { rftDate }
- 'xs:dateTime', { rftTime }
- 'xs:dateTime', { rftDateTime }
- 'xs:string', { rftString }
- 'xs:boolean', { rftBoolean }
- 'xs:base64Binary' { rftBlob }
- );
- procedure TADOOutputStreamer.WriteMetadata(aFieldList: TRestFieldPairArray);
- Var
- FMetadata : TDOMElement;
- F : TDomElement;
- P : TREstFieldPair;
- I : integer;
- S : Utf8String;
- K : TRestFieldType;
- begin
- FMetadata:=CreateXSD;
- For I:=0 to Length(aFieldList)-1 do
- begin
- P:=aFieldList[i];
- K:=P.RestField.FieldType;
- S:=XMLPropTypeNames[K];
- F:=FXML.CreateElement('xs:element');
- F['name']:=Utf8Decode(P.Restfield.PublicName);
- F['type']:=Utf8decode(S);
- F['minOccurs']:='0';
- FMetaData.AppendChild(F);
- end;
- end;
- class function TADOOutputStreamer.GetContentType: String;
- begin
- Result:='text/xml';
- end;
- function TADOOutputStreamer.RequireMetadata: Boolean;
- begin
- Result:=True;
- end;
- procedure TADOOutputStreamer.CreateErrorContent(aCode: Integer; const aMessage: String);
- Var
- ErrorObj : TDomElement;
- begin
- ErrorObj:=FXML.CreateElement(UTF8Decode(GetString(rpErrorRoot)));
- ErrorObj['code']:=UTF8Decode(IntToStr(aCode));
- ErrorObj['message']:=UTF8Decode(aMessage);
- FRoot.AppendChild(ErrorObj);
- end;
- destructor TADOOutputStreamer.Destroy;
- begin
- FreeAndNil(FXML);
- inherited Destroy;
- end;
- procedure TADOOutputStreamer.InitStreaming;
- begin
- FXML:=TXMLDocument.Create;
- FXML.XMLStandalone:=True;
- if DataName='' then
- DataName:='Data';
- FRoot:=FXML.CreateElement('Data');
- FXML.AppendChild(FRoot);
- if RowName='' then
- RowName:='Row';
- end;
- Initialization
- TADOInputStreamer.RegisterStreamer('ado');
- TADOOutputStreamer.RegisterStreamer('ado');
- end.
|