extjsdataset.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. unit extjsdataset;
  2. {
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 1999-2022 by Michael van Canney and other members of the
  5. Free Pascal development team
  6. extjs dataset
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {$mode objfpc}{$H+}
  14. interface
  15. uses
  16. Classes, SysUtils, db, fpjson, typinfo, fpjsondataset;
  17. Type
  18. { TExtJSJSONDataSet }
  19. // Base for ExtJS datasets. It handles MetaData conversion.
  20. TExtJSJSONDataSet = Class(TBaseJSONDataset)
  21. Private
  22. FFields : TJSONArray;
  23. Protected
  24. Function GenerateMetaData : TJSONObject;
  25. function ConvertDateFormat(S: String): String; virtual;
  26. Procedure MetaDataToFieldDefs; override;
  27. procedure InitDateTimeFields; override;
  28. function StringToFieldType(S: String): TFieldType;virtual;
  29. function GetStringFieldLength(F: TJSONObject; AName: String; AIndex: Integer): integer; virtual;
  30. Public
  31. // Use this to load MetaData/Rows from stream.
  32. // If no metadata is present in the stream, FieldDefs must be filled manually.
  33. Procedure LoadFromStream(S : TStream);
  34. // Use this to load MetaData/Rows from file.
  35. // If no metadata is present in the file, FieldDefs must be filled manually.
  36. Procedure LoadFromFile(Const AFileName: string);
  37. // Use this to save Rows and optionally metadata to Stream.
  38. // Note that MetaData must be set.
  39. Procedure SaveToStream(S : TStream; SaveMetaData : Boolean);
  40. // Use this to save Rows and optionally metadata to Stream.
  41. // Note that MetaData must be set.
  42. Procedure SaveToFile(Const AFileName : String; SaveMetaData : Boolean);
  43. // Can be set directly if the dataset is closed.
  44. Property MetaData;
  45. // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
  46. Property Rows;
  47. Published
  48. Property OwnsData;
  49. end;
  50. { TExtJSJSONObjectDataSet }
  51. // Use this dataset for data where the data is an array of objects.
  52. TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
  53. Function CreateFieldMapper : TJSONFieldMapper; override;
  54. end;
  55. { TExtJSJSONArrayDataSet }
  56. // Use this dataset for data where the data is an array of arrays.
  57. TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
  58. Function CreateFieldMapper : TJSONFieldMapper; override;
  59. end;
  60. implementation
  61. { TExtJSJSONDataSet }
  62. Function TExtJSJSONDataSet.StringToFieldType(S : String) : TFieldType;
  63. begin
  64. if (s='int') then
  65. Result:=ftLargeInt
  66. else if (s='float') then
  67. Result:=ftFloat
  68. else if (s='boolean') then
  69. Result:=ftBoolean
  70. else if (s='date') then
  71. Result:=ftDateTime
  72. else if (s='string') or (s='auto') or (s='') then
  73. Result:=ftString
  74. else
  75. if MapUnknownToStringType then
  76. Result:=ftString
  77. else
  78. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  79. end;
  80. Function TExtJSJSONDataSet.GetStringFieldLength(F : TJSONObject; AName : String; AIndex : Integer) : integer;
  81. Var
  82. I,L : Integer;
  83. D : TJSONData;
  84. begin
  85. Result:=0;
  86. I:=F.IndexOfName('maxlen');
  87. if (I<>-1) and (F.Items[I].jsonType=jtNumber) then
  88. begin
  89. Result:=StrToIntDef(trim(F.Items[i].AsString),-1);
  90. if (Result=-1) then
  91. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s : %s',[AName,F.Items[i].AsString])
  92. end
  93. else
  94. begin
  95. For I:=0 to Rows.Count-1 do
  96. begin
  97. D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
  98. if (D<>Nil) and (D.JsonType<>jtNull) then
  99. begin
  100. l:=Length(D.AsString);
  101. if L>Result then
  102. Result:=L;
  103. end;
  104. end;
  105. end;
  106. if (Result=0) then
  107. Result:=20;
  108. end;
  109. procedure TExtJSJSONDataSet.LoadFromStream(S: TStream);
  110. Var
  111. D : TJSONData;
  112. O : TJSONObject;
  113. N : String;
  114. I : Integer;
  115. begin
  116. D:=GetJSON(S);
  117. try
  118. if (D.JSONType=jtObject) then
  119. O:=D as TJSONObject
  120. else
  121. begin
  122. FreeAndNil(D);
  123. Raise EJSONDataset.Create('Not a valid ExtJS JSON data packet');
  124. end;
  125. N:='rows';
  126. // Check metadata
  127. I:=O.IndexOfName('metaData');
  128. if (I<>-1) then
  129. begin
  130. If (O.Items[i].JSONType<>jtObject) then
  131. Raise EJSONDataset.Create('Invalid ExtJS JSON metaData in data packet.');
  132. Metadata:=O.Objects['metaData'];
  133. O.Extract(I);
  134. I:=Metadata.IndexOfName('root');
  135. If (I<>-1) then
  136. begin
  137. if (MetaData.Items[i].JSONType<>jtString) then
  138. Raise EJSONDataset.Create('Invalid ExtJS JSON root element in metaData.');
  139. N:=MetaData.Strings['root'];
  140. end;
  141. end;
  142. // Check rows
  143. I:=O.IndexOfName(N);
  144. if (I=-1) then
  145. Raise EJSONDataset.Create('Missing rows in data packet');
  146. if (O.Items[i].JSONType<>jtArray) then
  147. Raise EJSONDataset.Create('Rows element must be an array');
  148. Rows:=O.Items[i] as TJSONArray;
  149. O.Extract(I);
  150. OwnsData:=True;
  151. finally
  152. D.Free;
  153. end;
  154. end;
  155. procedure TExtJSJSONDataSet.LoadFromFile(const AFileName: string);
  156. Var
  157. F : TFileStream;
  158. begin
  159. F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
  160. try
  161. LoadFromStream(F);
  162. finally
  163. F.Free;
  164. end;
  165. end;
  166. procedure TExtJSJSONDataSet.SaveToStream(S: TStream; SaveMetaData: Boolean);
  167. Var
  168. O : TJSONObject;
  169. SS : TStringStream;
  170. N : String;
  171. I : Integer;
  172. M : TJSONobject;
  173. begin
  174. O:=TJSONObject.Create;
  175. try
  176. N:='rows';
  177. If SaveMetaData then
  178. begin
  179. M:=MetaData;
  180. if M=Nil then
  181. M:=GenerateMetaData;
  182. O.Add('metaData',M);
  183. if M.IndexOfName('root')<>-1 then
  184. N:=M.Strings['root'];
  185. end;
  186. O.Add(N,Rows);
  187. SS:=TStringStream.Create(O.FormatJSON());
  188. try
  189. S.CopyFrom(SS,0);
  190. finally
  191. SS.Free;
  192. end;
  193. finally
  194. If (MetaData<>Nil) and SaveMetaData then
  195. begin
  196. I:=O.IndexOfName('metaData');
  197. if (I<>-1) then
  198. O.Extract(i);
  199. end;
  200. O.Extract(O.IndexOfName(N));
  201. O.Free;
  202. end;
  203. end;
  204. procedure TExtJSJSONDataSet.SaveToFile(const AFileName: String;
  205. SaveMetaData: Boolean);
  206. Var
  207. F : TFileStream;
  208. begin
  209. F:=TFileStream.Create(AFileName,fmCreate);
  210. try
  211. SaveToStream(F,SaveMetaData);
  212. finally
  213. F.Free;
  214. end;
  215. end;
  216. procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
  217. Var
  218. A : TJSONArray;
  219. F : TJSONObject;
  220. MaxLen,I,J,FS : Integer;
  221. N,idf : String;
  222. ft: TFieldType;
  223. D : TJSONData;
  224. begin
  225. FieldDefs.Clear;
  226. I:=Metadata.IndexOfName('fields');
  227. if (I=-1) or (MetaData.Items[i].JSONType<>jtArray) then
  228. Raise EJSONDataset.Create('Invalid metadata object');
  229. A:=Metadata.Arrays['fields'];
  230. For I:=0 to A.Count-1 do
  231. begin
  232. If (A.Types[i]<>jtObject) then
  233. Raise EJSONDataset.CreateFmt('Field definition %d in metadata (%s) is not an object',[i,A[i].AsJSON]);
  234. F:=A.Objects[i];
  235. J:=F.IndexOfName('name');
  236. If (J=-1) or (F.Items[J].JSONType<>jtString) then
  237. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  238. N:=F.Items[J].AsString;
  239. J:=F.IndexOfName('type');
  240. If (J=-1) then
  241. ft:=ftstring
  242. else If (F.Items[J].JSONType<>jtString) then
  243. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  244. else
  245. ft:=StringToFieldType(F.Items[J].asString);
  246. if (ft=ftString) then
  247. begin
  248. fs:=F.Get('maxLen',0);
  249. if fs=0 then
  250. fs:=GetStringFieldLength(F,N,I)
  251. end
  252. else
  253. fs:=0;
  254. FieldDefs.Add(N,ft,fs);
  255. end;
  256. FFields:=A;
  257. end;
  258. function TExtJSJSONDataSet.GenerateMetaData: TJSONObject;
  259. Var
  260. F : TJSONArray;
  261. O : TJSONObject;
  262. I,M : Integer;
  263. T : STring;
  264. begin
  265. Result:=TJSONObject.Create;
  266. F:=TJSONArray.Create;
  267. Result.Add('fields',F);
  268. For I:=0 to FieldDefs.Count -1 do
  269. begin
  270. O:=TJSONObject.Create(['name',FieldDefs[i].name]);
  271. F.Add(O);
  272. M:=0;
  273. case FieldDefs[i].DataType of
  274. ftfixedwidechar,
  275. ftwideString,
  276. ftfixedchar,
  277. ftString:
  278. begin
  279. T:='string';
  280. M:=FieldDefs[i].Size;
  281. end;
  282. ftBoolean: T:='boolean';
  283. ftDate,
  284. ftTime,
  285. ftDateTime: T:='date';
  286. ftFloat: t:='float';
  287. ftSmallint,
  288. ftInteger,
  289. ftAutoInc,
  290. ftLargeInt,
  291. ftword: t:='int';
  292. else
  293. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[GetEnumName(TypeInfo(TFieldType),Ord(FieldDefs[i].DataType))]);
  294. end; // case
  295. O.Strings['type']:=t;
  296. if M<>0 then
  297. O.Integers['maxlen']:=M;
  298. end;
  299. Result.strings['root']:='rows';
  300. end;
  301. Function TExtJSJSONDataSet.ConvertDateFormat(S : String) : String;
  302. { Not handled: N S w z W t L o O P T Z c U MS }
  303. begin
  304. Result:=StringReplace(S,'y','yy',[rfReplaceall]);
  305. Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
  306. Result:=StringReplace(Result,'g','h',[rfReplaceall]);
  307. Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
  308. Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
  309. Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
  310. Result:=StringReplace(Result,'n','m',[rfReplaceall]);
  311. Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
  312. Result:=StringReplace(Result,'j','d',[rfReplaceall]);
  313. Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
  314. Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
  315. Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
  316. Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
  317. Result:=LowerCase(Result);
  318. end;
  319. procedure TExtJSJSONDataSet.InitDateTimeFields;
  320. Var
  321. F : TJSONObject;
  322. FF : TField;
  323. I,J : Integer;
  324. Fmt : String;
  325. begin
  326. If (FFields=Nil) then
  327. Exit;
  328. For I:=0 to FFields.Count-1 do
  329. begin
  330. F:=FFields.Objects[i];
  331. J:=F.IndexOfName('type');
  332. if (J<>-1) and (F.Items[J].JSONType=jtString) and (F.items[J].AsString='date') then
  333. begin
  334. J:=F.IndexOfName('dateFormat');
  335. if (J<>-1) and (F.Items[J].JSONType=jtString) then
  336. begin
  337. FMT:=ConvertDateFormat(F.Items[J].AsString);
  338. FF:=FindField(F.Strings['name']);
  339. if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
  340. begin
  341. if FF is TJSONDateField then
  342. TJSONDateField(FF).DateFormat:=Fmt
  343. else if FF is TJSONTimeField then
  344. TJSONTimeField(FF).TimeFormat:=Fmt
  345. else if FF is TJSONDateTimeField then
  346. TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
  347. end;
  348. end;
  349. end;
  350. end;
  351. end;
  352. { TJSONArrayDataSet }
  353. function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
  354. begin
  355. Result:=TJSONArrayFieldMapper.Create;
  356. end;
  357. { TJSONObjectDataSet }
  358. function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
  359. begin
  360. Result:=TJSONObjectFieldMapper.Create;
  361. end;
  362. end.