All I want to do is to implement "Export to excel" option of a classical webbrowser, to Delphi2007 commands...... When I am using this option from a webbrowser to export a 12000 rows table it takes less than a minute to export the table from any web browser from windows. Trying to implement this in Delphi using 2D Array it takes 10 minutes... Trying to implement the export with parsing technique (Stringlists, strings, Pos(tr), pos (td) & some other string functions) it takes a long... Hence, which are the commands of a webbrowser to export an html table to excel that I have to convert them to Delphi? Should I use javascript inside Delphi? Should I use pointers? Should I use HTML entities? xml?...Any ideas? Thank you in advance.
2D ARRAY
Excel:= CreateOleObject('Excel.Application');
ovTable := WebBrowser1.OleObject.Document.all.tags('TABLE').item(0);
arrayn:=VarArrayCreate([1, ovTable.Rows.Length, 1, ovTable.Rows.Item(1).Cells.Length], varvariant);
for i:=0 to (ovTable.Rows.Length - 1) do
begin
for j := 0 to (ovTable.Rows.Item(i).Cells.Length - 1) do
Begin
arrayn[i+1, j+1]:=ovTable.Rows.Item(i).Cells.Item(j).InnerText;
Application.ProcessMessages;
end;end;
WS.range[ws.cells[1, 1], ws.cells[ovTable.Rows.Length, ovTable.Rows.Item(1).Cells.Length]].value:=arrayn;
Excel.WorkBooks[1].SaveAs(directorylistbox1.Directory+'\'+'test.xlsx');
WS := Excel.WorkBooks.close;
Excel.quit;
Excel:=unassigned;
HTML PARSING
function HTMLCleanUp(L : string) : string;
const
CSVTempSeparator = #255; //replaced by a comma
CRLF = #13#10;
var
P1,P2 : integer;
begin
P1 := Pos('<',L); //clean-up anything between <>
while (P1>0) do //WHILE1
begin
P2 := Pos('>',L);
if (P2>0)
then Begin Delete(L,P1,P2-P1+1); end;
P1 := Pos('<',L);
end; //WHILE1
L:=StringReplace(L,' ','-',[rfReplaceAll]);
L:=StringReplace(L,'-01','',[rfReplaceAll]);
L:=StringReplace(L,'-02','',[rfReplaceAll]);
L:=StringReplace(L,'-03','',[rfReplaceAll]);
Result := Trim(L);
end;
function HTMLTableToCSV(HTML,CSV : TStringList) : boolean;
const
CRLF = #13#10;
CSVTempSeparator = #9;
var
P1,P2,P3,P4, p5, P6, p11, p22 : integer;
S,TmpStr,CSVStr : string;
begin
Result := True;
S := Trim(StringReplace(HTML.Text,CRLF,'',[rfReplaceAll]));
P1 := PosEx('<TR',S, 1); //CASE SENSITIVE , TR->FIRST ROW
CSVStr := '';
while (P1>0) do //while1
begin
P2 := PosEx('</TR',S, P1);
if (P2>0) //if1
then begin
TmpStr := Copy(S,P1,P2-P1+1);
//Delete(S,P1,P2-P1+1);
CSVStr := ''; p11:=1;p22:=1;
P11 := PosEx('<TH',TmpStr,1);
while (P11>0) do //while2
begin
P22 := PosEx('</TH',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=
//CSVStr+Trim(Copy(TmpStr,P1+4,P2-P1-4));//+CSVTempSeparator;
CSVStr+Trim(Copy(TmpStr,P11,P22-P11))+CSVTempSeparator;
//Delete(TmpStr,P1,P2-P1+1);
end //if2
else begin
Result := False;
Exit;
end; //if2
P11 := PoseX('<TH',TmpStr, P22);
end; //while2
P11 := PosEx('<TD',TmpStr, 1);
while (P11>0) do //while2
begin
P22 := PosEx('</TD',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=
//CSVStr+Trim(Copy(TmpStr,P1+4,P2-P1-4));//+CSVTempSeparator;
CSVStr+Trim(Copy(TmpStr,P11,P22-P11))+CSVTempSeparator;
//Delete(TmpStr,P1,P2-P1+1);
end //if2
else begin
Result := False;
Exit;
end; //if2
P11 := PosEx('<TD',TmpStr,P22);
end; //while2
end //if1
else begin
Result:=false;
exit;
end; //if1
CSV.Add(HTMLCleanUp(CSVStr));
P1 := PosEx('<TR',S,P2); //CASE SENSITIVE
end; //while1
end;
procedure TForm11.Button1Click(Sender: TObject);
const
xlExcel7 = $00000027;
TmpFileName='c:\test\Test.txt';
VAR
Excel: Olevariant;
HTMLStrList,CSVSTRList : TStringList;
begin
HTMLStrList := TStringList.Create;
try
HTMLStrList.LoadFromFile('C:\test\TestTable1.htm');
CSVSTRList := TStringList.Create;
try
if HTMLTableToCSV(HTMLStrList,CSVSTRList)
then Begin
CSVSTRList.SaveToFile(TmpFileName);
Excel:= CreateOleObject('Excel.Application');
Excel.WorkBooks.opentext(TmpFileName);//OPEN TXT WITH EXCEL
Excel.DisplayAlerts := False;
Excel.WorkBooks[1].SaveAs('c:\test\Nisa.xls', xlExcel7);//SAVE TAB DELIMITED TEXT FILE
Excel.WorkBooks[1].close;
Excel.quit;
Excel:=unassigned;
End
else ShowMessage('Error converting HTML table to CSV');
finally
CSVSTRList.Free;
end;
finally
HTMLStrList.Free;
DeleteFile(TmpFileName);
end;
end;
procedure TForm11.FormCreate(Sender: TObject);
begin
webBrowser1.Navigate('http://samples.msdn.microsoft.com/workshop/samples/author/tables/HTML_ Table.htm');
end;
procedure TForm11.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
var
Document: IHtmlDocument2;
CurWebrowser : IWebBrowser;
TopWebBrowser: IWebBrowser;
WindowName : string;
begin
CurWebrowser := pDisp as IWebBrowser;
TopWebBrowser := (ASender as TWebBrowser).DefaultInterface;
if CurWebrowser=TopWebBrowser then
begin
document := webbrowser1.document as IHtmlDocument2;
memo3.lines.add(trim(document.body.innerhtml)); // to get html
ShowMessage('Document is complete.')
end;
end;
end.
I found the solution...HTML Table Parsing in Less than a second!
function HTMLCleanUp(L : string) : string;
var
P1,P2 : integer;
begin
P1 := Pos('<',L); //clean-up anything between <>
while (P1>0) do //WHILE1
begin
P2 := Pos('>',L);
if (P2>0)
then Begin Delete(L,P1,P2-P1+1); end;
P1 := Pos('<',L);
end; //WHILE1
L:=StringReplace(L,' ','-',[rfReplaceAll]);
Result := Trim(L);
end;
procedure TForm11.WB_SaveAs_HTML(WB : TWebBrowser; const FileName : string) ;
var
PersistStream: IPersistStreamInit;
Stream: IStream;
FileStream: TFileStream;
begin
if not Assigned(WB.Document) then
begin
ShowMessage('Document not loaded!') ;
Exit;
end;
PersistStream := WB.Document as IPersistStreamInit;
FileStream := TFileStream.Create(FileName, fmCreate) ;
try
Stream := TStreamAdapter.Create(FileStream, soReference) as IStream;
if Failed(PersistStream.Save(Stream, True)) then ShowMessage('SaveAs HTML fail!') ;
finally
FileStream.Free;
end;
end; (* WB_SaveAs_HTML *)
procedure TForm11.Button1Click(Sender: TObject);
const
xlExcel7 = $00000027;
TmpFileName='c:\test\xxxx.txt';
CRLF = #13#10;
CSVTempSeparator = #9; //#255; //replaced by a comma
ADPNEWHOTURL = 'http://samples.msdn.microsoft.com/workshop/samples/author/tables/HTML_Table.htm';
VAR
Excel, WS: Olevariant;
P1,P2,P3,P4, p5, P6, p11, p22 : integer;
i, j: Integer;
buffer,rawHTM,TmpStr,CSVStr:string;
HTMFile : TextFile;
CSVSTRList : TStringList;
begin
CSVSTRList := TStringList.Create;
WB_SaveAs_HTML(WebBrowser1,TmpFileName) ;
AssignFile(HTMFile, TmpFileName);//read the HTML file
Reset(HTMFile);
while not EOF(HTMFile) do begin
ReadLn(HTMFile, buffer);
rawHTM := Concat(rawHTM, buffer);
end;
i:=1;j:=1;
rawHTM := Trim(StringReplace(rawHTM,CRLF,'',[rfReplaceAll]));
P1 := PosEx('<TR',rawHTM, 1); //CASE SENSITIVE , TR->FIRST ROW
while (P1>0) do //while1
begin
P2 := PosEx('</TR',rawHTM, P1);
if (P2>0) //if1
then begin
TmpStr := Copy(rawHTM,P1,P2-P1+1);
CSVStr := '';p11:=1;p22:=1;
P11 := PosEx('<TH',TmpStr,1);
while (P11>0) do //while2
begin
P22 := PosEx('</TH',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=CSVStr+
HTMLCleanUp(Trim(Copy(TmpStr,P11,P22-P11)))+CSVTempSeparator; j:=j+1;
end //if2
else begin
Exit;
end; //if2
P11 := PoseX('<TH',TmpStr, P22);
end; //while2
P11 := PosEx('<TD',TmpStr, 1);
while (P11>0) do //while2
begin
P22 := PosEx('</TD',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=CSVStr+
HTMLCleanUp(Trim(Copy(TmpStr,P11,P22-P11)))+CSVTempSeparator; j:=j+1;
end //if2
else begin
Exit;
end; //if2
P11 := PosEx('<TD',TmpStr,P22);
end; //while2
end //if1
else begin
exit;
end; //if1
CSVSTRList.Add(CSVStr);
P1 := PosEx('<TR',rawHTM,P2); i:=i+1; j:=1; //CASE SENSITIVE
end; //while1
CSVSTRList.SaveToFile('c:\test\xxx2.txt');
Excel:= CreateOleObject('Excel.Application');
Excel.WorkBooks.opentext('c:\test\xxx2.txt');//OPEN TXT WITH EXCEL
Excel.visible := True;
CloseFile(HTMFile);
DeleteFile(TmpFileName);
end;
Related
Is it possible to call a function that called a function in a DLL that is written in Delphi? The calling program that loads the DLL just has access to my DLL's exported functions and can not export it's own functions (it's Easylanguge programming language and does not have a command to export or the ability to pass pointers). I do not need to pass any parameters when I call the from the DLL, just execute the code again after the return address point.
So if a function in Easylanguage calls a function from the DLL, can the return address from the Easylanguage function be used in the DLL to later call the Easylanguage function at the point of the return address? Even a hack will do.
I want to get this concept code I wrote working correctly before I try to apply it to the actual DLL & Easylanguage platform. I sometimes get access violations.
Delphi demo that simulates the interaction of the DLL & Easylanguage:
type
Tra_func = function: Integer;
var
Form9: TForm9;
ra: pointer;
ra_func: Tra_func;
implementation
{$R *.dfm}
function dll_func: integer;
begin
ra := System.ReturnAddress;
Form9.ListBox1.Items.Add(Format('RA to "easylanguage_func": %p', [ra]));
Form9.ListBox1.Items.Add('END of "dll" function');
result := 1;
end;
function easylanguage_func: integer; // temp stand-in function for Easylanguage
begin
Form9.ListBox1.Items.Add('Call "dll" to get return address...');
dll_func();
Form9.ListBox1.Items.Add('END of "easylanguage_func" function');
result := 1;
end;
procedure TForm9.Button1Click(Sender: TObject);
begin
easylanguage_func; // * this call would be from Easylanguage to the DLL
ListBox1.Items.Add('Calling RA address of "easylanguage_func"');
ra_func := Tra_func(ra);
ra_func; // * this call would be located in the DLL
end;
end.
What an Easylanguage routine that calls a DLL function could look like:
external: "ra_test_dll.dll", INT, "GetRAFunction";
method void ReturnFunction() // * can not export this *
begin
Print("GetRAFunction");
GetRAFunction(); // calls function in DLL
// *** returns here, start execution here when call from the DLL later
Print("*RA - next line*");
end;
String passing as parameters and returns in both directions..
Easylanguage:
external: "ts_dll_str_test.dll", String, "StringTest", String; // Delphi DLL function def
method void StrFunction(String ss)
variables:
String ss2;
begin
ss2 = StringTest(ss+"abc");
Print(ss2); // Output = ABCD5FGHIJKLM
end;
Call: StrFunction("0123456789")
Delphi DLL:
var
ss: AnsiString;
myCharPtr: PAnsiChar;
function StringTest(StrIn: PAnsiChar): PAnsiChar; stdcall; // called by EL
begin
ss := 'ABCDEFGHIJKLM';
myCharPtr := #ss[1];
myCharPtr[4] := StrIn[5];
result := myCharPtr;
end;
exports StringTest;
Thanks.
I designed a demo with Delphi used in both the calling application and the DLL. You'll have to apply the same "trick" in your EasyLanguage programming.
The idea is that when the DLL need to call a function in the executable - function which is not exported in anyway - it simply returns with a special value transporting all the information required to call whatever EasyLanguage (here Delphi) function.
This means that at both the caller and the DLL, the function are loops. The EXE calls the DLL passing the initial argument, the DLL get it and return a special value describing the function call it needs. The EXE recognize that, call the required function in his code and then call again the same function in the DLL, this time passing the result of the function call. And the process loops for a second, thirds and so on. Finally the DLL is able to produce the final result and return it without the mark indicating a function call.
Everything is handled using AnsiString since EasyLaguage do not support pointers.
The code below has been simplified at maximum so that it is more readable. In a real application it is much better to validate many things to avoid unexpected behaviour.
Here is the code for executable:
unit CallingCallerDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
ParamParsing;
type
TCallingCallerForm = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
public
function CallDll(Value: Integer): String;
function DemoSquare(Arg1: Integer): Integer;
function DemoSum(Arg1: Integer): Integer;
end;
// Declaration for function in DLL
// In this demo, the function takes one integer argument and return a string
// looking like "Value=4 Square=16 Sum=8". The value is the argument, the
// square and the sum are computed by THIS executable: the DLL somehow call
// this executable.
// The use of AnsiChar is required for this demo because it is normally not
// written in Delphi but in EasyLanguage which has only ANSI strings.
function DllFunc(
StrIn : PAnsiChar
) : PAnsiChar; stdcall; external 'CallingCallerDemoDll.dll';
var
CallingCallerForm: TCallingCallerForm;
implementation
{$R *.dfm}
function TCallingCallerForm.DemoSquare(Arg1 : Integer) : Integer;
begin
Result := Arg1 * Arg1;
Memo1.Lines.Add('DemoSquare called');
end;
function TCallingCallerForm.DemoSum(Arg1 : Integer) : Integer;
begin
Result := Arg1 + Arg1;
Memo1.Lines.Add('DemoSum called');
end;
function TCallingCallerForm.CallDll(Value : Integer) : String;
var
S : String;
DllFctPrm : AnsiString;
Params : String;
FctName : String;
Arg1 : Integer;
Status : Boolean;
State : String;
Value1 : Integer;
Value2 : Integer;
begin
DllFctPrm := '4';
while TRUE do begin
S := String(DllFunc(PAnsiChar(DllFctPrm)));
if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
Result := S;
Exit;
end
else begin
Params := Trim(Copy(S, 2, Length(S) - 2));
FctName := ParamByNameAsString(Params, 'FctName', Status, '');
State := ParamByNameAsString(Params, 'State', Status, '');
Memo1.Lines.Add('Callback="' + Params + '"');
if SameText(FctName, 'DemoSquare') then begin
Arg1 := ParamByNameAsInteger(Params, 'Arg1', Status, 0);
Value1 := DemoSquare(Arg1);
DllFctPrm := AnsiString('[' +
'State=' + State +';' +
'Value=' + IntToStr(Value1) +
']');
continue;
end
else if SameText(FctName, 'DemoSum') then begin
Arg1 := ParamByNameAsInteger(Params, 'Arg1', Status, 0);
Value2 := DemoSum(Arg1);
DllFctPrm := AnsiString('[' +
'State=' + State +';' +
'Value=' + IntToStr(Value2) +
']');
continue;
end
else
raise Exception.Create('Unexpected function name');
end;
end;
end;
procedure TCallingCallerForm.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Result: ' + CallDll(4));
end;
end.
Here is the code for the DLL:
library CallingCallerDemoDll;
uses
System.SysUtils,
System.Classes,
ParamParsing in '..\DirectCompute\Mandel\Delphi\ParamParsing.pas';
{$R *.res}
var
GBuffer : AnsiString;
Value : Integer;
Value1 : Integer;
Value2 : Integer;
function DllFunc(StrIn : PAnsiChar) : PAnsiChar; stdcall;
var
S : String;
Params : String;
State : Integer;
Status : Boolean;
begin
S := String(StrIn);
if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
// Normal call
State := 1;
Value := StrToInt(S);
Value1 := 0;
Value2 := 0;
end;
while TRUE do begin
if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
// Call caller
{$WARN USE_BEFORE_DEF OFF}
case State of
1: GBuffer := '[FctName=' + '"DemoSquare";' +
'Arg1=' + AnsiString(IntToStr(Value)) + ';' +
'State=' + AnsiString(IntToStr(State)) + ']';
2: GBuffer := '[FctName=' + '"DemoSum";' +
'Arg1=' + AnsiString(IntToStr(Value)) + ';' +
'State=' + AnsiString(IntToStr(State)) + ']';
end;
Result := PAnsiChar(GBuffer);
Exit;
end
else begin
// Return from function
Params := Trim(Copy(S, 2, Length(S) - 2));
State := StrToInt(ParamByNameAsString(Params, 'State', Status, ''));
case State of
1: begin
Value1 := ParamByNameAsInteger(Params, 'Value', Status, 0);
State := 2;
S := '';
continue;
end;
2: begin
Value2 := ParamByNameAsInteger(Params, 'Value', Status, 0);
GBuffer := AnsiString(Format('Value=%d Square=%d Sum=%d',
[Value, Value1, Value2]));
Result := PAnsiChar(GBuffer);
Exit;
end;
end;
end;
end;
end;
exports
DllFunc;
begin
end.
And finally a support unit to parse values:
unit ParamParsing;
interface
uses
SysUtils;
function ParamByNameAsString(
const Params : String;
const ParamName : String;
var Status : Boolean;
const DefValue : String) : String;
function ParamByNameAsInteger(
const Params : String;
const ParamName : String;
var Status : Boolean;
const DefValue : Integer) : Integer;
implementation
// Parameters format = 'name1="value";name2="value2";....;nameN="valueN"
function ParamByNameAsString(
const Params : String;
const ParamName : String;
var Status : Boolean;
const DefValue : String) : String;
var
I, J : Integer;
Ch : Char;
begin
Status := FALSE;
I := 1;
while I <= Length(Params) do begin
J := I;
while (I <= Length(Params)) and (Params[I] <> '=') do
Inc(I);
if I > Length(Params) then begin
Result := DefValue;
Exit; // Not found
end;
if SameText(ParamName, Trim(Copy(Params, J, I - J))) then begin
// Found parameter name, extract value
Inc(I); // Skip '='
// Skip spaces
J := I;
while (J < Length(Params)) and (Params[J] = ' ') do
Inc(J);
if (J <= Length(Params)) and (Params[J] = '"') then begin
// Value is between double quotes
// Embedded double quotes and backslashes are prefixed
// by backslash
I := J;
Status := TRUE;
Result := '';
Inc(I); // Skip starting delimiter
while I <= Length(Params) do begin
Ch := Params[I];
if Ch = '\' then begin
Inc(I); // Skip escape character
if I > Length(Params) then
break;
Ch := Params[I];
end
else if Ch = '"' then
break;
Result := Result + Ch;
Inc(I);
end;
end
else begin
// Value is up to semicolon or end of string
J := I;
while (I <= Length(Params)) and (Params[I] <> ';') do
Inc(I);
Result := Trim(Copy(Params, J, I - J));
Status := TRUE;
end;
Exit;
end;
// Not good parameter name, skip to next
Inc(I); // Skip '='
if (I <= Length(Params)) and (Params[I] = '"') then begin
Inc(I); // Skip starting delimiter
while I <= Length(Params) do begin
Ch := Params[I];
if Ch = '\' then begin
Inc(I); // Skip escape character
if I > Length(Params) then
break;
end
else if Ch = '"' then
break;
Inc(I);
end;
Inc(I); // Skip ending delimiter
end;
// Param ends with ';'
while (I <= Length(Params)) and (Params[I] <> ';') do
Inc(I);
Inc(I); // Skip semicolon
end;
Result := DefValue;
end;
function ParamByNameAsInteger(
const Params : String;
const ParamName : String;
var Status : Boolean;
const DefValue : Integer) : Integer;
begin
Result := StrToInt(ParamByNameAsString(Params, ParamName, Status, IntToStr(DefValue)));
end;
end.
Everything tested OK with Delphi 10.4.2 (Should work with any other recent Delphi).
I'm working with gmlib in Delphi Seattle 10.
My client application sends the location(Latitude and Longitude) through an fireMonkey application to my database InterBase XE7.
My admin console consists in display a google map with markers that come from a query,for later calculate the distance between all markers in the map.
The procedure that creates markers is working perfectly and at the same time i'm filling the GMDirection component with the coordinates of markers. Here is the code of "CreatePoint" procedure:
amplitud := 1;
posicion := 0;
Distancia := 0;
markerGM.Tag := 1;
qryDatos.Close;
qryDatos.Open;
while not qryDatos.Eof do
begin
SetLength(marcadores,amplitud);
marcadores[posicion] := qryDatos.FieldByName('PLULOG').AsInteger;
Latitud := qryDatos.FieldByName('LATITUD').AsFloat;
Longitud := qryDatos.FieldByName('LONGITUD').AsFloat;
autorizado := qryDatos.FieldByName('AUTORIZADO').AsString;
with markerGM.Add(Latitud,Longitud) do
begin
if autorizado = 'T' then
begin
if markerGM.Tag = 1 then
begin
directionGM.DirectionsRequest.Origin.LatLng.Lat := Latitud;
directionGM.DirectionsRequest.Origin.LatLng.Lng := Longitud;
end
else if markerGM.Tag = 2 then
begin
directionGM.DirectionsRequest.Destination.LatLng.Lat := Latitud;
directionGM.DirectionsRequest.Destination.LatLng.Lng := Longitud;
directionGM2.DirectionsRequest.Origin.LatLng.Lat := Latitud;
directionGM2.DirectionsRequest.Origin.LatLng.Lng := Longitud;
Distancia := DistanceBetween(directionGM.DirectionsRequest.Origin.LatLng.Lat,directionGM.DirectionsRequest.Origin.LatLng.Lng,
directionGM.DirectionsRequest.Destination.LatLng.Lat,directionGM.DirectionsRequest.Destination.LatLng.Lng);
end
else if markerGM.Tag = 3 then
begin
directionGM2.DirectionsRequest.Destination.LatLng.Lat := Latitud;
directionGM2.DirectionsRequest.Destination.LatLng.Lng := Longitud;
directionGM3.DirectionsRequest.Origin.LatLng.Lat := Latitud;
directionGM3.DirectionsRequest.Origin.LatLng.Lng := Longitud;
Distancia := Distancia + DistanceBetween(directionGM2.DirectionsRequest.Origin.LatLng.Lat,directionGM2.DirectionsRequest.Origin.LatLng.Lng,
directionGM2.DirectionsRequest.Destination.LatLng.Lat,directionGM2.DirectionsRequest.Destination.LatLng.Lng);
end
else if markerGM.Tag = 4 then
begin
directionGM3.DirectionsRequest.Destination.LatLng.Lat := Latitud;
directionGM3.DirectionsRequest.Destination.LatLng.Lng := Longitud;
directionGM4.DirectionsRequest.Origin.LatLng.Lat := Latitud;
directionGM4.DirectionsRequest.Origin.LatLng.Lng := Longitud;
Distancia := Distancia + DistanceBetween(directionGM3.DirectionsRequest.Origin.LatLng.Lat,directionGM3.DirectionsRequest.Origin.LatLng.Lng,
directionGM3.DirectionsRequest.Destination.LatLng.Lat,directionGM3.DirectionsRequest.Destination.LatLng.Lng);
end;
MarkerType := mtColored;
ColoredMarker.Width := 48 + (Index * 20);
ColoredMarker.Height := 48;
markerGM.Tag := markerGM.Tag + 1;
end;
end;
mapGM.RequiredProp.Center.Lat := Latitud;
mapGM.RequiredProp.Center.Lng := Longitud;
mapGM.RequiredProp.Zoom := 13;
amplitud := amplitud + 1;
posicion := posicion + 1;
qryDatos.Next;
end;
mapGM.Active := True;
And here is the code of the procedure of "DistanceBetween" from Internet:
function TfrmLocationMain.DistanceBetween(const Lat1: Extended; const Lon1: Extended; const Lat2: Extended; const Lon2: Extended): Extended;
begin
Result := RadToDeg(ArcCos(Sin(DegToRad(Lat1)) * Sin(DegToRad(Lat2)) + Cos(DegToRad(Lat1)) * Cos(DegToRad(Lat2)) * Cos(DegToRad(Lon1 - Lon2)))) * 69.09;
end;
And finally. When the google map with the markers are created and the components are full of data. I'm execute all the GMDirection components to calculate the distance and displays in a EditText.
procedure TfrmLocationMain.btnRutaClick(Sender: TObject);
begin
directionGM.Execute;
directionGM2.Execute;
directionGM3.Execute;
directionGM4.Execute;
Distancia := (Distancia/0.62137);
edtDistancia.Text := FloatToStr(Distancia);
mapGM.RequiredProp.Zoom := 14;
end;
All this code is working with all registers in a test database. With coordinates from my country El Salvador. But when I implemented in a database from Guatemala. Some coordinates are causing that GMDirection component give me the following error:
Could not convert variant of type(Null) into type(OleStr)
This happens whit some coordinates from a Guatemala's database.
For example. If the Query gives me the following data:
14.513,-90.558
14.559,-90.545
14.572,-90.542
All the code works perfectly. But if the Query gives me the following data:
14.505,-90.568
14.667,-90.494
14.666,-90.494
Give me the error above.
I don't know what is the problem. And I don't understand why the code works with some registers and with others not.
If someone has a similar problem or idea of what may be failing.
I would greatly appreciate your help with this.
Regards.
I have found the problem. To solve it, open unit GMDirection, add Variants unit to the uses clause
implementation
uses
{$IFDEF DELPHIXE2}
System.SysUtils, System.DateUtils, Xml.XMLIntf, Xml.XMLDoc, System.Variants,
{$ELSE}
SysUtils, DateUtils, XMLIntf, XMLDoc, Variants,
{$ENDIF}
Lang, GMFunctions;
Search line (3575 aprox)
if SameText(Node.NodeName, LBL_D_SUMMARY) then Result.FSumary := Node.NodeValue;
and replace by
if SameText(Node.NodeName, LBL_D_SUMMARY) and (Node.NodeValue <> null) then Result.FSumary := Node.NodeValue;
Recompile the components
That's all
procedure TGateScanForm.ebContainerKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
Found: Boolean;
begin
if Key = VK_RETURN then
begin
ebContainer.ValidateEdit(True);
if not BookingContainer.Active then BookingContainer.Open;
Found:= BookingContainer.Locate('no_container', ebContainer.Text, []);
if Found then
begin
no_booking:= BookingContainer.FieldByName('no_booking').Value;
no_bc11:= BookingContainer.FieldByName('no_bc11').Value;
no_container:= BookingContainer.FieldByName('no_container').Value;
statCont:= BookingContainer.FieldByName('status_container').Value;
statBooking:= BookingContainerstatus.Value;
bProses.Enabled:= True;
Key := VK_TAB;
end
else
begin
bProses.Enabled:= False;
BookingContainer.Close;
ShowMessage('Nomor Container tidak ditemukan');
end;
end;
end;
The qusetion is how i can convert the below line to query.
Found:= BookingContainer.Locate('no_container', ebContainer.Text, []);
If I have understood correctly, you may use :
function Found(S : string) : boolean;
begin
try
**Unsafe code:**
MyQuery.SQL := Format('SELECT NO_CONTAINER FROM TABLE_BookingContainer
WHERE NO_CONTAINER = ''%s''',[S]);
**Safe code:**
MyQuery.SQL := 'SELECT NO_CONTAINER FROM TABLE_BookingContainer
WHERE NO_CONTAINER = :p);
MyQuery.Params.ParamByName('p').AsString = s;
MyQuery.Open;
result := MyQuery.FieldByName('NO_CONTAINER').Value <> null;
finnaly
MyQuery.Close;
end;
end;
to execute function may use :
....
if Found(ebContainer.Text) then
.......
I don't know what for Dataset you use but something like this can search on the database.
BookingContainer.Close;
BookingContainer.SQL.Text := 'SELECT * FROM Table WHERE no_container= :no_container';
BookingContainer.Parameters.ParamByName('no_container').Value := ebContainer.Text;
BookingContainer.Open;
My project uses a REST server with FireDac.
I have a generic function that makes all my Selects but when I try to ApplyUpdates if does nothings. No message, no crash, it just keeps going and the data is not reflected to the database.
My Code:
function TServerMethods.ApplyUpdates(banco, tabela : String; const DeltaList: TFDJSONDeltas; var Mensagem : String) : Boolean;
var
LApply : IFDJSONDeltasApplyUpdates;
Query : TFDQuery;
begin
mensagem := '';
result := false;
try
try
LApply := TFDJSONDeltasApplyUpdates.Create(DeltaList);
Query := CriaQuery(banco,Tabela);
Query.Open();
LApply.ApplyUpdates(banco + '.' + tabela, Query.Command);
if LApply.Errors.Count > 0 then
raise Exception.Create(LApply.Errors.Strings.ToString);
result := true;
except
on E:Exception do
begin
mensagem := 'Ocorreu um Erro na atualização: ' + #13#10 + E.Message;
end;
end;
finally
end;
end;
The GetDeltas function (the function that generates the DeltaList):
function GetDeltas(Banco, Tabela : String; MemTable : TFDMemTable) : TFDJSONDeltas;
begin
if MemTable.State in [dsInsert, dsEdit] then
MemTable.Post;
result := TFDJSONDeltas.Create;
TFDJSONDeltasWriter.ListAdd(result, MemTable);
end;
My "CriaQuery" Function:
function TServerMethods.CriaQuery(Database : String; Tabela : String = '') : TFDQuery;
var
FieldName : Boolean;
i : Integer;
begin
result := TFDQuery.Create(self);
result.Connection := Connection;
result.FetchOptions.AutoFetchAll := afAll;
result.name := 'Qry' + Database + tabela;
result.SQL.Clear;
FieldName := false;
if Trim(Tabela) <> '' then
begin
result.SQL := MontaSQL(database + '.' + tabela);
result.SQL.Add(' and 1 = 0');
result.Open();
QryCampos.First;
result.IndexFieldNames := result.Fields[1].FieldName;
for i := 0 to result.Fields.Count-1 do
begin
if (UPPERCASE(Copy(result.Fields[i].FieldName, Length(result.Fields[i].FieldName)-1,2)) = 'ID') and
(not FieldName) then
begin
result.Fields[i].ProviderFlags := [pfInUpdate, pfInWhere, pfInKey];
FieldName := true;
end
else
result.Fields[i].ProviderFlags := [pfInUpdate];
end;
result.Close;
result.SQL.Delete(result.SQL.Count-1);
end;
end;
Function that generates the bindings of the components:
procedure LinkaComponente(Campo : TField; Dono : TFmxObject; Classe : String);
var
BindSource : TBindSourceDB;
BindingList : TBindingsList;
Link : TLinkControlToField;
begin
if Dono is TForm then
begin
BindSource := TBindSourceDB.Create(Dono);
end
else
begin
BindSource := TBindSourceDB.Create(Dono.Owner);
end;
BindingList := TBindingsList.Create(BindSource.Owner);
Link := TLinkControlToField.Create(BindSource.Owner);
BindSource.DataSet := Campo.DataSet;
if Classe = 'TCheckBox' then
begin
Link.Control := Dono.FindComponent(Campo.FieldName);
Link.CustomFormat := 'ToStr(%s) <> "N"';
Link.CustomParse := 'IfThen(%s,"S","N")';
end
else if Classe = 'TFrameF2' then
begin
Link.Control := (Dono.FindComponent('Frame' + Campo.FieldName) as TFrameF2).edtFK;
end
else
Link.Control := Dono.FindComponent(Campo.FieldName);
Link.DataSource := BindSource;
Link.FieldName := Campo.FieldName;
Link.Active := true;
end;
the moment I call the applyUpdates function:
procedure TDMPadrao.DMApplyUpdates;
var
Deltas : TFDJSONDeltas;
Mensagem : String;
begin
//repetir esses comando para todas as MemTables do DM na sua ordem de dependencia
// tabelas pai antes de tabelas filhas...
try
Deltas := GetDeltas(banco, tabela, FDMemTable);
except
on E:Exception do
begin
FDMemTable.Edit;
MostraMensagemBasica('Ocorreu um Erro na atualização:' + #13#10 + E.Message);
abort;
end;
end;
if not DMClient.ServerMethodsClient.ApplyUpdates(banco, tabela, Deltas, Mensagem) then
begin
FDMemTable.Edit;
MostraMensagemBasica(Mensagem);
abort;
end;
end;
Everything works fine when I'm reading. I Only get a problem when I call the ApplyUpdates function
Thanks.
I had similar problem and I got it resolved passing the table name to Query.UpdateOptions.UpdateTableName before ApplyUpdates.
Are you doing it inside "CriaQuery"?
What is your Delphi Version?
Here is my working code, I have tested it in Delphi XE7 e XE7 Update 1:
procedure TDBDM.ApplyDeltas(const ADeltaList: TFDJSONDeltas; const TableName: string);
var
JSONDeltas: IFDJSONDeltasApplyUpdates;
Query: TFDQuery;
begin
JSONDeltas := TFDJSONDeltasApplyUpdates.Create(ADeltaList);
Query := CreateQuery(TableName);
try
Query.UpdateOptions.UpdateTableName := TableName;
JSONDeltas.ApplyUpdates(0, Query.Command);
if JSONDeltas.Errors.Count > 0 then
begin
raise Exception.Create(JSONDeltas.Errors.Strings.Text);
end;
finally
Query.Free;
end;
end;
Notes
different from your code, Query.Open is not called.
TFDMemTable.CachedUpdates must be True
Edit: Added the client side code to applyUpdates
I call this method in TFDMemTable.AfterPost event.
const
CustomerTableName = 'CUSTOMER';
procedure TCustomersDataModuleClient.ApplyUpdates;
var
Deltas: TFDJSONDeltas;
begin
Deltas := TFDJSONDeltas.Create;
TFDJSONDeltasWriter.ListAdd(Deltas, CustomerTableName, CustomersMemTable);
RestClientModule.CustomersMethodsClient.ApplyUpdates(Deltas);
CustomersMemTable.CommitUpdates;
end;
I m parsing and go threw a JSON structure, in Delphi XE8, and it gives me an error because of the JSON Structure. if i use the REST Debugger i can see the data in the grid but if i try to get it manually i get an error.
here is the JSON :
{
"S":1,
"U":"2898",
"F":[],
"D":[
{
"PRJCT_ID":"7",
"PRJCT_NAME":"Hotel La Rosiere",
"PRJCT_ADRESS":"le plateau"
},
{
"PRJCT_ID":"8",
"PRJCT_NAME":"Hotel Delux",
"PRJCT_ADRESS":"Centre Maison"
},
{
"PRJCT_ID":"9",
"PRJCT_NAME":"villedieu",
"PRJCT_ADRESS":""
}
]
}
I can get the Pair list from level 1 properly : S, F, D
But how can i test if the data is an anonymous record of data :
i tried :
if JSO.Pairs[i].JsonString.Value = '' then
I get an Exception EListError Message... which is normal because no JSON Key associated for the value "{"PRJCT_ID":"7","PRJCT_NAME":"Hotel La Rosiere","PRJCT_ADRESS":"le plateau"}"
My code is
procedure TAArray2.ExtractValues(JSO : TJsonObject);
var
i : integer;
begin
try
i := 0;
while (i < JSO.Count) do
begin
if JSO.Pairs[i].JsonValue is TJSONArray then // array of values "Key":[{"Key":"Value","Key":"Value"},{"Key":"Value","Key":"Value"}]
begin
AddItem(JSO.Pairs[i].JsonString.Value, '', TJSonObject(JSO.Pairs[i].JsonValue)); // recursive call ExtractValues(TJSonObject(JSO.Pairs[i].JsonValue))
end
else if JSO.Pairs[i].JsonString.Value = '' then // ERROR HERE : anonymous key : {"Key":"Value","Key":"Value"},{"Key":"Value","Key":"Value"}
begin
AddItem(i, JSO.Pairs[i].JsonValue.Value);
end
else // standard creation : "Key":"Value"
begin
AddItem(JSO.Pairs[i].JsonString.Value, JSO.Pairs[i].JsonValue.Value);
end;
inc(i);
end;
finally
end;
end;
How can i do it ? Does anyone get an idea ?
Nota : i call anonymous JSON Record Set the portion of JSON that don't
is member of Array "D" like we could say : "D[1]":{ "PRJCT_ID":"7",
"PRJCT_NAME":"Hotel La Rosiere", "PRJCT_ADRESS":"le plateau"} i call it anonymouse because this record set doesn't have his own Reference Key.
Here is the full code : about building a dictionnary of TREE - data accessing as well as Key string value or index : (Note i will only "Parse" the node if needed (Get), if not it will stay stored like a string)
unit Unit3;
interface
uses
Classes, System.SysUtils, System.Types, REST.Types, System.JSON, Data.Bind.Components,
System.RegularExpressions, System.Variants,
Generics.Collections;
type
TAArray2 = class;
PTRec=^TRec;
TRec = class
public
Key : Variant;
isRequired : boolean;
Value : Variant;
OldValue : Variant;
JSON : string;
JSO : TJSonObject;
Items : TAArray2;
procedure Add(Key : Variant ; Value: TRec);
end;
TAArray2 = class(TDictionary<Variant, TRec>)
private
function Get(Index: variant): TRec;
procedure ExtractValues(JSO : TJsonObject);
public
procedure AddItem(Key: Variant; Value: Variant ; JSOnObject : TJSOnObject = nil);
procedure ExtractFromJSON(JSonString: string ; RootElement : string = '');
property Items[Cle : Variant]: TRec read Get; default;
end;
implementation
procedure TRec.Add(Key : Variant ; Value: TRec);
begin
if not(assigned(items)) then
self.Items := TAArray2.Create;
Items.Add( Key, Value);
end;
procedure TAArray2.AddItem(Key : Variant ; Value: Variant ; JSOnObject : TJSOnObject = nil);
var
LocalRec : TRec;
begin
LocalRec := Get(Key);
if assigned(LocalRec) then
begin
LocalRec.Key := Key;
LocalRec.Value := Value;
LocalRec.JSO := JSOnObject;
end
else
begin
LocalRec := TRec.Create;
LocalRec.Value := Value;
LocalRec.Key := Key;
LocalRec.JSO := JSOnObject;
inherited Add( Key, LocalRec);
end;
end;
function TAArray2.Get(Index: Variant): TRec;
var
LocalRec : TRec;
begin
if self.ContainsKey(Index) then
begin
LocalRec := inherited items[Index];
if (LocalRec.JSON <> '') or (LocalRec.JSO <> nil) then
begin
LocalRec.Items := TAArray2.Create;
// ExtractValues(JSO);
end;
Result := LocalRec;
end
else
begin
result := nil;
end;
end;
// *****************************************************************************
//
// *****************************************************************************
procedure TAArray2.ExtractFromJSON(JSonString: string ; RootElement : string = '');
var
JSO : TJsonObject;
JSP : TJSonPair;
begin
try
JSO := TJSOnObject.ParseJSONValue(JSonString) as TJSONObject;
try
if (RootElement <> '') then
begin
JSP := JSO.Get(RootElement);
if not(JSP.Null) then
begin
ExtractValues(TJSonObject(JSP.JsonValue));
end;
end
else if Not(JSO.Null) then
begin
ExtractValues(JSO);
end;
finally
JSO.Free();
end;
except
on E:Exception do showmessage('Data Structure Error');
end;
end;
I view the content with this code :
procedure TForm1.ShowAssocArray2(AAA : TAArray2 ; Level : integer);
var
i : Integer;
s : string;
MyRec : TRec;
begin
s := DupeString(' ',Level * 4);
for MyRec in AAA.Values Do
begin
memo2.Lines.Add(s + string(MyRec.Key) + ' = ' + string(MyRec.Value) + ' (' + string(MyRec.JSON) + ')'); // Error Here
AAA[MyRec.Key];
if assigned(MyRec.Items) then
begin
if MyRec.Items.Count > 0 then
ShowAssocArray2(MyRec.items, Level + 1); // recursive for childrens
end;
end;
end;
procedure TForm1.Button5Click(Sender: TObject);
var
MyList: TAArray2;
MyRec, MyRec2 : TRec;
i: Integer;
begin
MyList := TAArray2.Create;
MyList.ExtractFromJSON(Memo1.Lines.Text);
ShowAssocArray2(MyList, 0);
end;
You are not accessing the array correctly. When you find a TJSONArray, you are type-casting it to TJSONObject, but an array is not an object. You would have gotten a runtime error had you used the as operator for the type-cast, but you did not.
You need to do something more like this instead:
procedure TAArray2.ExtractValues(JSO : TJSONObject);
var
i, j: integer;
pair: TJSONPair;
arr: TJSONArray;
value: TJSONvalue;
begin
for i := 0 to JSO.Count-1 do
begin
pair := JSO.Pairs[i];
value := pair.JsonValue;
if value is TJSONArray then
begin
arr := TJSONArray(value);
for j := 0 to arr.Count-1 do
begin
value := arr[j];
if value is TJSONObject then
begin
ExtractValues(TJSONObject(value));
end
else
begin
// value is not an object, do something else...
end;
end;
end
else
begin
AddItem(pair.JsonString.Value, value.Value);
end;
end;
end;
Update: the JSON document is already in a tree structure. If you are trying to display that tree to the user, such as in a TTreeView, then you can use something like this:
function TAArray2.AddJSONValueToTreeView(const Name: String; Value: TJSONValue; ParentNode: TTreeNode = nil): TTreeNode;
var
i: integer;
obj: TJSONObject;
pair: TJSONPair;
arr: TJSONArray;
begin
if ParentNode <> nil then
Result := TreeView1.Items.AddChild(ParentNode, Name);
else
Result := TreeView1.Items.Add(nil, Name);
if Value is TJSONObject then
begin
obj := TJSONObject(Value);
for i := 0 to obj.Count-1 do
begin
pair := obj.Pairs[i];
AddJSONValueToTreeView(pair.JsonString.Value, pair.JsonValue, Result);
end;
end
else if Value is TJSONArray then
begin
arr := TJSONArray(Value);
for i := 0 to arr.Count-1 do
begin
AddJSONValueToTreeView('['+IntToStr(i)+']', arr.Items[i], Result);
end;
end
else
begin
TreeView1.Items.AddChild(Result, Value.Value);
end;
end;