source upload

This commit is contained in:
Razor12911
2022-01-17 22:16:47 +02:00
parent 12936d065b
commit 098e8c48de
1778 changed files with 1206749 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
TSynJSONTreeView
================
By *EMartin* (Esteban Martin).
# Presentation
`TSynJSONTreeView` is a treeview supporting mORMot's JSON document, the allowed tasks are:
- edit a node
- delete a node
- add a node
- save to file
- load from file
- generate JSON to string variable
- implement a custom input for edit/insert node
# Forum Thread
See http://synopse.info/forum/viewtopic.php?id=3451
# TODO
- FPC support (I just use Windows)
- Developed with Delphi 7, not tested with any other Delphi version.
# License
Feel free to use and/or append to Lib and extend if needed.

View File

@@ -0,0 +1 @@
{"HandleUserAuthentication":true,"HTTPServer":{"HttpQueueLength":1000,"MaxBandWidth":0,"MaxConnections":0,"BindPort":"8888","Authentication":"adDefault","EnableCORS":false,"ThreadCount":32,"Https":false,"HttpSysQueueName":"ARISQueue","WebSocketPassword":""},"HTTPServerWS":{"HttpQueueLength":0,"MaxBandWidth":0,"MaxConnections":0,"BindPort":"8889","Authentication":"adDefault","EnableCORS":false,"ThreadCount":0,"Https":false,"HttpSysQueueName":"ARISWSQueue","WebSocketPassword":"Bsyv1NapccWDNwPVgcQ+fU+pKhed5PGUVUT4h0Im+E07aA=="},"Log":{"Ident":0,"Level":["sllNone","sllInfo","sllDebug","sllTrace","sllWarning","sllError","sllEnter","sllLeave","sllLastError","sllException","sllExceptionOS","sllMemory","sllStackTrace","sllFail","sllSQL","sllCache","sllResult","sllDB","sllHTTP","sllClient","sllServer","sllServiceCall","sllServiceReturn","sllUserAuth","sllCustom1","sllCustom2","sllCustom3","sllCustom4","sllNewRun","sllDDDError","sllDDDInfo","sllMonitoring"],"LevelStackTrace":["sllError","sllLastError","sllException","sllExceptionOS","sllStackTrace","sllFail","sllDDDError"],"DestinationPath":"D:\\TecnoVoz\\Log\\","DefaultExtension":".synlog","IncludeComputerNameInFileName":false,"CustomFileName":"ARIS","ArchivePath":"D:\\TecnoVoz\\Log\\Archive","ArchiveAfterDays":1,"BufferSize":4096,"PerThreadLog":"ptIdentifiedInOnFile","HighResolutionTimeStamp":false,"LocalTimeStamp":true,"WithUnitName":true,"AutoFlushTimeOut":0,"NoEnvironmentVariable":false,"NoFile":false,"RotateFileCount":10,"RotateFileSizeKB":262144,"RotateFileDailyAtHour":-1,"StackTraceLevel":30,"StackTraceUse":"stOnlyAPI","FileExistsAction":"acAppend","EndOfLineCRLF":false},"SMTP":{"Authentication":"","Port":"25","Server":"tvzserver","SSLVersion":"sslvNone","UserName":"emartin","Password":"GaZZ0opaJqvttSQ="},"SQLDBConnections":{"ConnectionTimeoutMinutes":10,"Count":9,"ConnectionDefinitions":[{"DatabaseName":"DRIVER={ODBC Driver 11 for SQL Server};UID=USER;PWD=D3rNfXg2ug==;Server=DBEngines;DataBase=TEST;","DBMS":"dMSSQL","Kind":"TODBCConnectionProperties","Name":"TEST_MSSQL","ServerName":"","AllowGetObjects":false,"Description":"Conexi<78>n a la m<>quina virtual de bases de datos.","Properties":"","UserName":"TEST_USER","Password":""},{"DatabaseName":"FirebirdTest1","DBMS":"dFirebird","Kind":"TSQLDBZEOSConnectionProperties","Name":"APPROACH","ServerName":"firebird-2.5://DBEngines","AllowGetObjects":false,"Description":"","Properties":"hard_commit=true","UserName":"SYSDBA","Password":"D3rNfXg2ug=="},{"DatabaseName":"FirebirdTest2","DBMS":"dFirebird","Kind":"TSQLDBZEOSConnectionProperties","Name":"SCHEDULER","ServerName":"firebird-2.5://DBEngines","AllowGetObjects":false,"Description":"Any description with accented characters <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","Properties":"hard_commit=true","UserName":"SYSDBA","Password":"D3rNfXg2ug=="}]},"LastElement":true}

View File

@@ -0,0 +1,15 @@
program SynJSONTVEditor;
uses
Forms,
fMain in 'fMain.pas' {frmJSONEditor},
SynJSONTreeView in 'SynJSONTreeView.pas',
fLevel in 'fLevel.pas' {frmLevel};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TfrmJSONEditor, frmJSONEditor);
Application.Run;
end.

View File

