123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 1999-2008 by Joost van der Sluis, member of the
- Free Pascal development team
- TXMLDatapacketReader implementation
- 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 XMLDatapacketReader;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, Bufdataset, dom, db;
- type
- TChangeLogEntry = record
- UpdateKind : TUpdateKind;
- OrigEntry : integer;
- NewEntry : integer;
- end;
- TChangeLogEntryArr = array of TChangeLogEntry;
- type
- { TXMLDatapacketReader }
- TXMLDatapacketReader = class(TDataPacketReader)
- XMLDocument : TXMLDocument;
- DataPacketNode : TDOMElement;
- MetaDataNode : TDOMNode;
- FieldsNode : TDOMNode;
- FChangeLogNode,
- FParamsNode,
- FRowDataNode,
- FRecordNode : TDOMNode;
- FChangeLog : TChangeLogEntryArr;
- FEntryNr : integer;
- FLastChange : integer;
- public
- destructor destroy; override;
- procedure StoreFieldDefs(AnAutoIncValue : integer); override;
- procedure StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0); override;
- procedure FinalizeStoreRecords; override;
- procedure LoadFieldDefs(var AnAutoIncValue : integer); override;
- procedure InitLoadRecords; override;
- function GetCurrentRecord : boolean; override;
- function GetRecordRowState(out AUpdOrder : Integer) : TRowState; override;
- procedure RestoreRecord; override;
- procedure GotoNextRecord; override;
- class function RecognizeStream(AStream : TStream) : boolean; override;
- end;
- implementation
- uses xmlwrite, xmlread, base64;
- const
- XMLFieldtypenames : Array [TFieldType] of String[16] =
- (
- 'Unknown',
- 'string',
- 'i2',
- 'i4',
- 'i4',
- 'boolean',
- 'r8',
- 'r8:Money',
- 'fixed',
- 'date',
- 'time',
- 'datetime',
- 'bin.hex',
- 'bin.hex',
- 'i4:Autoinc',
- 'bin.hex:Binary',
- 'bin.hex:Text',
- 'bin.hex:Graphics',
- 'bin.hex:Formatted',
- 'bin.hex:Ole',
- 'bin.hex:Ole',
- 'bin.hex:Graphics',
- '',
- 'string', // ftFixedChar
- 'string.uni', // ftWideString
- 'i8',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- 'string:Guid', // ftGuid
- '',
- 'fixedFMT', // ftFmtBCD
- 'string.uni', // ftFixedWideChar
- 'bin.hex:WideText', // ftWideMemo
- '', // ftOraTimeStamp
- '', // ftOraInterval
- '', // ftLongWord
- '', // ftShortint
- '', // ftByte
- '' // ftExtended
- );
- resourcestring
- sUnknownXMLDatasetFormat = 'Unknown XML Dataset format';
- { TXMLDatapacketReader }
- destructor TXMLDatapacketReader.destroy;
- begin
- FieldsNode.Free;
- MetaDataNode.Free;
- DataPacketNode.Free;
- XMLDocument.Free;
- inherited destroy;
- end;
- procedure TXMLDatapacketReader.LoadFieldDefs(var AnAutoIncValue: integer);
- function GetNodeAttribute(const aNode : TDOMNode; AttName : String) : string;
- var AnAttr : TDomNode;
- begin
- AnAttr := ANode.Attributes.GetNamedItem(AttName);
- if assigned(AnAttr) then result := AnAttr.NodeValue
- else result := '';
- end;
- var i,s : integer;
- AFieldDef : TFieldDef;
- iFieldType : TFieldType;
- FTString : string;
- SubFTString : string;
- AFieldNode : TDOMNode;
- AnAutoIncNode: TDomNode;
- begin
- ReadXMLFile(XMLDocument,Stream);
- DataPacketNode := XMLDocument.FindNode('DATAPACKET') as TDOMElement;
- if not assigned(DataPacketNode) then DatabaseError(sUnknownXMLDatasetFormat);
- MetaDataNode := DataPacketNode.FindNode('METADATA');
- if not assigned(MetaDataNode) then DatabaseError(sUnknownXMLDatasetFormat);
- FieldsNode := MetaDataNode.FindNode('FIELDS');
- if not assigned(FieldsNode) then DatabaseError(sUnknownXMLDatasetFormat);
- with FieldsNode.ChildNodes do for i := 0 to Count - 1 do
- begin
- AFieldNode := item[i];
- if AFieldNode.CompareName('FIELD')=0 then
- begin
- AFieldDef := Dataset.FieldDefs.AddFieldDef;
- AFieldDef.DisplayName:=GetNodeAttribute(AFieldNode,'fieldname');
- AFieldDef.Name:=GetNodeAttribute(AFieldNode,'attrname');
- // Difference in casing between CDS and bufdataset...
- S:=StrToIntDef(GetNodeAttribute(AFieldNode,'width'),-1);
- if (S=-1) then
- S:=StrToIntDef(GetNodeAttribute(AFieldNode,'WIDTH'),0);
- AFieldDef.Size:=s;
- FTString:=GetNodeAttribute(AFieldNode,'fieldtype');
- SubFTString:=GetNodeAttribute(AFieldNode,'subtype');
- if SubFTString<>'' then
- FTString:=FTString+':'+SubFTString;
- AFieldDef.DataType:=ftUnknown;
- for iFieldType:=low(TFieldType) to high(TFieldType) do
- if SameText(XMLFieldtypenames[iFieldType],FTString) then
- begin
- AFieldDef.DataType:=iFieldType;
- break;
- end;
- end;
- end;
- FParamsNode := MetaDataNode.FindNode('PARAMS');
- if assigned(FParamsNode) then
- begin
- FChangeLogNode := FParamsNode.Attributes.GetNamedItem('CHANGE_LOG');
- AnAutoIncNode := FParamsNode.Attributes.GetNamedItem('AUTOINCVALUE');
- if assigned(AnAutoIncNode) then
- AnAutoIncValue := StrToIntDef(AnAutoIncNode.NodeValue,-1);
- end;
- FRowDataNode := DataPacketNode.FindNode('ROWDATA');
- FRecordNode := nil;
- end;
- procedure TXMLDatapacketReader.StoreFieldDefs(AnAutoIncValue: integer);
- var i,p : integer;
- AFieldNode : TDOMElement;
- AStringFT : string;
- begin
- XMLDocument := TXMLDocument.Create;
- DataPacketNode := XMLDocument.CreateElement('DATAPACKET');
- DataPacketNode.SetAttribute('Version','2.0');
- MetaDataNode := XMLDocument.CreateElement('METADATA');
- FieldsNode := XMLDocument.CreateElement('FIELDS');
- for i := 0 to DataSet.FieldDefs.Count - 1 do with DataSet.FieldDefs[i] do
- begin
- AFieldNode := XMLDocument.CreateElement('FIELD');
- if Name <> '' then AFieldNode.SetAttribute('fieldname',Name);
- AFieldNode.SetAttribute('attrname',DisplayName);
- if size <> 0 then AFieldNode.SetAttribute('width',IntToStr(Size));
- AStringFT:=XMLFieldtypenames[DataType];
- p := pos(':',AStringFT);
- if p > 1 then
- begin
- AFieldNode.SetAttribute('fieldtype',copy(AStringFT,1,p-1));
- AFieldNode.SetAttribute('subtype',copy(AStringFT,p+1,25));
- end
- else
- AFieldNode.SetAttribute('fieldtype',AStringFT);
- if faReadonly in Attributes then AFieldNode.SetAttribute('readonly','true');
- FieldsNode.AppendChild(AFieldNode);
- end;
- MetaDataNode.AppendChild(FieldsNode);
- FParamsNode := XMLDocument.CreateElement('PARAMS');
- if AnAutoIncValue>-1 then
- (FParamsNode as TDomElement).SetAttribute('AUTOINCVALUE',IntToStr(AnAutoIncValue));
- MetaDataNode.AppendChild(FParamsNode);
- DataPacketNode.AppendChild(MetaDataNode);
- FRowDataNode := XMLDocument.CreateElement('ROWDATA');
- setlength(FChangeLog,0);
- FEntryNr:=0;
- FLastChange:=-1;
- end;
- procedure TXMLDatapacketReader.FinalizeStoreRecords;
- var ChangeLogStr : String;
- i : integer;
- begin
- ChangeLogStr:='';
- for i := 0 to length(FChangeLog)-1 do with FChangeLog[i] do
- begin
- ChangeLogStr:=ChangeLogStr+' '+inttostr(NewEntry)+' '+inttostr(OrigEntry)+' ';
- if UpdateKind=ukModify then ChangeLogStr := ChangeLogStr+'8';
- if UpdateKind=ukInsert then ChangeLogStr := ChangeLogStr+'4';
- if UpdateKind=ukDelete then ChangeLogStr := ChangeLogStr+'2';
- end;
- setlength(FChangeLog,0);
- if ChangeLogStr<>'' then
- (FParamsNode as TDomElement).SetAttribute('CHANGE_LOG',Trim(ChangeLogStr));
- DataPacketNode.AppendChild(FRowDataNode);
- XMLDocument.AppendChild(DataPacketNode);
- WriteXML(XMLDocument,Stream);
- end;
- function TXMLDatapacketReader.GetCurrentRecord: boolean;
- begin
- Result := assigned(FRecordNode);
- end;
- function TXMLDatapacketReader.GetRecordRowState(out AUpdOrder: Integer
- ): TRowState;
- var ARowStateNode : TDOmNode;
- ARowState : integer;
- i : integer;
- begin
- ARowStateNode := FRecordNode.Attributes.GetNamedItem('RowState');
- if ARowStateNode = nil then // This item is not edited
- Result := []
- else
- begin
- Result := ByteToRowState(StrToIntDef(ARowStateNode.NodeValue,0));
- if Result = [rsvOriginal] then
- begin
- for i := 0 to length(FChangeLog)-1 do
- if FChangeLog[i].NewEntry=FEntryNr then break;
- assert(FChangeLog[i].NewEntry=FEntryNr);
- end
- else
- begin
- for i := 0 to length(FChangeLog)-1 do
- if FChangeLog[i].OrigEntry=FEntryNr then break;
- assert(FChangeLog[i].OrigEntry=FEntryNr);
- end;
- AUpdOrder:=i;
- end;
- end;
- procedure TXMLDatapacketReader.InitLoadRecords;
- var ChangeLogStr : String;
- i,cp : integer;
- ps : string;
- begin
- FRecordNode := FRowDataNode.FirstChild;
- FEntryNr := 1;
- setlength(FChangeLog,0);
- if assigned(FChangeLogNode) then
- ChangeLogStr:=FChangeLogNode.NodeValue
- else
- ChangeLogStr:='';
- ps := '';
- cp := 0;
- if ChangeLogStr<>'' then for i := 1 to length(ChangeLogStr)+1 do
- begin
- if not (ChangeLogStr[i] in [' ',#0]) then
- ps := ps + ChangeLogStr[i]
- else
- begin
- case (cp mod 3) of
- 0 : begin
- SetLength(FChangeLog,length(FChangeLog)+1);
- FChangeLog[cp div 3].OrigEntry:=StrToIntDef(ps,0);
- end;
- 1 : FChangeLog[cp div 3].NewEntry:=StrToIntDef(ps,0);
- 2 : begin
- if ps = '2' then
- FChangeLog[cp div 3].UpdateKind:=ukDelete
- else if ps = '4' then
- FChangeLog[cp div 3].UpdateKind:=ukInsert
- else if ps = '8' then
- FChangeLog[cp div 3].UpdateKind:=ukModify;
- end;
- end; {case}
- ps := '';
- inc(cp);
- end;
- end;
- end;
- procedure TXMLDatapacketReader.RestoreRecord;
- var FieldNr : integer;
- AFieldNode : TDomNode;
- ABufBlobField: TBufBlobField;
- AField: TField;
- s: string;
- ws: widestring;
- begin
- with DataSet do for FieldNr:=0 to FieldDefs.Count-1 do
- begin
- AField := Fields.FieldByNumber(FieldDefs[FieldNr].FieldNo);
- AFieldNode := FRecordNode.Attributes.GetNamedItem(FieldDefs[FieldNr].Name);
- if assigned(AFieldNode) then
- begin
- s := AFieldNode.NodeValue;
- if (FieldDefs[FieldNr].DataType in [ftBlob, ftBytes, ftVarBytes]) and (s <> '') then
- s := DecodeStringBase64(s);
- case FieldDefs[FieldNr].DataType of
- ftBlob, ftMemo:
- RestoreBlobField(AField, @s[1], length(s));
- ftWideMemo:
- begin
- ws := s;
- RestoreBlobField(AField, @ws[1], length(ws)*sizeof(WideChar));
- end
- else;
- AField.AsString := s; // set it to the filterbuffer
- end;
- end
- else
- AField.SetData(nil);
- end;
- end;
- procedure TXMLDatapacketReader.StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0);
- var FieldNr : Integer;
- AFieldDef: TFieldDef;
- AField: TField;
- ARecordNode : TDOMElement;
- begin
- inc(FEntryNr);
- ARecordNode := XMLDocument.CreateElement('ROW');
- with DataSet do for FieldNr := 0 to FieldDefs.Count-1 do
- begin
- AFieldDef := FieldDefs[FieldNr];
- AField := Fields.FieldByNumber(AFieldDef.FieldNo);
- if not AField.IsNull then
- if AFieldDef.DataType in [ftBlob, ftBytes, ftVarBytes] then
- ARecordNode.SetAttribute(AFieldDef.Name, EncodeStringBase64(AField.AsString))
- else
- ARecordNode.SetAttribute(AFieldDef.Name, AField.AsString);
- end;
- if ARowState<>[] then
- begin
- ARecordNode.SetAttribute('RowState',inttostr(RowStateToByte(ARowState)));
- if AUpdOrder>=length(FChangeLog) then
- setlength(FChangeLog,AUpdOrder+1);
- if (rsvOriginal in ARowState) or (rsvDeleted in ARowState) then
- FChangeLog[AUpdOrder].OrigEntry:=FEntryNr;
- if (rsvDeleted in ARowState) or (rsvUpdated in ARowState) or (rsvInserted in ARowState) then
- FChangeLog[AUpdOrder].NewEntry:=FEntryNr;
- if ARowState=[rsvUpdated] then
- FChangeLog[AUpdOrder].UpdateKind := ukModify;
- if ARowState=[rsvInserted] then
- FChangeLog[AUpdOrder].UpdateKind := ukInsert;
- if ARowState=[rsvDeleted] then
- FChangeLog[AUpdOrder].UpdateKind := ukDelete;
- end;
- FRowDataNode.AppendChild(ARecordNode);
- end;
- class function TXMLDatapacketReader.RecognizeStream(AStream: TStream): boolean;
- const XmlStart = '<?xml';
- var s : string;
- len : integer;
- begin
- Len := length(XmlStart);
- setlength(s,len);
- if (AStream.Read (s[1],len) = len)
- and (s=XmlStart) then
- Result := True
- else
- Result := False;
- end;
- procedure TXMLDatapacketReader.GotoNextRecord;
- begin
- FRecordNode := FRecordNode.NextSibling;
- inc(FEntryNr);
- while assigned(FRecordNode) and (FRecordNode.CompareName('ROW')<>0) do
- FRecordNode := FRecordNode.NextSibling;
- end;
- initialization
- RegisterDatapacketReader(TXMLDatapacketReader,dfXML);
- end.
|