@@ -0,0 +1,903 @@
/// JSON tree view bases on Synopse JSON parser
// - based on JSON TreeView VCL Component from pawel.glowacki@embarcadero.com
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynJSONTreeView;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
the Initial Developer. All Rights Reserved.
Contributor(s):
- Esteban Martin (EMartin)
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to Synopse mORMot Framework 1.18
TODO:
- FPC support (I just use Windows)
- Developed with Delphi 7, not tested with other Delphi versions
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
SysUtils,
Classes,
ComCtrls,
Controls,
SynCommons,
SynTable;
type
ESynJSONTreeView = class(ESynException);
TSynJSONTreeNode = class;
/// Tree node types
// - jtnObject: JSON object, i.e: {"fieldNumber":123,"fieldText":"text","fieldNull":null}
// - jtnObjectContent: conten of jtnObject, i.e: "fieldNumber":123
// - jtnArray: JSON array, i.e: [123,"text",{"fieldNumber":123,"fieldText":"text","fieldNull":null},null]
// - jtnArrayContent: content of jtnArray, i.e: 123 or {"fieldNumber":123,"fieldText":"text","fieldNull":null}
TSynJSONTreeNodeType = (jtnObject, jtnObjectContent, jtnArray, jtnArrayContent);
/// Event type for fire event when editing
TSynJSONTreeViewCustomInputEvent = procedure(Sender: TObject; Node: TSynJSONTreeNode;
var Prompt: RawUTF8; var Value: Variant;
var Handled: Boolean) of object;
/// Node extension for handling JSON value in the treeview
TSynJSONTreeNode = class(TTreeNode)
protected
fName: RawUTF8;
fJSONType: TSynJSONTreeNodeType;
fValue: Variant;
public
/// Clone the node
function Clone: TSynJSONTreeNode;
/// JSON type in the node
property JSONType: TSynJSONTreeNodeType read fJSONType write fJSONType;
/// Name of the JSON value
property Name: RawUTF8 read FName write fName;
/// JSON value
property Value: Variant read fValue write fValue;
end;
/// Allowed edition states of treeview
// - estEdit: allow edit a node
// - estDelete: allow delete a node
// - estInsert: allow insert a node
TSynJSONTreeViewEditionStateType = (estEdit , estDelete, estInsert);
TSynJSONTreeViewEditionStateSet = set of TSynJSONTreeViewEditionStateType;
/// TSynJSONTreeView is a treeview supporting mORMOn JSON document (TDocVariantData), the allowed tasks are:
// - edit a node
// - delete a node
// - add a node
// - save to file
// - load from file
// - generate JSON to string variable
// - implement a custom input for edit/insert node
TSynJSONTreeView = class(TTreeView)
private
fAllowedEditionStates: TSynJSONTreeViewEditionStateSet;
protected
fFirsttime: Boolean;
fJSONDocument: Variant;
fJSONText: RawUTF8;
fKey: Integer;
fOnDblClickBak: TNotifyEvent;
fOnEditingBak: TTVEditingEvent;
fOnKeyDownBak: TKeyEvent;
fOnCustomInput: TSynJSONTreeViewCustomInputEvent;
fVisibleChildrenCounts: boolean;
fVisibleByteSizes: boolean;
/// Add child node from JSON value
// - aNode: node from which add the child
// - aJSON: JSON value single or object/array
// - aNameOrIndex: if it is numeric, then is the index into the object/array, otherwise the name of the container node
function AddChildFromJSON(const aNode: TSynJSONTreeNode; const aJSON: Variant; const aNameOrIndex: Variant): TSynJSONTreeNode;
procedure BackupEvents;
procedure DeleteNode(Node: TSynJSONTreeNode; const aAsk: Boolean = True);
procedure DoOnCreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
procedure DoOnDblClick(Sender: TObject);
procedure DoOnEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean);
procedure DoOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure EditNode(Node: TSynJSONTreeNode);
procedure InsertNode(Node: TSynJSONTreeNode; const aJSON: Variant);
function IsActive: Boolean;
function IsSimpleJsonValue(v: Variant): boolean;{$ifdef HASINLINE}inline;{$endif}
procedure ProcessArray(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
procedure ProcessElement(currNode: TSynJSONTreeNode; arr: Variant; aIndex: integer);
function ProcessJSONText: boolean;
procedure ProcessObject(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
function ProcessPair(currNode: TSynJSONTreeNode; obj: Variant; aIndex: integer): TSynJSONTreeNode;
/// Assign and process the JSON text
// - if Value is null then clear the JSON text and the treeview content
procedure SetJSONText(const Value: RawUTF8);
/// Assign mORMot JSON Document
// - if Value is not equal to the previous value then load the JSON into the treeview
procedure SetSynJSONDocument(const Value: Variant);
/// Show how many children has a container node
procedure SetVisibleChildrenCounts(const Value: boolean);
/// Show byte size of a container node
procedure SetVisibleByteSizes(const Value: boolean);
/// Update info of a container node
procedure UpdateNodeInfo(aNode: TSynJSONTreeNode);
/// Get the original value from variant value
function VariantToRaw(const aVarData: Variant): Variant;
/// Assign source variant to dest variant but considering the dest data type
procedure VariantToVariant(aSource: Variant; var aDest: Variant);
/// Convert variant to UTF8 but checking the boolean type and convert it to true/false string
function VarToUTF8(const aVarData: Variant): RawUTF8;
public
constructor Create(AOwner: TComponent); override;
/// Remove items and JSON text
procedure ClearAll;
/// Load from file
procedure LoadFromFile(const aFileName: TFileName);
/// Load the JSONText in the treeview, clear if empty
procedure LoadJSON;
/// Save to file
procedure SaveToFile(const aFileName: TFileName);
/// Generate JSON from the treeview
function ToJSON: RawUTF8;
/// JSON DocVariantData
property JSONDocument: Variant read FJSONDocument write SeTSynJSONDocument;
published
/// Allowed state editions
property AllowedEditionStates: TSynJSONTreeViewEditionStateSet read fAllowedEditionStates write fAllowedEditionStates;
/// Content the JSON text, when assigned parse the the JSON and load in the treeview
property JSONText: RawUTF8 read fJSONText write SetJSONText;
/// Display info over how many children has each node
property VisibleChildrenCounts: boolean read FVisibleChildrenCounts write SetVisibleChildrenCounts;
/// Display info over the size of node
property VisibleByteSizes: boolean read FVisibleByteSizes write SetVisibleByteSizes;
/// Event fired when editing for custom input
property OnCustomInput: TSynJSONTreeViewCustomInputEvent read fOnCustomInput write fOnCustomInput;
end;
implementation
uses
Windows,
Variants,
mORMotUILogin;
resourcestring
SInvalidJSONValue = 'Invalid JSON value';
SInvalidJSON = 'Invalid JSON: %';
SEnterValue = 'Enter Value';
SInsertNewItem = 'Insert new item';
SInsertNewItemPrompt = 'Enter JSON:\n\n object: <name>:<value>\n array: [<index>]:<value>\n\n ' +
'In array the brackets are required.';
SRemoveChildren = 'This node has children, are you sure to remove them';
SDeleteNode = 'Delete node\n\n% ?';
{ TSynJSONTreeView }
procedure TSynJSONTreeView.ClearAll;
begin
SetJSONText('');
Items.Clear;
end;
constructor TSynJSONTreeView.Create(AOwner: TComponent);
begin
inherited;
OnCreateNodeClass := DoOnCreateNodeClass;
fAllowedEditionStates := [estEdit, estDelete, estInsert];
fFirstTime := True;
FVisibleChildrenCounts := true;
FVisibleByteSizes := false;
end;
procedure TSynJSONTreeView.LoadJson;
var
v: Variant;
begin
BackupEvents;
Items.Clear;
if IsActive then
begin
v := fJSONDocument;
Items.Clear;
if (v._Kind = dvObject) then
ProcessObject(nil, v, Unassigned)
else if (v._Kind = dvArray) then
ProcessArray(nil, v, Unassigned)
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
FullExpand;
end;
end;
function TSynJSONTreeView.ProcessPair(currNode: TSynJSONTreeNode; obj: Variant; aIndex: integer): TSynJSONTreeNode;
var
p: Variant;
begin
Result := nil;
p := obj._(aIndex);
if IsSimpleJsonValue(p) then
begin
AddChildFromJSON(currNode, obj, aIndex);
Exit;
end;
if (p._Kind = dvObject) then
ProcessObject(currNode, _CopyFast(p), obj.Name(aIndex))
else if (p._Kind = dvArray) then
ProcessArray(currNode, _CopyFast(p), obj.Name(aIndex))
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
end;
procedure TSynJSONTreeView.ProcessElement(currNode: TSynJSONTreeNode; arr: Variant; aIndex: integer);
var
v: Variant;
begin
v := arr._(aIndex);
if IsSimpleJsonValue(v) then
begin
AddChildFromJSON(currNode, arr, aIndex);
Exit;
end;
if (v._Kind = dvObject) then
ProcessObject(currNode, _CopyFast(v), IntToStr(aIndex))
else if (v._Kind = dvArray) then
ProcessArray(currNode, _CopyFast(v), aIndex)
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
end;
procedure TSynJSONTreeView.SetSynJSONDocument(const Value: Variant);
begin
if fJSONDocument <> Value then
begin
fJSONDocument := Value;
ClearAll;
if IsActive then
LoadJson;
end;
end;
procedure TSynJSONTreeView.SetVisibleByteSizes(const Value: boolean);
begin
if FVisibleByteSizes <> Value then
begin
FVisibleByteSizes := Value;
LoadJson;
end;
end;
procedure TSynJSONTreeView.SetVisibleChildrenCounts(const Value: boolean);
begin
if FVisibleChildrenCounts <> Value then
begin
FVisibleChildrenCounts := Value;
LoadJson;
end;
end;
function TSynJSONTreeView.VarToUTF8(const aVarData: Variant): RawUTF8;
begin
if (TVarData(aVarData).VType = varBoolean) then
begin
if TVarData(aVarData).VBoolean then
Result := 'true'
else
Result := 'false';
end else
Result := VariantToUTF8(aVarData);
end;
function TSynJSONTreeView.ToJSON: RawUTF8;
var
lW: TTextWriter;
procedure ProcessNode(aNode: TSynJSONTreeNode);
procedure CheckLastComma(aW: TTextWriter);
begin
if (aW.TextLength > 0) and (not (aW.LastChar in [',','[','{'])) then
aW.Add(',');
end;
var
lNode: TSynJSONTreeNode;
begin
lNode := aNode;
repeat
if Assigned(lNode) then begin
case lNode.JSONType of
jtnObject:
begin
CheckLastComma(lW);
// numeric name is excluded
if (lNode.Name <> '') and (StrToIntDef(lNode.Name, MaxInt) = MaxInt) then
lW.AddFieldName(lNode.Name);
lW.Add('{');
end;
jtnArray:
begin
CheckLastComma(lW);
if (lNode.Name <> '') then
lW.AddFieldName(lNode.Name);
lW.Add('[');
end;
jtnObjectContent:
begin
CheckLastComma(lW);
lW.AddFieldName(lNode.Name);
lW.AddVariant(lNode.Value);
lW.Add(',');
end;
jtnArrayContent:
begin
CheckLastComma(lW);
lW.AddVariant(lNode.Value);
lW.Add(',');
end;
end;
end;
if lNode.HasChildren then begin
ProcessNode(TSynJSONTreeNode(lNode.GetFirstChild));
lW.CancelLastComma;
case lNode.JSONType of
jtnObject: lW.Add('}');
jtnArray: lW.Add(']');
end;
end;
lNode := TSynJSONTreeNode(lNode.GetNextSibling);
until not Assigned(lNode);
end;
begin
lW := TTextWriter.CreateOwnedStream;
try
ProcessNode(TSynJSONTreeNode(Items.GetFirstNode));
lW.CancelLastComma;
lW.SetText(Result);
finally
lW.Free;
end;
end;
procedure TSynJSONTreeView.DoOnCreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
begin
NodeClass := TSynJSONTreeNode;
end;
function TSynJSONTreeView.VariantToRaw(const aVarData: Variant): Variant;
var
lVar: TSQLVar;
lTmp: RawByteString;
begin
if VarIs(aVarData, [varBoolean]) then
begin
Result := aVarData;
Exit;
end;
VariantToSQLVar(aVarData, lTmp, lVar);
with lVar do
case VType of
ftNull: Result := null;
ftCurrency: Result := VInt64;
ftUTF8:
if Assigned(VText) then
Result := RawUTF8(PUTF8Char(VText))
else
Result := '';
ftInt64: Result := VInt64;
ftDouble: Result := VDouble;
end;
end;
procedure TSynJSONTreeView.DoOnDblClick(Sender: TObject);
begin
if ReadOnly or (Selected.Count > 0) or (not (estEdit in fAllowedEditionStates)) then
Exit;
EditNode(TSynJSONTreeNode(Selected));
if Assigned(fOnDblClickBak) then
fOnDblClickBak(Sender);
end;
function TSynJSONTreeView.IsActive: Boolean;
begin
Result := (fJSONDocument <> Unassigned) and (not VarIsEmptyOrNull(fJSONDocument)) and (fJSONDocument._Kind <> dvUndefined);
end;
function TSynJSONTreeView.IsSimpleJsonValue(v: Variant): boolean;
begin
Result := VarIsEmptyOrNull(v) or VarIsNumeric(v) or VarIsStr(v) or VarIsStr(v);
end;
procedure TSynJSONTreeView.SetJSONText(const Value: RawUTF8);
begin
if fJSONText <> Value then
begin
fJSONText := Value;
if fJSONText <> '' then
ProcessJSONText
else
begin
TDocVariantData(fJSONDocument).Clear;
fJSONDocument := Unassigned;
end;
end;
end;
function TSynJSONTreeView.ProcessJSONText: boolean;
begin
if IsActive then
fJSONDocument.Clear;
TDocVariantData(fJSONDocument).InitJSON(fJSONText);
if (TDocVariantData(fJSONDocument).Kind = dvUndefined) then
raise ESynJSONTreeView.CreateUTF8(SInvalidJSON, [fJSONText]);
Result := IsActive;
end;
procedure TSynJSONTreeView.BackupEvents;
begin
if not fFirstTime then
Exit;
if not Assigned(fOnDblClickBak) and Assigned(OnDblClick) then
fOnDblClickBak := OnDblClick;
OnDblClick := DoOnDblClick;
if not Assigned(fOnEditingBak) and Assigned(OnEditing) then
fOnEditingBak := OnEditing;
OnEditing := DoOnEditing;
if not Assigned(fOnKeyDownBak) and Assigned(OnKeyDown) then
fOnKeyDownBak := OnKeyDown;
OnKeyDown := DoOnKeyDown;
fFirstTime := False;
end;
procedure TSynJSONTreeView.DoOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ReadOnly or (not (Key in [VK_DELETE, VK_INSERT, VK_RETURN])) or
((Key = VK_DELETE) and (not (estDelete in fAllowedEditionStates))) or
((Key = VK_RETURN) and (not (estEdit in fAllowedEditionStates))) or
((Key = VK_INSERT) and (not (estInsert in fAllowedEditionStates))) then
Exit;
case Key of
VK_DELETE: DeleteNode(TSynJSONTreeNode(Selected));
VK_INSERT: InsertNode(TSynJSONTreeNode(Selected), Unassigned);
VK_RETURN: EditNode(TSynJSONTreeNode(Selected));
end;
if Assigned(fOnKeyDownBak) then
fOnKeyDownBak(Sender, Key, Shift);
end;
procedure TSynJSONTreeView.UpdateNodeInfo(aNode: TSynJSONTreeNode);
procedure UpdateNodeText(aNode: TSynJSONTreeNode; const aText: RawUTF8);
begin
if not VarIsNull(aNode.Value) then
aNode.Text := aText
else
aNode.Text := aNode.Name + ': null';
end;
begin
case aNode.JSONType of
jtnObject: begin
if (not Assigned(aNode.Parent)) or (TSynJSONTreeNode(aNode.Parent).JSONType <> jtnArray) then
UpdateNodeText(aNode, aNode.Name + ' {}')
else
UpdateNodeText(aNode, FormatUTF8('[%] {}', [aNode.Name]));
end;
jtnArray:
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Name + ' []');
end;
case aNode.JSONType of
jtnObject, jtnArray: begin
if VisibleChildrenCounts then
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Text + ' (' + IntToStr(aNode.Value._Count) + ')')
else
UpdateNodeText(aNode, '');
if VisibleByteSizes then
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Text + ' (size: ' + IntToStr(Length(aNode.Value._JSON)) + ' bytes)')
else
UpdateNodeText(aNode, '');
end;
jtnObjectContent, jtnArrayContent: begin
UpdateNodeText(aNode, '');
end;
end;
end;
procedure TSynJSONTreeView.DeleteNode(Node: TSynJSONTreeNode; const aAsk: Boolean);
procedure RecalculateChildNames(aNode: TSynJSONTreeNode);
var
lNode: TSynJSONTreeNode;
I: Integer;
begin
lNode := TSynJSONTreeNode(aNode.GetFirstChild);
if not Assigned(lNode) then
Exit;
I := 0;
case lNode.JSONType of
jtnObject: begin
repeat
if Assigned(lNode) then begin
if (StrToIntDef(lNode.Name, MaxInt) <> MaxInt) then
lNode.Name := IntToStr(I);
UpdateNodeInfo(lNode);
Inc(I);
end;
lNode := TSynJSONTreeNode(lNode.getNextSibling);
until not Assigned(lNode);
end;
jtnArrayContent: begin
repeat
if Assigned(lNode) then begin
lNode.Name := IntToStr(I);
lNode.Text := UTF8ToString(FormatUTF8('[%]: %', [lNode.Name, lNode.Value]));
Inc(I);
end;
lNode := TSynJSONTreeNode(lNode.getNextSibling);
until not Assigned(lNode);
end;
end;
end;
procedure UpdateEmptyParent(aParent: TSynJSONTreeNode);
begin
if (aParent.Count = 0) then begin
aParent.Value := NULL;
case TSynJSONTreeNode(aParent.Parent).JSONType of
jtnObject: aParent.JSONType := jtnObjectContent;
jtnArray: aParent.JSONType := jtnArrayContent;
end;
end;
end;
var
lParent, lDeletedNode: TSynJSONTreeNode;
begin
if aAsk then
if (YesNo(UTF8ToString(FormatUTF8(SDeleteNode, [Node.Text])), '', False) <> mrYes) then
Exit;
lDeletedNode := Node.Clone;
lParent := TSynJSONTreeNode(Node.Parent);
Items.BeginUpdate;
try
Node.Delete;
Node := lDeletedNode;
// if node name is empty it means that the JSON object is empty
if (Node.Name = '') then begin
ClearAll;
Exit;
end;
case Node.JSONType of
jtnArray, jtnObject: begin
if (StrToIntDef(Node.Name, MaxInt) <> MaxInt) then
TDocVariantData(lParent.Value).Delete(StrToInt(Node.Name))
else
TDocVariantData(lParent.Value).Delete(Node.Name);
if (lParent.Count = 0) then
UpdateEmptyParent(lParent)
else
RecalculateChildNames(lParent);
UpdateNodeInfo(lParent);
end;
jtnArrayContent: begin
TDocVariantData(lParent.Value).Delete(StrToInt(Node.Name));
if (lParent.Count = 0) then
UpdateEmptyParent(lParent)
else
RecalculateChildNames(lParent);
UpdateNodeInfo(lParent);
end;
jtnObjectContent: begin
TDocVariantData(lParent.Value).Delete(Node.Name);
if (lParent.Count = 0) then
UpdateEmptyParent(lParent);
UpdateNodeInfo(lParent);
end;
end;
finally
Items.EndUpdate;
end;
end;
procedure TSynJSONTreeView.InsertNode(Node: TSynJSONTreeNode; const aJSON: Variant);
var
lJSON: Variant;
lPrompt: RawUTF8;
lValue: Variant;
I: Integer;
lCount: Integer;
lHandled: Boolean;
begin
if (aJSON = Unassigned) then begin
lPrompt := SInsertNewItemPrompt;
if Assigned(fOnCustomInput) then
fOnCustomInput(Self, Node, lPrompt, lValue, lHandled);
if not lHandled then
lValue := InputBox(SInsertNewItem, lPrompt, '');
if (lValue = '') then
Exit;
if (lValue[1] <> '[') then
if (lValue[1] <> '{') then
lValue := '{' + lValue + '}';
TDocVariantData(lJSON).InitJSON(lValue);
if (TDocVariantData(lJSON).Kind = dvUndefined) then
raise ESynJSONTreeView.CreateUTF8(SInvalidJSON, [lValue]);
end else
lJSON := aJSON;
// always will be an object the inserted node
lCount := lJSON._Count;
TDocVariantData(TSynJSONTreeNode(Node).Value).AddOrUpdateObject(lJSON);
for I := 0 to lCount-1 do begin
lValue := lJSON._(I);
if IsSimpleJsonValue(lValue) then begin
AddChildFromJSON(Node, lJSON, I);
end else
ProcessObject(Node, _CopyFast(lValue), lJSON.Name(I));
end;
UpdateNodeInfo(Node);
end;
function TSynJSONTreeView.AddChildFromJSON(const aNode: TSynJSONTreeNode; const aJSON: Variant; const aNameOrIndex: Variant): TSynJSONTreeNode;
var
lValue: Variant;
begin
Result := TSynJSONTreeNode(Items.AddChild(aNode, ''));
if not VarIsEmpty(aNameOrIndex) and (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then
lValue := VariantToRaw(aJSON._(aNameOrIndex))
else
lValue := aJSON;
if (TDocVariantData(lValue).Kind <> dvUndefined) then
Result.Value := _CopyFast(lValue)
else
Result.Value := lValue;
case TDocVariantData(aJSON).Kind of
dvObject:
if not VarIsEmpty(aNameOrIndex) then begin
// is name or index is numeric, the text property is assigned showing the name and value, otherwise is a
// container node
if (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then begin
Result.Name := aJSON.Name(aNameOrIndex);
Result.JSONType := jtnObjectContent;
Result.Text := Result.Name + ': ' + VarToUTF8(lValue);
end else begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnObject;
end;
end;
dvArray:
if not VarIsEmpty(aNameOrIndex) then begin
// is name or index is numeric, the text property is assigned showing the name and value, otherwise is a
// container node
if (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnArrayContent;
Result.Text := IntToStr(aNameOrIndex) + ': ' + VarToUTF8(lValue);
end else begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnArray;
end;
end;
end;
end;
procedure TSynJSONTreeView.ProcessObject(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
var
lCount: Integer;
I: Integer;
lCurrNode: TSynJSONTreeNode;
begin
lCurrNode := AddChildFromJSON(aCurrNode, aValue, aNameOrIndex);
UpdateNodeInfo(lCurrNode);
lCount := aValue._Count;
for I := 0 to lCount - 1 do
ProcessPair(lCurrNode, _CopyFast(aValue), I);
end;
procedure TSynJSONTreeView.ProcessArray(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
var
lCount: Integer;
I: Integer;
lCurrNode: TSynJSONTreeNode;
begin
lCurrNode := AddChildFromJSON(aCurrNode, aValue, aNameOrIndex);
UpdateNodeInfo(lCurrNode);
lCount := aValue._Count;
for I := 0 to lCount - 1 do
ProcessElement(lCurrNode, aValue, I);
end;
procedure TSynJSONTreeView.EditNode(Node: TSynJSONTreeNode);
var
lPrompt: RawUTF8;
lJSON, lValue, lDestValue: Variant;
lHandled: Boolean;
begin
lPrompt := UTF8ToString(TSynJSONTreeNode(Selected).Name);
if (Selected.Text[1] = '[') then
lPrompt := FormatUTF8('[%]', [lPrompt]);
lValue := '';
if not VarIsNull(TSynJSONTreeNode(Selected).Value) then
lValue := TSynJSONTreeNode(Selected).Value;
if Assigned(fOnCustomInput) then
fOnCustomInput(Self, Node, lPrompt, lValue, lHandled);
if not lHandled then
lValue := InputBox(SEnterValue, lPrompt, lValue);
// if not modification, exit
if (lValue = TSynJSONTreeNode(Selected).Value) then
Exit;
// remove children if input data was null
if (TDocVariantData(lValue).Kind = dvUndefined) and
((lValue = '') or IdemPChar(pointer(VariantToUTF8(lValue)), 'NULL')) and
Selected.HasChildren then begin
if (YesNo(SRemoveChildren, '', False) = mrYes) then begin
Selected.DeleteChildren;
TSynJSONTreeNode(Selected).Value := NULL;
UpdateNodeInfo(TSynJSONTreeNode(Selected));
Exit;
end;
end;
TDocVariantData(lJSON).InitJSON(lValue);
case TDocVariantData(lJSON).Kind of
dvUndefined: begin
Selected.Text := FormatUTF8('%: %', [lPrompt, lValue]);
lDestValue := TSynJSONTreeNode(Selected).Value;
VariantToVariant(lValue, lDestValue);
TSynJSONTreeNode(Selected).Value := lDestValue;
if Assigned(Selected.Parent) then begin
case TSynJSONTreeNode(Selected.Parent).JSONType of
jtnObject: TSynJSONTreeNode(Selected).JSONType := jtnObjectContent;
jtnArray: begin
TSynJSONTreeNode(Selected).JSONType := jtnArrayContent;
TDocVariantData(TSynJSONTreeNode(Selected.Parent).Value).Values[StrToInt(TSynJSONTreeNode(Selected).Name)] :=
TSynJSONTreeNode(Selected).Value;
end;
end;
end;
end;
else
case TDocVariantData(lJSON).Kind of
dvObject: TSynJSONTreeNode(Selected).JSONType := jtnObject;
dvArray: TSynJSONTreeNode(Selected).JSONType := jtnArray;
end;
Node.Value := _CopyFast(lJSON);
if Node.HasChildren then
Node.DeleteChildren;
InsertNode(Node, lJSON);
UpdateNodeInfo(Node);
end;
end;
procedure TSynJSONTreeView.LoadFromFile(const aFileName: TFileName);
begin
if (aFileName = '') then
Exit;
SetJSONText(StringFromFile(aFileName));
LoadJSON;
end;
procedure TSynJSONTreeView.SaveToFile(const aFileName: TFileName);
var
lJSON: RawUTF8;
begin
if (aFileName = '') then
Exit;
lJSON := ToJSON;
FileFromString(lJSON, aFileName);
end;
procedure TSynJSONTreeView.DoOnEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean);
begin
AllowEdit := (estEdit in fAllowedEditionStates);
if AllowEdit and Assigned(fOnEditingBak) then
fOnEditingBak(Sender, Node, AllowEdit);
end;
procedure TSynJSONTreeView.VariantToVariant(aSource: Variant; var aDest: Variant);
var
lVar: TSQLVar;
lTmp: RawByteString;
begin
VariantToSQLVar(aDest, lTmp, lVar);
with lVar do
case VType of
ftNull: aDest := aSource;
ftCurrency: aDest := aSource.VInt64;
ftUTF8: aDest := VariantToUTF8(aSource);
ftInt64: begin
if (aSource <> '') then
if (UpperCaseU(aSource) <> 'TRUE') and (UpperCaseU(aSource) <> 'FALSE') then begin
aDest := StrToInt64Def(aSource, MaxInt);
if (aDest = MaxInt) then
raise ESynJSONTreeView.CreateUTF8('Invalid input data type: %', [aSource]);
end else
if (UpperCaseU(aSource) = 'TRUE') then
aDest := True
else
aDest := False;
end;
ftDouble: aDest := aSource.VDouble;
end;
end;
{ TSynJSONTreeNode }
function TSynJSONTreeNode.Clone: TSynJSONTreeNode;
begin
Result := TSynJSONTreeNode.Create(nil);
Result.Name := fName;
Result.JSONType := fJSONType;
Result.Value := fValue;
end;
end.

View File

@@ -0,0 +1,48 @@
object frmLevel: TfrmLevel
Left = 746
Top = 164
Width = 346
Height = 490
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object chklstLevel: TCheckListBox
Left = 0
Top = 0
Width = 338
Height = 423
Align = alClient
ItemHeight = 13
TabOrder = 0
end
object pnlBottom: TPanel
Left = 0
Top = 423
Width = 338
Height = 36
Align = alBottom
TabOrder = 1
object btnOK: TBitBtn
Left = 84
Top = 6
Width = 75
Height = 25
TabOrder = 0
Kind = bkOK
end
object btnCancel: TBitBtn
Left = 180
Top = 6
Width = 75
Height = 25
TabOrder = 1
Kind = bkCancel
end
end
end

View File

@@ -0,0 +1,73 @@
unit fLevel;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ExtCtrls, CheckLst,
SynCommons;
type
TfrmLevel = class(TForm)
chklstLevel: TCheckListBox;
pnlBottom: TPanel;
btnOK: TBitBtn;
btnCancel: TBitBtn;
private
{ Private declarations }
public
{ Public declarations }
procedure FillListBox(const aJSON: Variant);
function ToJSON: RawUTF8;
end;
var
frmLevel: TfrmLevel;
implementation
{$R *.dfm}
{ TfrmLevel }
procedure TfrmLevel.FillListBox(const aJSON: Variant);
var
I, J, lCount: Integer;
begin
lCount := TDocVariantData(aJSON).Count;
chklstLevel.Items.BeginUpdate;
try
for I := 0 to lCount-1 do
begin
J := chklstLevel.Items.Add(TDocVariantData(aJSON).Values[I]);
chklstLevel.Checked[J] := True;
end;
finally
chklstLevel.Items.EndUpdate;
end;
end;
function TfrmLevel.ToJSON: RawUTF8;
var
lW: TTextWriter;
I: Integer;
begin
lW := TTextWriter.CreateOwnedStream;
try
lW.Add('[');
for I := 0 to chklstLevel.Items.Count-1 do
if chklstLevel.Checked[I] then
begin
lW.Add('"');
lW.AddString(chklstLevel.Items[I]);
lW.Add('"', ',');
end;
lW.CancelLastComma;
lW.Add(']');
lW.SetText(Result);
finally
lW.Free;
end;
end;
end.

View File

@@ -0,0 +1,430 @@
object frmJSONEditor: TfrmJSONEditor
Left = 600
Top = 213
Width = 466
Height = 643
Caption = 'Delphi JSON Editor'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 458
Height = 33
Align = alTop
TabOrder = 0
object BitBtn1: TBitBtn
Left = 5
Top = 4
Width = 75
Height = 25
Action = EditPaste1
Caption = '&Paste'
TabOrder = 0
Glyph.Data = {
36040000424D3604000000000000360000002800000010000000100000000100
2000000000000004000000000000000000000000000000000000FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF008000000080000000800000008000
0000800000008000000080000000800000008000000080000000FF00FF000000
00000000000000000000000000000000000080000000FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0080000000000000008686
86000080800086868600008080008686860080000000FFFFFF00000000000000
000000000000000000000000000000000000FFFFFF0080000000000000000080
80008686860000808000868686000080800080000000FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0080000000000000008686
86000080800086868600008080008686860080000000FFFFFF00000000000000
000000000000FFFFFF0080000000800000008000000080000000000000000080
80008686860000808000868686000080800080000000FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF0080000000FFFFFF0080000000FF00FF00000000008686
86000080800086868600008080008686860080000000FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF008000000080000000FF00FF00FF00FF00000000000080
8000868686000080800086868600008080008000000080000000800000008000
000080000000800000008000000000000000FF00FF00FF00FF00000000008686
8600008080008686860000808000868686000080800086868600008080008686
860000808000868686000080800000000000FF00FF00FF00FF00000000000080
8000868686000000000000000000000000000000000000000000000000000000
000000000000868686008686860000000000FF00FF00FF00FF00000000008686
86008686860000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF0000000000868686000080800000000000FF00FF00FF00FF00000000000080
800086868600008080000000000000FFFF00000000000000000000FFFF000000
000086868600008080008686860000000000FF00FF00FF00FF00FF00FF000000
00000000000000000000000000000000000000FFFF0000FFFF00000000000000
0000000000000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF0000000000000000000000000000000000FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00}
end
object btnClear: TBitBtn
Left = 91
Top = 4
Width = 75
Height = 25
Action = EditClear1
Caption = '&Clear'
TabOrder = 1
Glyph.Data = {
36040000424D3604000000000000360000002800000010000000100000000100
2000000000000004000000000000000000000000000000000000FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF0000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF0000000000FF00FF00FF00FF00FF00
FF00000000000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF0000000000000000000000000000000000FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF0000000000FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00000000000000000000000000FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF0000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00000000000000000000000000FF00FF00FF00FF00FF00
FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00000000000000000000000000FF00FF00FF00
FF000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF000000000000000000000000000000
000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF0000000000000000000000
0000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF000000000000000000000000000000
000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00000000000000000000000000FF00FF00FF00
FF000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF0000000000000000000000000000000000FF00FF00FF00FF00FF00
FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF0000000000000000000000000000000000FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF000000000000000000FF00FF00FF00FF00FF00FF00FF00
FF00000000000000000000000000FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF0000000000FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00}
end
object btnSaveJSON: TBitBtn
Left = 178
Top = 4
Width = 75
Height = 25
Action = EditSave1
Caption = '&Save'
TabOrder = 2
Glyph.Data = {
36040000424D3604000000000000360000002800000010000000100000000100
2000000000000004000000000000000000000000000000000000C8C8C8008F8D
8D008B8787008784840087848400878484008784840087848400878484008784
8400878484008784840087848400908E8E008F8D8D00DADADA008E8E8E008181
80006E6C6C00A6A2A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2
A200A6A2A200A6A2A200A6A2A200272424007C7B7B009C9A9A008E8C8C001F1C
1B008E8A8900EAE5E400E8E4E300E8E4E300E8E4E300E8E4E300E8E4E300E8E4
E300E8E4E300E8E4E300EBE7E600353130001F1C1B009C9B9B008F8D8D00211E
1E008D8A8900EFEAE900EFEAE900EFEAE900EFEAE900EFEAE900EFEAE900EFEA
E900EFEAE900EFEAE900EFEAE90036333300211E1E009C9C9C00908F8F002421
21008C898900E7E2E100E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0
DF00E5E0DF00E5E0DF00E8E2E10039363600242121009F9C9C00929090002724
23008C878700E4DEDE00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DD
DD00E3DDDD00E3DDDD00E6E0E0003B383800272423009F9C9C00939191002926
26008A878700E3DCDC00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DB
DA00E1DBDA00E1DBDA00E4DEDD003D3A3A00292626009F9C9C00949392002C29
280075727000CDC7C600CEC7C700CEC7C700CEC7C700CEC7C700CEC7C700CEC7
C700CEC7C700CEC7C700CAC4C400383433002C2928009F9F9F00969494002F2C
2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C
2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B009F9F9F0097959500312E
2D00312E2D0043403F00423F3F00434040004340400043404000434040004340
4000434040003F3C3C003C3B3A0038343300312E2D00A09F9F009A9898003734
330037343300807E7E00B5B3B30095939300B5B3B300B7B5B500B7B5B500B7B5
B500B7B5B50098969600201C1C00423F3E0037343300A2A0A0009E9C9B003F3B
3A003F3B3A0089868600ABAAAA0027232300B1B1B100B5B4B400B5B4B400B5B4
B400B5B4B4009E9A9A0027232300484545003F3B3A00A5A3A300A2A09F004743
4100474341008C898900ABAAAA002F2A2A00B1B1B100B5B4B400B5B4B400B5B4
B400B5B4B400A09C9C002F2A2A00504C4B0047434100A6A5A500A6A4A2004F4B
48004F4B48009A989800BFBEBE0037313100C8C7C700D1D0D000D1D0D000D1D0
D000D1D0D000AFABAB0037313100585352004F4B4800A8A6A600D6D6D5005E5B
5A0057535000A9A7A600E5E4E400C5C2C200EAE9E900EEEDED00EEEDED00EEED
ED00EEEDED00BDBBBB003F3838005F5B590057535000ABA9A800FF00FF00D9D8
D800AFADAC00A9A9A9009A9797009A9797009A9797009A9797009A9997009A99
97009A999700A6A5A500B7B7B700B5B5B300B0AFAE00D4D4D400}
end
object btnLoad: TBitBtn
Left = 264
Top = 4
Width = 75
Height = 25
Action = actEditLoad1
Caption = 'Load'
TabOrder = 3
Glyph.Data = {
36040000424D3604000000000000360000002800000010000000100000000100
2000000000000004000000000000000000000000000000000000FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00D2CE
CD00C4B09F00A4917E00A4917E00A4917E00A4917E00A4917E00A4917E00A491
7E00A4917E00A18C7B00C5B29F00CFC8BF00FF00FF00FF00FF00FF00FF008D89
8500A87B5500C7905D00C7905D00C7905D00C7905D00C78F5D00C78F5C00C78F
5C00C78F5C00C78F5C00C78F5C00BA895C00E4DCD600FF00FF00FF00FF00A2A2
A20087756500CA986A00CE9B6C00CE9B6C00CE9B6C00CE9B6C00CD9B6B00CD9B
6B00CD9A6B00CD9A6B00CD9A6B00CA976700DDC2AC00FF00FF00FF00FF009393
93009A928C00C59B7500D2A57B00D1A37800D0A17500D0A07400CFA07300CFA0
7300CF9F7200CF9F7200CF9F7200D0A17400CDA48500FF00FF00FF00FF009393
9300BAB8B600C6A28300D5A98200D3A87F00D3A77E00D3A77E00D3A77D00D3A6
7D00D2A57B00D1A37800CFA07300CF9F7200CAA07900FF00FF00E9E9E9009292
9200B8B8B800C1A08500D6AD8600D4AA8100D4A98100D4A98000D4A88000D4A8
7F00D3A87F00D3A77E00D3A67D00D0A17500CFA27900E2CEBC00E3E3E3009797
9700B0B0B000BCA48D00D5AD8800D5AD8600D5AC8500D5AC8400D5AB8400D4AB
8300D4AB8200D4AA8200D4AA8100D4A98000D4A87F00D4B29600DADADA00ABAB
AB00A4A4A400B2A29400CAA58300CDA98900CDA98900CDA98900CDA98900CDA9
8900CDA98900CDA98900C6A18400CEAA8800D3AD8C00D0AB8D00CFCFCF00B5B5
B50099999900B8B7B700A8A5A100A7A4A100A7A4A100A8A5A100A8A5A100A9A5
A200ADA8A400AEA9A400B0ABA600A8A29C00FF00FF00FF00FF00C7C7C700B4B4
B400A1A1A100BBBBBB00BDBDBD00B5B5B500B6B6B600B6B6B600B6B6B600B6B6
B600B7B7B700B8B8B800BEBEBE00B5B5B500E4E4E400FF00FF00C1C1C100B1B1
B100A8A8A800ADADAD00D2D2D200BEBEBE00BCBCBC00BDBDBD00BDBDBD00BDBD
BD00BEBEBE00C0C0C000C4C4C400CCCCCC00D7D7D700FF00FF00BEBEBE00B6B6
B600B1B1B100A2A2A200E3E3E300D0D0D000CDCDCD00CECECE00CFCFCF00CFCF
CF00CFCFCF00D3D3D300DDDDDD00E5E5E500D5D5D500FF00FF00FF00FF00D1D1
D100D4D4D400B4B4B400E8E8E800E5E5E500E4E4E400E3E3E300E3E3E300E3E3
E300E3E3E300E4E4E400E4E4E400E7E7E700CFCFCF00FF00FF00FF00FF00FF00
FF00FF00FF00FF00FF00D5D5D500D7D7D700D7D7D700D8D8D800D9D9D900DADA
DA00DCDCDC00DDDDDD00DDDDDD00DFDFDF00EAEAEA00FF00FF00}
end
end
object pnlJSONTreeView: TPanel
Left = 0
Top = 33
Width = 458
Height = 579
Align = alClient
Caption = 'JSON TreeView'
TabOrder = 1
end
object ActionList1: TActionList
Images = ImageList1
Left = 327
Top = 150
object EditPaste1: TEditPaste
Category = 'Edit'
Caption = '&Paste'
Hint = 'Paste|Inserts Clipboard contents'
ImageIndex = 0
ShortCut = 16470
OnExecute = EditPaste1Execute
end
object EditClear1: TEditDelete
Category = 'Edit'
Caption = '&Clear'
Enabled = False
Hint = 'Clear|Clears the selection'
ImageIndex = 1
ShortCut = 16430
OnExecute = EditClear1Execute
end
object ActionToggleVisibleChildrenCounts: TAction
AutoCheck = True
Caption = 'Visible Children Counts'
OnExecute = ActionToggleVisibleChildrenCountsExecute
end
object ActionToggleVisibleByteSizes: TAction
AutoCheck = True
Caption = 'Visible Byte Sizes'
OnExecute = ActionToggleVisibleByteSizesExecute
end
object EditSave1: TAction
Category = 'Edit'
Caption = '&Save'
Enabled = False
Hint = 'Save|Save to JSON file'
ImageIndex = 2
ShortCut = 16467
OnExecute = EditSave1Execute
end
object actEditLoad1: TAction
Category = 'Edit'
Caption = 'Load'
Hint = 'Load|Load JSON from file'
ImageIndex = 3
ShortCut = 16463
OnExecute = actEditLoad1Execute
end
end
object ImageList1: TImageList
Left = 335
Top = 238
Bitmap = {
494C0101040008000C0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000001000000001002000000000000010
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000C8C8C8008F8D8D008B8787008784
8400878484008784840087848400878484008784840087848400878484008784
840087848400908E8E008F8D8D00DADADA000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000080000000800000008000000080000000800000008000
0000800000008000000080000000800000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000008E8E8E00818180006E6C6C00A6A2
A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2A200A6A2
A200A6A2A200272424007C7B7B009C9A9A000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000080000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF00FFFFFF00800000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000008E8C8C001F1C1B008E8A8900EAE5
E400E8E4E300E8E4E300E8E4E300E8E4E300E8E4E300E8E4E300E8E4E300E8E4
E300EBE7E600353130001F1C1B009C9B9B0000000000D2CECD00C4B09F00A491
7E00A4917E00A4917E00A4917E00A4917E00A4917E00A4917E00A4917E00A18C
7B00C5B29F00CFC8BF0000000000000000000000000086868600008080008686
8600008080008686860080000000FFFFFF000000000000000000000000000000
00000000000000000000FFFFFF00800000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000008F8D8D00211E1E008D8A8900EFEA
E900EFEAE900EFEAE900EFEAE900EFEAE900EFEAE900EFEAE900EFEAE900EFEA
E900EFEAE90036333300211E1E009C9C9C00000000008D898500A87B5500C790
5D00C7905D00C7905D00C7905D00C78F5D00C78F5C00C78F5C00C78F5C00C78F
5C00C78F5C00BA895C00E4DCD600000000000000000000808000868686000080
8000868686000080800080000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
FF00FFFFFF00FFFFFF00FFFFFF00800000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000908F8F00242121008C898900E7E2
E100E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0DF00E5E0
DF00E8E2E10039363600242121009F9C9C0000000000A2A2A20087756500CA98
6A00CE9B6C00CE9B6C00CE9B6C00CE9B6C00CD9B6B00CD9B6B00CD9A6B00CD9A
6B00CD9A6B00CA976700DDC2AC00000000000000000086868600008080008686
8600008080008686860080000000FFFFFF00000000000000000000000000FFFF
FF00800000008000000080000000800000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000092909000272423008C878700E4DE
DE00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DDDD00E3DD
DD00E6E0E0003B383800272423009F9C9C0000000000939393009A928C00C59B
7500D2A57B00D1A37800D0A17500D0A07400CFA07300CFA07300CF9F7200CF9F
7200CF9F7200D0A17400CDA48500000000000000000000808000868686000080
8000868686000080800080000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
FF0080000000FFFFFF0080000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000093919100292626008A878700E3DC
DC00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DBDA00E1DB
DA00E4DEDD003D3A3A00292626009F9C9C000000000093939300BAB8B600C6A2
8300D5A98200D3A87F00D3A77E00D3A77E00D3A77D00D3A67D00D2A57B00D1A3
7800CFA07300CF9F7200CAA07900000000000000000086868600008080008686
8600008080008686860080000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF
FF00800000008000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000949392002C29280075727000CDC7
C600CEC7C700CEC7C700CEC7C700CEC7C700CEC7C700CEC7C700CEC7C700CEC7
C700CAC4C400383433002C2928009F9F9F00E9E9E90092929200B8B8B800C1A0
8500D6AD8600D4AA8100D4A98100D4A98000D4A88000D4A87F00D3A87F00D3A7
7E00D3A67D00D0A17500CFA27900E2CEBC000000000000808000868686000080
8000868686000080800080000000800000008000000080000000800000008000
0000800000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000969494002F2C2B002F2C2B002F2C
2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C2B002F2C
2B002F2C2B002F2C2B002F2C2B009F9F9F00E3E3E30097979700B0B0B000BCA4
8D00D5AD8800D5AD8600D5AC8500D5AC8400D5AB8400D4AB8300D4AB8200D4AA
8200D4AA8100D4A98000D4A87F00D4B296000000000086868600008080008686
8600008080008686860000808000868686000080800086868600008080008686
8600008080000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000097959500312E2D00312E2D004340
3F00423F3F004340400043404000434040004340400043404000434040003F3C
3C003C3B3A0038343300312E2D00A09F9F00DADADA00ABABAB00A4A4A400B2A2
9400CAA58300CDA98900CDA98900CDA98900CDA98900CDA98900CDA98900CDA9
8900C6A18400CEAA8800D3AD8C00D0AB8D000000000000808000868686000000
0000000000000000000000000000000000000000000000000000000000008686
8600868686000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000009A9898003734330037343300807E
7E00B5B3B30095939300B5B3B300B7B5B500B7B5B500B7B5B500B7B5B5009896
9600201C1C00423F3E0037343300A2A0A000CFCFCF00B5B5B50099999900B8B7
B700A8A5A100A7A4A100A7A4A100A8A5A100A8A5A100A9A5A200ADA8A400AEA9
A400B0ABA600A8A29C0000000000000000000000000086868600868686000000
0000000000000000000000000000000000000000000000000000000000008686
8600008080000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000009E9C9B003F3B3A003F3B3A008986
8600ABAAAA0027232300B1B1B100B5B4B400B5B4B400B5B4B400B5B4B4009E9A
9A0027232300484545003F3B3A00A5A3A300C7C7C700B4B4B400A1A1A100BBBB
BB00BDBDBD00B5B5B500B6B6B600B6B6B600B6B6B600B6B6B600B7B7B700B8B8
B800BEBEBE00B5B5B500E4E4E400000000000000000000808000868686000080
80000000000000FFFF00000000000000000000FFFF0000000000868686000080
8000868686000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000A2A09F0047434100474341008C89
8900ABAAAA002F2A2A00B1B1B100B5B4B400B5B4B400B5B4B400B5B4B400A09C
9C002F2A2A00504C4B0047434100A6A5A500C1C1C100B1B1B100A8A8A800ADAD
AD00D2D2D200BEBEBE00BCBCBC00BDBDBD00BDBDBD00BDBDBD00BEBEBE00C0C0
C000C4C4C400CCCCCC00D7D7D700000000000000000000000000000000000000
0000000000000000000000FFFF0000FFFF000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000A6A4A2004F4B48004F4B48009A98
9800BFBEBE0037313100C8C7C700D1D0D000D1D0D000D1D0D000D1D0D000AFAB
AB0037313100585352004F4B4800A8A6A600BEBEBE00B6B6B600B1B1B100A2A2
A200E3E3E300D0D0D000CDCDCD00CECECE00CFCFCF00CFCFCF00CFCFCF00D3D3
D300DDDDDD00E5E5E500D5D5D500000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000D6D6D5005E5B5A0057535000A9A7
A600E5E4E400C5C2C200EAE9E900EEEDED00EEEDED00EEEDED00EEEDED00BDBB
BB003F3838005F5B590057535000ABA9A80000000000D1D1D100D4D4D400B4B4
B400E8E8E800E5E5E500E4E4E400E3E3E300E3E3E300E3E3E300E3E3E300E4E4
E400E4E4E400E7E7E700CFCFCF00000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000D9D8D800AFADAC00A9A9
A9009A9797009A9797009A9797009A9797009A9997009A9997009A999700A6A5
A500B7B7B700B5B5B300B0AFAE00D4D4D4000000000000000000000000000000
0000D5D5D500D7D7D700D7D7D700D8D8D800D9D9D900DADADA00DCDCDC00DDDD
DD00DDDDDD00DFDFDF00EAEAEA0000000000424D3E000000000000003E000000
2800000040000000100000000100010000000000800000000000000000000000
000000000000000000000000FFFFFF00FFFFFFFF0000FFFFFC00FFFF0000FFFF
8000EFFD000080030000C7FF000080010000C3FB000080010000E3F700008001
0001F1E7000080010003F8CF000000000003FC1F000000000003FE3F00000000
0003FC1F000000030FC3F8CF000000010003E1E7000000018007C3F300000001
F87FC7FD00008001FFFFFFFF8000F00100000000000000000000000000000000
000000000000}
end
object PopupMenu1: TPopupMenu
Left = 323
Top = 92
object VisibleChildrenCounts1: TMenuItem
Action = ActionToggleVisibleChildrenCounts
AutoCheck = True
end
object VisibleByteSizes1: TMenuItem
Action = ActionToggleVisibleByteSizes
AutoCheck = True
end
end
object dlgSave: TSaveDialog
DefaultExt = 'json'
Filter = 'JSON file|*.json'
Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing]
Title = 'Save JSON file'
Left = 184
Top = 41
end
object dlgOpenJSONFile: TOpenDialog
DefaultExt = 'json'
Filter = 'JSON File|*.json'
Options = [ofHideReadOnly, ofPathMustExist, ofFileMustExist, ofEnableSizing]
Title = 'Select a JSON file'
Left = 272
Top = 97
end
end

View File

@@ -0,0 +1,155 @@
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, Buttons, ActnList, StdActns, ImgList, Menus,
ComCtrls,
SynJSONTreeView,
SynCommons;
type
TfrmJSONEditor = class(TForm)
Panel1: TPanel;
ActionList1: TActionList;
ImageList1: TImageList;
EditPaste1: TEditPaste;
EditClear1: TEditDelete;
BitBtn1: TBitBtn;
btnClear: TBitBtn;
ActionToggleVisibleChildrenCounts: TAction;
ActionToggleVisibleByteSizes: TAction;
PopupMenu1: TPopupMenu;
VisibleChildrenCounts1: TMenuItem;
VisibleByteSizes1: TMenuItem;
pnlJSONTreeView: TPanel;
btnSaveJSON: TBitBtn;
EditSave1: TAction;
dlgSave: TSaveDialog;
actEditLoad1: TAction;
btnLoad: TBitBtn;
dlgOpenJSONFile: TOpenDialog;
procedure ActionToggleVisibleChildrenCountsExecute(Sender: TObject);
procedure ActionToggleVisibleByteSizesExecute(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure EditPaste1Execute(Sender: TObject);
procedure EditClear1Execute(Sender: TObject);
procedure EditSave1Execute(Sender: TObject);
procedure actEditLoad1Execute(Sender: TObject);
private
{ Private declarations }
JSONTreeView1: TSynJSONTreeView;
procedure DoOnCustomInput(Sender: TObject; Node: TSynJSONTreeNode;
var Prompt: RawUTF8; var Value: Variant; var Handled: Boolean);
procedure SetJSON(const aJSON: RawUTF8);
procedure ToogleButtons;
public
{ Public declarations }
end;
var
frmJSONEditor: TfrmJSONEditor;
implementation
uses
Clipbrd,
fLevel;
{$R *.dfm}
procedure TfrmJSONEditor.FormCreate(Sender: TObject);
begin
JSONTreeView1 := TSynJSONTreeView.Create(pnlJSONTreeView);
JSONTreeView1.PopupMenu := PopupMenu1;
JSONTreeView1.Align := alClient;
JSONTreeView1.Parent := pnlJSONTreeView;
JSONTreeView1.OnCustomInput := DoOnCustomInput;
ActionToggleVisibleByteSizes.Checked := JSONTreeView1.VisibleByteSizes;
ActionToggleVisibleChildrenCounts.Checked := JSONTreeView1.VisibleChildrenCounts;
end;
procedure TfrmJSONEditor.ActionToggleVisibleByteSizesExecute(Sender: TObject);
begin
JSONTreeView1.VisibleByteSizes := not JSONTreeView1.VisibleByteSizes;
end;
procedure TfrmJSONEditor.ActionToggleVisibleChildrenCountsExecute(Sender: TObject);
begin
JSONTreeView1.VisibleChildrenCounts := not JSONTreeView1.VisibleChildrenCounts;
end;
procedure TfrmJSONEditor.EditClear1Execute(Sender: TObject);
begin
JSONTreeView1.ClearAll;
ToogleButtons;
end;
procedure TfrmJSONEditor.EditPaste1Execute(Sender: TObject);
begin
SetJSON(Clipboard.AsText);
ToogleButtons;
end;
procedure TfrmJSONEditor.EditSave1Execute(Sender: TObject);
begin
if dlgSave.Execute then
JSONTreeView1.SaveToFile(dlgSave.FileName);
end;
procedure TfrmJSONEditor.actEditLoad1Execute(Sender: TObject);
begin
if dlgOpenJSONFile.Execute then
begin
SetJSON(StringFromFile(dlgOpenJSONFile.FileName));
ToogleButtons;
end;
end;
procedure TfrmJSONEditor.SetJSON(const aJSON: RawUTF8);
begin
JSONTreeView1.JsonText := aJSON;
JSONTreeView1.Items.BeginUpdate;
JSONTreeView1.LoadJSON;
JSONTreeView1.Items.EndUpdate;
end;
procedure TfrmJSONEditor.ToogleButtons;
begin
EditSave1.Enabled := (JSONTreeView1.Items.Count > 0);
EditClear1.Enabled := (JSONTreeView1.Items.Count > 0);
end;
procedure TfrmJSONEditor.DoOnCustomInput(Sender: TObject;
Node: TSynJSONTreeNode; var Prompt: RawUTF8; var Value: Variant; var Handled: Boolean);
procedure ProcessLevel;
begin
if (TDocVariantData(Value).Kind <> dvArray) then
begin
MessageDlg('Level node is not an array', mtError, [mbOK], 0);
Exit;
end;
with TFrmLevel.Create(nil) do
begin
Caption := Prompt;
FillListBox(Value);
ShowModal;
if (ModalResult = mrOK) then
begin
TDocVariantData(Value).Clear;
TDocVariantData(Value).InitJSON(ToJSON);
end;
Handled := True;
end;
end;
begin
if IdemPChar(pointer(Prompt), 'LEVEL') then
ProcessLevel;
end;
end.

View File

@@ -0,0 +1,23 @@
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
PROJECTS = Server.exe FishFactSyn.exe
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
Server.exe: Server.dpr
$(DCC)
FishFactSyn.exe: FishFactSyn\FishFactSyn.dpr
$(DCC)

View File

@@ -0,0 +1,186 @@
object Form1: TForm1
Left = 341
Top = 83
BorderIcons = [biSystemMenu, biMinimize]
BorderStyle = bsSingle
Caption = 'FISH FACTS'
ClientHeight = 584
ClientWidth = 542
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlack
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = True
ShowHint = True
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 6
Top = 8
Width = 299
Height = 249
Hint = 'Scroll grid below to see other fish'
ParentShowHint = False
ShowHint = True
TabOrder = 0
object DBLabel1: TDBText
Left = 4
Top = 220
Width = 249
Height = 24
DataField = 'Common_Name'
DataSource = DataSource1
Font.Charset = DEFAULT_CHARSET
Font.Color = clRed
Font.Height = -19
Font.Name = 'MS Serif'
Font.Style = [fsBold, fsItalic]
ParentFont = False
end
object img: TImage
Left = 8
Top = 8
Width = 281
Height = 201
end
object btnUpload: TButton
Left = 212
Top = 216
Width = 75
Height = 25
Caption = 'Upload'
Enabled = False
TabOrder = 0
OnClick = btnUploadClick
end
end
object Panel2: TPanel
Left = 310
Top = 8
Width = 225
Height = 22
TabOrder = 1
object Label1: TLabel
Left = 7
Top = 4
Width = 56
Height = 13
Caption = 'About the'
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlue
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
ParentFont = False
end
object DBLabel2: TDBText
Left = 67
Top = 4
Width = 56
Height = 13
AutoSize = True
DataField = 'Common_Name'
DataSource = DataSource1
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlue
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
ParentFont = False
end
end
object Panel3: TPanel
Left = 312
Top = 32
Width = 223
Height = 187
BevelOuter = bvLowered
TabOrder = 2
object DBMemo1: TDBMemo
Left = 3
Top = 2
Width = 217
Height = 183
BorderStyle = bsNone
Color = clSilver
Ctl3D = False
DataField = 'Notes'
DataSource = DataSource1
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlack
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
ParentCtl3D = False
ParentFont = False
ScrollBars = ssVertical
TabOrder = 0
end
end
object Panel4: TPanel
Left = 0
Top = 260
Width = 542
Height = 324
Align = alBottom
BevelInner = bvRaised
BorderStyle = bsSingle
ParentShowHint = False
ShowHint = True
TabOrder = 3
object DBGrid1: TDBGrid
Left = 2
Top = 12
Width = 534
Height = 281
Hint = 'Scroll up/down to see other fish!'
Align = alBottom
DataSource = DataSource1
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
ParentFont = False
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clBlack
TitleFont.Height = -11
TitleFont.Name = 'MS Sans Serif'
TitleFont.Style = []
end
object dbnvgr1: TDBNavigator
Left = 2
Top = 293
Width = 534
Height = 25
DataSource = DataSource1
Align = alBottom
TabOrder = 1
OnClick = dbnvgr1Click
end
end
object DataSource1: TDataSource
Left = 19
Top = 193
end
object dlgOpenPic1: TOpenPictureDialog
Filter =
'All (*.png;*.jpg;*.jpeg;*.gif;*.cur;*.pcx;*.ani;*.jpg;*.jpeg;*.b' +
'mp;*.ico;*.emf;*.wmf)|*.png;*.jpg;*.jpeg;*.gif;*.cur;*.pcx;*.ani' +
';*.jpg;*.jpeg;*.bmp;*.ico;*.emf;*.wmf|JPEG Image File (*.jpg)|*.' +
'jpg|JPEG Image File (*.jpeg)|*.jpeg|CompuServe GIF Image (*.gif)' +
'|*.gif|Cursor files (*.cur)|*.cur|PCX Image (*.pcx)|*.pcx|ANI Im' +
'age (*.ani)|*.ani|JPEG Image File (*.jpg)|*.jpg|JPEG Image File ' +
'(*.jpeg)|*.jpeg|Bitmaps (*.bmp)|*.bmp|Icons (*.ico)|*.ico|Enhanc' +
'ed Metafiles (*.emf)|*.emf|Metafiles (*.wmf)|*.wmf|PNG Image Fil' +
'e (*.png)|*.png'
Options = [ofHideReadOnly, ofFileMustExist, ofEnableSizing]
Title = 'Fish Image'
Left = 174
Top = 224
end
end

View File

@@ -0,0 +1,99 @@
unit Ffactwin;
{ This application shows how to display TSynRestDataset style memo and graphic
fields in a form.
- This application use TWebBrowser for display the image from Project19Server.db3.
- Removed display of image because is need convert the Project19Server.db3 field image to base64 or any suggest.
- fixed memory leak (by houdw2006)
}
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, StdCtrls, DBCtrls, DBGrids, DB, Buttons, Grids, ExtCtrls,
SynRestMidasVCL, DBClient,
SynCommons, mORMot, OleCtrls, Dialogs, ExtDlgs,
SynGdiPlus;
type
TForm1 = class(TForm)
Panel1: TPanel;
Label1: TLabel;
DBLabel1: TDBText;
DBMemo1: TDBMemo;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
dbnvgr1: TDBNavigator;
btnUpload: TButton;
dlgOpenPic1: TOpenPictureDialog;
img: TImage;
procedure FormCreate(Sender: TObject);
procedure dbnvgr1Click(Sender: TObject; Button: TNavigateBtn);
procedure btnUploadClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
procedure DoOnAfterScroll(Dataset: TDataset);
public
{ Public declarations }
SynRestDataset: TSynRestDataset;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses SampleData;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
SynRestDataset := TSynRestDataset.Create(Nil);
SynRestDataset.DataSet.SQLModel := TSQLModel.Create([TSQLBioLife]);
SynRestDataset.CommandText := 'http://LocalHost:8080/root/BioLife?select=Species_No,Category,Common_Name,Species_Name,Length_cm,Length_in,Graphic,Notes,Som&sort=Species_No';
SynRestDataset.Open;
SynRestDataset.AfterScroll := DoOnAfterScroll;
DataSource1.DataSet := SynRestDataset;
// show the first record image
DoOnAfterScroll(Nil);
// hide blob fields in the grid
for I := 0 to DBGrid1.Columns.Count-1 do
if (DBGrid1.Columns[I].Field.DataType = DB.ftBlob) then
DBGrid1.Columns[I].Visible := False;
end;
procedure TForm1.dbnvgr1Click(Sender: TObject; Button: TNavigateBtn);
begin
case Button of
nbDelete, nbPost: SynRestDataset.ApplyUpdates(0);
end;
end;
procedure TForm1.btnUploadClick(Sender: TObject);
begin
// I don't know as encode this :(
if not (SynRestDataset.State in [dsEdit, dsInsert]) then
SynRestDataset.Edit;
if dlgOpenPic1.Execute then
TBlobField(SynRestDataset.FieldByName('Graphic')).LoadFromFile(dlgOpenPic1.FileName);
end;
procedure TForm1.DoOnAfterScroll(Dataset: TDataset);
begin
//img.Picture :=
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SynRestDataset.Dataset.SQLModel.Free;
SynRestDataset.Dataset.SQLModel := nil;
FreeAndNil(SynRestDataset);
end;
end.

View File

@@ -0,0 +1,17 @@
program FishFactSyn;
uses
{$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
Forms,
Ffactwin in 'Ffactwin.pas' {Form1},
SynRestVCL in '..\SynRestVCL.pas',
SynRestMidasVCL in '..\SynRestMidasVCL.pas',
SampleData in '..\SampleData.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

View File

@@ -0,0 +1,189 @@
TSynRestDataset
===============
By *EMartin* (Esteban Martin).
# Presentation
Migrating from *RemObjects* to *mORMot* I had to implement a GUI functionality that *RemObjects* has, an editable dataset connected through URL (RO version 3 use SOAP and other components adapters, etc.).
My implementation is basic and the most probably is not the best, but works for me, the same use RESTful URL for get and update data, also get data from a *mORMot* interface based services returning a *mORMot* JSON array but cannot update because the table not exists.
In this folder there are two units: `SynRestVCL.pas` and `SynRestMidasVCL.pas`, both have some duplicated code from its counterpart (`SynDBVCL.pas` and `SynDBMidasVCL.pas`) and the others, but the rest are modifications with use of RESTful instead of the `TSQLDBConnection` (this require the database client installed in the client machine).
A `TSQLModel` is required because the `TSynRestDataset` get the fields definition column type and size from this. Also is used from the `TSQLRecord` the defined validations (I used `InternalDefineModel`) and the `ComputeFieldsBeforeWrite` (I used this for default values).
This was developed with Delphi 7 on Windows 7 and probably (almost sure) is not cross platform.
If this serves for others may be the best option will be that *ab* integrate this in the framework and make this code more *mORMot*. Meanwhile I will update on the google drive.
I hope this is helpful to someone.
# Example 1: from a table
// defining the table
TSQLRecordTest = class(TSQLRecord)
private
fDecimal: Double;
fNumber: Double;
fTestID: Integer;
fText: RawUTF8;
fDateTime: TDateTime;
protected
class procedure InternalDefineModel(Props: TSQLRecordProperties); override;
public
procedure ComputeFieldsBeforeWrite(aRest: TSQLRest; aOccasion: TSQLEvent); override;
published
property Test_ID: Integer read fTestID write fTestID;
property Text: RawUTF8 index 255 read fText write fText;
property Date_Time: TDateTime read fDateTime write fDateTime;
property Number: Double read fNumber write fNumber;
property Decimal_: Double read fDecimal write fDecimal;
end;
...
{ TSQLRecordTest }
procedure TSQLRecordTest.ComputeFieldsBeforeWrite(aRest: TSQLRest; aOccasion: TSQLEvent);
begin
inherited;
fDateTime := Now;
end;
class procedure TSQLRecordTest.InternalDefineModel(Props: TSQLRecordProperties);
begin
AddFilterNotVoidText(['Text']);
AddFilterOrValidate('Text', TSynValidateNonNull.Create);
end;
// client
type
TForm3 = class(TForm)
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnOpen: TButton;
edtURL: TEdit;
dsRest: TDataSource;
procedure FormCreate(Sender: TObject);
procedure btnOpenClick(Sender: TObject);
procedure DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
private
{ Private declarations }
fRestDS: TSynRestDataset;
public
{ Public declarations }
end;
...
procedure TForm3.FormCreate(Sender: TObject);
begin
fRestDS := TSynRestDataset.Create(Self);
fRestDS.Dataset.SQLModel := TSQLModel.Create([TSQLRecordTest], 'root');
dsRest.Dataset := fRestDS;
end;
procedure TForm3.btnOpenClick(Sender: TObject);
begin
fRestDS.Close;
fRestDS.CommandText := edtURL.Text; // edtURL.Text = 'http://localhost:8888/root/Test/select=*
fRestDS.Open;
// you can filter by
// where: fRestDS.CommandText := edtURL.Text; // edtURL.Text = 'http://localhost:8888/root/Test/select=*&where=CONDITION
// fRestDS.Open;
// named parameter: fRestDS.CommandText := edtURL.Text; // edtURL.Text = 'http://localhost:8888/root/Test/select=*&where=:PARAMNAME
// fRestDS.Params.ParamByName('PARAMNAME').Value := XXX
// fRestDS.Open;
end;
procedure TForm3.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
begin
if (Button = nbPost) then
fRestDS.ApplyUpdates(0);
end;
# Example 2: from a service
// defining the table, the service name and operation name are required
TSQLRecordServiceName_OperationName = class(TSQLRecord)
private
fText: RawUTF8;
published
property Text: RawUTF8 index 255 read fText write fText;
end;
...
// server (the implementation)
TServiceName =class(TInterfacedObjectWithCustomCreate, IServiceName)
public
...
// this function can also be function OperationName(const aParamName: RawUTF8): RawUTF8;
function OperationName(const aParamName: RawUTF8; out aData: RawUTF8): Integer;
...
end;
...
function TServiceName.OperationName(const aParamName: RawUTF8; out aData: RawUTF8): Integer;
begin
Result := OK;
aData := '[{"text":"test"},{"text":"test1"}]';
end;
...
// client
type
TForm3 = class(TForm)
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnOpen: TButton;
edtURL: TEdit;
dsRest: TDataSource;
procedure FormCreate(Sender: TObject);
procedure btnOpenClick(Sender: TObject);
procedure DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
private
{ Private declarations }
fRestDS: TSynRestDataset;
public
{ Public declarations }
end;
...
procedure TForm3.FormCreate(Sender: TObject);
begin
fRestDS := TSynRestDataset.Create(Self);
fRestDS.Dataset.SQLModel := TSQLModel.Create([TSQLRecordServiceName_OperationName], 'root');
dsRest.Dataset := fRestDS;
end;
procedure TForm3.btnOpenClick(Sender: TObject);
begin
fRestDS.Close;
fRestDS.CommandText := edtURL.Text; // edtURL.Text = 'http://localhost:8888/root/ServiceName.OperationName?aParamName=XXX
fRestDS.Open;
// you can filter by named parameter:
// fRestDS.CommandText := edtURL.Text; // 'http://localhost:8888/root/ServiceName.OperationName?aParamName=:aParamName
// fRestDS.Params.ParamByName('aParamName').Value := XXX
// fRestDS.Open;
end;
procedure TForm3.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
begin
if (Button = nbPost) then
fRestDS.ApplyUpdates(0); // raise an error "Cannot update data from a service"
end;
# Forum Thread
See http://synopse.info/forum/viewtopic.php?id=2712
# License
Feel free to use and/or append to Lib and extend if needed.

View File

@@ -0,0 +1,51 @@
/// it's a good practice to put all data definition into a stand-alone unit
// - this unit will be shared between client and server
unit SampleData;
interface
uses
SynCommons,
mORMot;
type
/// here we declare the class containing the data
// - it just has to inherits from TSQLRecord, and the published
// properties will be used for the ORM (and all SQL creation)
// - the beginning of the class name must be 'TSQL' for proper table naming
// in client/server environnment
TSQLBiolife = class(TSQLRecord)
private
fSpecies_No: Integer;
fCategory: RawUTF8;
fCommon_Name: RawUTF8;
fSpecies_Name: RawUTF8;
fLength_cm: double;
fLength_in: double;
fNotes: TSQLRawBlob;
fGraphic: TSQLRawBlob;
fSom: TSQLRawBlob;
published
property Species_No: Integer read fSpecies_No write fSpecies_No;
property Category: RawUTF8 index 15 read fCategory write fCategory;
property Common_Name: RawUTF8 index 30 read fCommon_Name write fCommon_Name;
property Species_Name: RawUTF8 index 40 read fSpecies_Name write fSpecies_Name;
property Length_cm: Double read fLength_Cm write fLength_Cm;
property Length_In: Double read fLength_In write fLength_In;
property Notes: TSQLRawBlob read fNotes write fNotes;
property Graphic: TSQLRawBlob read fGraphic write fGraphic;
property Som: TSQLRawBlob read fSom write fSom;
end;
/// an easy way to create a database model for client and server
function CreateSampleModel: TSQLModel;
implementation
function CreateSampleModel: TSQLModel;
begin
result := TSQLModel.Create([TSQLBioLife]);
end;
end.

View File

@@ -0,0 +1,59 @@
{
Synopse mORMot framework
Sample 04 - HTTP Client-Server
purpose of this sample is to show HTTP Client/Server SQLite3 database usage:
- a TSQLSampleRecord class is defined in shared unit SampleData.pas
- this sample uses two projects, Project04Client.dpr and Project04Server.dpr
- a SQLite3 server is initialized in Project04Server
- the CreateMissingTables method will create all necessary tables in the
SQLite3 database
- one or more client instances can be run in Project04Client
- the purpose of the Client form in Unit1.pas is to add a record to the
database; the Time field is filled with the current date and time
- the 'Find a previous message' button show how to perform a basic query
- since the framework use UTF-8 encoding, we use some basic functions for
fast conversion to/from the User Interface; in real applications,
you should better use our SQLite3i18n unit and the corresponding
TLanguageFile.StringToUTF8() and TLanguageFile.UTF8ToString() methods
- note that you didn't need to write any SQL statement, only define a
class and call some methods; even the query was made very easy (just an
obvious WHERE clause to write)
- thanks to the true object oriented modelling of the framework, the same
exact Unit1 is used for both static in-memory database engine, or
with SQLite3 database storage, in local mode or in Client/Server mode:
only the TForm1.Database object creation instance was modified
- in order to register the URL for the http.sys server, you have to run
this program once as administrator, or call Project04ServerRegister first
- look at the tiny size of the EXE (even with SQLite3 engine embedded), less
than 400KB for the server, and 80KB for the client, with LVCL :)
Version 1.0 - February 07, 2010
Version 1.16
- added authentication to the remote process
Version 1.18
- added Project04ServerRegister.dpr program
}
program Server;
uses
{$I SynDprUses.inc} // use FastMM4 on older Delphi, or set FPC threads
Forms,
SysUtils,
fMain in 'fMain.pas' {frmMain},
SampleData in 'SampleData.pas';
{$R *.res}
begin
SetCurrentDir(ExtractFilePath(ParamStr(0)));
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.

View File

@@ -0,0 +1,433 @@
/// fill a VCL TClientDataset from SynRestVCL data access
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynRestMidasVCL;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
the Initial Developer. All Rights Reserved.
Contributor(s):
- Esteban Martin
- houdw2006
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to Synopse mORMot Framework 1.18,
which is an extraction from former SynRestVCL.pas unit (which is faster
but read/only)
- introducing TSynRestDataSet (under Delphi), which allows to apply updates:
will be used now for overloaded ToClientDataSet() functions result
- fixed Delphi XE2 compilation issue with SetCommandText declaration
- bug fix skipping first record
- fix memory leak adding TSynRestDataset.destroy (by houdw2006)
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
Classes,
{$ifndef DELPHI5OROLDER}
Variants,
{$ifndef FPC}
MidasLib,
{$endif}
{$endif}
mORMot,
mORMotHttpClient,
SynCommons,
SynTable,
SynDB,
SynRestVCL,
DB,
{$ifdef FPC}
BufDataset
{$else}
Contnrs,
DBClient,
Provider,
SqlConst
{$endif};
{$ifdef FPC} { TODO: duplicated code from SynDBMidasVCL }
type
/// FPC's pure pascal in-memory buffer is used instead of TClientDataSet
TClientDataSet = TBufDataset;
/// wrapper functions will use FPC's pure pascal in-memory buffer
TSynRestDataSet = TBufDataset;
{$else FPC}
type
/// A TSynRestDataset, inherited from TCustomClientDataSet, which allows to apply updates on a TWinHTTP connection.
// The TSQLModel is required for getting column datatype and size and if the TSQLRecord has defined
// InternalDefineModel for validations they will be associated to a TField.OnValidate. Similary if the method
// ComputeBeforeWriteFields is overridden this will be used.
// - typical usage may be for instance:
// ! ds := TSynRestDataSet.Create(MainForm);
// ! ds.Dataset.SQLModel := CreateModel; // The SQLModel is required
// ! ds.CommandText := 'http://host:port/root/TableName?select=*&where=condition&sort=fieldname';
// ! ds1.Dataset := ds; // assigning the rest dataset to TDatasource that can be associated a TDBGrid for example.
// ! ds.Open;
// ! // ... use ds as usual, including modifications
// ! ds.ApplyUpdates(0);
// or using from a service returning a dataset:
// ! ds := TSynRestDataSet.Create(MainForm);
// ! ds.Dataset.SQLModel := CreateModel; // The SQLModel is required
// ! the TSQLRecord associated should be defined with the same structure of the returned array from the service
// ! ds.CommandText := 'http://host:port/root/ServiceName.Operation?paramname=:paramvalue';
// ! ds.Params.ParamByName('paramname').Value := 'xyz';
// ! ds1.Dataset := ds; // assigning the rest dataset to TDatasource that can be associated a TDBGrid for example.
// ! ds.Open;
// ! // ... use ds as usual, including modifications
// ! ds.ApplyUpdates(0);
TSynRestDataSet = class(TCustomClientDataSet)
protected
fDataSet: TSynRestSQLDataset;
fProvider: TDataSetProvider;
procedure DoOnFieldValidate(Sender: TField);
procedure DoOnUpdateError(Sender: TObject; DataSet: TCustomClientDataSet; E: EUpdateError; UpdateKind: TUpdateKind; var Response: TResolverResponse);
// from TDataSet
procedure OpenCursor(InfoQuery: Boolean); override;
{$ifdef ISDELPHI2007ANDUP}
// from IProviderSupport
function PSGetCommandText: string; override;
{$endif}
{$IFNDEF NEXTGEN}
{$ifdef ISDELPHIXE}
procedure SetCommandText(Value: WideString); override;
{$else ISDELPHIXE}
procedure SetCommandText(Value: String); override;
{$endif ISDELPHIXE}
{$ELSE}
procedure SetCommandText(Value: String); override;
{$ENDIF !NEXTGEN}
procedure SetFieldValidateFromSQLRecordSynValidate;
public
/// initialize the instance
constructor Create(AOwner: TComponent); override;
/// destroy the instance
destructor Destroy; override;
/// initialize the internal TDataSet from a Rest statement result set
// - Statement will have the form http://host:port/root/tablename or
// http://host:port/root/servicename.operationname?paramname=:paramalias
// examples:
// http://host:port/root/tablename?select=XXX or
// http://host:port/root/tablename?select=XXX&where=field1=XXX or field2=XXX
// http://host:port/root/service.operation?param=:param
procedure From(Statement: RawUTF8; MaxRowCount: cardinal=0);
procedure FetchParams;
published
property CommandText;
property Active;
property Aggregates;
property AggregatesActive;
property AutoCalcFields;
property Constraints;
property DisableStringTrim;
property FileName;
property Filter;
property Filtered;
property FilterOptions;
property FieldDefs;
property IndexDefs;
property IndexFieldNames;
property IndexName;
property FetchOnDemand;
property MasterFields;
property MasterSource;
property ObjectView;
property PacketRecords;
property Params;
property ReadOnly;
property StoreDefs;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property AfterScroll;
property BeforeRefresh;
property AfterRefresh;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
property OnReconcileError;
property BeforeApplyUpdates;
property AfterApplyUpdates;
property BeforeGetRecords;
property AfterGetRecords;
property BeforeRowRequest;
property AfterRowRequest;
property BeforeExecute;
property AfterExecute;
property BeforeGetParams;
property AfterGetParams;
/// the associated SynRestVCL TDataSet, used to retrieve and update data
property DataSet: TSynRestSQLDataSet read fDataSet;
end;
{$endif FPC}
/// Convert JSON array to REST TClientDataset
// - the dataset is created inside this function
function JSONToSynRestDataset(const aJSON: RawUTF8; const aSQLModel: TSQLModel): TSynRestDataset;
implementation
uses
Dialogs;
type
TSynRestSQLDatasetHack = class(TSynRestSQLDataset);
TSynValidateRestHack = class(TSynValidateRest);
{$ifndef FPC}
function JSONToSynRestDataset(const aJSON: RawUTF8; const aSQLModel: TSQLModel): TSynRestDataset;
var
lSQLTableJSON: TSQLTableJSON;
lData: TRawByteStringStream;
begin
Result := Nil;
if (aJSON = '') then
Exit;
lSQLTableJSON := TSQLTableJSON.Create('', aJSON);
lData := TRawByteStringStream.Create('');
try
JSONToBinary(lSQLTableJSON, lData);
Result := TSynRestDataset.Create(Nil);
Result.Dataset.SQLModel := aSQLModel;
Result.DataSet.From(lData.DataString);
finally
FreeAndNil(lData);
FreeAndNil(lSQLTableJSON);
end;
end;
{ TSynRestDataSet }
constructor TSynRestDataSet.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fProvider := TDataSetProvider.Create(Self);
fProvider.Name := 'InternalProvider'; { Do not localize }
fProvider.SetSubComponent(True);
fProvider.Options := fProvider.Options+[poAllowCommandText];
fProvider.OnUpdateError := DoOnUpdateError;
SetProvider(fProvider);
fDataSet := TSynRestSQLDataSet.Create(Self);
fDataSet.Name := 'InternalDataSet'; { Do not localize }
fDataSet.SetSubComponent(True);
fProvider.DataSet := fDataSet;
end;
destructor TSynRestDataSet.Destroy;
begin
fProvider.DataSet := nil;
FreeAndNil(fDataSet);
FreeAndNil(fProvider);
inherited;
end;
procedure TSynRestDataSet.DoOnFieldValidate(Sender: TField);
var
lRec: TSQLRecord;
F: Integer; // fields
V: Integer; // validations
Validate: TSynValidate;
Value: RawUTF8;
lErrMsg: string;
lFields: TSQLPropInfoList;
lwasTSynValidateRest: boolean;
ValidateRest: TSynValidateRest absolute Validate;
begin
lRec := TSynRestSQLDatasetHack(fDataset).GetSQLRecordClass.Create;
try
lFields := lRec.RecordProps.Fields;
F := lFields.IndexByName(Sender.FieldName);
// the field has not validation
if (Length(lRec.RecordProps.Filters[F]) = 0) then
Exit;
if not (lFields.List[F].SQLFieldType in COPIABLE_FIELDS) then
Exit;
lRec.SetFieldValue(Sender.FieldName, PUTF8Char(VariantToUTF8(Sender.Value)));
for V := 0 to Length(lRec.RecordProps.Filters[F])-1 do begin
Validate := TSynValidate(lRec.RecordProps.Filters[F,V]);
if Validate.InheritsFrom(TSynValidate) then begin
Value := Sender.Value;
lwasTSynValidateRest := Validate.InheritsFrom(TSynValidateRest);
if lwasTSynValidateRest then begin // set additional parameters
TSynValidateRestHack(ValidateRest).fProcessRec := lRec;
TSynValidateRestHack(ValidateRest).fProcessRest := Nil; // no Rest for the moment
end;
try
if not Validate.Process(F,Value,lErrMsg) then begin
if lErrMsg='' then
// no custom message -> show a default message
lErrMsg := format(sValidationFailed,[GetCaptionFromClass(Validate.ClassType)])
else
raise ESQLRestException.CreateUTF8('Error % on field "%"', [lErrMsg, Sender.DisplayName]);
end;
finally
if lwasTSynValidateRest then begin // reset additional parameters
TSynValidateRestHack(ValidateRest).fProcessRec := nil;
TSynValidateRestHack(ValidateRest).fProcessRest := nil;
end;
end;
end;
end;
finally
lRec.Free;
end;
end;
procedure TSynRestDataSet.DoOnUpdateError(Sender: TObject; DataSet: TCustomClientDataSet; E: EUpdateError;
UpdateKind: TUpdateKind; var Response: TResolverResponse);
begin
Response := rrAbort;
MessageDlg(E.OriginalException.Message, mtError, [mbOK], 0);
end;
procedure TSynRestDataSet.From(Statement: RawUTF8; MaxRowCount: cardinal);
begin
fDataSet.From(Statement);
fDataSet.CommandText := ''; // ensure no SQL execution
Open;
fDataSet.CommandText := UTF8ToString(Statement); // assign it AFTER Open
end;
procedure TSynRestDataSet.FetchParams;
begin
if not HasAppServer and Assigned(FProvider) then
SetProvider(FProvider);
inherited FetchParams;
end;
procedure TSynRestDataSet.OpenCursor(InfoQuery: Boolean);
begin
if Assigned(fProvider) then
SetProvider(fProvider);
if fProvider.DataSet=self then
raise ESQLDBException.Create(SCircularProvider);
inherited OpenCursor(InfoQuery);
SetFieldValidateFromSQLRecordSynValidate;
end;
{$ifdef ISDELPHI2007ANDUP}
function TSynRestDataSet.PSGetCommandText: string;
{$ifdef ISDELPHIXE3}
var IP: IProviderSupportNG;
begin
if Supports(fDataSet, IProviderSupportNG, IP) then
{$else}
var IP: IProviderSupport;
begin
if Supports(fDataSet, IProviderSupport, IP) then
{$endif}
result := IP.PSGetCommandText else
result := CommandText;
end;
{$endif ISDELPHI2007ANDUP}
{$IFNDEF NEXTGEN}
{$ifdef ISDELPHIXE}
procedure TSynRestDataSet.SetCommandText(Value: WideString);
{$else ISDELPHIXE}
procedure TSynRestDataSet.SetCommandText(Value: String);
{$endif ISDELPHIXE}
{$ELSE}
procedure TSynRestDataSet.SetCommandText(Value: String);
{$ENDIF !NEXTGEN}
begin
TSynRestSQLDatasetHack(fDataset).SetCommandText(Value);
inherited SetCommandText(fDataset.CommandText);
// with this TSynRestSQLDataset can bind param values
TSynRestSQLDatasetHack(fDataset).fParams := Params;
if (Name = '') then
Name := 'rds' + StringReplaceChars(TSynRestSQLDatasetHack(fDataset).fTableName, '.', '_');
end;
procedure TSynRestDataSet.SetFieldValidateFromSQLRecordSynValidate;
var
F: Integer; // dataset fields
V: Integer; // validation fields
lProps: TSQLRecordProperties;
begin
// if not TSQLRecord associated, nothing to do
if (TSynRestSQLDatasetHack(fDataset).GetTableName = '') then
Exit;
lProps := TSynRestSQLDatasetHack(fDataset).GetSQLRecordClass.RecordProps;
// if there isn't filters, bye
if (Length(lProps.Filters) = 0) then
Exit;
for F := 0 to Fields.Count-1 do
begin
V := lProps.Fields.IndexByName(Fields[F].FieldName);
if (V > -1) then
begin
if (Length(lProps.Filters[V]) > 0) then
Fields[F].OnValidate := DoOnFieldValidate;
end;
end;
end;
{$endif FPC}
end.

View File

@@ -0,0 +1,845 @@
/// fill a VCL TClientDataset from SynVirtualDataset data access
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynRestVCL;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2020 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2020
the Initial Developer. All Rights Reserved.
Contributor(s):
- Esteban Martin (EMartin)
- houdw2006
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to Synopse mORMot Framework 1.18,
which is an extraction from former SynDBVCL.pas unit.
- Added that blob field updates they are made with AddJSONEscapeString.
- bug fix when updating accentuated string fields.
- bug fix with datetime fields
- bug fix with length string fields
- fixed Delphi XE3 compilation issue with PSExecuteStatement declaration (by houdw2006)
- added sftSessionUserID to SQLFIELDTYPETODBFIELDTYPE and SQLFieldTypeToVCLDB
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
{$ifdef ISDELPHIXE2}System.SysUtils,{$else}SysUtils,{$endif}
Classes,
{$ifndef DELPHI5OROLDER}
Variants,
{$ifndef FPC}
MidasLib,
{$endif}
{$endif}
mORMot,
mORMotHttpClient,
SynCrtSock, // remover una vez implementado TSQLHttpClient
SynCommons,
SynTable,
SynDB,
SynDBVCL,
DB,
{$ifdef FPC}
BufDataset
{$else}
Contnrs,
DBClient,
Provider,
SqlConst
{$endif};
type
/// generic Exception type
ESQLRestException = class(ESynException);
/// URI signature event
TOnGetURISignature = procedure(Sender: TObject; var aURI: string) of object;
/// a TDataSet which allows to apply updates on a Restful connection
// - typical usage may be for instance:
// ! ds := TSynRestDataSet.Create(MainForm);
// ! ds.Dataset.SQLModel := CreateModel; // The SQLModel is required
// ! ds.CommandText := 'http://host:port/root/TableName?select=*&where=condition&sort=fieldname';
// ! ds1.Dataset := ds; // assigning the rest dataset to TDatasource that can be associated a TDBGrid for example.
// ! ds.Open;
// ! // ... use ds as usual, including modifications
// ! ds.ApplyUpdates(0);
// or using from a service returning a dataset:
// ! ds := TSynRestDataSet.Create(MainForm);
// ! ds.Dataset.SQLModel := CreateModel; // The SQLModel is required
// ! the TSQLRecord associated should be defined with the same structure of the returned array from the service
// ! ds.CommandText := 'http://host:port/root/ServiceName.Operation?paramname=:paramvalue';
// ! ds.Params.ParamByName('paramname').Value := 'xyz';
// ! ds1.Dataset := ds; // assigning the rest dataset to TDatasource that can be associated a TDBGrid for example.
// ! ds.Open;
// ! // ... use ds as usual, including modifications
// ! ds.ApplyUpdates(0);
TSynRestSQLDataSet = class(TSynBinaryDataSet)
protected
fBaseURL: RawUTF8;
fCommandText: string;
fDataSet: TSynBinaryDataSet;
fOnGetURISignature: TOnGetURISignature;
fParams: TParams;
fProvider: TDataSetProvider;
fRoot: RawUTF8;
fSQLModel: TSQLModel;
fTableName: RawUTF8;
fURI: TURI;
function BindParams(const aStatement: RawUTF8): RawUTF8;
function BuildURI(const aURI: SockString): SockString;
function GetSQLRecordClass: TSQLRecordClass;
function GetTableName: string;
// get the data
procedure InternalInitFieldDefs; override;
function InternalFrom(const aStatement: RawUTF8): RawByteString;
procedure InternalOpen; override;
procedure InternalClose; override;
function IsTableFromService: Boolean;
procedure ParseCommandText;
// IProvider implementation
procedure PSSetCommandText(const ACommandText: string); override;
function PSGetTableName: string; override;
function PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean; override;
function PSIsSQLBased: Boolean; override;
function PSIsSQLSupported: Boolean; override;
{$ifdef ISDELPHIXE3}
function PSExecuteStatement(const ASQL: string; AParams: TParams): Integer; overload; override;
function PSExecuteStatement(const ASQL: string; AParams: TParams; var ResultSet: TDataSet): Integer; overload; override;
{$else}
function PSExecuteStatement(const ASQL: string; AParams: TParams; ResultSet: Pointer=nil): Integer; overload; override;
{$endif}
procedure SetCommandText(const Value: string);
public
/// the associated Model, if not defined an exception is raised.
property SQLModel: TSQLModel read fSQLModel write fSQLModel;
published
/// the GET RESTful URI
// - Statement will have the form http://host:port/root/tablename or
// http://host:port/root/servicename.operationname?paramname=:paramalias
// examples:
// http://host:port/root/tablename?select=XXX or
// http://host:port/root/tablename?select=XXX&where=field1=XXX or field2=XXX
// http://host:port/root/service.operation?param=:param
// if :param is used then before open assign the value: ds.Params.ParamByName('param').value := XXX
property CommandText: string read fCommandText write fCommandText;
/// the associated SynDB TDataSet, used to retrieve and update data
property DataSet: TSynBinaryDataSet read fDataSet;
/// event to get URI signature
property OnGetURISignature: TOnGetURISignature write fOnGetURISignature;
end;
// JSON columns to binary from a TSQLTableJSON, is not ideal because this code is a almost repeated code.
procedure JSONColumnsToBinary(const aTable: TSQLTableJSON; W: TFileBufferWriter;
Null: pointer; const ColTypes: TSQLDBFieldTypeDynArray);
// convert to binary from a TSQLTableJSON, is not ideal because this code is a almost repeated code.
function JSONToBinary(const aTable: TSQLTableJSON; Dest: TStream; MaxRowCount: cardinal=0; DataRowPosition: PCardinalDynArray=nil;
const DefaultDataType: TSQLDBFieldType = SynTable.ftUTF8; const DefaultFieldSize: Integer = 255): cardinal;
implementation
uses
DBCommon,
SynVirtualDataset;
const
FETCHALLTOBINARY_MAGIC = 1;
SQLFIELDTYPETODBFIELDTYPE: array[TSQLFieldType] of TSQLDBFieldType =
(SynTable.ftUnknown, // sftUnknown
SynTable.ftUTF8, // sftAnsiText
SynTable.ftUTF8, // sftUTF8Text
SynTable.ftInt64, // sftEnumerate
SynTable.ftInt64, // sftSet
SynTable.ftInt64, // sftInteger
SynTable.ftInt64, // sftID = TSQLRecord(aID)
SynTable.ftInt64, // sftRecord = TRecordReference
SynTable.ftInt64, // sftBoolean
SynTable.ftDouble, // sftFloat
SynTable.ftDate, // sftDateTime
SynTable.ftInt64, // sftTimeLog
SynTable.ftCurrency, // sftCurrency
SynTable.ftUTF8, // sftObject
{$ifndef NOVARIANTS}
SynTable.ftUTF8, // sftVariant
SynTable.ftUTF8, // sftNullable
{$endif}
SynTable.ftBlob, // sftBlob
SynTable.ftBlob, // sftBlobDynArray
SynTable.ftBlob, // sftBlobCustom
SynTable.ftUTF8, // sftUTF8Custom
SynTable.ftUnknown, // sftMany
SynTable.ftInt64, // sftModTime
SynTable.ftInt64, // sftCreateTime
SynTable.ftInt64, // sftTID
SynTable.ftInt64, // sftRecordVersion = TRecordVersion
SynTable.ftInt64, // sftSessionUserID
SynTable.ftDate, // sftDateTimeMS
SynTable.ftInt64, // sftUnixTime
SynTable.ftInt64); // sftUnixMSTime
SQLFieldTypeToVCLDB: array[TSQLFieldType] of TFieldType =
(DB.ftUnknown, // sftUnknown
DB.ftString, // sftAnsiText
DB.ftString, // sftUTF8Text
DB.ftLargeInt, // sftEnumerate
DB.ftLargeInt, // sftSet
DB.ftLargeInt, // sftInteger
DB.ftLargeInt, // sftID = TSQLRecord(aID)
DB.ftLargeInt, // sftRecord = TRecordReference
DB.ftLargeInt, // sftBoolean
DB.ftFloat, // sftFloat
DB.ftDateTime, // sftDateTime
DB.ftLargeInt, // sftTimeLog
DB.ftCurrency, // sftCurrency
DB.ftString, // sftObject
{$ifndef NOVARIANTS}
DB.ftString, // sftVariant
DB.ftString, // sftNullable
{$endif}
DB.ftBlob, // sftBlob
DB.ftBlob, // sftBlobDynArray
DB.ftBlob, // sftBlobCustom
DB.ftString, // sftUTF8Custom
DB.ftUnknown, // sftMany
DB.ftLargeInt, // sftModTime
DB.ftLargeInt, // sftCreateTime
DB.ftLargeInt, // sftTID
DB.ftLargeInt, // sftRecordVersion = TRecordVersion
DB.ftLargeInt, // sftSessionUserID
DB.ftDateTime, // sftDateTime
DB.ftLargeInt, // sftUnixTime
DB.ftLargeInt); // sftUnixMSTime
VCLDBFieldTypeSQLDB: array[0..23] of TSQLFieldType =
(sftUnknown, // ftUnknown
sftAnsiText, // ftString
sftUTF8Text, // ftString
sftEnumerate, // ftInteger
sftSet, // ftInteger
sftInteger, // ftInteger
sftID, // ftLargeInt = TSQLRecord(aID)
sftRecord, // ftLargeInt
sftBoolean, // ftBoolean
sftFloat, // ftFloat
sftDateTime, // ftDate
sftTimeLog, // ftLargeInt
sftCurrency, // ftCurrency
sftObject, // ftString
{$ifndef NOVARIANTS}
sftVariant, // ftString
{$endif}
sftBlob, // ftBlob
sftBlob, // ftBlob
sftBlob, // ftBlob
sftUTF8Custom, // ftString
sftMany, // ftUnknown
sftModTime, // ftLargeInt
sftCreateTime, // ftLargeInt
sftID, // ftLargeInt
sftRecordVersion); // ftLargeInt = TRecordVersion
{$ifndef FPC}
procedure JSONColumnsToBinary(const aTable: TSQLTableJSON; W: TFileBufferWriter;
Null: pointer; const ColTypes: TSQLDBFieldTypeDynArray);
var F: integer;
VDouble: double;
VCurrency: currency absolute VDouble;
VDateTime: TDateTime absolute VDouble;
colType: TSQLDBFieldType;
begin
for F := 0 to length(ColTypes)-1 do
if not GetBitPtr(Null,F) then begin
colType := ColTypes[F];
if colType<ftInt64 then begin // ftUnknown,ftNull
colType := SQLFIELDTYPETODBFIELDTYPE[aTable.FieldType(F)]; // per-row column type (SQLite3 only)
W.Write1(ord(colType));
end;
case colType of
ftInt64:
begin
W.WriteVarInt64(aTable.FieldAsInteger(F));
end;
ftDouble: begin
VDouble := aTable.FieldAsFloat(F);
W.Write(@VDouble,sizeof(VDouble));
end;
SynTable.ftCurrency: begin
VCurrency := aTable.Field(F);
W.Write(@VCurrency,sizeof(VCurrency));
end;
SynTable.ftDate: begin
VDateTime := aTable.Field(F);
W.Write(@VDateTime,sizeof(VDateTime));
end;
SynTable.ftUTF8:
begin
W.Write(aTable.FieldBuffer(F));
end;
SynTable.ftBlob:
begin
W.Write(aTable.FieldBuffer(F));
end;
else
raise ESQLDBException.CreateUTF8('JSONColumnsToBinary: Invalid ColumnType(%)=%',
[aTable.Get(0, F),ord(colType)]);
end;
end;
end;
function JSONToBinary(const aTable: TSQLTableJSON; Dest: TStream; MaxRowCount: cardinal=0; DataRowPosition: PCardinalDynArray=nil;
const DefaultDataType: TSQLDBFieldType = SynTable.ftUTF8; const DefaultFieldSize: Integer = 255): cardinal;
var F, FMax, FieldSize, NullRowSize: integer;
StartPos: cardinal;
Null: TByteDynArray;
W: TFileBufferWriter;
ColTypes: TSQLDBFieldTypeDynArray;
FieldType: TSQLDBFieldType;
begin
result := 0;
W := TFileBufferWriter.Create(Dest);
try
W.WriteVarUInt32(FETCHALLTOBINARY_MAGIC);
FMax := aTable.FieldCount;
W.WriteVarUInt32(FMax);
if FMax>0 then begin
// write column description
SetLength(ColTypes,FMax);
dec(FMax);
for F := 0 to FMax do begin
W.Write(aTable.Get(0, F));
FieldType := SQLFIELDTYPETODBFIELDTYPE[aTable.FieldType(F)];
if (FieldType = SynTable.ftUnknown) and (DefaultDataType <> SynTable.ftUnknown) then
FieldType := DefaultDataType;
ColTypes[F] := FieldType;
FieldSize := aTable.FieldLengthMax(F);
if (FieldSize = 0) and (FieldType = DefaultDataType) and (DefaultFieldSize <> 0) then
FieldSize := DefaultFieldSize;
W.Write1(ord(ColTypes[F]));
W.WriteVarUInt32(FieldSize);
end;
// initialize null handling
SetLength(Null,(FMax shr 3)+1);
NullRowSize := 0;
// save all data rows
StartPos := W.TotalWritten;
if aTable.Step or (aTable.RowCount=1) then // Need step first or error is raised in Table.Field function.
repeat
// save row position in DataRowPosition[] (if any)
if DataRowPosition<>nil then begin
if Length(DataRowPosition^)<=integer(result) then
SetLength(DataRowPosition^,result+result shr 3+256);
DataRowPosition^[result] := W.TotalWritten-StartPos;
end;
// first write null columns flags
if NullRowSize>0 then begin
FillChar(Null[0],NullRowSize,0);
NullRowSize := 0;
end;
for F := 0 to FMax do
begin
if VarIsNull(aTable.Field(F)) then begin
SetBitPtr(pointer(Null),F);
NullRowSize := (F shr 3)+1;
end;
end;
W.WriteVarUInt32(NullRowSize);
if NullRowSize>0 then
W.Write(Null,NullRowSize);
// then write data values
JSONColumnsToBinary(aTable, W,Null,ColTypes);
inc(result);
if (MaxRowCount>0) and (result>=MaxRowCount) then
break;
until not aTable.Step;
end;
W.Write(@result,SizeOf(result)); // fixed size at the end for row count
W.Flush;
finally
W.Free;
end;
end;
{ TSynRestSQLDataSet }
function TSynRestSQLDataSet.BindParams(const aStatement: RawUTF8): RawUTF8;
var
I: Integer;
lParamName: string;
begin
Result := aStatement;
if (Pos(':', aStatement) = 0) and (fParams.Count = 0) then
Exit;
if ((Pos(':', aStatement) = 0) and (fParams.Count > 0)) or ((Pos(':', aStatement) > 0) and (fParams.Count = 0)) then
raise ESQLRestException.CreateUTF8('Statement parameters (%) not match with Params (Count=%) property',
[aStatement, fParams.Count]);
for I := 0 to fParams.Count-1 do
begin
lParamName := ':' + fParams[I].Name;
Result := StringReplace(Result, lParamName, fParams[I].AsString, [rfIgnoreCase]);
end;
// remove space before and after &
Result := StringReplaceAll(Result, ' & ', '&');
end;
function TSynRestSQLDataSet.BuildURI(const aURI: SockString): SockString;
var
lTmpURI: string;
begin
lTmpURI := aURI;
if Assigned(fOnGetURISignature) then
fOnGetURISignature(Self, lTmpURI);
Result := FormatUTF8('%%' , [fBaseURL, lTmpURI]);
if fURI.Https and (Result[5] <> 's') then
System.Insert('s', Result, 5);
end;
function TSynRestSQLDataSet.GetSQLRecordClass: TSQLRecordClass;
begin
Result := fSQLModel.Table[GetTableName];
if not Assigned(Result) then
raise ESQLRestException.CreateUTF8('Table % not registered in SQL Model', [GetTableName]);
end;
function TSynRestSQLDataSet.GetTableName: string;
var
I: Integer;
begin
if not IsTableFromService then
Result := PSGetTableName
else
begin
Result := fTableName;
for I := 1 to Length(Result) do
if (Result[I] = '.') then
begin
Result[I] := '_'; // change only the firs found
Break;
end;
end;
end;
procedure TSynRestSQLDataSet.InternalClose;
begin
inherited InternalClose;
FreeAndNil(fDataAccess);
fData := '';
end;
function TSynRestSQLDataSet.InternalFrom(const aStatement: RawUTF8): RawByteString;
procedure UpdateFields(aSQLTableJSON: TSQLTableJSON);
var
I, J: Integer;
lFields: TSQLPropInfoList;
begin
lFields := GetSQLRecordClass.RecordProps.Fields;
for I := 0 to aSQLTableJSON.FieldCount-1 do
begin
J := lFields.IndexByName(aSQLTableJSON.Get(0, I));
if (J > -1) then
aSQLTableJSON.SetFieldType(I, lFields.Items[J].SQLFieldType, Nil, lFields.Items[J].FieldWidth);
end;
end;
var
lData: TRawByteStringStream;
lSQLTableJSON: TSQLTableJSON;
lStatement: RawUTF8;
lDocVar: TDocVariantData;
lTmp: RawUTF8;
lResp: TDocVariantData;
lErrMsg: RawUTF8;
lURI: RawUTF8;
begin
Result := '';
lStatement := BindParams(aStatement);
if (lStatement <> '') then
lStatement := '?' + lStatement;
lURI := BuildURI(fRoot + fTableName + lStatement);
Result := TWinHTTP.Get(lURI);
if (Result = '') then
raise ESynException.CreateUTF8('Cannot get response (timeout?) from %', [lURI]);
if (Result <> '') then
begin
lResp.InitJSON(Result);
if (lResp.Kind = dvUndefined) then
raise ESynException.CreateUTF8('Invalid JSON response' + sLineBreak + '%' + sLineBreak + 'from' + sLineBreak + '%',
[Result, lURI]);
if (lResp.Kind = dvObject) then
if (lResp.GetValueIndex('errorCode') > -1) then
if (lResp.GetValueIndex('errorText') > -1) then
begin
lErrMsg := AnyAnsiToUTF8(lResp.Value['errorText']);
raise ESynException.CreateUTF8('Error' + sLineBreak + '%' + sLineBreak + 'from' + sLineBreak + '%',
[lResp.Value['errorText'], lURI]);
end
else if (lResp.GetValueIndex('error') > -1) then
begin
lErrMsg := AnyAnsiToUTF8(lResp.Value['error']);
raise ESynException.CreateUTF8('Error' + sLineBreak + '%' + sLineBreak + 'from' + sLineBreak + '%', [lErrMsg, lURI]);
end;
if IsTableFromService then // is the source dataset from a service ?
begin
lDocVar.InitJSON(Result);
lTmp := lDocVar.Values[0];
lDocVar.Clear;
lDocVar.InitJSON(lTmp);
if (lDocVar.Kind <> dvArray) then
raise ESQLRestException.CreateUTF8('The service % not return an array: <%>', [fTableName, Result]);
// if the array is empty, nothing to return
Result := lDocVar.Values[0];
if (Result = '') or (Result = '[]') or (Result = '{}') then
raise ESQLRestException.CreateUTF8('Service % not return a valid array: <%>', [fTableName, Result]);
end;
lSQLTableJSON := TSQLTableJSON.CreateFromTables([GetSQLRecordClass], '', Result);
// update info fields for avoid error conversion in JSONToBinary
UpdateFields(lSQLTableJSON);
lData := TRawByteStringStream.Create('');
try
JSONToBinary(lSQLTableJSON, lData);
Result := lData.DataString
finally
FreeAndNil(lData);
FreeAndNil(lSQLTableJSON);
end;
end;
end;
procedure TSynRestSQLDataSet.InternalInitFieldDefs;
var F: integer;
lFields: TSQLPropInfoList;
lFieldDef: TFieldDef;
lOldSize: Int64;
begin
inherited;
if (GetTableName = '') then // JSON conversion to dataset ?
Exit;
// update field definitions from associated TSQLRecordClass of the table
lFields := GetSQLRecordClass.RecordProps.Fields;
for F := 0 to lFields.Count-1 do
begin
lFieldDef := TFieldDef(TDefCollection(FieldDefs).Find(lFields.Items[F].Name));
if Assigned(lFieldDef) then
begin
if (lFieldDef.DataType <> SQLFieldTypeToVCLDB[lFields.Items[F].SQLFieldType]) then
begin
lOldSize := lFieldDef.Size; // DB.pas.TFieldDef.SetDataType change the size
lFieldDef.DataType := SQLFieldTypeToVCLDB[lFields.Items[F].SQLFieldType];
end;
if (lFields.Items[F].FieldWidth > 0) and (lFieldDef.Size < lFields.Items[F].FieldWidth) then
lFieldDef.Size := lFields.Items[F].FieldWidth
else if (lOldSize > 0) and (lFieldDef.Size > 0) and (lOldSize <> lFieldDef.Size) then
lFieldDef.Size := lOldSize;
end;
end;
end;
function TSynRestSQLDataSet.IsTableFromService: Boolean;
begin
Result := (Pos('.', fTableName) > 0);
end;
procedure TSynRestSQLDataSet.InternalOpen;
var
lData: RawByteString;
begin
if (fCommandText='') and (not IsTableFromService) then begin
if fData<>'' then // called e.g. after From() method
inherited InternalOpen;
exit;
end;
lData := InternalFrom(fCommandText);
if (lData <> '') then
begin
From(lData);
inherited InternalOpen;
end;
end;
procedure TSynRestSQLDataSet.ParseCommandText;
var
lSQL: RawUTF8;
begin
// it is assumed http://host:port/root/tablename, the rest is optional: ?select=&where=&sort= etc.
if not fURI.From(fCommandText) then
raise ESynException.CreateUTF8('Invalid % command text. Must have the format protocol://host:port', [fCommandText]);
if not fURI.Https then
fBaseURL := FormatUTF8('http://%:%/', [fURI.Server, fURI.Port])
else
fBaseURL := FormatUTF8('https://%:%/', [fURI.Server, fURI.Port]);
Split(fURI.Address, '/', fRoot, fTableName);
if (fRoot = '') or (fTableName = '') then
raise ESynException.CreateUTF8('Invalid % root. Must have the format protocol://host:port/root/tablename', [fCommandText]);
fRoot := fRoot + '/';
if (Pos('?', fTableName) > 0) then
Split(fTableName, '?', fTableName, lSQL);
if not Assigned(fSQLModel) then
raise ESQLRestException.CreateUTF8('Error parsing command text. Empty Model.', []);
fCommandText := lSQL
end;
{$ifdef ISDELPHIXE3}
function TSynRestSQLDataSet.PSExecuteStatement(const ASQL: string;
AParams: TParams): Integer;
var DS: TDataSet;
begin
DS := nil;
result := PSExecuteStatement(ASQL,AParams,DS);
DS.Free;
end;
function TSynRestSQLDataSet.PSExecuteStatement(const ASQL: string; AParams: TParams; var ResultSet: TDataSet): Integer;
{$else}
function TSynRestSQLDataSet.PSExecuteStatement(const ASQL: string; AParams: TParams; ResultSet: Pointer): Integer;
{$endif}
function Compute(const aJSON: SockString; const aOccasion: TSQLOccasion): SockString;
var
lRec: TSQLRecord;
lRecBak: TSQLRecord; // backup for get modifications
lJSON: TDocVariantData;
I: Integer;
lCount: Integer;
lOccasion: TSQLEvent;
lVarValue: Variant;
lVarValueBak: Variant;
begin
lRec := GetSQLRecordClass.Create;
lRecBak := GetSQLRecordClass.Create;
try
lJSON.InitJSON(aJSON);
lCount := lJSON.Count;
// update record fields
for I := 0 to lCount-1 do
lRec.SetFieldVariant(lJSON.Names[I], lJSON.Values[I]);
lOccasion := seUpdate;
if (aOccasion = soInsert) then
lOccasion := seAdd;
lRec.ComputeFieldsBeforeWrite(Nil, lOccasion);
// get modified fields
for I := 0 to lRec.RecordProps.Fields.Count-1 do
begin
lRec.RecordProps.Fields.Items[I].GetVariant(lRec, lVarValue);
lRecBak.RecordProps.Fields.Items[I].GetVariant(lRecBak, lVarValueBak);
if (lVarValue <> lVarValueBak) then
lJSON.AddOrUpdateValue(lRec.RecordProps.Fields.Items[I].Name, lVarValue);
end;
Result := lJSON.ToJSON;
finally
lRec.Free;
lRecBak.Free;
end;
end;
function ExtractFields(const aSQL, aAfterStr, aBeforeStr: string): string;
var
lPosStart: Integer;
lPosEnd: Integer;
lSQL: string;
begin
lSQL := StringReplace(aSQL, sLineBreak, ' ', [rfReplaceAll]);
lPosStart := Pos(aAfterStr, lSQL)+Length(aAfterStr);
lPosEnd := Pos(aBeforeStr, lSQL);
Result := Trim(Copy(lSQL, lPosStart, lPosEnd-lPosStart));
end;
function SQLFieldsToJSON(const aSQLOccasion: TSQLOccasion; const aSQL, aAfterStr, aBeforeStr: string; aParams: TParams): SockString;
var
I: Integer;
lLastPos: Integer;
lFieldValues: TStrings;
lBlob: TSQLRawBlob;
begin
lFieldValues := TStringList.Create;
try
ExtractStrings([','], [], PChar(ExtractFields(aSQL, aAfterStr, aBeforeStr)), lFieldValues);
lLastPos := 0;
with TTextWriter.CreateOwnedStream do
begin
Add('{');
for I := 0 to lFieldValues.Count-1 do
begin
if (Pos('=', lFieldValues[I]) = 0) then
lFieldValues[I] := lFieldValues[I] + '=';
AddFieldName(Trim(lFieldValues.Names[I]));
if (aParams[I].DataType <> ftBlob) then
begin
if (TVarData(aParams[I].Value).VType = varString) then
AddVariant(StringToUTF8(aParams[I].Value))
else
AddVariant(aParams[I].Value);
end
else
begin
Add('"');
lBlob := BlobToTSQLRawBlob(PUTF8Char(aParams[I].AsBlob));
AddJSONEscapeString(lBlob);
Add('"');
end;
Add(',');
lLastPos := I;
end;
CancelLastComma;
Add('}');
Result := Text;
Free;
end;
lFieldValues.Clear;
// the first field after the where clause is the ID
if (aSQLOccasion <> soInsert) then
aParams[lLastPos+1].Name := 'ID';
finally
lFieldValues.Free;
end;
end;
function GetSQLOccasion(const aSQL: string): TSQLOccasion;
begin
if IdemPChar(PUTF8Char(UpperCase(aSQL)), 'DELETE') then
Result := soDelete
else if IdemPChar(PUTF8Char(UpperCase(aSQL)), 'INSERT') then
Result := soInsert
else
Result := soUpdate;
end;
var
lJSON: SockString;
lOccasion: TSQLOccasion;
lResult: SockString;
lURI: SockString;
lID: string;
begin // only execute writes in current implementation
Result := -1;
if IsTableFromService then
DatabaseError('Cannot apply updates from a service');
// build the RESTful URL
lURI := FormatUTF8('%/%', [fSQLModel.Root, StringToUTF8(PSGetTableName)]);
lOccasion := GetSQLOccasion(aSQL);
case lOccasion of
soDelete:
begin
lID := aParams[0].Value;
lURI := lURI + '/' + lID;
lResult := TWinHTTP.Delete(BuildURI(lURI), '');
if (lResult = '') then
Result := 1;
end;
soInsert:
begin
lJSON := SQLFieldsToJSON(soInsert, aSQL, '(', ') ', aParams);
try
lJSON := Compute(lJSON, soInsert);
except
Result := -1;
lResult := Exception(ExceptObject).Message;
end;
lResult := TWinHTTP.Post(BuildURI(lURI), lJSON);
if (lResult = '') then
Result := 1;
end;
soUpdate:
begin
lJSON := SQLFieldsToJSON(soUpdate, aSQL, 'set ', 'where ', aParams);
try
lJSON := Compute(lJSON, soUpdate);
except
Result := -1;
lResult := Exception(ExceptObject).Message;
end;
lID := aParams.ParamByName('ID').Value;
lURI := lURI + '/' + lID;
lResult := TWinHTTP.Put(BuildURI(lURI), lJSON);
if (lResult = '') then
Result := 1;
end
end;
if (Result = -1) and (lResult <> '') then
DatabaseError(lResult);
end;
function TSynRestSQLDataSet.PSGetTableName: string;
begin
Result := fTableName;
end;
function TSynRestSQLDataSet.PSIsSQLBased: Boolean;
begin
result := true;
end;
function TSynRestSQLDataSet.PSIsSQLSupported: Boolean;
begin
result := true;
end;
procedure TSynRestSQLDataSet.PSSetCommandText(const ACommandText: string);
begin
if (fCommandText <> ACommandText) then
SetCommandText(ACommandText);
end;
function TSynRestSQLDataSet.PSUpdateRecord(UpdateKind: TUpdateKind;
Delta: TDataSet): Boolean;
begin
result := false;
end;
procedure TSynRestSQLDataSet.SetCommandText(const Value: string);
begin
if (Value <> fCommandtext) then
begin
fCommandText := Value;
ParseCommandText;
end;
end;
{$endif FPC}
end.

View File

@@ -0,0 +1,48 @@
object frmMain: TfrmMain
Left = 198
Top = 124
Width = 418
Height = 240
Caption = 'SynRestDataset Demo'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 16
object Label1: TLabel
Left = 40
Top = 16
Width = 297
Height = 33
AutoSize = False
Font.Charset = DEFAULT_CHARSET
Font.Color = clTeal
Font.Height = -16
Font.Name = 'Tahoma'
Font.Style = [fsBold]
ParentFont = False
end
object Label2: TLabel
Left = 56
Top = 72
Width = 145
Height = 16
Caption = 'HTTP Server is running...'
end
object Button1: TButton
Left = 88
Top = 120
Width = 75
Height = 25
Caption = 'Quit'
TabOrder = 0
OnClick = Button1Click
end
end

View File

@@ -0,0 +1,67 @@
unit fMain;
interface
uses
{$ifdef MSWINDOWS}
Windows,
Messages,
{$endif}
SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
SynCommons, mORMot, mORMotSQLite3, SynSQLite3Static,
mORMotHttpServer, SampleData;
type
TfrmMain = class(TForm)
Label1: TLabel;
Button1: TButton;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
private
public
Model: TSQLModel;
DB: TSQLRestServerDB;
Server: TSQLHttpServer;
end;
var
frmMain: TfrmMain;
implementation
uses
mORMotDB;
{$R *.dfm}
procedure TfrmMain.Button1Click(Sender: TObject);
begin
Close;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
Model := CreateSampleModel;
DB := TSQLRestServerDB.Create(Model, 'Project19Server.db3', False);
DB.CreateMissingTables;
Server := TSQLHttpServer.Create('8080',[DB],'+',HTTP_DEFAULT_MODE);
Server.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
Server.Free;
DB.Free;
Model.Free;
end;
procedure TfrmMain.FormShow(Sender: TObject);
begin
Label1.Caption := Caption;
end;
end.