/mORMot/SynCommons.pas

https://bitbucket.org/sas_team/sas.requires · Pascal · 58304 lines · 42541 code · 3634 blank · 12129 comment · 4784 complexity · 31b9de4c98a1393c30d5e2b5ff69db45 MD5 · raw file

  1. /// common functions used by most Synopse projects
  2. // - this unit is a part of the freeware Synopse mORMot framework,
  3. // licensed under a MPL/GPL/LGPL tri-license; version 1.18
  4. unit SynCommons;
  5. (*
  6. This file is part of Synopse framework.
  7. Synopse framework. Copyright (C) 2016 Arnaud Bouchez
  8. Synopse Informatique - http://synopse.info
  9. *** BEGIN LICENSE BLOCK *****
  10. Version: MPL 1.1/GPL 2.0/LGPL 2.1
  11. The contents of this file are subject to the Mozilla Public License Version
  12. 1.1 (the "License"); you may not use this file except in compliance with
  13. the License. You may obtain a copy of the License at
  14. http://www.mozilla.org/MPL
  15. Software distributed under the License is distributed on an "AS IS" basis,
  16. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  17. for the specific language governing rights and limitations under the License.
  18. The Original Code is Synopse framework.
  19. The Initial Developer of the Original Code is Arnaud Bouchez.
  20. Portions created by the Initial Developer are Copyright (C) 2016
  21. the Initial Developer. All Rights Reserved.
  22. Contributor(s):
  23. - Aleksandr (sha)
  24. - Alfred Glaenzer (alf)
  25. - BigStar
  26. - itSDS
  27. - Johan Bontes
  28. - kevinday
  29. - mazinsw
  30. - Marius Maximus (mariuszekpl)
  31. - RalfS
  32. - Sanyin
  33. - Pavel (mpv)
  34. - Wloochacz
  35. - zed
  36. Alternatively, the contents of this file may be used under the terms of
  37. either the GNU General Public License Version 2 or later (the "GPL"), or
  38. the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  39. in which case the provisions of the GPL or the LGPL are applicable instead
  40. of those above. If you wish to allow use of your version of this file only
  41. under the terms of either the GPL or the LGPL, and not to allow others to
  42. use your version of this file under the terms of the MPL, indicate your
  43. decision by deleting the provisions above and replace them with the notice
  44. and other provisions required by the GPL or the LGPL. If you do not delete
  45. the provisions above, a recipient may use your version of this file under
  46. the terms of any one of the MPL, the GPL or the LGPL.
  47. ***** END LICENSE BLOCK *****
  48. Version 1.7
  49. - first public release, corresponding to SQLite3 Framework 1.7
  50. Version 1.8
  51. - includes Unitary Testing class and functions
  52. - bug fixed in WinAnsiBufferToUtf8() and all WinAnsi to UTF-8 encoding
  53. functions (issue identified thanks to new _UTF8 testing function)
  54. - bug fixed in val() under Delphi 2009/2010 for some values (issue identified
  55. thanks to new NumericalConversion testing function)
  56. - bug fixed in AnsiICompW() - used in SynPdf unit
  57. - ambiguous SameText() function rewritten as SameTextU() with UTF-8 decoding
  58. - TTextWriter class moved from SQLite3Commons to SynCommons
  59. - new JSONEncode and JSONDecode functions to directly encode or decode any
  60. content to/from a valid UTF-8 JSON object content
  61. - enhanced URLEncode() and URLDecode() functions
  62. - new ExtendedToStr/ExtendedToString functions
  63. - new tests added (mostly relative to the new functions or classes)
  64. Version 1.9
  65. - now compiles under CrossKylix, and tested under Linux
  66. - new JSONEncodeArray procedures, to create JSON array content from
  67. supplied Delphi arrays (handle RawUTF8 text, double or integer arrays)
  68. - new AddCSV methods in TTextWriter handling Delphi arrays to be added
  69. as Comma-Separated-Values (handle RawUTF8 text, double or integer arrays)
  70. - new definition of PtrInt/PtrUInt, to match NativeInt/NativeUInt types,
  71. available since Delphi 2007 - some code rewrite in order to avoid any
  72. implicit conversion from/to integer/cardinal
  73. Version 1.9.2
  74. - new StringReplaceChars function
  75. Version 1.10
  76. - code modifications to compile with Delphi 6 compiler (Delphi 5 failed due
  77. to some obscure compiler bugs in SynCrypto.pas)
  78. Version 1.11
  79. - fix some obscure Delphi 2009 bug according to NativeUInt :(
  80. - source code modified to be 7 bit Ansi (so will work with all encodings)
  81. - a lot of code refactoring for our internal fork of ZeosLib
  82. (e.g. ISO 8601 date time extracted from SQLite3Commons, QuotedStr..)
  83. - new TRawUTF8List class, which is able to emulate a TStringList with our
  84. native UTF-8 string type (cross-compiler, from Delphi 6 up to XE)
  85. - new TRawUTF8Stream class, to typecast a RawUTF8 into a TStream
  86. - new IsWow64 and SystemInfo global variables
  87. Version 1.12
  88. - fixed issue "JSON floats decimal separator depends on language settings"
  89. - new UTF8ToWideChar() overloaded function, with MaxDestChars parameter
  90. - new FillIncreasing() procedure
  91. - now handle our 32/64-bit variable-length integer encoding, via new
  92. FromVarUInt32/64 and ToVarUInt32/64 functions
  93. - new TFileBufferReader and TFileBufferWriter objects, implementing very fast
  94. read/write access to huge files, with new 32/64-bit variable-length integer
  95. encoding and optimized storage of IDs or Offsets (used in TSynBigTable)
  96. - new function UnQuoteSQLString()
  97. - another review of Pos() calls in the code (now use our fast PosEx)
  98. - TSynMemoryStream now replaces TRawUTF8Stream class, with constructors using
  99. either an AnsiString, either a memory buffer
  100. - new TSynTable, TSynTableFieldProperties and TSynTableStatement classes
  101. to handle a generic database table storing field values using our SBF
  102. compact binary format (more compact than BSON, similar to Protocol Buffers)
  103. - new WinAnsiToUnicodeString and Ansi7ToString functions
  104. - new TTextWriter.AddBinToHex method
  105. - new CompareOperator() functions and associated TCompareOperator type
  106. - new IntToThousandString() function (used for TSynTests e.g.)
  107. - new CreateInternalWindow() for creating a Windows Message handler in any object
  108. Version 1.13
  109. - unit now compiles and works with Delphi 5 compiler
  110. - new low-level RTTI functions for handling record types: RecordEquals,
  111. RecordSave, RecordSaveLength, RecordLoad, RecordClear and RecordCopy
  112. - new TDynArray object, which is a wrapper around any dynamic array: you can
  113. now access to the dynamic array using TList-like properties and methods,
  114. e.g. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort and some
  115. new methods like LoadFromStream, SaveToStream, LoadFrom and SaveTo which
  116. allow fast binary serialization of any dynamic array, even containing
  117. strings or records; a CreateOrderedIndex method is also available to
  118. create individual index according to the dynamic array content; and any
  119. dynamic array can be serialized as UTF-8 JSON via TTextWriter.AddDynArrayJSON
  120. and TDynArray.LoadFromJSON methods
  121. - introducing direct content filtering and validation using
  122. TSynFilterOrValidate dedicated classes, for both TSQLRecord and SynBigTable
  123. - filtering is handled via some TSynFilter classes - TSynFilterUpperCase,
  124. TSynFilterUpperCaseU, TSynFilterLowerCase, TSynFilterLowerCaseU and
  125. TSynFilterTrim e.g.
  126. - validation is handled via some TSynValidate classes - TSynValidateRest,
  127. TSynValidateIPAddress, TSynValidateEmail, TSynValidatePattern,
  128. TSynValidatePatternI, TSynValidateText, TSynValidatePassWord e.g.
  129. - dedicated TSynTableFieldProperties.Validate method for validation (e.g. a
  130. TSynValidateTableUniqueField instance is created if tfoUnique is in Options)
  131. - dedicated TSynTableFieldProperties.Filter method for filtering (using
  132. common TSynFilter classes, working at UTF-8 Text content)
  133. - faster implementation of Move() for Delphi versions with no FastCode inside
  134. - new ConvertCaseUTF8(), UpperCaseU(), LowerCaseU(), Int64ToUInt32(),
  135. GetCardinalDef(), IsValidEmail, IsValidIP4Address(), PatchCodePtrUInt(),
  136. GetCaptionFromClass(), GetDisplayNameFromClass(), DateTimeToIso8601Text()
  137. StrUInt32(), StringBufferToUtf8(), IsZero(), AddPrefixToCSV(), IntToString(),
  138. RawUTF8DynArrayEquals(), FromVarString(), GetBitCSV(), SetBitCSV()
  139. procedures or functions (with associated tests)
  140. - new grep-like IsMatch() function for basic pattern matching
  141. - new BinToBase64, Base64ToBin and IsBase64 *fast* conversion functions
  142. (with optimized assembler version, using CPU pipelining and lookup table)
  143. - introducing the GarbageCollector TObjectList for handling a global garbage
  144. collector for instances which must live during the whole executable process
  145. (used e.g. to avoid a memory leak for "class var" or such variables)
  146. - new TSynLog class to handle enhanced logging to any application, with
  147. exception handling (+stack trace) and customer-side performance profiling
  148. - new TSynMapFile class to retrieve debugging information from .map file (and
  149. able to save and read smaller .mab files) - used by TSynLog if available
  150. - new TSynTestsLogged test suit class, with automated test case logging
  151. - Windows version detection enhanced, now retrieving TWindowsVersion enumerate
  152. - great performance improvement in TSynTableFieldProperties for update process
  153. - added TMemoryMap and TSynMemoryStreamMapped to handle memory-mapped files
  154. - added TMemoryMapText class to fast handle big UTF-8 text files (like logs)
  155. - now TTextWriter can have a custom internal buffer size (default 4096 bytes)
  156. - now TFileBufferWriter and TFileVersion are regular classes, not an
  157. object/record any more (this was incoherent since Delphi 2010)
  158. - new TFileBufferReader.OpenFrom(Stream) and ReadRawUTF8 methods
  159. - now TSynCache will use faster TDynArrayHashed for its content hashing
  160. - new Escape: TTextWriterKind optional parameter for TTextWriter.Add()
  161. - new SynLZ related compression functions: FileSynLZ/FileUnSynLZ and
  162. StreamSynLZ/StreamUnSynLZ
  163. - source can now be parsed per all Delphi IDE pre-parser (dual declaration as
  164. record or object because of Delphi 2010 and up was not understood)
  165. - fixed issue in TSynTable.Data() method: ID was not set as expected
  166. - fixed issue in TSynTableFieldProperties: wrong constraint evaluation and
  167. index refresh at records update
  168. - fixed issue in ToVarUInt32Length/ToVarUInt32LengthWithData
  169. Version 1.14
  170. - fix issues with Curr64ToStr() and Curr64ToPChar() with negative amounts,
  171. with some speed enhancements and new associated tests
  172. - fixed issue in produced JSON stream using '=' instead of ':'
  173. - new DoubleToStr(), StrCurr64(), UnicodeBufferToString(),
  174. RawUnicodeToString(), FillChar(), UpperCopy255W(), GetCaptionFromEnum(),
  175. SortDynArrayUnicodeString(), SortDynArrayUnicodeStringI() functions
  176. Version 1.15
  177. - unit now tested with Delphi XE2 (32 Bit)
  178. - TSynLog now writes the elapsed time (in us) for Enter/Leave events, and
  179. will flush the log content to disk on any exception (for safety)
  180. - new sllTrace and sllWarning levels for TSynLog class
  181. - new TSynLog.DefaultExtension property (set to '.log' by default)
  182. - new TSynLogFile.LogProc[] property for customer-side method profiling,
  183. with LogProcSort method available for sorting the resulting array, and
  184. LogProcMerged property to merge the location name timing
  185. - new TSynMapFile.FindLocation method for high-level .map symbol access
  186. - TSynMapFile now handles huge .map file (bigger default in-memory buffer)
  187. - fix potential GPF issue in code using ConvertHexToBin[]
  188. - new TSynLog.EventCount method
  189. - new TMemoryMapText.LineContains method for fast case-insensitive search
  190. - TSynTests now writes the elapsed time in each test in the final report
  191. - faster late binding process for our variants custom types (i.e.
  192. TSynTableVariantType and TSQLDBRowVariantType): you can call
  193. SynRegisterCustomVariantType() function to register any other custom
  194. variant type, and enhance GetProperty/SetProperty process speed
  195. - includes our optimized RecordCopy procedure in replacement to the slower
  196. default System.@CopyRecord internal RTL function
  197. - our optimized Move() and FillChar() will replace the default System RTL
  198. function, for Delphi versions prior to 2007 (which didn't contain those)
  199. - new AnsiCharToUTF8(), StringToWinAnsi(), WideStringToWinAnsi(),
  200. WideStringToUTF8(), CSVOfValue(), IdemPCharArray(), FindUnicode(),
  201. UpperCaseUnicode(), LowerCaseUnicode() and Split() functions
  202. - faster GetInt64() function
  203. - Iso8601ToSecondsPUTF8Char() now returns 0 in case of unexpected format
  204. - fixed issue in StrCurr64() low-level conversion routine
  205. - fixed issue in Utf8DecodeToRawUnicodeUI() function
  206. - new TSynTableFieldProperties.OrderedIndexRefresh method, to allow access
  207. on OrderedIndex[] even if the index needs to be refreshed
  208. - new TDynArrayHashed.AddAndMakeUniqueName() method and Hash[] property
  209. - new TRawByteStringStream class (a TStream using a RawByteString as internal
  210. storage), especially useful since Delphi 2009
  211. - new TSynNameValue object, to efficiently handle Name/Value RawUTF8 pairs
  212. (using hashing for Name search)
  213. - TTextWriter.CreateOwnedStream now create an internal TRawByteStringStream
  214. instance for faster process and direct retrieval in the Text method
  215. - JSONEncode*() global functions will use an internal TRawByteStringStream
  216. instead of a supplied TMemoryStream
  217. - new FormatUTF8() overloaded function, handling both '%' and '?' parameters
  218. (inserting '?' as inlined :(...): parameters, with proper string quote) -
  219. with associated regression tests
  220. Version 1.16
  221. - introducing new TSynAnsiConvert and TSynAnsiFixedWidth classes, able to
  222. process Unicode to/from Ansi conversion in all possible code pages, with
  223. generic access methods and optimized handling of fixed width encodings
  224. - added dedicated Exception classes (ESynException, ETableDataException)
  225. - TSynLog allows read sharing of the .log created file
  226. - TSynLog now stores the executable build time, and library name (if any) -
  227. this is a small change of the .log format as expected by the LogViewer tool
  228. (so you should upgrade your LogViewer.exe to its latest version)
  229. - TSynLog and TSynMapFile now handle libraries (.dll/.ocx/.bpl) .map/.mab
  230. debugging information (only .exe was previously handled)
  231. - TSynCache now handles an optional Tag: PtrInt value parameter (used e.g.
  232. to store the row counts of a SQL result cache in mORMot)
  233. - TSynCache now uses the generic TSynNameValue object from its internal
  234. hashed list implementation (avoid duplicated code)
  235. - TMemoryMapText class (and therefore TSynLogFile) is now able to map/open
  236. an existing file: it will allow e.g. the SynLogViewer to browse a .log file
  237. which is actually still opened and working by the main application
  238. - faster RawUnicodeToUtf8() and UTF8ToWideChar() functions, thanks to very
  239. clever speed-up proposals by Sha (also included in TSynAnsi* classes)
  240. - JSONDecode() overloaded functions now accept parameter names without case
  241. sensibility (and a new HandleValuesAsObjectOrArray parameter)
  242. - new JSONDecode() overloaded function, to properly handle unserialization
  243. of a JSON object within a buffer (used e.g. for TDynArrayJSONCustomReader)
  244. - JSON functions now handle '0' as number according to http://json.org specs
  245. - new TTextWriter.AddJSONEscape() overloaded function, to be used to directly
  246. serialize some name/value pairs as a JSON object content (used e.g. for
  247. TDynArrayJSONCustomWriter callbacks)
  248. - new FileSize(), RoundTo2Digits() and RawByteStringArrayConcat() functions
  249. - new TPrecisionTimer Pause and Resume methods
  250. - new TSynTestCase.CheckFailed method (most of the time, Check is sufficient)
  251. - new TSynLogFamily.IncludeComputerNameInFileName property
  252. - new TTextWriter.WrRecord method for direct adding of a Base-64 record content
  253. - new TTextWriter.AddNoJSONEscapeString method
  254. - new TRawUTF8ListHashed class, with extend TRawUTF8List by using an internal
  255. hash table to optimized IndexOf() method call (including case sensitivity)
  256. - new ToVarInt64() and FromVarInt64() functions to encode and decode
  257. variable-length signed Int64 values (with the corresponding new tftVarInt64
  258. kind of variable-length column in TSynTableFieldType enumeration)
  259. - new GotoNextJSONObjectOrArray() and RawUTF8ArrayToQuotedCSV() functions
  260. - new ReadStringFromStream() and WriteStringToStream() functions
  261. - fixed some compilation warnings with Delphi XE and XE2
  262. - fixed issue in TDynArrayHashed if you do not use the external Count
  263. - fixed potential GPF in TDynArrayHashed.ReHash after TDynArray.Clear call
  264. - fixed issue in TSynTableFieldProperties.SaveTo about saving wrong indexes
  265. - fixed issue TSynTableStatement when only one column was retrieved
  266. - fixed rounding issue in ExtendedToString() and all corresponding wrappers
  267. like DoubleToStr*, Add(Double...)
  268. - fixed issue in Hash32() implementation (potential GPF when reading ahead
  269. by DWORD - get rid of unnecessary asm optimization)
  270. - fixed issues in function IsJSONString() which returned TRUE for '-' or '+',
  271. or false positives in some border-line cases (due to wrong uppercase guess):
  272. now this function is split into IsString() and IsStringJSON() functions
  273. to explicitely handle null/false/true constant recognition
  274. - fixed potential false positives of null/false/true in function GetJSONField
  275. - get rid of wrong "Decimal" parameter in float to text conversion
  276. - TFileBufferWriter.Create now accepts up to 4 MB internal buffer size
  277. - increased TDynArrayHashed number of void entries (for speed)
  278. - modified TDynArray.SaveToStream/LoadFromStream to read or save the data
  279. from the current stream position
  280. - fixed GPF in TDynArray.SaveTo in case of invalid internal record layout
  281. - modified StreamUnSynLZ() so that Source stream will point after all read data
  282. - TDynArray.SaveToStream() method can now save to any TStream class
  283. - added TTextWriter.RegisterCustomJSONSerializer() method to allow JSON
  284. serialization of any dynamic array content (used by TDynArray.LoadFromJSON
  285. and TTextWriter.AddDynArrayJSON) and record content (used by RecordLoadJSON
  286. and TTextWriter.AddRecordJSON)
  287. - added USEPACKAGES conditional to help compiling the unit within packages
  288. - added optional DOPATCHTRTL to patch the RTL (e.g. RecordCopy, RecordClear
  289. TObject.CleanupInstance low-level functions) only if needed (not patched
  290. by default, for compatibility reasons) - you may want to use our Enhanced
  291. RTL patchs instead for a whole better response
  292. - new function BinToBase64URI()
  293. - circumvent some bugs of Delphi XE2 background compiler (main compiler is OK)
  294. - add premilinary Windows 8 operating system detection (as wEight/wEightServer)
  295. Version 1.17
  296. - check of QueryPerformanceFrequency failure, and rollback to low-resolution timer
  297. - handle properly old .synlz layout (reading compatibility was broken)
  298. - added TObjectListHashed class, which behaves like TList/TObjectList, but
  299. will use hashing for (much) faster IndexOf() method, and associated
  300. TObjectListPropertyHashed class, which allows hashing of a sub-property
  301. of an object (including some changes made to TDynArray/TDynArrayHashed)
  302. - new TTextWriter.AddDateTime() overloaded method able to quote the output
  303. - new TTextWriter.AddFloatStr() method handling partial floating-point text
  304. - both TTextWriter.AddDateTime() overloaded methods will store '' when value
  305. is 0, or a pure ISO-8601 date or time if the value is defined as such,
  306. just as expected by http://www.sqlite.org/lang_datefunc.html - it will also
  307. reduce average generated JSON/text content size
  308. - fixed issue about BLOB unproperly serialized into JSON (e.g. now uses null)
  309. - fixed ticket [e5ad3684b2] about some .map files parsing in TSynMapFile
  310. - TSynLog stack tracing uses low-level RtlCaptureStackBackTrace() API on CPU64
  311. - changed the non expanded JSON format to use lowercase first column names:
  312. {"fieldCount":1,"values":["col1"... instead of {"FieldCount":1,"Values":[..
  313. - new SetInt64() procedure for direct assignment of the result
  314. - TSynTableStatement class now accepts '_' in table and column identifiers
  315. - fixed implementation issue in function FindNextUTF8WordBegin()
  316. - fixed false negative issue in TSynSoundEx.UTF8 and TSynSoundEx.Ansi
  317. - fixed wrong UTF-8 encoding of U+FFF0 used for JSON_BASE64_MAGIC
  318. - added an optional parameter to StrToCurr64() function, able to return
  319. a true Int64 value if no decimal is supplied within the input text buffer
  320. - enhanced TSynAnsiFixedWidth.UnicodeBufferToAnsi average process speed
  321. - TSynCache.Reset now returns a boolean stating if something was flushed
  322. - new SynUnicodeToUtf8(), ShortStringToUTF8(), StringToSynUnicode(),
  323. SynUnicodeToString() functions
  324. - new StrToCurrency() wrapper function
  325. - new IdemPropName() overloaded function with two PUTF8Char arguments
  326. - new UTF8UpperCopy() and UTF8UpperCopy255() optimized functions
  327. - new GotoNextNotSpace() and GotoEndOfQuotedString() functions
  328. - new TMemoryMap.Map() method expecting a file name as parameter
  329. - new TRawUTF8List.LoadFromFile method
  330. - new DateToSQL(), DateTimeToSQL() and Iso8601ToSQL() functions, returning
  331. a string with a JSON_SQLDATE_MAGIC prefix and proper UTF-8/ISO-8601 encoding
  332. to be inlined as ? bound parameter in any SQL query (allow binding of
  333. date/time parameters as request by some external database engine
  334. which does not accept ISO-8601 text in this case)
  335. - added TDynArray.Equals() method to compare two arrays efficiently
  336. - added TDynArray and TDynArrayHashed InitSpecific() method able to specify
  337. how (hashing and) comparison should be processed for a given record
  338. (includes also some TDynArray/TDynArrayHashed refactoring and optimization)
  339. - new TObjectHash abstract class to use hashing to find an object
  340. - TTextWriter.AddJSONEscape() method speed up
  341. - moved logging threadvars and associated structures into hidden internal
  342. declaration, for better work with packages (avoid W1032 warning)
  343. - now JSON parser will handle #1..' ' chars as whitespace (not only ' ')
  344. - now JSON parser will allow whitespace inserted between any pair of tokens,
  345. even after true/false/null, as expected by the specification
  346. - fixed potential Integer Overflow error in Iso8601ToDateTimePUTF8Char*()
  347. - added PatchCode() and RedirectCodeRestore() procedures, and some code
  348. refactoring about process in-memory code patching
  349. - internal FillChar() will now use faster SSE2 instructions on supported CPUs
  350. Version 1.18
  351. - BREAKING CHANGE: SynLog.pas and SynTests.pas were extracted from SynCommons
  352. - BREAKING CHANGE of TTextWriter.WriteObject() method: serialization is now
  353. defined with a new TTextWriterWriteObjectOptions set
  354. - BREAKING CHANGE rename of Iso8601 low-level structure as TTimeLogBits, to use
  355. explicitly the TTimeLog type and name for all Int64 bit-oriented functions -
  356. now "Iso8601" naming will be only for standard ISO-8601 text, not Int64 value
  357. - BREAKING CHANGE: TTextWriter.Add(Format) won't handle the alternate $ % tags
  358. any more, unless you define the OLDTEXTWRITERFORMAT conditional
  359. - BREAKING CHANGE: TTextWriter.AddDouble() and AddSingle() dedicated methods
  360. replacing ambiquituous Add(), which was not appropriate for single values
  361. - BREAKING CHANGE: FormatUTF8() and TTextWriter.Add(Format) PUTF8Char type for
  362. constant text parameter has been changed into RawUTF8, to let the compiler
  363. handle any Unicode content as expected
  364. - RawByteString is now defined as "= type AnsiString" under non Unicode Delphi
  365. so that it would be recognized with its own encoding (pseudo code page 65535)
  366. - Delphi XE4/XE5/XE6/XE7/XE8/10/10.1 compatibility (Win32/Win64 target
  367. platforms only for the SynCommons and mORMot* units, but SynCrossPlatform*
  368. supports clients on all other targets, including OSX and NextGen compilers)
  369. - unit fixed and tested with Delphi XE2 (and up) 64-bit compiler under Windows
  370. - now all variants created within our units will create string instances of
  371. kind varString and type RawUTF8 - prior to Delphi 2009, ensure you call
  372. UTF8ToString(aVariant) if you want to use the value with the VCL
  373. - introducing TDocVariant for variant-based process of any hierarchy
  374. of objects and/or arrays, with late binding optimized access and JSON
  375. serialization/unserialization (will also be used for BSON documents storage)
  376. - UTF-8 process will now handle UTF-16 surrogates - see ticket [4a0382367d] -
  377. UnicodeCharToUTF8/NextUTF8Char are renamed WideCharToUTF8/NextUTF8UCS4 and
  378. new UTF16CharToUTF8/UCS4ToUTF8 functions have been introduced
  379. - added ToUTF8() overloaded functions, which could be used on most simple types
  380. - introducing TSynTimeZone class, for cross-platform local time handling
  381. - added TextColor() and TextBackground() functions - will initialize internal
  382. console process after any manual AllocConsole call
  383. - added ConsoleWaitForEnterKey function, able to handle Synchronize() calls
  384. - StrLen() function will now use SSE2 or SSE4.2 instructions on supported CPUs
  385. - introduced StrLenPas() function, to be used when buffer is protected
  386. - UpperCopy255Buf() function will use SSE4.2 instrctions on supported CPUs
  387. to speed up e.g. HashAnsiStringI()
  388. - included Windows-1258 code page to be recognized as a fixed-width charset
  389. - TSynAnsiFixedWidth.Create(CODEPAGE_US) will now use a hard-coded table,
  390. since some Russian system do tweak the registry to force 1252 page maps 1251
  391. - introducing TSynAnsiUTF8/TSynAnsiUTF16 to handle CP_UTF8/CP_UTF16 codepages
  392. - added UTF8AnsiConvert instance, and let TSynAnsiConvert.Engine(0) return
  393. the main CurrentAnsiConvert instance
  394. - StrComp/StrIComp/StrLen() functions will now expect blank pointers, to
  395. circumvent type mismatchs when passing PAnsiChar or PUTF8Char buffers
  396. - get rid of 12 maximum count of supplied argument in FormatUTF8()
  397. - FormatUTF8() and VarRecToUTF8() will append the class name of any TObject
  398. - added JSONFormat optional parameter to FormatUTF8() to produce valid JSON
  399. content from a given set of values identified by ? - used e.g. by _JsonFmt()
  400. - added ESynException.CreateUTF8() constructor, more powerful than the
  401. default Exception.CreateFmt(): this CreateUTF8 method is now used everywhere
  402. - added QuotedStrJSON() and NextNotSpaceCharIs() functions
  403. - refactored GetMimeContentType() implementation - see also [fca72ba0ce] -
  404. and introduced GetMimeContentTypeHeader() function
  405. - added MultiPartFormDataDecode() to decode multipart/form-data POST requests
  406. - included x64 asm of FillChar() and Move() for Win64 - Delphi RTL will be
  407. patched at startup, if the DOPATCHTRTL conditional is defined
  408. - introduced FillcharFast() and MoveFast() global function variables,
  409. pointing to optimized asm versions, depending on the CPU abilities
  410. - FastCode-based x86 asm Move() procedure will handle source=dest
  411. - faster x86/x64 asm versions of StrUInt32() StrInt32() StrInt64() functions
  412. - new StrUInt64(), UniqueRawUTF8(), FastNewRawUTF8() and SetRawUTF8() functions
  413. - introducing UTF8ToInteger() overloaded functions
  414. - recognize 8.1 and upcoming "Threshold" 9 in TWindowsVersion
  415. - added TypeInfo, ElemSize, ElemType read-only properties to TDynArray
  416. - added DynArrayLoad() and DynArraySave() helper functions
  417. - introducing TObjectDynArrayWrapper class and IObjectDynArray interface
  418. - introducing T*ObjArray dynamic array storage via ObjArrayAdd/ObjArrayFind/
  419. ObjArrayDelete/ObjArraySort and ObjArrayClear functions
  420. - introducing T*InterfaceArray dynamic array storage via InterfaceArrayAdd/
  421. InterfaceArrayFind/InterfaceArrayDelete functions
  422. - added TPersistentWithCustomCreate, TInterfacedObjectWithCustomCreate and
  423. TSynPersistent abstract classes, allowing to define virtual constructors for
  424. TPersistent kind of objects (used e.g. with internal JSON serialization,
  425. for interface-based services, or for DDD objects)
  426. - introducing TSynPersistentLocked and TInterfacedObjectLocked classes,
  427. avoiding CPU cache line performance issue (so to be preferred to TMonitor or
  428. TCriticalSection)
  429. - new TSynPersistentWithPassword class, able to store the password with
  430. a custom simple encryption when serialized as JSON
  431. - introducing TSynAuthentication class for simple generic authentication
  432. - introducing TSynConnectionDefinition class used e.g. for JSON-defined
  433. runtime instantiation of a TSQLDBConnectionProperties or TSQLRest instance
  434. - added TDynArrayHashed.HashElement property
  435. - new TDynArrayHashed.AddUniqueName() method
  436. - introduced TSingleDynArray, recognized as such in JSON serialization
  437. - fixed "single" floating-point values JSON serialization
  438. - added WordScanIndex() and swap32() functions
  439. - speed improvement of IdemPropNameU() function, with new overload function
  440. - now FileSize() function won't raise any exception if the file does not exist
  441. and will return any size > 2 GB as expected
  442. - faster PosEx() function in pure pascal mode (based on Avatar Zondertau work)
  443. - introducing StreamToRawByteString() / RawByteStringToStream() functions
  444. - introducing RawByteStringToBytes() / BytesToRawByteString() functions
  445. - added StringDynArrayToRawUTF8DynArray() and StringListToRawUTF8DynArray()
  446. - added CSVToRawUTF8DynArray() overloaded functions
  447. - added GetLastCSVItem() function and dedicated HashPointer() function
  448. - added DirectoryDelete() and EnsureDirectoryExists() function
  449. - added FileOpenSequentialRead() function, used e.g. by StringFromFile()
  450. - added GetNextItemInteger(), GetNextItemCardinalStrict() and UpperCaseCopy()
  451. - added GetEnumNameValue() and UnQuotedSQLSymbolName() functions
  452. - added JSONEncodeArrayOfConst() function
  453. - JSONEncode() and TTextWriter.AddJSONEscape() with NameValuePairs parameters
  454. will now handle nested arrays or objects specified with '['..']' or '{'..'}'
  455. and nil parameter as null JSON value
  456. - new TTextWriter.AddJSON() method and JSONEncode() overloaded function able
  457. to recognize (extended) JSON content, including MongoDB shell extensions
  458. - added IsHTMLContentTypeTextual() function, and modified ExistsIniNameValue()
  459. - added ShortStringToAnsi7String() and UpperCopyWin255() functions
  460. - added IsEqualGUID/IsNullGuid/GUIDToText/GUIDToRawUTF8/GUIDToString functions
  461. - added AddGUID/RandomGUID/TextToGUID/RawUTF8ToGUID/StringToGUID functions
  462. - added TDynArray.ElemPtr() low-level method
  463. - let TDynArray.LoadFrom() accept Win32/Win64 cross platform binary content
  464. - new TDynArray.CopyFrom() method and associated procedure DynArrayCopy()
  465. - TDynArray will now recognize variant/interface fields
  466. - new TDynArray.FastLocateSorted FastAddSorted FastLocateOrAddSorted methods
  467. - new TPendingTaskList class, for storing e.g. a time-ordered list of tasks
  468. - code refactoring of TTextWriter to simplify flushing mechanism, and
  469. allow internal buffer auto-grow if it was found out to be too small (see
  470. FlushToStream / FlushFinal methods and FlushToStreamNoAutoResize property)
  471. - fixed ticket [5bd9df5979] about TTextWriter.CancelAll issue
  472. - added optional internal buffer size for TTextWriter.CreateOwnedStream()
  473. - added new constructor TTextWriter.CreateOwnedFileStream()
  474. - added TTextWriter.LastChar and TTextWriter.AddStrings() methods
  475. - added TTextWriter.ForceContent method
  476. - added faster TTextWriter.SetText() method in conjuction to Text function
  477. - added TTextWriter.Add(const guid: TGUID) and Add(Value: boolean) methods
  478. - TTextWriter.Add(Format..) will now ignore any character afer |, i.e. |$ = $
  479. - added TTextWriter.AddQuotedStr() and AddStringCopy() methods
  480. - added TTextWriter.AddVoidRecordJSON() method
  481. - added TTextWriter.AddJSONEscapeAnsiString() method
  482. - added TTextWriter.AddAnyAnsiString() method and AnyAnsiToUTF8() function
  483. - added TTextWriter.EndOfLineCRLF property
  484. - for Delphi 2010 and up, RecordSaveJSON/RecordLoadJSON will use enhanced RTTI
  485. - before Delphi 2010, you can specify the record layout as text to
  486. TTextWriter.RegisterCustomJSONSerializerFromText() for JSON serialization
  487. - added TTextWriter.RegisterCustomJSONSerializerSetOptions() for [da22968223]
  488. - added TTextWriter.AddDynArrayJSON() overloaded method and new functions
  489. DynArrayLoadJSON() and DynArraySaveJSON() to be used e.g. for custom
  490. record JSON serialization, using TDynArrayJSONCustomReader/Writer
  491. callbacks and/or RegisterCustomJSONSerializerFromText(), or enhanced RTTI
  492. - added TTextWriter.AddDynArrayJSONAsString method, and moved
  493. TTextWriter.WriteObjectAsString from TJSONSerializer
  494. - added TTextWriter.UnRegisterCustomJSONSerializer() method
  495. - added TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType() method
  496. - added TTextWriter.AddTypedJSON() and AddCRAndIdent methods
  497. - added TTextWriter.SetDefaultJSONClass to force TJSONSerializer use
  498. - added TJSONWriter.EndJSONObject() method, for writing an optional
  499. ',"rowCount":' field in non expanded mode - used for all JSON creation
  500. - added TTextWriter.EchoAdd() and EchoRemove() methods
  501. - added QuickSortIndexedPUTF8Char() and FastFindIndexedPUTF8Char()
  502. - added overloaded QuickSortInteger() for synchronous sort of two arrays
  503. - fixed potential critical issue [99fe8a1eba] in SortDynArrayInt64/Cardinal
  504. - added GetNextItem64() Int64Scan() Int64ScanExists() QuickSortInt64()
  505. FastFindInt64Sorted() AddInt64() CSVToInt64DynArray() Int64DynArrayToCSV()
  506. and VariantToInt64() functions (used during TID=Int64 introduction in ORM)
  507. - added RawUnicodeToUtf8() and UTF8ToSynUnicode() overloaded procedures
  508. - added HexToChar/HexToCharValid and UrlDecodeNextName(), UrlDecodeNextValue()
  509. and UrlDecodeNextNameValue() functions
  510. - added Utf8DecodeToRawUnicodeUI() overloaded function returning text as var
  511. - added UrlEncodeJsonObject() and new overloaded JSONDecode() function
  512. - added TRawUTF8DynArrayFrom(const Values: array of RawUTF8) function
  513. - added overloaded function FindRawUTF8() using array of RawUTF8 parameter
  514. - added TPropNameList record/object to maintain a stack-based list of names
  515. - speeed enhancement for TRawUTF8List.Add()
  516. - new TRawUTF8List.SaveToStream and SaveToFile methods
  517. - new TRawUTF8List.PopFirst and PopLast methods
  518. - added optional aOwnObjects parameter to TRawUTF8List.Create() constructor
  519. - new TRawUTF8List.GetObjectByName() method
  520. - refactoring of CaseSensitive property for TRawUTF8List / TRawUTF8ListHashed
  521. - added TRawUTF8List.CaseSensitive property as requested by [806332d296]
  522. - added TRawUTF8MethodList class (based on TRawUTF8ListHashed)
  523. - added TRawUTF8ListHashedLocked class (based on TRawUTF8ListHashed)
  524. - added TPointerClassHash and TPointerClassHashLocked classes (used e.g.
  525. to store RTTI cache, for T*ObjArray process)
  526. - added TAutoLocker/IAutoLocker and TLockedDocVariant/ILockedDocVariant types
  527. - added TAutoFree class, for automatic local variable lifetime management
  528. - added JSON_CONTENT_TYPE_HEADER and XML_CONTENT_TYPE[_HEADER] constants
  529. - new DateToSQL() overloaded function with direct Year/Month/Day parameters
  530. - added Base64MagicDecode(), Base64MagicCheckAndDecode() and SQLToDateTime()
  531. - added IsEqual(const A,B: TSQLFieldBits): boolean function
  532. - enhanced FPC/Lazarus Win32/Win64 compilation
  533. - TDynArrayHashed is now a record with Delphi 2009+, due to a bug in latest
  534. version of Delphi compiler when using TDynArrayHashed = object(TDynArray)
  535. - fixed [7658da5529] unexpected hash collision in TDynArrayHashed
  536. - fixed unexpected GPF in TSynCache.Find() e.g. when cache is disabled
  537. - handle variant serialization in/from JSON using new VariantLoadJSON(),
  538. VariantSaveJSON(), VariantSaveJSONLength() functions and corresponding
  539. TTextWriter.AddVariant() method
  540. - handle variant serialization in/from our binary custom format, using new
  541. VariantLoad(), VariantSaveLength() and VariantSave() functions
  542. - added VariantToUTF8() overloaded functions for fast conversion
  543. - added VariantToInteger()/VariantToIntegerDef()/VariantToInt64() functions
  544. for direct process of numerical variants (e.g. array indexes)
  545. - new RawUTF8ToVariant() and VarRecToVariant() functions
  546. - new RawByteStringToVariant() and VariantToRawByteString() functions
  547. - added VariantDynArrayToJSON/JSONToVariantDynArray/ValuesToVariantDynArray()
  548. - added VariantDynArrayClear() function (faster e.g. for array of TDocVariant)
  549. - added VariantToInlineValue() and VarRecToInlineValue() functions
  550. - added VarRecToInt64(), VarRecToDouble() and VarRecAsChar() functions
  551. - added overloaded Int32ToUTF8() Int64ToStr() Curr64ToStr() ExtendedToStr()
  552. PointerToHex() UInt32ToUtf8() procedures
  553. - handle binary serialization of variant via FromVarVariant() procedure and
  554. TFileBufferWriter.Write() method
  555. - added ToVarString(), FromVarInt64Value() and FromVarBlob() functions
  556. - added TFileBufferWriter.WriteVarInt64 and TFileBufferReader.ReadVarInt64
  557. - added TFileBufferWriter.Write1/Write4/Write8 methods and Tag property
  558. - new overloaded TFileBufferWriter.Create() constructor able to use a TStream
  559. class to replace CreateInRawByteStringStream and CreateInMemoryStream methods
  560. - now TFileBufferReader.Read() allows forward reading when Data=nil
  561. - added RecordSaveJSON() function which follows TTextWriter.AddRecordJSON() format
  562. - added SaveJSON() function to handle any kind of complex types from RTTI
  563. - added TSynNameValue.InitFromIniSection() method and optional default value
  564. parameter to TSynNameValue.Value()
  565. - added TSynNameValue.Delete() and SetBlobDataPtr() methods
  566. - added TSynNameValue.OnAfterAdd callback event
  567. - added TObjectListLocked class
  568. - expose all internal Hash*() functions (following TDynArrayHashOne prototype)
  569. in interface section of the unit
  570. - added crc32c() function using either optimized unrolled version, or SSE 4.2
  571. instruction: crc32cfast() is 1.7 GB/s, crc32csse42() is 3.7 GB/s
  572. - added fnv32() function, slower than kr32, but with less collisions
  573. - added SynLZCompress/SynLZDecompress functions, using crc32c() for hashing
  574. - added SymmetricEncrypt() function
  575. - added GetAllBits() function
  576. - changed GetBitCSV/SetBitCSV CSV format to use 'first-last,' pattern to
  577. regroup set bits (reduce storage size e.g. for TSQLAccessRights) - format
  578. is still compatible with old layout, but will more optimized and readable
  579. - TSynTableStatement.Create() SQL statement parser will handle optional
  580. LIMIT [OFFSET] clause (in new Limit/Offset integer properties),
  581. ORDER BY ... [DESC/ASC] clause (in new OrderByField/OrderByDesc properties),
  582. GROUP BY ... clause (in GroupByField property), "LIKE", "IN(...)" and
  583. "IS [NOT] NULL" operators and custom functions in the WHERE clause
  584. - TSynTableStatement.Where[] is now an array to allow complex WHERE clause
  585. - TSynTableStatement.Select[] is now an array to allow aggregate functions,
  586. (e.g. Count,Max or Distinct), column aliases, or simple +/- computation
  587. - introducing TSQLFieldIndex and TSQLFieldIndexDynArray types and associated
  588. functions so that TSynTableStatement would store the SELECT column order
  589. - SQLParamContent() / ExtractInlineParameters() functions moved from mORMot.pas
  590. (now properly handles SQL null and more than MAX_SQLFIELDS parameters)
  591. - introducing TSQLParamType / TSQLParamTypeDynArray generic parameters
  592. - added RemoveCommentsFromJSON() procedure - from MPV proposal
  593. - added GarbageCollectorFreeAndNil() procedure to handle global variables
  594. proper finalization to nil - avoid error [8e3073c8c7] and [8546b4af1d] e.g.
  595. when used as design package in Delphi IDE (for all globals and class VMTs)
  596. - made GarbageCollectorFree public - may be usefull e.g. with packages
  597. - added GlobalLock/GlobalUnlock functions, used e.g. for ticket [ea4e8bd544]
  598. - fixed rouding issue e.g. for ExtendedToString(double(22.99999999999997))
  599. - fixed potential GPF in TRawUTF8List.SetTextPtr() - ticket [d947b36cf9]
  600. - fixed potential GPF in function UrlDecodeNeedParameters()
  601. - fixed ticket [c8a8c71b12] allowing decoding of URI computed by browsers,
  602. even if they do not follow the RFC 3986 specifications
  603. - fixed potential GPF in serveral functions, when working with WideString
  604. (WideString aka OleStr do store their length in bytes, not WideChars)
  605. - fixed TDynArray.AddArray() method when Count parameter is not specified,
  606. and introducing TDynArray.AddDynArray() method
  607. - fixed ticket [ad55566b10] about JSON string escape parsing
  608. - fixed ticket [cce54e98ca], [388c2768b6] and [355249a9d1] about overflow in
  609. TTextWriter.AddJSONEscapeW()
  610. - fixed ticket [a75c0c6759] about TTextWriter.AddNoJSONEscapeW()
  611. - added TTextWriter.AddHtmlEscape() and TTextWriter.AddXmlEscape() methods
  612. - new TTextWriter.AddHtmlEscapeWiki() method, supporting wiki-like syntax
  613. - TTextWriter.AddJSONEscape/AddJSONEscapeW methods speed up
  614. - fixed ticket [01408fd389] in TRawUTF8List.GetText()
  615. - fixed ticket [e3ae1005dc] about potential GPF in TRawUTF8List.Delete()
  616. - fixed ticket [1c940a4437] to avoid negative value in TPrecisionTimer.PerSec,
  617. in case of incorrect Start/Stop methods sequence
  618. - implement ticket [e3f9742865] for enhanced JSON in soWriteHumanReadable mode
  619. - added TPrecisionTimer.ProfileCurrentMethod() and TimeInMicroSec property
  620. for feature request [1abca090ee]
  621. - added TLocalPrecisionTimer/ILocalPrecisionTimer to alllocate a local timer
  622. instance on the stack
  623. - fixed ticket [815facfe57] in UTF8ILComp()
  624. - fixed UTF8ToWideChar() functions to always append a WideChar(0) to the end
  625. of the destination buffer, even if returned length is 0
  626. - added AnyTextFileToString, AnyTextFileToSynUnicode and AnyTextFileToRawUTF8
  627. - declared PByteArray, PWordArray, PPointerArray here - see [d6b38a96e6]
  628. - fixed IdemPChar() in pure pascal to behave like the asm version (i.e.
  629. if up parameter is nil, will return TRUE)
  630. - added IdemPCharWithoutWhiteSpace() function
  631. - confusing-named RoundTo2Digits() function renamed into Trunc2ToDigit()
  632. - added simple, non banker rounding SimpleRoundTo2Digits() function
  633. - fixed potential comparison error in TSynTableFieldProperties.SortCompare()
  634. when sorting UTF8 Field with tfoCaseInsensitive in Options
  635. - speedup of QuotedStr() function and TDynArrayHashed hashing process
  636. - added GotoEndOfJSONString() function
  637. - added GetJSONPropName() and GotoNextJSONPropName() functions, able to
  638. understand MongoDB extended syntax
  639. - added JSONArrayCount/JSONObjectPropCount, JsonArayItem and
  640. JsonObjectItem/JsonObjectByPath/JsonObjectsByPath functions
  641. - several speedup in GetJSONField() and JSON parsing: it will now expect true,
  642. false or null to be in lowercase only (as in json.org specifications)
  643. - fixed function GetJSONField() to properly decode JSON number with exponent
  644. - added function GetJSONFieldOrObjectOrArray() in unit's interface section
  645. - function GotoNextJSONField() renamed GotoNextJSONItem(), and fixed to
  646. handle nested JSON array or objects in addition to string/numbers
  647. - added GotoEndJSONItem() and GetJSONItemAsRawJSON() functions
  648. - added function JSONRetrieveIDField() for fast retrieval of a "ID":.. value
  649. - added function JSONRetrieveStringField() for retrieval of a string field
  650. name or value from JSON buffer
  651. - added PtrUIntScanIndex() and UnixTimeToDateTime/DateTimeToUnixTime()
  652. UnixMSTimeToDateTime/DateTimeToUnixMSTime functions
  653. - fixed ticket [aff1352239] to identify 9999-12-31 dates as valid
  654. - added Iso8601ToTimePUTF8Char[Var]() and IntervalTextToDateTime[Var]() functions
  655. - added DateTimeToIso8601ExpandedPChar() and Iso8601CheckAndDecode() functions
  656. - added TTimeLogBits.FromUTCTime method and NowUTC / TimeLogNowUTC functions
  657. - added TTimeLogBits.FromUnixTime/FromUnixMSTime/ToUnixTime/ToUnixMSTime
  658. - added TTimeLogBits.Year/Month/Day/Hour/Minute/Second functions
  659. - added GetTickCount64() function, native since Vista, emulated e.g. for XP
  660. - introducing InterlockedIncrement/IntelrlockedDecrement compatibility functions
  661. - fixed TTextWriter.RegisterCustomJSONSerializer() method when unregistering
  662. - fixed TTextWriter.AddFloatStr() method when processing '-.5' input
  663. - fixed potential random GPF in TTextWriter after Flush - see [577ad95cfd0]
  664. - added TTextWriter.Add(const Values: array of const) method
  665. - added JSONToXML() JSONBufferToXML() and TTextWriter.JSONBufferToXML()
  666. for direct and fast conversion of any JSON into the corresponding <XML>
  667. - added JSONReformat() JSONBufferReformat() JSONReformatToFile()
  668. JSONBufferReformatToFile() and TTextWriter.AddJSONReformat()
  669. for fast conversion into more readable, compact or extended layout
  670. - fixed potential GPF issue in TMemoryMapText.LoadFromMap()
  671. - added TMemoryMapText.AddInMemoryLine method to allow runtime appending of
  672. new lines of text - used e.g. by TSynLogFile for life update of remote logs
  673. - added TMemoryMapText.SaveToFile() and TMemoryMapText.SaveToStream() methods
  674. - allow file size of 0 byte in TMemoryMap.Map()
  675. - introduced TSynInvokeableVariantType.Clear() and Copy() default methods
  676. - added TSynInvokeableVariantType.CopyByValue() virtual method
  677. - added TSynInvokeableVariantType.IsOfType() method
  678. - TSynInvokeableVariantType.SetProperty() will now convert any varOleStr into
  679. a RawUTF8/varString, and dereference any simple varByRef transmitted values
  680. so that we could safely use late-binding with any kind of value
  681. - internal DispInvoke() function speed-up by caching the latest accessed type
  682. - enabled DispInvoke() function for Delphi XE2 and up (it will also fix the
  683. regression issue in the new RTL which let the field names be uppercased)
  684. - several TSynTableFieldProperties speed up, when working with variants
  685. - removed several compilation hints when assertions are set to off
  686. - UnCamelCase() functions will now handle capital words and numbers at the
  687. beginning, middle or end of the text - implements request [d0c8210fae]
  688. - added TSynBackgroundThreadAbstract class for generic background process, and
  689. callback-driven TSynBackgroundThreadEvent / TSynBackgroundThreadProcedure /
  690. TSynBackgroundThreadMethod inherited classes
  691. - new TSynParallelProcess for parallel processing of indexed information
  692. - added SetThreadName/SetCurrentThreadName functions for request [6acfd0a3d3]
  693. - added TSynFPUException class to allow per-method customization of the FPU
  694. exception mapping: to be used e.g. when mixing code between external
  695. libraries and Delphi code
  696. - added new TSynValidateNonVoidText and TSynFilterTruncate classes
  697. - added Utf8TruncateToUnicodeLength() and Utf8TruncateToLength() functions
  698. - added MaxAlphaCount, MaxDigitCount, MaxPunctCount, MaxLowerCount and
  699. MaxUpperCount properties to TSynValidateText class
  700. - added ResourceToRawByteString and ResourceSynLZToRawByteString functions
  701. - if DOPATCHTRTL is defined, will enable asm-optimized RecordClear and
  702. _InitializeRecord functions in replacement to the slower RTL version, and
  703. patch TObject.CleanupInstance before Delphi 2009 (since TMonitor.Destroy
  704. is sadly private to System.pas)
  705. - introducing TSQLVar to define database-oriented values
  706. used by SynDB, mORMot, mORMotDB and mORMotSQLite3 units (instead of former
  707. confusing TVarData record, which is now dedicated to variant mapping)
  708. - moved TSQLDBFieldType from SynDB to SynCommons, and used by TSQLVar and all
  709. database-related process (i.e. in mORMot and SynDB units)
  710. - SYNOPSE_FRAMEWORK_VERSION constant will now include a per-commit increasing
  711. number (generated by SourceCodeRep tool), to specify the exact source state
  712. and a more complete SYNOPSE_FRAMEWORK_FULLVERSION constant has been added
  713. *)
  714. {$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER
  715. interface
  716. uses
  717. {$ifndef LVCL}
  718. {$ifndef FPC}
  719. {$ifndef HASFASTMM4}
  720. FastMM4,
  721. {$endif}
  722. {$endif}
  723. {$endif}
  724. {$ifdef MSWINDOWS}
  725. Windows,
  726. Messages,
  727. {$ifndef LVCL}
  728. Registry,
  729. {$endif}
  730. {$else MSWINDOWS}
  731. {$ifdef KYLIX3}
  732. Types,
  733. LibC,
  734. SynKylix,
  735. {$endif}
  736. {$ifdef FPC}
  737. BaseUnix,
  738. {$endif}
  739. {$endif MSWINDOWS}
  740. Classes,
  741. {$ifndef LVCL}
  742. SyncObjs, // for TEvent and TCriticalSection
  743. Contnrs, // for TObjectList
  744. {$ifdef HASINLINE}
  745. Types,
  746. {$endif}
  747. {$endif}
  748. {$ifndef NOVARIANTS}
  749. Variants,
  750. {$endif}
  751. SynLZ, // needed for TSynMapFile .mab format
  752. SysUtils;
  753. const
  754. /// the corresponding version of the freeware Synopse framework
  755. // - includes a commit increasing number (generated by SourceCodeRep tool)
  756. // - a similar constant shall be defined in SynCrtSock.pas
  757. SYNOPSE_FRAMEWORK_VERSION = {$I SynopseCommit.inc};
  758. /// a text including the version and the main active conditional options
  759. // - usefull for low-level debugging purpose
  760. SYNOPSE_FRAMEWORK_FULLVERSION = SYNOPSE_FRAMEWORK_VERSION
  761. {$ifdef LVCL}+'_LVCL'{$else}
  762. {$ifdef ENHANCEDRTL}+' ERTL'{$endif}{$endif}
  763. {$ifdef DOPATCHTRTL}+' PRTL'{$endif}
  764. {$ifdef INCLUDE_FTS3}+' FTS3'{$endif};
  765. { ************ common types used for compatibility between compilers and CPU }
  766. const
  767. /// internal Code Page for UTF-16 Unicode encoding
  768. // - used e.g. for Delphi 2009+ UnicodeString=String type
  769. CP_UTF16 = 1200;
  770. /// fake code page used to recognize TSQLRawBlob
  771. // - as returned e.g. by TTypeInfo.AnsiStringCodePage from mORMot.pas
  772. CP_SQLRAWBLOB = 65534;
  773. /// internal Code Page for RawByteString undefined string
  774. CP_RAWBYTESTRING = 65535;
  775. /// US English Windows Code Page, i.e. WinAnsi standard character encoding
  776. CODEPAGE_US = 1252;
  777. {$ifndef MSWINDOWS}
  778. /// estimate the system code page is WinAnsi
  779. GetACP = CODEPAGE_US;
  780. /// internal Code Page for UTF-8 Unicode encoding
  781. CP_UTF8 = 65001;
  782. {$endif}
  783. {$ifdef FPC} { make cross-compiler and cross-CPU types available to Delphi }
  784. type
  785. PBoolean = ^Boolean;
  786. {$else FPC}
  787. type
  788. /// a CPU-dependent unsigned integer type cast of a pointer / register
  789. // - used for 64 bits compatibility, native under Free Pascal Compiler
  790. {$ifdef ISDELPHI2009}
  791. PtrUInt = cardinal; { see http://synopse.info/forum/viewtopic.php?id=136 }
  792. {$else}
  793. {$ifdef UNICODE}
  794. PtrUInt = NativeUInt;
  795. {$else}
  796. PtrUInt = cardinal;
  797. {$endif}
  798. {$endif}
  799. /// a CPU-dependent unsigned integer type cast of a pointer of pointer
  800. // - used for 64 bits compatibility, native under Free Pascal Compiler
  801. PPtrUInt = ^PtrUInt;
  802. /// a CPU-dependent signed integer type cast of a pointer / register
  803. // - used for 64 bits compatibility, native under Free Pascal Compiler
  804. {$ifdef ISDELPHI2009}
  805. PtrInt = integer;
  806. {$else}
  807. {$ifdef UNICODE}
  808. PtrInt = NativeInt;
  809. {$else}
  810. PtrInt = integer;
  811. {$endif}
  812. {$endif}
  813. /// a CPU-dependent signed integer type cast of a pointer of pointer
  814. // - used for 64 bits compatibility, native under Free Pascal Compiler
  815. PPtrInt = ^PtrInt;
  816. /// unsigned Int64 doesn't exist under older Delphi, but is defined in FPC
  817. // - and UInt64 is buggy as hell under Delphi 2007 when inlining functions
  818. {$ifdef FPC_OR_UNICODE}
  819. QWord = UInt64;
  820. {$else}
  821. QWord = type Int64;
  822. {$endif}
  823. /// points to an unsigned Int64
  824. PQWord = ^QWord;
  825. {$ifndef ISDELPHIXE2}
  826. /// used to store the handle of a system Thread
  827. TThreadID = cardinal;
  828. {$endif}
  829. {$endif FPC}
  830. {$ifdef DELPHI5OROLDER}
  831. // Delphi 5 doesn't have those basic types defined :(
  832. const
  833. varShortInt = $0010;
  834. varInt64 = $0014; { vt_i8 }
  835. soBeginning = soFromBeginning;
  836. soCurrent = soFromCurrent;
  837. reInvalidPtr = 2;
  838. PathDelim = '\';
  839. sLineBreak = #13#10;
  840. type
  841. PPointer = ^Pointer;
  842. PPAnsiChar = ^PAnsiChar;
  843. PInteger = ^Integer;
  844. PCardinal = ^Cardinal;
  845. PWord = ^Word;
  846. PByte = ^Byte;
  847. PBoolean = ^Boolean;
  848. PComp = ^Comp;
  849. THandle = LongWord;
  850. UInt64 = Int64;
  851. PVarData = ^TVarData;
  852. TVarData = packed record
  853. // mostly used for varNull, varInt64, varDouble, varString and varAny
  854. VType: word;
  855. case Integer of
  856. 0: (Reserved1: Word;
  857. case Integer of
  858. 0: (Reserved2, Reserved3: Word;
  859. case Integer of
  860. varSmallInt: (VSmallInt: SmallInt);
  861. varInteger: (VInteger: Integer);
  862. varSingle: (VSingle: Single);
  863. varDouble: (VDouble: Double); // DOUBLE
  864. varCurrency: (VCurrency: Currency);
  865. varDate: (VDate: TDateTime);
  866. varOleStr: (VOleStr: PWideChar);
  867. varDispatch: (VDispatch: Pointer);
  868. varError: (VError: HRESULT);
  869. varBoolean: (VBoolean: WordBool);
  870. varUnknown: (VUnknown: Pointer);
  871. varByte: (VByte: Byte);
  872. varInt64: (VInt64: Int64); // INTEGER
  873. varString: (VString: Pointer); // TEXT
  874. varAny: (VAny: Pointer);
  875. varArray: (VArray: PVarArray);
  876. varByRef: (VPointer: Pointer);
  877. );
  878. 1: (VLongs: array[0..2] of LongInt); );
  879. end;
  880. {$endif}
  881. type
  882. /// a pointer to a PtrUInt array
  883. TPtrUIntArray = array[0..MaxInt div SizeOf(PtrUInt)-1] of PtrUInt;
  884. PPtrUIntArray = ^TPtrUIntArray;
  885. /// a dynamic array of PtrUInt values
  886. TPtrUIntDynArray = array of PtrUInt;
  887. {$ifndef NOVARIANTS}
  888. /// a variant values array
  889. TVariantArray = array[0..MaxInt div SizeOf(Variant)-1] of Variant;
  890. /// a pointer to a variant array
  891. PVariantArray = ^TVariantArray;
  892. /// a dynamic array of variant values
  893. TVariantDynArray = array of variant;
  894. {$endif}
  895. /// RawUnicode is an Unicode String stored in an AnsiString
  896. // - faster than WideString, which are allocated in Global heap (for COM)
  897. // - an AnsiChar(#0) is added at the end, for having a true WideChar(#0) at ending
  898. // - length(RawUnicode) returns memory bytes count: use (length(RawUnicode) shr 1)
  899. // for WideChar count (that's why the definition of this type since Delphi 2009
  900. // is AnsiString(1200) and not UnicodeString)
  901. // - pointer(RawUnicode) is compatible with Win32 'Wide' API call
  902. // - mimic Delphi 2009 UnicodeString, without the WideString or Ansi conversion overhead
  903. // - all conversion to/from AnsiString or RawUTF8 must be explicit: the
  904. // compiler is not able to make valid implicit conversion on CP_UTF16
  905. {$ifdef HASCODEPAGE}
  906. RawUnicode = type AnsiString(CP_UTF16); // Codepage for an UnicodeString
  907. {$else}
  908. RawUnicode = type AnsiString;
  909. {$endif}
  910. /// RawUTF8 is an UTF-8 String stored in an AnsiString
  911. // - use this type instead of System.UTF8String, which behavior changed
  912. // between Delphi 2009 compiler and previous versions: our implementation
  913. // is consistent and compatible with all versions of Delphi compiler
  914. // - mimic Delphi 2009 UTF8String, without the charset conversion overhead
  915. // - all conversion to/from AnsiString or RawUnicode must be explicit
  916. {$ifdef HASCODEPAGE}
  917. RawUTF8 = type AnsiString(CP_UTF8); // Codepage for an UTF8 string
  918. {$else}
  919. RawUTF8 = type AnsiString;
  920. {$endif}
  921. /// WinAnsiString is a WinAnsi-encoded AnsiString (code page 1252)
  922. // - use this type instead of System.String, which behavior changed
  923. // between Delphi 2009 compiler and previous versions: our implementation
  924. // is consistent and compatible with all versions of Delphi compiler
  925. // - all conversion to/from RawUTF8 or RawUnicode must be explicit
  926. {$ifdef HASCODEPAGE}
  927. WinAnsiString = type AnsiString(CODEPAGE_US); // WinAnsi Codepage
  928. {$else}
  929. WinAnsiString = type AnsiString;
  930. {$endif}
  931. {$ifdef HASCODEPAGE}
  932. {$ifdef FPC}
  933. // missing declaration
  934. PRawByteString = ^RawByteString;
  935. {$endif}
  936. {$else}
  937. /// define RawByteString, as it does exist in Delphi 2009+
  938. // - to be used for byte storage into an AnsiString
  939. // - use this type if you don't want the Delphi compiler not to do any
  940. // code page conversions when you assign a typed AnsiString to a RawByteString,
  941. // i.e. a RawUTF8 or a WinAnsiString
  942. RawByteString = type AnsiString;
  943. /// pointer to a RawByteString
  944. PRawByteString = ^RawByteString;
  945. {$endif}
  946. /// RawJSON will indicate that this variable content would stay in raw JSON
  947. // - i.e. won't be serialized into values
  948. // - could be any JSON content: number, string, object or array
  949. // - e.g. interface-based service will use it for efficient and AJAX-ready
  950. // transmission of TSQLTableJSON result
  951. RawJSON = type RawUTF8;
  952. /// SynUnicode is the fastest available Unicode native string type, depending
  953. // on the compiler used
  954. // - this type is native to the compiler, so you can use Length() Copy() and
  955. // such functions with it (this is not possible with RawUnicodeString type)
  956. // - before Delphi 2009+, it uses slow OLE compatible WideString
  957. // (with our Enhanced RTL, WideString allocation can be made faster by using
  958. // an internal caching mechanism of allocation buffers - WideString allocation
  959. // has been made much faster since Windows Vista/Seven)
  960. // - starting with Delphi 2009, it uses fastest UnicodeString type, which
  961. // allow Copy On Write, Reference Counting and fast heap memory allocation
  962. {$ifdef UNICODE}
  963. SynUnicode = UnicodeString;
  964. {$else}
  965. SynUnicode = WideString;
  966. {$endif}
  967. PRawUnicode = ^RawUnicode;
  968. PRawJSON = ^RawJSON;
  969. PRawUTF8 = ^RawUTF8;
  970. PWinAnsiString = ^WinAnsiString;
  971. PWinAnsiChar = type PAnsiChar;
  972. PSynUnicode = ^SynUnicode;
  973. /// a simple wrapper to UTF-8 encoded zero-terminated PAnsiChar
  974. // - PAnsiChar is used only for Win-Ansi encoded text
  975. // - the Synopse mORMot framework uses mostly this PUTF8Char type,
  976. // because all data is internaly stored and expected to be UTF-8 encoded
  977. PUTF8Char = type PAnsiChar;
  978. PPUTF8Char = ^PUTF8Char;
  979. /// a Row/Col array of PUTF8Char, for containing sqlite3_get_table() result
  980. TPUtf8CharArray = array[0..MaxInt div SizeOf(PUTF8Char)-1] of PUTF8Char;
  981. PPUtf8CharArray = ^TPUtf8CharArray;
  982. /// a pointer to a PAnsiChar array
  983. TPAnsiCharArray = array[0..MaxInt div SizeOf(PAnsiChar)-1] of PAnsiChar;
  984. PPAnsiCharArray = ^TPAnsiCharArray;
  985. /// a dynamic array of PUTF8Char pointers
  986. TPUTF8CharDynArray = array of PUTF8Char;
  987. /// a pointer to a RawUTF8 array
  988. TRawUTF8Array = array[0..MaxInt div SizeOf(RawUTF8)-1] of RawUTF8;
  989. PRawUTF8Array = ^TRawUTF8Array;
  990. /// a dynamic array of UTF-8 encoded strings
  991. TRawUTF8DynArray = array of RawUTF8;
  992. PRawUTF8DynArray = ^TRawUTF8DynArray;
  993. /// a dynamic array of dynamic array of UTF-8 encoded strings
  994. TRawUTF8DynArrayDynArray = array of TRawUTF8DynArray;
  995. /// a dynamic array of WinAnsi encoded strings
  996. TWinAnsiDynArray = array of WinAnsiString;
  997. PWinAnsiDynArray = ^TWinAnsiDynArray;
  998. /// a dynamic array of RawByteString
  999. TRawByteStringDynArray = array of RawByteString;
  1000. /// a dynamic array of TVarRec, i.e. could match an "array of const" parameter
  1001. TTVarRecDynArray = array of TVarRec;
  1002. /// a dynamic array of generic VCL strings
  1003. TStringDynArray = array of string;
  1004. PStringDynArray = ^TStringDynArray;
  1005. /// a dynamic array of TDateTime values
  1006. TDateTimeDynArray = array of TDateTime;
  1007. PDateTimeDynArray = ^TDateTimeDynArray;
  1008. {$ifndef DELPHI5OROLDER}
  1009. /// a dynamic array of interface values
  1010. TInterfaceDynArray = array of IInterface;
  1011. PInterfaceDynArray = ^TInterfaceDynArray;
  1012. {$endif}
  1013. /// a dynamic array of WideString values
  1014. TWideStringDynArray = array of WideString;
  1015. PWideStringDynArray = ^TWideStringDynArray;
  1016. /// a dynamic array of SynUnicode values
  1017. TSynUnicodeDynArray = array of SynUnicode;
  1018. PSynUnicodeDynArray = ^TSynUnicodeDynArray;
  1019. PIntegerDynArray = ^TIntegerDynArray;
  1020. TIntegerDynArray = array of integer;
  1021. PCardinalDynArray = ^TCardinalDynArray;
  1022. TCardinalDynArray = array of cardinal;
  1023. PSingleDynArray = ^TSingleDynArray;
  1024. TSingleDynArray = array of Single;
  1025. PInt64DynArray = ^TInt64DynArray;
  1026. TInt64DynArray = array of Int64;
  1027. PDoubleDynArray = ^TDoubleDynArray;
  1028. TDoubleDynArray = array of double;
  1029. PCurrencyDynArray = ^TCurrencyDynArray;
  1030. TCurrencyDynArray = array of Currency;
  1031. TWordDynArray = array of word;
  1032. PWordDynArray = ^TWordDynArray;
  1033. TByteDynArray = array of byte;
  1034. PByteDynArray = ^TByteDynArray;
  1035. TObjectDynArray = array of TObject;
  1036. PObjectDynArray = ^TObjectDynArray;
  1037. TPersistentDynArray = array of TPersistent;
  1038. PPersistentDynArray = ^TPersistentDynArray;
  1039. TPointerDynArray = array of pointer;
  1040. PPointerDynArray = ^TPointerDynArray;
  1041. TPPointerDynArray = array of PPointer;
  1042. PPPointerDynArray = ^TPPointerDynArray;
  1043. TMethodDynArray = array of TMethod;
  1044. PMethodDynArray = ^TMethodDynArray;
  1045. TObjectListDynArray = array of TObjectList;
  1046. PObjectListDynArray = ^TObjectListDynArray;
  1047. TFileNameDynArray = array of TFileName;
  1048. PFileNameDynArray = ^TFileNameDynArray;
  1049. TBooleanDynArray = array of boolean;
  1050. PBooleanDynArray = ^TBooleanDynArray;
  1051. PByteArray = ^TByteArray;
  1052. TByteArray = array[0..MaxInt-1] of Byte; // redefine here with {$R-}
  1053. PBooleanArray = ^TBooleanArray;
  1054. TBooleanArray = array[0..MaxInt-1] of Boolean;
  1055. TWordArray = array[0..MaxInt div SizeOf(word)-1] of word;
  1056. PWordArray = ^TWordArray;
  1057. TIntegerArray = array[0..MaxInt div SizeOf(integer)-1] of integer;
  1058. PIntegerArray = ^TIntegerArray;
  1059. TCardinalArray = array[0..MaxInt div SizeOf(cardinal)-1] of cardinal;
  1060. PCardinalArray = ^TCardinalArray;
  1061. TInt64Array = array[0..MaxInt div SizeOf(Int64)-1] of Int64;
  1062. PInt64Array = ^TInt64Array;
  1063. TSmallIntArray = array[0..MaxInt div SizeOf(SmallInt)-1] of SmallInt;
  1064. PSmallIntArray = ^TSmallIntArray;
  1065. TSingleArray = array[0..MaxInt div SizeOf(Single)-1] of Single;
  1066. PSingleArray = ^TSingleArray;
  1067. TDoubleArray = array[0..MaxInt div SizeOf(double)-1] of double;
  1068. PDoubleArray = ^TDoubleArray;
  1069. TRawByteStringArray = array[0..MaxInt div SizeOf(RawByteString)-1] of RawByteString;
  1070. PRawByteStringArray = ^TRawByteStringArray;
  1071. PointerArray = array [0..MaxInt div SizeOf(pointer)-1] of Pointer;
  1072. PPointerArray = ^PointerArray;
  1073. TObjectArray = array [0..MaxInt div SizeOf(TObject)-1] of TObject;
  1074. PObjectArray = ^TObjectArray;
  1075. TPtrIntArray = array[0..MaxInt div SizeOf(PtrInt)-1] of PtrInt;
  1076. PPtrIntArray = ^TPtrIntArray;
  1077. TGUIDDynArray = array of TGUID;
  1078. PInt64Rec = ^Int64Rec;
  1079. {$ifndef DELPHI5OROLDER}
  1080. PIInterface = ^IInterface;
  1081. {$endif}
  1082. {$ifndef LVCL}
  1083. TCollectionClass = class of TCollection;
  1084. TCollectionItemClass = class of TCollectionItem;
  1085. {$endif}
  1086. /// class-reference type (metaclass) of a TStream
  1087. TStreamClass = class of TStream;
  1088. /// class-reference type (metaclass) of a TInterfacedObject
  1089. TInterfacedObjectClass = class of TInterfacedObject;
  1090. PObject = ^TObject;
  1091. { ************ fast UTF-8 / Unicode / Ansi types and conversion routines **** }
  1092. type
  1093. /// kind of adding in a TTextWriter
  1094. TTextWriterKind = (twNone, twJSONEscape, twOnSameLine);
  1095. /// an abstract class to handle Ansi to/from Unicode translation
  1096. // - implementations of this class will handle efficiently all Code Pages
  1097. // - this default implementation will use the Operating System APIs
  1098. // - you should not create your own class instance by yourself, but should
  1099. // better retrieve an instance using TSynAnsiConvert.Engine(), which will
  1100. // initialize either a TSynAnsiFixedWidth or a TSynAnsiConvert instance on need
  1101. TSynAnsiConvert = class
  1102. protected
  1103. fCodePage: cardinal;
  1104. fAnsiCharShift: byte;
  1105. {$ifdef KYLIX3}
  1106. fIConvCodeName: RawUTF8;
  1107. {$endif}
  1108. procedure InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  1109. DestTextWriter: TObject; Escape: TTextWriterKind); virtual;
  1110. public
  1111. /// initialize the internal conversion engine
  1112. constructor Create(aCodePage: cardinal); reintroduce; virtual;
  1113. /// returns the engine corresponding to a given code page
  1114. // - a global list of TSynAnsiConvert instances is handled by the unit -
  1115. // therefore, caller should not release the returned instance
  1116. // - will return nil in case of unhandled code page
  1117. // - is aCodePage is 0, will return CurrentAnsiConvert value
  1118. class function Engine(aCodePage: cardinal): TSynAnsiConvert;
  1119. /// direct conversion of a PAnsiChar buffer into an Unicode buffer
  1120. // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
  1121. // - this default implementation will use the Operating System APIs
  1122. function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; overload; virtual;
  1123. /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
  1124. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1125. // - a #0 char is appended at the end (and result will point to it)
  1126. // - this default implementation will use the Operating System APIs
  1127. function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; overload; virtual;
  1128. /// convert any Ansi Text into an UTF-16 Unicode String
  1129. // - returns a value using our RawUnicode kind of string
  1130. function AnsiToRawUnicode(const AnsiText: RawByteString): RawUnicode; overload;
  1131. /// convert any Ansi buffer into an Unicode String
  1132. // - returns a value using our RawUnicode kind of string
  1133. function AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode; overload; virtual;
  1134. /// convert any Ansi buffer into an Unicode String
  1135. // - returns a SynUnicode, i.e. Delphi 2009+ UnicodeString or a WideString
  1136. function AnsiToUnicodeString(Source: PAnsiChar; SourceChars: Cardinal): SynUnicode; overload;
  1137. /// convert any Ansi buffer into an Unicode String
  1138. // - returns a SynUnicode, i.e. Delphi 2009+ UnicodeString or a WideString
  1139. function AnsiToUnicodeString(const Source: RawByteString): SynUnicode; overload;
  1140. /// convert any Ansi Text into an UTF-8 encoded String
  1141. // - internaly calls AnsiBufferToUTF8 virtual method
  1142. function AnsiToUTF8(const AnsiText: RawByteString): RawUTF8; virtual;
  1143. /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded string
  1144. // - will call AnsiBufferToUnicode() overloaded virtual method
  1145. function AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8; overload; virtual;
  1146. /// direct conversion of an Unicode buffer into a PAnsiChar buffer
  1147. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1148. // - this default implementation will rely on the Operating System for
  1149. // all non ASCII-7 chars
  1150. function UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; overload; virtual;
  1151. /// direct conversion of an Unicode buffer into an Ansi Text
  1152. function UnicodeBufferToAnsi(Source: PWideChar; SourceChars: Cardinal): RawByteString; overload;
  1153. /// convert any Unicode-encoded String into Ansi Text
  1154. // - internaly calls UnicodeBufferToAnsi virtual method
  1155. function RawUnicodeToAnsi(const Source: RawUnicode): RawByteString;
  1156. /// direct conversion of an UTF-8 encoded buffer into a PAnsiChar buffer
  1157. // - Dest^ buffer must be reserved with at least SourceChars bytes
  1158. function UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar; overload; virtual;
  1159. /// convert any UTF-8 encoded buffer into Ansi Text
  1160. // - internaly calls UTF8BufferToAnsi virtual method
  1161. function UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal): RawByteString; overload;
  1162. {$ifdef HASINLINE}inline;{$endif}
  1163. /// convert any UTF-8 encoded buffer into Ansi Text
  1164. // - internaly calls UTF8BufferToAnsi virtual method
  1165. procedure UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal; var result: RawByteString); overload; virtual;
  1166. /// convert any UTF-8 encoded String into Ansi Text
  1167. // - internaly calls UTF8BufferToAnsi virtual method
  1168. function UTF8ToAnsi(const UTF8: RawUTF8): RawByteString; virtual;
  1169. /// direct conversion of a UTF-8 encoded string into a WinAnsi buffer
  1170. // - will truncate the destination string to DestSize bytes (including the
  1171. // trailing #0), with a maximum handled size of 2048 bytes
  1172. // - returns the number of bytes stored in Dest^ (i.e. the position of #0)
  1173. function Utf8ToAnsiBuffer(const S: RawUTF8; Dest: PAnsiChar; DestSize: integer): integer;
  1174. /// convert any Ansi Text (providing a From converted) into Ansi Text
  1175. function AnsiToAnsi(From: TSynAnsiConvert; const Source: RawByteString): RawByteString; overload;
  1176. /// convert any Ansi buffer (providing a From converted) into Ansi Text
  1177. function AnsiToAnsi(From: TSynAnsiConvert; Source: PAnsiChar; SourceChars: cardinal): RawByteString; overload;
  1178. {$ifdef HASINLINE}inline;{$endif}
  1179. /// corresponding code page
  1180. property CodePage: Cardinal read fCodePage;
  1181. end;
  1182. /// a class to handle Ansi to/from Unicode translation of fixed width encoding
  1183. // (i.e. non MBCS)
  1184. // - this class will handle efficiently all Code Page availables without MBCS
  1185. // encoding - like WinAnsi (1252) or Russian (1251)
  1186. // - it will use internal fast look-up tables for such encodings
  1187. // - this class could take some time to generate, and will consume more than
  1188. // 64 KB of memory: you should not create your own class instance by yourself,
  1189. // but should better retrieve an instance using TSynAnsiConvert.Engine(), which
  1190. // will initialize either a TSynAnsiFixedWidth or a TSynAnsiConvert instance
  1191. // on need
  1192. // - this class has some additional methods (e.g. IsValid*) which take
  1193. // advantage of the internal lookup tables to provide some fast process
  1194. TSynAnsiFixedWidth = class(TSynAnsiConvert)
  1195. protected
  1196. fAnsiToWide: TWordDynArray;
  1197. fWideToAnsi: TByteDynArray;
  1198. procedure InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  1199. DestTextWriter: TObject; Escape: TTextWriterKind); override;
  1200. public
  1201. /// initialize the internal conversion engine
  1202. constructor Create(aCodePage: cardinal); override;
  1203. /// direct conversion of a PAnsiChar buffer into an Unicode buffer
  1204. // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
  1205. function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; override;
  1206. /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded buffer
  1207. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1208. // - a #0 char is appended at the end (and result will point to it)
  1209. function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; override;
  1210. /// convert any Ansi buffer into an Unicode String
  1211. // - returns a value using our RawUnicode kind of string
  1212. function AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode; override;
  1213. /// direct conversion of an Unicode buffer into a PAnsiChar buffer
  1214. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1215. // - this overridden version will use internal lookup tables for fast process
  1216. function UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; override;
  1217. /// direct conversion of an UTF-8 encoded buffer into a PAnsiChar buffer
  1218. // - Dest^ buffer must be reserved with at least SourceChars bytes
  1219. function UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar; override;
  1220. /// conversion of a wide char into the corresponding Ansi character
  1221. // - return -1 for an unknown WideChar in the current code page
  1222. function WideCharToAnsiChar(wc: cardinal): integer;
  1223. /// return TRUE if the supplied unicode buffer only contains characters of
  1224. // the corresponding Ansi code page
  1225. // - i.e. if the text can be displayed using this code page
  1226. function IsValidAnsi(WideText: PWideChar; Length: integer): boolean; overload;
  1227. /// return TRUE if the supplied unicode buffer only contains characters of
  1228. // the corresponding Ansi code page
  1229. // - i.e. if the text can be displayed using this code page
  1230. function IsValidAnsi(WideText: PWideChar): boolean; overload;
  1231. /// return TRUE if the supplied UTF-8 buffer only contains characters of
  1232. // the corresponding Ansi code page
  1233. // - i.e. if the text can be displayed using this code page
  1234. function IsValidAnsiU(UTF8Text: PUTF8Char): boolean;
  1235. /// return TRUE if the supplied UTF-8 buffer only contains 8 bits characters
  1236. // of the corresponding Ansi code page
  1237. // - i.e. if the text can be displayed with only 8 bit unicode characters
  1238. // (e.g. no "tm" or such) within this code page
  1239. function IsValidAnsiU8Bit(UTF8Text: PUTF8Char): boolean;
  1240. /// direct access to the Ansi-To-Unicode lookup table
  1241. // - use this array like AnsiToWide: array[byte] of word
  1242. property AnsiToWide: TWordDynArray read fAnsiToWide;
  1243. /// direct access to the Unicode-To-Ansi lookup table
  1244. // - use this array like WideToAnsi: array[word] of byte
  1245. // - any unhandled WideChar will return ord('?')
  1246. property WideToAnsi: TByteDynArray read fWideToAnsi;
  1247. end;
  1248. /// a class to handle UTF-8 to/from Unicode translation
  1249. // - match the TSynAnsiConvert signature, for code page CP_UTF8
  1250. // - this class is mostly a non-operation for conversion to/from UTF-8
  1251. TSynAnsiUTF8 = class(TSynAnsiConvert)
  1252. protected
  1253. procedure InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  1254. DestTextWriter: TObject; Escape: TTextWriterKind); override;
  1255. public
  1256. /// initialize the internal conversion engine
  1257. constructor Create(aCodePage: cardinal); override;
  1258. /// direct conversion of a PAnsiChar UTF-8 buffer into an Unicode buffer
  1259. // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
  1260. function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; override;
  1261. /// direct conversion of a PAnsiChar UTF-8 buffer into a UTF-8 encoded buffer
  1262. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1263. // - a #0 char is appended at the end (and result will point to it)
  1264. function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; override;
  1265. /// convert any UTF-8 Ansi buffer into an Unicode String
  1266. // - returns a value using our RawUnicode kind of string
  1267. function AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode; override;
  1268. /// direct conversion of an Unicode buffer into a PAnsiChar UTF-8 buffer
  1269. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1270. function UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; override;
  1271. /// direct conversion of an UTF-8 encoded buffer into a PAnsiChar UTF-8 buffer
  1272. // - Dest^ buffer must be reserved with at least SourceChars bytes
  1273. function UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar; override;
  1274. /// convert any UTF-8 encoded buffer into Ansi Text
  1275. procedure UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal; var result: RawByteString); override;
  1276. /// convert any UTF-8 encoded String into Ansi Text
  1277. // - internaly calls UTF8BufferToAnsi virtual method
  1278. function UTF8ToAnsi(const UTF8: RawUTF8): RawByteString; override;
  1279. /// convert any Ansi Text into an UTF-8 encoded String
  1280. function AnsiToUTF8(const AnsiText: RawByteString): RawUTF8; override;
  1281. /// direct conversion of a PAnsiChar buffer into a UTF-8 encoded string
  1282. function AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8; override;
  1283. end;
  1284. /// a class to handle UTF-16 to/from Unicode translation
  1285. // - match the TSynAnsiConvert signature, for code page CP_UTF16
  1286. // - even if UTF-16 is not an Ansi format, code page CP_UTF16 may have been
  1287. // used to store UTF-16 encoded binary content
  1288. // - this class is mostly a non-operation for conversion to/from Unicode
  1289. TSynAnsiUTF16 = class(TSynAnsiConvert)
  1290. public
  1291. /// initialize the internal conversion engine
  1292. constructor Create(aCodePage: cardinal); override;
  1293. /// direct conversion of a PAnsiChar UTF-16 buffer into an Unicode buffer
  1294. // - Dest^ buffer must be reserved with at least SourceChars*2 bytes
  1295. function AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal): PWideChar; override;
  1296. /// direct conversion of a PAnsiChar UTF-16 buffer into a UTF-8 encoded buffer
  1297. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1298. // - a #0 char is appended at the end (and result will point to it)
  1299. function AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char; override;
  1300. /// convert any UTF-16 Ansi buffer into an Unicode String
  1301. // - returns a value using our RawUnicode kind of string
  1302. function AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode; override;
  1303. /// direct conversion of an Unicode buffer into a PAnsiChar UTF-16 buffer
  1304. // - Dest^ buffer must be reserved with at least SourceChars*3 bytes
  1305. function UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; override;
  1306. /// direct conversion of an UTF-8 encoded buffer into a PAnsiChar UTF-16 buffer
  1307. // - Dest^ buffer must be reserved with at least SourceChars bytes
  1308. function UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar; override;
  1309. end;
  1310. /// implements a stack-based storage of some (UTF-8 or binary) text
  1311. // - could be used e.g. to make a temporary copy when JSON would be
  1312. // parsed in-place
  1313. // - call one of the Init() overloaded methods, then Done to release its memory
  1314. // - will avoid temporary memory allocation via the heap for up to 4KB of text
  1315. {$ifdef UNICODE}
  1316. TSynTempBuffer = record
  1317. {$else}
  1318. TSynTempBuffer = object
  1319. {$endif}
  1320. public
  1321. /// the text length, in bytes, excluding the trailing #0
  1322. len: integer;
  1323. /// where the text has been copied
  1324. // - equals nil if len=0
  1325. buf: pointer;
  1326. /// initialize a temporary copy of the supplied text supplied as RawByteString
  1327. procedure Init(const Source: RawByteString); overload;
  1328. /// initialize a temporary copy of the supplied text buffer, ending with #0
  1329. procedure Init(Source: PUTF8Char); overload;
  1330. /// initialize a temporary copy of the supplied text buffer
  1331. procedure Init(Source: pointer; SourceLen: integer); overload;
  1332. /// initialize a new temporary buffer of a given number of bytes
  1333. procedure Init(SourceLen: integer); overload;
  1334. /// finalize the temporary storage
  1335. procedure Done; {$ifdef HASINLINE}inline;{$endif}
  1336. private
  1337. tmp: array[0..4095] of AnsiChar;
  1338. end;
  1339. /// implements a stack-based writable storage of binary content
  1340. // - memory allocation is performed via a TSynTempBuffer
  1341. {$ifdef UNICODE}
  1342. TSynTempWriter = record
  1343. {$else}
  1344. TSynTempWriter = object
  1345. {$endif}
  1346. private
  1347. tmp: TSynTempBuffer;
  1348. public
  1349. /// the current writable position in tmp.buf
  1350. pos: PAnsiChar;
  1351. /// initialize a new temporary buffer of a given number of bytes
  1352. // - if maxsize is left to its 0 default value, the default stack-allocated
  1353. // memory size is used, i.e. 4 KB
  1354. procedure Init(maxsize: integer=0);
  1355. /// finalize the temporary storage
  1356. procedure Done;
  1357. /// append some binary to the internal buffer
  1358. // - would raise an ESynException in case of potential overflow
  1359. procedure wr(const val; len: integer);
  1360. /// append some shortstring as binary to the internal buffer
  1361. procedure wrss(const str: shortstring);
  1362. /// append some 8-bit value as binary to the internal buffer
  1363. procedure wrb(b: byte);
  1364. /// append some 16-bit value as binary to the internal buffer
  1365. procedure wrw(w: word);
  1366. /// append some 32-bit value as binary to the internal buffer
  1367. procedure wrint(int: integer);
  1368. /// append some 32-bit/64-bit pointer value as binary to the internal buffer
  1369. procedure wrptr(ptr: pointer);
  1370. /// append some 32-bit/64-bit integer as binary to the internal buffer
  1371. procedure wrptrint(int: PtrInt);
  1372. /// append some fixed-value bytes as binary to the internal buffer
  1373. // - returns a pointer to the first byte of the added memory chunk
  1374. function wrfillchar(count: integer; value: byte): PAnsiChar;
  1375. /// returns the current offset position in the internal buffer
  1376. function Position: integer;
  1377. /// returns the buffer as a RawByteString instance
  1378. function AsBinary: RawByteString;
  1379. end;
  1380. var
  1381. /// global TSynAnsiConvert instance to handle WinAnsi encoding (code page 1252)
  1382. // - this instance is global and instantied during the whole program life time
  1383. // - it will be created from hard-coded values, and not using the system API,
  1384. // since it appeared that some systems (e.g. in Russia) did tweak the registry
  1385. // so that 1252 code page maps 1251 code page
  1386. WinAnsiConvert: TSynAnsiFixedWidth;
  1387. /// global TSynAnsiConvert instance to handle current system encoding
  1388. // - this is the encoding as used by the AnsiString Delphi, so will be used
  1389. // before Delphi 2009 to speed-up VCL string handling (especially for UTF-8)
  1390. // - this instance is global and instantied during the whole program life time
  1391. CurrentAnsiConvert: TSynAnsiConvert;
  1392. /// global TSynAnsiConvert instance to handle UTF-8 encoding (code page CP_UTF8)
  1393. // - this instance is global and instantied during the whole program life time
  1394. UTF8AnsiConvert: TSynAnsiUTF8;
  1395. const
  1396. /// HTTP header name for the content type, as defined in the corresponding RFC
  1397. HEADER_CONTENT_TYPE = 'Content-Type: ';
  1398. /// HTTP header name for the content type, in upper case
  1399. // - as defined in the corresponding RFC
  1400. // - could be used e.g. with IdemPChar() to retrieve the Content-Type value
  1401. HEADER_CONTENT_TYPE_UPPER = 'CONTENT-TYPE: ';
  1402. /// MIME content type used for JSON communication (as used by the Microsoft
  1403. // WCF framework and the YUI framework)
  1404. JSON_CONTENT_TYPE = 'application/json; charset=UTF-8';
  1405. /// HTTP header for MIME content type used for plain JSON
  1406. JSON_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE+JSON_CONTENT_TYPE;
  1407. /// MIME content type used for plain JSON, in upper case
  1408. // - could be used e.g. with IdemPChar() to retrieve the Content-Type value
  1409. JSON_CONTENT_TYPE_UPPER = 'APPLICATION/JSON';
  1410. /// HTTP header for MIME content type used for plain JSON, in upper case
  1411. // - could be used e.g. with IdemPChar() to retrieve the Content-Type value
  1412. JSON_CONTENT_TYPE_HEADER_UPPER = HEADER_CONTENT_TYPE_UPPER+JSON_CONTENT_TYPE_UPPER;
  1413. /// MIME content type used for plain UTF-8 text
  1414. TEXT_CONTENT_TYPE = 'text/plain; charset=UTF-8';
  1415. /// HTTP header for MIME content type used for plain UTF-8 text
  1416. TEXT_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE+TEXT_CONTENT_TYPE;
  1417. /// MIME content type used for UTF-8 encoded HTML
  1418. HTML_CONTENT_TYPE = 'text/html; charset=UTF-8';
  1419. /// HTTP header for MIME content type used for UTF-8 encoded HTML
  1420. HTML_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE+HTML_CONTENT_TYPE;
  1421. /// MIME content type used for UTF-8 encoded XML
  1422. XML_CONTENT_TYPE = 'text/xml; charset=UTF-8';
  1423. /// HTTP header for MIME content type used for UTF-8 encoded XML
  1424. XML_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE+XML_CONTENT_TYPE;
  1425. /// MIME content type used for raw binary data
  1426. BINARY_CONTENT_TYPE = 'application/octet-stream';
  1427. /// HTTP header for MIME content type used for raw binary data
  1428. BINARY_CONTENT_TYPE_HEADER = HEADER_CONTENT_TYPE+BINARY_CONTENT_TYPE;
  1429. var
  1430. /// MIME content type used for JSON communication
  1431. // - this global will be initialized with JSON_CONTENT_TYPE constant, to
  1432. // avoid a memory allocation each time it is assigned to a variable
  1433. JSON_CONTENT_TYPE_VAR: RawUTF8;
  1434. /// HTTP header for MIME content type used for plain JSON
  1435. // - this global will be initialized with JSON_CONTENT_TYPE_HEADER constant,
  1436. // to avoid a memory allocation each time it is assigned to a variable
  1437. JSON_CONTENT_TYPE_HEADER_VAR: RawUTF8;
  1438. /// faster equivalence to SetString() function for a RawUTF8
  1439. // - will reallocate the content in-place if the string refcount is 1
  1440. // - to be used instead of SetString() for "var" RawUTF8 parameters
  1441. // - for RawUTF8 function result, SetString is still faster:
  1442. // ! SynCommons.UInt32ToUtf8(Value: cardinal): RawUTF8; SetRawUTF8 245.64ms
  1443. // ! SynCommons.UInt32ToUtf8(Value: cardinal): RawUTF8; SetString 136.39ms
  1444. procedure SetRawUTF8(var Dest: RawUTF8; text: pointer; len: integer);
  1445. /// faster equivalence to SetString(s,nil,len) function for a RawUTF8
  1446. // - won't allocate the content if the string refcount is 1 and len matches
  1447. procedure FastNewRawUTF8(var s: RawUTF8; len: integer);
  1448. /// equivalence to @UTF8[1] expression to ensure a RawUTF8 variable is unique
  1449. // - will ensure that the string refcount is 1, and return a pointer to the text
  1450. // - under FPC, @UTF8[1] does not call UniqueString() as it does with Delphi
  1451. // - if UTF8 is a constant (refcount=-1), will create a temporary copy in heap
  1452. function UniqueRawUTF8(var UTF8: RawUTF8): pointer;
  1453. {$ifdef HASINLINE}inline;{$endif}
  1454. /// conversion of a wide char into a WinAnsi (CodePage 1252) char
  1455. // - return '?' for an unknown WideChar in code page 1252
  1456. function WideCharToWinAnsiChar(wc: cardinal): AnsiChar;
  1457. {$ifdef HASINLINE}inline;{$endif}
  1458. /// conversion of a wide char into a WinAnsi (CodePage 1252) char index
  1459. // - return -1 for an unknown WideChar in code page 1252
  1460. function WideCharToWinAnsi(wc: cardinal): integer;
  1461. {$ifdef HASINLINE}inline;{$endif}
  1462. /// return TRUE if the supplied buffer only contains 7-bits Ansi characters
  1463. function IsAnsiCompatible(PC: PAnsiChar): boolean; overload;
  1464. /// return TRUE if the supplied buffer only contains 7-bits Ansi characters
  1465. function IsAnsiCompatible(PC: PAnsiChar; Len: integer): boolean; overload;
  1466. /// return TRUE if the supplied buffer only contains 7-bits Ansi characters
  1467. function IsAnsiCompatible(PW: PWideChar): boolean; overload;
  1468. /// return TRUE if the supplied text only contains 7-bits Ansi characters
  1469. function IsAnsiCompatible(const Text: RawByteString): boolean; overload;
  1470. {$ifdef HASINLINE}inline;{$endif}
  1471. /// return TRUE if the supplied buffer only contains 7-bits Ansi characters
  1472. function IsAnsiCompatible(PW: PWideChar; Len: integer): boolean; overload;
  1473. /// return TRUE if the supplied unicode buffer only contains WinAnsi characters
  1474. // - i.e. if the text can be displayed using ANSI_CHARSET
  1475. function IsWinAnsi(WideText: PWideChar): boolean; overload;
  1476. {$ifdef HASINLINE}inline;{$endif}
  1477. /// return TRUE if the supplied unicode buffer only contains WinAnsi characters
  1478. // - i.e. if the text can be displayed using ANSI_CHARSET
  1479. function IsWinAnsi(WideText: PWideChar; Length: integer): boolean; overload;
  1480. {$ifdef HASINLINE}inline;{$endif}
  1481. /// return TRUE if the supplied UTF-8 buffer only contains WinAnsi characters
  1482. // - i.e. if the text can be displayed using ANSI_CHARSET
  1483. function IsWinAnsiU(UTF8Text: PUTF8Char): boolean;
  1484. {$ifdef HASINLINE}inline;{$endif}
  1485. /// return TRUE if the supplied UTF-8 buffer only contains WinAnsi 8 bit characters
  1486. // - i.e. if the text can be displayed using ANSI_CHARSET with only 8 bit unicode
  1487. // characters (e.g. no "tm" or such)
  1488. function IsWinAnsiU8Bit(UTF8Text: PUTF8Char): boolean;
  1489. {$ifdef HASINLINE}inline;{$endif}
  1490. /// UTF-8 encode one UTF-16 character into Dest
  1491. // - return the number of bytes written into Dest (i.e. 1,2 or 3)
  1492. // - this method does NOT handle UTF-16 surrogate pairs
  1493. function WideCharToUtf8(Dest: PUTF8Char; aWideChar: PtrUInt): integer;
  1494. {$ifdef HASINLINE}inline;{$endif}
  1495. /// UTF-8 encode one UTF-16 encoded UCS4 character into Dest
  1496. // - return the number of bytes written into Dest (i.e. from 1 up to 6)
  1497. // - Source will contain the next UTF-16 character
  1498. // - this method DOES handle UTF-16 surrogate pairs
  1499. function UTF16CharToUtf8(Dest: PUTF8Char; var Source: PWord): integer;
  1500. /// UTF-8 encode one UCS4 character into Dest
  1501. // - return the number of bytes written into Dest (i.e. from 1 up to 6)
  1502. // - this method DOES handle UTF-16 surrogate pairs
  1503. function UCS4ToUTF8(ucs4: cardinal; Dest: PUTF8Char): integer;
  1504. /// direct conversion of an AnsiString with an unknown code page into an
  1505. // UTF-8 encoded String
  1506. // - will assume CurrentAnsiConvert.CodePage prior to Delphi 2009
  1507. // - newer UNICODE versions of Delphi will retrieve the code page from string
  1508. procedure AnyAnsiToUTF8(const s: RawByteString; var result: RawUTF8); overload;
  1509. /// direct conversion of an AnsiString with an unknown code page into an
  1510. // UTF-8 encoded String
  1511. // - will assume CurrentAnsiConvert.CodePage prior to Delphi 2009
  1512. // - newer UNICODE versions of Delphi will retrieve the code page from string
  1513. function AnyAnsiToUTF8(const s: RawByteString): RawUTF8; overload;
  1514. {$ifdef HASINLINE}inline;{$endif}
  1515. /// direct conversion of a WinAnsi (CodePage 1252) string into a UTF-8 encoded String
  1516. // - faster than SysUtils: don't use Utf8Encode(WideString) -> no Windows.Global(),
  1517. // and use a fixed pre-calculated array for individual chars conversion
  1518. function WinAnsiToUtf8(const S: WinAnsiString): RawUTF8; overload;
  1519. {$ifdef HASINLINE}inline;{$endif}
  1520. /// direct conversion of a WinAnsi (CodePage 1252) string into a UTF-8 encoded String
  1521. // - faster than SysUtils: don't use Utf8Encode(WideString) -> no Windows.Global(),
  1522. // and use a fixed pre-calculated array for individual chars conversion
  1523. function WinAnsiToUtf8(WinAnsi: PAnsiChar; WinAnsiLen: integer): RawUTF8; overload;
  1524. {$ifdef HASINLINE}inline;{$endif}
  1525. /// direct conversion of a WinAnsi PAnsiChar buffer into a UTF-8 encoded buffer
  1526. // - Dest^ buffer must be reserved with at least SourceChars*3
  1527. // - call internally WinAnsiConvert fast conversion class
  1528. function WinAnsiBufferToUtf8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
  1529. {$ifdef HASINLINE}inline;{$endif}
  1530. /// direct conversion of a WinAnsi shortstring into a UTF-8 text
  1531. // - call internally WinAnsiConvert fast conversion class
  1532. function ShortStringToUTF8(const source: ShortString): RawUTF8;
  1533. {$ifdef HASINLINE}inline;{$endif}
  1534. /// direct conversion of a WinAnsi (CodePage 1252) string into a Unicode encoded String
  1535. // - very fast, by using a fixed pre-calculated array for individual chars conversion
  1536. function WinAnsiToRawUnicode(const S: WinAnsiString): RawUnicode;
  1537. /// direct conversion of a WinAnsi (CodePage 1252) string into a Unicode buffer
  1538. // - very fast, by using a fixed pre-calculated array for individual chars conversion
  1539. // - text will be truncated if necessary to avoid buffer overflow in Dest[]
  1540. procedure WinAnsiToUnicodeBuffer(const S: WinAnsiString; Dest: PWordArray; DestLen: integer);
  1541. {$ifdef HASINLINE}inline;{$endif}
  1542. /// direct conversion of a UTF-8 encoded string into a WinAnsi String
  1543. function Utf8ToWinAnsi(const S: RawUTF8): WinAnsiString; overload;
  1544. {$ifdef HASINLINE}inline;{$endif}
  1545. /// direct conversion of a UTF-8 encoded zero terminated buffer into a WinAnsi String
  1546. function Utf8ToWinAnsi(P: PUTF8Char): WinAnsiString; overload;
  1547. {$ifdef HASINLINE}inline;{$endif}
  1548. /// direct conversion of a UTF-8 encoded zero terminated buffer into a RawUTF8 String
  1549. procedure Utf8ToRawUTF8(P: PUTF8Char; var result: RawUTF8);
  1550. {$ifdef HASINLINE}inline;{$endif}
  1551. /// direct conversion of a UTF-8 encoded buffer into a WinAnsi PAnsiChar buffer
  1552. function UTF8ToWinPChar(dest: PAnsiChar; source: PUTF8Char; count: integer): integer;
  1553. {$ifdef HASINLINE}inline;{$endif}
  1554. /// direct conversion of a UTF-8 encoded buffer into a WinAnsi shortstring buffer
  1555. procedure UTF8ToShortString(var dest: shortstring; source: PUTF8Char);
  1556. /// direct conversion of an ANSI-7 shortstring into an AnsiString
  1557. // - can be used e.g. for names retrieved from RTTI to convert them into RawUTF8
  1558. function ShortStringToAnsi7String(const source: shortstring): RawByteString; overload;
  1559. {$ifdef HASINLINE}inline;{$endif}
  1560. /// direct conversion of an ANSI-7 shortstring into an AnsiString
  1561. // - can be used e.g. for names retrieved from RTTI to convert them into RawUTF8
  1562. procedure ShortStringToAnsi7String(const source: shortstring; var result: RawUTF8); overload;
  1563. {$ifdef HASINLINE}inline;{$endif}
  1564. /// convert an UTF-8 encoded text into a WideChar array
  1565. // - faster than System.UTF8ToUnicode
  1566. // - sourceBytes can by 0, therefore length is computed from zero terminated source
  1567. // - enough place must be available in dest
  1568. // - a WideChar(#0) is added at the end (if something is written)
  1569. // - returns the BYTE count written in dest, excluding the ending WideChar(#0)
  1570. function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; sourceBytes: PtrInt=0): PtrInt; overload;
  1571. /// convert an UTF-8 encoded text into a WideChar array
  1572. // - faster than System.UTF8ToUnicode
  1573. // - this overloaded function expect a MaxDestChars parameter
  1574. // - sourceBytes can not be 0 for this function
  1575. // - enough place must be available in dest
  1576. // - a WideChar(#0) is added at the end (if something is written)
  1577. // - returns the BYTE COUNT (not WideChar count) written in dest, excluding the
  1578. // ending WideChar(#0)
  1579. function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; MaxDestChars, sourceBytes: PtrInt): PtrInt; overload;
  1580. /// calculate the UTF-16 Unicode characters count, UTF-8 encoded in source^
  1581. // - count may not match the UCS4 glyphs number, in case of UTF-16 surrogates
  1582. // - faster than System.UTF8ToUnicode with dest=nil
  1583. function Utf8ToUnicodeLength(source: PUTF8Char): PtrUInt;
  1584. /// returns TRUE if the supplied buffer has valid UTF-8 encoding
  1585. function IsValidUTF8(source: PUTF8Char): Boolean;
  1586. /// returns TRUE if the supplied buffer has valid UTF-8 encoding with no #1..#31
  1587. // control characters
  1588. function IsValidUTF8WithoutControlChars(source: PUTF8Char): Boolean;
  1589. /// will truncate the supplied UTF-8 value if its length exceeds the specified
  1590. // UTF-16 Unicode characters count
  1591. // - count may not match the UCS4 glyphs number, in case of UTF-16 surrogates
  1592. // - returns FALSE if text was not truncated, TRUE otherwise
  1593. function Utf8TruncateToUnicodeLength(var text: RawUTF8; maxUtf16: integer): boolean;
  1594. /// will truncate the supplied UTF-8 value if its length exceeds the specified
  1595. // UTF-8 Unicode characters count
  1596. // - this function will ensure that the returned content will contain only valid
  1597. // UTF-8 sequence, i.e. will trim the whole trailing UTF-8 sequence
  1598. // - returns FALSE if text was not truncated, TRUE otherwise
  1599. function Utf8TruncateToLength(var text: RawUTF8; maxUTF8: cardinal): boolean;
  1600. /// calculate the UTF-16 Unicode characters count of the UTF-8 encoded first line
  1601. // - count may not match the UCS4 glyphs number, in case of UTF-16 surrogates
  1602. // - end the parsing at first #13 or #10 character
  1603. function Utf8FirstLineToUnicodeLength(source: PUTF8Char): PtrInt;
  1604. /// convert a UTF-8 encoded buffer into a RawUnicode string
  1605. // - if L is 0, L is computed from zero terminated P buffer
  1606. // - RawUnicode is ended by a WideChar(#0)
  1607. // - faster than System.Utf8Decode() which uses slow widestrings
  1608. function Utf8DecodeToRawUnicode(P: PUTF8Char; L: integer): RawUnicode; overload;
  1609. /// convert a UTF-8 string into a RawUnicode string
  1610. function Utf8DecodeToRawUnicode(const S: RawUTF8): RawUnicode; overload;
  1611. {$ifdef HASINLINE}inline;{$endif}
  1612. /// convert a UTF-8 string into a RawUnicode string
  1613. // - this version doesn't resize the length of the result RawUnicode
  1614. // and is therefore useful before a Win32 Unicode API call (with nCount=-1)
  1615. // - if DestLen is not nil, the resulting length (in bytes) will be stored within
  1616. function Utf8DecodeToRawUnicodeUI(const S: RawUTF8; DestLen: PInteger=nil): RawUnicode; overload;
  1617. /// convert a UTF-8 string into a RawUnicode string
  1618. // - returns the resulting length (in bytes) will be stored within Dest
  1619. function Utf8DecodeToRawUnicodeUI(const S: RawUTF8; var Dest: RawUnicode): integer; overload;
  1620. /// convert a RawUnicode PWideChar into a UTF-8 string
  1621. procedure RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer; var result: RawUTF8); overload;
  1622. /// convert a RawUnicode PWideChar into a UTF-8 string
  1623. function RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer): RawUTF8; overload;
  1624. {$ifdef HASINLINE}inline;{$endif}
  1625. /// convert a RawUnicode PWideChar into a UTF-8 buffer
  1626. // - replace system.UnicodeToUtf8 implementation, which is rather slow
  1627. // since Delphi 2009+
  1628. function RawUnicodeToUtf8(Dest: PUTF8Char; DestLen: PtrInt; Source: PWideChar; SourceLen: PtrInt): PtrInt; overload;
  1629. /// convert a RawUnicode PWideChar into a UTF-8 string
  1630. // - this version doesn't resize the resulting RawUTF8 string, but return
  1631. // the new resulting RawUTF8 byte count into UTF8Length
  1632. function RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer; out UTF8Length: integer): RawUTF8; overload;
  1633. /// convert a RawUnicode string into a UTF-8 string
  1634. function RawUnicodeToUtf8(const Unicode: RawUnicode): RawUTF8; overload;
  1635. /// convert a SynUnicode string into a UTF-8 string
  1636. function SynUnicodeToUtf8(const Unicode: SynUnicode): RawUTF8;
  1637. /// convert a WideString into a UTF-8 string
  1638. function WideStringToUTF8(const aText: WideString): RawUTF8;
  1639. {$ifdef HASINLINE}inline;{$endif}
  1640. /// direct conversion of a Unicode encoded buffer into a WinAnsi PAnsiChar buffer
  1641. procedure RawUnicodeToWinPChar(dest: PAnsiChar; source: PWideChar; WideCharCount: integer);
  1642. {$ifdef HASINLINE}inline;{$endif}
  1643. /// convert a RawUnicode PWideChar into a WinAnsi (code page 1252) string
  1644. function RawUnicodeToWinAnsi(WideChar: PWideChar; WideCharCount: integer): WinAnsiString; overload;
  1645. {$ifdef HASINLINE}inline;{$endif}
  1646. /// convert a RawUnicode string into a WinAnsi (code page 1252) string
  1647. function RawUnicodeToWinAnsi(const Unicode: RawUnicode): WinAnsiString; overload;
  1648. {$ifdef HASINLINE}inline;{$endif}
  1649. /// convert a WideString into a WinAnsi (code page 1252) string
  1650. function WideStringToWinAnsi(const Wide: WideString): WinAnsiString;
  1651. {$ifdef HASINLINE}inline;{$endif}
  1652. /// convert an AnsiChar buffer (of a given code page) into a UTF-8 string
  1653. procedure AnsiCharToUTF8(P: PAnsiChar; L: Integer; var result: RawUTF8; ACP: integer);
  1654. /// convert any Raw Unicode encoded String into a generic SynUnicode Text
  1655. function RawUnicodeToSynUnicode(const Unicode: RawUnicode): SynUnicode; overload;
  1656. {$ifdef HASINLINE}inline;{$endif}
  1657. /// convert any Raw Unicode encoded String into a generic SynUnicode Text
  1658. function RawUnicodeToSynUnicode(WideChar: PWideChar; WideCharCount: integer): SynUnicode; overload;
  1659. {$ifdef HASINLINE}inline;{$endif}
  1660. /// convert an Unicode buffer into a WinAnsi (code page 1252) string
  1661. procedure UnicodeBufferToWinAnsi(source: PWideChar; out Dest: WinAnsiString);
  1662. /// convert an Unicode buffer into a generic VCL string
  1663. function UnicodeBufferToString(source: PWideChar): string;
  1664. {$ifdef HASVARUSTRING}
  1665. /// convert a Delphi 2009+ or FPC Unicode string into our UTF-8 string
  1666. function UnicodeStringToUtf8(const S: UnicodeString): RawUTF8; inline;
  1667. // this function is the same as direct RawUTF8=AnsiString(CP_UTF8) assignment
  1668. // but is faster, since it uses no Win32 API call
  1669. function UTF8DecodeToUnicodeString(const S: RawUTF8): UnicodeString; overload; inline;
  1670. /// convert our UTF-8 encoded buffer into a Delphi 2009+ Unicode string
  1671. // - this function is the same as direct assignment, since RawUTF8=AnsiString(CP_UTF8),
  1672. // but is faster, since use no Win32 API call
  1673. procedure UTF8DecodeToUnicodeString(P: PUTF8Char; L: integer; var result: UnicodeString); overload;
  1674. /// convert a Delphi 2009+ Unicode string into a WinAnsi (code page 1252) string
  1675. function UnicodeStringToWinAnsi(const S: string): WinAnsiString; inline;
  1676. /// convert our UTF-8 encoded buffer into a Delphi 2009+ Unicode string
  1677. // - this function is the same as direct assignment, since RawUTF8=AnsiString(CP_UTF8),
  1678. // but is faster, since use no Win32 API call
  1679. function UTF8DecodeToUnicodeString(P: PUTF8Char; L: integer): UnicodeString; overload; inline;
  1680. /// convert a Win-Ansi encoded buffer into a Delphi 2009+ Unicode string
  1681. // - this function is faster than default RTL, since use no Win32 API call
  1682. function WinAnsiToUnicodeString(WinAnsi: PAnsiChar; WinAnsiLen: integer): UnicodeString; overload;
  1683. /// convert a Win-Ansi string into a Delphi 2009+ Unicode string
  1684. // - this function is faster than default RTL, since use no Win32 API call
  1685. function WinAnsiToUnicodeString(const WinAnsi: WinAnsiString): UnicodeString; inline; overload;
  1686. {$endif HASVARUSTRING}
  1687. /// convert any generic VCL Text into an UTF-8 encoded String
  1688. // - it's prefered to use TLanguageFile.StringToUTF8() method in mORMoti18n,
  1689. // which will handle full i18n of your application
  1690. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1691. // - under older version of Delphi (no unicode), it will use the
  1692. // current RTL codepage, as with WideString conversion (but without slow
  1693. // WideString usage)
  1694. function StringToUTF8(const Text: string): RawUTF8; overload;
  1695. {$ifdef HASINLINE}inline;{$endif}
  1696. /// convert any generic VCL Text into an UTF-8 encoded String
  1697. // - this overloaded function use a faster by-reference parameter for the result
  1698. procedure StringToUTF8(const Text: string; var result: RawUTF8); overload;
  1699. {$ifdef HASINLINE}inline;{$endif}
  1700. /// convert any generic VCL Text into an UTF-8 encoded String
  1701. function ToUTF8(const Text: string): RawUTF8; overload;
  1702. {$ifdef HASINLINE}inline;{$endif}
  1703. /// convert any UTF-8 encoded shortstring Text into an UTF-8 encoded String
  1704. // - expects the supplied content to be already ASCII-7 or UTF-8 encoded, e.g.
  1705. // a RTTI type or property name: it won't work with Ansi-encoded strings
  1706. function ToUTF8(const Ansi7Text: ShortString): RawUTF8; overload;
  1707. {$ifdef HASINLINE}inline;{$endif}
  1708. /// convert a TGUID into UTF-8 encoded text
  1709. // - will return e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without the {})
  1710. // - if you need the embracing { }, use GUIDToRawUTF8() function instead
  1711. function ToUTF8(const guid: TGUID): RawUTF8; overload;
  1712. {$ifndef NOVARIANTS}
  1713. type
  1714. /// function prototype used internally for variant comparaison
  1715. // - used in mORMot.pas unit e.g. by TDocVariantData.SortByValue
  1716. TVariantCompare = function(const V1,V2: variant): PtrInt;
  1717. /// convert any Variant into UTF-8 encoded String
  1718. // - use VariantSaveJSON() instead if you need a conversion to JSON with
  1719. // custom parameters
  1720. function VariantToUTF8(const V: Variant): RawUTF8; overload;
  1721. {$ifdef HASINLINE}inline;{$endif}
  1722. /// convert any Variant into UTF-8 encoded String
  1723. // - use VariantSaveJSON() instead if you need a conversion to JSON with
  1724. // custom parameters
  1725. function ToUTF8(const V: Variant): RawUTF8; overload;
  1726. {$ifdef HASINLINE}inline;{$endif}
  1727. /// convert any Variant into UTF-8 encoded String
  1728. // - use VariantSaveJSON() instead if you need a conversion to JSON with
  1729. // custom parameters
  1730. // - wasString is set if the V value was a text
  1731. // - custom variant types will be stored as JSON
  1732. procedure VariantToUTF8(const V: Variant; var result: RawUTF8;
  1733. var wasString: boolean); overload;
  1734. /// convert any Variant into UTF-8 encoded String
  1735. // - use VariantSaveJSON() instead if you need a conversion to JSON with
  1736. // custom parameters
  1737. // - returns TRUE if the V value was a text, FALSE if was not (e.g. a number)
  1738. // - custom variant types will be stored as JSON
  1739. function VariantToUTF8(const V: Variant; var Text: RawUTF8): boolean; overload;
  1740. /// convert any date/time Variant into a TDateTime value
  1741. // - would handle varDate kind of variant, or use a string conversion and
  1742. // ISO-8601 parsing if possible
  1743. function VariantToDateTime(const V: Variant; var Value: TDateTime): boolean;
  1744. /// fast comparison of a Variant and UTF-8 encoded String
  1745. // - slightly faster than plain V=Str, which computes a temporary variant
  1746. function VariantEquals(const V: Variant; const Str: RawUTF8): boolean; overload;
  1747. /// convert any Variant into a VCL string type
  1748. // - expects any varString value to be stored as a RawUTF8
  1749. // - prior to Delphi 2009, use VariantToString(aVariant) instead of
  1750. // string(aVariant) to safely retrieve a string=AnsiString value from a variant
  1751. // generated by our framework units - otherwise, you may loose encoded characters
  1752. // - for Unicode versions of Delphi, there won't be any potential data loss,
  1753. // but this version may be slightly faster than a string(aVariant)
  1754. function VariantToString(const V: Variant): string;
  1755. /// convert any Variant into a value encoded as with :(..:) inlined parameters
  1756. // in FormatUTF8(Format,Args,Params)
  1757. procedure VariantToInlineValue(const V: Variant; var result: RawUTF8);
  1758. /// convert any Variant into another Variant storing an RawUTF8 of the value
  1759. // - e.g. VariantToVariantUTF8('toto')='toto' and VariantToVariantUTF8(12)='12'
  1760. function VariantToVariantUTF8(const V: Variant): variant;
  1761. /// faster alternative to Finalize(aVariantDynArray)
  1762. // - this function will take in account and optimize the release of a dynamic
  1763. // array of custom variant types values
  1764. // - for instance, an array of TDocVariant will be optimized for speed
  1765. procedure VariantDynArrayClear(var Value: TVariantDynArray);
  1766. {$endif NOVARIANTS}
  1767. { note: those VariantToInteger*() functions are expected to be there }
  1768. /// convert any numerical Variant into a 32 bit integer
  1769. // - it will expect true numerical Variant and won't convert any string nor
  1770. // floating-pointer Variant, which will return FALSE and won't change the
  1771. // Value variable content
  1772. function VariantToInteger(const V: Variant; var Value: integer): boolean;
  1773. /// convert any numerical Variant into a 64 bit integer
  1774. // - it will expect true numerical Variant and won't convert any string nor
  1775. // floating-pointer Variant, which will return FALSE and won't change the
  1776. // Value variable content
  1777. function VariantToInt64(const V: Variant; var Value: Int64): boolean;
  1778. /// convert any numerical Variant into a 64 bit integer
  1779. // - it will expect true numerical Variant and won't convert any string nor
  1780. // floating-pointer Variant, which will return the supplied DefaultValue
  1781. function VariantToInt64Def(const V: Variant; DefaultValue: Int64): Int64;
  1782. /// convert any numerical Variant into a floating point value
  1783. function VariantToDouble(const V: Variant; var Value: double): boolean;
  1784. /// convert any numerical Variant into a fixed decimals floating point value
  1785. function VariantToCurrency(const V: Variant; var Value: currency): boolean;
  1786. /// convert any numerical Variant into a boolean value
  1787. function VariantToBoolean(const V: Variant; var Value: Boolean): boolean;
  1788. /// convert any numerical Variant into an integer
  1789. // - it will expect true numerical Variant and won't convert any string nor
  1790. // floating-pointer Variant, which will return the supplied DefaultValue
  1791. function VariantToIntegerDef(const V: Variant; DefaultValue: integer): integer; overload;
  1792. /// convert any generic VCL Text buffer into an UTF-8 encoded buffer
  1793. // - Dest must be able to receive at least SourceChars*3 bytes
  1794. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1795. // - under older version of Delphi (no unicode), it will use the
  1796. // current RTL codepage, as with WideString conversion (but without slow
  1797. // WideString usage)
  1798. function StringBufferToUtf8(Dest: PUTF8Char; Source: PChar; SourceChars: PtrInt): PUTF8Char;
  1799. /// convert any generic VCL Text into a Raw Unicode encoded String
  1800. // - it's prefered to use TLanguageFile.StringToUTF8() method in mORMoti18n,
  1801. // which will handle full i18n of your application
  1802. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1803. // - under older version of Delphi (no unicode), it will use the
  1804. // current RTL codepage, as with WideString conversion (but without slow
  1805. // WideString usage)
  1806. function StringToRawUnicode(const S: string): RawUnicode; overload;
  1807. /// convert any generic VCL Text into a SynUnicode encoded String
  1808. // - it's prefered to use TLanguageFile.StringToUTF8() method in mORMoti18n,
  1809. // which will handle full i18n of your application
  1810. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1811. // - under older version of Delphi (no unicode), it will use the
  1812. // current RTL codepage, as with WideString conversion (but without slow
  1813. // WideString usage)
  1814. function StringToSynUnicode(const S: string): SynUnicode;
  1815. /// convert any generic VCL Text into a Raw Unicode encoded String
  1816. // - it's prefered to use TLanguageFile.StringToUTF8() method in mORMoti18n,
  1817. // which will handle full i18n of your application
  1818. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1819. // - under older version of Delphi (no unicode), it will use the
  1820. // current RTL codepage, as with WideString conversion (but without slow
  1821. // WideString usage)
  1822. function StringToRawUnicode(P: PChar; L: integer): RawUnicode; overload;
  1823. /// convert any Raw Unicode encoded string into a generic VCL Text
  1824. function RawUnicodeToString(const U: RawUnicode): string; overload;
  1825. /// convert any Raw Unicode encoded buffer into a generic VCL Text
  1826. function RawUnicodeToString(P: PWideChar; L: integer): string; overload;
  1827. /// convert any Raw Unicode encoded buffer into a generic VCL Text
  1828. procedure RawUnicodeToString(P: PWideChar; L: integer; var result: string); overload;
  1829. /// convert any SynUnicode encoded string into a generic VCL Text
  1830. function SynUnicodeToString(const U: SynUnicode): string;
  1831. {$ifdef HASINLINE}inline;{$endif}
  1832. /// convert any UTF-8 encoded String into a generic VCL Text
  1833. // - it's prefered to use TLanguageFile.UTF8ToString() in mORMoti18n,
  1834. // which will handle full i18n of your application
  1835. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1836. // - under older version of Delphi (no unicode), it will use the
  1837. // current RTL codepage, as with WideString conversion (but without slow
  1838. // WideString usage)
  1839. function UTF8ToString(const Text: RawUTF8): string;
  1840. {$ifdef HASINLINE}inline;{$endif}
  1841. /// convert any UTF-8 encoded buffer into a generic VCL Text
  1842. // - it's prefered to use TLanguageFile.UTF8ToString() in mORMoti18n,
  1843. // which will handle full i18n of your application
  1844. // - it will work as is with Delphi 2009+ (direct unicode conversion)
  1845. // - under older version of Delphi (no unicode), it will use the
  1846. // current RTL codepage, as with WideString conversion (but without slow
  1847. // WideString usage)
  1848. function UTF8DecodeToString(P: PUTF8Char; L: integer): string; overload;
  1849. {$ifdef UNICODE}inline;{$endif}
  1850. /// convert any UTF-8 encoded buffer into a generic VCL Text
  1851. procedure UTF8DecodeToString(P: PUTF8Char; L: integer; var result: string); overload;
  1852. /// convert any UTF-8 encoded String into a generic WideString Text
  1853. function UTF8ToWideString(const Text: RawUTF8): WideString; overload;
  1854. {$ifdef HASINLINE}inline;{$endif}
  1855. /// convert any UTF-8 encoded String into a generic WideString Text
  1856. procedure UTF8ToWideString(const Text: RawUTF8; var result: WideString); overload;
  1857. {$ifdef HASINLINE}inline;{$endif}
  1858. /// convert any UTF-8 encoded String into a generic WideString Text
  1859. procedure UTF8ToWideString(Text: PUTF8Char; Len: integer; var result: WideString); overload;
  1860. /// convert any UTF-8 encoded String into a generic SynUnicode Text
  1861. function UTF8ToSynUnicode(const Text: RawUTF8): SynUnicode; overload;
  1862. /// convert any UTF-8 encoded String into a generic SynUnicode Text
  1863. procedure UTF8ToSynUnicode(const Text: RawUTF8; var result: SynUnicode); overload;
  1864. /// convert any UTF-8 encoded buffer into a generic SynUnicode Text
  1865. procedure UTF8ToSynUnicode(Text: PUTF8Char; Len: integer; var result: SynUnicode); overload;
  1866. /// convert any Ansi 7 bit encoded String into a generic VCL Text
  1867. // - the Text content must contain only 7 bit pure ASCII characters
  1868. function Ansi7ToString(const Text: RawByteString): string; overload;
  1869. {$ifndef UNICODE}{$ifdef HASINLINE}inline;{$endif}{$endif}
  1870. /// convert any Ansi 7 bit encoded String into a generic VCL Text
  1871. // - the Text content must contain only 7 bit pure ASCII characters
  1872. function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string; overload;
  1873. {$ifdef HASINLINE}inline;{$endif}
  1874. /// convert any Ansi 7 bit encoded String into a generic VCL Text
  1875. // - the Text content must contain only 7 bit pure ASCII characters
  1876. procedure Ansi7ToString(Text: PWinAnsiChar; Len: integer; var result: string); overload;
  1877. /// convert any generic VCL Text into Ansi 7 bit encoded String
  1878. // - the Text content must contain only 7 bit pure ASCII characters
  1879. function StringToAnsi7(const Text: string): RawByteString;
  1880. /// convert any generic VCL Text into WinAnsi (Win-1252) 8 bit encoded String
  1881. function StringToWinAnsi(const Text: string): WinAnsiString;
  1882. {$ifdef UNICODE}inline;{$endif}
  1883. /// fast Format() function replacement, optimized for RawUTF8
  1884. // - only supported token is %, which will be inlined in the resulting string
  1885. // according to each Args[] supplied item
  1886. // - resulting string has no length limit and uses fast concatenation
  1887. // - note that cardinal values should be type-casted to Int64() (otherwise
  1888. // the integer mapped value will be transmitted, therefore wrongly)
  1889. // - any supplied TObject instance will be written as their class name
  1890. function FormatUTF8(const Format: RawUTF8; const Args: array of const): RawUTF8; overload;
  1891. /// fast Format() function replacement, optimized for RawUTF8
  1892. // - overloaded function, which avoid a temporary RawUTF8 string on stack
  1893. procedure FormatUTF8(const Format: RawUTF8; const Args: array of const;
  1894. var result: RawUTF8); overload;
  1895. /// fast Format() function replacement, handling % and ? parameters
  1896. // - will include Args[] for every % in Format
  1897. // - will inline Params[] for every ? in Format, handling special "inlined"
  1898. // parameters, as exected by mORMot.pas unit, i.e. :(1234): for numerical
  1899. // values, and :('quoted '' string'): for textual values
  1900. // - if optional JSONFormat parameter is TRUE, ? parameters will be written
  1901. // as JSON quoted strings, without :(...): tokens, e.g. "quoted "" string"
  1902. // - resulting string has no length limit and uses fast concatenation
  1903. // - note that cardinal values should be type-casted to Int64() (otherwise
  1904. // the integer mapped value will be transmitted, therefore wrongly)
  1905. // - any supplied TObject instance will be written as their class name
  1906. function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const;
  1907. JSONFormat: boolean=false): RawUTF8; overload;
  1908. /// convert an open array (const Args: array of const) argument to an UTF-8
  1909. // encoded text
  1910. // - note that cardinal values should be type-casted to Int64() (otherwise
  1911. // the signed integer mapped value will be transmitted, therefore wrongly)
  1912. // - any supplied TObject instance will be written as their class name
  1913. procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8;
  1914. wasString: PBoolean=nil);
  1915. type
  1916. /// a memory structure which avoid a temporary RawUTF8 allocation
  1917. // - used by VarRecToTempUTF8() and FormatUTF8()
  1918. TTempUTF8 = record
  1919. Text: PUTF8Char;
  1920. Len: integer;
  1921. Temp: array[0..23] of AnsiChar;
  1922. end;
  1923. /// convert an open array (const Args: array of const) argument to an UTF-8
  1924. // encoded text, using a specified temporary buffer
  1925. // - this function would allocate a RawUTF8 in tmpStr only if needed,
  1926. // but use the supplied Res.Temp[] buffer for numbers to text conversion
  1927. // - it would return the number of UTF-8 bytes, i.e. Res.Len
  1928. // - note that cardinal values should be type-casted to Int64() (otherwise
  1929. // the signed integer mapped value will be transmitted, therefore wrongly)
  1930. // - any supplied TObject instance will be written as their class name
  1931. function VarRecToTempUTF8(const V: TVarRec; var tmpStr: RawUTF8; var Res: TTempUTF8): integer;
  1932. /// convert an open array (const Args: array of const) argument to an UTF-8
  1933. // encoded text, returning FALSE if the argument was not a string value
  1934. function VarRecToUTF8IsString(const V: TVarRec; var value: RawUTF8): boolean;
  1935. {$ifdef HASINLINE}inline;{$endif}
  1936. /// convert an open array (const Args: array of const) argument to an Int64
  1937. // - returns TRUE and set Value if the supplied argument is a vtInteger, vtInt64
  1938. // or vtBoolean
  1939. // - returns FALSE if the argument is not an integer
  1940. // - note that cardinal values should be type-casted to Int64() (otherwise
  1941. // the signed integer mapped value will be transmitted, therefore wrongly)
  1942. function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;
  1943. /// convert an open array (const Args: array of const) argument to a floating
  1944. // point value
  1945. // - returns TRUE and set Value if the supplied argument is a number (e.g.
  1946. // vtInteger, vtInt64, vtCurrency or vtExtended)
  1947. // - returns FALSE if the argument is not a number
  1948. // - note that cardinal values should be type-casted to Int64() (otherwise
  1949. // the signed integer mapped value will be transmitted, therefore wrongly)
  1950. function VarRecToDouble(const V: TVarRec; out value: double): boolean;
  1951. /// convert an open array (const Args: array of const) argument to a value
  1952. // encoded as with :(...): inlined parameters in FormatUTF8(Format,Args,Params)
  1953. // - note that cardinal values should be type-casted to Int64() (otherwise
  1954. // the signed integer mapped value will be transmitted, therefore wrongly)
  1955. // - any supplied TObject instance will be written as their class name
  1956. procedure VarRecToInlineValue(const V: TVarRec; var result: RawUTF8);
  1957. /// get an open array (const Args: array of const) character argument
  1958. // - only handle varChar and varWideChar kind of arguments
  1959. function VarRecAsChar(const V: TVarRec): integer;
  1960. {$ifdef HASINLINE}inline;{$endif}
  1961. type
  1962. /// function prototype used internally for UTF-8 buffer comparaison
  1963. // - used in mORMot.pas unit during TSQLTable rows sort and by TSQLQuery
  1964. TUTF8Compare = function(P1,P2: PUTF8Char): PtrInt;
  1965. /// convert the endianness of a given unsigned 32 bit integer into BigEndian
  1966. function bswap32(a: cardinal): cardinal;
  1967. {$ifndef ISDELPHI2007ANDUP}
  1968. type
  1969. TBytes = array of byte;
  1970. {$endif}
  1971. /// fast concatenation of several AnsiStrings
  1972. function RawByteStringArrayConcat(const Values: array of RawByteString): RawByteString;
  1973. /// creates a TBytes from a RawByteString memory buffer
  1974. procedure RawByteStringToBytes(const buf: RawByteString; out bytes: TBytes);
  1975. /// creates a RawByteString memory buffer from a TBytes content
  1976. procedure BytesToRawByteString(const bytes: TBytes; out buf: RawByteString);
  1977. {$ifdef HASINLINE}inline;{$endif}
  1978. /// creates a RawByteString memory buffer from an embedded resource
  1979. // - returns '' if the resource is not found
  1980. // - warning: resources size may be rounded up to alignment
  1981. procedure ResourceToRawByteString(const ResName: string; ResType: PChar;
  1982. out buf: RawByteString);
  1983. /// creates a RawByteString memory buffer from an SynLZ-compressed embedded resource
  1984. // - returns '' if the resource is not found
  1985. // - this method would use SynLZDecompress() after ResourceToRawByteString(),
  1986. // with a ResType=PChar(10) (i.e. RC_DATA)
  1987. procedure ResourceSynLZToRawByteString(const ResName: string;
  1988. out buf: RawByteString);
  1989. {$ifndef ENHANCEDRTL} { is our Enhanced Runtime (or LVCL) library not installed? }
  1990. /// fast dedicated RawUTF8 version of Trim()
  1991. // - implemented using x86 asm, if possible
  1992. // - this Trim() is seldom used, but this RawUTF8 specific version is needed
  1993. // e.g. by Delphi 2009+, to avoid two unnecessary conversions into UnicodeString
  1994. function Trim(const S: RawUTF8): RawUTF8;
  1995. {$define OWNNORMTOUPPER} { NormToUpper[] exists only in our enhanced RTL }
  1996. {$ifndef PUREPASCAL}
  1997. {$ifndef LVCL} { don't define these functions twice }
  1998. /// use our fast asm version of CompareMem()
  1999. function CompareMem(P1, P2: Pointer; Length: Integer): Boolean;
  2000. {$endif LVCL}
  2001. {$endif PUREPASCAL}
  2002. {$endif ENHANCEDRTL}
  2003. /// convert some ASCII-7 text into binary, using Emile Baudot code
  2004. // - as used in telegraphs, covering a-z 0-9 - ' , ! : ( + ) $ ? @ . / ; charset
  2005. // - also #13 and #10 control chars will be transcoded
  2006. // - any upper case char will be converted into lowercase during encoding
  2007. // - other characters (e.g. UTF-8 accents, or controls chars) will be ignored
  2008. // - resulting binary will consume 5 (or 10) bits per character
  2009. // - reverse of the BaudotToAscii() function
  2010. // - the "baud" symbol rate measurement comes from Emile's name ;)
  2011. function AsciiToBaudot(P: PAnsiChar; len: integer): RawByteString; overload;
  2012. /// convert some ASCII-7 text into binary, using Emile Baudot code
  2013. // - as used in telegraphs, covering a-z 0-9 - ' , ! : ( + ) $ ? @ . / ; charset
  2014. // - also #13 and #10 control chars will be transcoded
  2015. // - any upper case char will be converted into lowercase during encoding
  2016. // - other characters (e.g. UTF-8 accents, or controls chars) will be ignored
  2017. // - resulting binary will consume 5 (or 10) bits per character
  2018. // - reverse of the BaudotToAscii() function
  2019. // - the "baud" symbol rate measurement comes from Emile's name ;)
  2020. function AsciiToBaudot(const Text: RawUTF8): RawByteString; overload;
  2021. /// convert some Baudot code binary, into ASCII-7 text
  2022. // - reverse of the AsciiToBaudot() function
  2023. // - any uppercase character would be decoded as lowercase - and some characters
  2024. // may have disapeared
  2025. // - the "baud" symbol rate measurement comes from Emile's name ;)
  2026. function BaudotToAscii(Baudot: PByteArray; len: integer): RawUTF8; overload;
  2027. /// convert some Baudot code binary, into ASCII-7 text
  2028. // - reverse of the AsciiToBaudot() function
  2029. // - any uppercase character would be decoded as lowercase - and some characters
  2030. // may have disapeared
  2031. // - the "baud" symbol rate measurement comes from Emile's name ;)
  2032. function BaudotToAscii(const Baudot: RawByteString): RawUTF8; overload;
  2033. {$ifdef UNICODE}
  2034. /// our fast RawUTF8 version of Pos(), for Unicode only compiler
  2035. // - this Pos() is seldom used, but this RawUTF8 specific version is needed
  2036. // by Delphi 2009+, to avoid two unnecessary conversions into UnicodeString
  2037. // - just a wrapper around PosEx(substr,str,1)
  2038. function Pos(const substr, str: RawUTF8): Integer; overload; inline;
  2039. {$endif UNICODE}
  2040. /// use our fast RawUTF8 version of IntToStr()
  2041. // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
  2042. // - only useful if our Enhanced Runtime (or LVCL) library is not installed
  2043. function Int64ToUtf8(Value: Int64): RawUTF8; overload;
  2044. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2045. /// use our fast RawUTF8 version of IntToStr()
  2046. // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
  2047. // - only useful if our Enhanced Runtime (or LVCL) library is not installed
  2048. function Int32ToUtf8(Value: integer): RawUTF8; overload;
  2049. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2050. /// use our fast RawUTF8 version of IntToStr()
  2051. // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
  2052. // - result as var parameter saves a local assignment and a try..finally
  2053. procedure Int32ToUTF8(Value: integer; var result: RawUTF8); overload;
  2054. {$ifdef HASINLINE}inline;{$endif}
  2055. /// use our fast RawUTF8 version of IntToStr()
  2056. // - without any slow UnicodeString=String->AnsiString conversion for Delphi 2009
  2057. // - result as var parameter saves a local assignment and a try..finally
  2058. procedure Int64ToUtf8(Value: Int64; var result: RawUTF8); overload;
  2059. {$ifdef HASINLINE}inline;{$endif}
  2060. /// use our fast RawUTF8 version of IntToStr()
  2061. function ToUTF8(Value: PtrInt): RawUTF8; overload;
  2062. {$ifdef HASINLINE}inline;{$endif}
  2063. {$ifndef CPU64}
  2064. /// use our fast RawUTF8 version of IntToStr()
  2065. function ToUTF8(Value: Int64): RawUTF8; overload;
  2066. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2067. {$endif}
  2068. /// optimized conversion of a cardinal into RawUTF8
  2069. function UInt32ToUtf8(Value: cardinal): RawUTF8; overload;
  2070. {$ifdef HASINLINE}inline;{$endif}
  2071. /// optimized conversion of a cardinal into RawUTF8
  2072. procedure UInt32ToUtf8(Value: cardinal; var result: RawUTF8); overload;
  2073. {$ifdef HASINLINE}inline;{$endif}
  2074. /// faster version than default SysUtils.IntToStr implementation
  2075. function IntToString(Value: integer): string; overload;
  2076. /// faster version than default SysUtils.IntToStr implementation
  2077. function IntToString(Value: cardinal): string; overload;
  2078. /// faster version than default SysUtils.IntToStr implementation
  2079. function IntToString(Value: Int64): string; overload;
  2080. /// convert a floating-point value to its numerical text equivalency
  2081. function DoubleToString(Value: Double): string;
  2082. /// convert a currency value from its Int64 binary representation into
  2083. // its numerical text equivalency
  2084. // - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
  2085. function Curr64ToString(Value: Int64): string;
  2086. var
  2087. /// best possible precision when rendering a "single" kind of float
  2088. // - can be used as parameter for ExtendedToString/ExtendedToStr
  2089. // - is defined as a var, so that you may be able to override the default
  2090. // settings, for the whole process
  2091. SINGLE_PRECISION: integer = 8;
  2092. /// best possible precision when rendering a "double" kind of float
  2093. // - can be used as parameter for ExtendedToString/ExtendedToStr
  2094. // - is defined as a var, so that you may be able to override the default
  2095. // settings, for the whole process
  2096. DOUBLE_PRECISION: integer = 15;
  2097. /// best possible precision when rendering a "extended" kind of float
  2098. // - can be used as parameter for ExtendedToString/ExtendedToStr
  2099. // - is defined as a var, so that you may be able to override the default
  2100. // settings, for the whole process
  2101. EXTENDED_PRECISION: integer = 18;
  2102. type
  2103. {$ifdef CPUARM}
  2104. // ARM does not support 80bit extended -> 64bit double is enough for us
  2105. TSynExtended = double;
  2106. {$else}
  2107. {$ifdef CPU64}
  2108. TSynExtended = double;
  2109. {$else}
  2110. /// the floating-point type to be used for best precision and speed
  2111. // - will allow to fallback to double e.g. on x64 and ARM CPUs
  2112. TSynExtended = extended;
  2113. {$endif}
  2114. {$endif}
  2115. /// convert a floating-point value to its numerical text equivalency
  2116. // - returns the count of chars stored into S (S[0] is not set)
  2117. function ExtendedToString(var S: ShortString; Value: TSynExtended; Precision: integer): integer;
  2118. /// convert a floating-point value to its numerical text equivalency
  2119. function ExtendedToStr(Value: TSynExtended; Precision: integer): RawUTF8; overload;
  2120. /// convert a floating-point value to its numerical text equivalency
  2121. procedure ExtendedToStr(Value: TSynExtended; Precision: integer; var result: RawUTF8); overload;
  2122. /// convert a floating-point value to its numerical text equivalency
  2123. function DoubleToStr(Value: Double): RawUTF8;
  2124. /// fast retrieve the position of a given character
  2125. function PosChar(Str: PUTF8Char; Chr: AnsiChar): PUTF8Char;
  2126. /// fast retrieve the position of any value of a given set of characters
  2127. function PosCharAny(Str: PUTF8Char; Characters: PAnsiChar): PUTF8Char;
  2128. /// a non case-sensitive RawUTF8 version of Pos()
  2129. // - uppersubstr is expected to be already in upper case
  2130. // - this version handle only 7 bit ASCII (no accentuated characters)
  2131. function PosI(uppersubstr: PUTF8Char; const str: RawUTF8): Integer;
  2132. /// a non case-sensitive version of Pos()
  2133. // - uppersubstr is expected to be already in upper case
  2134. // - this version handle only 7 bit ASCII (no accentuated characters)
  2135. function StrPosI(uppersubstr,str: PUTF8Char): PUTF8Char;
  2136. /// a non case-sensitive RawUTF8 version of Pos()
  2137. // - substr is expected to be already in upper case
  2138. // - this version will decode the UTF-8 content before using NormToUpper[]
  2139. function PosIU(substr: PUTF8Char; const str: RawUTF8): Integer;
  2140. /// internal fast integer val to text conversion
  2141. // - expect the last available temporary char position in P
  2142. // - return the last written char position (write in reverse order in P^)
  2143. // - typical use:
  2144. // !function Int32ToUTF8(Value : integer): RawUTF8;
  2145. // !var tmp: array[0..15] of AnsiChar;
  2146. // ! P: PAnsiChar;
  2147. // !begin
  2148. // ! P := StrInt32(@tmp[15],Value);
  2149. // ! SetString(result,P,@tmp[15]-P);
  2150. // !end;
  2151. // - not to be called directly: use IntToStr() instead
  2152. function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar;
  2153. /// internal fast unsigned integer val to text conversion
  2154. // - expect the last available temporary char position in P
  2155. // - return the last written char position (write in reverse order in P^)
  2156. function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar;
  2157. /// internal fast Int64 val to text conversion
  2158. // - same calling convention as with StrInt32() above
  2159. function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar;
  2160. {$ifdef HASINLINE}inline;{$endif}
  2161. /// internal fast unsigned Int64 val to text conversion
  2162. // - same calling convention as with StrInt32() above
  2163. function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar;
  2164. {$ifdef CPU64}inline;{$endif}
  2165. /// fast add some characters to a RawUTF8 string
  2166. // - faster than SetString(tmp,Buffer,BufferLen); Text := Text+tmp;
  2167. procedure AppendBufferToRawUTF8(var Text: RawUTF8; Buffer: pointer; BufferLen: PtrInt);
  2168. /// fast add one character to a RawUTF8 string
  2169. // - faster than Text := Text + ch;
  2170. procedure AppendCharToRawUTF8(var Text: RawUTF8; Ch: AnsiChar);
  2171. /// fast add some characters to a RawUTF8 string
  2172. // - faster than Text := Text+RawUTF8(Buffers[0])+RawUTF8(Buffers[0])+...
  2173. procedure AppendBuffersToRawUTF8(var Text: RawUTF8; const Buffers: array of PUTF8Char);
  2174. /// fast add some characters from a RawUTF8 string into a given buffer
  2175. // - warning: the Buffer should contain enough space to store the Text, otherwise
  2176. // you may encounter buffer overflows and random memory errors
  2177. function AppendRawUTF8ToBuffer(Buffer: PUTF8Char; const Text: RawUTF8): PUTF8Char;
  2178. {$ifdef PUREPASCAL}
  2179. /// inlined StrComp(), to be used with PUTF8Char/PAnsiChar
  2180. function StrComp(Str1, Str2: pointer): PtrInt;
  2181. {$ifdef HASINLINE}inline;{$endif}
  2182. /// buffer-safe version of StrComp(), to be used with PUTF8Char/PAnsiChar
  2183. // - pure pascal StrComp() won't access the memory beyond the string, but this
  2184. // function is defined for compatibility with SSE 4.2 expectations
  2185. function StrCompFast(Str1, Str2: pointer): PtrInt;
  2186. {$ifdef HASINLINE}inline;{$endif}
  2187. {$else}
  2188. /// x86 asm version of StrComp(), to be used with PUTF8Char/PAnsiChar
  2189. // - this version won't access the memory beyond the string, so may be
  2190. // preferred to StrcompSSE42 or StrComp, when using e.g. mapped files
  2191. function StrCompFast(Str1, Str2: pointer): PtrInt;
  2192. /// SSE 4.2 version of StrComp(), to be used with PUTF8Char/PAnsiChar
  2193. // - please note that this optimized version may read up to 15 bytes
  2194. // beyond the string; this is rarely a problem but it can in principle
  2195. // generate a protection violation (e.g. when used over mapped files) - in this
  2196. // case, you can use the slightly slower StrCompFast() function instead
  2197. function StrCompSSE42(Str1, Str2: pointer): PtrInt;
  2198. /// fastest available version of StrComp(), to be used with PUTF8Char/PAnsiChar
  2199. // - will use SSE4.2 instructions on supported CPUs - and potentiall read up
  2200. // to 15 bytes beyond the string: use StrCompFast() for a safe memory read
  2201. var StrComp: function (Str1, Str2: pointer): PtrInt = StrCompFast;
  2202. {$endif}
  2203. /// use our fast version of StrIComp(), to be used with PUTF8Char/PAnsiChar
  2204. function StrIComp(Str1, Str2: pointer): PtrInt;
  2205. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  2206. /// slower version of StrLen(), but which will never read beyond the string
  2207. // - this version won't access the memory beyond the string, so may be
  2208. // preferred to StrLen(), when using e.g. mapped files or any memory
  2209. // protected buffer
  2210. function StrLenPas(S: pointer): PtrInt;
  2211. {$ifdef FPC}
  2212. /// FPC will use its internal optimized implementations
  2213. function StrLen(S: pointer): sizeint; external name 'FPC_PCHAR_LENGTH';
  2214. var FillcharFast: procedure(var Dest; count: PtrInt; Value: byte) = System.FillChar;
  2215. {$else}
  2216. /// our fast version of StrLen(), to be used with PUTF8Char/PAnsiChar
  2217. // - this version will use fast SSE2/SSE4.2 instructions (if available), on both
  2218. // Win32 and Win64 platforms: please note that in this case, it may read up to
  2219. // 15 bytes before or beyond the string; this is rarely a problem but it can in
  2220. // principle generate a protection violation (e.g. when used over mapped files):
  2221. // you can use the slightly slower StrLenPas() function instead with such input
  2222. var StrLen: function(S: pointer): PtrInt = StrLenPas;
  2223. /// our fast version of FillChar()
  2224. // - this version will use fast SSE2 instructions (if available), on both Win32
  2225. // and Win64 platforms, or an optimized X86 revision on older CPUs
  2226. var FillcharFast: procedure(var Dest; count: PtrInt; Value: byte);
  2227. {$endif FPC}
  2228. /// our fast version of move()
  2229. // - this version will use fast SSE2 instructions (if available), on both Win32
  2230. // and Win64 platforms, or an optimized X86 revision on older CPUs
  2231. var MoveFast: procedure(const Source; var Dest; Count: PtrInt);
  2232. /// our fast version of StrLen(), to be used with PWideChar
  2233. function StrLenW(S: PWideChar): PtrInt;
  2234. /// use our fast version of StrComp(), to be used with PWideChar
  2235. function StrCompW(Str1, Str2: PWideChar): PtrInt;
  2236. {$ifdef HASINLINE}inline;{$endif}
  2237. /// use our fast version of StrCompL(), to be used with PUTF8Char
  2238. function StrCompL(P1,P2: PUTF8Char; L, Default: Integer): PtrInt;
  2239. {$ifdef HASINLINE}inline;{$endif}
  2240. /// use our fast version of StrCompIL(), to be used with PUTF8Char
  2241. function StrCompIL(P1,P2: PUTF8Char; L: Integer; Default: Integer=0): PtrInt;
  2242. {$ifdef HASINLINE}inline;{$endif}
  2243. {$ifdef USENORMTOUPPER}
  2244. {$ifdef OWNNORMTOUPPER}
  2245. type
  2246. TNormTable = packed array[AnsiChar] of AnsiChar;
  2247. TNormTableByte = packed array[byte] of byte;
  2248. var
  2249. /// the NormToUpper[] array is defined in our Enhanced RTL: define it now
  2250. // if it was not installed
  2251. // - handle 8 bit upper chars as in WinAnsi / code page 1252 (e.g. accents)
  2252. NormToUpper: TNormTable;
  2253. NormToUpperByte: TNormTableByte absolute NormToUpper;
  2254. /// the NormToLower[] array is defined in our Enhanced RTL: define it now
  2255. // if it was not installed
  2256. // - handle 8 bit upper chars as in WinAnsi / code page 1252 (e.g. accents)
  2257. NormToLower: TNormTable;
  2258. NormToLowerByte: TNormTableByte absolute NormToLower;
  2259. {$endif}
  2260. {$else}
  2261. {$undef OWNNORMTOUPPER}
  2262. {$endif}
  2263. var
  2264. /// this table will convert 'a'..'z' into 'A'..'Z'
  2265. // - so it will work with UTF-8 without decoding, whereas NormToUpper[] expects
  2266. // WinAnsi encoding
  2267. NormToUpperAnsi7: TNormTable;
  2268. NormToUpperAnsi7Byte: TNormTableByte absolute NormToUpperAnsi7;
  2269. /// get the signed 32 bits integer value stored in P^
  2270. // - we use the PtrInt result type, even if expected to be 32 bits, to use
  2271. // native CPU register size (don't want any 32 bits overflow here)
  2272. // - it will stop the parsing when P^ does not contain numbers any more
  2273. function GetInteger(P: PUTF8Char): PtrInt; overload;
  2274. /// get the signed 32 bits integer value stored in P^
  2275. // - if P if nil or not start with a valid numerical value, returns Default
  2276. function GetIntegerDef(P: PUTF8Char; Default: PtrInt): PtrInt;
  2277. {$ifdef HASINLINE}inline;{$endif}
  2278. /// get the signed 32 bits integer value stored in P^
  2279. // - this version return 0 in err if no error occured, and 1 if an invalid
  2280. // character was found, not its exact index as for the val() function
  2281. function GetInteger(P: PUTF8Char; var err: integer): PtrInt; overload;
  2282. /// get the unsigned 32 bits integer value stored in P^
  2283. // - we use the PtrUInt result type, even if expected to be 32 bits, to use
  2284. // native CPU register size (don't want any 32 bits overflow here)
  2285. function GetCardinal(P: PUTF8Char): PtrUInt;
  2286. /// get the unsigned 32 bits integer value stored in P^
  2287. // - if P if nil or not start with a valid numerical value, returns Default
  2288. function GetCardinalDef(P: PUTF8Char; Default: PtrUInt): PtrUInt;
  2289. /// get the unsigned 32 bits integer value stored as Unicode string in P^
  2290. function GetCardinalW(P: PWideChar): PtrUInt;
  2291. /// get a boolean value stored as true/false text in P^
  2292. // - would also recognize any non 0 integer as true
  2293. function GetBoolean(P: PUTF8Char): boolean;
  2294. /// get the 64 bits integer value stored in P^
  2295. function GetInt64(P: PUTF8Char): Int64; overload;
  2296. {$ifdef HASINLINE}inline;{$endif}
  2297. /// get the 64 bits integer value stored in P^
  2298. procedure SetInt64(P: PUTF8Char; var result: Int64);
  2299. {$ifdef CPU64}inline;{$endif}
  2300. /// get the 64 bits integer value stored in P^
  2301. // - set the err content to the index of any faulty character, 0 if conversion
  2302. // was successful (same as the standard val function)
  2303. function GetInt64(P: PUTF8Char; var err: integer): Int64; overload;
  2304. {$ifdef CPU64}inline;{$endif}
  2305. /// get the extended floating point value stored in P^
  2306. // - set the err content to the index of any faulty character, 0 if conversion
  2307. // was successful (same as the standard val function)
  2308. function GetExtended(P: PUTF8Char; out err: integer): TSynExtended; overload;
  2309. /// get the extended floating point value stored in P^
  2310. // - this overloaded version returns 0 as a result if the content of P is invalid
  2311. function GetExtended(P: PUTF8Char): TSynExtended; overload;
  2312. /// get the WideChar stored in P^ (decode UTF-8 if necessary)
  2313. // - any surrogate (UCS4>$ffff) will be returned as '?'
  2314. function GetUTF8Char(P: PUTF8Char): cardinal;
  2315. {$ifdef HASINLINE}inline;{$endif}
  2316. /// get the UCS4 char stored in P^ (decode UTF-8 if necessary)
  2317. function NextUTF8UCS4(var P: PUTF8Char): cardinal;
  2318. {$ifdef HASINLINE}inline;{$endif}
  2319. /// get the signed 32 bits integer value stored in a RawUTF8 string
  2320. // - we use the PtrInt result type, even if expected to be 32 bits, to use
  2321. // native CPU register size (don't want any 32 bits overflow here)
  2322. function UTF8ToInteger(const value: RawUTF8; Default: PtrInt=0): PtrInt; overload;
  2323. {$ifdef HASINLINE}inline;{$endif}
  2324. /// get and check range of a signed 32 bits integer stored in a RawUTF8 string
  2325. // - we use the PtrInt result type, even if expected to be 32 bits, to use
  2326. // native CPU register size (don't want any 32 bits overflow here)
  2327. function UTF8ToInteger(const value: RawUTF8; Min,Max: PtrInt; Default: PtrInt=0): PtrInt; overload;
  2328. {$ifdef HASINLINE}inline;{$endif}
  2329. /// encode a string to be compatible with URI encoding
  2330. function UrlEncode(const svar: RawUTF8): RawUTF8; overload;
  2331. /// encode a string to be compatible with URI encoding
  2332. function UrlEncode(Text: PUTF8Char): RawUTF8; overload;
  2333. /// encode supplied parameters to be compatible with URI encoding
  2334. // - parameters must be supplied two by two, as Name,Value pairs, e.g.
  2335. // ! url := UrlEncode(['select','*','where','ID=12','offset',23,'object',aObject]);
  2336. // - parameters names should be plain ASCII-7 RFC compatible identifiers
  2337. // (0..9a..zA..Z_.~), otherwise their values are skipped
  2338. // - parameters values can be either textual, integer or extended, or any TObject
  2339. // - TObject serialization into UTF-8 will be processed by the ObjectToJSON()
  2340. // function
  2341. function UrlEncode(const NameValuePairs: array of const): RawUTF8; overload;
  2342. /// encode a JSON object UTF-8 buffer into URI parameters
  2343. // - you can specify property names to ignore during the object decoding
  2344. function UrlEncodeJsonObject(const URIName: RawUTF8; ParametersJSON: PUTF8Char;
  2345. const PropNamesToIgnore: array of RawUTF8): RawUTF8;
  2346. /// decode a string compatible with URI encoding into its original value
  2347. // - you can specify the decoding range (as in copy(s,i,len) function)
  2348. function UrlDecode(const s: RawUTF8; i: PtrInt = 1; len: PtrInt = -1): RawUTF8; overload;
  2349. /// decode a string compatible with URI encoding into its original value
  2350. function UrlDecode(U: PUTF8Char): RawUTF8; overload;
  2351. /// decode a specified parameter compatible with URI encoding into its original
  2352. // textual value
  2353. // - UrlDecodeValue('select=%2A&where=LastName%3D%27M%C3%B4net%27','SELECT=',V,@Next)
  2354. // will return Next^='where=...' and V='*'
  2355. // - if Upper is not found, Value is not modified, and result is FALSE
  2356. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2357. function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8;
  2358. Next: PPUTF8Char=nil): boolean;
  2359. /// decode a specified parameter compatible with URI encoding into its original
  2360. // integer numerical value
  2361. // - UrlDecodeInteger('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next)
  2362. // will return Next^='where=...' and O=20
  2363. // - if Upper is not found, Value is not modified, and result is FALSE
  2364. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2365. function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar;var Value: integer;
  2366. Next: PPUTF8Char=nil): boolean;
  2367. /// decode a specified parameter compatible with URI encoding into its original
  2368. // cardinal numerical value
  2369. // - UrlDecodeCardinal('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next)
  2370. // will return Next^='where=...' and O=20
  2371. // - if Upper is not found, Value is not modified, and result is FALSE
  2372. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2373. function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal;
  2374. Next: PPUTF8Char=nil): boolean;
  2375. /// decode a specified parameter compatible with URI encoding into its original
  2376. // Int64 numerical value
  2377. // - UrlDecodeInt64('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next)
  2378. // will return Next^='where=...' and O=20
  2379. // - if Upper is not found, Value is not modified, and result is FALSE
  2380. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2381. function UrlDecodeInt64(U: PUTF8Char; Upper: PAnsiChar;var Value: Int64;
  2382. Next: PPUTF8Char=nil): boolean;
  2383. /// decode a specified parameter compatible with URI encoding into its original
  2384. // floating-point value
  2385. // - UrlDecodeExtended('price=20.45&where=LastName%3D%27M%C3%B4net%27','PRICE=',P,@Next)
  2386. // will return Next^='where=...' and P=20.45
  2387. // - if Upper is not found, Value is not modified, and result is FALSE
  2388. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2389. function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar; var Value: TSynExtended;
  2390. Next: PPUTF8Char=nil): boolean;
  2391. /// decode a specified parameter compatible with URI encoding into its original
  2392. // floating-point value
  2393. // - UrlDecodeDouble('price=20.45&where=LastName%3D%27M%C3%B4net%27','PRICE=',P,@Next)
  2394. // will return Next^='where=...' and P=20.45
  2395. // - if Upper is not found, Value is not modified, and result is FALSE
  2396. // - if Upper is found, Value is modified with the supplied content, and result is TRUE
  2397. function UrlDecodeDouble(U: PUTF8Char; Upper: PAnsiChar; var Value: double;
  2398. Next: PPUTF8Char=nil): boolean;
  2399. /// returns TRUE if all supplied parameters do exist in the URI encoded text
  2400. // - CSVNames parameter shall provide as a CSV list of names
  2401. // - e.g. UrlDecodeNeedParameters('price=20.45&where=LastName%3D','price,where')
  2402. // will return TRUE
  2403. function UrlDecodeNeedParameters(U, CSVNames: PUTF8Char): boolean;
  2404. /// decode the next Name=Value&.... pair from input URI
  2405. // - Name is returned directly (should be plain ASCII 7 bit text)
  2406. // - Value is returned after URI decoding (from %.. patterns)
  2407. // - if a pair is decoded, return a PUTF8Char pointer to the next pair in
  2408. // the input buffer, or points to #0 if all content has been processed
  2409. // - if a pair is not decoded, return nil
  2410. function UrlDecodeNextNameValue(U: PUTF8Char; var Name,Value: RawUTF8): PUTF8Char;
  2411. /// decode a URI-encoded Value from an input buffer
  2412. // - decoded value is set in Value out variable
  2413. // - returns a pointer just after the decoded value (may points e.g. to
  2414. // #0 or '&') - it is up to the caller to continue the process or not
  2415. function UrlDecodeNextValue(U: PUTF8Char; out Value: RawUTF8): PUTF8Char;
  2416. /// decode a URI-encoded Name from an input buffer
  2417. // - decoded value is set in Name out variable
  2418. // - returns a pointer just after the decoded name, after the '='
  2419. // - returns nil if there was no name=... pattern in U
  2420. function UrlDecodeNextName(U: PUTF8Char; out Name: RawUTF8): PUTF8Char;
  2421. /// encode name/value pairs into CSV/INI raw format
  2422. function CSVEncode(const NameValuePairs: array of const;
  2423. const KeySeparator: RawUTF8='='; const ValueSeparator: RawUTF8=#13#10): RawUTF8;
  2424. /// find a given name in name/value pairs, and returns the value as RawUTF8
  2425. function ArrayOfConstValueAsText(const NameValuePairs: array of const;
  2426. const aName: RawUTF8): RawUTF8;
  2427. /// returns TRUE if the given text buffer contains A..Z,0..9,_ characters
  2428. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2429. // - i.e. can be tested via IdemPropName*() functions, and the MongoDB-like
  2430. // extended JSON syntax as generated by dvoSerializeAsExtendedJson
  2431. // - first char must be alphabetical or '_', following chars can be
  2432. // alphanumerical or '_'
  2433. function PropNameValid(P: PUTF8Char): boolean;
  2434. {$ifdef HASINLINE}inline;{$endif}
  2435. /// returns TRUE if the given text buffer contains simple characters as
  2436. // recognized by JSON extended syntax
  2437. // - follow GetJSONPropName and GotoNextJSONObjectOrArray expectations
  2438. function JsonPropNameValid(P: PUTF8Char): boolean;
  2439. {$ifdef HASINLINE}inline;{$endif}
  2440. /// returns TRUE if the given text buffers would be escaped when written as JSON
  2441. // - e.g. if contains " or \ characters, as defined by
  2442. // http://www.ietf.org/rfc/rfc4627.txt
  2443. function NeedsJsonEscape(const Text: RawUTF8): boolean;
  2444. /// case unsensitive test of P1 and P2 content
  2445. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2446. function IdemPropName(const P1,P2: shortstring): boolean; overload;
  2447. {$ifdef HASINLINE}inline;{$endif}
  2448. /// case unsensitive test of P1 and P2 content
  2449. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2450. // - this version expects P2 to be a PAnsiChar with a specified length
  2451. function IdemPropName(const P1: shortstring; P2: PUTF8Char; P2Len: integer): boolean; overload;
  2452. {$ifdef HASINLINE}inline;{$endif}
  2453. /// case unsensitive test of P1 and P2 content
  2454. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2455. // - this version expects P1 and P2 to be a PAnsiChar with specified lengths
  2456. function IdemPropName(P1,P2: PUTF8Char; P1Len,P2Len: integer): boolean; overload;
  2457. {$ifdef HASINLINE}inline;{$endif}
  2458. /// case unsensitive test of P1 and P2 content
  2459. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2460. // - this version expects P2 to be a PAnsiChar with specified length
  2461. function IdemPropNameU(const P1: RawUTF8; P2: PUTF8Char; P2Len: integer): boolean; overload;
  2462. {$ifdef HASINLINE}inline;{$endif}
  2463. /// case unsensitive test of P1 and P2 content of same length
  2464. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2465. // - this version expects P1 and P2 to be a PAnsiChar with an already checked
  2466. // identical length, so may be used for a faster process, e.g. in a loop
  2467. // - if P1 and P2 are RawUTF8, you should better call overloaded function
  2468. // IdemPropNameU(const P1,P2: RawUTF8), which would be slightly faster by
  2469. // using the length stored before the actual text buffer of each RawUTF8
  2470. function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: integer): boolean;
  2471. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2472. /// case unsensitive test of P1 and P2 content
  2473. // - use it with property names values (i.e. only including A..Z,0..9,_ chars)
  2474. function IdemPropNameU(const P1,P2: RawUTF8): boolean; overload;
  2475. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2476. /// returns true if the beginning of p^ is the same as up^
  2477. // - ignore case - up^ must be already Upper
  2478. // - chars are compared as 7 bit Ansi only (no accentuated characters): but when
  2479. // you only need to search for field names e.g. IdemPChar() is prefered, because
  2480. // it'll be faster than IdemPCharU(), if UTF-8 decoding is not mandatory
  2481. // - if p is nil, will return FALSE
  2482. // - if up is nil, will return TRUE
  2483. function IdemPChar(p: PUTF8Char; up: PAnsiChar): boolean;
  2484. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  2485. /// returns true if the beginning of p^ is the same as up^, ignoring white spaces
  2486. // - ignore case - up^ must be already Upper
  2487. // - any white space in the input p^ buffer is just ignored
  2488. // - chars are compared as 7 bit Ansi only (no accentuated characters): but when
  2489. // you only need to search for field names e.g. IdemPChar() is prefered, because
  2490. // it'll be faster than IdemPCharU(), if UTF-8 decoding is not mandatory
  2491. // - if p is nil, will return FALSE
  2492. // - if up is nil, will return TRUE
  2493. function IdemPCharWithoutWhiteSpace(p: PUTF8Char; up: PAnsiChar): boolean;
  2494. /// returns the index of a matching beginning of p^ in upArray[]
  2495. // - returns -1 if no item matched
  2496. // - ignore case - upArray^ must be already Upper
  2497. // - chars are compared as 7 bit Ansi only (no accentuated characters)
  2498. // - warning: this function expects upArray[] items to have AT LEAST TWO
  2499. // CHARS (it will use a fast comparison of initial 2 bytes)
  2500. function IdemPCharArray(p: PUTF8Char; const upArray: array of PAnsiChar): integer;
  2501. /// returns true if the beginning of p^ is the same as up^
  2502. // - ignore case - up^ must be already Upper
  2503. // - this version will decode the UTF-8 content before using NormToUpper[], so
  2504. // it will be slower than the IdemPChar() function above, but will handle
  2505. // WinAnsi accentuated characters (e.g. 'e' acute will be matched as 'E')
  2506. function IdemPCharU(p, up: PUTF8Char): boolean;
  2507. {$ifdef HASINLINE}inline;{$endif}
  2508. /// returns true if the beginning of p^ is same as up^
  2509. // - ignore case - up^ must be already Upper
  2510. // - this version expects p^ to point to an Unicode char array
  2511. function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean;
  2512. /// returns true if the file name extension contained in p^ is the same same as extup^
  2513. // - ignore case - extup^ must be already Upper
  2514. // - chars are compared as WinAnsi (codepage 1252), not as UTF-8
  2515. // - could be used e.g. like IdemFileExt(aFileName,'.JP');
  2516. function IdemFileExt(p: PUTF8Char; extup: PAnsiChar; sepChar: AnsiChar='.'): Boolean;
  2517. /// internal function, used to retrieve a UCS4 char (>127) from UTF-8
  2518. // - not to be called directly, but from inlined higher-level functions
  2519. // - here U^ shall be always >= #80
  2520. function GetHighUTF8UCS4(var U: PUTF8Char): cardinal;
  2521. /// retrieve the next UCS4 value stored in U, then update the U pointer
  2522. // - this function will decode the UTF-8 content before using NormToUpper[]
  2523. // - will return '?' if the UCS4 value is higher than #255: so use this function
  2524. // only if you need to deal with ASCII characters (e.g. it's used for Soundex
  2525. // and for ContainsUTF8 function)
  2526. function GetNextUTF8Upper(var U: PUTF8Char): cardinal;
  2527. {$ifdef HASINLINE}inline;{$endif}
  2528. /// points to the beginning of the next word stored in U
  2529. // - returns nil if reached the end of U (i.e. #0 char)
  2530. // - here a "word" is a Win-Ansi word, i.e. '0'..'9', 'A'..'Z'
  2531. function FindNextUTF8WordBegin(U: PUTF8Char): PUTF8Char;
  2532. /// return true if up^ is contained inside the UTF-8 buffer p^
  2533. // - search up^ at the beginning of every UTF-8 word (aka in Soundex)
  2534. // - here a "word" is a Win-Ansi word, i.e. '0'..'9', 'A'..'Z'
  2535. // - up^ must be already Upper
  2536. function ContainsUTF8(p, up: PUTF8Char): boolean;
  2537. const
  2538. /// used e.g. by inlined function GetLineContains()
  2539. ANSICHARNOT01310: set of AnsiChar = [#1..#9,#11,#12,#14..#255];
  2540. /// returns TRUE if the supplied uppercased text is contained in the text buffer
  2541. function GetLineContains(p,pEnd, up: PUTF8Char): boolean;
  2542. {$ifdef HASINLINE}inline;{$endif}
  2543. /// copy source into a 256 chars dest^ buffer with 7 bits upper case conversion
  2544. // - used internally for short keys match or case-insensitive hash
  2545. // - returns final dest pointer
  2546. // - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as
  2547. // array[byte] of AnsiChar on the caller stack)
  2548. function UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PAnsiChar; overload;
  2549. {$ifdef HASINLINE}inline;{$endif}
  2550. /// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion
  2551. // - used internally for short keys match or case-insensitive hash
  2552. // - will use SSE4.2 instructions on supported CPUs - and potentiall read up
  2553. // to 15 bytes beyond the string: use UpperCopy255BufPas() for a safer memory read
  2554. // - returns final dest pointer
  2555. // - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as
  2556. // array[byte] of AnsiChar on the caller stack)
  2557. var UpperCopy255Buf: function(dest: PAnsiChar; source: PUTF8Char; sourceLen: integer): PAnsiChar;
  2558. /// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion
  2559. // - used internally for short keys match or case-insensitive hash
  2560. // - this version is written in optimized pascal
  2561. // - you should not have to call this function, but rely on UpperCopy255Buf()
  2562. // - returns final dest pointer
  2563. // - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as
  2564. // array[byte] of AnsiChar on the caller stack)
  2565. function UpperCopy255BufPas(dest: PAnsiChar; source: PUTF8Char; sourceLen: integer): PAnsiChar;
  2566. {$ifndef PUREPASCAL}
  2567. {$ifndef DELPHI5OROLDER}
  2568. /// copy source^ into a 256 chars dest^ buffer with 7 bits upper case conversion
  2569. // - used internally for short keys match or case-insensitive hash
  2570. // - this version will use SSE4.2 instructions on supported CPUs - and potentiall
  2571. // read up to 15 bytes beyond the string
  2572. // - you should not have to call this function, but rely on UpperCopy255Buf()
  2573. // - returns final dest pointer
  2574. // - will copy up to 255 AnsiChar (expect the dest buffer to be defined e.g. as
  2575. // array[byte] of AnsiChar on the caller stack)
  2576. function UpperCopy255BufSSE42(dest: PAnsiChar; source: PUTF8Char; sourceLen: integer): PAnsiChar;
  2577. {$endif}
  2578. {$endif}
  2579. /// copy source into dest^ with WinAnsi 8 bits upper case conversion
  2580. // - used internally for short keys match or case-insensitive hash
  2581. // - returns final dest pointer
  2582. // - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
  2583. // AnsiChar)
  2584. function UpperCopyWin255(dest: PWinAnsiChar; const source: RawUTF8): PWinAnsiChar;
  2585. /// copy WideChar source into dest^ with upper case conversion
  2586. // - used internally for short keys match or case-insensitive hash
  2587. // - returns final dest pointer
  2588. // - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
  2589. // AnsiChar)
  2590. function UpperCopy255W(dest: PAnsiChar; const source: SynUnicode): PAnsiChar; overload;
  2591. /// copy WideChar source into dest^ with upper case conversion
  2592. // - used internally for short keys match or case-insensitive hash
  2593. // - returns final dest pointer
  2594. // - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
  2595. // AnsiChar)
  2596. function UpperCopy255W(dest: PAnsiChar; source: PWideChar; L: integer): PAnsiChar; overload;
  2597. /// copy source into dest^ with 7 bits upper case conversion
  2598. // - returns final dest pointer
  2599. // - will copy up to the source buffer end: so Dest^ should be big enough -
  2600. // which will the case e.g. if Dest := pointer(source)
  2601. function UpperCopy(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;
  2602. /// copy source into dest^ with 7 bits upper case conversion
  2603. // - returns final dest pointer
  2604. // - this special version expect source to be a shortstring
  2605. function UpperCopyShort(dest: PAnsiChar; const source: shortstring): PAnsiChar;
  2606. {$ifdef USENORMTOUPPER}
  2607. /// fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values
  2608. // - this version expects u1 and u2 to be zero-terminated
  2609. // - this version will decode each UTF-8 glyph before using NormToUpper[]
  2610. // - current implementation handles UTF-16 surrogates
  2611. function UTF8IComp(u1, u2: PUTF8Char): PtrInt;
  2612. /// copy WideChar source into dest^ with upper case conversion, using the
  2613. // NormToUpper[] array for all 8 bits values, encoding the result as UTF-8
  2614. // - returns final dest pointer
  2615. // - current implementation handles UTF-16 surrogates
  2616. function UTF8UpperCopy(Dest, Source: PUTF8Char; SourceChars: Cardinal): PUTF8Char;
  2617. /// copy WideChar source into dest^ with upper case conversion, using the
  2618. // NormToUpper[] array for all 8 bits values, encoding the result as UTF-8
  2619. // - returns final dest pointer
  2620. // - will copy up to 255 AnsiChar (expect the dest buffer to be array[byte] of
  2621. // AnsiChar), with UTF-8 encoding
  2622. function UTF8UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PUTF8Char;
  2623. {$ifdef HASINLINE}inline;{$endif}
  2624. /// fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values
  2625. // - this version expects u1 and u2 not to be necessary zero-terminated, but
  2626. // uses L1 and L2 as length for u1 and u2 respectively
  2627. // - use this function for SQLite3 collation (TSQLCollateFunc)
  2628. // - this version will decode the UTF-8 content before using NormToUpper[]
  2629. // - current implementation handles UTF-16 surrogates
  2630. function UTF8ILComp(u1, u2: PUTF8Char; L1,L2: cardinal): PtrInt;
  2631. /// fast case-insensitive Unicode comparaison
  2632. // - use the NormToUpperAnsi7Byte[] array, i.e. compare 'a'..'z' as 'A'..'Z'
  2633. // - this version expects u1 and u2 to be zero-terminated
  2634. function AnsiICompW(u1, u2: PWideChar): PtrInt;
  2635. /// SameText() overloaded function with proper UTF-8 decoding
  2636. // - fast version using NormToUpper[] array for all Win-Ansi characters
  2637. // - this version will decode each UTF-8 glyph before using NormToUpper[]
  2638. // - current implementation handles UTF-16 surrogates as UTF8IComp()
  2639. function SameTextU(const S1, S2: RawUTF8): Boolean;
  2640. {$ifdef HASINLINE}inline;{$endif}
  2641. /// fast conversion of the supplied text into 8 bit uppercase
  2642. // - this will not only convert 'a'..'z' into 'A'..'Z', but also accentuated
  2643. // latin characters ('e' acute into 'E' e.g.), using NormToUpper[] array
  2644. // - it will decode the supplied UTF-8 content to handle more than
  2645. // 7 bit of ascii characters (so this function is dedicated to WinAnsi code page
  2646. // 1252 characters set)
  2647. function UpperCaseU(const S: RawUTF8): RawUTF8;
  2648. /// fast conversion of the supplied text into 8 bit lowercase
  2649. // - this will not only convert 'A'..'Z' into 'a'..'z', but also accentuated
  2650. // latin characters ('E' acute into 'e' e.g.), using NormToLower[] array
  2651. // - it will convert decode the supplied UTF-8 content to handle more than
  2652. // 7 bit of ascii characters
  2653. function LowerCaseU(const S: RawUTF8): RawUTF8;
  2654. /// fast conversion of the supplied text into 8 bit case sensitivity
  2655. // - convert the text in-place, returns the resulting length
  2656. // - it will decode the supplied UTF-8 content to handle more than 7 bit
  2657. // of ascii characters during the conversion (leaving not WinAnsi characters
  2658. // untouched)
  2659. // - will not set the last char to #0 (caller must do that if necessary)
  2660. function ConvertCaseUTF8(P: PUTF8Char; const Table: TNormTableByte): PtrInt;
  2661. {$endif USENORMTOUPPER}
  2662. /// fast conversion of the supplied text into uppercase
  2663. // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and
  2664. // will therefore by correct with true UTF-8 content, but only for 7 bit
  2665. function UpperCase(const S: RawUTF8): RawUTF8;
  2666. /// fast conversion of the supplied text into uppercase
  2667. // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and
  2668. // will therefore by correct with true UTF-8 content, but only for 7 bit
  2669. procedure UpperCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8); overload;
  2670. /// fast conversion of the supplied text into uppercase
  2671. // - this will only convert 'a'..'z' into 'A'..'Z' (no NormToUpper use), and
  2672. // will therefore by correct with true UTF-8 content, but only for 7 bit
  2673. procedure UpperCaseCopy(const Source: RawUTF8; var Dest: RawUTF8); overload;
  2674. /// fast conversion of the supplied text into lowercase
  2675. // - this will only convert 'A'..'Z' into 'a'..'z' (no NormToLower use), and
  2676. // will therefore by correct with true UTF-8 content
  2677. function LowerCase(const S: RawUTF8): RawUTF8;
  2678. /// fast conversion of the supplied text into lowercase
  2679. // - this will only convert 'A'..'Z' into 'a'..'z' (no NormToLower use), and
  2680. // will therefore by correct with true UTF-8 content
  2681. procedure LowerCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8);
  2682. /// accurate conversion of the supplied UTF-8 content into the corresponding
  2683. // upper-case Unicode characters
  2684. // - this version will use the Operating System API, and will therefore be
  2685. // much slower than UpperCase/UpperCaseU versions, but will handle all
  2686. // kind of unicode characters
  2687. function UpperCaseUnicode(const S: RawUTF8): RawUTF8;
  2688. /// accurate conversion of the supplied UTF-8 content into the corresponding
  2689. // lower-case Unicode characters
  2690. // - this version will use the Operating System API, and will therefore be
  2691. // much slower than LowerCase/LowerCaseU versions, but will handle all
  2692. // kind of unicode characters
  2693. function LowerCaseUnicode(const S: RawUTF8): RawUTF8;
  2694. /// trims leading whitespace characters from the string by removing
  2695. // new line, space, and tab characters
  2696. function TrimLeft(const S: RawUTF8): RawUTF8;
  2697. /// trims trailing whitespace characters from the string by removing trailing
  2698. // newline, space, and tab characters
  2699. function TrimRight(const S: RawUTF8): RawUTF8;
  2700. /// fast WinAnsi comparaison using the NormToUpper[] array for all 8 bits values
  2701. function AnsiIComp(Str1, Str2: PWinAnsiChar): PtrInt;
  2702. {$ifndef USENORMTOUPPER} {$ifdef PUREPASCAL}
  2703. {$ifdef HASINLINE}inline;{$endif} {$endif} {$endif}
  2704. /// extract a line from source array of chars
  2705. // - next will contain the beginning of next line, or nil if source if ended
  2706. function GetNextLine(source: PUTF8Char; out next: PUTF8Char): RawUTF8;
  2707. {$ifdef UNICODE}
  2708. /// extract a line from source array of chars
  2709. // - next will contain the beginning of next line, or nil if source if ended
  2710. // - this special version expect UnicodeString pointers, and return an UnicodeString
  2711. function GetNextLineW(source: PWideChar; out next: PWideChar): string;
  2712. /// find the Value of UpperName in P, till end of current section
  2713. // - expect UpperName as 'NAME='
  2714. // - this special version expect UnicodeString pointer, and return a VCL string
  2715. function FindIniNameValueW(P: PWideChar; UpperName: PUTF8Char): string;
  2716. /// find a Name= Value in a [Section] of a INI Unicode Content
  2717. // - this function scans the Content memory buffer, and is
  2718. // therefore very fast (no temporary TMemIniFile is created)
  2719. // - if Section equals '', find the Name= value before any [Section]
  2720. function FindIniEntryW(const Content: string; const Section, Name: RawUTF8): string;
  2721. {$ifdef PUREPASCAL}
  2722. {$ifndef UNICODE}
  2723. /// our fast RawUTF8 version of Pos(), for Unicode only compiler
  2724. // - this Pos() is seldom used, but this RawUTF8 specific version is needed
  2725. // by Delphi 2009+, to avoid two unnecessary conversions into UnicodeString
  2726. function Pos(const substr, str: RawUTF8): Integer; overload; inline;
  2727. {$endif UNICODE}
  2728. {$else}
  2729. {$endif PUREPASCAL}
  2730. {$endif UNICODE}
  2731. /// faster RawUTF8 Equivalent of standard StrUtils.PosEx
  2732. function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt=1): Integer;
  2733. /// split a RawUTF8 string into two strings, according to SepStr separator
  2734. // - if SepStr is not found, LeftStr=Str and RightStr=''
  2735. // - if ToUpperCase is TRUE, then LeftStr and RightStr will be made uppercase
  2736. procedure Split(const Str, SepStr: RawUTF8; var LeftStr, RightStr: RawUTF8; ToUpperCase: boolean=false); overload;
  2737. /// split a RawUTF8 string into two strings, according to SepStr separator
  2738. // - this overloaded function returns the right string as function result
  2739. // - if SepStr is not found, LeftStr=Str and result=''
  2740. // - if ToUpperCase is TRUE, then LeftStr and result will be made uppercase
  2741. function Split(const Str, SepStr: RawUTF8; var LeftStr: RawUTF8; ToUpperCase: boolean=false): RawUTF8; overload;
  2742. /// returns the left part of a RawUTF8 string, according to SepStr separator
  2743. // - if SepStr is found, returns Str first chars until (and exluding) SepStr
  2744. // - if SepStr is not found, returns Str
  2745. function Split(const Str, SepStr: RawUTF8; StartPos: integer=1): RawUTF8; overload;
  2746. /// split a RawUTF8 string into several strings, according to SepStr separator
  2747. // - this overloaded function will fill a DestPtr[] array of PRawUTF8
  2748. // - if any DestPtr[]=nil, the item will be skipped
  2749. procedure Split(const Str: RawUTF8; const SepStr: array of RawUTF8;
  2750. const DestPtr: array of PRawUTF8); overload;
  2751. /// fast version of StringReplace(S, OldPattern, NewPattern,[rfReplaceAll]);
  2752. function StringReplaceAll(const S, OldPattern, NewPattern: RawUTF8): RawUTF8;
  2753. /// fast replace of a specified char by a given string
  2754. function StringReplaceChars(const Source: RawUTF8; OldChar, NewChar: AnsiChar): RawUTF8;
  2755. /// fast replace of all #9 chars by a given string
  2756. function StringReplaceTabs(const Source,TabText: RawUTF8): RawUTF8;
  2757. /// format a text content with SQL-like quotes
  2758. // - UTF-8 version of the function available in SysUtils
  2759. // - this function implements what is specified in the official SQLite3
  2760. // documentation: "A string constant is formed by enclosing the string in single
  2761. // quotes ('). A single quote within the string can be encoded by putting two
  2762. // single quotes in a row - as in Pascal."
  2763. function QuotedStr(const S: RawUTF8; Quote: AnsiChar=''''): RawUTF8; overload;
  2764. {$ifdef HASINLINE}inline;{$endif}
  2765. /// format a buffered text content with SQL-like quotes
  2766. // - this function implements what is specified in the official SQLite3
  2767. // documentation: "A string constant is formed by enclosing the string in single
  2768. // quotes ('). A single quote within the string can be encoded by putting two
  2769. // single quotes in a row - as in Pascal."
  2770. function QuotedStr(Text: PUTF8Char; Quote: AnsiChar): RawUTF8; overload;
  2771. {$ifdef HASINLINE}inline;{$endif}
  2772. /// format a buffered text content with SQL-like quotes
  2773. // - this function implements what is specified in the official SQLite3
  2774. // documentation: "A string constant is formed by enclosing the string in single
  2775. // quotes ('). A single quote within the string can be encoded by putting two
  2776. // single quotes in a row - as in Pascal."
  2777. procedure QuotedStr(Text: PUTF8Char; Quote: AnsiChar; var result: RawUTF8); overload;
  2778. /// convert a buffered text content into a JSON string
  2779. // - with proper escaping of the content, and surounding " characters
  2780. procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8);
  2781. /// unquote a SQL-compatible string
  2782. // - the first character in P^ must be either ', either " then double quotes
  2783. // are transformed into single quotes
  2784. // - 'text '' end' -> text ' end
  2785. // - "text "" end" -> text " end
  2786. // - returns nil if P doesn't contain a valid SQL string
  2787. // - returns a pointer just after the quoted text otherwise
  2788. function UnQuoteSQLStringVar(P: PUTF8Char; out Value: RawUTF8): PUTF8Char;
  2789. /// unquote a SQL-compatible string
  2790. function UnQuoteSQLString(const Value: RawUTF8): RawUTF8;
  2791. /// unquote a SQL-compatible symbol name
  2792. // - e.g. '[symbol]' -> 'symbol' or '"symbol"' -> 'symbol'
  2793. function UnQuotedSQLSymbolName(const ExternalDBSymbol: RawUTF8): RawUTF8;
  2794. /// get the next character after a quoted buffer
  2795. // - the first character in P^ must be either ', either "
  2796. // - it will return the latest quote position, ignoring double quotes within
  2797. function GotoEndOfQuotedString(P: PUTF8Char): PUTF8Char;
  2798. {$ifdef HASINLINE}inline;{$endif}
  2799. /// get the next character after a quoted buffer
  2800. // - the first character in P^ must be "
  2801. // - it will return the latest " position, ignoring \" within
  2802. function GotoEndOfJSONString(P: PUTF8Char): PUTF8Char;
  2803. {$ifdef HASINLINE}inline;{$endif}
  2804. /// get the next character not in [#1..' ']
  2805. function GotoNextNotSpace(P: PUTF8Char): PUTF8Char;
  2806. {$ifdef HASINLINE}inline;{$endif}
  2807. /// check if the next character not in [#1..' '] matchs a given value
  2808. // - first ignore any non space character
  2809. // - then returns TRUE if P^=ch, setting P to the character after ch
  2810. // - or returns FALSE if P^<>ch, leaving P at the level of the unexpected char
  2811. function NextNotSpaceCharIs(var P: PUTF8Char; ch: AnsiChar): boolean;
  2812. {$ifdef HASINLINE}inline;{$endif}
  2813. /// go to the beginning of the SQL statement, ignoring all blanks and comments
  2814. // - used to check the SQL statement command (e.g. is it a SELECT?)
  2815. function SQLBegin(P: PUTF8Char): PUTF8Char;
  2816. /// add a condition to a SQL WHERE clause, with an ' and ' if where is not void
  2817. procedure SQLAddWhereAnd(var where: RawUTF8; const condition: RawUTF8);
  2818. /// return true if the parameter is void or begin with a 'SELECT' SQL statement
  2819. // - used to avoid code injection and to check if the cache must be flushed
  2820. // - VACUUM, PRAGMA, or EXPLAIN statements also return true, since they won't
  2821. // change the data content
  2822. // - WITH recursive statement expect no INSERT/UPDATE/DELETE pattern in the SQL
  2823. // - if P^ is a SELECT and SelectClause is set to a variable, it would
  2824. // contain the field names, from SELECT ...field names... FROM
  2825. function isSelect(P: PUTF8Char; SelectClause: PRawUTF8=nil): boolean;
  2826. /// return true if IdemPChar(source,searchUp), and go to the next line of source
  2827. function IdemPCharAndGetNextLine(var source: PUTF8Char; searchUp: PAnsiChar): boolean;
  2828. /// return true if IdemPChar(source,searchUp), and retrieve the value item
  2829. // - typical use may be:
  2830. // ! if IdemPCharAndGetNextItem(P,
  2831. // ! 'CONTENT-DISPOSITION: FORM-DATA; NAME="',Name,'"') then ...
  2832. function IdemPCharAndGetNextItem(var source: PUTF8Char; const searchUp: RawUTF8;
  2833. var Item: RawUTF8; Sep: AnsiChar=#13): boolean;
  2834. /// return line begin from source array of chars, and go to next line
  2835. // - next will contain the beginning of next line, or nil if source if ended
  2836. function GetNextLineBegin(source: PUTF8Char; out next: PUTF8Char): PUTF8Char;
  2837. {$ifdef HASINLINE}inline;{$endif}
  2838. /// compute the line length from source array of chars
  2839. // - end counting at either #0, #13 or #10
  2840. function GetLineSize(P,PEnd: PUTF8Char): PtrUInt;
  2841. /// returns true if the line length from source array of chars is not less than
  2842. // the specified count
  2843. function GetLineSizeSmallerThan(P,PEnd: PUTF8Char; aMinimalCount: integer): boolean;
  2844. /// return next CSV string from P, nil if no more
  2845. function GetNextItem(var P: PUTF8Char; Sep: AnsiChar= ','): RawUTF8;
  2846. /// return next CSV string from P, nil if no more
  2847. // - this function returns the generic string type of the compiler, and
  2848. // therefore can be used with ready to be displayed text (e.g. for the VCL)
  2849. function GetNextItemString(var P: PChar; Sep: Char= ','): string;
  2850. /// return next string delimited with #13#10 from P, nil if no more
  2851. // - this function returns a RawUnicode string type
  2852. function GetNextStringLineToRawUnicode(var P: PChar): RawUnicode;
  2853. /// append some text lines with the supplied Values[]
  2854. // - if any Values[] item is '', no line is added
  2855. // - otherwise, appends 'Caption: Value', with Caption taken from CSV
  2856. procedure AppendCSVValues(const CSV: string; const Values: array of string;
  2857. var Result: string; const AppendBefore: string=#13#10);
  2858. /// return a CSV list of the iterated same value
  2859. // - e.g. CSVOfValue('?',3)='?,?,?'
  2860. function CSVOfValue(const Value: RawUTF8; Count: cardinal; const Sep: RawUTF8=','): RawUTF8;
  2861. /// retrieve the next CSV separated bit index
  2862. // - each bit was stored as BitIndex+1, i.e. 0 to mark end of CSV chunk
  2863. // - several bits set to one can be regrouped via 'first-last,' syntax
  2864. procedure SetBitCSV(var Bits; BitsCount: integer; var P: PUTF8Char);
  2865. /// convert a set of bit into a CSV content
  2866. // - each bit is stored as BitIndex+1, and separated by a ','
  2867. // - several bits set to one can be regrouped via 'first-last,' syntax
  2868. // - ',0' is always appended at the end of the CSV chunk to mark its end
  2869. function GetBitCSV(const Bits; BitsCount: integer): RawUTF8;
  2870. /// return next CSV string from P, nil if no more
  2871. // - output text would be trimmed from any left or right space
  2872. procedure GetNextItemShortString(var P: PUTF8Char; out Dest: ShortString; Sep: AnsiChar= ',');
  2873. /// return next CSV string as unsigned integer from P, 0 if no more
  2874. function GetNextItemCardinal(var P: PUTF8Char; Sep: AnsiChar= ','): PtrUInt;
  2875. /// return next CSV string as signed integer from P, 0 if no more
  2876. function GetNextItemInteger(var P: PUTF8Char; Sep: AnsiChar= ','): PtrInt;
  2877. /// return next CSV string as 64 bit signed integer from P, 0 if no more
  2878. function GetNextItemInt64(var P: PUTF8Char; Sep: AnsiChar= ','): Int64;
  2879. /// return next CSV string as unsigned integer from P, 0 if no more
  2880. // - P^ will point to the first non digit character (the item separator, e.g.
  2881. // ',' for CSV)
  2882. function GetNextItemCardinalStrict(var P: PUTF8Char): PtrUInt;
  2883. /// return next CSV string as unsigned integer from P, 0 if no more
  2884. // - this version expects P^ to point to an Unicode char array
  2885. function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar= ','): PtrUInt;
  2886. /// return next CSV string as double from P, 0.0 if no more
  2887. function GetNextItemDouble(var P: PUTF8Char; Sep: AnsiChar= ','): double;
  2888. /// return n-th indexed CSV string in P, starting at Index=0 for first one
  2889. function GetCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar = ','): RawUTF8;
  2890. /// return n-th indexed CSV string in P, starting at Index=0 for first one
  2891. // - this function return the generic string type of the compiler, and
  2892. // therefore can be used with ready to be displayed text (i.e. the VCL)
  2893. function GetCSVItemString(P: PChar; Index: PtrUInt; Sep: Char = ','): string;
  2894. /// return last CSV string in the supplied UTF-8 content
  2895. function GetLastCSVItem(const CSV: RawUTF8; Sep: AnsiChar=','): RawUTF8;
  2896. /// return the index of a Value in a CSV string
  2897. // - start at Index=0 for first one
  2898. // - return -1 if specified Value was not found in CSV items
  2899. function FindCSVIndex(CSV: PUTF8Char; const Value: RawUTF8; Sep: AnsiChar = ',';
  2900. CaseSensitive: boolean=true; TrimValue: boolean=false): integer;
  2901. /// add the strings in the specified CSV text into a dynamic array of UTF-8 strings
  2902. procedure CSVToRawUTF8DynArray(CSV: PUTF8Char; var Result: TRawUTF8DynArray;
  2903. Sep: AnsiChar=','); overload;
  2904. /// add the strings in the specified CSV text into a dynamic array of UTF-8 strings
  2905. procedure CSVToRawUTF8DynArray(const CSV,Sep,SepEnd: RawUTF8; var Result: TRawUTF8DynArray); overload;
  2906. /// return the corresponding CSV text from a dynamic array of UTF-8 strings
  2907. function RawUTF8ArrayToCSV(const Values: array of RawUTF8; const Sep: RawUTF8= ','): RawUTF8;
  2908. /// return the corresponding CSV quoted text from a dynamic array of UTF-8 strings
  2909. // - apply QuoteStr() function to each Values[] item
  2910. function RawUTF8ArrayToQuotedCSV(const Values: array of RawUTF8; const Sep: RawUTF8=',';
  2911. Quote: AnsiChar=''''): RawUTF8;
  2912. /// append some prefix to all CSV values
  2913. // ! AddPrefixToCSV('One,Two,Three','Pre')='PreOne,PreTwo,PreThree'
  2914. function AddPrefixToCSV(CSV: PUTF8Char; const Prefix: RawUTF8;
  2915. Sep: AnsiChar = ','): RawUTF8;
  2916. /// quick helper to initialize a dynamic array of RawUTF8 from some constants
  2917. // - can be used e.g. as:
  2918. // ! MyArray := TRawUTF8DynArrayFrom(['a','b','c']);
  2919. function TRawUTF8DynArrayFrom(const Values: array of RawUTF8): TRawUTF8DynArray;
  2920. /// append one or several values to a local "array of const" variable
  2921. procedure AddArrayOfConst(var Dest: TTVarRecDynArray; const Values: array of const);
  2922. /// return the index of Value in Values[], -1 if not found
  2923. function FindRawUTF8(const Values: TRawUTF8DynArray; const Value: RawUTF8;
  2924. CaseSensitive: boolean=true): integer; overload;
  2925. /// return the index of Value in Values[], -1 if not found
  2926. // - can optionally call IdemPropNameU() for property matching
  2927. function FindRawUTF8(const Values: TRawUTF8DynArray; ValuesCount: integer;
  2928. const Value: RawUTF8; SearchPropName: boolean): integer; overload;
  2929. /// return the index of Value in Values[], -1 if not found
  2930. function FindRawUTF8(const Values: array of RawUTF8; const Value: RawUTF8;
  2931. CaseSensitive: boolean=true): integer; overload;
  2932. /// return the index of Value in Values[], -1 if not found
  2933. // - here name search would use fast IdemPropNameU() function
  2934. function FindPropName(const Names: array of RawUTF8; const Name: RawUTF8): integer;
  2935. /// true if Value was added successfully in Values[]
  2936. function AddRawUTF8(var Values: TRawUTF8DynArray; const Value: RawUTF8;
  2937. NoDuplicates: boolean=false; CaseSensitive: boolean=true): boolean; overload;
  2938. /// add the Value to Values[], with an external count variable, for performance
  2939. procedure AddRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  2940. const Value: RawUTF8); overload;
  2941. type
  2942. /// simple stack-allocated type for handling a type names list
  2943. TPropNameList = {$ifndef UNICODE}object{$else}record{$endif}
  2944. Values: TRawUTF8DynArray;
  2945. Count: Integer;
  2946. /// initialize the list
  2947. // - set Count := 0
  2948. procedure Init;
  2949. /// search for a Value within Values[0..Count-1] using IdemPropNameU()
  2950. function FindPropName(const Value: RawUTF8): Integer;
  2951. /// if Value is in Values[0..Count-1] using IdemPropNameU() returns FALSE
  2952. // - otherwise, returns TRUE and add Value to Values[]
  2953. function AddPropName(const Value: RawUTF8): Boolean;
  2954. end;
  2955. /// true if both TRawUTF8DynArray are the same
  2956. // - comparison is case-sensitive
  2957. function RawUTF8DynArrayEquals(const A,B: TRawUTF8DynArray): boolean;
  2958. /// convert the string dynamic array into a dynamic array of UTF-8 strings
  2959. procedure StringDynArrayToRawUTF8DynArray(const Source: TStringDynArray;
  2960. var Result: TRawUTF8DynArray);
  2961. /// convert the string list into a dynamic array of UTF-8 strings
  2962. procedure StringListToRawUTF8DynArray(Source: TStringList; var Result: TRawUTF8DynArray);
  2963. /// find a Name= Value in a [Section] of a INI RawUTF8 Content
  2964. // - this function scans the Content memory buffer, and is
  2965. // therefore very fast (no temporary TMemIniFile is created)
  2966. // - if Section equals '', find the Name= value before any [Section]
  2967. function FindIniEntry(const Content, Section,Name: RawUTF8): RawUTF8;
  2968. /// find a Name= Value in a [Section] of a INI WinAnsi Content
  2969. // - same as FindIniEntry(), but the value is converted from WinAnsi into UTF-8
  2970. function FindWinAnsiIniEntry(const Content, Section,Name: RawUTF8): RawUTF8;
  2971. /// find a Name= numeric Value in a [Section] of a INI RawUTF8 Content and
  2972. // return it as an integer, or 0 if not found
  2973. // - this function scans the Content memory buffer, and is
  2974. // therefore very fast (no temporary TMemIniFile is created)
  2975. // - if Section equals '', find the Name= value before any [Section]
  2976. function FindIniEntryInteger(const Content, Section,Name: RawUTF8): integer;
  2977. {$ifdef HASINLINE}inline;{$endif}
  2978. /// find a Name= Value in a [Section] of a .INI file
  2979. // - if Section equals '', find the Name= value before any [Section]
  2980. // - use internaly fast FindIniEntry() function above
  2981. function FindIniEntryFile(const FileName: TFileName; const Section,Name: RawUTF8): RawUTF8;
  2982. /// update a Name= Value in a [Section] of a INI RawUTF8 Content
  2983. // - this function scans and update the Content memory buffer, and is
  2984. // therefore very fast (no temporary TMemIniFile is created)
  2985. // - if Section equals '', update the Name= value before any [Section]
  2986. procedure UpdateIniEntry(var Content: RawUTF8; const Section,Name,Value: RawUTF8);
  2987. /// update a Name= Value in a [Section] of a .INI file
  2988. // - if Section equals '', update the Name= value before any [Section]
  2989. // - use internaly fast UpdateIniEntry() function above
  2990. procedure UpdateIniEntryFile(const FileName: TFileName; const Section,Name,Value: RawUTF8);
  2991. /// find the position of the [SEARCH] section in source
  2992. // - return true if [SEARCH] was found, and store pointer to the line after it in source
  2993. function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean;
  2994. /// find the position of the [SEARCH] section in source
  2995. // - return true if [SEARCH] was found, and store pointer to the line after it in source
  2996. // - this version expects source^ to point to an Unicode char array
  2997. function FindSectionFirstLineW(var source: PWideChar; search: PUTF8Char): boolean;
  2998. /// retrieve the whole content of a section as a string
  2999. // - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
  3000. function GetSectionContent(SectionFirstLine: PUTF8Char): RawUTF8; overload;
  3001. /// retrieve the whole content of a section as a string
  3002. // - use SectionFirstLine() then previous GetSectionContent()
  3003. function GetSectionContent(const Content, SectionName: RawUTF8): RawUTF8; overload;
  3004. /// delete a whole [Section]
  3005. // - if EraseSectionHeader is TRUE (default), then the [Section] line is also
  3006. // deleted together with its content lines
  3007. // - return TRUE if something was changed in Content
  3008. // - return FALSE if [Section] doesn't exist or is already void
  3009. function DeleteSection(var Content: RawUTF8; const SectionName: RawUTF8;
  3010. EraseSectionHeader: boolean=true): boolean; overload;
  3011. /// delete a whole [Section]
  3012. // - if EraseSectionHeader is TRUE (default), then the [Section] line is also
  3013. // deleted together with its content lines
  3014. // - return TRUE if something was changed in Content
  3015. // - return FALSE if [Section] doesn't exist or is already void
  3016. // - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
  3017. function DeleteSection(SectionFirstLine: PUTF8Char; var Content: RawUTF8;
  3018. EraseSectionHeader: boolean=true): boolean; overload;
  3019. /// replace a whole [Section] content by a new content
  3020. // - create a new [Section] if none was existing
  3021. procedure ReplaceSection(var Content: RawUTF8; const SectionName,
  3022. NewSectionContent: RawUTF8); overload;
  3023. /// replace a whole [Section] content by a new content
  3024. // - create a new [Section] if none was existing
  3025. // - SectionFirstLine may have been obtained by FindSectionFirstLine() function above
  3026. procedure ReplaceSection(SectionFirstLine: PUTF8Char;
  3027. var Content: RawUTF8; const NewSectionContent: RawUTF8); overload;
  3028. /// return TRUE if Value of UpperName does exist in P, till end of current section
  3029. // - expect UpperName as 'NAME='
  3030. function ExistsIniName(P: PUTF8Char; UpperName: PAnsiChar): boolean;
  3031. /// find the Value of UpperName in P, till end of current section
  3032. // - expect UpperName as 'NAME='
  3033. function FindIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
  3034. /// return TRUE if one of the Value of UpperName exists in P, till end of
  3035. // current section
  3036. // - expect UpperName e.g. as 'CONTENT-TYPE: '
  3037. // - expect UpperValues to be any upper value with left side matching, e.g. as
  3038. // used by IsHTMLContentTypeTextual() function:
  3039. // ! result := ExistsIniNameValue(htmlHeaders,HEADER_CONTENT_TYPE_UPPER,
  3040. // ! ['TEXT/','APPLICATION/JSON','APPLICATION/XML']);
  3041. // - warning: this function calls IdemPCharArray(), so expects UpperValues[]
  3042. /// items to have AT LEAST TWO CHARS (it will use fast initial 2 bytes compare)
  3043. function ExistsIniNameValue(P: PUTF8Char; const UpperName: RawUTF8;
  3044. const UpperValues: array of PAnsiChar): boolean;
  3045. /// find the integer Value of UpperName in P, till end of current section
  3046. // - expect UpperName as 'NAME='
  3047. // - return 0 if no NAME= entry was found
  3048. function FindIniNameValueInteger(P: PUTF8Char; UpperName: PAnsiChar): integer;
  3049. {$ifdef HASINLINE}inline;{$endif}
  3050. /// read a File content into a String
  3051. // - content can be binary or text
  3052. // - returns '' if file was not found or any read error occured
  3053. // - uses RawByteString for byte storage, whatever the codepage is
  3054. function StringFromFile(const FileName: TFileName): RawByteString;
  3055. /// create a File from a string content
  3056. // - uses RawByteString for byte storage, whatever the codepage is
  3057. function FileFromString(const Content: RawByteString; const FileName: TFileName;
  3058. FlushOnDisk: boolean=false): boolean;
  3059. /// get text File contents (even Unicode or UTF8) and convert it into a
  3060. // Charset-compatible AnsiString (for Delphi 7) or an UnicodeString (for Delphi
  3061. // 2009 and up) according to any BOM marker at the beginning of the file
  3062. // - before Delphi 2009, the current string code page is used (i.e. CurrentAnsiConvert)
  3063. function AnyTextFileToString(const FileName: TFileName; ForceUTF8: boolean=false): string;
  3064. /// get text file contents (even Unicode or UTF8) and convert it into an
  3065. // Unicode string according to any BOM marker at the beginning of the file
  3066. // - any file without any BOM marker will be interpreted as plain ASCII: in this
  3067. // case, the current string code page is used (i.e. CurrentAnsiConvert class)
  3068. function AnyTextFileToSynUnicode(const FileName: TFileName; ForceUTF8: boolean=false): SynUnicode;
  3069. /// get text file contents (even Unicode or UTF8) and convert it into an
  3070. // UTF-8 string according to any BOM marker at the beginning of the file
  3071. // - if AssumeUTF8IfNoBOM is FALSE, the current string code page is used (i.e.
  3072. // CurrentAnsiConvert class) for conversion from ANSI into UTF-8
  3073. // - if AssumeUTF8IfNoBOM is TRUE, any file without any BOM marker will be
  3074. // interpreted as UTF-8
  3075. function AnyTextFileToRawUTF8(const FileName: TFileName; AssumeUTF8IfNoBOM: boolean=false): RawUTF8;
  3076. /// read a TStream content into a String
  3077. // - it will read binary or text content from the current position until the
  3078. // end (using TStream.Size)
  3079. // - uses RawByteString for byte storage, whatever the codepage is
  3080. function StreamToRawByteString(aStream: TStream): RawByteString;
  3081. /// create a TStream from a string content
  3082. // - uses RawByteString for byte storage, whatever the codepage is
  3083. // - in fact, the returned TStream is a TRawByteString instance, since this
  3084. // function is just a wrapper around:
  3085. // ! result := TRawByteStringStream.Create(aString);
  3086. function RawByteStringToStream(const aString: RawByteString): TStream;
  3087. {$ifdef HASINLINE}inline;{$endif}
  3088. /// read an UTF-8 text from a TStream
  3089. // - format is Length(Integer):Text, i.e. the one used by WriteStringToStream
  3090. // - will return '' if there is no such text in the stream
  3091. // - you can set a MaxAllowedSize value, if you know how long the size should be
  3092. // - it will read from the current position in S: so if you just write into S,
  3093. // it could be a good idea to rewind it before call, e.g.:
  3094. // ! WriteStringToStream(Stream,aUTF8Text);
  3095. // ! Stream.Seek(0,soBeginning);
  3096. // ! str := ReadStringFromStream(Stream);
  3097. function ReadStringFromStream(S: TStream; MaxAllowedSize: integer=255): RawUTF8;
  3098. /// write an UTF-8 text into a TStream
  3099. // - format is Length(Integer):Text, i.e. the one used by ReadStringFromStream
  3100. function WriteStringToStream(S: TStream; const Text: RawUTF8): boolean;
  3101. /// get a file date and time, from its name
  3102. // - returns 0 if file doesn't exist
  3103. // - under Windows, will use GetFileAttributesEx fast API
  3104. function FileAgeToDateTime(const FileName: TFileName): TDateTime;
  3105. /// get a file size, from its name
  3106. // - returns 0 if file doesn't exist
  3107. // - under Windows, will use GetFileAttributesEx fast API
  3108. function FileSize(const FileName: TFileName): Int64;
  3109. /// get a file date and time, from a FindFirst/FindNext search
  3110. // - the returned timestamp is in local time, not UTC
  3111. // - this method would use the F.TimeStamp field available since Delphi XE2
  3112. function SearchRecToDateTime(const F: TSearchRec): TDateTime;
  3113. {$ifdef HASINLINE}inline;{$endif}
  3114. /// delete the content of a specified directory
  3115. // - only one level of file is deleted within the folder: no recursive deletion
  3116. // is processed by this function
  3117. // - if DeleteOnlyFilesNotDirectory is TRUE, it won't remove the folder itself,
  3118. // but just the files found in it
  3119. function DirectoryDelete(const Directory: TFileName; const Mask: TFileName='*.*';
  3120. DeleteOnlyFilesNotDirectory: Boolean=false; DeletedCount: PInteger=nil): Boolean;
  3121. /// delete the files older than a given age in a specified directory
  3122. // - for instance, to delete all files older than one day:
  3123. // ! DirectoryDeleteOlderFiles(FolderName, 1);
  3124. // - only one level of file is deleted within the folder: no recursive deletion
  3125. // is processed by this function, unless Recursive is TRUE
  3126. function DirectoryDeleteOlderFiles(const Directory: TFileName; TimePeriod: TDateTime;
  3127. const Mask: TFileName='*.*'; Recursive: Boolean=false): Boolean;
  3128. /// creates a directory if not already existing
  3129. // - returns the full expanded directory name, including trailing backslash
  3130. function EnsureDirectoryExists(const Directory: TFileName;
  3131. RaiseExceptionOnCreationFailure: boolean=false): TFileName;
  3132. type
  3133. {$A-}
  3134. /// file found result item, as returned by FindFiles()
  3135. TFindFiles = {$ifndef UNICODE}object{$else}record{$endif}
  3136. /// the matching file name, including its folder name
  3137. Name: TFileName;
  3138. /// the matching file attributes
  3139. Attr: Integer;
  3140. /// the matching file size
  3141. Size: Int64;
  3142. /// the matching file date/time
  3143. TimeStamp: TDateTime;
  3144. /// fill the item properties from a FindFirst/FindNext's TSearchRec
  3145. procedure FromSearchRec(const Directory: TFileName; const F: TSearchRec);
  3146. end;
  3147. {$A+}
  3148. /// result list, as returned by FindFiles()
  3149. TFindFilesDynArray = array of TFindFiles;
  3150. /// search for matching file names
  3151. // - just a wrapper around FindFirst/FindNext
  3152. // - you may specify several masks in Mask, e.g. as '*.htm;*.html'
  3153. function FindFiles(const Directory,Mask: TFileName;
  3154. const IgnoreFileName: TFileName=''; SortByName: boolean=false;
  3155. IncludesDir: boolean=true): TFindFilesDynArray;
  3156. {$ifdef DELPHI5OROLDER}
  3157. /// DirectoryExists returns a boolean value that indicates whether the
  3158. // specified directory exists (and is actually a directory)
  3159. function DirectoryExists(const Directory: string): Boolean;
  3160. /// retrieve the corresponding environment variable value
  3161. function GetEnvironmentVariable(const Name: string): string;
  3162. /// retrieve the full path name of the given execution module (e.g. library)
  3163. function GetModuleName(Module: HMODULE): TFileName;
  3164. /// try to encode a time
  3165. function TryEncodeTime(Hour, Min, Sec, MSec: Word; var Time: TDateTime): Boolean;
  3166. /// alias to ExcludeTrailingBackslash() function
  3167. function ExcludeTrailingPathDelimiter(const FileName: TFileName): TFileName;
  3168. /// alias to IncludeTrailingBackslash() function
  3169. function IncludeTrailingPathDelimiter(const FileName: TFileName): TFileName;
  3170. type
  3171. EOSError = class(Exception)
  3172. public
  3173. ErrorCode: DWORD;
  3174. end;
  3175. /// raise an EOSError exception corresponding to the last error reported by Windows
  3176. procedure RaiseLastOSError;
  3177. {$endif DELPHI5OROLDER}
  3178. /// extract file name, without its extension
  3179. function GetFileNameWithoutExt(const FileName: TFileName): TFileName;
  3180. /// extract a file extension from a file name, then compare with a comma
  3181. // separated list of extensions
  3182. // - e.g. GetFileNameExtIndex('test.log','exe,log,map')=1
  3183. // - will return -1 if no file extension match
  3184. // - will return any matching extension, starting count at 0
  3185. // - extension match is case-insensitive
  3186. function GetFileNameExtIndex(const FileName, CSVExt: TFileName): integer;
  3187. /// copy one file to another, similar to the Windows API
  3188. function CopyFile(const Source, Target: TFileName; FailIfExists: boolean): boolean;
  3189. /// copy the date of one file to another
  3190. function FileSetDateFrom(const Dest: TFileName; SourceHandle: integer): boolean;
  3191. /// retrieve a property value in a text-encoded class
  3192. // - follows the Delphi serialized text object format, not standard .ini
  3193. // - if the property is a string, the simple quotes ' are trimed
  3194. function FindObjectEntry(const Content, Name: RawUTF8): RawUTF8;
  3195. /// retrieve a filename property value in a text-encoded class
  3196. // - follows the Delphi serialized text object format, not standard .ini
  3197. // - if the property is a string, the simple quotes ' are trimed
  3198. // - any file path and any extension are trimmed
  3199. function FindObjectEntryWithoutExt(const Content, Name: RawUTF8): RawUTF8;
  3200. type
  3201. /// available pronunciations for our fast Soundex implementation
  3202. TSynSoundExPronunciation =
  3203. (sndxEnglish, sndxFrench, sndxSpanish, sndxNone);
  3204. TSoundExValues = array[0..ord('Z')-ord('B')] of byte;
  3205. PSoundExValues = ^TSoundExValues;
  3206. PSynSoundEx = ^TSynSoundEx;
  3207. /// fast search of a text value, using the Soundex approximation mechanism
  3208. // - Soundex is a phonetic algorithm for indexing names by sound,
  3209. // as pronounced in a given language. The goal is for homophones to be
  3210. // encoded to the same representation so that they can be matched despite
  3211. // minor differences in spelling
  3212. // - this implementation is very fast and can be used e.g. to parse and search
  3213. // in a huge text buffer
  3214. // - This version also handles french and spanish pronunciations on request,
  3215. // which differs from default Soundex, i.e. English
  3216. TSynSoundEx = {$ifndef UNICODE}object{$else}record{$endif}
  3217. private
  3218. Search, FirstChar: cardinal;
  3219. fValues: PSoundExValues;
  3220. public
  3221. /// prepare for a Soundex search
  3222. // - you can specify another language pronunciation than default english
  3223. function Prepare(UpperValue: PAnsiChar;
  3224. Lang: TSynSoundExPronunciation=sndxEnglish): boolean;
  3225. /// return true if prepared value is contained in a text buffer
  3226. // (UTF-8 encoded), by using the SoundEx comparison algorithm
  3227. // - search prepared value at every word beginning in U^
  3228. function UTF8(U: PUTF8Char): boolean;
  3229. /// return true if prepared value is contained in a ANSI text buffer
  3230. // by using the SoundEx comparison algorithm
  3231. // - search prepared value at every word beginning in A^
  3232. function Ansi(A: PAnsiChar): boolean;
  3233. end;
  3234. /// Retrieve the Soundex value of a text word, from Ansi buffer
  3235. // - Return the soundex value as an easy to use cardinal value, 0 if the
  3236. // incoming string contains no valid word
  3237. // - if next is defined, its value is set to the end of the encoded word
  3238. // (so that you can call again this function to encode a full sentence)
  3239. function SoundExAnsi(A: PAnsiChar; next: PPAnsiChar=nil;
  3240. Lang: TSynSoundExPronunciation=sndxEnglish): cardinal;
  3241. /// Retrieve the Soundex value of a text word, from UTF-8 buffer
  3242. // - Return the soundex value as an easy to use cardinal value, 0 if the
  3243. // incoming string contains no valid word
  3244. // - if next is defined, its value is set to the end of the encoded word
  3245. // (so that you can call again this function to encode a full sentence)
  3246. // - very fast: all UTF-8 decoding is handled on the fly
  3247. function SoundExUTF8(U: PUTF8Char; next: PPUTF8Char=nil;
  3248. Lang: TSynSoundExPronunciation=sndxEnglish): cardinal;
  3249. const
  3250. /// number of bits to use for each interresting soundex char
  3251. // - default is to use 8 bits, i.e. 4 soundex chars, which is the
  3252. // standard approach
  3253. // - for a more detailled soundex, use 4 bits resolution, which will
  3254. // compute up to 7 soundex chars in a cardinal (that's our choice)
  3255. SOUNDEX_BITS = 4;
  3256. /// return true if UpperValue (Ansi) is contained in A^ (Ansi)
  3257. // - find UpperValue starting at word beginning, not inside words
  3258. function FindAnsi(A, UpperValue: PAnsiChar): boolean;
  3259. /// return true if UpperValue (Ansi) is contained in U^ (UTF-8 encoded)
  3260. // - find UpperValue starting at word beginning, not inside words
  3261. // - UTF-8 decoding is done on the fly (no temporary decoding buffer is used)
  3262. function FindUTF8(U: PUTF8Char; UpperValue: PAnsiChar): boolean;
  3263. /// return true if Upper (Unicode encoded) is contained in U^ (UTF-8 encoded)
  3264. // - will use the slow but accurate Operating System API to perform the
  3265. // comparison at Unicode-level
  3266. function FindUnicode(PW: PWideChar; Upper: PWideChar; UpperLen: integer): boolean;
  3267. /// trim first lowercase chars ('otDone' will return 'Done' e.g.)
  3268. // - return a PUTF8Char to avoid any memory allocation
  3269. function TrimLeftLowerCase(const V: RawUTF8): PUTF8Char;
  3270. /// trim first lowercase chars ('otDone' will return 'Done' e.g.)
  3271. // - return an RawUTF8 string: enumeration names are pure 7bit ANSI with Delphi 7
  3272. // to 2007, and UTF-8 encoded with Delphi 2009+
  3273. function TrimLeftLowerCaseShort(V: PShortString): RawUTF8;
  3274. /// trim first lowercase chars ('otDone' will return 'Done' e.g.)
  3275. // - return a shortstring: enumeration names are pure 7bit ANSI with Delphi 7
  3276. // to 2007, and UTF-8 encoded with Delphi 2009+
  3277. function TrimLeftLowerCaseToShort(V: PShortString): ShortString;
  3278. /// convert a CamelCase string into a space separated one
  3279. // - 'OnLine' will return 'On line' e.g., and 'OnMyLINE' will return 'On my LINE'
  3280. // - will handle capital words at the beginning, middle or end of the text, e.g.
  3281. // 'KLMFlightNumber' will return 'KLM flight number' and 'GoodBBCProgram' will
  3282. // return 'Good BBC program'
  3283. // - will handle a number at the beginning, middle or end of the text, e.g.
  3284. // 'Email12' will return 'Email 12'
  3285. // - '_' char is transformed into ' - '
  3286. // - '__' chars are transformed into ': '
  3287. // - return an RawUTF8 string: enumeration names are pure 7bit ANSI with Delphi 7
  3288. // to 2007, and UTF-8 encoded with Delphi 2009+
  3289. function UnCamelCase(const S: RawUTF8): RawUTF8; overload;
  3290. /// convert a CamelCase string into a space separated one
  3291. // - 'OnLine' will return 'On line' e.g., and 'OnMyLINE' will return 'On my LINE'
  3292. // - will handle capital words at the beginning, middle or end of the text, e.g.
  3293. // 'KLMFlightNumber' will return 'KLM flight number' and 'GoodBBCProgram' will
  3294. // return 'Good BBC program'
  3295. // - will handle a number at the beginning, middle or end of the text, e.g.
  3296. // 'Email12' will return 'Email 12'
  3297. // - return the char count written into D^
  3298. // - D^ and P^ are expected to be UTF-8 encoded: enumeration and property names
  3299. // are pure 7bit ANSI with Delphi 7 to 2007, and UTF-8 encoded with Delphi 2009+
  3300. // - '_' char is transformed into ' - '
  3301. // - '__' chars are transformed into ': '
  3302. function UnCamelCase(D, P: PUTF8Char): integer; overload;
  3303. /// UnCamelCase and translate a char buffer
  3304. // - P is expected to be #0 ended
  3305. // - return "string" type, i.e. UnicodeString for Delphi 2009+
  3306. procedure GetCaptionFromPCharLen(P: PUTF8Char; out result: string);
  3307. /// will get a class name as UTF-8
  3308. // - will trim 'T', 'TSyn', 'TSQL' or 'TSQLRecord' left side of the class name
  3309. // - will encode the class name as UTF-8 (for Unicode Delphi versions)
  3310. // - is used e.g. to extract the SQL table name for a TSQLRecord class
  3311. function GetDisplayNameFromClass(C: TClass): RawUTF8;
  3312. /// UnCamelCase and translate the class name, triming any left 'T', 'TSyn',
  3313. // 'TSQL' or 'TSQLRecord'
  3314. // - return generic VCL string type, i.e. UnicodeString for Delphi 2009+
  3315. function GetCaptionFromClass(C: TClass): string;
  3316. /// UnCamelCase and translate the enumeration item
  3317. function GetCaptionFromEnum(aTypeInfo: pointer; aIndex: integer): string;
  3318. {$ifdef LINUX}
  3319. const
  3320. ANSI_CHARSET = 0;
  3321. DEFAULT_CHARSET = 1;
  3322. SYMBOL_CHARSET = 2;
  3323. SHIFTJIS_CHARSET = $80;
  3324. HANGEUL_CHARSET = 129;
  3325. GB2312_CHARSET = 134;
  3326. CHINESEBIG5_CHARSET = 136;
  3327. OEM_CHARSET = 255;
  3328. JOHAB_CHARSET = 130;
  3329. HEBREW_CHARSET = 177;
  3330. ARABIC_CHARSET = 178;
  3331. GREEK_CHARSET = 161;
  3332. TURKISH_CHARSET = 162;
  3333. VIETNAMESE_CHARSET = 163;
  3334. THAI_CHARSET = 222;
  3335. EASTEUROPE_CHARSET = 238;
  3336. RUSSIAN_CHARSET = 204;
  3337. BALTIC_CHARSET = 186;
  3338. {$else}
  3339. {$ifdef FPC}
  3340. const
  3341. VIETNAMESE_CHARSET = 163;
  3342. {$endif}
  3343. {$endif}
  3344. /// convert a char set to a code page
  3345. function CharSetToCodePage(CharSet: integer): cardinal;
  3346. /// convert a code page to a char set
  3347. function CodePageToCharSet(CodePage: Cardinal): Integer;
  3348. /// retrieve the MIME content type from a supplied binary buffer
  3349. // - return the MIME type, ready to be appended to a 'Content-Type: ' HTTP header
  3350. // - returns DefaultContentType if the binary buffer has an unknown layout
  3351. function GetMimeContentTypeFromBuffer(Content: Pointer; Len: integer;
  3352. const DefaultContentType: RawUTF8): RawUTF8;
  3353. /// retrieve the MIME content type from a supplied binary buffer or file name
  3354. // - return the MIME type, ready to be appended to a 'Content-Type: ' HTTP header
  3355. // - default is 'application/octet-stream' (BINARY_CONTENT_TYPE) or
  3356. // 'application/extension' if FileName was specified
  3357. // - see @http://en.wikipedia.org/wiki/Internet_media_type for most common values
  3358. function GetMimeContentType(Content: Pointer; Len: integer;
  3359. const FileName: TFileName=''): RawUTF8;
  3360. /// retrieve the HTTP header for MIME content type from a supplied binary buffer
  3361. // - just append HEADER_CONTENT_TYPE and GetMimeContentType() result
  3362. // - can be used as such:
  3363. // ! Call.OutHead := GetMimeContentTypeHeader(Call.OutBody,aFileName);
  3364. function GetMimeContentTypeHeader(const Content: RawByteString;
  3365. const FileName: TFileName=''): RawUTF8;
  3366. /// retrieve if some content is compressed, from a supplied binary buffer
  3367. // - returns TRUE, if the header in binary buffer "may" be compressed (this method
  3368. // can trigger false positives), e.g. begin with zip/gz/gif/wma/png/jpeg markers
  3369. function IsContentCompressed(Content: Pointer; Len: integer): boolean;
  3370. /// returns TRUE if the supplied HTML Headers contains 'Content-Type: text/...',
  3371. // 'Content-Type: application/json' or 'Content-Type: application/xml'
  3372. function IsHTMLContentTypeTextual(Headers: PUTF8Char): Boolean;
  3373. type
  3374. /// used by MultiPartFormDataDecode() to return one item of its data
  3375. TMultiPart = record
  3376. Name: RawUTF8;
  3377. FileName: RawUTF8;
  3378. ContentType: RawUTF8;
  3379. Encoding: RawUTF8;
  3380. Content: RawByteString;
  3381. end;
  3382. /// used by MultiPartFormDataDecode() to return all its data items
  3383. TMultiPartDynArray = array of TMultiPart;
  3384. /// decode multipart/form-data POST request content
  3385. // - following RFC1867
  3386. function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
  3387. var MultiPart: TMultiPartDynArray): boolean;
  3388. /// encode multipart fields and files
  3389. // - only one of them can be used because MultiPartFormDataDecode must implement
  3390. // both decodings
  3391. // - MultiPart: parts to build the multipart content from, which may be created
  3392. // using MultiPartFormDataAddFile/MultiPartFormDataAddField
  3393. // - MultiPartContentType: variable returning
  3394. // $ Content-Type: multipart/form-data; boundary=xxx
  3395. // where xxx is the first generated boundary
  3396. // - MultiPartContent: generated multipart content
  3397. function MultiPartFormDataEncode(const MultiPart: TMultiPartDynArray;
  3398. var MultiPartContentType, MultiPartContent: RawUTF8): boolean;
  3399. /// encode a file in a multipart array
  3400. // - FileName: file to encode
  3401. // - Multipart: where the part is added
  3402. // - Name: name of the part, is empty the name 'File###' is generated
  3403. function MultiPartFormDataAddFile(const FileName: TFileName;
  3404. var MultiPart: TMultiPartDynArray; const Name: RawUTF8 = ''): boolean;
  3405. /// encode a field in a multipart array
  3406. // - FieldName: field name of the part
  3407. // - FieldValue: value of the field
  3408. // - Multipart: where the part is added
  3409. function MultiPartFormDataAddField(const FieldName, FieldValue: RawUTF8;
  3410. var MultiPart: TMultiPartDynArray): boolean;
  3411. /// retrieve the index where to insert a PUTF8Char in a sorted PUTF8Char array
  3412. // - R is the last index of available entries in P^ (i.e. Count-1)
  3413. // - string comparison is case-sensitive (so will work with any PAnsiChar)
  3414. // - returns -1 if the specified Value was found (i.e. adding will duplicate a value)
  3415. function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt; overload;
  3416. /// retrieve the index where to insert a PUTF8Char in a sorted PUTF8Char array
  3417. // - this overloaded function accept a custom comparison function for sorting
  3418. // - R is the last index of available entries in P^ (i.e. Count-1)
  3419. // - string comparison is case-sensitive (so will work with any PAnsiChar)
  3420. // - returns -1 if the specified Value was found (i.e. adding will duplicate a value)
  3421. function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char;
  3422. Compare: TUTF8Compare): PtrInt; overload;
  3423. /// retrieve the index where is located a PUTF8Char in a sorted PUTF8Char array
  3424. // - R is the last index of available entries in P^ (i.e. Count-1)
  3425. // - string comparison is case-sensitive (so will work with any PAnsiChar)
  3426. // - returns -1 if the specified Value was not found
  3427. function FastFindPUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt; overload;
  3428. /// retrieve the index where is located a PUTF8Char in a sorted PUTF8Char array
  3429. // - R is the last index of available entries in P^ (i.e. Count-1)
  3430. // - string comparison is case-sensitive (so will work with any PAnsiChar)
  3431. // - returns -1 if the specified Value was not found
  3432. function FastFindPUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char;
  3433. Compare: TUTF8Compare): PtrInt; overload;
  3434. /// retrieve the index of a PUTF8Char in a PUTF8Char array via a sort indexed
  3435. function FastFindIndexedPUTF8Char(P: PPUTF8CharArray; R: PtrInt;
  3436. var SortedIndexes: TCardinalDynArray; Value: PUTF8Char;
  3437. ItemComp: TUTF8Compare): PtrInt;
  3438. /// add a RawUTF8 value in an alphaticaly sorted dynamic array of RawUTF8
  3439. // - returns the index where the Value was added successfully in Values[]
  3440. // - returns -1 if the specified Value was alredy present in Values[]
  3441. // (we must avoid any duplicate for binary search)
  3442. // - if CoValues is set, its content will be moved to allow inserting a new
  3443. // value at CoValues[result] position - a typical usage of CoValues is to store
  3444. // the corresponding ID to each RawUTF8 item
  3445. // - if FastLocatePUTF8CharSorted() has been already called, this index can
  3446. // be set to optional ForceIndex parameter
  3447. // - by default, exact (case-sensitive) match is used; you can specify a custom
  3448. // compare function if needed in Compare optional parameter
  3449. function AddSortedRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  3450. const Value: RawUTF8; CoValues: PIntegerDynArray=nil; ForcedIndex: PtrInt=-1;
  3451. Compare: TUTF8Compare=nil): PtrInt;
  3452. /// delete a RawUTF8 item in a dynamic array of RawUTF8
  3453. // - if CoValues is set, the integer item at the same index is also deleted
  3454. function DeleteRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  3455. Index: integer; CoValues: PIntegerDynArray=nil): boolean;
  3456. /// sort a dynamic array of RawUTF8 items
  3457. // - if CoValues is set, the integer items are also synchronized
  3458. // - by default, exact (case-sensitive) match is used; you can specify a custom
  3459. // compare function if needed in Compare optional parameter
  3460. procedure QuickSortRawUTF8(var Values: TRawUTF8DynArray; ValuesCount: integer;
  3461. CoValues: PIntegerDynArray=nil; Compare: TUTF8Compare=nil);
  3462. /// sort a dynamic array of PUTF8Char items, via an external array of indexes
  3463. // - you can use FastFindIndexedPUTF8Char() for fast binary search
  3464. procedure QuickSortIndexedPUTF8Char(Values: PPUtf8CharArray; Count: Integer;
  3465. var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean=false);
  3466. /// fast search of an unsigned integer position in an integer array
  3467. // - Count is the number of cardinal entries in P^
  3468. // - returns P where P^=Value
  3469. // - returns nil if Value was not found
  3470. function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal;
  3471. /// fast search of an integer position in a 64 bit integer array
  3472. // - Count is the number of Int64 entries in P^
  3473. // - returns P where P^=Value
  3474. // - returns nil if Value was not found
  3475. function Int64Scan(P: PInt64Array; Count: PtrInt; const Value: Int64): PInt64;
  3476. /// fast search of an unsigned integer in an integer array
  3477. // - returns true if P^=Value within Count entries
  3478. // - returns false if Value was not found
  3479. function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean;
  3480. /// fast search of an integer value in a 64 bit integer array
  3481. // - returns true if P^=Value within Count entries
  3482. // - returns false if Value was not found
  3483. function Int64ScanExists(P: PInt64Array; Count: PtrInt; const Value: Int64): boolean;
  3484. /// fast search of an unsigned integer position in an integer array
  3485. // - Count is the number of integer entries in P^
  3486. // - return index of P^[index]=Value
  3487. // - return -1 if Value was not found
  3488. function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt;
  3489. /// fast search of a pointer-sized unsigned integer position
  3490. // in an pointer-sized integer array
  3491. // - Count is the number of pointer-sized integer entries in P^
  3492. // - return index of P^[index]=Value
  3493. // - return -1 if Value was not found
  3494. function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrInt;
  3495. /// fast search of a pointer-sized unsigned integer position
  3496. // in an pointer-sized integer array
  3497. // - Count is the number of pointer-sized integer entries in P^
  3498. // - returns true if P^=Value within Count entries
  3499. // - returns false if Value was not found
  3500. function PtrUIntScanExists(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): boolean;
  3501. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  3502. /// fast search of an unsigned Word value position in a Word array
  3503. // - Count is the number of Word entries in P^
  3504. // - return index of P^[index]=Value
  3505. // - return -1 if Value was not found
  3506. function WordScanIndex(P: PWordArray; Count: PtrInt; Value: word): integer;
  3507. {$ifdef HASINLINE}inline;{$endif}
  3508. /// sort an Integer array, low values first
  3509. procedure QuickSortInteger(ID: PIntegerArray; L, R: PtrInt); overload;
  3510. /// sort an Integer array, low values first
  3511. procedure QuickSortInteger(ID,CoValues: PIntegerArray; L, R: PtrInt); overload;
  3512. /// sort an Integer array, low values first
  3513. procedure QuickSortInteger(var ID: TIntegerDynArray); overload;
  3514. /// sort a 64 bit Integer array, low values first
  3515. procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); overload;
  3516. /// sort a 64 bit Integer array, low values first
  3517. procedure QuickSortInt64(ID,CoValues: PInt64Array; L, R: PtrInt); overload;
  3518. /// copy an integer array, then sort it, low values first
  3519. procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
  3520. var Dest: TIntegerDynArray);
  3521. /// copy an integer array, then sort it, low values first
  3522. procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
  3523. var Dest: TInt64DynArray);
  3524. /// fast binary search of an integer value in a sorted integer array
  3525. // - R is the last index of available integer entries in P^ (i.e. Count-1)
  3526. // - return index of P^[result]=Value
  3527. // - return -1 if Value was not found
  3528. function FastFindIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt; overload;
  3529. /// fast binary search of an integer value in a sorted integer array
  3530. // - return index of Values[result]=Value
  3531. // - return -1 if Value was not found
  3532. function FastFindIntegerSorted(const Values: TIntegerDynArray; Value: integer): PtrInt; overload;
  3533. /// fast binary search of a 64 bit integer value in a sorted array
  3534. // - R is the last index of available integer entries in P^ (i.e. Count-1)
  3535. // - return index of P^[result]=Value
  3536. // - return -1 if Value was not found
  3537. function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): PtrInt; overload;
  3538. /// sort a PtrInt array, low values first
  3539. procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt);
  3540. {$ifdef HASINLINE}inline;{$endif}
  3541. /// fast binary search of a PtrInt value in a sorted array
  3542. function FastFindPtrIntSorted(P: PPtrIntArray; R: PtrInt; Value: PtrInt): PtrInt; overload;
  3543. {$ifdef HASINLINE}inline;{$endif}
  3544. /// sort a pointer array, low values first
  3545. procedure QuickSortPointer(P: PPointerArray; L, R: PtrInt);
  3546. {$ifdef HASINLINE}inline;{$endif}
  3547. /// fast binary search of a Pointer value in a sorted array
  3548. function FastFindPointerSorted(P: PPointerArray; R: PtrInt; Value: Pointer): PtrInt; overload;
  3549. {$ifdef HASINLINE}inline;{$endif}
  3550. /// retrieve the index where to insert an integer value in a sorted integer array
  3551. // - R is the last index of available integer entries in P^ (i.e. Count-1)
  3552. // - returns -1 if the specified Value was found (i.e. adding will duplicate a value)
  3553. function FastLocateIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt;
  3554. /// add an integer value in a sorted dynamic array of integers
  3555. // - returns the index where the Value was added successfully in Values[]
  3556. // - returns -1 if the specified Value was already present in Values[]
  3557. // (we must avoid any duplicate for binary search)
  3558. // - if CoValues is set, its content will be moved to allow inserting a new
  3559. // value at CoValues[result] position
  3560. function AddSortedInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  3561. Value: integer; CoValues: PIntegerDynArray=nil): PtrInt; overload;
  3562. /// add an integer value in a sorted dynamic array of integers
  3563. // - overloaded function which do not expect an external Count variable
  3564. function AddSortedInteger(var Values: TIntegerDynArray;
  3565. Value: integer; CoValues: PIntegerDynArray=nil): PtrInt; overload;
  3566. /// insert an integer value at the specified index position of a dynamic array
  3567. // of integers
  3568. // - if Index is invalid, the Value is inserted at the end of the array
  3569. function InsertInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  3570. Value: Integer; Index: PtrInt; CoValues: PIntegerDynArray=nil): PtrInt;
  3571. /// add an integer value at the end of a dynamic array of integers
  3572. // - returns TRUE if Value was added successfully in Values[], in this case
  3573. // length(Values) will be increased
  3574. function AddInteger(var Values: TIntegerDynArray; Value: integer;
  3575. NoDuplicates: boolean=false): boolean; overload;
  3576. /// add an integer value at the end of a dynamic array of integers
  3577. // - this overloaded function will use a separate Count variable (faster)
  3578. // - it won't search for any existing duplicate
  3579. procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  3580. Value: integer); overload;
  3581. {$ifdef HASINLINE}inline;{$endif}
  3582. /// add an integer value at the end of a dynamic array of integers
  3583. // - this overloaded function will use a separate Count variable (faster),
  3584. // and would allow to search for duplicates
  3585. // - returns TRUE if Value was added successfully in Values[], in this case
  3586. // ValuesCount will be increased, but length(Values) would stay fixed most
  3587. // of the time (since it stores the Values[] array capacity)
  3588. function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  3589. Value: integer; NoDuplicates: boolean): boolean; overload;
  3590. /// add a 64-bit integer value at the end of a dynamic array of integers
  3591. procedure AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64); overload;
  3592. {$ifdef HASINLINE}inline;{$endif}
  3593. /// add a 64-bit integer value at the end of a dynamic array of integers
  3594. procedure AddInt64(var Values: TInt64DynArray; Value: Int64); overload;
  3595. {$ifdef HASINLINE}inline;{$endif}
  3596. /// delete any 32-bit integer in Values[]
  3597. procedure DeleteInteger(var Values: TIntegerDynArray; Index: PtrInt); overload;
  3598. /// delete any 32-bit integer in Values[]
  3599. procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: Integer; Index: PtrInt); overload;
  3600. /// delete any 64-bit integer in Values[]
  3601. procedure DeleteInt64(var Values: TInt64DynArray; Index: PtrInt); overload;
  3602. /// find the maximum 32-bit integer in Values[]
  3603. function MaxInteger(const Values: TIntegerDynArray; ValuesCount: integer;
  3604. MaxStart: integer=-1): Integer;
  3605. /// fill already allocated Reversed[] so that Reversed[Values[i]]=i
  3606. procedure Reverse(const Values: TIntegerDynArray; ValuesCount: integer;
  3607. Reversed: PIntegerArray);
  3608. /// fill some values with i,i+1,i+2...i+Count-1
  3609. procedure FillIncreasing(Values: PIntegerArray; StartValue, Count: integer);
  3610. /// copy some Int64 values into an unsigned integer array
  3611. procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: integer);
  3612. /// add the strings in the specified CSV text into a dynamic array of integer
  3613. procedure CSVToIntegerDynArray(CSV: PUTF8Char; var Result: TIntegerDynArray);
  3614. /// add the strings in the specified CSV text into a dynamic array of integer
  3615. procedure CSVToInt64DynArray(CSV: PUTF8Char; var Result: TInt64DynArray);
  3616. /// return the corresponding CSV text from a dynamic array of 32-bit integer
  3617. // - you can set some custom Prefix and Suffix text
  3618. function IntegerDynArrayToCSV(const Values: array of integer; ValuesCount: integer;
  3619. const Prefix: RawUTF8=''; const Suffix: RawUTF8=''): RawUTF8;
  3620. /// return the corresponding CSV text from a dynamic array of 64-bit integers
  3621. // - you can set some custom Prefix and Suffix text
  3622. function Int64DynArrayToCSV(const Values: array of Int64; ValuesCount: integer;
  3623. const Prefix: RawUTF8=''; const Suffix: RawUTF8=''): RawUTF8;
  3624. /// quick helper to initialize a dynamic array of integer from some constants
  3625. // - can be used e.g. as:
  3626. // ! MyArray := TIntegerDynArrayFrom([1,2,3]);
  3627. function TIntegerDynArrayFrom(const Values: array of integer): TIntegerDynArray;
  3628. /// quick helper to initialize a dynamic array of integer from 64-bit integers
  3629. // - would raise a ESynException if any Value[] can not fit into 32-bit, unless
  3630. // raiseExceptionOnOverflow is FALSE and the returned array slot is filled
  3631. // with maxInt/minInt
  3632. function TIntegerDynArrayFrom64(const Values: TInt64DynArray;
  3633. raiseExceptionOnOverflow: boolean=true): TIntegerDynArray;
  3634. /// quick helper to initialize a dynamic array of 64-bit integers from 32-bit values
  3635. function TInt64DynArrayFrom(const Values: TIntegerDynArray): TInt64DynArray;
  3636. type
  3637. /// used to store and retrieve Words in a sorted array
  3638. // - is defined either as an object either as a record, due to a bug
  3639. // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  3640. // if defined as an object on the stack, but will be as a record :(
  3641. TSortedWordArray = {$ifndef UNICODE}object{$else}record{$endif}
  3642. public
  3643. Values: TWordDynArray;
  3644. Count: integer;
  3645. /// add a value into the sorted array
  3646. // - return the index of the new inserted value into the Values[] array
  3647. // - return -(foundindex+1) if this value is already in the Values[] array
  3648. function Add(aValue: Word): PtrInt;
  3649. /// return the index if the supplied value in the Values[] array
  3650. // - return -1 if not found
  3651. function IndexOf(aValue: Word): PtrInt; {$ifdef HASINLINE}inline;{$endif}
  3652. end;
  3653. /// convert a cardinal into a 32-bit variable-length integer buffer
  3654. function ToVarUInt32(Value: PtrUInt; Dest: PByte): PByte;
  3655. /// return the number of bytes necessary to store a 32-bit variable-length integer
  3656. // - i.e. the ToVarUInt32() buffer size
  3657. function ToVarUInt32Length(Value: PtrUInt): PtrUInt;
  3658. {$ifdef HASINLINE}inline;{$endif}
  3659. /// return the number of bytes necessary to store some data with a its
  3660. // 32-bit variable-length integer legnth
  3661. function ToVarUInt32LengthWithData(Value: PtrUInt): PtrUInt;
  3662. {$ifdef HASINLINE}inline;{$endif}
  3663. /// convert an integer into a 32-bit variable-length integer buffer
  3664. // - store negative values as cardinal two-complement, i.e.
  3665. // 0=0,1=1,2=-1,3=2,4=-2...
  3666. function ToVarInt32(Value: PtrInt; Dest: PByte): PByte;
  3667. /// convert a 32-bit variable-length integer buffer into a cardinal
  3668. function FromVarUInt32(var Source: PByte): cardinal;
  3669. {$ifdef HASINLINE}inline;{$endif}
  3670. /// convert a 32-bit variable-length integer buffer into a cardinal
  3671. // - this version must be called if Source^ has already been checked to be > $7f
  3672. // ! result := Source^;
  3673. // ! inc(Source);
  3674. // ! if result>$7f then
  3675. // ! result := (result and $7F) or FromVarUInt32Up128(Source);
  3676. function FromVarUInt32Up128(var Source: PByte): cardinal;
  3677. /// convert a 32-bit variable-length integer buffer into a cardinal
  3678. // - this version must be called if Source^ has already been checked to be > $7f
  3679. function FromVarUInt32High(var Source: PByte): cardinal;
  3680. /// convert a 32-bit variable-length integer buffer into an integer
  3681. // - decode negative values from cardinal two-complement, i.e.
  3682. // 0=0,1=1,2=-1,3=2,4=-2...
  3683. function FromVarInt32(var Source: PByte): integer; {$ifdef HASINLINE}inline;{$endif}
  3684. /// convert a UInt64 into a 64-bit variable-length integer buffer
  3685. function ToVarUInt64(Value: QWord; Dest: PByte): PByte;
  3686. /// convert a 64-bit variable-length integer buffer into a UInt64
  3687. function FromVarUInt64(var Source: PByte): QWord;
  3688. /// convert a Int64 into a 64-bit variable-length integer buffer
  3689. function ToVarInt64(Value: Int64; Dest: PByte): PByte; {$ifdef HASINLINE}inline;{$endif}
  3690. /// convert a 64-bit variable-length integer buffer into a Int64
  3691. function FromVarInt64(var Source: PByte): Int64;
  3692. /// convert a 64-bit variable-length integer buffer into a Int64
  3693. // - this version won't update the Source pointer
  3694. function FromVarInt64Value(Source: PByte): Int64;
  3695. /// jump a value in the 32-bit or 64-bit variable-length integer buffer
  3696. function GotoNextVarInt(Source: PByte): pointer; {$ifdef HASINLINE}inline;{$endif}
  3697. /// convert a RawUTF8 into an UTF-8 encoded variable-length buffer
  3698. function ToVarString(const Value: RawUTF8; Dest: PByte): PByte;
  3699. /// jump a value in variable-length text buffer
  3700. function GotoNextVarString(Source: PByte): pointer; {$ifdef HASINLINE}inline;{$endif}
  3701. /// retrieve a variable-length UTF-8 encoded text buffer in a newly allocation RawUTF8
  3702. function FromVarString(var Source: PByte): RawUTF8; overload;
  3703. /// retrieve a variable-length text buffer
  3704. // - this overloaded function will set the supplied code page to the AnsiString
  3705. procedure FromVarString(var Source: PByte; var Value: RawByteString;
  3706. CodePage: integer); overload;
  3707. /// retrieve a variable-length UTF-8 encoded text buffer in a temporary buffer
  3708. // - caller should call Value.Done after use of the Value.buf memory
  3709. // - this overloaded function would include a trailing #0, so Value.buf could
  3710. // be parsed as a valid PUTF8Char buffer (e.g. containing JSON)
  3711. procedure FromVarString(var Source: PByte; var Value: TSynTempBuffer); overload;
  3712. type
  3713. /// kind of result returned by FromVarBlob() function
  3714. TValueResult = record
  3715. /// start of data value
  3716. Ptr: PAnsiChar;
  3717. /// value length (in bytes)
  3718. Len: integer;
  3719. end;
  3720. /// retrieve pointer and length to a variable-length text/blob buffer
  3721. function FromVarBlob(Data: PByte): TValueResult; {$ifdef HASINLINE}inline;{$endif}
  3722. { ************ low-level RTTI types and conversion routines ***************** }
  3723. type
  3724. /// function prototype to be used for TDynArray Sort and Find method
  3725. // - common functions exist for base types: see e.g. SortDynArrayByte,
  3726. // SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal,
  3727. // SordDynArraySingle, SortDynArrayInt64, SortDynArrayBoolean,
  3728. // SortDynArrayDouble, SortDynArrayAnsiString, SortDynArrayAnsiStringI,
  3729. // SortDynArrayUnicodeString, SortDynArrayUnicodeStringI,
  3730. // SortDynArrayString, SortDynArrayStringI
  3731. // - any custom type (even records) can be compared then sort by defining
  3732. // such a custom function
  3733. // - must return 0 if A=B, -1 if A<B, 1 if A>B
  3734. TDynArraySortCompare = function(const A,B): integer;
  3735. /// event oriented version of TDynArraySortCompare
  3736. TEventDynArraySortCompare = function(const A,B): integer of object;
  3737. /// internal enumeration used to specify some standard Delphi arrays
  3738. // - will be used e.g. to match JSON serialization or TDynArray search
  3739. // (see TDynArray and TDynArrayHash InitSpecific method)
  3740. // - djBoolean would generate an array of JSON boolean values
  3741. // - djByte .. djTimeLog match numerical JSON values
  3742. // - djDateTime .. djSynUnicode match textual JSON values
  3743. // - djVariant will match standard variant JSON serialization (including
  3744. // TDocVariant or other custom types, if any)
  3745. // - djCustom will be used for registered JSON serializer (invalid for
  3746. // InitSpecific methods call)
  3747. // - see also djPointer and djObject constant aliases for a pointer or
  3748. // TObject field hashing / comparison
  3749. // - is used also by TDynArray.InitSpecific() to define the main field type
  3750. TDynArrayKind = (
  3751. djNone,
  3752. djBoolean, djByte, djWord, djInteger, djCardinal, djSingle,
  3753. djInt64, djDouble, djCurrency,
  3754. djTimeLog, djDateTime, djRawUTF8, djWinAnsi, djString, djRawByteString,
  3755. djWideString, djSynUnicode, djInterface,
  3756. {$ifndef NOVARIANTS}djVariant,{$endif}
  3757. djCustom);
  3758. /// internal set to specify some standard Delphi arrays
  3759. TDynArrayKinds = set of TDynArrayKind;
  3760. const
  3761. /// TDynArrayKind alias for a pointer field hashing / comparison
  3762. djPointer = {$ifdef CPU64}djInt64{$else}djCardinal{$endif};
  3763. /// TDynArrayKind alias for a TObject field hashing / comparison
  3764. djObject = djPointer;
  3765. type
  3766. /// a wrapper around a dynamic array with one dimension
  3767. // - provide TList-like methods using fast RTTI information
  3768. // - can be used to fast save/retrieve all memory content to a TStream
  3769. // - note that the "const Elem" is not checked at compile time nor runtime:
  3770. // you must ensure that Elem matchs the element type of the dynamic array
  3771. // - can use external Count storage to make Add() and Delete() much faster
  3772. // (avoid most reallocation of the memory buffer)
  3773. // - Note that TDynArray is just a wrapper around an existing dynamic array:
  3774. // methods can modify the content of the associated variable but the TDynArray
  3775. // doesn't contain any data by itself. It is therefore aimed to initialize
  3776. // a TDynArray wrapper on need, to access any existing dynamic array.
  3777. // - is defined either as an object either as a record, due to a bug
  3778. // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  3779. // if defined as an object on the stack, but will be as a record :(
  3780. {$ifdef UNDIRECTDYNARRAY}
  3781. TDynArray = record
  3782. private
  3783. {$else}
  3784. TDynArray = object
  3785. protected
  3786. {$endif}
  3787. fValue: PPointer;
  3788. fTypeInfo: pointer;
  3789. fElemSize: PtrUInt;
  3790. fElemType: pointer;
  3791. fCountP: PInteger;
  3792. fCompare: TDynArraySortCompare;
  3793. fKnownSize: integer;
  3794. fSorted: boolean;
  3795. fKnownType: TDynArrayKind;
  3796. fIsObjArray: (oaUnknown, oaTrue, oaFalse);
  3797. function GetCount: integer; {$ifdef HASINLINE}inline;{$endif}
  3798. procedure SetCount(aCount: integer);
  3799. function GetCapacity: integer;
  3800. procedure SetCapacity(aCapacity: integer);
  3801. procedure SetCompare(const aCompare: TDynArraySortCompare); {$ifdef HASINLINE}inline;{$endif}
  3802. function FindIndex(const Elem; aIndex: PIntegerDynArray;
  3803. aCompare: TDynArraySortCompare): integer;
  3804. function GetArrayTypeName: RawUTF8;
  3805. function GetIsObjArray: boolean; {$ifdef HASINLINE}inline;{$endif}
  3806. procedure SetIsObjArray(aValue: boolean); {$ifdef HASINLINE}inline;{$endif}
  3807. /// will set fKnownType and fKnownOffset/fKnownSize fields
  3808. function ToKnownType(exactType: boolean=false): TDynArrayKind;
  3809. /// faster than System.DynArraySetLength() function + handle T*ObjArray
  3810. procedure InternalSetLength(NewLength: PtrUInt);
  3811. public
  3812. /// initialize the wrapper with a one-dimension dynamic array
  3813. // - the dynamic array must have been defined with its own type
  3814. // (e.g. TIntegerDynArray = array of Integer)
  3815. // - if aCountPointer is set, it will be used instead of length() to store
  3816. // the dynamic array items count - it will be much faster when adding
  3817. // elements to the array, because the dynamic array won't need to be
  3818. // resized each time - but in this case, you should use the Count property
  3819. // instead of length(array) or high(array) when accessing the data: in fact
  3820. // length(array) will store the memory size reserved, not the items count
  3821. // - if aCountPointer is set, its content will be set to 0, whatever the
  3822. // array length is, or the current aCountPointer^ value is
  3823. // - a sample usage may be:
  3824. // !var DA: TDynArray;
  3825. // ! A: TIntegerDynArray;
  3826. // !begin
  3827. // ! DA.Init(TypeInfo(TIntegerDynArray),A);
  3828. // ! (...)
  3829. // - a sample usage may be (using a count variable):
  3830. // !var DA: TDynArray;
  3831. // ! A: TIntegerDynArray;
  3832. // ! ACount: integer;
  3833. // ! i: integer;
  3834. // !begin
  3835. // ! DA.Init(TypeInfo(TIntegerDynArray),A,@ACount);
  3836. // ! for i := 1 to 100000 do
  3837. // ! DA.Add(i); // MUCH faster using the ACount variable
  3838. // ! (...) // now you should use DA.Count or Count instead of length(A)
  3839. procedure Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
  3840. /// initialize the wrapper with a one-dimension dynamic array
  3841. // - this version accepts to specify how comparison should occur, using
  3842. // TDynArrayKind kind of first field
  3843. // - djNone and djCustom are too vague, and would raise an exception
  3844. // - no RTTI check is made over the corresponding array layout: you shall
  3845. // ensure that the aKind parameter matches the dynamic array element definition
  3846. // - aCaseInsensitive will be used for djRawUTF8..djSynUnicode comparison
  3847. procedure InitSpecific(aTypeInfo: pointer; var aValue; aKind: TDynArrayKind;
  3848. aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  3849. /// define the reference to an external count integer variable
  3850. // - Init and InitSpecific methods will reset the aCountPointer to 0: you
  3851. // can use this method to set the external count variable without overriding
  3852. // the current value
  3853. procedure UseExternalCount(var aCountPointer: Integer);
  3854. {$ifdef HASINLINE}inline;{$endif}
  3855. /// initialize the wrapper to point to no dynamic array
  3856. procedure Void;
  3857. /// check if the wrapper points to a dynamic array
  3858. function IsVoid: boolean;
  3859. /// add an element to the dynamic array
  3860. // - warning: Elem must be of the same exact type than the dynamic array,
  3861. // and must be a reference to a variable (you can't write Add(i+10) e.g.)
  3862. // - returns the index of the added element in the dynamic array
  3863. // - note that because of dynamic array internal memory managment, adding
  3864. // will be a bit slower than e.g. with a TList: the list is reallocated
  3865. // every time a record is added - but in practice, with FastMM4 or
  3866. // SynScaleMM, there is no big speed penalty - for even better speed, you
  3867. // can also specify an external count variable in Init(...,@Count) method
  3868. function Add(const Elem): integer;
  3869. /// add an element to the dynamic array
  3870. // - this version add a void element to the array, and returns its index
  3871. function New: integer;
  3872. /// add an element to the dynamic array at the position specified by Index
  3873. // - warning: Elem must be of the same exact type than the dynamic array,
  3874. // and must be a reference to a variable (you can't write Insert(10,i+10) e.g.)
  3875. procedure Insert(Index: Integer; const Elem);
  3876. /// delete the whole dynamic array content
  3877. // - this method will recognize T*ObjArray types and free all instances
  3878. procedure Clear;
  3879. {$ifdef HASINLINE}inline;{$endif}
  3880. /// delete one item inside the dynamic array
  3881. // - the deleted element is finalized if necessary
  3882. // - this method will recognize T*ObjArray types and free all instances
  3883. procedure Delete(aIndex: Integer);
  3884. /// returns a pointer to an element of the array
  3885. // - returns nil if aIndex is out of range
  3886. // - since TDynArray is just a wrapper around an existing array, you should
  3887. // better use direct access to its wrapped variable, and not using this slower
  3888. // and more error prone method (such pointer access lacks of strong typing
  3889. // abilities)
  3890. function ElemPtr(aIndex: integer): pointer;
  3891. /// search for an element value inside the dynamic array
  3892. // - return the index found (0..Count-1), or -1 if Elem was not found
  3893. // - will search for all properties content of the eLement: TList.IndexOf()
  3894. // searches by address, this method searches by content using the RTTI
  3895. // element description (and not the Compare property function)
  3896. // - use the Find() method if you want the search via the Compare property
  3897. // function, or e.g. to search only with some part of the element content
  3898. // - will work with simple types: binaries (byte, word, integer, Int64,
  3899. // Currency, array[0..255] of byte, packed records with no reference-counted
  3900. // type within...), string types (e.g. array of string), and packed records
  3901. // with binary and string types within (like TFileVersion)
  3902. // - won't work with not packed types (like a shorstring, or a record
  3903. // with byte or word fields with {$A+}): in this case, the padding data
  3904. // (i.e. the bytes between the aligned feeds can be filled as random, and
  3905. // there is no way with standard RTTI do know which they are)
  3906. // - warning: Elem must be of the same exact type than the dynamic array,
  3907. // and must be a reference to a variable (you can't write IndexOf(i+10) e.g.)
  3908. function IndexOf(const Elem): integer;
  3909. /// search for an element value inside the dynamic array
  3910. // - this method will use the Compare property function for the search
  3911. // - return the index found (0..Count-1), or -1 if Elem was not found
  3912. // - if the array is sorted, it will use fast binary search
  3913. // - if the array is not sorted, it will use slower iterating search
  3914. // - warning: Elem must be of the same exact type than the dynamic array,
  3915. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  3916. function Find(const Elem): integer; overload;
  3917. /// search for an element value inside the dynamic array, from an external
  3918. // indexed lookup table
  3919. // - return the index found (0..Count-1), or -1 if Elem was not found
  3920. // - this method will use a custom comparison function, with an external
  3921. // integer table, as created by the CreateOrderedIndex() method: it allows
  3922. // multiple search orders in the same dynamic array content
  3923. // - if an indexed lookup is supplied, it must already be sorted:
  3924. // this function will then use fast binary search
  3925. // - if an indexed lookup is not supplied (i.e aIndex=nil),
  3926. // this function will use slower but accurate iterating search
  3927. // - warning; the lookup index should be synchronized if array content
  3928. // is modified (in case of adding or deletion)
  3929. function Find(const Elem; const aIndex: TIntegerDynArray;
  3930. aCompare: TDynArraySortCompare): integer; overload;
  3931. /// search for an element value, then fill all properties if match
  3932. // - this method will use the Compare property function for the search,
  3933. // or the supplied indexed lookup table and its associated compare function
  3934. // - if Elem content matches, all Elem fields will be filled with the record
  3935. // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
  3936. // first string field (i.e. set to SortDynArrayString), you can fill the
  3937. // first string field with the searched value (if returned index is >= 0)
  3938. // - return the index found (0..Count-1), or -1 if Elem was not found
  3939. // - if the array is sorted, it will use fast binary search
  3940. // - if the array is not sorted, it will use slower iterating search
  3941. // - warning: Elem must be of the same exact type than the dynamic array,
  3942. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  3943. function FindAndFill(var Elem; aIndex: PIntegerDynArray=nil;
  3944. aCompare: TDynArraySortCompare=nil): integer;
  3945. /// search for an element value, then delete it if match
  3946. // - this method will use the Compare property function for the search,
  3947. // or the supplied indexed lookup table and its associated compare function
  3948. // - if Elem content matches, this item will be deleted from the array
  3949. // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
  3950. // first string field (i.e. set to SortDynArrayString), you can fill the
  3951. // first string field with the searched value (if returned index is >= 0)
  3952. // - return the index deleted (0..Count-1), or -1 if Elem was not found
  3953. // - if the array is sorted, it will use fast binary search
  3954. // - if the array is not sorted, it will use slower iterating search
  3955. // - warning: Elem must be of the same exact type than the dynamic array,
  3956. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  3957. function FindAndDelete(const Elem; aIndex: PIntegerDynArray=nil;
  3958. aCompare: TDynArraySortCompare=nil): integer;
  3959. /// search for an element value, then update the item if match
  3960. // - this method will use the Compare property function for the search,
  3961. // or the supplied indexed lookup table and its associated compare function
  3962. // - if Elem content matches, this item will be updated with the supplied
  3963. // value
  3964. // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
  3965. // first string field (i.e. set to SortDynArrayString), you can fill the
  3966. // first string field with the searched value (if returned index is >= 0)
  3967. // - return the index found (0..Count-1), or -1 if Elem was not found
  3968. // - if the array is sorted, it will use fast binary search
  3969. // - if the array is not sorted, it will use slower iterating search
  3970. // - warning: Elem must be of the same exact type than the dynamic array,
  3971. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  3972. function FindAndUpdate(const Elem; aIndex: PIntegerDynArray=nil;
  3973. aCompare: TDynArraySortCompare=nil): integer;
  3974. /// search for an element value, then add it if none matched
  3975. // - this method will use the Compare property function for the search,
  3976. // or the supplied indexed lookup table and its associated compare function
  3977. // - if no Elem content matches, the item will added to the array
  3978. // - can be used e.g. as a simple dictionary: if Compare will match e.g. the
  3979. // first string field (i.e. set to SortDynArrayString), you can fill the
  3980. // first string field with the searched value (if returned index is >= 0)
  3981. // - return the index found (0..Count-1), or -1 if Elem was not found and
  3982. // the supplied element has been succesfully added
  3983. // - if the array is sorted, it will use fast binary search
  3984. // - if the array is not sorted, it will use slower iterating search
  3985. // - warning: Elem must be of the same exact type than the dynamic array,
  3986. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  3987. function FindAndAddIfNotExisting(const Elem; aIndex: PIntegerDynArray=nil;
  3988. aCompare: TDynArraySortCompare=nil): integer;
  3989. /// sort the dynamic array elements, using the Compare property function
  3990. // - it will change the dynamic array content, and exchange all elements
  3991. // in order to be sorted in increasing order according to Compare function
  3992. procedure Sort(aCompare: TDynArraySortCompare=nil);
  3993. /// search for an element value inside a sorted dynamic array
  3994. // - this method will use the Compare property function for the search
  3995. // - will be faster than a manual FindAndAddIfNotExisting+Sort process
  3996. // - returns TRUE and the index of existing Elem, or FALSE and the index
  3997. // where the Elem is to be inserted so that the array remains sorted
  3998. // - you should then call FastAddSorted() later with the returned Index
  3999. // - if the array is not sorted, returns FALSE and Index=-1
  4000. // - warning: Elem must be of the same exact type than the dynamic array,
  4001. // and must be a reference to a variable (no FastLocateSorted(i+10) e.g.)
  4002. function FastLocateSorted(const Elem; out Index: Integer): boolean;
  4003. /// insert a sorted element value at the proper place
  4004. // - the index should have been computed by FastLocateSorted(): false
  4005. // - you may consider using FastLocateOrAddSorted() instead
  4006. procedure FastAddSorted(Index: Integer; const Elem);
  4007. /// search and add an element value inside a sorted dynamic array
  4008. // - this method will use the Compare property function for the search
  4009. // - will be faster than a manual FindAndAddIfNotExisting+Sort process
  4010. // - returns the index of the existing Elem and wasAdded^=false
  4011. // - returns the sorted index of the inserted Elem and wasAdded^=true
  4012. // - if the array is not sorted, returns -1 and wasAdded^=false
  4013. // - is just a wrapper around FastLocateSorted+FastAddSorted
  4014. function FastLocateOrAddSorted(const Elem; wasAdded: PBoolean=nil): integer;
  4015. /// delete a sorted element value at the proper place
  4016. // - plain Delete(Index) would reset the fSorted flag to FALSE, so use
  4017. // this method with a FastLocateSorted/FastAddSorted array
  4018. procedure FastDeleteSorted(Index: Integer);
  4019. /// will reverse all array elements, in place
  4020. procedure Reverse;
  4021. /// sort the dynamic array elements using a lookup array of indexes
  4022. // - in comparison to the Sort method, this CreateOrderedIndex won't change
  4023. // the dynamic array content, but only create (or update) the supplied
  4024. // integer lookup array, using the specified comparison function
  4025. // - if aCompare is not supplied, the method will use fCompare (if defined)
  4026. // - you should provide either a void either a valid lookup table, that is
  4027. // a table with one to one lookup (e.g. created with FillIncreasing)
  4028. // - if the lookup table has less elements than the main dynamic array,
  4029. // its content will be recreated
  4030. procedure CreateOrderedIndex(var aIndex: TIntegerDynArray;
  4031. aCompare: TDynArraySortCompare);
  4032. /// save the dynamic array content into a (memory) stream
  4033. // - will handle array of binaries values (byte, word, integer...), array of
  4034. // strings or array of packed records, with binaries and string properties
  4035. // - will use a proprietary binary format, with some variable-length encoding
  4036. // of the string length
  4037. // - Stream position will be set just after the added data
  4038. // - is optimized for memory streams, but will work with any kind of TStream
  4039. procedure SaveToStream(Stream: TStream);
  4040. /// load the dynamic array content from a (memory) stream
  4041. // - stream content must have been created using SaveToStream method
  4042. // - will handle array of binaries values (byte, word, integer...), array of
  4043. // strings or array of packed records, with binaries and string properties
  4044. // - will use a proprietary binary format, with some variable-length encoding
  4045. // of the string length
  4046. procedure LoadFromStream(Stream: TCustomMemoryStream);
  4047. /// save the dynamic array content into an allocated memory buffer
  4048. // - Dest buffer must have been allocated to contain at least the number
  4049. // of bytes returned by the SaveToLength method
  4050. // - return a pointer at the end of the data written in Dest, nil in case
  4051. // of an invalid input buffer
  4052. // - this method will raise an ESynException for T*ObjArray types
  4053. function SaveTo(Dest: PAnsiChar): PAnsiChar; overload;
  4054. /// compute the number of bytes needed to save a dynamic array content
  4055. // - this method will raise an ESynException for T*ObjArray types
  4056. function SaveToLength: integer;
  4057. /// save the dynamic array content into a RawByteString
  4058. // - this method will raise an ESynException for T*ObjArray types
  4059. function SaveTo: RawByteString; overload;
  4060. /// load the dynamic array content from a memory buffer
  4061. // - return nil if the Source buffer is incorrect (invalid type or internal
  4062. // checksum e.g.)
  4063. // - in case of success, return the memory buffer pointer just after the
  4064. // read content
  4065. // - this method will raise an ESynException for T*ObjArray types
  4066. // - return a pointer at the end of the data read from Source, nil on error
  4067. function LoadFrom(Source: PAnsiChar): PAnsiChar;
  4068. /// serialize the dynamic array content as JSON
  4069. // - is just a wrapper around TTextWriter.AddDynArrayJSON()
  4070. // - this method will therefore recognize T*ObjArray types
  4071. function SaveToJSON(EnumSetsAsText: boolean=false): RawUTF8;
  4072. /// load the dynamic array content from an UTF-8 encoded JSON buffer
  4073. // - expect the format as saved by TTextWriter.AddDynArrayJSON method, i.e.
  4074. // handling TBooleanDynArray, TIntegerDynArray, TInt64DynArray, TCardinalDynArray,
  4075. // TDoubleDynArray, TCurrencyDynArray, TWordDynArray, TByteDynArray,
  4076. // TRawUTF8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
  4077. // TStringDynArray, TWideStringDynArray, TSynUnicodeDynArray,
  4078. // TTimeLogDynArray and TDateTimeDynArray as JSON array - or any customized
  4079. // valid JSON serialization as set by TTextWriter.RegisterCustomJSONSerializer
  4080. // - or any other kind of array as Base64 encoded binary stream precessed
  4081. // via JSON_BASE64_MAGIC (UTF-8 encoded \uFFF0 special code)
  4082. // - typical handled content could be
  4083. // ! '[1,2,3,4]' or '["\uFFF0base64encodedbinary"]'
  4084. // - return a pointer at the end of the data read from P, nil in case
  4085. // of an invalid input buffer
  4086. // - this method will recognize T*ObjArray types, and will first free
  4087. // any existing instance before unserializing, to avoid memory leak
  4088. // - warning: the content of P^ will be modified during parsing: please
  4089. // make a local copy if it will be needed later (using e.g. TSynTempBufer)
  4090. function LoadFromJSON(P: PUTF8Char; aEndOfObject: PUTF8Char=nil): PUTF8Char;
  4091. /// select a sub-section (slice) of a dynamic array content
  4092. procedure Slice(var Dest; aCount: Cardinal; aFirstIndex: cardinal=0);
  4093. /// add elements from a given dynamic array variable
  4094. // - the supplied source DynArray MUST be of the same exact type as the
  4095. // current used for this TDynArray - warning: pass here a reference to
  4096. // a "array of ..." variable, not another TDynArray instance; if you
  4097. // want to add another TDynArray, use AddDynArray() method
  4098. // - you can specify the start index and the number of items to take from
  4099. // the source dynamic array (leave as -1 to add till the end)
  4100. procedure AddArray(const DynArrayVar; aStartIndex: integer=0; aCount: integer=-1);
  4101. {$ifndef DELPHI5OROLDER}
  4102. /// add elements from a given TDynArray
  4103. // - the supplied source TDynArray MUST be of the same exact type as the
  4104. // current used for this TDynArray, otherwise it won't do anything
  4105. // - you can specify the start index and the number of items to take from
  4106. // the source dynamic array (leave as -1 to add till the end)
  4107. procedure AddDynArray(const aSource: TDynArray; aStartIndex: integer=0; aCount: integer=-1);
  4108. /// compare the content of the two arrays, returning TRUE if both match
  4109. // - this method compares first using any supplied Compare property,
  4110. // then by content using the RTTI element description of the whole record
  4111. // - warning: this method won't compare T*ObjArray kind of arrays
  4112. function Equals(const B: TDynArray): boolean;
  4113. /// set all content of one dynamic array to the current array
  4114. // - both must be of the same exact type
  4115. procedure Copy(const Source: TDynArray);
  4116. /// set all content of one dynamic array to the current array
  4117. // - both must be of the same exact type
  4118. procedure CopyFrom(const Source; MaxElem: integer);
  4119. {$endif}
  4120. /// compare the content of two elements, returning TRUE if both values equal
  4121. // - this method compares first using any supplied Compare property,
  4122. // then by content using the RTTI element description of the whole record
  4123. function ElemEquals(const A,B): boolean;
  4124. /// will reset the element content
  4125. procedure ElemClear(var Elem);
  4126. /// will copy one element content
  4127. procedure ElemCopy(const A; var B);
  4128. /// save an array element into a serialized buffer
  4129. // - you can use ElemLoad method later to retrieve its content
  4130. // - warning: Elem must be of the same exact type than the dynamic array,
  4131. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  4132. function ElemSave(const Elem): RawByteString;
  4133. /// load an array element as saved by the ElemSave method
  4134. // - warning: Elem must be of the same exact type than the dynamic array,
  4135. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  4136. procedure ElemLoad(Source: PAnsiChar; var Elem); overload;
  4137. /// load an array element as saved by the ElemSave method
  4138. // - this overloaded method will retrieve the element as a memory buffer
  4139. // and caller MUST call ElemLoadClear() method to finalize its content
  4140. function ElemLoad(Source: PAnsiChar): RawByteString; overload;
  4141. /// release memory allocated by ElemLoad(): RawByteString
  4142. procedure ElemLoadClear(var ElemLoaded: RawByteString);
  4143. /// search for an array element as saved by the ElemSave method
  4144. // - same as ElemLoad() + Find()/IndexOf() + ElemLoadClear()
  4145. // - will call Find() method if Compare property is set
  4146. // - will call generic IndexOf() method if no Compare property is set
  4147. function ElemLoadFind(Source: PAnsiChar): integer;
  4148. /// retrieve or set the number of elements of the dynamic array
  4149. // - same as length(DynArray) or SetLenght(DynArray)
  4150. // - this property will recognize T*ObjArray types, so will free any stored
  4151. // instance if the array is sized down
  4152. property Count: integer read GetCount write SetCount;
  4153. /// the internal buffer capacity
  4154. // - if no external Count pointer was set with Init, is the same as Count
  4155. // - if an external Count pointer is set, you can set a value to this
  4156. // property before a massive use of the Add() method e.g.
  4157. // - if no external Count pointer is set, set a value to this property
  4158. // will affect the Count value, i.e. Add() will append after this count
  4159. // - this property will recognize T*ObjArray types, so will free any stored
  4160. // instance if the array is sized down
  4161. property Capacity: integer read GetCapacity write SetCapacity;
  4162. /// the compare function to be used for Sort and Find methods
  4163. // - by default, no comparison function is set
  4164. // - common functions exist for base types: e.g. SortDynArrayByte, SortDynArrayBoolean,
  4165. // SortDynArrayWord, SortDynArrayInteger, SortDynArrayCardinal, SortDynArraySingle,
  4166. // SortDynArrayInt64, SortDynArrayDouble, SortDynArrayAnsiString,
  4167. // SortDynArrayAnsiStringI, SortDynArrayString, SortDynArrayStringI,
  4168. // SortDynArrayUnicodeString, SortDynArrayUnicodeStringI
  4169. property Compare: TDynArraySortCompare read fCompare write SetCompare;
  4170. /// must be TRUE if the array is currently in sorted order according to
  4171. // the compare function
  4172. // - Add/Delete/Insert/Load* methods will reset this property to false
  4173. // - Sort method will set this property to true
  4174. // - you MUST set this property to false if you modify the dynamic array
  4175. // content in your code, so that Find() won't try to use binary search in
  4176. // an usorted array, and miss its purpose
  4177. property Sorted: boolean read fSorted write fSorted;
  4178. /// low-level direct access to the storage variable
  4179. property Value: PPointer read fValue;
  4180. /// the known type, possibly retrieved from dynamic array RTTI
  4181. property KnownType: TDynArrayKind read fKnownType;
  4182. /// the known RTTI information of the whole array
  4183. property ArrayType: pointer read fTypeInfo;
  4184. /// the known type name of the whole array
  4185. property ArrayTypeName: RawUTF8 read GetArrayTypeName;
  4186. /// the internal in-memory size of one element, as retrieved from RTTI
  4187. property ElemSize: PtrUInt read fElemSize;
  4188. /// the internal type information of one element, as retrieved from RTTI
  4189. property ElemType: pointer read fElemType;
  4190. /// if this dynamic aray is a T*ObjArray
  4191. property IsObjArray: boolean read GetIsObjArray write SetIsObjArray;
  4192. end;
  4193. /// function prototype to be used for hashing of an element
  4194. // - it must return a cardinal hash, with as less collision as possible
  4195. // - a good candidate is our crc32() function in optimized asm in SynZip unit
  4196. // - TDynArrayHashed.Init will use crc32c() if no custom function is supplied,
  4197. // which will run either as software or SSE4.2 hardware
  4198. THasher = function(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  4199. /// function prototype to be used for hashing of a dynamic array element
  4200. // - this function must use the supplied hasher on the Elem data
  4201. TDynArrayHashOne = function(const Elem; Hasher: THasher): cardinal;
  4202. /// event handler to be used for hashing of a dynamic array element
  4203. TOnDynArrayHashOne = function(const Elem): cardinal of object;
  4204. /// internal structure used to store one item hash
  4205. // - used e.g. by TDynArrayHashed or TObjectHash via TSynHashDynArray
  4206. TSynHash = record
  4207. /// unsigned integer hash of the item
  4208. Hash: cardinal;
  4209. /// index of the item in the main storage array
  4210. Index: cardinal;
  4211. end;
  4212. /// internal structure used to store hashs of items
  4213. // - used e.g. by TDynArrayHashed or TObjectHash
  4214. TSynHashDynArray = array of TSynHash;
  4215. {.$define DYNARRAYHASHCOLLISIONCOUNT}
  4216. /// used to access any dynamic arrray elements using fast hash
  4217. // - by default, binary sort could be used for searching items for TDynArray:
  4218. // using a hash is faster on huge arrays for implementing a dictionary
  4219. // - in this current implementation, modification (update or delete) of an
  4220. // element is not handled yet: you should rehash all content - only
  4221. // TDynArrayHashed.FindHashedForAdding / FindHashedAndUpdate /
  4222. // FindHashedAndDelete will refresh the internal hash
  4223. // - this object extends the TDynArray type, since presence of Hashs[] dynamic
  4224. // array will increase code size if using TDynArrayHashed instead of TDynArray
  4225. // - in order to have the better performance, you should use an external Count
  4226. // variable, AND set the Capacity property to the expected maximum count (this
  4227. // will avoid most ReHash calls for FindHashedForAdding+FindHashedAndUpdate)
  4228. {$ifdef UNDIRECTDYNARRAY}
  4229. TDynArrayHashed = record
  4230. // pseudo inheritance for most used methods
  4231. private
  4232. procedure SetCount(aCount: Integer); inline;
  4233. procedure SetCapacity(aCapacity: Integer); inline;
  4234. function GetCapacity: Integer; inline;
  4235. public
  4236. InternalDynArray: TDynArray;
  4237. function Count: Integer; inline;
  4238. function fValue: PPointer; inline;
  4239. function ElemSize: PtrUInt; inline;
  4240. function ElemType: Pointer; inline;
  4241. function KnownType: TDynArrayKind; inline;
  4242. procedure Clear; inline;
  4243. procedure ElemCopy(const A; var B); inline;
  4244. // warning: you shall call ReHash() after manual Add/Delete
  4245. function Add(const Elem): integer; inline;
  4246. procedure Delete(aIndex: Integer); inline;
  4247. function SaveTo: RawByteString; overload; inline;
  4248. function SaveTo(Dest: PAnsiChar): PAnsiChar; overload; inline;
  4249. function SaveToJSON(EnumSetsAsText: boolean=false): RawUTF8; inline;
  4250. function LoadFromJSON(P: PUTF8Char; aEndOfObject: PUTF8Char=nil): PUTF8Char; inline;
  4251. function SaveToLength: integer; inline;
  4252. function LoadFrom(Source: PAnsiChar): PAnsiChar; inline;
  4253. property Capacity: integer read GetCapacity write SetCapacity;
  4254. private
  4255. {$else UNDIRECTDYNARRAY}
  4256. TDynArrayHashed = object(TDynArray)
  4257. protected
  4258. {$endif UNDIRECTDYNARRAY}
  4259. fHashElement: TDynArrayHashOne;
  4260. fHasher: THasher;
  4261. fHashs: TSynHashDynArray;
  4262. fHashsCount: integer;
  4263. fEventCompare: TEventDynArraySortCompare;
  4264. fHashCountTrigger: integer;
  4265. fHashFindCount: integer;
  4266. {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  4267. fHashFindCollisions: cardinal;
  4268. {$endif}
  4269. procedure HashAdd(const Elem; aHashCode: Cardinal; var result: integer);
  4270. /// low-level search of an element from its pre-computed hash
  4271. // - you should NOT use this method, but rather high-level FindHashed*()
  4272. function HashFind(aHashCode: cardinal; const Elem): integer; overload;
  4273. /// low-level search of an element from its pre-computed hash
  4274. // - this overloaded method will return the first matching item: use the
  4275. // HashFind(...; const Elem) method to avoid any HashElement collision issue
  4276. // - you should NOT use this method, but rather high-level FindHashed*()
  4277. function HashFind(aHashCode: cardinal): integer; overload;
  4278. function GetHashFromIndex(aIndex: Integer): Cardinal;
  4279. public
  4280. /// initialize the wrapper with a one-dimension dynamic array
  4281. // - this version accepts some hash-dedicated parameters: aHashElement to
  4282. // set how to hash each element, aCompare to handle hash collision
  4283. // - if no aHashElement is supplied, it will hash according to the RTTI, i.e.
  4284. // strings or binary types, and the first field for records (strings included)
  4285. // - if no aCompare is supplied, it will use default Equals() method
  4286. // - if no THasher function is supplied, it will use the one supplied in
  4287. // DefaultHasher global variable, set to crc32c() by default - using
  4288. // SSE4.2 instruction if available
  4289. // - if CaseInsensitive is set to TRUE, it will ignore difference in 7 bit
  4290. // alphabetic characters (e.g. compare 'a' and 'A' as equal)
  4291. procedure Init(aTypeInfo: pointer; var aValue;
  4292. aHashElement: TDynArrayHashOne=nil; aCompare: TDynArraySortCompare=nil;
  4293. aHasher: THasher=nil; aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  4294. /// initialize the wrapper with a one-dimension dynamic array
  4295. // - this version accepts to specify how both hashing and comparison should
  4296. // occur, using TDynArrayKind kind of first field
  4297. // - djNone and djCustom are too vague, and would raise an exception
  4298. // - no RTTI check is made over the corresponding array layout: you shall
  4299. // ensure that the aKind parameter matches the dynamic array element definition
  4300. // - aCaseInsensitive will be used for djRawUTF8..djSynUnicode comparison
  4301. procedure InitSpecific(aTypeInfo: pointer; var aValue; aKind: TDynArrayKind;
  4302. aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  4303. /// will compute all hash from the current elements of the dynamic array
  4304. // - is called within the TDynArrayHashed.Init method to initialize the
  4305. // internal hash array
  4306. // - can be called on purpose, when modifications have been performed on
  4307. // the dynamic array content (e.g. in case of element deletion or update,
  4308. // or after calling LoadFrom/Clear method) - this is not necessary after
  4309. // FindHashedForAdding / FindHashedAndUpdate / FindHashedAndDelete methods
  4310. function ReHash(aHasher: TOnDynArrayHashOne=nil): boolean;
  4311. /// low-level function which would inspect the internal fHashs[] array for
  4312. // any collision
  4313. // - is a brute force search within fHashs[].Hash values, which may be handy
  4314. // to validate the current HashElement() function
  4315. // - returns -1 if no collision was found, or the index of the first collision
  4316. function IsHashElementWithoutCollision: integer;
  4317. /// search for an element value inside the dynamic array using hashing
  4318. // - ELem should be of the same exact type than the dynamic array, or at
  4319. // least matchs the fields used by both the hash function and Equals method:
  4320. // e.g. if the searched/hashed field in a record is a string as first field,
  4321. // you may use a string variable as Elem: other fields will be ignored
  4322. // - returns -1 if not found, or the index in the dynamic array if found
  4323. function FindHashed(const Elem): integer;
  4324. /// search for an element value inside the dynamic array using hashing, and
  4325. // fill Elem with the found content
  4326. // - return the index found (0..Count-1), or -1 if Elem was not found
  4327. // - warning: Elem must be of the same exact type than the dynamic array,
  4328. // and must be a reference to a variable (you can't write Find(i+10) e.g.)
  4329. function FindHashedAndFill(var ElemToFill): integer;
  4330. /// search for an element value inside the dynamic array using hashing, and
  4331. // add a void entry to the array if was not found
  4332. // - this method will use hashing for fast retrieval
  4333. // - Elem should be of the same exact type than the dynamic array, or at
  4334. // least matchs the fields used by both the hash function and Equals method:
  4335. // e.g. if the searched/hashed field in a record is a string as first field,
  4336. // you may use a string variable as Elem: other fields will be ignored
  4337. // - returns either the index in the dynamic array if found (and set wasAdded
  4338. // to false), either the newly created index in the dynamic array (and set
  4339. // wasAdded to true)
  4340. // - for faster process (avoid ReHash), please set the Capacity property
  4341. // - warning: in contrast to the Add() method, if an entry is added to the
  4342. // array (wasAdded=true), the entry is left VOID: you must set the field
  4343. // content to expecting value - in short, Elem is used only for searching,
  4344. // not copied to the newly created entry in the array
  4345. // - optional aHashCode parameter can be supplied with an already hashed
  4346. // value of the item, to be used e.g. after a call to HashFind() - default
  4347. // 0 will use fHashElement(Elem,fHasher)
  4348. function FindHashedForAdding(const Elem; out wasAdded: boolean;
  4349. aHashCode: cardinal=0): integer;
  4350. /// ensure a given element name is unique, then add it to the array
  4351. // - expected element layout is to have a RawUTF8 field at first position
  4352. // - the aName is searched (using hashing) to be unique, and if not the case,
  4353. // an ESynException.CreateUTF8() is raised with the supplied arguments
  4354. // - use internaly FindHashedForAdding method
  4355. // - this version will set the field content with the unique value
  4356. // - returns a pointer to the newly added element (to set other fields)
  4357. function AddUniqueName(const aName: RawUTF8;
  4358. const ExceptionMsg: RawUTF8; const ExceptionArgs: array of const): pointer;
  4359. /// search for a given element name, make it unique, and add it to the array
  4360. // - expected element layout is to have a RawUTF8 field at first position
  4361. // - the aName is searched (using hashing) to be unique, and if not the case,
  4362. // some suffix is added to make it unique
  4363. // - use internaly FindHashedForAdding method
  4364. // - this version will set the field content with the unique value
  4365. // - returns a pointer to the newly added element (to set other fields)
  4366. function AddAndMakeUniqueName(aName: RawUTF8): pointer;
  4367. /// search for an element value inside the dynamic array using hashing, then
  4368. // update any matching item, or add the item if none matched
  4369. // - if AddIfNotExisting is FALSE, returns the index found (0..Count-1),
  4370. // or -1 if Elem was not found - update will force slow rehash all content
  4371. // - if AddIfNotExisting is TRUE, returns the index found (0..Count-1),
  4372. // or the index newly created/added is the Elem value was not matching -
  4373. // add won't rehash all content - for even faster process (avoid ReHash),
  4374. // please set the Capacity property
  4375. // - warning: Elem must be of the same exact type than the dynamic array, and
  4376. // must refer to a variable (you can't write FindHashedAndUpdate(i+10) e.g.)
  4377. function FindHashedAndUpdate(const Elem; AddIfNotExisting: boolean): integer;
  4378. /// search for an element value inside the dynamic array using hashing, and
  4379. // delete it if matchs
  4380. // - return the index deleted (0..Count-1), or -1 if Elem was not found
  4381. // - this will rehash all content: this method could be slow in the current
  4382. // implementation
  4383. // - warning: Elem must be of the same exact type than the dynamic array, and
  4384. // must refer to a variable (you can't write FindHashedAndDelete(i+10) e.g.)
  4385. function FindHashedAndDelete(const Elem): integer;
  4386. /// will search for an element value inside the dynamic array without hashing
  4387. // - is used internally when Count < HashCountTrigger
  4388. // - is preferred to Find(), since EventCompare would be used if defined
  4389. // - Elem should be of the same exact type than the dynamic array, or at
  4390. // least matchs the fields used by both the hash function and Equals method:
  4391. // e.g. if the searched/hashed field in a record is a string as first field,
  4392. // you may use a string variable as Elem: other fields will be ignored
  4393. // - returns -1 if not found, or the index in the dynamic array if found
  4394. function Scan(const Elem): integer;
  4395. /// retrieve the hash value of a given item, from its index
  4396. property Hash[aIndex: Integer]: Cardinal read GetHashFromIndex;
  4397. /// alternative event-oriented Compare function to be used for Sort and Find
  4398. // - will be used instead of Compare, to allow object-oriented callbacks
  4399. property EventCompare: TEventDynArraySortCompare read fEventCompare write fEventCompare;
  4400. /// custom hash function to be used for hashing of a dynamic array element
  4401. property HashElement: TDynArrayHashOne read fHashElement;
  4402. /// after how many items the hashing take place
  4403. // - for smallest arrays, O(n) seach if faster than O(1) hashing, since
  4404. // maintaining the fHashs[] lookup has some CPU and memory costs
  4405. // - equals 32 by default
  4406. property HashCountTrigger: integer read fHashCountTrigger write fHashCountTrigger;
  4407. {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  4408. /// access to the internal collision of HashFind()
  4409. // - it won't depend only on the HashElement(), but also on the internal
  4410. // hash bucket size (which is much lower than 2^32 items)
  4411. property HashFindCollisions: cardinal read fHashFindCollisions write fHashFindCollisions;
  4412. {$endif}
  4413. end;
  4414. /// defines a wrapper interface around a dynamic array of TObject
  4415. // - implemented by TObjectDynArrayWrapper for instance
  4416. // - i.e. most common methods are available to work with a dynamic array
  4417. // - warning: the IObjectDynArray MUST be defined in the stack, class or
  4418. // record BEFORE the dynamic array it is wrapping, otherwise you may leak
  4419. // memory - see for instance TSQLRestServer class:
  4420. // ! fSessionAuthentications: IObjectDynArray; // defined before the array
  4421. // ! fSessionAuthentication: TSQLRestServerAuthenticationDynArray;
  4422. IObjectDynArray = interface
  4423. ['{A0D50BD0-0D20-4552-B365-1D63393511FC}']
  4424. /// search one element within the TObject instances
  4425. function Find(Instance: TObject): integer;
  4426. /// add one element to the dynamic array of TObject instances
  4427. // - once added, the Instance will be owned by this TObjectDynArray instance
  4428. function Add(Instance: TObject): integer;
  4429. /// delete one element from the TObject dynamic array
  4430. // - deleted TObject instance will be freed as expected
  4431. procedure Delete(Index: integer);
  4432. /// sort the dynamic array content according to a specified comparer
  4433. procedure Sort(Compare: TDynArraySortCompare);
  4434. /// delete all TObject instances, and release the memory
  4435. // - is not to be called for most use, thanks to reference-counting memory
  4436. // handling, but can be handy for quick release
  4437. procedure Clear;
  4438. /// returns the number of TObject instances available
  4439. // - note that the length of the associated dynamic array is used to store
  4440. // the capacity of the list, so won't probably never match with this value
  4441. function Count: integer;
  4442. /// returns the internal array capacity of TObject instances available
  4443. // - which is in fact the length() of the associated dynamic array
  4444. function Capacity: integer;
  4445. end;
  4446. /// a wrapper to own a dynamic array of TObject
  4447. // - this version behave list a TObjectList (i.e. owning the class instances)
  4448. // - but the dynamic array is NOT owned by the instance
  4449. // - will define an internal Count property, using the dynamic array length
  4450. // as capacity: adding and deleting will be much faster
  4451. // - implements IObjectDynArray, so that most common methods are available
  4452. // to work with the dynamic array
  4453. // - does not need any sub-classing of generic overhead to work, and will be
  4454. // reference counted
  4455. // - warning: the IObjectDynArray MUST be defined in the stack, class or
  4456. // record BEFORE the dynamic array it is wrapping, otherwise you may leak
  4457. // memory, and TObjectDynArrayWrapper.Destroy will raise an ESynException
  4458. // - a sample usage may be:
  4459. // !var DA: IObjectDynArray; // defined BEFORE the dynamic array itself
  4460. // ! A: array of TMyObject;
  4461. // ! i: integer;
  4462. // !begin
  4463. // ! DA := TObjectDynArrayWrapper.Create(A);
  4464. // ! DA.Add(TMyObject.Create('one'));
  4465. // ! DA.Add(TMyObject.Create('two'));
  4466. // ! DA.Delete(0);
  4467. // ! assert(DA.Count=1);
  4468. // ! assert(A[0].Name='two');
  4469. // ! DA.Clear;
  4470. // ! assert(DA.Count=0);
  4471. // ! DA.Add(TMyObject.Create('new'));
  4472. // ! assert(DA.Count=1);
  4473. // !end; // will auto-release DA (no need of try..finally DA.Free)
  4474. TObjectDynArrayWrapper = class(TInterfacedObject, IObjectDynArray)
  4475. protected
  4476. fValue: PPointer;
  4477. fCount: integer;
  4478. public
  4479. /// initialize the wrapper with a one-dimension dynamic array of TObject
  4480. constructor Create(var aValue);
  4481. /// will release all associated TObject instances
  4482. destructor Destroy; override;
  4483. /// search one element within the TObject instances
  4484. function Find(Instance: TObject): integer;
  4485. /// add one element to the dynamic array of TObject instances
  4486. // - once added, the Instance will be owned by this TObjectDynArray instance
  4487. function Add(Instance: TObject): integer;
  4488. /// delete one element from the TObject dynamic array
  4489. // - deleted TObject instance will be freed as expected
  4490. procedure Delete(Index: integer);
  4491. /// sort the dynamic array content according to a specified comparer
  4492. procedure Sort(Compare: TDynArraySortCompare);
  4493. /// delete all TObject instances, and release the memory
  4494. // - is not to be called for most use, thanks to reference-counting memory
  4495. // handling, but can be handy for quick release
  4496. procedure Clear;
  4497. /// returns the number of TObject instances available
  4498. // - note that the length() of the associated dynamic array is used to store
  4499. // the capacity of the list, so won't probably never match with this value
  4500. function Count: integer;
  4501. /// returns the internal array capacity of TObject instances available
  4502. // - which is in fact the length() of the associated dynamic array
  4503. function Capacity: integer;
  4504. end;
  4505. /// abstract class able to use hashing to find an object in O(1) speed
  4506. // - all protected abstract methods shall be overridden and implemented
  4507. // - use this class instead of a plain TDynArrayHashed, since it would
  4508. // feature its own dedicated hashing, and any abstract mean of value storage
  4509. TObjectHash = class
  4510. protected
  4511. fHashs: TSynHashDynArray;
  4512. procedure HashInit(aCountToHash: integer);
  4513. function HashFind(aHashCode: cardinal; Item: TObject): integer;
  4514. /// abstract method to hash an item
  4515. // - note that the overridden method shall not return 0 (mark void fHashs[])
  4516. function Hash(Item: TObject): cardinal; virtual; abstract;
  4517. /// abstract method to compare two items
  4518. function Compare(Item1,Item2: TObject): boolean; virtual; abstract;
  4519. /// abstract method to get an item
  4520. // - shall return nil if Index is out of range (e.g. >= Count)
  4521. // - will be called e.g. by Find() with Compare() to avoid collision
  4522. function Get(Index: integer): TObject; virtual; abstract;
  4523. /// used to retrieve the number of items
  4524. function Count: integer; virtual; abstract;
  4525. public
  4526. /// search one item in the internal hash array
  4527. function Find(Item: TObject): integer;
  4528. /// search one item using slow list browsing
  4529. // - this version expects the internal list count to be supplied, if some
  4530. // last items are to be ignored (used e.g. in EnsureJustAddedNotDuplicated)
  4531. function Scan(Item: TObject; ListCount: integer): integer; virtual;
  4532. /// to be called when an item is modified
  4533. // - for Delete/Update will force a full rehash on next Find() call
  4534. procedure Invalidate;
  4535. /// to be called when an item has just been added
  4536. // - the index of the latest added item should be Count-1
  4537. // - this method will update the internal hash table, and check if
  4538. // the newly added value is not duplicated
  4539. // - return FALSE if this item is already existing (i.e. insert error)
  4540. // - return TRUE if has been added to the internal hash table
  4541. function EnsureJustAddedNotDuplicated: boolean;
  4542. end;
  4543. /// abstract parent class with a virtual constructor, ready to be overridden
  4544. // to initialize the instance
  4545. // - you can specify such a class if you need an object including published
  4546. // properties (like TPersistent) with a virtual constructor (e.g. to
  4547. // initialize some nested class properties)
  4548. TPersistentWithCustomCreate = class(TPersistent)
  4549. public
  4550. /// this virtual constructor will be called at instance creation
  4551. // - this constructor does nothing, but is declared as virtual so that
  4552. // inherited classes may safely override this default void implementation
  4553. constructor Create; virtual;
  4554. end;
  4555. {$M+}
  4556. /// abstract parent class with threadsafe implementation of IInterface and
  4557. // a virtual constructor
  4558. // - you can specify e.g. such a class to TSQLRestServer.ServiceRegister() if
  4559. // you need an interfaced object with a virtual constructor, ready to be
  4560. // overridden to initialize the instance
  4561. TInterfacedObjectWithCustomCreate = class(TInterfacedObject)
  4562. public
  4563. /// this virtual constructor will be called at instance creation
  4564. // - this constructor does nothing, but is declared as virtual so that
  4565. // inherited classes may safely override this default void implementation
  4566. constructor Create; virtual;
  4567. /// used to mimic TInterfacedObject reference counting
  4568. // - Release=true will call TInterfacedObject._Release
  4569. // - Release=false will call TInterfacedObject._AddRef
  4570. // - could be used to emulate proper reference counting of the instance
  4571. // via interfaces variables, but still storing plain class instances
  4572. // (e.g. in a global list of instances)
  4573. procedure RefCountUpdate(Release: boolean); virtual;
  4574. end;
  4575. /// our own empowered TPersistent-like parent class
  4576. // - TPersistent has an unexpected speed overhead due a giant lock introduced
  4577. // to manage property name fixup resolution (which we won't use outside the VCL)
  4578. // - this class has a virtual constructor, so is a preferred alternative
  4579. // to both TPersistent and TPersistentWithCustomCreate classes
  4580. // - for best performance, any type inheriting from this class will bypass
  4581. // some regular steps: do not implement interfaces or use TMonitor with them!
  4582. TSynPersistent = class(TObject)
  4583. protected
  4584. // this default implementation will call AssignError()
  4585. procedure AssignTo(Dest: TSynPersistent); virtual;
  4586. procedure AssignError(Source: TSynPersistent);
  4587. public
  4588. /// this virtual constructor will be called at instance creation
  4589. // - this constructor does nothing, but is declared as virtual so that
  4590. // inherited classes may safely override this default void implementation
  4591. constructor Create; virtual;
  4592. /// allows to implement a TPersistent-like assignement mechanism
  4593. // - inherited class should override AssignTo() protected method
  4594. // to implement the proper assignment
  4595. procedure Assign(Source: TSynPersistent); virtual;
  4596. {$ifndef FPC_OR_PUREPASCAL}
  4597. /// optimized x86 asm initialization code
  4598. // - warning: this optimized version won't initialize the vmtIntfTable
  4599. // for this class hierarchy: as a result, you would NOT be able to
  4600. // implement an interface with a TSynPersistent descendent (but you should
  4601. // not need to, but inherit from TInterfacedObject)
  4602. class function NewInstance: TObject; override;
  4603. /// optimized x86 asm finalization code
  4604. // - warning: this version won't release either any allocated TMonitor
  4605. // (as available since Delphi 2009) - do not use TMonitor with
  4606. // TSynPersistent, but rather the faster TSynPersistentLocked class
  4607. procedure FreeInstance; override;
  4608. {$endif}
  4609. end;
  4610. {$M-}
  4611. /// allow to add cross-platform locking methods to any class instance
  4612. // - typical use is to define a Safe: TSynLocker property, call Safe.Init
  4613. // and Safe.Done in constructor/destructor methods, and use Safe.Lock/UnLock
  4614. // methods in a try ... finally section
  4615. // - in respect to the TCriticalSection class, fix a potential CPU cache line
  4616. // conflict which may degrade the multi-threading performance, as reported by
  4617. // @http://www.delphitools.info/2011/11/30/fixing-tcriticalsection
  4618. // - internal padding is used to safely store up to 7 values protected
  4619. // from concurrent access with a mutex
  4620. {$ifdef UNICODE}
  4621. TSynLocker = record
  4622. {$else}
  4623. TSynLocker = object
  4624. {$endif}
  4625. private
  4626. fSection: TRTLCriticalSection;
  4627. {$ifndef NOVARIANTS}
  4628. function GetVariant(Index: integer): Variant;
  4629. procedure SetVariant(Index: integer; const Value: Variant);
  4630. function GetInt64(Index: integer): Int64;
  4631. procedure SetInt64(Index: integer; const Value: Int64);
  4632. function GetUnlockedInt64(Index: integer): Int64;
  4633. procedure SetUnlockedInt64(Index: integer; const Value: Int64);
  4634. function GetPointer(Index: integer): Pointer;
  4635. procedure SetPointer(Index: integer; const Value: Pointer);
  4636. function GetUTF8(Index: integer): RawUTF8;
  4637. procedure SetUTF8(Index: integer; const Value: RawUTF8);
  4638. {$endif}
  4639. public
  4640. /// internal padding data, also used to store up to 7 variant values
  4641. // - this memory buffer will ensure no CPU cache line mixup occurs
  4642. // - you should not use this field directly, but rather the Locked[],
  4643. // LockedInt64[], LockedUTF8[] or LockedPointer[] methods
  4644. // - if you want to access those array values, ensure you protect them
  4645. // using a Safe.Lock; try ... Padding[n] ... finally Safe.Unlock structure,
  4646. // and maintain the PaddingMaxUsedIndex field accurately
  4647. Padding: array[0..6] of TVarData;
  4648. /// maximum index of the last value stored in the internal Padding[] array
  4649. // - equals -1 if no value is actually stored, or a 0..6 number otherwise
  4650. // - you should not have to use this field, but for optimized low-level
  4651. // direct access to Padding[] values, within a Lock/UnLock safe block
  4652. PaddingMaxUsedIndex: integer;
  4653. /// initialize the mutex
  4654. // - calling this method is mandatory (e.g. in the class constructor owning
  4655. // the TSynLocker instance), otherwise you may encounter unexpected
  4656. // behavior, like access violations or memory leaks
  4657. procedure Init;
  4658. /// finalize the mutex
  4659. // - calling this method is mandatory (e.g. in the class constructor owning
  4660. // the TSynLocker instance), otherwise you may encounter unexpected
  4661. // behavior, like access violations or memory leaks
  4662. procedure Done;
  4663. /// lock the instance for exclusive access
  4664. // - use as such to avoid race condition (from a Safe: TSynLocker property):
  4665. // ! Safe.Lock;
  4666. // ! try
  4667. // ! ...
  4668. // ! finally
  4669. // ! Safe.Unlock;
  4670. // ! end;
  4671. procedure Lock;
  4672. {$ifdef HASINLINE}inline;{$endif}
  4673. /// will try to acquire the mutex
  4674. // - use as such to avoid race condition (from a Safe: TSynLocker property):
  4675. // ! if Safe.TryLock then
  4676. // ! try
  4677. // ! ...
  4678. // ! finally
  4679. // ! Safe.Unlock;
  4680. // ! end;
  4681. function TryLock: boolean;
  4682. {$ifdef HASINLINE}inline;{$endif}
  4683. /// release the instance for exclusive access
  4684. procedure UnLock;
  4685. {$ifdef HASINLINE}inline;{$endif}
  4686. /// will enter the mutex until the IUnknown reference is released
  4687. // - could be used as such under Delphi:
  4688. // !begin
  4689. // ! ... // unsafe code
  4690. // ! Safe.ProtectMethod;
  4691. // ! ... // thread-safe code
  4692. // !end; // local hidden IUnknown will release the lock for the method
  4693. // - warning: under FPC, you should assign its result to a local variable -
  4694. // see bug http://bugs.freepascal.org/view.php?id=26602
  4695. // !var LockFPC: IUnknown;
  4696. // !begin
  4697. // ! ... // unsafe code
  4698. // ! LockFPC := Safe.ProtectMethod;
  4699. // ! ... // thread-safe code
  4700. // !end; // LockFPC will release the lock for the method
  4701. // or
  4702. // !begin
  4703. // ! ... // unsafe code
  4704. // ! with Safe.ProtectMethod do begin
  4705. // ! ... // thread-safe code
  4706. // ! end; // local hidden IUnknown will release the lock for the method
  4707. // !end;
  4708. function ProtectMethod: IUnknown;
  4709. {$ifndef NOVARIANTS}
  4710. /// safe locked access to a Variant value
  4711. // - you may store up to 7 variables, using an 0..6 index, shared with
  4712. // LockedPointer and LockedUTF8 array properties
  4713. // - returns null if the Index is out of range
  4714. property Locked[Index: integer]: Variant read GetVariant write SetVariant;
  4715. /// safe locked access to a Int64 value
  4716. // - you may store up to 7 variables, using an 0..6 index, shared with
  4717. // Locked and LockedUTF8 array properties
  4718. // - Int64s will be stored internally as a varInt64 variant
  4719. // - returns nil if the Index is out of range, or does not store a Int64
  4720. property LockedInt64[Index: integer]: Int64 read GetInt64 write SetInt64;
  4721. /// safe locked access to a pointer/TObject value
  4722. // - you may store up to 7 variables, using an 0..6 index, shared with
  4723. // Locked and LockedUTF8 array properties
  4724. // - pointers will be stored internally as a varUnknown variant
  4725. // - returns nil if the Index is out of range, or does not store a pointer
  4726. property LockedPointer[Index: integer]: Pointer read GetPointer write SetPointer;
  4727. /// safe locked access to an UTF-8 string value
  4728. // - you may store up to 7 variables, using an 0..6 index, shared with
  4729. // Locked and LockedPointer array properties
  4730. // - UTF-8 string will be stored internally as a varString variant
  4731. // - returns '' if the Index is out of range, or does not store a string
  4732. property LockedUTF8[Index: integer]: RawUTF8 read GetUTF8 write SetUTF8;
  4733. /// safe locked in-place increment to an Int64 value
  4734. // - you may store up to 7 variables, using an 0..6 index, shared with
  4735. // Locked and LockedUTF8 array properties
  4736. // - Int64s will be stored internally as a varInt64 variant
  4737. // - returns the newly stored value
  4738. // - if the internal value is not defined yet, would use 0 as default value
  4739. function LockedInt64Increment(Index: integer; const Increment: Int64): Int64;
  4740. /// safe locked in-place exchange of a Variant value
  4741. // - you may store up to 7 variables, using an 0..6 index, shared with
  4742. // Locked and LockedUTF8 array properties
  4743. // - returns the previous stored value, or null if the Index is out of range
  4744. function LockedExchange(Index: integer; const Value: variant): variant;
  4745. /// safe locked in-place exchange of a pointer/TObject value
  4746. // - you may store up to 7 variables, using an 0..6 index, shared with
  4747. // Locked and LockedUTF8 array properties
  4748. // - pointers will be stored internally as a varUnknown variant
  4749. // - returns the previous stored value, nil if the Index is out of range,
  4750. // or does not store a pointer
  4751. function LockedPointerExchange(Index: integer; Value: pointer): pointer;
  4752. /// unsafe access to a Int64 value
  4753. // - you may store up to 7 variables, using an 0..6 index, shared with
  4754. // Locked and LockedUTF8 array properties
  4755. // - Int64s will be stored internally as a varInt64 variant
  4756. // - returns nil if the Index is out of range, or does not store a Int64
  4757. // - you should rather call LockedInt64[] property, or use this property
  4758. // with a Lock; try ... finally UnLock block
  4759. property UnlockedInt64[Index: integer]: Int64 read GetUnlockedInt64 write SetUnlockedInt64;
  4760. {$endif NOVARIANTS}
  4761. end;
  4762. PSynLocker = ^TSynLocker;
  4763. /// adding locking methods to a TSynPersistent with virtual constructor
  4764. // - you may use this class instead of the RTL TCriticalSection, since it
  4765. // would use a TSynLocker which does not suffer from CPU cache line conflit
  4766. TSynPersistentLocked = class(TSynPersistent)
  4767. protected
  4768. fSafe: TSynLocker;
  4769. public
  4770. /// initialize the object instance, and its associated lock
  4771. constructor Create; override;
  4772. /// release the instance (including the locking resource)
  4773. destructor Destroy; override;
  4774. /// access to the locking methods of this instance
  4775. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  4776. property Safe: TSynLocker read fSafe;
  4777. end;
  4778. /// adding locking methods to a TInterfacedObject with virtual constructor
  4779. TInterfacedObjectLocked = class(TInterfacedObjectWithCustomCreate)
  4780. protected
  4781. fSafe: TSynLocker;
  4782. public
  4783. /// initialize the object instance, and its associated lock
  4784. constructor Create; override;
  4785. /// release the instance (including the locking resource)
  4786. destructor Destroy; override;
  4787. /// access to the locking methods of this instance
  4788. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  4789. property Safe: TSynLocker read fSafe;
  4790. end;
  4791. /// used to determine the exact class type of a TInterfacedObjectWithCustomCreate
  4792. // - could be used to create instances using its virtual constructor
  4793. TInterfacedObjectWithCustomCreateClass = class of TInterfacedObjectWithCustomCreate;
  4794. /// used to determine the exact class type of a TPersistentWithCustomCreateClass
  4795. // - could be used to create instances using its virtual constructor
  4796. TPersistentWithCustomCreateClass = class of TPersistentWithCustomCreate;
  4797. /// used to determine the exact class type of a TSynPersistent
  4798. // - could be used to create instances using its virtual constructor
  4799. TSynPersistentClass = class of TSynPersistent;
  4800. /// internal item definition, used by TPendingTaskList storage
  4801. TPendingTaskListItem = packed record
  4802. /// the task should be executed when TPendingTaskList.GetTimeStamp reaches
  4803. // this value
  4804. TimeStamp: Int64;
  4805. /// the associated task, stored by representation as raw binary
  4806. Task: RawByteString;
  4807. end;
  4808. /// internal list definition, used by TPendingTaskList storage
  4809. TPendingTaskListItemDynArray = array of TPendingTaskListItem;
  4810. /// handle a list of tasks, stored as RawByteString, with a time stamp
  4811. // - internal time stamps would be GetTickCount64 by default, so have a
  4812. // resolution of about 16 ms under Windows
  4813. // - you can add tasks to the internal list, to be executed after a given
  4814. // delay, using a post/peek like algorithm
  4815. // - execution delays are not expected to be accurate, but are best guess,
  4816. // according to NextTask call
  4817. // - this implementation is thread-safe, thanks to the Safe internal locker
  4818. TPendingTaskList = class(TSynPersistent)
  4819. protected
  4820. fCount: Integer;
  4821. fTask: TPendingTaskListItemDynArray;
  4822. fTasks: TDynArray;
  4823. fSafe: TSynLocker;
  4824. function GetCount: integer;
  4825. function GetTimeStamp: Int64; virtual;
  4826. public
  4827. /// initialize the list memory and resources
  4828. constructor Create; override;
  4829. /// finaalize the list memory and resources
  4830. destructor Destroy; override;
  4831. /// append a task, specifying a delay in milliseconds from current time
  4832. procedure AddTask(aMilliSecondsDelayFromNow: integer; const aTask: RawByteString); virtual;
  4833. /// append several tasks, specifying a delay in milliseconds between tasks
  4834. // - first supplied delay would be computed from the current time, then
  4835. // it would specify how much time to wait between the next supplied task
  4836. procedure AddTasks(const aMilliSecondsDelays: array of integer;
  4837. const aTasks: array of RawByteString);
  4838. /// retrieve the next pending task
  4839. // - returns '' if there is no scheduled task available at the current time
  4840. // - returns the next stack as defined corresponding to its specified delay
  4841. function NextPendingTask: RawByteString; virtual;
  4842. /// flush all pending tasks
  4843. procedure Clear; virtual;
  4844. /// access to the locking methods of this instance
  4845. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  4846. property Safe: TSynlocker read fSafe;
  4847. /// access to the internal TPendingTaskListItem.TimeStamp stored value
  4848. // - corresponding to the current time
  4849. // - default implementation is to return GetTickCount64, with a 16 ms
  4850. // typical resolution under Windows
  4851. property TimeStamp: Int64 read GetTimeStamp;
  4852. /// how many pending tasks are currently defined
  4853. property Count: integer read GetCount;
  4854. /// direct low-level access to the internal task list
  4855. // - warning: this dynamic array length is the list capacity: use Count
  4856. // property to retrieve the exact number of stored items
  4857. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block for
  4858. // thread-safe access to this array
  4859. // - items are stored in increasing TimeStamp, i.e. the first item is
  4860. // the next one which would be returned by the NextPendingTask method
  4861. property Task: TPendingTaskListItemDynArray read fTask;
  4862. end;
  4863. /// store one Name/Value pair, as used by TSynNameValue class
  4864. TSynNameValueItem = record
  4865. /// the name of the Name/Value pair
  4866. // - this property is hashed by TSynNameValue for fast retrieval
  4867. Name: RawUTF8;
  4868. /// the value of the Name/Value pair
  4869. Value: RawUTF8;
  4870. /// any associated Pointer or numerical value
  4871. Tag: PtrInt;
  4872. end;
  4873. /// Name/Value pairs storage, as used by TSynNameValue class
  4874. TSynNameValueItemDynArray = array of TSynNameValueItem;
  4875. /// event handler used to convert on the fly some UTF-8 text content
  4876. TConvertRawUTF8 = function(const text: RawUTF8): RawUTF8 of object;
  4877. /// callback event used by TSynNameValue
  4878. TSynNameValueNotify = procedure(const Item: TSynNameValueItem; Index: PtrInt) of object;
  4879. /// pseudo-class used to store Name/Value RawUTF8 pairs
  4880. // - use internaly a TDynArrayHashed instance for fast retrieval
  4881. // - is therefore faster than TRawUTF8List
  4882. // - is defined as an object, not as a class: you can use this in any
  4883. // class, without the need to destroy the content
  4884. // - is defined either as an object either as a record, due to a bug
  4885. // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  4886. // if defined as an object on the stack, but will be as a record :(
  4887. TSynNameValue = {$ifndef UNICODE}object{$else}record{$endif}
  4888. fDynArray: TDynArrayHashed;
  4889. fOnAdd: TSynNameValueNotify;
  4890. function GetBlobData: RawByteString;
  4891. procedure SetBlobData(const aValue: RawByteString);
  4892. function GetStr(const aName: RawUTF8): RawUTF8; {$ifdef HASINLINE}inline;{$endif}
  4893. function GetInt(const aName: RawUTF8): Int64; {$ifdef HASINLINE}inline;{$endif}
  4894. function GetBool(const aName: RawUTF8): Boolean; {$ifdef HASINLINE}inline;{$endif}
  4895. public
  4896. /// the internal Name/Value storage
  4897. List: TSynNameValueItemDynArray;
  4898. /// the number of Name/Value pairs
  4899. Count: integer;
  4900. /// initialize the storage
  4901. // - will also reset the internal List[] and the internal hash array
  4902. procedure Init(aCaseSensitive: boolean);
  4903. /// add an element to the array
  4904. // - if aName already exists, its associated Value will be updated
  4905. procedure Add(const aName, aValue: RawUTF8; aTag: PtrInt=0);
  4906. /// reset content, then add all name=value pairs from a supplied .ini file
  4907. // section content
  4908. // - will first call Init(false) to initialize the internal array
  4909. // - Section can be retrieved e.g. via FindSectionFirstLine()
  4910. procedure InitFromIniSection(Section: PUTF8Char; OnTheFlyConvert: TConvertRawUTF8=nil;
  4911. OnAdd: TSynNameValueNotify=nil);
  4912. /// reset content, then add all name=value; CSV pairs
  4913. // - will first call Init(false) to initialize the internal array
  4914. procedure InitFromCSV(CSV: PUTF8Char; NameValueSep: AnsiChar='=';
  4915. ItemSep: AnsiChar=#10);
  4916. /// search for a Name, return the index in List
  4917. // - using fast O(1) hash algoritm
  4918. function Find(const aName: RawUTF8): integer;
  4919. /// search for the first chars of a Name, return the index in List
  4920. // - using O(n) calls of IdemPChar() function
  4921. // - here aUpperName should be already uppercase, as expected by IdemPChar()
  4922. function FindStart(const aUpperName: RawUTF8): integer;
  4923. /// search for a Value, return the index in List
  4924. // - using O(n) brute force algoritm with case-sensitive aValue search
  4925. function FindByValue(const aValue: RawUTF8): integer;
  4926. /// search for a Name, and delete its entry in the List if it exists
  4927. function Delete(const aName: RawUTF8): boolean;
  4928. /// search for a Value, and delete its entry in the List if it exists
  4929. // - returns the number of deleted entries
  4930. // - you may search for more than one match, by setting a >1 Limit value
  4931. function DeleteByValue(const aValue: RawUTF8; Limit: integer=1): integer;
  4932. /// search for a Name, return the associated Value as a UTF-8 string
  4933. function Value(const aName: RawUTF8; const aDefaultValue: RawUTF8=''): RawUTF8;
  4934. /// search for a Name, return the associated Value as integer
  4935. function ValueInt(const aName: RawUTF8; const aDefaultValue: Int64=0): Int64;
  4936. /// search for a Name, return the associated Value as boolean
  4937. // - returns true only if the value is exactly '1'
  4938. function ValueBool(const aName: RawUTF8): Boolean;
  4939. /// returns all values, as CSV or INI content
  4940. function AsCSV(const KeySeparator: RawUTF8='=';
  4941. const ValueSeparator: RawUTF8=#13#10; const IgnoreKey: RawUTF8=''): RawUTF8;
  4942. /// fill the supplied two arrays of RawUTF8 with the stored values
  4943. procedure AsNameValues(out Names,Values: TRawUTF8DynArray);
  4944. {$ifndef NOVARIANTS}
  4945. /// search for a Name, return the associated Value as variant
  4946. // - returns null if the name was not found
  4947. function ValueVariantOrNull(const aName: RawUTF8): variant;
  4948. /// compute a TDocVariant document from the stored values
  4949. // - output variant will be reset and filled as a TDocVariant instance,
  4950. // ready to be serialized as a JSON object
  4951. // - if there is no value stored (i.e. Count=0), set null
  4952. procedure AsDocVariant(out DocVariant: variant;
  4953. ExtendedJson: boolean=false; ValueAsString: boolean=true); overload;
  4954. /// compute a TDocVariant document from the stored values
  4955. function AsDocVariant(ExtendedJson: boolean=false; ValueAsString: boolean=true): variant; overload; {$ifdef HASINLINE}inline;{$endif}
  4956. /// merge the stored values into a TDocVariant document
  4957. // - existing properties would be updated, then new values will be added to
  4958. // the supplied TDocVariant instance, ready to be serialized as a JSON object
  4959. // - if ValueAsString is TRUE, values would be stored as string
  4960. // - if ValueAsString is FALSE, numerical values would be identified by
  4961. // IsString() and stored as such in the resulting TDocVariant
  4962. // - if you let ChangedProps point to a TDocVariantData, it would contain
  4963. // an object with the stored values, just like AsDocVariant
  4964. // - returns the number of updated values in the TDocVariant, 0 if
  4965. // no value was changed
  4966. function MergeDocVariant(var DocVariant: variant;
  4967. ValueAsString: boolean; ChangedProps: PVariant=nil;
  4968. ExtendedJson: Boolean=false): integer;
  4969. {$endif}
  4970. /// returns true if the Init() method has been called
  4971. function Initialized: boolean;
  4972. /// can be used to set all data from one BLOB memory buffer
  4973. procedure SetBlobDataPtr(aValue: pointer);
  4974. /// can be used to set or retrieve all stored data as one BLOB content
  4975. property BlobData: RawByteString read GetBlobData write SetBlobData;
  4976. /// event triggerred after an item has just been added to the list
  4977. property OnAfterAdd: TSynNameValueNotify read fOnAdd write fOnAdd;
  4978. /// search for a Name, return the associated Value as a UTF-8 string
  4979. // - returns '' if aName is not found in the stored keys
  4980. property Str[const aName: RawUTF8]: RawUTF8 read GetStr; default;
  4981. /// search for a Name, return the associated Value as integer
  4982. // - returns 0 if aName is not found, or not a valid Int64 in the stored keys
  4983. property Int[const aName: RawUTF8]: Int64 read GetInt;
  4984. /// search for a Name, return the associated Value as boolean
  4985. // - returns true if aName stores '1' as associated value
  4986. property Bool[const aName: RawUTF8]: Boolean read GetBool;
  4987. end;
  4988. /// a reference pointer to a Name/Value RawUTF8 pairs storage
  4989. PSynNameValue = ^TSynNameValue;
  4990. /// wrapper to add an item to a array of pointer dynamic array storage
  4991. function PtrArrayAdd(var aPtrArray; aItem: pointer): integer;
  4992. {$ifdef HASINLINE}inline;{$endif}
  4993. /// wrapper to add an item to a T*ObjArray dynamic array storage
  4994. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  4995. // - could be used as such (note the T*ObjArray type naming convention):
  4996. // ! TUserObjArray = array of TUser;
  4997. // ! ...
  4998. // ! var arr: TUserObjArray;
  4999. // ! user: TUser;
  5000. // ! ..
  5001. // ! try
  5002. // ! user := TUser.Create;
  5003. // ! user.Name := 'Name';
  5004. // ! index := ObjArrayAdd(arr,user);
  5005. // ! ...
  5006. // ! finally
  5007. // ! ObjArrayClear(arr); // release all items
  5008. // ! end;
  5009. // - return the index of the item in the dynamic array
  5010. function ObjArrayAdd(var aObjArray; aItem: TObject): integer;
  5011. {$ifdef HASINLINE}inline;{$endif}
  5012. /// wrapper to add once an item to a T*ObjArray dynamic array storage
  5013. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5014. // - if the object is already in the array (searching by address/reference,
  5015. // not by content), return its current index in the dynamic array
  5016. // - if the object does not appear in the array, add it at the end
  5017. procedure ObjArrayAddOnce(var aObjArray; aItem: TObject);
  5018. /// wrapper to set the length of a T*ObjArray dynamic array storage
  5019. // - could be used as an alternative to SetLength() when you do not
  5020. // know the exact T*ObjArray type
  5021. procedure ObjArraySetLength(var aObjArray; aLength: integer);
  5022. {$ifdef HASINLINE}inline;{$endif}
  5023. /// wrapper to search an item in a T*ObjArray dynamic array storage
  5024. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5025. // - search is performed by address/reference, not by content
  5026. // - returns -1 if the item is not found in the dynamic array
  5027. function ObjArrayFind(const aObjArray; aItem: TObject): integer;
  5028. {$ifdef HASINLINE}inline;{$endif}
  5029. /// wrapper to delete an item in a T*ObjArray dynamic array storage
  5030. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5031. // - do nothing if the index is out of range in the dynamic array
  5032. procedure ObjArrayDelete(var aObjArray; aItemIndex: integer); overload;
  5033. /// wrapper to delete an item in a T*ObjArray dynamic array storage
  5034. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5035. // - search is performed by address/reference, not by content
  5036. // - do nothing if the item is not found in the dynamic array
  5037. function ObjArrayDelete(var aObjArray; aItem: TObject): integer; overload;
  5038. /// wrapper to sort the items stored in a T*ObjArray dynamic array
  5039. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5040. procedure ObjArraySort(var aObjArray; Compare: TDynArraySortCompare);
  5041. /// wrapper to release all items stored in a T*ObjArray dynamic array
  5042. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5043. // - you should always use ObjArrayClear() before the array storage is released,
  5044. // e.g. in the owner class destructor
  5045. // - will also set the dynamic array length to 0, so could be used to re-use
  5046. // an existing T*ObjArray
  5047. procedure ObjArrayClear(var aObjArray);
  5048. /// wrapper to release all items stored in an array of T*ObjArray dynamic array
  5049. // - e.g. aObjArray may be defined as "array of array of TSynFilter"
  5050. procedure ObjArrayObjArrayClear(var aObjArray);
  5051. /// wrapper to release all items stored in several T*ObjArray dynamic arrays
  5052. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  5053. procedure ObjArraysClear(const aObjArray: array of pointer);
  5054. {$ifndef DELPHI5OROLDER}
  5055. /// wrapper to add an item to a T*InterfaceArray dynamic array storage
  5056. function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): integer;
  5057. /// wrapper to add once an item to a T*InterfaceArray dynamic array storage
  5058. procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown);
  5059. /// wrapper to search an item in a T*InterfaceArray dynamic array storage
  5060. // - search is performed by address/reference, not by content
  5061. // - return -1 if the item is not found in the dynamic array, or the index of
  5062. // the matching entry otherwise
  5063. function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): integer;
  5064. {$ifdef HASINLINE}inline;{$endif}
  5065. /// wrapper to delete an item in a T*InterfaceArray dynamic array storage
  5066. // - search is performed by address/reference, not by content
  5067. // - do nothing if the item is not found in the dynamic array
  5068. function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): integer; overload;
  5069. /// wrapper to delete an item in a T*InterfaceArray dynamic array storage
  5070. // - do nothing if the item is not found in the dynamic array
  5071. procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: integer); overload;
  5072. {$endif DELPHI5OROLDER}
  5073. /// helper to retrieve the text of an enumerate item
  5074. // - you'd better use RTTI related classes of mORMot.pas unit, e.g. TEnumType
  5075. function GetEnumName(aTypeInfo: pointer; aIndex: integer): PShortString;
  5076. /// helper to retrieve the index of an enumerate item from its text
  5077. // - returns -1 if aValue was not found
  5078. // - will search for the exact text and also trim the lowercase 'a'..'z' chars on
  5079. // left side of the text if no exact match is found and AlsoTrimLowerCase is TRUE
  5080. // - see also RTTI related classes of mORMot.pas unit, e.g. TEnumType
  5081. function GetEnumNameValue(aTypeInfo: pointer; aValue: PUTF8Char; aValueLen: integer;
  5082. AlsoTrimLowerCase: boolean=false): Integer; overload;
  5083. /// retrieve the index of an enumerate item from its left-trimmed text
  5084. // - will trim the lowercase 'a'..'z' chars on left side of the supplied aValue text
  5085. // - returns -1 if aValue was not found
  5086. function GetEnumNameValueTrimmed(aTypeInfo: pointer; aValue: PUTF8Char; aValueLen: integer): integer;
  5087. /// helper to retrieve the index of an enumerate item from its text
  5088. function GetEnumNameValue(aTypeInfo: pointer; const aValue: RawUTF8;
  5089. AlsoTrimLowerCase: boolean=false): Integer; overload;
  5090. /// helper to retrieve the bit mapped integer value of a set from its JSON text
  5091. // - if supplied P^ is a JSON integer number, will read it directly
  5092. // - if P^ maps some ["item1","item2"] content, would fill all matching bits
  5093. // - if P^ contains ['*'], would fill all bits
  5094. function GetSetNameValue(aTypeInfo: pointer; var P: PUTF8Char;
  5095. out EndOfObject: AnsiChar): cardinal;
  5096. /// fast search of an exact case-insensitive match of a RTTI's PShortString array
  5097. function FindShortStringListExact(List: PShortString; MaxValue: integer;
  5098. aValue: PUTF8Char; aValueLen: integer): integer;
  5099. /// fast search of an left-trimmed lowercase match of a RTTI's PShortString array
  5100. function FindShortStringListTrimLowerCase(List: PShortString; MaxValue: integer;
  5101. aValue: PUTF8Char; aValueLen: integer): integer;
  5102. /// retrieve the type name from its low-level RTTI
  5103. function TypeInfoToName(aTypeInfo: pointer): RawUTF8; overload;
  5104. {$ifdef HASINLINE}inline;{$endif}
  5105. /// retrieve the type name from its low-level RTTI
  5106. procedure TypeInfoToName(aTypeInfo: pointer; var result: RawUTF8;
  5107. const default: RawUTF8=''); overload;
  5108. /// retrieve the unit name and type name from its low-level RTTI
  5109. procedure TypeInfoToQualifiedName(aTypeInfo: pointer; var result: RawUTF8;
  5110. const default: RawUTF8='');
  5111. /// retrieve the record size from its low-level RTTI
  5112. function RecordTypeInfoSize(aRecordTypeInfo: pointer): integer;
  5113. /// retrieve the item type information of a dynamic array low-level RTTI
  5114. function DynArrayTypeInfoToRecordInfo(aDynArrayTypeInfo: pointer;
  5115. aDataSize: PInteger=nil): pointer;
  5116. /// compare two TGUID values
  5117. // - this version is faster than the one supplied by SysUtils
  5118. function IsEqualGUID(const guid1, guid2: TGUID): Boolean;
  5119. {$ifdef HASINLINE}inline;{$endif}
  5120. /// returns the index of a matching TGUID in an array
  5121. // - returns -1 if no item matched
  5122. function IsEqualGUIDArray(const guid: TGUID; const guids: array of TGUID): integer;
  5123. /// check if a TGUID value contains only 0 bytes
  5124. // - this version is faster than the one supplied by SysUtils
  5125. function IsNullGUID(const guid: TGUID): Boolean;
  5126. {$ifdef HASINLINE}inline;{$endif}
  5127. /// append one TGUID item to a TGUID dynamic array
  5128. // - returning the newly inserted index in guids[], or an existing index in
  5129. // guids[] if NoDuplicates is TRUE and TGUID already exists
  5130. function AddGUID(var guids: TGUIDDynArray; const guid: TGUID;
  5131. NoDuplicates: boolean=false): integer;
  5132. /// append a TGUID binary content as text
  5133. // - will store e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without any {})
  5134. // - this will be the format used for JSON encoding, e.g.
  5135. // $ { "UID": "C9A646D3-9C61-4CB7-BFCD-EE2522C8F633" }
  5136. function GUIDToText(P: PUTF8Char; guid: PByteArray): PUTF8Char;
  5137. /// convert a TGUID into UTF-8 encoded text
  5138. // - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
  5139. // - if you do not need the embracing { }, use ToUTF8() overloaded function
  5140. function GUIDToRawUTF8(const guid: TGUID): RawUTF8;
  5141. /// convert a TGUID into text
  5142. // - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
  5143. // - this version is faster than the one supplied by SysUtils
  5144. function GUIDToString(const guid: TGUID): string;
  5145. /// fill some memory buffer with random values
  5146. // - the destination buffer is expected to be allocated as 32 bit items
  5147. // - use internally crc32c() hashing with some rough entropy source, and
  5148. // hardware RDRAND Intel x86/x64 opcode if available
  5149. // - consider using instead the cryptographic secure TAESPRNG.Main.FillRandom()
  5150. // method from the SynCrypto unit
  5151. procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer);
  5152. /// compute a random GUID value
  5153. procedure RandomGUID(out result: TGUID); overload;
  5154. {$ifdef HASINLINE}inline;{$endif}
  5155. /// compute a random GUID value
  5156. function RandomGUID: TGUID; overload;
  5157. {$ifdef HASINLINE}inline;{$endif}
  5158. type
  5159. /// stack-allocated ASCII string, used by GUIDToShort() function
  5160. TGUIDShortString = string[38];
  5161. const
  5162. /// a TGUID containing '{00000000-0000-0000-0000-00000000000}'
  5163. GUID_NULL: TGUID = ();
  5164. /// convert a TGUID into text
  5165. // - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
  5166. // - using a shortstring will allow fast allocation on the stack, so is
  5167. // preferred e.g. when providing a GUID to a ESynException.CreateUTF8()
  5168. function GUIDToShort(const guid: TGUID): TGUIDShortString;
  5169. /// convert some text into its TGUID binary value
  5170. // - expect e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without any {})
  5171. // - return if the supplied text buffer is not a valid TGUID
  5172. // - this will be the format used for JSON encoding, e.g.
  5173. // $ { "UID": "C9A646D3-9C61-4CB7-BFCD-EE2522C8F633" }
  5174. function TextToGUID(P: PUTF8Char; guid: PByteArray): PUTF8Char;
  5175. /// convert some text into a TGUID
  5176. // - expect e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
  5177. // - return {00000000-0000-0000-0000-000000000000} if the supplied text buffer
  5178. // is not a valid TGUID
  5179. function StringToGUID(const text: string): TGUID;
  5180. /// convert some UTF-8 encoded text into a TGUID
  5181. // - expect e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
  5182. // - return {00000000-0000-0000-0000-000000000000} if the supplied text buffer
  5183. // is not a valid TGUID
  5184. function RawUTF8ToGUID(const text: RawByteString): TGUID;
  5185. /// serialize most kind of content as JSON, using its RTTI
  5186. // - is just a wrapper around TTextWriter.AddTypedJSON()
  5187. // - so would handle tkClass, tkEnumeration, tkSet, tkRecord, tkDynArray,
  5188. // tkVariant kind of content - other kinds would return 'null'
  5189. procedure SaveJSON(const Value; TypeInfo: pointer;
  5190. EnumSetsAsText: boolean; var result: RawUTF8); overload;
  5191. /// serialize most kind of content as JSON, using its RTTI
  5192. // - is just a wrapper around TTextWriter.AddTypedJSON()
  5193. // - so would handle tkClass, tkEnumeration, tkSet, tkRecord, tkDynArray,
  5194. // tkVariant kind of content - other kinds would return 'null'
  5195. function SaveJSON(const Value; TypeInfo: pointer;
  5196. EnumSetsAsText: boolean=false): RawUTF8; overload;
  5197. {$ifdef HASINLINE}inline;{$endif}
  5198. /// check equality of two records by content
  5199. // - will handle packed records, with binaries (byte, word, integer...) and
  5200. // string types properties
  5201. // - will use binary-level comparison: it could fail to match two floating-point
  5202. // values because of rounding issues (Currency won't have this problem)
  5203. function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
  5204. /// save a record content into a RawByteString
  5205. // - will handle packed records, with binaries (byte, word, integer...) and
  5206. // string types properties (but not with internal raw pointers, of course)
  5207. // - will use a proprietary binary format, with some variable-length encoding
  5208. // of the string length
  5209. // - warning: will encode generic string fields as AnsiString (one byte per char)
  5210. // prior to Delphi 2009, and as UnicodeString (two bytes per char) since Delphi
  5211. // 2009: if you want to use this function between UNICODE and NOT UNICODE
  5212. // versions of Delphi, you should use some explicit types like RawUTF8,
  5213. // WinAnsiString, SynUnicode or even RawUnicode/WideString
  5214. function RecordSave(const Rec; TypeInfo: pointer): RawByteString; overload;
  5215. /// save a record content into a destination memory buffer
  5216. // - Dest must be at least RecordSaveLength() bytes long
  5217. // - will handle packed records, with binaries (byte, word, integer...) and
  5218. // string types properties (but not with internal raw pointers, of course)
  5219. // - will use a proprietary binary format, with some variable-length encoding
  5220. // of the string length
  5221. // - warning: will encode generic string fields as AnsiString (one byte per char)
  5222. // prior to Delphi 2009, and as UnicodeString (two bytes per char) since Delphi
  5223. // 2009: if you want to use this function between UNICODE and NOT UNICODE
  5224. // versions of Delphi, you should use some explicit types like RawUTF8,
  5225. // WinAnsiString, SynUnicode or even RawUnicode/WideString
  5226. function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer): PAnsiChar; overload;
  5227. /// save a record content into a Base-64 encoded UTF-8 text content
  5228. // - will use RecordSave() format, with a left-sided binary CRC
  5229. function RecordSaveBase64(const Rec; TypeInfo: pointer; UriCompatible: boolean=false): RawUTF8;
  5230. /// compute the number of bytes needed to save a record content
  5231. // using the RecordSave() function
  5232. // - will return 0 in case of an invalid (not handled) record type (e.g. if
  5233. // it contains an unknown variant)
  5234. function RecordSaveLength(const Rec; TypeInfo: pointer): integer;
  5235. /// save record into its JSON serialization as saved by TTextWriter.AddRecordJSON
  5236. // - will use default Base64 encoding over RecordSave() binary - or custom true
  5237. // JSON format (as set by TTextWriter.RegisterCustomJSONSerializer or via
  5238. // enhanced RTTI), if available (following EnumSetsAsText optional parameter
  5239. // for nested enumerates and sets)
  5240. function RecordSaveJSON(const Rec; TypeInfo: pointer;
  5241. EnumSetsAsText: boolean=false): RawUTF8;
  5242. {$ifdef HASINLINE}inline;{$endif}
  5243. /// fill a record content from a memory buffer as saved by RecordSave()
  5244. // - return nil if the Source buffer is incorrect
  5245. // - in case of success, return the memory buffer pointer just after the
  5246. // read content
  5247. function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer): PAnsiChar;
  5248. /// read a record content from a Base-64 encoded content
  5249. // - expects RecordSaveBase64() format, with a left-sided binary CRC
  5250. function RecordLoadBase64(Source: PAnsiChar; Len: integer; var Rec; TypeInfo: pointer;
  5251. UriCompatible: boolean=false): boolean;
  5252. /// fill a record content from a JSON serialization as saved by
  5253. // TTextWriter.AddRecordJSON / RecordSaveJSON
  5254. // - will use default Base64 encoding over RecordSave() binary - or custom true
  5255. // JSON format (as set by TTextWriter.RegisterCustomJSONSerializer or via
  5256. // enhanced RTTI), if available
  5257. // - returns nil on error, or the end of buffer on success
  5258. // - warning: the JSON buffer will be modified in-place during process - use
  5259. // a temporary copy if you need to access it later, or the overloaded RecordLoadJSON()
  5260. function RecordLoadJSON(var Rec; JSON: PUTF8Char; TypeInfo: pointer;
  5261. EndOfObject: PUTF8Char=nil): PUTF8Char; overload;
  5262. /// fill a record content from a JSON serialization as saved by
  5263. // TTextWriter.AddRecordJSON / RecordSaveJSON
  5264. // - will use default Base64 encoding over RecordSave() binary - or custom true
  5265. // JSON format (as set by TTextWriter.RegisterCustomJSONSerializer or via
  5266. // enhanced RTTI), if available
  5267. function RecordLoadJSON(var Rec; const JSON: RawUTF8; TypeInfo: pointer): boolean; overload;
  5268. /// copy a record content from source to Dest
  5269. // - this unit includes a fast optimized asm version for x86
  5270. procedure RecordCopy(var Dest; const Source; TypeInfo: pointer);
  5271. /// clear a record content
  5272. // - this unit includes a fast optimized asm version for x86
  5273. procedure RecordClear(var Dest; TypeInfo: pointer);
  5274. {$ifndef DELPHI5OROLDER}
  5275. /// copy a dynamic array content from source to Dest
  5276. // - uses internally the TDynArray.CopyFrom() method and two temporary
  5277. // TDynArray wrappers
  5278. procedure DynArrayCopy(var Dest; const Source; SourceMaxElem: integer;
  5279. TypeInfo: pointer);
  5280. {$endif}
  5281. /// fill a dynamic array content from a binary serialization as saved by
  5282. // DynArraySave() / TDynArray.Save()
  5283. // - Value shall be set to the target dynamic array field
  5284. // - just a function helper around TDynArray.Load()
  5285. function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: pointer): PAnsiChar;
  5286. /// serialize a dynamic array content as binary, ready to be loaded by
  5287. // DynArrayLoad() / TDynArray.Load()
  5288. // - Value shall be set to the source dynamic array field
  5289. // - just a function helper around TDynArray.Load()
  5290. function DynArraySave(var Value; TypeInfo: pointer): RawByteString;
  5291. /// fill a dynamic array content from a JSON serialization as saved by
  5292. // TTextWriter.AddDynArrayJSON
  5293. // - Value shall be set to the target dynamic array field
  5294. // - is just a wrapper around TDynArray.LoadFromJSON(), creating a temporary
  5295. // TDynArray wrapper on the stack
  5296. // - to be used e.g. for custom record JSON unserialization, within a
  5297. // TDynArrayJSONCustomReader callback
  5298. // - warning: the JSON buffer will be modified in-place during process - use
  5299. // a temporary copy if you need to access it later
  5300. function DynArrayLoadJSON(var Value; JSON: PUTF8Char; TypeInfo: pointer;
  5301. EndOfObject: PUTF8Char=nil): PUTF8Char;
  5302. /// serialize a dynamic array content as JSON
  5303. // - Value shall be set to the source dynamic array field
  5304. // - is just a wrapper around TTextWriter.AddDynArrayJSON(), creating
  5305. // a temporary TDynArray wrapper on the stack
  5306. // - to be used e.g. for custom record JSON serialization, within a
  5307. // TDynArrayJSONCustomWriter callback or RegisterCustomJSONSerializerFromText()
  5308. // (following EnumSetsAsText optional parameter for nested enumerates and sets)
  5309. function DynArraySaveJSON(const Value; TypeInfo: pointer;
  5310. EnumSetsAsText: boolean=false): RawUTF8; overload;
  5311. {$ifdef HASINLINE}inline;{$endif}
  5312. /// serialize a dynamic array content, supplied as raw binary, as JSON
  5313. // - Value shall be set to the source dynamic array field
  5314. // - is just a wrapper around TTextWriter.AddDynArrayJSON(), creating
  5315. // a temporary TDynArray wrapper on the stack
  5316. // - to be used e.g. for custom record JSON serialization, within a
  5317. // TDynArrayJSONCustomWriter callback or RegisterCustomJSONSerializerFromText()
  5318. function DynArraySaveJSON(TypeInfo: pointer; const BlobValue: RawByteString): RawUTF8;
  5319. overload;
  5320. /// compute a dynamic array element information
  5321. // - will raise an exception if the supplied RTTI is not a dynamic array
  5322. // - will return the element type name and set ElemTypeInfo otherwise
  5323. // - if there is no element type information, an approximative element type name
  5324. // will be returned (e.g. 'byte' for an array of 1 byte items), and ElemTypeInfo
  5325. // will be set to nil
  5326. // - this low-level function is used e.g. by mORMotWrappers unit
  5327. function DynArrayElementTypeName(TypeInfo: pointer; ElemTypeInfo: PPointer=nil): RawUTF8;
  5328. /// compare two "array of boolean" elements
  5329. function SortDynArrayBoolean(const A,B): integer;
  5330. /// compare two "array of byte" elements
  5331. function SortDynArrayByte(const A,B): integer;
  5332. /// compare two "array of word" elements
  5333. function SortDynArrayWord(const A,B): integer;
  5334. /// compare two "array of integer" elements
  5335. function SortDynArrayInteger(const A,B): integer;
  5336. /// compare two "array of cardinal" elements
  5337. function SortDynArrayCardinal(const A,B): integer;
  5338. /// compare two "array of Int64 or array of Currency" elements
  5339. function SortDynArrayInt64(const A,B): integer;
  5340. /// compare two "array of TObject/pointer" elements
  5341. function SortDynArrayPointer(const A,B): integer;
  5342. /// compare two "array of single" elements
  5343. function SortDynArraySingle(const A,B): integer;
  5344. /// compare two "array of double" elements
  5345. function SortDynArrayDouble(const A,B): integer;
  5346. /// compare two "array of AnsiString" elements, with case sensitivity
  5347. function SortDynArrayAnsiString(const A,B): integer;
  5348. /// compare two "array of AnsiString" elements, with no case sensitivity
  5349. function SortDynArrayAnsiStringI(const A,B): integer;
  5350. /// compare two "array of PUTF8Char/PAnsiChar" elements, with case sensitivity
  5351. function SortDynArrayPUTF8Char(const A,B): integer;
  5352. /// compare two "array of PUTF8Char/PAnsiChar" elements, with no case sensitivity
  5353. function SortDynArrayPUTF8CharI(const A,B): integer;
  5354. /// compare two "array of WideString/UnicodeString" elements, with case sensitivity
  5355. function SortDynArrayUnicodeString(const A,B): integer;
  5356. /// compare two "array of WideString/UnicodeString" elements, with no case sensitivity
  5357. function SortDynArrayUnicodeStringI(const A,B): integer;
  5358. /// compare two "array of generic string" elements, with case sensitivity
  5359. // - the expected string type is the generic VCL string
  5360. function SortDynArrayString(const A,B): integer;
  5361. /// compare two "array of generic string" elements, with no case sensitivity
  5362. // - the expected string type is the generic VCL string
  5363. function SortDynArrayStringI(const A,B): integer;
  5364. {$ifndef NOVARIANTS}
  5365. /// compare two "array of variant" elements, with case sensitivity
  5366. function SortDynArrayVariant(const A,B): integer;
  5367. /// compare two "array of variant" elements, with no case sensitivity
  5368. function SortDynArrayVariantI(const A,B): integer;
  5369. /// compare two "array of variant" elements, with or without case sensitivity
  5370. function SortDynArrayVariantComp(const A,B: TVarData; caseInsensitive: boolean): integer;
  5371. {$endif NOVARIANTS}
  5372. /// hash one AnsiString content with the suppplied Hasher() function
  5373. function HashAnsiString(const Elem; Hasher: THasher): cardinal;
  5374. /// case-insensitive hash one AnsiString content with the suppplied Hasher() function
  5375. function HashAnsiStringI(const Elem; Hasher: THasher): cardinal;
  5376. /// hash one SynUnicode content with the suppplied Hasher() function
  5377. // - work with WideString for all Delphi versions, or UnicodeString in Delphi 2009+
  5378. function HashSynUnicode(const Elem; Hasher: THasher): cardinal;
  5379. /// case-insensitive hash one SynUnicode content with the suppplied Hasher() function
  5380. // - work with WideString for all Delphi versions, or UnicodeString in Delphi 2009+
  5381. function HashSynUnicodeI(const Elem; Hasher: THasher): cardinal;
  5382. /// hash one WideString content with the suppplied Hasher() function
  5383. // - work with WideString for all Delphi versions
  5384. function HashWideString(const Elem; Hasher: THasher): cardinal;
  5385. /// case-insensitive hash one WideString content with the suppplied Hasher() function
  5386. // - work with WideString for all Delphi versions
  5387. function HashWideStringI(const Elem; Hasher: THasher): cardinal;
  5388. {$ifdef UNICODE}
  5389. /// hash one UnicodeString content with the suppplied Hasher() function
  5390. // - work with UnicodeString in Delphi 2009+
  5391. function HashUnicodeString(const Elem; Hasher: THasher): cardinal;
  5392. /// case-insensitive hash one UnicodeString content with the suppplied Hasher() function
  5393. // - work with UnicodeString in Delphi 2009+
  5394. function HashUnicodeStringI(const Elem; Hasher: THasher): cardinal;
  5395. {$endif UNICODE}
  5396. {$ifndef NOVARIANTS}
  5397. /// case-sensitive hash one variant content with the suppplied Hasher() function
  5398. function HashVariant(const Elem; Hasher: THasher): cardinal;
  5399. /// case-insensitive hash one variant content with the suppplied Hasher() function
  5400. function HashVariantI(const Elem; Hasher: THasher): cardinal;
  5401. {$endif NOVARIANTS}
  5402. /// hash one PtrUInt (=NativeUInt) value with the suppplied Hasher() function
  5403. function HashPtrUInt(const Elem; Hasher: THasher): cardinal;
  5404. /// hash one Byte value - simply return the value ignore Hasher() parameter
  5405. function HashByte(const Elem; Hasher: THasher): cardinal;
  5406. /// hash one Word value - simply return the value ignore Hasher() parameter
  5407. function HashWord(const Elem; Hasher: THasher): cardinal;
  5408. /// hash one Integer value - simply return the value ignore Hasher() parameter
  5409. function HashInteger(const Elem; Hasher: THasher): cardinal;
  5410. /// hash one Cardinal value - simply return the value ignore Hasher() parameter
  5411. function HashCardinal(const Elem; Hasher: THasher): cardinal;
  5412. /// hash one Int64 value with the suppplied Hasher() function
  5413. function HashInt64(const Elem; Hasher: THasher): cardinal;
  5414. /// hash one pointer value with the suppplied Hasher() function
  5415. // - this version is not the same as HashPtrUInt, since it will always
  5416. // use the hasher function
  5417. function HashPointer(const Elem; Hasher: THasher): cardinal;
  5418. var
  5419. /// helper array to get the comparison function corresponding to a given
  5420. // standard array type
  5421. // - not to be used as such, but e.g. when inlining TDynArray methods
  5422. DYNARRAY_SORTFIRSTFIELD: array[boolean,TDynArrayKind] of TDynArraySortCompare = (
  5423. (nil, SortDynArrayBoolean, SortDynArrayByte, SortDynArrayWord,
  5424. SortDynArrayInteger, SortDynArrayCardinal, SortDynArraySingle,
  5425. SortDynArrayInt64, SortDynArrayDouble,
  5426. SortDynArrayInt64, SortDynArrayInt64, SortDynArrayDouble,
  5427. SortDynArrayAnsiString, SortDynArrayAnsiString, SortDynArrayString,
  5428. SortDynArrayAnsiString, SortDynArrayUnicodeString,
  5429. SortDynArrayUnicodeString, SortDynArrayPointer,
  5430. {$ifndef NOVARIANTS}SortDynArrayVariant,{$endif} nil),
  5431. (nil, SortDynArrayBoolean, SortDynArrayByte, SortDynArrayWord,
  5432. SortDynArrayInteger, SortDynArrayCardinal, SortDynArraySingle,
  5433. SortDynArrayInt64, SortDynArrayDouble,
  5434. SortDynArrayInt64, SortDynArrayInt64, SortDynArrayDouble,
  5435. SortDynArrayAnsiStringI, SortDynArrayAnsiStringI, SortDynArrayStringI,
  5436. SortDynArrayAnsiStringI, SortDynArrayUnicodeStringI,
  5437. SortDynArrayUnicodeStringI, SortDynArrayPointer,
  5438. {$ifndef NOVARIANTS}SortDynArrayVariantI,{$endif} nil));
  5439. /// helper array to get the hashing function corresponding to a given
  5440. // standard array type
  5441. // - not to be used as such, but e.g. when inlining TDynArray methods
  5442. DYNARRAY_HASHFIRSTFIELD: array[boolean,TDynArrayKind] of TDynArrayHashOne = (
  5443. (nil, HashByte, HashByte, HashWord, HashInteger,
  5444. HashCardinal, HashCardinal, HashInt64, HashInt64,
  5445. HashInt64, HashInt64, HashInt64,
  5446. HashAnsiString, HashAnsiString,
  5447. {$ifdef UNICODE}HashUnicodeString{$else}HashAnsiString{$endif},
  5448. HashAnsiString, HashWideString, HashSynUnicode, HashPointer,
  5449. {$ifndef NOVARIANTS}HashVariant,{$endif} nil),
  5450. (nil, HashByte, HashByte, HashWord, HashInteger,
  5451. HashCardinal, HashCardinal, HashInt64, HashInt64,
  5452. HashInt64, HashInt64, HashInt64,
  5453. HashAnsiStringI, HashAnsiStringI,
  5454. {$ifdef UNICODE}HashUnicodeStringI{$else}HashAnsiStringI{$endif},
  5455. HashAnsiStringI, HashWideStringI, HashSynUnicodeI, HashPointer,
  5456. {$ifndef NOVARIANTS}HashVariantI,{$endif} nil));
  5457. /// initialize the structure with a one-dimension dynamic array
  5458. // - the dynamic array must have been defined with its own type
  5459. // (e.g. TIntegerDynArray = array of Integer)
  5460. // - if aCountPointer is set, it will be used instead of length() to store
  5461. // the dynamic array items count - it will be much faster when adding
  5462. // elements to the array, because the dynamic array won't need to be
  5463. // resized each time - but in this case, you should use the Count property
  5464. // instead of length(array) or high(array) when accessing the data: in fact
  5465. // length(array) will store the memory size reserved, not the items count
  5466. // - if aCountPointer is set, its content will be set to 0, whatever the
  5467. // array length is, or the current aCountPointer^ value is
  5468. // - a typical usage could be:
  5469. // !var IntArray: TIntegerDynArray;
  5470. // !begin
  5471. // ! with DynArray(TypeInfo(TIntegerDynArray),IntArray) do
  5472. // ! begin
  5473. // ! (...)
  5474. // ! end;
  5475. // ! (...)
  5476. // ! DynArray(TypeInfo(TIntegerDynArray),IntArrayA).SaveTo
  5477. function DynArray(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil): TDynArray;
  5478. {$ifdef HASINLINE}inline;{$endif}
  5479. /// wrap a simple dynamic array BLOB content as stored by TDynArray.SaveTo
  5480. // - a "simple" dynamic array contains data with no reference count, e.g. byte,
  5481. // word, integer, cardinal, Int64, double or Currency
  5482. // - same as TDynArray.LoadFrom() with no memory allocation nor memory copy: so
  5483. // is much faster than creating a temporary dynamic array to load the data
  5484. // - will return nil if no or invalid data, or a pointer to the data
  5485. // array otherwise, with the items number stored in Count and the individual
  5486. // element size in ElemSize (e.g. 2 for a TWordDynArray)
  5487. function SimpleDynArrayLoadFrom(Source: PAnsiChar; aTypeInfo: pointer;
  5488. var Count, ElemSize: integer): pointer;
  5489. /// wrap an Integer dynamic array BLOB content as stored by TDynArray.SaveTo
  5490. // - same as TDynArray.LoadFrom() with no memory allocation nor memory copy: so
  5491. // is much faster than creating a temporary dynamic array to load the data
  5492. // - will return nil if no or invalid data, or a pointer to the integer
  5493. // array otherwise, with the items number stored in Count
  5494. // - a bit faster than SimpleDynArrayLoadFrom(Source,TypeInfo(TIntegerDynArray),Count)
  5495. function IntegerDynArrayLoadFrom(Source: PAnsiChar; var Count: integer): PIntegerArray;
  5496. /// search in a RawUTF8 dynamic array BLOB content as stored by TDynArray.SaveTo
  5497. // - same as search within TDynArray.LoadFrom() with no memory allocation nor
  5498. // memory copy: so is much faster
  5499. // - will return -1 if no match or invalid data, or the matched entry index
  5500. function RawUTF8DynArrayLoadFromContains(Source: PAnsiChar;
  5501. Value: PUTF8Char; ValueLen: integer; CaseSensitive: boolean): integer;
  5502. var
  5503. /// mORMot.pas will registry here its T*ObjArray serialization process
  5504. DynArrayIsObjArray: function(aDynArrayTypeInfo: Pointer): boolean;
  5505. { ****************** text buffer and JSON functions and classes ************ }
  5506. const
  5507. /// maximum number of fields in a database Table
  5508. // - is included in SynCommons so that all DB-related work will be able to
  5509. // share the same low-level types and functions (e.g. TSQLFieldBits,
  5510. // TJSONWriter, TSynTableStatement, TSynTable)
  5511. // - default is 64, but can be set to any value (64, 128, 192 and 256 optimized)
  5512. // - this constant is used internaly to optimize memory usage in the
  5513. // generated asm code, and statically allocate some arrays for better speed
  5514. // - note that due to Delphi compiler restriction, 256 is the maximum value
  5515. // (this is the maximum number of items in a Delphi set)
  5516. MAX_SQLFIELDS = 64;
  5517. /// sometimes, the ID field is included in a bits set
  5518. MAX_SQLFIELDS_INCLUDINGID = MAX_SQLFIELDS+1;
  5519. /// UTF-8 encoded \uFFF0 special code to mark Base64 binary content in JSON
  5520. // - Unicode special char U+FFF0 is UTF-8 encoded as EF BF B0 bytes
  5521. // - as generated by BinToBase64WithMagic() functions, and expected by
  5522. // SQLParamContent() and ExtractInlineParameters() functions
  5523. // - used e.g. when transmitting TDynArray.SaveTo() content
  5524. JSON_BASE64_MAGIC = $b0bfef;
  5525. /// '"' + UTF-8 encoded \uFFF0 special code to mark Base64 binary in JSON
  5526. JSON_BASE64_MAGIC_QUOTE = ord('"')+cardinal(JSON_BASE64_MAGIC) shl 8;
  5527. /// '"' + UTF-8 encoded \uFFF0 special code to mark Base64 binary in JSON
  5528. // - defined as a cardinal variable to be used as:
  5529. // ! AddNoJSONEscape(@JSON_BASE64_MAGIC_QUOTE_VAR,4);
  5530. JSON_BASE64_MAGIC_QUOTE_VAR: cardinal = JSON_BASE64_MAGIC_QUOTE;
  5531. /// UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
  5532. // - e.g. '"\uFFF12012-05-04"' pattern
  5533. // - Unicode special char U+FFF1 is UTF-8 encoded as EF BF B1 bytes
  5534. // - as generated by DateToSQL/DateTimeToSQL/TimeLogToSQL functions, and
  5535. // expected by SQLParamContent() and ExtractInlineParameters() functions
  5536. JSON_SQLDATE_MAGIC = $b1bfef;
  5537. /// '"' + UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
  5538. JSON_SQLDATE_MAGIC_QUOTE = ord('"')+cardinal(JSON_SQLDATE_MAGIC) shl 8;
  5539. ///'"' + UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
  5540. // - defined as a cardinal variable to be used as:
  5541. // ! AddNoJSONEscape(@JSON_SQLDATE_MAGIC_QUOTE_VAR,4);
  5542. JSON_SQLDATE_MAGIC_QUOTE_VAR: cardinal = JSON_SQLDATE_MAGIC_QUOTE;
  5543. type
  5544. /// handled field/parameter/column types for abstract database access
  5545. // - this will map JSON-compatible low-level database-level access types, not
  5546. // high-level Delphi types as TSQLFieldType defined in mORMot.pas
  5547. // - it does not map either all potential types as defined in DB.pas (which
  5548. // are there for compatibility with old RDBMS, and are not abstract enough)
  5549. // - those types can be mapped to standard SQLite3 generic types, i.e.
  5550. // NULL, INTEGER, REAL, TEXT, BLOB (with the addition of a ftCurrency and
  5551. // ftDate type, for better support of most DB engines)
  5552. // see @http://www.sqlite.org/datatype3.html
  5553. // - the only string type handled here uses UTF-8 encoding (implemented
  5554. // using our RawUTF8 type), for cross-Delphi true Unicode process
  5555. TSQLDBFieldType =
  5556. (ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, ftDate, ftUTF8, ftBlob);
  5557. /// set of field/parameter/column types for abstract database access
  5558. TSQLDBFieldTypes = set of TSQLDBFieldType;
  5559. /// array of field/parameter/column types for abstract database access
  5560. TSQLDBFieldTypeDynArray = array of TSQLDBFieldType;
  5561. /// array of field/parameter/column types for abstract database access
  5562. // - this array as a fixed size, ready to handle up to MAX_SQLFIELDS items
  5563. TSQLDBFieldTypeArray = array[0..MAX_SQLFIELDS-1] of TSQLDBFieldType;
  5564. /// memory structure used for database values by reference storage
  5565. // - used mainly by SynDB, mORMot, mORMotDB and mORMotSQLite3 units
  5566. // - defines only TSQLDBFieldType data types (similar to those handled by
  5567. // SQLite3, with the addition of ftCurrency and ftDate)
  5568. // - cleaner/lighter dedicated type than TValue or variant/TVarData, strong
  5569. // enough to be marshalled as JSON content
  5570. // - variable-length data (e.g. UTF-8 text or binary BLOB) are never stored
  5571. // within this record, but VText/VBlob will point to an external (temporary)
  5572. // memory buffer
  5573. // - date/time is stored as ISO-8601 text, and currency as double or
  5574. // BCD in most databases
  5575. TSQLVar = record
  5576. case VType: TSQLDBFieldType of
  5577. ftInt64: (
  5578. VInt64: Int64);
  5579. ftDouble: (
  5580. VDouble: double);
  5581. ftDate: (
  5582. VDateTime: TDateTime);
  5583. ftCurrency: (
  5584. VCurrency: Currency);
  5585. ftUTF8: (
  5586. VText: PUTF8Char);
  5587. ftBlob: (
  5588. VBlob: pointer;
  5589. VBlobLen: Integer)
  5590. end;
  5591. /// dynamic array of database values by reference storage
  5592. TSQLVarDynArray = array of TSQLVar;
  5593. /// used to store bit set for all available fields in a Table
  5594. // - with current MAX_SQLFIELDS value, 256 bits uses 64 bytes of memory
  5595. // - see also IsZero() and IsEqual() functions
  5596. // - you can also use ALL_FIELDS as defined in mORMot.pas
  5597. TSQLFieldBits = set of 0..MAX_SQLFIELDS-1;
  5598. /// used to store a field index in a Table
  5599. // - note that -1 is commonly used for the ID/RowID field so the values should
  5600. // be signed
  5601. // - even if ShortInt (-128..127) may have been enough, we define a 16 bit
  5602. // safe unsigned integer to let the source compile with Delphi 5
  5603. TSQLFieldIndex = SmallInt; // -32768..32767
  5604. /// used to store field indexes in a Table
  5605. // - same as TSQLFieldBits, but allowing to store the proper order
  5606. TSQLFieldIndexDynArray = array of TSQLFieldIndex;
  5607. /// points to a bit set used for all available fields in a Table
  5608. PSQLFieldBits = ^TSQLFieldBits;
  5609. /// generic parameter types, as recognized by SQLParamContent() and
  5610. // ExtractInlineParameters() functions
  5611. TSQLParamType = (sptUnknown, sptInteger, sptFloat, sptText, sptBlob, sptDateTime);
  5612. /// array of parameter types, as recognized by SQLParamContent() and
  5613. // ExtractInlineParameters() functions
  5614. TSQLParamTypeDynArray = array of TSQLParamType;
  5615. TTextWriter = class;
  5616. /// method prototype for custom serialization of a dynamic array item
  5617. // - each element of the dynamic array will be called as aValue parameter
  5618. // of this callback
  5619. // - can be used also at record level, if the record has a type information
  5620. // (i.e. shall contain a managed type within its fields)
  5621. // - to be used with TTextWriter.RegisterCustomJSONSerializer() method
  5622. // - note that the generated JSON content will be appended after a '[' and
  5623. // before a ']' as a normal JSON arrray, but each item can be any JSON
  5624. // structure (i.e. a number, a string, but also an object or an array)
  5625. // - implementation code could call aWriter.Add/AddJSONEscapeString...
  5626. // - implementation code shall follow the same exact format for the
  5627. // associated TDynArrayJSONCustomReader callback
  5628. TDynArrayJSONCustomWriter = procedure(const aWriter: TTextWriter; const aValue) of object;
  5629. /// method prototype for custom unserialization of a dynamic array item
  5630. // - each element of the dynamic array will be called as aValue parameter
  5631. // of this callback
  5632. // - can be used also at record level, if the record has a type information
  5633. // (i.e. shall contain a managed type within its fields)
  5634. // - to be used with TTextWriter.RegisterCustomJSONSerializer() method
  5635. // - implementation code could call e.g. GetJSONField() low-level function, and
  5636. // returns a pointer to the last handled element of the JSON input buffer,
  5637. // as such (aka EndOfBuffer variable as expected by GetJSONField):
  5638. // ! var V: TFV absolute aValue;
  5639. // ! begin
  5640. // ! (...)
  5641. // ! V.Detailed := UTF8ToString(GetJSONField(P,P));
  5642. // ! if P=nil then
  5643. // ! exit;
  5644. // ! aValid := true;
  5645. // ! result := P; // ',' or ']' for last item of array
  5646. // ! end;
  5647. // - implementation code shall follow the same exact format for the
  5648. // associated TDynArrayJSONCustomWriter callback
  5649. TDynArrayJSONCustomReader = function(P: PUTF8Char; var aValue;
  5650. out aValid: Boolean): PUTF8Char of object;
  5651. /// the kind of variables handled by TJSONCustomParser
  5652. // - the last item should be ptCustom, for non simple types
  5653. TJSONCustomParserRTTIType = (
  5654. ptArray, ptBoolean, ptByte, ptCardinal, ptCurrency, ptDouble, ptExtended,
  5655. ptInt64, ptInteger, ptRawByteString, ptRawJSON, ptRawUTF8, ptRecord,
  5656. ptSingle, ptString, ptSynUnicode, ptDateTime, ptGUID, ptID, ptTimeLog,
  5657. {$ifndef NOVARIANTS}ptVariant, {$endif}
  5658. ptWideString, ptWord, ptCustom);
  5659. /// how TJSONCustomParser would serialize/unserialize JSON content
  5660. TJSONCustomParserSerializationOption = (
  5661. soReadIgnoreUnknownFields, soWriteHumanReadable,
  5662. soCustomVariantCopiedByReference);
  5663. /// how TJSONCustomParser would serialize/unserialize JSON content
  5664. // - by default, during reading any unexpected field will stop and fail the
  5665. // process - if soReadIgnoreUnknownFields is defined, such properties will
  5666. // be ignored (can be very handy when parsing JSON from a remote service)
  5667. // - by default, JSON content will be written in its compact standard form,
  5668. // ready to be parsed by any client - you can specify soWriteHumanReadable
  5669. // so that some line feeds and indentation will make the content more readable
  5670. // - by default, internal TDocVariant variants will be copied by-value from
  5671. // one instance to another, to ensure proper safety - but it may be too slow:
  5672. // if you set soCustomVariantCopiedByReference, any internal
  5673. // TDocVariantData.VValue/VName instances will be copied by-reference,
  5674. // to avoid memory allocations, BUT it may break internal process if you change
  5675. // some values in place (since VValue/VName and VCount won't match) - as such,
  5676. // if you set this option, ensure that you use the content as read-only
  5677. TJSONCustomParserSerializationOptions = set of TJSONCustomParserSerializationOption;
  5678. TJSONCustomParserRTTI = class;
  5679. /// an array of RTTI properties information
  5680. // - we use dynamic arrays, since all the information is static and we
  5681. // do not need to remove any RTTI information
  5682. TJSONCustomParserRTTIs = array of TJSONCustomParserRTTI;
  5683. /// used to store additional RTTI in TJSONCustomParser internal structures
  5684. TJSONCustomParserRTTI = class
  5685. protected
  5686. fPropertyName: RawUTF8;
  5687. fFullPropertyName: RawUTF8;
  5688. fPropertyType: TJSONCustomParserRTTIType;
  5689. fCustomTypeName: RawUTF8;
  5690. fNestedProperty: TJSONCustomParserRTTIs;
  5691. fDataSize: integer;
  5692. fNestedDataSize: integer;
  5693. procedure ComputeDataSizeAfterAdd; virtual;
  5694. procedure ComputeNestedDataSize;
  5695. procedure ComputeFullPropertyName;
  5696. procedure FinalizeNestedRecord(var Data: PByte);
  5697. procedure FinalizeNestedArray(var Data: PtrUInt);
  5698. procedure AllocateNestedArray(var Data: PtrUInt; NewLength: integer);
  5699. procedure ReAllocateNestedArray(var Data: PtrUInt; NewLength: integer);
  5700. public
  5701. /// initialize the instance
  5702. constructor Create(const aPropertyName: RawUTF8;
  5703. aPropertyType: TJSONCustomParserRTTIType);
  5704. /// initialize an instance from the RTTI type information
  5705. // - will return an instance of this class of any inherited class
  5706. class function CreateFromRTTI(const PropertyName: RawUTF8;
  5707. Info: pointer; ItemSize: integer): TJSONCustomParserRTTI;
  5708. /// create an instance from a specified type name
  5709. // - will return an instance of this class of any inherited class
  5710. class function CreateFromTypeName(const aPropertyName,
  5711. aCustomRecordTypeName: RawUTF8): TJSONCustomParserRTTI;
  5712. /// recognize a simple type from a supplied type name
  5713. // - will return ptCustom for any unknown type
  5714. class function TypeNameToSimpleRTTIType(
  5715. const TypeName: RawUTF8): TJSONCustomParserRTTIType; overload;
  5716. /// recognize a simple type from a supplied type name
  5717. // - will return ptCustom for any unknown type
  5718. class function TypeNameToSimpleRTTIType(
  5719. TypeName: PShortString): TJSONCustomParserRTTIType; overload;
  5720. /// recognize a simple type from a supplied type name
  5721. // - will return ptCustom for any unknown type
  5722. class function TypeNameToSimpleRTTIType(TypeName: PUTF8Char; TypeNameLen: Integer;
  5723. var ItemTypeName: RawUTF8): TJSONCustomParserRTTIType; overload;
  5724. /// recognize a simple type from a supplied type information
  5725. // - to be called if TypeNameToSimpleRTTIType() did fail, i.e. return ptCustom
  5726. // - will return ptCustom for any unknown type
  5727. class function TypeInfoToSimpleRTTIType(Info: pointer; ItemSize: integer): TJSONCustomParserRTTIType;
  5728. /// unserialize some JSON content into its binary internal representation
  5729. // - on error, returns false and P should point to the faulty text input
  5730. function ReadOneLevel(var P: PUTF8Char; var Data: PByte;
  5731. Options: TJSONCustomParserSerializationOptions): boolean; virtual;
  5732. /// serialize a binary internal representation into JSON content
  5733. // - this method won't append a trailing ',' character
  5734. procedure WriteOneLevel(aWriter: TTextWriter; var P: PByte;
  5735. Options: TJSONCustomParserSerializationOptions); virtual;
  5736. /// the associated type name, e.g. for a record
  5737. property CustomTypeName: RawUTF8 read fCustomTypeName;
  5738. /// the property name
  5739. // - may be void for the Root element
  5740. // - e.g. 'SubProp'
  5741. property PropertyName: RawUTF8 read fPropertyName;
  5742. /// the property name, including all parent elements
  5743. // - may be void for the Root element
  5744. // - e.g. 'MainProp.SubProp'
  5745. property FullPropertyName: RawUTF8 read fFullPropertyName;
  5746. /// the property type
  5747. // - support only a limited set of simple types, or ptRecord for a nested
  5748. // record, or ptArray for a nested array
  5749. property PropertyType: TJSONCustomParserRTTIType read fPropertyType;
  5750. /// the nested array of properties (if any)
  5751. // - assigned only if PropertyType is [ptRecord,ptArray]
  5752. // - is either the record type of each ptArray item:
  5753. // ! SubProp: array of record ...
  5754. // - or one NestedProperty[0] entry with PropertyName='' and PropertyType
  5755. // not in [ptRecord,ptArray]:
  5756. // ! SubPropNumber: array of integer;
  5757. // ! SubPropText: array of RawUTF8;
  5758. property NestedProperty: TJSONCustomParserRTTIs read fNestedProperty;
  5759. end;
  5760. /// used to store additional RTTI as a ptCustom kind of property
  5761. TJSONCustomParserCustom = class(TJSONCustomParserRTTI)
  5762. protected
  5763. fCustomTypeInfo: pointer;
  5764. public
  5765. /// initialize the instance
  5766. constructor Create(const aPropertyName, aCustomTypeName: RawUTF8); virtual;
  5767. /// abstract method to write the instance as JSON
  5768. procedure CustomWriter(const aWriter: TTextWriter; const aValue); virtual; abstract;
  5769. /// abstract method to read the instance from JSON
  5770. // - should return nil on parsing error
  5771. function CustomReader(P: PUTF8Char; var aValue; out EndOfObject: AnsiChar): PUTF8Char; virtual; abstract;
  5772. /// release any memory used by the instance
  5773. procedure FinalizeItem(Data: Pointer); virtual;
  5774. /// the associated RTTI structure
  5775. property CustomTypeInfo: pointer read fCustomTypeInfo;
  5776. end;
  5777. /// which kind of property does TJSONCustomParserCustomSimple refer to
  5778. TJSONCustomParserCustomSimpleKnownType = (
  5779. ktNone, ktEnumeration, ktSet, ktGUID,
  5780. ktFixedArray, ktStaticArray, ktDynamicArray);
  5781. /// used to store additional RTTI for simple type as a ptCustom kind
  5782. // - this class handle currently enumerate, TGUID or static/dynamic arrays
  5783. TJSONCustomParserCustomSimple = class(TJSONCustomParserCustom)
  5784. protected
  5785. fKnownType: TJSONCustomParserCustomSimpleKnownType;
  5786. fTypeData: pointer;
  5787. fFixedSize: integer;
  5788. fNestedArray: TJSONCustomParserRTTI;
  5789. public
  5790. /// initialize the instance from the given RTTI structure
  5791. constructor Create(const aPropertyName, aCustomTypeName: RawUTF8;
  5792. aCustomType: pointer); reintroduce;
  5793. /// initialize the instance for a static array
  5794. constructor CreateFixedArray(const aPropertyName: RawUTF8;
  5795. aFixedSize: cardinal);
  5796. /// released used memory
  5797. destructor Destroy; override;
  5798. /// method to write the instance as JSON
  5799. procedure CustomWriter(const aWriter: TTextWriter; const aValue); override;
  5800. /// method to read the instance from JSON
  5801. function CustomReader(P: PUTF8Char; var aValue; out EndOfObject: AnsiChar): PUTF8Char; override;
  5802. /// which kind of simple property this instance does refer to
  5803. property KnownType: TJSONCustomParserCustomSimpleKnownType read fKnownType;
  5804. /// the element type for ktStaticArray and ktDynamicArray
  5805. property NestedArray: TJSONCustomParserRTTI read fNestedArray;
  5806. end;
  5807. /// implement a reference to a registered record type
  5808. // - i.e. ptCustom kind of property, handled by the
  5809. // TTextWriter.RegisterCustomJSONSerializer*() internal list
  5810. TJSONCustomParserCustomRecord = class(TJSONCustomParserCustom)
  5811. protected
  5812. fCustomTypeIndex: integer;
  5813. function GetJSONCustomParserRegistration: pointer;
  5814. public
  5815. { /// initialize the instance from the given RTTI name
  5816. constructor Create(const aPropertyName, aCustomTypeName: RawUTF8); overload; override; }
  5817. /// initialize the instance from the given record custom serialization index
  5818. constructor Create(const aPropertyName: RawUTF8;
  5819. aCustomTypeIndex: integer); reintroduce; overload;
  5820. /// method to write the instance as JSON
  5821. procedure CustomWriter(const aWriter: TTextWriter; const aValue); override;
  5822. /// method to read the instance from JSON
  5823. function CustomReader(P: PUTF8Char; var aValue; out EndOfObject: AnsiChar): PUTF8Char; override;
  5824. /// release any memory used by the instance
  5825. procedure FinalizeItem(Data: Pointer); override;
  5826. end;
  5827. /// how an RTTI expression is expected to finish
  5828. TJSONCustomParserRTTIExpectedEnd = (eeNothing, eeSquare, eeCurly, eeEndKeyWord);
  5829. TJSONRecordAbstract = class;
  5830. (* /// implement a reference to a unregistered record type
  5831. // - i.e. ptCustom kind of property, not handled by the
  5832. // TTextWriter.RegisterCustomJSONSerializer*() internal list
  5833. TJSONCustomParserCustomRecord = class(TJSONCustomParserCustom)
  5834. protected
  5835. fCustomRecord: TJSONRecordAbstract;
  5836. public
  5837. /// initialize the instance from the given RTTI name
  5838. constructor Create(const aPropertyName, aCustomTypeName: RawUTF8); overload; override;
  5839. /// initialize the instance from the given record custom serialization index
  5840. constructor Create(const aPropertyName: RawUTF8;
  5841. aCustomTypeIndex: integer); reintroduce; overload;
  5842. /// method to write the instance as JSON
  5843. procedure CustomWriter(const aWriter: TTextWriter; const aValue); override;
  5844. /// method to read the instance from JSON
  5845. function CustomReader(P: PUTF8Char; var aValue; out EndOfObject: AnsiChar): PUTF8Char; override;
  5846. /// release any memory used by the instance
  5847. procedure FinalizeItem(Data: Pointer); override;
  5848. end; *)
  5849. /// used to handle additional RTTI for JSON record serialization
  5850. // - this class is used to define how a record is defined, and will work
  5851. // with any version of Delphi
  5852. // - this Abstract class is not to be used as-this, but contains all
  5853. // needed information to provide CustomWriter/CustomReader methods
  5854. // - you can use e.g. TJSONRecordTextDefinition for text-based RTTI
  5855. // manual definition, or (not yet provided) a version based on Delphi 2010+
  5856. // new RTTI information
  5857. TJSONRecordAbstract = class
  5858. protected
  5859. /// internal storage of TJSONCustomParserRTTI instances
  5860. fItems: TObjectList;
  5861. fRoot: TJSONCustomParserRTTI;
  5862. fOptions: TJSONCustomParserSerializationOptions;
  5863. function AddItem(const aPropertyName: RawUTF8; aPropertyType: TJSONCustomParserRTTIType;
  5864. const aCustomRecordTypeName: RawUTF8): TJSONCustomParserRTTI;
  5865. public
  5866. /// initialize the class instance
  5867. constructor Create;
  5868. /// callback for custom JSON serialization
  5869. // - will follow the RTTI textual information as supplied to the constructor
  5870. procedure CustomWriter(const aWriter: TTextWriter; const aValue);
  5871. /// callback for custom JSON unserialization
  5872. // - will follow the RTTI textual information as supplied to the constructor
  5873. function CustomReader(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
  5874. /// release used memory
  5875. // - when created via Compute() call, instances of this class are managed
  5876. // via a GarbageCollector() global list, so you do not need to free them
  5877. destructor Destroy; override;
  5878. /// store the RTTI information of properties at root level
  5879. // - is one instance with PropertyType=ptRecord and PropertyName=''
  5880. property Root: TJSONCustomParserRTTI read fRoot;
  5881. /// how this class would serialize/unserialize JSON content
  5882. // - by default, no option is defined
  5883. // - you can set the option with the instance returned by
  5884. // TTextWriter.RegisterCustomJSONSerializerFromText() method
  5885. property Options: TJSONCustomParserSerializationOptions read fOptions write fOptions;
  5886. end;
  5887. /// used to handle JSON record serialization using RTTI
  5888. // - is able to handle any kind of record since Delphi 2010, thanks to
  5889. // enhanced RTTI
  5890. TJSONRecordRTTI = class(TJSONRecordAbstract)
  5891. protected
  5892. fRecordTypeInfo: pointer;
  5893. function AddItemFromRTTI(const PropertyName: RawUTF8;
  5894. Info: pointer; ItemSize: integer): TJSONCustomParserRTTI;
  5895. {$ifdef ISDELPHI2010}
  5896. procedure FromEnhancedRTTI(Props: TJSONCustomParserRTTI; Info: pointer);
  5897. {$endif}
  5898. public
  5899. /// initialize the instance
  5900. // - you should NOT use this constructor directly, but let e.g.
  5901. // TJSONCustomParsers.TryToGetFromRTTI() create it for you
  5902. constructor Create(aRecordTypeInfo: pointer; aRoot: TJSONCustomParserRTTI); reintroduce;
  5903. /// the low-level address of the enhanced RTTI
  5904. property RecordTypeInfo: pointer read fRecordTypeInfo;
  5905. end;
  5906. /// used to handle text-defined additional RTTI for JSON record serialization
  5907. // - is used by TTextWriter.RegisterCustomJSONSerializerFromText() method
  5908. TJSONRecordTextDefinition = class(TJSONRecordAbstract)
  5909. protected
  5910. fDefinition: RawUTF8;
  5911. procedure Parse(Props: TJSONCustomParserRTTI; var P: PUTF8Char;
  5912. PEnd: TJSONCustomParserRTTIExpectedEnd);
  5913. public
  5914. /// initialize a custom JSON serializer/unserializer from pseudo RTTI
  5915. // - you should NOT use this constructor directly, but call the FromCache()
  5916. // class function, which will use an internal definition cache
  5917. constructor Create(aRecordTypeInfo: pointer; const aDefinition: RawUTF8); reintroduce;
  5918. /// retrieve a custom cached JSON serializer/unserializer from pseudo RTTI
  5919. // - returned class instance will be cached for any further use
  5920. // - the record where the data will be stored should be defined as PACKED:
  5921. // ! type TMyRecord = packed record
  5922. // ! A,B,C: integer;
  5923. // ! D: RawUTF8;
  5924. // ! E: record; // or array of record/integer/string/...
  5925. // ! E1,E2: double;
  5926. // ! end;
  5927. // ! end;
  5928. // - only known sub types are integer, cardinal, Int64, single, double,
  5929. // currency, TDateTime, TTimeLog, RawUTF8, String, WideString, SynUnicode,
  5930. // or a nested record or dynamic array
  5931. // - RTTI textual information shall be supplied as text, with the
  5932. // same format as with a pascal record, or with some shorter variations:
  5933. // ! FromCache('A,B,C: integer; D: RawUTF8; E: record E1,E2: double; end;');
  5934. // ! FromCache('A,B,C: integer; D: RawUTF8; E: array of record E1,E2: double; end;');
  5935. // ! 'A,B,C: integer; D: RawUTF8; E: array of SynUnicode; F: array of integer'
  5936. // or a shorter alternative syntax for records and arrays:
  5937. // ! FromCache('A,B,C: integer; D: RawUTF8; E: {E1,E2: double}');
  5938. // ! FromCache('A,B,C: integer; D: RawUTF8; E: [E1,E2: double]');
  5939. // in fact ; could be ignored:
  5940. // ! FromCache('A,B,C:integer D:RawUTF8 E:{E1,E2:double}');
  5941. // ! FromCache('A,B,C:integer D:RawUTF8 E:[E1,E2:double]');
  5942. // or even : could be ignored:
  5943. // ! FromCache('A,B,C integer D RawUTF8 E{E1,E2 double}');
  5944. // ! FromCache('A,B,C integer D RawUTF8 E[E1,E2 double]');
  5945. class function FromCache(aTypeInfo: pointer;
  5946. const aDefinition: RawUTF8): TJSONRecordTextDefinition;
  5947. /// the textual definition of this RTTI information
  5948. property Definition: RawUTF8 read fDefinition;
  5949. end;
  5950. /// the available logging events, as handled by TSynLog
  5951. // - defined in SynCommons so that it may be used with TTextWriter.AddEndOfLine
  5952. // - sllInfo will log general information events
  5953. // - sllDebug will log detailed debugging information
  5954. // - sllTrace will log low-level step by step debugging information
  5955. // - sllWarning will log unexpected values (not an error)
  5956. // - sllError will log errors
  5957. // - sllEnter will log every method start
  5958. // - sllLeave will log every method exit
  5959. // - sllLastError will log the GetLastError OS message
  5960. // - sllException will log all exception raised - available since Windows XP
  5961. // - sllExceptionOS will log all OS low-level exceptions (EDivByZero,
  5962. // ERangeError, EAccessViolation...)
  5963. // - sllMemory will log memory statistics
  5964. // - sllStackTrace will log caller's stack trace (it's by default part of
  5965. // TSynLogFamily.LevelStackTrace like sllError, sllException, sllExceptionOS,
  5966. // sllLastError and sllFail)
  5967. // - sllFail was defined for TSynTestsLogged.Failed method, and can be used
  5968. // to log some customer-side assertions (may be notifications, not errors)
  5969. // - sllSQL is dedicated to trace the SQL statements
  5970. // - sllCache should be used to trace the internal caching mechanism
  5971. // - sllResult could trace the SQL results, JSON encoded
  5972. // - sllDB is dedicated to trace low-level database engine features
  5973. // - sllHTTP could be used to trace HTTP process
  5974. // - sllClient/sllServer could be used to trace some Client or Server process
  5975. // - sllServiceCall/sllServiceReturn to trace some remote service or library
  5976. // - sllUserAuth to trace user authentication (e.g. for individual requests)
  5977. // - sllCustom* items can be used for any purpose
  5978. // - sllNewRun will be written when a process opens a rotated log
  5979. // - sllDDDError will log any DDD-related low-level error information
  5980. // - sllDDDInfo will log any DDD-related low-level debugging information
  5981. // - sllMonitoring will log the statistics information (if available),
  5982. // or may be used for real-time chat among connected people to ToolsAdmin
  5983. TSynLogInfo = (
  5984. sllNone, sllInfo, sllDebug, sllTrace, sllWarning, sllError,
  5985. sllEnter, sllLeave,
  5986. sllLastError, sllException, sllExceptionOS, sllMemory, sllStackTrace,
  5987. sllFail, sllSQL, sllCache, sllResult, sllDB, sllHTTP, sllClient, sllServer,
  5988. sllServiceCall, sllServiceReturn, sllUserAuth,
  5989. sllCustom1, sllCustom2, sllCustom3, sllCustom4, sllNewRun,
  5990. sllDDDError, sllDDDInfo, sllMonitoring);
  5991. /// used to define a set of logging level abilities
  5992. // - i.e. a combination of none or several logging event
  5993. // - e.g. use LOG_VERBOSE constant to log all events, or LOG_STACKTRACE
  5994. // to log all errors and exceptions
  5995. TSynLogInfos = set of TSynLogInfo;
  5996. /// a dynamic array of logging event levels
  5997. TSynLogInfoDynArray = array of TSynLogInfo;
  5998. /// available options for TTextWriter.WriteObject() method
  5999. // - woHumanReadable will add some line feeds and indentation to the content,
  6000. // to make it more friendly to the human eye
  6001. // - woDontStoreDefault (which is set by default for WriteObject method) will
  6002. // avoid serializing properties including a default value (JSONToObject function
  6003. // will set the default values, so it may help saving some bandwidth or storage)
  6004. // - woFullExpand will generate a debugger-friendly layout, including instance
  6005. // class name, sets/enumerates as text, and reference pointer - as used by
  6006. // TSynLog and ObjectToJSONFull()
  6007. // - woStoreClassName will add a "ClassName":"TMyClass" field
  6008. // - woStorePointer will add a "Address":"0431298a" field
  6009. // - woStoreStoredFalse will write the 'stored false' properties, even
  6010. // if they are marked as such (used e.g. to persist all settings on file,
  6011. // but disallow the sensitive - password - fields be logged)
  6012. // - woHumanReadableFullSetsAsStar will store an human-readable set with
  6013. // all its enumerates items set to be stored as ["*"]
  6014. // - woHumanReadableEnumSetAsComment will add a comment at the end of the
  6015. // line, containing all available values of the enumaration or set, e.g:
  6016. // $ "Enum": "Destroying", // Idle,Started,Finished,Destroying
  6017. // - woEnumSetsAsText will store sets and enumerables as text (is also
  6018. // included in woFullExpand or woHumanReadable)
  6019. // - woDateTimeWithMagic will append the JSON_SQLDATE_MAGIC (i.e. U+FFF1)
  6020. // before the ISO-8601 encoded TDateTime value
  6021. // - woDateTimeWithZSuffix will append the Z suffix to the ISO-8601 encoded
  6022. // TDateTime value, to identify the content as strict UTC value
  6023. // - TTimeLog would be serialized as Int64, unless woTimeLogAsText is defined
  6024. // - since TSQLRecord.ID could be huge Int64 numbers, they may be truncated
  6025. // on client side, e.g. to 53-bit range in JavaScript: you could define
  6026. // woIDAsIDstr to append an additional "ID_str":"##########" field
  6027. // - by default, TSQLRawBlob properties are serialized as null, unless
  6028. // woSQLRawBlobAsBase64 is defined
  6029. // - if woHideSynPersistentPassword is set, TSynPersistentWithPassword.Password
  6030. // field will be serialized as "***" to prevent security issues (e.g. in log)
  6031. // - by default, TObjectList will set the woStoreClassName for its nested
  6032. // objects, unless woObjectListWontStoreClassName is defined
  6033. // - void strings would be serialized as "", unless woDontStoreEmptyString
  6034. // is defined so that such properties would not be written
  6035. // - all inherited properties would be serialized, unless woDontStoreInherited
  6036. // is defined, and only the topmost class level properties would be serialized
  6037. TTextWriterWriteObjectOption = (
  6038. woHumanReadable, woDontStoreDefault, woFullExpand,
  6039. woStoreClassName, woStorePointer, woStoreStoredFalse,
  6040. woHumanReadableFullSetsAsStar, woHumanReadableEnumSetAsComment,
  6041. woEnumSetsAsText, woDateTimeWithMagic, woDateTimeWithZSuffix, woTimeLogAsText,
  6042. woIDAsIDstr, woSQLRawBlobAsBase64, woHideSynPersistentPassword,
  6043. woObjectListWontStoreClassName, woDontStoreEmptyString,
  6044. woDontStoreInherited);
  6045. /// options set for TTextWriter.WriteObject() method
  6046. TTextWriterWriteObjectOptions = set of TTextWriterWriteObjectOption;
  6047. /// callback used to echo each line of TTextWriter class
  6048. // - should return TRUE on sucess, FALSE if the log was not echoed: but
  6049. // TSynLog will continue logging, even if this event returned FALSE
  6050. TOnTextWriterEcho = function(Sender: TTextWriter; Level: TSynLogInfo;
  6051. const Text: RawUTF8): boolean of object;
  6052. /// callback used by TTextWriter.WriteObject to customize class instance
  6053. // serialization
  6054. // - should return TRUE if the supplied property has been written (including
  6055. // the property name and the ending ',' character), and doesn't need to be
  6056. // processed with the default RTTI-based serializer
  6057. TOnTextWriterObjectProp = function(Sender: TTextWriter; Value: TObject;
  6058. PropInfo: pointer; Options: TTextWriterWriteObjectOptions): boolean of object;
  6059. /// class of our simple writer to a Stream, specialized for the TEXT format
  6060. TTextWriterClass = class of TTextWriter;
  6061. /// the available JSON format, for TTextWriter.AddJSONReformat() and its
  6062. // JSONBufferReformat() and JSONReformat() wrappers
  6063. // - jsonCompact is the default machine-friendly single-line layout
  6064. // - jsonHumanReadable will add line feeds and indentation, for a more
  6065. // human-friendly result
  6066. // - jsonUnquotedPropName will emit the jsonHumanReadable layout, but
  6067. // with all property names being quoted only if necessary: this format
  6068. // could be used e.g. for configuration files - this format, similar to the
  6069. // one used in the MongoDB extended syntax, is not JSON compatible: do not
  6070. // use it e.g. with AJAX clients, but is would be handled as expected by all
  6071. // our units as valid JSON input, without previous correction
  6072. // - jsonUnquotedPropNameCompact will emit single-line layout with unquoted
  6073. // property names
  6074. TTextWriterJSONFormat = (
  6075. jsonCompact, jsonHumanReadable,
  6076. jsonUnquotedPropName, jsonUnquotedPropNameCompact);
  6077. /// the potential places were TTextWriter.HtmlEscape should process
  6078. // proper HTML string escaping
  6079. // $ < > & " -> &lt; &gt; &amp; &quote;
  6080. // by default (hfAnyWhere)
  6081. // $ < > & -> &lt; &gt; &amp;
  6082. // outside HTML attributes (hfOutsideAttributes)
  6083. // $ & " -> &amp; &quote;
  6084. // within HTML attributes (hfWithinAttributes)
  6085. TTextWriterHTMLFormat = (
  6086. hfAnyWhere, hfOutsideAttributes, hfWithinAttributes);
  6087. /// available global options for a TTextWriter instance
  6088. // - TTextWriter.WriteObject() method behavior would be set via their own
  6089. // TTextWriterWriteObjectOptions, and work in conjunction with those settings
  6090. // - twoStreamIsOwned would be set if the associated TStream is owned by
  6091. // the TTextWriter instance
  6092. // - twoFlushToStreamNoAutoResize would forbid FlushToStream to resize the
  6093. // internal memory buffer when it appears undersized - FlushFinal will set it
  6094. // before calling a last FlushToStream
  6095. // - by default, custom serializers defined via RegisterCustomJSONSerializer()
  6096. // would let AddRecordJSON() and AddDynArrayJSON() write enumerates and sets
  6097. // as integer numbers, unless twoEnumSetsAsTextInRecord is set
  6098. // - variants and nested objects would be serialized with their default
  6099. // JSON serialization options, unless twoForceJSONExtended or
  6100. // twoForceJSONStandard is defined
  6101. // - when enumerates and sets are serialized as text into JSON, you may force
  6102. // the identifiers to be left-trimed for all their lowercase characters
  6103. // (e.g. sllError -> 'Error') by setting twoTrimLeftEnumSets: this option
  6104. // would default to the global TTextWriter.SetDefaultEnumTrim setting
  6105. // - twoEndOfLineCRLF would reflect the TTextWriter.EndOfLineCRLF property
  6106. TTextWriterOption = (
  6107. twoStreamIsOwned,
  6108. twoFlushToStreamNoAutoResize,
  6109. twoEnumSetsAsTextInRecord,
  6110. twoForceJSONExtended,
  6111. twoForceJSONStandard,
  6112. twoTrimLeftEnumSets,
  6113. twoEndOfLineCRLF);
  6114. /// options set for a TTextWriter instance
  6115. // - allows to override e.g. AddRecordJSON() and AddDynArrayJSON() behavior;
  6116. // or set global process customization for a TTextWriter
  6117. TTextWriterOptions = set of TTextWriterOption;
  6118. /// simple writer to a Stream, specialized for the TEXT format
  6119. // - use an internal buffer, faster than string+string
  6120. // - some dedicated methods is able to encode any data with JSON escape
  6121. TTextWriter = class
  6122. protected
  6123. B, BEnd: PUTF8Char;
  6124. fStream: TStream;
  6125. fInitialStreamPosition: cardinal;
  6126. fTotalFileSize: cardinal;
  6127. fCustomOptions: TTextWriterOptions;
  6128. // internal temporary buffer
  6129. fTempBufSize: Integer;
  6130. fTempBuf: PUTF8Char;
  6131. fHumanReadableLevel: integer;
  6132. fEchoBuf: RawUTF8;
  6133. fEchoStart: integer;
  6134. fEchos: array of TOnTextWriterEcho;
  6135. fOnWriteObject: TOnTextWriterObjectProp;
  6136. /// used by WriteObjectAsString/AddDynArrayJSONAsString methods
  6137. fInternalJSONWriter: TTextWriter;
  6138. function GetLength: cardinal;
  6139. procedure SetStream(aStream: TStream);
  6140. function EchoFlush: integer;
  6141. procedure InternalAddFixedAnsi(Source: PAnsiChar; SourceChars: Cardinal;
  6142. const AnsiToWide: TWordDynArray; Escape: TTextWriterKind);
  6143. function GetEndOfLineCRLF: boolean;
  6144. {$ifdef HASINLINE}inline;{$endif}
  6145. procedure SetEndOfLineCRLF(aEndOfLineCRLF: boolean);
  6146. public
  6147. /// the data will be written to the specified Stream
  6148. // - aStream may be nil: in this case, it MUST be set before using any
  6149. // Add*() method
  6150. // - default internal buffer size if 8192
  6151. constructor Create(aStream: TStream; aBufSize: integer=8192);
  6152. /// the data will be written to an internal TRawByteStringStream
  6153. // - TRawByteStringStream.DataString method will be used by TTextWriter.Text
  6154. // to retrieve directly the content without any data move nor allocation
  6155. // - default internal buffer size if 4096 (enough for most JSON objects)
  6156. constructor CreateOwnedStream(aBufSize: integer=4096);
  6157. /// the data will be written to an external file
  6158. // - you should call explicitly FlushFinal or FlushToStream to write
  6159. // any pending data to the file
  6160. constructor CreateOwnedFileStream(const aFileName: TFileName; aBufSize: integer=8192);
  6161. /// release all internal structures
  6162. // - e.g. free fStream if the instance was owned by this class
  6163. destructor Destroy; override;
  6164. /// you can use this method to override the default JSON serialization class
  6165. // - if only SynCommons.pas is used, it will be TTextWriter
  6166. // - but mORMot.pas will call it to use the TJSONSerializer instead, which
  6167. // is able to serialize any class as JSON
  6168. class procedure SetDefaultJSONClass(aClass: TTextWriterClass);
  6169. /// allow to override the default JSON serialization of enumerations and
  6170. // sets as text, which would write the whole identifier (e.g. 'sllError')
  6171. // - calling SetDefaultEnumTrim(true) would force the enumerations to
  6172. // be trimmed for any lower case char, e.g. sllError -> 'Error'
  6173. // - this is global to the current process, and should be use mainly for
  6174. // compatibility purposes for the whole process
  6175. // - you may change the default behavior by setting twoTrimLeftEnumSets
  6176. // in the TTextWriter.CustomOptions property of a given serializer
  6177. // - note that unserialization process would recognize both formats
  6178. class procedure SetDefaultEnumTrim(aShouldTrimEnumsAsText: boolean);
  6179. /// retrieve the data as a string
  6180. function Text: RawUTF8;
  6181. {$ifdef HASINLINE}inline;{$endif}
  6182. /// retrieve the data as a string
  6183. // - will avoid creation of a temporary RawUTF8 variable as for Text function
  6184. procedure SetText(var result: RawUTF8);
  6185. /// set the internal stream content with the supplied UTF-8 text
  6186. procedure ForceContent(const text: RawUTF8);
  6187. /// write pending data to the Stream, with automatic buffer resizal
  6188. // - you should not have to call FlushToStream in most cases, but FlushFinal
  6189. // at the end of the process, just before using the resulting Stream
  6190. // - FlushToStream may be used to force immediate writing of the internal
  6191. // memory buffer to the destination Stream
  6192. // - you can set FlushToStreamNoAutoResize=true or call FlushFinal if you
  6193. // do not want the automatic memory buffer resizal to take place
  6194. procedure FlushToStream; virtual;
  6195. /// write pending data to the Stream, without automatic buffer resizal
  6196. // - will append the internal memory buffer to the Stream
  6197. // - in short, FlushToStream may be called during the adding process, and
  6198. // FlushFinal at the end of the process, just before using the resulting Stream
  6199. // - if you don't call FlushToStream or FlushFinal, some pending characters
  6200. // may not be copied to the Stream: you should call it before using the Stream
  6201. procedure FlushFinal;
  6202. /// gives access to an internal temporary TTextWriter
  6203. // - may be used to escape some JSON espaced value (i.e. escape it twice),
  6204. // in conjunction with AddJSONEscape(Source: TTextWriter)
  6205. function InternalJSONWriter: TTextWriter;
  6206. /// add a callback to echo each line written by this class
  6207. // - this class expects AddEndOfLine to mark the end of each line
  6208. procedure EchoAdd(const aEcho: TOnTextWriterEcho);
  6209. /// remove a callback to echo each line written by this class
  6210. // - event should have been previously registered by a EchoAdd() call
  6211. procedure EchoRemove(const aEcho: TOnTextWriterEcho);
  6212. /// reset the internal buffer used for echoing content
  6213. procedure EchoReset;
  6214. /// append one ASCII char to the buffer
  6215. procedure Add(c: AnsiChar); overload;
  6216. {$ifdef HASINLINE}inline;{$endif}
  6217. /// append two chars to the buffer
  6218. procedure Add(c1,c2: AnsiChar); overload;
  6219. {$ifdef HASINLINE}inline;{$endif}
  6220. {$ifndef CPU64} // already implemented by Add(Value: PtrInt) method
  6221. /// append a 64 bit signed Integer Value as text
  6222. procedure Add(Value: Int64); overload;
  6223. {$endif}
  6224. /// append a 32 bit signed Integer Value as text
  6225. procedure Add(Value: PtrInt); overload;
  6226. /// append a boolean Value as text
  6227. // - write either 'true' or 'false'
  6228. procedure Add(Value: boolean); overload;
  6229. /// append a Currency from its Int64 in-memory representation
  6230. procedure AddCurr64(const Value: Int64); overload;
  6231. /// append a Currency from its Int64 in-memory representation
  6232. procedure AddCurr64(const Value: currency); overload;
  6233. /// append a TTimeLog value, expanded as Iso-8601 encoded text
  6234. procedure AddTimeLog(Value: PInt64);
  6235. /// append a TDateTime value, expanded as Iso-8601 encoded text
  6236. procedure AddDateTime(Value: PDateTime; FirstChar: AnsiChar='T'; QuoteChar: AnsiChar=#0); overload;
  6237. /// append a TDateTime value, expanded as Iso-8601 encoded text
  6238. procedure AddDateTime(const Value: TDateTime); overload;
  6239. /// append an Unsigned Integer Value as a String
  6240. procedure AddU(Value: cardinal);
  6241. /// append a GUID value, encoded as text without any {}
  6242. // - will store e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
  6243. procedure Add(const guid: TGUID); overload;
  6244. /// append a floating-point Value as a String
  6245. procedure AddDouble(Value: double);
  6246. /// append a floating-point Value as a String
  6247. procedure AddSingle(Value: single);
  6248. /// append a floating-point Value as a String
  6249. procedure Add(Value: Extended; precision: integer); overload;
  6250. /// append a floating-point text buffer
  6251. // - will correct on the fly '.5' -> '0.5' and '-.5' -> '-0.5'
  6252. procedure AddFloatStr(P: PUTF8Char);
  6253. /// append strings or integers with a specified format
  6254. // - % = #37 marks a string, integer, floating-point, or class parameter
  6255. // to be appended as text (e.g. class name)
  6256. // - if StringEscape is false (by default), the text won't be escaped before
  6257. // adding; but if set to true text will be JSON escaped at writing
  6258. // - note that due to a limitation of the "array of const" format, cardinal
  6259. // values should be type-casted to Int64() - otherwise the integer mapped
  6260. // value will be transmitted, therefore wrongly
  6261. // - CR = #13 writes CR+LF chars (i.e. if you write #13 in the Format
  6262. // string, it will output #13#10 chars)
  6263. {$ifdef OLDTEXTWRITERFORMAT}
  6264. // - $ = #36 indicates an integer to be written with 2 digits and a comma
  6265. // - £ = #163 indicates an integer to be written with 4 digits and a comma
  6266. // - µ = #181 indicates an integer to be written with 3 digits without any comma
  6267. // - ¤ = #164 indicates CR+LF chars
  6268. // - § = #167 indicates to trim last comma
  6269. // - | = #124 will write the next char e.g. Add('%|$',[10]) will write '10$'
  6270. // - since some of this characters above are > #127, they are not UTF-8
  6271. // ready, so we expect the input format to be WinAnsi, i.e. mostly English
  6272. // text (with chars < #128) with some values to be inserted inside
  6273. {$endif}
  6274. procedure Add(const Format: RawUTF8; const Values: array of const;
  6275. Escape: TTextWriterKind=twNone); overload;
  6276. /// append some values at once
  6277. // - text values (e.g. RawUTF8) will be escaped as JSON
  6278. procedure Add(const Values: array of const); overload;
  6279. /// append CR+LF (#13#10) chars
  6280. // - this method won't call EchoAdd() registered events - use AddEndOfLine()
  6281. // method instead
  6282. procedure AddCR;
  6283. /// mark an end of line, ready to be "echoed" to registered listeners
  6284. // - append a CR (#13) char or CR+LF (#13#10) chars to the buffer, depending
  6285. // on the EndOfLineCRLF property value (default is CR, to minimize storage)
  6286. // - any callback registered via EchoAdd() will monitor this line
  6287. // - used e.g. by TSynLog for console output, as stated by Level parameter
  6288. procedure AddEndOfLine(aLevel: TSynLogInfo=sllNone);
  6289. /// append CR+LF (#13#10) chars and #9 indentation
  6290. // - indentation depth is defined by fHumanReadableLevel protected field
  6291. procedure AddCRAndIndent;
  6292. /// write the same character multiple times
  6293. procedure AddChars(aChar: AnsiChar; aCount: integer);
  6294. /// append an Integer Value as a 2 digits String with comma
  6295. procedure Add2(Value: integer);
  6296. /// append the current UTC date and time, in a log-friendly format
  6297. // - e.g. append '20110325 19241502 '
  6298. // - you may set LocalTime=TRUE to write the local date and time instead
  6299. // - this method is very fast, and avoid most calculation or API calls
  6300. procedure AddCurrentLogTime(LocalTime: boolean=false; Use16msCache: boolean=true);
  6301. /// append a time period, specified in micro seconds
  6302. procedure AddMicroSec(MS: cardinal);
  6303. /// append an Integer Value as a 4 digits String with comma
  6304. procedure Add4(Value: integer);
  6305. /// append an Integer Value as a 3 digits String without any added comma
  6306. procedure Add3(Value: integer);
  6307. /// append a line of text with CR+LF at the end
  6308. procedure AddLine(const Text: shortstring);
  6309. /// append an UTF-8 String, with no JSON escaping
  6310. procedure AddString(const Text: RawUTF8);
  6311. {$ifdef HASINLINE}inline;{$endif}
  6312. /// append several UTF-8 strings
  6313. procedure AddStrings(const Text: array of RawUTF8); overload;
  6314. /// append an UTF-8 string several times
  6315. procedure AddStrings(const Text: RawUTF8; count: integer); overload;
  6316. /// append a ShortString
  6317. procedure AddShort(const Text: ShortString);
  6318. {$ifdef HASINLINE}inline;{$endif}
  6319. /// append a sub-part of an UTF-8 String
  6320. // - emulates AddString(copy(Text,start,len))
  6321. procedure AddStringCopy(const Text: RawUTF8; start,len: integer);
  6322. /// append after trim first lowercase chars ('otDone' will add 'Done' e.g.)
  6323. procedure AddTrimLeftLowerCase(Text: PShortString);
  6324. /// append a ShortString property name, as '"PropName":'
  6325. // - PropName content should not need to be JSON escaped (e.g. no " within,
  6326. // and only ASCII 7-bit characters)
  6327. // - if twoForceJSONExtended is defined in CustomOptions, it would append
  6328. // 'PropName:' without the double quotes
  6329. procedure AddPropName(const PropName: ShortString);
  6330. /// append a RawUTF8 property name, as '"FieldName":'
  6331. // - FieldName content should not need to be JSON escaped (e.g. no " within)
  6332. procedure AddFieldName(const FieldName: RawUTF8); overload;
  6333. {$ifdef HASINLINE}inline;{$endif}
  6334. /// append a UTF8-encoded property name, as '"FieldName":'
  6335. // - FieldName content should not need to be JSON escaped (e.g. no " within)
  6336. procedure AddFieldName(FieldName: PUTF8Char; FieldNameLen: integer); overload;
  6337. /// append the class name of an Object instance as text
  6338. // - aClass must be not nil
  6339. procedure AddClassName(aClass: TClass);
  6340. /// append an Instance name and pointer, as '"TObjectList(00425E68)"'+SepChar
  6341. // - Instance must be not nil
  6342. procedure AddInstanceName(Instance: TObject; SepChar: AnsiChar);
  6343. /// append an Instance name and pointer, as 'TObjectList(00425E68)'+SepChar
  6344. // - Instance must be not nil
  6345. // - overriden version in TJSONSerializer would implement IncludeUnitName
  6346. procedure AddInstancePointer(Instance: TObject; SepChar: AnsiChar;
  6347. IncludeUnitName: boolean); virtual;
  6348. /// append a quoted string as JSON, with in-place decoding
  6349. // - if QuotedString does not start with ' or ", it will written directly
  6350. // (i.e. expects to be a number, or null/true/false constants)
  6351. // - as used e.g. by TJSONObjectDecoder.EncodeAsJSON method and
  6352. // JSONEncodeNameSQLValue() function
  6353. procedure AddQuotedStringAsJSON(const QuotedString: RawUTF8);
  6354. /// append an array of integers as CSV
  6355. procedure AddCSVInteger(const Integers: array of Integer); overload;
  6356. /// append an array of doubles as CSV
  6357. procedure AddCSVDouble(const Doubles: array of double); overload;
  6358. /// append an array of RawUTF8 as CSV
  6359. procedure AddCSVUTF8(const Values: array of RawUTF8); overload;
  6360. /// append an array of const as CSV
  6361. procedure AddCSVConst(const Values: array of const);
  6362. /// write some data Base64 encoded
  6363. // - if withMagic is TRUE, will write as '"\uFFF0base64encodedbinary"'
  6364. procedure WrBase64(P: PAnsiChar; Len: cardinal; withMagic: boolean);
  6365. /// write some record content as binary, Base64 encoded with our magic prefix
  6366. procedure WrRecord(const Rec; TypeInfo: pointer);
  6367. /// write some #0 ended UTF-8 text, according to the specified format
  6368. procedure Add(P: PUTF8Char; Escape: TTextWriterKind); overload;
  6369. /// write some #0 ended UTF-8 text, according to the specified format
  6370. procedure Add(P: PUTF8Char; Len: PtrInt; Escape: TTextWriterKind); overload;
  6371. {$ifdef HASINLINE}inline;{$endif}
  6372. /// write some #0 ended Unicode text as UTF-8, according to the specified format
  6373. procedure AddW(P: PWord; Len: PtrInt; Escape: TTextWriterKind);
  6374. {$ifdef HASINLINE}inline;{$endif}
  6375. /// append some UTF-8 encoded chars to the buffer, from the main AnsiString type
  6376. // - use the current system code page for AnsiString parameter
  6377. procedure AddAnsiString(const s: AnsiString; Escape: TTextWriterKind); overload;
  6378. /// append some UTF-8 encoded chars to the buffer, from any AnsiString value
  6379. // - if CodePage is left to its default value of -1, it will assume
  6380. // CurrentAnsiConvert.CodePage prior to Delphi 2009, but newer UNICODE
  6381. // versions of Delphi will retrieve the code page from string
  6382. // - if CodePage is defined to a >= 0 value, the encoding will take place
  6383. procedure AddAnyAnsiString(const s: RawByteString; Escape: TTextWriterKind;
  6384. CodePage: Integer=-1);
  6385. /// append some UTF-8 encoded chars to the buffer, from any Ansi buffer
  6386. // - the codepage should be specified, e.g. CP_UTF8, CP_RAWBYTESTRING,
  6387. // CODEPAGE_US, or any version supported by the Operating System
  6388. // - if codepage is 0, the current CurrentAnsiConvert.CodePage would be used
  6389. // - will use TSynAnsiConvert to perform the conversion to UTF-8
  6390. procedure AddAnyAnsiBuffer(P: PAnsiChar; Len: integer;
  6391. Escape: TTextWriterKind; CodePage: Integer);
  6392. /// append some UTF-8 chars to the buffer
  6393. // - input length is calculated from zero-ended char
  6394. // - don't escapes chars according to the JSON RFC
  6395. procedure AddNoJSONEscape(P: Pointer); overload;
  6396. /// append some UTF-8 chars to the buffer
  6397. // - don't escapes chars according to the JSON RFC
  6398. procedure AddNoJSONEscape(P: Pointer; Len: integer); overload;
  6399. /// append some UTF-8 chars to the buffer
  6400. // - don't escapes chars according to the JSON RFC
  6401. procedure AddNoJSONEscapeUTF8(const text: RawByteString);
  6402. {$ifdef HASINLINE}inline;{$endif}
  6403. /// flush a supplied TTextWriter, and write pending data as JSON escaped text
  6404. // - may be used with InternalJSONWriter, as a faster alternative to
  6405. // ! AddNoJSONEscapeUTF8(Source.Text);
  6406. procedure AddNoJSONEscape(Source: TTextWriter); overload;
  6407. /// append some chars, quoting all " chars
  6408. // - same algorithm than AddString(QuotedStr()) - without memory allocation
  6409. // - this function implements what is specified in the official SQLite3
  6410. // documentation: "A string constant is formed by enclosing the string in single
  6411. // quotes ('). A single quote within the string can be encoded by putting two
  6412. // single quotes in a row - as in Pascal."
  6413. procedure AddQuotedStr(Text: PUTF8Char; Quote: AnsiChar; TextLen: integer=0);
  6414. /// append some chars, escaping all HTML special chars as expected
  6415. procedure AddHtmlEscape(Text: PUTF8Char; Fmt: TTextWriterHTMLFormat=hfAnyWhere); overload;
  6416. /// append some chars, escaping all HTML special chars as expected
  6417. procedure AddHtmlEscape(Text: PUTF8Char; TextLen: integer;
  6418. Fmt: TTextWriterHTMLFormat=hfAnyWhere); overload;
  6419. /// append some chars, escaping all HTML special chars as expected
  6420. procedure AddHtmlEscapeString(const Text: string;
  6421. Fmt: TTextWriterHTMLFormat=hfAnyWhere);
  6422. /// append some chars, escaping all HTML special chars as expected
  6423. procedure AddHtmlEscapeUTF8(const Text: RawUTF8;
  6424. Fmt: TTextWriterHTMLFormat=hfAnyWhere);
  6425. /// convert some wiki-like text into proper HTML
  6426. // - convert all #13#10 into <p>...</p>, *..* into <i>..</i> and +..+ into
  6427. // <b>..</b>, then escape http:// as <a href=...> and any HTML special chars
  6428. procedure AddHtmlEscapeWiki(P: PUTF8Char);
  6429. /// append some chars, escaping all XML special chars as expected
  6430. // - i.e. < > & " ' as &lt; &gt; &amp; &quote; &apos;
  6431. // - and all control chars (i.e. #1..#31) as &#..;
  6432. // - see @http://www.w3.org/TR/xml/#syntax
  6433. procedure AddXmlEscape(Text: PUTF8Char);
  6434. /// append some chars, replacing a given character with another
  6435. procedure AddReplace(Text: PUTF8Char; Orig,Replaced: AnsiChar);
  6436. /// append some binary data as hexadecimal text conversion
  6437. procedure AddBinToHex(Bin: Pointer; BinBytes: integer);
  6438. /// fast conversion from binary data into hexa chars, ready to be displayed
  6439. // - using this function with Bin^ as an integer value will encode it
  6440. // in big-endian order (most-signignifican byte first): use it for display
  6441. // - up to 128 bytes may be converted
  6442. procedure AddBinToHexDisplay(Bin: pointer; BinBytes: integer);
  6443. /// add the pointer into hexa chars, ready to be displayed
  6444. procedure AddPointer(P: PtrUInt);
  6445. /// write a byte as hexa chars
  6446. procedure AddByteToHex(Value: byte);
  6447. /// write a Int18 value (0..262143) as 3 chars
  6448. // - this encoding is faster than Base64, and has spaces on the left side
  6449. // - use function Chars3ToInt18() to decode the textual content
  6450. procedure AddInt18ToChars3(Value: cardinal);
  6451. /// append some unicode chars to the buffer
  6452. // - WideCharCount is the unicode chars count, not the byte size
  6453. // - don't escapes chars according to the JSON RFC
  6454. // - will convert the Unicode chars into UTF-8
  6455. procedure AddNoJSONEscapeW(WideChar: PWord; WideCharCount: integer);
  6456. /// append some UTF-8 encoded chars to the buffer
  6457. // - if Len is 0, Len is calculated from zero-ended char
  6458. // - escapes chars according to the JSON RFC
  6459. procedure AddJSONEscape(P: Pointer; Len: PtrInt=0); overload;
  6460. /// append some UTF-8 encoded chars to the buffer, from a generic string type
  6461. // - faster than AddJSONEscape(pointer(StringToUTF8(string))
  6462. // - escapes chars according to the JSON RFC
  6463. procedure AddJSONEscapeString(const s: string);
  6464. {$ifdef HASINLINE}inline;{$endif}
  6465. /// append some UTF-8 encoded chars to the buffer, from the main AnsiString type
  6466. // - escapes chars according to the JSON RFC
  6467. procedure AddJSONEscapeAnsiString(const s: AnsiString);
  6468. /// append some UTF-8 encoded chars to the buffer, from a generic string type
  6469. // - faster than AddNoJSONEscape(pointer(StringToUTF8(string))
  6470. // - don't escapes chars according to the JSON RFC
  6471. // - will convert the Unicode chars into UTF-8
  6472. procedure AddNoJSONEscapeString(const s: string);
  6473. {$ifdef UNICODE}inline;{$endif}
  6474. /// append some Unicode encoded chars to the buffer
  6475. // - if Len is 0, Len is calculated from zero-ended widechar
  6476. // - escapes chars according to the JSON RFC
  6477. procedure AddJSONEscapeW(P: PWord; Len: PtrInt=0);
  6478. /// append an open array constant value to the buffer
  6479. // - "" will be added if necessary
  6480. // - escapes chars according to the JSON RFC
  6481. // - very fast (avoid most temporary storage)
  6482. procedure AddJSONEscape(const V: TVarRec); overload;
  6483. /// flush a supplied TTextWriter, and write pending data as JSON escaped text
  6484. // - may be used with InternalJSONWriter, as a faster alternative to
  6485. // ! AddJSONEscape(Pointer(fInternalJSONWriter.Text),0);
  6486. procedure AddJSONEscape(Source: TTextWriter); overload;
  6487. /// append an open array constant value to the buffer
  6488. // - "" won't be added for string values
  6489. // - string values may be escaped, depending on the supplied parameter
  6490. // - very fast (avoid most temporary storage)
  6491. procedure Add(const V: TVarRec; Escape: TTextWriterKind=twNone); overload;
  6492. /// encode the supplied data as an UTF-8 valid JSON object content
  6493. // - data must be supplied two by two, as Name,Value pairs, e.g.
  6494. // ! aWriter.AddJSONEscape(['name','John','year',1972]);
  6495. // will append to the buffer:
  6496. // ! '{"name":"John","year":1972}'
  6497. // - or you can specify nested arrays or objects with '['..']' or '{'..'}':
  6498. // ! aWriter.AddJSONEscape(['doc','{','name','John','ab','[','a','b']','}','id',123]);
  6499. // will append to the buffer:
  6500. // ! '{"doc":{"name":"John","abc":["a","b"]},"id":123}'
  6501. // - note that cardinal values should be type-casted to Int64() (otherwise
  6502. // the integer mapped value will be transmitted, therefore wrongly)
  6503. // - you can pass nil as parameter for a null JSON value
  6504. procedure AddJSONEscape(const NameValuePairs: array of const); overload;
  6505. {$ifndef NOVARIANTS}
  6506. /// encode the supplied (extended) JSON content, with parameters,
  6507. // as an UTF-8 valid JSON object content
  6508. // - in addition to the JSON RFC specification strict mode, this method will
  6509. // handle some BSON-like extensions, e.g. unquoted field names:
  6510. // ! aWriter.AddJSON('{id:?,%:{name:?,birthyear:?}}',['doc'],[10,'John',1982]);
  6511. // - you can use nested _Obj() / _Arr() instances
  6512. // ! aWriter.AddJSON('{%:{$in:[?,?]}}',['type'],['food','snack']);
  6513. // ! aWriter.AddJSON('{type:{$in:?}}',[],[_Arr(['food','snack'])]);
  6514. // ! // which are the same as:
  6515. // ! aWriter.AddShort('{"type":{"$in":["food","snack"]}}');
  6516. // - if the SynMongoDB unit is used in the application, the MongoDB Shell
  6517. // syntax will also be recognized to create TBSONVariant, like
  6518. // ! new Date() ObjectId() MinKey MaxKey /<jRegex>/<jOptions>
  6519. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  6520. // ! aWriter.AddJSON('{name:?,field:/%/i}',['acme.*corp'],['John']))
  6521. // ! // will write
  6522. // ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
  6523. // - will call internally _JSONFastFmt() to create a temporary TDocVariant
  6524. // with all its features - so is slightly slower than other AddJSON* methods
  6525. procedure AddJSON(const Format: RawUTF8; const Args,Params: array of const);
  6526. {$endif}
  6527. /// append two JSON arrays of keys and values as one JSON object
  6528. // - i.e. makes the following transformation:
  6529. // $ [key1,key2...] + [value1,value2...] -> {key1:value1,key2,value2...}
  6530. // - this method won't allocate any memory during its process, nor
  6531. // modify the keys and values input buffers
  6532. // - is the reverse of the JSONObjectAsJSONArrays() function
  6533. procedure AddJSONArraysAsJSONObject(keys,values: PUTF8Char);
  6534. /// append a dynamic array content as UTF-8 encoded JSON array
  6535. // - expect a dynamic array TDynArray wrapper as incoming parameter
  6536. // - TIntegerDynArray, TInt64DynArray, TCardinalDynArray, TDoubleDynArray,
  6537. // TCurrencyDynArray, TWordDynArray and TByteDynArray will be written as
  6538. // numerical JSON values
  6539. // - TRawUTF8DynArray, TWinAnsiDynArray, TRawByteStringDynArray,
  6540. // TStringDynArray, TWideStringDynArray, TSynUnicodeDynArray, TTimeLogDynArray,
  6541. // and TDateTimeDynArray will be written as escaped UTF-8 JSON strings
  6542. // (and Iso-8601 textual encoding if necessary)
  6543. // - you can add some custom serializers via RegisterCustomJSONSerializer()
  6544. // class method, to serialize any dynamic array as valid JSON
  6545. // - any other non-standard or non-registered kind of dynamic array (including
  6546. // array of records) will be written as Base64 encoded binary stream, with a
  6547. // JSON_BASE64_MAGIC prefix (UTF-8 encoded \uFFF0 special code) - this will
  6548. // include TBytes (i.e. array of bytes) content, which is a good candidate
  6549. // for BLOB stream
  6550. // - typical content could be
  6551. // ! '[1,2,3,4]' or '["\uFFF0base64encodedbinary"]'
  6552. // - by default, custom serializers defined via RegisterCustomJSONSerializer()
  6553. // would write enumerates and sets as integer numbers, unless
  6554. // twoEnumSetsAsTextInRecord is set in the instance Options
  6555. procedure AddDynArrayJSON(const aDynArray: TDynArray); overload;
  6556. /// append a dynamic array content as UTF-8 encoded JSON array
  6557. // - just a wrapper around the other overloaded method, creating a
  6558. // temporary TDynArray wrapper on the stack
  6559. // - to be used e.g. for custom record JSON serialization, within a
  6560. // TDynArrayJSONCustomWriter callback
  6561. procedure AddDynArrayJSON(aTypeInfo: pointer; const aValue); overload;
  6562. {$ifdef UNDIRECTDYNARRAY}
  6563. /// append a dynamic array content as UTF-8 encoded JSON array
  6564. // - expect a dynamic array TDynArrayHashed wrapper as incoming parameter
  6565. // - this method is needed by the fact that "object" is buggy under
  6566. // newest versions of the Delphi compiler
  6567. procedure AddDynArrayJSON(const aDynArray: TDynArrayHashed); overload; inline;
  6568. {$endif}
  6569. /// same as AddDynArrayJSON(), but will double all internal " and bound with "
  6570. // - this implementation will avoid most memory allocations
  6571. procedure AddDynArrayJSONAsString(aTypeInfo: pointer; var aValue);
  6572. /// append a T*ObjArray dynamic array as a JSON array
  6573. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  6574. procedure AddObjArrayJSON(const aObjArray;
  6575. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]);
  6576. /// append a record content as UTF-8 encoded JSON or custom serialization
  6577. // - default serialization will use Base64 encoded binary stream, or
  6578. // a custom serialization, in case of a previous registration via
  6579. // RegisterCustomJSONSerializer() class method - from a dynamic array
  6580. // handling this kind of records, or directly from TypeInfo() of the record
  6581. // - by default, custom serializers defined via RegisterCustomJSONSerializer()
  6582. // would write enumerates and sets as integer numbers, unless
  6583. // twoEnumSetsAsTextInRecord is set in the instance Options
  6584. procedure AddRecordJSON(const Rec; TypeInfo: pointer);
  6585. {$ifndef NOVARIANTS}
  6586. /// append a variant content as number or string
  6587. // - default Escape=twJSONEscape will create valid JSON content, which
  6588. // can be converted back to a variant value using VariantLoadJSON()
  6589. // - default JSON serialization options would apply, unless
  6590. // twoForceJSONExtended or twoForceJSONStandard is defined
  6591. // - note that before Delphi 2009, any varString value is expected to be
  6592. // a RawUTF8 instance - which does make sense in the mORMot context
  6593. procedure AddVariant(const Value: variant; Escape: TTextWriterKind=twJSONEscape);
  6594. {$endif}
  6595. /// append a void record content as UTF-8 encoded JSON or custom serialization
  6596. // - this method will first create a void record (i.e. filled with #0 bytes)
  6597. // then save its content with default or custom serialization
  6598. procedure AddVoidRecordJSON(TypeInfo: pointer);
  6599. /// append a JSON value from its RTTI type
  6600. // - handle tkClass,tkEnumeration,tkSet,tkRecord,tkDynArray,tkVariant types
  6601. // - write null for other types
  6602. // - if EnumSetsAsText is FALSE, tkEnumeration and tkSet would be written as
  6603. // integer numbers; if EnumSetsAsText is TRUE, they would be serialized as
  6604. // a JSON string (for tkEnumeration) or JSON array of strings (for tkSet)
  6605. // - if FullSetsAsStar is TRUE, a tkSet value containing all its items
  6606. // would be serialized as the ["*"] JSON array
  6607. procedure AddTypedJSON(aTypeInfo: pointer; const aValue;
  6608. EnumSetsAsText, FullSetsAsStar: boolean);
  6609. /// serialize as JSON the given object
  6610. // - this default implementation will write null, or only write the
  6611. // class name and pointer if FullExpand is true - use TJSONSerializer.
  6612. // WriteObject method for full RTTI handling
  6613. // - default implementation will write TList/TCollection/TStrings/TRawUTF8List
  6614. // as appropriate array of class name/pointer (if woFullExpand is set)
  6615. procedure WriteObject(Value: TObject;
  6616. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]); virtual;
  6617. /// same as WriteObject(), but will double all internal " and bound with "
  6618. // - this implementation will avoid most memory allocations
  6619. procedure WriteObjectAsString(Value: TObject;
  6620. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]);
  6621. /// append a JSON value, array or document as simple XML content
  6622. // - you can use JSONBufferToXML() and JSONToXML() functions as wrappers
  6623. // - this method is called recursively to handle all kind of JSON values
  6624. // - WARNING: the JSON buffer is decoded in-place, so will be changed
  6625. // - returns the end of the current JSON converted level, or nil if the
  6626. // supplied content was not correct JSON
  6627. function AddJSONToXML(JSON: PUTF8Char; ArrayName: PUTF8Char=nil;
  6628. EndOfObject: PUTF8Char=nil): PUTF8Char;
  6629. /// append a JSON value, array or document, in a specified format
  6630. // - will parse the JSON buffer and write its content with proper line
  6631. // feeds and indentation, according to the supplied TTextWriterJSONFormat
  6632. // - see also JSONReformat() and JSONBufferReformat() wrappers
  6633. // - this method is called recursively to handle all kind of JSON values
  6634. // - WARNING: the JSON buffer is decoded in-place, so will be changed
  6635. // - returns the end of the current JSON converted level, or nil if the
  6636. // supplied content was not valid JSON
  6637. function AddJSONReformat(JSON: PUTF8Char; Format: TTextWriterJSONFormat;
  6638. EndOfObject: PUTF8Char): PUTF8Char;
  6639. /// define a custom serialization for a given dynamic array or record
  6640. // - expects TypeInfo() from a dynamic array or a record (will raise an
  6641. // exception otherwise)
  6642. // - for a dynamic array, the associated item record RTTI will be registered
  6643. // - for a record, any matching dynamic array will also be registered
  6644. // - by default, TIntegerDynArray and such known classes are processed as
  6645. // true JSON arrays: but you can specify here some callbacks to perform
  6646. // the serialization process for any kind of dynamic array
  6647. // - any previous registration is overridden
  6648. // - setting both aReader=aWriter=nil will return back to the default
  6649. // binary + Base64 encoding serialization (i.e. undefine custom serializer)
  6650. class procedure RegisterCustomJSONSerializer(aTypeInfo: pointer;
  6651. aReader: TDynArrayJSONCustomReader; aWriter: TDynArrayJSONCustomWriter);
  6652. /// define a custom serialization for a given dynamic array or record
  6653. // - the RTTI information will here be defined as plain text
  6654. // - since Delphi 2010, you can call directly
  6655. // RegisterCustomJSONSerializerFromTextSimpleType()
  6656. // - aTypeInfo may be valid TypeInfo(), or any fixed pointer value if the
  6657. // record does not have any RTTI (e.g. a record without any nested reference-
  6658. // counted types)
  6659. // - the record where the data will be stored should be defined as PACKED:
  6660. // ! type TMyRecord = packed record
  6661. // ! A,B,C: integer;
  6662. // ! D: RawUTF8;
  6663. // ! E: record; // or array of record/integer/string/...
  6664. // ! E1,E2: double;
  6665. // ! end;
  6666. // ! end;
  6667. // - call this method with aRTTIDefinition='' to return back to the default
  6668. // binary + Base64 encoding serialization (i.e. undefine custom serializer)
  6669. // - only known sub types are byte, word, integer, cardinal, Int64, single,
  6670. // double, currency, TDateTime, TTimeLog, RawUTF8, String, WideString,
  6671. // SynUnicode, TGUID (encoded via GUIDToText) or a nested record or dynamic
  6672. // array of the same simple types or record
  6673. // - RTTI textual information shall be supplied as text, with the
  6674. // same format as with a pascal record:
  6675. // ! 'A,B,C: integer; D: RawUTF8; E: record E1,E2: double; end;'
  6676. // ! 'A,B,C: integer; D: RawUTF8; E: array of record E1,E2: double; end;'
  6677. // ! 'A,B,C: integer; D: RawUTF8; E: array of SynUnicode; F: array of TGUID'
  6678. // or a shorter alternative syntax for records and arrays:
  6679. // ! 'A,B,C: integer; D: RawUTF8; E: {E1,E2: double}'
  6680. // ! 'A,B,C: integer; D: RawUTF8; E: [E1,E2: double]'
  6681. // in fact ; could be ignored:
  6682. // ! 'A,B,C:integer D:RawUTF8 E:{E1,E2:double}'
  6683. // ! 'A,B,C:integer D:RawUTF8 E:[E1,E2:double]'
  6684. // or even : could be ignored:
  6685. // ! 'A,B,C integer D RawUTF8 E{E1,E2 double}'
  6686. // ! 'A,B,C integer D RawUTF8 E[E1,E2 double]'
  6687. // - it will return the cached TJSONRecordTextDefinition
  6688. // instance corresponding to the supplied RTTI text definition
  6689. class function RegisterCustomJSONSerializerFromText(aTypeInfo: pointer;
  6690. const aRTTIDefinition: RawUTF8): TJSONRecordAbstract; overload;
  6691. /// define a custom serialization for several dynamic arrays or records
  6692. // - the TypeInfo() and textual RTTI information will here be defined as
  6693. // ([TypeInfo(TType1),_TType1,TypeInfo(TType2),_TType2]) pairs
  6694. // - a wrapper around the overloaded RegisterCustomJSONSerializerFromText()
  6695. class procedure RegisterCustomJSONSerializerFromText(
  6696. const aTypeInfoTextDefinitionPairs: array of const); overload;
  6697. /// change options for custom serialization of dynamic array or record
  6698. // - will return TRUE if the options have been changed, FALSE if the
  6699. // supplied type info was not previously registered
  6700. // - if AddIfNotExisting is TRUE, and enhanced RTTI is available (since
  6701. // Delphi 2010), you would be able to customize the options of this type
  6702. class function RegisterCustomJSONSerializerSetOptions(aTypeInfo: pointer;
  6703. aOptions: TJSONCustomParserSerializationOptions;
  6704. aAddIfNotExisting: boolean=false): boolean;
  6705. /// retrieve a previously registered custom parser instance from its type
  6706. // - will return nil if the type info was not available, or defined just
  6707. // with some callbacks
  6708. // - if AddIfNotExisting is TRUE, and enhanced RTTI is available (since
  6709. // Delphi 2010), you would be able to retrieve this type's parser even
  6710. // if the record type has not been previously used
  6711. class function RegisterCustomJSONSerializerFindParser(
  6712. aTypeInfo: pointer; aAddIfNotExisting: boolean=false): TJSONRecordAbstract;
  6713. /// define a custom serialization for a given simple type
  6714. // - you should be able to use this type in the RTTI text definition
  6715. // of any further RegisterCustomJSONSerializerFromText() call
  6716. // - the RTTI information should be enough to serialize the type from
  6717. // its name (e.g. an enumeration for older Delphi revision, but all records
  6718. // since Delphi 2010)
  6719. // - you can supply a custom type name, which will be registered in addition
  6720. // to the "official" name defined at RTTI level
  6721. // - on older Delphi versions (up to Delphi 2009), it will handle only
  6722. // enumerations, which will be transmitted as JSON string instead of numbers
  6723. // - since Delphi 2010, any record type can be supplied - which is more
  6724. // convenient than calling RegisterCustomJSONSerializerFromText()
  6725. class procedure RegisterCustomJSONSerializerFromTextSimpleType(aTypeInfo: pointer;
  6726. aTypeName: RawUTF8=''); overload;
  6727. /// define a custom serialization for several simple types
  6728. // - will call the overloaded RegisterCustomJSONSerializerFromTextSimpleType
  6729. // method for each supplied type information
  6730. class procedure RegisterCustomJSONSerializerFromTextSimpleType(
  6731. const aTypeInfos: array of pointer); overload;
  6732. /// undefine a custom serialization for a given dynamic array or record
  6733. // - it will un-register any callback or text-based custom serialization
  6734. // i.e. any previous RegisterCustomJSONSerializer() or
  6735. // RegisterCustomJSONSerializerFromText() call
  6736. // - expects TypeInfo() from a dynamic array or a record (will raise an
  6737. // exception otherwise)
  6738. // - it will set back to the default binary + Base64 encoding serialization
  6739. class procedure UnRegisterCustomJSONSerializer(aTypeInfo: pointer);
  6740. /// append some chars to the buffer in one line
  6741. // - P should be ended with a #0
  6742. // - will write #1..#31 chars as spaces (so content will stay on the same line)
  6743. procedure AddOnSameLine(P: PUTF8Char); overload;
  6744. /// append some chars to the buffer in one line
  6745. // - will write #0..#31 chars as spaces (so content will stay on the same line)
  6746. procedure AddOnSameLine(P: PUTF8Char; Len: PtrInt); overload;
  6747. /// append some wide chars to the buffer in one line
  6748. // - will write #0..#31 chars as spaces (so content will stay on the same line)
  6749. procedure AddOnSameLineW(P: PWord; Len: PtrInt);
  6750. /// return the last char appended
  6751. function LastChar: AnsiChar;
  6752. /// how many bytes are currently in the internal buffer and not on disk
  6753. // - see TextLength for the total number of bytes, on both disk and memory
  6754. function PendingBytes: PtrUInt;
  6755. {$ifdef HASINLINE}inline;{$endif}
  6756. /// how many bytes were currently written on disk
  6757. // - excluding the bytes in the internal buffer
  6758. // - see TextLength for the total number of bytes, on both disk and memory
  6759. property WrittenBytes: cardinal read fTotalFileSize;
  6760. /// the last char appended is canceled
  6761. procedure CancelLastChar; overload;
  6762. {$ifdef HASINLINE}inline;{$endif}
  6763. /// the last char appended is canceled, if match the supplied one
  6764. procedure CancelLastChar(aCharToCancel: AnsiChar); overload;
  6765. {$ifdef HASINLINE}inline;{$endif}
  6766. /// the last char appended is canceled if it was a ','
  6767. procedure CancelLastComma;
  6768. {$ifdef HASINLINE}inline;{$endif}
  6769. /// rewind the Stream to the position when Create() was called
  6770. // - note that this does not clear the Stream content itself, just
  6771. // move back its writing position to its initial place
  6772. procedure CancelAll;
  6773. /// count of added bytes to the stream
  6774. // - see PendingBytes for the number of bytes currently in the memory buffer
  6775. // or WrittenBytes for the number of bytes already written to disk
  6776. property TextLength: cardinal read GetLength;
  6777. /// define how AddEndOfLine method stores its line feed characters
  6778. // - by default (FALSE), it will append a CR (#13) char to the buffer
  6779. // - you can set this property to TRUE, so that CR+LF (#13#10) chars will
  6780. // be appended instead
  6781. // - is just a wrapper around twoEndOfLineCRLF item in CustomOptions
  6782. property EndOfLineCRLF: boolean read GetEndOfLineCRLF write SetEndOfLineCRLF;
  6783. /// allows to override default WriteObject property JSON serialization
  6784. property OnWriteObject: TOnTextWriterObjectProp read fOnWriteObject write fOnWriteObject;
  6785. /// the internal TStream used for storage
  6786. // - you should call the FlushFinal (or FlushToStream) methods before using
  6787. // this TStream content, to flush all pending characters
  6788. // - if the TStream instance has not been specified when calling the
  6789. // TTextWriter constructor, it can be forced via this property, before
  6790. // any writting
  6791. property Stream: TStream read fStream write SetStream;
  6792. /// global options to customize this TTextWriter instance process
  6793. // - allows to override e.g. AddRecordJSON() and AddDynArrayJSON() behavior
  6794. property CustomOptions: TTextWriterOptions read fCustomOptions write fCustomOptions;
  6795. end;
  6796. /// simple writer to a Stream, specialized for the JSON format and SQL export
  6797. // - use an internal buffer, faster than string+string
  6798. TJSONWriter = class(TTextWriter)
  6799. protected
  6800. /// used to store output format
  6801. fExpand: boolean;
  6802. /// used to store output format for TSQLRecord.GetJSONValues()
  6803. fWithID: boolean;
  6804. /// used to store field for TSQLRecord.GetJSONValues()
  6805. fFields: TSQLFieldIndexDynArray;
  6806. /// if not Expanded format, contains the Stream position of the first
  6807. // useful Row of data; i.e. ',val11' position in:
  6808. // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
  6809. fStartDataPosition: integer;
  6810. public
  6811. /// used internally to store column names and count for AddColumns
  6812. ColNames: TRawUTF8DynArray;
  6813. /// the data will be written to the specified Stream
  6814. // - if no Stream is supplied, a temporary memory stream will be created
  6815. // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream)
  6816. constructor Create(aStream: TStream; Expand, withID: boolean;
  6817. const Fields: TSQLFieldBits); overload;
  6818. /// the data will be written to the specified Stream
  6819. // - if no Stream is supplied, a temporary memory stream will be created
  6820. // (it's faster to supply one, e.g. any TSQLRest.TempMemoryStream)
  6821. constructor Create(aStream: TStream; Expand, withID: boolean;
  6822. const Fields: TSQLFieldIndexDynArray=nil); overload;
  6823. /// rewind the Stream position and write void JSON object
  6824. procedure CancelAllVoid;
  6825. /// write or init field names for appropriate JSON Expand later use
  6826. // - ColNames[] must have been initialized before calling this procedure
  6827. // - if aKnownRowsCount is not null, a "rowCount":... item will be added
  6828. // to the generated JSON stream (for faster unserialization of huge content)
  6829. procedure AddColumns(aKnownRowsCount: integer=0);
  6830. /// allow to change on the fly an expanded format column layout
  6831. // - by definition, a non expanded format would raise a ESynException
  6832. // - caller should then set ColNames[] and run AddColumns()
  6833. procedure ChangeExpandedFields(aWithID: boolean; const aFields: TSQLFieldIndexDynArray); overload;
  6834. /// end the serialized JSON object
  6835. // - cancel last ','
  6836. // - close the JSON object ']' or ']}'
  6837. // - write non expanded postlog (,"rowcount":...), if needed
  6838. // - flush the internal buffer content
  6839. procedure EndJSONObject(aKnownRowsCount,aRowsCount: integer);
  6840. {$ifdef HASINLINE}inline;{$endif}
  6841. /// the first data row is erased from the content
  6842. // - only works if the associated storage stream is TMemoryStream
  6843. // - expect not Expanded format
  6844. procedure TrimFirstRow;
  6845. /// is set to TRUE in case of Expanded format
  6846. property Expand: boolean read fExpand write fExpand;
  6847. /// is set to TRUE if the ID field must be appended to the resulting JSON
  6848. property WithID: boolean read fWithID;
  6849. /// Read-Only access to the field bits set for each column to be stored
  6850. property Fields: TSQLFieldIndexDynArray read fFields;
  6851. /// if not Expanded format, contains the Stream position of the first
  6852. // useful Row of data; i.e. ',val11' position in:
  6853. // & { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
  6854. property StartDataPosition: integer read fStartDataPosition;
  6855. end;
  6856. /// abstract TSynPersistent class allowing safe storage of a password
  6857. // - the associated Password, e.g. for storage or transmission encryption
  6858. // will be persisted encrypted with a private key (which can be customized)
  6859. // - a published property should be defined as such in inherited class:
  6860. // ! property PasswordPropertyName: RawUTF8 read fPassword write fPassword;
  6861. // - use the PassWordPlain property to access to its uncyphered value
  6862. TSynPersistentWithPassword = class(TSynPersistent)
  6863. protected
  6864. fPassWord: RawUTF8;
  6865. fKey: cardinal;
  6866. function GetKey: cardinal;
  6867. {$ifdef HASINLINE}inline;{$endif}
  6868. function GetPassWordPlain: RawUTF8;
  6869. procedure SetPassWordPlain(const Value: RawUTF8);
  6870. public
  6871. /// this class method could be used to compute the encrypted password,
  6872. // ready to be stored as JSON, according to a given private key
  6873. class function ComputePassword(const PlainPassword: RawUTF8;
  6874. CustomKey: cardinal=0): RawUTF8;
  6875. /// this class method could be used to decrypt a password, stored as JSON,
  6876. // according to a given private key
  6877. class function ComputePlainPassword(const CypheredPassword: RawUTF8;
  6878. CustomKey: cardinal=0): RawUTF8;
  6879. /// low-level function used to identify if a given field is a Password
  6880. // - this method is used e.g. by TJSONSerializer.WriteObject to identify the
  6881. // password field, since its published name is set by the inherited classes
  6882. function GetPasswordFieldAddress: pointer;
  6883. {$ifdef HASINLINE}inline;{$endif}
  6884. /// the private key used to cypher the password storage on serialization
  6885. // - application can override the default 0 value at runtime
  6886. property Key: cardinal read GetKey write fKey;
  6887. /// access to the associated unencrypted Password value
  6888. property PasswordPlain: RawUTF8 read GetPassWordPlain write SetPassWordPlain;
  6889. end;
  6890. /// could be used to store a credential pair, as user name and password
  6891. // - password will be stored with TSynPersistentWithPassword encryption
  6892. TSynUserPassword = class(TSynPersistentWithPassword)
  6893. protected
  6894. fUserName: RawUTF8;
  6895. published
  6896. /// the associated user name
  6897. property UserName: RawUTF8 read FUserName write FUserName;
  6898. /// the associated encrypted password
  6899. // - use the PasswordPlain public property to access to the uncrypted password
  6900. property Password: RawUTF8 read FPassword write FPassword;
  6901. end;
  6902. /// handle safe storage of any connection properties
  6903. // - would be used by SynDB.pas to serialize TSQLDBConnectionProperties, or
  6904. // by mORMot.pas to serialize TSQLRest instances
  6905. // - the password will be stored as Base64, after a simple encryption as
  6906. // defined by TSynPersistentWithPassword
  6907. // - typical content could be:
  6908. // $ {
  6909. // $ "Kind": "TSQLDBSQLite3ConnectionProperties",
  6910. // $ "ServerName": "server",
  6911. // $ "DatabaseName": "",
  6912. // $ "User": "",
  6913. // $ "Password": "PtvlPA=="
  6914. // $ }
  6915. // - the "Kind" value will be used to let the corresponding TSQLRest or
  6916. // TSQLDBConnectionProperties NewInstance*() class methods create the
  6917. // actual instance, from its class name
  6918. TSynConnectionDefinition = class(TSynPersistentWithPassword)
  6919. protected
  6920. fKind: string;
  6921. fServerName: RawUTF8;
  6922. fDatabaseName: RawUTF8;
  6923. fUser: RawUTF8;
  6924. public
  6925. /// unserialize the database definition from JSON
  6926. // - as previously serialized with the SaveToJSON method
  6927. // - you can specify a custom Key used for password encryption, if the
  6928. // default value is not safe enough for you
  6929. // - this method won't use JSONToObject() so avoid any dependency to mORMot.pas
  6930. constructor CreateFromJSON(const JSON: RawUTF8; Key: cardinal=0); virtual;
  6931. /// serialize the database definition as JSON
  6932. // - this method won't use ObjectToJSON() so avoid any dependency to mORMot.pas
  6933. function SaveToJSON: RawUTF8; virtual;
  6934. published
  6935. /// the class name implementing the connection or TSQLRest instance
  6936. // - will be used to instantiate the expected class type
  6937. property Kind: string read fKind write fKind;
  6938. /// the associated server name (or file, for SQLite3) to be connected to
  6939. property ServerName: RawUTF8 read fServerName write fServerName;
  6940. /// the associated database name (if any), or additional options
  6941. property DatabaseName: RawUTF8 read fDatabaseName write fDatabaseName;
  6942. /// the associated User Identifier (if any)
  6943. property User: RawUTF8 read fUser write fUser;
  6944. /// the associated Password, e.g. for storage or transmission encryption
  6945. // - will be persisted encrypted with a private key
  6946. // - use the PassWordPlain property to access to its uncyphered value
  6947. property Password: RawUTF8 read fPassword write fPassword;
  6948. end;
  6949. /// will serialize any TObject into its UTF-8 JSON representation
  6950. /// - serialize as JSON the published integer, Int64, floating point values,
  6951. // TDateTime (stored as ISO 8601 text), string, variant and enumerate
  6952. // (e.g. boolean) properties of the object (and its parents)
  6953. // - would set twoForceJSONStandard to force standard (non-extended) JSON
  6954. // - the enumerates properties are stored with their integer index value
  6955. // - will write also the properties published in the parent classes
  6956. // - nested properties are serialized as nested JSON objects
  6957. // - any TCollection property will also be serialized as JSON arrays
  6958. // - you can add some custom serializers for ANY Delphi class, via mORMot.pas'
  6959. // TJSONSerializer.RegisterCustomSerializer() class method
  6960. // - call internaly TJSONSerializer.WriteObject() method (or fallback to
  6961. // TJSONWriter if mORMot.pas is not linked to the executable)
  6962. function ObjectToJSON(Value: TObject;
  6963. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]): RawUTF8;
  6964. /// will serialize set of TObject into its UTF-8 JSON representation
  6965. // - follows ObjectToJSON()/TTextWriter.WriterObject() functions output
  6966. // - if Names is not supplied, the corresponding class names would be used
  6967. function ObjectsToJSON(const Names: array of RawUTF8; const Values: array of TObject;
  6968. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]): RawUTF8;
  6969. {$ifndef NOVARIANTS}
  6970. /// will convert any TObject into a TDocVariant document instance
  6971. // - a faster alternative to Dest := _JsonFast(ObjectToJSON(Value))
  6972. // - this would convert the TObject by representation, using only serializable
  6973. // published properties: do not use this function to store temporary a class
  6974. // instance, but e.g. to store an object values in a NoSQL database
  6975. procedure ObjectToVariant(Value: TObject; out Dest: variant); overload;
  6976. {$ifdef HASINLINE}inline;{$endif}
  6977. /// will convert any TObject into a TDocVariant document instance
  6978. // - a faster alternative to _JsonFast(ObjectToJSON(Value))
  6979. function ObjectToVariant(Value: TObject; EnumSetsAsText: boolean=false): variant; overload;
  6980. /// will convert any TObject into a TDocVariant document instance
  6981. // - a faster alternative to _Json(ObjectToJSON(Value),Options)
  6982. // - note that the result variable should already be cleared: no VarClear()
  6983. // is done by this function
  6984. // - would be used e.g. by VarRecToVariant() function
  6985. procedure ObjectToVariant(Value: TObject; var result: variant;
  6986. Options: TTextWriterWriteObjectOptions); overload;
  6987. {$endif}
  6988. type
  6989. /// implement a cache of some key/value pairs, e.g. to improve reading speed
  6990. // - used e.g. by TSQLDataBase for caching the SELECT statements results in an
  6991. // internal JSON format (which is faster than a query to the SQLite3 engine)
  6992. // - internally make use of an efficient hashing algorithm for fast response
  6993. // (i.e. TSynNameValue will use the TDynArrayHashed wrapper mechanism)
  6994. TSynCache = class
  6995. protected
  6996. /// last index in fNameValue.List[] if was added by Find()
  6997. // - contains -1 if no previous immediate call to Find()
  6998. fFindLastAddedIndex: integer;
  6999. /// store Key/Value pairs
  7000. fNameValue: TSynNameValue;
  7001. /// the global size of Values in cache, in bytes (to prevent memory burn)
  7002. fValueSize: cardinal;
  7003. /// the maximum RAM to be used for values, in bytes
  7004. fMaxCacheRamUsed: cardinal;
  7005. public
  7006. /// initialize the internal storage
  7007. // - aMaxCacheRamUsed can set the maximum RAM to be used for values, in bytes
  7008. // (default is 16 MB per cache): cache will be reset when so much value
  7009. // will be reached
  7010. // - by default, key search is done case-insensitively, but you can specify
  7011. // another option here
  7012. constructor Create(aMaxCacheRamUsed: cardinal=16384*1024;
  7013. aCaseSensitive: boolean=false);
  7014. /// find a Key in the cache entries
  7015. // - return '' if nothing found
  7016. // - return the associated Value otherwise, and the associated integer tag
  7017. // if aResultTag address is supplied
  7018. function Find(const aKey: RawUTF8; aResultTag: PPtrInt): RawUTF8;
  7019. /// add a Key and its associated value (and tag) to the cache entries
  7020. // - you MUST always call Find() with the associated Key first
  7021. procedure Add(const aValue: RawUTF8; aTag: PtrInt);
  7022. /// called after a write access to the database to flush the cache
  7023. // - set Count to 0
  7024. // - release all cache memory
  7025. // - returns TRUE if was flushed, i.e. if there was something in cache
  7026. function Reset: boolean;
  7027. /// number of entries in the cache
  7028. {$ifdef VER220} { circumvent Delphi XE compilation with packages }
  7029. function Count: integer;
  7030. {$else}
  7031. property Count: integer read fNameValue.Count;
  7032. {$endif}
  7033. end;
  7034. /// abstract ancestor to manage a dynamic array of TObject
  7035. // - do not use this abstract class directly, but rather the inherited
  7036. // TObjectListHashed and TObjectListPropertyHashed
  7037. TObjectListHashedAbstract = class
  7038. protected
  7039. fList: TObjectDynArray;
  7040. fCount: integer;
  7041. fHash: TDynArrayHashed;
  7042. fFreeItems: boolean;
  7043. fHashValid: boolean;
  7044. fHashed: boolean;
  7045. public
  7046. /// initialize the class instance
  7047. // - if aFreeItems is TRUE (default), will behave like a TObjectList
  7048. // - if aFreeItems is FALSE, will behave like a TList
  7049. constructor Create(aFreeItems: boolean=true); reintroduce;
  7050. /// release used memory
  7051. destructor Destroy; override;
  7052. /// search and add an object reference to the list
  7053. // - returns the found/added index
  7054. function Add(aObject: TObject; out wasAdded: boolean): integer; virtual; abstract;
  7055. /// retrieve an object index within the list, using a fast hash table
  7056. // - returns -1 if not found
  7057. function IndexOf(aObject: TObject): integer; virtual; abstract;
  7058. /// delete an object from the list
  7059. procedure Delete(aIndex: integer); overload;
  7060. /// delete an object from the list
  7061. procedure Delete(aObject: TObject); overload;
  7062. /// direct access to the items list array
  7063. property List: TObjectDynArray read fList;
  7064. /// returns the count of stored objects
  7065. property Count: integer read fCount;
  7066. /// direct access to the underlying hashing engine
  7067. property Hash: TDynArrayHashed read fHash;
  7068. end;
  7069. /// this class behaves like TList/TObjectList, but will use hashing
  7070. // for (much) faster IndexOf() method
  7071. TObjectListHashed = class(TObjectListHashedAbstract)
  7072. public
  7073. /// search and add an object reference to the list
  7074. // - returns the found/added index
  7075. // - if added, hash is stored and Items[] := aObject
  7076. function Add(aObject: TObject; out wasAdded: boolean): integer; override;
  7077. /// retrieve an object index within the list, using a fast hash table
  7078. // - returns -1 if not found
  7079. function IndexOf(aObject: TObject): integer; override;
  7080. end;
  7081. /// function prototype used to retrieve the hashed property of a
  7082. // TObjectListPropertyHashed list
  7083. TObjectListPropertyHashedAccessProp = function(aObject: TObject): pointer;
  7084. /// this class will hash and search for a sub property of the stored objects
  7085. TObjectListPropertyHashed = class(TObjectListHashedAbstract)
  7086. protected
  7087. fSubPropAccess: TObjectListPropertyHashedAccessProp;
  7088. function IntHash(const Elem): cardinal;
  7089. function IntComp(const A,B): integer;
  7090. procedure IntHashValid;
  7091. public
  7092. /// initialize the class instance with the corresponding callback in order
  7093. // to handle sub-property hashing and search
  7094. // - see TSetWeakZeroClass in mORMot.pas unit as example:
  7095. // ! function WeakZeroClassSubProp(aObject: TObject): TObject;
  7096. // ! begin
  7097. // ! result := TSetWeakZeroInstance(aObject).fInstance;
  7098. // ! end;
  7099. // - by default, aHashElement/aCompare will hash/search for pointers:
  7100. // you can specify the hash/search methods according to your sub property
  7101. // (e.g. HashAnsiStringI/SortDynArrayAnsiStringI for a RawUTF8)
  7102. // - if aFreeItems is TRUE (default), will behave like a TObjectList;
  7103. // if aFreeItems is FALSE, will behave like a TList
  7104. constructor Create(aSubPropAccess: TObjectListPropertyHashedAccessProp;
  7105. aHashElement: TDynArrayHashOne=nil; aCompare: TDynArraySortCompare=nil;
  7106. aFreeItems: boolean=true); reintroduce;
  7107. /// search and add an object reference to the list
  7108. // - returns the found/added index
  7109. // - if added, only the hash is stored: caller has to set List[i]
  7110. function Add(aObject: TObject; out wasAdded: boolean): integer; override;
  7111. /// retrieve an object index within the list, using a fast hash table
  7112. // - returns -1 if not found
  7113. function IndexOf(aObject: TObject): integer; override;
  7114. end;
  7115. /// abstract class stored by a TPointerClassHash list
  7116. TPointerClassHashed = class
  7117. protected
  7118. fInfo: pointer;
  7119. public
  7120. /// initialize the instance
  7121. constructor Create(aInfo: pointer);
  7122. /// the associated information of this instance
  7123. // - may be e.g. a PTypeInfo value, when caching RTTI information
  7124. property Info: pointer read fInfo write fInfo;
  7125. end;
  7126. /// a reference to a TPointerClassHashed instance
  7127. PPointerClassHashed = ^TPointerClassHashed;
  7128. /// handle a O(1) hashed-based storage of TPointerClassHashed, from a pointer
  7129. // - used e.g. to store RTTI information from its PTypeInfo value
  7130. // - if not thread safe, but could be used to store RTTI, since all type
  7131. // information should have been initialized before actual process
  7132. TPointerClassHash = class(TObjectListPropertyHashed)
  7133. public
  7134. /// initialize the storage list
  7135. constructor Create;
  7136. /// try to add an entry to the storage
  7137. // - returns nil if the supplied information is already in the list
  7138. // - returns a pointer to where a newly created TPointerClassHashed
  7139. // instance should be stored
  7140. // - this method is not thread-safe
  7141. function TryAdd(aInfo: pointer): PPointerClassHashed;
  7142. /// search for a stored instance, from its supplied pointer reference
  7143. // - returns nil if aInfo was not previously added by FindOrAdd()
  7144. // - this method is not thread-safe
  7145. function Find(aInfo: pointer): TPointerClassHashed;
  7146. end;
  7147. /// handle a O(1) hashed-based storage of TPointerClassHashed, from a pointer
  7148. // - this inherited class add a mutex to be thread-safe
  7149. TPointerClassHashLocked = class(TPointerClassHash)
  7150. protected
  7151. fSafe: TSynLocker;
  7152. public
  7153. /// initialize the storage list
  7154. constructor Create;
  7155. /// finalize the storage list
  7156. destructor Destroy; override;
  7157. /// try to add an entry to the storage
  7158. // - returns false if the supplied information is already in the list
  7159. // - returns true, and a pointer to where a newly created TPointerClassHashed
  7160. // instance should be stored: in this case, you should call UnLock once set
  7161. // - could be used as such:
  7162. // !var entry: PPointerClassHashed;
  7163. // !...
  7164. // ! if HashList.TryAddLocked(aTypeInfo,entry) then
  7165. // ! try
  7166. // ! entry^ := TMyCustomPointerClassHashed.Create(aTypeInfo,...);
  7167. // ! finally
  7168. // ! HashList.Unlock;
  7169. // ! end;
  7170. // !...
  7171. function TryAddLocked(aInfo: pointer; out aNewEntry: PPointerClassHashed): boolean;
  7172. /// release the lock after a previous TryAddLocked()=true call
  7173. procedure Unlock;
  7174. /// search for a stored instance, from its supplied pointer reference
  7175. // - returns nil if aInfo was not previously added by FindOrAdd()
  7176. // - this overriden method is thread-safe
  7177. function FindLocked(aInfo: pointer): TPointerClassHashed;
  7178. end;
  7179. /// add locking methods to a standard TObjectList
  7180. // - this class overrides the regular TObjectList, and do not share any code
  7181. // with the TObjectListHashedAbstract/TObjectListHashed classes
  7182. // - caller has to call the Lock/Unlock methods by hand to protect the
  7183. // execution of regular TObjectList methods (like Add/Remove/Count...)
  7184. TObjectListLocked = class(TObjectList)
  7185. protected
  7186. fSafe: TSynLocker;
  7187. public
  7188. /// initialize the list instance
  7189. // - the stored TObject instances will be owned by this TObjectListLocked,
  7190. // unless AOwnsObjects is set to false
  7191. constructor Create(AOwnsObjects: Boolean=true); reintroduce;
  7192. /// release the list instance (including the locking resource)
  7193. destructor Destroy; override;
  7194. /// the critical section associated to this list instance
  7195. // - could be used to protect shared resources within the internal process
  7196. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  7197. property Safe: TSynLocker read fSafe;
  7198. end;
  7199. /// This class is able to emulate a TStringList with our native UTF-8 string type
  7200. // - cross-compiler, from Delphi 6 and up, i.e is Unicode Ready for all
  7201. TRawUTF8List = class
  7202. protected
  7203. fCount: PtrInt;
  7204. fList: TRawUTF8DynArray;
  7205. fObjects: TObjectDynArray;
  7206. fObjectsOwned: boolean;
  7207. fNameValueSep: AnsiChar;
  7208. fCaseSensitive: boolean;
  7209. fOnChange, fOnChangeHidden: TNotifyEvent;
  7210. fOnChangeTrigerred: boolean;
  7211. fOnChangeLevel: PtrInt;
  7212. procedure Changed; virtual;
  7213. procedure OnChangeHidden(Sender: TObject);
  7214. procedure SetCapacity(const Value: PtrInt);
  7215. function GetCapacity: PtrInt;
  7216. procedure Put(Index: PtrInt; const Value: RawUTF8);
  7217. function GetCount: PtrInt; {$ifdef HASINLINE}inline;{$endif}
  7218. procedure PutObject(Index: PtrInt; const Value: TObject);
  7219. function GetName(Index: PtrInt): RawUTF8;
  7220. function GetValue(const Name: RawUTF8): RawUTF8;
  7221. procedure SetValue(const Name, Value: RawUTF8);
  7222. function GetTextCRLF: RawUTF8;
  7223. procedure SetTextCRLF(const Value: RawUTF8);
  7224. procedure SetTextPtr(P: PUTF8Char; const Delimiter: RawUTF8);
  7225. function GetListPtr: PPUtf8CharArray;
  7226. function GetObjectPtr: PPointerArray; {$ifdef HASINLINE}inline;{$endif}
  7227. procedure SetCaseSensitive(Value: boolean); virtual;
  7228. public
  7229. /// initialize the class instance
  7230. // - by default, any associated Objects[] are just weak references
  7231. // - also define CaseSensitive=true
  7232. // - you may supply aOwnObjects=true to force object instance management
  7233. constructor Create(aOwnObjects: boolean=false);
  7234. /// finalize the internal objects stored
  7235. // - if instance was created with aOwnObjects=true
  7236. destructor Destroy; override;
  7237. /// get a stored RawUTF8 item
  7238. // - returns '' and raise no exception in case of out of range supplied index
  7239. function Get(Index: PtrInt): RawUTF8; {$ifdef HASINLINE}inline;{$endif}
  7240. /// get a stored Object item by index
  7241. // - returns nil and raise no exception in case of out of range supplied index
  7242. function GetObject(Index: PtrInt): TObject; {$ifdef HASINLINE}inline;{$endif}
  7243. /// get a stored Object item by name
  7244. // - returns nil and raise no exception in case of out of range supplied index
  7245. function GetObjectByName(const Name: RawUTF8): TObject;
  7246. /// store a new RawUTF8 item
  7247. // - returns -1 and raise no exception in case of self=nil
  7248. function Add(const aText: RawUTF8): PtrInt; {$ifdef HASINLINE}inline;{$endif}
  7249. /// store a new RawUTF8 item if not already in the list
  7250. // - returns -1 and raise no exception in case of self=nil
  7251. function AddIfNotExisting(const aText: RawUTF8; wasAdded: PBoolean=nil): PtrInt; virtual;
  7252. /// store a new RawUTF8 item, and its associated TObject
  7253. // - returns -1 and raise no exception in case of self=nil
  7254. function AddObject(const aText: RawUTF8; aObject: TObject): PtrInt;
  7255. /// store a new RawUTF8 item if not already in the list, and its associated TObject
  7256. // - returns -1 and raise no exception in case of self=nil
  7257. function AddObjectIfNotExisting(const aText: RawUTF8; aObject: TObject;
  7258. wasAdded: PBoolean=nil): PtrInt; virtual;
  7259. /// append a specified list to the current content
  7260. procedure AddRawUTF8List(List: TRawUTF8List);
  7261. /// delete a stored RawUTF8 item, and its associated TObject
  7262. // - raise no exception in case of out of range supplied index
  7263. procedure Delete(Index: PtrInt); overload; virtual;
  7264. /// delete a stored RawUTF8 item, and its associated TObject
  7265. // - will search for the value using IndexOf(aText), and returns its index
  7266. // - returns -1 if no entry was found and deleted
  7267. function Delete(const aText: RawUTF8): PtrInt; overload; virtual;
  7268. /// delete a stored RawUTF8 item, and its associated TObject, from
  7269. // a given Name when stored as 'Name=Value' pairs
  7270. // - raise no exception in case of out of range supplied index
  7271. function DeleteFromName(const Name: RawUTF8): PtrInt; virtual;
  7272. /// update Value from an existing Name=Value, then optinally delete the entry
  7273. procedure UpdateValue(const Name: RawUTF8; var Value: RawUTF8; ThenDelete: boolean);
  7274. /// retrieve and delete the first RawUTF8 item in the list
  7275. // - could be used as a FIFO
  7276. function PopFirst(out aText: RawUTF8; aObject: PObject=nil): boolean; virtual;
  7277. /// retrieve and delete the last RawUTF8 item in the list
  7278. // - could be used as a FILO
  7279. function PopLast(out aText: RawUTF8; aObject: PObject=nil): boolean; virtual;
  7280. /// erase all stored RawUTF8 items
  7281. // - and corresponding objects (if aOwnObjects was true at constructor)
  7282. procedure Clear; virtual;
  7283. /// find a RawUTF8 item in the stored Strings[] list
  7284. // - this search is case sensitive if CaseSensitive property is TRUE (which
  7285. // is the default)
  7286. function IndexOf(const aText: RawUTF8): PtrInt; virtual;
  7287. /// find the index of a given Name when stored as 'Name=Value' pairs
  7288. // - search on Name is case-insensitive with 'Name=Value' pairs
  7289. function IndexOfName(const Name: RawUTF8): PtrInt;
  7290. /// find a TObject item index in the stored Objects[] list
  7291. function IndexOfObject(aObject: TObject): PtrInt;
  7292. /// access to the Value of a given 'Name=Value' pair
  7293. function GetValueAt(Index: PtrInt): RawUTF8;
  7294. /// retrieve the all lines, separated by the supplied delimiter
  7295. function GetText(const Delimiter: RawUTF8=#13#10): RawUTF8;
  7296. /// the OnChange event will be raised only when EndUpdate will be called
  7297. procedure BeginUpdate;
  7298. /// call the OnChange event if changes occured
  7299. procedure EndUpdate;
  7300. /// set all lines, separated by the supplied delimiter
  7301. procedure SetText(const aText: RawUTF8; const Delimiter: RawUTF8=#13#10);
  7302. /// set all lines from an UTF-8 text file
  7303. // - expect the file is explicitly an UTF-8 file
  7304. // - will ignore any trailing UTF-8 BOM in the file content, but will not
  7305. // expect one either
  7306. procedure LoadFromFile(const FileName: TFileName);
  7307. /// write all lines into the supplied stream
  7308. procedure SaveToStream(Dest: TStream; const Delimiter: RawUTF8=#13#10);
  7309. /// write all lines into a new file
  7310. procedure SaveToFile(const FileName: TFileName; const Delimiter: RawUTF8=#13#10);
  7311. /// return the count of stored RawUTF8
  7312. property Count: PtrInt read GetCount;
  7313. /// set or retrive the current memory capacity of the RawUTF8 list
  7314. property Capacity: PtrInt read GetCapacity write SetCapacity;
  7315. /// get or set a RawUTF8 item
  7316. // - returns '' and raise no exception in case of out of range supplied index
  7317. // - if you want to use it with the VCL, use UTF8ToString() function
  7318. property Strings[Index: PtrInt]: RawUTF8 read Get write Put; default;
  7319. /// get or set a Object item
  7320. // - returns nil and raise no exception in case of out of range supplied index
  7321. property Objects[Index: PtrInt]: TObject read GetObject write PutObject;
  7322. /// set if IndexOf() shall be case sensitive or not
  7323. // - default is TRUE
  7324. property CaseSensitive: boolean read fCaseSensitive write SetCaseSensitive;
  7325. /// retrieve the corresponding Name when stored as 'Name=Value' pairs
  7326. property Names[Index: PtrInt]: RawUTF8 read GetName;
  7327. /// access to the corresponding 'Name=Value' pairs
  7328. // - search on Name is case-insensitive with 'Name=Value' pairs
  7329. property Values[const Name: RawUTF8]: RawUTF8 read GetValue write SetValue;
  7330. /// the char separator between 'Name=Value' pairs
  7331. // - equals '=' by default
  7332. property NameValueSep: AnsiChar read fNameValueSep write fNameValueSep;
  7333. /// set or retrieve all items as text lines
  7334. // - lines are separated by #13#10 (CRLF) by default; use GetText and
  7335. // SetText methods if you want to use another line delimiter (even a comma)
  7336. property Text: RawUTF8 read GetTextCRLF write SetTextCRLF;
  7337. /// Event triggered when an entry is modified
  7338. property OnChange: TNotifyEvent read fOnChange write fOnChange;
  7339. /// direct access to the memory of the RawUTF8 array
  7340. property ListPtr: PPUtf8CharArray read GetListPtr;
  7341. /// direct access to the memory of the Objects array
  7342. property ObjectPtr: PPointerArray read GetObjectPtr;
  7343. end;
  7344. /// a TRawUTF8List with an associated lock for thread-safety
  7345. TRawUTF8ListLocked = class(TRawUTF8List)
  7346. protected
  7347. fSafe: TSynLocker;
  7348. public
  7349. /// initialize the class instance
  7350. constructor Create(aOwnObjects: boolean=false);
  7351. /// finalize the instance
  7352. // - and all internal objects stored, if was created with Create(true)
  7353. destructor Destroy; override;
  7354. /// access to the locking methods of this instance
  7355. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  7356. property Safe: TSynLocker read fSafe;
  7357. end;
  7358. /// a TRawUTF8List which will use an internal hash table for faster IndexOf()
  7359. // - this is a rather rough implementation: all values are re-hashed after
  7360. // change: but purpose of this class is to allow faster access of a static
  7361. // list of identifiers (e.g. service method names) which are fixed during run
  7362. TRawUTF8ListHashed = class(TRawUTF8List)
  7363. protected
  7364. fHash: TDynArrayHashed;
  7365. fChanged: boolean;
  7366. procedure SetCaseSensitive(Value: boolean); override;
  7367. /// will set fChanged=true to force re-hash of all items
  7368. procedure Changed; override;
  7369. public
  7370. /// initialize the class instance
  7371. constructor Create(aOwnObjects: boolean=false);
  7372. /// find a RawUTF8 item in the stored Strings[] list
  7373. // - this overridden method will update the internal hash table (if needed),
  7374. // then use it to retrieve the corresponding matching index
  7375. // - if your purpose is to test if an item is existing, then add it on need,
  7376. // use rather the AddObjectIfNotExisting() method which would preserve
  7377. // the internal hash array, so would perform better
  7378. function IndexOf(const aText: RawUTF8): PtrInt; override;
  7379. /// store a new RawUTF8 item if not already in the list
  7380. // - returns -1 and raise no exception in case of self=nil
  7381. // - this overridden method will update and use the internal hash table,
  7382. // so is preferred to plain Add if you want faster insertion
  7383. // into the TRawUTF8ListHashed
  7384. function AddIfNotExisting(const aText: RawUTF8; wasAdded: PBoolean=nil): PtrInt; override;
  7385. /// store a new RawUTF8 item if not already in the list, and its associated TObject
  7386. // - returns -1 and raise no exception in case of self=nil
  7387. // - this overridden method will update and use the internal hash table,
  7388. // so is preferred to plain Add if you want faster insertion
  7389. // into the TRawUTF8ListHashed
  7390. function AddObjectIfNotExisting(const aText: RawUTF8; aObject: TObject;
  7391. wasAdded: PBoolean=nil): PtrInt; override;
  7392. /// search in the low-level internal hashing table
  7393. function HashFind(aHashCode: cardinal): integer; {$ifdef HASINLINE}inline;{$endif}
  7394. /// access to the low-level internal hashing table
  7395. property Hash: TDynArrayHashed read fHash;
  7396. end;
  7397. /// a TRawUTF8List with an internal hash, with locking methods
  7398. // - by default, inherited methods are not protected by the mutex: you have
  7399. // to explicitely call Lock/UnLock to enter or leave the critical section
  7400. TRawUTF8ListHashedLocked = class(TRawUTF8ListHashed)
  7401. protected
  7402. fSafe: TSynLocker;
  7403. public
  7404. /// initialize the class instance
  7405. constructor Create(aOwnObjects: boolean=false);
  7406. /// finalize the instance
  7407. // - and all internal objects stored, if was created with Create(true)
  7408. destructor Destroy; override;
  7409. /// access to the locking methods of this instance
  7410. // - use Safe.Lock/TryLock with a try ... finally Safe.Unlock block
  7411. property Safe: TSynLocker read fSafe;
  7412. /// add a RawUTF8 item in the stored Strings[] list
  7413. // - just a wrapper over Add() using Safe.Lock/Unlock
  7414. // - warning: this method WON'T update the internal hash array: use
  7415. // AddIfNotExisting/AddObjectIfNotExisting() methods instead
  7416. function LockedAdd(const aText: RawUTF8): PtrInt; virtual;
  7417. /// find a RawUTF8 item in the stored Strings[] list
  7418. // - just a wrapper over IndexOf() using Safe.Lock/Unlock
  7419. function IndexOf(const aText: RawUTF8): PtrInt; override;
  7420. /// find a RawUTF8 item in the stored Strings[] list
  7421. // - just a wrapper over GetObjectByName() using Safe.Lock/Unlock
  7422. // - warning: the object instance should remain in the list, so the caller
  7423. // should not make any Delete/LockedDeleteFromName otherwise a GPF may occur
  7424. function LockedGetObjectByName(const aText: RawUTF8): TObject; virtual;
  7425. /// add a RawUTF8 item in the internal storage
  7426. // - just a wrapper over AddIfNotExisting() using Safe.Lock/Unlock
  7427. function AddIfNotExisting(const aText: RawUTF8; wasAdded: PBoolean=nil): PtrInt; override;
  7428. /// add a RawUTF8 item in the internal storage, with an optional object
  7429. // - just a wrapper over AddObjectIfNotExisting() using Safe.Lock/Unlock
  7430. function AddObjectIfNotExisting(const aText: RawUTF8; aObject: TObject;
  7431. wasAdded: PBoolean=nil): PtrInt; override;
  7432. /// find and delete an RawUTF8 item in the stored Strings[] list
  7433. // - just a wrapper over inherited Delete(aText) using Safe.Lock/Unlock
  7434. function Delete(const aText: RawUTF8): PtrInt; override;
  7435. /// find and delete an RawUTF8 item from its Name=... in the stored Strings[] list
  7436. // - just a wrapper over inherited DeleteFromName() using Safe.Lock/Unlock
  7437. function DeleteFromName(const Name: RawUTF8): PtrInt; override;
  7438. /// retrieve and delete the first RawUTF8 item in the list
  7439. // - could be used as a FIFO
  7440. // - just a wrapper over inherited PopFirst() using Safe.Lock/Unlock
  7441. function PopFirst(out aText: RawUTF8; aObject: PObject=nil): boolean; override;
  7442. /// retrieve and delete the last RawUTF8 item in the list
  7443. // - could be used as a FILO
  7444. // - just a wrapper over inherited PopLast() using Safe.Lock/Unlock
  7445. function PopLast(out aText: RawUTF8; aObject: PObject=nil): boolean; override;
  7446. /// delete all RawUTF8 items in the list
  7447. // - just a wrapper over inherited Clear using Safe.Lock/Unlock
  7448. procedure Clear; override;
  7449. end;
  7450. /// This class is able to emulate a TStringList with our native UTF-8 string
  7451. // type and storing TMethod callbacks
  7452. // - cross-compiler, from Delphi 6 and up, i.e is Unicode Ready for all
  7453. TRawUTF8MethodList = class(TRawUTF8ListHashed)
  7454. protected
  7455. fEvents: TMethodDynArray;
  7456. public
  7457. /// delete a stored RawUTF8 item, and its associated event
  7458. // - raise no exception in case of out of range supplied index
  7459. procedure Delete(Index: PtrInt); override;
  7460. /// erase all stored RawUTF8 items and events
  7461. procedure Clear; override;
  7462. /// register a callback with its name
  7463. function AddEvent(const aName: RawUTF8; const aEvent: TMethod): PtrInt;
  7464. /// retrieve a callback from its index
  7465. // - return FALSE if not previously set via AddEvent()
  7466. // - return TRUE if found, and set aEvent to the corresponding callback
  7467. function GetEvent(aIndex: PtrInt; out aEvent: TMethod): boolean;
  7468. /// retrieve a callback from its hashed name
  7469. // - return FALSE if not found
  7470. // - return TRUE if found, and set aEvent to the corresponding callback
  7471. function GetEventByName(const aText: RawUTF8; out aEvent: TMethod): boolean;
  7472. end;
  7473. TSynDictionaryInArray = (
  7474. iaFind, iaFindAndDelete, iaFindAndUpdate, iaFindAndAddIfNotExisting, iaAdd);
  7475. /// event called by TSynDictionary.ForEach methods to iterate over stored items
  7476. // - if the implementation method returns TRUE, will continue the loopp
  7477. // - if the implementation method returns FALSE, will stop values browsing
  7478. TSynDictionaryEvent = function(const aKey, aValue; aIndex,aCount: integer): boolean of object;
  7479. /// thread-safe dictionary to store some values from associated keys
  7480. // - will maintain a dynamic array of values, associated with a hashed dynamic
  7481. // array for the keys, so that setting or retrieving values would be O(1)
  7482. // - all process would be protected by a TSynLocker, so would be thread-safe
  7483. // - TDynArray is a wrapper which do not store anything, whereas this class
  7484. // is able to store both keys and values, and provide convenient methods to
  7485. // access the stored data, including JSON serialization and binary storage
  7486. TSynDictionary = class(TSynPersistentLocked)
  7487. protected
  7488. fKeys: TDynArrayHashed;
  7489. fValues: TDynArray;
  7490. function InArray(const aKey,aArrayValue; aAction: TSynDictionaryInArray): boolean;
  7491. public
  7492. /// initialize the dictionary storage, for a given dynamic array value
  7493. // - aKeyTypeInfo should be a dynamic array TypeInfo() RTTI pointer, which
  7494. // would store the keys within this TSynDictionary instance
  7495. // - aValueTypeInfo should be a dynamic array TypeInfo() RTTI pointer, which
  7496. // would store the values within this TSynDictionary instance
  7497. // - by default, string keys would be searched following exact case, unless
  7498. // aKeyCaseInsensitive is TRUE
  7499. constructor Create(aKeyTypeInfo,aValueTypeInfo: pointer;
  7500. aKeyCaseInsensitive: boolean=false); reintroduce; virtual;
  7501. /// finalize the storage
  7502. // - would release all internal stored values
  7503. destructor Destroy; override;
  7504. /// try to add a value associated with a primary key
  7505. // - returns the index of the inserted item, -1 if aKey is already existing
  7506. // - this method is thread-safe, since it would lock the instance
  7507. function Add(const aKey, aValue): integer;
  7508. /// store a value associated with a primary key
  7509. // - returns the index of the matching item
  7510. // - if aKey does not exist, a new entry is added
  7511. // - if aKey does exist, the existing entry is overriden with aValue
  7512. // - this method is thread-safe, since it would lock the instance
  7513. function AddOrUpdate(const aKey, aValue): integer;
  7514. /// clear the value associated via aKey
  7515. // - does not delete the entry, but reset its value
  7516. // - returns the index of the matching item, -1 if aKey was not found
  7517. // - this method is thread-safe, since it would lock the instance
  7518. function Clear(const aKey): integer;
  7519. /// delete all key/value stored in the current instance
  7520. procedure DeleteAll;
  7521. /// delete a key/value association from its supplied aKey
  7522. // - this would delete the entry, i.e. matching key and value pair
  7523. // - returns the index of the deleted item, -1 if aKey was not found
  7524. // - this method is thread-safe, since it would lock the instance
  7525. function Delete(const aKey): integer;
  7526. /// search of a primary key within the internal hashed dictionary
  7527. // - returns the index of the matching item, -1 if aKey was not found
  7528. // - if you want to access the value, you should use fSafe.Lock/Unlock:
  7529. // consider using Exists or FindAndCopy thread-safe methods instead
  7530. function Find(const aKey): integer;
  7531. /// search of a stored value by its primary key, and return a local copy
  7532. // - so this method is thread-safe
  7533. // - returns TRUE if aKey was found, FALSE if no match exists
  7534. function FindAndCopy(const aKey; out aValue): boolean;
  7535. /// search for a primary key presence
  7536. // - returns TRUE if aKey was found, FALSE if no match exists
  7537. // - this method is thread-safe
  7538. function Exists(const aKey): boolean;
  7539. /// apply a specified event over all items stored in this dictionnary
  7540. // - would browse the list in the adding order
  7541. // - returns the number of times OnEach has been called
  7542. // - this method is thread-safe, since it would lock the instance
  7543. function ForEach(const OnEach: TSynDictionaryEvent): integer; overload;
  7544. /// apply a specified event over matching items stored in this dictionnary
  7545. // - would browse the list in the adding order, comparing each key and/or
  7546. // value item with the supplied comparison functions and aKey/aValue content
  7547. // - returns the number of times OnMatch has been called, i.e. how many times
  7548. // KeyCompare(aKey,Keys[#])=0 or ValueCompare(aValue,Values[#])=0
  7549. // - this method is thread-safe, since it would lock the instance
  7550. function ForEach(const OnMatch: TSynDictionaryEvent;
  7551. KeyCompare,ValueCompare: TDynArraySortCompare; const aKey,aValue): integer; overload;
  7552. /// search aArrayValue item in a dynamic-array value associated via aKey
  7553. // - expect the stored value to be a dynamic array itself
  7554. // - would search for aKey as primary key, then use TDynArray.Find
  7555. // to delete any aArrayValue match in the associated dynamic array
  7556. // - returns FALSE if Values is not a tkDynArray, or if aKey or aArrayValue were
  7557. // not found
  7558. // - this method is thread-safe, since it would lock the instance
  7559. function FindInArray(const aKey, aArrayValue): boolean;
  7560. /// add aArrayValue item within a dynamic-array value associated via aKey
  7561. // - expect the stored value to be a dynamic array itself
  7562. // - would search for aKey as primary key, then use TDynArray.Add
  7563. // to add aArrayValue to the associated dynamic array
  7564. // - returns FALSE if Values is not a tkDynArray, or if aKey was not found
  7565. // - this method is thread-safe, since it would lock the instance
  7566. function AddInArray(const aKey, aArrayValue): boolean;
  7567. /// add once aArrayValue within a dynamic-array value associated via aKey
  7568. // - expect the stored value to be a dynamic array itself
  7569. // - would search for aKey as primary key, then use
  7570. // TDynArray.FindAndAddIfNotExisting to add once aArrayValue to the
  7571. // associated dynamic array
  7572. // - returns FALSE if Values is not a tkDynArray, or if aKey was not found
  7573. // - this method is thread-safe, since it would lock the instance
  7574. function AddOnceInArray(const aKey, aArrayValue): boolean;
  7575. /// clear aArrayValue item of a dynamic-array value associated via aKey
  7576. // - expect the stored value to be a dynamic array itself
  7577. // - would search for aKey as primary key, then use TDynArray.FindAndDelete
  7578. // to delete any aArrayValue match in the associated dynamic array
  7579. // - returns FALSE if Values is not a tkDynArray, or if aKey or aArrayValue were
  7580. // not found
  7581. // - this method is thread-safe, since it would lock the instance
  7582. function DeleteInArray(const aKey, aArrayValue): boolean;
  7583. /// replace aArrayValue item of a dynamic-array value associated via aKey
  7584. // - expect the stored value to be a dynamic array itself
  7585. // - would search for aKey as primary key, then use TDynArray.FindAndUpdate
  7586. // to delete any aArrayValue match in the associated dynamic array
  7587. // - returns FALSE if Values is not a tkDynArray, or if aKey or aArrayValue were
  7588. // not found
  7589. // - this method is thread-safe, since it would lock the instance
  7590. function UpdateInArray(const aKey, aArrayValue): boolean;
  7591. /// serialize the content as a "key":value JSON object
  7592. procedure SaveToJSON(W: TTextWriter; EnumSetsAsText: boolean=false); overload;
  7593. /// serialize the content as a "key":value JSON object
  7594. function SaveToJSON(EnumSetsAsText: boolean=false): RawUTF8; overload;
  7595. /// unserialize the content from "key":value JSON object
  7596. // - if the JSON input may not be correct (i.e. if not coming from SaveToJSON),
  7597. // you may set EnsureNoKeyCollision=TRUE for a slow but safe keys validation
  7598. function LoadFromJSON(const JSON: RawUTF8; EnsureNoKeyCollision: boolean=false): boolean; overload;
  7599. /// unserialize the content from "key":value JSON object
  7600. // - note that input JSON buffer is not modified in place: no need to create
  7601. // a temporary copy if the buffer is about to be re-used
  7602. // - if the JSON input may not be correct (i.e. if not coming from SaveToJSON),
  7603. // you may set EnsureNoKeyCollision=TRUE for a slow but safe keys validation
  7604. function LoadFromJSON(JSON: PUTF8Char; EnsureNoKeyCollision: boolean=false): boolean; overload;
  7605. /// save the content as SynLZ-compressed raw binary data
  7606. // - warning: this format is tied to the values low-level RTTI, so if you
  7607. // change the value/key type definitions, LoadFromBinary() would fail
  7608. function SaveToBinary: RawByteString;
  7609. /// load the content from SynLZ-compressed raw binary data
  7610. // - as previously saved by SaveToBinary method
  7611. function LoadFromBinary(const binary: RawByteString): boolean;
  7612. /// returns how many items are currently stored in this dictionary
  7613. // - this method is thread-safe
  7614. function Count: integer;
  7615. /// direct access to the primary key identifiers
  7616. // - if you want to access the keys, you should use fSafe.Lock/Unlock
  7617. property Keys: TDynArrayHashed read fKeys;
  7618. /// direct access to the associated stored values
  7619. // - if you want to access the values, you should use fSafe.Lock/Unlock
  7620. property Values: TDynArray read fValues;
  7621. end;
  7622. /// event signature to locate a service for a given string key
  7623. // - used e.g. by TRawUTF8ObjectCacheList.OnKeyResolve property
  7624. TOnKeyResolve = function(const aInterface: TGUID; const Key: RawUTF8; out Obj): boolean of object;
  7625. /// event signature to notify a given string key
  7626. TOnKeyNotify = procedure(Sender: TObject; const Key: RawUTF8) of object;
  7627. const
  7628. /// convert identified field types into high-level ORM types
  7629. // - as will be implemented in unit mORMot.pas
  7630. SQLDBFIELDTYPE_TO_DELPHITYPE: array[TSQLDBFieldType] of RawUTF8 = (
  7631. '???','???', 'Int64', 'Double', 'Currency', 'TDateTime', 'RawUTF8', 'TSQLRawBlob');
  7632. type
  7633. /// handle memory mapping of a file content
  7634. /// used to store and retrieve Words in a sorted array
  7635. TMemoryMap = {$ifndef UNICODE}object{$else}record{$endif}
  7636. private
  7637. fBuf: PAnsiChar;
  7638. fBufSize: cardinal;
  7639. fFile: THandle;
  7640. {$ifdef MSWINDOWS}
  7641. fMap: THandle;
  7642. {$endif}
  7643. fFileSize: Int64;
  7644. fFileLocal: boolean;
  7645. public
  7646. /// map the corresponding file handle
  7647. // - if aCustomSize and aCustomOffset are specified, the corresponding
  7648. // map view if created (by default, will map whole file)
  7649. function Map(aFile: THandle; aCustomSize: cardinal=0; aCustomOffset: Int64=0): boolean; overload;
  7650. /// map the file specified by its name
  7651. // - file will be closed when UnMap will be called
  7652. function Map(const aFileName: TFileName): boolean; overload;
  7653. /// set a fixed buffer for the content
  7654. // - emulated a memory-mapping from an existing buffer
  7655. procedure Map(aBuffer: pointer; aBufferSize: cardinal); overload;
  7656. /// unmap the file
  7657. procedure UnMap;
  7658. /// retrieve the memory buffer mapped to the file content
  7659. property Buffer: PAnsiChar read fBuf;
  7660. /// retrieve the buffer size
  7661. property Size: cardinal read fBufSize;
  7662. end;
  7663. {$M+}
  7664. /// able to read a UTF-8 text file using memory map
  7665. // - much faster than TStringList.LoadFromFile()
  7666. // - will ignore any trailing UTF-8 BOM in the file content, but will not
  7667. // expect one either
  7668. TMemoryMapText = class
  7669. protected
  7670. fLines: PPointerArray;
  7671. fLinesMax: integer;
  7672. fCount: integer;
  7673. fMapEnd: PUTF8Char;
  7674. fMap: TMemoryMap;
  7675. fFileName: TFileName;
  7676. fAppendedLines: TRawUTF8DynArray;
  7677. fAppendedLinesCount: integer;
  7678. function GetLine(aIndex: integer): RawUTF8; {$ifdef HASINLINE}inline;{$endif}
  7679. function GetString(aIndex: integer): string; {$ifdef HASINLINE}inline;{$endif}
  7680. /// call once by Create constructors when fMap has been initialized
  7681. procedure LoadFromMap(AverageLineLength: integer=32); virtual;
  7682. /// call once per line, from LoadFromMap method
  7683. // - default implementation will set fLines[fCount] := LineBeg;
  7684. // - override this method to add some per-line process at loading: it will
  7685. // avoid reading the entire file more than once
  7686. procedure ProcessOneLine(LineBeg, LineEnd: PUTF8Char); virtual;
  7687. public
  7688. /// initialize the memory mapped text file
  7689. // - this default implementation just do nothing but is called by overloaded
  7690. // constructors so may be overriden to initialize an inherited class
  7691. constructor Create; overload; virtual;
  7692. /// read an UTF-8 encoded text file
  7693. // - every line beginning is stored into LinePointers[]
  7694. constructor Create(const aFileName: TFileName); overload;
  7695. /// read an UTF-8 encoded text file content
  7696. // - every line beginning is stored into LinePointers[]
  7697. // - this overloaded constructor accept an existing memory buffer (some
  7698. // uncompressed data e.g.)
  7699. constructor Create(aFileContent: PUTF8Char; aFileSize: integer); overload;
  7700. /// release the memory map and internal LinePointers[]
  7701. destructor Destroy; override;
  7702. /// save the whole content into a specified stream
  7703. // - including any runtime appended values via AddInMemoryLine()
  7704. procedure SaveToStream(Dest: TStream; const Header: RawUTF8);
  7705. /// save the whole content into a specified file
  7706. // - including any runtime appended values via AddInMemoryLine()
  7707. // - an optional header text can be added to the beginning of the file
  7708. procedure SaveToFile(FileName: TFileName; const Header: RawUTF8='');
  7709. /// add a new line to the already parsed content
  7710. // - this line won't be stored in the memory mapped file, but stay in memory
  7711. // and appended to the existing lines, until this instance is released
  7712. procedure AddInMemoryLine(const aNewLine: RawUTF8); virtual;
  7713. /// clear all in-memory appended rows
  7714. procedure AddInMemoryLinesClear; virtual;
  7715. /// retrieve the number of UTF-8 chars of the given line
  7716. // - warning: no range check is performed about supplied index
  7717. function LineSize(aIndex: integer): integer;
  7718. {$ifdef HASINLINE}inline;{$endif}
  7719. /// check if there is at least a given number of UTF-8 chars in the given line
  7720. // - this is faster than LineSize(aIndex)<aMinimalCount for big lines
  7721. function LineSizeSmallerThan(aIndex, aMinimalCount: integer): boolean;
  7722. {$ifdef HASINLINE}inline;{$endif}
  7723. /// returns TRUE if the supplied text is contained in the corresponding line
  7724. function LineContains(const aUpperSearch: RawUTF8; aIndex: Integer): Boolean; virtual;
  7725. /// retrieve a line content as UTF-8
  7726. // - a temporary UTF-8 string is created
  7727. // - will return '' if aIndex is out of range
  7728. property Lines[aIndex: integer]: RawUTF8 read GetLine;
  7729. /// retrieve a line content as generic VCL string type
  7730. // - a temporary VCL string is created (after conversion for UNICODE Delphi)
  7731. // - will return '' if aIndex is out of range
  7732. property Strings[aIndex: integer]: string read GetString;
  7733. /// direct access to each text line
  7734. // - use LineSize() method to retrieve line length, since end of line will
  7735. // NOT end with #0, but with #13 or #10
  7736. // - warning: no range check is performed about supplied index
  7737. property LinePointers: PPointerArray read fLines;
  7738. /// the memory map used to access the raw file content
  7739. property Map: TMemoryMap read fMap;
  7740. published
  7741. /// the file name which was opened by this instance
  7742. property FileName: TFileName read fFileName write fFileName;
  7743. /// the number of text lines
  7744. property Count: integer read fCount;
  7745. end;
  7746. {$M-}
  7747. /// a fake TStream, which will just count the number of bytes written
  7748. TFakeWriterStream = class(TStream)
  7749. public
  7750. function Read(var Buffer; Count: Longint): Longint; override;
  7751. function Write(const Buffer; Count: Longint): Longint; override;
  7752. function Seek(Offset: Longint; Origin: Word): Longint; override;
  7753. end;
  7754. /// a TStream using a RawByteString as internal storage
  7755. // - default TStringStream uses WideChars since Delphi 2009, so it is
  7756. // not compatible with previous versions, and it does make sense to
  7757. // work with RawByteString in our UTF-8 oriented framework
  7758. TRawByteStringStream = class(TStream)
  7759. protected
  7760. fDataString: RawByteString;
  7761. fPosition: Integer;
  7762. procedure SetSize(NewSize: Longint); override;
  7763. public
  7764. constructor Create(const aString: RawByteString=''); overload;
  7765. function Read(var Buffer; Count: Longint): Longint; override;
  7766. function Seek(Offset: Longint; Origin: Word): Longint; override;
  7767. function Write(const Buffer; Count: Longint): Longint; override;
  7768. property DataString: RawByteString read fDataString write fDataString;
  7769. end;
  7770. /// a TStream pointing to some in-memory data, for instance UTF-8 text
  7771. // - warning: there is no local copy of the supplied content: the
  7772. // source data must be available during all the TSynMemoryStream usage
  7773. TSynMemoryStream = class(TCustomMemoryStream)
  7774. public
  7775. /// create a TStream with the supplied text data
  7776. // - warning: there is no local copy of the supplied content: the aText
  7777. // variable must be available during all the TSynMemoryStream usage:
  7778. // don't release aText before calling TSynMemoryStream.Free
  7779. // - aText can be on any AnsiString format, e.g. RawUTF8 or RawByteString
  7780. constructor Create(const aText: RawByteString); overload;
  7781. /// create a TStream with the supplied data buffer
  7782. // - warning: there is no local copy of the supplied content: the
  7783. // Data/DataLen buffer must be available during all the TSynMemoryStream usage:
  7784. // don't release the source Data before calling TSynMemoryStream.Free
  7785. constructor Create(Data: pointer; DataLen: integer); overload;
  7786. /// this TStream is read-only: calling this method will raise an exception
  7787. function Write(const Buffer; Count: Longint): Longint; override;
  7788. end;
  7789. /// a TStream created from a file content, using fast memory mapping
  7790. TSynMemoryStreamMapped = class(TSynMemoryStream)
  7791. protected
  7792. fMap: TMemoryMap;
  7793. fFileStream: TFileStream;
  7794. fFileName: TFileName;
  7795. public
  7796. /// create a TStream from a file content using fast memory mapping
  7797. // - if aCustomSize and aCustomOffset are specified, the corresponding
  7798. // map view if created (by default, will map whole file)
  7799. constructor Create(const aFileName: TFileName;
  7800. aCustomSize: cardinal=0; aCustomOffset: Int64=0); overload;
  7801. /// create a TStream from a file content using fast memory mapping
  7802. // - if aCustomSize and aCustomOffset are specified, the corresponding
  7803. // map view if created (by default, will map whole file)
  7804. constructor Create(aFile: THandle;
  7805. aCustomSize: cardinal=0; aCustomOffset: Int64=0); overload;
  7806. /// release any internal mapped file instance
  7807. destructor Destroy; override;
  7808. /// the file name, if created from such Create(aFileName) constructor
  7809. property FileName: TFileName read fFileName;
  7810. end;
  7811. /// available kind of integer array storage, corresponding to the data layout
  7812. // - wkUInt32 will write the content as "plain" 4 bytes binary (this is the
  7813. // prefered way if the integers can be negative)
  7814. // - wkVarUInt32 will write the content using our 32-bit variable-length integer
  7815. // encoding
  7816. // - wkVarInt32 will write the content using our 32-bit variable-length integer
  7817. // encoding and the by-two complement (0=0,1=1,2=-1,3=2,4=-2...)
  7818. // - wkSorted will write an increasing array of integers, handling the special
  7819. // case of a difference of similar value (e.g. 1) between two values
  7820. // - wkOffsetU and wkOffsetI will write the difference between two successive
  7821. // values, handling constant difference (Unsigned or Integer) in an optimized manner
  7822. // - wkFakeMarker won't be used by WriteVarUInt32Array, but to notify a
  7823. // custom encoding
  7824. TFileBufferWriterKind = (wkUInt32, wkVarUInt32, wkVarInt32, wkSorted,
  7825. wkOffsetU, wkOffsetI, wkFakeMarker);
  7826. /// this class can be used to speed up writing to a file
  7827. // - big speed up if data is written in small blocks
  7828. // - also handle optimized storage of any dynamic array of Integer/Int64/RawUTF8
  7829. TFileBufferWriter = class
  7830. private
  7831. fPos: integer;
  7832. fBufLen: Integer;
  7833. fStream: TStream;
  7834. fTotalWritten: Int64;
  7835. fInternalStream: boolean;
  7836. fTag: PtrInt;
  7837. fBuf: RawByteString;
  7838. public
  7839. /// initialize the buffer, and specify a file handle to use for writing
  7840. // - use an internal buffer of the specified size
  7841. constructor Create(aFile: THandle; BufLen: integer=65536); overload;
  7842. /// initialize the buffer, and specify a TStream to use for writing
  7843. // - use an internal buffer of the specified size
  7844. constructor Create(aStream: TStream; BufLen: integer=65536); overload;
  7845. /// initialize the buffer, and specify a file to use for writing
  7846. // - use an internal buffer of the specified size
  7847. // - would replace any existing file by default, unless Append is TRUE
  7848. constructor Create(const aFileName: TFileName; BufLen: integer=65536;
  7849. Append: boolean=false); overload;
  7850. /// initialize the buffer, using an internal TStream instance
  7851. // - parameter could be e.g. THeapMemoryStream or TRawByteStringStream
  7852. // - use Flush then TMemoryStream(Stream) to retrieve its content, or
  7853. // TRawByteStringStream(Stream).DataString
  7854. constructor Create(aClass: TStreamClass; BufLen: integer=4096); overload;
  7855. /// release internal TStream (after AssignToHandle call)
  7856. destructor Destroy; override;
  7857. /// append some data at the current position
  7858. procedure Write(Data: pointer; DataLen: integer); overload;
  7859. /// append 1 byte of data at the current position
  7860. procedure Write1(Data: Byte); {$ifdef HASINLINE}inline;{$endif}
  7861. /// append 4 bytes of data at the current position
  7862. procedure Write4(Data: integer); {$ifdef HASINLINE}inline;{$endif}
  7863. /// append 4 bytes of data, encoded as BigEndian, at the current position
  7864. procedure Write4BigEndian(Data: integer); {$ifdef HASINLINE}inline;{$endif}
  7865. /// append 8 bytes of data at the current position
  7866. procedure Write8(const Data8Bytes); {$ifdef HASINLINE}inline;{$endif}
  7867. /// append the same byte a given number of occurences at the current position
  7868. procedure WriteN(Data: Byte; Count: integer);
  7869. /// append some UTF-8 encoded text at the current position
  7870. // - will write the string length, then the string content, as expected
  7871. // by the FromVarString() function
  7872. procedure Write(const Text: RawByteString); overload;
  7873. /// append some UTF-8 encoded text at the current position
  7874. // - will write the string length, then the string content
  7875. procedure WriteShort(const Text: ShortString);
  7876. /// append some content at the current position
  7877. // - will write the binary data, without any length prefix
  7878. procedure WriteBinary(const Data: RawByteString);
  7879. {$ifndef NOVARIANTS}
  7880. /// append some variant value at the current position
  7881. // - matches FromVarVariant() and VariantSave/VariantLoad format
  7882. procedure Write(const Value: variant); overload;
  7883. {$endif}
  7884. /// append "New[0..Len-1] xor Old[0..Len-1]" bytes
  7885. // - as used e.g. by ZeroCompressXor/TSynBloomFilterDiff.SaveTo
  7886. procedure WriteXor(New,Old: PAnsiChar; Len: integer; crc: PCardinal=nil);
  7887. /// append a cardinal value using 32-bit variable-length integer encoding
  7888. procedure WriteVarUInt32(Value: PtrUInt);
  7889. /// append an integer value using 32-bit variable-length integer encoding of
  7890. // the by-two complement of the given value
  7891. procedure WriteVarInt32(Value: PtrInt);
  7892. /// append an integer value using 64-bit variable-length integer encoding of
  7893. // the by-two complement of the given value
  7894. procedure WriteVarInt64(Value: Int64);
  7895. /// append an unsigned integer value using 64-bit variable-length encoding
  7896. procedure WriteVarUInt64(Value: QWord);
  7897. /// append cardinal values (NONE must be negative!) using 32-bit
  7898. // variable-length integer encoding or other specialized algorithm,
  7899. // depending on the data layout
  7900. procedure WriteVarUInt32Array(const Values: TIntegerDynArray; ValuesCount: integer;
  7901. DataLayout: TFileBufferWriterKind);
  7902. /// append UInt64 values using 64-bit variable length integer encoding
  7903. // - if Offset is TRUE, then it will store the difference between
  7904. // two values using 32-bit variable-length integer encoding (in this case,
  7905. // a fixed-sized record storage is also handled separately)
  7906. procedure WriteVarUInt64DynArray(const Values: TInt64DynArray;
  7907. ValuesCount: integer; Offset: Boolean);
  7908. /// append the RawUTF8 dynamic array
  7909. // - handled the fixed size strings array case in a very efficient way
  7910. procedure WriteRawUTF8DynArray(const Values: TRawUTF8DynArray; ValuesCount: integer);
  7911. /// append the RawUTF8List content
  7912. // - if StoreObjectsAsVarUInt32 is TRUE, all Objects[] properties will be
  7913. // stored as VarUInt32
  7914. procedure WriteRawUTF8List(List: TRawUTF8List; StoreObjectsAsVarUInt32: Boolean=false);
  7915. /// append a TStream content
  7916. // - is StreamSize is left as -1, the Stream.Size is used
  7917. // - the size of the content is stored in the resulting stream
  7918. procedure WriteStream(aStream: TCustomMemoryStream; aStreamSize: Integer=-1);
  7919. /// allows to write directly to a memory buffer
  7920. // - caller should specify the maximum possible number of bytes to be written
  7921. // - then write the data to the returned pointer, and call WriteDirectEnd
  7922. function WriteDirectStart(maxSize: integer; const TooBigMessage: RawUTF8=''): PByte;
  7923. /// finalize a direct write to a memory buffer
  7924. // - by specifying the number of bytes written to the buffer
  7925. procedure WriteDirectEnd(realSize: integer);
  7926. /// write any pending data in the internal buffer to the file
  7927. // - after a Flush, it's possible to call FileSeek64(aFile,....)
  7928. // - returns the number of bytes written between two FLush method calls
  7929. function Flush: Int64;
  7930. /// rewind the Stream to the position when Create() was called
  7931. // - note that this does not clear the Stream content itself, just
  7932. // move back its writing position to its initial place
  7933. procedure CancelAll; virtual;
  7934. /// the associated writing stream
  7935. property Stream: TStream read fStream;
  7936. /// get the byte count written since last Flush
  7937. property TotalWritten: Int64 read fTotalWritten;
  7938. /// simple property used to store some integer content
  7939. property Tag: PtrInt read fTag write fTag;
  7940. end;
  7941. PFileBufferReader = ^TFileBufferReader;
  7942. /// this structure can be used to speed up reading from a file
  7943. // - use internaly memory mapped files for a file up to 2 GB (Windows has
  7944. // problems with memory mapped files bigger than this size limit - at least
  7945. // with 32 bit executables) - but sometimes, Windows fails to allocate
  7946. // more than 512 MB for a memory map, because it does lack of contiguous
  7947. // memory space: in this case, we fall back on direct file reading
  7948. // - maximum handled file size has no limit (but will use slower direct
  7949. // file reading)
  7950. // - is defined either as an object either as a record, due to a bug
  7951. // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  7952. // if defined as an object on the stack, but will be as a record :(
  7953. {$ifdef UNICODE}
  7954. TFileBufferReader = record
  7955. private
  7956. {$else}
  7957. TFileBufferReader = object
  7958. protected
  7959. {$endif}
  7960. fCurrentPos: PtrUInt;
  7961. fMap: TMemoryMap;
  7962. /// get Isize + buffer from current memory map or fBufTemp into (P,PEnd)
  7963. procedure ReadChunk(var P, PEnd: PByte; var BufTemp: RawByteString);
  7964. public
  7965. /// initialize the buffer, and specify a file to use for reading
  7966. // - will try to map the whole file content in memory
  7967. // - if memory mapping failed, methods will use default slower file API
  7968. procedure Open(aFile: THandle);
  7969. /// initialize the buffer from an already existing memory block
  7970. // - may be e.g. a resource or a TMemoryStream
  7971. procedure OpenFrom(aBuffer: pointer; aBufferSize: cardinal); overload;
  7972. /// initialize the buffer from an already existing Stream
  7973. // - accept either TFileStream or TCustomMemoryStream kind of stream
  7974. function OpenFrom(Stream: TStream): boolean; overload;
  7975. /// close all internal mapped files
  7976. // - call Open() again to use the Read() methods
  7977. procedure Close;
  7978. {$ifndef CPU64}
  7979. /// change the current reading position, from the beginning of the file
  7980. // - returns TRUE if success, or FALSE if Offset is out of range
  7981. function Seek(Offset: Int64): boolean; overload;
  7982. {$endif}
  7983. /// change the current reading position, from the beginning of the file
  7984. // - returns TRUE if success, or FALSE if Offset is out of range
  7985. function Seek(Offset: PtrInt): boolean; overload;
  7986. /// read some bytes from the given reading position
  7987. // - returns the number of bytes which was read
  7988. // - if Data is nil, it won't read content but will forward reading position
  7989. function Read(Data: pointer; DataLen: integer): integer; overload;
  7990. /// read some UTF-8 encoded text at the current position
  7991. // - returns the resulting text length, in bytes
  7992. function Read(out Text: RawUTF8): integer; overload;
  7993. /// read some buffer texgt at the current position
  7994. // - returns the resulting text length, in bytes
  7995. function Read(out Text: RawByteString): integer; overload;
  7996. /// read some UTF-8 encoded text at the current position
  7997. // - returns the resulting text
  7998. function ReadRawUTF8: RawUTF8; {$ifdef HASINLINE}inline;{$endif}
  7999. /// read one byte
  8000. // - if reached end of file, don't raise any error, but returns 0
  8001. function ReadByte: PtrUInt; {$ifdef HASINLINE}inline;{$endif}
  8002. /// read one cardinal, which was written as fixed length
  8003. // - if reached end of file, don't raise any error, but returns 0
  8004. function ReadCardinal: cardinal;
  8005. /// read one cardinal value encoded using our 32-bit variable-length integer
  8006. function ReadVarUInt32: PtrUInt;
  8007. /// read one integer value encoded using our 32-bit variable-length integer,
  8008. // and the by-two complement
  8009. function ReadVarInt32: PtrInt;
  8010. /// read one UInt64 value encoded using our 64-bit variable-length integer
  8011. function ReadVarUInt64: QWord;
  8012. /// read one Int64 value encoded using our 64-bit variable-length integer
  8013. function ReadVarInt64: Int64;
  8014. /// retrieved cardinal values encoded with TFileBufferWriter.WriteVarUInt32Array
  8015. // - returns the number of items read into Values[] (may differ from
  8016. // length(Values), which will be resized, so could be void before calling)
  8017. // - if the returned integer is negative, it is -Count, and testifies from
  8018. // wkFakeMarker and the content should be retrieved by the caller
  8019. function ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt;
  8020. /// retrieved Int64 values encoded with TFileBufferWriter.WriteVarUInt64DynArray
  8021. // - returns the number of items read into Values[] (may differ from length(Values))
  8022. function ReadVarUInt64Array(var Values: TInt64DynArray): PtrInt;
  8023. /// retrieved RawUTF8 values encoded with TFileBufferWriter.WriteRawUTF8DynArray
  8024. // - returns the number of items read into Values[] (may differ from length(Values))
  8025. function ReadVarRawUTF8DynArray(var Values: TRawUTF8DynArray): PtrInt;
  8026. /// retrieve the RawUTF8List content encoded with TFileBufferWriter.WriteRawUTF8List
  8027. // - if StoreObjectsAsVarUInt32 was TRUE, all Objects[] properties will be
  8028. // retrieved as VarUInt32
  8029. function ReadRawUTF8List(List: TRawUTF8List): boolean;
  8030. /// retrieve a pointer to the current position, for a given data length
  8031. // - if the data is available in the current memory mapped file, it
  8032. // will just return a pointer to it
  8033. // - otherwise (i.e. if the data is split between to 1GB memory map buffers),
  8034. // data will be copied into the temporary aTempData buffer before retrieval
  8035. function ReadPointer(DataLen: PtrUInt; var aTempData: RawByteString): pointer;
  8036. /// create a TMemoryStream instance from the current position
  8037. // - the content size is either specified by DataLen>=0, either available at
  8038. // the current position, as saved by TFileBufferWriter.WriteStream method
  8039. // - if this content fit in the current 1GB memory map buffer, a
  8040. // TSynMemoryStream instance is returned, with no data copy (faster)
  8041. // - if this content is not already mapped in memory, a separate memory map
  8042. // will be created (the returned instance is a TSynMemoryStreamMapped)
  8043. function ReadStream(DataLen: PtrInt=-1): TCustomMemoryStream;
  8044. /// retrieve the current in-memory pointer
  8045. // - if file was not memory-mapped, returns nil
  8046. function CurrentMemory: pointer;
  8047. /// retrieve the current in-memory position
  8048. // - if file was not memory-mapped, returns -1
  8049. function CurrentPosition: integer;
  8050. /// raise an exception in case of invalid content
  8051. procedure ErrorInvalidContent;
  8052. /// read-only access to the global file size
  8053. property FileSize: Int64 read fMap.fFileSize;
  8054. /// read-only access to the global mapped buffer binary
  8055. property MappedBuffer: PAnsiChar read fMap.fBuf;
  8056. end;
  8057. /// implements a thread-safe Bloom Filter storage
  8058. // - a "Bloom Filter" is a space-efficient probabilistic data structure,
  8059. // that is used to test whether an element is a member of a set. False positive
  8060. // matches are possible, but false negatives are not. Elements can be added to
  8061. // the set, but not removed. Typical use cases are to avoid unecessary
  8062. // slow disk or network access if possible, when a lot of items are involved.
  8063. // - memory use is very low, when compared to storage of all values: fewer
  8064. // than 10 bits per element are required for a 1% false positive probability,
  8065. // independent of the size or number of elements in the set - for instance,
  8066. // storing 10,000,000 items presence with 1% of false positive ratio
  8067. // would consume only 11.5 MB of memory, using 7 hash functions
  8068. // - use Insert() methods to add an item to the internal bits array, and
  8069. // Reset() to clear all bits array, if needed
  8070. // - MayExist() function would check if the supplied item was probably set
  8071. // - SaveTo() and LoadFrom() methods allow transmission of the bits array,
  8072. // for a disk/database storage or transmission over a network
  8073. // - internally, several (hardware-accelerated) crc32c hash functions will be
  8074. // used, with some random seed values, to simulate several hashing functions
  8075. // - Insert/MayExist/Reset methods are thread-safe
  8076. TSynBloomFilter = class(TSynPersistentLocked)
  8077. private
  8078. fSize: cardinal;
  8079. fFalsePositivePercent: double;
  8080. fBits: cardinal;
  8081. fHashFunctions: cardinal;
  8082. fInserted: cardinal;
  8083. fStore: RawByteString;
  8084. function GetInserted: cardinal;
  8085. public
  8086. /// initialize the internal bits storage for a given number of items
  8087. // - by default, internal bits array size will be guess from a 1 % false
  8088. // positive rate - but you may specify another value, to reduce memory use
  8089. // - this constructor would compute and initialize Bits and HashFunctions
  8090. // corresponding to the expected false positive ratio
  8091. constructor Create(aSize: integer; aFalsePositivePercent: double = 1); reintroduce; overload;
  8092. /// initialize the internal bits storage from a SaveTo() binary buffer
  8093. // - this constructor will initialize the internal bits array calling LoadFrom()
  8094. constructor Create(const aSaved: RawByteString; aMagic: cardinal=$B1003F11); reintroduce; overload;
  8095. /// add an item in the internal bits array storage
  8096. // - this method is thread-safe
  8097. procedure Insert(const aValue: RawByteString); overload;
  8098. /// add an item in the internal bits array storage
  8099. // - this method is thread-safe
  8100. procedure Insert(aValue: pointer; aValueLen: integer); overload; virtual;
  8101. /// clear the internal bits array storage
  8102. // - you may call this method after some time, if some items may have
  8103. // been removed, to reduce false positives
  8104. // - this method is thread-safe
  8105. procedure Reset; virtual;
  8106. /// returns TRUE if the supplied items was probably set via Insert()
  8107. // - some false positive may occur, but not much than FalsePositivePercent
  8108. // - this method is thread-safe
  8109. function MayExist(const aValue: RawByteString): boolean; overload;
  8110. /// returns TRUE if the supplied items was probably set via Insert()
  8111. // - some false positive may occur, but not much than FalsePositivePercent
  8112. // - this method is thread-safe
  8113. function MayExist(aValue: pointer; aValueLen: integer): boolean; overload;
  8114. /// store the internal bits array into a binary buffer
  8115. // - may be used to transmit or store the state of a dataset, avoiding
  8116. // to recompute all Insert() at program startup, or to synchronize
  8117. // networks nodes information and reduce the number of remote requests
  8118. function SaveTo(aMagic: cardinal=$B1003F11): RawByteString; overload;
  8119. /// store the internal bits array into a binary buffer
  8120. // - may be used to transmit or store the state of a dataset, avoiding
  8121. // to recompute all Insert() at program startup, or to synchronize
  8122. // networks nodes information and reduce the number of remote requests
  8123. procedure SaveTo(aDest: TFileBufferWriter; aMagic: cardinal=$B1003F11); overload;
  8124. /// read the internal bits array from a binary buffer
  8125. // - as previously serialized by the SaveTo method
  8126. // - may be used to transmit or store the state of a dataset
  8127. function LoadFrom(const aSaved: RawByteString; aMagic: cardinal=$B1003F11): boolean; overload;
  8128. /// read the internal bits array from a binary buffer
  8129. // - as previously serialized by the SaveTo method
  8130. // - may be used to transmit or store the state of a dataset
  8131. function LoadFrom(P: PByte; PLen: integer; aMagic: cardinal=$B1003F11): boolean; overload; virtual;
  8132. published
  8133. /// maximum number of items which are expected to be inserted
  8134. property Size: cardinal read fSize;
  8135. /// expected percentage (1..100) of false positive results for MayExists()
  8136. property FalsePositivePercent: double read fFalsePositivePercent;
  8137. /// number of bits stored in the internal bits array
  8138. property Bits: cardinal read fBits;
  8139. /// how many hash functions would be applied for each Insert()
  8140. property HashFunctions: cardinal read fHashFunctions;
  8141. /// how many times the Insert() method has been called
  8142. property Inserted: cardinal read GetInserted;
  8143. end;
  8144. /// implements a thread-safe differential Bloom Filter storage
  8145. // - this inherited class is able to compute incremental serialization of
  8146. // its internal bits array, to reduce network use
  8147. // - an obfuscated revision counter is used to identify storage history
  8148. TSynBloomFilterDiff = class(TSynBloomFilter)
  8149. protected
  8150. fRevision: Int64;
  8151. fSnapShotAfterMinutes: cardinal;
  8152. fSnapshotAfterInsertCount: cardinal;
  8153. fSnapshotTimeStamp: Int64;
  8154. fSnapshotInsertCount: cardinal;
  8155. fKnownRevision: Int64;
  8156. fKnownStore: RawByteString;
  8157. public
  8158. /// add an item in the internal bits array storage
  8159. // - this overloaded thread-safe method would compute fRevision
  8160. procedure Insert(aValue: pointer; aValueLen: integer); override;
  8161. /// clear the internal bits array storage
  8162. // - this overloaded thread-safe method would reset fRevision
  8163. procedure Reset; override;
  8164. /// store the internal bits array into an incremental binary buffer
  8165. // - here the difference from a previous SaveToDiff revision will be computed
  8166. // - if aKnownRevision is outdated (e.g. if equals 0), the whole bits array
  8167. // would be returned, and around 10 bits per item would be transmitted
  8168. // (for 1% false positive ratio)
  8169. // - incremental retrieval would then return around 10 bytes per newly added
  8170. // item since the last snapshot reference state (with 1% ratio, i.e. 7 hash
  8171. // functions)
  8172. function SaveToDiff(const aKnownRevision: Int64): RawByteString;
  8173. /// use the current internal bits array state as known revision
  8174. // - is done the first time SaveToDiff() is called, then after 1/32th of
  8175. // the filter size has been inserted (see SnapshotAfterInsertCount property),
  8176. // or after SnapShotAfterMinutes property timeout period
  8177. procedure DiffSnapshot;
  8178. /// retrieve the revision number from an incremental binary buffer
  8179. // - returns 0 if the supplied binary buffer does not match this bloom filter
  8180. function DiffKnownRevision(const aDiff: RawByteString): Int64;
  8181. /// read the internal bits array from an incremental binary buffer
  8182. // - as previously serialized by the SaveToDiff() method
  8183. // - may be used to transmit or store the state of a dataset
  8184. // - returns false if the supplied content is incorrect, e.g. if the known
  8185. // revision is deprecated
  8186. function LoadFromDiff(const aDiff: RawByteString): boolean;
  8187. /// the opaque revision number of this internal storage
  8188. property Revision: Int64 read fRevision;
  8189. /// after how many Insert() the internal bits array storage should be
  8190. // promoted as known revision
  8191. // - equals Size div 32 by default
  8192. property SnapshotAfterInsertCount: cardinal read fSnapshotAfterInsertCount
  8193. write fSnapshotAfterInsertCount;
  8194. /// after how many time the internal bits array storage should be
  8195. // promoted as known revision
  8196. // - equals 30 minutes by default
  8197. property SnapShotAfterMinutes: cardinal read fSnapShotAfterMinutes
  8198. write fSnapShotAfterMinutes;
  8199. end;
  8200. /// FileSeek() overloaded function, working with huge files
  8201. // - Delphi FileSeek() is buggy -> use this function to safe access files > 2 GB
  8202. // (thanks to sanyin for the report)
  8203. function FileSeek64(Handle: THandle; const Offset: Int64; Origin: cardinal): Int64;
  8204. /// encode the supplied data as an UTF-8 valid JSON object content
  8205. // - data must be supplied two by two, as Name,Value pairs, e.g.
  8206. // ! JSONEncode(['name','John','year',1972]) = '{"name":"John","year":1972}'
  8207. // - or you can specify nested arrays or objects with '['..']' or '{'..'}':
  8208. // ! J := JSONEncode(['doc','{','name','John','abc','[','a','b','c',']','}','id',123]);
  8209. // ! assert(J='{"doc":{"name":"John","abc":["a","b","c"]},"id":123}');
  8210. // - note that cardinal values should be type-casted to Int64() (otherwise
  8211. // the integer mapped value will be transmitted, therefore wrongly)
  8212. // - you can pass nil as parameter for a null JSON value
  8213. function JSONEncode(const NameValuePairs: array of const): RawUTF8; overload;
  8214. {$ifndef NOVARIANTS}
  8215. /// encode the supplied (extended) JSON content, with parameters,
  8216. // as an UTF-8 valid JSON object content
  8217. // - in addition to the JSON RFC specification strict mode, this method will
  8218. // handle some BSON-like extensions, e.g. unquoted field names:
  8219. // ! aJSON := JSONEncode('{id:?,%:{name:?,birthyear:?}}',['doc'],[10,'John',1982]);
  8220. // - you can use nested _Obj() / _Arr() instances
  8221. // ! aJSON := JSONEncode('{%:{$in:[?,?]}}',['type'],['food','snack']);
  8222. // ! aJSON := JSONEncode('{type:{$in:?}}',[],[_Arr(['food','snack'])]);
  8223. // ! // will both return
  8224. // ! '{"type":{"$in":["food","snack"]}}')
  8225. // - if the SynMongoDB unit is used in the application, the MongoDB Shell
  8226. // syntax will also be recognized to create TBSONVariant, like
  8227. // ! new Date() ObjectId() MinKey MaxKey /<jRegex>/<jOptions>
  8228. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  8229. // ! aJSON := JSONEncode('{name:?,field:/%/i}',['acme.*corp'],['John']))
  8230. // ! // will return
  8231. // ! '{"name":"John","field":{"$regex":"acme.*corp","$options":"i"}}'
  8232. // - will call internally _JSONFastFmt() to create a temporary TDocVariant with
  8233. // all its features - so is slightly slower than other JSONEncode* functions
  8234. function JSONEncode(const Format: RawUTF8; const Args,Params: array of const): RawUTF8; overload;
  8235. {$endif}
  8236. /// encode the supplied RawUTF8 array data as an UTF-8 valid JSON array content
  8237. function JSONEncodeArrayUTF8(const Values: array of RawUTF8): RawUTF8; overload;
  8238. /// encode the supplied integer array data as a valid JSON array
  8239. function JSONEncodeArrayInteger(const Values: array of integer): RawUTF8; overload;
  8240. /// encode the supplied floating-point array data as a valid JSON array
  8241. function JSONEncodeArrayDouble(const Values: array of double): RawUTF8; overload;
  8242. /// encode the supplied array data as a valid JSON array content
  8243. // - if WithoutBraces is TRUE, no [ ] will be generated
  8244. // - note that cardinal values should be type-casted to Int64() (otherwise
  8245. // the integer mapped value will be transmitted, therefore wrongly)
  8246. function JSONEncodeArrayOfConst(const Values: array of const;
  8247. WithoutBraces: boolean=false): RawUTF8; overload;
  8248. /// encode the supplied array data as a valid JSON array content
  8249. // - if WithoutBraces is TRUE, no [ ] will be generated
  8250. // - note that cardinal values should be type-casted to Int64() (otherwise
  8251. // the integer mapped value will be transmitted, therefore wrongly)
  8252. procedure JSONEncodeArrayOfConst(const Values: array of const;
  8253. WithoutBraces: boolean; var result: RawUTF8); overload;
  8254. /// encode as JSON {"name":value} object, from a potential SQL quoted value
  8255. // - will unquote the SQLValue using TTextWriter.AddQuotedStringAsJSON()
  8256. procedure JSONEncodeNameSQLValue(const Name,SQLValue: RawUTF8; var result: RawUTF8);
  8257. /// decode the supplied UTF-8 JSON content for the supplied names
  8258. // - data will be set in Values, according to the Names supplied e.g.
  8259. // ! JSONDecode(JSON,['name','year'],Values) -> Values[0]^='John'; Values[1]^='1972';
  8260. // - if any supplied name wasn't found its corresponding Values[] will be nil
  8261. // - this procedure will decode the JSON content in-memory, i.e. the PUtf8Char
  8262. // array is created inside JSON, which is therefore modified: make a private
  8263. // copy first if you want to reuse the JSON content
  8264. // - if HandleValuesAsObjectOrArray is TRUE, then this procedure will handle
  8265. // JSON arrays or objects
  8266. // - support enhanced JSON syntax, e.g. '{name:'"John",year:1972}' is decoded
  8267. // just like '{"name":'"John","year":1972}'
  8268. procedure JSONDecode(var JSON: RawUTF8;
  8269. const Names: array of PUTF8Char; var Values: TPUtf8CharDynArray;
  8270. HandleValuesAsObjectOrArray: Boolean=false); overload;
  8271. /// wrapper to serialize a T*ObjArray dynamic array as JSON
  8272. // - as expected by TJSONSerializer.RegisterObjArrayForJSON()
  8273. function ObjArrayToJSON(const aObjArray;
  8274. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]): RawUTF8;
  8275. type
  8276. /// store one name/value pair of raw UTF-8 content, from a JSON buffer
  8277. // - used e.g. by JSONDecode() overloaded function to returns names/values
  8278. TNameValuePUTF8Char = record
  8279. Name: PUTF8Char;
  8280. Value: PUTF8Char;
  8281. end;
  8282. /// used e.g. by JSONDecode() overloaded function to returns name/value pairs
  8283. TNameValuePUTF8CharDynArray = array of TNameValuePUTF8Char;
  8284. /// decode the supplied UTF-8 JSON content into an array of name/value pairs
  8285. // - this procedure will decode the JSON content in-memory, i.e. the PUtf8Char
  8286. // array is created inside JSON, which is therefore modified: make a private
  8287. // copy first if you want to reuse the JSON content
  8288. // - the supplied JSON buffer should stay available until Name/Value pointers
  8289. // from returned Values[] are accessed
  8290. // - if HandleValuesAsObjectOrArray is TRUE, then this procedure will handle
  8291. // JSON arrays or objects
  8292. // - support enhanced JSON syntax, e.g. '{name:'"John",year:1972}' is decoded
  8293. // just like '{"name":'"John","year":1972}'
  8294. function JSONDecode(P: PUTF8Char; out Values: TNameValuePUTF8CharDynArray;
  8295. HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char; overload;
  8296. /// decode the supplied UTF-8 JSON content for the supplied names
  8297. // - data will be set in Values, according to the Names supplied e.g.
  8298. // ! JSONDecode(P,['name','year'],Values) -> Values[0]^='John'; Values[1]^='1972';
  8299. // - if any supplied name wasn't found its corresponding Values[] will be nil
  8300. // - this procedure will decode the JSON content in-memory, i.e. the PUtf8Char
  8301. // array is created inside P, which is therefore modified: make a private
  8302. // copy first if you want to reuse the JSON content
  8303. // - if HandleValuesAsObjectOrArray is TRUE, then this procedure will handle
  8304. // JSON arrays or objects
  8305. // - returns a pointer to the next content item in the JSON buffer
  8306. function JSONDecode(P: PUTF8Char; const Names: array of PUTF8Char;
  8307. var Values: TPUtf8CharDynArray; HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char; overload;
  8308. /// decode the supplied UTF-8 JSON content for the one supplied name
  8309. // - this function will decode the JSON content in-memory, so will unescape it
  8310. // in-place: it must be called only once with the same JSON data
  8311. function JSONDecode(var JSON: RawUTF8; const aName: RawUTF8='result';
  8312. wasString: PBoolean=nil; HandleValuesAsObjectOrArray: Boolean=false): RawUTF8; overload;
  8313. /// retrieve a pointer to JSON string field content
  8314. // - returns either ':' for name field, either '}',',' for value field
  8315. // - returns nil on JSON content error
  8316. // - this function won't touch the JSON buffer, so you can call it before
  8317. // using in-place escape process via JSONDecode() or GetJSONField()
  8318. function JSONRetrieveStringField(P: PUTF8Char; out Field: PUTF8Char;
  8319. out FieldLen: integer; ExpectNameField: boolean): PUTF8Char;
  8320. {$ifdef HASINLINE}inline;{$endif}
  8321. /// decode a JSON field in an UTF-8 encoded buffer (used in TSQLTableJSON.Create)
  8322. // - this function decodes in the P^ buffer memory itself (no memory allocation
  8323. // or copy), for faster process - so take care that P^ is not shared
  8324. // - PDest points to the next field to be decoded, or nil on any unexpected end
  8325. // - optional wasString is set to true if the JSON value was a JSON "string"
  8326. // - null is decoded as nil, with wasString=false
  8327. // - true/false boolean values are returned as 'true'/'false', with wasString=false
  8328. // - '"strings"' are decoded as 'strings', with wasString=true, properly JSON
  8329. // unescaped (e.g. any \u0123 pattern would be converted into UTF-8 content)
  8330. // - any integer value is left as its ascii representation, with wasString=true
  8331. // - works for both field names or values (e.g. '"FieldName":' or 'Value,')
  8332. // - EndOfObject (if not nil) is set to the JSON value char (',' ':' or '}' e.g.)
  8333. function GetJSONField(P: PUTF8Char; out PDest: PUTF8Char;
  8334. wasString: PBoolean=nil; EndOfObject: PUTF8Char=nil): PUTF8Char;
  8335. /// decode a JSON field name in an UTF-8 encoded buffer
  8336. // - this function decodes in the P^ buffer memory itself (no memory allocation
  8337. // or copy), for faster process - so take care that P^ is not shared
  8338. // - it will return the property name (with an ending #0) or nil on error
  8339. // - this function will handle strict JSON property name (i.e. a "string"), but
  8340. // also MongoDB extended syntax, e.g. {age:{$gt:18}} or {'people.age':{$gt:18}}
  8341. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  8342. function GetJSONPropName(var P: PUTF8Char): PUTF8Char; overload;
  8343. /// decode a JSON field name in an UTF-8 encoded shortstring variable
  8344. // - this function would left the P^ buffer memory untouched, so may be safer
  8345. // than the overloaded GetJSONPropName() function in some cases
  8346. // - it will return the property name as a local UTF-8 encoded shortstring,
  8347. // or PropName='' on error
  8348. // - this function won't unescape the property name, as strict JSON (i.e. a "st\"ring")
  8349. // - but it will handle MongoDB syntax, e.g. {age:{$gt:18}} or {'people.age':{$gt:18}}
  8350. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  8351. procedure GetJSONPropName(var P: PUTF8Char; out PropName: shortstring); overload;
  8352. /// decode a JSON content in an UTF-8 encoded buffer
  8353. // - GetJSONField() will only handle JSON "strings" or numbers - if
  8354. // HandleValuesAsObjectOrArray is TRUE, this function will process JSON {
  8355. // objects } or [ arrays ] and add a #0 at the end of it
  8356. // - this function decodes in the P^ buffer memory itself (no memory allocation
  8357. // or copy), for faster process - so take care that it is an unique string
  8358. // - PDest points to the next field to be decoded, or nil on any unexpected end
  8359. // - wasString is set to true if the JSON value was a "string"
  8360. // - EndOfObject (if not nil) is set to the JSON value end char (',' ':' or '}')
  8361. function GetJSONFieldOrObjectOrArray(var P: PUTF8Char; wasString: PBoolean=nil;
  8362. EndOfObject: PUTF8Char=nil; HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char;
  8363. /// retrieve the next JSON item as a RawJSON variable
  8364. // - buffer can be either any JSON item, i.e. a string, a number or even a
  8365. // JSON array (ending with ]) or a JSON object (ending with })
  8366. // - EndOfObject (if not nil) is set to the JSON value end char (',' ':' or '}')
  8367. procedure GetJSONItemAsRawJSON(var P: PUTF8Char; var result: RawJSON; EndOfObject: PAnsiChar=nil);
  8368. /// test if the supplied buffer is a "string" value or a numerical value
  8369. // (floating point or integer), according to the characters within
  8370. // - this version will recognize null/false/true as strings
  8371. // - e.g. IsString('0')=false, IsString('abc')=true, IsString('null')=true
  8372. function IsString(P: PUTF8Char): boolean;
  8373. /// test if the supplied buffer is a "string" value or a numerical value
  8374. // (floating or integer), according to the JSON encoding schema
  8375. // - this version will NOT recognize JSON null/false/true as strings
  8376. // - e.g. IsStringJSON('0')=false, IsStringJSON('abc')=true,
  8377. // but IsStringJSON('null')=false
  8378. // - will follow the JSON definition of number, i.e. '0123' is a string (i.e.
  8379. // '0' is excluded at the begining of a number) and '123' is not a string
  8380. function IsStringJSON(P: PUTF8Char): boolean;
  8381. /// reach positon just after the current JSON item in the supplied UTF-8 buffer
  8382. // - buffer can be either any JSON item, i.e. a string, a number or even a
  8383. // JSON array (ending with ]) or a JSON object (ending with })
  8384. // - returns nil if the specified buffer is not valid JSON content
  8385. // - returns the position in buffer just after the item excluding the separator
  8386. // character - i.e. result^ may be ',','}',']'
  8387. function GotoEndJSONItem(P: PUTF8Char): PUTF8Char;
  8388. /// reach the positon of the next JSON item in the supplied UTF-8 buffer
  8389. // - buffer can be either any JSON item, i.e. a string, a number or even a
  8390. // JSON array (ending with ]) or a JSON object (ending with })
  8391. // - returns nil if the specified number of items is not available in buffer
  8392. // - returns the position in buffer after the item including the separator
  8393. // character (optionally in EndOfObject) - i.e. result will be at the start of
  8394. // the next object, and EndOfObject may be ',','}',']'
  8395. function GotoNextJSONItem(P: PUTF8Char; NumberOfItemsToJump: cardinal=1;
  8396. EndOfObject: PAnsiChar=nil): PUTF8Char;
  8397. /// read the position of the JSON value just after a property identifier
  8398. // - this function will handle strict JSON property name (i.e. a "string"), but
  8399. // also MongoDB extended syntax, e.g. {age:{$gt:18}} or {'people.age':{$gt:18}}
  8400. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  8401. function GotoNextJSONPropName(P: PUTF8Char): PUTF8Char;
  8402. /// reach the position of the next JSON object of JSON array
  8403. // - first char is expected to be either '[' or '{' with default EndChar=#0
  8404. // - or you can specify ']' or '}' as the expected EndChar
  8405. // - will return nil in case of parsing error or unexpected end (#0)
  8406. // - will return the next character after ending ] or } - i.e. may be , } ]
  8407. function GotoNextJSONObjectOrArray(P: PUTF8Char; EndChar: AnsiChar=#0): PUTF8Char;
  8408. /// reach the position of the next JSON object of JSON array
  8409. // - first char is expected to be either '[' or '{'
  8410. // - this version expects a maximum position in PMax: it may be handy to break
  8411. // the parsing for HUGE content - used e.g. by JSONArrayCount(P,PMax)
  8412. // - will return nil in case of parsing error or if P reached PMax limit
  8413. // - will return the next character after ending ] or { - i.e. may be , } ]
  8414. function GotoNextJSONObjectOrArrayMax(P,PMax: PUTF8Char): PUTF8Char;
  8415. /// compute the number of elements of a JSON array
  8416. // - this will handle any kind of arrays, including those with nested
  8417. // JSON objects or arrays
  8418. // - incoming P^ should point to the first char after the initial '[' (which
  8419. // may be a closing ']')
  8420. function JSONArrayCount(P: PUTF8Char): integer; overload;
  8421. /// compute the number of elements of a JSON array
  8422. // - this will handle any kind of arrays, including those with nested
  8423. // JSON objects or arrays
  8424. // - incoming P^ should point to the first char after the initial '[' (which
  8425. // may be a closing ']')
  8426. // - this overloaded method will abort if P reaches a certain position: for
  8427. // really HUGE arrays, it is faster to allocate the content within the loop,
  8428. // not ahead of time
  8429. function JSONArrayCount(P,PMax: PUTF8Char): integer; overload;
  8430. /// go to the #nth item of a JSON array
  8431. // - implemented via a fast SAX-like approach: the input buffer is not changed,
  8432. // nor no memory buffer allocated neither content copied
  8433. // - returns nil if the supplied index is out of range
  8434. // - returns a pointer to the index-nth item in the JSON array (first index=0)
  8435. // - this will handle any kind of arrays, including those with nested
  8436. // JSON objects or arrays
  8437. // - incoming P^ should point to the first initial '[' char
  8438. function JSONArrayItem(P: PUTF8Char; Index: integer): PUTF8Char;
  8439. /// compute the number of fields in a JSON object
  8440. // - this will handle any kind of objects, including those with nested
  8441. // JSON objects or arrays
  8442. // - incoming P^ should point to the first char after the initial '{' (which
  8443. // may be a closing '}')
  8444. function JSONObjectPropCount(P: PUTF8Char): integer;
  8445. /// go to a named property of a JSON object
  8446. // - implemented via a fast SAX-like approach: the input buffer is not changed,
  8447. // nor no memory buffer allocated neither content copied
  8448. // - returns nil if the supplied property name does not exist
  8449. // - returns a pointer to the matching item in the JSON object
  8450. // - this will handle any kind of objects, including those with nested
  8451. // JSON objects or arrays
  8452. // - incoming P^ should point to the first initial '{' char
  8453. function JsonObjectItem(P: PUTF8Char; const PropName: RawUTF8;
  8454. PropNameFound: PRawUTF8=nil): PUTF8Char;
  8455. /// go to a property of a JSON object, by its full path, e.g. 'parent.child'
  8456. // - implemented via a fast SAX-like approach: the input buffer is not changed,
  8457. // nor no memory buffer allocated neither content copied
  8458. // - returns nil if the supplied property path does not exist
  8459. // - returns a pointer to the matching item in the JSON object
  8460. // - this will handle any kind of objects, including those with nested
  8461. // JSON objects or arrays
  8462. // - incoming P^ should point to the first initial '{' char
  8463. function JsonObjectByPath(JsonObject,PropPath: PUTF8Char): PUTF8Char;
  8464. /// return all matching properties of a JSON object
  8465. // - here the PropPath could be a comma-separated list of full paths,
  8466. // e.g. 'Prop1,Prop2' or 'Obj1.Obj2.Prop1,Obj1.Prop2'
  8467. // - returns '' if no property did match
  8468. // - returns a JSON object of all matching properties
  8469. // - this will handle any kind of objects, including those with nested
  8470. // JSON objects or arrays
  8471. // - incoming P^ should point to the first initial '{' char
  8472. function JsonObjectsByPath(JsonObject,PropPath: PUTF8Char): RawUTF8;
  8473. /// convert one JSON object into two JSON arrays of keys and values
  8474. // - i.e. makes the following transformation:
  8475. // $ {key1:value1,key2,value2...} -> [key1,key2...] + [value1,value2...]
  8476. // - this function won't allocate any memory during its process, nor
  8477. // modify the JSON input buffer
  8478. // - is the reverse of the TTextWriter.AddJSONArraysAsJSONObject() method
  8479. function JSONObjectAsJSONArrays(JSON: PUTF8Char; out keys,values: RawUTF8): boolean;
  8480. /// remove comments from a text buffer before passing it to JSON parser
  8481. // - handle two types of comments: starting from // till end of line
  8482. // or /* ..... */ blocks anywhere in the text content
  8483. // - may be used to prepare configuration files before loading;
  8484. // for example we store server configuration in file config.json and
  8485. // put some comments in this file then code for loading is:
  8486. // !var cfg: RawUTF8;
  8487. // ! cfg := StringFromFile(ExtractFilePath(paramstr(0))+'Config.json');
  8488. // ! RemoveCommentsFromJSON(@cfg[1]);
  8489. // ! pLastChar := JSONToObject(sc,pointer(cfg),configValid);
  8490. procedure RemoveCommentsFromJSON(P: PUTF8Char);
  8491. const
  8492. /// standard header for an UTF-8 encoded XML file
  8493. XMLUTF8_HEADER = '<?xml version="1.0" encoding="UTF-8"?>'#13#10;
  8494. /// standard namespace for a generic XML File
  8495. XMLUTF8_NAMESPACE = '<contents xmlns="http://www.w3.org/2001/XMLSchema-instance">';
  8496. /// convert a JSON array or document into a simple XML content
  8497. // - just a wrapper around TTextWriter.AddJSONToXML, with an optional
  8498. // header before the XML converted data (e.g. XMLUTF8_HEADER), and an optional
  8499. // name space content node which will nest the generated XML data (e.g.
  8500. // '<contents xmlns="http://www.w3.org/2001/XMLSchema-instance">') - the
  8501. // corresponding ending token will be appended after (e.g. '</contents>')
  8502. // - WARNING: the JSON buffer is decoded in-place, so P^ WILL BE modified
  8503. procedure JSONBufferToXML(P: PUTF8Char; const Header,NameSpace: RawUTF8; out result: RawUTF8);
  8504. /// convert a JSON array or document into a simple XML content
  8505. // - just a wrapper around TTextWriter.AddJSONToXML, making a private copy
  8506. // of the supplied JSON buffer using TSynTempBuffer (so that JSON content
  8507. // would stay untouched)
  8508. // - the optional header is added at the beginning of the resulting string
  8509. // - an optional name space content node could be added around the generated XML,
  8510. // e.g. '<content>'
  8511. function JSONToXML(const JSON: RawUTF8; const Header: RawUTF8=XMLUTF8_HEADER;
  8512. const NameSpace: RawUTF8=''): RawUTF8;
  8513. /// formats and indents a JSON array or document to the specified layout
  8514. // - just a wrapper around TTextWriter.AddJSONReformat() method
  8515. // - WARNING: the JSON buffer is decoded in-place, so P^ WILL BE modified
  8516. procedure JSONBufferReformat(P: PUTF8Char; out result: RawUTF8;
  8517. Format: TTextWriterJSONFormat=jsonHumanReadable);
  8518. /// formats and indents a JSON array or document to the specified layout
  8519. // - just a wrapper around TTextWriter.AddJSONReformat, making a private
  8520. // of the supplied JSON buffer (so that JSON content would stay untouched)
  8521. function JSONReformat(const JSON: RawUTF8;
  8522. Format: TTextWriterJSONFormat=jsonHumanReadable): RawUTF8;
  8523. /// formats and indents a JSON array or document as a file
  8524. // - just a wrapper around TTextWriter.AddJSONReformat() method
  8525. // - WARNING: the JSON buffer is decoded in-place, so P^ WILL BE modified
  8526. function JSONBufferReformatToFile(P: PUTF8Char; const Dest: TFileName;
  8527. Format: TTextWriterJSONFormat=jsonHumanReadable): boolean;
  8528. /// formats and indents a JSON array or document as a file
  8529. // - just a wrapper around TTextWriter.AddJSONReformat, making a private
  8530. // of the supplied JSON buffer (so that JSON content would stay untouched)
  8531. function JSONReformatToFile(const JSON: RawUTF8; const Dest: TFileName;
  8532. Format: TTextWriterJSONFormat=jsonHumanReadable): boolean;
  8533. const
  8534. /// map a PtrInt type to the TJSONCustomParserRTTIType set
  8535. ptPtrInt = {$ifdef CPU64}ptInt64{$else}ptInteger{$endif};
  8536. /// map a PtrUInt type to the TJSONCustomParserRTTIType set
  8537. ptPtrUInt = {$ifdef CPU64}ptInt64{$else}ptCardinal{$endif};
  8538. /// which TJSONCustomParserRTTIType types are not simple types
  8539. // - ptTimeLog is complex, since could be also TCreateTime or TModTime
  8540. PT_COMPLEXTYPES = [ptArray, ptRecord, ptCustom, ptTimeLog];
  8541. /// could be used to compute the index in a pointer list from its position
  8542. POINTERSHR = {$ifdef CPU64}3{$else}2{$endif};
  8543. { ************ filtering and validation classes and functions ************** }
  8544. /// return TRUE if the supplied content is a valid email address
  8545. // - follows RFC 822, to validate local-part@domain email format
  8546. function IsValidEmail(P: PUTF8Char): boolean;
  8547. /// return TRUE if the supplied content is a valid IP v4 address
  8548. function IsValidIP4Address(P: PUTF8Char): boolean;
  8549. /// return TRUE if the supplied content matchs to a grep-like pattern
  8550. // - ? Matches any single characer
  8551. // - * Matches any contiguous characters
  8552. // - [abc] Matches a or b or c at that position
  8553. // - [^abc] Matches anything but a or b or c at that position
  8554. // - [!abc] Matches anything but a or b or c at that position
  8555. // - [a-e] Matches a through e at that position
  8556. // - [abcx-z] Matches a or b or c or x or y or or z, as does [a-cx-z]
  8557. // - 'ma?ch.*' would match match.exe, mavch.dat, march.on, etc..
  8558. // - 'this [e-n]s a [!zy]est' would match 'this is a test', but would not
  8559. // match 'this as a test' nor 'this is a zest'
  8560. // - initial C version by Kevin Boylan, first Delphi port by Sergey Seroukhov
  8561. function IsMatch(const Pattern, Text: RawUTF8; CaseInsensitive: boolean=false): boolean;
  8562. type
  8563. TSynFilterOrValidate = class;
  8564. TSynFilterOrValidateObjArray = array of TSynFilterOrValidate;
  8565. TSynFilterOrValidateObjArrayArray = array of TSynFilterOrValidateObjArray;
  8566. /// will define a filter (transformation) or a validation process to be
  8567. // applied to a database Record content (typicaly a TSQLRecord)
  8568. // - the optional associated parameters are to be supplied JSON-encoded
  8569. TSynFilterOrValidate = class
  8570. protected
  8571. fParameters: RawUTF8;
  8572. /// children must override this method in order to parse the JSON-encoded
  8573. // parameters, and store it in protected field values
  8574. procedure SetParameters(const Value: RawUTF8); virtual;
  8575. public
  8576. /// add the filter or validation process to a list, checking if not present
  8577. // - if an instance with the same class type and parameters is already
  8578. // registered, will call aInstance.Free and return the exising instance
  8579. // - if there is no similar instance, will add it to the list and return it
  8580. function AddOnce(var aObjArray: TSynFilterOrValidateObjArray;
  8581. aFreeIfAlreadyThere: boolean=true): TSynFilterOrValidate;
  8582. public
  8583. /// initialize the filter (transformation) or validation instance
  8584. // - most of the time, optional parameters may be specified as JSON,
  8585. // possibly with the extended MongoDB syntax
  8586. constructor Create(const aParameters: RawUTF8=''); overload; virtual;
  8587. /// initialize the filter or validation instance
  8588. /// - this overloaded constructor will allow to easily set the parameters
  8589. constructor CreateUTF8(const Format: RawUTF8; const Args, Params: array of const); overload;
  8590. /// the optional associated parameters, supplied as JSON-encoded
  8591. property Parameters: RawUTF8 read fParameters write SetParameters;
  8592. end;
  8593. /// will define a validation to be applied to a Record (typicaly a TSQLRecord)
  8594. // field content
  8595. // - a typical usage is to validate an email or IP adress e.g.
  8596. // - the optional associated parameters are to be supplied JSON-encoded
  8597. TSynValidate = class(TSynFilterOrValidate)
  8598. public
  8599. /// perform the validation action to the specified value
  8600. // - the value is expected by be UTF-8 text, as generated by
  8601. // TPropInfo.GetValue e.g.
  8602. // - if the validation failed, must return FALSE and put some message in
  8603. // ErrorMsg (translated into the current language: you could e.g. use
  8604. // a resourcestring and a SysUtils.Format() call for automatic translation
  8605. // via the mORMoti18n unit - you can leave ErrorMsg='' to trigger a
  8606. // generic error message from clas name ('"Validate email" rule failed'
  8607. // for TSynValidateEmail class e.g.)
  8608. // - if the validation passed, will return TRUE
  8609. function Process(FieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean;
  8610. virtual; abstract;
  8611. end;
  8612. /// points to a TSynValidate variable
  8613. // - used e.g. as optional parameter to TSQLRecord.Validate/FilterAndValidate
  8614. PSynValidate = ^TSynValidate;
  8615. /// IP v4 address validation to be applied to a Record field content
  8616. // (typicaly a TSQLRecord)
  8617. // - this versions expect no parameter
  8618. TSynValidateIPAddress = class(TSynValidate)
  8619. protected
  8620. public
  8621. /// perform the IP Address validation action to the specified value
  8622. function Process(aFieldIndex: integer; const Value: RawUTF8;
  8623. var ErrorMsg: string): boolean; override;
  8624. end;
  8625. /// IP address validation to be applied to a Record field content
  8626. // (typicaly a TSQLRecord)
  8627. // - optional JSON encoded parameters are "AllowedTLD" or "ForbiddenTLD",
  8628. // expecting a CSV lis of Top-Level-Domain (TLD) names, e.g.
  8629. // $ '{"AllowedTLD":"com,org,net","ForbiddenTLD":"fr"}'
  8630. // $ '{AnyTLD:true,ForbiddenDomains:"mailinator.com,yopmail.com"}'
  8631. // - this will process a validation according to RFC 822 (calling the
  8632. // IsValidEmail() function) then will check for the TLD to be in one of
  8633. // the Top-Level domains ('.com' and such) or a two-char country, and
  8634. // then will check the TLD according to AllowedTLD and ForbiddenTLD
  8635. TSynValidateEmail = class(TSynValidate)
  8636. private
  8637. fAllowedTLD: RawUTF8;
  8638. fForbiddenTLD: RawUTF8;
  8639. fForbiddenDomains: RawUTF8;
  8640. fAnyTLD: boolean;
  8641. protected
  8642. /// decode all published properties from their JSON representation
  8643. procedure SetParameters(const Value: RawUTF8); override;
  8644. public
  8645. /// perform the Email Address validation action to the specified value
  8646. // - call IsValidEmail() function and check for the supplied TLD
  8647. function Process(aFieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean; override;
  8648. /// allow any TLD to be allowed, even if not a generic TLD (.com,.net ...)
  8649. // - this may be mandatory since already over 1,300 new gTLD names or
  8650. // "strings" could become available in the next few years: there is a
  8651. // growing list of new gTLDs available at
  8652. // @http://newgtlds.icann.org/en/program-status/delegated-strings
  8653. // - the only restriction is that it should be ascii characters
  8654. property AnyTLD: boolean read fAnyTLD write fAnyTLD;
  8655. /// a CSV list of allowed TLD
  8656. // - if accessed directly, should be set as lower case values
  8657. // - e.g. 'com,org,net'
  8658. property AllowedTLD: RawUTF8 read fAllowedTLD write fAllowedTLD;
  8659. /// a CSV list of forbidden TLD
  8660. // - if accessed directly, should be set as lower case values
  8661. // - e.g. 'fr'
  8662. property ForbiddenTLD: RawUTF8 read fForbiddenTLD write fForbiddenTLD;
  8663. /// a CSV list of forbidden domain names
  8664. // - if accessed directly, should be set as lower case values
  8665. // - not only the TLD, but whole domains like 'cracks.ru,hotmail.com' or such
  8666. property ForbiddenDomains: RawUTF8 read fForbiddenDomains write fForbiddenDomains;
  8667. end;
  8668. /// grep-like case-sensitive pattern validation of a Record field content
  8669. // - parameter is NOT JSON encoded, but is some basic grep-like pattern
  8670. // - ? Matches any single characer
  8671. // - * Matches any contiguous characters
  8672. // - [abc] Matches a or b or c at that position
  8673. // - [^abc] Matches anything but a or b or c at that position
  8674. // - [!abc] Matches anything but a or b or c at that position
  8675. // - [a-e] Matches a through e at that position
  8676. // - [abcx-z] Matches a or b or c or x or y or or z, as does [a-cx-z]
  8677. // - 'ma?ch.*' would match match.exe, mavch.dat, march.on, etc..
  8678. // - 'this [e-n]s a [!zy]est' would match 'this is a test', but would not
  8679. // match 'this as a test' nor 'this is a zest'
  8680. // - pattern check IS case sensitive (TSynValidatePatternI is not)
  8681. // - this class is not as complete as PCRE regex for example,
  8682. // but code overhead is very small
  8683. TSynValidatePattern = class(TSynValidate)
  8684. public
  8685. /// perform the pattern validation to the specified value
  8686. // - pattern can be e.g. '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]'
  8687. // - this method will implement both TSynValidatePattern and
  8688. // TSynValidatePatternI, checking the current class
  8689. function Process(aFieldIndex: integer; const Value: RawUTF8;
  8690. var ErrorMsg: string): boolean; override;
  8691. end;
  8692. /// grep-like case-insensitive pattern validation of a text field content
  8693. // (typicaly a TSQLRecord)
  8694. // - parameter is NOT JSON encoded, but is some basic grep-like pattern
  8695. // - same as TSynValidatePattern, but is NOT case sensitive
  8696. TSynValidatePatternI = class(TSynValidatePattern);
  8697. /// text validation to ensure that to any text field would not be ''
  8698. TSynValidateNonVoidText = class(TSynValidate)
  8699. public
  8700. /// perform the non void text validation action to the specified value
  8701. function Process(aFieldIndex: integer; const Value: RawUTF8;
  8702. var ErrorMsg: string): boolean; override;
  8703. end;
  8704. TSynValidateTextProps = array[0..15] of cardinal;
  8705. {$M+} // to have existing RTTI for published properties
  8706. /// text validation to be applied to any Record field content
  8707. // - default MinLength value is 1, MaxLength is maxInt: so a blank
  8708. // TSynValidateText.Create('') is the same as TSynValidateNonVoidText
  8709. // - MinAlphaCount, MinDigitCount, MinPunctCount, MinLowerCount and
  8710. // MinUpperCount allow you to specify the minimal count of respectively
  8711. // alphabetical [a-zA-Z], digit [0-9], punctuation [_!;.,/:?%$="#@(){}+-*],
  8712. // lower case or upper case characters
  8713. // - expects optional JSON parameters of the allowed text length range as
  8714. // $ '{"MinLength":5,"MaxLength":10,"MinAlphaCount":1,"MinDigitCount":1,
  8715. // $ "MinPunctCount":1,"MinLowerCount":1,"MinUpperCount":1}
  8716. TSynValidateText = class(TSynValidate)
  8717. private
  8718. /// used to store all associated validation properties by index
  8719. fProps: TSynValidateTextProps;
  8720. fUTF8Length: boolean;
  8721. protected
  8722. /// use sInvalidTextChar resourcestring to create a translated error message
  8723. procedure SetErrorMsg(fPropsIndex, InvalidTextIndex, MainIndex: integer;
  8724. var result: string);
  8725. /// decode "MinLength", "MaxLength", and other parameters into fProps[]
  8726. procedure SetParameters(const Value: RawUTF8); override;
  8727. public
  8728. /// perform the text length validation action to the specified value
  8729. function Process(aFieldIndex: integer; const Value: RawUTF8;
  8730. var ErrorMsg: string): boolean; override;
  8731. published
  8732. /// Minimal length value allowed for the text content
  8733. // - the length is calculated with UTF-16 Unicode codepoints, unless
  8734. // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked
  8735. // - default is 1, i.e. a void text will not pass the validation
  8736. property MinLength: cardinal read fProps[0] write fProps[0];
  8737. /// Maximal length value allowed for the text content
  8738. // - the length is calculated with UTF-16 Unicode codepoints, unless
  8739. // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked
  8740. // - default is maxInt, i.e. no maximum length is set
  8741. property MaxLength: cardinal read fProps[1] write fProps[1];
  8742. /// Minimal alphabetical character [a-zA-Z] count
  8743. // - default is 0, i.e. no minimum set
  8744. property MinAlphaCount: cardinal read fProps[2] write fProps[2];
  8745. /// Maximal alphabetical character [a-zA-Z] count
  8746. // - default is maxInt, i.e. no Maximum set
  8747. property MaxAlphaCount: cardinal read fProps[10] write fProps[10];
  8748. /// Minimal digit character [0-9] count
  8749. // - default is 0, i.e. no minimum set
  8750. property MinDigitCount: cardinal read fProps[3] write fProps[3];
  8751. /// Maximal digit character [0-9] count
  8752. // - default is maxInt, i.e. no Maximum set
  8753. property MaxDigitCount: cardinal read fProps[11] write fProps[11];
  8754. /// Minimal punctuation sign [_!;.,/:?%$="#@(){}+-*] count
  8755. // - default is 0, i.e. no minimum set
  8756. property MinPunctCount: cardinal read fProps[4] write fProps[4];
  8757. /// Maximal punctuation sign [_!;.,/:?%$="#@(){}+-*] count
  8758. // - default is maxInt, i.e. no Maximum set
  8759. property MaxPunctCount: cardinal read fProps[12] write fProps[12];
  8760. /// Minimal alphabetical lower case character [a-z] count
  8761. // - default is 0, i.e. no minimum set
  8762. property MinLowerCount: cardinal read fProps[5] write fProps[5];
  8763. /// Maximal alphabetical lower case character [a-z] count
  8764. // - default is maxInt, i.e. no Maximum set
  8765. property MaxLowerCount: cardinal read fProps[13] write fProps[13];
  8766. /// Minimal alphabetical upper case character [A-Z] count
  8767. // - default is 0, i.e. no minimum set
  8768. property MinUpperCount: cardinal read fProps[6] write fProps[6];
  8769. /// Maximal alphabetical upper case character [A-Z] count
  8770. // - default is maxInt, i.e. no Maximum set
  8771. property MaxUpperCount: cardinal read fProps[14] write fProps[14];
  8772. /// Minimal space count inside the value text
  8773. // - default is 0, i.e. any space number allowed
  8774. property MinSpaceCount: cardinal read fProps[7] write fProps[7];
  8775. /// Maximal space count inside the value text
  8776. // - default is maxInt, i.e. any space number allowed
  8777. property MaxSpaceCount: cardinal read fProps[15] write fProps[15];
  8778. /// Maximal space count allowed on the Left side
  8779. // - default is maxInt, i.e. any Left space allowed
  8780. property MaxLeftTrimCount: cardinal read fProps[8] write fProps[8];
  8781. /// Maximal space count allowed on the Right side
  8782. // - default is maxInt, i.e. any Right space allowed
  8783. property MaxRightTrimCount: cardinal read fProps[9] write fProps[9];
  8784. /// defines if lengths parameters expects UTF-8 or UTF-16 codepoints number
  8785. // - with default FALSE, the length is calculated with UTF-16 Unicode
  8786. // codepoints - MaxLength may not match the UCS4 glyphs number, in case of
  8787. // UTF-16 surrogates
  8788. // - you can set this property to TRUE so that the UTF-8 byte count would
  8789. // be used for truncation againts the MaxLength parameter
  8790. property UTF8Length: boolean read fUTF8Length write fUTF8Length;
  8791. end;
  8792. {$M-}
  8793. /// strong password validation for a Record field content (typicaly a TSQLRecord)
  8794. // - the following parameters are set by default to
  8795. // $ '{"MinLength":5,"MaxLength":20,"MinAlphaCount":1,"MinDigitCount":1,
  8796. // $ "MinPunctCount":1,"MinLowerCount":1,"MinUpperCount":1,"MaxSpaceCount":0}'
  8797. // - you can specify some JSON encoded parameters to change this default
  8798. // values, which will validate the text field only if it contains from 5 to 10
  8799. // characters, with at least one digit, one upper case letter, one lower case
  8800. // letter, and one ponctuation sign, with no space allowed inside
  8801. TSynValidatePassWord = class(TSynValidateText)
  8802. protected
  8803. /// set password specific parameters
  8804. procedure SetParameters(const Value: RawUTF8); override;
  8805. end;
  8806. { C++Builder doesn't support array elements as properties (RSP-12595).
  8807. For now, simply exclude the relevant classes from C++Builder. }
  8808. {$NODEFINE TSynValidateTextProps}
  8809. {$NODEFINE TSynValidateText }
  8810. {$NODEFINE TSynValidatePassWord }
  8811. /// will define a transformation to be applied to a Record field content
  8812. // (typicaly a TSQLRecord)
  8813. // - here "filter" means that content would be transformed according to a
  8814. // set of defined rules
  8815. // - a typical usage is to convert to lower or upper case, or
  8816. // trim any time or date value in a TDateTime field
  8817. // - the optional associated parameters are to be supplied JSON-encoded
  8818. TSynFilter = class(TSynFilterOrValidate)
  8819. protected
  8820. public
  8821. /// perform the transformation to the specified value
  8822. // - the value is converted into UTF-8 text, as expected by
  8823. // TPropInfo.GetValue / TPropInfo.SetValue e.g.
  8824. procedure Process(aFieldIndex: integer; var Value: RawUTF8); virtual; abstract;
  8825. end;
  8826. /// class-refrence type (metaclass) for a TSynFilter or a TSynValidate
  8827. TSynFilterOrValidateClass = class of TSynFilterOrValidate;
  8828. /// class-reference type (metaclass) of a record filter (transformation)
  8829. TSynFilterClass = class of TSynFilter;
  8830. /// convert the value into ASCII Upper Case characters
  8831. // - UpperCase conversion is made for ASCII-7 only, i.e. 'a'..'z' characters
  8832. // - this version expects no parameter
  8833. TSynFilterUpperCase = class(TSynFilter)
  8834. public
  8835. /// perform the case conversion to the specified value
  8836. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8837. end;
  8838. /// convert the value into WinAnsi Upper Case characters
  8839. // - UpperCase conversion is made for all latin characters in the WinAnsi
  8840. // code page only, e.g. 'e' acute will be converted to 'E'
  8841. // - this version expects no parameter
  8842. TSynFilterUpperCaseU = class(TSynFilter)
  8843. public
  8844. /// perform the case conversion to the specified value
  8845. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8846. end;
  8847. /// convert the value into ASCII Lower Case characters
  8848. // - LowerCase conversion is made for ASCII-7 only, i.e. 'A'..'Z' characters
  8849. // - this version expects no parameter
  8850. TSynFilterLowerCase = class(TSynFilter)
  8851. public
  8852. /// perform the case conversion to the specified value
  8853. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8854. end;
  8855. /// convert the value into WinAnsi Lower Case characters
  8856. // - LowerCase conversion is made for all latin characters in the WinAnsi
  8857. // code page only, e.g. 'E' acute will be converted to 'e'
  8858. // - this version expects no parameter
  8859. TSynFilterLowerCaseU = class(TSynFilter)
  8860. public
  8861. /// perform the case conversion to the specified value
  8862. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8863. end;
  8864. /// trim any space character left or right to the value
  8865. // - this versions expect no parameter
  8866. TSynFilterTrim = class(TSynFilter)
  8867. public
  8868. /// perform the space triming conversion to the specified value
  8869. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8870. end;
  8871. /// truncate a text above a given maximum length
  8872. // - expects optional JSON parameters of the allowed text length range as
  8873. // $ '{MaxLength":10}
  8874. TSynFilterTruncate = class(TSynFilter)
  8875. protected
  8876. fMaxLength: cardinal;
  8877. fUTF8Length: boolean;
  8878. /// decode the MaxLength: and UTF8Length: parameters
  8879. procedure SetParameters(const Value: RawUTF8); override;
  8880. public
  8881. /// perform the length truncation of the specified value
  8882. procedure Process(aFieldIndex: integer; var Value: RawUTF8); override;
  8883. /// Maximal length value allowed for the text content
  8884. // - the length is calculated with UTF-16 Unicode codepoints, unless
  8885. // UTF8Length has been set to TRUE so that the UTF-8 byte count is checked
  8886. // - default is 0, i.e. no maximum length is forced
  8887. property MaxLength: cardinal read fMaxLength write fMaxLength;
  8888. /// defines if MaxLength is stored as UTF-8 or UTF-16 codepoints number
  8889. // - with default FALSE, the length is calculated with UTF-16 Unicode
  8890. // codepoints - MaxLength may not match the UCS4 glyphs number, in case of
  8891. // UTF-16 surrogates
  8892. // - you can set this property to TRUE so that the UTF-8 byte count would
  8893. // be used for truncation againts the MaxLength parameter
  8894. property UTF8Length: boolean read fUTF8Length write fUTF8Length;
  8895. end;
  8896. { ************ some other common types and conversion routines ************** }
  8897. type
  8898. /// calling context of TSynLogExceptionToStr callbacks
  8899. TSynLogExceptionContext = record
  8900. /// the raised exception class
  8901. EClass: ExceptClass;
  8902. /// the Delphi Exception instance
  8903. // - may be nil for external/OS exceptions
  8904. EInstance: Exception;
  8905. /// the OS-level exception code
  8906. // - could be $0EEDFAE0 of $0EEDFADE for Delphi-generated exceptions
  8907. ECode: DWord;
  8908. /// the address where the exception occured
  8909. EAddr: PtrUInt;
  8910. /// the optional stack trace
  8911. EStack: PPtrUInt;
  8912. /// the logging level corresponding to this exception
  8913. // - may be either sllException or sllExceptionOS
  8914. ELevel: TSynLogInfo;
  8915. end;
  8916. /// global hook callback to customize exceptions logged by TSynLog
  8917. // - should return TRUE if all needed information has been logged by the
  8918. // event handler
  8919. // - should return FALSE if Context.EAddr and Stack trace is to be appended
  8920. TSynLogExceptionToStr = function(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean;
  8921. {$M+}
  8922. /// generic parent class of all custom Exception types of this unit
  8923. // - all our classes inheriting from ESynException are serializable,
  8924. // so you could use ObjectToJSONDebug(anyESynException) to retrieve some
  8925. // extended information
  8926. ESynException = class(Exception)
  8927. public
  8928. /// constructor which will use FormatUTF8() instead of Format()
  8929. // - expect % as delimitor, so is less error prone than %s %d %g
  8930. // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
  8931. // appending class name for any class or object, the hexa value for a
  8932. // pointer, or the JSON representation of the supplied variant
  8933. constructor CreateUTF8(const Format: RawUTF8; const Args: array of const);
  8934. {$ifndef NOEXCEPTIONINTERCEPT}
  8935. /// can be used to customize how the exception is logged
  8936. // - this default implementation will call the DefaultSynLogExceptionToStr()
  8937. // function or the TSynLogExceptionToStrCustom global callback, if defined
  8938. // - override this method to provide a custom logging content
  8939. // - should return TRUE if Context.EAddr and Stack trace is not to be
  8940. // written (i.e. as for any TSynLogExceptionToStr callback)
  8941. function CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; virtual;
  8942. {$endif}
  8943. published
  8944. property Message;
  8945. end;
  8946. {$M-}
  8947. ESynExceptionClass = class of ESynException;
  8948. /// exception raised by all TSynTable related code
  8949. ETableDataException = class(ESynException);
  8950. /// exception class associated to TDocVariant JSON/BSON document
  8951. EDocVariant = class(ESynException);
  8952. var
  8953. /// allow to customize the ESynException logging message
  8954. TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;
  8955. {$ifndef NOEXCEPTIONINTERCEPT}
  8956. /// default exception logging callback - will be set by the SynLog unit
  8957. // - will add the default Exception details, including any Exception.Message
  8958. // - if the exception inherits from ESynException
  8959. // - returns TRUE: caller will then append ' at EAddr' and the stack trace
  8960. DefaultSynLogExceptionToStr: TSynLogExceptionToStr = nil;
  8961. {$endif}
  8962. /// convert a string into its INTEGER Curr64 (value*10000) representation
  8963. // - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
  8964. // - fast conversion, using only integer operations
  8965. // - if NoDecimal is defined, will be set to TRUE if there is no decimal, AND
  8966. // the returned value will be an Int64 (not a PInt64(@Curr)^)
  8967. function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64;
  8968. /// convert a string into its currency representation
  8969. // - will call StrToCurr64()
  8970. function StrToCurrency(P: PUTF8Char): currency;
  8971. /// convert a currency value into a string
  8972. // - fast conversion, using only integer operations
  8973. // - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
  8974. function CurrencyToStr(Value: currency): RawUTF8;
  8975. /// convert an INTEGER Curr64 (value*10000) into a string
  8976. // - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
  8977. // - fast conversion, using only integer operations
  8978. // - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
  8979. function Curr64ToStr(const Value: Int64): RawUTF8; overload;
  8980. /// convert an INTEGER Curr64 (value*10000) into a string
  8981. // - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
  8982. // - fast conversion, using only integer operations
  8983. // - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
  8984. procedure Curr64ToStr(const Value: Int64; var result: RawUTF8); overload;
  8985. /// convert an INTEGER Curr64 (value*10000) into a string
  8986. // - this type is compatible with Delphi currency memory map with PInt64(@Curr)^
  8987. // - fast conversion, using only integer operations
  8988. // - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
  8989. // - return the number of chars written to Dest^
  8990. function Curr64ToPChar(const Value: Int64; Dest: PUTF8Char): PtrInt;
  8991. /// internal fast INTEGER Curr64 (value*10000) value to text conversion
  8992. // - expect the last available temporary char position in P
  8993. // - return the last written char position (write in reverse order in P^)
  8994. // - will return 0 for Value=0, or a string representation with always 4 decimals
  8995. // (e.g. 1->'0.0001' 500->'0.0500' 25000->'2.5000' 30000->'3.0000')
  8996. // - is called by Curr64ToPChar() and Curr64ToStr() functions
  8997. function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar;
  8998. {$ifdef HASINLINE}inline;{$endif}
  8999. /// truncate a Currency value to only 2 digits
  9000. // - implementation will use fast Int64 math to avoid any precision loss due to
  9001. // temporary floating-point conversion
  9002. function TruncTo2Digits(Value: Currency): Currency;
  9003. /// simple, no banker rounding of a Currency value to only 2 digits
  9004. // - #.##51 will round to #.##+0.01 and #.##50 will be truncated to #.##
  9005. // - implementation will use fast Int64 math to avoid any precision loss due to
  9006. // temporary floating-point conversion
  9007. function SimpleRoundTo2Digits(Value: Currency): Currency;
  9008. var
  9009. /// a conversion table from hexa chars into binary data
  9010. // - returns 255 for any character out of 0..9,A..Z,a..z range
  9011. // - used e.g. by HexToBin() function
  9012. ConvertHexToBin: array[byte] of byte;
  9013. /// fast conversion from hexa chars into binary data
  9014. // - BinBytes contain the bytes count to be converted: Hex^ must contain
  9015. // at least BinBytes*2 chars to be converted, and Bin^ enough space
  9016. // - if Bin=nil, no output data is written, but the Hex^ format is checked
  9017. // - return false if any invalid (non hexa) char is found in Hex^
  9018. // - using this function with Bin^ as an integer value will decode in big-endian
  9019. // order (most-signignifican byte first)
  9020. function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: Integer): boolean; overload;
  9021. /// fast conversion from one hexa char pair into a 8 bit AnsiChar
  9022. // - return false if any invalid (non hexa) char is found in Hex^
  9023. // - similar to HexToBin(Hex,nil,1)
  9024. function HexToCharValid(Hex: PAnsiChar): boolean;
  9025. {$ifdef HASINLINE}inline;{$endif}
  9026. /// fast conversion from one hexa char pair into a 8 bit AnsiChar
  9027. // - return false if any invalid (non hexa) char is found in Hex^
  9028. // - similar to HexToBin(Hex,Bin,1) but with Bin<>nil
  9029. // - use HexToCharValid if you want to check a hexadecimal char content
  9030. function HexToChar(Hex: PAnsiChar; Bin: PUTF8Char): boolean;
  9031. {$ifdef HASINLINE}inline;{$endif}
  9032. /// fast conversion from two hexa bytes into a 16 bit UTF-16 WideChar
  9033. // - similar to HexToBin(Hex,@wordvar,2) + bswap(wordvar)
  9034. function HexToWideChar(Hex: PAnsiChar): cardinal;
  9035. {$ifdef HASINLINE}inline;{$endif}
  9036. /// fast conversion from binary data into hexa chars
  9037. // - BinBytes contain the bytes count to be converted: Hex^ must contain
  9038. // enough space for at least BinBytes*2 chars
  9039. // - using this function with BinBytes^ as an integer value will encode it
  9040. // in low-endian order (less-signignifican byte first): don't use it for display
  9041. procedure BinToHex(Bin, Hex: PAnsiChar; BinBytes: integer); overload;
  9042. /// fast conversion from hexa chars into binary data
  9043. function HexToBin(const Hex: RawUTF8): RawByteString; overload;
  9044. /// fast conversion from binary data into hexa chars
  9045. function BinToHex(const Bin: RawByteString): RawUTF8; overload;
  9046. /// fast conversion from binary data into hexa chars
  9047. function BinToHex(Bin: PAnsiChar; BinBytes: integer): RawUTF8; overload;
  9048. /// fast conversion from binary data into hexa chars, ready to be displayed
  9049. // - BinBytes contain the bytes count to be converted: Hex^ must contain
  9050. // enough space for at least BinBytes*2 chars
  9051. // - using this function with Bin^ as an integer value will encode it
  9052. // in big-endian order (most-signignifican byte first): use it for display
  9053. procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: integer); overload;
  9054. /// fast conversion from binary data into hexa chars, ready to be displayed
  9055. function BinToHexDisplay(Bin: PAnsiChar; BinBytes: integer): RawUTF8; overload;
  9056. /// fast conversion from a pointer data into hexa chars, ready to be displayed
  9057. // - use internally BinToHexDisplay()
  9058. function PointerToHex(aPointer: Pointer): RawUTF8; overload;
  9059. /// fast conversion from a pointer data into hexa chars, ready to be displayed
  9060. // - use internally BinToHexDisplay()
  9061. procedure PointerToHex(aPointer: Pointer; var result: RawUTF8); overload;
  9062. /// fast conversion from a Cardinal value into hexa chars, ready to be displayed
  9063. // - use internally BinToHexDisplay()
  9064. // - reverse function of HexDisplayToCardinal()
  9065. function CardinalToHex(aCardinal: Cardinal): RawUTF8;
  9066. /// fast conversion from a Int64 value into hexa chars, ready to be displayed
  9067. // - use internally BinToHexDisplay()
  9068. // - reverse function of HexDisplayToInt64()
  9069. function Int64ToHex(aInt64: Int64): RawUTF8; overload;
  9070. /// fast conversion from a Int64 value into hexa chars, ready to be displayed
  9071. // - use internally BinToHexDisplay()
  9072. // - reverse function of HexDisplayToInt64()
  9073. procedure Int64ToHex(aInt64: Int64; var result: RawUTF8); overload;
  9074. /// fast conversion from hexa chars into a pointer
  9075. function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: integer): boolean;
  9076. /// fast conversion from hexa chars into a cardinal
  9077. // - reverse function of CardinalToHex()
  9078. function HexDisplayToCardinal(Hex: PAnsiChar; out aValue: cardinal): boolean;
  9079. {$ifndef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif}
  9080. // inline gives an error under release conditions with FPC
  9081. /// fast conversion from hexa chars into a cardinal
  9082. // - reverse function of Int64ToHex()
  9083. function HexDisplayToInt64(Hex: PAnsiChar; out aValue: Int64): boolean; overload;
  9084. {$ifndef FPC}{$ifdef HASINLINE}inline;{$endif}{$endif}
  9085. // inline gives an error under release conditions with FPC
  9086. /// fast conversion from hexa chars into a cardinal
  9087. // - reverse function of Int64ToHex()
  9088. // - returns 0 if the supplied text buffer is not a valid 16-char hexadecimal
  9089. function HexDisplayToInt64(const Hex: RawByteString): Int64; overload;
  9090. {$ifdef HASINLINE}inline;{$endif}
  9091. /// fast conversion from binary data into Base64 encoded UTF-8 text
  9092. function BinToBase64(const s: RawByteString): RawUTF8; overload;
  9093. /// fast conversion from binary data into Base64 encoded UTF-8 text
  9094. function BinToBase64(Bin: PAnsiChar; BinBytes: integer): RawUTF8; overload;
  9095. /// fast conversion from binary data into Base64-like URI-compatible encoded text
  9096. // - will trim any right-sided '=' unsignificant characters, and replace
  9097. // '+' or '/' by '_' or '-'
  9098. function BinToBase64URI(Bin: PAnsiChar; BinBytes: integer): RawUTF8;
  9099. /// conversion from any Base64 encoded value into URI-compatible encoded text
  9100. // - will trim any right-sided '=' unsignificant characters, and replace
  9101. // '+' or '/' by '_' or '-'
  9102. procedure Base64ToURI(var base64: RawUTF8);
  9103. /// conversion from URI-compatible encoded text into its original Base64 value
  9104. // - will add any right-sided '=' unsignificant characters, and replace back
  9105. // '_' or '-' by '+' or '/'
  9106. procedure Base64FromURI(var base64: RawUTF8);
  9107. /// fast conversion from binary data into Base64 encoded UTF-8 text
  9108. // with JSON_BASE64_MAGIC prefix (UTF-8 encoded \uFFF0 special code)
  9109. function BinToBase64WithMagic(const s: RawByteString): RawUTF8; overload;
  9110. /// fast conversion from binary data into Base64 encoded UTF-8 text
  9111. // with JSON_BASE64_MAGIC prefix (UTF-8 encoded \uFFF0 special code)
  9112. function BinToBase64WithMagic(Data: pointer; DataLen: integer): RawUTF8; overload;
  9113. /// fast conversion from Base64 encoded text into binary data
  9114. function Base64ToBin(const s: RawByteString): RawByteString; overload;
  9115. {$ifdef HASINLINE}inline;{$endif}
  9116. /// fast conversion from Base64 encoded text into binary data
  9117. function Base64ToBin(sp: PAnsiChar; len: PtrInt): RawByteString; overload;
  9118. {$ifdef HASINLINE}inline;{$endif}
  9119. /// fast conversion from Base64 encoded text into binary data
  9120. procedure Base64ToBin(sp: PAnsiChar; len: PtrInt; var result: RawByteString); overload;
  9121. /// fast conversion from Base64 encoded text into binary data
  9122. procedure Base64ToBin(sp: PAnsiChar; len: PtrInt; var result: TSynTempBuffer); overload;
  9123. /// fast conversion from Base64 encoded text into binary data
  9124. // - returns TRUE on success, FALSE if base64 does not match binlen
  9125. // - if nofullcheck is FALSE, IsBase64() will be first called to validate the input
  9126. function Base64ToBin(base64, bin: PAnsiChar; base64len, binlen: PtrInt;
  9127. nofullcheck: boolean=true): boolean; overload;
  9128. /// just a wrapper around Base64ToBin() for in-place decode of JSON_BASE64_MAGIC
  9129. // '\uFFF0base64encodedbinary' content into binary
  9130. // - input ParamValue shall have been checked to match the expected pattern
  9131. procedure Base64MagicDecode(var ParamValue: RawUTF8);
  9132. /// check and decode '\uFFF0base64encodedbinary' content into binary
  9133. // - this method will check the supplied value to match the expected
  9134. // JSON_BASE64_MAGIC pattern, decode and set Blob and return TRUE
  9135. function Base64MagicCheckAndDecode(Value: PUTF8Char; var Blob: RawByteString): boolean; overload;
  9136. /// check and decode '\uFFF0base64encodedbinary' content into binary
  9137. // - this method will check the supplied value to match the expected
  9138. // JSON_BASE64_MAGIC pattern, decode and set Blob and return TRUE
  9139. function Base64MagicCheckAndDecode(Value: PUTF8Char; ValueLen: Integer;
  9140. var Blob: RawByteString): boolean; overload;
  9141. /// check and decode '\uFFF0base64encodedbinary' content into binary
  9142. // - this method will check the supplied value to match the expected
  9143. // JSON_BASE64_MAGIC pattern, decode and set Blob and return TRUE
  9144. function Base64MagicCheckAndDecode(Value: PUTF8Char; var Blob: TSynTempBuffer): boolean; overload;
  9145. /// check if the supplied text is a valid Base64 encoded stream
  9146. function IsBase64(const s: RawByteString): boolean; overload;
  9147. {$ifdef HASINLINE}inline;{$endif}
  9148. /// check if the supplied text is a valid Base64 encoded stream
  9149. function IsBase64(sp: PAnsiChar; len: PtrInt): boolean; overload;
  9150. /// retrieve the expected encoded length after Base64 process
  9151. function BinToBase64Length(len: PtrUInt): PtrUInt;
  9152. {$ifdef HASINLINE}inline;{$endif}
  9153. /// retrieve the expected undecoded length of a Base64 encoded buffer
  9154. // - here len is the number 16 bytes in sp
  9155. function Base64ToBinLength(sp: PAnsiChar; len: PtrInt): PtrInt;
  9156. /// direct low-level decoding of a Base64 encoded buffer
  9157. // - here len is the number of 16 bytes chunks in sp
  9158. // - you should better not use this, but Base64ToBin() overloaded functions
  9159. procedure Base64Decode(sp,rp: PAnsiChar; len: PtrInt);
  9160. /// generate some pascal source code holding some data binary as constant
  9161. // - can store sensitive information (e.g. certificates) within the executable
  9162. // - generates a source code snippet of the following format:
  9163. // ! const
  9164. // ! // Comment
  9165. // ! ConstName: array[0..2] of byte = (
  9166. // ! $01,$02,$03);
  9167. procedure BinToSource(Dest: TTextWriter; const ConstName, Comment: RawUTF8;
  9168. Data: pointer; Len: integer; PerLine: integer=16); overload;
  9169. /// generate some pascal source code holding some data binary as constant
  9170. // - can store sensitive information (e.g. certificates) within the executable
  9171. // - generates a source code snippet of the following format:
  9172. // ! const
  9173. // ! // Comment
  9174. // ! ConstName: array[0..2] of byte = (
  9175. // ! $01,$02,$03);
  9176. function BinToSource(const ConstName, Comment: RawUTF8; Data: pointer;
  9177. Len: integer; PerLine: integer=16; const Suffix: RawUTF8=''): RawUTF8; overload;
  9178. /// revert the value as encoded by TTextWriter.AddInt18ToChars3() method
  9179. function Chars3ToInt18(P: pointer): cardinal;
  9180. {$ifdef HASINLINE}inline;{$endif}
  9181. /// convert a date to a ISO-8601 string format for SQL '?' inlined parameters
  9182. // - will return the date encoded as '\uFFF1YYYY-MM-DD' - therefore
  9183. // ':("\uFFF12012-05-04"):' pattern will be recognized as a sftDateTime
  9184. // inline parameter in SQLParamContent() / ExtractInlineParameters() functions
  9185. // (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern)
  9186. // - to be used e.g. as in:
  9187. // ! aRec.CreateAndFillPrepare(Client,'Datum=?',[DateToSQL(EncodeDate(2012,5,4))]);
  9188. function DateToSQL(Date: TDateTime): RawUTF8; overload;
  9189. /// convert a date to a ISO-8601 string format for SQL '?' inlined parameters
  9190. // - will return the date encoded as '\uFFF1YYYY-MM-DD' - therefore
  9191. // ':("\uFFF12012-05-04"):' pattern will be recognized as a sftDateTime
  9192. // inline parameter in SQLParamContent() / ExtractInlineParameters() functions
  9193. // (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern)
  9194. // - to be used e.g. as in:
  9195. // ! aRec.CreateAndFillPrepare(Client,'Datum=?',[DateToSQL(2012,5,4)]);
  9196. function DateToSQL(Year,Month,Day: cardinal): RawUTF8; overload;
  9197. /// convert a date/time to a ISO-8601 string format for SQL '?' inlined parameters
  9198. // - if DT=0, returns ''
  9199. // - if DT contains only a date, returns the date encoded as '\uFFF1YYYY-MM-DD'
  9200. // - if DT contains only a time, returns the time encoded as '\uFFF1Thh:mm:ss'
  9201. // - otherwise, returns the ISO-8601 date and time encoded as '\uFFF1YYYY-MM-DDThh:mm:ss'
  9202. // (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern)
  9203. // - to be used e.g. as in:
  9204. // ! aRec.CreateAndFillPrepare(Client,'Datum<=?',[DateTimeToSQL(Now)]);
  9205. // - see TimeLogToSQL() if you are using TTimeLog/TModTime/TCreateTime values
  9206. function DateTimeToSQL(DT: TDateTime): RawUTF8;
  9207. /// decode a SQL '?' inlined parameter (i.e. with JSON_SQLDATE_MAGIC prefix)
  9208. // - as generated by DateToSQL/DateTimeToSQL/TimeLogToSQL functions
  9209. function SQLToDateTime(const ParamValueWithMagic: RawUTF8): TDateTime;
  9210. /// guess the content type of an UTF-8 SQL value, in :(....): format
  9211. // - will be used e.g. by ExtractInlineParameters() to un-inline a SQL statement
  9212. // - sftInteger is returned for an INTEGER value, e.g. :(1234):
  9213. // - sftFloat is returned for any floating point value (i.e. some digits
  9214. // separated by a '.' character), e.g. :(12.34): or :(12E-34):
  9215. // - sftUTF8Text is returned for :("text"): or :('text'):, with double quoting
  9216. // inside the value
  9217. // - sftBlob will be recognized from the ':("\uFFF0base64encodedbinary"):'
  9218. // pattern, and return raw binary (for direct blob parameter assignment)
  9219. // - sftDateTime will be recognized from ':(\uFFF1"2012-05-04"):' pattern,
  9220. // i.e. JSON_SQLDATE_MAGIC-prefixed string as returned by DateToSQL() or
  9221. // DateTimeToSQL() functions
  9222. // - sftUnknown is returned on invalid content, or if wasNull is set to TRUE
  9223. // - if ParamValue is not nil, the pointing RawUTF8 string is set with the
  9224. // value inside :(...): without double quoting in case of sftUTF8Text
  9225. // - wasNull is set to TRUE if P was ':(null):' and ParamType is sftUnknwown
  9226. function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8;
  9227. out wasNull: boolean): PUTF8Char;
  9228. /// this function will extract inlined :(1234): parameters into Types[]/Values[]
  9229. // - will return the generic SQL statement with ? instead of :(1234):
  9230. // - call internaly SQLParamContent() function for inline parameters decoding
  9231. // - will set maxParam=0 in case of no inlined parameters
  9232. // - recognized types are sptInteger, sptFloat, sptDateTime ('\uFFF1...'),
  9233. // sptUTF8Text and sptBlob ('\uFFF0...')
  9234. // - sptUnknown is returned on invalid content
  9235. function ExtractInlineParameters(const SQL: RawUTF8;
  9236. var Types: TSQLParamTypeDynArray; var Values: TRawUTF8DynArray;
  9237. var maxParam: integer; var Nulls: TSQLFieldBits): RawUTF8;
  9238. /// add the 4 digits of integer Y to P^
  9239. procedure YearToPChar(Y: cardinal; P: PUTF8Char);
  9240. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9241. /// creates a 3 digits string from a 0..999 value
  9242. function UInt3DigitsToUTF8(Value: Cardinal): RawUTF8;
  9243. {$ifdef HASINLINE}inline;{$endif}
  9244. /// creates a 4 digits string from a 0..9999 value
  9245. function UInt4DigitsToUTF8(Value: Cardinal): RawUTF8;
  9246. {$ifdef HASINLINE}inline;{$endif}
  9247. type
  9248. /// used e.g. by UInt4DigitsToShort/UInt3DigitsToShort/UInt2DigitsToShort
  9249. // - such result type would avoid a string allocation on heap
  9250. Short4 = string[4];
  9251. /// creates a 4 digits short string from a 0..9999 value
  9252. // - using Short4 as returned string would avoid a string allocation on heap
  9253. // - could be used e.g. as parameter to FormatUTF8()
  9254. function UInt4DigitsToShort(Value: Cardinal): Short4;
  9255. /// creates a 3 digits short string from a 0..999 value
  9256. // - using Short4 as returned string would avoid a string allocation on heap
  9257. // - could be used e.g. as parameter to FormatUTF8()
  9258. function UInt3DigitsToShort(Value: Cardinal): Short4;
  9259. /// creates a 2 digits short string from a 0..99 value
  9260. // - using Short4 as returned string would avoid a string allocation on heap
  9261. // - could be used e.g. as parameter to FormatUTF8()
  9262. function UInt2DigitsToShort(Value: byte): Short4;
  9263. /// compare to floating point values, with IEEE 754 double precision
  9264. // - use this function instead of raw = operator
  9265. // - the precision is calculated from the A and B value range
  9266. // - faster equivalent than SameValue() in Math unit
  9267. // - if you know the precision range of A and B, it's faster to check abs(A-B)<range
  9268. function SameValue(const A, B: Double; DoublePrec: double = 1E-12): Boolean;
  9269. /// compare to floating point values, with IEEE 754 double precision
  9270. // - use this function instead of raw = operator
  9271. // - the precision is calculated from the A and B value range
  9272. // - faster equivalent than SameValue() in Math unit
  9273. // - if you know the precision range of A and B, it's faster to check abs(A-B)<range
  9274. function SameValueFloat(const A, B: TSynExtended; DoublePrec: TSynExtended = 1E-12): Boolean;
  9275. // our custom hash function, specialized for Text comparaison
  9276. // - has less colision than Adler32 for short strings
  9277. // - is faster than CRC32 or Adler32, since use DQWord (128 bytes) aligned read:
  9278. // Hash32() is 2.5 GB/s, kr32() 0.9 GB/s, crc32c() 1.7 GB/s or 3.7 GB/s (SSE4.2)
  9279. // - overloaded version for direct binary content hashing
  9280. function Hash32(Data: pointer; Len: integer): cardinal; overload;
  9281. // our custom hash function, specialized for Text comparaison
  9282. // - has less colision than Adler32 for short strings
  9283. // - is faster than CRC32 or Adler32, since use DQWord (128 bytes) aligned read
  9284. // - uses RawByteString for binary content hashing, whatever the codepage is
  9285. function Hash32(const Text: RawByteString): cardinal; overload;
  9286. {$ifdef HASINLINE}inline;{$endif}
  9287. /// standard Kernighan & Ritchie hash from "The C programming Language", 3rd edition
  9288. // - simple and efficient code, but too much collisions for THasher
  9289. // - kr32() is 898.8 MB/s - crc32cfast() 1.7 GB/s, crc32csse42() 3.7 GB/s
  9290. function kr32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  9291. /// simple FNV-1a hashing function
  9292. // - when run over our regression suite, is similar to crc32c() about collisions,
  9293. // and 4 times better than kr32(), but also slower than the others
  9294. // - fnv32() is 715.5 MB/s - kr32() 898.8 MB/s
  9295. // - this hash function should not be usefull, unless you need several hashing
  9296. // algorithms at once (e.g. if crc32c with diverse seeds is not enough)
  9297. function fnv32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  9298. var
  9299. /// tables used by crc32cfast() function
  9300. // - created with a polynom diverse from zlib's crc32() algorithm, but
  9301. // compatible with SSE 4.2 crc32 instruction
  9302. // - tables content is created from code in initialization section below
  9303. crc32ctab: array[0..{$ifdef PUREPASCAL}3{$else}7{$endif},byte] of cardinal;
  9304. /// compute CRC32C checksum on the supplied buffer using x86/x64 code
  9305. // - result is compatible with SSE 4.2 based hardware accelerated instruction
  9306. // - result is not compatible with zlib's crc32() - not the same polynom
  9307. // - crc32cfast() is 1.7 GB/s, crc32csse42() is 3.7 GB/s
  9308. function crc32cfast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  9309. /// compute CRC64C checksum on the supplied buffer
  9310. // - will use SSE 4.2 hardware accelerated instruction, if available
  9311. // - will combine two crc32c() calls into a single Int64 result
  9312. // - by design, such combined hashes cannot be cascaded
  9313. function crc64c(buf: PAnsiChar; len: cardinal): Int64;
  9314. /// compute CRC63C checksum on the supplied buffer
  9315. // - similar to crc64c, but with 63-bit, so no negative value, so may be used
  9316. // safely e.g. as mORMot's TID source
  9317. // - will use SSE 4.2 hardware accelerated instruction, if available
  9318. // - will combine two crc32c() calls into a single Int64 result
  9319. // - by design, such combined hashes cannot be cascaded
  9320. function crc63c(buf: PAnsiChar; len: cardinal): Int64;
  9321. type
  9322. /// store a 128-bit hash value
  9323. // - e.g. a MD5 digest, or an AES block
  9324. THash128 = array[0..15] of byte;
  9325. /// store a 256-bit hash value
  9326. // - e.g. a SHA-256 digest, or a TECCSignature result
  9327. THash256 = array[0..31] of byte;
  9328. /// compute a 128-bit checksum on the supplied buffer using crc32c
  9329. // - will use SSE 4.2 hardware accelerated instruction, if available
  9330. // - will combine two crc32c() calls into a single TAESBlock result
  9331. // - by design, such combined hashes cannot be cascaded
  9332. procedure crc128c(buf: PAnsiChar; len: cardinal; out crc: THash128);
  9333. /// returns TRUE if all 16 bytes of this 128-bit buffer equal zero
  9334. // - e.g. a MD5 digest, or an AES block
  9335. function IsZero(const dig: THash128): boolean; overload;
  9336. {$ifdef HASINLINE}inline;{$endif} overload;
  9337. /// returns TRUE if all 16 bytes of both 128-bit buffers do match
  9338. // - e.g. a MD5 digest, or an AES block
  9339. function IsEqual(const A,B: THash128): boolean; overload;
  9340. {$ifdef HASINLINE}inline;{$endif} overload;
  9341. /// fill all 16 bytes of this 128-bit buffer with zero
  9342. // - may be used to cleanup stack-allocated content
  9343. // ! ... finally FillZero(digest); end;
  9344. procedure FillZero(out dig: THash128); overload;
  9345. /// compute a 256-bit checksum on the supplied buffer using crc32c
  9346. // - will use SSE 4.2 hardware accelerated instruction, if available
  9347. // - will combine two crc32c() calls into a single TDigest256 result
  9348. // - by design, such combined hashes cannot be cascaded
  9349. procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256);
  9350. /// returns TRUE if all 32 bytes of this 256-bit buffer equal zero
  9351. // - e.g. a SHA-256 digest, or a TECCSignature result
  9352. function IsZero(const dig: THash256): boolean; overload;
  9353. {$ifdef HASINLINE}inline;{$endif} overload;
  9354. /// returns TRUE if all 32 bytes of both 256-bit buffers do match
  9355. // - e.g. a SHA-256 digest, or a TECCSignature result
  9356. function IsEqual(const A,B: THash256): boolean; overload;
  9357. {$ifdef HASINLINE}inline;{$endif} overload;
  9358. /// fill all 32 bytes of this 256-bit buffer with zero
  9359. // - may be used to cleanup stack-allocated content
  9360. // ! ... finally FillZero(digest); end;
  9361. procedure FillZero(out dig: THash256); overload;
  9362. type
  9363. /// the potential features, retrieved from an Intel CPU
  9364. // - see https://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits
  9365. TIntelCpuFeature =
  9366. ( { in EDX }
  9367. cfFPU, cfVME, cfDE, cfPSE, cfTSC, cfMSR, cfPAE, cfMCE,
  9368. cfCX8, cfAPIC, cf_d10, cfSEP, cfMTRR, cfPGE, cfMCA, cfCMOV,
  9369. cfPAT, cfPSE36, cfPSN, cfCLFSH, cf_d20, cfDS, cfACPI, cfMMX,
  9370. cfFXSR, cfSSE, cfSSE2, cfSS, cfHTT, cfTM, cfIA64, cfPBE,
  9371. { in ECX }
  9372. cfSSE3, cfCLMUL, cfDS64, cfMON, cfDSCPL, cfVMX, cfSMX, cfEST,
  9373. cfTM2, cfSSSE3, cfCID, cfSDBG, cfFMA, cfCX16, cfXTPR, cfPDCM,
  9374. cf_c16, cfPCID, cfDCA, cfSSE41, cfSSE42, cfX2A, cfMOVBE, cfPOPCNT,
  9375. cfTSC2, cfAESNI, cfXS, cfOSXS, cfAVX, cfF16C, cfRAND, cfHYP,
  9376. { extended features in EBX, ECX }
  9377. cfFSGS, cf_b01, cfSGX, cfBMI1, cfHLE, cfAVX2, cf_b06, cfSMEP, cfBMI2,
  9378. cfERMS, cfINVPCID, cfRTM, cfPQM, cf_b13, cfMPX, cfPQE, cfAVX512F,
  9379. cfAVX512DQ, cfRDSEED, cfADX, cfSMAP, cfAVX512IFMA, cfPCOMMIT,
  9380. cfCLFLUSH, cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD,
  9381. cfSHA, cfAVX512BW, cfAVX512VL, cfPREFW1, cfAVX512VBMI);
  9382. /// all features, as retrieved from an Intel CPU
  9383. TIntelCpuFeatures = set of TIntelCpuFeature;
  9384. /// convert Intel CPU features as plain CSV text
  9385. function ToText(const aIntelCPUFeatures: TIntelCpuFeatures;
  9386. const Sep: RawUTF8=','): RawUTF8; overload;
  9387. {$ifdef CPUINTEL}
  9388. var
  9389. /// the available CPU features, as recognized at program startup
  9390. CpuFeatures: TIntelCpuFeatures;
  9391. /// compute CRC32C checksum on the supplied buffer using SSE 4.2
  9392. // - use Intel Streaming SIMD Extensions 4.2 hardware accelerated instruction
  9393. // - SSE 4.2 shall be available on the processor (i.e. cfSSE42 in CpuFeatures)
  9394. // - result is not compatible with zlib's crc32() - not the same polynom
  9395. // - crc32cfast() is 1.7 GB/s, crc32csse42() is 3.7 GB/s
  9396. function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  9397. {$endif CPUINTEL}
  9398. /// naive symmetric encryption scheme using a 32 bit key
  9399. // - fast, but not very secure (consider using SynCrypto instead)
  9400. procedure SymmetricEncrypt(key: cardinal; var data: RawByteString);
  9401. var
  9402. /// compute CRC32C checksum on the supplied buffer
  9403. // - this variable will use the fastest mean available, e.g. SSE 4.2
  9404. // - you should use this function instead of crc32cfast() or crc32csse42()
  9405. crc32c: THasher;
  9406. /// compute the hexadecimal representation of the crc32 checkum of a given text
  9407. // - wrapper around CardinalToHex(crc32c(...))
  9408. function crc32cUTF8ToHex(const str: RawUTF8): RawUTF8;
  9409. var
  9410. /// the default hasher used by TDynArrayHashed()
  9411. // - is set to crc32c() function above by default
  9412. DefaultHasher: THasher;
  9413. /// retrieve a particular bit status from a bit array
  9414. function GetBit(const Bits; aIndex: PtrInt): boolean;
  9415. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9416. /// set a particular bit into a bit array
  9417. procedure SetBit(var Bits; aIndex: PtrInt);
  9418. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9419. /// unset/clear a particular bit into a bit array
  9420. procedure UnSetBit(var Bits; aIndex: PtrInt);
  9421. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9422. /// compute the number of bits set in a bit array
  9423. // - Count is the bit count, not byte size
  9424. function GetBitsCount(const Bits; Count: PtrInt): integer;
  9425. const
  9426. /// constant array used by GetAllBits() function (when inlined)
  9427. ALLBITS_CARDINAL: array[1..32] of Cardinal = (
  9428. 1 shl 1-1, 1 shl 2-1, 1 shl 3-1, 1 shl 4-1, 1 shl 5-1, 1 shl 6-1,
  9429. 1 shl 7-1, 1 shl 8-1, 1 shl 9-1, 1 shl 10-1, 1 shl 11-1, 1 shl 12-1,
  9430. 1 shl 13-1, 1 shl 14-1, 1 shl 15-1, 1 shl 16-1, 1 shl 17-1, 1 shl 18-1,
  9431. 1 shl 19-1, 1 shl 20-1, 1 shl 21-1, 1 shl 22-1, 1 shl 23-1, 1 shl 24-1,
  9432. 1 shl 25-1, 1 shl 26-1, 1 shl 27-1, 1 shl 28-1, 1 shl 29-1, 1 shl 30-1,
  9433. $7fffffff, $ffffffff);
  9434. /// returns TRUE if all BitCount bits are set in the input cardinal
  9435. function GetAllBits(Bits: Cardinal; BitCount: Integer): boolean;
  9436. {$ifdef HASINLINE}inline;{$endif}
  9437. /// retrieve a particular bit status from a Int64 bit array (max aIndex is 63)
  9438. function GetBit64(const Bits; aIndex: PtrInt): boolean;
  9439. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9440. /// set a particular bit into a Int64 bit array (max aIndex is 63)
  9441. procedure SetBit64(var Bits: Int64; aIndex: PtrInt);
  9442. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9443. /// unset/clear a particular bit into a Int64 bit array (max aIndex is 63)
  9444. procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt);
  9445. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9446. /// logical OR of two memory buffers
  9447. procedure OrMemory(Dest,Source: PByteArray; size: integer);
  9448. {$ifdef HASINLINE}inline;{$endif}
  9449. /// logical XOR of two memory buffers
  9450. procedure XorMemory(Dest,Source: PByteArray; size: integer); overload;
  9451. {$ifdef HASINLINE}inline;{$endif}
  9452. /// logical XOR of two memory buffers into a third
  9453. procedure XorMemory(Dest,Source1,Source2: PByteArray; size: integer); overload;
  9454. {$ifdef HASINLINE}inline;{$endif}
  9455. /// logical AND of two memory buffers
  9456. procedure AndMemory(Dest,Source: PByteArray; size: integer);
  9457. {$ifdef HASINLINE}inline;{$endif}
  9458. /// returns TRUE if all bytes equal zero
  9459. function IsZero(P: pointer; Length: integer): boolean; overload;
  9460. /// returns TRUE if Value is nil or all supplied Values[] equal ''
  9461. function IsZero(const Values: TRawUTF8DynArray): boolean; overload;
  9462. /// returns TRUE if Value is nil or all supplied Values[] equal 0
  9463. function IsZero(const Values: TIntegerDynArray): boolean; overload;
  9464. /// returns TRUE if Value is nil or all supplied Values[] equal 0
  9465. function IsZero(const Values: TInt64DynArray): boolean; overload;
  9466. /// returns TRUE if no bit inside this TSQLFieldBits is set
  9467. // - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS)
  9468. // - will work also with any other value
  9469. function IsZero(const Fields: TSQLFieldBits): boolean; overload;
  9470. {$ifdef HASINLINE}inline;{$endif}
  9471. /// fast comparison of two TSQLFieldBits values
  9472. // - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS)
  9473. // - will work also with any other value
  9474. function IsEqual(const A,B: TSQLFieldBits): boolean; overload;
  9475. {$ifdef HASINLINE}inline;{$endif}
  9476. /// fill all entries of a supplied array of RawUTF8 with ''
  9477. procedure FillZero(var Values: TRawUTF8DynArray); overload;
  9478. /// fill all entries of a supplied array of 32-bit integers with 0
  9479. procedure FillZero(var Values: TIntegerDynArray); overload;
  9480. /// fill all entries of a supplied array of 64-bit integers with 0
  9481. procedure FillZero(var Values: TInt64DynArray); overload;
  9482. /// fast initialize a TSQLFieldBits with 0
  9483. // - is optimized for 64, 128, 192 and 256 max bits count (i.e. MAX_SQLFIELDS)
  9484. // - will work also with any other value
  9485. procedure FillZero(var Fields: TSQLFieldBits); overload;
  9486. {$ifdef HASINLINE}inline;{$endif}
  9487. /// convert a TSQLFieldBits set of bits into an array of integers
  9488. procedure FieldBitsToIndex(const Fields: TSQLFieldBits;
  9489. var Index: TSQLFieldIndexDynArray; MaxLength: integer=MAX_SQLFIELDS;
  9490. IndexStart: integer=0); overload;
  9491. /// convert a TSQLFieldBits set of bits into an array of integers
  9492. function FieldBitsToIndex(const Fields: TSQLFieldBits;
  9493. MaxLength: integer=MAX_SQLFIELDS): TSQLFieldIndexDynArray; overload;
  9494. {$ifdef HASINLINE}inline;{$endif}
  9495. /// add a field index to an array of field indexes
  9496. // - returns the index in Indexes[] of the newly appended Field value
  9497. function AddFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;
  9498. /// convert an array of field indexes into a TSQLFieldBits set of bits
  9499. procedure FieldIndexToBits(const Index: TSQLFieldIndexDynArray; var Fields: TSQLFieldBits); overload;
  9500. // search a field index in an array of field indexes
  9501. // - returns the index in Indexes[] of the given Field value, -1 if not found
  9502. function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;
  9503. /// convert an array of field indexes into a TSQLFieldBits set of bits
  9504. function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits; overload;
  9505. {$ifdef HASINLINE}inline;{$endif}
  9506. /// name the current thread so that it would be easily identified in the IDE debugger
  9507. procedure SetCurrentThreadName(const Format: RawUTF8; const Args: array of const);
  9508. /// name a thread so that it would be easily identified in the IDE debugger
  9509. // - you can force this function to do nothing by setting the NOSETTHREADNAME
  9510. // conditional, if you have issues with this feature when debugging your app
  9511. procedure SetThreadName(ThreadID: TThreadID; const Format: RawUTF8;
  9512. const Args: array of const);
  9513. /// could be used to override SetThreadNameInternal()
  9514. procedure SetThreadNameDefault(ThreadID: TThreadID; const Name: RawUTF8);
  9515. var
  9516. /// is overriden e.g. by mORMot.pas to log the thread name
  9517. SetThreadNameInternal: procedure(ThreadID: TThreadID; const Name: RawUTF8) = SetThreadNameDefault;
  9518. {$ifndef LVCL} // LVCL does not implement TEvent
  9519. type
  9520. {$M+}
  9521. TSynBackgroundThreadAbstract = class;
  9522. TSynBackgroundThreadEvent = class;
  9523. {$M-}
  9524. /// idle method called by TSynBackgroundThreadAbstract in the caller thread
  9525. // during remote blocking process in a background thread
  9526. // - typical use is to run Application.ProcessMessages, e.g. for
  9527. // TSQLRestClientURI.URI() to provide a responsive UI even in case of slow
  9528. // blocking remote access
  9529. // - provide the time elapsed (in milliseconds) from the request start (can be
  9530. // used e.g. to popup a temporary message to wait)
  9531. // - is call once with ElapsedMS=0 at request start
  9532. // - is call once with ElapsedMS=-1 at request ending
  9533. // - see TLoginForm.OnIdleProcess and OnIdleProcessForm in mORMotUILogin.pas
  9534. TOnIdleSynBackgroundThread = procedure(Sender: TSynBackgroundThreadAbstract;
  9535. ElapsedMS: Integer) of object;
  9536. /// event prototype used e.g. by TSynBackgroundThreadAbstract callbacks
  9537. // - a similar signature is defined in SynCrtSock and LVCL.Classes
  9538. TNotifyThreadEvent = procedure(Sender: TThread) of object;
  9539. /// abstract TThread with its own execution content
  9540. // - you should not use this class directly, but use either
  9541. // TSynBackgroundThreadMethodAbstract / TSynBackgroundThreadEvent /
  9542. // TSynBackgroundThreadMethod and provide a much more convenient callback
  9543. TSynBackgroundThreadAbstract = class(TThread)
  9544. protected
  9545. fPendingProcessLock: TRTLCriticalSection;
  9546. fProcessEvent: TEvent;
  9547. fOnBeforeExecute: TNotifyThreadEvent;
  9548. fOnAfterExecute: TNotifyThreadEvent;
  9549. fThreadName: RawUTF8;
  9550. fExecuteFinished: boolean;
  9551. /// where the main process takes place
  9552. procedure Execute; override;
  9553. procedure ExecuteLoop; virtual; abstract;
  9554. public
  9555. /// initialize the thread
  9556. // - you could define some callbacks to nest the thread execution, e.g.
  9557. // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread
  9558. constructor Create(const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil;
  9559. OnAfterExecute: TNotifyThreadEvent=nil); reintroduce;
  9560. /// release used resources
  9561. destructor Destroy; override;
  9562. /// access to the low-level associated event used to notify task execution
  9563. // to the background thread
  9564. property ProcessEvent: TEvent read fProcessEvent;
  9565. /// defined as public since may be used to terminate the processing methods
  9566. property Terminated;
  9567. end;
  9568. /// state machine status of the TSynBackgroundThreadAbstract process
  9569. TSynBackgroundThreadProcessStep = (
  9570. flagIdle, flagStarted, flagFinished, flagDestroying);
  9571. /// state machine statuses of the TSynBackgroundThreadAbstract process
  9572. TSynBackgroundThreadProcessSteps = set of TSynBackgroundThreadProcessStep;
  9573. /// abstract TThread able to run a method in its own execution content
  9574. // - typical use is a background thread for processing data or remote access,
  9575. // while the UI will be still responsive by running OnIdle event in loop: see
  9576. // e.g. how TSQLRestClientURI.OnIdle handle this in mORMot.pas unit
  9577. // - you should not use this class directly, but inherit from it and override
  9578. // the Process method, or use either TSynBackgroundThreadEvent /
  9579. // TSynBackgroundThreadMethod and provide a much more convenient callback
  9580. TSynBackgroundThreadMethodAbstract = class(TSynBackgroundThreadAbstract)
  9581. protected
  9582. fPendingProcessFlag: TSynBackgroundThreadProcessStep;
  9583. fCallerEvent: TEvent;
  9584. fParam: pointer;
  9585. fCallerThreadID: TThreadID;
  9586. fBackgroundException: Exception;
  9587. fOnIdle: TOnIdleSynBackgroundThread;
  9588. fOnBeforeProcess: TNotifyThreadEvent;
  9589. fOnAfterProcess: TNotifyThreadEvent;
  9590. procedure ExecuteLoop; override;
  9591. function OnIdleProcessNotify(start: Int64): integer;
  9592. function GetOnIdleBackgroundThreadActive: boolean;
  9593. function GetPendingProcess: TSynBackgroundThreadProcessStep;
  9594. procedure SetPendingProcess(State: TSynBackgroundThreadProcessStep);
  9595. // returns flagIdle if acquired, flagDestroying if terminated
  9596. function AcquireThread: TSynBackgroundThreadProcessStep;
  9597. procedure WaitForFinished(start: Int64);
  9598. /// called by Execute method when fProcessParams<>nil and fEvent is notified
  9599. procedure Process; virtual; abstract;
  9600. public
  9601. /// initialize the thread
  9602. // - if aOnIdle is not set (i.e. equals nil), it will simply wait for
  9603. // the background process to finish until RunAndWait() will return
  9604. // - you could define some callbacks to nest the thread execution, e.g.
  9605. // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread
  9606. constructor Create(aOnIdle: TOnIdleSynBackgroundThread;
  9607. const aThreadName: RawUTF8; OnBeforeExecute: TNotifyThreadEvent=nil;
  9608. OnAfterExecute: TNotifyThreadEvent=nil); reintroduce;
  9609. /// finalize the thread
  9610. destructor Destroy; override;
  9611. /// launch Process abstract method asynchronously in the background thread
  9612. // - wait until process is finished, calling OnIdle() callback in
  9613. // the meanwhile
  9614. // - any exception raised in background thread will be translated in the
  9615. // caller thread
  9616. // - returns false if self is not set, or if called from the same thread
  9617. // as it is currently processing (to avoid race condition from OnIdle()
  9618. // callback)
  9619. // - returns true when the background process is finished
  9620. // - OpaqueParam will be used to specify a thread-safe content for the
  9621. // background process
  9622. // - this method is thread-safe, that is it will wait for any started process
  9623. // already launch by another thread: you may call this method from any
  9624. // thread, even if its main purpose is to be called from the main UI thread
  9625. function RunAndWait(OpaqueParam: pointer): boolean;
  9626. /// set a callback event to be executed in loop during remote blocking
  9627. // process, e.g. to refresh the UI during a somewhat long request
  9628. // - you can assign a callback to this property, calling for instance
  9629. // Application.ProcessMessages, to execute the remote request in a
  9630. // background thread, but let the UI still be reactive: the
  9631. // TLoginForm.OnIdleProcess and OnIdleProcessForm methods of
  9632. // mORMotUILogin.pas will match this property expectations
  9633. // - if OnIdle is not set (i.e. equals nil), it will simply wait for
  9634. // the background process to finish until RunAndWait() will return
  9635. property OnIdle: TOnIdleSynBackgroundThread read fOnIdle write fOnIdle;
  9636. /// TRUE if the background thread is active, and OnIdle event is called
  9637. // during process
  9638. // - to be used e.g. to ensure no re-entrance from User Interface messages
  9639. property OnIdleBackgroundThreadActive: Boolean read GetOnIdleBackgroundThreadActive;
  9640. /// optional callback event triggered in Execute before each Process
  9641. property OnBeforeProcess: TNotifyThreadEvent read fOnBeforeProcess write fOnBeforeProcess;
  9642. /// optional callback event triggered in Execute after each Process
  9643. property OnAfterProcess: TNotifyThreadEvent read fOnAfterProcess write fOnAfterProcess;
  9644. end;
  9645. /// background process method called by TSynBackgroundThreadEvent
  9646. // - will supply the OpaqueParam parameter as provided to RunAndWait()
  9647. // method when the Process virtual method will be executed
  9648. TOnProcessSynBackgroundThread = procedure(Sender: TSynBackgroundThreadEvent;
  9649. ProcessOpaqueParam: pointer) of object;
  9650. /// allow background thread process of a method callback
  9651. TSynBackgroundThreadEvent = class(TSynBackgroundThreadMethodAbstract)
  9652. protected
  9653. fOnProcess: TOnProcessSynBackgroundThread;
  9654. /// just call the OnProcess handler
  9655. procedure Process; override;
  9656. public
  9657. /// initialize the thread
  9658. // - if aOnIdle is not set (i.e. equals nil), it will simply wait for
  9659. // the background process to finish until RunAndWait() will return
  9660. constructor Create(aOnProcess: TOnProcessSynBackgroundThread;
  9661. aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce;
  9662. /// provide a method handler to be execute in the background thread
  9663. // - triggered by RunAndWait() method - which will wait until finished
  9664. // - the OpaqueParam as specified to RunAndWait() will be supplied here
  9665. property OnProcess: TOnProcessSynBackgroundThread read fOnProcess write fOnProcess;
  9666. end;
  9667. /// allow background thread process of a variable TThreadMethod callback
  9668. TSynBackgroundThreadMethod = class(TSynBackgroundThreadMethodAbstract)
  9669. protected
  9670. /// just call the TThreadMethod, as supplied to RunAndWait()
  9671. procedure Process; override;
  9672. public
  9673. /// run once the supplied TThreadMethod callback
  9674. // - use this method, and not the inherited RunAndWait()
  9675. procedure RunAndWait(Method: TThreadMethod); reintroduce;
  9676. end;
  9677. /// background process procedure called by TSynBackgroundThreadProcedure
  9678. // - will supply the OpaqueParam parameter as provided to RunAndWait()
  9679. // method when the Process virtual method will be executed
  9680. TOnProcessSynBackgroundThreadProc = procedure(ProcessOpaqueParam: pointer);
  9681. /// allow background thread process of a procedure callback
  9682. TSynBackgroundThreadProcedure = class(TSynBackgroundThreadMethodAbstract)
  9683. protected
  9684. fOnProcess: TOnProcessSynBackgroundThreadProc;
  9685. /// just call the OnProcess handler
  9686. procedure Process; override;
  9687. public
  9688. /// initialize the thread
  9689. // - if aOnIdle is not set (i.e. equals nil), it will simply wait for
  9690. // the background process to finish until RunAndWait() will return
  9691. constructor Create(aOnProcess: TOnProcessSynBackgroundThreadProc;
  9692. aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8); reintroduce;
  9693. /// provide a procedure handler to be execute in the background thread
  9694. // - triggered by RunAndWait() method - which will wait until finished
  9695. // - the OpaqueParam as specified to RunAndWait() will be supplied here
  9696. property OnProcess: TOnProcessSynBackgroundThreadProc read fOnProcess write fOnProcess;
  9697. end;
  9698. /// callback implementing some parallelized process for TSynParallelProcess
  9699. // - if 0<=IndexStart<=IndexStop, it should execute some process
  9700. TSynParallelProcessMethod = procedure(IndexStart, IndexStop: integer) of object;
  9701. /// thread executing process for TSynParallelProcess
  9702. TSynParallelProcessThread = class(TSynBackgroundThreadMethodAbstract)
  9703. protected
  9704. fMethod: TSynParallelProcessMethod;
  9705. fIndexStart, fIndexStop: integer;
  9706. procedure Start(Method: TSynParallelProcessMethod; IndexStart,IndexStop: integer);
  9707. /// executes fMethod(fIndexStart,fIndexStop)
  9708. procedure Process; override;
  9709. public
  9710. end;
  9711. /// an exception which would be raised by TSynParallelProcess
  9712. ESynParallelProcess = class(ESynException);
  9713. /// allow parallel execution of an index-based process in a thread pool
  9714. // - will create its own thread pool, then execute any method by spliting the
  9715. // work into each thread
  9716. TSynParallelProcess = class(TSynPersistentLocked)
  9717. protected
  9718. fThreadName: RawUTF8;
  9719. fPool: array of TSynParallelProcessThread;
  9720. fThreadPoolCount: integer;
  9721. fParallelRunCount: integer;
  9722. public
  9723. /// initialize the thread pool
  9724. // - you could define some callbacks to nest the thread execution, e.g.
  9725. // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread
  9726. // - up to 32 threads could be setup
  9727. // - if ThreadPoolCount is 0, no thread would be created, and process
  9728. // would take place in the current thread
  9729. constructor Create(ThreadPoolCount: integer; const ThreadName: RawUTF8;
  9730. OnBeforeExecute: TNotifyThreadEvent=nil; OnAfterExecute: TNotifyThreadEvent=nil); reintroduce; virtual;
  9731. /// finalize the thread pool
  9732. destructor Destroy; override;
  9733. /// run a method in parallel, and wait for the execution to finish
  9734. // - will split Method[0..MethodCount-1] execution over the threads
  9735. // - in case of any exception during process, an ESynParallelProcess
  9736. // exception would be raised by this method
  9737. procedure ParallelRunAndWait(Method: TSynParallelProcessMethod; MethodCount: integer);
  9738. published
  9739. /// how many threads have been activated
  9740. property ParallelRunCount: integer read fParallelRunCount;
  9741. /// how many threads are currently in this instance thread pool
  9742. property ThreadPoolCount: integer read fThreadPoolCount;
  9743. /// some text identifier, used to distinguish each owned thread
  9744. property ThreadName: RawUTF8 read fThreadName;
  9745. end;
  9746. {$endif LVCL} // LVCL does not implement TEvent
  9747. /// low-level wrapper to add a callback to a dynamic list of events
  9748. // - by default, you can assign only one callback to an Event: but by storing
  9749. // it as a dynamic array of events, you can use this wrapper to add one callback
  9750. // to this list of events
  9751. // - if the event was already registered, do nothing (i.e. won't call it twice)
  9752. // - since this function uses an unsafe typeless EventList parameter, you should
  9753. // not use it in high-level code, but only as wrapper within dedicated methods
  9754. // - will add Event to EventList[] unless Event is already registered
  9755. // - is used e.g. by TTextWriter as such:
  9756. // ! ...
  9757. // ! fEchos: array of TOnTextWriterEcho;
  9758. // ! ...
  9759. // ! procedure EchoAdd(const aEcho: TOnTextWriterEcho);
  9760. // ! ...
  9761. // ! procedure TTextWriter.EchoAdd(const aEcho: TOnTextWriterEcho);
  9762. // ! begin
  9763. // ! MultiEventAdd(fEchos,TMethod(aEcho));
  9764. // ! end;
  9765. // then callbacks are then executed as such:
  9766. // ! if fEchos<>nil then
  9767. // ! for i := 0 to length(fEchos)-1 do
  9768. // ! fEchos[i](self,fEchoBuf);
  9769. // - use MultiEventRemove() to un-register a callback from the list
  9770. function MultiEventAdd(var EventList; const Event: TMethod): boolean;
  9771. /// low-level wrapper to remove a callback from a dynamic list of events
  9772. // - by default, you can assign only one callback to an Event: but by storing
  9773. // it as a dynamic array of events, you can use this wrapper to remove one
  9774. // callback already registered by MultiEventAdd() to this list of events
  9775. // - since this function uses an unsafe typeless EventList parameter, you should
  9776. // not use it in high-level code, but only as wrapper within dedicated methods
  9777. // - is used e.g. by TTextWriter as such:
  9778. // ! ...
  9779. // ! fEchos: array of TOnTextWriterEcho;
  9780. // ! ...
  9781. // ! procedure EchoRemove(const aEcho: TOnTextWriterEcho);
  9782. // ! ...
  9783. // ! procedure TTextWriter.EchoRemove(const aEcho: TOnTextWriterEcho);
  9784. // ! begin
  9785. // ! MultiEventRemove(fEchos,TMethod(aEcho));
  9786. // ! end;
  9787. procedure MultiEventRemove(var EventList; const Event: TMethod); overload;
  9788. /// low-level wrapper to remove a callback from a dynamic list of events
  9789. // - same as the same overloaded procedure, but accepting an EventList[] index
  9790. // to identify the Event to be suppressed
  9791. procedure MultiEventRemove(var EventList; Index: Integer); overload;
  9792. /// low-level wrapper to check if a callback is in a dynamic list of events
  9793. // - by default, you can assign only one callback to an Event: but by storing
  9794. // it as a dynamic array of events, you can use this wrapper to check if
  9795. // a callback has already been registered to this list of events
  9796. // - used internally by MultiEventAdd() and MultiEventRemove() functions
  9797. function MultiEventFind(const EventList; const Event: TMethod): integer;
  9798. /// low-level wrapper to add one or several callbacks from another list of events
  9799. // - all events of the ToBeAddedList would be added to DestList
  9800. // - the list is not checked for duplicates
  9801. procedure MultiEventMerge(var DestList; const ToBeAddedList);
  9802. { ************ fast ISO-8601 types and conversion routines ***************** }
  9803. type
  9804. /// fast bit-encoded date and time value
  9805. // - faster than Iso-8601 text and TDateTime, e.g. can be used as published
  9806. // property field in mORMot's TSQLRecord (see also TModTime and TCreateTime)
  9807. // - use internally for computation an abstract "year" of 16 months of 32 days
  9808. // of 32 hours of 64 minutes of 64 seconds - same as Iso8601ToTimeLog()
  9809. // - use TimeLogFromDateTime/TimeLogToDateTime/TimeLogNow functions, or
  9810. // type-cast any TTimeLog value with the TTimeLogBits memory structure for
  9811. // direct access to its bit-oriented content (or via PTimeLogBits pointer)
  9812. // - since TTimeLog type is bit-oriented, you can't just add or substract two
  9813. // TTimeLog values when doing date/time computation: use a TDateTime temporary
  9814. // conversion in such case:
  9815. // ! aTimeStamp := TimeLogFromDateTime(IncDay(TimeLogToDateTime(aTimeStamp)));
  9816. TTimeLog = type Int64;
  9817. /// dynamic array of TTimeLog
  9818. // - used by TDynArray JSON serialization to handle textual serialization
  9819. TTimeLogDynArray = array of TTimeLog;
  9820. /// pointer to a memory structure for direct access to a TTimeLog type value
  9821. PTimeLogBits = ^TTimeLogBits;
  9822. /// internal memory structure for direct access to a TTimeLog type value
  9823. // - most of the time, you should not use this object, but higher level
  9824. // TimeLogFromDateTime/TimeLogToDateTime/TimeLogNow/Iso8601ToTimeLog functions
  9825. // - since TTimeLogBits.Value is bit-oriented, you can't just add or substract
  9826. // two TTimeLog values when doing date/time computation: use a TDateTime
  9827. // temporary conversion in such case
  9828. // - TTimeLogBits.Value has a 38-bit precision, so features exact representation
  9829. // as JavaScript numbers (stored in a 52-bit mantissa)
  9830. TTimeLogBits = {$ifndef UNICODE}object{$else}record{$endif}
  9831. /// the bit-encoded value itself, which follows an abstract "year" of 16
  9832. // months of 32 days of 32 hours of 64 minutes of 64 seconds
  9833. // - bits 0..5 = Seconds (0..59)
  9834. // - bits 6..11 = Minutes (0..59)
  9835. // - bits 12..16 = Hours (0..23)
  9836. // - bits 17..21 = Day-1 (0..31)
  9837. // - bits 22..25 = Month-1 (0..11)
  9838. // - bits 26..38 = Year (0..4095)
  9839. Value: Int64;
  9840. /// extract the date and time content in Value into individual values
  9841. procedure Expand(out Date: TSystemTime);
  9842. /// convert to Iso-8601 encoded text
  9843. function Text(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8; overload;
  9844. /// convert to Iso-8601 encoded text
  9845. function Text(Dest: PUTF8Char; Expanded: boolean;
  9846. FirstTimeChar: AnsiChar = 'T'): integer; overload;
  9847. /// convert to ready-to-be displayed text
  9848. // - using i18nDateText global event, if set (e.g. by mORMoti18n.pas)
  9849. function i18nText: string;
  9850. /// convert to a Delphi Time
  9851. function ToTime: TDateTime;
  9852. /// convert to a Delphi Date
  9853. // - will return 0 if the stored value is not a valid date
  9854. function ToDate: TDateTime;
  9855. /// convert to a Delphi Date and Time
  9856. // - will return 0 if the stored value is not a valid date
  9857. function ToDateTime: TDateTime;
  9858. /// convert to a second-based c-encoded time (from Unix epoch 1/1/1970)
  9859. function ToUnixTime: Int64;
  9860. /// convert to a millisecond-based c-encoded time (from Unix epoch 1/1/1970)
  9861. function ToUnixMSTime: Int64;
  9862. /// fill Value from specified Date and Time
  9863. procedure From(Y,M,D, HH,MM,SS: cardinal); overload;
  9864. /// fill Value from specified TDateTime
  9865. procedure From(DateTime: TDateTime; DateOnly: Boolean=false); overload;
  9866. /// fill Value from specified File Date
  9867. procedure From(FileDate: integer); overload;
  9868. /// fill Value from Iso-8601 encoded text
  9869. procedure From(P: PUTF8Char; L: integer); overload;
  9870. /// fill Value from Iso-8601 encoded text
  9871. procedure From(const S: RawUTF8); overload;
  9872. /// fill Value from specified Date/Time individual fields
  9873. procedure From(const Time: TSystemTime); overload;
  9874. /// fill Value from second-based c-encoded time (from Unix epoch 1/1/1970)
  9875. procedure FromUnixTime(const UnixTime: Int64);
  9876. /// fill Value from millisecond-based c-encoded time (from Unix epoch 1/1/1970)
  9877. procedure FromUnixMSTime(const UnixMSTime: Int64);
  9878. /// fill Value from current local system Date and Time
  9879. procedure FromNow;
  9880. /// fill Value from current UTC system Date and Time
  9881. // - FromNow uses local time: this function retrieves the system time
  9882. // expressed in Coordinated Universal Time (UTC)
  9883. procedure FromUTCTime;
  9884. /// get the year (e.g. 2015) of the TTimeLog value
  9885. function Year: Integer; {$ifdef HASINLINE}inline;{$endif}
  9886. /// get the month (1..12) of the TTimeLog value
  9887. function Month: Integer; {$ifdef HASINLINE}inline;{$endif}
  9888. /// get the day (1..31) of the TTimeLog value
  9889. function Day: Integer; {$ifdef HASINLINE}inline;{$endif}
  9890. /// get the hour (0..23) of the TTimeLog value
  9891. function Hour: integer; {$ifdef HASINLINE}inline;{$endif}
  9892. /// get the minute (0..59) of the TTimeLog value
  9893. function Minute: integer; {$ifdef HASINLINE}inline;{$endif}
  9894. /// get the second (0..59) of the TTimeLog value
  9895. function Second: integer; {$ifdef HASINLINE}inline;{$endif}
  9896. end;
  9897. /// get TTimeLog value from current local system date and time
  9898. // - handle TTimeLog bit-encoded Int64 format
  9899. function TimeLogNow: TTimeLog;
  9900. {$ifdef HASINLINE}inline;{$endif}
  9901. /// get TTimeLog value from current UTC system Date and Time
  9902. // - handle TTimeLog bit-encoded Int64 format
  9903. function TimeLogNowUTC: TTimeLog;
  9904. {$ifdef HASINLINE}inline;{$endif}
  9905. /// get TTimeLog value from a file date and time
  9906. // - handle TTimeLog bit-encoded Int64 format
  9907. function TimeLogFromFile(const FileName: TFileName): TTimeLog;
  9908. /// get TTimeLog value from a given Delphi date and time
  9909. // - handle TTimeLog bit-encoded Int64 format
  9910. // - just a wrapper around PTimeLogBits(@aTime)^.From()
  9911. // - we defined such a function since TTimeLogBits(aTimeLog).From() won't change
  9912. // the aTimeLog variable content
  9913. function TimeLogFromDateTime(DateTime: TDateTime): TTimeLog;
  9914. {$ifdef HASINLINE}inline;{$endif}
  9915. /// Date/Time conversion from a TTimeLog value
  9916. // - handle TTimeLog bit-encoded Int64 format
  9917. // - just a wrapper around PTimeLogBits(@TimeStamp)^.ToDateTime
  9918. // - we defined such a function since TTimeLogBits(aTimeLog).ToDateTime gives an
  9919. // internall compiler error on some Delphi IDE versions (e.g. Delphi 6)
  9920. function TimeLogToDateTime(const TimeStamp: TTimeLog): TDateTime; overload;
  9921. {$ifdef HASINLINE}inline;{$endif}
  9922. /// convert a Iso8601 encoded string into a TTimeLog value
  9923. // - handle TTimeLog bit-encoded Int64 format
  9924. // - use this function only for fast comparaison between two Iso8601 date/time
  9925. // - conversion is faster than Iso8601ToDateTime: use only binary integer math
  9926. // - ContainsNoTime optional pointer can be set to a boolean, which will be
  9927. // set according to the layout in P (e.g. TRUE for '2012-05-26')
  9928. // - returns 0 in case of invalid input string
  9929. function Iso8601ToTimeLogPUTF8Char(P: PUTF8Char; L: integer; ContainsNoTime: PBoolean=nil): TTimeLog;
  9930. /// convert a Iso8601 encoded string into a TTimeLog value
  9931. // - handle TTimeLog bit-encoded Int64 format
  9932. // - use this function only for fast comparaison between two Iso8601 date/time
  9933. // - conversion is faster than Iso8601ToDateTime: use only binary integer math
  9934. function Iso8601ToTimeLog(const S: RawByteString): TTimeLog;
  9935. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9936. /// convert a TTimeLog value into a ISO-8601 string format for SQL '?' inlined
  9937. // parameters
  9938. // - handle TTimeLog bit-encoded Int64 format
  9939. // - follows the same pattern as DateToSQL or DateTimeToSQL functions, i.e.
  9940. // will return the date or time encoded as '\uFFF1YYYY-MM-DDThh:mm:ss' -
  9941. // therefore ':("\uFFF12012-05-04T20:12:13"):' pattern will be recognized as a
  9942. // sftDateTime inline parameter in SQLParamContent() / ExtractInlineParameters()
  9943. // (JSON_SQLDATE_MAGIC will be used as prefix to create '\uFFF1...' pattern)
  9944. // - to be used e.g. as in:
  9945. // ! aRec.CreateAndFillPrepare(Client,'Datum<=?',[TimeLogToSQL(TimeLogNow)]);
  9946. function TimeLogToSQL(const TimeStamp: TTimeLog): RawUTF8;
  9947. /// test if P^ contains a valid ISO-8601 text encoded value
  9948. // - calls internally Iso8601ToTimeLogPUTF8Char() and returns true if contains
  9949. // at least a valid year (YYYY)
  9950. function IsIso8601(P: PUTF8Char; L: integer): boolean;
  9951. {$ifdef HASINLINE}inline;{$endif}
  9952. /// Date/Time conversion from ISO-8601
  9953. // - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
  9954. function Iso8601ToDateTime(const S: RawByteString): TDateTime; overload;
  9955. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif} {$endif}
  9956. /// Date/Time conversion from ISO-8601
  9957. // - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
  9958. // - if L is left to default 0, it will be computed from StrLen(P)
  9959. function Iso8601ToDateTimePUTF8Char(P: PUTF8Char; L: integer=0): TDateTime;
  9960. {$ifdef HASINLINE}inline;{$endif}
  9961. /// Date/Time conversion from ISO-8601
  9962. // - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format, with potentially
  9963. // shorten versions has handled by the ISO-8601 standard (e.g. 'YYYY')
  9964. // - if L is left to default 0, it will be computed from StrLen(P)
  9965. procedure Iso8601ToDateTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TDateTime);
  9966. /// Date/Time conversion from strict ISO-8601 content
  9967. // - recognize only 'YYYY-MM-DDThh:mm:ss' or 'YYYY-MM-DD' or 'Thh:mm:ss'
  9968. // patterns, as e.g. generated by TTextWriter.AddDateTime() or RecordSaveJSON()
  9969. function Iso8601CheckAndDecode(P: PUTF8Char; L: integer; var Value: TDateTime): boolean;
  9970. /// Time conversion from ISO-8601 (with no Date part)
  9971. // - handle 'hhmmss' and 'hh:mm:ss' format
  9972. // - if L is left to default 0, it will be computed from StrLen(P)
  9973. function Iso8601ToTimePUTF8Char(P: PUTF8Char; L: integer=0): TDateTime; overload;
  9974. {$ifdef HASINLINE}inline;{$endif}
  9975. /// Time conversion from ISO-8601 (with no Date part)
  9976. // - handle 'hhmmss' and 'hh:mm:ss' format
  9977. // - if L is left to default 0, it will be computed from StrLen(P)
  9978. procedure Iso8601ToTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TDateTime);
  9979. /// Time conversion from ISO-8601 (with no Date part)
  9980. // - handle 'hhmmss' and 'hh:mm:ss' format
  9981. // - if L is left to default 0, it will be computed from StrLen(P)
  9982. function Iso8601ToTimePUTF8Char(P: PUTF8Char; L: integer; var H,M,S: cardinal): boolean; overload;
  9983. /// Interval date/time conversion from simple text
  9984. // - expected format does not match ISO-8601 Time intervals format, but Oracle
  9985. // interval litteral representation, i.e. '+/-D HH:MM:SS'
  9986. // - e.g. IntervalTextToDateTime('+0 06:03:20') will return 0.25231481481 and
  9987. // IntervalTextToDateTime('-20 06:03:20') -20.252314815
  9988. // - as a consequence, negative intervals will be written as TDateTime values:
  9989. // !DateTimeToIso8601Text(IntervalTextToDateTime('+0 06:03:20'))='T06:03:20'
  9990. // !DateTimeToIso8601Text(IntervalTextToDateTime('+1 06:03:20'))='1899-12-31T06:03:20'
  9991. // !DateTimeToIso8601Text(IntervalTextToDateTime('-2 06:03:20'))='1899-12-28T06:03:20'
  9992. function IntervalTextToDateTime(Text: PUTF8Char): TDateTime;
  9993. {$ifdef HASINLINE}inline;{$endif}
  9994. /// Interval date/time conversion from simple text
  9995. // - expected format does not match ISO-8601 Time intervals format, but Oracle
  9996. // interval litteral representation, i.e. '+/-D HH:MM:SS'
  9997. // - e.g. '+1 06:03:20' will return 1.25231481481
  9998. procedure IntervalTextToDateTimeVar(Text: PUTF8Char; var result: TDateTime);
  9999. /// basic Date/Time conversion into ISO-8601
  10000. // - use 'YYYYMMDDThhmmss' format if not Expanded
  10001. // - use 'YYYY-MM-DDThh:mm:ss' format if Expanded
  10002. function DateTimeToIso8601(D: TDateTime; Expanded: boolean;
  10003. FirstChar: AnsiChar='T'): RawUTF8;
  10004. /// basic Date conversion into ISO-8601
  10005. // - use 'YYYYMMDD' format if not Expanded
  10006. // - use 'YYYY-MM-DD' format if Expanded
  10007. function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUTF8; overload;
  10008. /// basic Date conversion into ISO-8601
  10009. // - use 'YYYYMMDD' format if not Expanded
  10010. // - use 'YYYY-MM-DD' format if Expanded
  10011. function DateToIso8601(Y,M,D: cardinal; Expanded: boolean): RawUTF8; overload;
  10012. /// basic Time conversion into ISO-8601
  10013. // - use 'Thhmmss' format if not Expanded
  10014. // - use 'Thh:mm:ss' format if Expanded
  10015. function TimeToIso8601(Time: TDateTime; Expanded: boolean; FirstChar: AnsiChar='T'): RawUTF8;
  10016. /// Write a Date to P^ Ansi buffer
  10017. // - if Expanded is false, 'YYYYMMDD' date format is used
  10018. // - if Expanded is true, 'YYYY-MM-DD' date format is used
  10019. procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: cardinal); overload;
  10020. /// convert a date into 'YYYY-MM-DD' date format
  10021. // - resulting text is compatible with all ISO-8601 functions
  10022. function DateToIso8601Text(Date: TDateTime): RawUTF8;
  10023. /// Write a Date/Time to P^ Ansi buffer
  10024. procedure DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean); overload;
  10025. /// Write a TDateTime value, expanded as Iso-8601 encoded text into P^ Ansi buffer
  10026. // - if DT=0, returns ''
  10027. // - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
  10028. // - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
  10029. // - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
  10030. procedure DateTimeToIso8601ExpandedPChar(const Value: TDateTime; Dest: PUTF8Char;
  10031. FirstChar: AnsiChar='T');
  10032. /// write a TDateTime into strict ISO-8601 date and/or time text
  10033. // - if DT=0, returns ''
  10034. // - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
  10035. // - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
  10036. // - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
  10037. // - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
  10038. function DateTimeToIso8601Text(DT: TDateTime; FirstChar: AnsiChar='T'): RawUTF8;
  10039. {$ifdef HASINLINE}inline;{$endif}
  10040. /// write a TDateTime into strict ISO-8601 date and/or time text
  10041. // - if DT=0, returns ''
  10042. // - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
  10043. // - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
  10044. // - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
  10045. // - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
  10046. procedure DateTimeToIso8601TextVar(DT: TDateTime; FirstChar: AnsiChar; var result: RawUTF8);
  10047. /// write a TDateTime into strict ISO-8601 date and/or time text
  10048. // - if DT=0, returns ''
  10049. // - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
  10050. // - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
  10051. // - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
  10052. // - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
  10053. procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar; var result: string);
  10054. /// Write a Time to P^ Ansi buffer
  10055. // - if Expanded is false, 'Thhmmss' time format is used
  10056. // - if Expanded is true, 'Thh:mm:ss' time format is used
  10057. // - you can custom the first char in from of the resulting text time
  10058. procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S: cardinal;
  10059. FirstChar: AnsiChar = 'T'); overload;
  10060. /// Write a Time to P^ Ansi buffer
  10061. // - if Expanded is false, 'Thhmmss' time format is used
  10062. // - if Expanded is true, 'Thh:mm:ss' time format is used
  10063. // - you can custom the first char in from of the resulting text time
  10064. procedure TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean;
  10065. FirstChar: AnsiChar = 'T'); overload;
  10066. /// fast conversion of 2 digit characters into a 0..99 value
  10067. // - returns FALSE on success, TRUE if P^ is not correct
  10068. function Char2ToByte(P: PUTF8Char; out Value: Cardinal): Boolean;
  10069. /// fast conversion of 3 digit characters into a 0..9999 value
  10070. // - returns FALSE on success, TRUE if P^ is not correct
  10071. function Char3ToWord(P: PUTF8Char; out Value: Cardinal): Boolean;
  10072. /// fast conversion of 4 digit characters into a 0..9999 value
  10073. // - returns FALSE on success, TRUE if P^ is not correct
  10074. function Char4ToWord(P: PUTF8Char; out Value: Cardinal): Boolean;
  10075. /// our own fast version of the corresponding low-level function
  10076. function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean;
  10077. /// retrieve the current Date, in the ISO 8601 layout, but expanded and
  10078. // ready to be displayed
  10079. function NowToString(Expanded: boolean=true; FirstTimeChar: AnsiChar = ' '): RawUTF8;
  10080. /// retrieve the current UTC Date, in the ISO 8601 layout, but expanded and
  10081. // ready to be displayed
  10082. function NowUTCToString(Expanded: boolean=true; FirstTimeChar: AnsiChar = ' '): RawUTF8;
  10083. /// convert some date/time to the ISO 8601 text layout, including milliseconds
  10084. // - i.e. 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
  10085. function DateTimeMSToString(DateTime: TDateTime; Expanded: boolean=true;
  10086. FirstTimeChar: AnsiChar = ' '; UTC: boolean=true): RawUTF8;
  10087. /// convert some date/time to the "HTTP-date" format as defined by RFC 7231
  10088. // - i.e. "Tue, 15 Nov 1994 12:45:26 GMT" to be used as a value of
  10089. // "Date", "Expires" or "Last-Modified" HTTP header
  10090. // - if you care about timezones Value must be converted to UTC first
  10091. // using TSynTimeZone.LocalToUtc
  10092. function DateTimeToHTTPDate(UTCDateTime: TDateTime): RawUTF8;
  10093. /// retrieve the current Time (whithout Date), in the ISO 8601 layout
  10094. // - useful for direct on screen logging e.g.
  10095. function TimeToString: RawUTF8;
  10096. /// convert a second-based c-encoded time (from Unix epoch 1/1/1970) as TDateTime
  10097. function UnixTimeToDateTime(const UnixTime: Int64): TDateTime;
  10098. {$ifdef HASINLINE}inline;{$endif}
  10099. /// convert a TDateTime into a second-based c-encoded time (from Unix epoch 1/1/1970)
  10100. function DateTimeToUnixTime(const AValue: TDateTime): Int64;
  10101. {$ifdef HASINLINE}inline;{$endif}
  10102. /// convert a millisecond-based c-encoded time (from Unix epoch 1/1/1970) as TDateTime
  10103. function UnixMSTimeToDateTime(const UnixTime: Int64): TDateTime;
  10104. {$ifdef HASINLINE}inline;{$endif}
  10105. /// convert a TDateTime into a millisecond-based c-encoded time (from Unix epoch 1/1/1970)
  10106. function DateTimeToUnixMSTime(const AValue: TDateTime): Int64;
  10107. {$ifdef HASINLINE}inline;{$endif}
  10108. /// returns the current UTC system date and time
  10109. // - SysUtils.Now returns local time: this function returns the system time
  10110. // expressed in Coordinated Universal Time (UTC)
  10111. function NowUTC: TDateTime;
  10112. type
  10113. {$A-}
  10114. /// used to store a Date/Time in TSynTimeZone internal structures
  10115. // - map Windows.TSystemTime, since it is how it is stored in the Registry
  10116. TTimeZoneValue = {$ifdef ISDELPHI2006ANDUP}record{$else}object{$endif}
  10117. wYear: Word;
  10118. wMonth: Word;
  10119. wDayOfWeek: Word;
  10120. wDay: Word;
  10121. wHour: Word;
  10122. wMinute: Word;
  10123. wSecond: Word;
  10124. wMilliseconds: Word;
  10125. function IsZero: boolean;
  10126. {$ifdef HASINLINE}inline;{$endif}
  10127. function EncodeForTimeChange(const year: word): TDateTime;
  10128. end;
  10129. /// used to store Time Zone bias in TSynTimeZone
  10130. // - map how low-level information is stored in the Windows Registry
  10131. TTimeZoneInfo = record
  10132. Bias: LongInt;
  10133. bias_std: LongInt;
  10134. bias_dlt: LongInt;
  10135. change_time_std: TTimeZoneValue;
  10136. change_time_dlt: TTimeZoneValue;
  10137. end;
  10138. PTimeZoneInfo = ^TTimeZoneInfo;
  10139. /// text identifier of a Time Zone, following Microsoft Windows naming
  10140. TTimeZoneID = type RawUTF8;
  10141. /// used to store Time Zone information for a single area in TSynTimeZone
  10142. TTimeZoneData = {$ifdef ISDELPHI2006ANDUP}record{$else}object{$endif}
  10143. id: TTimeZoneID;
  10144. display: RawUTF8;
  10145. tzi: TTimeZoneInfo;
  10146. dyn: array of packed record
  10147. year: integer;
  10148. tzi: TTimeZoneInfo;
  10149. end;
  10150. function GetTziFor(year: integer): PTimeZoneInfo;
  10151. end;
  10152. /// used to store the Time Zone information of a TSynTimeZone class
  10153. TTimeZoneDataDynArray = array of TTimeZoneData;
  10154. {$A+}
  10155. /// handle cross-platform time conversions, following Microsoft time zones
  10156. // - is able to retrieve accurate information from the Windows registry,
  10157. // or from a binary compressed file on other platforms (which should have been
  10158. // saved from a Windows system first)
  10159. // - each time zone will be idendified by its TzId string, as defined by
  10160. // Microsoft for its Windows Operating system
  10161. TSynTimeZone = class
  10162. protected
  10163. fZone: TTimeZoneDataDynArray;
  10164. fZones: TDynArrayHashed;
  10165. fLastZone: TTimeZoneID;
  10166. fLastIndex: integer;
  10167. fIds: TStringList;
  10168. fDisplays: TStringList;
  10169. public
  10170. /// will retrieve the default shared TSynTimeZone instance
  10171. // - locally created via the CreateDefault constructor
  10172. // - this is the usual entry point for time zone process, calling e.g.
  10173. // $ aLocalTime := TSynTimeZone.Default.NowToLocal(aTimeZoneID);
  10174. class function Default: TSynTimeZone;
  10175. /// initialize the internal storage
  10176. // - but no data is available, until Load* methods are called
  10177. constructor Create;
  10178. /// retrieve the time zones from Windows registry, or from a local file
  10179. // - under Linux, the file should be located with the executable, renamed
  10180. // with a .tz extension - may have been created via SaveToFile(''), or
  10181. // from a 'TSynTimeZone' bound resource
  10182. // "dummy" parameter exists only to disambiguate constructors for C++
  10183. constructor CreateDefault(dummy: integer=0);
  10184. /// finalize the instance
  10185. destructor Destroy; override;
  10186. {$ifdef MSWINDOWS}
  10187. {$ifndef LVCL}
  10188. /// read time zone information from the Windows registry
  10189. procedure LoadFromRegistry;
  10190. {$endif}
  10191. {$endif MSWINDOWS}
  10192. /// read time zone information from a compressed file
  10193. // - if no file name is supplied, a ExecutableName.tz file would be used
  10194. procedure LoadFromFile(const FileName: TFileName='');
  10195. /// read time zone information from a compressed memory buffer
  10196. procedure LoadFromBuffer(const Buffer: RawByteString);
  10197. /// read time zone information from a 'TSynTimeZone' resource
  10198. // - the resource should contain the SaveToBuffer compressed binary content
  10199. // - is no resource matching the TSynTimeZone class name and ResType=10
  10200. // do exist, nothing would be loaded
  10201. // - the resource could be created as such, from a Windows system:
  10202. // ! TSynTimeZone.Default.SaveToFile('TSynTimeZone.data');
  10203. // then compile the resource as expected, with a brcc32 .rc entry:
  10204. // ! TSynTimeZone 10 "TSynTimeZone.data"
  10205. procedure LoadFromResource;
  10206. /// write then time zone information into a compressed file
  10207. // - if no file name is supplied, a ExecutableName.tz file would be created
  10208. procedure SaveToFile(const FileName: TFileName);
  10209. /// write then time zone information into a compressed memory buffer
  10210. function SaveToBuffer: RawByteString;
  10211. /// retrieve the time bias (in minutes) for a given date/time on a TzId
  10212. function GetBiasForDateTime(const Value: TDateTime; const TzId: TTimeZoneID;
  10213. out Bias: integer; out HaveDaylight: boolean): boolean;
  10214. /// retrieve the display text corresponding to a TzId
  10215. // - returns '' if the supplied TzId is not recognized
  10216. function GetDisplay(const TzId: TTimeZoneID): RawUTF8;
  10217. /// compute the UTC date/time corrected for a given TzId
  10218. function UtcToLocal(const UtcDateTime: TDateTime; const TzId: TTimeZoneID): TDateTime;
  10219. /// compute the current date/time corrected for a given TzId
  10220. function NowToLocal(const TzId: TTimeZoneID): TDateTime;
  10221. /// compute the UTC date/time for a given local TzId value
  10222. // - by definition, a local time may correspond to two UTC times, during the
  10223. // time biais period, so the returned value is informative only, and any
  10224. // stored value should be following UTC
  10225. function LocalToUtc(const LocalDateTime: TDateTime; const TzID: TTimeZoneID): TDateTime;
  10226. /// direct access to the low-level time zone information
  10227. property Zone: TTimeZoneDataDynArray read fZone;
  10228. /// direct access to the wrapper over the time zone information array
  10229. property Zones: TDynArrayHashed read fZones;
  10230. /// returns a TStringList of all TzID values
  10231. // - could be used to fill any VCL component to select the time zone
  10232. // - order in Ids[] array follows the Zone[].id information
  10233. function Ids: TStrings;
  10234. /// returns a TStringList of all Display text values
  10235. // - could be used to fill any VCL component to select the time zone
  10236. // - order in Displays[] array follows the Zone[].display information
  10237. function Displays: TStrings;
  10238. end;
  10239. var
  10240. /// custom date to ready to be displayed text function
  10241. // - you can override this pointer in order to display the text according
  10242. // to your expected i18n settings
  10243. // - this callback will therefore be set by the mORMoti18n.pas unit
  10244. // - used by TTimeLogBits.i18nText and by TSQLTable.ExpandAsString() method,
  10245. // i.e. TSQLTableToGrid.DrawCell()
  10246. i18nDateText: function(Iso: TTimeLog): string = nil;
  10247. {$ifndef ENHANCEDRTL}
  10248. {$ifndef LVCL} { don't define these twice }
  10249. var
  10250. /// these procedure type must be defined if a default system.pas is used
  10251. // - mORMoti18n.pas unit will hack default LoadResString() procedure
  10252. // - already defined in our Extended system.pas unit
  10253. // - needed with FPC, Delphi 2009 and up, i.e. when ENHANCEDRTL is not defined
  10254. // - expect generic "string" type, i.e. UnicodeString for Delphi 2009+
  10255. // - not needed with the LVCL framework (we should be on server side)
  10256. LoadResStringTranslate: procedure(var Text: string) = nil;
  10257. /// current LoadResString() cached entries count
  10258. // - i.e. resourcestring caching for faster use
  10259. // - used only if a default system.pas is used, not our Extended version
  10260. // - defined here, but resourcestring caching itself is implemented in the
  10261. // mORMoti18n.pas unit, if the ENHANCEDRTL conditional is not defined
  10262. CacheResCount: integer = -1;
  10263. {$endif}
  10264. {$endif}
  10265. type
  10266. /// a generic callback, which can be used to translate some text on the fly
  10267. // - maps procedure TLanguageFile.Translate(var English: string) signature
  10268. // as defined in mORMoti18n.pas
  10269. // - can be used e.g. for TSynMustache's {{"English text}} callback
  10270. TOnStringTranslate = procedure (var English: string) of object;
  10271. /// log a message to a local text file
  10272. // - the text file is located in the executable directory, and its name is
  10273. // simply the executable file name with the '.log' extension instead of '.exe'
  10274. // - format contains the current date and time, then the Msg on one line
  10275. // - date and time format used is 'YYYYMMDD hh:mm:ss (i.e. ISO-8601)'
  10276. procedure LogToTextFile(Msg: RawUTF8);
  10277. /// log a message to a local text file
  10278. // - this version expects the filename to be specified
  10279. // - format contains the current date and time, then the Msg on one line
  10280. // - date and time format used is 'YYYYMMDD hh:mm:ss'
  10281. procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName);
  10282. const
  10283. /// Rotate local log file if reached this size (1MB by default)
  10284. // - .log file will be save as .log.bak file
  10285. // - a new .log file is created
  10286. // - used by AppendToTextFile() and LogToTextFile() functions (not TSynLog)
  10287. MAXLOGSIZE = 1024*1024;
  10288. { ************ fast low-level lookup types used by internal conversion routines }
  10289. {$ifndef ENHANCEDRTL}
  10290. {$ifndef LVCL} { don't define these const twice }
  10291. const
  10292. /// fast lookup table for converting any decimal number from
  10293. // 0 to 99 into their ASCII equivalence
  10294. // - our enhanced SysUtils.pas (normal and LVCL) contains the same array
  10295. TwoDigitLookup: packed array[0..99] of array[1..2] of AnsiChar =
  10296. ('00','01','02','03','04','05','06','07','08','09',
  10297. '10','11','12','13','14','15','16','17','18','19',
  10298. '20','21','22','23','24','25','26','27','28','29',
  10299. '30','31','32','33','34','35','36','37','38','39',
  10300. '40','41','42','43','44','45','46','47','48','49',
  10301. '50','51','52','53','54','55','56','57','58','59',
  10302. '60','61','62','63','64','65','66','67','68','69',
  10303. '70','71','72','73','74','75','76','77','78','79',
  10304. '80','81','82','83','84','85','86','87','88','89',
  10305. '90','91','92','93','94','95','96','97','98','99');
  10306. {$endif}
  10307. {$endif}
  10308. var
  10309. /// fast lookup table for converting any decimal number from
  10310. // 0 to 99 into their ASCII equivalence
  10311. TwoDigitLookupW: packed array[0..99] of word absolute TwoDigitLookup;
  10312. const
  10313. /// used internaly for fast word recognition (32 bytes const)
  10314. IsWord: set of byte =
  10315. [ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z')];
  10316. /// used internaly for fast identifier recognition (32 bytes const)
  10317. // - can be used e.g. for field or table name
  10318. // - this char set matches the classical pascal definition of identifiers
  10319. // - see also PropNameValid()
  10320. IsIdentifier: set of byte =
  10321. [ord('_'),ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z')];
  10322. /// used internaly for fast URI "unreserved" characters identifier
  10323. // - defined as unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  10324. // in @http://tools.ietf.org/html/rfc3986#section-2.3
  10325. IsURIUnreserved: set of byte =
  10326. [ord('a')..ord('z'),ord('A')..ord('Z'),ord('0')..ord('9'),
  10327. ord('-'),ord('.'),ord('_'),ord('~')];
  10328. /// used internaly for fast extended JSON property name recognition (32 bytes const)
  10329. // - can be used e.g. for extended JSON object field
  10330. // - follow JsonPropNameValid, GetJSONPropName and GotoNextJSONObjectOrArray
  10331. IsJsonIdentifierFirstChar: set of byte =
  10332. [ord('_'),ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z'),ord('$')];
  10333. /// used internaly for fast extended JSON property name recognition (32 bytes const)
  10334. // - can be used e.g. for extended JSON object field
  10335. // - follow JsonPropNameValid, GetJSONPropName and GotoNextJSONObjectOrArray
  10336. IsJsonIdentifier: set of byte =
  10337. [ord('_'),ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z'),
  10338. ord('.'),ord('['),ord(']')];
  10339. {$M+} // to have existing RTTI for published properties
  10340. type
  10341. /// used to retrieve version information from any EXE
  10342. // - under Linux, all version numbers are set to 0 by default
  10343. // - you should not have to use this class directly, but via the
  10344. // ExeVersion global variable
  10345. TFileVersion = class
  10346. protected
  10347. fDetailed: string;
  10348. fFileName: TFileName;
  10349. fBuildDateTime: TDateTime;
  10350. /// change the version (not to be used in most cases)
  10351. procedure SetVersion(aMajor,aMinor,aRelease,aBuild: integer);
  10352. public
  10353. /// executable major version number
  10354. Major: Integer;
  10355. /// executable minor version number
  10356. Minor: Integer;
  10357. /// executable release version number
  10358. Release: Integer;
  10359. /// executable release build number
  10360. Build: Integer;
  10361. /// build year of this exe file
  10362. BuildYear: word;
  10363. /// version info of the exe file as '3.1'
  10364. // - return "string" type, i.e. UnicodeString for Delphi 2009+
  10365. Main: string;
  10366. /// retrieve application version from exe file name
  10367. // - DefaultVersion32 is used if no information Version was included into
  10368. // the executable resources (on compilation time)
  10369. // - you should not have to use this constructor, but rather access the
  10370. // ExeVersion global variable
  10371. constructor Create(const aFileName: TFileName; aMajor: integer=0;
  10372. aMinor: integer=0; aRelease: integer=0; aBuild: integer=0);
  10373. /// retrieve the version as a 32 bits integer with Major.Minor.Release
  10374. // - following Major shl 16+Minor shl 8+Release bit pattern
  10375. function Version32: integer;
  10376. /// build date and time of this exe file, as plain text
  10377. function BuildDateTimeString: string;
  10378. /// returns the version information of this exe file as text
  10379. // - includes FileName (without path), Detailed and BuildDateTime properties
  10380. // - e.g. 'myprogram.exe 3.1.0.123 2016-06-14 19:07:55'
  10381. function VersionInfo: RawUTF8;
  10382. /// returns the version information of a specified exe file as text
  10383. // - includes FileName (without path), Detailed and BuildDateTime properties
  10384. // - e.g. 'myprogram.exe 3.1.0.123 2016-06-14 19:07:55'
  10385. class function GetVersionInfo(const aFileName: TFileName): RawUTF8;
  10386. published
  10387. /// version info of the exe file as '3.1.0.123'
  10388. // - return "string" type, i.e. UnicodeString for Delphi 2009+
  10389. // - under Linux, always return '0.0.0.0' if no custom version number
  10390. // has been defined
  10391. property Detailed: string read fDetailed write fDetailed;
  10392. /// build date and time of this exe file
  10393. property BuildDateTime: TDateTime read fBuildDateTime write fBuildDateTime;
  10394. end;
  10395. {$M-}
  10396. {$ifdef DELPHI6OROLDER}
  10397. // define some common constants not available prior to Delphi 7
  10398. const
  10399. HoursPerDay = 24;
  10400. MinsPerHour = 60;
  10401. SecsPerMin = 60;
  10402. MSecsPerSec = 1000;
  10403. MinsPerDay = HoursPerDay * MinsPerHour;
  10404. SecsPerDay = MinsPerDay * SecsPerMin;
  10405. MSecsPerDay = SecsPerDay * MSecsPerSec;
  10406. UnixDateDelta = 25569;
  10407. /// GetFileVersion returns the most significant 32 bits of a file's binary
  10408. // version number
  10409. // - typically, this includes the major and minor version placed
  10410. // together in one 32-bit integer
  10411. // - generally does not include the release or build numbers
  10412. // - returns Cardinal(-1) in case of failure
  10413. function GetFileVersion(const FileName: TFileName): cardinal;
  10414. {$endif}
  10415. /// returns a JSON object containing basic information about the computer
  10416. // - including Host, User, CPU, OS, freemem, freedisk...
  10417. function SystemInfoJson: RawUTF8;
  10418. {$ifdef MSWINDOWS}
  10419. type
  10420. /// the recognized Windows versions
  10421. TWindowsVersion = (
  10422. wUnknown, w2000, wXP, wXP_64, wServer2003, wServer2003_R2,
  10423. wVista, wVista_64, wServer2008, wServer2008_64,
  10424. wSeven, wSeven_64, wServer2008_R2, wServer2008_R2_64,
  10425. wEight, wEight_64, wServer2012, wServer2012_64,
  10426. wEightOne, wEightOne_64, wServer2012R2, wServer2012R2_64,
  10427. wTen, wTen_64, wServer2016, wServer2016_64);
  10428. {$ifndef UNICODE}
  10429. /// not defined in older Delphi versions
  10430. TOSVersionInfoEx = record
  10431. dwOSVersionInfoSize: DWORD;
  10432. dwMajorVersion: DWORD;
  10433. dwMinorVersion: DWORD;
  10434. dwBuildNumber: DWORD;
  10435. dwPlatformId: DWORD;
  10436. szCSDVersion: array[0..127] of char;
  10437. wServicePackMajor: WORD;
  10438. wServicePackMinor: WORD;
  10439. wSuiteMask: WORD;
  10440. wProductType: BYTE;
  10441. wReserved: BYTE;
  10442. end;
  10443. {$endif}
  10444. const
  10445. /// the recognized Windows versions, as plain text
  10446. WINDOWS_NAME: array[TWindowsVersion] of RawUTF8 = (
  10447. '', '2000', 'XP', 'XP 64bit', 'Server 2003', 'Server 2003 R2',
  10448. 'Vista', 'Vista 64bit', 'Server 2008', 'Server 2008 64bit',
  10449. '7', '7 64bit', 'Server 2008 R2', 'Server 2008 R2 64bit',
  10450. '8', '8 64bit', 'Server 2012', 'Server 2012 64bit',
  10451. '8.1', '8.1 64bit', 'Server 2012 R2', 'Server 2012 R2 64bit',
  10452. '10', '10 64bit', 'Server 2016', 'Server 2016 64bit');
  10453. var
  10454. /// is set to TRUE if the current process is a 32 bit image running under WOW64
  10455. // - WOW64 is the x86 emulator that allows 32-bit Windows-based applications
  10456. // to run seamlessly on 64-bit Windows
  10457. // - equals always FALSE if the current executable is a 64 bit image
  10458. IsWow64: boolean;
  10459. /// the current System information, as retrieved for the current process
  10460. // - under a WOW64 process, it will use the GetNativeSystemInfo() new API
  10461. // to retrieve the real top-most system information
  10462. // - note that the lpMinimumApplicationAddress field is replaced by a
  10463. // more optimistic/realistic value ($100000 instead of default $10000)
  10464. SystemInfo: TSystemInfo;
  10465. /// the current Operating System information, as retrieved for the current process
  10466. OSVersionInfo: TOSVersionInfoEx;
  10467. /// the current Operating System version, as retrieved for the current process
  10468. OSVersion: TWindowsVersion;
  10469. /// the current Operating System version, as retrieved for the current process
  10470. // - contains e.g. 'Windows Seven 64 SP1 (6.1.7601)'
  10471. OSVersionText: RawUTF8;
  10472. /// this function can be used to create a GDI compatible window, able to
  10473. // receive Windows Messages for fast local communication
  10474. // - will return 0 on failure (window name already existing e.g.), or
  10475. // the created HWND handle on success
  10476. // - it will call the supplied message handler defined for a given Windows Message:
  10477. // for instance, define such a method in any object definition:
  10478. // ! procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;
  10479. function CreateInternalWindow(const aWindowName: string; aObject: TObject): HWND;
  10480. /// delete the window resources used to receive Windows Messages
  10481. // - must be called for each CreateInternalWindow() function
  10482. // - both parameter values are then reset to ''/0
  10483. function ReleaseInternalWindow(var aWindowName: string; var aWindow: HWND): boolean;
  10484. var
  10485. /// the number of milliseconds that have elapsed since the system was started
  10486. // - compatibility function, to be implemented according to the running OS
  10487. // - will use the corresponding native API function under Vista+, or
  10488. // will emulate it for older Windows versions
  10489. GetTickCount64: function: Int64; stdcall;
  10490. /// similar to Windows sleep() API call, to be truly cross-platform
  10491. // - it should have a millisecond resolution, and handle ms=0 as a switch to
  10492. // another pending thread, i.e. under Windows will call SwitchToThread API
  10493. procedure SleepHiRes(ms: cardinal);
  10494. {$else MSWINDOWS}
  10495. var
  10496. SystemInfo: record
  10497. nprocs: integer;
  10498. uts: UtsName;
  10499. end;
  10500. OSVersionText: RawUTF8;
  10501. {$ifdef KYLIX3}
  10502. /// compatibility function for Linux
  10503. function GetCurrentThreadID: TThreadID; cdecl;
  10504. external 'libpthread.so.0' name 'pthread_self';
  10505. /// overloaded function using open64() to allow 64 bit positions
  10506. function FileOpen(const FileName: string; Mode: LongWord): Integer;
  10507. {$endif}
  10508. /// compatibility function, to be implemented according to the running OS
  10509. // - expect more or less the same result as the homonymous Win32 API function
  10510. // - will call the corresponding function in SynKylix.pas or SynFPCLinux.pas
  10511. function GetTickCount64: Int64;
  10512. {$endif MSWINDOWS}
  10513. /// overloaded function optimized for one pass file reading
  10514. // - will use e.g. the FILE_FLAG_SEQUENTIAL_SCAN flag under Windows, as stated
  10515. // by http://blogs.msdn.com/b/oldnewthing/archive/2012/01/20/10258690.aspx
  10516. // - is used e.g. by StringFromFile() and TSynMemoryStreamMapped.Create()
  10517. function FileOpenSequentialRead(const FileName: string): Integer;
  10518. {$ifdef HASINLINE}inline;{$endif}
  10519. /// returns a TFileStream optimized for one pass file reading
  10520. // - will use FileOpenSequentialRead(), i.e. FILE_FLAG_SEQUENTIAL_SCAN
  10521. function FileStreamSequentialRead(const FileName: string): TFileStream;
  10522. /// check if the current timestamp, in ms, matched a given period
  10523. // - will compare the current GetTickCount64 to the supplied PreviousTix
  10524. // - returns TRUE if the Internal ms period was not elapsed
  10525. // - returns TRUE, and set PreviousTix, if the Interval ms period was elapsed
  10526. // - possible use case may be:
  10527. // !var Last: Int64;
  10528. // !...
  10529. // ! Last := GetTickCount64;
  10530. // ! repeat
  10531. // ! ...
  10532. // ! if Elapsed(Last,1000) then begin
  10533. // ! ... // do something every second
  10534. // ! end;
  10535. // ! until Terminated;
  10536. // !...
  10537. function Elapsed(var PreviousTix: Int64; Interval: Integer): Boolean;
  10538. {$ifndef FPC} { FPC defines those functions as built-in }
  10539. /// compatibility function, to be implemented according to the running CPU
  10540. // - expect the same result as the homonymous Win32 API function
  10541. function InterlockedIncrement(var I: Integer): Integer;
  10542. {$ifdef PUREPASCAL}{$ifndef MSWINDOWS}{$ifdef HASINLINE}inline;{$endif}{$endif}{$endif}
  10543. /// compatibility function, to be implemented according to the running CPU
  10544. // - expect the same result as the homonymous Win32 API function
  10545. function InterlockedDecrement(var I: Integer): Integer;
  10546. {$ifdef PUREPASCAL}{$ifdef HASINLINE}inline;{$endif}{$endif}
  10547. {$endif FPC}
  10548. type
  10549. /// stores some global information about the current executable and computer
  10550. TExeVersion = record
  10551. /// the main executable name, without any path nor extension
  10552. // - e.g. 'Test' for 'c:\pathto\Test.exe'
  10553. ProgramName: RawUTF8;
  10554. /// the main executable details, as used e.g. by TSynLog
  10555. // - e.g. 'C:\Dev\lib\SQLite3\exe\TestSQL3.exe 0.0.0.0 (2011-03-29 11:09:06)'
  10556. ProgramFullSpec: RawUTF8;
  10557. /// the main executable file name (including full path)
  10558. // - same as paramstr(0)
  10559. ProgramFileName: TFileName;
  10560. /// the main executable full path (excluding .exe file name)
  10561. // - same as ExtractFilePath(paramstr(0))
  10562. ProgramFilePath: TFileName;
  10563. /// the full path of the running executable or library
  10564. // - for an executable, same as paramstr(0)
  10565. // - for a library, will contain the whole .dll file name
  10566. InstanceFileName: TFileName;
  10567. /// the current executable version
  10568. Version: TFileVersion;
  10569. /// the current computer host name
  10570. Host: RawUTF8;
  10571. /// the current computer user name
  10572. User: RawUTF8;
  10573. end;
  10574. var
  10575. /// global information about the current executable and computer
  10576. // - this structure is initialized in this unit's initialization block below
  10577. // - you can call SetExecutableVersion() with a custom version, if needed
  10578. ExeVersion: TExeVersion;
  10579. /// initialize ExeVersion global variable, supplying a custom version number
  10580. // - by default, the version numbers will be retrieved at startup from the
  10581. // executable itself (if it was included at build time)
  10582. // - but you can use this function to set any custom version numbers
  10583. procedure SetExecutableVersion(aMajor,aMinor,aRelease,aBuild: integer); overload;
  10584. /// initialize ExeVersion global variable, supplying the version as text
  10585. // - e.g. SetExecutableVersion('7.1.2.512');
  10586. procedure SetExecutableVersion(const aVersionText: RawUTF8); overload;
  10587. /// self-modifying code - change some memory buffer in the code segment
  10588. // - if Backup is not nil, it should point to a Size array of bytes, ready
  10589. // to contain the overridden code buffer, for further hook disabling
  10590. procedure PatchCode(Old,New: pointer; Size: integer; Backup: pointer=nil;
  10591. LeaveUnprotected: boolean=false);
  10592. /// self-modifying code - change one PtrUInt in the code segment
  10593. procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt;
  10594. LeaveUnprotected: boolean=false);
  10595. {$ifdef CPUINTEL}
  10596. type
  10597. /// small memory buffer used to backup a RedirectCode() redirection hook
  10598. TPatchCode = array[0..4] of byte;
  10599. /// pointer to a small memory buffer used to backup a RedirectCode() hook
  10600. PPatchCode = ^TPatchCode;
  10601. /// self-modifying code - add an asm JUMP to a redirected function
  10602. // - if Backup is not nil, it should point to a TPatchCode buffer, ready
  10603. // to contain the overridden code buffer, for further hook disabling
  10604. procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil);
  10605. /// self-modifying code - restore a code from its RedirectCode() backup
  10606. procedure RedirectCodeRestore(Func: pointer; const Backup: TPatchCode);
  10607. {$endif CPUINTEL}
  10608. /// allow to fix TEvent.WaitFor() method for Kylix
  10609. // - under Windows or with FPC, will call original TEvent.WaitFor() method
  10610. function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult;
  10611. /// allow to fix TEvent.WaitFor(Event,INFINITE) method for Kylix
  10612. // - under Windows or with FPC, will call original TEvent.WaitFor() method
  10613. procedure FixedWaitForever(Event: TEvent);
  10614. type
  10615. /// to be used instead of TMemoryStream, for speed
  10616. // - allocates memory from Delphi heap (i.e. FastMM4/SynScaleMM)
  10617. // and not GlobalAlloc()
  10618. // - uses bigger growing size of the capacity
  10619. {$ifdef LVCL} // LVCL already use Delphi heap instead of GlobalAlloc()
  10620. THeapMemoryStream = TMemoryStream;
  10621. {$else}
  10622. {$ifdef FPC} // FPC already use Delphi heap instead of GlobalAlloc()
  10623. THeapMemoryStream = TMemoryStream;
  10624. {$else}
  10625. {$ifdef MSWINDOWS}
  10626. THeapMemoryStream = class(TMemoryStream)
  10627. protected
  10628. function Realloc(var NewCapacity: longint): Pointer; override;
  10629. end;
  10630. {$else}
  10631. THeapMemoryStream = TMemoryStream;
  10632. {$endif}
  10633. {$endif}
  10634. {$endif}
  10635. var
  10636. /// a global "Garbage collector", for some classes instances which must
  10637. // live during whole main executable process
  10638. // - used to avoid any memory leak with e.g. 'class var RecordProps', i.e.
  10639. // some singleton or static objects
  10640. // - to be used, e.g. as:
  10641. // ! Version := TFileVersion.Create(InstanceFileName,DefaultVersion32);
  10642. // ! GarbageCollector.Add(Version);
  10643. // - see also GarbageCollectorFreeAndNil() as an alternative
  10644. GarbageCollector: TObjectList;
  10645. /// set to TRUE when the global "Garbage collector" are beeing freed
  10646. GarbageCollectorFreeing: boolean;
  10647. /// a global "Garbage collector" for some TObject global variables which must
  10648. // live during whole main executable process
  10649. // - this list expects a pointer to the TObject instance variable to be
  10650. // specified, and will be set to nil (like a FreeAndNil)
  10651. // - this may be useful when used when targetting Delphi IDE packages,
  10652. // to circumvent the bug of duplicated finalization of units, in the scope
  10653. // of global variables
  10654. // - to be used, e.g. as:
  10655. // ! if SynAnsiConvertList=nil then
  10656. // ! GarbageCollectorFreeAndNil(SynAnsiConvertList,TObjectList.Create);
  10657. procedure GarbageCollectorFreeAndNil(var InstanceVariable; Instance: TObject);
  10658. /// force the global "Garbage collector" list to be released immediately
  10659. // - this function is called in the finalization section of this unit
  10660. // - you should NEVER have to call this function, unless some specific cases
  10661. // (e.g. when using Delphi packages, just before releasing the package)
  10662. procedure GarbageCollectorFree;
  10663. /// enter a giant lock for thread-safe shared process
  10664. // - shall be protected as such:
  10665. // ! GlobalLock;
  10666. // ! try
  10667. // ! .... do something thread-safe but as short as possible
  10668. // ! finally
  10669. // ! GlobalUnLock;
  10670. // ! end;
  10671. // - you should better not use such a giant-lock, but an instance-dedicated
  10672. // critical section - these functions are just here to be convenient, for
  10673. // non time critical process
  10674. procedure GlobalLock;
  10675. /// release the giant lock for thread-safe shared process
  10676. // - you should better not use such a giant-lock, but an instance-dedicated
  10677. // critical section - these functions are just here to be convenient, for
  10678. // non time critical process
  10679. procedure GlobalUnLock;
  10680. { ************ TSynTable generic types and classes ************************** }
  10681. {$define SORTCOMPAREMETHOD}
  10682. { if defined, the field content comparison will use a method instead of fixed
  10683. functions - could be mandatory for tftArray field kind }
  10684. type
  10685. /// the available types for any TSynTable field property
  10686. // - this is used in our so-called SBF compact binary format
  10687. // (similar to BSON or Protocol Buffers)
  10688. // - those types are used for both storage and JSON conversion
  10689. // - basic types are similar to SQLite3, i.e. Int64/Double/UTF-8/Blob
  10690. // - storage can be of fixed size, or of variable length
  10691. // - you can specify to use WinAnsi encoding instead of UTF-8 for string storage
  10692. // (it can use less space on disk than UTF-8 encoding)
  10693. // - BLOB fields can be either internal (i.e. handled by TSynTable like a
  10694. // RawByteString text storage), either external (i.e. must be stored in a dedicated
  10695. // storage structure - e.g. another TSynBigTable instance)
  10696. TSynTableFieldType =
  10697. (// unknown or not defined field type
  10698. tftUnknown,
  10699. // some fixed-size field value
  10700. tftBoolean, tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64,
  10701. tftCurrency, tftDouble,
  10702. // some variable-size field value
  10703. tftVarUInt32, tftVarInt32, tftVarUInt64,
  10704. // text storage
  10705. tftWinAnsi, tftUTF8,
  10706. // BLOB fields
  10707. tftBlobInternal, tftBlobExternal,
  10708. // other variable-size field value
  10709. tftVarInt64);
  10710. /// set of available field types for TSynTable
  10711. TSynTableFieldTypes = set of TSynTableFieldType;
  10712. /// available option types for a field property
  10713. // - tfoIndex is set if an index must be created for this field
  10714. // - tfoUnique is set if field values must be unique (if set, the tfoIndex
  10715. // will be always forced)
  10716. // - tfoCaseInsensitive can be set to make no difference between 'a' and 'A'
  10717. // (by default, comparison is case-sensitive) - this option has an effect
  10718. // not only if tfoIndex or tfoUnique is set, but also for iterating search
  10719. TSynTableFieldOption = (
  10720. tfoIndex, tfoUnique, tfoCaseInsensitive);
  10721. /// set of option types for a field
  10722. TSynTableFieldOptions = set of TSynTableFieldOption;
  10723. /// used to store bit set for all available fiels in a Table
  10724. // - with current format, maximum field count is 64
  10725. TSynTableFieldBits = set of 0..63;
  10726. /// an custom RawByteString type used to store internaly a data in
  10727. // our SBF compact binary format
  10728. TSBFString = type RawByteString;
  10729. /// function prototype used to retrieve the index of a specified property name
  10730. // - 'ID' is handled separately: here must be available only the custom fields
  10731. TSynTableFieldIndex = function(const PropName: RawUTF8): integer of object;
  10732. /// the recognized operators for a TSynTableStatement where clause
  10733. TSynTableStatementOperator = (
  10734. opEqualTo,
  10735. opNotEqualTo,
  10736. opLessThan,
  10737. opLessThanOrEqualTo,
  10738. opGreaterThan,
  10739. opGreaterThanOrEqualTo,
  10740. opIn,
  10741. opIsNull,
  10742. opIsNotNull,
  10743. opLike,
  10744. opContains,
  10745. opFunction);
  10746. TSynTableFieldProperties = class;
  10747. /// one recognized SELECT expression for TSynTableStatement
  10748. TSynTableStatementSelect = record
  10749. /// the column SELECTed for the SQL statement, in the expected order
  10750. // - contains 0 for ID/RowID, or the RTTI field index + 1
  10751. Field: integer;
  10752. /// an optional integer to be added
  10753. // - recognized from .. +123 .. -123 patterns in the select
  10754. ToBeAdded: integer;
  10755. /// the optional column alias, e.g. 'MaxID' for 'max(id) as MaxID'
  10756. Alias: RawUTF8;
  10757. /// the optional function applied to the SELECTed column
  10758. // - e.g. Max(RowID) would store 'Max' and SelectField[0]=0
  10759. // - but Count(*) would store 'Count' and SelectField[0]=0, and
  10760. // set FunctionIsCountStart = TRUE
  10761. FunctionName: RawUTF8;
  10762. /// if the function needs a special process
  10763. // - e.g. funcCountStar for the special Count(*) expression or
  10764. // funcDistinct, funcMax for distinct(...)/max(...) aggregation
  10765. FunctionKnown: (funcNone, funcCountStar, funcDistinct, funcMax);
  10766. end;
  10767. /// the recognized SELECT expressions for TSynTableStatement
  10768. TSynTableStatementSelectDynArray = array of TSynTableStatementSelect;
  10769. /// one recognized WHERE expression for TSynTableStatement
  10770. TSynTableStatementWhere = record
  10771. /// any '(' before the actual expression
  10772. ParenthesisBefore: RawUTF8;
  10773. /// any ')' after the actual expression
  10774. ParenthesisAfter: RawUTF8;
  10775. /// expressions are evaluated as AND unless this field is set to TRUE
  10776. JoinedOR: boolean;
  10777. /// if this expression is preceded by a NOT modifier
  10778. NotClause: boolean;
  10779. /// the index of the field used for the WHERE expression
  10780. // - WhereField=0 for ID, 1 for field # 0, 2 for field #1,
  10781. // and so on... (i.e. WhereField = RTTI field index +1)
  10782. Field: integer;
  10783. /// the operator of the WHERE expression
  10784. Operator: TSynTableStatementOperator;
  10785. /// the SQL function name associated to a Field and Value
  10786. // - e.g. 'INTEGERDYNARRAYCONTAINS' and Field=0 for
  10787. // IntegerDynArrayContains(RowID,10) and ValueInteger=10
  10788. // - Value does not contain anything
  10789. FunctionName: RawUTF8;
  10790. /// the value used for the WHERE expression
  10791. Value: RawUTF8;
  10792. /// the raw value SQL buffer used for the WHERE expression
  10793. ValueSQL: PUTF8Char;
  10794. /// the raw value SQL buffer length used for the WHERE expression
  10795. ValueSQLLen: integer;
  10796. /// an integer representation of WhereValue (used for ID check e.g.)
  10797. ValueInteger: integer;
  10798. /// used to fast compare with SBF binary compact formatted data
  10799. ValueSBF: TSBFString;
  10800. {$ifndef NOVARIANTS}
  10801. /// the value used for the WHERE expression, encoded as Variant
  10802. // - may be a TDocVariant for the IN operator
  10803. ValueVariant: variant;
  10804. {$endif}
  10805. end;
  10806. /// the recognized WHERE expressions for TSynTableStatement
  10807. TSynTableStatementWhereDynArray = array of TSynTableStatementWhere;
  10808. /// used to parse a SELECT SQL statement, following the SQlite3 syntax
  10809. // - handle basic REST commands, i.e. a SELECT over a single table (no JOIN)
  10810. // with its WHERE clause, and result column aliases
  10811. // - handle also aggregate functions like "SELECT Count(*) FROM TableName"
  10812. // - will also parse any LIMIT, OFFSET, ORDER BY, GROUP BY statement clause
  10813. TSynTableStatement = class
  10814. protected
  10815. fSQLStatement: RawUTF8;
  10816. fSelect: TSynTableStatementSelectDynArray;
  10817. fSelectFunctionCount: integer;
  10818. fTableName: RawUTF8;
  10819. fWhere: TSynTableStatementWhereDynArray;
  10820. fOrderByField: TSQLFieldIndexDynArray;
  10821. fGroupByField: TSQLFieldIndexDynArray;
  10822. fWhereHasParenthesis: boolean;
  10823. fOrderByDesc: boolean;
  10824. fLimit: integer;
  10825. fOffset: integer;
  10826. fWriter: TJSONWriter;
  10827. public
  10828. /// parse the given SELECT SQL statement and retrieve the corresponding
  10829. // parameters into this class read-only properties
  10830. // - the supplied GetFieldIndex() method is used to populate the
  10831. // SelectedFields and Where[].Field properties
  10832. // - SimpleFieldsBits is used for '*' field names
  10833. // - SQLStatement is left '' if the SQL statement is not correct
  10834. // - if SQLStatement is set, the caller must check for TableName to match
  10835. // the expected value, then use the Where[] to retrieve the content
  10836. // - if FieldProp is set, then the Where[].ValueSBF property is initialized
  10837. // with the SBF equivalence of the Where[].Value
  10838. constructor Create(const SQL: RawUTF8; GetFieldIndex: TSynTableFieldIndex;
  10839. SimpleFieldsBits: TSQLFieldBits=[0..MAX_SQLFIELDS-1];
  10840. FieldProp: TSynTableFieldProperties=nil);
  10841. /// compute the SELECT column bits from the SelectFields array
  10842. procedure SelectFieldBits(var Fields: TSQLFieldBits; var withID: boolean);
  10843. /// the SELECT SQL statement parsed
  10844. // - equals '' if the parsing failed
  10845. property SQLStatement: RawUTF8 read fSQLStatement;
  10846. /// the column SELECTed for the SQL statement, in the expected order
  10847. property Select: TSynTableStatementSelectDynArray read fSelect;
  10848. /// if the SELECTed expression of this SQL statement have any function defined
  10849. property SelectFunctionCount: integer read fSelectFunctionCount;
  10850. /// the retrieved table name
  10851. property TableName: RawUTF8 read fTableName;
  10852. /// the WHERE clause of this SQL statement
  10853. property Where: TSynTableStatementWhereDynArray read fWhere;
  10854. /// if the WHERE clause contains any ( ) parenthesis expression
  10855. property WhereHasParenthesis: boolean read fWhereHasParenthesis;
  10856. /// recognize an GROUP BY clause with one or several fields
  10857. // - here 0 = ID, otherwise RTTI field index +1
  10858. property GroupByField: TSQLFieldIndexDynArray read fGroupByField;
  10859. /// recognize an ORDER BY clause with one or several fields
  10860. // - here 0 = ID, otherwise RTTI field index +1
  10861. property OrderByField: TSQLFieldIndexDynArray read fOrderByField;
  10862. /// false for default ASC order, true for DESC attribute
  10863. property OrderByDesc: boolean read fOrderByDesc;
  10864. /// the number specified by the optional LIMIT ... clause
  10865. // - set to 0 by default (meaning no LIMIT clause)
  10866. property Limit: integer read fLimit;
  10867. /// the number specified by the optional OFFSET ... clause
  10868. // - set to 0 by default (meaning no OFFSET clause)
  10869. property Offset: integer read fOffset;
  10870. /// optional associated writer
  10871. property Writer: TJSONWriter read fWriter write fWriter;
  10872. end;
  10873. /// function prototype used to retrieve the RECORD data of a specified Index
  10874. // - the index is not the per-ID index, but the "physical" index, i.e. the
  10875. // index value used to retrieve data from low-level (and faster) method
  10876. // - should return nil if Index is out of range
  10877. // - caller must provide a temporary storage buffer to be used optionally
  10878. TSynTableGetRecordData = function(
  10879. Index: integer; var aTempData: RawByteString): pointer of object;
  10880. TSynTable = class;
  10881. {$ifdef SORTCOMPAREMETHOD}
  10882. /// internal value used by TSynTableFieldProperties.SortCompare() method to
  10883. // avoid stack allocation
  10884. TSortCompareTmp = record
  10885. PB1, PB2: PByte;
  10886. L1,L2: integer;
  10887. end;
  10888. {$endif}
  10889. /// store the type properties of a given field / database column
  10890. TSynTableFieldProperties = class
  10891. protected
  10892. /// used during OrderedIndexSort to prevent stack usage
  10893. SortPivot: pointer;
  10894. {$ifdef SORTCOMPAREMETHOD}
  10895. /// internal value used by SortCompare() method to avoid stack allocation
  10896. SortCompareTmp: TSortCompareTmp;
  10897. {$endif}
  10898. /// these two temporary buffers are used to call TSynTableGetRecordData
  10899. DataTemp1, DataTemp2: RawByteString;
  10900. /// the associated table which own this field property
  10901. Owner: TSynTable;
  10902. /// the global size of a default field value, as encoded
  10903. // in our SBF compact binary format
  10904. fDefaultFieldLength: integer;
  10905. /// a default field data, as encoded in our SBF compact binary format
  10906. fDefaultFieldData: TSBFString;
  10907. /// last >=0 value returned by the last OrderedIndexFindAdd() call
  10908. fOrderedIndexFindAdd: integer;
  10909. /// used for internal QuickSort of OrderedIndex[]
  10910. // - call SortCompare() for sorting the items
  10911. procedure OrderedIndexSort(L,R: PtrInt);
  10912. /// retrieve an index from OrderedIndex[] of the given value
  10913. // - call SortCompare() to compare to the reference value
  10914. function OrderedIndexFind(Value: pointer): PtrInt;
  10915. /// retrieve an index where a Value must be added into OrderedIndex[]
  10916. // - call SortCompare() to compare to the reference value
  10917. // - returns -1 if Value is there, or the index where to insert
  10918. // - the returned value (if >= 0) will be stored in fOrderedIndexFindAdd
  10919. function OrderedIndexFindAdd(Value: pointer): PtrInt;
  10920. /// set OrderedIndexReverse[OrderedIndex[aOrderedIndex]] := aOrderedIndex;
  10921. procedure OrderedIndexReverseSet(aOrderedIndex: integer);
  10922. public
  10923. /// the field name
  10924. Name: RawUTF8;
  10925. /// kind of field (defines both value type and storage to be used)
  10926. FieldType: TSynTableFieldType;
  10927. /// the fixed-length size, or -1 for a varInt, -2 for a variable string
  10928. FieldSize: integer;
  10929. /// options of this field
  10930. Options: TSynTableFieldOptions;
  10931. /// contains the offset of this field, in case of fixed-length field
  10932. // - normaly, fixed-length fields are stored in the beginning of the record
  10933. // storage: in this case, a value >= 0 will point to the position of the
  10934. // field value of this field
  10935. // - if the value is < 0, its absolute will be the field number to be counted
  10936. // after TSynTable.fFieldVariableOffset (-1 for first item)
  10937. Offset: integer;
  10938. /// number of the field in the table (starting at 0)
  10939. FieldNumber: integer;
  10940. /// if allocated, contains the storage indexes of every item, in sorted order
  10941. // - only available if tfoIndex is in Options
  10942. // - the index is not the per-ID index, but the "physical" index, i.e. the
  10943. // index value used to retrieve data from low-level (and faster) method
  10944. OrderedIndex: TIntegerDynArray;
  10945. /// if allocated, contains the reverse storage index of OrderedIndex
  10946. // - i.e. OrderedIndexReverse[OrderedIndex[i]] := i;
  10947. // - used to speed up the record update procedure with huge number of
  10948. // records
  10949. OrderedIndexReverse: TIntegerDynArray;
  10950. /// number of items in OrderedIndex[]
  10951. // - is set to 0 when the content has been modified (mark force recreate)
  10952. OrderedIndexCount: integer;
  10953. /// if set to TRUE after an OrderedIndex[] refresh but with not sorting
  10954. // - OrderedIndexSort(0,OrderedIndexCount-1) must be called before using
  10955. // the OrderedIndex[] array
  10956. // - you should call OrderedIndexRefresh method to ensure it is sorted
  10957. OrderedIndexNotSorted: boolean;
  10958. /// all TSynValidate instances registered per each field
  10959. Filters: TObjectList;
  10960. /// all TSynValidate instances registered per each field
  10961. Validates: TObjectList;
  10962. /// low-level binary comparison used by IDSort and TSynTable.IterateJSONValues
  10963. // - P1 and P2 must point to the values encoded in our SBF compact binary format
  10964. {$ifdef SORTCOMPAREMETHOD}
  10965. function SortCompare(P1,P2: PUTF8Char): PtrInt;
  10966. {$else}
  10967. SortCompare: TUTF8Compare;
  10968. {$endif}
  10969. /// read entry from a specified file reader
  10970. constructor CreateFrom(var RD: TFileBufferReader);
  10971. /// release associated memory and objects
  10972. destructor Destroy; override;
  10973. /// save entry to a specified file writer
  10974. procedure SaveTo(WR: TFileBufferWriter);
  10975. {$ifndef DELPHI5OROLDER}
  10976. /// decode the value from our SBF compact binary format into UTF-8 JSON
  10977. // - returns the next FieldBuffer value
  10978. function GetJSON(FieldBuffer: pointer; W: TTextWriter): pointer;
  10979. {$endif DELPHI5OROLDER}
  10980. /// decode the value from our SBF compact binary format into UTF-8 text
  10981. // - this method does not check for FieldBuffer to be not nil -> caller
  10982. // should check this explicitely
  10983. function GetValue(FieldBuffer: pointer): RawUTF8;
  10984. /// decode the value from a record buffer into an Boolean
  10985. // - will call Owner.GetData to retrieve then decode the field SBF content
  10986. function GetBoolean(RecordBuffer: pointer): Boolean;
  10987. {$ifdef HASINLINE}inline;{$endif}
  10988. /// decode the value from a record buffer into an integer
  10989. // - will call Owner.GetData to retrieve then decode the field SBF content
  10990. function GetInteger(RecordBuffer: pointer): Integer;
  10991. /// decode the value from a record buffer into an Int64
  10992. // - will call Owner.GetData to retrieve then decode the field SBF content
  10993. function GetInt64(RecordBuffer: pointer): Int64;
  10994. /// decode the value from a record buffer into an floating-point value
  10995. // - will call Owner.GetData to retrieve then decode the field SBF content
  10996. function GetDouble(RecordBuffer: pointer): Double;
  10997. /// decode the value from a record buffer into an currency value
  10998. // - will call Owner.GetData to retrieve then decode the field SBF content
  10999. function GetCurrency(RecordBuffer: pointer): Currency;
  11000. /// decode the value from a record buffer into a RawUTF8 string
  11001. // - will call Owner.GetData to retrieve then decode the field SBF content
  11002. function GetRawUTF8(RecordBuffer: pointer): RawUTF8;
  11003. {$ifndef NOVARIANTS}
  11004. /// decode the value from our SBF compact binary format into a Variant
  11005. function GetVariant(FieldBuffer: pointer): Variant; overload;
  11006. {$ifdef HASINLINE}inline;{$endif}
  11007. /// decode the value from our SBF compact binary format into a Variant
  11008. procedure GetVariant(FieldBuffer: pointer; var result: Variant); overload;
  11009. {$endif}
  11010. /// retrieve the binary length (in bytes) of some SBF compact binary format
  11011. function GetLength(FieldBuffer: pointer): Integer;
  11012. {$ifdef HASINLINE}inline;{$endif}
  11013. /// create some SBF compact binary format from a Delphi binary value
  11014. // - will return '' if the field type doesn't match a boolean
  11015. function SBF(const Value: Boolean): TSBFString; overload;
  11016. /// create some SBF compact binary format from a Delphi binary value
  11017. // - will encode any byte, word, integer, cardinal, Int64 value
  11018. // - will return '' if the field type doesn't match an integer
  11019. function SBF(const Value: Int64): TSBFString; overload;
  11020. /// create some SBF compact binary format from a Delphi binary value
  11021. // - will encode any byte, word, integer, cardinal value
  11022. // - will return '' if the field type doesn't match an integer
  11023. function SBF(const Value: Integer): TSBFString; overload;
  11024. /// create some SBF compact binary format from a Delphi binary value
  11025. // - will return '' if the field type doesn't match a currency
  11026. // - we can't use SBF() method name because of Currency/Double ambiguity
  11027. function SBFCurr(const Value: Currency): TSBFString;
  11028. /// create some SBF compact binary format from a Delphi binary value
  11029. // - will return '' if the field type doesn't match a floating-point
  11030. // - we can't use SBF() method name because of Currency/Double ambiguity
  11031. function SBFFloat(const Value: Double): TSBFString;
  11032. /// create some SBF compact binary format from a Delphi binary value
  11033. // - expect a RawUTF8 string: will be converted to WinAnsiString
  11034. // before storage, for tftWinAnsi
  11035. // - will return '' if the field type doesn't match a string
  11036. function SBF(const Value: RawUTF8): TSBFString; overload;
  11037. /// create some SBF compact binary format from a BLOB memory buffer
  11038. // - will return '' if the field type doesn't match tftBlobInternal
  11039. function SBF(Value: pointer; ValueLen: integer): TSBFString; overload;
  11040. /// convert any UTF-8 encoded value into our SBF compact binary format
  11041. // - can be used e.g. from a WHERE clause, for fast comparison in
  11042. // TSynTableStatement.WhereValue content using OrderedIndex[]
  11043. // - is the reverse of GetValue/GetRawUTF8 methods above
  11044. function SBFFromRawUTF8(const aValue: RawUTF8): TSBFString;
  11045. {$ifndef NOVARIANTS}
  11046. /// create some SBF compact binary format from a Variant value
  11047. function SBF(const Value: Variant): TSBFString; overload;
  11048. {$endif}
  11049. /// will update then sort the array of indexes used for the field index
  11050. // - the OrderedIndex[] array is first refreshed according to the
  11051. // aOldIndex, aNewIndex parameters: aOldIndex=-1 for Add, aNewIndex=-1 for
  11052. // Delete, or both >= 0 for update
  11053. // - call with both indexes = -1 will sort the existing OrderedIndex[] array
  11054. // - GetData property must have been set with a method returning a pointer
  11055. // to the field data for a given index (this index is not the per-ID index,
  11056. // but the "physical" index, i.e. the index value used to retrieve data
  11057. // from low-level (and fast) GetData method)
  11058. // - aOldRecordData and aNewRecordData can be specified in order to guess
  11059. // if the field data has really been modified (speed up the update a lot
  11060. // to only sort indexed fields if its content has been really modified)
  11061. // - returns FALSE if any parameter is invalid
  11062. function OrderedIndexUpdate(aOldIndex, aNewIndex: integer;
  11063. aOldRecordData, aNewRecordData: pointer): boolean;
  11064. /// retrieve one or more "physical" indexes matching a WHERE Statement
  11065. // - is faster than a GetIteraring(), because will use binary search using
  11066. // the OrderedIndex[] array
  11067. // - returns the resulting indexes as a a sorted list in MatchIndex/MatchIndexCount
  11068. // - if the indexes are already present in the list, won't duplicate them
  11069. // - WhereSBFValue must be a valid SBF formated field buffer content
  11070. // - the Limit parameter is similar to the SQL LIMIT clause: if greater than 0,
  11071. // an upper bound on the number of rows returned is placed (e.g. set Limit=1
  11072. // to only retrieve the first match)
  11073. // - GetData property must have been set with a method returning a pointer
  11074. // to the field data for a given index (this index is not the per-ID index,
  11075. // but the "physical" index, i.e. the index value used to retrieve data
  11076. // from low-level (and fast) GetData method)
  11077. // - in this method, indexes are not the per-ID indexes, but the "physical"
  11078. // indexes, i.e. each index value used to retrieve data from low-level
  11079. // (and fast) GetData method
  11080. function OrderedIndexMatch(WhereSBFValue: pointer;
  11081. var MatchIndex: TIntegerDynArray; var MatchIndexCount: integer;
  11082. Limit: Integer=0): Boolean;
  11083. /// will force refresh the OrderedIndex[] array
  11084. // - to be called e.g. if OrderedIndexNotSorted = TRUE, if you want to
  11085. // access to the OrderedIndex[] array
  11086. procedure OrderedIndexRefresh;
  11087. /// register a custom filter or validation rule to the class for this field
  11088. // - this will be used by Filter() and Validate() methods
  11089. // - will return the specified associated TSynFilterOrValidate instance
  11090. // - a TSynValidateTableUniqueField is always added by
  11091. // TSynTable.AfterFieldModif if tfoUnique is set in Options
  11092. function AddFilterOrValidate(aFilter: TSynFilterOrValidate): TSynFilterOrValidate;
  11093. /// check the registered constraints
  11094. // - returns '' on success
  11095. // - returns an error message e.g. if a tftUnique constraint failed
  11096. // - RecordIndex=-1 in case of adding, or the physical index of the updated record
  11097. function Validate(RecordBuffer: pointer; RecordIndex: integer): string;
  11098. /// some default SBF compact binary format content
  11099. property SBFDefault: TSBFString read fDefaultFieldData;
  11100. end;
  11101. {$ifndef DELPHI5OROLDER}
  11102. /// a pointer to structure used to store a TSynTable record
  11103. PSynTableData = ^TSynTableData;
  11104. {$A-} { packet object not allowed since Delphi 2009 :( }
  11105. /// used to store a TSynTable record using our SBF compact binary format
  11106. // - this object can be created on the stack
  11107. // - it is mapped into a variant TVarData, to be retrieved by the
  11108. // TSynTable.Data method - but direct allocation of a TSynTableData on the
  11109. // stack is faster (due to the Variant overhead)
  11110. // - is defined either as an object either as a record, due to a bug
  11111. // in Delphi 2009/2010 compiler (at least): this structure is not initialized
  11112. // if defined as an object on the stack, but will be as a record :(
  11113. {$ifdef UNICODE}
  11114. TSynTableData = record
  11115. private
  11116. {$else}
  11117. TSynTableData = object
  11118. protected
  11119. {$endif UNICODE}
  11120. VType: TVarType;
  11121. Filler: array[1..SizeOf(TVarData)-SizeOf(TVarType)-SizeOf(pointer)*2-4] of byte;
  11122. VID: integer;
  11123. VTable: TSynTable;
  11124. VValue: TSBFString;
  11125. {$ifndef NOVARIANTS}
  11126. function GetFieldValue(const FieldName: RawUTF8): Variant; overload;
  11127. procedure GetFieldVariant(const FieldName: RawUTF8; var result: Variant);
  11128. procedure SetFieldValue(const FieldName: RawUTF8; const Value: Variant); overload;
  11129. {$endif}
  11130. /// raise an exception if VTable=nil
  11131. procedure CheckVTableInitialized;
  11132. {$ifdef HASINLINE}inline;{$endif}
  11133. public
  11134. /// initialize a record data content for a specified table
  11135. // - a void content is set
  11136. procedure Init(aTable: TSynTable; aID: Integer=0); overload; {$ifdef HASINLINE}inline;{$endif}
  11137. /// initialize a record data content for a specified table
  11138. // - the specified SBF content is store inside this TSynTableData
  11139. procedure Init(aTable: TSynTable; aID: Integer; RecordBuffer: pointer;
  11140. RecordBufferLen: integer); overload;
  11141. /// the associated record ID
  11142. property ID: integer read VID write VID;
  11143. /// the associated TSynTable instance
  11144. property Table: TSynTable read VTable write VTable;
  11145. /// the record content, SBF compact binary format encoded
  11146. property SBF: TSBFString read VValue;
  11147. {$ifndef NOVARIANTS}
  11148. /// set or retrieve a field value from a variant data
  11149. property Field[const FieldName: RawUTF8]: Variant read GetFieldValue write SetFieldValue;
  11150. /// get a field value for a specified field
  11151. // - this method is faster than Field[], because it won't look for the field name
  11152. function GetFieldValue(aField: TSynTableFieldProperties): Variant; overload;
  11153. /// set a field value for a specified field
  11154. // - this method is faster than Field[], because it won't look for the field name
  11155. procedure SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant); overload;
  11156. {$ifdef HASINLINE}inline;{$endif}
  11157. {$endif}
  11158. /// set a field value for a specified field, from SBF-encoded data
  11159. // - this method is faster than the other, because it won't look for the field
  11160. // name nor make any variant conversion
  11161. procedure SetFieldSBFValue(aField: TSynTableFieldProperties; const Value: TSBFString);
  11162. /// get a field value for a specified field, into SBF-encoded data
  11163. // - this method is faster than the other, because it won't look for the field
  11164. // name nor make any variant conversion
  11165. function GetFieldSBFValue(aField: TSynTableFieldProperties): TSBFString;
  11166. /// filter the SBF buffer record content with all registered filters
  11167. // - all field values are filtered in-place, following our SBF compact
  11168. // binary format encoding for this record
  11169. procedure FilterSBFValue; {$ifdef HASINLINE}inline;{$endif}
  11170. /// check the registered constraints according to a record SBF buffer
  11171. // - returns '' on success
  11172. // - returns an error message e.g. if a tftUnique constraint failed
  11173. // - RecordIndex=-1 in case of adding, or the physical index of the updated record
  11174. function ValidateSBFValue(RecordIndex: integer): string;
  11175. end;
  11176. {$A+} { packet object not allowed since Delphi 2009 :( }
  11177. {$endif DELPHI5OROLDER}
  11178. PUpdateFieldEvent = ^TUpdateFieldEvent;
  11179. /// an opaque structure used for TSynTable.UpdateFieldEvent method
  11180. TUpdateFieldEvent = record
  11181. /// the number of record added
  11182. Count: integer;
  11183. /// the list of IDs added
  11184. // - this list is already in increasing order, because GetIterating was
  11185. // called with the ioID order
  11186. IDs: TIntegerDynArray;
  11187. /// the offset of every record added
  11188. // - follows the IDs[] order
  11189. Offsets64: TInt64DynArray;
  11190. /// previous indexes: NewIndexs[oldIndex] := newIndex
  11191. NewIndexs: TIntegerDynArray;
  11192. /// the list of existing field in the previous data
  11193. AvailableFields: TSQLFieldBits;
  11194. /// where to write the updated data
  11195. WR: TFileBufferWriter;
  11196. end;
  11197. /// will define a validation to be applied to a TSynTableFieldProperties field
  11198. // - a typical usage is to validate a value to be unique in the table
  11199. // (implemented in the TSynValidateTableUniqueField class)
  11200. // - the optional associated parameters are to be supplied JSON-encoded
  11201. // - ProcessField and ProcessRecordIndex properties will be filled before
  11202. // Process method call by TSynTableFieldProperties.Validate()
  11203. TSynValidateTable = class(TSynValidate)
  11204. protected
  11205. fProcessField: TSynTableFieldProperties;
  11206. fProcessRecordIndex: integer;
  11207. public
  11208. /// the associated TSQLRest instance
  11209. // - this value is filled by TSynTableFieldProperties.Validate with its
  11210. // self value to be used for the validation
  11211. // - it can be used in the overridden Process method
  11212. property ProcessField: TSynTableFieldProperties read fProcessField write fProcessField;
  11213. /// the associated record index (in case of update)
  11214. // - is set to -1 in case of adding, or the physical index of the updated record
  11215. // - this value is filled by TSynTableFieldProperties.Validate
  11216. // - it can be used in the overridden Process method
  11217. property ProcessRecordIndex: integer read fProcessRecordIndex write fProcessRecordIndex;
  11218. end;
  11219. /// will define a validation for a TSynTableFieldProperties Unique field
  11220. // - implement constraints check e.g. if tfoUnique is set in Options
  11221. // - it will check that the field value is not void
  11222. // - it will check that the field value is not a duplicate
  11223. TSynValidateTableUniqueField = class(TSynValidateTable)
  11224. public
  11225. /// perform the unique field validation action to the specified value
  11226. // - duplication value check will use the ProcessField and
  11227. // ProcessRecordIndex properties, which will be filled before call by
  11228. // TSynTableFieldProperties.Validate()
  11229. // - aFieldIndex parameter is not used here, since we have already the
  11230. // ProcessField property set
  11231. // - here the Value is expected to be UTF-8 text, as converted from our SBF
  11232. // compact binary format via e.g. TSynTableFieldProperties.GetValue /
  11233. // GetRawUTF8: this is mandatory to have the validation rule fit with other
  11234. // TSynValidateTable classes
  11235. function Process(aFieldIndex: integer; const Value: RawUTF8; var ErrorMsg: string): boolean; override;
  11236. end;
  11237. /// store the description of a table with records, to implement a Database
  11238. // - can be used with several storage engines, for instance TSynBigTableRecord
  11239. // - each record can have up to 64 fields
  11240. // - a mandatory ID field must be handled by the storage engine itself
  11241. // - will handle the storage of records into our SBF compact binary format, in
  11242. // which fixed-length fields are stored leftmost side, then variable-length
  11243. // fields follow
  11244. TSynTable = class
  11245. protected
  11246. fTableName: RawUTF8;
  11247. /// list of TSynTableFieldProperties instances
  11248. fField: TObjectList;
  11249. /// offset of the first variable length value field
  11250. fFieldVariableOffset: PtrUInt;
  11251. /// index of the first variable length value field
  11252. // - equals -1 if no variable length field exists
  11253. fFieldVariableIndex: integer;
  11254. /// bit is set for a tftWinAnsi, tftUTF8 or tftBlobInternal kind of field
  11255. // - these kind of field are encoded as a VarInt length, then the data
  11256. fFieldIsVarString: TSynTableFieldBits;
  11257. /// bit is set for a tftBlobExternal kind of field e.g.
  11258. fFieldIsExternal: TSynTableFieldBits;
  11259. /// event used for proper data retrieval of a given record buffer
  11260. fGetRecordData: TSynTableGetRecordData;
  11261. /// the global size of a default value, as encoded in our SBF compact binary format
  11262. fDefaultRecordLength: integer;
  11263. /// a default record data, as encoded in our SBF compact binary format
  11264. fDefaultRecordData: TSBFString;
  11265. /// list of TSynTableFieldProperties added via all AddField() call
  11266. fAddedField: TList;
  11267. /// true if any field has a tfoUnique option set
  11268. fFieldHasUniqueIndexes: boolean;
  11269. function GetFieldType(Index: integer): TSynTableFieldProperties;
  11270. function GetFieldCount: integer;
  11271. function GetFieldFromName(const aName: RawUTF8): TSynTableFieldProperties;
  11272. function GetFieldIndexFromName(const aName: RawUTF8): integer;
  11273. /// this method matchs the TSynTableFieldIndex event type
  11274. function GetFieldIndexFromShortName(const aName: ShortString): integer;
  11275. /// refresh Offset,FieldNumber,FieldSize and fFieldVariableIndex,fFieldVariableOffset
  11276. procedure AfterFieldModif;
  11277. public
  11278. /// create a table definition instance
  11279. constructor Create(const aTableName: RawUTF8);
  11280. /// create a table definition instance from a specified file reader
  11281. procedure LoadFrom(var RD: TFileBufferReader);
  11282. /// release used memory
  11283. destructor Destroy; override;
  11284. /// save field properties to a specified file writer
  11285. procedure SaveTo(WR: TFileBufferWriter);
  11286. /// retrieve to the corresponding data address of a given field
  11287. function GetData(RecordBuffer: PUTF8Char; Field: TSynTableFieldProperties): pointer;
  11288. /// add a field description to the table
  11289. // - warning: the class responsible of the storage itself must process the
  11290. // data already stored when a field is created, e.g. in
  11291. // TSynBigTableRecord.AddFieldUpdate method
  11292. // - physical order does not necessary follow the AddField() call order:
  11293. // for better performance, it will try to store fixed-sized record first,
  11294. // multiple of 4 bytes first (access is faster if dat is 4 byte aligned),
  11295. // then variable-length after fixed-sized fields; in all case, a field
  11296. // indexed will be put first
  11297. function AddField(const aName: RawUTF8; aType: TSynTableFieldType;
  11298. aOptions: TSynTableFieldOptions=[]): TSynTableFieldProperties;
  11299. /// update a record content
  11300. // - return the updated record data, in our SBF compact binary format
  11301. // - if NewFieldData is not specified, a default 0 or '' value is appended
  11302. // - if NewFieldData is set, it must match the field value kind
  11303. // - warning: this method will update result in-place, so RecordBuffer MUST
  11304. // be <> pointer(result) or data corruption may occur
  11305. procedure UpdateFieldData(RecordBuffer: PUTF8Char; RecordBufferLen,
  11306. FieldIndex: integer; var result: TSBFString; const NewFieldData: TSBFString='');
  11307. /// update a record content after any AddfieldUpdate, to refresh the data
  11308. // - AvailableFields must contain the list of existing fields in the previous data
  11309. function UpdateFieldRecord(RecordBuffer: PUTF8Char; var AvailableFields: TSQLFieldBits): TSBFString;
  11310. /// this Event is to be called for all data records (via a GetIterating method)
  11311. // after any AddfieldUpdate, to refresh the data
  11312. // - Opaque is in fact a pointer to a TUpdateFieldEvent record, and will contain
  11313. // all parameters set by TSynBigTableRecord.AddFieldUpdate, including a
  11314. // TFileBufferWriter instance to use to write the recreated data
  11315. // - it will work with either any newly added field, handly also field data
  11316. // order change in SBF record (e.g. when a fixed-sized field has been added
  11317. // on a record containing variable-length fields)
  11318. function UpdateFieldEvent(Sender: TObject; Opaque: pointer; ID, Index: integer;
  11319. Data: pointer; DataLen: integer): boolean;
  11320. /// event which must be called by the storage engine when some values are modified
  11321. // - if aOldIndex and aNewIndex are both >= 0, the corresponding aOldIndex
  11322. // will be replaced by aNewIndex value (i.e. called in case of a data Update)
  11323. // - if aOldIndex is -1 and aNewIndex is >= 0, aNewIndex refers to a just
  11324. // created item (i.e. called in case of a data Add)
  11325. // - if aOldIndex is >= 0 and aNewIndex is -1, aNewIndex refers to a just
  11326. // deleted item (i.e. called in case of a data Delete)
  11327. // - will update then sort all existing TSynTableFieldProperties.OrderedIndex
  11328. // values
  11329. // - the GetDataBuffer protected virtual method must have been overridden to
  11330. // properly return the record data for a given "physical/stored" index
  11331. // - aOldRecordData and aNewRecordData can be specified in order to guess
  11332. // if the field data has really been modified (speed up the update a lot
  11333. // to only sort indexed fields if its content has been really modified)
  11334. procedure FieldIndexModify(aOldIndex, aNewIndex: integer;
  11335. aOldRecordData, aNewRecordData: pointer);
  11336. /// return the total length of the given record buffer, encoded in our SBF
  11337. // compact binary format
  11338. function DataLength(RecordBuffer: pointer): integer;
  11339. {$ifndef NOVARIANTS}
  11340. /// create a Variant able to access any field content via late binding
  11341. // - i.e. you can use Var.Name to access the 'Name' field of record Var
  11342. // - if you leave ID and RecordBuffer void, a void record is created
  11343. function Data(aID: integer=0; RecordBuffer: pointer=nil;
  11344. RecordBufferLen: Integer=0): Variant; overload;
  11345. {$endif NOVARIANTS}
  11346. /// return a default content for ALL record fields
  11347. // - uses our SBF compact binary format
  11348. property DefaultRecordData: TSBFString read fDefaultRecordData;
  11349. /// list of TSynTableFieldProperties added via all AddField() call
  11350. // - this list will allow TSynBigTableRecord.AddFieldUpdate to refresh
  11351. // the data on disk according to the new field configuration
  11352. property AddedField: TList read fAddedField write fAddedField;
  11353. /// offset of the first variable length value field
  11354. property FieldVariableOffset: PtrUInt read fFieldVariableOffset;
  11355. public
  11356. {$ifndef DELPHI5OROLDER}
  11357. /// create a TJSONWriter, ready to be filled with GetJSONValues(W) below
  11358. // - will initialize all TJSONWriter.ColNames[] values according to the
  11359. // specified Fields index list, and initialize the JSON content
  11360. function CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  11361. const Fields: TSQLFieldIndexDynArray): TJSONWriter; overload;
  11362. /// create a TJSONWriter, ready to be filled with GetJSONValues(W) below
  11363. // - will initialize all TJSONWriter.ColNames[] values according to the
  11364. // specified Fields bit set, and initialize the JSON content
  11365. function CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  11366. const Fields: TSQLFieldBits): TJSONWriter; overload;
  11367. (** return the UTF-8 encoded JSON objects for the values contained
  11368. in the specified RecordBuffer encoded in our SBF compact binary format,
  11369. according to the Expand/WithID/Fields parameters of W
  11370. - if W.Expand is true, JSON data is an object, for direct use with any Ajax or .NET client:
  11371. ! {"col1":val11,"col2":"val12"}
  11372. - if W.Expand is false, JSON data is serialized (as used in TSQLTableJSON)
  11373. ! { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
  11374. - only fields with a bit set in W.Fields will be appended
  11375. - if W.WithID is true, then the first ID field value is included *)
  11376. procedure GetJSONValues(aID: integer; RecordBuffer: PUTF8Char; W: TJSONWriter);
  11377. /// can be used to retrieve all values matching a preparated TSynTableStatement
  11378. // - this method matchs the TSynBigTableIterateEvent callback definition
  11379. // - Sender will be the TSynBigTable instance, and Opaque will point to a
  11380. // TSynTableStatement instance (with all fields initialized, including Writer)
  11381. function IterateJSONValues(Sender: TObject; Opaque: pointer; ID: integer;
  11382. Data: pointer; DataLen: integer): boolean;
  11383. {$endif DELPHI5OROLDER}
  11384. /// check the registered constraints according to a record SBF buffer
  11385. // - returns '' on success
  11386. // - returns an error message e.g. if a tftUnique constraint failed
  11387. // - RecordIndex=-1 in case of adding, or the physical index of the updated record
  11388. function Validate(RecordBuffer: pointer; RecordIndex: integer): string;
  11389. /// filter the SBF buffer record content with all registered filters
  11390. // - all field values are filtered in-place, following our SBF compact
  11391. // binary format encoding for this record
  11392. procedure Filter(var RecordBuffer: TSBFString);
  11393. /// event used for proper data retrieval of a given record buffer, according
  11394. // to the physical/storage index value (not per-ID index)
  11395. // - if not set, field indexes won't work
  11396. // - will be mapped e.g. to TSynBigTable.GetPointerFromPhysicalIndex
  11397. property GetRecordData: TSynTableGetRecordData read fGetRecordData write fGetRecordData;
  11398. public
  11399. /// the internal Table name used to identify it (e.g. from JSON or SQL)
  11400. // - similar to the SQL Table name
  11401. property TableName: RawUTF8 read fTableName write fTableName;
  11402. /// number of fields in this table
  11403. property FieldCount: integer read GetFieldCount;
  11404. /// retrieve the properties of a given field
  11405. // - returns nil if the specified Index is out of range
  11406. property Field[Index: integer]: TSynTableFieldProperties read GetFieldType;
  11407. /// retrieve the properties of a given field
  11408. // - returns nil if the specified Index is out of range
  11409. property FieldFromName[const aName: RawUTF8]: TSynTableFieldProperties read GetFieldFromName; default;
  11410. /// retrieve the index of a given field
  11411. // - returns -1 if the specified Index is out of range
  11412. property FieldIndexFromName[const aName: RawUTF8]: integer read GetFieldIndexFromName;
  11413. /// read-only access to the Field list
  11414. property FieldList: TObjectList read fField;
  11415. /// true if any field has a tfoUnique option set
  11416. property HasUniqueIndexes: boolean read fFieldHasUniqueIndexes;
  11417. end;
  11418. /// SQL Query comparison operators
  11419. // - these operators are e.g. used by CompareOperator() functions
  11420. TCompareOperator = (
  11421. soEqualTo,
  11422. soNotEqualTo,
  11423. soLessThan,
  11424. soLessThanOrEqualTo,
  11425. soGreaterThan,
  11426. soGreaterThanOrEqualTo,
  11427. soBeginWith,
  11428. soContains,
  11429. soSoundsLikeEnglish,
  11430. soSoundsLikeFrench,
  11431. soSoundsLikeSpanish);
  11432. /// low-level integer comparison according to a specified operator
  11433. // - SBF must point to the values encoded in our SBF compact binary format
  11434. // - Value must contain the plain integer value
  11435. // - Value can be a Currency accessed via a PInt64
  11436. // - will work only for tftBoolean, tftUInt8, tftUInt16, tftUInt24,
  11437. // tftInt32, tftInt64 and tftCurrency field types
  11438. // - will handle only soEqualTo...soGreaterThanOrEqualTo operators
  11439. // - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd
  11440. // (can be used for tftArray)
  11441. // - returns true if both values match, or false otherwise
  11442. function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char;
  11443. Value: Int64; Oper: TCompareOperator): boolean; overload;
  11444. /// low-level floating-point comparison according to a specified operator
  11445. // - SBF must point to the values encoded in our SBF compact binary format
  11446. // - Value must contain the plain floating-point value
  11447. // - will work only for tftDouble field type
  11448. // - will handle only soEqualTo...soGreaterThanOrEqualTo operators
  11449. // - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd
  11450. // (can be used for tftArray)
  11451. // - returns true if both values match, or false otherwise
  11452. function CompareOperator(SBF, SBFEnd: PUTF8Char;
  11453. Value: double; Oper: TCompareOperator): boolean; overload;
  11454. /// low-level text comparison according to a specified operator
  11455. // - SBF must point to the values encoded in our SBF compact binary format
  11456. // - Value must contain the plain text value, in the same encoding (either
  11457. // WinAnsi either UTF-8, as FieldType defined for the SBF value)
  11458. // - will work only for tftWinAnsi and tftUTF8 field types
  11459. // - will handle all kind of operators (including soBeginWith, soContains and
  11460. // soSoundsLike*) but soSoundsLike* won't make use of the CaseSensitive parameter
  11461. // - for soSoundsLikeEnglish, soSoundsLikeFrench and soSoundsLikeSpanish
  11462. // operators, Value is not a real PUTF8Char but a prepared PSynSoundEx
  11463. // - if SBFEnd is not nil, it will test for all values until SBF>=SBFEnd
  11464. // (can be used for tftArray)
  11465. // - returns true if both values match, or false otherwise
  11466. function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char;
  11467. Value: PUTF8Char; ValueLen: integer; Oper: TCompareOperator;
  11468. CaseSensitive: boolean): boolean; overload;
  11469. /// JSON compatible representation of a boolean value
  11470. // - returns either 'true' or 'false'
  11471. procedure JSONBoolean(value: boolean; var result: RawUTF8);
  11472. {$ifdef HASINLINE}inline;{$endif} overload;
  11473. const
  11474. /// can be used e.g. in logs
  11475. BOOL_STR: array[boolean] of string[7] = ('false','true');
  11476. /// can be used to append to most English nouns to form a plural
  11477. PLURAL_FORM: array[boolean] of RawUTF8 = ('','s');
  11478. /// used by TSynTableStatement.WhereField for "SELECT .. FROM TableName WHERE ID=?"
  11479. SYNTABLESTATEMENTWHEREID = 0;
  11480. /// convert any AnsiString content into our SBF compact binary format storage
  11481. procedure ToSBFStr(const Value: RawByteString; out Result: TSBFString);
  11482. /// returns TRUE if the specified field name is either 'ID', either 'ROWID'
  11483. function IsRowID(FieldName: PUTF8Char): boolean;
  11484. {$ifdef HASINLINE}inline;{$endif} overload;
  11485. /// returns TRUE if the specified field name is either 'ID', either 'ROWID'
  11486. function IsRowID(FieldName: PUTF8Char; FieldLen: integer): boolean;
  11487. {$ifdef HASINLINE}inline;{$endif} overload;
  11488. /// returns TRUE if the specified field name is either 'ID', either 'ROWID'
  11489. function IsRowIDShort(const FieldName: shortstring): boolean;
  11490. {$ifdef HASINLINE}inline;{$endif} overload;
  11491. /// retrieve the next identifier within the UTF-8 buffer
  11492. // - returns true if something was set to Prop
  11493. function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean;
  11494. { ************ variant-based process, including JSON/BSON document content }
  11495. const
  11496. /// this variant type is not defined in older versions of Delphi
  11497. varWord64 = 21;
  11498. /// this variant type will map the current SynUnicode type
  11499. // - depending on the compiler version
  11500. varSynUnicode = {$ifdef HASVARUSTRING}varUString{$else}varOleStr{$endif};
  11501. /// this variant type will map the current string type
  11502. // - depending on the compiler version
  11503. varNativeString = {$ifdef UNICODE}varUString{$else}varString{$endif};
  11504. /// those TVarData.VType values are un-managed and do not need to be cleared
  11505. // - used mainly in low-level code similar to the folllowing:
  11506. // ! if TVarData(aVariant).VType and VTYPE_STATIC<>0 then
  11507. // ! VarClear(aVariant);
  11508. // - equals private constant varDeepData in Variants.pas
  11509. VTYPE_STATIC = $BFE8;
  11510. /// same as Dest := TVarData(Source) for simple values
  11511. // - will return TRUE for all simple values after varByRef unreference, and
  11512. // copying the unreferenced Source value into Dest raw storage
  11513. // - will return FALSE for not varByRef values, or complex values (e.g. string)
  11514. function SetVariantUnRefSimpleValue(const Source: variant; var Dest: TVarData): boolean;
  11515. {$ifdef HASINLINE}inline;{$endif}
  11516. {$ifndef LVCL}
  11517. /// convert a raw binary buffer into a variant RawByteString varString
  11518. // - you can then use VariantToRawByteString() to retrieve the binary content
  11519. procedure RawByteStringToVariant(Data: PByte; DataLen: Integer; var Value: variant); overload;
  11520. /// convert a RawByteString content into a variant varString
  11521. // - you can then use VariantToRawByteString() to retrieve the binary content
  11522. procedure RawByteStringToVariant(const Data: RawByteString; var Value: variant); overload;
  11523. /// convert back a RawByteString from a variant
  11524. // - the supplied variant should have been created via a RawByteStringToVariant()
  11525. // function call
  11526. procedure VariantToRawByteString(const Value: variant; var Dest: RawByteString);
  11527. /// same as Value := Null, but slightly faster
  11528. procedure SetVariantNull(var Value: variant);
  11529. {$ifdef HASINLINE}inline;{$endif}
  11530. const
  11531. NullVarData: TVarData = (VType: varNull);
  11532. var
  11533. /// a slightly faster alternative to Variants.Null function
  11534. Null: variant absolute NullVarData;
  11535. {$endif}
  11536. /// same as VarIsEmpty(V) or VarIsEmpty(V), but faster
  11537. // - we also discovered some issues with FPC's Variants unit, so this function
  11538. // may be used even in end-user cross-compiler code
  11539. function VarIsEmptyOrNull(const V: Variant): Boolean;
  11540. {$ifdef HASINLINE}inline;{$endif}
  11541. /// same as VarIsEmpty(PVariant(V)^) or VarIsEmpty(PVariant(V)^), but faster
  11542. // - we also discovered some issues with FPC's Variants unit, so this function
  11543. // may be used even in end-user cross-compiler code
  11544. function VarDataIsEmptyOrNull(VarData: pointer): Boolean;
  11545. {$ifdef HASINLINE}inline;{$endif}
  11546. /// fastcheck if a variant hold a value
  11547. // - varEmpty, varNull or a '' string would be considered as void
  11548. // - varBoolean=false or varDate=0 would be considered as void
  11549. // - a TDocVariantData with Count=0 would be considered as void
  11550. // - any other value (e.g. integer) would be considered as not void
  11551. function VarIsVoid(const V: Variant): boolean;
  11552. type
  11553. TVarDataTypes = set of 0..255;
  11554. /// allow to check for a specific set of TVarData.VType
  11555. function VarIs(const V: Variant; const VTypes: TVarDataTypes): Boolean;
  11556. {$ifdef HASINLINE}inline;{$endif}
  11557. {$ifndef NOVARIANTS}
  11558. type
  11559. /// an abstract ancestor for faster access of properties
  11560. // - default GetProperty/SetProperty methods are called via some protected
  11561. // virtual IntGet/IntSet methods, with less overhead
  11562. // - these kind of custom variants will be faster than the default
  11563. // TInvokeableVariantType for properties getter/setter, but you should
  11564. // manually register each type by calling SynRegisterCustomVariantType()
  11565. // - also feature custom JSON parsing, via TryJSONToVariant() protected method
  11566. TSynInvokeableVariantType = class(TInvokeableVariantType)
  11567. protected
  11568. {$ifndef FPC}
  11569. {$ifndef DELPHI6OROLDER}
  11570. /// our custom call backs do not want the function names to be uppercased
  11571. function FixupIdent(const AText: string): string; override;
  11572. {$endif}
  11573. {$endif}
  11574. /// override those two abstract methods for fast getter/setter implementation
  11575. procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); virtual; abstract;
  11576. procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); virtual; abstract;
  11577. public
  11578. /// customization of JSON parsing into variants
  11579. // - will be called by e.g. by VariantLoadJSON() or GetVariantFromJSON()
  11580. // with Options: PDocVariantOptions parameter not nil
  11581. // - this default implementation will always returns FALSE,
  11582. // meaning that the supplied JSON is not to be handled by this custom
  11583. // (abstract) variant type
  11584. // - this method could be overridden to identify any custom JSON content
  11585. // and convert it into a dedicated variant instance, then return TRUE
  11586. // - warning: should NOT modify JSON buffer in-place, unless it returns true
  11587. function TryJSONToVariant(var JSON: PUTF8Char; var Value: variant;
  11588. EndOfObject: PUTF8Char): boolean; virtual;
  11589. /// customization of variant into JSON serialization
  11590. procedure ToJSON(W: TTextWriter; const Value: variant; Escape: TTextWriterKind); overload; virtual;
  11591. /// retrieve the field/column value
  11592. // - this method will call protected IntGet abstract method
  11593. function GetProperty(var Dest: TVarData; const V: TVarData;
  11594. const Name: String): Boolean; override;
  11595. /// set the field/column value
  11596. // - this method will call protected IntSet abstract method
  11597. {$ifdef FPC_VARIANTSETVAR} // see http://mantis.freepascal.org/view.php?id=26773
  11598. function SetProperty(var V: TVarData; const Name: string;
  11599. const Value: TVarData): Boolean; override;
  11600. {$else}
  11601. function SetProperty(const V: TVarData; const Name: string;
  11602. const Value: TVarData): Boolean; override;
  11603. {$endif}
  11604. /// clear the content
  11605. // - this default implementation will set VType := varEmpty
  11606. // - override it if your custom type needs to manage its internal memory
  11607. procedure Clear(var V: TVarData); override;
  11608. /// copy two variant content
  11609. // - this default implementation will copy the TVarData memory
  11610. // - override it if your custom type needs to manage its internal structure
  11611. procedure Copy(var Dest: TVarData; const Source: TVarData;
  11612. const Indirect: Boolean); override;
  11613. /// copy two variant content by value
  11614. // - this default implementation will call the Copy() method
  11615. // - override it if your custom types may use a by reference copy pattern
  11616. procedure CopyByValue(var Dest: TVarData; const Source: TVarData); virtual;
  11617. /// this method will allow to look for dotted name spaces, e.g. 'parent.child'
  11618. // - should return Unassigned if the FullName does not match any value
  11619. // - this default implementation will handle TDocVariant storage, or using
  11620. // generic TSynInvokeableVariantType.IntGet() until nested value match
  11621. // - you can override it with a more optimized version
  11622. procedure Lookup(var Dest: TVarData; const V: TVarData; FullName: PUTF8Char); virtual;
  11623. /// will check if the value is an array, and return the number of items
  11624. // - if the document is an array, will return the items count (0 meaning
  11625. // void array)
  11626. // - this default implementation will return -1 (meaning this is not an array)
  11627. // - overridden method could implement it, e.g. for TDocVariant of kind dvArray
  11628. function IterateCount(const V: TVarData): integer; virtual;
  11629. /// allow to loop over an array value
  11630. // - Index should be in 0..IterateCount-1 range
  11631. // - this default implementation will do nothing
  11632. procedure Iterate(var Dest: TVarData; const V: TVarData; Index: integer); virtual;
  11633. /// returns TRUE if the supplied variant is of the exact custom type
  11634. function IsOfType(const V: variant): boolean;
  11635. end;
  11636. /// a custom variant type used to have direct access to a record content
  11637. // - use TSynTable.Data method to retrieve such a Variant
  11638. // - this variant will store internaly a SBF compact binary format
  11639. // representation of the record content
  11640. // - uses internally a TSynTableData object
  11641. TSynTableVariantType = class(TSynInvokeableVariantType)
  11642. protected
  11643. procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); override;
  11644. procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); override;
  11645. public
  11646. /// retrieve the SBF compact binary format representation of a record content
  11647. class function ToSBF(const V: Variant): TSBFString;
  11648. /// retrieve the ID value associated to a record content
  11649. class function ToID(const V: Variant): integer;
  11650. /// retrieve the TSynTable instance associated to a record content
  11651. class function ToTable(const V: Variant): TSynTable;
  11652. /// clear the content
  11653. procedure Clear(var V: TVarData); override;
  11654. /// copy two record content
  11655. procedure Copy(var Dest: TVarData; const Source: TVarData;
  11656. const Indirect: Boolean); override;
  11657. end;
  11658. /// class-reference type (metaclass) of custom variant type definition
  11659. // - used by SynRegisterCustomVariantType() function
  11660. TSynInvokeableVariantTypeClass = class of TSynInvokeableVariantType;
  11661. /// register a custom variant type to handle properties
  11662. // - this will implement an internal mechanism used to bypass the default
  11663. // _DispInvoke() implementation in Variant.pas, to use a faster version
  11664. // - is called in case of TSynTableVariant, TDocVariant, TBSONVariant or
  11665. // TSQLDBRowVariant
  11666. function SynRegisterCustomVariantType(aClass: TSynInvokeableVariantTypeClass): TSynInvokeableVariantType;
  11667. type
  11668. /// possible options for a TDocVariant JSON/BSON document storage
  11669. // - dvoNameCaseSensitive will be used for every name lookup - here
  11670. // case-insensitivity is restricted to a-z A-Z 0-9 and _ characters
  11671. // - dvoCheckForDuplicatedNames will be used for method
  11672. // TDocVariantData.AddValue(), but not when setting properties at
  11673. // variant level: for consistency, "aVariant.AB := aValue" will replace
  11674. // any previous value for the name "AB"
  11675. // - dvoReturnNullForUnknownProperty will be used when retrieving any value
  11676. // from its name (for dvObject kind of instance)
  11677. // - dvoReturnNullForOutOfRangeIndex will be used when retrieving any value
  11678. // from its index (for dvArray or dvObject kind of instance)
  11679. // - by default, internal values will be copied by-value from one variant
  11680. // instance to another, to ensure proper safety - but it may be too slow:
  11681. // if you set dvoValueCopiedByReference, the internal
  11682. // TDocVariantData.VValue/VName instances will be copied by-reference,
  11683. // to avoid memory allocations, BUT it may break internal process if you change
  11684. // some values in place (since VValue/VName and VCount won't match) - as such,
  11685. // if you set this option, ensure that you use the content as read-only
  11686. // - any registered custom types may have an extended JSON syntax (e.g.
  11687. // TBSONVariant does for MongoDB types), and will be searched during JSON
  11688. // parsing, unless dvoJSONParseDoNotTryCustomVariants is set (slightly faster)
  11689. // - by default, it will only handle direct JSON [array] of {object}: but if
  11690. // you define dvoJSONObjectParseWithinString, it will also try to un-escape
  11691. // a JSON string first, i.e. handle "[array]" or "{object}" content (may be
  11692. // used e.g. when JSON has been retrieved from a database TEXT column) - is
  11693. // used for instance by VariantLoadJSON()
  11694. // - JSON serialization will follow the standard layout, unless
  11695. // dvoSerializeAsExtendedJson is set so that the property names would not
  11696. // be escaped with double quotes, writing '{name:"John",age:123}' instead of
  11697. // '{"name":"John","age":123}': this extended json layout is compatible with
  11698. // http://docs.mongodb.org/manual/reference/mongodb-extended-json and with
  11699. // TDocVariant JSON unserialization, also our SynCrossPlatformJSON unit, but
  11700. // NOT recognized by most JSON clients, like AJAX/JavaScript or C#/Java
  11701. TDocVariantOption =
  11702. (dvoNameCaseSensitive, dvoCheckForDuplicatedNames,
  11703. dvoReturnNullForUnknownProperty, dvoReturnNullForOutOfRangeIndex,
  11704. dvoValueCopiedByReference, dvoJSONParseDoNotTryCustomVariants,
  11705. dvoJSONObjectParseWithinString, dvoSerializeAsExtendedJson);
  11706. /// set of options for a TDocVariant storage
  11707. // - you can use JSON_OPTIONS[true] if you want to create a fast by-reference
  11708. // local document as with _ObjFast/_ArrFast/_JsonFast - i.e.
  11709. // [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]
  11710. TDocVariantOptions = set of TDocVariantOption;
  11711. /// pointer to a set of options for a TDocVariant storage
  11712. PDocVariantOptions = ^TDocVariantOptions;
  11713. const
  11714. /// some convenient TDocVariant options
  11715. // - JSON_OPTIONS[false] is e.g. _Json() and _JsonFmt() functions default
  11716. // - JSON_OPTIONS[true] are used e.g. by _JsonFast() and _JsonFastFmt() functions
  11717. JSON_OPTIONS: array[Boolean] of TDocVariantOptions = (
  11718. [dvoReturnNullForUnknownProperty],
  11719. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]);
  11720. /// same as JSON_OPTIONS[true], but can not be used as PDocVariantOptions
  11721. JSON_OPTIONS_FAST =
  11722. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference];
  11723. /// TDocVariant options which may be used for plain JSON parsing
  11724. // - this won't recognize any extended syntax
  11725. JSON_OPTIONS_FAST_STRICTJSON: TDocVariantOptions =
  11726. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
  11727. dvoJSONParseDoNotTryCustomVariants];
  11728. /// TDocVariant options to be used for case-sensitive TSynNameValue-like
  11729. // storage, with optional extended JSON syntax serialization
  11730. JSON_OPTIONS_NAMEVALUE: array[boolean] of TDocVariantOptions = (
  11731. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
  11732. dvoNameCaseSensitive],
  11733. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
  11734. dvoNameCaseSensitive,dvoSerializeAsExtendedJson]);
  11735. /// TDocVariant options to be used so that JSON serialization would
  11736. // use the unquoted JSON syntax for field names
  11737. // - you could use it e.g. on a TSQLRecord variant published field to
  11738. // reduce the JSON escape process during storage in the database, by
  11739. // customizing your TSQLModel instance:
  11740. // ! (aModel.Props[TSQLMyRecord]['VariantProp'] as TSQLPropInfoRTTIVariant).
  11741. // ! DocVariantOptions := JSON_OPTIONS_FAST_EXTENDED;
  11742. // or - in a cleaner way - by overriding TSQLRecord.InternalDefineModel():
  11743. // ! class procedure TSQLMyRecord.InternalDefineModel(Props: TSQLRecordProperties);
  11744. // ! begin
  11745. // ! (Props.Fields.ByName('VariantProp') as TSQLPropInfoRTTIVariant).
  11746. // ! DocVariantOptions := JSON_OPTIONS_FAST_EXTENDED;
  11747. // ! end;
  11748. // or to set all variant fields at once:
  11749. // ! class procedure TSQLMyRecord.InternalDefineModel(Props: TSQLRecordProperties);
  11750. // ! begin
  11751. // ! Props.SetVariantFieldsDocVariantOptions(JSON_OPTIONS_FAST_EXTENDED);
  11752. // ! end;
  11753. // - consider using JSON_OPTIONS_NAMEVALUE[true] for TSynNameValue-like storage
  11754. JSON_OPTIONS_FAST_EXTENDED: TDocVariantOptions =
  11755. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,
  11756. dvoSerializeAsExtendedJson];
  11757. /// same as Dest := Source, but copying by reference
  11758. // - i.e. VType is defined as varVariant or varByRef
  11759. // - for instance, it will be used for late binding of TDocVariant properties,
  11760. // to let following statements work as expected:
  11761. // ! V := _Json('{arr:[1,2]}');
  11762. // ! V.arr.Add(3); // will work, since V.arr will be returned by reference
  11763. // ! writeln(V); // will write '{"arr":[1,2,3]}'
  11764. procedure SetVariantByRef(const Source: Variant; var Dest: Variant);
  11765. /// same as Dest := Source, but copying by value
  11766. // - will unreference any varByRef content
  11767. // - will convert any string value into RawUTF8 (varString) for consistency
  11768. procedure SetVariantByValue(const Source: Variant; var Dest: Variant);
  11769. /// same as FillChar(Value^,sizeof(TVarData),0)
  11770. // - so can be used for TVarData or Variant
  11771. // - it will set V.VType := varEmpty, so Value will be Unassigned
  11772. // - it won't call VarClear(variant(Value)): it should have been cleaned before
  11773. procedure ZeroFill(Value: PVarData);
  11774. {$ifdef HASINLINE}inline;{$endif}
  11775. /// retrieve a variant value from variable-length buffer
  11776. // - matches TFileBufferWriter.Write()
  11777. // - how custom type variants are created can be defined via CustomVariantOptions
  11778. // - is just a wrapper around VariantLoad()
  11779. procedure FromVarVariant(var Source: PByte; var Value: variant;
  11780. CustomVariantOptions: PDocVariantOptions=nil);
  11781. {$ifdef HASINLINE}inline;{$endif}
  11782. /// compute the number of bytes needed to save a Variant content
  11783. // using the VariantSave() function
  11784. // - will return 0 in case of an invalid (not handled) Variant type
  11785. function VariantSaveLength(const Value: variant): integer;
  11786. /// save a Variant content into a destination memory buffer
  11787. // - Dest must be at least VariantSaveLength() bytes long
  11788. // - will handle standard Variant types and custom types (serialized as JSON)
  11789. // - will return nil in case of an invalid (not handled) Variant type
  11790. // - will use a proprietary binary format, with some variable-length encoding
  11791. // of the string length (i.e. the RecordLoad/RecordSave layout)
  11792. // - warning: will encode generic string fields as within the variant type
  11793. // itself: using this function between UNICODE and NOT UNICODE
  11794. // versions of Delphi, will propably fail - you have been warned!
  11795. function VariantSave(const Value: variant; Dest: PAnsiChar): PAnsiChar; overload;
  11796. /// save a Variant content into a binary buffer
  11797. // - will handle standard Variant types and custom types (serialized as JSON)
  11798. // - will return '' in case of an invalid (not handled) Variant type
  11799. // - just a wrapper around VariantSaveLength()+VariantSave()
  11800. // - warning: will encode generic string fields as within the variant type
  11801. // itself: using this function between UNICODE and NOT UNICODE
  11802. // versions of Delphi, will propably fail - you have been warned!
  11803. function VariantSave(const Value: variant): RawByteString; overload;
  11804. /// retrieve a variant value from our optimized binary serialization format
  11805. // - follow the data layout as used by RecordLoad() or VariantSave() function
  11806. // - return nil if the Source buffer is incorrect
  11807. // - in case of success, return the memory buffer pointer just after the
  11808. // read content
  11809. // - how custom type variants are created can be defined via CustomVariantOptions
  11810. function VariantLoad(var Value: variant; Source: PAnsiChar;
  11811. CustomVariantOptions: PDocVariantOptions): PAnsiChar; overload;
  11812. /// retrieve a variant value from our optimized binary serialization format
  11813. // - follow the data layout as used by RecordLoad() or VariantSave() function
  11814. // - return varEmpty if the Source buffer is incorrect
  11815. // - just a wrapper around VariantLoad()
  11816. // - how custom type variants are created can be defined via CustomVariantOptions
  11817. function VariantLoad(const Bin: RawByteString;
  11818. CustomVariantOptions: PDocVariantOptions): variant; overload;
  11819. /// retrieve a variant value from a JSON number or string
  11820. // - follows TTextWriter.AddVariant() format (calls GetVariantFromJSON)
  11821. // - will instantiate either an Integer, Int64, currency, double or string value
  11822. // (as RawUTF8), guessing the best numeric type according to the textual content,
  11823. // and string in all other cases, except TryCustomVariants points to some options
  11824. // (e.g. @JSON_OPTIONS[true] for fast instance) and input is a known object or
  11825. // array, either encoded as strict-JSON (i.e. {..} or [..]), or with some
  11826. // extended (e.g. BSON) syntax
  11827. // - warning: the JSON buffer will be modified in-place during process - use
  11828. // a temporary copy or the overloaded functions with RawUTF8 parameter
  11829. // if you need to access it later
  11830. function VariantLoadJSON(var Value: variant; JSON: PUTF8Char;
  11831. EndOfObject: PUTF8Char=nil; TryCustomVariants: PDocVariantOptions=nil): PUTF8Char; overload;
  11832. /// retrieve a variant value from a JSON number or string
  11833. // - follows TTextWriter.AddVariant() format (calls GetVariantFromJSON)
  11834. // - will instantiate either an Integer, Int64, currency, double or string value
  11835. // (as RawUTF8), guessing the best numeric type according to the textual content,
  11836. // and string in all other cases, except TryCustomVariants points to some options
  11837. // (e.g. @JSON_OPTIONS[true] for fast instance) and input is a known object or
  11838. // array, either encoded as strict-JSON (i.e. {..} or [..]), or with some
  11839. // extended (e.g. BSON) syntax
  11840. // - this overloaded procedure will make a temporary copy before JSON parsing
  11841. // and return the variant as result
  11842. procedure VariantLoadJSON(var Value: Variant; const JSON: RawUTF8;
  11843. TryCustomVariants: PDocVariantOptions=nil); overload;
  11844. /// retrieve a variant value from a JSON number or string
  11845. // - follows TTextWriter.AddVariant() format (calls GetVariantFromJSON)
  11846. // - will instantiate either an Integer, Int64, currency, double or string value
  11847. // (as RawUTF8), guessing the best numeric type according to the textual content,
  11848. // and string in all other cases, except TryCustomVariants points to some options
  11849. // (e.g. @JSON_OPTIONS[true] for fast instance) and input is a known object or
  11850. // array, either encoded as strict-JSON (i.e. {..} or [..]), or with some
  11851. // extended (e.g. BSON) syntax
  11852. // - this overloaded procedure will make a temporary copy before JSON parsing
  11853. // and return the variant as result
  11854. function VariantLoadJSON(const JSON: RawUTF8;
  11855. TryCustomVariants: PDocVariantOptions=nil): variant; overload;
  11856. /// save a variant value into a JSON content
  11857. // - follows the TTextWriter.AddVariant() and VariantLoadJSON() format
  11858. // - is able to handle simple and custom variant types, for instance:
  11859. // ! VariantSaveJSON(1.5)='1.5'
  11860. // ! VariantSaveJSON('test')='"test"'
  11861. // ! o := _Json('{ BSON: [ "test", 5.05, 1986 ] }');
  11862. // ! VariantSaveJSON(o)='{"BSON":["test",5.05,1986]}'
  11863. // ! o := _Obj(['name','John','doc',_Obj(['one',1,'two',_Arr(['one',2])])]);
  11864. // ! VariantSaveJSON(o)='{"name":"John","doc":{"one":1,"two":["one",2]}}'
  11865. // - note that before Delphi 2009, any varString value is expected to be
  11866. // a RawUTF8 instance - which does make sense in the mORMot area
  11867. function VariantSaveJSON(const Value: variant; Escape: TTextWriterKind=twJSONEscape): RawUTF8; overload;
  11868. /// save a variant value into a JSON content
  11869. // - follows the TTextWriter.AddVariant() and VariantLoadJSON() format
  11870. // - is able to handle simple and custom variant types, for instance:
  11871. // ! VariantSaveJSON(1.5)='1.5'
  11872. // ! VariantSaveJSON('test')='"test"'
  11873. // ! o := _Json('{BSON: ["test", 5.05, 1986]}');
  11874. // ! VariantSaveJSON(o)='{"BSON":["test",5.05,1986]}'
  11875. // ! o := _Obj(['name','John','doc',_Obj(['one',1,'two',_Arr(['one',2])])]);
  11876. // ! VariantSaveJSON(o)='{"name":"John","doc":{"one":1,"two":["one",2]}}'
  11877. // - note that before Delphi 2009, any varString value is expected to be
  11878. // a RawUTF8 instance - which does make sense in the mORMot area
  11879. procedure VariantSaveJSON(const Value: variant; Escape: TTextWriterKind;
  11880. var result: RawUTF8); overload;
  11881. /// compute the number of chars needed to save a variant value into a JSON content
  11882. // - follows the TTextWriter.AddVariant() and VariantLoadJSON() format
  11883. // - this will be much faster than length(VariantSaveJSON()) for huge content
  11884. // - note that before Delphi 2009, any varString value is expected to be
  11885. // a RawUTF8 instance - which does make sense in the mORMot area
  11886. function VariantSaveJSONLength(const Value: variant; Escape: TTextWriterKind=twJSONEscape): integer;
  11887. /// low-level function to set a variant from an unescaped JSON number or string
  11888. // - expect the JSON input buffer to be already unescaped, e.g. by GetJSONField()
  11889. // - is called e.g. by function VariantLoadJSON()
  11890. // - will instantiate either an Integer, Int64, currency, double or string value
  11891. // (as RawUTF8), guessing the best numeric type according to the textual content,
  11892. // and string in all other cases, except TryCustomVariants points to some options
  11893. // (e.g. @JSON_OPTIONS[true] for fast instance) and input is a known object or
  11894. // array, either encoded as strict-JSON (i.e. {..} or [..]), or with some
  11895. // extended (e.g. BSON) syntax
  11896. procedure GetVariantFromJSON(JSON: PUTF8Char; wasString: Boolean; var Value: variant;
  11897. TryCustomVariants: PDocVariantOptions=nil);
  11898. /// identify either varInt64, varDouble, varCurrency types following JSON format
  11899. // - any non valid number is returned as varString
  11900. // - is used e.g. by GetVariantFromJSON() to guess the destination variant type
  11901. // - warning: supplied JSON is expected to be not nil
  11902. function TextToVariantNumberType(JSON: PUTF8Char): word;
  11903. /// low-level function to set a numerical variant from an unescaped JSON number
  11904. // - returns TRUE if TextToVariantNumberType(JSON) identified it as a number,
  11905. // and set Value to the corresponding content
  11906. // - returns FALSE if JSON is a string, or null/true/false
  11907. function GetNumericVariantFromJSON(JSON: PUTF8Char; var Value: TVarData): boolean;
  11908. /// convert an UTF-8 encoded text buffer into a variant RawUTF8 varString
  11909. procedure RawUTF8ToVariant(Txt: PUTF8Char; TxtLen: integer; var Value: variant); overload;
  11910. /// convert an UTF-8 encoded string into a variant RawUTF8 varString
  11911. procedure RawUTF8ToVariant(const Txt: RawUTF8; var Value: variant); overload;
  11912. /// convert an UTF-8 encoded string into a variant RawUTF8 varString
  11913. function RawUTF8ToVariant(const Txt: RawUTF8): variant; overload;
  11914. /// convert an UTF-8 encoded text buffer into a variant RawUTF8 varString
  11915. // - this overloaded version expects a destination variant type (e.g. varString
  11916. // varOleStr / varUString) - if the type is not handled, will raise an
  11917. // EVariantTypeCastError
  11918. procedure RawUTF8ToVariant(const Txt: RawUTF8; var Value: TVarData;
  11919. ExpectedValueType: word); overload;
  11920. /// convert an open array (const Args: array of const) argument to a variant
  11921. // - note that cardinal values should be type-casted to Int64() (otherwise
  11922. // the integer mapped value will be transmitted, therefore wrongly)
  11923. procedure VarRecToVariant(const V: TVarRec; var result: variant); overload;
  11924. /// convert an open array (const Args: array of const) argument to a variant
  11925. // - note that cardinal values should be type-casted to Int64() (otherwise
  11926. // the integer mapped value will be transmitted, therefore wrongly)
  11927. function VarRecToVariant(const V: TVarRec): variant; overload;
  11928. {$ifdef HASINLINE}inline;{$endif}
  11929. /// convert a variant to an open array (const Args: array of const) argument
  11930. // - will always map to a vtVariant kind of argument
  11931. procedure VariantToVarRec(const V: variant; var result: TVarRec);
  11932. {$ifdef HASINLINE}inline;{$endif}
  11933. /// convert any Variant into a database value
  11934. // - ftBlob kind won't be handled by this function
  11935. // - complex variant types would be converted into ftUTF8 JSON object/array
  11936. procedure VariantToSQLVar(const Input: variant; var temp: RawByteString;
  11937. var Output: TSQLVar);
  11938. /// convert a dynamic array of variants into its JSON serialization
  11939. // - will use a TDocVariantData temporary storage
  11940. function VariantDynArrayToJSON(const V: TVariantDynArray): RawUTF8;
  11941. /// convert a JSON array into a dynamic array of variants
  11942. // - will use a TDocVariantData temporary storage
  11943. function JSONToVariantDynArray(const JSON: RawUTF8): TVariantDynArray;
  11944. /// convert an open array list into a dynamic array of variants
  11945. // - will use a TDocVariantData temporary storage
  11946. function ValuesToVariantDynArray(const items: array of const): TVariantDynArray;
  11947. /// guess the correct TSQLDBFieldType from a variant value
  11948. function VariantTypeToSQLDBFieldType(const V: Variant): TSQLDBFieldType;
  11949. var
  11950. /// the internal custom variant type used to register TDocVariant
  11951. DocVariantType: TSynInvokeableVariantType = nil;
  11952. /// copy of DocVariantType.VarType
  11953. // - as used by inlined functions of TDocVariantData
  11954. DocVariantVType: integer;
  11955. type
  11956. /// pointer to a TDocVariant storage
  11957. // - since variants may be stored by reference (i.e. as varByRef), it may
  11958. // be a good idea to use such a pointer via DocVariantData(aVariant)^ or
  11959. // _Safe(aVariant)^ instead of TDocVariantData(aVariant),
  11960. // if you are not sure how aVariant was allocated (may be not _Obj/_Json)
  11961. PDocVariantData = ^TDocVariantData;
  11962. /// a custom variant type used to store any JSON/BSON document-based content
  11963. // - i.e. name/value pairs for objects, or an array of values (including
  11964. // nested documents), stored in a TDocVariantData memory structure
  11965. // - you can use _Obj()/_ObjFast() _Arr()/_ArrFast() _Json()/_JsonFast() or
  11966. // _JsonFmt()/_JsonFastFmt() functions to create instances of such variants
  11967. // - property access may be done via late-binding - with some restrictions
  11968. // for older versions of FPC, e.g. allowing to write:
  11969. // ! TDocVariant.NewFast(aVariant);
  11970. // ! aVariant.Name := 'John';
  11971. // ! aVariant.Age := 35;
  11972. // ! writeln(aVariant.Name,' is ',aVariant.Age,' years old');
  11973. // - it also supports a small set of pseudo-properties or pseudo-methods:
  11974. // ! aVariant._Count = DocVariantData(aVariant).Count
  11975. // ! aVariant._Kind = ord(DocVariantData(aVariant).Kind)
  11976. // ! aVariant._JSON = DocVariantData(aVariant).JSON
  11977. // ! aVariant._(i) = DocVariantData(aVariant).Value[i]
  11978. // ! aVariant.Value(i) = DocVariantData(aVariant).Value[i]
  11979. // ! aVariant.Value(aName) = DocVariantData(aVariant).Value[aName]
  11980. // ! aVariant.Name(i) = DocVariantData(aVariant).Name[i]
  11981. // ! aVariant.Add(aItem) = DocVariantData(aVariant).AddItem(aItem)
  11982. // ! aVariant._ := aItem = DocVariantData(aVariant).AddItem(aItem)
  11983. // ! aVariant.Add(aName,aValue) = DocVariantData(aVariant).AddValue(aName,aValue)
  11984. // ! aVariant.Exists(aName) = DocVariantData(aVariant).GetValueIndex(aName)>=0
  11985. // ! aVariant.Delete(i) = DocVariantData(aVariant).Delete(i)
  11986. // ! aVariant.Delete(aName) = DocVariantData(aVariant).Delete(aName)
  11987. // ! aVariant.NameIndex(aName) = DocVariantData(aVariant).GetValueIndex(aName)
  11988. // - it features direct JSON serialization/unserialization, e.g.:
  11989. // ! assert(_Json('["one",2,3]')._JSON='["one",2,3]');
  11990. // - it features direct trans-typing into a string encoded as JSON, e.g.:
  11991. // ! assert(_Json('["one",2,3]')='["one",2,3]');
  11992. TDocVariant = class(TSynInvokeableVariantType)
  11993. protected
  11994. /// fast getter/setter implementation
  11995. procedure IntGet(var Dest: TVarData; const V: TVarData; Name: PAnsiChar); override;
  11996. procedure IntSet(const V, Value: TVarData; Name: PAnsiChar); override;
  11997. public
  11998. /// initialize a variant instance to store some document-based content
  11999. // - by default, every internal value will be copied, so access of nested
  12000. // properties can be slow - if you expect the data to be read-only or not
  12001. // propagated into another place, set aOptions=[dvoValueCopiedByReference]
  12002. // will increase the process speed a lot
  12003. class procedure New(out aValue: variant;
  12004. aOptions: TDocVariantOptions=[]); overload;
  12005. {$ifdef HASINLINE}inline;{$endif}
  12006. /// initialize a variant instance to store per-reference document-based content
  12007. // - same as New(aValue,JSON_OPTIONS[true]);
  12008. // - to be used e.g. as
  12009. // !var v: variant;
  12010. // !begin
  12011. // ! TDocVariant.NewFast(v);
  12012. // ! ...
  12013. class procedure NewFast(out aValue: variant); overload;
  12014. {$ifdef HASINLINE}inline;{$endif}
  12015. /// ensure a variant is a TDocVariant instance
  12016. // - if aValue is not a TDocVariant, will create a new JSON_OPTIONS[true]
  12017. class procedure IsOfTypeOrNewFast(var aValue: variant);
  12018. /// initialize several variant instances to store document-based content
  12019. // - replace several calls to TDocVariantData.InitFast
  12020. // - to be used e.g. as
  12021. // !var v1,v2,v3: TDocVariantData;
  12022. // !begin
  12023. // ! TDocVariant.NewFast([@v1,@v2,@v3]);
  12024. // ! ...
  12025. class procedure NewFast(const aValues: array of PDocVariantData); overload;
  12026. /// initialize a variant instance to store some document-based content
  12027. // - you can use this function to create a variant, which can be nested into
  12028. // another document, e.g.:
  12029. // ! aVariant := TDocVariant.New;
  12030. // ! aVariant.id := 10;
  12031. // - by default, every internal value will be copied, so access of nested
  12032. // properties can be slow - if you expect the data to be read-only or not
  12033. // propagated into another place, set Options=[dvoValueCopiedByReference]
  12034. // will increase the process speed a lot
  12035. // - in practice, you should better use _Obj()/_ObjFast() _Arr()/_ArrFast()
  12036. // functions or TDocVariant.NewFast()
  12037. class function New(Options: TDocVariantOptions=[]): variant; overload;
  12038. {$ifdef HASINLINE}inline;{$endif}
  12039. /// initialize a variant instance to store some document-based object content
  12040. // - object will be initialized with data supplied two by two, as Name,Value
  12041. // pairs, e.g.
  12042. // ! aVariant := TDocVariant.NewObject(['name','John','year',1972]);
  12043. // which is the same as:
  12044. // ! TDocVariant.New(aVariant);
  12045. // ! TDocVariantData(aVariant).AddValue('name','John');
  12046. // ! TDocVariantData(aVariant).AddValue('year',1972);
  12047. // - by default, every internal value will be copied, so access of nested
  12048. // properties can be slow - if you expect the data to be read-only or not
  12049. // propagated into another place, set Options=[dvoValueCopiedByReference]
  12050. // will increase the process speed a lot
  12051. // - in practice, you should better use the function _Obj() which is a
  12052. // wrapper around this class method
  12053. class function NewObject(const NameValuePairs: array of const;
  12054. Options: TDocVariantOptions=[]): variant;
  12055. /// initialize a variant instance to store some document-based array content
  12056. // - array will be initialized with data supplied as parameters, e.g.
  12057. // ! aVariant := TDocVariant.NewArray(['one',2,3.0]);
  12058. // which is the same as:
  12059. // ! TDocVariant.New(aVariant);
  12060. // ! TDocVariantData(aVariant).AddItem('one');
  12061. // ! TDocVariantData(aVariant).AddItem(2);
  12062. // ! TDocVariantData(aVariant).AddItem(3.0);
  12063. // - by default, every internal value will be copied, so access of nested
  12064. // properties can be slow - if you expect the data to be read-only or not
  12065. // propagated into another place, set aOptions=[dvoValueCopiedByReference]
  12066. // will increase the process speed a lot
  12067. // - in practice, you should better use the function _Arr() which is a
  12068. // wrapper around this class method
  12069. class function NewArray(const Items: array of const;
  12070. Options: TDocVariantOptions=[]): variant; overload;
  12071. /// initialize a variant instance to store some document-based array content
  12072. // - array will be initialized with data supplied dynamic array of variants
  12073. class function NewArray(const Items: TVariantDynArray;
  12074. Options: TDocVariantOptions=[]): variant; overload;
  12075. /// initialize a variant instance to store some document-based object content
  12076. // from a supplied (extended) JSON content
  12077. // - in addition to the JSON RFC specification strict mode, this method will
  12078. // handle some BSON-like extensions, e.g. unquoted field names
  12079. // - a private copy of the incoming JSON buffer will be used, then
  12080. // it will call the TDocVariantData.InitJSONInPlace() method
  12081. // - to be used e.g. as:
  12082. // ! var V: variant;
  12083. // ! begin
  12084. // ! V := TDocVariant.NewJSON('{"id":10,"doc":{"name":"John","birthyear":1972}}');
  12085. // ! assert(V.id=10);
  12086. // ! assert(V.doc.name='John');
  12087. // ! assert(V.doc.birthYear=1972);
  12088. // ! // and also some pseudo-properties:
  12089. // ! assert(V._count=2);
  12090. // ! assert(V.doc._kind=ord(dvObject));
  12091. // - or with a JSON array:
  12092. // ! V := TDocVariant.NewJSON('["one",2,3]');
  12093. // ! assert(V._kind=ord(dvArray));
  12094. // ! for i := 0 to V._count-1 do
  12095. // ! writeln(V._(i));
  12096. // - by default, every internal value will be copied, so access of nested
  12097. // properties can be slow - if you expect the data to be read-only or not
  12098. // propagated into another place, add dvoValueCopiedByReference in Options
  12099. // will increase the process speed a lot
  12100. // - in practice, you should better use the function _Json()/_JsonFast()
  12101. // which are handy wrappers around this class method
  12102. class function NewJSON(const JSON: RawUTF8;
  12103. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant;
  12104. {$ifdef HASINLINE}inline;{$endif}
  12105. /// initialize a variant instance to store some document-based object content
  12106. // from a supplied existing TDocVariant instance
  12107. // - for instance, the following:
  12108. // ! oSeasons := TDocVariant.NewUnique(o.Seasons);
  12109. // is the same as:
  12110. // ! oSeasons := o.Seasons;
  12111. // ! _Unique(oSeasons);
  12112. // or even:
  12113. // ! oSeasons := _Copy(o.Seasons);
  12114. class function NewUnique(const SourceDocVariant: variant;
  12115. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant;
  12116. {$ifdef HASINLINE}inline;{$endif}
  12117. /// will return the unique element of a TDocVariant array or a default
  12118. // - if the value is a dvArray with one single item, it will this value
  12119. // - if the value is not a TDocVariant nor a dvArray with one single item,
  12120. // it wil return the default value
  12121. class procedure GetSingleOrDefault(const docVariantArray, default: variant;
  12122. var result: variant);
  12123. // this implementation will write the content as JSON object or array
  12124. procedure ToJSON(W: TTextWriter; const Value: variant; Escape: TTextWriterKind); override;
  12125. /// will check if the value is an array, and return the number of items
  12126. // - if the document is an array, will return the items count (0 meaning
  12127. // void array)
  12128. // - this overridden method will implement it for dvArray instance kind
  12129. function IterateCount(const V: TVarData): integer; override;
  12130. /// allow to loop over an array value
  12131. // - Index should be in 0..IterateCount-1 range
  12132. // - this default implementation will do handle dvArray instance kind
  12133. procedure Iterate(var Dest: TVarData; const V: TVarData; Index: integer); override;
  12134. /// low-level callback to access internal pseudo-methods
  12135. // - mainly the _(Index: integer): variant method to retrieve an item
  12136. // if the document is an array
  12137. function DoFunction(var Dest: TVarData; const V: TVarData;
  12138. const Name: string; const Arguments: TVarDataArray): Boolean; override;
  12139. /// low-level callback to clear the content
  12140. procedure Clear(var V: TVarData); override;
  12141. /// low-level callback to copy two variant content
  12142. // - such copy will by default be done by-value, for safety
  12143. // - if you are sure you will use the variants as read-only, you can set
  12144. // the dvoValueCopiedByReference Option to use faster by-reference copy
  12145. procedure Copy(var Dest: TVarData; const Source: TVarData;
  12146. const Indirect: Boolean); override;
  12147. /// copy two variant content by value
  12148. // - overridden method since instance may use a by-reference copy pattern
  12149. procedure CopyByValue(var Dest: TVarData; const Source: TVarData); override;
  12150. /// handle type conversion
  12151. // - only types processed by now are string/OleStr/UnicodeString/date
  12152. procedure Cast(var Dest: TVarData; const Source: TVarData); override;
  12153. /// handle type conversion
  12154. // - only types processed by now are string/OleStr/UnicodeString/date
  12155. procedure CastTo(var Dest: TVarData; const Source: TVarData;
  12156. const AVarType: TVarType); override;
  12157. /// compare two variant values
  12158. // - it uses case-sensitive text comparison of the JSON representation
  12159. // of each variant (including TDocVariant instances)
  12160. procedure Compare(const Left, Right: TVarData;
  12161. var Relationship: TVarCompareResult); override;
  12162. end;
  12163. /// define the TDocVariant storage layout
  12164. // - if it has one or more named properties, it is a dvObject
  12165. // - if it has no name property, it is a dvArray
  12166. TDocVariantKind = (dvUndefined, dvObject, dvArray);
  12167. /// method used by TDocVariantData.ReduceAsArray to filter each object
  12168. // - should return TRUE if the item match the expectations
  12169. TOnReducePerItem = function(Item: PDocVariantData): boolean of object;
  12170. /// method used by TDocVariantData.ReduceAsArray to filter each object
  12171. // - should return TRUE if the item match the expectations
  12172. TOnReducePerValue = function(const Value: variant): boolean of object;
  12173. {$A-} { packet object not allowed since Delphi 2009 :( }
  12174. /// memory structure used for TDocVariant storage of any JSON/BSON
  12175. // document-based content as variant
  12176. // - i.e. name/value pairs for objects, or an array of values (including
  12177. // nested documents)
  12178. // - you can use _Obj()/_ObjFast() _Arr()/_ArrFast() _Json()/_JsonFast() or
  12179. // _JsonFmt()/_JsonFastFmt() functions to create instances of such variants
  12180. // - you can transtype such an allocated variant into TDocVariantData
  12181. // to access directly its internals (like Count or Values[]/Names[]):
  12182. // ! aVariantObject := TDocVariant.NewObject(['name','John','year',1972]);
  12183. // ! aVariantObject := _ObjFast(['name','John','year',1972]);
  12184. // ! with TDocVariantData(aVariantObject) do
  12185. // ! for i := 0 to Count-1 do
  12186. // ! writeln(Names[i],'=',Values[i]); // for an object
  12187. // ! aVariantArray := TDocVariant.NewArray(['one',2,3.0]);
  12188. // ! aVariantArray := _JsonFast('["one",2,3.0]');
  12189. // ! with TDocVariantData(aVariantArray) do
  12190. // ! for i := 0 to Count-1 do
  12191. // ! writeln(Values[i]); // for an array
  12192. // here, using "with TDocVariantData(...) do" syntax can be very convenient
  12193. // - since variants may be stored by reference (i.e. as varByRef), it may
  12194. // be a good idea to use DocVariantData(aVariant)^ or _Safe(aVariant)^ instead
  12195. // of TDocVariantData(aVariant), if you are not sure how aVariant was allocated
  12196. // (may be not _Obj/_Json, but retrieved as varByRef e.g. from late binding)
  12197. {$ifdef UNICODE}
  12198. TDocVariantData = record
  12199. private
  12200. {$else}
  12201. TDocVariantData = object
  12202. protected
  12203. {$endif}
  12204. VType: TVarType;
  12205. VOptions: TDocVariantOptions;
  12206. VKind: TDocVariantKind;
  12207. (* this structure uses all TVarData available space: no filler needed!
  12208. {$HINTS OFF} // does not complain if Filler is declared but never used
  12209. Filler: array[1..SizeOf(TVarData)-SizeOf(TVarType)-SizeOf(TDocVariantOptions)-
  12210. SizeOf(TDocVariantKind)-SizeOf(TRawUTF8DynArray)-SizeOf(TVariantDynArray)-
  12211. SizeOf(integer)] of byte;
  12212. {$HINTS ON} *)
  12213. VName: TRawUTF8DynArray;
  12214. VValue: TVariantDynArray;
  12215. VCount: integer;
  12216. // retrieve the value as varByRef
  12217. function GetValueOrItem(const aNameOrIndex: variant): variant;
  12218. procedure SetValueOrItem(const aNameOrIndex, aValue: variant);
  12219. procedure SetCapacity(aValue: integer);
  12220. function GetCapacity: integer;
  12221. {$ifdef HASINLINE}inline;{$endif}
  12222. // implement U[] I[] B[] D[] O[] O_[] A[] A_[] _[] properties
  12223. function GetOrAddValueIndex(const aName: RawUTF8): integer;
  12224. function GetVarDataByName(const aName: RawUTF8): PVariant;
  12225. function GetRawUTF8ByName(const aName: RawUTF8): RawUTF8;
  12226. procedure SetRawUTF8ByName(const aName, aValue: RawUTF8);
  12227. function GetInt64ByName(const aName: RawUTF8): Int64;
  12228. procedure SetInt64ByName(const aName: RawUTF8; const aValue: Int64);
  12229. function GetBooleanByName(const aName: RawUTF8): Boolean;
  12230. procedure SetBooleanByName(const aName: RawUTF8; aValue: Boolean);
  12231. function GetDoubleByName(const aName: RawUTF8): Double;
  12232. procedure SetDoubleByName(const aName: RawUTF8; const aValue: Double);
  12233. function GetDocVariantExistingByName(const aName: RawUTF8;
  12234. aNotMatchingKind: TDocVariantKind): PDocVariantData;
  12235. function GetObjectExistingByName(const aName: RawUTF8): PDocVariantData;
  12236. function GetDocVariantOrAddByName(const aName: RawUTF8;
  12237. aKind: TDocVariantKind): PDocVariantData;
  12238. function GetObjectOrAddByName(const aName: RawUTF8): PDocVariantData;
  12239. function GetArrayExistingByName(const aName: RawUTF8): PDocVariantData;
  12240. function GetArrayOrAddByName(const aName: RawUTF8): PDocVariantData;
  12241. function GetAsDocVariantByIndex(aIndex: integer): PDocVariantData;
  12242. procedure ExchgValues(v1,v2: integer);
  12243. public
  12244. /// initialize a TDocVariantData to store some document-based content
  12245. // - can be used with a stack-allocated TDocVariantData variable:
  12246. // !var Doc: TDocVariantData; // stack-allocated variable
  12247. // !begin
  12248. // ! Doc.Init;
  12249. // ! Doc.AddValue('name','John');
  12250. // ! assert(Doc.Value['name']='John');
  12251. // ! assert(variant(Doc).name='John');
  12252. // !end;
  12253. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12254. procedure Init(aOptions: TDocVariantOptions=[]; aKind: TDocVariantKind=dvUndefined);
  12255. /// initialize a TDocVariantData to store per-reference document-based content
  12256. // - same as Doc.Init(JSON_OPTIONS[true]);
  12257. // - can be used with a stack-allocated TDocVariantData variable:
  12258. // !var Doc: TDocVariantData; // stack-allocated variable
  12259. // !begin
  12260. // ! Doc.InitFast;
  12261. // ! Doc.AddValue('name','John');
  12262. // ! assert(Doc.Value['name']='John');
  12263. // ! assert(variant(Doc).name='John');
  12264. // !end;
  12265. // - see also TDocVariant.NewFast() if you want to initialize several
  12266. // TDocVariantData variable instances at once
  12267. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12268. procedure InitFast; overload;
  12269. /// initialize a TDocVariantData to store per-reference document-based content
  12270. // - this overloaded method allows to specify an estimation of how many
  12271. // properties or items this aKind document would contain
  12272. procedure InitFast(InitialCapacity: integer; aKind: TDocVariantKind); overload;
  12273. /// initialize a TDocVariantData to store document-based object content
  12274. // - object will be initialized with data supplied two by two, as Name,Value
  12275. // pairs, e.g.
  12276. // !var Doc: TDocVariantData; // stack-allocated variable
  12277. // !begin
  12278. // ! Doc.InitObject(['name','John','year',1972]);
  12279. // which is the same as:
  12280. // ! var Doc: TDocVariantData;
  12281. // !begin
  12282. // ! Doc.Init;
  12283. // ! Doc.AddValue('name','John');
  12284. // ! Doc.AddValue('year',1972);
  12285. // - this method is called e.g. by _Obj() and _ObjFast() global functions
  12286. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12287. procedure InitObject(const NameValuePairs: array of const;
  12288. aOptions: TDocVariantOptions=[]);
  12289. /// initialize a variant instance to store some document-based array content
  12290. // - array will be initialized with data supplied as parameters, e.g.
  12291. // !var Doc: TDocVariantData; // stack-allocated variable
  12292. // !begin
  12293. // ! Doc.InitArray(['one',2,3.0]);
  12294. // ! assert(Doc.Count=3);
  12295. // !end;
  12296. // which is the same as:
  12297. // ! var Doc: TDocVariantData;
  12298. // ! i: integer;
  12299. // !begin
  12300. // ! Doc.Init;
  12301. // ! Doc.AddItem('one');
  12302. // ! Doc.AddItem(2);
  12303. // ! Doc.AddItem(3.0);
  12304. // ! assert(Doc.Count=3);
  12305. // ! for i := 0 to Doc.Count-1 do
  12306. // ! writeln(Doc.Value[i]);
  12307. // !end;
  12308. // - this method is called e.g. by _Arr() and _ArrFast() global functions
  12309. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12310. procedure InitArray(const Items: array of const;
  12311. aOptions: TDocVariantOptions=[]);
  12312. /// initialize a variant instance to store some document-based array content
  12313. // - array will be initialized with data supplied as variant dynamic array
  12314. // - if Items is [], the variant will be set as null
  12315. // - will be almost immediate, since TVariantDynArray is reference-counted,
  12316. // unless ItemsCopiedByReference is set to FALSE
  12317. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12318. procedure InitArrayFromVariants(const Items: TVariantDynArray;
  12319. aOptions: TDocVariantOptions=[]; ItemsCopiedByReference: boolean=true);
  12320. /// initialize a variant instance to store some RawUTF8 array content
  12321. procedure InitArrayFrom(const Items: TRawUTF8DynArray; aOptions: TDocVariantOptions); overload;
  12322. /// initialize a variant instance to store some 32-bit integer array content
  12323. procedure InitArrayFrom(const Items: TIntegerDynArray; aOptions: TDocVariantOptions); overload;
  12324. /// initialize a variant instance to store some 64-bit integer array content
  12325. procedure InitArrayFrom(const Items: TInt64DynArray; aOptions: TDocVariantOptions); overload;
  12326. /// initialize a variant instance to store a T*ObjArray content
  12327. // - will call internally ObjectToVariant() to make the conversion
  12328. procedure InitArrayFromObjArray(const ObjArray; aOptions: TDocVariantOptions;
  12329. aWriterOptions: TTextWriterWriteObjectOptions=[woDontStoreDefault]);
  12330. /// initialize a variant instance to store document-based array content
  12331. // - array will be initialized from the supplied variable (which would be
  12332. // e.g. a T*ObjArray or a dynamic array), using RTTI
  12333. // - will use a temporary JSON serialization via SaveJSON()
  12334. procedure InitFromTypeInfo(const aValue; aTypeInfo: pointer;
  12335. aEnumSetsAsText: boolean; aOptions: TDocVariantOptions);
  12336. /// initialize a variant instance to store some document-based object content
  12337. // - object will be initialized with names and values supplied as dynamic arrays
  12338. // - if aNames and aValues are [] or do have matching sizes, the variant
  12339. // will be set as null
  12340. // - will be almost immediate, since Names and Values are reference-counted
  12341. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12342. procedure InitObjectFromVariants(const aNames: TRawUTF8DynArray;
  12343. const aValues: TVariantDynArray; aOptions: TDocVariantOptions=[]);
  12344. /// initialize a variant instance to store a document-based object with a
  12345. // single property
  12346. // - the supplied path could be 'Main.Second.Third', to create nested
  12347. // objects, e.g. {"Main":{"Second":{"Third":value}}}
  12348. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12349. procedure InitObjectFromPath(const aPath: RawUTF8; const aValue: variant;
  12350. aOptions: TDocVariantOptions=[]);
  12351. /// initialize a variant instance to store some document-based object content
  12352. // from a supplied JSON array or JSON object content
  12353. // - warning: the incoming JSON buffer will be modified in-place: so you should
  12354. // make a private copy before running this method, e.g. using TSynTempBuffer
  12355. // - this method is called e.g. by _JsonFmt() _JsonFastFmt() global functions
  12356. // with a temporary JSON buffer content created from a set of parameters
  12357. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12358. function InitJSONInPlace(JSON: PUTF8Char;
  12359. aOptions: TDocVariantOptions=[]; aEndOfObject: PUTF8Char=nil): PUTF8Char;
  12360. /// initialize a variant instance to store some document-based object content
  12361. // from a supplied JSON array of JSON object content
  12362. // - a private copy of the incoming JSON buffer will be used, then
  12363. // it will call the other overloaded InitJSONInPlace() method
  12364. // - this method is called e.g. by _Json() and _JsonFast() global functions
  12365. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12366. function InitJSON(const JSON: RawUTF8; aOptions: TDocVariantOptions=[]): boolean;
  12367. /// initialize a variant instance to store some document-based object content
  12368. // from a JSON array of JSON object content, stored in a file
  12369. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12370. function InitJSONFromFile(const JsonFile: TFileName; aOptions: TDocVariantOptions=[]): boolean;
  12371. /// ensure a document-based variant instance will have one unique options set
  12372. // - this will create a copy of the supplied TDocVariant instance, forcing
  12373. // all nested events to have the same set of Options
  12374. // - you can use this function to ensure that all internal properties of this
  12375. // variant will be copied e.g. per-reference (if you set JSON_OPTIONS[false])
  12376. // or per-value (if you set JSON_OPTIONS[false]) whatever options the nested
  12377. // objects or arrays were created with
  12378. // - will raise an EDocVariant if the supplied variant is not a TDocVariant
  12379. // - you may rather use _Unique() or _UniqueFast() wrappers if you want to
  12380. // ensure that a TDocVariant instance is unique
  12381. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12382. procedure InitCopy(const SourceDocVariant: variant; aOptions: TDocVariantOptions);
  12383. /// initialize a variant instance to store some document-based object content
  12384. // from a supplied CSV UTF-8 encoded text
  12385. // - the supplied content may have been generated by ToTextPairs() method
  12386. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12387. procedure InitCSV(CSV: PUTF8Char; aOptions: TDocVariantOptions;
  12388. NameValueSep: AnsiChar='='; ItemSep: AnsiChar=#10; DoTrim: boolean=true); overload;
  12389. /// initialize a variant instance to store some document-based object content
  12390. // from a supplied CSV UTF-8 encoded text
  12391. // - the supplied content may have been generated by ToTextPairs() method
  12392. // - if you call Init*() methods in a row, ensure you call Clear in-between
  12393. procedure InitCSV(const CSV: RawUTF8; aOptions: TDocVariantOptions;
  12394. NameValueSep: AnsiChar='='; ItemSep: AnsiChar=#10; DoTrim: boolean=true); overload;
  12395. {$ifdef HASINLINE}inline;{$endif}
  12396. /// to be called before any Init*() method call, when a previous Init*()
  12397. // has already be performed on the same instance, to avoid memory leaks
  12398. // - for instance:
  12399. // !var Doc: TDocVariantData; // stack-allocated variable
  12400. // !begin
  12401. // ! Doc.InitArray(['one',2,3.0]); // no need of any Doc.Clear here
  12402. // ! assert(Doc.Count=3);
  12403. // ! Doc.Clear; // to release memory before following InitObject()
  12404. // ! Doc.InitObject(['name','John','year',1972]);
  12405. // !end;
  12406. // - implemented as just a wrapper around DocVariantType.Clear()
  12407. procedure Clear;
  12408. /// delete all internal stored values
  12409. // - like Clear + Init() with the same options
  12410. procedure Reset;
  12411. /// force a number of items
  12412. // - could be used to fast add items to the internal Values[]/Names[] arrays
  12413. // - just set VCount, do not resize the arrays: caller should ensure that
  12414. // Capacity is big enough
  12415. procedure SetCount(aCount: integer);
  12416. /// low-level method called internally
  12417. // - you should not have to use it, unless you want to add some items
  12418. // directly within the Values[]/Names[] arrays, using e.g.
  12419. // InitFast(InitialCapacity) to initialize the document
  12420. function InternalAdd(const aName: RawUTF8): integer;
  12421. /// save a document as UTF-8 encoded JSON
  12422. // - will write either a JSON object or array, depending of the internal
  12423. // layout of this instance (i.e. Kind property value)
  12424. // - will write 'null' if Kind is dvUndefined
  12425. // - implemented as just a wrapper around VariantSaveJSON()
  12426. function ToJSON(const Prefix: RawUTF8=''; const Suffix: RawUTF8='';
  12427. Format: TTextWriterJSONFormat=jsonCompact): RawUTF8;
  12428. /// save an array of objects as UTF-8 encoded non expanded layout JSON
  12429. // - returned content would be a JSON object in mORMot's TSQLTable non
  12430. // expanded format, with reduced JSON size, i.e.
  12431. // $ {"fieldCount":3,"values":["ID","FirstName","LastName",...']}
  12432. // - will write '' if Kind is dvUndefined or dvObject
  12433. // - will raise an exception if the array document is not an array of
  12434. // objects with identical field names
  12435. function ToNonExpandedJSON: RawUTF8;
  12436. /// save a document as an array of UTF-8 encoded JSON
  12437. // - will expect the document to be a dvArray - otherwise, will raise a
  12438. // EDocVariant exception
  12439. // - will use VariantToUTF8() to populate the result array: as a consequence,
  12440. // any nested custom variant types (e.g. TDocVariant) will be stored as JSON
  12441. procedure ToRawUTF8DynArray(out Result: TRawUTF8DynArray); overload;
  12442. /// save a document as an array of UTF-8 encoded JSON
  12443. // - will expect the document to be a dvArray - otherwise, will raise a
  12444. // EDocVariant exception
  12445. // - will use VariantToUTF8() to populate the result array: as a consequence,
  12446. // any nested custom variant types (e.g. TDocVariant) will be stored as JSON
  12447. function ToRawUTF8DynArray: TRawUTF8DynArray; overload;
  12448. {$ifdef HASINLINE}inline;{$endif}
  12449. /// save a document as an CSV of UTF-8 encoded JSON
  12450. // - will expect the document to be a dvArray - otherwise, will raise a
  12451. // EDocVariant exception
  12452. // - will use VariantToUTF8() to populate the result array: as a consequence,
  12453. // any nested custom variant types (e.g. TDocVariant) will be stored as JSON
  12454. function ToCSV(const Separator: RawUTF8=','): RawUTF8;
  12455. /// save a document as UTF-8 encoded Name=Value pairs
  12456. // - will follow by default the .INI format, but you can specify your
  12457. // own expected layout
  12458. procedure ToTextPairsVar(out result: RawUTF8; const NameValueSep: RawUTF8='=';
  12459. const ItemSep: RawUTF8=#13#10; Escape: TTextWriterKind=twJSONEscape);
  12460. /// save a document as UTF-8 encoded Name=Value pairs
  12461. // - will follow by default the .INI format, but you can specify your
  12462. // own expected layout
  12463. function ToTextPairs(const NameValueSep: RawUTF8='=';
  12464. const ItemSep: RawUTF8=#13#10; Escape: TTextWriterKind=twJSONEscape): RawUTF8;
  12465. {$ifdef HASINLINE}inline;{$endif}
  12466. /// save an array document as an array of TVarRec, i.e. an array of const
  12467. // - will expect the document to be a dvArray - otherwise, will raise a
  12468. // EDocVariant exception
  12469. // - would allow to write code as such:
  12470. // ! Doc.InitArray(['one',2,3]);
  12471. // ! Doc.ToArrayOfConst(vr);
  12472. // ! s := FormatUTF8('[%,%,%]',vr,[],true);
  12473. // ! // here s='[one,2,3]') since % would be replaced by Args[] parameters
  12474. // ! s := FormatUTF8('[?,?,?]',[],vr,true);
  12475. // ! // here s='["one",2,3]') since ? would be escaped by Params[] parameters
  12476. procedure ToArrayOfConst(out Result: TTVarRecDynArray); overload;
  12477. /// save an array document as an array of TVarRec, i.e. an array of const
  12478. // - will expect the document to be a dvArray - otherwise, will raise a
  12479. // EDocVariant exception
  12480. // - would allow to write code as such:
  12481. // ! Doc.InitArray(['one',2,3]);
  12482. // ! s := FormatUTF8('[%,%,%]',Doc.ToArrayOfConst,[],true);
  12483. // ! // here s='[one,2,3]') since % would be replaced by Args[] parameters
  12484. // ! s := FormatUTF8('[?,?,?]',[],Doc.ToArrayOfConst,true);
  12485. // ! // here s='["one",2,3]') since ? would be escaped by Params[] parameters
  12486. function ToArrayOfConst: TTVarRecDynArray; overload;
  12487. {$ifdef HASINLINE}inline;{$endif}
  12488. /// save an object document as an URI-encoded list of parameters
  12489. // - object field names should be plain ASCII-7 RFC compatible identifiers
  12490. // (0..9a..zA..Z_.~), otherwise their values are skipped
  12491. function ToUrlEncode(const UriRoot: RawUTF8): RawUTF8;
  12492. /// find an item index in this document from its name
  12493. // - search will follow dvoNameCaseSensitive option of this document
  12494. // - returns -1 if not found
  12495. function GetValueIndex(const aName: RawUTF8): integer; overload;
  12496. {$ifdef HASINLINE}inline;{$endif}
  12497. /// find an item index in this document from its name
  12498. // - returns -1 if not found
  12499. function GetValueIndex(aName: PUTF8Char; aNameLen: integer; aCaseSensitive: boolean): integer; overload;
  12500. /// find an item in this document, and returns its value
  12501. // - raise an EDocVariant if not found and dvoReturnNullForUnknownProperty
  12502. // is not set in Options (in this case, it will return Null)
  12503. function GetValueOrRaiseException(const aName: RawUTF8): variant;
  12504. /// find an item in this document, and returns its value
  12505. // - return the supplied default if aName is not found, or if the instance
  12506. // is not a TDocVariant
  12507. function GetValueOrDefault(const aName: RawUTF8; const aDefault: variant): variant;
  12508. /// find an item in this document, and returns its value
  12509. // - return null if aName is not found, or if the instance is not a TDocVariant
  12510. function GetValueOrNull(const aName: RawUTF8): variant;
  12511. /// find an item in this document, and returns its value
  12512. // - return a cleared variant if aName is not found, or if the instance is
  12513. // not a TDocVariant
  12514. function GetValueOrEmpty(const aName: RawUTF8): variant;
  12515. /// returns a TDocVariant object containing all properties matching the
  12516. // first characters of the supplied property name
  12517. // - returns null if the document is not a dvObject
  12518. // - will use IdemPChar(), so search would be case-insensitive
  12519. function GetValuesByStartName(const aStartName: RawUTF8;
  12520. TrimLeftStartName: boolean=false): variant;
  12521. /// returns a JSON object containing all properties matching the
  12522. // first characters of the supplied property name
  12523. // - returns null if the document is not a dvObject
  12524. // - will use IdemPChar(), so search would be case-insensitive
  12525. function GetJsonByStartName(const aStartName: RawUTF8): RawUTF8;
  12526. /// find an item in this document, and returns its value as TVarData
  12527. // - return false if aName is not found, or if the instance is not a TDocVariant
  12528. // - return true if the name has been found, and aValue stores the value
  12529. // - will use simple loop lookup to identify the name, unless aSortedCompare
  12530. // is set, and would let use a faster binary search after a SortByName()
  12531. function GetVarData(const aName: RawUTF8; var aValue: TVarData;
  12532. aSortedCompare: TUTF8Compare=nil): boolean; overload;
  12533. {$ifdef HASINLINE}inline;{$endif}
  12534. /// find an item in this document, and returns its value as TVarData pointer
  12535. // - return nil if aName is not found, or if the instance is not a TDocVariant
  12536. // - return a pointer to the value if the name has been found
  12537. // - after a SortByName(aSortedCompare), would use faster binary search
  12538. function GetVarData(const aName: RawUTF8;
  12539. aSortedCompare: TUTF8Compare=nil): PVarData; overload;
  12540. /// find an item in this document, and returns its value as boolean
  12541. // - return false if aName is not found, or if the instance is not a TDocVariant
  12542. // - return true if the name has been found, and aValue stores the value
  12543. // - after a SortByName(aSortedCompare), would use faster binary search
  12544. // - consider using B[] property if you want simple read/write typed access
  12545. function GetAsBoolean(const aName: RawUTF8; out aValue: boolean;
  12546. aSortedCompare: TUTF8Compare=nil): Boolean;
  12547. /// find an item in this document, and returns its value as integer
  12548. // - return false if aName is not found, or if the instance is not a TDocVariant
  12549. // - return true if the name has been found, and aValue stores the value
  12550. // - after a SortByName(aSortedCompare), would use faster binary search
  12551. // - consider using I[] property if you want simple read/write typed access
  12552. function GetAsInteger(const aName: RawUTF8; out aValue: integer;
  12553. aSortedCompare: TUTF8Compare=nil): Boolean;
  12554. /// find an item in this document, and returns its value as integer
  12555. // - return false if aName is not found, or if the instance is not a TDocVariant
  12556. // - return true if the name has been found, and aValue stores the value
  12557. // - after a SortByName(aSortedCompare), would use faster binary search
  12558. // - consider using I[] property if you want simple read/write typed access
  12559. function GetAsInt64(const aName: RawUTF8; out aValue: Int64;
  12560. aSortedCompare: TUTF8Compare=nil): Boolean;
  12561. /// find an item in this document, and returns its value as floating point
  12562. // - return false if aName is not found, or if the instance is not a TDocVariant
  12563. // - return true if the name has been found, and aValue stores the value
  12564. // - after a SortByName(aSortedCompare), would use faster binary search
  12565. // - consider using D[] property if you want simple read/write typed access
  12566. function GetAsDouble(const aName: RawUTF8; out aValue: double;
  12567. aSortedCompare: TUTF8Compare=nil): Boolean;
  12568. /// find an item in this document, and returns its value as RawUTF8
  12569. // - return false if aName is not found, or if the instance is not a TDocVariant
  12570. // - return true if the name has been found, and aValue stores the value
  12571. // - after a SortByName(aSortedCompare), would use faster binary search
  12572. // - consider using U[] property if you want simple read/write typed access
  12573. function GetAsRawUTF8(const aName: RawUTF8; out aValue: RawUTF8;
  12574. aSortedCompare: TUTF8Compare=nil): Boolean;
  12575. /// find an item in this document, and returns its value as a TDocVariantData
  12576. // - return false if aName is not found, or if the instance is not a TDocVariant
  12577. // - return true if the name has been found and points to a TDocVariant:
  12578. // then aValue stores a pointer to the value
  12579. // - after a SortByName(aSortedCompare), would use faster binary search
  12580. function GetAsDocVariant(const aName: RawUTF8; out aValue: PDocVariantData;
  12581. aSortedCompare: TUTF8Compare=nil): boolean; overload;
  12582. /// find an item in this document, and returns its value as a TDocVariantData
  12583. // - returns a void TDocVariant if aName is not a document
  12584. // - after a SortByName(aSortedCompare), would use faster binary search
  12585. // - consider using O[] or A[] properties if you want simple read-only
  12586. // access, or O_[] or A_[] properties if you want the ability to add
  12587. // a missing object or array in the document
  12588. function GetAsDocVariantSafe(const aName: RawUTF8;
  12589. aSortedCompare: TUTF8Compare=nil): PDocVariantData;
  12590. /// find an item in this document, and returns pointer to its value
  12591. // - return false if aName is not found
  12592. // - return true if the name has been found: then aValue stores a pointer
  12593. // to the value
  12594. // - after a SortByName(aSortedCompare), would use faster binary search
  12595. function GetAsPVariant(const aName: RawUTF8; out aValue: PVariant;
  12596. aSortedCompare: TUTF8Compare=nil): boolean;
  12597. /// retrieve a value, given its path
  12598. // - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
  12599. // - it will return Unassigned if the path does match the supplied aPath
  12600. function GetValueByPath(const aPath: RawUTF8): variant; overload;
  12601. /// retrieve a value, given its path
  12602. // - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
  12603. // - it will return FALSE if the path does not match the supplied aPath
  12604. // - returns TRUE and set the found value in aValue
  12605. function GetValueByPath(const aPath: RawUTF8; out aValue: variant): boolean; overload;
  12606. /// retrieve a value, given its path
  12607. // - path is defined as a list of names, e.g. ['doc','glossary','title']
  12608. // - it will return Unassigned if the path does not match the data
  12609. // - this method will only handle nested TDocVariant values: use the
  12610. // slightly slower GetValueByPath() overloaded method, if any nested object
  12611. // may be of another type (e.g. a TBSONVariant)
  12612. function GetValueByPath(const aDocVariantPath: array of RawUTF8): variant; overload;
  12613. /// retrieve a reference to a value, given its path
  12614. // - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
  12615. // - if the supplied aPath does not match any object, it will return nil
  12616. // unless addIfNotExisting=true and it would add the new object
  12617. // - if aPath is found (or added), it will return a pointer to the
  12618. // corresponding value
  12619. function GetPVariantByPath(const aPath: RawUTF8; addIfNotExisting: boolean=false): PVariant;
  12620. /// retrieve an dvObject, from a property name in the dvArray document
  12621. // - returns false if no object in the dvArray contains the supplied
  12622. // "aPropName":"aPropValue" property
  12623. // - returns true, and copy the corresponding VValue[] item into Dest
  12624. // if a match was found
  12625. // - create a copy of the variant by default, unless DestByRef is TRUE
  12626. function GetItemByProp(const aPropName,aPropValue: RawUTF8;
  12627. aCaseSensitive: boolean; var Dest: variant; DestByRef: boolean=false): boolean;
  12628. /// retrieve an dvObject, from a property name in the dvArray document
  12629. // - returns false if no object in the dvArray contains the supplied
  12630. // "aPropName":"aPropValue" property
  12631. // - returns true, and create a reference to the VValue[] item into Dest
  12632. // if a match was found
  12633. function GetDocVariantByProp(const aPropName,aPropValue: RawUTF8;
  12634. aCaseSensitive: boolean; out Dest: PDocVariantData): boolean;
  12635. /// find an item in this document, and returns its value
  12636. // - raise an EDocVariant if not found and dvoReturnNullForUnknownProperty
  12637. // is not set in Options (in this case, it will return Null)
  12638. // - create a copy of the variant by default, unless DestByRef is TRUE
  12639. procedure RetrieveValueOrRaiseException(aName: PUTF8Char; aNameLen: integer;
  12640. aCaseSensitive: boolean; var Dest: variant; DestByRef: boolean); overload;
  12641. /// retrieve an item in this document from its index, and returns its value
  12642. // - raise an EDocVariant if the supplied Index is not in the 0..Count-1
  12643. // range and dvoReturnNullForOutOfRangeIndex is set in Options
  12644. // - create a copy of the variant by default, unless DestByRef is TRUE
  12645. procedure RetrieveValueOrRaiseException(Index: integer;
  12646. var Dest: variant; DestByRef: boolean); overload;
  12647. /// retrieve an item in this document from its index, and returns its Name
  12648. // - raise an EDocVariant if the supplied Index is not in the 0..Count-1
  12649. // range and dvoReturnNullForOutOfRangeIndex is set in Options
  12650. procedure RetrieveNameOrRaiseException(Index: integer; var Dest: RawUTF8);
  12651. /// set an item in this document from its index
  12652. // - raise an EDocVariant if the supplied Index is not in 0..Count-1 range
  12653. procedure SetValueOrRaiseException(Index: integer; const NewValue: variant);
  12654. /// add a value in this document
  12655. // - if aName is set, if dvoCheckForDuplicatedNames option is set, any
  12656. // existing duplicated aName will raise an EDocVariant; if instance's
  12657. // kind is dvArray and aName is defined, it will raise an EDocVariant
  12658. // - aName may be '' e.g. if you want to store an array: in this case,
  12659. // dvoCheckForDuplicatedNames option should not be set; if instance's Kind
  12660. // is dvObject, it will raise an EDocVariant exception
  12661. // - you can therefore write e.g.:
  12662. // ! TDocVariant.New(aVariant);
  12663. // ! Assert(TDocVariantData(aVariant).Kind=dvUndefined);
  12664. // ! TDocVariantData(aVariant).AddValue('name','John');
  12665. // ! Assert(TDocVariantData(aVariant).Kind=dvObject);
  12666. // - returns the index of the corresponding newly added value
  12667. function AddValue(const aName: RawUTF8; const aValue: variant): integer; overload;
  12668. /// add a value in this document
  12669. // - overloaded function accepting a UTF-8 encoded buffer for the name
  12670. function AddValue(aName: PUTF8Char; aNameLen: integer; const aValue: variant): integer; overload;
  12671. /// add a value in this document, or update an existing entry
  12672. // - if instance's Kind is dvArray, it will raise an EDocVariant exception
  12673. // - any existing Name would be updated with the new Value, unless
  12674. // OnlyAddMissing is set to TRUE, in which case existing values would remain
  12675. // - returns the index of the corresponding value, which may be just added
  12676. function AddOrUpdateValue(const aName: RawUTF8; const aValue: variant;
  12677. wasAdded: PBoolean=nil; OnlyAddMissing: boolean=false): integer;
  12678. /// add a value in this document
  12679. // - this function expects a UTF-8 text for the value, which would be
  12680. // converted to a variant number, if possible
  12681. // - if Update=TRUE, will set the property, even if it is existing
  12682. function AddValueFromText(const aName,aValue: RawUTF8; Update: boolean=false): integer;
  12683. /// add some properties to a TDocVariantData dvObject
  12684. // - data is supplied two by two, as Name,Value pairs
  12685. // - caller should ensure that VKind=dvObject, otherwise it won't do anything
  12686. // - any existing Name would be duplicated
  12687. procedure AddNameValuesToObject(const NameValuePairs: array of const);
  12688. /// merge some properties to a TDocVariantData dvObject
  12689. // - data is supplied two by two, as Name,Value pairs
  12690. // - caller should ensure that VKind=dvObject, otherwise it won't do anything
  12691. // - any existing Name would be updated with the new Value
  12692. procedure AddOrUpdateNameValuesToObject(const NameValuePairs: array of const);
  12693. /// merge some TDocVariantData dvObject properties to a TDocVariantData dvObject
  12694. // - data is supplied two by two, as Name,Value pairs
  12695. // - caller should ensure that both variants have VKind=dvObject, otherwise
  12696. // it won't do anything
  12697. // - any existing Name would be updated with the new Value, unless
  12698. // OnlyAddMissing is set to TRUE, in which case existing values would remain
  12699. procedure AddOrUpdateObject(const NewValues: variant; OnlyAddMissing: boolean=false);
  12700. /// add a value to this document, handled as array
  12701. // - if instance's Kind is dvObject, it will raise an EDocVariant exception
  12702. // - you can therefore write e.g.:
  12703. // ! TDocVariant.New(aVariant);
  12704. // ! Assert(TDocVariantData(aVariant).Kind=dvUndefined);
  12705. // ! TDocVariantData(aVariant).AddItem('one');
  12706. // ! Assert(TDocVariantData(aVariant).Kind=dvArray);
  12707. // - returns the index of the corresponding newly added item
  12708. function AddItem(const aValue: variant): integer;
  12709. /// add one or several values to this document, handled as array
  12710. // - if instance's Kind is dvObject, it will raise an EDocVariant exception
  12711. procedure AddItems(const aValue: array of const);
  12712. /// add one or several values from another document
  12713. // - supplied document should be of the same kind than the current one,
  12714. // otherwise nothing is added
  12715. procedure AddFrom(const aDocVariant: Variant);
  12716. /// add one or several properties, specified by path, from another object
  12717. // - path are defined as a dotted name-space, e.g. 'doc.glossary.title'
  12718. // - matching values would be added as root values, with the path as name
  12719. // - instance and supplied aSource should be a dvObject
  12720. procedure AddByPath(const aSource: TDocVariantData; const aPaths: array of RawUTF8);
  12721. /// delete a value/item in this document, from its index
  12722. // - return TRUE on success, FALSE if the supplied index is not correct
  12723. function Delete(Index: integer): boolean; overload;
  12724. /// delete a value/item in this document, from its name
  12725. // - return TRUE on success, FALSE if the supplied name does not exist
  12726. function Delete(const aName: RawUTF8): boolean; overload;
  12727. /// delete a value in this document, by property name match
  12728. // - {aPropName:aPropValue} will be searched within the stored array,
  12729. // and the corresponding item will be deleted, on match
  12730. // - returns FALSE if no match is found, TRUE if as deleted
  12731. function DeleteByProp(const aPropName,aPropValue: RawUTF8;
  12732. aCaseSensitive: boolean): boolean;
  12733. /// delete one or several value/item in this document, from its value
  12734. // - return TRUE on success, FALSE if the supplied value does not exist
  12735. // - if the value exists several times, all occurences would be removed
  12736. function DeleteByValue(const aValue: Variant; CaseInsensitive: boolean=false): boolean;
  12737. /// delete all values matching the first characters of a property name
  12738. // - returns the number of deleted items
  12739. // - returns 0 if the document is not a dvObject, or if no match was found
  12740. // - will use IdemPChar(), so search would be case-insensitive
  12741. function DeleteByStartName(aStartName: PUTF8Char; aStartNameLen: integer): integer;
  12742. /// search a property match in this document, handled as array
  12743. // - {aPropName:aPropValue} will be searched within the stored array,
  12744. // and the corresponding item index will be returned, on match
  12745. // - returns -1 if no match is found
  12746. function SearchItemByProp(const aPropName,aPropValue: RawUTF8;
  12747. aCaseSensitive: boolean): integer;
  12748. /// search a value in this document, handled as array
  12749. // - aValue will be searched within the stored array
  12750. // and the corresponding item index will be returned, on match
  12751. // - returns -1 if no match is found
  12752. // - you could make several searches, using the StartIndex optional parameter
  12753. function SearchItemByValue(const aValue: Variant;
  12754. CaseInsensitive: boolean=false; StartIndex: integer=0): integer;
  12755. /// sort the document object values by name
  12756. // - do nothing if the document is not a dvObject
  12757. // - will follow case-insensitive order (@StrIComp) by default, but you
  12758. // can specify @StrComp as comparer function for case-sensitive ordering
  12759. // - once sorted, you can use GetVarData(..,Compare) or GetAs*(..,Compare)
  12760. // methods for much faster binary search
  12761. procedure SortByName(Compare: TUTF8Compare=nil);
  12762. /// sort the document object values by value
  12763. // - do nothing if the document is not a dvObject
  12764. procedure SortByValue(Compare: TVariantCompare);
  12765. /// reverse the order of the document object or array items
  12766. procedure Reverse;
  12767. /// create a TDocVariant object, from a selection of properties of this
  12768. // document, by property name
  12769. // - if the document is a dvObject, to reduction will be applied to all
  12770. // its properties
  12771. // - if the document is a dvArray, the reduction will be applied to each
  12772. // stored item, if it is a document
  12773. procedure Reduce(const aPropNames: array of RawUTF8; aCaseSensitive: boolean;
  12774. out result: TDocVariantData; aDoNotAddVoidProp: boolean=false); overload;
  12775. /// create a TDocVariant object, from a selection of properties of this
  12776. // document, by property name
  12777. // - always returns a TDocVariantData, even if no property name did match
  12778. // (in this case, it is dvUndefined)
  12779. function Reduce(const aPropNames: array of RawUTF8; aCaseSensitive: boolean;
  12780. aDoNotAddVoidProp: boolean=false): variant; overload;
  12781. /// create a TDocVariant array, from the values of a single properties of
  12782. // this document, specified by name
  12783. // - you can optionally apply an additional filter to each reduced item
  12784. procedure ReduceAsArray(const aPropName: RawUTF8; out result: TDocVariantData;
  12785. OnReduce: TOnReducePerItem=nil); overload;
  12786. /// create a TDocVariant array, from the values of a single properties of
  12787. // this document, specified by name
  12788. // - always returns a TDocVariantData, even if no property name did match
  12789. // (in this case, it is dvUndefined)
  12790. // - you can optionally apply an additional filter to each reduced item
  12791. function ReduceAsArray(const aPropName: RawUTF8; OnReduce: TOnReducePerItem=nil): variant; overload;
  12792. /// create a TDocVariant array, from the values of a single properties of
  12793. // this document, specified by name
  12794. // - this overloaded method accepts an additional filter to each reduced item
  12795. procedure ReduceAsArray(const aPropName: RawUTF8; out result: TDocVariantData;
  12796. OnReduce: TOnReducePerValue); overload;
  12797. /// create a TDocVariant array, from the values of a single properties of
  12798. // this document, specified by name
  12799. // - always returns a TDocVariantData, even if no property name did match
  12800. // (in this case, it is dvUndefined)
  12801. // - this overloaded method accepts an additional filter to each reduced item
  12802. function ReduceAsArray(const aPropName: RawUTF8; OnReduce: TOnReducePerValue): variant; overload;
  12803. /// map {"obj.prop1"..,"obj.prop2":..} into {"obj":{"prop1":..,"prop2":...}}
  12804. // - the supplied aObjectPropName should match the incoming dotted value
  12805. // of all properties (e.g. 'obj' for "obj.prop1")
  12806. // - if any of the incoming property is not of "obj.prop#" form, the
  12807. // whole process would be ignored
  12808. // - return FALSE if the TDocVariant did not change
  12809. // - return TRUE if the TDocVariant has been flattened
  12810. function FlattenAsNestedObject(const aObjectPropName: RawUTF8): boolean;
  12811. /// how this document will behave
  12812. // - those options are set when creating the instance
  12813. property Options: TDocVariantOptions read VOptions write VOptions;
  12814. /// returns the instance internal layout
  12815. // - just after initialization, it will return dvUndefined
  12816. // - most of the time, you will add named values with AddValue() or by
  12817. // setting the variant properties: it will return dvObject
  12818. // - but is you use AddItem(), values will have no associated names: the
  12819. // document will be a dvArray
  12820. property Kind: TDocVariantKind read VKind;
  12821. /// return the custom variant type identifier, i.e. DocVariantType.VarType
  12822. property VarType: word read VType;
  12823. /// number of items stored in this document
  12824. // - is 0 if Kind=dvUndefined
  12825. // - is the number of name/value pairs for Kind=dvObject
  12826. // - is the number of items for Kind=dvArray
  12827. property Count: integer read VCount;
  12828. /// the current capacity of this document
  12829. // - allow direct access to VValue[] length
  12830. property Capacity: integer read GetCapacity write SetCapacity;
  12831. /// direct acces to the low-level internal array of values
  12832. // - transtyping a variant and direct access to TDocVariantData is the
  12833. // fastest way of accessing all properties of a given dvObject:
  12834. // ! with TDocVariantData(aVariantObject) do
  12835. // ! for i := 0 to Count-1 do
  12836. // ! writeln(Names[i],'=',Values[i]);
  12837. // - or to access a dvArray items (e.g. a MongoDB collection):
  12838. // ! with TDocVariantData(aVariantArray) do
  12839. // ! for i := 0 to Count-1 do
  12840. // ! writeln(Values[i]);
  12841. property Values: TVariantDynArray read VValue;
  12842. /// direct acces to the low-level internal array of names
  12843. // - is void (nil) if Kind is not dvObject
  12844. // - transtyping a variant and direct access to TDocVariantData is the
  12845. // fastest way of accessing all properties of a given dvObject:
  12846. // ! with TDocVariantData(aVariantObject) do
  12847. // ! for i := 0 to Count-1 do
  12848. // ! writeln(Names[i],'=',Values[i]);
  12849. property Names: TRawUTF8DynArray read VName;
  12850. /// find an item in this document, and returns its value
  12851. // - raise an EDocVariant if aNameOrIndex is neither an integer nor a string
  12852. // - raise an EDocVariant if Kind is dvArray and aNameOrIndex is a string
  12853. // or if Kind is dvObject and aNameOrIndex is an integer
  12854. // - raise an EDocVariant if Kind is dvObject and if aNameOrIndex is a
  12855. // string, which is not found within the object property names and
  12856. // dvoReturnNullForUnknownProperty is set in Options
  12857. // - raise an EDocVariant if Kind is dvArray and if aNameOrIndex is a
  12858. // integer, which is not within 0..Count-1 and
  12859. // dvoReturnNullForOutOfRangeIndex is set in Options
  12860. // - so you can use directly:
  12861. // ! // for an array document:
  12862. // ! aVariant := TDocVariant.NewArray(['one',2,3.0]);
  12863. // ! for i := 0 to TDocVariantData(aVariant).Count-1 do
  12864. // ! aValue := TDocVariantData(aVariant).Value[i];
  12865. // ! // for an object document:
  12866. // ! aVariant := TDocVariant.NewObject(['name','John','year',1972]);
  12867. // ! assert(aVariant.Name=TDocVariantData(aVariant)['name']);
  12868. // ! assert(aVariant.year=TDocVariantData(aVariant)['year']);
  12869. // - due to the internal implementation of variant execution (somewhat
  12870. // slow _DispInvoke() function), it is a bit faster to execute:
  12871. // ! aValue := TDocVariantData(aVariant).Value['name'];
  12872. // instead of
  12873. // ! aValue := aVariant.name;
  12874. // but of course, if want to want to access the content by index (typically
  12875. // for a dvArray), using Values[] - and Names[] - properties is much faster
  12876. // than this variant-indexed pseudo-property:
  12877. // ! with TDocVariantData(aVariant) do
  12878. // ! for i := 0 to Count-1 do
  12879. // ! Writeln(Values[i]);
  12880. // is faster than:
  12881. // ! with TDocVariantData(aVariant) do
  12882. // ! for i := 0 to Count-1 do
  12883. // ! Writeln(Value[i]);
  12884. // which is faster than:
  12885. // ! for i := 0 to aVariant.Count-1 do
  12886. // ! Writeln(aVariant._(i));
  12887. // - this property will return the value as varByRef (just like with
  12888. // variant late binding of any TDocVariant instance), so you can write:
  12889. // !var Doc: TDocVariantData; // stack-allocated variable
  12890. // !begin
  12891. // ! Doc.InitJSON('{arr:[1,2]}');
  12892. // ! assert(Doc.Count=2);
  12893. // ! Doc.Value['arr'].Add(3); // works since Doc.Value['arr'] is varByRef
  12894. // ! writeln(Doc.ToJSON); // will write '{"arr":[1,2,3]}'
  12895. // !end;
  12896. // - if you want to access a property as a copy, you can use:
  12897. // ! Doc.GetValueOrRaiseException('arr').Add(4); // won't work
  12898. // - see U[] I[] B[] D[] O[] O_[] A[] A_[] _[] properties for direct access
  12899. // of strong typed values
  12900. property Value[const aNameOrIndex: Variant]: Variant read GetValueOrItem
  12901. write SetValueOrItem; default;
  12902. /// direct access to a dvObject UTF-8 stored property value from its name
  12903. // - slightly faster than the variant-based Value[] default property
  12904. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12905. // - use GetAsRawUTF8() if you want to check the availability of the field
  12906. // - U['prop'] := 'value' would add a new property, or overwrite an existing
  12907. property U[const aName: RawUTF8]: RawUTF8 read GetRawUTF8ByName write SetRawUTF8ByName;
  12908. /// direct access to a dvObject Integer stored property value from its name
  12909. // - slightly faster than the variant-based Value[] default property
  12910. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12911. // - use GetAsInt/GetAsInt64 if you want to check the availability of the field
  12912. // - I['prop'] := 123 would add a new property, or overwrite an existing
  12913. property I[const aName: RawUTF8]: Int64 read GetInt64ByName write SetInt64ByName;
  12914. /// direct access to a dvObject Boolean stored property value from its name
  12915. // - slightly faster than the variant-based Value[] default property
  12916. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12917. // - use GetAsBoolean if you want to check the availability of the field
  12918. // - B['prop'] := true would add a new property, or overwrite an existing
  12919. property B[const aName: RawUTF8]: Boolean read GetBooleanByName write SetBooleanByName;
  12920. /// direct access to a dvObject floating-point stored property value from its name
  12921. // - slightly faster than the variant-based Value[] default property
  12922. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12923. // - use GetAsDouble if you want to check the availability of the field
  12924. // - D['prop'] := 1.23 would add a new property, or overwrite an existing
  12925. property D[const aName: RawUTF8]: Double read GetDoubleByName write SetDoubleByName;
  12926. /// direct access to a dvObject existing dvObject property from its name
  12927. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12928. // - O['prop'] would return a fake void TDocVariant if the property is not
  12929. // existing or not a dvObject, just like GetAsDocVariantSafe()
  12930. // - use O_['prop'] to force adding any missing property
  12931. property O[const aName: RawUTF8]: PDocVariantData read GetObjectExistingByName;
  12932. /// direct access or add a dvObject's dvObject property from its name
  12933. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12934. // - O_['prop'] would add a new property if there is none existing, or
  12935. // overwrite an existing property which is not a dvObject
  12936. property O_[const aName: RawUTF8]: PDocVariantData read GetObjectOrAddByName;
  12937. /// direct access to a dvObject existing dvArray property from its name
  12938. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12939. // - A['prop'] would return a fake void TDocVariant if the property is not
  12940. // existing or not a dvArray, just like GetAsDocVariantSafe()
  12941. // - use A_['prop'] to force adding any missing property
  12942. property A[const aName: RawUTF8]: PDocVariantData read GetArrayExistingByName;
  12943. /// direct access or add a dvObject's dvArray property from its name
  12944. // - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
  12945. // - A_['prop'] would add a new property if there is none existing, or
  12946. // overwrite an existing property which is not a dvArray
  12947. property A_[const aName: RawUTF8]: PDocVariantData read GetArrayOrAddByName;
  12948. /// direct access to a dvArray's TDocVariant property from its index
  12949. // - simple values may directly use Values[] dynamic array, but to access
  12950. // a TDocVariantData members, this property is safer
  12951. // - follows dvoReturnNullForOutOfRangeIndex option to raise an exception
  12952. // - _[ndx] would return a fake void TDocVariant if aIndex is out of range,
  12953. // if the property is not existing or not a TDocVariantData (just like
  12954. // GetAsDocVariantSafe)
  12955. property _[aIndex: integer]: PDocVariantData read GetAsDocVariantByIndex;
  12956. end;
  12957. {$A+} { packet object not allowed since Delphi 2009 :( }
  12958. /// direct access to a TDocVariantData from a given variant instance
  12959. // - return a pointer to the TDocVariantData corresponding to the variant
  12960. // instance, which may be of kind varByRef (e.g. when retrieved by late binding)
  12961. // - raise an EDocVariant exception if the instance is not a TDocVariant
  12962. // - the following direct trans-typing may fail, e.g. for varByRef value:
  12963. // ! TDocVariantData(aVarDoc.ArrayProp).Add('new item');
  12964. // - so you can write the following:
  12965. // ! DocVariantData(aVarDoc.ArrayProp).AddItem('new item');
  12966. function DocVariantData(const DocVariant: variant): PDocVariantData;
  12967. const
  12968. /// constant used e.g. by _Safe() overloaded functions
  12969. // - will be in code section of the exe, so will be read-only by design
  12970. // - would have VKind=dvUndefined and VCount=0, so _Safe() would return
  12971. // a valid, but void document
  12972. // - its VType is varNull, so would be viewed as a null variant
  12973. DocVariantDataFake: TDocVariantData = (VType:1);
  12974. /// direct access to a TDocVariantData from a given variant instance
  12975. // - return a pointer to the TDocVariantData corresponding to the variant
  12976. // instance, which may be of kind varByRef (e.g. when retrieved by late binding)
  12977. // - will return a read-only fake TDocVariantData with Kind=dvUndefined if the
  12978. // supplied variant is not a TDocVariant instance, so could be safely used
  12979. // in a with block (use "with" moderation, of course):
  12980. // ! with _Safe(aDocVariant)^ do
  12981. // ! for ndx := 0 to Count-1 do // here Count=0 for the "fake" result
  12982. // ! writeln(Names[ndx]);
  12983. function _Safe(const DocVariant: variant): PDocVariantData; overload;
  12984. {$ifdef HASINLINE}inline;{$endif}
  12985. /// direct access to a TDocVariantData from a given variant instance
  12986. // - return a pointer to the TDocVariantData corresponding to the variant
  12987. // instance, which may be of kind varByRef (e.g. when retrieved by late binding)
  12988. // - will check the supplied document kind, i.e. either dvObject or dvArray and
  12989. // raise a EDocVariant exception if it does not match
  12990. function _Safe(const DocVariant: variant; ExpectedKind: TDocVariantKind): PDocVariantData; overload;
  12991. /// initialize a variant instance to store some document-based object content
  12992. // - object will be initialized with data supplied two by two, as Name,Value
  12993. // pairs, e.g.
  12994. // ! aVariant := _Obj(['name','John','year',1972]);
  12995. // or even with nested objects:
  12996. // ! aVariant := _Obj(['name','John','doc',_Obj(['one',1,'two',2.0])]);
  12997. // - this global function is an alias to TDocVariant.NewObject()
  12998. // - by default, every internal value will be copied, so access of nested
  12999. // properties can be slow - if you expect the data to be read-only or not
  13000. // propagated into another place, set Options=[dvoValueCopiedByReference]
  13001. // or using _ObjFast() will increase the process speed a lot
  13002. function _Obj(const NameValuePairs: array of const;
  13003. Options: TDocVariantOptions=[]): variant;
  13004. /// add some property values to a document-based object content
  13005. // - if Obj is a TDocVariant object, will add the Name/Value pairs
  13006. // - if Obj is not a TDocVariant, will create a new fast document,
  13007. // initialized with supplied the Name/Value pairs
  13008. // - this function will also ensure that ensure Obj is not stored by reference,
  13009. // but as a true TDocVariantData
  13010. procedure _ObjAddProps(const NameValuePairs: array of const; var Obj: variant); overload;
  13011. /// add the property values of a document to a document-based object content
  13012. // - if Document is not a TDocVariant object, will do nothing
  13013. // - if Obj is a TDocVariant object, will add Document fields to its content
  13014. // - if Obj is not a TDocVariant object, Document will be copied to Obj
  13015. procedure _ObjAddProps(const Document: variant; var Obj: variant); overload;
  13016. /// initialize a variant instance to store some document-based array content
  13017. // - array will be initialized with data supplied as parameters, e.g.
  13018. // ! aVariant := _Arr(['one',2,3.0]);
  13019. // - this global function is an alias to TDocVariant.NewArray()
  13020. // - by default, every internal value will be copied, so access of nested
  13021. // properties can be slow - if you expect the data to be read-only or not
  13022. // propagated into another place, set Options=[dvoValueCopiedByReference]
  13023. // or using _ArrFast() will increase the process speed a lot
  13024. function _Arr(const Items: array of const;
  13025. Options: TDocVariantOptions=[]): variant;
  13026. /// initialize a variant instance to store some document-based content
  13027. // from a supplied (extended) JSON content
  13028. // - this global function is an alias to TDocVariant.NewJSON(), and
  13029. // will return an Unassigned variant if JSON content was not correctly converted
  13030. // - object or array will be initialized from the supplied JSON content, e.g.
  13031. // ! aVariant := _Json('{"id":10,"doc":{"name":"John","birthyear":1972}}');
  13032. // ! // now you can access to the properties via late binding
  13033. // ! assert(aVariant.id=10);
  13034. // ! assert(aVariant.doc.name='John');
  13035. // ! assert(aVariant.doc.birthYear=1972);
  13036. // ! // and also some pseudo-properties:
  13037. // ! assert(aVariant._count=2);
  13038. // ! assert(aVariant.doc._kind=ord(dvObject));
  13039. // ! // or with a JSON array:
  13040. // ! aVariant := _Json('["one",2,3]');
  13041. // ! assert(aVariant._kind=ord(dvArray));
  13042. // ! for i := 0 to aVariant._count-1 do
  13043. // ! writeln(aVariant._(i));
  13044. // - in addition to the JSON RFC specification strict mode, this method will
  13045. // handle some BSON-like extensions, e.g. unquoted field names:
  13046. // ! aVariant := _Json('{id:10,doc:{name:"John",birthyear:1972}}');
  13047. // - if the SynMongoDB unit is used in the application, the MongoDB Shell
  13048. // syntax will also be recognized to create TBSONVariant, like
  13049. // ! new Date() ObjectId() MinKey MaxKey /<jRegex>/<jOptions>
  13050. // see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
  13051. // - by default, every internal value will be copied, so access of nested
  13052. // properties can be slow - if you expect the data to be read-only or not
  13053. // propagated into another place, add dvoValueCopiedByReference in Options
  13054. // will increase the process speed a lot, or use _JsonFast()
  13055. function _Json(const JSON: RawUTF8;
  13056. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant; overload;
  13057. {$ifdef HASINLINE}inline;{$endif}
  13058. /// initialize a variant instance to store some document-based content
  13059. // from a supplied (extended) JSON content, with parameters formating
  13060. // - wrapper around the _Json(FormatUTF8(...,JSONFormat=true)) function,
  13061. // i.e. every Args[] will be inserted for each % and Params[] for each ?,
  13062. // with proper JSON escaping of string values, and writing nested _Obj() /
  13063. // _Arr() instances as expected JSON objects / arrays
  13064. // - typical use (in the context of SynMongoDB unit) could be:
  13065. // ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack']);
  13066. // ! aVariant := _JSONFmt('{type:{$in:?}}',[],[_Arr(['food','snack'])]);
  13067. // ! // which are the same as:
  13068. // ! aVariant := _JSONFmt('{type:{$in:["food","snack"]}}');
  13069. // ! // in this context:
  13070. // ! u := VariantSaveJSON(aVariant);
  13071. // ! assert(u='{"type":{"$in":["food","snack"]}}');
  13072. // ! u := VariantSaveMongoJSON(aVariant,modMongoShell);
  13073. // ! assert(u='{type:{$in:["food","snack"]}}');
  13074. // - by default, every internal value will be copied, so access of nested
  13075. // properties can be slow - if you expect the data to be read-only or not
  13076. // propagated into another place, add dvoValueCopiedByReference in Options
  13077. // will increase the process speed a lot, or use _JsonFast()
  13078. function _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  13079. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant; overload;
  13080. /// initialize a variant instance to store some document-based content
  13081. // from a supplied (extended) JSON content, with parameters formating
  13082. // - this overload function will set directly a local variant variable,
  13083. // and would be used by inlined _JsonFmt/_JsonFastFmt functions
  13084. procedure _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  13085. Options: TDocVariantOptions; out result: variant); overload;
  13086. /// initialize a variant instance to store some document-based content
  13087. // from a supplied (extended) JSON content
  13088. // - this global function is an alias to TDocVariant.NewJSON(), and
  13089. // will return TRUE if JSON content was correctly converted into a variant
  13090. // - in addition to the JSON RFC specification strict mode, this method will
  13091. // handle some BSON-like extensions, e.g. unquoted field names or ObjectID()
  13092. // - by default, every internal value will be copied, so access of nested
  13093. // properties can be slow - if you expect the data to be read-only or not
  13094. // propagated into another place, add dvoValueCopiedByReference in Options
  13095. // will increase the process speed a lot, or use _JsonFast()
  13096. function _Json(const JSON: RawUTF8; var Value: variant;
  13097. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): boolean; overload;
  13098. {$ifdef HASINLINE}inline;{$endif}
  13099. /// initialize a variant instance to store some document-based object content
  13100. // - this global function is an handy alias to:
  13101. // ! Obj(NameValuePairs,JSON_OPTIONS[true]);
  13102. // - so all created objects and arrays will be handled by reference, for best
  13103. // speed - but you should better write on the resulting variant tree with caution
  13104. function _ObjFast(const NameValuePairs: array of const): variant; overload;
  13105. /// initialize a variant instance to store any object as a TDocVariant
  13106. // - is a wrapper around _JsonFast(ObjectToJson(aObject,aOptions))
  13107. function _ObjFast(aObject: TObject;
  13108. aOptions: TTextWriterWriteObjectOptions=[woDontStoreDefault]): variant; overload;
  13109. /// initialize a variant instance to store some document-based array content
  13110. // - this global function is an handy alias to:
  13111. // ! _Array(Items,JSON_OPTIONS[true]);
  13112. // - so all created objects and arrays will be handled by reference, for best
  13113. // speed - but you should better write on the resulting variant tree with caution
  13114. function _ArrFast(const Items: array of const): variant; overload;
  13115. /// initialize a variant instance to store some document-based content
  13116. // from a supplied (extended) JSON content
  13117. // - this global function is an handy alias to:
  13118. // ! _Json(JSON,JSON_OPTIONS[true]);
  13119. // so it will return an Unassigned variant if JSON content was not correct
  13120. // - so all created objects and arrays will be handled by reference, for best
  13121. // speed - but you should better write on the resulting variant tree with caution
  13122. // - in addition to the JSON RFC specification strict mode, this method will
  13123. // handle some BSON-like extensions, e.g. unquoted field names or ObjectID()
  13124. function _JsonFast(const JSON: RawUTF8): variant;
  13125. {$ifdef HASINLINE}inline;{$endif}
  13126. /// initialize a variant instance to store some document-based content
  13127. // from a supplied (extended) JSON content, with parameters formating
  13128. // - this global function is an handy alias e.g. to:
  13129. // ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack'],JSON_OPTIONS[true]);
  13130. // - so all created objects and arrays will be handled by reference, for best
  13131. // speed - but you should better write on the resulting variant tree with caution
  13132. // - in addition to the JSON RFC specification strict mode, this method will
  13133. // handle some BSON-like extensions, e.g. unquoted field names or ObjectID():
  13134. function _JsonFastFmt(const Format: RawUTF8; const Args,Params: array of const): variant;
  13135. /// ensure a document-based variant instance will have only per-value nested
  13136. // objects or array documents
  13137. // - is just a wrapper around:
  13138. // ! TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS[false])
  13139. // - you can use this function to ensure that all internal properties of this
  13140. // variant will be copied per-value whatever options the nested objects or
  13141. // arrays were created with
  13142. // - for huge document with a big depth of nested objects or arrays, a full
  13143. // per-value copy may be time and resource consuming, but will be also safe
  13144. // - will raise an EDocVariant if the supplied variant is not a TDocVariant or
  13145. // a varByRef pointing to a TDocVariant
  13146. procedure _Unique(var DocVariant: variant);
  13147. /// ensure a document-based variant instance will have only per-value nested
  13148. // objects or array documents
  13149. // - is just a wrapper around:
  13150. // ! TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS[true])
  13151. // - you can use this function to ensure that all internal properties of this
  13152. // variant will be copied per-reference whatever options the nested objects or
  13153. // arrays were created with
  13154. // - for huge document with a big depth of nested objects or arrays, it will
  13155. // first create a whole copy of the document nodes, but further assignments
  13156. // of the resulting value will be per-reference, so will be almost instant
  13157. // - will raise an EDocVariant if the supplied variant is not a TDocVariant or
  13158. // a varByRef pointing to a TDocVariant
  13159. procedure _UniqueFast(var DocVariant: variant);
  13160. /// return a full nested copy of a document-based variant instance
  13161. // - is just a wrapper around:
  13162. // ! TDocVariant.NewUnique(DocVariant,JSON_OPTIONS[false])
  13163. // - you can use this function to ensure that all internal properties of this
  13164. // variant will be copied per-value whatever options the nested objects or
  13165. // arrays were created with
  13166. // - for huge document with a big depth of nested objects or arrays, a full
  13167. // per-value copy may be time and resource consuming, but will be also safe
  13168. // - will raise an EDocVariant if the supplied variant is not a TDocVariant or
  13169. // a varByRef pointing to a TDocVariant
  13170. function _Copy(const DocVariant: variant): variant;
  13171. {$ifdef HASINLINE}inline;{$endif}
  13172. /// return a full nested copy of a document-based variant instance
  13173. // - is just a wrapper around:
  13174. // ! TDocVariant.NewUnique(DocVariant,JSON_OPTIONS[true])
  13175. // - you can use this function to ensure that all internal properties of this
  13176. // variant will be copied per-value whatever options the nested objects or
  13177. // arrays were created with
  13178. // - for huge document with a big depth of nested objects or arrays, a full
  13179. // per-value copy may be time and resource consuming, but will be also safe
  13180. // - will raise an EDocVariant if the supplied variant is not a TDocVariant or
  13181. // a varByRef pointing to a TDocVariant
  13182. function _CopyFast(const DocVariant: variant): variant;
  13183. {$ifdef HASINLINE}inline;{$endif}
  13184. {$endif NOVARIANTS}
  13185. { ************ some console functions ************************************** }
  13186. type
  13187. /// available console colors (under Windows at least)
  13188. TConsoleColor = (
  13189. ccBlack, ccBlue, ccGreen, ccCyan, ccRed, ccMagenta, ccBrown, ccLightGray,
  13190. ccDarkGray, ccLightBlue, ccLightGreen, ccLightCyan, ccLightRed, ccLightMagenta,
  13191. ccYellow, ccWhite);
  13192. /// change the Windows console text writing color
  13193. // - call this procedure to initialize internal console process, if you manually
  13194. // intialized the Windows console, e.g. via the following code:
  13195. // ! AllocConsole;
  13196. // ! TextColor(ccLightGray);
  13197. procedure TextColor(Color: TConsoleColor);
  13198. /// change the Windows console text background color
  13199. procedure TextBackground(Color: TConsoleColor);
  13200. /// will wait for the ENTER key to be pressed, processing the internal
  13201. // Windows Message loop and any Synchronize() pending notification
  13202. // - to be used e.g. for proper work of console applications with interface-based
  13203. // service implemented as optExecInMainThread
  13204. procedure ConsoleWaitForEnterKey;
  13205. {$ifdef MSWINDOWS}
  13206. /// low-level access to the keyboard state of a given key
  13207. function ConsoleKeyPressed(ExpectedKey: Word): Boolean;
  13208. {$endif}
  13209. /// direct conversion of a UTF-8 encoded string into a console OEM-encoded String
  13210. // - under Windows, will use the CP_OEMCP encoding
  13211. // - under Linux, will expect the console is defined with UTF-8 encoding
  13212. function Utf8ToConsole(const S: RawUTF8): RawByteString;
  13213. {$ifndef MSWINDOWS}{$ifdef HASINLINE}inline;{$endif}{$endif}
  13214. /// could be used in the main program block of a console application to
  13215. // handle unexpected fatal exceptions
  13216. // - typical use may be:
  13217. // !begin
  13218. // ! try
  13219. // ! ... // main console process
  13220. // ! except
  13221. // ! on E: Exception do
  13222. // ! ConsoleShowFatalException(E);
  13223. // ! end;
  13224. // !end.
  13225. procedure ConsoleShowFatalException(E: Exception);
  13226. var
  13227. /// low-level handle used for console writing
  13228. // - may be overriden when console is redirected
  13229. StdOut: THandle;
  13230. { ******************* process monitoring / statistics ********************** }
  13231. type
  13232. /// the kind of value stored in a TSynMonitor / TSynMonitorUsage property
  13233. // - i.e. match TSynMonitorTotalMicroSec, TSynMonitorOneMicroSec,
  13234. // TSynMonitorOneCount, TSynMonitorOneBytes, TSynMonitorBytesPerSec,
  13235. // TSynMonitorTotalBytes, TSynMonitorCount and TSynMonitorCount64 types as
  13236. // used to store statistic information
  13237. // - "cumulative" values would sum each process values, e.g. total elapsed
  13238. // time for SOA execution, task count or total I/O bytes
  13239. // - "immediate" (e.g. svOneBytes or smvBytesPerSec) values would be an evolving
  13240. // single value, e.g. an average value or current disk free size
  13241. // - use SYNMONITORVALUE_CUMULATIVE = [smvMicroSec,smvBytes,smvCount,smvCount64]
  13242. // constant to identify the kind of value
  13243. // - TSynMonitorUsage.Track() would use MonitorPropUsageValue() to guess
  13244. // the tracked properties type from class RTTI
  13245. TSynMonitorType = (
  13246. smvUndefined, smvOneMicroSec, smvOneBytes, smvOneCount, smvBytesPerSec,
  13247. smvMicroSec, smvBytes, smvCount, smvCount64);
  13248. /// value types as stored in TSynMonitor / TSynMonitorUsage
  13249. TSynMonitorTypes = set of TSynMonitorType;
  13250. /// would identify a cumulative time process information in micro seconds, during monitoring
  13251. // - "cumulative" time would add each process timing, e.g. for statistics about
  13252. // SOA computation of a given service
  13253. // - any property defined with this type would be identified by TSynMonitorUsage
  13254. TSynMonitorTotalMicroSec = type QWord;
  13255. /// would identify an immediate time count information, during monitoring
  13256. // - "immediate" counts won't accumulate, e.g. may store the current number
  13257. // of thread used by a process
  13258. // - any property defined with this type would be identified by TSynMonitorUsage
  13259. TSynMonitorOneCount = type cardinal;
  13260. /// would identify an immediate time process information in micro seconds, during monitoring
  13261. // - "immediate" time won't accumulate, i.e. may store the duration of the
  13262. // latest execution of a SOA computation
  13263. // - any property defined with this type would be identified by TSynMonitorUsage
  13264. TSynMonitorOneMicroSec = type QWord;
  13265. /// would identify a process information as cumulative bytes count, during monitoring
  13266. // - "cumulative" size would add some byte for each process, e.g. input/output
  13267. // - any property defined with this type would be identified by TSynMonitorUsage
  13268. TSynMonitorTotalBytes = type QWord;
  13269. /// would identify an immediate process information as bytes count, during monitoring
  13270. // - "immediate" size won't accumulate, i.e. may be e.g. computer free memory
  13271. // at a given time
  13272. // - any property defined with this type would be identified by TSynMonitorUsage
  13273. TSynMonitorOneBytes = type QWord;
  13274. /// would identify the process throughput, during monitoring
  13275. // - it indicates e.g. "immediate" bandwith usage
  13276. // - any property defined with this type would be identified by TSynMonitorUsage
  13277. TSynMonitorBytesPerSec = type QWord;
  13278. /// would identify a cumulative number of processes, during monitoring
  13279. // - any property defined with this type would be identified by TSynMonitorUsage
  13280. TSynMonitorCount = type cardinal;
  13281. /// would identify a cumulative number of processes, during monitoring
  13282. // - any property defined with this type would be identified by TSynMonitorUsage
  13283. TSynMonitorCount64 = type QWord;
  13284. /// pointer to a high resolution timer object/record
  13285. PPrecisionTimer = ^TPrecisionTimer;
  13286. /// indirect reference to a pointer to a high resolution timer object/record
  13287. PPPrecisionTimer = ^PPrecisionTimer;
  13288. /// high resolution timer (for accurate speed statistics)
  13289. // - WARNING: this record MUST be aligned to 32 bit, otherwise iFreq=0 -
  13290. // so you can use TLocalPrecisionTimer/ILocalPrecisionTimer if you want
  13291. // to alllocate a local timer instance on the stack
  13292. TPrecisionTimer = {$ifndef UNICODE}object{$else}record{$endif}
  13293. private
  13294. iStart,iStop,iResume,iLast: Int64;
  13295. iFreq: Int64;
  13296. /// contains the time elapsed in micro seconds between Start and Stop
  13297. iTime: TSynMonitorTotalMicroSec;
  13298. /// contains the time elapsed in micro seconds between Resume and Pause
  13299. iLastTime: TSynMonitorOneMicroSec;
  13300. fPauseCount: TSynMonitorCount;
  13301. public
  13302. /// initialize the timer
  13303. // - not necessary if created on the heap (e.g. as class member)
  13304. // - will set all fields to 0
  13305. procedure Init;
  13306. /// initialize and start the high resolution timer
  13307. procedure Start;
  13308. /// stop the timer, setting the Time elapsed since last Start
  13309. procedure ComputeTime;
  13310. /// stop the timer, returning the time elapsed as text with time resolution
  13311. // (us,ms,s)
  13312. // - is just a wrapper around ComputeTime + GetTime
  13313. function Stop: RawUTF8;
  13314. /// stop the timer, ready to continue its time measurement via Resume
  13315. procedure Pause;
  13316. /// resume a paused timer
  13317. // - if the previous method called was Pause, it will ignore all the
  13318. // time elapsed since then
  13319. // - if the previous method called was Start, it will start as if it was
  13320. // in pause mode
  13321. procedure Resume;
  13322. /// resume a paused timer until the method ends
  13323. // - will internaly create a TInterfaceObject class to let the compiler
  13324. // generate a try..finally block as expected to call Pause at method ending
  13325. // - is therefore very convenient to have consistent Resume/Pause calls
  13326. // - for proper use, expect TPrecisionTimer to be initialized to 0 before
  13327. // execution (e.g. define it as a protected member of a class)
  13328. // - typical use is to declare a fTimeElapsed: TPrecisionTimer protected
  13329. // member, then call fTimeElapsed.ProfileCurrentMethod at the beginning of
  13330. // all process expecting some timing, then log/save fTimeElapsed.Stop content
  13331. function ProfileCurrentMethod: IUnknown;
  13332. /// low-level method to force values settings to allow thread safe timing
  13333. // - by default, this timer is not thread safe: you can use this method to
  13334. // set the timing values from manually computed performance counters
  13335. // - the caller should also use a mutex to prevent from race conditions:
  13336. // see e.g. TSynMonitor.FromExternalQueryPerformanceCounters implementation
  13337. // - returns the time elapsed, in micro seconds (i.e. LastTime value)
  13338. // - warning: Start, Stop, Pause and Resume methods are then disallowed
  13339. function FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord;
  13340. /// low-level method to force values settings to allow thread safe timing
  13341. // - by default, this timer is not thread safe: you can use this method to
  13342. // set the timing values from manually computed performance counters
  13343. // - the caller should also use a mutex to prevent from race conditions:
  13344. // see e.g. TSynMonitor.FromExternalMicroSeconds implementation
  13345. // - warning: Start, Stop, Pause and Resume methods are then disallowed
  13346. procedure FromExternalMicroSeconds(const MicroSeconds: QWord);
  13347. {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
  13348. /// compute the per second count
  13349. function PerSec(const Count: QWord): QWord;
  13350. /// compute the time elapsed by count, with appened time resolution (us,ms,s)
  13351. function ByCount(Count: QWord): RawUTF8;
  13352. /// textual representation of time after counter stopped
  13353. // - with appened time resolution (us,ms,s)
  13354. // - not to be used in normal code, but e.g. for custom performance analysis
  13355. function Time: RawUTF8;
  13356. /// time elapsed in micro seconds after counter stopped
  13357. // - not to be used in normal code, but e.g. for custom performance analysis
  13358. property TimeInMicroSec: TSynMonitorTotalMicroSec read iTime write iTime;
  13359. /// textual representation of last process timing after counter stopped
  13360. // - with appened time resolution (us,ms,s)
  13361. // - not to be used in normal code, but e.g. for custom performance analysis
  13362. function LastTime: RawUTF8;
  13363. /// timing in micro seconds of the last process
  13364. // - not to be used in normal code, but e.g. for custom performance analysis
  13365. property LastTimeInMicroSec: TSynMonitorOneMicroSec read iLastTime write iLastTime;
  13366. /// how many times the Pause method was called, i.e. the number of tasks
  13367. // processeed
  13368. property PauseCount: TSynMonitorCount read fPauseCount;
  13369. end;
  13370. /// interface to a reference counted high resolution timer instance
  13371. // - implemented by TLocalPrecisionTimer
  13372. ILocalPrecisionTimer = interface
  13373. /// start the high resolution timer
  13374. procedure Start;
  13375. /// stop the timer, returning the time elapsed, with appened time resolution (us,ms,s)
  13376. function Stop: RawUTF8;
  13377. /// stop the timer, ready to continue its time measure
  13378. procedure Pause;
  13379. /// resume a paused timer
  13380. procedure Resume;
  13381. /// compute the per second count
  13382. function PerSec(Count: cardinal): cardinal;
  13383. /// compute the time elapsed by count, with appened time resolution (us,ms,s)
  13384. function ByCount(Count: cardinal): RawUTF8;
  13385. end;
  13386. /// reference counted high resolution timer (for accurate speed statistics)
  13387. // - since TPrecisionTimer shall be 32 bit aligned, you can use this class
  13388. // to initialize a local auto-freeing ILocalPrecisionTimer variable on stack
  13389. // - to be used as such:
  13390. // ! var Timer: ILocalPrecisionTimer;
  13391. // ! (...)
  13392. // ! Timer := TLocalPrecisionTimer.Create;
  13393. // ! Timer.Start;
  13394. // ! (...)
  13395. TLocalPrecisionTimer = class(TInterfacedObject,ILocalPrecisionTimer)
  13396. protected
  13397. fTimer: TPrecisionTimer;
  13398. public
  13399. /// initialize the instance, and start the high resolution timer
  13400. constructor CreateAndStart;
  13401. /// start the high resolution timer
  13402. procedure Start;
  13403. /// stop the timer, returning the time elapsed, with appened time resolution (us,ms,s)
  13404. function Stop: RawUTF8;
  13405. /// stop the timer, ready to continue its time measure
  13406. procedure Pause;
  13407. /// resume a paused timer
  13408. procedure Resume;
  13409. /// compute the per second count
  13410. function PerSec(Count: cardinal): cardinal;
  13411. /// compute the time elapsed by count, with appened time resolution (us,ms,s)
  13412. function ByCount(Count: cardinal): RawUTF8;
  13413. end;
  13414. {$M+}
  13415. /// able to serialize any cumulative timing as raw micro-seconds number or text
  13416. // - "cumulative" time would add each process value, e.g. SOA methods execution
  13417. TSynMonitorTime = class
  13418. protected
  13419. fMicroSeconds: TSynMonitorTotalMicroSec;
  13420. function GetAsText: RawUTF8;
  13421. public
  13422. /// compute a number per second, of the current value
  13423. function PerSecond(const Count: QWord): QWord;
  13424. {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
  13425. published
  13426. /// micro seconds time elapsed, as raw number
  13427. property MicroSec: TSynMonitorTotalMicroSec read fMicroSeconds write fMicroSeconds;
  13428. /// micro seconds time elapsed, as '... us-ns-ms-s' text
  13429. property Text: RawUTF8 read GetAsText;
  13430. end;
  13431. /// able to serialize any immediate timing as raw micro-seconds number or text
  13432. // - "immediate" size won't accumulate, i.e. may be e.g. last process time
  13433. TSynMonitorOneTime = class
  13434. protected
  13435. fMicroSeconds: TSynMonitorOneMicroSec;
  13436. function GetAsText: RawUTF8;
  13437. public
  13438. /// compute a number per second, of the current value
  13439. function PerSecond(const Count: QWord): QWord;
  13440. {$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
  13441. published
  13442. /// micro seconds time elapsed, as raw number
  13443. property MicroSec: TSynMonitorOneMicroSec read fMicroSeconds write fMicroSeconds;
  13444. /// micro seconds time elapsed, as '... us-ns-ms-s' text
  13445. property Text: RawUTF8 read GetAsText;
  13446. end;
  13447. /// able to serialize any cumulative size as bytes number
  13448. // - "cumulative" time would add each process value, e.g. global IO
  13449. TSynMonitorSize = class
  13450. protected
  13451. fBytes: TSynMonitorTotalBytes;
  13452. function GetAsText: RawUTF8;
  13453. published
  13454. /// number of bytes, as raw number
  13455. property Bytes: TSynMonitorTotalBytes read fBytes write fBytes;
  13456. /// number of bytes, as '... B-KB-MB-GB' text
  13457. property Text: RawUTF8 read GetAsText;
  13458. end;
  13459. /// able to serialize any immediate size as bytes number
  13460. // - "immediate" size won't accumulate, i.e. may be e.g. computer free memory
  13461. // at a given time
  13462. TSynMonitorOneSize = class
  13463. protected
  13464. fBytes: TSynMonitorOneBytes;
  13465. function GetAsText: RawUTF8;
  13466. published
  13467. /// number of bytes, as raw number
  13468. property Bytes: TSynMonitorOneBytes read fBytes write fBytes;
  13469. /// number of bytes, as '... B-KB-MB-GB' text
  13470. property Text: RawUTF8 read GetAsText;
  13471. end;
  13472. /// able to serialize any bandwith as bytes count per second
  13473. // - is usually associated with TSynMonitorOneSize properties,
  13474. // e.g. to monitor IO activity
  13475. TSynMonitorThroughput = class
  13476. protected
  13477. fBytesPerSec: QWord;
  13478. function GetAsText: RawUTF8;
  13479. published
  13480. /// number of bytes per second, as raw number
  13481. property BytesPerSec: QWord read fBytesPerSec write fBytesPerSec;
  13482. /// number of bytes per second, as '... B-KB-MB-GB/s' text
  13483. property Text: RawUTF8 read GetAsText;
  13484. end;
  13485. /// a generic value object able to handle any task / process statistic
  13486. // - base class shared e.g. for ORM, SOA or DDD, when a repeatable data
  13487. // process is to be monitored
  13488. // - this class is thread-safe for its methods, but you should call explicitly
  13489. // Lock/UnLock to access its individual properties
  13490. TSynMonitor = class(TSynPersistent)
  13491. protected
  13492. fName: RawUTF8;
  13493. fProcessing: boolean;
  13494. fTaskCount: TSynMonitorCount64;
  13495. fInternalErrors: TSynMonitorCount;
  13496. fLastInternalError: variant;
  13497. fTotalTime: TSynMonitorTime;
  13498. fLastTime: TSynMonitorOneTime;
  13499. fMinimalTime: TSynMonitorOneTime;
  13500. fAverageTime: TSynMonitorOneTime;
  13501. fMaximalTime: TSynMonitorOneTime;
  13502. fPerSec: QWord;
  13503. fTaskStatus: (taskNotStarted,taskStarted);
  13504. fLock: TRTLCriticalSection;
  13505. procedure LockedPerSecProperties; virtual;
  13506. procedure LockedFromProcessTimer; virtual;
  13507. procedure LockedSum(another: TSynMonitor); virtual;
  13508. procedure WriteDetailsTo(W: TTextWriter); virtual;
  13509. procedure Changed; virtual;
  13510. public
  13511. /// low-level high-precision timer instance
  13512. InternalTimer: TPrecisionTimer;
  13513. /// initialize the instance nested class properties
  13514. // - you can specify identifier associated to this monitored resource
  13515. // which would be used for TSynMonitorUsage persistence
  13516. constructor Create(const aName: RawUTF8); reintroduce; overload; virtual;
  13517. /// initialize the instance nested class properties
  13518. constructor Create; overload; override;
  13519. /// finalize the instance
  13520. destructor Destroy; override;
  13521. /// lock the instance for exclusive access
  13522. // - needed only if you access directly the instance properties
  13523. procedure Lock; {$ifdef HASINLINE}inline;{$endif}
  13524. /// release the instance for exclusive access
  13525. // - needed only if you access directly the instance properties
  13526. procedure UnLock; {$ifdef HASINLINE}inline;{$endif}
  13527. /// create Count instances of this actual class in the supplied ObjArr[]
  13528. class procedure InitializeObjArray(var ObjArr; Count: integer); virtual;
  13529. /// should be called when the process starts, to resume the internal timer
  13530. // - thread-safe method
  13531. procedure ProcessStart; virtual;
  13532. /// should be called each time a pending task is processed
  13533. // - will increase the TaskCount property
  13534. // - thread-safe method
  13535. procedure ProcessDoTask; virtual;
  13536. /// should be called when the process starts, and a task is processed
  13537. // - similar to ProcessStart + ProcessDoTask
  13538. // - thread-safe method
  13539. procedure ProcessStartTask; virtual;
  13540. /// should be called when an error occurred
  13541. // - typical use is with ObjectToVariantDebug(E,...) kind of information
  13542. // - thread-safe method
  13543. procedure ProcessError(const info: variant); virtual;
  13544. /// should be called when an error occurred
  13545. // - typical use is with a HTTP status, e.g. as ProcessError(Call.OutStatus)
  13546. // - just a wraper around overloaded ProcessError(), so a thread-safe method
  13547. procedure ProcessErrorNumber(info: integer);
  13548. /// should be called when the process stops, to pause the internal timer
  13549. // - thread-safe method
  13550. procedure ProcessEnd; virtual;
  13551. /// could be used to manage information average or sums
  13552. // - thread-safe method calling LockedSum protected virtual method
  13553. procedure Sum(another: TSynMonitor);
  13554. /// returns a JSON content with all published properties information
  13555. // - thread-safe method
  13556. function ComputeDetailsJSON: RawUTF8;
  13557. /// appends a JSON content with all published properties information
  13558. // - thread-safe method
  13559. procedure ComputeDetailsTo(W: TTextWriter); virtual;
  13560. {$ifndef NOVARIANTS}
  13561. /// returns a TDocVariant with all published properties information
  13562. // - thread-safe method
  13563. function ComputeDetails: variant;
  13564. {$endif}
  13565. /// used to allow thread safe timing
  13566. // - by default, the internal TPrecisionTimer is not thread safe: you can
  13567. // use this method to update the timing from many threads
  13568. // - if you use this method, ProcessStart, ProcessDoTask and ProcessEnd
  13569. // methods are disallowed, and the global fTimer won't be used any more
  13570. // - will return the processing time, converted into micro seconds, ready
  13571. // to be logged if needed
  13572. // - thread-safe method
  13573. function FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord;
  13574. /// used to allow thread safe timing
  13575. // - by default, the internal TPrecisionTimer is not thread safe: you can
  13576. // use this method to update the timing from many threads
  13577. // - if you use this method, ProcessStart, ProcessDoTask and ProcessEnd
  13578. // methods are disallowed, and the global fTimer won't be used any more
  13579. // - thread-safe method
  13580. procedure FromExternalMicroSeconds(const MicroSecondsElapsed: QWord);
  13581. /// an identifier associated to this monitored resource
  13582. // - is used e.g. for TSynMonitorUsage persistence/tracking
  13583. property Name: RawUTF8 read fName write fName;
  13584. published
  13585. /// indicates if this thread is currently working on some process
  13586. property Processing: boolean read fProcessing write fProcessing;
  13587. /// how many times the task was performed
  13588. property TaskCount: TSynMonitorCount64 read fTaskCount write fTaskCount;
  13589. /// the whole time spend during all working process
  13590. property TotalTime: TSynMonitorTime read fTotalTime;
  13591. /// the time spend during the last task processing
  13592. property LastTime: TSynMonitorOneTime read fLastTime;
  13593. /// the lowest time spent during any working process
  13594. property MinimalTime: TSynMonitorOneTime read fMinimalTime;
  13595. /// the time spent in average during any working process
  13596. property AverageTime: TSynMonitorOneTime read fAverageTime;
  13597. /// the highest time spent during any working process
  13598. property MaximalTime: TSynMonitorOneTime read fMaximalTime;
  13599. /// average of how many tasks did occur per second
  13600. property PerSec: QWord read fPerSec;
  13601. /// how many errors did occur during the processing
  13602. property Errors: TSynMonitorCount read fInternalErrors;
  13603. /// information about the last error which occured during the processing
  13604. property LastError: variant read fLastInternalError;
  13605. end;
  13606. /// references a TSynMonitor instance
  13607. PSynMonitor = ^TSynMonitor;
  13608. /// handle generic process statistic with a processing data size and bandwitdh
  13609. TSynMonitorWithSize = class(TSynMonitor)
  13610. protected
  13611. fSize: TSynMonitorSize;
  13612. fThroughput: TSynMonitorThroughput;
  13613. procedure LockedPerSecProperties; override;
  13614. procedure LockedSum(another: TSynMonitor); override;
  13615. public
  13616. /// initialize the instance nested class properties
  13617. constructor Create; override;
  13618. /// finalize the instance
  13619. destructor Destroy; override;
  13620. /// increase the internal size counter
  13621. // - thread-safe method
  13622. procedure AddSize(const Bytes: QWord);
  13623. published
  13624. /// how many total data has been hanlded during all working process
  13625. property Size: TSynMonitorSize read fSize;
  13626. /// data processing bandwith, returned as B/KB/MB per second
  13627. property Throughput: TSynMonitorThroughput read fThroughput;
  13628. end;
  13629. /// handle generic process statistic with a incoming and outgoing processing
  13630. // data size and bandwitdh
  13631. TSynMonitorInputOutput = class(TSynMonitor)
  13632. protected
  13633. fInput: TSynMonitorSize;
  13634. fOutput: TSynMonitorSize;
  13635. fInputThroughput: TSynMonitorThroughput;
  13636. fOutputThroughput: TSynMonitorThroughput;
  13637. procedure LockedPerSecProperties; override;
  13638. procedure LockedSum(another: TSynMonitor); override;
  13639. public
  13640. /// increase the internal size counters
  13641. // - thread-safe method
  13642. procedure AddSize(const Incoming, Outgoing: QWord);
  13643. published
  13644. /// initialize the instance nested class properties
  13645. constructor Create; override;
  13646. /// finalize the instance
  13647. destructor Destroy; override;
  13648. /// how many data has been received
  13649. property Input: TSynMonitorSize read fInput;
  13650. /// how many data has been sent back
  13651. property Output: TSynMonitorSize read fOutput;
  13652. /// incoming data processing bandwith, returned as B/KB/MB per second
  13653. property InputThroughput: TSynMonitorThroughput read fInputThroughput;
  13654. /// outgoing data processing bandwith, returned as B/KB/MB per second
  13655. property OutputThroughput: TSynMonitorThroughput read fOutputThroughput;
  13656. end;
  13657. /// could monitor a standard Server
  13658. // - including Input/Output statistics and connected Clients count
  13659. TSynMonitorServer = class(TSynMonitorInputOutput)
  13660. protected
  13661. fCurrentRequestCount: integer;
  13662. fClientsCurrent: TSynMonitorOneCount;
  13663. fClientsMax: TSynMonitorOneCount;
  13664. public
  13665. /// update ClientsCurrent and ClientsMax
  13666. // - thread-safe method
  13667. procedure ClientConnect;
  13668. /// update ClientsCurrent and ClientsMax
  13669. // - thread-safe method
  13670. procedure ClientDisconnect;
  13671. /// update ClientsCurrent to 0
  13672. // - thread-safe method
  13673. procedure ClientDisconnectAll;
  13674. /// retrieve the number of connected clients
  13675. // - thread-safe method
  13676. function GetClientsCurrent: TSynMonitorOneCount;
  13677. /// how many concurrent requests are currently processed
  13678. // - returns the updated number of requests
  13679. // - thread-safe method
  13680. function AddCurrentRequestCount(diff: integer): integer;
  13681. published
  13682. /// current count of connected clients
  13683. property ClientsCurrent: TSynMonitorOneCount read fClientsCurrent;
  13684. /// max count of connected clients
  13685. property ClientsMax: TSynMonitorOneCount read fClientsMax;
  13686. /// how many concurrent requests are currently processed
  13687. // - modified via AddCurrentRequestCount() in TSQLRestServer.URI()
  13688. property CurrentRequestCount: integer read fCurrentRequestCount;
  13689. end;
  13690. {$M-}
  13691. /// a list of simple process statistics
  13692. TSynMonitorObjArray = array of TSynMonitor;
  13693. /// a list of data process statistics
  13694. TSynMonitorWithSizeObjArray = array of TSynMonitorWithSize;
  13695. /// a list of incoming/outgoing data process statistics
  13696. TSynMonitorInputOutputObjArray = array of TSynMonitorInputOutput;
  13697. /// class-reference type (metaclass) of a process statistic information
  13698. TSynMonitorClass = class of TSynMonitor;
  13699. /// value object able to gather information about the current system memory
  13700. TSynMonitorMemory = class(TSynPersistent)
  13701. protected
  13702. FAllocatedUsed: TSynMonitorOneSize;
  13703. FAllocatedReserved: TSynMonitorOneSize;
  13704. FMemoryLoadPercent: integer;
  13705. FPhysicalMemoryFree: TSynMonitorOneSize;
  13706. FVirtualMemoryFree: TSynMonitorOneSize;
  13707. FPagingFileTotal: TSynMonitorOneSize;
  13708. FPhysicalMemoryTotal: TSynMonitorOneSize;
  13709. FVirtualMemoryTotal: TSynMonitorOneSize;
  13710. FPagingFileFree: TSynMonitorOneSize;
  13711. fLastMemoryInfoRetrievedTix: cardinal;
  13712. procedure RetrieveMemoryInfo; virtual;
  13713. function GetAllocatedUsed: TSynMonitorOneSize;
  13714. function GetAllocatedReserved: TSynMonitorOneSize;
  13715. function GetMemoryLoadPercent: integer;
  13716. function GetPagingFileFree: TSynMonitorOneSize;
  13717. function GetPagingFileTotal: TSynMonitorOneSize;
  13718. function GetPhysicalMemoryFree: TSynMonitorOneSize;
  13719. function GetPhysicalMemoryTotal: TSynMonitorOneSize;
  13720. function GetVirtualMemoryFree: TSynMonitorOneSize;
  13721. function GetVirtualMemoryTotal: TSynMonitorOneSize;
  13722. public
  13723. /// initialize the class, and its nested TSynMonitorOneSize instances
  13724. constructor Create; override;
  13725. /// finalize the class, and its nested TSynMonitorOneSize instances
  13726. destructor Destroy; override;
  13727. /// some text corresponding to current 'free/total' memory information
  13728. // - returns e.g. '10.3 GB / 15.6 GB'
  13729. class function FreeAsText: RawUTF8;
  13730. {$ifndef NOVARIANTS}
  13731. /// fill a TDocVariant with the current system memory information
  13732. // - numbers would be given in KB (Bytes shl 10)
  13733. class function ToVariant: variant;
  13734. {$endif}
  13735. published
  13736. /// Total of allocated memory used by the program
  13737. property AllocatedUsed: TSynMonitorOneSize read GetAllocatedUsed;
  13738. /// Total of allocated memory reserved by the program
  13739. property AllocatedReserved: TSynMonitorOneSize read GetAllocatedReserved;
  13740. /// Percent of memory in use for the system
  13741. property MemoryLoadPercent: integer read GetMemoryLoadPercent;
  13742. /// Total of physical memory for the system
  13743. property PhysicalMemoryTotal: TSynMonitorOneSize read GetPhysicalMemoryTotal;
  13744. /// Free of physical memory for the system
  13745. property PhysicalMemoryFree: TSynMonitorOneSize read GetPhysicalMemoryFree;
  13746. /// Total of paging file for the system
  13747. property PagingFileTotal: TSynMonitorOneSize read GetPagingFileTotal;
  13748. /// Free of paging file for the system
  13749. property PagingFileFree: TSynMonitorOneSize read GetPagingFileFree;
  13750. {$ifdef MSWINDOWS}
  13751. /// Total of virtual memory for the system
  13752. // - property not defined under Linux, since not applying to this OS
  13753. property VirtualMemoryTotal: TSynMonitorOneSize read GetVirtualMemoryTotal;
  13754. /// Free of virtual memory for the system
  13755. // - property not defined under Linux, since not applying to this OS
  13756. property VirtualMemoryFree: TSynMonitorOneSize read GetVirtualMemoryFree;
  13757. {$endif}
  13758. end;
  13759. /// value object able to gather information about a system drive
  13760. TSynMonitorDisk = class(TSynPersistent)
  13761. protected
  13762. fName: RawUTF8;
  13763. fVolumeName: RawUTF8;
  13764. fAvailableSize: TSynMonitorOneSize;
  13765. fFreeSize: TSynMonitorOneSize;
  13766. fTotalSize: TSynMonitorOneSize;
  13767. fLastDiskInfoRetrievedTix: cardinal;
  13768. procedure RetrieveDiskInfo; virtual;
  13769. function GetName: RawUTF8;
  13770. function GetAvailable: TSynMonitorOneSize;
  13771. function GetFree: TSynMonitorOneSize;
  13772. function GetTotal: TSynMonitorOneSize;
  13773. public
  13774. /// initialize the class, and its nested TSynMonitorOneSize instances
  13775. constructor Create; override;
  13776. /// finalize the class, and its nested TSynMonitorOneSize instances
  13777. destructor Destroy; override;
  13778. /// some text corresponding to current 'free/total' disk information
  13779. // - could return e.g. 'D: 64.4 GB / 213.4 GB'
  13780. class function FreeAsText: RawUTF8;
  13781. published
  13782. /// the disk name
  13783. property Name: RawUTF8 read GetName;
  13784. /// the volume name
  13785. property VolumeName: RawUTF8 read fVolumeName write fVolumeName;
  13786. /// space currently available on this disk for the current user
  13787. // - may be less then FreeSize, if user quotas are specified
  13788. property AvailableSize: TSynMonitorOneSize read GetAvailable;
  13789. /// free space currently available on this disk
  13790. property FreeSize: TSynMonitorOneSize read GetFree;
  13791. /// total space
  13792. property TotalSize: TSynMonitorOneSize read GetTotal;
  13793. end;
  13794. { ******************* cross-cutting classes and functions ***************** }
  13795. type
  13796. /// an abstract ancestor, for implementing a custom TInterfacedObject like class
  13797. // - by default, will do nothing: no instance would be retrieved by
  13798. // QueryInterface unless the VirtualQueryInterface protected method is
  13799. // overriden, and _AddRef/_Release methods would call VirtualAddRef and
  13800. // VirtualRelease pure abstract methods
  13801. // - using this class will leverage the signature difference between Delphi
  13802. // and FPC, among all supported platforms
  13803. // - the class includes a RefCount integer field
  13804. TSynInterfacedObject = class(TObject,IUnknown)
  13805. protected
  13806. fRefCount: integer;
  13807. // returns E_NOINTERFACE
  13808. function VirtualQueryInterface(const IID: TGUID; out Obj): HResult; virtual;
  13809. // always return 1 for a "non allocated" instance (0 triggers release)
  13810. function VirtualAddRef: Integer; virtual; abstract;
  13811. function VirtualRelease: Integer; virtual; abstract;
  13812. {$ifdef FPC}
  13813. function QueryInterface(
  13814. {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID;
  13815. out Obj): longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  13816. function _AddRef: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  13817. function _Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  13818. {$else}
  13819. function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  13820. function _AddRef: Integer; stdcall;
  13821. function _Release: Integer; stdcall;
  13822. {$endif}
  13823. public
  13824. /// the associated reference count
  13825. property RefCount: integer read fRefCount write fRefCount;
  13826. end;
  13827. {$ifdef MSWINDOWS}
  13828. {$ifndef DELPHI5OROLDER}
  13829. /// a simple class which will set FPU exception flags for a code block
  13830. // - using an IUnknown interface to let the compiler auto-generate a
  13831. // try..finally block statement to reset the FPU exception register
  13832. // - to be used e.g. as such:
  13833. // !begin
  13834. // ! TSynFPUException.ForLibrayCode;
  13835. // ! ... now FPU exceptions will be ignored
  13836. // ! ... so here it is safe to call external libray code
  13837. // !end; // now FPU exception will be reset as with standard Delphi
  13838. // - it will avoid any unexpected invalid floating point operation in Delphi
  13839. // code, whereas it was in fact triggerred in some external library code
  13840. TSynFPUException = class(TSynInterfacedObject)
  13841. protected
  13842. fExpected8087, fSaved8087: word;
  13843. function VirtualAddRef: Integer; override;
  13844. function VirtualRelease: Integer; override;
  13845. public
  13846. /// internal constructor
  13847. // - do not call this constructor directly, but rather use
  13848. // ForLibraryCode/ForDelphiCode class methods
  13849. // - flags are $1332 for Delphi, or $133F for library (mask all exceptions)
  13850. constructor Create(Expected8087Flag: word); reintroduce;
  13851. /// after this method call, all FPU exceptions will be ignored
  13852. // - until the method finishes (a try..finally block is generated by
  13853. // the compiler), then FPU exceptions will be
  13854. // - you have to put this e.g. before calling an external libray
  13855. // - this method is thread-safe and re-entrant (by reference-counting)
  13856. class function ForLibraryCode: IUnknown;
  13857. /// after this method call, all FPU exceptions will be enabled
  13858. // - this is the Delphi normal behavior
  13859. // - until the method finishes (a try..finally block is generated by
  13860. // the compiler)
  13861. // - you have to put this e.g. before running an external libray
  13862. // - this method is thread-safe and re-entrant (by reference-counting)
  13863. class function ForDelphiCode: IUnknown;
  13864. end;
  13865. {$endif DELPHI5OROLDER}
  13866. {$endif MSWINDOWS}
  13867. /// interface for TAutoFree to register another TObject instance
  13868. // to an existing IAutoFree local variable
  13869. IAutoFree = interface
  13870. procedure Another(var objVar; obj: TObject);
  13871. end;
  13872. /// simple reference-counted storage for local objects
  13873. // - be aware that it won't implement a full ARC memory model, but may be
  13874. // just used to avoid writing some try ... finally blocks on local variables
  13875. // - use with caution, only on well defined local scope
  13876. TAutoFree = class(TInterfacedObject,IAutoFree)
  13877. protected
  13878. fObject: TObject;
  13879. fObjectList: array of TObject;
  13880. public
  13881. /// initialize the TAutoFree class for one local variable
  13882. // - do not call this constructor, but class function One() instead
  13883. constructor Create(var localVariable; obj: TObject); reintroduce; overload;
  13884. /// initialize the TAutoFree class for several local variables
  13885. // - do not call this constructor, but class function Several() instead
  13886. constructor Create(const varObjPairs: array of pointer); reintroduce; overload;
  13887. /// protect one local TObject variable instance life time
  13888. // - for instance, instead of writing:
  13889. // !var myVar: TMyClass;
  13890. // !begin
  13891. // ! myVar := TMyClass.Create;
  13892. // ! try
  13893. // ! ... use myVar
  13894. // ! finally
  13895. // ! myVar.Free;
  13896. // ! end;
  13897. // !end;
  13898. // - you may write:
  13899. // !var myVar: TMyClass;
  13900. // !begin
  13901. // ! TAutoFree.One(myVar,TMyClass.Create);
  13902. // ! ... use myVar
  13903. // !end; // here myVar will be released
  13904. // - warning: under FPC, you should assign the result of this method to a local
  13905. // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
  13906. class function One(var localVariable; obj: TObject): IAutoFree;
  13907. /// protect several local TObject variable instances life time
  13908. // - specified as localVariable/objectInstance pairs
  13909. // - you may write:
  13910. // !var var1,var2: TMyClass;
  13911. // !begin
  13912. // ! TAutoFree.Several([
  13913. // ! @var1,TMyClass.Create,
  13914. // ! @var2,TMyClass.Create]);
  13915. // ! ... use var1 and var2
  13916. // !end; // here var1 and var2 will be released
  13917. // - warning: under FPC, you should assign the result of this method to a local
  13918. // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602
  13919. class function Several(const varObjPairs: array of pointer): IAutoFree;
  13920. /// protect another TObject variable to an existing IAutoFree instance life time
  13921. // - you may write:
  13922. // !var var1,var2: TMyClass;
  13923. // ! auto: IAutoFree;
  13924. // !begin
  13925. // ! auto := TAutoFree.One(var1,TMyClass.Create);,
  13926. // ! .... do something
  13927. // ! auto.Another(var2,TMyClass.Create);
  13928. // ! ... use var1 and var2
  13929. // !end; // here var1 and var2 will be released
  13930. procedure Another(var localVariable; obj: TObject);
  13931. /// will finalize the associated TObject instances
  13932. // - note that releasing the TObject instances won't be protected, so
  13933. // any exception here may induce a memory leak: use only with "safe"
  13934. // simple objects, e.g. mORMot's TSQLRecord
  13935. destructor Destroy; override;
  13936. end;
  13937. {$ifdef DELPHI5OROLDER} // IAutoLocker -> internal error C3517 under Delphi 5 :(
  13938. TAutoLocker = class
  13939. {$else}
  13940. /// an interface used by TAutoLocker to protect multi-thread execution
  13941. IAutoLocker = interface
  13942. ['{97559643-6474-4AD3-AF72-B9BB84B4955D}']
  13943. /// enter the mutex
  13944. // - any call to Enter should be ended with a call to Leave, and
  13945. // protected by a try..finally block, as such:
  13946. // !begin
  13947. // ! ... // unsafe code
  13948. // ! fSharedAutoLocker.Enter;
  13949. // ! try
  13950. // ! ... // thread-safe code
  13951. // ! finally
  13952. // ! fSharedAutoLocker.Leave;
  13953. // ! end;
  13954. // !end;
  13955. procedure Enter;
  13956. /// leave the mutex
  13957. // - any call to Leave should be preceded with a call to Enter
  13958. procedure Leave;
  13959. /// will enter the mutex until the IUnknown reference is released
  13960. // - using an IUnknown interface to let the compiler auto-generate a
  13961. // try..finally block statement to release the lock for the code block
  13962. // - could be used as such under Delphi:
  13963. // !begin
  13964. // ! ... // unsafe code
  13965. // ! fSharedAutoLocker.ProtectMethod;
  13966. // ! ... // thread-safe code
  13967. // !end; // local hidden IUnknown will release the lock for the method
  13968. // - warning: under FPC, you should assign its result to a local variable -
  13969. // see bug http://bugs.freepascal.org/view.php?id=26602
  13970. // !var LockFPC: IUnknown;
  13971. // !begin
  13972. // ! ... // unsafe code
  13973. // ! LockFPC := fSharedAutoLocker.ProtectMethod;
  13974. // ! ... // thread-safe code
  13975. // !end; // LockFPC will release the lock for the method
  13976. // or
  13977. // !begin
  13978. // ! ... // unsafe code
  13979. // ! with fSharedAutoLocker.ProtectMethod do begin
  13980. // ! ... // thread-safe code
  13981. // ! end; // local hidden IUnknown will release the lock for the method
  13982. // !end;
  13983. function ProtectMethod: IUnknown;
  13984. /// gives an access to the internal low-level TSynLocker instance used
  13985. function Safe: PSynLocker;
  13986. end;
  13987. /// reference-counted block code critical section
  13988. // - you can use one instance of this to protect multi-threaded execution
  13989. // - the main class may initialize a IAutoLocker property in Create, then call
  13990. // IAutoLocker.ProtectMethod in any method to make its execution thread safe
  13991. // - this class inherits from TInterfacedObjectWithCustomCreate so you
  13992. // could define one published property of a mORMot.pas' TInjectableObject
  13993. // as IAutoLocker so that this class may be automatically injected
  13994. // - you may use the inherited TAutoLockerDebug class, as defined in SynLog.pas,
  13995. // to debug unexpected race conditions due to such critical sections
  13996. TAutoLocker = class(TInterfacedObjectWithCustomCreate,IAutoLocker)
  13997. {$endif DELPHI5OROLDER}
  13998. protected
  13999. fSafe: TSynLocker;
  14000. public
  14001. /// initialize the mutex
  14002. constructor Create; {$ifndef DELPHI5OROLDER} override; {$endif}
  14003. /// finalize the mutex
  14004. destructor Destroy; override;
  14005. /// will enter the mutex until the IUnknown reference is released
  14006. // - could be used as such under Delphi:
  14007. // !begin
  14008. // ! ... // unsafe code
  14009. // ! fSharedAutoLocker.ProtectMethod;
  14010. // ! ... // thread-safe code
  14011. // !end; // local hidden IUnknown will release the lock for the method
  14012. // - warning: under FPC, you should assign its result to a local variable -
  14013. // see bug http://bugs.freepascal.org/view.php?id=26602
  14014. // !var LockFPC: IUnknown;
  14015. // !begin
  14016. // ! ... // unsafe code
  14017. // ! LockFPC := fSharedAutoLocker.ProtectMethod;
  14018. // ! ... // thread-safe code
  14019. // !end; // LockFPC will release the lock for the method
  14020. // or
  14021. // !begin
  14022. // ! ... // unsafe code
  14023. // ! with fSharedAutoLocker.ProtectMethod do begin
  14024. // ! ... // thread-safe code
  14025. // ! end; // local hidden IUnknown will release the lock for the method
  14026. // !end;
  14027. function ProtectMethod: IUnknown;
  14028. /// enter the mutex
  14029. // - any call to Enter should be ended with a call to Leave, and
  14030. // protected by a try..finally block, as such:
  14031. // !begin
  14032. // ! ... // unsafe code
  14033. // ! fSharedAutoLocker.Enter;
  14034. // ! try
  14035. // ! ... // thread-safe code
  14036. // ! finally
  14037. // ! fSharedAutoLocker.Leave;
  14038. // ! end;
  14039. // !end;
  14040. procedure Enter; virtual;
  14041. /// leave the mutex
  14042. procedure Leave; virtual;
  14043. /// access to the locking methods of this instance
  14044. function Safe: PSynLocker;
  14045. end;
  14046. /// the current state of a TBlockingProcess instance
  14047. TBlockingEvent = (evNone,evWaiting,evTimeOut,evRaised);
  14048. {$M+}
  14049. /// a semaphore used to wait for some process to be finished
  14050. // - used e.g. by TBlockingCallback in mORMot.pas
  14051. // - once created, process would block via a WaitFor call, which would be
  14052. // released when NotifyFinished is called by the process background thread
  14053. TBlockingProcess = class(TEvent)
  14054. protected
  14055. fTimeOutMs: integer;
  14056. fEvent: TBlockingEvent;
  14057. fSafe: PSynLocker;
  14058. fOwnedSafe: TAutoLocker;
  14059. procedure ResetInternal; virtual; // override to reset associated params
  14060. public
  14061. /// initialize the semaphore instance
  14062. // - specify a time out millliseconds period after which blocking execution
  14063. // should be handled as failure (if 0 is set, default 3000 would be used)
  14064. // - an associated mutex shall be supplied
  14065. constructor Create(aTimeOutMs: integer; const aSafe: TSynLocker); reintroduce; overload; virtual;
  14066. /// initialize the semaphore instance
  14067. // - specify a time out millliseconds period after which blocking execution
  14068. // should be handled as failure (if 0 is set, default 3000 would be used)
  14069. // - an associated mutex would be created and owned by this instance
  14070. constructor Create(aTimeOutMs: integer); reintroduce; overload; virtual;
  14071. /// finalize the instance
  14072. destructor Destroy; override;
  14073. /// called to wait for NotifyFinished() to be called, or trigger timeout
  14074. // - returns the final state of the process, i.e. evRaised or evTimeOut
  14075. function WaitFor: TBlockingEvent; reintroduce; overload; virtual;
  14076. /// called to wait for NotifyFinished() to be called, or trigger timeout
  14077. // - returns the final state of the process, i.e. evRaised or evTimeOut
  14078. function WaitFor(TimeOutMS: integer): TBlockingEvent; reintroduce; overload;
  14079. /// should be called by the background process when it is finished
  14080. // - the caller would then let its WaitFor method return
  14081. // - returns TRUE on success (i.e. status was not evRaised or evTimeout)
  14082. // - if the instance is already locked (e.g. when retrieved from
  14083. // TBlockingProcessPool.FromCallLocked), you may set alreadyLocked=TRUE
  14084. function NotifyFinished(alreadyLocked: boolean=false): boolean; virtual;
  14085. /// just a wrapper to reset the internal Event state to evNone
  14086. // - may be used to re-use the same TBlockingProcess instance, after
  14087. // a successfull WaitFor/NotifyFinished process
  14088. // - returns TRUE on success (i.e. status was not evWaiting), setting
  14089. // the current state to evNone, and the Call property to 0
  14090. // - if there is a WaitFor currently in progress, returns FALSE
  14091. function Reset: boolean; virtual;
  14092. /// just a wrapper around fSafe^.Lock
  14093. procedure Lock;
  14094. /// just a wrapper around fSafe^.Unlock
  14095. procedure Unlock;
  14096. published
  14097. /// the current state of process
  14098. // - use Reset method to re-use this instance after a WaitFor process
  14099. property Event: TBlockingEvent read fEvent;
  14100. /// the time out period, in ms, as defined at constructor level
  14101. property TimeOutMs: integer read fTimeOutMS;
  14102. end;
  14103. {$M-}
  14104. /// used to identify each TBlockingProcessPool call
  14105. // - allow to match a given TBlockingProcessPoolItem semaphore
  14106. TBlockingProcessPoolCall = type integer;
  14107. /// a semaphore used in the TBlockingProcessPool
  14108. // - such semaphore have a Call field to identify each execution
  14109. TBlockingProcessPoolItem = class(TBlockingProcess)
  14110. protected
  14111. fCall: TBlockingProcessPoolCall;
  14112. procedure ResetInternal; override;
  14113. published
  14114. /// an unique identifier, when owned by a TBlockingProcessPool
  14115. // - Reset would restore this field to its 0 default value
  14116. property Call: TBlockingProcessPoolCall read fCall;
  14117. end;
  14118. /// class-reference type (metaclass) of a TBlockingProcess
  14119. TBlockingProcessPoolItemClass = class of TBlockingProcessPoolItem;
  14120. /// manage a pool of TBlockingProcessPoolItem instances
  14121. // - each call will be identified via a TBlockingProcessPoolCall unique value
  14122. // - to be used to emulate e.g. blocking execution from an asynchronous
  14123. // event-driven DDD process
  14124. // - it would also allow to re-use TEvent system resources
  14125. TBlockingProcessPool = class(TSynPersistent)
  14126. protected
  14127. fClass: TBlockingProcessPoolItemClass;
  14128. fPool: TObjectListLocked;
  14129. fCallCounter: TBlockingProcessPoolCall; // set TBlockingProcessPoolItem.Call
  14130. public
  14131. /// initialize the pool, for a given implementation class
  14132. constructor Create(aClass: TBlockingProcessPoolItemClass=nil); reintroduce;
  14133. /// finalize the pool
  14134. // - would also force all pending WaitFor to trigger a evTimeOut
  14135. destructor Destroy; override;
  14136. /// book a TBlockingProcess from the internal pool
  14137. // - returns nil on error (e.g. the instance is destroying)
  14138. // - or returns the blocking process instance corresponding to this call;
  14139. // its Call property would identify the call for the asynchronous callback,
  14140. // then after WaitFor, the Reset method should be run to release the mutex
  14141. // for the pool
  14142. function NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem; virtual;
  14143. /// retrieve a TBlockingProcess from its call identifier
  14144. // - may be used e.g. from the callback of the asynchronous process
  14145. // to set some additional parameters to the inherited TBlockingProcess,
  14146. // then call NotifyFinished to release the caller WaitFor
  14147. // - if leavelocked is TRUE, the returned instance would be locked: caller
  14148. // should execute result.Unlock or NotifyFinished(true) after use
  14149. function FromCall(call: TBlockingProcessPoolCall;
  14150. locked: boolean=false): TBlockingProcessPoolItem; virtual;
  14151. end;
  14152. {$ifndef DELPHI5OROLDER} // internal error C3517 under Delphi 5 :(
  14153. {$ifndef NOVARIANTS}
  14154. /// ref-counted interface for thread-safe access to a TDocVariant document
  14155. // - is implemented e.g. by TLockedDocVariant, for IoC/DI resolution
  14156. // - fast and safe storage of any JSON-like object, as property/value pairs,
  14157. // or a JSON-like array, as values
  14158. ILockedDocVariant = interface
  14159. ['{CADC2C20-3F5D-4539-9D23-275E833A86F3}']
  14160. function GetValue(const Name: RawUTF8): Variant;
  14161. procedure SetValue(const Name: RawUTF8; const Value: Variant);
  14162. /// check and return a given property by name
  14163. // - returns TRUE and fill Value with the value associated with the supplied
  14164. // Name, using an internal lock for thread-safety
  14165. // - returns FALSE if the Name was not found, releasing the internal lock:
  14166. // use ExistsOrLock() if you want to add the missing value
  14167. function Exists(const Name: RawUTF8; out Value: Variant): boolean;
  14168. /// check and return a given property by name
  14169. // - returns TRUE and fill Value with the value associated with the supplied
  14170. // Name, using an internal lock for thread-safety
  14171. // - returns FALSE and set the internal lock if Name does not exist:
  14172. // caller should then release the lock via ReplaceAndUnlock()
  14173. function ExistsOrLock(const Name: RawUTF8; out Value: Variant): boolean;
  14174. /// set a value by property name, and set a local copy
  14175. // - could be used as such, for implementing a thread-safe cache:
  14176. // ! if not cache.ExistsOrLock('prop',local) then
  14177. // ! cache.ReplaceAndUnlock('prop',newValue,local);
  14178. // - call of this method should have been precedeed by ExistsOrLock()
  14179. // returning false, i.e. be executed on a locked instance
  14180. procedure ReplaceAndUnlock(const Name: RawUTF8; const Value: Variant; out LocalValue: Variant);
  14181. /// add an existing property value to the given TDocVariant document object
  14182. // - returns TRUE and add the Name/Value pair to Obj if Name is existing,
  14183. // using an internal lock for thread-safety
  14184. // - returns FALSE if Name is not existing in the stored document, and
  14185. // lock the internal storage: caller should eventually release the lock
  14186. // via AddNewPropAndUnlock()
  14187. // - could be used as such, for implementing a thread-safe cache:
  14188. // ! if not cache.AddExistingPropOrLock('Articles',Scope) then
  14189. // ! cache.AddNewPropAndUnlock('Articles',GetArticlesFromDB,Scope);
  14190. // here GetArticlesFromDB would occur inside the main lock
  14191. function AddExistingPropOrLock(const Name: RawUTF8; var Obj: variant): boolean;
  14192. /// add a property value to the given TDocVariant document object and
  14193. // to the internal stored document, then release a previous lock
  14194. // - call of this method should have been precedeed by AddExistingPropOrLock()
  14195. // returning false, i.e. be executed on a locked instance
  14196. procedure AddNewPropAndUnlock(const Name: RawUTF8; const Value: variant; var Obj: variant);
  14197. /// add an existing property value to the given TDocVariant document object
  14198. // - returns TRUE and add the Name/Value pair to Obj if Name is existing
  14199. // - returns FALSE if Name is not existing in the stored document
  14200. // - this method would use a lock during the Name lookup, but would always
  14201. // release the lock, even if returning FALSE (see AddExistingPropOrLock)
  14202. function AddExistingProp(const Name: RawUTF8; var Obj: variant): boolean;
  14203. /// add a property value to the given TDocVariant document object
  14204. // - this method would not expect the resource to be locked when called,
  14205. // as with AddNewPropAndUnlock
  14206. // - will use the internal lock for thread-safety
  14207. // - if the Name is already existing, would update/change the existing value
  14208. // - could be used as such, for implementing a thread-safe cache:
  14209. // ! if not cache.AddExistingProp('Articles',Scope) then
  14210. // ! cache.AddNewProp('Articles',GetArticlesFromDB,Scope);
  14211. // here GetArticlesFromDB would occur outside the main lock
  14212. procedure AddNewProp(const Name: RawUTF8; const Value: variant; var Obj: variant);
  14213. /// append a value to the internal TDocVariant document array
  14214. // - you should not use this method in conjunction with other document-based
  14215. // alternatives, like Exists/AddExistingPropOrLock or AddExistingProp
  14216. procedure AddItem(const Value: variant);
  14217. /// makes a thread-safe copy of the internal TDocVariant document object or array
  14218. function Copy: variant;
  14219. /// delete all stored properties
  14220. procedure Clear;
  14221. /// save the stored values as UTF-8 encoded JSON Object
  14222. function ToJSON(HumanReadable: boolean=false): RawUTF8;
  14223. /// the document fields would be safely accessed via this property
  14224. // - this is the main entry point of this storage
  14225. // - would raise an EDocVariant exception if Name does not exist at reading
  14226. // - implementation class would make a thread-safe copy of the variant value
  14227. property Value[const Name: RawUTF8]: Variant read GetValue write SetValue; default;
  14228. end;
  14229. /// allows thread-safe access to a TDocVariant document
  14230. // - this class inherits from TInterfacedObjectWithCustomCreate so you
  14231. // could define one published property of a mORMot.pas' TInjectableObject
  14232. // as ILockedDocVariant so that this class may be automatically injected
  14233. TLockedDocVariant = class(TInterfacedObjectWithCustomCreate,ILockedDocVariant)
  14234. protected
  14235. fValue: TDocVariantData;
  14236. fLock: TAutoLocker;
  14237. function GetValue(const Name: RawUTF8): Variant;
  14238. procedure SetValue(const Name: RawUTF8; const Value: Variant);
  14239. public
  14240. /// initialize the thread-safe document with a fast TDocVariant
  14241. // - i.e. call Create(true) aka Create(JSON_OPTIONS[true])
  14242. // - will be the TInterfacedObjectWithCustomCreate default constructor,
  14243. // called e.g. during IoC/DI resolution
  14244. constructor Create; overload; override;
  14245. /// initialize the thread-safe document storage
  14246. constructor Create(FastStorage: boolean); reintroduce; overload;
  14247. /// initialize the thread-safe document storage with the corresponding options
  14248. constructor Create(options: TDocVariantOptions); reintroduce; overload;
  14249. /// finalize the storage
  14250. destructor Destroy; override;
  14251. /// check and return a given property by name
  14252. function Exists(const Name: RawUTF8; out Value: Variant): boolean;
  14253. /// check and return a given property by name
  14254. // - this version
  14255. function ExistsOrLock(const Name: RawUTF8; out Value: Variant): boolean;
  14256. /// set a value by property name, and set a local copy
  14257. procedure ReplaceAndUnlock(const Name: RawUTF8; const Value: Variant; out LocalValue: Variant);
  14258. /// add an existing property value to the given TDocVariant document object
  14259. // - returns TRUE and add the Name/Value pair to Obj if Name is existing
  14260. // - returns FALSE if Name is not existing in the stored document
  14261. function AddExistingPropOrLock(const Name: RawUTF8; var Obj: variant): boolean;
  14262. /// add a property value to the given TDocVariant document object and
  14263. // to the internal stored document
  14264. procedure AddNewPropAndUnlock(const Name: RawUTF8; const Value: variant; var Obj: variant);
  14265. /// add an existing property value to the given TDocVariant document object
  14266. // - returns TRUE and add the Name/Value pair to Obj if Name is existing
  14267. // - returns FALSE if Name is not existing in the stored document
  14268. // - this method would use a lock during the Name lookup, but would always
  14269. // release the lock, even if returning FALSE (see AddExistingPropOrLock)
  14270. function AddExistingProp(const Name: RawUTF8; var Obj: variant): boolean;
  14271. /// add a property value to the given TDocVariant document object
  14272. // - this method would not expect the resource to be locked when called,
  14273. // as with AddNewPropAndUnlock
  14274. // - will use the internal lock for thread-safety
  14275. // - if the Name is already existing, would update/change the existing value
  14276. procedure AddNewProp(const Name: RawUTF8; const Value: variant; var Obj: variant);
  14277. /// append a value to the internal TDocVariant document array
  14278. procedure AddItem(const Value: variant);
  14279. /// makes a thread-safe copy of the internal TDocVariant document object or array
  14280. function Copy: variant;
  14281. /// delete all stored properties
  14282. procedure Clear;
  14283. /// save the stored value as UTF-8 encoded JSON Object
  14284. // - implemented as just a wrapper around VariantSaveJSON()
  14285. function ToJSON(HumanReadable: boolean=false): RawUTF8;
  14286. /// the document fields would be safely accessed via this property
  14287. // - would raise an EDocVariant exception if Name does not exist
  14288. // - result variant is returned as a copy, not as varByRef, since a copy
  14289. // will definitively be more thread safe
  14290. property Value[const Name: RawUTF8]: Variant read GetValue write SetValue; default;
  14291. end;
  14292. {$endif}
  14293. {$endif}
  14294. type
  14295. TSynBackgroundThreadProcess = class;
  14296. /// event callback executed periodically by TSynBackgroundThreadProcess
  14297. // - Event is wrTimeout after the OnProcessMS waiting period
  14298. // - Event is wrSignaled if ProcessEvent.SetEvent has been called
  14299. TOnSynBackgroundThreadProcess = procedure(Sender: TSynBackgroundThreadProcess;
  14300. Event: TWaitResult) of object;
  14301. /// TThread able to run a method at a given periodic pace
  14302. TSynBackgroundThreadProcess = class(TSynBackgroundThreadAbstract)
  14303. protected
  14304. fOnProcess: TOnSynBackgroundThreadProcess;
  14305. fOnException: TNotifyEvent;
  14306. fOnProcessMS: cardinal;
  14307. fStats: TSynMonitor;
  14308. procedure ExecuteLoop; override;
  14309. public
  14310. /// initialize the thread for a periodic task processing
  14311. // - aOnProcess would be called when ProcessEvent.SetEvent is called or
  14312. // aOnProcessMS milliseconds period was elapse since last process
  14313. // - if aOnProcessMS is 0, will wait until ProcessEvent.SetEvent is called
  14314. // - you could define some callbacks to nest the thread execution, e.g.
  14315. // assigned to TSQLRestServer.BeginCurrentThread/EndCurrentThread
  14316. constructor Create(const aThreadName: RawUTF8;
  14317. aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal;
  14318. aOnBeforeExecute: TNotifyThreadEvent=nil;
  14319. aOnAfterExecute: TNotifyThreadEvent=nil;
  14320. aStats: TSynMonitorClass = nil); reintroduce; virtual;
  14321. /// finalize the thread
  14322. destructor Destroy; override;
  14323. /// access to the implementation event of the periodic task
  14324. property OnProcess: TOnSynBackgroundThreadProcess read fOnProcess;
  14325. /// event callback executed when OnProcess did raise an exception
  14326. // - supplied Sender parameter is the raised Exception instance
  14327. property OnException: TNotifyEvent read fOnException write fOnException;
  14328. published
  14329. /// access to the delay, in milliseconds, of the periodic task processing
  14330. property OnProcessMS: cardinal read fOnProcessMS write fOnProcessMS;
  14331. /// processing statistics
  14332. property Stats: TSynMonitor read fStats;
  14333. end;
  14334. type
  14335. /// class-reference type (metaclass) of an authentication class
  14336. TSynAuthenticationClass = class of TSynAuthenticationAbstract;
  14337. /// abstract authentication class, implementing safe token/challenge security
  14338. // and a list of active sessions
  14339. // - do not use this class, but plain TSynAuthentication
  14340. TSynAuthenticationAbstract = class
  14341. protected
  14342. fSessions: TIntegerDynArray;
  14343. fSessionsCount: Integer;
  14344. fSessionGenerator: integer;
  14345. fTokenSeed: Int64;
  14346. fSafe: TSynLocker;
  14347. function ComputeCredential(previous: boolean; const UserName,PassWord: RawUTF8): cardinal; virtual;
  14348. function GetPassword(const UserName: RawUTF8; out Password: RawUTF8): boolean; virtual; abstract;
  14349. function GetUsersCount: integer; virtual; abstract;
  14350. public
  14351. /// initialize the authentication scheme
  14352. constructor Create;
  14353. /// finalize the authentation
  14354. destructor Destroy; override;
  14355. /// register one credential for a given user
  14356. // - this abstract method would raise an exception: inherited classes should
  14357. // implement them as expected
  14358. procedure AuthenticateUser(const aName, aPassword: RawUTF8); virtual;
  14359. /// unregister one credential for a given user
  14360. // - this abstract method would raise an exception: inherited classes should
  14361. // implement them as expected
  14362. procedure DisauthenticateUser(const aName: RawUTF8); virtual;
  14363. /// create a new session
  14364. // - should return 0 on authentication error, or an integer session ID
  14365. // - this method will check the User name and password, and create a new session
  14366. function CreateSession(const User: RawUTF8; Hash: cardinal): integer; virtual;
  14367. /// check if the session exists in the internal list
  14368. function SessionExists(aID: integer): boolean;
  14369. /// delete a session
  14370. procedure RemoveSession(aID: integer);
  14371. /// returns the current identification token
  14372. // - to be sent to the client for its authentication challenge
  14373. function CurrentToken: Int64;
  14374. /// the number of current opened sessions
  14375. property SessionsCount: integer read fSessionsCount;
  14376. /// the number of registered users
  14377. property UsersCount: integer read GetUsersCount;
  14378. /// to be used to compute a Hash on the client, for a given Token
  14379. // - the token should have been retrieved from the server, and the client
  14380. // should compute and return this hash value, to perform the authentication
  14381. // challenge and create the session
  14382. class function ComputeHash(Token: Int64; const UserName,PassWord: RawUTF8): cardinal; virtual;
  14383. end;
  14384. /// simple authentication class, implementing safe token/challenge security
  14385. // - maintain a list of user / name credential pairs, and a list of sessions
  14386. // - is not meant to handle authorization, just plain user access validation
  14387. // - used e.g. by TSQLDBConnection.RemoteProcessMessage (on server side) and
  14388. // TSQLDBProxyConnectionPropertiesAbstract (on client side) in SynDB.pas
  14389. TSynAuthentication = class(TSynAuthenticationAbstract)
  14390. protected
  14391. fCredentials: TSynNameValue;
  14392. function GetPassword(const UserName: RawUTF8; out Password: RawUTF8): boolean; override;
  14393. function GetUsersCount: integer; override;
  14394. public
  14395. /// initialize the authentication scheme
  14396. // - you can optionally register one user credential
  14397. constructor Create(const aUserName: RawUTF8=''; const aPassword: RawUTF8=''); reintroduce;
  14398. /// register one credential for a given user
  14399. procedure AuthenticateUser(const aName, aPassword: RawUTF8); override;
  14400. /// unregister one credential for a given user
  14401. procedure DisauthenticateUser(const aName: RawUTF8); override;
  14402. end;
  14403. type
  14404. /// 64-bit integer unique identifier, as computed by TSynUniqueIdentifierGenerator
  14405. // - they are increasing over time (so are much easier to store/shard/balance
  14406. // than UUID/GUID), and contain generation time and a 16-bit process ID
  14407. // - mapped by TSynUniqueIdentifierBits memory structure
  14408. // - may be used on client side for something similar to a MongoDB ObjectID,
  14409. // but compatible with TSQLRecord.ID: TID properties
  14410. TSynUniqueIdentifier = type Int64;
  14411. /// 16-bit unique process identifier, used to compute TSynUniqueIdentifier
  14412. // - each TSynUniqueIdentifierGenerator instance is expected to have
  14413. // its own unique process identifier, stored as a 16 bit integer 1..65535 value
  14414. TSynUniqueIdentifierProcess = type word;
  14415. {$A-}
  14416. /// map 64-bit integer unique identifier internal memory structure
  14417. // - as stored in TSynUniqueIdentifier = Int64 values, and computed by
  14418. // TSynUniqueIdentifierGenerator
  14419. // - bits 0..14 map a 15-bit increasing counter (collision-free)
  14420. // - bits 15..30 map a 16-bit process identifier
  14421. // - bits 31..63 map a 33-bit UTC time, encoded as seconds since Unix epoch
  14422. {$ifndef UNICODE}
  14423. TSynUniqueIdentifierBits = object
  14424. {$else}
  14425. TSynUniqueIdentifierBits = record
  14426. {$endif}
  14427. public
  14428. /// the actual 64-bit storage value
  14429. Value: TSynUniqueIdentifier;
  14430. /// 15-bit counter (0..32767), starting with a random value
  14431. function Counter: word;
  14432. {$ifdef HASINLINE}inline;{$endif}
  14433. /// 16-bit unique process identifier
  14434. // - as specified to TSynUniqueIdentifierGenerator constructor
  14435. function ProcessID: TSynUniqueIdentifierProcess;
  14436. {$ifdef HASINLINE}inline;{$endif}
  14437. /// low-endian 4-byte value representing the seconds since the Unix epoch
  14438. // - time is expressed in Coordinated Universal Time (UTC), not local time
  14439. // - it uses in fact a 33-bit resolution, so is "Year 2038" bug-free
  14440. function CreateTimeUnix: cardinal;
  14441. {$ifdef HASINLINE}inline;{$endif}
  14442. /// fill this unique identifier structure from its TSynUniqueIdentifier value
  14443. // - is just a wrapper around PInt64(@self)^
  14444. procedure From(const AID: TSynUniqueIdentifier);
  14445. {$ifdef HASINLINE}inline;{$endif}
  14446. {$ifndef NOVARIANTS}
  14447. /// convert this identifier as an explicit TDocVariant JSON object
  14448. // - returns e.g.
  14449. // ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1,
  14450. // ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"}
  14451. function AsVariant: variant;
  14452. {$endif NOVARIANTS}
  14453. /// extract the UTC generation timestamp from the identifier as TDateTime
  14454. // - time is expressed in Coordinated Universal Time (UTC), not local time
  14455. function CreateDateTime: TDateTime;
  14456. {$ifdef HASINLINE}inline;{$endif}
  14457. /// extract the UTC generation timestamp from the identifier
  14458. // - time is expressed in Coordinated Universal Time (UTC), not local time
  14459. function CreateTimeLog: TTimeLog;
  14460. {$ifndef DELPHI5OROLDER}
  14461. /// compare two Identifiers
  14462. function Equal(const Another: TSynUniqueIdentifierBits): boolean;
  14463. {$ifdef HASINLINE}inline;{$endif}
  14464. {$endif}
  14465. /// convert the identifier into a 16 chars hexadecimal string
  14466. function ToHexa: RawUTF8;
  14467. {$ifdef HASINLINE}inline;{$endif}
  14468. /// fill this unique identifier back from a 16 chars hexadecimal string
  14469. // - returns TRUE if the supplied hexadecimal is on the expected format
  14470. // - returns FALSE if the supplied text is invalid
  14471. function FromHexa(const hexa: RawUTF8): boolean;
  14472. /// fill this unique identifier with a fake value corresponding to a given
  14473. // timestamp
  14474. // - may be used e.g. to limit database queries on a particular time range
  14475. // - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0
  14476. procedure FromDateTime(aDateTime: TDateTime);
  14477. end;
  14478. {$A+}
  14479. /// points to a 64-bit integer identifier, as computed by TSynUniqueIdentifierGenerator
  14480. // - may be used to access the identifier internals, from its stored
  14481. // Int64 or TSynUniqueIdentifier value
  14482. PSynUniqueIdentifierBits = ^TSynUniqueIdentifierBits;
  14483. /// a 24 chars cyphered hexadecimal string, mapping a TSynUniqueIdentifier
  14484. // - has handled by TSynUniqueIdentifierGenerator.ToObfuscated/FromObfuscated
  14485. TSynUniqueIdentifierObfuscated = type RawUTF8;
  14486. /// thread-safe 64-bit integer unique identifier computation
  14487. // - may be used on client side for something similar to a MongoDB ObjectID,
  14488. // but compatible with TSQLRecord.ID: TID properties
  14489. // - each identifier would contain a 16-bit process identifier, which is
  14490. // supplied by the application, and should be unique for this process at a
  14491. // given time
  14492. // - identifiers may be obfuscated as hexadecimal text, using both encryption
  14493. // and digital signature
  14494. TSynUniqueIdentifierGenerator = class(TSynPersistent)
  14495. protected
  14496. fLastTix: cardinal;
  14497. fUnixCreateTime: cardinal;
  14498. fLatestCounterOverflowUnixCreateTime: cardinal;
  14499. fIdentifier: TSynUniqueIdentifierProcess;
  14500. fIdentifierShifted: cardinal;
  14501. fLastCounter: cardinal;
  14502. fCrypto: array[0..7] of cardinal; // only fCrypto[6..7] are used in practice
  14503. fCryptoCRC: cardinal;
  14504. fSafe: TSynLocker;
  14505. function GetComputedCount: Int64;
  14506. public
  14507. /// initialize the generator for the given 16-bit process identifier
  14508. // - you can supply an obfuscation key, which should be shared for the
  14509. // whole system, so that you may use FromObfuscated/ToObfuscated methods
  14510. constructor Create(aIdentifier: TSynUniqueIdentifierProcess;
  14511. const aSharedObfuscationKey: RawUTF8=''); reintroduce;
  14512. /// finalize the generator structure
  14513. destructor Destroy; override;
  14514. /// return a new unique ID
  14515. // - this method is very optimized, and would use very little CPU
  14516. procedure ComputeNew(out result: TSynUniqueIdentifierBits); overload;
  14517. /// return a new unique ID, type-casted to an Int64
  14518. function ComputeNew: Int64; overload;
  14519. {$ifdef HASINLINE}inline;{$endif}
  14520. /// return an unique ID matching this generator pattern, at a given timestamp
  14521. // - may be used e.g. to limit database queries on a particular time range
  14522. procedure ComputeFromDateTime(aDateTime: TDateTime; out result: TSynUniqueIdentifierBits);
  14523. /// return an unique ID matching this generator pattern, at a given timestamp
  14524. // - may be used e.g. to limit database queries on a particular time range
  14525. procedure ComputeFromUnixTime(aUnixTime: Int64; out result: TSynUniqueIdentifierBits);
  14526. /// map a TSynUniqueIdentifier as 24 chars cyphered hexadecimal text
  14527. // - cyphering includes simple key-based encryption and a CRC-32 digital signature
  14528. function ToObfuscated(const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated;
  14529. /// retrieve a TSynUniqueIdentifier from 24 chars cyphered hexadecimal text
  14530. // - any file extension (e.g. '.jpeg') would be first deleted from the
  14531. // supplied obfuscated text
  14532. // - returns true if the supplied obfuscated text has the expected layout
  14533. // and a valid digital signature
  14534. // - returns false if the supplied obfuscated text is invalid
  14535. function FromObfuscated(const aObfuscated: TSynUniqueIdentifierObfuscated;
  14536. out aIdentifier: TSynUniqueIdentifier): boolean;
  14537. published
  14538. /// the process identifier, associated with this generator
  14539. property Identifier: TSynUniqueIdentifierProcess read fIdentifier;
  14540. /// how many times ComputeNew method has been called
  14541. property ComputedCount: Int64 read GetComputedCount;
  14542. end;
  14543. /// convert a size to a human readable value
  14544. // - append TB, GB, MB, KB or B symbol
  14545. // - for TB, GB, MB and KB, add one fractional digit
  14546. function KB(bytes: Int64): RawUTF8;
  14547. /// convert a micro seconds elapsed time into a human readable value
  14548. // - append 'us', 'ms' or 's' symbol
  14549. // - for 'us' and 'ms', add two fractional digits
  14550. function MicroSecToString(Micro: QWord): RawUTF8;
  14551. /// convert an integer value into its textual representation with thousands marked
  14552. // - ThousandSep is the character used to separate thousands in numbers with
  14553. // more than three digits to the left of the decimal separator
  14554. function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;
  14555. /// return the Delphi Compiler Version
  14556. // - returns 'Delphi 2007' or 'Delphi 2010' e.g.
  14557. function GetDelphiCompilerVersion: RawUTF8;
  14558. /// returns TRUE if the supplied mutex has been initialized
  14559. // - will check if the supplied mutex is void (i.e. all filled with 0 bytes)
  14560. function IsInitializedCriticalSection(const CS: TRTLCriticalSection): Boolean;
  14561. {$ifdef HASINLINE}inline;{$endif}
  14562. /// on need initialization of a mutex, then enter the lock
  14563. // - if the supplied mutex has been initialized, do nothing
  14564. // - if the supplied mutex is void (i.e. all filled with 0), initialize it
  14565. procedure InitializeCriticalSectionIfNeededAndEnter(var CS: TRTLCriticalSection);
  14566. {$ifdef HASINLINE}inline;{$endif}
  14567. /// on need finalization of a mutex
  14568. // - if the supplied mutex has been initialized, delete it
  14569. // - if the supplied mutex is void (i.e. all filled with 0), do nothing
  14570. procedure DeleteCriticalSectionIfNeeded(var CS: TRTLCriticalSection);
  14571. /// compress a data content using the SynLZ algorithm
  14572. // - as expected by THttpSocket.RegisterCompress
  14573. // - will return 'synlz' as ACCEPT-ENCODING: header parameter
  14574. // - will store a hash of both compressed and uncompressed stream: if the
  14575. // data is corrupted during transmission, will instantly return ''
  14576. function CompressSynLZ(var DataRawByteString; Compress: boolean): AnsiString;
  14577. /// compress a data content using the SynLZ algorithm from one stream into another
  14578. // - returns the number of bytes written to Dest
  14579. // - you should specify a Magic number to be used to identify the block
  14580. function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream;
  14581. Magic: cardinal): integer; overload;
  14582. /// compress a data content using the SynLZ algorithm from one stream into a file
  14583. // - returns the number of bytes written to the destination file
  14584. // - you should specify a Magic number to be used to identify the block
  14585. function StreamSynLZ(Source: TCustomMemoryStream; const DestFile: TFileName;
  14586. Magic: cardinal): integer; overload;
  14587. /// uncompress using the SynLZ algorithm from one stream into another
  14588. // - returns a newly create memory stream containing the uncompressed data
  14589. // - returns nil if source data is invalid
  14590. // - you should specify a Magic number to be used to identify the block
  14591. // - this function will also recognize the block at the end of the source stream
  14592. // (if was appended to an existing data - e.g. a .mab at the end of a .exe)
  14593. // - on success, Source will point after all read data (so that you can e.g.
  14594. // append several data blocks to the same stream)
  14595. function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream; overload;
  14596. /// compute the real length of a given StreamSynLZ-compressed buffer
  14597. // - allows to replace an existing appended content, for instance
  14598. function StreamSynLZComputeLen(P: PAnsiChar; Len, aMagic: cardinal): integer;
  14599. /// uncompress using the SynLZ algorithm from one file into another
  14600. // - returns a newly create memory stream containing the uncompressed data
  14601. // - returns nil if source file is invalid (e.g. invalid name or invalid content)
  14602. // - you should specify a Magic number to be used to identify the block
  14603. // - this function will also recognize the block at the end of the source file
  14604. // (if was appended to an existing data - e.g. a .mab at the end of a .exe)
  14605. function StreamUnSynLZ(const Source: TFileName; Magic: cardinal): TMemoryStream; overload;
  14606. /// compress a file content using the SynLZ algorithm a file content
  14607. // - source file is split into 128 MB blocks for fast in-memory compression of
  14608. // any file size
  14609. // - you should specify a Magic number to be used to identify the compressed
  14610. // file format
  14611. function FileSynLZ(const Source, Dest: TFileName; Magic: Cardinal): boolean;
  14612. /// compress a file content using the SynLZ algorithm a file content
  14613. // - you should specify a Magic number to be used to identify the compressed
  14614. // file format
  14615. function FileUnSynLZ(const Source, Dest: TFileName; Magic: Cardinal): boolean;
  14616. /// returns TRUE if the supplied file name is a SynLZ compressed file,
  14617. // matching the Magic number as supplied to FileSynLZ() function
  14618. function FileIsZynLZ(const Name: TFileName; Magic: Cardinal): boolean;
  14619. /// compress a memory bufer using the SynLZ algorithm and crc32c hashing
  14620. function SynLZCompress(const Data: RawByteString; CompressionSizeTrigger: integer=100): RawByteString; overload;
  14621. {$ifdef HASINLINE}inline;{$endif}
  14622. /// compress a memory bufer using the SynLZ algorithm and crc32c hashing
  14623. procedure SynLZCompress(P: PAnsiChar; PLen: integer; out Result: RawByteString;
  14624. CompressionSizeTrigger: integer=100); overload;
  14625. /// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
  14626. function SynLZDecompress(const Data: RawByteString): RawByteString; overload;
  14627. {$ifdef HASINLINE}inline;{$endif}
  14628. /// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
  14629. procedure SynLZDecompress(P: PAnsiChar; PLen: integer; out Result: RawByteString); overload;
  14630. /// compress a memory bufer using the SynLZ algorithm and crc32c hashing
  14631. function SynLZCompressToBytes(const Data: RawByteString;
  14632. CompressionSizeTrigger: integer=100): TByteDynArray; overload;
  14633. {$ifdef HASINLINE}inline;{$endif}
  14634. /// compress a memory bufer using the SynLZ algorithm and crc32c hashing
  14635. function SynLZCompressToBytes(P: PAnsiChar; PLen: integer;
  14636. CompressionSizeTrigger: integer=100): TByteDynArray; overload;
  14637. /// uncompress a memory bufer using the SynLZ algorithm and crc32c hashing
  14638. function SynLZDecompress(const Data: TByteDynArray): RawByteString; overload;
  14639. /// directly returns the buffer
  14640. // - returns nil if crc32 hash failed, i.e. if the supplied Data is not correct
  14641. // - returns a pointer to the raw data and fill Len variable, after crc32c hash
  14642. // - avoid any memory allocation in case of a stored content - otherwise, would
  14643. // uncompress to the tmp variable, and return pointer(tmp) and length(tmp)
  14644. function SynLZDecompress(const Data: RawByteString; out Len: integer;
  14645. var tmp: RawByteString): pointer; overload;
  14646. /// directly returns a stored buffer, if SynLZCompress() just stored it
  14647. // - returns nil if crc32 hash failed, i.e. if the supplied Data is not correct
  14648. // - returns a pointer to the raw data and fill Len variable, after crc32c hash
  14649. // - avoid any memory allocation in case of a stored content - otherwise, would
  14650. // uncompress to the tmp variable, and return pointer(tmp) and length(tmp)
  14651. function SynLZDecompress(P: PAnsiChar; PLen: integer; out Len: integer;
  14652. var tmp: RawByteString): pointer; overload;
  14653. /// RLE compression of a memory buffer containing mostly zeros
  14654. // - will store the number of consecutive zeros instead of plain zero bytes
  14655. // - used for spare bit sets, e.g. TSynBloomFilter serialization
  14656. // - will also compute the crc32c of the supplied content
  14657. // - use ZeroDecompress() to expand the compressed result
  14658. // - resulting content would be at most 14 bytes bigger than the input
  14659. // - you may use this function before SynLZ compression
  14660. procedure ZeroCompress(P: PAnsiChar; Len: integer; Dest: TFileBufferWriter);
  14661. /// RLE uncompression of a memory buffer containing mostly zeros
  14662. // - returns Dest='' if P^ is not a valid ZeroCompress() function result
  14663. // - used for spare bit sets, e.g. TSynBloomFilter serialization
  14664. // - will also check the crc32c of the supplied content
  14665. procedure ZeroDecompress(P: PByte; Len: integer; out Dest: RawByteString);
  14666. /// RLE compression of XORed memory buffers resulting in mostly zeros
  14667. // - will perform ZeroCompress(Dest^ := New^ xor Old^) without any temporary
  14668. // memory allocation
  14669. // - is used e.g. by TSynBloomFilterDiff.SaveToDiff() in incremental mode
  14670. // - will also compute the crc32c of the supplied content
  14671. procedure ZeroCompressXor(New,Old: PAnsiChar; Len: cardinal; Dest: TFileBufferWriter);
  14672. /// RLE uncompression and ORing of a memory buffer containing mostly zeros
  14673. // - will perform Dest^ := Dest^ or ZeroDecompress(P^) without any temporary
  14674. // memory allocation
  14675. // - is used e.g. by TSynBloomFilterDiff.LoadFromDiff() in incremental mode
  14676. // - returns false if P^ is not a valid ZeroCompress/ZeroCompressXor() result
  14677. // - will also check the crc32c of the supplied content
  14678. function ZeroDecompressOr(P,Dest: PAnsiChar; Len,DestLen: integer): boolean;
  14679. resourcestring
  14680. sInvalidIPAddress = '"%s" is an invalid IP v4 address';
  14681. sInvalidEmailAddress = '"%s" is an invalid email address';
  14682. sInvalidPattern = '"%s" does not match the expected pattern';
  14683. sCharacter01n = 'character,character,characters';
  14684. sInvalidTextLengthMin = 'Expect at least %d %s';
  14685. sInvalidTextLengthMax = 'Expect up to %d %s';
  14686. sInvalidTextChar = 'Expect at least %d %s %s,Expect up to %d %s %s,'+
  14687. 'alphabetical,digital,punctuation,lowercase,uppercase,space,'+
  14688. 'Too much spaces on the left,Too much spaces on the right';
  14689. sValidationFailed = '"%s" rule failed';
  14690. sValidationFieldVoid = 'An unique key field must not be void';
  14691. sValidationFieldDuplicate = 'Value already used for this unique key field';
  14692. implementation
  14693. {$ifdef FPC}
  14694. uses
  14695. {$ifdef Linux}
  14696. SynFPCLinux, Unix, dynlibs, Linux,
  14697. {$ifndef Darwin}
  14698. SysCall,
  14699. {$endif}
  14700. {$endif}
  14701. SynFPCTypInfo, TypInfo; // small wrapper unit around FPC's TypInfo.pp
  14702. {$endif}
  14703. { ************ some fast UTF-8 / Unicode / Ansi conversion routines }
  14704. var
  14705. // internal list of TSynAnsiConvert instances
  14706. SynAnsiConvertList: TObjectList = nil;
  14707. // some constants used for UTF-8 conversion, including surrogates
  14708. const
  14709. UTF16_HISURROGATE_MIN = $d800;
  14710. UTF16_HISURROGATE_MAX = $dbff;
  14711. UTF16_LOSURROGATE_MIN = $dc00;
  14712. UTF16_LOSURROGATE_MAX = $dfff;
  14713. UTF8_EXTRABYTES: array[$80..$ff] of byte = (
  14714. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  14715. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  14716. 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  14717. 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,0,0);
  14718. UTF8_EXTRA: array[0..6] of record
  14719. offset, minimum: cardinal;
  14720. end = ( // http://floodyberry.wordpress.com/2007/04/14/utf-8-conversion-tricks
  14721. (offset: $00000000; minimum: $00010000),
  14722. (offset: $00003080; minimum: $00000080),
  14723. (offset: $000e2080; minimum: $00000800),
  14724. (offset: $03c82080; minimum: $00010000),
  14725. (offset: $fa082080; minimum: $00200000),
  14726. (offset: $82082080; minimum: $04000000),
  14727. (offset: $00000000; minimum: $04000000));
  14728. UTF8_EXTRA_SURROGATE = 3;
  14729. UTF8_FIRSTBYTE: array[2..6] of byte = ($c0,$e0,$f0,$f8,$fc);
  14730. { TSynAnsiConvert }
  14731. const
  14732. DefaultCharVar: AnsiChar = '?';
  14733. function TSynAnsiConvert.AnsiBufferToUnicode(Dest: PWideChar;
  14734. Source: PAnsiChar; SourceChars: Cardinal): PWideChar;
  14735. var c: cardinal;
  14736. {$ifndef MSWINDOWS}
  14737. {$ifdef FPC}
  14738. tmp: UnicodeString;
  14739. {$endif}
  14740. {$ifdef KYLIX3}
  14741. ic: iconv_t;
  14742. DestBegin: PAnsiChar;
  14743. SourceCharsBegin: integer;
  14744. {$endif}
  14745. {$endif}
  14746. begin
  14747. {$ifdef KYLIX3}
  14748. SourceCharsBegin := SourceChars;
  14749. DestBegin := pointer(Dest);
  14750. {$endif}
  14751. // first handle trailing 7 bit ASCII chars, by quad (Sha optimization)
  14752. if SourceChars>=4 then
  14753. repeat
  14754. c := PCardinal(Source)^;
  14755. if c and $80808080<>0 then
  14756. break; // break on first non ASCII quad
  14757. dec(SourceChars,4);
  14758. inc(Source,4);
  14759. PCardinal(Dest)^ := (c shl 8 or (c and $FF)) and $00ff00ff;
  14760. c := c shr 16;
  14761. PCardinal(Dest+2)^ := (c shl 8 or c) and $00ff00ff;
  14762. inc(Dest,4);
  14763. until SourceChars<4;
  14764. if (SourceChars>0) and (ord(Source^)<128) then
  14765. repeat
  14766. dec(SourceChars);
  14767. PWord(Dest)^ := ord(Source^); // much faster than dest^ := WideChar(c) for FPC
  14768. inc(Source);
  14769. inc(Dest);
  14770. until (SourceChars=0) or (ord(Source^)>=128);
  14771. // rely on the Operating System for all remaining ASCII characters
  14772. if SourceChars=0 then
  14773. result := Dest else begin
  14774. {$ifdef MSWINDOWS}
  14775. result := Dest+MultiByteToWideChar(
  14776. fCodePage,MB_PRECOMPOSED,Source,SourceChars,Dest,SourceChars);
  14777. {$else}
  14778. {$ifdef ISDELPHIXE} // use cross-platform wrapper for MultiByteToWideChar()
  14779. result := Dest+UnicodeFromLocaleChars(
  14780. fCodePage,MB_PRECOMPOSED,Source,SourceChars,Dest,SourceChars);
  14781. {$else}
  14782. {$ifdef FPC}
  14783. widestringmanager.Ansi2UnicodeMoveProc(Source,
  14784. {$ifdef ISFPC27}fCodePage,{$endif}tmp,SourceChars);
  14785. MoveFast(Pointer(tmp)^,Dest^,length(tmp)*2);
  14786. result := Dest+SourceChars;
  14787. {$else}
  14788. {$ifdef KYLIX3}
  14789. result := Dest; // makes compiler happy
  14790. ic := LibC.iconv_open('UTF-16LE',Pointer(fIConvCodeName));
  14791. if PtrInt(ic)>=0 then
  14792. try
  14793. result := IconvBufConvert(ic,Source,SourceChars,1,
  14794. Dest,SourceCharsBegin*2-(PAnsiChar(Dest)-DestBegin),2);
  14795. finally
  14796. LibC.iconv_close(ic);
  14797. end else
  14798. {$else}
  14799. raise ESynException.CreateUTF8('%.AnsiBufferToUnicode() not supported yet for CP=%',
  14800. [self,CodePage]);
  14801. {$endif KYLIX3}
  14802. {$endif FPC}
  14803. {$endif ISDELPHIXE}
  14804. {$endif MSWINDOWS}
  14805. end;
  14806. result^ := #0;
  14807. end;
  14808. function TSynAnsiConvert.AnsiBufferToUTF8(Dest: PUTF8Char;
  14809. Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
  14810. var tmp: array[0..256*6] of WideChar;
  14811. c: cardinal;
  14812. U: PWideChar;
  14813. begin
  14814. // first handle trailing 7 bit ASCII chars, by quad (Sha optimization)
  14815. if SourceChars>=4 then
  14816. repeat
  14817. c := PCardinal(Source)^;
  14818. if c and $80808080<>0 then
  14819. break; // break on first non ASCII quad
  14820. PCardinal(Dest)^ := c;
  14821. dec(SourceChars,4);
  14822. inc(Source,4);
  14823. inc(Dest,4);
  14824. until SourceChars<4;
  14825. if (SourceChars>0) and (ord(Source^)<128) then
  14826. repeat
  14827. Dest^ := Source^;
  14828. dec(SourceChars);
  14829. inc(Source);
  14830. inc(Dest);
  14831. until (SourceChars=0) or (ord(Source^)>=128);
  14832. // rely on the Operating System for all remaining ASCII characters
  14833. if SourceChars=0 then
  14834. result := Dest else
  14835. if SourceChars<SizeOf(tmp)div 3 then
  14836. result := Dest+RawUnicodeToUTF8(Dest,SourceChars*3,tmp,
  14837. (PtrUInt(AnsiBufferToUnicode(tmp,Source,SourceChars))-PtrUInt(@tmp))shr 1) else begin
  14838. GetMem(U,SourceChars*3+2);
  14839. result := Dest+RawUnicodeToUtf8(Dest,SourceChars*3,U,
  14840. AnsiBufferToUnicode(U,Source,SourceChars)-U);
  14841. FreeMem(U);
  14842. end;
  14843. result^ := #0;
  14844. end;
  14845. // UTF-8 is AT MOST 50% bigger than UTF-16 in bytes in range U+0800..U+FFFF
  14846. // see http://stackoverflow.com/a/7008095/458259 -> WideCharCount*3 below
  14847. procedure TSynAnsiConvert.InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  14848. DestTextWriter: TObject; Escape: TTextWriterKind);
  14849. var W: TTextWriter absolute DestTextWriter;
  14850. tmp: TSynTempBuffer;
  14851. begin // rely on explicit conversion
  14852. tmp.Init(SourceChars*3+1);
  14853. SourceChars := AnsiBufferToUTF8(tmp.buf,Source,SourceChars)-tmp.buf;
  14854. W.Add(tmp.buf,SourceChars,Escape);
  14855. tmp.Done;
  14856. end;
  14857. function TSynAnsiConvert.AnsiToRawUnicode(const AnsiText: RawByteString): RawUnicode;
  14858. begin
  14859. result := AnsiToRawUnicode(pointer(AnsiText),length(AnsiText));
  14860. end;
  14861. procedure FastNewRawUTF8(var s: RawUTF8; len: integer);
  14862. {$ifdef FPC} inline;
  14863. begin
  14864. SetString(s,nil,len);
  14865. end;
  14866. {$else}
  14867. {$ifdef PUREPASCAL} {$ifdef HASINLINE}inline;{$endif}
  14868. begin
  14869. if len<>0 then
  14870. if (PtrUInt(s)=0) or // s=''
  14871. (PInteger(PtrUInt(s)-8)^<>1) or // s.refcount<>1
  14872. (PInteger(PtrUInt(s)-4)^<>len) then // s.length<>len
  14873. SetString(s,nil,len) else
  14874. exit else
  14875. if s='' then
  14876. exit else
  14877. s := '';
  14878. end;
  14879. {$else}
  14880. asm // eax=s edx=len
  14881. test edx,edx
  14882. mov ecx,[eax]
  14883. jz System.@LStrClr
  14884. test ecx,ecx
  14885. jz @set
  14886. cmp dword ptr [ecx-8],1
  14887. jne @set
  14888. cmp dword ptr [ecx-4],edx
  14889. je @out
  14890. @set:mov ecx,edx
  14891. xor edx,edx
  14892. {$ifdef UNICODE}
  14893. push CP_UTF8 // UTF-8 code page for Delphi 2009+
  14894. call System.@LStrFromPCharLen // we need a call, not a jmp here
  14895. {$else}
  14896. jmp System.@LStrFromPCharLen
  14897. {$endif}
  14898. @out:
  14899. end;
  14900. {$endif PUREPASCAL}
  14901. {$endif FPC}
  14902. function TSynAnsiConvert.AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode;
  14903. var U: PWideChar;
  14904. begin
  14905. if SourceChars=0 then
  14906. result := '' else begin
  14907. SetString(result,nil,SourceChars*2+1);
  14908. U := AnsiBufferToUnicode(pointer(result),Source,SourceChars);
  14909. U^ := #0;
  14910. SetLength(result,PtrUInt(U)-PtrUInt(result)+1);
  14911. end;
  14912. end;
  14913. function TSynAnsiConvert.AnsiToUnicodeString(Source: PAnsiChar; SourceChars: Cardinal): SynUnicode;
  14914. var tmp: TSynTempBuffer;
  14915. U: PWideChar;
  14916. begin
  14917. if SourceChars=0 then
  14918. result := '' else begin
  14919. tmp.Init(SourceChars*2); // max dest size in bytes
  14920. U := AnsiBufferToUnicode(tmp.buf,Source,SourceChars);
  14921. SetString(result,PWideChar(tmp.buf),(PtrUInt(U)-PtrUInt(tmp.buf))shr 1);
  14922. tmp.Done;
  14923. end;
  14924. end;
  14925. function TSynAnsiConvert.AnsiToUnicodeString(const Source: RawByteString): SynUnicode;
  14926. var tmp: TSynTempBuffer;
  14927. U: PWideChar;
  14928. begin
  14929. if Source='' then
  14930. result := '' else begin
  14931. tmp.Init(length(Source)*2); // max dest size in bytes
  14932. U := AnsiBufferToUnicode(tmp.buf,pointer(Source),length(Source));
  14933. SetString(result,PWideChar(tmp.buf),(PtrUInt(U)-PtrUInt(tmp.buf))shr 1);
  14934. tmp.Done;
  14935. end;
  14936. end;
  14937. function TSynAnsiConvert.AnsiToUTF8(const AnsiText: RawByteString): RawUTF8;
  14938. begin
  14939. result := AnsiBufferToRawUTF8(pointer(AnsiText),length(AnsiText));
  14940. end;
  14941. function TSynAnsiConvert.AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8;
  14942. var tmp: TSynTempBuffer;
  14943. begin
  14944. if (Source=nil) or (SourceChars=0) then
  14945. result := '' else begin
  14946. tmp.Init(SourceChars*3+1);
  14947. SetString(result,PAnsiChar(tmp.buf),AnsiBufferToUTF8(tmp.buf,Source,SourceChars)-tmp.buf);
  14948. tmp.Done;
  14949. end;
  14950. end;
  14951. constructor TSynAnsiConvert.Create(aCodePage: cardinal);
  14952. begin
  14953. fCodePage := aCodePage;
  14954. fAnsiCharShift := 1; // default is safe
  14955. {$ifdef KYLIX3}
  14956. fIConvCodeName := 'CP'+UInt32ToUTF8(aCodePage);
  14957. {$endif}
  14958. end;
  14959. function IsFixedWidthCodePage(aCodePage: cardinal): boolean;
  14960. begin
  14961. result := (aCodePage>=1250) and (aCodePage<=1258);
  14962. end;
  14963. class function TSynAnsiConvert.Engine(aCodePage: cardinal): TSynAnsiConvert;
  14964. var i: integer;
  14965. begin
  14966. if SynAnsiConvertList=nil then begin
  14967. GarbageCollectorFreeAndNil(SynAnsiConvertList,TObjectList.Create);
  14968. CurrentAnsiConvert := TSynAnsiConvert.Engine(GetACP);
  14969. WinAnsiConvert := TSynAnsiConvert.Engine(CODEPAGE_US) as TSynAnsiFixedWidth;
  14970. UTF8AnsiConvert := TSynAnsiConvert.Engine(CP_UTF8) as TSynAnsiUTF8;
  14971. end;
  14972. if aCodePage<=0 then begin
  14973. result := CurrentAnsiConvert;
  14974. exit;
  14975. end;
  14976. with SynAnsiConvertList do
  14977. for i := 0 to Count-1 do begin
  14978. result := List[i];
  14979. if result.CodePage=aCodePage then
  14980. exit;
  14981. end;
  14982. if aCodePage=CP_UTF8 then
  14983. result := TSynAnsiUTF8.Create(CP_UTF8) else
  14984. if aCodePage=CP_UTF16 then
  14985. result := TSynAnsiUTF16.Create(CP_UTF16) else
  14986. if IsFixedWidthCodePage(aCodePage) then
  14987. result := TSynAnsiFixedWidth.Create(aCodePage) else
  14988. result := TSynAnsiConvert.Create(aCodePage);
  14989. SynAnsiConvertList.Add(result);
  14990. end;
  14991. function TSynAnsiConvert.UnicodeBufferToAnsi(Dest: PAnsiChar;
  14992. Source: PWideChar; SourceChars: Cardinal): PAnsiChar;
  14993. var c: cardinal;
  14994. {$ifndef MSWINDOWS}
  14995. {$ifdef FPC}
  14996. tmp: RawByteString;
  14997. {$endif}
  14998. {$ifdef KYLIX3}
  14999. ic: iconv_t;
  15000. DestBegin: PAnsiChar;
  15001. SourceCharsBegin: integer;
  15002. {$endif}
  15003. {$endif MSWINDOWS}
  15004. begin
  15005. {$ifdef KYLIX3}
  15006. SourceCharsBegin := SourceChars;
  15007. DestBegin := Dest;
  15008. {$endif}
  15009. // first handle trailing 7 bit ASCII chars, by pairs (Sha optimization)
  15010. if SourceChars>=2 then
  15011. repeat
  15012. c := PCardinal(Source)^;
  15013. if c and $ff80ff80<>0 then
  15014. break; // break on first non ASCII pair
  15015. dec(SourceChars,2);
  15016. inc(Source,2);
  15017. c := c shr 8 or c;
  15018. PWord(Dest)^ := c;
  15019. inc(Dest,2);
  15020. until SourceChars<2;
  15021. if (SourceChars>0) and (ord(Source^)<128) then
  15022. repeat
  15023. Dest^ := AnsiChar(ord(Source^));
  15024. dec(SourceChars);
  15025. inc(Source);
  15026. inc(Dest);
  15027. until (SourceChars=0) or (ord(Source^)>=128);
  15028. // rely on the Operating System for all remaining ASCII characters
  15029. if SourceChars=0 then
  15030. result := Dest else begin
  15031. {$ifdef MSWINDOWS}
  15032. result := Dest+WideCharToMultiByte(
  15033. fCodePage,0,Source,SourceChars,Dest,SourceChars*3,@DefaultCharVar,nil);
  15034. {$else}
  15035. {$ifdef ISDELPHIXE} // use cross-platform wrapper for WideCharToMultiByte()
  15036. result := Dest+System.LocaleCharsFromUnicode(
  15037. fCodePage,0,Source,SourceChars,Dest,SourceChars*3,@DefaultCharVar,nil);
  15038. {$else}
  15039. {$ifdef FPC}
  15040. widestringmanager.Unicode2AnsiMoveProc(Source,tmp,
  15041. {$ifdef ISFPC27}fCodePage,{$endif}SourceChars);
  15042. MoveFast(Pointer(tmp)^,Dest^,length(tmp));
  15043. result := Dest+length(tmp);
  15044. {$else}
  15045. {$ifdef KYLIX3}
  15046. result := Dest; // makes compiler happy
  15047. ic := LibC.iconv_open(Pointer(fIConvCodeName),'UTF-16LE');
  15048. if PtrInt(ic)>=0 then
  15049. try
  15050. result := IconvBufConvert(ic,Source,SourceChars,2,
  15051. Dest,SourceCharsBegin*3-(PAnsiChar(Dest)-DestBegin),1);
  15052. finally
  15053. LibC.iconv_close(ic);
  15054. end else
  15055. {$else}
  15056. raise ESynException.CreateUTF8('%.UnicodeBufferToAnsi() not supported yet for CP=%',
  15057. [self,CodePage]); {$endif KYLIX3}
  15058. {$endif FPC}
  15059. {$endif ISDELPHIXE}
  15060. {$endif MSWINDOWS}
  15061. end;
  15062. end;
  15063. function TSynAnsiConvert.UTF8BufferToAnsi(Dest: PAnsiChar;
  15064. Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar;
  15065. var tmp: array[0..256*6] of WideChar;
  15066. U: PWideChar;
  15067. begin
  15068. if SourceChars<SizeOf(tmp)div 3 then
  15069. result := UnicodeBufferToAnsi(Dest,tmp,UTF8ToWideChar(tmp,Source,SourceChars) shr 1) else begin
  15070. Getmem(U,SourceChars*3+2);
  15071. result := UnicodeBufferToAnsi(Dest,U,UTF8ToWideChar(U,Source,SourceChars) shr 1);
  15072. Freemem(U);
  15073. end;
  15074. end;
  15075. function TSynAnsiConvert.UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal): RawByteString;
  15076. begin
  15077. UTF8BufferToAnsi(Source,SourceChars,result);
  15078. end;
  15079. procedure TSynAnsiConvert.UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal;
  15080. var result: RawByteString);
  15081. var tmp: TSynTempBuffer;
  15082. begin
  15083. if (Source=nil) or (SourceChars=0) then
  15084. result := '' else begin
  15085. tmp.Init((SourceChars+1) shl fAnsiCharShift);
  15086. SetString(result,PAnsiChar(tmp.buf),Utf8BufferToAnsi(tmp.buf,Source,SourceChars)-tmp.buf);
  15087. tmp.Done;
  15088. {$ifdef HASCODEPAGE}
  15089. SetCodePage(result,fCodePage,false);
  15090. {$endif}
  15091. end;
  15092. end;
  15093. function TSynAnsiConvert.UTF8ToAnsi(const UTF8: RawUTF8): RawByteString;
  15094. begin
  15095. UTF8BufferToAnsi(pointer(UTF8),length(UTF8),result);
  15096. end;
  15097. function TSynAnsiConvert.Utf8ToAnsiBuffer(const S: RawUTF8;
  15098. Dest: PAnsiChar; DestSize: integer): integer;
  15099. var tmp: array[0..2047] of AnsiChar; // truncated to 2KB as documented
  15100. begin
  15101. if (DestSize<=0) or (Dest=nil) then begin
  15102. result := 0;
  15103. exit;
  15104. end;
  15105. result := length(s);
  15106. if result>0 then begin
  15107. if result>sizeof(tmp) then
  15108. result := sizeof(tmp);
  15109. result := UTF8BufferToAnsi(tmp,pointer(s),result)-tmp;
  15110. if result>=DestSize then
  15111. result := DestSize-1;
  15112. MoveFast(tmp,Dest^,result);
  15113. end;
  15114. Dest[result] := #0;
  15115. end;
  15116. function TSynAnsiConvert.UnicodeBufferToAnsi(Source: PWideChar; SourceChars: Cardinal): RawByteString;
  15117. var tmp: TSynTempBuffer;
  15118. begin
  15119. if (Source=nil) or (SourceChars=0) then
  15120. result := '' else begin
  15121. tmp.Init((SourceChars+1) shl fAnsiCharShift);
  15122. SetString(result,PAnsiChar(tmp.buf),UnicodeBufferToAnsi(tmp.buf,Source,SourceChars)-tmp.buf);
  15123. tmp.Done;
  15124. {$ifdef HASCODEPAGE}
  15125. SetCodePage(result,fCodePage,false);
  15126. {$endif}
  15127. end;
  15128. end;
  15129. function TSynAnsiConvert.RawUnicodeToAnsi(const Source: RawUnicode): RawByteString;
  15130. begin
  15131. result := UnicodeBufferToAnsi(pointer(Source),length(Source) shr 1);
  15132. end;
  15133. function TSynAnsiConvert.AnsiToAnsi(From: TSynAnsiConvert; const Source: RawByteString): RawByteString;
  15134. begin
  15135. if From=self then
  15136. result := Source else
  15137. result := AnsiToAnsi(From,pointer(Source),length(Source));
  15138. end;
  15139. function TSynAnsiConvert.AnsiToAnsi(From: TSynAnsiConvert; Source: PAnsiChar; SourceChars: cardinal): RawByteString;
  15140. var tmpU: array[byte] of WideChar;
  15141. U: PWideChar;
  15142. begin
  15143. if From=self then
  15144. SetString(result,Source,SourceChars) else
  15145. if (Source=nil) or (SourceChars=0) then
  15146. result := '' else
  15147. if SourceChars<sizeof(tmpU) shr 1 then
  15148. result := UnicodeBufferToAnsi(tmpU,
  15149. (PtrUInt(From.AnsiBufferToUnicode(tmpU,Source,SourceChars))-PtrUInt(@tmpU))shr 1) else begin
  15150. GetMem(U,SourceChars*2+2);
  15151. result := UnicodeBufferToAnsi(U,From.AnsiBufferToUnicode(U,Source,SourceChars)-U);
  15152. FreeMem(U);
  15153. end;
  15154. {$ifdef HASCODEPAGE}
  15155. SetCodePage(result,fCodePage,false);
  15156. {$endif}
  15157. end;
  15158. { TSynAnsiFixedWidth }
  15159. function TSynAnsiFixedWidth.AnsiBufferToUnicode(Dest: PWideChar;
  15160. Source: PAnsiChar; SourceChars: Cardinal): PWideChar;
  15161. var i: Integer;
  15162. begin
  15163. // PWord*(Dest)[] is much faster than dest^ := WideChar(c) for FPC
  15164. for i := 1 to SourceChars shr 2 do begin
  15165. PWordArray(Dest)[0] := fAnsiToWide[Ord(Source[0])];
  15166. PWordArray(Dest)[1] := fAnsiToWide[Ord(Source[1])];
  15167. PWordArray(Dest)[2] := fAnsiToWide[Ord(Source[2])];
  15168. PWordArray(Dest)[3] := fAnsiToWide[Ord(Source[3])];
  15169. inc(Source,4);
  15170. inc(Dest,4);
  15171. end;
  15172. for i := 1 to SourceChars and 3 do begin
  15173. PWord(Dest)^ := fAnsiToWide[Ord(Source^)];
  15174. inc(Dest);
  15175. inc(Source);
  15176. end;
  15177. Dest^ := #0;
  15178. result := Dest;
  15179. end;
  15180. {$ifdef CPUARM} // circumvent FPC issue on ARM
  15181. function ToByte(value: cardinal): cardinal; inline;
  15182. begin
  15183. result := value and $ff;
  15184. end;
  15185. {$else}
  15186. type ToByte = byte;
  15187. {$endif}
  15188. function TSynAnsiFixedWidth.AnsiBufferToUTF8(Dest: PUTF8Char;
  15189. Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
  15190. var EndSource, EndSourceBy4: PAnsiChar;
  15191. c: Cardinal;
  15192. label By4, By1; // ugly but faster
  15193. begin
  15194. if (self=nil) or (Dest=nil) then begin
  15195. Result := nil;
  15196. Exit;
  15197. end else
  15198. if (Source<>nil) and (SourceChars>0) then begin
  15199. // handle 7 bit ASCII WideChars, by quads (Sha optimization)
  15200. EndSource := Source+SourceChars;
  15201. EndSourceBy4 := EndSource-4;
  15202. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then
  15203. repeat
  15204. By4: c := PCardinal(Source)^;
  15205. if c and $80808080<>0 then
  15206. goto By1; // break on first non ASCII quad
  15207. inc(Source,4);
  15208. PCardinal(Dest)^ := c;
  15209. inc(Dest,4);
  15210. until Source>EndSourceBy4;
  15211. // generic loop, handling one WideChar per iteration
  15212. if Source<EndSource then
  15213. repeat
  15214. By1: c := byte(Source^); inc(Source);
  15215. if c<=$7F then begin
  15216. Dest^ := AnsiChar(c); // 0..127 don't need any translation
  15217. Inc(Dest);
  15218. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then goto By4;
  15219. if Source<endSource then continue else break;
  15220. end
  15221. else begin // no surrogate is expected in TSynAnsiFixedWidth charsets
  15222. c := fAnsiToWide[c]; // convert FixedAnsi char into Unicode char
  15223. if c>$7ff then begin
  15224. Dest[0] := AnsiChar($E0 or (c shr 12));
  15225. Dest[1] := AnsiChar($80 or ((c shr 6) and $3F));
  15226. Dest[2] := AnsiChar($80 or (c and $3F));
  15227. Inc(Dest,3);
  15228. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then goto By4;
  15229. if Source<EndSource then continue else break;
  15230. end else begin
  15231. Dest[0] := AnsiChar($C0 or (c shr 6));
  15232. Dest[1] := AnsiChar($80 or (c and $3F));
  15233. Inc(Dest,2);
  15234. if (PtrUInt(Source) and 3=0) and (Source<EndSourceBy4) then goto By4;
  15235. if Source<endSource then continue else break;
  15236. end;
  15237. end;
  15238. until false;
  15239. end;
  15240. Dest^ := #0;
  15241. Result := Dest;
  15242. end;
  15243. procedure TSynAnsiFixedWidth.InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  15244. DestTextWriter: TObject; Escape: TTextWriterKind);
  15245. begin
  15246. TTextWriter(DestTextWriter).InternalAddFixedAnsi(Source,SourceChars,fAnsiToWide,Escape);
  15247. end;
  15248. function TSynAnsiFixedWidth.AnsiToRawUnicode(Source: PAnsiChar; SourceChars: Cardinal): RawUnicode;
  15249. begin
  15250. if SourceChars=0 then
  15251. result := '' else begin
  15252. SetString(result,nil,SourceChars*2+1);
  15253. AnsiBufferToUnicode(pointer(result),Source,SourceChars);
  15254. end;
  15255. end;
  15256. const
  15257. /// used for fast WinAnsi to Unicode conversion
  15258. // - this table contain all the unicode characters corresponding to
  15259. // the Ansi Code page 1252 (i.e. WinAnsi), which unicode value are > 255
  15260. // - values taken from MultiByteToWideChar(1252,0,@Tmp,256,@WinAnsiTable,256)
  15261. // so these values are available outside the Windows platforms (e.g. Linux/BSD)
  15262. // and even if registry has been tweaked as such:
  15263. // http://www.fas.harvard.edu/~chgis/data/chgis/downloads/v4/howto/cyrillic.html
  15264. WinAnsiUnicodeChars: packed array[128..159] of word =
  15265. (8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338,
  15266. 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482,
  15267. 353, 8250, 339, 157, 382, 376);
  15268. constructor TSynAnsiFixedWidth.Create(aCodePage: cardinal);
  15269. var i: integer;
  15270. A256: array[0..256] of AnsiChar;
  15271. U256: array[0..256] of WideChar; // AnsiBufferToUnicode() write a last #0
  15272. begin
  15273. inherited;
  15274. if not IsFixedWidthCodePage(aCodePage) then
  15275. // ESynException.CreateUTF8() uses UTF8ToString() -> use CreateFmt() here
  15276. raise ESynException.CreateFmt('%s.Create - Invalid code page %d',
  15277. [ClassName,fCodePage]);
  15278. // create internal look-up tables
  15279. SetLength(fAnsiToWide,256);
  15280. if aCodePage=CODEPAGE_US then begin // do not trust the Windows API :(
  15281. for i := 0 to 255 do
  15282. fAnsiToWide[i] := i;
  15283. for i := low(WinAnsiUnicodeChars) to high(WinAnsiUnicodeChars) do
  15284. fAnsiToWide[i] := WinAnsiUnicodeChars[i];
  15285. end else begin // from Operating System returned values
  15286. for i := 0 to 255 do
  15287. A256[i] := AnsiChar(i);
  15288. FillcharFast(U256,sizeof(U256),0);
  15289. if PtrUInt(inherited AnsiBufferToUnicode(U256,A256,256))-PtrUInt(@U256)>512 then
  15290. // ESynException.CreateUTF8() uses UTF8ToString() -> use CreateFmt() here
  15291. raise ESynException.CreateFmt('OS error for %s.Create(%d)',[ClassName,aCodePage]);
  15292. MoveFast(U256[0],fAnsiToWide[0],512);
  15293. end;
  15294. SetLength(fWideToAnsi,65536);
  15295. for i := 1 to 126 do
  15296. fWideToAnsi[i] := i;
  15297. FillcharFast(fWideToAnsi[127],65536-127,ord('?')); // '?' for unknown char
  15298. for i := 127 to 255 do
  15299. if (fAnsiToWide[i]<>0) and (fAnsiToWide[i]<>ord('?')) then
  15300. fWideToAnsi[fAnsiToWide[i]] := i;
  15301. // fixed width Ansi will never be bigger than UTF-8
  15302. fAnsiCharShift := 0;
  15303. end;
  15304. function TSynAnsiFixedWidth.IsValidAnsi(WideText: PWideChar; Length: integer): boolean;
  15305. var i: integer;
  15306. wc: cardinal;
  15307. begin
  15308. result := false;
  15309. if WideText<>nil then
  15310. for i := 0 to Length-1 do begin
  15311. wc := cardinal(WideText[i]);
  15312. if wc=0 then
  15313. break else
  15314. if wc<256 then
  15315. if fAnsiToWide[wc]<256 then
  15316. continue else
  15317. exit else
  15318. if fWideToAnsi[wc]=ord('?') then
  15319. exit else
  15320. continue;
  15321. end;
  15322. result := true;
  15323. end;
  15324. function TSynAnsiFixedWidth.IsValidAnsi(WideText: PWideChar): boolean;
  15325. var wc: cardinal;
  15326. begin
  15327. result := false;
  15328. if WideText<>nil then
  15329. repeat
  15330. wc := cardinal(WideText^);
  15331. inc(WideText);
  15332. if wc=0 then
  15333. break else
  15334. if wc<256 then
  15335. if fAnsiToWide[wc]<256 then
  15336. continue else
  15337. exit else
  15338. if fWideToAnsi[wc]=ord('?') then
  15339. exit else
  15340. continue;
  15341. until false;
  15342. result := true;
  15343. end;
  15344. function TSynAnsiFixedWidth.IsValidAnsiU(UTF8Text: PUTF8Char): boolean;
  15345. var c: cardinal;
  15346. i, extra: integer;
  15347. begin
  15348. result := false;
  15349. if UTF8Text<>nil then
  15350. repeat
  15351. c := byte(UTF8Text^);
  15352. inc(UTF8Text);
  15353. if c=0 then break else
  15354. if c and $80=0 then
  15355. continue else begin
  15356. extra := UTF8_EXTRABYTES[c];
  15357. if UTF8_EXTRA[extra].minimum>$ffff then
  15358. exit;
  15359. for i := 1 to extra do begin
  15360. if byte(UTF8Text^) and $c0<>$80 then exit; // invalid UTF-8 content
  15361. c := c shl 6+byte(UTF8Text^);
  15362. inc(UTF8Text);
  15363. end;
  15364. dec(c,UTF8_EXTRA[extra].offset);
  15365. if (c>$ffff) or (fWideToAnsi[c]=ord('?')) then
  15366. exit; // invalid char in the WinAnsi code page
  15367. end;
  15368. until false;
  15369. result := true;
  15370. end;
  15371. function TSynAnsiFixedWidth.IsValidAnsiU8Bit(UTF8Text: PUTF8Char): boolean;
  15372. var c: Cardinal;
  15373. i, extra: integer;
  15374. begin
  15375. result := false;
  15376. if UTF8Text<>nil then
  15377. repeat
  15378. c := byte(UTF8Text^);
  15379. inc(UTF8Text);
  15380. if c=0 then break else
  15381. if c and $80=0 then
  15382. continue else begin
  15383. extra := UTF8_EXTRABYTES[c];
  15384. if UTF8_EXTRA[extra].minimum>$ffff then
  15385. exit;
  15386. for i := 1 to extra do begin
  15387. if byte(UTF8Text^) and $c0<>$80 then exit; // invalid UTF-8 content
  15388. c := c shl 6+byte(UTF8Text^);
  15389. inc(UTF8Text);
  15390. end;
  15391. dec(c,UTF8_EXTRA[extra].offset);
  15392. if (c>255) or (fAnsiToWide[c]>255) then
  15393. exit; // not 8 bit char (like "tm" or such) is marked invalid
  15394. end;
  15395. until false;
  15396. result := true;
  15397. end;
  15398. function TSynAnsiFixedWidth.UnicodeBufferToAnsi(Dest: PAnsiChar;
  15399. Source: PWideChar; SourceChars: Cardinal): PAnsiChar;
  15400. var c: cardinal;
  15401. begin
  15402. // first handle trailing 7 bit ASCII chars, by pairs (Sha optimization)
  15403. if SourceChars>=2 then
  15404. repeat
  15405. c := PCardinal(Source)^;
  15406. if c and $ff80ff80<>0 then
  15407. break; // break on first non ASCII pair
  15408. dec(SourceChars,2);
  15409. inc(Source,2);
  15410. c := c shr 8 or c;
  15411. PWord(Dest)^ := c;
  15412. inc(Dest,2);
  15413. until SourceChars<2;
  15414. // use internal lookup tables for fast process of remaining chars
  15415. for c := 1 to SourceChars shr 2 do begin
  15416. Dest[0] := AnsiChar(fWideToAnsi[Ord(Source[0])]);
  15417. Dest[1] := AnsiChar(fWideToAnsi[Ord(Source[1])]);
  15418. Dest[2] := AnsiChar(fWideToAnsi[Ord(Source[2])]);
  15419. Dest[3] := AnsiChar(fWideToAnsi[Ord(Source[3])]);
  15420. inc(Source,4);
  15421. inc(Dest,4);
  15422. end;
  15423. for c := 1 to SourceChars and 3 do begin
  15424. Dest^ := AnsiChar(fWideToAnsi[Ord(Source^)]);
  15425. inc(Dest);
  15426. inc(Source);
  15427. end;
  15428. result := Dest;
  15429. end;
  15430. function TSynAnsiFixedWidth.UTF8BufferToAnsi(Dest: PAnsiChar;
  15431. Source: PUTF8Char; SourceChars: Cardinal): PAnsiChar;
  15432. var c: cardinal;
  15433. endSource, endSourceBy4: PUTF8Char;
  15434. i,extra: integer;
  15435. label By1, By4, Quit; // ugly but faster
  15436. begin
  15437. // first handle trailing 7 bit ASCII chars, by quad (Sha optimization)
  15438. endSource := Source+SourceChars;
  15439. endSourceBy4 := endSource-4;
  15440. if (PtrUInt(Source) and 3=0) and (Source<=endSourceBy4) then
  15441. repeat
  15442. By4: c := PCardinal(Source)^;
  15443. if c and $80808080<>0 then
  15444. goto By1; // break on first non ASCII quad
  15445. PCardinal(Dest)^ := c;
  15446. inc(Source,4);
  15447. inc(Dest,4);
  15448. until Source>endSourceBy4;
  15449. // generic loop, handling one UTF-8 code per iteration
  15450. if Source<endSource then
  15451. repeat
  15452. By1: c := byte(Source^);
  15453. inc(Source);
  15454. if ord(c) and $80=0 then begin
  15455. Dest^ := AnsiChar(c);
  15456. inc(Dest);
  15457. if (PtrUInt(Source) and 3=0) and (Source<=endSourceBy4) then goto By4;
  15458. if Source<endSource then continue else break;
  15459. end else begin
  15460. extra := UTF8_EXTRABYTES[c];
  15461. if (extra=0) or (Source+extra>endSource) then break;
  15462. for i := 1 to extra do begin
  15463. if byte(Source^) and $c0<>$80 then
  15464. goto Quit; // invalid UTF-8 content
  15465. c := c shl 6+byte(Source^);
  15466. inc(Source);
  15467. end;
  15468. dec(c,UTF8_EXTRA[extra].offset);
  15469. if c>$ffff then
  15470. Dest^ := '?' else // '?' as in unknown fWideToAnsi[] items
  15471. Dest^ := AnsiChar(fWideToAnsi[c]);
  15472. inc(Dest);
  15473. if (PtrUInt(Source) and 3=0) and (Source<=endSourceBy4) then goto By4;
  15474. if Source<endSource then continue else break;
  15475. end;
  15476. until false;
  15477. Quit:
  15478. result := Dest;
  15479. end;
  15480. function TSynAnsiFixedWidth.WideCharToAnsiChar(wc: cardinal): integer;
  15481. begin
  15482. if wc<256 then
  15483. if fAnsiToWide[wc]<256 then
  15484. result := wc else
  15485. result := -1 else
  15486. if wc<=65535 then begin
  15487. result := fWideToAnsi[wc];
  15488. if result=ord('?') then
  15489. result := -1;
  15490. end else
  15491. result := -1;
  15492. end;
  15493. { TSynAnsiUTF8 }
  15494. function TSynAnsiUTF8.AnsiBufferToUnicode(Dest: PWideChar;
  15495. Source: PAnsiChar; SourceChars: Cardinal): PWideChar;
  15496. begin
  15497. result := Dest+(UTF8ToWideChar(Dest,PUTF8Char(Source),SourceChars) shr 1);
  15498. result^ := #0;
  15499. end;
  15500. function TSynAnsiUTF8.AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar;
  15501. SourceChars: Cardinal): PUTF8Char;
  15502. begin
  15503. MoveFast(Source^,Dest^,SourceChars);
  15504. result := Dest+SourceChars;
  15505. end;
  15506. procedure TSynAnsiUTF8.InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal;
  15507. DestTextWriter: TObject; Escape: TTextWriterKind);
  15508. begin
  15509. TTextWriter(DestTextWriter).Add(PUTF8Char(Source),SourceChars,Escape);
  15510. end;
  15511. function TSynAnsiUTF8.AnsiToRawUnicode(Source: PAnsiChar;
  15512. SourceChars: Cardinal): RawUnicode;
  15513. begin
  15514. result := Utf8DecodeToRawUniCode(PUTF8Char(Source),SourceChars);
  15515. end;
  15516. constructor TSynAnsiUTF8.Create(aCodePage: cardinal);
  15517. begin
  15518. if aCodePage<>CP_UTF8 then
  15519. raise ESynException.CreateUTF8('%.Create(%)',[self,aCodePage]);
  15520. inherited Create(aCodePage);
  15521. end;
  15522. function TSynAnsiUTF8.UnicodeBufferToAnsi(Dest: PAnsiChar;
  15523. Source: PWideChar; SourceChars: Cardinal): PAnsiChar;
  15524. begin
  15525. result := Dest+RawUnicodeToUTF8(PUTF8Char(Dest),SourceChars,Source,SourceChars);
  15526. end;
  15527. function TSynAnsiUTF8.UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char;
  15528. SourceChars: Cardinal): PAnsiChar;
  15529. begin
  15530. MoveFast(Source^,Dest^,SourceChars);
  15531. result := Dest+SourceChars;
  15532. end;
  15533. procedure TSynAnsiUTF8.UTF8BufferToAnsi(Source: PUTF8Char; SourceChars: Cardinal;
  15534. var result: RawByteString);
  15535. begin
  15536. SetString(Result,Source,SourceChars);
  15537. end;
  15538. function TSynAnsiUTF8.UTF8ToAnsi(const UTF8: RawUTF8): RawByteString;
  15539. begin
  15540. result := UTF8;
  15541. {$ifdef HASCODEPAGE}
  15542. SetCodePage(result,CP_UTF8,false);
  15543. {$endif}
  15544. end;
  15545. function TSynAnsiUTF8.AnsiToUTF8(const AnsiText: RawByteString): RawUTF8;
  15546. begin
  15547. result := AnsiText;
  15548. {$ifdef HASCODEPAGE}
  15549. SetCodePage(RawByteString(result),CP_UTF8,false);
  15550. {$endif}
  15551. end;
  15552. function TSynAnsiUTF8.AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8;
  15553. begin
  15554. SetString(Result,Source,SourceChars);
  15555. end;
  15556. { TSynAnsiUTF16 }
  15557. function TSynAnsiUTF16.AnsiBufferToUnicode(Dest: PWideChar;
  15558. Source: PAnsiChar; SourceChars: Cardinal): PWideChar;
  15559. begin
  15560. MoveFast(Source^,Dest^,SourceChars);
  15561. result := Pointer(PtrUInt(Dest)+SourceChars);
  15562. result^ := #0;
  15563. end;
  15564. function TSynAnsiUTF16.AnsiBufferToUTF8(Dest: PUTF8Char; Source: PAnsiChar;
  15565. SourceChars: Cardinal): PUTF8Char;
  15566. begin
  15567. SourceChars := SourceChars shr 1; // from byte count to WideChar count
  15568. result := Dest+RawUnicodeToUtf8(Dest,SourceChars*3,PWideChar(Source),SourceChars);
  15569. end;
  15570. function TSynAnsiUTF16.AnsiToRawUnicode(Source: PAnsiChar;
  15571. SourceChars: Cardinal): RawUnicode;
  15572. begin
  15573. SetString(result,Source,SourceChars); // byte count
  15574. end;
  15575. constructor TSynAnsiUTF16.Create(aCodePage: cardinal);
  15576. begin
  15577. if aCodePage<>CP_UTF16 then
  15578. raise ESynException.CreateUTF8('%.Create(%)',[self,aCodePage]);
  15579. inherited Create(aCodePage);
  15580. end;
  15581. function TSynAnsiUTF16.UnicodeBufferToAnsi(Dest: PAnsiChar;
  15582. Source: PWideChar; SourceChars: Cardinal): PAnsiChar;
  15583. begin
  15584. SourceChars := SourceChars shl 1; // from WideChar count to byte count
  15585. MoveFast(Source^,Dest^,SourceChars);
  15586. result := Dest+SourceChars;
  15587. end;
  15588. function TSynAnsiUTF16.UTF8BufferToAnsi(Dest: PAnsiChar; Source: PUTF8Char;
  15589. SourceChars: Cardinal): PAnsiChar;
  15590. begin
  15591. result := Dest+UTF8ToWideChar(PWideChar(Dest),Source,SourceChars);
  15592. end;
  15593. { TSynTempBuffer }
  15594. procedure TSynTempBuffer.Init(const Source: RawByteString);
  15595. begin
  15596. len := length(Source);
  15597. if len=0 then
  15598. buf := nil else begin
  15599. if len<sizeof(tmp) then
  15600. buf := @tmp else
  15601. GetMem(buf,len+1); // +1 to include trailing #0
  15602. MoveFast(pointer(Source)^,buf^,len+1); // +1 to include trailing #0
  15603. end;
  15604. end;
  15605. procedure TSynTempBuffer.Init(Source: PUTF8Char);
  15606. begin
  15607. len := StrLen(Source);
  15608. if len=0 then
  15609. buf := nil else begin
  15610. if len<sizeof(tmp) then
  15611. buf := @tmp else
  15612. GetMem(buf,len+1); // +1 to include trailing #0
  15613. MoveFast(Source^,buf^,len+1);
  15614. end;
  15615. end;
  15616. procedure TSynTempBuffer.Init(Source: pointer; SourceLen: integer);
  15617. begin
  15618. len := SourceLen;
  15619. if len=0 then
  15620. buf := nil else begin
  15621. if len<sizeof(tmp) then
  15622. buf := @tmp else
  15623. GetMem(buf,len+1); // +1 to include trailing #0
  15624. MoveFast(Source^,buf^,len+1);
  15625. end;
  15626. end;
  15627. procedure TSynTempBuffer.Init(SourceLen: integer);
  15628. begin
  15629. len := SourceLen;
  15630. if len=0 then
  15631. buf := nil else
  15632. if len<sizeof(tmp) then
  15633. buf := @tmp else
  15634. GetMem(buf,len+1); // +1 to include trailing #0
  15635. end;
  15636. procedure TSynTempBuffer.Done;
  15637. begin
  15638. if buf<>@tmp then
  15639. if buf<>nil then
  15640. FreeMem(buf);
  15641. end;
  15642. { TSynTempWriter }
  15643. procedure TSynTempWriter.Init(maxsize: integer);
  15644. begin
  15645. if maxsize<=0 then
  15646. maxsize := sizeof(tmp.tmp)-1; // -1 for trailing #0
  15647. tmp.Init(maxsize);
  15648. pos := tmp.buf;
  15649. end;
  15650. procedure TSynTempWriter.Done;
  15651. begin
  15652. tmp.Done;
  15653. end;
  15654. function TSynTempWriter.AsBinary: RawByteString;
  15655. begin
  15656. SetString(result,PAnsiChar(tmp.buf),pos-tmp.buf);
  15657. end;
  15658. function TSynTempWriter.Position: integer;
  15659. begin
  15660. result := pos-tmp.buf;
  15661. end;
  15662. procedure TSynTempWriter.wr(const val; len: integer);
  15663. begin
  15664. if pos-tmp.buf+len>tmp.len then
  15665. raise ESynException.CreateUTF8('TSynTempWriter(%) overflow',[tmp.len]);
  15666. MoveFast(val,pos^,len);
  15667. inc(pos,len);
  15668. end;
  15669. procedure TSynTempWriter.wrb(b: byte);
  15670. begin
  15671. wr(b,1);
  15672. end;
  15673. procedure TSynTempWriter.wrint(int: integer);
  15674. begin
  15675. wr(int,4);
  15676. end;
  15677. procedure TSynTempWriter.wrptrint(int: PtrInt);
  15678. begin
  15679. wr(int,sizeof(int));
  15680. end;
  15681. procedure TSynTempWriter.wrptr(ptr: pointer);
  15682. begin
  15683. wr(ptr,sizeof(ptr));
  15684. end;
  15685. procedure TSynTempWriter.wrss(const str: shortstring);
  15686. begin
  15687. wr(str,ord(str[0])+1);
  15688. end;
  15689. procedure TSynTempWriter.wrw(w: word);
  15690. begin
  15691. wr(w,2);
  15692. end;
  15693. function TSynTempWriter.wrfillchar(count: integer; value: byte): PAnsiChar;
  15694. begin
  15695. if pos-tmp.buf+count>tmp.len then
  15696. raise ESynException.CreateUTF8('TSynTempWriter(%) overflow',[tmp.len]);
  15697. FillCharFast(pos^,count,value);
  15698. result := pos;
  15699. inc(pos,count);
  15700. end;
  15701. function WideCharToUtf8(Dest: PUTF8Char; aWideChar: PtrUInt): integer;
  15702. begin
  15703. if aWideChar<=$7F then begin
  15704. Dest^ := AnsiChar(aWideChar);
  15705. result := 1;
  15706. end else
  15707. if aWideChar>$7ff then begin
  15708. Dest[0] := AnsiChar($E0 or (aWideChar shr 12));
  15709. Dest[1] := AnsiChar($80 or ((aWideChar shr 6) and $3F));
  15710. Dest[2] := AnsiChar($80 or (aWideChar and $3F));
  15711. result := 3;
  15712. end else begin
  15713. Dest[0] := AnsiChar($C0 or (aWideChar shr 6));
  15714. Dest[1] := AnsiChar($80 or (aWideChar and $3F));
  15715. result := 2;
  15716. end;
  15717. end;
  15718. function UTF16CharToUtf8(Dest: PUTF8Char; var Source: PWord): integer;
  15719. var c: cardinal;
  15720. j: integer;
  15721. begin
  15722. c := Source^;
  15723. inc(Source);
  15724. case c of
  15725. 0..$7f: begin
  15726. Dest^ := AnsiChar(c);
  15727. result := 1;
  15728. exit;
  15729. end;
  15730. UTF16_HISURROGATE_MIN..UTF16_HISURROGATE_MAX: begin
  15731. c := ((c-$D7C0)shl 10)+(Source^ xor UTF16_LOSURROGATE_MIN);
  15732. inc(Source);
  15733. end;
  15734. UTF16_LOSURROGATE_MIN..UTF16_LOSURROGATE_MAX: begin
  15735. c := ((cardinal(Source^)-$D7C0)shl 10)+(c xor UTF16_LOSURROGATE_MIN);
  15736. inc(Source);
  15737. end;
  15738. end; // now c is the UTF-32/UCS4 code point
  15739. case c of
  15740. 0..$7ff: result := 2;
  15741. $800..$ffff: result := 3;
  15742. $10000..$1FFFFF: result := 4;
  15743. $200000..$3FFFFFF: result := 5;
  15744. else result := 6;
  15745. end;
  15746. for j := result-1 downto 1 do begin
  15747. Dest[j] := AnsiChar((c and $3f)+$80);
  15748. c := c shr 6;
  15749. end;
  15750. Dest^ := AnsiChar(Byte(c) or UTF8_FIRSTBYTE[result]);
  15751. end;
  15752. function UCS4ToUTF8(ucs4: cardinal; Dest: PUTF8Char): integer;
  15753. var j: integer;
  15754. begin
  15755. case ucs4 of
  15756. 0..$7f: begin
  15757. Dest^ := AnsiChar(ucs4);
  15758. result := 1;
  15759. exit;
  15760. end;
  15761. $80..$7ff: result := 2;
  15762. $800..$ffff: result := 3;
  15763. $10000..$1FFFFF: result := 4;
  15764. $200000..$3FFFFFF: result := 5;
  15765. else result := 6;
  15766. end;
  15767. for j := result-1 downto 1 do begin
  15768. Dest[j] := AnsiChar((ucs4 and $3f)+$80);
  15769. ucs4 := ucs4 shr 6;
  15770. end;
  15771. Dest^ := AnsiChar(Byte(ucs4) or UTF8_FIRSTBYTE[result]);
  15772. end;
  15773. procedure AnyAnsiToUTF8(const s: RawByteString; var result: RawUTF8);
  15774. {$ifdef HASCODEPAGE}var CodePage: Cardinal;{$endif}
  15775. begin
  15776. if s='' then
  15777. result := '' else begin
  15778. {$ifdef HASCODEPAGE}
  15779. CodePage := StringCodePage(s);
  15780. if (CodePage=CP_UTF8) or (CodePage=CP_RAWBYTESTRING) then
  15781. result := s else
  15782. result := TSynAnsiConvert.Engine(CodePage).
  15783. {$else}
  15784. result := CurrentAnsiConvert.
  15785. {$endif}
  15786. AnsiBufferToRawUTF8(pointer(s),length(s));
  15787. end;
  15788. end;
  15789. function AnyAnsiToUTF8(const s: RawByteString): RawUTF8;
  15790. begin
  15791. AnyAnsiToUTF8(s,result);
  15792. end;
  15793. function WinAnsiBufferToUtf8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: Cardinal): PUTF8Char;
  15794. begin
  15795. result := WinAnsiConvert.AnsiBufferToUTF8(Dest,Source,SourceChars);
  15796. end;
  15797. function ShortStringToUTF8(const source: ShortString): RawUTF8;
  15798. begin
  15799. result := WinAnsiConvert.AnsiBufferToRawUTF8(@source[1],ord(source[0]));
  15800. end;
  15801. procedure WinAnsiToUnicodeBuffer(const S: WinAnsiString; Dest: PWordArray; DestLen: integer);
  15802. var L: PtrInt;
  15803. begin
  15804. L := length(S);
  15805. if L<>0 then begin
  15806. if L>=DestLen then
  15807. L := DestLen-1; // truncate to avoid buffer overflow
  15808. WinAnsiConvert.AnsiBufferToUnicode(PWideChar(Dest),pointer(S),L); // include last #0
  15809. end else
  15810. Dest^[0] := 0;
  15811. end;
  15812. function WinAnsiToRawUnicode(const S: WinAnsiString): RawUnicode;
  15813. begin
  15814. result := WinAnsiConvert.AnsiToRawUnicode(S);
  15815. end;
  15816. function WinAnsiToUtf8(const S: WinAnsiString): RawUTF8;
  15817. begin
  15818. result := WinAnsiConvert.AnsiBufferToRawUTF8(pointer(S),length(s));
  15819. end;
  15820. function WinAnsiToUtf8(WinAnsi: PAnsiChar; WinAnsiLen: integer): RawUTF8;
  15821. begin
  15822. result := WinAnsiConvert.AnsiBufferToRawUTF8(WinAnsi,WinAnsiLen);
  15823. end;
  15824. function WideCharToWinAnsiChar(wc: cardinal): AnsiChar;
  15825. begin
  15826. wc := WinAnsiConvert.WideCharToAnsiChar(wc);
  15827. if integer(wc)=-1 then
  15828. result := '?' else
  15829. result := AnsiChar(wc);
  15830. end;
  15831. function WideCharToWinAnsi(wc: cardinal): integer;
  15832. begin
  15833. result := WinAnsiConvert.WideCharToAnsiChar(wc);
  15834. end;
  15835. function IsWinAnsi(WideText: PWideChar; Length: integer): boolean;
  15836. begin
  15837. result := WinAnsiConvert.IsValidAnsi(WideText,Length);
  15838. end;
  15839. function IsAnsiCompatible(PC: PAnsiChar): boolean;
  15840. begin
  15841. result := false;
  15842. if PC<>nil then
  15843. while true do
  15844. if PC^=#0 then
  15845. break else
  15846. if PC^<=#127 then
  15847. inc(PC) else // 7 bits chars are always OK, whatever codepage/charset is used
  15848. exit;
  15849. result := true;
  15850. end;
  15851. function IsAnsiCompatible(PC: PAnsiChar; Len: integer): boolean;
  15852. var i: integer;
  15853. begin
  15854. result := false;
  15855. if PC<>nil then begin
  15856. for i := 1 to Len shr 2 do
  15857. if PCardinal(PC)^ and $80808080<>0 then
  15858. exit else
  15859. inc(PC,4);
  15860. for i := 0 to (Len and 3)-1 do
  15861. if PC[i]>=#127 then
  15862. exit;
  15863. end;
  15864. result := true;
  15865. end;
  15866. function IsAnsiCompatible(const Text: RawByteString): boolean; overload;
  15867. begin
  15868. result := IsAnsiCompatible(PAnsiChar(pointer(Text)),length(Text));
  15869. end;
  15870. function IsAnsiCompatible(PW: PWideChar): boolean; overload;
  15871. begin
  15872. result := false;
  15873. if PW<>nil then
  15874. while true do
  15875. if ord(PW^)=0 then
  15876. break else
  15877. if ord(PW^)<=127 then
  15878. inc(PW) else // 7 bits chars are always OK, whatever codepage/charset is used
  15879. exit;
  15880. result := true;
  15881. end;
  15882. function IsAnsiCompatible(PW: PWideChar; Len: integer): boolean; overload;
  15883. var i: integer;
  15884. begin
  15885. result := false;
  15886. if PW<>nil then
  15887. for i := 0 to Len-1 do
  15888. if ord(PW[i])>127 then
  15889. exit;
  15890. result := true;
  15891. end;
  15892. function IsWinAnsi(WideText: PWideChar): boolean;
  15893. begin
  15894. result := WinAnsiConvert.IsValidAnsi(WideText);
  15895. end;
  15896. function IsWinAnsiU(UTF8Text: PUTF8Char): boolean;
  15897. begin
  15898. result := WinAnsiConvert.IsValidAnsiU(UTF8Text);
  15899. end;
  15900. function IsWinAnsiU8Bit(UTF8Text: PUTF8Char): boolean;
  15901. begin
  15902. result := WinAnsiConvert.IsValidAnsiU8Bit(UTF8Text);
  15903. end;
  15904. function UTF8ToWinPChar(dest: PAnsiChar; source: PUTF8Char; count: integer): integer;
  15905. begin
  15906. result := WinAnsiConvert.UTF8BufferToAnsi(dest,source,count)-dest;
  15907. end;
  15908. function ShortStringToAnsi7String(const source: shortstring): RawByteString;
  15909. begin
  15910. SetString(result,PAnsiChar(@source[1]),ord(source[0]));
  15911. end;
  15912. procedure ShortStringToAnsi7String(const source: shortstring; var result: RawUTF8);
  15913. begin
  15914. SetString(result,PAnsiChar(@source[1]),ord(source[0]));
  15915. end;
  15916. procedure UTF8ToShortString(var dest: shortstring; source: PUTF8Char);
  15917. var c: cardinal;
  15918. len,extra,i: integer;
  15919. begin
  15920. len := 0;
  15921. if source<>nil then
  15922. repeat
  15923. c := byte(source^); inc(source);
  15924. if c=0 then break else
  15925. if c and $80=0 then begin
  15926. inc(len); dest[len] := AnsiChar(c);
  15927. if len<253 then continue else break;
  15928. end else begin
  15929. extra := UTF8_EXTRABYTES[c];
  15930. if extra=0 then break; // invalid leading byte
  15931. for i := 1 to extra do begin
  15932. if byte(source^) and $c0<>$80 then begin
  15933. dest[0] := AnsiChar(len);
  15934. exit; // invalid UTF-8 content
  15935. end;
  15936. c := c shl 6+byte(source^);
  15937. inc(Source);
  15938. end;
  15939. dec(c,UTF8_EXTRA[extra].offset);
  15940. // #256.. -> slower but accurate conversion
  15941. inc(len);
  15942. if c>$ffff then
  15943. dest[len] := '?' else
  15944. dest[len] := AnsiChar(WinAnsiConvert.fWideToAnsi[c]);
  15945. if len<253 then continue else break;
  15946. end;
  15947. until false;
  15948. dest[0] := AnsiChar(len);
  15949. end;
  15950. function Utf8ToWinAnsi(const S: RawUTF8): WinAnsiString;
  15951. begin
  15952. result := WinAnsiConvert.UTF8ToAnsi(S);
  15953. end;
  15954. function Utf8ToWinAnsi(P: PUTF8Char): WinAnsiString;
  15955. begin
  15956. result := WinAnsiConvert.UTF8ToAnsi(P);
  15957. end;
  15958. procedure Utf8ToRawUTF8(P: PUTF8Char; var result: RawUTF8);
  15959. begin // fast and Delphi 2009+ ready
  15960. SetRawUTF8(result,P,StrLen(P));
  15961. end;
  15962. function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; MaxDestChars, sourceBytes: PtrInt): PtrInt;
  15963. // faster than System.Utf8ToUnicode()
  15964. var c: cardinal;
  15965. begd: PWideChar;
  15966. endSource: PUTF8Char;
  15967. endDest: PWideChar;
  15968. i,extra: integer;
  15969. label Quit, NoSource;
  15970. begin
  15971. result := 0;
  15972. if dest=nil then
  15973. exit;
  15974. if source=nil then
  15975. goto NoSource;
  15976. if sourceBytes=0 then begin
  15977. if source^=#0 then
  15978. goto NoSource;
  15979. sourceBytes := StrLen(source);
  15980. end;
  15981. endSource := source+sourceBytes;
  15982. endDest := dest+MaxDestChars;
  15983. begd := dest;
  15984. repeat
  15985. c := byte(source^);
  15986. inc(source);
  15987. if c and $80=0 then begin
  15988. PWord(dest)^ := c; // much faster than dest^ := WideChar(c) for FPC
  15989. inc(dest);
  15990. if (source<endsource) and (dest<endDest) then
  15991. continue else
  15992. break;
  15993. end;
  15994. extra := UTF8_EXTRABYTES[c];
  15995. if (extra=0) or (Source+extra>endSource) then break;
  15996. for i := 1 to extra do begin
  15997. if byte(Source^) and $c0<>$80 then
  15998. goto Quit; // invalid input content
  15999. c := c shl 6+byte(Source^);
  16000. inc(Source);
  16001. end;
  16002. with UTF8_EXTRA[extra] do begin
  16003. dec(c,offset);
  16004. if c<minimum then
  16005. break; // invalid input content
  16006. end;
  16007. if c<=$ffff then begin
  16008. PWord(dest)^ := c;
  16009. inc(dest);
  16010. if (source<endsource) and (dest<endDest) then
  16011. continue else
  16012. break;
  16013. end;
  16014. dec(c,$10000); // store as UTF-16 surrogates
  16015. PWordArray(dest)[0] := c shr 10 +UTF16_HISURROGATE_MIN;
  16016. PWordArray(dest)[1] := c and $3FF+UTF16_LOSURROGATE_MIN;
  16017. inc(dest,2);
  16018. if (source>=endsource) or (dest>=endDest) then
  16019. break;
  16020. until false;
  16021. Quit:
  16022. result := PtrUInt(dest)-PtrUInt(begd); // dest-begd return byte length
  16023. NoSource:
  16024. dest^ := #0; // always append a WideChar(0) to the end of the buffer
  16025. end;
  16026. function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; sourceBytes: PtrInt=0): PtrInt;
  16027. // faster than System.UTF8Decode()
  16028. var c: cardinal;
  16029. begd: PWideChar;
  16030. endSource, endSourceBy4: PUTF8Char;
  16031. i,extra: PtrInt;
  16032. label Quit, NoSource, By1, By4;
  16033. begin
  16034. result := 0;
  16035. if dest=nil then
  16036. exit;
  16037. if source=nil then
  16038. goto NoSource;
  16039. if sourceBytes=0 then begin
  16040. if source^=#0 then
  16041. goto NoSource;
  16042. sourceBytes := StrLen(source);
  16043. end;
  16044. begd := dest;
  16045. endSource := Source+SourceBytes;
  16046. endSourceBy4 := endSource-4;
  16047. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then
  16048. repeat // handle 7 bit ASCII chars, by quad (Sha optimization)
  16049. By4: c := PCardinal(Source)^;
  16050. if c and $80808080<>0 then
  16051. goto By1; // break on first non ASCII quad
  16052. inc(Source,4);
  16053. PCardinal(dest)^ := (c shl 8 or (c and $FF)) and $00ff00ff;
  16054. c := c shr 16;
  16055. PCardinal(dest+2)^ := (c shl 8 or c) and $00ff00ff;
  16056. inc(dest,4);
  16057. until Source>EndSourceBy4;
  16058. if Source<endSource then
  16059. repeat
  16060. By1: c := byte(Source^); inc(Source);
  16061. if c and $80=0 then begin
  16062. PWord(dest)^ := c; // much faster than dest^ := WideChar(c) for FPC
  16063. inc(dest);
  16064. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then goto By4;
  16065. if Source<endSource then continue else break;
  16066. end;
  16067. extra := UTF8_EXTRABYTES[c];
  16068. if (extra=0) or (Source+extra>endSource) then break;
  16069. for i := 1 to extra do begin
  16070. if byte(Source^) and $c0<>$80 then
  16071. goto Quit; // invalid input content
  16072. c := c shl 6+byte(Source^);
  16073. inc(Source);
  16074. end;
  16075. with UTF8_EXTRA[extra] do begin
  16076. dec(c,offset);
  16077. if c<minimum then
  16078. break; // invalid input content
  16079. end;
  16080. if c<=$ffff then begin
  16081. PWord(dest)^ := c;
  16082. inc(dest);
  16083. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then goto By4;
  16084. if Source<endSource then continue else break;
  16085. end;
  16086. dec(c,$10000); // store as UTF-16 surrogates
  16087. PWordArray(dest)[0] := c shr 10 +UTF16_HISURROGATE_MIN;
  16088. PWordArray(dest)[1] := c and $3FF+UTF16_LOSURROGATE_MIN;
  16089. inc(dest,2);
  16090. if (PtrUInt(Source) and 3=0) and (Source<=EndSourceBy4) then goto By4;
  16091. if Source>=endSource then break;
  16092. until false;
  16093. Quit:
  16094. result := PtrUInt(dest)-PtrUInt(begd); // dest-begd return char length
  16095. NoSource:
  16096. dest^ := #0; // always append a WideChar(0) to the end of the buffer
  16097. end;
  16098. function IsValidUTF8(source: PUTF8Char): Boolean;
  16099. var extra, i: integer;
  16100. c: cardinal;
  16101. begin
  16102. result := false;
  16103. if source<>nil then
  16104. repeat
  16105. c := byte(source^);
  16106. inc(source);
  16107. if c=0 then break else
  16108. if c and $80<>0 then begin
  16109. extra := UTF8_EXTRABYTES[c];
  16110. if extra=0 then exit else // invalid leading byte
  16111. for i := 1 to extra do
  16112. if byte(source^) and $c0<>$80 then
  16113. exit else
  16114. inc(source); // check valid UTF-8 content
  16115. end;
  16116. until false;
  16117. result := true;
  16118. end;
  16119. function IsValidUTF8WithoutControlChars(source: PUTF8Char): Boolean;
  16120. var extra, i: integer;
  16121. c: cardinal;
  16122. begin
  16123. result := false;
  16124. if source<>nil then
  16125. repeat
  16126. c := byte(source^);
  16127. inc(source);
  16128. if c=0 then break else
  16129. if c<32 then exit else // disallow #1..#31 control char
  16130. if c and $80<>0 then begin
  16131. extra := UTF8_EXTRABYTES[c];
  16132. if extra=0 then exit else // invalid leading byte
  16133. for i := 1 to extra do
  16134. if byte(source^) and $c0<>$80 then
  16135. exit else
  16136. inc(source); // check valid UTF-8 content
  16137. end;
  16138. until false;
  16139. result := true;
  16140. end;
  16141. function Utf8ToUnicodeLength(source: PUTF8Char): PtrUInt;
  16142. var c: byte;
  16143. extra,i: integer;
  16144. begin
  16145. result := 0;
  16146. if source<>nil then
  16147. repeat
  16148. c := byte(source^);
  16149. inc(source);
  16150. if c=0 then break else
  16151. if c and $80=0 then
  16152. inc(result) else begin
  16153. extra := UTF8_EXTRABYTES[c];
  16154. if extra=0 then exit else // invalid leading byte
  16155. if extra>=UTF8_EXTRA_SURROGATE then
  16156. inc(result,2) else
  16157. inc(result);
  16158. for i := 1 to extra do // inc(source,extra) is faster but not safe
  16159. if byte(source^) and $c0<>$80 then
  16160. exit else
  16161. inc(source); // check valid UTF-8 content
  16162. end;
  16163. until false;
  16164. end;
  16165. function Utf8TruncateToUnicodeLength(var text: RawUTF8; maxUTF16: integer): boolean;
  16166. var c: byte;
  16167. extra,i: integer;
  16168. source: PUTF8Char;
  16169. begin
  16170. source := pointer(text);
  16171. if (source<>nil) and (cardinal(maxUtf16)<cardinal(length(text))) then
  16172. repeat
  16173. if maxUTF16<=0 then begin
  16174. SetLength(text,source-pointer(text)); // truncate
  16175. result := true;
  16176. exit;
  16177. end;
  16178. c := byte(source^);
  16179. inc(source);
  16180. if c=0 then break else
  16181. if c and $80=0 then
  16182. dec(maxUTF16) else begin
  16183. extra := UTF8_EXTRABYTES[c];
  16184. if extra=0 then break else // invalid leading byte
  16185. if extra>=UTF8_EXTRA_SURROGATE then
  16186. dec(maxUTF16,2) else
  16187. dec(maxUTF16);
  16188. for i := 1 to extra do // inc(source,extra) is faster but not safe
  16189. if byte(source^) and $c0<>$80 then
  16190. break else
  16191. inc(source); // check valid UTF-8 content
  16192. end;
  16193. until false;
  16194. result := false;
  16195. end;
  16196. function Utf8TruncateToLength(var text: RawUTF8; maxUTF8: cardinal): boolean;
  16197. begin
  16198. if cardinal(length(text))<maxUTF8 then begin
  16199. result := false;
  16200. exit; // nothing to truncate
  16201. end;
  16202. while (maxUTF8>0) and (ord(Text[maxUTF8]) and $c0=$80) do dec(maxUTF8);
  16203. SetLength(text,maxUTF8);
  16204. result := true;
  16205. end;
  16206. function Utf8FirstLineToUnicodeLength(source: PUTF8Char): PtrInt;
  16207. var c: byte;
  16208. extra: Integer;
  16209. begin
  16210. result := 0;
  16211. if source<>nil then
  16212. repeat
  16213. c := byte(source^);
  16214. inc(source);
  16215. if c in [0,10,13] then break else // #0, #10 or #13 stop the count
  16216. if c and $80=0 then
  16217. inc(result) else begin
  16218. extra := UTF8_EXTRABYTES[c];
  16219. if extra=0 then exit else // invalid leading byte
  16220. if extra>=UTF8_EXTRA_SURROGATE then
  16221. inc(result,2) else
  16222. inc(result);
  16223. inc(source,extra); // a bit less safe, but faster
  16224. end;
  16225. until false;
  16226. end;
  16227. function Utf8DecodeToRawUnicode(P: PUTF8Char; L: integer): RawUnicode; overload;
  16228. var short: array[0..256*6] of WideChar;
  16229. U: PWideChar;
  16230. begin
  16231. result := ''; // somewhat faster if result is freed before any SetLength()
  16232. if L=0 then
  16233. L := StrLen(P);
  16234. if L=0 then
  16235. exit;
  16236. // +1 below is for #0 ending -> true WideChar(#0) ending
  16237. if L<sizeof(short)div 3 then // mostly avoid tmp memory allocation on heap
  16238. SetString(result,PAnsiChar(@short),UTF8ToWideChar(short,P,L)+1) else begin
  16239. GetMem(U,L*3+2); // maximum posible unicode size (if all <#128)
  16240. SetString(result,PAnsiChar(U),UTF8ToWideChar(U,P,L)+1);
  16241. FreeMem(U);
  16242. end;
  16243. end;
  16244. function Utf8DecodeToRawUnicode(const S: RawUTF8): RawUnicode; overload;
  16245. begin
  16246. if S='' then
  16247. result := '' else
  16248. result := Utf8DecodeToRawUnicode(pointer(S),length(S));
  16249. end;
  16250. function Utf8DecodeToRawUnicodeUI(const S: RawUTF8; DestLen: PInteger=nil): RawUnicode;
  16251. var L: integer;
  16252. begin
  16253. L := Utf8DecodeToRawUnicodeUI(S,result);
  16254. if DestLen<>nil then
  16255. DestLen^ := L;
  16256. end;
  16257. function Utf8DecodeToRawUnicodeUI(const S: RawUTF8; var Dest: RawUnicode): integer; overload;
  16258. begin
  16259. Dest := ''; // somewhat faster if Dest is freed before any SetLength()
  16260. if S='' then begin
  16261. result := 0;
  16262. exit;
  16263. end;
  16264. result := length(S);
  16265. SetLength(Dest,result*2+2);
  16266. result := UTF8ToWideChar(pointer(Dest),Pointer(S),result);
  16267. end;
  16268. function RawUnicodeToUtf8(Dest: PUTF8Char; DestLen: PtrInt; Source: PWideChar;
  16269. SourceLen: PtrInt): PtrInt; overload;
  16270. var c: Cardinal;
  16271. Tail: PWideChar;
  16272. i,j: integer;
  16273. begin
  16274. result := PtrInt(Dest);
  16275. inc(DestLen,PtrInt(Dest));
  16276. if (Source<>nil) and (Dest<>nil) then begin
  16277. // first handle 7 bit ASCII WideChars, by pairs (Sha optimization)
  16278. SourceLen := SourceLen*2+PtrInt(Source);
  16279. Tail := PWideChar(SourceLen)-2;
  16280. if (PtrInt(Dest)<DestLen) and (Source<=Tail) then
  16281. repeat
  16282. c := PCardinal(Source)^;
  16283. if c and $ff80ff80<>0 then
  16284. break; // break on first non ASCII pair
  16285. inc(Source,2);
  16286. c := c shr 8 or c;
  16287. PWord(Dest)^ := c;
  16288. inc(Dest,2);
  16289. until (Source>Tail) or (PtrInt(Dest)>=DestLen);
  16290. // generic loop, handling one UCS4 char per iteration
  16291. if (PtrInt(Dest)<DestLen) and (PtrInt(Source)<SourceLen) then
  16292. repeat
  16293. // inlined UTF16CharToUtf8()
  16294. c := cardinal(Source^);
  16295. inc(Source);
  16296. case c of
  16297. 0..$7f: begin
  16298. Dest^ := AnsiChar(c);
  16299. inc(Dest);
  16300. if (PtrInt(Dest)<DestLen) and (PtrInt(Source)<SourceLen) then continue else break;
  16301. end;
  16302. UTF16_HISURROGATE_MIN..UTF16_HISURROGATE_MAX: begin
  16303. if PtrInt(Source)>=SourceLen then break;
  16304. c := ((c-$D7C0)shl 10)+(cardinal(Source^) xor UTF16_LOSURROGATE_MIN);
  16305. inc(Source);
  16306. end;
  16307. UTF16_LOSURROGATE_MIN..UTF16_LOSURROGATE_MAX: begin
  16308. if PtrInt(Source)>=SourceLen then break;
  16309. c := ((cardinal(Source^)-$D7C0)shl 10)+(c xor UTF16_LOSURROGATE_MIN);
  16310. inc(Source);
  16311. end;
  16312. end; // now c is the UTF-32/UCS4 code point
  16313. case c of
  16314. 0..$7ff: i := 2;
  16315. $800..$ffff: i := 3;
  16316. $10000..$1FFFFF: i := 4;
  16317. $200000..$3FFFFFF: i := 5;
  16318. else i := 6;
  16319. end;
  16320. if PtrInt(Dest)+i>DestLen then
  16321. break;
  16322. for j := i-1 downto 1 do begin
  16323. Dest[j] := AnsiChar((c and $3f)+$80);
  16324. c := c shr 6;
  16325. end;
  16326. Dest^ := AnsiChar(Byte(c) or UTF8_FIRSTBYTE[i]);
  16327. inc(Dest,i);
  16328. if (PtrInt(Dest)<DestLen) and (PtrInt(Source)<SourceLen) then continue else break;
  16329. until false;
  16330. Dest^ := #0;
  16331. end;
  16332. result := PtrInt(Dest)-result;
  16333. end;
  16334. procedure RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer; var result: RawUTF8);
  16335. var tmp: TSynTempBuffer;
  16336. begin
  16337. if (WideChar=nil) or (WideCharCount=0) then
  16338. result := '' else begin
  16339. tmp.Init(WideCharCount*3);
  16340. SetRawUTF8(Result,tmp.buf,RawUnicodeToUtf8(tmp.buf,tmp.len+1,WideChar,WideCharCount));
  16341. tmp.Done;
  16342. end;
  16343. end;
  16344. function RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer): RawUTF8;
  16345. begin
  16346. RawUnicodeToUTF8(WideChar,WideCharCount,result);
  16347. end;
  16348. function RawUnicodeToUtf8(WideChar: PWideChar; WideCharCount: integer; out UTF8Length: integer): RawUTF8; overload;
  16349. var LW: integer;
  16350. begin
  16351. result := ''; // somewhat faster if result is freed before any SetLength()
  16352. if WideCharCount=0 then
  16353. exit;
  16354. LW := WideCharCount*3; // maximum resulting length
  16355. SetLength(result,LW);
  16356. UTF8Length := RawUnicodeToUtf8(pointer(result),LW+1,WideChar,WideCharCount);
  16357. if UTF8Length<=0 then
  16358. result := '';
  16359. end;
  16360. /// convert a RawUnicode string into a UTF-8 string
  16361. function RawUnicodeToUtf8(const Unicode: RawUnicode): RawUTF8;
  16362. begin
  16363. RawUnicodeToUtf8(pointer(Unicode),length(Unicode) shr 1,result);
  16364. end;
  16365. function SynUnicodeToUtf8(const Unicode: SynUnicode): RawUTF8;
  16366. begin
  16367. RawUnicodeToUtf8(pointer(Unicode),length(Unicode),result);
  16368. end;
  16369. function RawUnicodeToSynUnicode(const Unicode: RawUnicode): Synunicode;
  16370. begin
  16371. SetString(result,PWideChar(pointer(Unicode)),length(Unicode) shr 1);
  16372. end;
  16373. function RawUnicodeToSynUnicode(WideChar: PWideChar; WideCharCount: integer): SynUnicode; overload;
  16374. begin
  16375. SetString(result,WideChar,WideCharCount);
  16376. end;
  16377. procedure RawUnicodeToWinPChar(dest: PAnsiChar; source: PWideChar; WideCharCount: Integer);
  16378. begin
  16379. WinAnsiConvert.UnicodeBufferToAnsi(dest,source,WideCharCount);
  16380. end;
  16381. function RawUnicodeToWinAnsi(WideChar: PWideChar; WideCharCount: integer): WinAnsiString; overload;
  16382. begin
  16383. result := WinAnsiConvert.UnicodeBufferToAnsi(WideChar,WideCharCount);
  16384. end;
  16385. function RawUnicodeToWinAnsi(const Unicode: RawUnicode): WinAnsiString;
  16386. begin
  16387. result := WinAnsiConvert.UnicodeBufferToAnsi(pointer(Unicode),length(Unicode) shr 1);
  16388. end;
  16389. function WideStringToWinAnsi(const Wide: WideString): WinAnsiString;
  16390. begin
  16391. result := WinAnsiConvert.UnicodeBufferToAnsi(pointer(Wide),length(Wide));
  16392. end;
  16393. procedure UnicodeBufferToWinAnsi(source: PWideChar; out Dest: WinAnsiString);
  16394. var L: integer;
  16395. begin
  16396. L := StrLenW(source);
  16397. SetLength(Dest,L);
  16398. WinAnsiConvert.UnicodeBufferToAnsi(pointer(Dest),source,L);
  16399. end;
  16400. function UnicodeBufferToString(source: PWideChar): string;
  16401. begin
  16402. result := RawUnicodeToString(source,StrLenW(source));
  16403. end;
  16404. procedure AnsiCharToUTF8(P: PAnsiChar; L: Integer; var result: RawUTF8; ACP: integer);
  16405. begin
  16406. result := TSynAnsiConvert.Engine(ACP).AnsiBufferToRawUTF8(P,L);
  16407. end;
  16408. {$ifdef HASVARUSTRING}
  16409. function UnicodeStringToUtf8(const S: UnicodeString): RawUTF8;
  16410. begin
  16411. RawUnicodeToUtf8(pointer(S),length(S),result);
  16412. end;
  16413. function UTF8DecodeToUnicodeString(const S: RawUTF8): UnicodeString;
  16414. begin
  16415. UTF8DecodeToUnicodeString(pointer(S),length(S),result);
  16416. end;
  16417. procedure UTF8DecodeToUnicodeString(P: PUTF8Char; L: integer; var result: UnicodeString);
  16418. var short: array[byte] of WideChar;
  16419. U: PWideChar;
  16420. begin
  16421. if (P=nil) or (L=0) then
  16422. result := '' else
  16423. if L<sizeof(short)div 3 then
  16424. SetString(result,short,UTF8ToWideChar(short,P,L) shr 1) else begin
  16425. GetMem(U,L*3+2); // maximum posible unicode size (if all <#128)
  16426. SetString(result,U,UTF8ToWideChar(U,P,L) shr 1);
  16427. FreeMem(U);
  16428. end;
  16429. end;
  16430. function UnicodeStringToWinAnsi(const S: string): WinAnsiString;
  16431. begin
  16432. result := RawUnicodeToWinAnsi(pointer(S),length(S));
  16433. end;
  16434. function UTF8DecodeToUnicodeString(P: PUTF8Char; L: integer): UnicodeString;
  16435. begin
  16436. UTF8DecodeToUnicodeString(P,L,result);
  16437. end;
  16438. function WinAnsiToUnicodeString(WinAnsi: PAnsiChar; WinAnsiLen: integer): UnicodeString;
  16439. begin
  16440. SetString(result,nil,WinAnsiLen);
  16441. WinAnsiConvert.AnsiBufferToUnicode(pointer(result),WinAnsi,WinAnsiLen);
  16442. end;
  16443. function WinAnsiToUnicodeString(const WinAnsi: WinAnsiString): UnicodeString;
  16444. begin
  16445. result := WinAnsiToUnicodeString(pointer(WinAnsi),length(WinAnsi));
  16446. end;
  16447. {$endif HASVARUSTRING}
  16448. {$ifdef UNICODE}
  16449. function Ansi7ToString(const Text: RawByteString): string;
  16450. var i: integer;
  16451. begin
  16452. SetString(result,nil,length(Text));
  16453. for i := 0 to length(Text)-1 do
  16454. PWordArray(result)[i] := PByteArray(Text)[i]; // no conversion for 7 bit Ansi
  16455. end;
  16456. {$else}
  16457. function Ansi7ToString(const Text: RawByteString): string;
  16458. begin
  16459. result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign
  16460. end;
  16461. {$endif}
  16462. {$ifdef UNICODE}
  16463. function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string;
  16464. begin
  16465. Ansi7ToString(Text,Len,result);
  16466. end;
  16467. {$else}
  16468. function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string;
  16469. begin
  16470. SetString(result,PAnsiChar(Text),Len);
  16471. end;
  16472. {$endif}
  16473. {$ifdef UNICODE}
  16474. procedure Ansi7ToString(Text: PWinAnsiChar; Len: integer; var result: string);
  16475. var i: integer;
  16476. begin
  16477. SetString(result,nil,Len);
  16478. for i := 0 to Len-1 do
  16479. PWordArray(result)[i] := PByteArray(Text)[i]; // no conversion for 7 bit Ansi
  16480. end;
  16481. {$else}
  16482. procedure Ansi7ToString(Text: PWinAnsiChar; Len: integer; var result: string);
  16483. begin
  16484. SetString(result,PAnsiChar(Text),Len);
  16485. end;
  16486. {$endif}
  16487. {$ifdef UNICODE}
  16488. function StringToAnsi7(const Text: string): RawByteString;
  16489. var i: integer;
  16490. begin
  16491. SetString(result,nil,length(Text));
  16492. for i := 0 to length(Text)-1 do
  16493. PByteArray(result)[i] := PWordArray(Text)[i]; // no conversion for 7 bit Ansi
  16494. end;
  16495. {$else}
  16496. function StringToAnsi7(const Text: string): RawByteString;
  16497. begin
  16498. result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign
  16499. end;
  16500. {$endif}
  16501. {$ifdef UNICODE}
  16502. function StringToWinAnsi(const Text: string): WinAnsiString;
  16503. begin
  16504. result := RawUnicodeToWinAnsi(Pointer(Text),length(Text));
  16505. end;
  16506. {$else}
  16507. function StringToWinAnsi(const Text: string): WinAnsiString;
  16508. begin
  16509. result := WinAnsiConvert.AnsiToAnsi(CurrentAnsiConvert,Text);
  16510. end;
  16511. {$endif}
  16512. {$ifdef UNICODE}
  16513. function StringBufferToUtf8(Dest: PUTF8Char; Source: PChar; SourceChars: PtrInt): PUTF8Char;
  16514. begin
  16515. result := Dest+RawUnicodeToUtf8(Dest,SourceChars*3,PWideChar(Source),SourceChars);
  16516. end;
  16517. {$else}
  16518. function StringBufferToUtf8(Dest: PUTF8Char; Source: PChar; SourceChars: PtrInt): PUTF8Char;
  16519. begin
  16520. result := CurrentAnsiConvert.AnsiBufferToUTF8(Dest,Source,SourceChars);
  16521. end;
  16522. {$endif}
  16523. {$ifdef UNICODE}
  16524. function StringToUTF8(const Text: string): RawUTF8;
  16525. begin
  16526. RawUnicodeToUtf8(pointer(Text),length(Text),result);
  16527. end;
  16528. {$else}
  16529. function StringToUTF8(const Text: string): RawUTF8;
  16530. begin
  16531. result := CurrentAnsiConvert.AnsiToUTF8(Text);
  16532. end;
  16533. {$endif}
  16534. {$ifdef UNICODE}
  16535. procedure StringToUTF8(const Text: string; var result: RawUTF8);
  16536. begin
  16537. RawUnicodeToUtf8(pointer(Text),length(Text),result);
  16538. end;
  16539. {$else}
  16540. procedure StringToUTF8(const Text: string; var result: RawUTF8);
  16541. begin
  16542. result := CurrentAnsiConvert.AnsiToUTF8(Text);
  16543. end;
  16544. {$endif}
  16545. {$ifdef UNICODE}
  16546. function ToUTF8(const Text: string): RawUTF8;
  16547. begin
  16548. RawUnicodeToUtf8(pointer(Text),length(Text),result);
  16549. end;
  16550. {$else}
  16551. function ToUTF8(const Text: string): RawUTF8;
  16552. begin
  16553. result := CurrentAnsiConvert.AnsiToUTF8(Text);
  16554. end;
  16555. {$endif}
  16556. function ToUTF8(const Ansi7Text: ShortString): RawUTF8;
  16557. begin
  16558. SetString(result,PAnsiChar(@Ansi7Text[1]),ord(Ansi7Text[0]));
  16559. end;
  16560. function ToUTF8(const guid: TGUID): RawUTF8;
  16561. begin
  16562. FastNewRawUTF8(result,36);
  16563. GUIDToText(pointer(result),@guid);
  16564. end;
  16565. procedure Int32ToUTF8(Value : integer; var result: RawUTF8);
  16566. var tmp: array[0..15] of AnsiChar;
  16567. P: PAnsiChar;
  16568. begin
  16569. P := StrInt32(@tmp[15],Value);
  16570. SetRawUTF8(result,P,@tmp[15]-P);
  16571. end;
  16572. procedure Int64ToUtf8(Value: Int64; var result: RawUTF8);
  16573. var tmp: array[0..23] of AnsiChar;
  16574. P: PAnsiChar;
  16575. begin
  16576. P := StrInt64(@tmp[23],Value);
  16577. SetRawUTF8(result,P,@tmp[23]-P);
  16578. end;
  16579. function VarRecAsChar(const V: TVarRec): integer;
  16580. begin
  16581. case V.VType of
  16582. vtChar: result := ord(V.VChar);
  16583. vtWideChar: result := ord(V.VWideChar);
  16584. else result := 0;
  16585. end;
  16586. end;
  16587. function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;
  16588. begin
  16589. case V.VType of
  16590. vtInteger: value := V.VInteger;
  16591. vtInt64: value := V.VInt64^;
  16592. vtBoolean: if V.VBoolean then
  16593. value := 1 else
  16594. value := 0;
  16595. {$ifndef NOVARIANTS}
  16596. vtVariant: value := V.VVariant^;
  16597. {$endif}
  16598. else begin
  16599. result := false;
  16600. exit;
  16601. end;
  16602. end;
  16603. result := true;
  16604. end;
  16605. function VarRecToDouble(const V: TVarRec; out value: double): boolean;
  16606. begin
  16607. case V.VType of
  16608. vtInteger: value := V.VInteger;
  16609. vtInt64: value := V.VInt64^;
  16610. vtBoolean: if V.VBoolean then
  16611. value := 1 else
  16612. value := 0;
  16613. vtExtended: value := V.VExtended^;
  16614. vtCurrency: value := V.VCurrency^;
  16615. {$ifndef NOVARIANTS}
  16616. vtVariant: value := V.VVariant^;
  16617. {$endif}
  16618. else begin
  16619. result := false;
  16620. exit;
  16621. end;
  16622. end;
  16623. result := true;
  16624. end;
  16625. function VarRecToTempUTF8(const V: TVarRec; var tmpStr: RawUTF8; var Res: TTempUTF8): integer;
  16626. {$ifndef NOVARIANTS}
  16627. var isString: boolean;
  16628. {$endif}
  16629. begin
  16630. case V.VType of
  16631. vtString: begin
  16632. Res.Text := @V.VString^[1];
  16633. Res.Len := ord(V.VString^[0]);
  16634. result := Res.Len;
  16635. exit;
  16636. end;
  16637. vtAnsiString: begin // expect UTF-8 content
  16638. Res.Text := pointer(V.VAnsiString);
  16639. Res.Len := length(RawUTF8(V.VAnsiString));
  16640. result := Res.Len;
  16641. exit;
  16642. end;
  16643. {$ifdef HASVARUSTRING}
  16644. vtUnicodeString:
  16645. RawUnicodeToUtf8(V.VPWideChar,length(UnicodeString(V.VUnicodeString)),tmpStr);
  16646. {$endif}
  16647. vtWideString:
  16648. RawUnicodeToUtf8(V.VPWideChar,length(WideString(V.VWideString)),tmpStr);
  16649. vtPChar: begin
  16650. Res.Text := V.VPointer;
  16651. Res.Len := StrLen(V.VPointer);
  16652. result := Res.Len;
  16653. exit;
  16654. end;
  16655. vtChar: begin
  16656. {$ifdef FPC} // alf: to circumvent FPC issues
  16657. RawUnicodeToUtf8(@V.VChar,1,tmpStr);
  16658. {$else}
  16659. Res.Text := @V.VChar;
  16660. Res.Len := 1;
  16661. result := 1;
  16662. exit;
  16663. {$endif}
  16664. end;
  16665. vtPWideChar:
  16666. RawUnicodeToUtf8(V.VPWideChar,StrLenW(V.VPWideChar),tmpStr);
  16667. vtWideChar:
  16668. RawUnicodeToUtf8(@V.VWideChar,1,tmpStr);
  16669. vtBoolean: begin
  16670. Res.Temp[0] := AnsiChar(ord(V.VBoolean)+48);
  16671. Res.Text := @Res.Temp;
  16672. Res.Len := 1;
  16673. result := 1;
  16674. exit;
  16675. end;
  16676. vtInteger: begin
  16677. Res.Text := PUTF8Char(StrInt32(@Res.Temp[23],V.VInteger));
  16678. Res.Len := @Res.Temp[23]-Res.Text;
  16679. result := Res.Len;
  16680. exit;
  16681. end;
  16682. vtInt64: begin
  16683. Res.Text := PUTF8Char(StrInt64(@Res.Temp[23],V.VInt64^));
  16684. Res.Len := @Res.Temp[23]-Res.Text;
  16685. result := Res.Len;
  16686. exit;
  16687. end;
  16688. vtCurrency: begin
  16689. Res.Text := @Res.Temp;
  16690. Res.Len := Curr64ToPChar(V.VInt64^,Res.Temp);
  16691. result := Res.Len;
  16692. exit;
  16693. end;
  16694. vtExtended:
  16695. ExtendedToStr(V.VExtended^,DOUBLE_PRECISION,tmpStr);
  16696. vtPointer,vtInterface: begin
  16697. Res.Text := @Res.Temp;
  16698. Res.Len := sizeof(pointer)*2;
  16699. BinToHexDisplay(V.VPointer,@Res.Temp,sizeof(Pointer));
  16700. result := sizeof(pointer)*2;
  16701. exit;
  16702. end;
  16703. vtClass: begin
  16704. if V.VClass<>nil then begin
  16705. Res.Text := PUTF8Char(PPointer(PtrInt(V.VClass)+vmtClassName)^)+1;
  16706. Res.Len := ord(Res.Text[-1]);
  16707. end else
  16708. Res.Len := 0;
  16709. result := Res.Len;
  16710. exit;
  16711. end;
  16712. vtObject: begin
  16713. if V.VObject<>nil then begin
  16714. Res.Text := PUTF8Char(PPointer(PPtrInt(V.VObject)^+vmtClassName)^)+1;
  16715. Res.Len := ord(Res.Text[-1]);
  16716. end else
  16717. Res.Len := 0;
  16718. result := Res.Len;
  16719. exit;
  16720. end;
  16721. {$ifndef NOVARIANTS}
  16722. vtVariant:
  16723. VariantToUTF8(V.VVariant^,tmpStr,isString);
  16724. {$endif}
  16725. else begin
  16726. Res.Len := 0;
  16727. result := 0;
  16728. exit;
  16729. end;
  16730. end;
  16731. Res.Text := pointer(tmpStr);
  16732. Res.Len := length(tmpStr);
  16733. result := Res.Len;
  16734. end;
  16735. procedure VarRecToUTF8(const V: TVarRec; var result: RawUTF8; wasString: PBoolean=nil);
  16736. var isString: boolean;
  16737. begin
  16738. isString := not (V.VType in [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended]);
  16739. with V do
  16740. case V.VType of
  16741. vtString:
  16742. SetRawUTF8(result,@VString^[1],ord(VString^[0]));
  16743. vtAnsiString:
  16744. result := RawUTF8(VAnsiString); // expect UTF-8 content
  16745. {$ifdef HASVARUSTRING}
  16746. vtUnicodeString:
  16747. result := UnicodeStringToUtf8(UnicodeString(VUnicodeString));
  16748. {$endif}
  16749. vtWideString:
  16750. RawUnicodeToUtf8(VWideString,length(WideString(VWideString)),result);
  16751. vtPChar:
  16752. SetRawUTF8(result,VPChar,StrLen(VPChar));
  16753. vtChar:
  16754. SetRawUTF8(result,PAnsiChar(@VChar),1);
  16755. vtPWideChar:
  16756. RawUnicodeToUtf8(VPWideChar,StrLenW(VPWideChar),result);
  16757. vtWideChar:
  16758. RawUnicodeToUtf8(@VWideChar,1,result);
  16759. vtBoolean:
  16760. if VBoolean then result := '1' else result := '0';
  16761. vtInteger:
  16762. Int32ToUtf8(VInteger,result);
  16763. vtInt64:
  16764. Int64ToUtf8(VInt64^,result);
  16765. vtCurrency:
  16766. Curr64ToStr(VInt64^,result);
  16767. vtExtended:
  16768. ExtendedToStr(VExtended^,DOUBLE_PRECISION,result);
  16769. vtPointer:
  16770. PointerToHex(VPointer,result);
  16771. vtClass:
  16772. if VClass<>nil then
  16773. result := PShortString(PPointer(PtrInt(VClass)+vmtClassName)^)^ else
  16774. result := '';
  16775. vtObject:
  16776. if VObject<>nil then
  16777. result := PShortString(PPointer(PPtrInt(VObject)^+vmtClassName)^)^ else
  16778. result := '';
  16779. vtInterface:
  16780. {$ifdef HASINTERFACEASTOBJECT}
  16781. if VInterface<>nil then
  16782. result := PShortString(PPointer(PPtrInt(IInterface(VInterface) as TObject)^+vmtClassName)^)^ else
  16783. result := '';
  16784. {$else}
  16785. PointerToHex(VInterface,result);
  16786. {$endif}
  16787. {$ifndef NOVARIANTS}
  16788. vtVariant:
  16789. VariantToUTF8(VVariant^,result,isString);
  16790. {$endif}
  16791. else begin
  16792. isString := false;
  16793. result := '';
  16794. end;
  16795. end;
  16796. if wasString<>nil then
  16797. wasString^ := isString;
  16798. end;
  16799. function VarRecToUTF8IsString(const V: TVarRec; var value: RawUTF8): boolean;
  16800. begin
  16801. VarRecToUTF8(V,value,@result);
  16802. end;
  16803. procedure VarRecToInlineValue(const V: TVarRec; var result: RawUTF8);
  16804. var wasString: boolean;
  16805. begin
  16806. VarRecToUTF8(V,result,@wasString);
  16807. if wasString then
  16808. result := QuotedStr(pointer(result),'"');
  16809. end;
  16810. {$ifdef UNICODE}
  16811. function StringToRawUnicode(const S: string): RawUnicode;
  16812. begin
  16813. SetString(result,PAnsiChar(pointer(S)),length(S)*2+1); // +1 for last wide #0
  16814. end;
  16815. {$else}
  16816. function StringToRawUnicode(const S: string): RawUnicode;
  16817. begin
  16818. result := CurrentAnsiConvert.AnsiToRawUnicode(S);
  16819. end;
  16820. {$endif}
  16821. {$ifdef UNICODE}
  16822. function StringToSynUnicode(const S: string): SynUnicode;
  16823. begin
  16824. result := S;
  16825. end;
  16826. {$else}
  16827. function StringToSynUnicode(const S: string): SynUnicode;
  16828. begin
  16829. result := CurrentAnsiConvert.AnsiToUnicodeString(pointer(S),length(S));
  16830. end;
  16831. {$endif}
  16832. {$ifdef UNICODE}
  16833. function StringToRawUnicode(P: PChar; L: integer): RawUnicode;
  16834. begin
  16835. SetString(result,PAnsiChar(P),L*2+1); // +1 for last wide #0
  16836. end;
  16837. {$else}
  16838. function StringToRawUnicode(P: PChar; L: integer): RawUnicode;
  16839. begin
  16840. result := CurrentAnsiConvert.AnsiToRawUnicode(P,L);
  16841. end;
  16842. {$endif}
  16843. {$ifdef UNICODE}
  16844. function RawUnicodeToString(P: PWideChar; L: integer): string; overload;
  16845. begin
  16846. SetString(result,P,L);
  16847. end;
  16848. {$else}
  16849. function RawUnicodeToString(P: PWideChar; L: integer): string; overload;
  16850. begin
  16851. result := CurrentAnsiConvert.UnicodeBufferToAnsi(P,L);
  16852. end;
  16853. {$endif}
  16854. {$ifdef UNICODE}
  16855. procedure RawUnicodeToString(P: PWideChar; L: integer; var result: string); overload;
  16856. begin
  16857. SetString(result,P,L);
  16858. end;
  16859. {$else}
  16860. procedure RawUnicodeToString(P: PWideChar; L: integer; var result: string); overload;
  16861. begin
  16862. result := CurrentAnsiConvert.UnicodeBufferToAnsi(P,L);
  16863. end;
  16864. {$endif}
  16865. {$ifdef UNICODE}
  16866. function RawUnicodeToString(const U: RawUnicode): string;
  16867. begin // uses StrLenW() and not length(U) to handle case when was used as buffer
  16868. SetString(result,PWideChar(pointer(U)),StrLenW(Pointer(U)));
  16869. end;
  16870. {$else}
  16871. function RawUnicodeToString(const U: RawUnicode): string;
  16872. begin // uses StrLenW() and not length(U) to handle case when was used as buffer
  16873. result := CurrentAnsiConvert.UnicodeBufferToAnsi(Pointer(U),StrLenW(Pointer(U)));
  16874. end;
  16875. {$endif}
  16876. {$ifdef UNICODE}
  16877. function SynUnicodeToString(const U: SynUnicode): string;
  16878. begin
  16879. result := U;
  16880. end;
  16881. {$else}
  16882. function SynUnicodeToString(const U: SynUnicode): string;
  16883. begin
  16884. result := CurrentAnsiConvert.UnicodeBufferToAnsi(Pointer(U),length(U));
  16885. end;
  16886. {$endif}
  16887. {$ifdef UNICODE}
  16888. function UTF8DecodeToString(P: PUTF8Char; L: integer): string;
  16889. begin
  16890. UTF8DecodeToUnicodeString(P,L,result);
  16891. end;
  16892. {$else}
  16893. function UTF8DecodeToString(P: PUTF8Char; L: integer): string;
  16894. begin
  16895. CurrentAnsiConvert.UTF8BufferToAnsi(P,L,RawByteString(result));
  16896. end;
  16897. {$endif}
  16898. {$ifdef UNICODE}
  16899. procedure UTF8DecodeToString(P: PUTF8Char; L: integer; var result: string);
  16900. begin
  16901. UTF8DecodeToUnicodeString(P,L,result);
  16902. end;
  16903. {$else}
  16904. procedure UTF8DecodeToString(P: PUTF8Char; L: integer; var result: string);
  16905. begin
  16906. CurrentAnsiConvert.UTF8BufferToAnsi(P,L,RawByteString(result));
  16907. end;
  16908. {$endif}
  16909. {$ifdef UNICODE}
  16910. function UTF8ToString(const Text: RawUTF8): string;
  16911. begin
  16912. UTF8DecodeToUnicodeString(pointer(Text),length(Text),result);
  16913. end;
  16914. {$else}
  16915. function UTF8ToString(const Text: RawUTF8): string;
  16916. begin
  16917. CurrentAnsiConvert.UTF8BufferToAnsi(pointer(Text),length(Text),RawByteString(result));
  16918. end;
  16919. {$endif}
  16920. function UTF8ToWideString(const Text: RawUTF8): WideString;
  16921. begin
  16922. {$ifdef FPC}
  16923. result := '';
  16924. {$endif}
  16925. UTF8ToWideString(Text,result);
  16926. end;
  16927. procedure UTF8ToWideString(const Text: RawUTF8; var result: WideString);
  16928. begin
  16929. UTF8ToWideString(pointer(Text),Length(Text),result);
  16930. end;
  16931. procedure UTF8ToWideString(Text: PUTF8Char; Len: integer; var result: WideString); overload;
  16932. var short: array[0..256*6] of WideChar;
  16933. U: PWideChar;
  16934. begin
  16935. if (Text=nil) or (Len=0) then
  16936. result := '' else
  16937. if Len<sizeof(short)div 3 then
  16938. SetString(result,short,UTF8ToWideChar(short,Text,Len) shr 1) else begin
  16939. GetMem(U,Len*3+2); // maximum posible unicode size (if all <#128)
  16940. SetString(result,U,UTF8ToWideChar(U,Text,Len) shr 1);
  16941. FreeMem(U);
  16942. end;
  16943. end;
  16944. function WideStringToUTF8(const aText: WideString): RawUTF8;
  16945. begin
  16946. RawUnicodeToUtf8(pointer(aText),length(aText),result);
  16947. end;
  16948. function UTF8ToSynUnicode(const Text: RawUTF8): SynUnicode;
  16949. begin
  16950. UTF8ToSynUnicode(pointer(Text),length(Text),result);
  16951. end;
  16952. procedure UTF8ToSynUnicode(const Text: RawUTF8; var result: SynUnicode); overload;
  16953. begin
  16954. UTF8ToSynUnicode(pointer(Text),length(Text),result);
  16955. end;
  16956. procedure UTF8ToSynUnicode(Text: PUTF8Char; Len: integer; var result: SynUnicode); overload;
  16957. var short: array[byte] of WideChar;
  16958. U: PWideChar;
  16959. begin
  16960. if (Text=nil) or (Len=0) then
  16961. result := '' else
  16962. if Len<sizeof(short)div 3 then
  16963. SetString(result,short,UTF8ToWideChar(short,Text,Len) shr 1) else begin
  16964. GetMem(U,Len*3+2); // maximum posible unicode size (if all <#128)
  16965. SetString(result,U,UTF8ToWideChar(U,Text,Len) shr 1);
  16966. FreeMem(U);
  16967. end;
  16968. end;
  16969. function StrInt32(P: PAnsiChar; val: PtrInt): PAnsiChar;
  16970. {$ifdef CPU64}
  16971. {$ifdef FPC}
  16972. begin // fallback to pure pascal version, since asm version below make GPFs for FPC
  16973. if val<0 then begin
  16974. result := StrUInt32(P,PtrUInt(-val))-1;
  16975. result^ := '-';
  16976. end else
  16977. result := StrUInt32(P,val);
  16978. end;
  16979. {$else}
  16980. {$ifdef FPC}nostackframe; assembler;
  16981. asm
  16982. {$else}
  16983. asm // rcx=P, rdx=val (Linux: rdi,rsi)
  16984. .NOFRAME
  16985. {$endif FPC}
  16986. {$ifndef win64}
  16987. mov rcx,rdi
  16988. mov rdx,rsi
  16989. {$endif win64}
  16990. mov r10,rdx
  16991. sar r10,63 // r10=0 if val>=0 or -1 if val<0
  16992. xor rdx,r10
  16993. sub rdx,r10 // rdx=abs(val)
  16994. cmp rdx,10; jb @3 // direct process of common val<10
  16995. mov rax,rdx
  16996. lea r8,TwoDigitLookup
  16997. @s: cmp rax,100
  16998. lea rcx,[rcx-2]
  16999. jb @2
  17000. lea r9,[rax*2]
  17001. shr rax,2
  17002. mov rdx,2951479051793528259 // use power of two reciprocal to avoid division
  17003. mul rdx
  17004. shr rdx,2
  17005. mov rax,rdx
  17006. imul rdx,-200
  17007. lea rdx,[rdx+r8]
  17008. movzx rdx,word ptr [rdx+r9]
  17009. mov [rcx],dx
  17010. cmp rax,10
  17011. jae @s
  17012. @1: or al,'0'
  17013. mov byte ptr [rcx-2],'-'
  17014. mov [rcx-1],al
  17015. lea rax,[rcx+r10-1] // includes '-' if val<0
  17016. ret
  17017. @2: movzx eax,word ptr [r8+rax*2]
  17018. mov byte ptr [rcx-1],'-'
  17019. mov [rcx],ax
  17020. lea rax,[rcx+r10] // includes '-' if val<0
  17021. ret
  17022. @3: or dl,'0'
  17023. mov byte ptr [rcx-2],'-'
  17024. mov [rcx-1],dl
  17025. lea rax,[rcx+r10-1] // includes '-' if val<0
  17026. end;
  17027. {$endif FPC}
  17028. {$else}
  17029. {$ifdef PUREPASCAL}
  17030. begin // this code is faster than the Borland's original str() or IntToStr()
  17031. if val<0 then begin
  17032. result := StrUInt32(P,PtrUInt(-val))-1;
  17033. result^ := '-';
  17034. end else
  17035. result := StrUInt32(P,val);
  17036. end;
  17037. {$else}
  17038. asm // eax=P, edx=val
  17039. mov ecx,edx
  17040. sar ecx,31 // 0 if val>=0 or -1 if val<0
  17041. push ecx
  17042. xor edx,ecx
  17043. sub edx,ecx // edx=abs(val)
  17044. cmp edx,10; jb @3 // direct process of common val<10
  17045. push edi
  17046. mov edi,eax
  17047. mov eax,edx
  17048. //nop; nop // for loop alignment
  17049. @s: cmp eax,100
  17050. lea edi,[edi-2]
  17051. jb @2
  17052. mov ecx,eax
  17053. mov edx,1374389535 // use power of two reciprocal to avoid division
  17054. mul edx
  17055. shr edx,5 // now edx=eax div 100
  17056. mov eax,edx
  17057. imul edx,-200
  17058. movzx edx,word ptr [TwoDigitLookup+ecx*2+edx]
  17059. mov [edi],dx
  17060. cmp eax,10
  17061. jae @s
  17062. @1: dec edi
  17063. or al,'0'
  17064. mov byte ptr [edi-1],'-'
  17065. mov [edi],al
  17066. mov eax,edi
  17067. pop edi
  17068. pop ecx
  17069. lea eax,[eax+ecx] // includes '-' if val<0
  17070. ret
  17071. @2: movzx eax,word ptr [TwoDigitLookup+eax*2]
  17072. mov byte ptr [edi-1],'-'
  17073. mov [edi],ax
  17074. mov eax,edi
  17075. pop edi
  17076. pop ecx
  17077. lea eax,[eax+ecx] // includes '-' if val<0
  17078. ret
  17079. @3: dec eax
  17080. pop ecx
  17081. or dl,'0'
  17082. mov byte ptr [eax-1],'-'
  17083. mov [eax],dl
  17084. lea eax,[eax+ecx] // includes '-' if val<0
  17085. end;
  17086. {$endif CPU64}
  17087. {$endif PUREPASCAL}
  17088. function StrUInt32(P: PAnsiChar; val: PtrUInt): PAnsiChar;
  17089. {$ifdef CPUX64}
  17090. {$ifdef FPC}nostackframe; assembler;
  17091. asm
  17092. {$else}
  17093. asm // rcx=P, rdx=val (Linux: rdi,rsi)
  17094. .NOFRAME
  17095. {$endif FPC}
  17096. {$ifndef win64}
  17097. mov rcx,rdi
  17098. mov rdx,rsi
  17099. {$endif win64}
  17100. cmp rdx,10; jb @3 // direct process of common val<10
  17101. mov rax,rdx
  17102. lea r8, [rip+TwoDigitLookup]
  17103. @s: cmp rax,100
  17104. lea rcx,[rcx-2]
  17105. jb @2
  17106. lea r9,[rax*2]
  17107. shr rax,2
  17108. mov rdx,2951479051793528259 // use power of two reciprocal to avoid division
  17109. mul rdx
  17110. shr rdx,2
  17111. mov rax,rdx
  17112. imul rdx,-200
  17113. lea rdx,[rdx+r8]
  17114. movzx rdx,word ptr [rdx+r9]
  17115. mov [rcx],dx
  17116. cmp rax,10
  17117. jae @s
  17118. @1: dec rcx
  17119. or al,'0'
  17120. mov [rcx],al
  17121. @0: mov rax,rcx
  17122. ret
  17123. @2: movzx eax,word ptr [r8+rax*2]
  17124. mov [rcx],ax
  17125. mov rax,rcx
  17126. ret
  17127. @3: lea rax,[rcx-1]
  17128. or dl,'0'
  17129. mov [rax],dl
  17130. end;
  17131. {$else}
  17132. {$ifdef PUREPASCAL}
  17133. var c100: PtrUInt;
  17134. begin // this code is faster than the Borland's original str() or IntToStr()
  17135. repeat
  17136. if val<10 then begin
  17137. dec(P);
  17138. P^ := AnsiChar(val+ord('0'));
  17139. break;
  17140. end else
  17141. if val<100 then begin
  17142. dec(P,2);
  17143. PWord(P)^ := TwoDigitLookupW[val];
  17144. break;
  17145. end;
  17146. dec(P,2);
  17147. c100 := val div 100;
  17148. dec(val,c100*100);
  17149. PWord(P)^ := TwoDigitLookupW[val];
  17150. val := c100;
  17151. if c100=0 then break;
  17152. until false;
  17153. result := P;
  17154. end;
  17155. {$else}
  17156. asm // eax=P, edx=val
  17157. cmp edx,10; jb @3 // direct process of common val=0 (or val<10)
  17158. push edi
  17159. mov edi,eax
  17160. mov eax,edx
  17161. nop; nop // for loop alignment
  17162. @s: cmp eax,100
  17163. lea edi,[edi-2]
  17164. jb @2
  17165. mov ecx,eax
  17166. mov edx,1374389535 // use power of two reciprocal to avoid division
  17167. mul edx
  17168. shr edx,5 // now edx=eax div 100
  17169. mov eax,edx
  17170. imul edx,-200
  17171. movzx edx,word ptr [TwoDigitLookup+ecx*2+edx]
  17172. mov [edi],dx
  17173. cmp eax,10
  17174. jae @s
  17175. @1: dec edi
  17176. or al,'0'
  17177. mov [edi],al
  17178. mov eax,edi
  17179. pop edi
  17180. ret
  17181. @2: movzx eax,word ptr [TwoDigitLookup+eax*2]
  17182. mov [edi],ax
  17183. mov eax,edi
  17184. pop edi
  17185. ret
  17186. @3: dec eax
  17187. or dl,'0'
  17188. mov [eax],dl
  17189. end;
  17190. {$endif CPU64}
  17191. {$endif PUREPASCAL}
  17192. function StrUInt64(P: PAnsiChar; const val: QWord): PAnsiChar;
  17193. {$ifdef CPU64}
  17194. begin // StrUInt32 aldready implemented PtrUInt=UInt64
  17195. result := StrUInt32(P,val);
  17196. end;
  17197. {$else}
  17198. var c,c100: QWord;
  17199. begin
  17200. if Int64Rec(val).Hi=0 then
  17201. P := StrUInt32(P,Int64Rec(val).Lo) else begin
  17202. c := val;
  17203. repeat
  17204. {$ifdef PUREPASCAL}
  17205. c100 := c div 100; // one div by two digits
  17206. dec(c,c100*100); // fast c := c mod 100
  17207. {$else}
  17208. asm // by-passing the RTL is a good idea here
  17209. push ebx
  17210. mov edx,dword ptr [c+4]
  17211. mov eax,dword ptr [c]
  17212. mov ebx,100
  17213. mov ecx,eax
  17214. mov eax,edx
  17215. xor edx,edx
  17216. div ebx
  17217. mov dword ptr [c100+4],eax
  17218. xchg eax,ecx
  17219. div ebx
  17220. mov dword ptr [c100],eax
  17221. imul ebx,ecx
  17222. mov ecx,100
  17223. mul ecx
  17224. add edx,ebx
  17225. pop ebx
  17226. sub dword ptr [c+4],edx
  17227. sbb dword ptr [c],eax
  17228. end;
  17229. {$endif}
  17230. dec(P,2);
  17231. PWord(P)^ := TwoDigitLookupW[c];
  17232. c := c100;
  17233. if Int64Rec(c).Hi=0 then begin
  17234. if Int64Rec(c).Lo<>0 then
  17235. P := StrUInt32(P,Int64Rec(c).Lo);
  17236. break;
  17237. end;
  17238. until false;
  17239. end;
  17240. result := P;
  17241. end;
  17242. {$endif}
  17243. function StrInt64(P: PAnsiChar; const val: Int64): PAnsiChar;
  17244. begin
  17245. if val<0 then begin
  17246. P := StrUInt64(P,-val)-1;
  17247. P^ := '-';
  17248. end else
  17249. P := StrUInt64(P,val);
  17250. result := P;
  17251. end;
  17252. const
  17253. // see https://en.wikipedia.org/wiki/Baudot_code
  17254. B2A: array[0..63] of AnsiChar =
  17255. #0'e'#10'a siu'#13'drjnfcktzlwhypqobg'#254'mxv'#255+
  17256. #0'3'#10'- ''87'#13#0'4'#0',!:(5+)2$6019?@'#254'./;'#255;
  17257. var
  17258. A2B: array[AnsiChar] of byte;
  17259. function AsciiToBaudot(const Text: RawUTF8): RawByteString;
  17260. begin
  17261. result := AsciiToBaudot(pointer(Text),length(Text));
  17262. end;
  17263. function AsciiToBaudot(P: PAnsiChar; len: integer): RawByteString; overload;
  17264. var i,c,d,bits: integer;
  17265. shift: boolean;
  17266. dest: PByte;
  17267. tmp: TSynTempBuffer;
  17268. begin
  17269. result := '';
  17270. if (P=nil) or (len=0) then
  17271. exit;
  17272. shift := false;
  17273. tmp.Init((len*10)shr 3);
  17274. dest := tmp.buf;
  17275. d := 0;
  17276. bits := 0;
  17277. for i := 0 to len-1 do begin
  17278. c := A2B[P[i]];
  17279. if c>32 then begin
  17280. if not shift then begin
  17281. d := (d shl 5) or 27;
  17282. inc(bits,5);
  17283. shift := true;
  17284. end;
  17285. d := (d shl 5) or (c-32);
  17286. inc(bits,5);
  17287. end else
  17288. if c>0 then begin
  17289. if shift and (P[i]>=' ') then begin
  17290. d := (d shl 5) or 31;
  17291. inc(bits,5);
  17292. shift := false;
  17293. end;
  17294. d := (d shl 5) or c;
  17295. inc(bits,5);
  17296. end;
  17297. while bits>=8 do begin
  17298. dec(bits,8);
  17299. dest^ := d shr bits;
  17300. inc(dest);
  17301. end;
  17302. end;
  17303. if bits>0 then begin
  17304. dest^ := d shl (8-bits);
  17305. inc(dest);
  17306. end;
  17307. SetString(result,PAnsiChar(tmp.buf),PAnsiChar(dest)-PAnsiChar(tmp.buf));
  17308. tmp.Done;
  17309. end;
  17310. function BaudotToAscii(const Baudot: RawByteString): RawUTF8;
  17311. begin
  17312. result := BaudotToAscii(pointer(Baudot),length(Baudot));
  17313. end;
  17314. function BaudotToAscii(Baudot: PByteArray; len: integer): RawUTF8; overload;
  17315. var i,c,b,bits,shift: integer;
  17316. tmp: TSynTempBuffer;
  17317. dest: PAnsiChar;
  17318. begin
  17319. result := '';
  17320. if (Baudot=nil) or (len=0) then
  17321. exit;
  17322. tmp.Init((len*8)div 5+1);
  17323. dest := tmp.buf;
  17324. shift := 0;
  17325. b := 0;
  17326. bits := 0;
  17327. for i := 0 to len-1 do begin
  17328. b := (b shl 8) or Baudot[i];
  17329. inc(bits,8);
  17330. while bits>=5 do begin
  17331. dec(bits,5);
  17332. c := (b shr bits) and 31;
  17333. case c of
  17334. 27: if shift<>0 then
  17335. exit else
  17336. shift := 32;
  17337. 31: if shift<>0 then
  17338. shift := 0 else
  17339. exit;
  17340. else begin
  17341. c := ord(B2A[c+shift]);
  17342. if c=0 then
  17343. if Baudot[i+1]=0 then // allow triming of last 5 bits
  17344. break else
  17345. exit;
  17346. dest^ := AnsiChar(c);
  17347. inc(dest);
  17348. end;
  17349. end;
  17350. end;
  17351. end;
  17352. SetString(result,PAnsiChar(tmp.buf),dest-PAnsiChar(tmp.buf));
  17353. tmp.Done;
  17354. end;
  17355. {$ifdef CPU64}
  17356. procedure Exchg16(P1,P2: PInt64Array);
  17357. var c: Int64;
  17358. begin
  17359. c := P1[0];
  17360. P1[0] := P2[0];
  17361. P2[0] := c;
  17362. c := P1[1];
  17363. P1[1] := P2[1];
  17364. P2[1] := c;
  17365. end;
  17366. {$else}
  17367. procedure Exchg16(P1,P2: PIntegerArray);
  17368. var c: integer;
  17369. begin
  17370. c := P1[0];
  17371. P1[0] := P2[0];
  17372. P2[0] := c;
  17373. c := P1[1];
  17374. P1[1] := P2[1];
  17375. P2[1] := c;
  17376. c := P1[2];
  17377. P1[2] := P2[2];
  17378. P2[2] := c;
  17379. c := P1[3];
  17380. P1[3] := P2[3];
  17381. P2[3] := c;
  17382. end;
  17383. {$endif}
  17384. procedure Exchg(P1,P2: PAnsiChar; count: PtrInt);
  17385. {$ifdef PUREPASCAL}
  17386. var i, c: PtrInt;
  17387. u: AnsiChar;
  17388. begin
  17389. for i := 1 to count shr POINTERSHR do begin
  17390. c := PPtrInt(P1)^;
  17391. PPtrInt(P1)^ := PPtrInt(P2)^;
  17392. PPtrInt(P2)^ := c;
  17393. inc(P1,SizeOf(c));
  17394. inc(P2,SizeOf(c));
  17395. end;
  17396. for i := 0 to (count and pred(sizeof(c)))-1 do begin
  17397. u := P1[i];
  17398. P1[i] := P2[i];
  17399. P2[i] := u;
  17400. end;
  17401. end;
  17402. {$else}
  17403. asm // eax=P1, edx=P2, ecx=count
  17404. push ebx
  17405. push esi
  17406. push ecx
  17407. shr ecx,2
  17408. jz @2
  17409. @4:dec ecx
  17410. mov ebx,[eax]
  17411. mov esi,[edx]
  17412. mov [eax],esi
  17413. mov [edx],ebx
  17414. lea eax,[eax+4]
  17415. lea edx,[edx+4]
  17416. jnz @4
  17417. @2:pop ecx
  17418. and ecx,3
  17419. jz @0
  17420. @1:dec ecx
  17421. mov bl,[eax]
  17422. mov bh,[edx]
  17423. mov [eax],bh
  17424. mov [edx],bl
  17425. lea eax,[eax+1]
  17426. lea edx,[edx+1]
  17427. jnz @1
  17428. @0:pop esi
  17429. pop ebx
  17430. end;
  17431. {$endif}
  17432. {$ifdef FPC}
  17433. type
  17434. /// available type families for Free Pascal RTTI values
  17435. // - values differs from Delphi, and are taken from FPC typinfo.pp unit
  17436. // - here below, we defined tkLString instead of FPC tkAString to match
  17437. // Delphi - see http://lists.freepascal.org/fpc-devel/2013-June/032233.html
  17438. TTypeKind = (tkUnknown,tkInteger,tkChar,tkEnumeration,tkFloat,
  17439. tkSet,tkMethod,tkSString,tkLStringOld,tkLString,
  17440. tkWString,tkVariant,tkArray,tkRecord,tkInterface,
  17441. tkClass,tkObject,tkWChar,tkBool,tkInt64,tkQWord,
  17442. tkDynArray,tkInterfaceRaw,tkProcVar,tkUString,tkUChar,tkHelper);
  17443. const
  17444. // all potentially managed types
  17445. tkManagedTypes = [tkLStringOld,tkLString,tkWstring,tkUstring,tkArray,
  17446. tkObject,tkRecord,tkDynArray,tkInterface,tkVariant];
  17447. // maps record or object types
  17448. tkRecordTypes = [tkObject,tkRecord];
  17449. tkRecordTypeOrSet = [tkObject,tkRecord];
  17450. type
  17451. TDelphiTypeKind = (dkUnknown, dkInteger, dkChar, dkEnumeration, dkFloat,
  17452. dkString, dkSet, dkClass, dkMethod, dkWChar, dkLString, dkWString,
  17453. dkVariant, dkArray, dkRecord, dkInterface, dkInt64, dkDynArray,
  17454. dkUString, dkClassRef, dkPointer, dkProcedure);
  17455. const
  17456. FPCTODELPHI: array[TTypeKind] of TDelphiTypeKind = (
  17457. dkUnknown,dkInteger,dkChar,dkEnumeration,dkFloat,
  17458. dkSet,dkMethod,dkString,dkLString,dkLString,
  17459. dkWString,dkVariant,dkArray,dkRecord,dkInterface,
  17460. dkClass,dkRecord,dkWChar,dkEnumeration,dkInt64,dkInt64,
  17461. dkDynArray,dkInterface,dkProcedure,dkUString,dkWChar,dkPointer);
  17462. DELPHITOFPC: array[TDelphiTypeKind] of TTypeKind = (
  17463. tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
  17464. tkSString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
  17465. tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray,
  17466. tkUString, tkProcVar, tkProcVar, tkProcVar);
  17467. {$else}
  17468. type
  17469. /// available type families for Delphi 6 and up, similar to typinfo.pas
  17470. TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
  17471. tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
  17472. tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray
  17473. {$ifdef UNICODE}, tkUString, tkClassRef, tkPointer, tkProcedure{$endif});
  17474. const
  17475. // maps record or object types
  17476. tkRecordTypes = [tkRecord];
  17477. tkRecordTypeOrSet = tkRecord;
  17478. {$endif}
  17479. type
  17480. TOrdType = (otSByte,otUByte,otSWord,otUWord,otSLong,otULong);
  17481. TFloatType = (ftSingle,ftDoub,ftExtended,ftComp,ftCurr);
  17482. TTypeKinds = set of TTypeKind;
  17483. PTypeKind = ^TTypeKind;
  17484. PStrRec = ^TStrRec;
  17485. /// map the Delphi/FPC string header, as defined in System.pas
  17486. TStrRec =
  17487. {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  17488. packed
  17489. {$endif FPC_REQUIRES_PROPER_ALIGNMENT}
  17490. record
  17491. {$ifdef FPC}
  17492. {$ifdef ISFPC27}
  17493. codePage: Word;
  17494. elemSize: Word;
  17495. {$endif}
  17496. {$ifdef CPU64}
  17497. _Padding: LongInt;
  17498. {$endif}
  17499. refCnt: SizeInt;
  17500. length: SizeInt;
  17501. {$else FPC}
  17502. {$ifdef UNICODE}
  17503. {$ifdef CPU64}
  17504. /// padding bytes for 16 byte alignment of the header
  17505. _Padding: LongInt;
  17506. {$endif}
  17507. /// the associated code page used for this string
  17508. // - exist only since Delphi/FPC 2009
  17509. // - 0 or 65535 for RawByteString
  17510. // - 1200=CP_UTF16 for UnicodeString
  17511. // - 65001=CP_UTF8 for RawUTF8
  17512. // - the current code page for AnsiString
  17513. codePage: Word;
  17514. /// either 1 (for AnsiString) or 2 (for UnicodeString)
  17515. // - exist only since Delphi/FPC 2009
  17516. elemSize: Word;
  17517. {$endif UNICODE}
  17518. /// COW string reference count (basic garbage memory mechanism)
  17519. refCnt: Longint;
  17520. /// length in characters
  17521. // - size in bytes = length*elemSize
  17522. length: Longint;
  17523. {$endif FPC}
  17524. end;
  17525. /// map the Delphi/FPC dynamic array header (stored before each instance)
  17526. TDynArrayRec = packed record
  17527. /// dynamic array reference count (basic garbage memory mechanism)
  17528. {$ifdef FPC}
  17529. refCnt: PtrInt;
  17530. high: tdynarrayindex;
  17531. function GetLength: sizeint; inline;
  17532. procedure SetLength(len: sizeint); inline;
  17533. property length: sizeint read GetLength write SetLength;
  17534. {$else}
  17535. {$ifdef CPUX64}
  17536. _Padding: LongInt; // Delphi/FPC XE2+ expects 16 byte alignment
  17537. {$endif}
  17538. refCnt: Longint;
  17539. /// length in element count
  17540. // - size in bytes = length*ElemSize
  17541. length: PtrInt;
  17542. {$endif}
  17543. end;
  17544. PDynArrayRec = ^TDynArrayRec;
  17545. {$ifdef FPC}
  17546. {$PACKRECORDS C}
  17547. {$endif FPC}
  17548. PTypeInfo = ^TTypeInfo;
  17549. {$ifdef HASDIRECTTYPEINFO}
  17550. PTypeInfoStored = PTypeInfo;
  17551. {$else}
  17552. PTypeInfoStored = ^PTypeInfo;
  17553. {$endif}
  17554. /// map the Delphi/FPC record field RTTI
  17555. TFieldInfo =
  17556. {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  17557. packed
  17558. {$endif FPC_REQUIRES_PROPER_ALIGNMENT}
  17559. record
  17560. TypeInfo: PTypeInfoStored;
  17561. {$ifdef FPC}
  17562. Offset: sizeint;
  17563. {$else}
  17564. Offset: PtrUInt;
  17565. {$endif FPC}
  17566. end;
  17567. {$ifdef ISDELPHI2010}
  17568. /// map the Delphi record field enhanced RTTI (available since Delphi 2010)
  17569. TEnhancedFieldInfo = packed record
  17570. TypeInfo: PTypeInfoStored;
  17571. Offset: PtrUInt;
  17572. Flags: Byte;
  17573. NameLen: byte; // = Name[0] = length(Name)
  17574. end;
  17575. PEnhancedFieldInfo = ^TEnhancedFieldInfo;
  17576. {$endif}
  17577. /// map the Delphi/FPC RTTI content
  17578. TTypeInfo =
  17579. {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  17580. packed
  17581. {$endif FPC_REQUIRES_PROPER_ALIGNMENT}
  17582. record
  17583. kind: TTypeKind;
  17584. NameLen: byte;
  17585. case TTypeKind of
  17586. tkUnknown: (
  17587. NameFirst: AnsiChar;
  17588. );
  17589. tkDynArray: (
  17590. {$ifdef FPC}
  17591. elSize: SizeUInt;
  17592. elType2: PTypeInfoStored;
  17593. varType: LongInt;
  17594. elType: PTypeInfoStored;
  17595. //DynUnitName: ShortStringBase;
  17596. {$else}
  17597. // storage byte count for this field
  17598. elSize: Longint;
  17599. // nil for unmanaged field
  17600. elType: PTypeInfoStored;
  17601. // OleAuto compatible type
  17602. varType: Integer;
  17603. // also unmanaged field
  17604. elType2: PTypeInfoStored;
  17605. {$endif}
  17606. );
  17607. tkArray: (
  17608. {$ifdef FPC}
  17609. // and $7FFFFFFF needed
  17610. arraySize: SizeInt;
  17611. // product of lengths of all dimensions
  17612. elCount: SizeInt;
  17613. {$else}
  17614. arraySize: Integer;
  17615. // product of lengths of all dimensions
  17616. elCount: Integer;
  17617. {$endif}
  17618. arrayType: PTypeInfoStored;
  17619. dimCount: Byte;
  17620. dims: array[0..255 {DimCount-1}] of PTypeInfoStored;
  17621. );
  17622. {$ifdef FPC}
  17623. tkRecord, tkObject:(
  17624. recSize: longint;
  17625. ManagedCount: longint;
  17626. {$else}
  17627. tkRecord: (
  17628. recSize: cardinal;
  17629. ManagedCount: integer;
  17630. {$endif FPC}
  17631. ManagedFields: array[0..0] of TFieldInfo;
  17632. {$ifdef ISDELPHI2010} // enhanced RTTI containing info about all fields
  17633. NumOps: Byte;
  17634. //RecOps: array[0..0] of Pointer;
  17635. AllCount: Integer; // !!!! may need $RTTI EXPLICIT FIELDS([vcPublic])
  17636. AllFields: array[0..0] of TEnhancedFieldInfo;
  17637. {$endif ISDELPHI2010}
  17638. );
  17639. tkEnumeration: (
  17640. EnumType: TOrdType;
  17641. {$ifdef FPC_ENUMHASINNER}
  17642. inner:
  17643. {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  17644. packed
  17645. {$endif}
  17646. record
  17647. {$endif}
  17648. MinValue: longint;
  17649. MaxValue: longint;
  17650. EnumBaseType: PTypeInfoStored;
  17651. {$ifdef FPC_ENUMHASINNER}
  17652. end;
  17653. {$endif}
  17654. NameList: string[255];
  17655. );
  17656. tkInteger: (
  17657. IntegerType: TOrdType;
  17658. );
  17659. tkSet: (
  17660. SetType: TOrdType;
  17661. SetBaseType: PTypeInfoStored;
  17662. );
  17663. tkFloat: (
  17664. FloatType: TFloatType;
  17665. );
  17666. tkClass: (
  17667. ClassType: PAnsiChar; // TClass;
  17668. ParentInfo: PTypeInfoStored;
  17669. PropCount: SmallInt;
  17670. UnitNameLen: byte;
  17671. );
  17672. end;
  17673. TPropInfo = packed record
  17674. PropType: PTypeInfoStored;
  17675. GetProc: PtrInt;
  17676. SetProc: PtrInt;
  17677. StoredProc: PtrInt;
  17678. Index: Integer;
  17679. Default: Longint;
  17680. NameIndex: SmallInt;
  17681. {$ifdef FPC}
  17682. PropProcs : Byte;
  17683. {$endif}
  17684. NameLen: byte;
  17685. end;
  17686. PPropInfo = ^TPropInfo;
  17687. const
  17688. /// codePage offset = string header size
  17689. // - used to calc the beginning of memory allocation of a string
  17690. STRRECSIZE = SizeOf(TStrRec);
  17691. function ToText(k: TTypeKind): PShortString; overload;
  17692. begin
  17693. result := GetEnumName(TypeInfo(TTypeKind),ord(k));
  17694. end;
  17695. type
  17696. TTypeInfoSaved = type TRawByteStringDynArray;
  17697. function TypeInfoFind(const rttitypes: TTypeInfoSaved;
  17698. const typename: RawUTF8): pointer;
  17699. var i,len: integer;
  17700. begin
  17701. len := length(typename);
  17702. if len<>0 then begin
  17703. for i := 0 to length(rttitypes)-1 do
  17704. with PTypeInfo(rttitypes[i])^ do
  17705. if (NameLen=len) and
  17706. IdemPropNameUSameLen(@NameFirst,pointer(typename),len) then begin
  17707. result := @kind;
  17708. exit;
  17709. end;
  17710. end;
  17711. result := nil;
  17712. end;
  17713. function TypeInfoFindIndex(const rttitypes: TTypeInfoSaved;
  17714. info: pointer): integer;
  17715. var len: integer;
  17716. begin
  17717. if info<>nil then begin
  17718. len := PTypeInfo(info)^.NameLen+2; // compare Kind+Name
  17719. for result := 0 to length(rttitypes)-1 do
  17720. if CompareMem(pointer(rttitypes[result]),info,len) then
  17721. exit;
  17722. end;
  17723. result := -1;
  17724. end;
  17725. var
  17726. KnownTypeInfo: array of PTypeInfo;
  17727. {$ifdef HASDIRECTTYPEINFO}
  17728. type
  17729. Deref = PTypeInfo;
  17730. {$else}
  17731. function Deref(Info: PTypeInfoStored): PTypeInfo;
  17732. {$ifdef HASINLINE} inline;
  17733. begin
  17734. if Info=nil then
  17735. result := pointer(Info) else
  17736. result := Info^;
  17737. end;
  17738. {$else}
  17739. asm // Delphi is so bad at compiling above code...
  17740. or eax,eax
  17741. jz @z
  17742. mov eax,[eax]
  17743. ret
  17744. @z: db $f3 // rep ret
  17745. end;
  17746. {$endif HASINLINE}
  17747. {$endif HASDIRECTTYPEINFO}
  17748. /// add some TypeInfo() RTTI for TypeInfoSave/TypeInfoLoad function
  17749. // - warning: calling this after TypeInfoLoad() would trigger GPF
  17750. procedure TypeInfoSaveRegisterKnown(const Types: array of pointer);
  17751. var i,n: integer;
  17752. begin
  17753. n := length(KnownTypeInfo);
  17754. SetLength(KnownTypeInfo,n+length(Types));
  17755. for i := 0 to high(Types) do
  17756. KnownTypeInfo[n+i] := Types[i];
  17757. end;
  17758. function FindKnownTypeInfoIndex(typeinfo: pointer): integer;
  17759. function Search(KindNameLen: word; Name: PUTF8Char; NameLen: integer): integer;
  17760. begin // compare Kind+NameLen, then case-insensitive Name
  17761. for result := 0 to length(KnownTypeInfo)-1 do
  17762. with PTypeInfo(KnownTypeInfo[result])^ do
  17763. if (PWord(kind)^=KindNameLen) and
  17764. IdemPropNameUSameLen(@NameFirst,Name,NameLen) then
  17765. exit;
  17766. result := -1;
  17767. end;
  17768. begin
  17769. if typeinfo=nil then
  17770. result := -1 else
  17771. with PTypeInfo(typeinfo)^ do
  17772. result := Search(PWord(@kind)^,@NameFirst,NameLen);
  17773. end;
  17774. /// binary external storage of low-level RTTI
  17775. // - add the RTTI to rttitypes[] in a stand-alone way (i.e. with no pointer)
  17776. // - return the index of the type in rttitypes[]
  17777. function TypeInfoSave(var rttitypes: TTypeInfoSaved;
  17778. info: pointer): integer;
  17779. var k: TTypeKind;
  17780. i,offs: integer;
  17781. n: PAnsiChar;
  17782. np: ^TPropInfo absolute n;
  17783. rtti: PTypeInfo;
  17784. tmp: TSynTempWriter;
  17785. procedure wrtype(nested: PTypeInfoStored);
  17786. var nfo: PTypeInfo;
  17787. known: integer;
  17788. begin
  17789. nfo := Deref(nested);
  17790. if nfo=nil then
  17791. tmp.wrw(0) else
  17792. if nfo=info then
  17793. tmp.wrw(result+2) else begin
  17794. known := FindKnownTypeInfoIndex(nfo);
  17795. if known<0 then
  17796. tmp.wrw(TypeInfoSave(rttitypes,nfo)+2) else begin
  17797. tmp.wrw(1); // would be recognized by name
  17798. with PTypeInfo(nfo)^ do
  17799. tmp.wr(kind,NameLen+2); // match FindKnownTypeInfoIndex()
  17800. end;
  17801. end;
  17802. end;
  17803. begin
  17804. result := TypeInfoFindIndex(rttitypes,info);
  17805. if (result>=0) or (info=nil) then
  17806. exit;
  17807. result := length(rttitypes);
  17808. tmp.Init; // no need of tmp.Done since maxsize=0 will use the stack
  17809. rtti := info;
  17810. k := rtti^.Kind;
  17811. {$ifdef FPC} // storage binary layout is Delphi's
  17812. i := ord(FPCTODELPHI[k]);
  17813. tmp.wr(i,1);
  17814. {$else}
  17815. tmp.wr(k,sizeof(k));
  17816. {$endif}
  17817. tmp.wr(rtti^.NameLen,rtti^.NameLen+1);
  17818. inc(PByte(rtti),rtti^.NameLen);
  17819. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  17820. rtti := align(rtti,sizeof(rtti));
  17821. {$endif}
  17822. with rtti^ do
  17823. case k of
  17824. tkChar, tkWChar, tkLString, tkWString, tkVariant, tkInt64
  17825. {$ifdef UNICODE}, tkUString{$endif}:
  17826. ; // no additional RTTI needed for those types
  17827. tkDynArray: begin
  17828. tmp.wrint(elSize);
  17829. wrtype(elType);
  17830. wrtype(elType2);
  17831. end;
  17832. tkEnumeration:
  17833. {$ifdef FPC_ENUMHASINNER}with inner do{$endif} begin
  17834. tmp.wr(EnumType,sizeof(EnumType));
  17835. if MinValue<>0 then
  17836. raise ESynException.CreateUTF8('TypeInfoSave MinValue=%',[MinValue]);
  17837. tmp.wrw(MaxValue);
  17838. wrtype(EnumBaseType);
  17839. n := @NameList;
  17840. for i := MinValue to MaxValue do
  17841. inc(n,ord(n^)+1); // next short string (no align() needed on FPC)
  17842. i := n-@NameList;
  17843. tmp.wrw(i);
  17844. tmp.wr(NameList,i);
  17845. end;
  17846. tkSet: begin
  17847. tmp.wr(SetType,sizeof(SetType));
  17848. wrtype(SetBaseType);
  17849. end;
  17850. tkInteger:
  17851. tmp.wr(IntegerType,sizeof(IntegerType));
  17852. tkFloat:
  17853. tmp.wr(FloatType,sizeof(FloatType));
  17854. tkClass: begin
  17855. wrtype(ParentInfo);
  17856. tmp.wrint(PropCount);
  17857. tmp.wr(UnitNameLen,UnitNameLen+1);
  17858. n := @UnitNameLen;
  17859. inc(n,UnitNameLen+1);
  17860. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  17861. n := align(n,sizeof(n));
  17862. {$endif}
  17863. for i := 1 to PropCount do begin
  17864. wrtype(np^.PropType);
  17865. offs := np^.GetProc;
  17866. {$ifndef FPC}
  17867. if offs and {$ifdef CPU64}$ff00000000000000{$else}$ff000000{$endif}<>0 then
  17868. raise ESynException.CreateUTF8('TypeInfoSave no getter for %',
  17869. [PShortString(np^.NameLen)^]);
  17870. {$endif}
  17871. tmp.wrint(offs);
  17872. tmp.wrb(np^.StoredProc);
  17873. tmp.wrint(np^.Index);
  17874. tmp.wrint(np^.Default);
  17875. tmp.wrw(np^.NameIndex);
  17876. tmp.wr(np^.NameLen,np^.NameLen+1);
  17877. n := PAnsiChar(@np^.NameLen)+np^.NameLen+1;
  17878. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  17879. n := align(n,sizeof(n));
  17880. {$endif}
  17881. end;
  17882. end;
  17883. else
  17884. raise ESynException.CreateUTF8('TypeInfoSave(%) unsupported',[ToText(k)^]);
  17885. end;
  17886. SetLength(rttitypes,result+1);
  17887. rttitypes[result] := tmp.AsBinary;
  17888. end;
  17889. procedure TypeInfoLoad(var rttitypes: TTypeInfoSaved);
  17890. var rtti: PTypeInfo;
  17891. tmp: TSynTempWriter;
  17892. i,t,j,pcount: integer;
  17893. offs: PtrUInt;
  17894. stored: boolean;
  17895. k: TTypeKind;
  17896. n: PAnsiChar;
  17897. types: array of array of packed record
  17898. offs: word;
  17899. typindex: word;
  17900. end;
  17901. p1: pointer;
  17902. function nint: integer;
  17903. begin
  17904. result := PInteger(n)^;
  17905. inc(n,4);
  17906. end;
  17907. function nw: integer;
  17908. begin
  17909. result := PWord(n)^;
  17910. inc(n,2);
  17911. end;
  17912. function nb: integer;
  17913. begin
  17914. result := PByte(n)^;
  17915. inc(n);
  17916. end;
  17917. procedure wrss;
  17918. var len: integer;
  17919. begin
  17920. len := PByte(n)^+1;
  17921. tmp.wr(n^,len); // copy whole shortstring at once
  17922. inc(n,len);
  17923. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  17924. n := align(n,sizeof(n));
  17925. {$endif}
  17926. end;
  17927. function wrtype: pointer;
  17928. var index,off,ti: integer;
  17929. begin
  17930. result := tmp.pos;
  17931. index := nw;
  17932. if index=1 then begin
  17933. ti := FindKnownTypeInfoIndex(n);
  17934. if ti<0 then
  17935. raise ESynException.CreateUTF8('TypeInfoLoad index=1 %?',
  17936. [PShortString(@PTypeInfo(n)^.NameLen)^]);
  17937. inc(n,PTypeInfo(n)^.NameLen+2);
  17938. {$ifdef FPC} // follow PTypeInfoStored pattern
  17939. tmp.wrptr(pointer(KnownTypeInfo[ti]));
  17940. {$else}
  17941. // warning: any future TypeInfoSaveRegisterKnown() would trigger GPF
  17942. tmp.wrptr(@pointer(KnownTypeInfo[ti]));
  17943. {$endif}
  17944. exit;
  17945. end;
  17946. off := tmp.Position;
  17947. tmp.wrptr(nil);
  17948. if index=0 then
  17949. exit;
  17950. SetLength(types[i],t+1);
  17951. with types[i,t] do begin
  17952. offs := off;
  17953. typindex := index-2;
  17954. end;
  17955. inc(t);
  17956. end;
  17957. begin
  17958. SetLength(types,Length(rttitypes));
  17959. for i := 0 to Length(rttitypes)-1 do begin
  17960. t := 0;
  17961. tmp.Init;
  17962. rtti := pointer(rttitypes[i]);
  17963. {$ifdef FPC}
  17964. k := DELPHITOFPC[TDelphiTypeKind(rtti^.Kind)];
  17965. if (k=tkEnumeration) and
  17966. IdemPropName(PShortString(rtti^.NameLen)^,'boolean') then
  17967. k := tkBool;
  17968. {$else}
  17969. k := rtti^.Kind;
  17970. {$endif}
  17971. tmp.wr(k,sizeof(k));
  17972. n := @rtti^.NameLen; // n^ points to variable buffer -> use nb/nw/nint
  17973. wrss; // copy Name
  17974. case k of
  17975. tkChar, tkWChar, tkLString, tkWString, tkVariant, tkInt64
  17976. {$ifdef UNICODE}, tkUString{$endif}
  17977. {$ifdef FPC}, tkBool{$endif}:
  17978. ; // no additional RTTI needed for those types
  17979. tkDynArray: begin // elSize,elType,elType2
  17980. {$ifdef FPC}
  17981. tmp.wrptrint(nint);
  17982. p1 := wrtype;
  17983. tmp.wrint(0);
  17984. Exchg(p1,wrtype,sizeof(pointer)); // invert elType <-> elType2
  17985. {$else}
  17986. tmp.wrint(nint);
  17987. wrtype;
  17988. tmp.wrint(0);
  17989. wrtype;
  17990. {$endif}
  17991. end;
  17992. tkEnumeration: begin
  17993. tmp.wrb(nb);
  17994. tmp.wrint(0); // MinValue
  17995. tmp.wrint(nint);
  17996. wrtype;
  17997. j := nw;
  17998. tmp.wr(n^,j); // NameList
  17999. end;
  18000. tkInteger, tkFloat:
  18001. tmp.wrb(nb);
  18002. tkSet: begin
  18003. tmp.wrb(nb);
  18004. wrtype;
  18005. end;
  18006. tkClass: begin
  18007. p1 := tmp.wrfillchar(sizeof(pointer),0);
  18008. wrtype;
  18009. pcount := nw;
  18010. tmp.wrw(pcount);
  18011. wrss; // copy UnitName
  18012. for j := 1 to pcount do begin
  18013. wrtype; // PropType
  18014. offs := nint; // GetProc=SetProc=fieldaddr
  18015. {$ifndef FPC}
  18016. offs := offs or {$ifdef CPU64}$ff00000000000000{$else}$ff000000{$endif};
  18017. {$endif}
  18018. tmp.wrptrint(offs);
  18019. tmp.wrptrint(offs);
  18020. stored := nb<>0;
  18021. if stored then
  18022. tmp.wrptrint(-1) else
  18023. tmp.wrptrint(0);
  18024. tmp.wrint(nint); // Index
  18025. tmp.wrint(nint); // Default
  18026. tmp.wrw(nw); // NameIndex
  18027. {$ifdef FPC} // PropProcs: GetProc=SetProc=ptField
  18028. if stored then
  18029. tmp.wrb(ptconst shl 4) else
  18030. tmp.wrb(0);
  18031. {$endif}
  18032. wrss; // copy Name
  18033. end;
  18034. // FIX: compute TClass at p1^
  18035. PPointer(p1)^ := nil;
  18036. end;
  18037. else
  18038. raise ESynException.CreateUTF8('TypeInfoLoad(%) unsupported',[ToText(k)^]);
  18039. end;
  18040. rttitypes[i] := tmp.AsBinary; // replace with true RTTI
  18041. end;
  18042. // fix all internal pointers
  18043. for i := 0 to Length(rttitypes)-1 do begin
  18044. n := pointer(rttitypes[i]);
  18045. for t := 0 to length(types[i])-1 do
  18046. with types[i,t] do
  18047. {$ifdef FPC} // follow PTypeInfoStored pattern
  18048. PPointer(n+offs)^ := pointer(rttitypes[typindex]);
  18049. {$else}
  18050. PPointer(n+offs)^ := @pointer(rttitypes[typindex]);
  18051. {$endif}
  18052. end;
  18053. end;
  18054. procedure SetRawUTF8(var Dest: RawUTF8; text: pointer; len: integer);
  18055. {$ifdef FPC}inline;
  18056. begin
  18057. if (len>128) or (len=0) or (text<>pointer(Dest)) then
  18058. SetString(Dest,PAnsiChar(text),len) else
  18059. SetLength(Dest,len);
  18060. end;
  18061. {$else}
  18062. {$ifdef PUREPASCAL}
  18063. var P: PStrRec;
  18064. begin
  18065. if (len>128) or (len=0) or (PtrInt(Dest)=0) or // Dest=''
  18066. (PStrRec(PtrInt(Dest)-STRRECSIZE)^.refCnt<>1) then
  18067. SetString(Dest,PAnsiChar(text),len) else begin
  18068. if PStrRec(Pointer(PtrInt(Dest)-STRRECSIZE))^.length<>len then begin
  18069. P := Pointer(PtrInt(Dest)-STRRECSIZE);
  18070. ReallocMem(P,len+(STRRECSIZE+1));
  18071. P^.length := len;
  18072. pointer(Dest) := pointer(PAnsiChar(P)+STRRECSIZE);
  18073. PByteArray(Dest)[len] := 0;
  18074. end;
  18075. MoveFast(pointer(text)^,pointer(Dest)^,len);
  18076. end;
  18077. end;
  18078. {$else}
  18079. asm // eax=@Dest text=edx len=ecx
  18080. cmp ecx,128 // avoid huge move() in ReallocMem()
  18081. {$ifdef UNICODE}
  18082. ja @3
  18083. {$else}
  18084. ja System.@LStrFromPCharLen
  18085. {$endif}
  18086. test ecx,ecx // len=0
  18087. {$ifdef UNICODE}
  18088. jz @3
  18089. {$else}
  18090. jz System.@LStrFromPCharLen
  18091. {$endif}
  18092. push ebx
  18093. mov ebx,[eax]
  18094. test ebx,ebx
  18095. jnz @2
  18096. @0: pop ebx
  18097. {$ifdef UNICODE}
  18098. @3: push CP_UTF8 // UTF-8 code page for Delphi 2009+
  18099. call System.@LStrFromPCharLen // we need a call, not a jmp here
  18100. rep ret
  18101. {$else}
  18102. jmp System.@LStrFromPCharLen
  18103. {$endif}
  18104. @2: cmp dword ptr [ebx-8],1
  18105. jne @0
  18106. cmp dword ptr [ebx-4],ecx
  18107. je @1
  18108. sub ebx,STRRECSIZE
  18109. push edx
  18110. push eax
  18111. push ecx
  18112. push ebx
  18113. mov eax,esp // ReallocMem() over ebx pointer on stack
  18114. lea edx,ecx+STRRECSIZE+1
  18115. call System.@ReallocMem
  18116. pop ebx
  18117. pop ecx
  18118. add ebx,STRRECSIZE
  18119. pop eax
  18120. pop edx
  18121. mov [eax],ebx
  18122. mov dword ptr [ebx-4],ecx
  18123. mov byte ptr [ebx+ecx],0
  18124. @1: mov eax,edx
  18125. mov edx,ebx
  18126. call dword ptr [MoveFast]
  18127. pop ebx
  18128. end;
  18129. {$endif}
  18130. {$endif}
  18131. function UniqueRawUTF8(var UTF8: RawUTF8): pointer;
  18132. begin
  18133. {$ifdef FPC}
  18134. UniqueString(UTF8); // @UTF8[1] won't call UniqueString() under FPC :(
  18135. {$endif}
  18136. result := @UTF8[1];
  18137. end;
  18138. {$ifdef FPC}
  18139. function TDynArrayRec.GetLength: sizeint;
  18140. begin
  18141. result := high+1;
  18142. end;
  18143. procedure TDynArrayRec.SetLength(len: sizeint);
  18144. begin
  18145. high := len-1;
  18146. end;
  18147. {$endif}
  18148. function DynArrayLength(Value: Pointer): integer;
  18149. {$ifdef HASINLINE}inline;{$endif}
  18150. begin
  18151. if Value=nil then
  18152. result := PtrInt(Value) else begin
  18153. {$ifdef FPC}
  18154. result := PDynArrayRec(PtrUInt(Value)-SizeOf(TDynArrayRec))^.length;
  18155. {$else}
  18156. result := PInteger(PtrUInt(Value)-sizeof(PtrInt))^;
  18157. {$endif}
  18158. end;
  18159. end;
  18160. function GetTypeInfo(aTypeInfo: pointer; aExpectedKind: TTypeKind): PTypeInfo; overload;
  18161. {$ifdef HASINLINE} inline;
  18162. begin
  18163. if (aTypeInfo<>nil) and (PTypeKind(aTypeInfo)^=aExpectedKind) then begin
  18164. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  18165. result := GetFPCAlignPtr(aTypeInfo);
  18166. {$else}
  18167. result := aTypeInfo;
  18168. inc(PtrUInt(result),result^.NameLen);
  18169. {$endif}
  18170. end else
  18171. result := nil;
  18172. end;
  18173. {$else}
  18174. asm
  18175. test eax,eax
  18176. jz @n
  18177. cmp dl,[eax]
  18178. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18179. jnz @n
  18180. add eax,ecx
  18181. ret
  18182. @n: xor eax,eax
  18183. end;
  18184. {$endif}
  18185. function GetTypeInfo(aTypeInfo: pointer; const aExpectedKind: TTypeKinds): PTypeInfo; overload;
  18186. {$ifdef HASINLINE} inline;
  18187. begin
  18188. result := aTypeInfo;
  18189. if (result<>nil) and (result^.Kind in aExpectedKind) then
  18190. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  18191. result := GetFPCAlignPtr(result)
  18192. {$else}
  18193. inc(PtrUInt(result),result^.NameLen)
  18194. {$endif}
  18195. else
  18196. result := nil;
  18197. end;
  18198. {$else}
  18199. asm // eax=aTypeInfo edx=aExpectedKind
  18200. test eax,eax
  18201. jz @n
  18202. movzx ecx,byte ptr [eax]
  18203. bt edx,ecx
  18204. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18205. jnb @n
  18206. add eax,ecx
  18207. ret
  18208. @n: xor eax,eax
  18209. end;
  18210. {$endif}
  18211. function DynArrayTypeInfoToRecordInfo(aDynArrayTypeInfo: pointer;
  18212. aDataSize: PInteger=nil): pointer;
  18213. var info: PTypeInfo;
  18214. begin
  18215. result := nil;
  18216. info := GetTypeInfo(aDynArrayTypeInfo,tkDynArray);
  18217. if info=nil then
  18218. exit;
  18219. if info^.elType<>nil then
  18220. result := Deref(info^.elType);
  18221. if aDataSize<>nil then
  18222. aDataSize^ := info^.elSize {$ifdef FPC}and $7FFFFFFF{$endif};
  18223. end;
  18224. procedure TypeInfoToName(aTypeInfo: pointer; var result: RawUTF8;
  18225. const default: RawUTF8='');
  18226. begin
  18227. if aTypeInfo<>nil then
  18228. SetRawUTF8(result,PAnsiChar(@PTypeInfo(aTypeInfo)^.NameLen)+1,
  18229. PTypeInfo(aTypeInfo)^.NameLen) else
  18230. result := default;
  18231. end;
  18232. procedure TypeInfoToQualifiedName(aTypeInfo: pointer; var result: RawUTF8;
  18233. const default: RawUTF8='');
  18234. var unitname: RawUTF8;
  18235. begin
  18236. if aTypeInfo<>nil then begin
  18237. SetRawUTF8(result,PAnsiChar(@PTypeInfo(aTypeInfo)^.NameLen)+1,
  18238. PTypeInfo(aTypeInfo)^.NameLen);
  18239. if PTypeInfo(aTypeInfo)^.Kind=tkClass then begin
  18240. with GetTypeInfo(aTypeInfo,PTypeKind(aTypeInfo)^)^ do
  18241. SetRawUTF8(unitname,PAnsiChar(@UnitNameLen)+1,UnitNameLen);
  18242. result := unitname+'.'+result;
  18243. end;
  18244. end else result := default;
  18245. end;
  18246. function TypeInfoToName(aTypeInfo: pointer): RawUTF8;
  18247. begin
  18248. TypeInfoToName(aTypeInfo,Result,'');
  18249. end;
  18250. function RecordTypeInfoSize(aRecordTypeInfo: Pointer): integer;
  18251. var info: PTypeInfo;
  18252. begin
  18253. info := GetTypeInfo(aRecordTypeInfo,tkRecordTypeOrSet);
  18254. if info=nil then
  18255. result := 0 else
  18256. result := info^.recSize;
  18257. end;
  18258. function GetEnumInfo(aTypeInfo: pointer; out MaxValue: Integer;
  18259. out Names: PShortString): boolean;
  18260. {$ifdef HASINLINE} inline;
  18261. var info: PTypeInfo;
  18262. begin
  18263. info := GetTypeInfo(aTypeInfo,tkEnumeration);
  18264. if info<>nil then begin
  18265. {$ifdef FPC}
  18266. if info^.{$ifdef FPC_ENUMHASINNER}inner.{$endif}EnumBaseType<>nil then
  18267. {$endif}
  18268. info := GetTypeInfo(Deref(info^.{$ifdef FPC_ENUMHASINNER}inner.{$endif}EnumBaseType),tkEnumeration);
  18269. MaxValue := info^.{$ifdef FPC_ENUMHASINNER}inner.{$endif}MaxValue;
  18270. Names := @info.NameList;
  18271. result := true;
  18272. end else
  18273. result := false;
  18274. end;
  18275. {$else}
  18276. asm // eax=aTypeInfo edx=@MaxValue ecx=@Names
  18277. test eax,eax
  18278. jz @n
  18279. cmp byte ptr [eax],tkEnumeration
  18280. jnz @n
  18281. push ecx
  18282. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18283. mov eax,[eax+ecx+TTypeInfo.EnumBaseType]
  18284. mov eax,[eax]
  18285. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18286. add eax,ecx
  18287. mov ecx,[eax+TTypeInfo.MaxValue]
  18288. mov [edx],ecx
  18289. pop ecx
  18290. lea eax,[eax+TTypeInfo.NameList]
  18291. mov [ecx],eax
  18292. mov al,1
  18293. ret
  18294. @n: xor eax,eax
  18295. end;
  18296. {$endif}
  18297. function GetSetInfo(aTypeInfo: pointer; out MaxValue: Integer;
  18298. out Names: PShortString): boolean;
  18299. var info: PTypeInfo;
  18300. begin
  18301. info := GetTypeInfo(aTypeInfo,tkSet);
  18302. if info<>nil then
  18303. {$ifdef FPC}
  18304. if info^.SetBaseType=nil then
  18305. result := GetEnumInfo(aTypeInfo,MaxValue,Names) else
  18306. {$endif}
  18307. result := GetEnumInfo(Deref(info^.SetBaseType),MaxValue,Names) else
  18308. result := false;
  18309. end;
  18310. function GetEnumName(aTypeInfo: pointer; aIndex: integer): PShortString;
  18311. const NULL_SHORTSTRING: string[1] = '';
  18312. {$ifdef HASINLINE}
  18313. var MaxValue: integer;
  18314. begin
  18315. if GetEnumInfo(aTypeInfo,MaxValue,result) and
  18316. (cardinal(aIndex)<=cardinal(MaxValue)) then
  18317. while aIndex>0 do begin
  18318. dec(aIndex);
  18319. inc(PByte(result),ord(result^[0])+1); // next short string
  18320. end else
  18321. result := @NULL_SHORTSTRING;
  18322. end;
  18323. {$else}
  18324. asm // eax=aTypeInfo edx=aIndex
  18325. test eax,eax
  18326. jz @0
  18327. cmp byte ptr [eax],tkEnumeration
  18328. jnz @0
  18329. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18330. mov eax,[eax+ecx+TTypeInfo.EnumBaseType]
  18331. mov eax,[eax]
  18332. movzx ecx,byte ptr [eax+TTypeInfo.NameLen]
  18333. cmp edx,[eax+ecx+TTypeInfo.MaxValue]
  18334. ja @0
  18335. lea eax,[eax+ecx+TTypeInfo.NameList]
  18336. test edx,edx
  18337. jz @z
  18338. push edx
  18339. shr edx,2 // fast pipelined by-four scanning
  18340. jz @1
  18341. @4: dec edx
  18342. movzx ecx,byte ptr [eax]
  18343. lea eax,[eax+ecx+1]
  18344. movzx ecx,byte ptr [eax]
  18345. lea eax,[eax+ecx+1]
  18346. movzx ecx,byte ptr [eax]
  18347. lea eax,[eax+ecx+1]
  18348. movzx ecx,byte ptr [eax]
  18349. lea eax,[eax+ecx+1]
  18350. jnz @4
  18351. pop edx
  18352. and edx,3
  18353. jnz @s
  18354. ret
  18355. @1: pop edx
  18356. @s: movzx ecx,byte ptr [eax]
  18357. dec edx
  18358. lea eax,[eax+ecx+1] // next short string
  18359. jnz @s
  18360. ret
  18361. @z: rep ret
  18362. @0: lea eax,NULL_SHORTSTRING
  18363. end;
  18364. {$endif}
  18365. function FindShortStringListExact(List: PShortString; MaxValue: integer;
  18366. aValue: PUTF8Char; aValueLen: integer): integer;
  18367. var PLen: integer;
  18368. begin
  18369. for result := 0 to MaxValue do begin
  18370. PLen := ord(List^[0]);
  18371. if (PLen=aValuelen) and IdemPropNameUSameLen(@List^[1],aValue,aValueLen) then
  18372. exit else
  18373. inc(PByte(List),PLen+1); // next short string
  18374. end;
  18375. result := -1;
  18376. end;
  18377. function FindShortStringListTrimLowerCase(List: PShortString; MaxValue: integer;
  18378. aValue: PUTF8Char; aValueLen: integer): integer;
  18379. var PLen: integer;
  18380. begin
  18381. for result := 0 to MaxValue do begin
  18382. PLen := ord(List^[0]);
  18383. inc(PUTF8Char(List));
  18384. repeat
  18385. if not(PUTF8Char(List)^ in ['a'..'z']) then
  18386. break;
  18387. inc(PUTF8Char(List));
  18388. dec(PLen);
  18389. until PLen=0;
  18390. if (PLen=aValueLen) and IdemPropNameUSameLen(aValue,PUTF8Char(List),PLen) then
  18391. exit else
  18392. inc(PUTF8Char(List),PLen);
  18393. end;
  18394. result := -1;
  18395. end;
  18396. function GetEnumNameValue(aTypeInfo: pointer; aValue: PUTF8Char; aValueLen: integer;
  18397. AlsoTrimLowerCase: boolean): Integer;
  18398. var List: PShortString;
  18399. MaxValue: integer;
  18400. begin
  18401. if GetEnumInfo(aTypeInfo,MaxValue,List) then begin
  18402. result := FindShortStringListExact(List,MaxValue,aValue,aValueLen);
  18403. if (result<0) and AlsoTrimLowerCase then
  18404. result := FindShortStringListTrimLowerCase(List,MaxValue,aValue,aValueLen);
  18405. end else
  18406. result := -1;
  18407. end;
  18408. function GetEnumNameValueTrimmed(aTypeInfo: pointer; aValue: PUTF8Char; aValueLen: integer): integer;
  18409. var List: PShortString;
  18410. MaxValue: integer;
  18411. begin
  18412. if GetEnumInfo(aTypeInfo,MaxValue,List) then
  18413. result := FindShortStringListTrimLowerCase(List,MaxValue,aValue,aValueLen) else
  18414. result := -1;
  18415. end;
  18416. function GetEnumNameValue(aTypeInfo: pointer; const aValue: RawUTF8;
  18417. AlsoTrimLowerCase: boolean=false): Integer;
  18418. begin
  18419. result := GetEnumNameValue(aTypeInfo, pointer(aValue), length(aValue),
  18420. AlsoTrimLowerCase);
  18421. end;
  18422. function GetSetNameValue(aTypeInfo: pointer; var P: PUTF8Char;
  18423. out EndOfObject: AnsiChar): cardinal;
  18424. var Names: PShortString;
  18425. Text: PUTF8Char;
  18426. wasString: boolean;
  18427. MaxValue, TextLen, i: integer;
  18428. begin
  18429. result := 0;
  18430. if (P<>nil) and GetSetInfo(aTypeInfo,MaxValue,Names) then begin
  18431. P := GotoNextNotSpace(P);
  18432. if P^='[' then begin
  18433. P := GotoNextNotSpace(P+1);
  18434. if P^=']' then begin
  18435. EndOfObject := ']';
  18436. P := GotoNextNotSpace(P+1);
  18437. end else
  18438. repeat
  18439. Text := GetJSONField(P,P,@wasString,@EndOfObject);
  18440. if (Text=nil) or not wasString then begin
  18441. P := nil;
  18442. break;
  18443. end;
  18444. if Text^='*' then begin
  18445. if MaxValue<32 then
  18446. result := ALLBITS_CARDINAL[MaxValue+1] else
  18447. result := cardinal(-1);
  18448. exit;
  18449. end;
  18450. TextLen := StrLen(Text);
  18451. if Text^ in ['a'..'z'] then
  18452. i := FindShortStringListExact(Names,MaxValue,Text,TextLen) else
  18453. i := -1;
  18454. if i<0 then
  18455. i := FindShortStringListTrimLowerCase(Names,MaxValue,Text,TextLen);
  18456. if i>=0 then
  18457. SetBit(result,i);
  18458. // unknown enum names (i=-1) would just be ignored
  18459. until EndOfObject=']';
  18460. end else
  18461. result := GetCardinal(GetJSONField(P,P,nil,@EndOfObject));
  18462. end;
  18463. end;
  18464. { note: those low-level VariantTo*() functions are expected to be there
  18465. even if NOVARIANTS conditional is defined (used e.g. by SynDB.TQuery) }
  18466. function VariantToInteger(const V: Variant; var Value: integer): boolean;
  18467. var tmp: TVarData;
  18468. begin
  18469. with TVarData(V) do
  18470. case VType of
  18471. varNull,
  18472. varEmpty: Value := 0;
  18473. varBoolean: Value := ord(VBoolean);
  18474. varSmallint: Value := VSmallInt;
  18475. {$ifndef DELPHI5OROLDER}
  18476. varShortInt: Value := VShortInt;
  18477. varWord: Value := VWord;
  18478. varLongWord:
  18479. if (VLongWord>=cardinal(Low(integer))) and (VLongWord<=cardinal(High(integer))) then
  18480. Value := VLongWord else begin
  18481. result := false;
  18482. exit;
  18483. end;
  18484. {$endif}
  18485. varByte: Value := VByte;
  18486. varInteger: Value := VInteger;
  18487. varWord64:
  18488. if (VInt64>=0) and (VInt64<=High(integer)) then
  18489. Value := VInt64 else begin
  18490. result := False;
  18491. exit;
  18492. end;
  18493. varInt64:
  18494. if (VInt64>=Low(integer)) and (VInt64<=High(integer)) then
  18495. Value := VInt64 else begin
  18496. result := False;
  18497. exit;
  18498. end;
  18499. else
  18500. if SetVariantUnRefSimpleValue(V,tmp) then begin
  18501. result := VariantToInteger(variant(tmp),Value);
  18502. exit;
  18503. end else begin
  18504. result := false;
  18505. exit;
  18506. end;
  18507. end;
  18508. result := true;
  18509. end;
  18510. function VariantToDouble(const V: Variant; var Value: double): boolean;
  18511. var tmp: TVarData;
  18512. begin
  18513. with TVarData(V) do
  18514. if VType=varVariant or varByRef then
  18515. result := VariantToDouble(PVariant(VPointer)^,Value) else
  18516. if VariantToInt64(V,tmp.VInt64) then begin // also handle varEmpty,varNull
  18517. Value := tmp.VInt64;
  18518. result := true;
  18519. end else
  18520. case VType of
  18521. varDouble,varDate: begin
  18522. Value := VDouble;
  18523. result := true;
  18524. end;
  18525. varSingle: begin
  18526. Value := VSingle;
  18527. result := true;
  18528. end;
  18529. varCurrency: begin
  18530. Value := VCurrency;
  18531. result := true;
  18532. end else
  18533. if SetVariantUnRefSimpleValue(V,tmp) then
  18534. result := VariantToDouble(variant(tmp),Value) else
  18535. result := false;
  18536. end;
  18537. end;
  18538. function VariantToCurrency(const V: Variant; var Value: currency): boolean;
  18539. var tmp: TVarData;
  18540. begin
  18541. with TVarData(V) do
  18542. if VType=varVariant or varByRef then
  18543. result := VariantToCurrency(PVariant(VPointer)^,Value) else
  18544. if VariantToInt64(V,tmp.VInt64) then begin
  18545. Value := tmp.VInt64;
  18546. result := true;
  18547. end else
  18548. case VType of
  18549. varDouble,varDate: begin
  18550. Value := VDouble;
  18551. result := true;
  18552. end;
  18553. varSingle: begin
  18554. Value := VSingle;
  18555. result := true;
  18556. end;
  18557. varCurrency: begin
  18558. Value := VCurrency;
  18559. result := true;
  18560. end else
  18561. if SetVariantUnRefSimpleValue(V,tmp) then
  18562. result := VariantToCurrency(variant(tmp),Value) else
  18563. result := false;
  18564. end;
  18565. end;
  18566. function VariantToBoolean(const V: Variant; var Value: Boolean): boolean;
  18567. var tmp: TVarData;
  18568. begin
  18569. if TVarData(V).VType=varBoolean then
  18570. Value := TVarData(V).VBoolean else
  18571. if SetVariantUnRefSimpleValue(V,tmp) then
  18572. if tmp.VType=varBoolean then
  18573. Value := tmp.VBoolean else begin
  18574. result := false;
  18575. exit;
  18576. end else begin
  18577. result := false;
  18578. exit;
  18579. end;
  18580. result := true;
  18581. end;
  18582. function VariantToInt64(const V: Variant; var Value: Int64): boolean;
  18583. var tmp: TVarData;
  18584. begin
  18585. with TVarData(V) do
  18586. case VType of
  18587. varNull,
  18588. varEmpty: Value := 0;
  18589. varBoolean: Value := ord(VBoolean);
  18590. varSmallint: Value := VSmallInt;
  18591. {$ifndef DELPHI5OROLDER}
  18592. varShortInt: Value := VShortInt;
  18593. varWord: Value := VWord;
  18594. varLongWord: Value := VLongWord;
  18595. {$endif}
  18596. varByte: Value := VByte;
  18597. varInteger: Value := VInteger;
  18598. varWord64,
  18599. varInt64: Value := VInt64;
  18600. else
  18601. if SetVariantUnRefSimpleValue(V,tmp) then begin
  18602. result := VariantToInt64(variant(tmp),Value);
  18603. exit;
  18604. end else begin
  18605. result := false;
  18606. exit;
  18607. end;
  18608. end;
  18609. result := true;
  18610. end;
  18611. function VariantToInt64Def(const V: Variant; DefaultValue: Int64): Int64;
  18612. begin
  18613. if not VariantToInt64(V,result) then
  18614. result := DefaultValue;
  18615. end;
  18616. function VariantToIntegerDef(const V: Variant; DefaultValue: integer): integer;
  18617. begin
  18618. if not VariantToInteger(V,result) then
  18619. result := DefaultValue;
  18620. end;
  18621. {$ifndef NOVARIANTS}
  18622. function VariantToDateTime(const V: Variant; var Value: TDateTime): boolean;
  18623. var tmp: RawUTF8;
  18624. vd: TVarData;
  18625. begin
  18626. with TVarData(V) do
  18627. if VType=varVariant or varByRef then
  18628. result := VariantToDateTime(PVariant(VPointer)^,Value) else
  18629. case VType of
  18630. varDouble,varDate: begin
  18631. Value := VDouble;
  18632. result := true;
  18633. end;
  18634. varSingle: begin
  18635. Value := VSingle;
  18636. result := true;
  18637. end;
  18638. varCurrency: begin
  18639. Value := VCurrency;
  18640. result := true;
  18641. end else
  18642. if SetVariantUnRefSimpleValue(V,vd) then
  18643. result := VariantToDateTime(variant(vd),Value) else begin
  18644. VariantToUTF8(V,tmp);
  18645. Iso8601ToDateTimePUTF8CharVar(pointer(tmp),length(tmp),Value);
  18646. result := Value<>0;
  18647. end;
  18648. end;
  18649. end;
  18650. procedure VariantToInlineValue(const V: Variant; var result: RawUTF8);
  18651. var wasString: boolean;
  18652. begin
  18653. VariantToUTF8(V,result,wasString);
  18654. if wasString then
  18655. result := QuotedStr(pointer(result),'"');
  18656. end;
  18657. function VariantToVariantUTF8(const V: Variant): variant;
  18658. var tmp: RawUTF8;
  18659. wasString: boolean;
  18660. begin
  18661. VariantToUTF8(V,tmp,wasString);
  18662. if wasString then
  18663. result := V else
  18664. RawUTF8ToVariant(tmp,result);
  18665. end;
  18666. procedure VariantToUTF8(const V: Variant; var result: RawUTF8;
  18667. var wasString: boolean); overload;
  18668. var tmp: TVarData;
  18669. begin
  18670. wasString := false;
  18671. with TVarData(V) do
  18672. case VType of
  18673. varEmpty,
  18674. varNull:
  18675. result := 'null';
  18676. varSmallint:
  18677. Int32ToUTF8(VSmallInt,result);
  18678. {$ifndef DELPHI5OROLDER}
  18679. varShortInt:
  18680. Int32ToUTF8(VShortInt,result);
  18681. varWord:
  18682. UInt32ToUTF8(VWord,result);
  18683. varLongWord:
  18684. UInt32ToUTF8(VLongWord,result);
  18685. {$endif}
  18686. varByte,
  18687. varBoolean:
  18688. UInt32ToUTF8(VByte,result);
  18689. varInteger:
  18690. Int32ToUTF8(VInteger,result);
  18691. varInt64,
  18692. varWord64:
  18693. Int64ToUTF8(VInt64,result);
  18694. varSingle:
  18695. ExtendedToStr(VSingle,SINGLE_PRECISION,result);
  18696. varDouble:
  18697. ExtendedToStr(VDouble,DOUBLE_PRECISION,result);
  18698. varCurrency:
  18699. Curr64ToStr(VInt64,result);
  18700. varDate: begin
  18701. wasString := true;
  18702. DateTimeToIso8601TextVar(VDate,'T',result);
  18703. end;
  18704. varString: begin
  18705. wasString := true;
  18706. {$ifdef HASCODEPAGE}
  18707. AnyAnsiToUTF8(RawByteString(VString),result);
  18708. {$else}
  18709. result := RawUTF8(VString);
  18710. {$endif}
  18711. end;
  18712. {$ifdef HASVARUSTRING}
  18713. varUString: begin
  18714. wasString := true;
  18715. RawUnicodeToUtf8(VAny,length(UnicodeString(VAny)),result);
  18716. end;
  18717. {$endif}
  18718. varOleStr: begin
  18719. wasString := true;
  18720. RawUnicodeToUtf8(VAny,length(WideString(VAny)),result);
  18721. end;
  18722. else
  18723. if SetVariantUnRefSimpleValue(V,tmp) then
  18724. VariantToUTF8(Variant(tmp),result,wasString) else
  18725. if VType=varVariant or varByRef then // complex varByRef
  18726. VariantToUTF8(PVariant(VPointer)^,result,wasString) else
  18727. if VType=varByRef or varString then begin
  18728. wasString := true;
  18729. {$ifdef HASCODEPAGE}
  18730. AnyAnsiToUTF8(PRawByteString(VString)^,result);
  18731. {$else}
  18732. result := PRawUTF8(VString)^;
  18733. {$endif}
  18734. end else
  18735. if VType=varByRef or varOleStr then begin
  18736. wasString := true;
  18737. RawUnicodeToUtf8(pointer(PWideString(VAny)^),length(PWideString(VAny)^),result);
  18738. end else
  18739. {$ifdef HASVARUSTRING}
  18740. if VType=varByRef or varUString then begin
  18741. wasString := true;
  18742. RawUnicodeToUtf8(pointer(PUnicodeString(VAny)^),length(PUnicodeString(VAny)^),result);
  18743. end else
  18744. {$endif}
  18745. VariantSaveJSON(V,twJSONEscape,result); // will handle also custom types
  18746. end;
  18747. end;
  18748. function VariantToUTF8(const V: Variant): RawUTF8;
  18749. var wasString: boolean;
  18750. begin
  18751. VariantToUTF8(V,result,wasString);
  18752. end;
  18753. function ToUTF8(const V: Variant): RawUTF8; overload;
  18754. var wasString: boolean;
  18755. begin
  18756. VariantToUTF8(V,result,wasString);
  18757. end;
  18758. function VariantToUTF8(const V: Variant; var Text: RawUTF8): boolean;
  18759. begin
  18760. VariantToUTF8(V,Text,result);
  18761. end;
  18762. function VariantEquals(const V: Variant; const Str: RawUTF8): boolean;
  18763. function Complex(const V: Variant; const Str: RawUTF8): boolean;
  18764. var wasString: boolean;
  18765. tmp: RawUTF8;
  18766. begin
  18767. VariantToUTF8(V,tmp,wasString);
  18768. result := (tmp=Str);
  18769. end;
  18770. begin
  18771. with TVarData(V) do
  18772. case VType of
  18773. varEmpty,varNull:
  18774. result := false;
  18775. varString:
  18776. result := RawUTF8(VAny)=Str;
  18777. else
  18778. result := Complex(V,Str);
  18779. end;
  18780. end;
  18781. function VariantToString(const V: Variant): string;
  18782. var wasString: boolean;
  18783. tmp: RawUTF8;
  18784. begin
  18785. with TVarData(V) do
  18786. case VType of
  18787. varEmpty,varNull:
  18788. result := ''; // default VariantToUTF8(null)='null'
  18789. {$ifdef UNICODE} // not HASVARUSTRING: here we handle string=UnicodeString
  18790. varUString:
  18791. result := UnicodeString(VAny);
  18792. else
  18793. if VType=varByRef or varUString then
  18794. result := PUnicodeString(VAny)^
  18795. {$endif}
  18796. else begin
  18797. VariantToUTF8(V,tmp,wasString);
  18798. if tmp='' then
  18799. result := '' else
  18800. UTF8DecodeToString(pointer(tmp),length(tmp),result);
  18801. end;
  18802. end;
  18803. end;
  18804. procedure VariantToSQLVar(const Input: variant; var temp: RawByteString;
  18805. var Output: TSQLVar);
  18806. var wasString: boolean;
  18807. begin
  18808. with TVarData(Input) do
  18809. if VType=varVariant or varByRef then
  18810. VariantToSQLVar(PVariant(VPointer)^,temp,Output) else
  18811. case VType of
  18812. varEmpty, varNull:
  18813. Output.VType := ftNull;
  18814. varCurrency: begin
  18815. Output.VType := ftCurrency;
  18816. Output.VInt64 := VInt64;
  18817. end;
  18818. varString: begin // assume RawUTF8
  18819. Output.VType := ftUTF8;
  18820. Output.VText := VPointer;
  18821. end;
  18822. varInteger: begin
  18823. Output.VType := ftInt64;
  18824. Output.VInt64 := VInteger;
  18825. end;
  18826. varInt64: begin
  18827. Output.VType := ftInt64;
  18828. Output.VInt64 := VInt64;
  18829. end;
  18830. varSingle: begin
  18831. Output.VType := ftDouble;
  18832. Output.VDouble := VSingle;
  18833. end;
  18834. varDouble: begin // varDate would be converted into ISO8601 by VariantToUTF8()
  18835. Output.VType := ftDouble;
  18836. Output.VDouble := VDouble;
  18837. end;
  18838. else // handle less current cases
  18839. if VariantToInt64(Input,Output.VInt64) then
  18840. Output.VType := ftInt64 else begin
  18841. VariantToUTF8(Input,RawUTF8(temp),wasString);
  18842. if wasString then begin
  18843. Output.VType := ftUTF8;
  18844. Output.VText := pointer(temp);
  18845. end else
  18846. Output.VType := ftNull;
  18847. end;
  18848. end;
  18849. end;
  18850. procedure VariantDynArrayClear(var Value: TVariantDynArray);
  18851. var p: PDynArrayRec;
  18852. V: PVarData;
  18853. i: integer;
  18854. handler: TCustomVariantType;
  18855. begin
  18856. if pointer(Value)=nil then
  18857. exit;
  18858. p := pointer(PtrUInt(Value)-Sizeof(TDynArrayRec)); // p^ = start of heap object
  18859. V := pointer(Value);
  18860. pointer(Value) := nil;
  18861. if p^.refCnt>1 then begin
  18862. InterlockedDecrement(PInteger(@p^.refCnt)^); // FPC has refCnt: PtrInt
  18863. exit;
  18864. end;
  18865. handler := nil;
  18866. for i := 1 to p^.length do begin
  18867. case V^.VType of
  18868. varEmpty..varDate,varError,varBoolean,varShortInt..varWord64: ;
  18869. varString: RawUTF8(V^.VAny) := '';
  18870. varOleStr: WideString(V^.VAny) := '';
  18871. {$ifdef HASVARUSTRING}
  18872. varUString: UnicodeString(V^.VAny) := '';
  18873. {$endif}
  18874. else
  18875. if V^.VType=word(DocVariantVType) then
  18876. DocVariantType.Clear(V^) else
  18877. if V^.VType=varVariant or varByRef then
  18878. VarClear(PVariant(V^.VPointer)^) else
  18879. if handler=nil then
  18880. if (V^.VType and varByRef=0) and
  18881. FindCustomVariantType(V^.VType,handler) then
  18882. handler.Clear(V^) else
  18883. VarClear(variant(V^)) else
  18884. if V^.VType=handler.VarType then
  18885. handler.Clear(V^) else
  18886. VarClear(variant(V^));
  18887. end;
  18888. inc(V);
  18889. end;
  18890. FreeMem(p);
  18891. end;
  18892. {$endif NOVARIANTS}
  18893. {$ifdef UNICODE}
  18894. // this Pos() is seldom used, it was decided to only define it under
  18895. // Delphi 2009+ (which expect such a RawUTF8 specific overloaded version)
  18896. function Pos(const substr, str: RawUTF8): Integer; overload;
  18897. begin
  18898. Result := PosEx(substr, str, 1);
  18899. end;
  18900. function IntToString(Value: integer): string;
  18901. var tmp: array[0..15] of AnsiChar;
  18902. P: PAnsiChar;
  18903. begin
  18904. P := StrInt32(@tmp[15],Value);
  18905. Ansi7ToString(PWinAnsiChar(P),@tmp[15]-P,result);
  18906. end;
  18907. function IntToString(Value: cardinal): string;
  18908. var tmp: array[0..15] of AnsiChar;
  18909. P: PAnsiChar;
  18910. begin
  18911. P := StrUInt32(@tmp[15],Value);
  18912. Ansi7ToString(PWinAnsiChar(P),@tmp[15]-P,result);
  18913. end;
  18914. function IntToString(Value: Int64): string;
  18915. var tmp: array[0..31] of AnsiChar;
  18916. P: PAnsiChar;
  18917. begin
  18918. P := StrInt64(@tmp[31],Value);
  18919. Ansi7ToString(PWinAnsiChar(P),@tmp[31]-P,result);
  18920. end;
  18921. function DoubleToString(Value: Double): string;
  18922. var tmp: ShortString;
  18923. begin
  18924. if Value=0 then
  18925. result := '0' else
  18926. Ansi7ToString(PWinAnsiChar(@tmp[1]),
  18927. ExtendedToString(tmp,Value,DOUBLE_PRECISION),result);
  18928. end;
  18929. function Curr64ToString(Value: Int64): string;
  18930. var tmp: array[0..31] of AnsiChar;
  18931. begin
  18932. Ansi7ToString(tmp,Curr64ToPChar(Value,tmp),result);
  18933. end;
  18934. {$else UNICODE}
  18935. function IntToString(Value: integer): string;
  18936. {$ifdef PUREPASCAL}
  18937. var tmp: array[0..15] of AnsiChar;
  18938. P: PAnsiChar;
  18939. begin
  18940. P := StrInt32(@tmp[15],Value);
  18941. SetString(result,P,@tmp[15]-P);
  18942. end;
  18943. {$else}
  18944. asm
  18945. jmp Int32ToUTF8
  18946. end;
  18947. {$endif}
  18948. function IntToString(Value: cardinal): string;
  18949. var tmp: array[0..15] of AnsiChar;
  18950. P: PAnsiChar;
  18951. begin
  18952. P := StrUInt32(@tmp[15],Value);
  18953. SetString(result,P,@tmp[15]-P);
  18954. end;
  18955. function IntToString(Value: Int64): string;
  18956. var tmp: array[0..31] of AnsiChar;
  18957. P: PAnsiChar;
  18958. begin
  18959. P := StrInt64(@tmp[31],Value);
  18960. SetString(result,P,@tmp[31]-P);
  18961. end;
  18962. function DoubleToString(Value: Double): string;
  18963. var tmp: ShortString;
  18964. begin
  18965. if Value=0 then
  18966. result := '0' else
  18967. SetString(result,PAnsiChar(@tmp[1]),ExtendedToString(tmp,Value,DOUBLE_PRECISION));
  18968. end;
  18969. function Curr64ToString(Value: Int64): string;
  18970. begin
  18971. result := Curr64ToStr(Value);
  18972. end;
  18973. {$endif UNICODE}
  18974. {$ifdef PUREPASCAL}
  18975. function bswap32(a: cardinal): cardinal; {$ifdef HASINLINE}inline;{$endif}
  18976. begin
  18977. result := ((a and $ff)shl 24)or((a and $ff00)shl 8)or
  18978. ((a and $ff0000)shr 8)or((a and $ff000000)shr 24);
  18979. end;
  18980. {$else}
  18981. {$ifdef CPUX64}
  18982. function bswap32(a: cardinal): cardinal;
  18983. {$ifdef FPC}nostackframe; assembler;
  18984. asm
  18985. {$else}
  18986. asm
  18987. .NOFRAME // ecx=a (Linux: edi)
  18988. {$endif FPC}
  18989. {$ifdef win64}
  18990. mov eax,ecx
  18991. {$else}
  18992. mov eax,edi
  18993. {$endif win64}
  18994. bswap eax
  18995. end;
  18996. {$endif CPUX64}
  18997. {$ifdef CPUX86}
  18998. function bswap32(a: cardinal): cardinal;
  18999. asm
  19000. bswap eax
  19001. end;
  19002. {$endif CPUX86}
  19003. {$endif PUREPASCAL}
  19004. {$ifndef PUREPASCAL} { these functions are implemented in asm }
  19005. {$ifndef LVCL} { don't define these functions twice }
  19006. {$ifndef FPC} { these asm function use some low-level system.pas calls }
  19007. {$define DEFINED_INT32TOUTF8}
  19008. function Int32ToUTF8(Value : integer): RawUtf8; // 3x faster than SysUtils.IntToStr
  19009. // from IntToStr32_JOH_IA32_6_a, adapted for Delphi 2009+
  19010. asm // eax=Value, edx=@result
  19011. push ebx
  19012. push edi
  19013. push esi
  19014. mov ebx,eax {Value}
  19015. sar ebx,31 {0 for +ve Value or -1 for -ve Value}
  19016. xor eax,ebx
  19017. sub eax,ebx {ABS(Value)}
  19018. mov esi,10 {Max Digits in result}
  19019. mov edi,edx {@result}
  19020. cmp eax,10; sbb esi, 0
  19021. cmp eax,100; sbb esi, 0
  19022. cmp eax,1000; sbb esi, 0
  19023. cmp eax,10000; sbb esi, 0
  19024. cmp eax,100000; sbb esi, 0
  19025. cmp eax,1000000; sbb esi, 0
  19026. cmp eax,10000000; sbb esi, 0
  19027. cmp eax,100000000; sbb esi, 0
  19028. cmp eax,1000000000; sbb esi, ebx {esi=Digits (Including Sign Character)}
  19029. mov ecx,[edx] {result}
  19030. test ecx,ecx
  19031. je @@NewStr {Create New string for result}
  19032. cmp dword ptr [ecx-8], 1
  19033. jne @@ChangeStr {Reference Count <> 1}
  19034. cmp esi,[ecx-4]
  19035. je @@LengthOk {Existing Length = Required Length}
  19036. sub ecx,STRRECSIZE {Allocation Address}
  19037. push eax {ABS(Value)}
  19038. push ecx
  19039. mov eax,esp
  19040. lea edx,[esi+STRRECSIZE+1] {New Allocation Size}
  19041. call system.@ReallocMem {Reallocate result string}
  19042. pop ecx
  19043. pop eax {ABS(Value)}
  19044. add ecx,STRRECSIZE {result}
  19045. mov [ecx-4],esi {Set New Length}
  19046. mov byte ptr [ecx+esi],0 {Add Null Terminator}
  19047. mov [edi],ecx {Set result Address}
  19048. jmp @@LengthOk
  19049. @@ChangeStr:
  19050. mov edx,dword ptr [ecx-8] {Reference Count}
  19051. add edx,1
  19052. jz @@NewStr {RefCount = -1 (string Constant)}
  19053. lock dec dword ptr [ecx-8] {Decrement Existing Reference Count}
  19054. @@NewStr:
  19055. push eax {ABS(Value)}
  19056. mov eax,esi {Length}
  19057. {$ifdef UNICODE}
  19058. mov edx,CP_UTF8 // UTF-8 code page for Delphi 2009+
  19059. {$endif}
  19060. call system.@NewAnsiString
  19061. mov [edi],eax {Set result Address}
  19062. mov ecx,eax {result}
  19063. pop eax {ABS(Value)}
  19064. @@LengthOk:
  19065. mov byte ptr [ecx],'-' {Store '-' Character (May be Overwritten)}
  19066. add esi,ebx {Digits (Excluding Sign Character)}
  19067. sub ecx,ebx {Destination of 1st Digit}
  19068. sub esi,2 {Digits (Excluding Sign Character) - 2}
  19069. jle @@FinalDigits {1 or 2 Digit Value}
  19070. cmp esi,8 {10 Digit Value?}
  19071. jne @@SetResult {Not a 10 Digit Value}
  19072. sub eax,2000000000 {Digit 10 must be either '1' or '2'}
  19073. mov dl,'2'
  19074. jnc @@SetDigit10 {Digit 10 = '2'}
  19075. mov dl,'1' {Digit 10 = '1'}
  19076. add eax,1000000000
  19077. @@SetDigit10:
  19078. mov [ecx],dl {Save Digit 10}
  19079. mov esi,7 {9 Digits Remaining}
  19080. add ecx,1 {Destination of 2nd Digit}
  19081. @@SetResult:
  19082. mov edi,$28F5C29 {((2^32)+100-1)/100}
  19083. @@Loop:
  19084. mov ebx,eax {Dividend}
  19085. mul edi {EDX = Dividend DIV 100}
  19086. mov eax,edx {Set Next Dividend}
  19087. imul edx,-200 {-2 * (100 * Dividend DIV 100)}
  19088. movzx edx,word ptr [TwoDigitLookup+ebx*2+edx] {Dividend MOD 100 in ASCII}
  19089. mov [ecx+esi],dx
  19090. sub esi,2
  19091. jg @@Loop {Loop until 1 or 2 Digits Remaining}
  19092. @@FinalDigits:
  19093. pop esi
  19094. pop edi
  19095. pop ebx
  19096. jnz @@LastDigit
  19097. movzx eax,word ptr [TwoDigitLookup+eax*2]
  19098. mov [ecx],ax {Save Final 2 Digits}
  19099. ret
  19100. @@LastDigit:
  19101. or al,'0' {Ascii Adjustment}
  19102. mov [ecx],al {Save Final Digit}
  19103. end;
  19104. function Int64ToUTF8(Value: Int64): RawUtf8;
  19105. // from IntToStr64_JOH_IA32_6_b, adapted for Delphi 2009+
  19106. asm
  19107. push ebx
  19108. mov ecx, [ebp+8] {Low Integer of Value}
  19109. mov edx, [ebp+12] {High Integer of Value}
  19110. xor ebp, ebp {Clear Sign Flag (EBP Already Pushed)}
  19111. mov ebx, ecx {Low Integer of Value}
  19112. test edx, edx
  19113. jnl @@AbsValue
  19114. mov ebp, 1 {EBP = 1 for -ve Value or 0 for +ve Value}
  19115. neg ecx
  19116. adc edx, 0
  19117. neg edx
  19118. @@AbsValue: {EDX:ECX = Abs(Value)}
  19119. jnz @@Large
  19120. test ecx, ecx
  19121. js @@Large
  19122. mov edx, eax {@Result}
  19123. mov eax, ebx {Low Integer of Value}
  19124. call Int32ToUTF8 {Call Fastest Integer IntToStr Function}
  19125. pop ebx
  19126. @@Exit:
  19127. pop ebp {Restore Stack and Exit}
  19128. ret 8
  19129. @@Large:
  19130. push edi
  19131. push esi
  19132. mov edi, eax
  19133. xor ebx, ebx
  19134. xor eax, eax
  19135. @@Test15: {Test for 15 or More Digits}
  19136. cmp edx, $00005af3 {100000000000000 div $100000000}
  19137. jne @@Check15
  19138. cmp ecx, $107a4000 {100000000000000 mod $100000000}
  19139. @@Check15:
  19140. jb @@Test13
  19141. @@Test17: {Test for 17 or More Digits}
  19142. cmp edx, $002386f2 {10000000000000000 div $100000000}
  19143. jne @@Check17
  19144. cmp ecx, $6fc10000 {10000000000000000 mod $100000000}
  19145. @@Check17:
  19146. jb @@Test15or16
  19147. @@Test19: {Test for 19 Digits}
  19148. cmp edx, $0de0b6b3 {1000000000000000000 div $100000000}
  19149. jne @@Check19
  19150. cmp ecx, $a7640000 {1000000000000000000 mod $100000000}
  19151. @@Check19:
  19152. jb @@Test17or18
  19153. mov al, 19
  19154. jmp @@SetLength
  19155. @@Test17or18: {17 or 18 Digits}
  19156. mov bl, 18
  19157. cmp edx, $01634578 {100000000000000000 div $100000000}
  19158. jne @@SetLen
  19159. cmp ecx, $5d8a0000 {100000000000000000 mod $100000000}
  19160. jmp @@SetLen
  19161. @@Test15or16: {15 or 16 Digits}
  19162. mov bl, 16
  19163. cmp edx, $00038d7e {1000000000000000 div $100000000}
  19164. jne @@SetLen
  19165. cmp ecx, $a4c68000 {1000000000000000 mod $100000000}
  19166. jmp @@SetLen
  19167. @@Test13: {Test for 13 or More Digits}
  19168. cmp edx, $000000e8 {1000000000000 div $100000000}
  19169. jne @@Check13
  19170. cmp ecx, $d4a51000 {1000000000000 mod $100000000}
  19171. @@Check13:
  19172. jb @@Test11
  19173. @@Test13or14: {13 or 14 Digits}
  19174. mov bl, 14
  19175. cmp edx, $00000918 {10000000000000 div $100000000}
  19176. jne @@SetLen
  19177. cmp ecx, $4e72a000 {10000000000000 mod $100000000}
  19178. jmp @@SetLen
  19179. @@Test11: {10, 11 or 12 Digits}
  19180. cmp edx, $02 {10000000000 div $100000000}
  19181. jne @@Check11
  19182. cmp ecx, $540be400 {10000000000 mod $100000000}
  19183. @@Check11:
  19184. mov bl, 11
  19185. jb @@SetLen {10 Digits}
  19186. @@Test11or12: {11 or 12 Digits}
  19187. mov bl, 12
  19188. cmp edx, $17 {100000000000 div $100000000}
  19189. jne @@SetLen
  19190. cmp ecx, $4876e800 {100000000000 mod $100000000}
  19191. @@SetLen:
  19192. sbb eax, 0 {Adjust for Odd/Evem Digit Count}
  19193. add eax, ebx
  19194. @@SetLength: {Abs(Value) in EDX:ECX, Digits in EAX}
  19195. push ecx {Save Abs(Value)}
  19196. push edx
  19197. lea edx, [eax+ebp] {Digits Needed (Including Sign Character)}
  19198. mov ecx, [edi] {@Result}
  19199. mov esi, edx {Digits Needed (Including Sign Character)}
  19200. test ecx, ecx
  19201. je @@NewStr {Create New AnsiString for Result}
  19202. cmp dword ptr [ecx-8], 1
  19203. jne @@ChangeStr {Reference Count <> 1}
  19204. cmp esi, [ecx-4]
  19205. je @@LengthOk {Existing Length = Required Length}
  19206. sub ecx, STRRECSIZE {Allocation Address}
  19207. push eax {ABS(Value)}
  19208. push ecx
  19209. mov eax, esp
  19210. lea edx, [esi+STRRECSIZE+1] {New Allocation Size}
  19211. call system.@ReallocMem {Reallocate Result AnsiString}
  19212. pop ecx
  19213. pop eax {ABS(Value)}
  19214. add ecx, STRRECSIZE {@Result}
  19215. mov [ecx-4], esi {Set New Length}
  19216. mov byte ptr [ecx+esi], 0 {Add Null Terminator}
  19217. mov [edi], ecx {Set Result Address}
  19218. jmp @@LengthOk
  19219. @@ChangeStr:
  19220. mov edx, dword ptr [ecx-8] {Reference Count}
  19221. add edx, 1
  19222. jz @@NewStr {RefCount = -1 (AnsiString Constant)}
  19223. lock dec dword ptr [ecx-8] {Decrement Existing Reference Count}
  19224. @@NewStr:
  19225. push eax {ABS(Value)}
  19226. mov eax, esi {Length}
  19227. {$ifdef UNICODE}
  19228. mov edx,CP_UTF8 // UTF-8 code page for Delphi 2009+
  19229. {$endif}
  19230. call system.@NewAnsiString
  19231. mov [edi], eax {Set Result Address}
  19232. mov ecx, eax {@Result}
  19233. pop eax {ABS(Value)}
  19234. @@LengthOk:
  19235. mov edi, [edi] {@Result}
  19236. sub esi, ebp {Digits Needed (Excluding Sign Character)}
  19237. mov byte ptr [edi], '-' {Store '-' Character (May be Overwritten)}
  19238. add edi, ebp {Destination of 1st Digit}
  19239. pop edx {Restore Abs(Value)}
  19240. pop eax
  19241. cmp esi, 17
  19242. jl @@LessThan17Digits {Digits < 17}
  19243. je @@SetDigit17 {Digits = 17}
  19244. cmp esi, 18
  19245. je @@SetDigit18 {Digits = 18}
  19246. mov cl, '0' - 1
  19247. mov ebx, $a7640000 {1000000000000000000 mod $100000000}
  19248. mov ebp, $0de0b6b3 {1000000000000000000 div $100000000}
  19249. @@CalcDigit19:
  19250. add ecx, 1
  19251. sub eax, ebx
  19252. sbb edx, ebp
  19253. jnc @@CalcDigit19
  19254. add eax, ebx
  19255. adc edx, ebp
  19256. mov [edi], cl
  19257. add edi, 1
  19258. @@SetDigit18:
  19259. mov cl, '0' - 1
  19260. mov ebx, $5d8a0000 {100000000000000000 mod $100000000}
  19261. mov ebp, $01634578 {100000000000000000 div $100000000}
  19262. @@CalcDigit18:
  19263. add ecx, 1
  19264. sub eax, ebx
  19265. sbb edx, ebp
  19266. jnc @@CalcDigit18
  19267. add eax, ebx
  19268. adc edx, ebp
  19269. mov [edi], cl
  19270. add edi, 1
  19271. @@SetDigit17:
  19272. mov cl, '0' - 1
  19273. mov ebx, $6fc10000 {10000000000000000 mod $100000000}
  19274. mov ebp, $002386f2 {10000000000000000 div $100000000}
  19275. @@CalcDigit17:
  19276. add ecx, 1
  19277. sub eax, ebx
  19278. sbb edx, ebp
  19279. jnc @@CalcDigit17
  19280. add eax, ebx
  19281. adc edx, ebp
  19282. mov [edi], cl
  19283. add edi, 1 {Update Destination}
  19284. mov esi, 16 {Set 16 Digits Left}
  19285. @@LessThan17Digits: {Process Next 8 Digits}
  19286. mov ecx, 100000000 {EDX:EAX = Abs(Value) = Dividend}
  19287. div ecx
  19288. mov ebp, eax {Dividend DIV 100000000}
  19289. mov ebx, edx
  19290. mov eax, edx {Dividend MOD 100000000}
  19291. mov edx, $51EB851F
  19292. mul edx
  19293. shr edx, 5 {Dividend DIV 100}
  19294. mov eax, edx {Set Next Dividend}
  19295. lea edx, [edx*4+edx]
  19296. lea edx, [edx*4+edx]
  19297. shl edx, 2 {Dividend DIV 100 * 100}
  19298. sub ebx, edx {Remainder (0..99)}
  19299. movzx ebx, word ptr [TwoDigitLookup+ebx*2]
  19300. shl ebx, 16
  19301. mov edx, $51EB851F
  19302. mov ecx, eax {Dividend}
  19303. mul edx
  19304. shr edx, 5 {Dividend DIV 100}
  19305. mov eax, edx
  19306. lea edx, [edx*4+edx]
  19307. lea edx, [edx*4+edx]
  19308. shl edx, 2 {Dividend DIV 100 * 100}
  19309. sub ecx, edx {Remainder (0..99)}
  19310. or bx, word ptr [TwoDigitLookup+ecx*2]
  19311. mov [edi+esi-4], ebx {Store 4 Digits}
  19312. mov ebx, eax
  19313. mov edx, $51EB851F
  19314. mul edx
  19315. shr edx, 5 {EDX = Dividend DIV 100}
  19316. lea eax, [edx*4+edx]
  19317. lea eax, [eax*4+eax]
  19318. shl eax, 2 {EAX = Dividend DIV 100 * 100}
  19319. sub ebx, eax {Remainder (0..99)}
  19320. movzx ebx, word ptr [TwoDigitLookup+ebx*2]
  19321. movzx ecx, word ptr [TwoDigitLookup+edx*2]
  19322. shl ebx, 16
  19323. or ebx, ecx
  19324. mov [edi+esi-8], ebx {Store 4 Digits}
  19325. mov eax, ebp {Remainder}
  19326. sub esi, 10 {Digits Left - 2}
  19327. jz @@Last2Digits
  19328. @@SmallLoop: {Process Remaining Digits}
  19329. mov edx, $28F5C29 {((2^32)+100-1)/100}
  19330. mov ebx, eax {Dividend}
  19331. mul edx
  19332. mov eax, edx {Set Next Dividend}
  19333. imul edx, -200
  19334. movzx edx, word ptr [TwoDigitLookup+ebx*2+edx] {Dividend MOD 100 in ASCII}
  19335. mov [edi+esi], dx
  19336. sub esi, 2
  19337. jg @@SmallLoop {Repeat Until Less than 2 Digits Remaining}
  19338. jz @@Last2Digits
  19339. or al , '0' {Ascii Adjustment}
  19340. mov [edi], al {Save Final Digit}
  19341. jmp @@Done
  19342. @@Last2Digits:
  19343. movzx eax, word ptr [TwoDigitLookup+eax*2]
  19344. mov [edi], ax {Save Final 2 Digits}
  19345. @@Done:
  19346. pop esi
  19347. pop edi
  19348. pop ebx
  19349. end;
  19350. function Trim(const S: RawUTF8): RawUTF8;
  19351. asm // fast implementation by John O'Harrow, modified for Delphi 2009+
  19352. test eax,eax {S = nil?}
  19353. xchg eax,edx
  19354. jz System.@LStrClr {Yes, Return Empty String}
  19355. mov ecx,[edx-4] {Length(S)}
  19356. cmp byte ptr [edx],' ' {S[1] <= ' '?}
  19357. jbe @@TrimLeft {Yes, Trim Leading Spaces}
  19358. cmp byte ptr [edx+ecx-1],' ' {S[Length(S)] <= ' '?}
  19359. jbe @@TrimRight {Yes, Trim Trailing Spaces}
  19360. jmp System.@LStrLAsg {No, Result := S (which occurs most time)}
  19361. @@TrimLeft: {Strip Leading Whitespace}
  19362. dec ecx
  19363. jle System.@LStrClr {All Whitespace}
  19364. inc edx
  19365. cmp byte ptr [edx],' '
  19366. jbe @@TrimLeft
  19367. @@CheckDone:
  19368. cmp byte ptr [edx+ecx-1],' '
  19369. {$ifdef UNICODE}
  19370. jbe @@TrimRight
  19371. push CP_UTF8 // UTF-8 code page for Delphi 2009+
  19372. call System.@LStrFromPCharLen // we need a call, not a jmp here
  19373. rep ret
  19374. {$else}
  19375. ja System.@LStrFromPCharLen
  19376. {$endif}
  19377. @@TrimRight: {Strip Trailing Whitespace}
  19378. dec ecx
  19379. jmp @@CheckDone
  19380. end;
  19381. {$endif FPC} { these asm function had some low-level system.pas calls }
  19382. function CompareMem(P1, P2: Pointer; Length: Integer): Boolean;
  19383. asm // eax=P1 edx=P2 ecx=Length
  19384. cmp eax,edx
  19385. je @0 // P1=P2
  19386. sub ecx,8
  19387. jl @small
  19388. push ebx
  19389. mov ebx,[eax] // Compare First 4 Bytes
  19390. cmp ebx,[edx]
  19391. jne @setbig
  19392. lea ebx,[eax+ecx] // Compare Last 8 Bytes
  19393. add edx,ecx
  19394. mov eax,[ebx]
  19395. cmp eax,[edx]
  19396. jne @setbig
  19397. mov eax,[ebx+4]
  19398. cmp eax,[edx+4]
  19399. jne @setbig
  19400. sub ecx,4
  19401. jle @true // All Bytes already Compared
  19402. neg ecx // ecx=-(Length-12)
  19403. add ecx,ebx // DWORD Align Reads
  19404. and ecx,-4
  19405. sub ecx,ebx
  19406. @loop: mov eax,[ebx+ecx] // Compare 8 Bytes per Loop
  19407. cmp eax,[edx+ecx]
  19408. jne @setbig
  19409. mov eax,[ebx+ecx+4]
  19410. cmp eax,[edx+ecx+4]
  19411. jne @setbig
  19412. add ecx,8
  19413. jl @loop
  19414. @true: pop ebx
  19415. @0: mov al,1
  19416. ret
  19417. @setbig:pop ebx
  19418. setz al
  19419. ret
  19420. @small: add ecx,8 // ecx=0..7
  19421. jle @0 // Length <= 0
  19422. neg ecx // ecx=-1..-7
  19423. lea ecx,[@1+ecx*8+8] // each @#: line below = 8 bytes
  19424. jmp ecx
  19425. @7: mov cl,[eax+6]; cmp cl,[edx+6]; jne @setsml
  19426. @6: mov ch,[eax+5]; cmp ch,[edx+5]; jne @setsml
  19427. @5: mov cl,[eax+4]; cmp cl,[edx+4]; jne @setsml
  19428. @4: mov ch,[eax+3]; cmp ch,[edx+3]; jne @setsml
  19429. @3: mov cl,[eax+2]; cmp cl,[edx+2]; jne @setsml
  19430. @2: mov ch,[eax+1]; cmp ch,[edx+1]; jne @setsml
  19431. @1: mov al,[eax]; cmp al,[edx]
  19432. @setsml:setz al
  19433. end;
  19434. {$ifndef ISDELPHI2007ANDUP}
  19435. {$endif ISDELPHI2007ANDUP}
  19436. {$endif LVCL}
  19437. {$endif PUREPASCAL}
  19438. {$ifdef PUREPASCAL} // from Aleksandr Sharahov's PosEx_Sha_Pas_2()
  19439. function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt = 1): Integer;
  19440. var len, lenSub: PtrInt;
  19441. ch: AnsiChar;
  19442. p, pSub, pStart, pStop: PUTF8Char;
  19443. label Loop0, Loop4, TestT, Test0, Test1, Test2, Test3, Test4,
  19444. AfterTestT, AfterTest0, Ret, Exit;
  19445. begin;
  19446. pSub := pointer(SubStr);
  19447. p := pointer(S);
  19448. if (p=nil) or (pSub=nil) or (Offset<1) then begin
  19449. Result := 0;
  19450. goto Exit;
  19451. end;
  19452. {$ifdef FPC}
  19453. len := PStrRec(Pointer(PtrInt(p)-STRRECSIZE))^.length;
  19454. lenSub := PStrRec(Pointer(PtrInt(pSub)-STRRECSIZE))^.length-1;
  19455. {$else}
  19456. len := PInteger(p-4)^;
  19457. lenSub := PInteger(pSub-4)^-1;
  19458. {$endif}
  19459. if (len<lenSub+PtrInt(Offset)) or (lenSub<0) then begin
  19460. Result := 0;
  19461. goto Exit;
  19462. end;
  19463. pStop := p+len;
  19464. p := p+lenSub;
  19465. pSub := pSub+lenSub;
  19466. pStart := p;
  19467. p := p+Offset+3;
  19468. ch := pSub[0];
  19469. lenSub := -lenSub;
  19470. if p<pStop then goto Loop4;
  19471. p := p-4;
  19472. goto Loop0;
  19473. Loop4:
  19474. if ch=p[-4] then goto Test4;
  19475. if ch=p[-3] then goto Test3;
  19476. if ch=p[-2] then goto Test2;
  19477. if ch=p[-1] then goto Test1;
  19478. Loop0:
  19479. if ch=p[0] then goto Test0;
  19480. AfterTest0:
  19481. if ch=p[1] then goto TestT;
  19482. AfterTestT:
  19483. p := p+6;
  19484. if p<pStop then goto Loop4;
  19485. p := p-4;
  19486. if p<pStop then goto Loop0;
  19487. Result := 0;
  19488. goto Exit;
  19489. Test3: p := p-2;
  19490. Test1: p := p-2;
  19491. TestT: len := lenSub;
  19492. if lenSub<>0 then
  19493. repeat
  19494. if (psub[len]<>p[len+1]) or (psub[len+1]<>p[len+2]) then
  19495. goto AfterTestT;
  19496. len := len+2;
  19497. until len>=0;
  19498. p := p+2;
  19499. if p<=pStop then goto Ret;
  19500. Result := 0;
  19501. goto Exit;
  19502. Test4: p := p-2;
  19503. Test2: p := p-2;
  19504. Test0: len := lenSub;
  19505. if lenSub<>0 then
  19506. repeat
  19507. if (psub[len]<>p[len]) or (psub[len+1]<>p[len+1]) then
  19508. goto AfterTest0;
  19509. len := len+2;
  19510. until len>=0;
  19511. inc(p);
  19512. Ret:
  19513. Result := p-pStart;
  19514. Exit:
  19515. end;
  19516. {$else}
  19517. function PosEx(const SubStr, S: RawUTF8; Offset: PtrUInt = 1): Integer;
  19518. asm // eax=SubStr, edx=S, ecx=Offset
  19519. push ebx
  19520. push esi
  19521. push edx // @Str
  19522. test eax,eax
  19523. jz @@NotFound // Exit if SubStr = ''
  19524. test edx,edx
  19525. jz @@NotFound // Exit if Str = ''
  19526. mov esi,ecx
  19527. mov ecx,[edx-4] // Length(Str)
  19528. mov ebx,[eax-4] // Length(Search string)
  19529. add ecx,edx
  19530. sub ecx,ebx // ecx = Max Start Pos for Full Match
  19531. lea edx,[edx+esi-1] // edx = Start Position
  19532. cmp edx,ecx
  19533. jg @@NotFound // StartPos > Max Start Pos
  19534. cmp ebx,1 // Length(SubStr)
  19535. jle @@SingleChar // Length(SubStr) <= 1
  19536. push edi
  19537. push ebp
  19538. lea edi,[ebx-2] // edi = Length(Search string) - 2
  19539. mov esi,eax // esi = Search string
  19540. movzx ebx,byte ptr [eax] // bl = Search Character
  19541. @@Loop: // Compare 2 Characters per Loop
  19542. cmp bl,[edx]
  19543. je @@Char1Found
  19544. @@NotChar1:
  19545. cmp bl,[edx+1]
  19546. je @@Char2Found
  19547. @@NotChar2:
  19548. lea edx,[edx+2]
  19549. cmp edx,ecx // Next Start Position <= Max Start Position
  19550. jle @@Loop
  19551. pop ebp
  19552. pop edi
  19553. @@NotFound:
  19554. xor eax,eax // returns 0 if not found
  19555. pop edx
  19556. pop esi
  19557. pop ebx
  19558. ret
  19559. @@Char1Found:
  19560. mov ebp,edi // ebp = Length(Search string) - 2
  19561. @@Char1Loop:
  19562. movzx eax,word ptr [esi+ebp]
  19563. cmp ax,[edx+ebp] // Compare 2 Chars per Char1Loop (may include #0)
  19564. jne @@NotChar1
  19565. sub ebp,2
  19566. jnc @@Char1Loop
  19567. pop ebp
  19568. pop edi
  19569. jmp @@SetResult
  19570. @@Char2Found:
  19571. mov ebp,edi // ebp = Length(Search string) - 2
  19572. @@Char2Loop:
  19573. movzx eax,word ptr [esi+ebp]
  19574. cmp ax,[edx+ebp+1] // Compare 2 Chars per Char2Loop (may include #0)
  19575. jne @@NotChar2
  19576. sub ebp,2
  19577. jnc @@Char2Loop
  19578. pop ebp
  19579. pop edi
  19580. jmp @@CheckResult
  19581. @@SingleChar:
  19582. jl @@NotFound // Needed for Zero-Length Non-NIL Strings
  19583. movzx eax,byte ptr [eax] // Search Character
  19584. @@CharLoop:
  19585. cmp al,[edx]
  19586. je @@SetResult
  19587. cmp al,[edx+1]
  19588. je @@CheckResult
  19589. lea edx,[edx+2]
  19590. cmp edx,ecx
  19591. jle @@CharLoop
  19592. jmp @@NotFound
  19593. @@CheckResult: // Check within AnsiString
  19594. cmp edx,ecx
  19595. jge @@NotFound
  19596. add edx,1
  19597. @@SetResult:
  19598. pop ecx // @Str
  19599. pop esi
  19600. pop ebx
  19601. neg ecx
  19602. lea eax,[edx+ecx+1]
  19603. end;
  19604. {$endif PUREPASCAL}
  19605. function Split(const Str, SepStr: RawUTF8; StartPos: integer): RawUTF8;
  19606. var i: integer;
  19607. begin
  19608. i := PosEx(SepStr,Str,StartPos);
  19609. if i>0 then
  19610. result := Copy(Str,StartPos,i-StartPos) else
  19611. if StartPos=1 then
  19612. result := Str else
  19613. result := Copy(Str,StartPos,maxInt);
  19614. end;
  19615. procedure Split(const Str, SepStr: RawUTF8; var LeftStr, RightStr: RawUTF8; ToUpperCase: boolean);
  19616. var i: integer;
  19617. tmp: RawUTF8; // may be called as Split(Str,SepStr,Str,RightStr)
  19618. begin
  19619. i := PosEx(SepStr,Str);
  19620. if i=0 then begin
  19621. LeftStr := Str;
  19622. RightStr := '';
  19623. end else begin
  19624. tmp := copy(Str,1,i-1);
  19625. RightStr := copy(Str,i+length(SepStr),maxInt);
  19626. LeftStr := tmp;
  19627. end;
  19628. if ToUpperCase then begin
  19629. LeftStr := UpperCaseU(LeftStr);
  19630. RightStr := UpperCaseU(RightStr);
  19631. end;
  19632. end;
  19633. function Split(const Str, SepStr: RawUTF8; var LeftStr: RawUTF8; ToUpperCase: boolean=false): RawUTF8; overload;
  19634. begin
  19635. Split(Str,SepStr,LeftStr,result,ToUpperCase);
  19636. end;
  19637. procedure Split(const Str: RawUTF8; const SepStr: array of RawUTF8;
  19638. const DestPtr: array of PRawUTF8);
  19639. var s,i,j,n: integer;
  19640. begin
  19641. j := 1;
  19642. n := 0;
  19643. s := 0;
  19644. if high(SepStr)>=0 then
  19645. while n<=high(DestPtr) do begin
  19646. i := PosEx(SepStr[s],Str,j);
  19647. if i=0 then begin
  19648. if DestPtr[n]<>nil then
  19649. DestPtr[n]^ := copy(Str,j,MaxInt);
  19650. inc(n);
  19651. break;
  19652. end;
  19653. if DestPtr[n]<>nil then
  19654. DestPtr[n]^ := copy(Str,j,i-j);
  19655. inc(n);
  19656. if s<high(SepStr) then
  19657. inc(s);
  19658. j := i+1;
  19659. end;
  19660. for i := n to high(DestPtr) do
  19661. if DestPtr[i]<>nil then
  19662. DestPtr[i]^ := '';
  19663. end;
  19664. function StringReplaceAll(const S, OldPattern, NewPattern: RawUTF8): RawUTF8;
  19665. procedure Process(j: integer);
  19666. var oldlen,newlen,i,last,posCount,sharedlen: integer;
  19667. pos: TIntegerDynArray;
  19668. src,dst: PAnsiChar;
  19669. begin
  19670. oldlen := length(OldPattern);
  19671. newlen := length(NewPattern);
  19672. SetLength(pos,64);
  19673. pos[0] := j;
  19674. posCount := 1;
  19675. repeat
  19676. j := PosEx(OldPattern,S,j+oldlen);
  19677. if j=0 then
  19678. break;
  19679. AddInteger(pos,posCount,j);
  19680. until false;
  19681. SetString(result,nil,Length(S)+(newlen-oldlen)*posCount);
  19682. last := 1;
  19683. src := pointer(s);
  19684. dst := pointer(result);
  19685. for i := 0 to posCount-1 do begin
  19686. sharedlen := pos[i]-last;
  19687. MoveFast(src^,dst^,sharedlen);
  19688. inc(src,sharedlen+oldlen);
  19689. inc(dst,sharedlen);
  19690. MoveFast(pointer(NewPattern)^,dst^,newlen);
  19691. inc(dst,newlen);
  19692. last := pos[i]+oldlen;
  19693. end;
  19694. MoveFast(src^,dst^,length(S)-last+1);
  19695. end;
  19696. var j: integer;
  19697. begin
  19698. if (S='') or (OldPattern='') or (OldPattern=NewPattern) then
  19699. result := S else begin
  19700. j := PosEx(OldPattern, S, 1); // our PosEx() is faster than Pos()
  19701. if j=0 then
  19702. result := S else
  19703. Process(j);
  19704. end;
  19705. end;
  19706. function StringReplaceTabs(const Source,TabText: RawUTF8): RawUTF8;
  19707. procedure Process(S,D,T: PAnsiChar; TLen: integer);
  19708. begin
  19709. repeat
  19710. if S^=#0 then
  19711. break else
  19712. if S^<>#9 then begin
  19713. D^ := S^;
  19714. inc(D);
  19715. inc(S);
  19716. end else begin
  19717. MoveFast(T^,D^,TLen);
  19718. inc(D,TLen);
  19719. inc(S);
  19720. end;
  19721. until false;
  19722. end;
  19723. var L,i,n,ttl: integer;
  19724. begin
  19725. ttl := length(TabText);
  19726. L := Length(Source);
  19727. n := 0;
  19728. if ttl<>0 then
  19729. for i := 1 to L do
  19730. if Source[i]=#9 then
  19731. inc(n);
  19732. if n=0 then begin
  19733. result := Source;
  19734. exit;
  19735. end;
  19736. SetLength(result,L+n*pred(ttl));
  19737. Process(pointer(Source),pointer(result),pointer(TabText),ttl);
  19738. end;
  19739. function PosChar(Str: PUTF8Char; Chr: AnsiChar): PUTF8Char;
  19740. {$ifdef PUREPASCAL}
  19741. begin
  19742. result := nil;
  19743. if Str<>nil then begin
  19744. repeat
  19745. if Str^=#0 then
  19746. exit else
  19747. if Str^=Chr then
  19748. break;
  19749. inc(Str);
  19750. if Str^=#0 then
  19751. exit else
  19752. if Str^=Chr then
  19753. break;
  19754. inc(Str);
  19755. until false;
  19756. result := Str;
  19757. end;
  19758. end;
  19759. {$else}
  19760. asm // faster version by AB - eax=Str dl=Chr
  19761. test eax,eax
  19762. jz @z
  19763. @1: mov ecx,[eax]
  19764. cmp cl,dl
  19765. jz @z
  19766. lea eax,[eax+1]
  19767. test cl,cl
  19768. jz @e
  19769. cmp ch,dl
  19770. jz @z
  19771. lea eax,[eax+1]
  19772. test ch,ch
  19773. jnz @1
  19774. @e: xor eax,eax
  19775. @z:
  19776. end;
  19777. {$endif}
  19778. function PosCharAny(Str: PUTF8Char; Characters: PAnsiChar): PUTF8Char;
  19779. var s: PAnsiChar;
  19780. c: AnsiChar;
  19781. begin
  19782. if (Str<>nil) and (Characters<>nil) then
  19783. repeat
  19784. c := Str^;
  19785. if c=#0 then
  19786. break;
  19787. s := Characters;
  19788. repeat
  19789. if s^=c then begin
  19790. result := Str;
  19791. exit;
  19792. end;
  19793. inc(s);
  19794. until s^=#0;
  19795. inc(Str);
  19796. until false;
  19797. result := nil;
  19798. end;
  19799. function StringReplaceChars(const Source: RawUTF8; OldChar, NewChar: AnsiChar): RawUTF8;
  19800. var i,j,n: integer;
  19801. begin
  19802. if (OldChar<>NewChar) and (Source<>'') then begin
  19803. n := length(Source);
  19804. for i := 0 to n-1 do
  19805. if PAnsiChar(pointer(Source))[i]=OldChar then begin
  19806. SetString(result,PAnsiChar(pointer(Source)),n);
  19807. for j := i to n-1 do
  19808. if PAnsiChar(pointer(result))[j]=OldChar then
  19809. PAnsiChar(pointer(result))[j] := NewChar;
  19810. exit;
  19811. end;
  19812. end;
  19813. result := Source;
  19814. end;
  19815. function PosI(uppersubstr: PUTF8Char; const str: RawUTF8): Integer;
  19816. var C: AnsiChar;
  19817. begin
  19818. if uppersubstr<>nil then begin
  19819. C := uppersubstr^;
  19820. for result := 1 to Length(str) do
  19821. if NormToUpperAnsi7[str[result]]=C then
  19822. if IdemPChar(@PUTF8Char(pointer(str))[result],PAnsiChar(uppersubstr)+1) then
  19823. exit;
  19824. end;
  19825. result := 0;
  19826. end;
  19827. function StrPosI(uppersubstr,str: PUTF8Char): PUTF8Char;
  19828. var C: AnsiChar;
  19829. begin
  19830. if (uppersubstr<>nil) and (str<>nil) then begin
  19831. C := uppersubstr^;
  19832. result := str;
  19833. while result^<>#0 do begin
  19834. if NormToUpperAnsi7[result^]=C then
  19835. if IdemPChar(result+1,PAnsiChar(uppersubstr)+1) then
  19836. exit;
  19837. inc(result);
  19838. end;
  19839. end;
  19840. result := nil;
  19841. end;
  19842. function PosIU(substr: PUTF8Char; const str: RawUTF8): Integer;
  19843. var p: PUTF8Char;
  19844. begin
  19845. if (substr<>nil) and (str<>'') then begin
  19846. p := pointer(str);
  19847. repeat
  19848. if GetNextUTF8Upper(p)=ord(substr^) then
  19849. if IdemPCharU(p,substr+1) then begin
  19850. result := p-pointer(str);
  19851. exit;
  19852. end;
  19853. until p^=#0;
  19854. end;
  19855. result := 0;
  19856. end;
  19857. procedure AppendCharToRawUTF8(var Text: RawUTF8; Ch: AnsiChar);
  19858. var L: integer;
  19859. begin
  19860. L := length(Text);
  19861. SetLength(Text,L+1);
  19862. PByteArray(Text)[L] := ord(Ch);
  19863. end;
  19864. procedure AppendBufferToRawUTF8(var Text: RawUTF8; Buffer: pointer; BufferLen: PtrInt);
  19865. var L: PtrInt;
  19866. begin
  19867. if BufferLen<=0 then
  19868. exit;
  19869. L := PtrInt(Text);
  19870. if L<>0 then
  19871. L := PStrRec(Pointer(L-STRRECSIZE))^.length;
  19872. SetLength(Text,L+BufferLen);
  19873. MoveFast(Buffer^,pointer(PtrInt(Text)+L)^,BufferLen);
  19874. end;
  19875. procedure AppendBuffersToRawUTF8(var Text: RawUTF8; const Buffers: array of PUTF8Char);
  19876. var i,len,TextLen: integer;
  19877. lens: array[0..63] of integer;
  19878. P: PUTF8Char;
  19879. begin
  19880. if high(Buffers)>high(lens) then
  19881. raise ESynException.Create('Too many params in AppendBuffersToRawUTF8()');
  19882. len := 0;
  19883. for i := 0 to high(Buffers) do begin
  19884. lens[i] := StrLen(Buffers[i]);
  19885. inc(len,lens[i]);
  19886. end;
  19887. TextLen := Length(Text);
  19888. SetLength(Text,TextLen+len);
  19889. P := pointer(Text);
  19890. inc(P,TextLen);
  19891. for i := 0 to high(Buffers) do
  19892. if Buffers[i]<>nil then begin
  19893. MoveFast(Buffers[i]^,P^,lens[i]);
  19894. inc(P,lens[i]);
  19895. end;
  19896. end;
  19897. function AppendRawUTF8ToBuffer(Buffer: PUTF8Char; const Text: RawUTF8): PUTF8Char;
  19898. var L: PtrInt;
  19899. begin
  19900. L := PtrInt(Text);
  19901. if L<>0 then begin
  19902. L := PStrRec(Pointer(L-STRRECSIZE))^.length;
  19903. MoveFast(Pointer(Text)^,Buffer^,L);
  19904. inc(Buffer,L);
  19905. end;
  19906. result := Buffer;
  19907. end;
  19908. function QuotedStr(const S: RawUTF8; Quote: AnsiChar): RawUTF8;
  19909. begin
  19910. QuotedStr(Pointer(S),Quote,result);
  19911. end;
  19912. function QuotedStr(Text: PUTF8Char; Quote: AnsiChar): RawUTF8;
  19913. begin
  19914. QuotedStr(Text,Quote,result);
  19915. end;
  19916. procedure QuotedStr(Text: PUTF8Char; Quote: AnsiChar; var result: RawUTF8);
  19917. var n, L, first: integer;
  19918. P: PUTF8Char;
  19919. label quot;
  19920. begin
  19921. n := 0;
  19922. L := 0;
  19923. first := n;
  19924. if Text<>nil then begin
  19925. P := Text;
  19926. repeat
  19927. if P[L]=#0 then
  19928. break else
  19929. if P[L]<>Quote then begin
  19930. inc(L);
  19931. continue;
  19932. end;
  19933. first := L;
  19934. inc(L);
  19935. inc(n);
  19936. repeat
  19937. if P[L]=#0 then
  19938. break else
  19939. if P[L]<>Quote then begin
  19940. inc(L);
  19941. continue;
  19942. end;
  19943. inc(L);
  19944. inc(n);
  19945. until false;
  19946. break;
  19947. until false;
  19948. end;
  19949. FastNewRawUTF8(result,L+n+2);
  19950. P := pointer(Result);
  19951. P^ := Quote;
  19952. inc(P);
  19953. if n=0 then begin
  19954. MoveFast(Text^,P^,L);
  19955. inc(P,L);
  19956. end else begin
  19957. MoveFast(Text^,P^,first);
  19958. n := first;
  19959. L := first;
  19960. goto quot;
  19961. repeat
  19962. if Text[L]=#0 then
  19963. break else
  19964. if Text[L]<>Quote then begin
  19965. P[n] := Text[L];
  19966. inc(L);
  19967. inc(n);
  19968. end else begin
  19969. quot: PWord(P+n)^ := ord(Quote)+ord(Quote) shl 8;
  19970. inc(L);
  19971. inc(n,2);
  19972. end;
  19973. until false;
  19974. inc(P,n);
  19975. end;
  19976. P^ := Quote;
  19977. //Assert(P-pointer(Result)+1=length(result));
  19978. end;
  19979. function GotoEndOfQuotedString(P: PUTF8Char): PUTF8Char;
  19980. var quote: AnsiChar;
  19981. begin // P^='"' at function call
  19982. quote := P^;
  19983. inc(P);
  19984. repeat
  19985. if P^=#0 then
  19986. break else
  19987. if P^<>quote then
  19988. inc(P) else
  19989. if P[1]=quote then // allow double quotes inside string
  19990. inc(P,2) else
  19991. break; // end quote
  19992. until false;
  19993. result := P;
  19994. end; // P^='"' at function return
  19995. procedure QuotedStrJSON(const aText: RawUTF8; var result: RawUTF8);
  19996. var i: integer;
  19997. begin
  19998. for i := 1 to length(aText) do
  19999. case aText[i] of
  20000. #0..#31,'\','"':
  20001. with TTextWriter.CreateOwnedStream do
  20002. try
  20003. Add('"');
  20004. AddJSONEscape(pointer(aText));
  20005. Add('"');
  20006. SetText(result);
  20007. exit;
  20008. finally
  20009. Free;
  20010. end;
  20011. end;
  20012. // if we reached here, no character needs to be escaped in this string
  20013. result := '"'+aText+'"';
  20014. end;
  20015. function GotoEndOfJSONString(P: PUTF8Char): PUTF8Char;
  20016. begin // P^='"' at function call
  20017. inc(P);
  20018. repeat
  20019. if P^=#0 then
  20020. break else
  20021. if P^<>'\' then
  20022. if P^<>'"' then // ignore \"
  20023. inc(P) else
  20024. break else // found ending "
  20025. if P[1]=#0 then // avoid potential buffer overflow issue for \#0
  20026. break else
  20027. inc(P,2); // ignore \?
  20028. until false;
  20029. result := P;
  20030. end; // P^='"' at function return
  20031. function GotoNextNotSpace(P: PUTF8Char): PUTF8Char;
  20032. begin
  20033. if P^ in [#1..' '] then
  20034. repeat
  20035. inc(P)
  20036. until not(P^ in [#1..' ']);
  20037. result := P;
  20038. end;
  20039. function NextNotSpaceCharIs(var P: PUTF8Char; ch: AnsiChar): boolean;
  20040. begin
  20041. if P^ in [#1..' '] then
  20042. repeat
  20043. inc(P)
  20044. until not(P^ in [#1..' ']);
  20045. if P^=ch then begin
  20046. inc(P);
  20047. result := true;
  20048. end else
  20049. result := false;
  20050. end;
  20051. function UnQuoteSQLStringVar(P: PUTF8Char; out Value: RawUTF8): PUTF8Char;
  20052. var quote: AnsiChar;
  20053. PBeg, PS: PUTF8Char;
  20054. n: PtrInt;
  20055. begin
  20056. if P=nil then begin
  20057. result := nil;
  20058. exit;
  20059. end;
  20060. quote := P^;
  20061. inc(P);
  20062. // compute unquoted string length
  20063. PBeg := P;
  20064. n := 0;
  20065. repeat
  20066. if P^=#0 then
  20067. break;
  20068. if P^<>quote then
  20069. inc(P) else
  20070. if P[1]=quote then begin
  20071. inc(P,2); // allow double quotes inside string
  20072. inc(n);
  20073. end else
  20074. break; // end quote
  20075. until false;
  20076. if P^=#0 then begin
  20077. result := nil; // end of string before end quote -> incorrect
  20078. exit;
  20079. end;
  20080. // create unquoted string
  20081. if n=0 then
  20082. // no quote within
  20083. SetRawUTF8(Value,PAnsiChar(PBeg),P-PBeg) else begin
  20084. // unescape internal quotes
  20085. SetLength(Value,P-PBeg-n);
  20086. P := PBeg;
  20087. PS := Pointer(Value);
  20088. repeat
  20089. if P^=quote then
  20090. if P[1]=quote then
  20091. inc(P) else // allow double quotes inside string
  20092. break; // end quote
  20093. PS^ := P^;
  20094. inc(PS);
  20095. inc(P);
  20096. until false;
  20097. end;
  20098. result := P+1;
  20099. end;
  20100. function UnQuoteSQLString(const Value: RawUTF8): RawUTF8;
  20101. begin
  20102. UnQuoteSQLStringVar(pointer(Value),result);
  20103. end;
  20104. function UnQuotedSQLSymbolName(const ExternalDBSymbol: RawUTF8): RawUTF8;
  20105. begin
  20106. if (ExternalDBSymbol<>'') and
  20107. (ExternalDBSymbol[1] in ['[','"','''','(']) then // e.g. for ZDBC's GetFields()
  20108. result := copy(ExternalDBSymbol,2,length(ExternalDBSymbol)-2) else
  20109. result := ExternalDBSymbol;
  20110. end;
  20111. function isSelect(P: PUTF8Char; SelectClause: PRawUTF8): boolean;
  20112. var from: PUTF8Char;
  20113. begin
  20114. if P<>nil then begin
  20115. P := SQLBegin(P);
  20116. if IdemPChar(P,'SELECT ') then begin
  20117. if SelectClause<>nil then begin
  20118. inc(P,7);
  20119. from := StrPosI(' FROM ',P);
  20120. if from=nil then
  20121. SelectClause^ := '' else
  20122. SetString(SelectClause^,PAnsiChar(P),from-P);
  20123. end;
  20124. result := true;
  20125. end else
  20126. result := IdemPChar(P,'EXPLAIN ') or
  20127. ((IdemPChar(P,'VACUUM') or IdemPChar(P,'PRAGMA')) and (P[6] in [#0..' ',';'])) or
  20128. (((IdemPChar(P,'WITH') ) and (P[4] in [#0..' ',';'])) and
  20129. not (ContainsUTF8(P,'INSERT') or ContainsUTF8(P,'UPDATE') or
  20130. ContainsUTF8(P,'DELETE')));
  20131. end else
  20132. result := true; // assume '' statement is SELECT command
  20133. end;
  20134. function SQLBegin(P: PUTF8Char): PUTF8Char;
  20135. begin
  20136. if P<>nil then
  20137. repeat
  20138. if P^<=' ' then // ignore blanks
  20139. repeat
  20140. if P^=#0 then
  20141. break else
  20142. inc(P)
  20143. until P^>' ';
  20144. if PWord(P)^=ord('-')+ord('-')shl 8 then // SQL comments
  20145. repeat
  20146. inc(P)
  20147. until P^ in [#0,#10]
  20148. else
  20149. if PWord(P)^=ord('/')+ord('*')shl 8 then begin // C comments
  20150. inc(P);
  20151. repeat
  20152. inc(P);
  20153. if PWord(P)^=ord('*')+ord('/')shl 8 then begin
  20154. inc(P,2);
  20155. break;
  20156. end;
  20157. until P^=#0;
  20158. end
  20159. else break;
  20160. until false;
  20161. result := P;
  20162. end;
  20163. procedure SQLAddWhereAnd(var where: RawUTF8; const condition: RawUTF8);
  20164. begin
  20165. if where='' then
  20166. where := condition else
  20167. where := where+' and '+condition;
  20168. end;
  20169. procedure Base64MagicDecode(var ParamValue: RawUTF8);
  20170. begin // '\uFFF0base64encodedbinary' decode into binary (input shall have been checked)
  20171. ParamValue := Base64ToBin(PAnsiChar(pointer(ParamValue))+3,length(ParamValue)-3);
  20172. end;
  20173. function Base64MagicCheckAndDecode(Value: PUTF8Char; var Blob: RawByteString): boolean;
  20174. var ValueLen: integer;
  20175. begin // '\uFFF0base64encodedbinary' checked and decode into binary
  20176. if (Value=nil) or (Value[0]=#0) or (Value[1]=#0) or (Value[2]=#0) or
  20177. (PCardinal(Value)^ and $ffffff<>JSON_BASE64_MAGIC) then
  20178. result := false else begin
  20179. ValueLen := StrLen(Value)-3;
  20180. if ValueLen>0 then begin
  20181. Blob := Base64ToBin(PAnsiChar(Value)+3,ValueLen);
  20182. result := true;
  20183. end else
  20184. result := false;
  20185. end;
  20186. end;
  20187. function Base64MagicCheckAndDecode(Value: PUTF8Char; var Blob: TSynTempBuffer): boolean;
  20188. var ValueLen: integer;
  20189. begin // '\uFFF0base64encodedbinary' checked and decode into binary
  20190. if (Value=nil) or (Value[0]=#0) or (Value[1]=#0) or (Value[2]=#0) or
  20191. (PCardinal(Value)^ and $ffffff<>JSON_BASE64_MAGIC) then
  20192. result := false else begin
  20193. ValueLen := StrLen(Value)-3;
  20194. if ValueLen>0 then begin
  20195. Base64ToBin(PAnsiChar(Value)+3,ValueLen,Blob);
  20196. result := true;
  20197. end else
  20198. result := false;
  20199. end;
  20200. end;
  20201. function Base64MagicCheckAndDecode(Value: PUTF8Char; ValueLen: integer;
  20202. var Blob: RawByteString): boolean;
  20203. begin // '\uFFF0base64encodedbinary' checked and decode into binary
  20204. if (ValueLen<4) or (PCardinal(Value)^ and $ffffff<>JSON_BASE64_MAGIC) then
  20205. result := false else begin
  20206. Blob := Base64ToBin(PAnsiChar(Value)+3,ValueLen-3);
  20207. result := true;
  20208. end;
  20209. end;
  20210. const
  20211. NULL_LOW = ord('n')+ord('u')shl 8+ord('l')shl 16+ord('l')shl 24;
  20212. FALSE_LOW = ord('f')+ord('a')shl 8+ord('l')shl 16+ord('s')shl 24;
  20213. TRUE_LOW = ord('t')+ord('r')shl 8+ord('u')shl 16+ord('e')shl 24;
  20214. NULL_UPP = ord('N')+ord('U')shl 8+ord('L')shl 16+ord('L')shl 24;
  20215. EndOfJSONValueField = [#0,#9,#10,#13,' ',',','}',']'];
  20216. EndOfJSONField = [',',']','}',':'];
  20217. DigitChars = ['-','+','0'..'9'];
  20218. DigitFirstChars = ['-','1'..'9']; // 0/- excluded by JSON!
  20219. DigitFloatChars = ['-','+','0'..'9','.','E','e'];
  20220. function SQLParamContent(P: PUTF8Char; out ParamType: TSQLParamType; out ParamValue: RawUTF8;
  20221. out wasNull: boolean): PUTF8Char;
  20222. var PBeg: PAnsiChar;
  20223. L: integer;
  20224. c: cardinal;
  20225. begin
  20226. ParamType := sptUnknown;
  20227. wasNull := false;
  20228. result := nil;
  20229. if P=nil then
  20230. exit;
  20231. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  20232. case P^ of
  20233. '''','"': begin
  20234. P := UnQuoteSQLStringVar(P,ParamValue);
  20235. if P=nil then
  20236. exit; // not a valid quoted string (e.g. unexpected end in middle of it)
  20237. ParamType := sptText;
  20238. L := length(ParamValue)-3;
  20239. if L>0 then begin
  20240. c := PInteger(ParamValue)^ and $00ffffff;
  20241. if c=JSON_BASE64_MAGIC then begin
  20242. // ':("\uFFF0base64encodedbinary"):' format -> decode
  20243. Base64MagicDecode(ParamValue); // wrapper function to avoid temp. string
  20244. ParamType := sptBlob;
  20245. end else
  20246. if (c=JSON_SQLDATE_MAGIC) and // handle ':("\uFFF112012-05-04"):' format
  20247. IsIso8601(PUTF8Char(pointer(ParamValue))+3,L) then begin
  20248. ParamValue := copy(ParamValue,4,L); // return ISO-8601 text
  20249. ParamType := sptDateTime; // identified as Date/Time
  20250. end;
  20251. end;
  20252. end;
  20253. '-','+','0'..'9': begin // allow 0 or + in SQL
  20254. // check if P^ is a true numerical value
  20255. PBeg := pointer(P);
  20256. ParamType := sptInteger;
  20257. repeat inc(P) until not (P^ in ['0'..'9']); // check digits
  20258. if P^='.' then begin
  20259. inc(P);
  20260. if P^ in ['0'..'9'] then begin
  20261. ParamType := sptFloat;
  20262. repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits
  20263. end else begin
  20264. ParamType := sptUnknown; // invalid '23023.' value
  20265. exit;
  20266. end;
  20267. end;
  20268. if byte(P^) and $DF=ord('E') then begin
  20269. ParamType := sptFloat;
  20270. inc(P);
  20271. if P^='+' then inc(P) else
  20272. if P^='-' then inc(P);
  20273. while P^ in ['0'..'9'] do inc(P);
  20274. end;
  20275. SetRawUTF8(ParamValue,PBeg,P-PBeg);
  20276. end;
  20277. 'n':
  20278. if PInteger(P)^=NULL_LOW then begin
  20279. inc(P,4);
  20280. wasNull := true;
  20281. end else
  20282. exit; // invalid content (only :(null): expected)
  20283. else
  20284. exit; // invalid content
  20285. end;
  20286. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  20287. if PWord(P)^<>Ord(')')+Ord(':')shl 8 then
  20288. // we expect finishing with P^ pointing at '):'
  20289. ParamType := sptUnknown else
  20290. // result<>nil only if value content in P^
  20291. result := P+2;
  20292. end;
  20293. function ExtractInlineParameters(const SQL: RawUTF8;
  20294. var Types: TSQLParamTypeDynArray; var Values: TRawUTF8DynArray;
  20295. var maxParam: integer; var Nulls: TSQLFieldBits): RawUTF8;
  20296. var ppBeg: integer;
  20297. P, Gen: PUTF8Char;
  20298. wasNull: boolean;
  20299. begin
  20300. maxParam := 0;
  20301. FillcharFast(Nulls,sizeof(Nulls),0);
  20302. ppBeg := PosEx(RawUTF8(':('),SQL,1);
  20303. if (ppBeg=0) or (PosEx(RawUTF8('):'),SQL,ppBeg+2)=0) then begin
  20304. // SQL code with no valid :(...): internal parameters -> leave maxParam=0
  20305. result := SQL;
  20306. exit;
  20307. end;
  20308. SetString(result,PAnsiChar(pointer(SQL)),length(SQL));
  20309. // compute GenericSQL from SQL, converting :(...): into ?
  20310. Gen := PUTF8Char(pointer(result))+ppBeg-1; // Gen^ just before :(
  20311. P := PUTF8Char(pointer(SQL))+ppBeg+1; // P^ just after :(
  20312. repeat
  20313. Gen^ := '?'; // replace :(...): by ?
  20314. inc(Gen);
  20315. if length(Values)<=maxParam then
  20316. SetLength(Values,maxParam+16);
  20317. if length(Types)<=maxParam then
  20318. SetLength(Types,maxParam+64);
  20319. P := SQLParamContent(P,Types[maxParam],Values[maxParam],wasNull);
  20320. if P=nil then begin
  20321. maxParam := 0;
  20322. result := SQL;
  20323. exit; // any invalid parameter -> try direct SQL
  20324. end;
  20325. if wasNull then
  20326. include(Nulls,maxParam);
  20327. while (P^<>#0) and (PWord(P)^<>Ord(':')+Ord('(')shl 8) do begin
  20328. Gen^ := P^;
  20329. inc(Gen);
  20330. inc(P);
  20331. end;
  20332. if P^=#0 then
  20333. Break;
  20334. inc(P,2);
  20335. inc(maxParam);
  20336. until false;
  20337. // return the correct SQL statement, with params in Values[]
  20338. SetLength(result,Gen-pointer(result));
  20339. inc(maxParam);
  20340. end;
  20341. {$ifndef DEFINED_INT32TOUTF8}
  20342. function Int32ToUTF8(Value : integer): RawUTF8; // faster than SysUtils.IntToStr
  20343. var tmp: array[0..15] of AnsiChar;
  20344. P: PAnsiChar;
  20345. begin
  20346. P := StrInt32(@tmp[15],Value);
  20347. SetString(result,P,@tmp[15]-P);
  20348. end;
  20349. function Int64ToUtf8(Value: Int64): RawUTF8; // faster than SysUtils.IntToStr
  20350. var tmp: array[0..23] of AnsiChar;
  20351. P: PAnsiChar;
  20352. begin
  20353. P := StrInt64(@tmp[23],Value);
  20354. SetString(result,P,@tmp[23]-P);
  20355. end;
  20356. function Trim(const S: RawUTF8): RawUTF8;
  20357. var I,L: Integer;
  20358. begin
  20359. L := Length(S);
  20360. I := 1;
  20361. while (I<=L) and (S[I]<=' ') do inc(I);
  20362. if I>L then
  20363. result := '' else
  20364. if (I=1) and (S[L]>' ') then
  20365. result := S else begin
  20366. while S[L]<=' ' do dec(L);
  20367. result := Copy(S,I,L-I+1);
  20368. end;
  20369. end;
  20370. {$endif}
  20371. {$ifndef CPU64} // already implemented by ToUTF8(Value: PtrInt) below
  20372. function ToUTF8(Value: Int64): RawUTF8;
  20373. var tmp: array[0..23] of AnsiChar;
  20374. P: PAnsiChar;
  20375. begin
  20376. P := StrInt64(@tmp[23],Value);
  20377. SetString(result,P,@tmp[23]-P);
  20378. end;
  20379. {$endif}
  20380. function ToUTF8(Value: PtrInt): RawUTF8;
  20381. var tmp: array[0..15] of AnsiChar;
  20382. P: PAnsiChar;
  20383. begin
  20384. P := StrInt32(@tmp[15],Value);
  20385. SetString(result,P,@tmp[15]-P);
  20386. end;
  20387. function UInt32ToUTF8(Value: Cardinal): RawUTF8;
  20388. var tmp: array[0..15] of AnsiChar;
  20389. P: PAnsiChar;
  20390. begin
  20391. P := StrUInt32(@tmp[15],Value);
  20392. SetString(result,P,@tmp[15]-P);
  20393. end;
  20394. procedure UInt32ToUtf8(Value: cardinal; var result: RawUTF8);
  20395. var tmp: array[0..15] of AnsiChar;
  20396. P: PAnsiChar;
  20397. begin
  20398. P := StrUInt32(@tmp[15],Value);
  20399. SetRawUTF8(result,P,@tmp[15]-P);
  20400. end;
  20401. {$ifndef EXTENDEDTOSTRING_USESTR}
  20402. var // standard FormatSettings (US)
  20403. SettingsUS: TFormatSettings;
  20404. {$endif}
  20405. function ExtendedToString(var S: ShortString; Value: TSynExtended;
  20406. Precision: integer): integer;
  20407. {$ifdef EXTENDEDTOSTRING_USESTR}
  20408. var i,prec: integer;
  20409. begin
  20410. str(Value:0:Precision,S); // not str(Value:0,S) -> ' 0.0E+0000'
  20411. // using str() here avoid FloatToStrF() usage -> LVCL is enough
  20412. result := length(S);
  20413. prec := result; // if no decimal
  20414. if S[1]='-' then
  20415. dec(prec);
  20416. for i := 2 to result do // test if scientific format -> return as this
  20417. case S[i] of
  20418. 'E': exit; // pos('E',S)>0; which Delphi 2009+ don't like
  20419. '.': dec(prec);
  20420. end;
  20421. if (prec>=Precision) and (prec<>result) then begin
  20422. dec(result,prec-Precision);
  20423. if S[result+1]>'5' then begin // manual rounding
  20424. prec := result;
  20425. repeat
  20426. case S[prec] of
  20427. '.': ; // just ignore decimal separator
  20428. '0'..'8': begin
  20429. inc(S[prec]);
  20430. break;
  20431. end;
  20432. '9': begin
  20433. S[prec] := '0';
  20434. if ((prec=2) and (S[1]='-')) or (prec=1) then begin
  20435. MoveFast(S[prec],S[prec+1],result);
  20436. S[prec] := '1';
  20437. break;
  20438. end;
  20439. end;
  20440. else break;
  20441. end;
  20442. dec(prec);
  20443. until prec=0;
  20444. end; // note: this fixes http://stackoverflow.com/questions/2335162
  20445. end;
  20446. while S[result]='0' do begin
  20447. dec(result); // trunc any trimming 0
  20448. if S[result]='.' then begin
  20449. dec(result);
  20450. if (result=2) and (S[1]='-') and (S[2]='0') then begin
  20451. result := 1;
  20452. S[1] := '0'; // '-0.000' -> '0'
  20453. end;
  20454. break; // decimal were all '0' -> return only integer part
  20455. end;
  20456. end;
  20457. {$else}
  20458. {$ifdef UNICODE}
  20459. var i: integer;
  20460. {$endif}
  20461. begin
  20462. // use ffGeneral: see http://synopse.info/forum/viewtopic.php?pid=442#p442
  20463. result := FloatToText(PChar(@S[1]), Value, fvExtended, ffGeneral,
  20464. Precision, 0, SettingsUS);
  20465. {$ifdef UNICODE} // FloatToText(PWideChar) is faster than FloatToText(PAnsiChar)
  20466. for i := 1 to result do
  20467. PByteArray(@S)[i] := PWordArray(PtrInt(@S)-1)[i];
  20468. {$endif}
  20469. {$endif EXTENDEDTOSTRING_USESTR}
  20470. end;
  20471. function ExtendedToStr(Value: TSynExtended; Precision: integer): RawUTF8;
  20472. var tmp: ShortString;
  20473. begin
  20474. if Value=0 then
  20475. result := '0' else
  20476. SetRawUTF8(result,@tmp[1],ExtendedToString(tmp,Value,Precision));
  20477. end;
  20478. procedure ExtendedToStr(Value: TSynExtended; Precision: integer;
  20479. var result: RawUTF8);
  20480. var tmp: ShortString;
  20481. begin
  20482. if Value=0 then
  20483. result := '0' else
  20484. SetRawUTF8(result,@tmp[1],ExtendedToString(tmp,Value,Precision));
  20485. end;
  20486. function DoubleToStr(Value: Double): RawUTF8;
  20487. var tmp: ShortString;
  20488. begin
  20489. if Value=0 then
  20490. result := '0' else
  20491. SetRawUTF8(result,@tmp[1],ExtendedToString(tmp,Value,DOUBLE_PRECISION));
  20492. end;
  20493. function FormatUTF8(const Format: RawUTF8; const Args: array of const): RawUTF8;
  20494. begin
  20495. FormatUTF8(Format,Args,result);
  20496. end;
  20497. procedure FormatUTF8(const Format: RawUTF8; const Args: array of const;
  20498. var result: RawUTF8);
  20499. // only supported token is %, with any const arguments
  20500. var i, blocksN, L, argN: PtrInt;
  20501. tmpStr: TRawUTF8DynArray;
  20502. F,FDeb: PUTF8Char;
  20503. blocks: array[0..49] of TTempUTF8;
  20504. begin
  20505. if (Format='') or (high(Args)<0) then begin
  20506. result := Format; // no formatting to process
  20507. exit;
  20508. end;
  20509. if Format='%' then begin
  20510. VarRecToUTF8(Args[0],result); // optimize raw conversion
  20511. exit;
  20512. end;
  20513. result := '';
  20514. if length(Args)*2+1>high(blocks) then
  20515. raise ESynException.Create('FormatUTF8: too many args (max=25)!');
  20516. SetLength(tmpStr,length(Args));
  20517. blocksN := 0;
  20518. argN := 0;
  20519. L := 0;
  20520. F := pointer(Format);
  20521. while F^<>#0 do begin
  20522. if F^<>'%' then begin
  20523. FDeb := F;
  20524. while (F^<>'%') and (F^<>#0) do inc(F);
  20525. with blocks[blocksN] do begin
  20526. Text := FDeb;
  20527. Len := F-FDeb;
  20528. inc(L,Len);
  20529. inc(blocksN);
  20530. end;
  20531. end;
  20532. if F^=#0 then break;
  20533. inc(F); // jump '%'
  20534. if argN<=high(Args) then begin
  20535. inc(L,VarRecToTempUTF8(Args[argN],tmpStr[argN],blocks[blocksN]));
  20536. inc(blocksN);
  20537. inc(argN);
  20538. end else
  20539. if F^<>#0 then begin // no more available Args -> add all remaining text
  20540. with blocks[blocksN] do begin
  20541. Text := F;
  20542. Len := StrLen(F);
  20543. inc(L,Len);
  20544. inc(blocksN);
  20545. end;
  20546. break;
  20547. end;
  20548. end;
  20549. if L=0 then
  20550. exit;
  20551. SetLength(result,L);
  20552. F := pointer(result);
  20553. for i := 0 to blocksN-1 do begin
  20554. MoveFast(blocks[i].Text^,F^,blocks[i].Len);
  20555. inc(F,blocks[i].Len);
  20556. end;
  20557. end;
  20558. function FormatUTF8(const Format: RawUTF8; const Args, Params: array of const; JSONFormat: boolean): RawUTF8; overload;
  20559. var i, tmpN, L, A, P, len: PtrInt;
  20560. isParam: AnsiChar;
  20561. tmp: TRawUTF8DynArray;
  20562. inlin: set of 0..255;
  20563. F,FDeb: PUTF8Char;
  20564. wasString: Boolean;
  20565. const QUOTECHAR: array[boolean] of AnsiChar = ('''','"');
  20566. NOTTOQUOTE: array[boolean] of set of 0..31 = (
  20567. [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended],
  20568. [vtBoolean,vtInteger,vtInt64,vtCurrency,vtExtended,vtVariant]);
  20569. label Txt;
  20570. begin
  20571. if Format='' then begin
  20572. result := '';
  20573. exit;
  20574. end;
  20575. if (high(Args)<0) and (high(Params)<0) then begin
  20576. // no formatting to process, but may be a const -> make unique
  20577. SetString(result,PAnsiChar(pointer(Format)),length(Format));
  20578. exit; // e.g. _JsonFmt() will parse it in-place
  20579. end;
  20580. if high(Params)<0 then begin
  20581. FormatUTF8(Format,Args,result); // slightly faster overloaded function
  20582. exit;
  20583. end;
  20584. if Format='%' then begin
  20585. VarRecToUTF8(Args[0],result); // optimize raw conversion
  20586. exit;
  20587. end;
  20588. result := '';
  20589. tmpN := 0;
  20590. FillcharFast(inlin,SizeOf(inlin),0);
  20591. L := 0;
  20592. A := 0;
  20593. P := 0;
  20594. F := pointer(Format);
  20595. {$ifdef FPC}
  20596. try // alf: to circumvent FPC issues
  20597. {$endif}
  20598. while F^<>#0 do begin
  20599. if F^<>'%' then begin
  20600. FDeb := F;
  20601. while not (F^ in [#0,'%','?']) do inc(F);
  20602. Txt: len := F-FDeb;
  20603. if len>0 then begin
  20604. inc(L,len);
  20605. if tmpN=length(tmp) then
  20606. SetLength(tmp,tmpN+8);
  20607. SetString(tmp[tmpN],FDeb,len); // add inbetween text
  20608. inc(tmpN);
  20609. end;
  20610. end;
  20611. if F^=#0 then
  20612. break;
  20613. isParam := F^;
  20614. inc(F); // jump '%' or '?'
  20615. if (isParam='%') and (A<=high(Args)) then begin // handle % substitution
  20616. if tmpN=length(tmp) then
  20617. SetLength(tmp,tmpN+8);
  20618. VarRecToUTF8(Args[A],tmp[tmpN]);
  20619. inc(A);
  20620. if tmp[tmpN]<>'' then begin
  20621. inc(L,length(tmp[tmpN]));
  20622. inc(tmpN);
  20623. end;
  20624. end else
  20625. if (isParam='?') and (P<=high(Params)) then begin // handle ? substitution
  20626. if tmpN=length(tmp) then
  20627. SetLength(tmp,tmpN+8);
  20628. {$ifndef NOVARIANTS}
  20629. if JSONFormat and (Params[P].VType=vtVariant) then
  20630. VariantSaveJSON(Params[P].VVariant^,twJSONEscape,tmp[tmpN]) else
  20631. {$endif}
  20632. begin
  20633. VarRecToUTF8(Params[P],tmp[tmpN]);
  20634. wasString := not (Params[P].VType in NOTTOQUOTE[JSONFormat]);
  20635. if wasString then
  20636. if JSONFormat then
  20637. QuotedStrJSON(tmp[tmpN],tmp[tmpN]) else
  20638. tmp[tmpN] := QuotedStr(pointer(tmp[tmpN]),'''');
  20639. if not JSONFormat then begin
  20640. inc(L,4); // space for :():
  20641. include(inlin,tmpN);
  20642. end;
  20643. end;
  20644. inc(P);
  20645. inc(L,length(tmp[tmpN]));
  20646. inc(tmpN);
  20647. end else
  20648. if F^<>#0 then begin // no more available Args -> add all remaining text
  20649. FDeb := F;
  20650. repeat inc(F) until (F^=#0);
  20651. goto Txt;
  20652. end;
  20653. end;
  20654. if L=0 then
  20655. exit;
  20656. if (not JSONFormat) and (tmpN>SizeOf(inlin)shl 3) then
  20657. raise ESynException.CreateUTF8(
  20658. 'Too many parameters for FormatUTF8(): %>%',[tmpN,SizeOf(inlin)shl 3]);
  20659. SetLength(result,L);
  20660. F := pointer(result);
  20661. for i := 0 to tmpN-1 do
  20662. if tmp[i]<>'' then begin
  20663. if i in inlin then begin
  20664. PWord(F)^ := ord(':')+ord('(')shl 8;
  20665. inc(F,2);
  20666. end;
  20667. {$ifdef FPC}
  20668. L := PStrRec(Pointer(PtrInt(tmp[i])-STRRECSIZE))^.length;
  20669. {$else}
  20670. L := PInteger(PtrInt(tmp[i])-sizeof(integer))^;
  20671. {$endif}
  20672. MoveFast(pointer(tmp[i])^,F^,L);
  20673. inc(F,L);
  20674. if i in inlin then begin
  20675. PWord(F)^ := ord(')')+ord(':')shl 8;
  20676. inc(F,2);
  20677. end;
  20678. end;
  20679. {$ifdef FPC}
  20680. finally
  20681. finalize(tmp);
  20682. end;
  20683. {$endif}
  20684. end;
  20685. function RawByteStringArrayConcat(const Values: array of RawByteString): RawByteString;
  20686. var i, L: integer;
  20687. P: PAnsiChar;
  20688. begin
  20689. L := 0;
  20690. for i := 0 to high(Values) do
  20691. inc(L,length(Values[i]));
  20692. SetString(Result,nil,L);
  20693. P := pointer(Result);
  20694. for i := 0 to high(Values) do begin
  20695. L := length(Values[i]);
  20696. MoveFast(pointer(Values[i])^,P^,L);
  20697. inc(P,L);
  20698. end;
  20699. end;
  20700. procedure RawByteStringToBytes(const buf: RawByteString; out bytes: TBytes);
  20701. var L: Integer;
  20702. begin
  20703. L := Length(buf);
  20704. if L<>0 then begin
  20705. SetLength(bytes,L);
  20706. MoveFast(pointer(buf)^,pointer(bytes)^,L);
  20707. end;
  20708. end;
  20709. procedure BytesToRawByteString(const bytes: TBytes; out buf: RawByteString);
  20710. begin
  20711. SetString(buf,PAnsiChar(pointer(bytes)),Length(bytes));
  20712. end;
  20713. procedure ResourceToRawByteString(const ResName: string; ResType: PChar;
  20714. out buf: RawByteString);
  20715. var HResInfo: THandle;
  20716. HGlobal: THandle;
  20717. begin
  20718. HResInfo := FindResource(HInstance,PChar(ResName),ResType);
  20719. if HResInfo=0 then
  20720. exit;
  20721. HGlobal := LoadResource(HInstance,HResInfo);
  20722. if HGlobal<>0 then
  20723. SetString(buf,PAnsiChar(LockResource(HGlobal)),SizeofResource(HInstance,HResInfo));
  20724. end;
  20725. procedure ResourceSynLZToRawByteString(const ResName: string;
  20726. out buf: RawByteString);
  20727. var HResInfo: THandle;
  20728. HGlobal: THandle;
  20729. begin
  20730. HResInfo := FindResource(HInstance,PChar(ResName),PChar(10));
  20731. if HResInfo=0 then
  20732. exit;
  20733. HGlobal := LoadResource(HInstance,HResInfo);
  20734. if HGlobal<>0 then // direct decompression from memory mapped .exe content
  20735. SynLZDecompress(LockResource(HGlobal),SizeofResource(HInstance,HResInfo),buf);
  20736. end;
  20737. function StrIComp(Str1, Str2: pointer): PtrInt;
  20738. {$ifdef PUREPASCAL}
  20739. var C1, C2: AnsiChar;
  20740. begin
  20741. if Str1<>Str2 then
  20742. if Str1<>nil then
  20743. if Str2<>nil then begin
  20744. repeat
  20745. C1 := PAnsiChar(Str1)^;
  20746. C2 := PAnsiChar(Str2)^;
  20747. if C1 in ['a'..'z'] then dec(C1,32);
  20748. if C2 in ['a'..'z'] then dec(C2,32);
  20749. if (C1<>C2) or (C1=#0) then
  20750. break;
  20751. Inc(PtrUInt(Str1));
  20752. Inc(PtrUInt(Str2));
  20753. until false;
  20754. Result := Ord(C1) - Ord(C2);
  20755. end else
  20756. result := 1 else // Str2=''
  20757. result := -1 else // Str1=''
  20758. result := 0; // Str1=Str2
  20759. end;
  20760. {$else}
  20761. asm // faster version by AB, from Agner Fog's original
  20762. mov ecx,eax
  20763. test eax,edx
  20764. jz @n
  20765. @ok: sub edx,eax
  20766. jz @0
  20767. @10: mov al,[ecx]
  20768. cmp al,[ecx+edx]
  20769. jne @20
  20770. inc ecx
  20771. test al,al
  20772. jnz @10 // continue with next byte
  20773. // terminating zero found. Strings are equal
  20774. @0: xor eax,eax
  20775. ret
  20776. @20: // bytes are different. check case
  20777. xor al,20H // toggle case
  20778. cmp al,[ecx+edx]
  20779. jne @30
  20780. // possibly differing only by case. Check if a-z
  20781. or al,20H // upper case
  20782. sub al,'a'
  20783. cmp al,'z'-'a'
  20784. ja @30 // not a-z
  20785. // a-z and differing only by case
  20786. inc ecx
  20787. jmp @10 // continue with next byte
  20788. @30: // bytes are different,even after changing case
  20789. movzx eax,byte [ecx] // get original value again
  20790. sub eax,'A'
  20791. cmp eax,'Z' - 'A'
  20792. ja @40
  20793. add eax,20H
  20794. @40: movzx edx,byte [ecx+edx]
  20795. sub edx,'A'
  20796. cmp edx,'Z' - 'A'
  20797. ja @50
  20798. add edx,20H
  20799. @50: sub eax,edx // subtract to get result
  20800. ret
  20801. @n: cmp eax,edx
  20802. je @0
  20803. test eax,eax // Str1='' ?
  20804. jz @max
  20805. test edx,edx // Str2='' ?
  20806. jnz @ok
  20807. mov eax,1
  20808. ret
  20809. @max: dec eax
  20810. end;
  20811. {$endif}
  20812. function StrLenW(S: PWideChar): PtrInt;
  20813. begin
  20814. result := 0;
  20815. if S<>nil then
  20816. while true do
  20817. if S[result+0]<>#0 then
  20818. if S[result+1]<>#0 then
  20819. if S[result+2]<>#0 then
  20820. if S[result+3]<>#0 then
  20821. inc(result,4) else begin
  20822. inc(result,3);
  20823. exit;
  20824. end else begin
  20825. inc(result,2);
  20826. exit;
  20827. end else begin
  20828. inc(result);
  20829. exit;
  20830. end else
  20831. exit;
  20832. end;
  20833. function StrCompW(Str1, Str2: PWideChar): PtrInt;
  20834. begin
  20835. if Str1<>Str2 then
  20836. if Str1<>nil then
  20837. if Str2<>nil then begin
  20838. if Str1^=Str2^ then
  20839. repeat
  20840. if (Str1^=#0) or (Str2^=#0) then break;
  20841. inc(Str1);
  20842. inc(Str2);
  20843. until Str1^<>Str2^;
  20844. result := PWord(Str1)^-PWord(Str2)^;
  20845. exit;
  20846. end else
  20847. result := 1 else // Str2=''
  20848. result := -1 else // Str1=''
  20849. result := 0; // Str1=Str2
  20850. end;
  20851. {$ifdef PUREPASCAL}
  20852. function StrLenPas(S: pointer): PtrInt;
  20853. begin
  20854. result := 0;
  20855. if S<>nil then
  20856. while true do
  20857. if PAnsiChar(S)[result+0]<>#0 then
  20858. if PAnsiChar(S)[result+1]<>#0 then
  20859. if PAnsiChar(S)[result+2]<>#0 then
  20860. if PAnsiChar(S)[result+3]<>#0 then
  20861. inc(result,4) else begin
  20862. inc(result,3);
  20863. exit;
  20864. end else begin
  20865. inc(result,2);
  20866. exit;
  20867. end else begin
  20868. inc(result);
  20869. exit;
  20870. end else
  20871. exit;
  20872. end;
  20873. function StrComp(Str1, Str2: pointer): PtrInt;
  20874. begin
  20875. if Str1<>Str2 then
  20876. if Str1<>nil then
  20877. if Str2<>nil then begin
  20878. if PByte(Str1)^=PByte(Str2)^ then
  20879. repeat
  20880. if PByte(Str1)^=0 then break;
  20881. inc(PByte(Str1));
  20882. inc(PByte(Str2));
  20883. until PByte(Str1)^<>PByte(Str2)^;
  20884. result := PByte(Str1)^-PByte(Str2)^;
  20885. exit;
  20886. end else
  20887. result := 1 else // Str2=''
  20888. result := -1 else // Str1=''
  20889. result := 0; // Str1=Str2
  20890. end;
  20891. function StrCompFast(Str1, Str2: pointer): PtrInt;
  20892. begin
  20893. if Str1<>Str2 then
  20894. if Str1<>nil then
  20895. if Str2<>nil then begin
  20896. if PByte(Str1)^=PByte(Str2)^ then
  20897. repeat
  20898. if PByte(Str1)^=0 then break;
  20899. inc(PByte(Str1));
  20900. inc(PByte(Str2));
  20901. until PByte(Str1)^<>PByte(Str2)^;
  20902. result := PByte(Str1)^-PByte(Str2)^;
  20903. exit;
  20904. end else
  20905. result := 1 else // Str2=''
  20906. result := -1 else // Str1=''
  20907. result := 0; // Str1=Str2
  20908. end;
  20909. {$else}
  20910. function StrLenPas(S: pointer): PtrInt;
  20911. asm // slower than x86/SSE* StrLen(), but won't read any byte beyond the string
  20912. test eax,eax
  20913. mov edx,eax
  20914. jz @0
  20915. xor eax,eax
  20916. @s: cmp byte ptr [eax+edx+0],0; je @0
  20917. cmp byte ptr [eax+edx+1],0; je @1
  20918. cmp byte ptr [eax+edx+2],0; je @2
  20919. cmp byte ptr [eax+edx+3],0; je @3
  20920. add eax,4
  20921. jmp @s
  20922. @1: inc eax
  20923. ret
  20924. @0: rep ret
  20925. @2: add eax,2; ret
  20926. @3: add eax,3
  20927. end;
  20928. function StrCompFast(Str1, Str2: pointer): PtrInt;
  20929. asm // no branch taken in case of not equal first char
  20930. cmp eax,edx
  20931. je @zero // same string or both nil
  20932. test eax,edx
  20933. jz @maynil
  20934. @1: mov cl,[eax]
  20935. mov ch,[edx]
  20936. test cl,cl
  20937. lea eax,[eax+1]
  20938. lea edx,[edx+1]
  20939. jz @exit
  20940. cmp cl,ch
  20941. je @1
  20942. @exit: movzx eax,cl
  20943. movzx edx,ch
  20944. sub eax,edx
  20945. ret
  20946. @maynil:test eax,eax // Str1='' ?
  20947. jz @max
  20948. test edx,edx // Str2='' ?
  20949. jnz @1
  20950. mov eax,1
  20951. ret
  20952. @max: dec eax
  20953. ret
  20954. @zero: xor eax,eax
  20955. end;
  20956. const
  20957. EQUAL_EACH = 8; // see https://msdn.microsoft.com/en-us/library/bb531463
  20958. NEGATIVE_POLARITY = 16;
  20959. function StrCompSSE42(Str1, Str2: pointer): PtrInt;
  20960. asm // warning: may read up to 15 bytes beyond the string itself
  20961. test eax,edx
  20962. jz @n
  20963. @ok: sub eax,edx
  20964. jz @0
  20965. {$ifdef HASAESNI}
  20966. movdqu xmm0,dqword [edx]
  20967. pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH+NEGATIVE_POLARITY // result in ecx
  20968. {$else}
  20969. db $F3,$0F,$6F,$02
  20970. db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH+NEGATIVE_POLARITY
  20971. {$endif}
  20972. ja @1
  20973. jc @2
  20974. xor eax,eax
  20975. ret
  20976. @1: add edx,16
  20977. {$ifdef HASAESNI}
  20978. movdqu xmm0,dqword [edx]
  20979. pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH+NEGATIVE_POLARITY // result in ecx
  20980. {$else}
  20981. db $F3,$0F,$6F,$02
  20982. db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH+NEGATIVE_POLARITY
  20983. {$endif}
  20984. ja @1
  20985. jc @2
  20986. @0: xor eax,eax // Str1=Str2
  20987. ret
  20988. @n: cmp eax,edx
  20989. je @0
  20990. test eax,eax // Str1='' ?
  20991. jz @max
  20992. test edx,edx // Str2='' ?
  20993. jnz @ok
  20994. mov eax,1
  20995. ret
  20996. @max: dec eax
  20997. ret
  20998. @2: add eax,edx
  20999. movzx eax,byte ptr [eax+ecx]
  21000. movzx edx,byte ptr [edx+ecx]
  21001. sub eax,edx
  21002. end;
  21003. function SortDynArrayAnsiStringSSE42(const A,B): integer;
  21004. asm // warning: may read up to 15 bytes beyond the string itself
  21005. mov eax,[eax]
  21006. mov edx,[edx]
  21007. test eax,edx
  21008. jz @n
  21009. @ok: sub eax,edx
  21010. jz @0
  21011. {$ifdef HASAESNI}
  21012. movdqu xmm0,dqword [edx]
  21013. pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH+NEGATIVE_POLARITY // result in ecx
  21014. {$else}
  21015. db $F3,$0F,$6F,$02
  21016. db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH+NEGATIVE_POLARITY
  21017. {$endif}
  21018. ja @1
  21019. jc @2
  21020. xor eax,eax
  21021. ret
  21022. @1: add edx,16
  21023. {$ifdef HASAESNI}
  21024. movdqu xmm0,dqword [edx]
  21025. pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH+NEGATIVE_POLARITY // result in ecx
  21026. {$else}
  21027. db $F3,$0F,$6F,$02
  21028. db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH+NEGATIVE_POLARITY
  21029. {$endif}
  21030. ja @1
  21031. jc @2
  21032. @0: xor eax,eax // Str1=Str2
  21033. ret
  21034. @n: cmp eax,edx
  21035. je @0
  21036. test eax,eax // Str1='' ?
  21037. jz @max
  21038. test edx,edx // Str2='' ?
  21039. jnz @ok
  21040. or eax,-1
  21041. ret
  21042. @max: inc eax
  21043. ret
  21044. @2: add eax,edx
  21045. movzx eax,byte ptr [eax+ecx]
  21046. movzx edx,byte ptr [edx+ecx]
  21047. sub eax,edx
  21048. end;
  21049. {$endif PUREPASCAL}
  21050. function IdemPropNameU(const P1,P2: RawUTF8): boolean;
  21051. {$ifdef PUREPASCAL}
  21052. var i,j,L: integer;
  21053. begin
  21054. result := false;
  21055. L := length(P1);
  21056. if L<>length(P2) then
  21057. exit;
  21058. j := 1;
  21059. for i := 1 to L shr 2 do
  21060. if (PCardinal(@P1[j])^ xor PCardinal(@P2[j])^) and $dfdfdfdf<>0 then
  21061. exit else
  21062. inc(j,4);
  21063. for i := j to L do
  21064. if (ord(P1[i]) xor ord(P2[i])) and $df<>0 then
  21065. exit;
  21066. result := true;
  21067. end;
  21068. {$else}
  21069. asm // eax=p1, edx=p2
  21070. cmp eax,edx
  21071. je @out1
  21072. test eax,edx
  21073. jz @maybenil
  21074. @notnil:mov ecx,[eax-4] // compare lengths
  21075. cmp ecx,[edx-4]
  21076. jne @out1
  21077. push ebx
  21078. lea edx,[edx+ecx-4] // may include the length for shortest strings
  21079. lea ebx,[eax+ecx-4]
  21080. neg ecx
  21081. mov eax,[ebx] // compare last 4 chars
  21082. xor eax,[edx]
  21083. and eax,$dfdfdfdf // case insensitive
  21084. jne @out2
  21085. @by4: add ecx,4
  21086. jns @match
  21087. mov eax,[ebx+ecx]
  21088. xor eax,[edx+ecx]
  21089. and eax,$dfdfdfdf // case insensitive
  21090. je @by4
  21091. @out2: pop ebx
  21092. @out1: setz al
  21093. ret
  21094. @match: mov al,1
  21095. pop ebx
  21096. ret
  21097. @maybenil: // here we know that eax<>edx
  21098. test eax,eax
  21099. jz @nil0 // eax=nil and eax<>edx -> edx<>nil -> false
  21100. test edx,edx
  21101. jnz @notnil
  21102. mov al,dl // eax<>nil and edx=nil -> false
  21103. @nil0:
  21104. end;
  21105. {$endif}
  21106. function IdemPropName(const P1,P2: shortstring): boolean; overload;
  21107. begin
  21108. if P1[0]=P2[0] then
  21109. result := IdemPropNameUSameLen(@P1[1],@P2[1],ord(P2[0])) else
  21110. result := false;
  21111. end;
  21112. function IdemPropName(const P1: shortstring; P2: PUTF8Char; P2Len: integer): boolean; overload;
  21113. begin
  21114. if ord(P1[0])=P2Len then
  21115. result := IdemPropNameUSameLen(@P1[1],P2,P2Len) else
  21116. result := false;
  21117. end;
  21118. function IdemPropName(P1,P2: PUTF8Char; P1Len,P2Len: integer): boolean; overload;
  21119. begin
  21120. if P1Len=P2Len then
  21121. result := IdemPropNameUSameLen(P1,P2,P2Len) else
  21122. result := false;
  21123. end;
  21124. function IdemPropNameU(const P1: RawUTF8; P2: PUTF8Char; P2Len: integer): boolean;
  21125. begin
  21126. if length(P1)=P2Len then
  21127. result := IdemPropNameUSameLen(pointer(P1),P2,P2Len) else
  21128. result := false;
  21129. end;
  21130. function IdemPropNameUSameLen(P1,P2: PUTF8Char; P1P2Len: integer): boolean;
  21131. {$ifdef PUREPASCAL}
  21132. var i,j: integer;
  21133. begin
  21134. result := false;
  21135. j := 0;
  21136. for i := 1 to P1P2Len shr 2 do
  21137. if (PCardinal(PtrInt(P1)+j)^ xor PCardinal(@P2[j])^) and $dfdfdfdf<>0 then
  21138. exit else
  21139. inc(j,4);
  21140. for i := j to P1P2Len-1 do
  21141. if (PByteArray(P1)^[i] xor ord(P2[i])) and $df<>0 then
  21142. exit;
  21143. result := true;
  21144. end;
  21145. {$else}
  21146. asm // eax=p1, edx=p2, ecx=P1P2Len
  21147. cmp eax,edx
  21148. je @out2
  21149. cmp ecx,4
  21150. jbe @sml
  21151. push ebx
  21152. lea edx,[edx+ecx-4]
  21153. lea ebx,[eax+ecx-4]
  21154. neg ecx
  21155. mov eax,[ebx] // compare last 4 chars
  21156. xor eax,[edx]
  21157. and eax,$dfdfdfdf // case insensitive
  21158. jne @out1
  21159. @by4: add ecx,4
  21160. jns @match
  21161. mov eax,[ebx+ecx]
  21162. xor eax,[edx+ecx]
  21163. and eax,$dfdfdfdf // case insensitive
  21164. je @by4
  21165. @out1: pop ebx
  21166. @out2: setz al
  21167. ret
  21168. nop; nop
  21169. @match: pop ebx
  21170. mov al,1
  21171. ret
  21172. @mask: dd 0,$df,$dfdf,$dfdfdf,$dfdfdfdf // compare 1..4 chars
  21173. @sml: test ecx,ecx
  21174. jz @smlo // P1P2Len=0
  21175. mov eax,[eax]
  21176. xor eax,[edx]
  21177. and eax,dword ptr [@mask+ecx*4]
  21178. @smlo: setz al
  21179. end;
  21180. {$endif}
  21181. {$ifdef MSWINDOWS}
  21182. const
  21183. // lpMinimumApplicationAddress retrieved from Windows is very low $10000
  21184. // - i.e. maximum number of ID per table would be 65536 in TSQLRecord.GetID
  21185. // - so we'll force an higher and almost "safe" value as 1,048,576
  21186. // (real value from runnning Windows is greater than $400000)
  21187. MIN_PTR_VALUE = $100000;
  21188. // see http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
  21189. VER_NT_WORKSTATION = 1;
  21190. VER_NT_DOMAIN_CONTROLLER = 2;
  21191. VER_NT_SERVER = 3;
  21192. SM_SERVERR2 = 89;
  21193. PROCESSOR_ARCHITECTURE_AMD64 = 9;
  21194. {$ifndef UNICODE}
  21195. function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall;
  21196. external kernel32 name 'GetVersionExA';
  21197. {$endif}
  21198. function GetSystemTimeMillisecondsForXP: Int64; stdcall;
  21199. var fileTime: TFileTime;
  21200. begin
  21201. GetSystemTimeAsFileTime(fileTime); // very fast, with 100 ns unit
  21202. {$ifdef CPU64} // 64 bit XP ? not very likely - but who knows :)
  21203. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284 states:
  21204. // do not cast a pointer to a FILETIME structure to either a int64* value
  21205. // because it can cause alignment faults on 64-bit Windows -> manual compute
  21206. result := fileTime.dwHighDateTime;
  21207. result := (result shl 32)+fileTime.dwLowDateTime;
  21208. result := result div 10000;
  21209. {$else}
  21210. result := trunc(PInt64(@fileTime)^/10000); // 100 ns unit
  21211. {$endif}
  21212. end;
  21213. {$ifdef FPC} // oddly not defined in fpc\rtl\win
  21214. function SwitchToThread: BOOL; stdcall; external kernel32 name 'SwitchToThread';
  21215. {$endif}
  21216. procedure SleepHiRes(ms: cardinal);
  21217. begin
  21218. if (ms<>0) or not SwitchToThread then
  21219. Windows.Sleep(ms);
  21220. end;
  21221. procedure RetrieveSystemInfo;
  21222. var
  21223. IsWow64Process: function(Handle: THandle; var Res: BOOL): BOOL; stdcall;
  21224. GetNativeSystemInfo: procedure(var SystemInfo: TSystemInfo); stdcall;
  21225. Res: BOOL;
  21226. Kernel: THandle;
  21227. P: pointer;
  21228. Vers: TWindowsVersion;
  21229. begin
  21230. Kernel := GetModuleHandle(kernel32);
  21231. GetTickCount64 := GetProcAddress(Kernel,'GetTickCount64');
  21232. if not Assigned(GetTickCount64) then
  21233. GetTickCount64 := @GetSystemTimeMillisecondsForXP;
  21234. IsWow64Process := GetProcAddress(Kernel,'IsWow64Process');
  21235. Res := false;
  21236. IsWow64 := Assigned(IsWow64Process) and
  21237. IsWow64Process(GetCurrentProcess,Res) and Res;
  21238. FillcharFast(SystemInfo,sizeof(SystemInfo),0);
  21239. if IsWow64 then // see http://msdn.microsoft.com/en-us/library/ms724381(v=VS.85).aspx
  21240. GetNativeSystemInfo := GetProcAddress(Kernel,'GetNativeSystemInfo') else
  21241. @GetNativeSystemInfo := nil;
  21242. if Assigned(GetNativeSystemInfo) then
  21243. GetNativeSystemInfo(SystemInfo) else
  21244. Windows.GetSystemInfo(SystemInfo);
  21245. GetMem(P,10); // ensure that using MIN_PTR_VALUE won't break anything
  21246. if (PtrUInt(P)>MIN_PTR_VALUE) and
  21247. (PtrUInt(SystemInfo.lpMinimumApplicationAddress)<=MIN_PTR_VALUE) then
  21248. PtrUInt(SystemInfo.lpMinimumApplicationAddress) := MIN_PTR_VALUE;
  21249. Freemem(P);
  21250. OSVersionInfo.dwOSVersionInfoSize := sizeof(OSVersionInfo);
  21251. GetVersionEx(OSVersionInfo);
  21252. Vers := wUnknown;
  21253. with OSVersionInfo do
  21254. // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833
  21255. case dwMajorVersion of
  21256. 5: case dwMinorVersion of
  21257. 0: Vers := w2000;
  21258. 1: Vers := wXP;
  21259. 2: if (wProductType=VER_NT_WORKSTATION) and
  21260. (SystemInfo.wProcessorArchitecture=PROCESSOR_ARCHITECTURE_AMD64) then
  21261. Vers := wXP_64 else
  21262. if GetSystemMetrics(SM_SERVERR2)=0 then
  21263. Vers := wServer2003 else
  21264. Vers := wServer2003_R2;
  21265. end;
  21266. 6: case dwMinorVersion of
  21267. 0: Vers := wVista;
  21268. 1: Vers := wSeven;
  21269. 2: Vers := wEight;
  21270. 3: Vers := wEightOne;
  21271. 4: Vers := wTen;
  21272. end;
  21273. 10: Vers := wTen;
  21274. end;
  21275. if Vers>=wVista then begin
  21276. if OSVersionInfo.wProductType<>VER_NT_WORKSTATION then
  21277. inc(Vers,2); // e.g. wEight -> wServer2012
  21278. if SystemInfo.wProcessorArchitecture=PROCESSOR_ARCHITECTURE_AMD64 then
  21279. inc(Vers); // e.g. wEight -> wEight64
  21280. end;
  21281. OSVersion := Vers;
  21282. with OSVersionInfo do
  21283. if wServicePackMajor=0 then
  21284. FormatUTF8('Windows % (%.%.%)',[WINDOWS_NAME[Vers],
  21285. dwMajorVersion,dwMinorVersion,dwBuildNumber],OSVersionText) else
  21286. FormatUTF8('Windows % SP% (%.%.%)',[WINDOWS_NAME[Vers],wServicePackMajor,
  21287. dwMajorVersion,dwMinorVersion,dwBuildNumber],OSVersionText);
  21288. end;
  21289. {$else}
  21290. procedure RetrieveSystemInfo;
  21291. begin
  21292. {$ifdef KYLIX3}
  21293. SystemInfo.nprocs := LibC.get_nprocs;
  21294. uname(SystemInfo.uts);
  21295. {$else}
  21296. FPUname(SystemInfo.uts);
  21297. {$endif}
  21298. with SystemInfo.uts do
  21299. FormatUTF8('%-% %',[sysname,release,version],OSVersionText);
  21300. end;
  21301. {$ifdef KYLIX3}
  21302. function FileOpen(const FileName: string; Mode: LongWord): Integer;
  21303. const
  21304. SHAREMODE: array[0..fmShareDenyNone shr 4] of Byte = (
  21305. 0, // No share mode specified
  21306. F_WRLCK, // fmShareExclusive
  21307. F_RDLCK, // fmShareDenyWrite
  21308. 0); // fmShareDenyNone
  21309. var FileHandle, Tvar: Integer;
  21310. LockVar: TFlock;
  21311. smode: Byte;
  21312. begin
  21313. result := -1;
  21314. if FileExists(FileName) and
  21315. ((Mode and 3)<=fmOpenReadWrite) and ((Mode and $F0)<=fmShareDenyNone) then begin
  21316. FileHandle := open64(pointer(FileName),(Mode and 3),FileAccessRights);
  21317. if FileHandle=-1 then
  21318. exit;
  21319. smode := Mode and $F0 shr 4;
  21320. if SHAREMODE[smode]<>0 then begin
  21321. with LockVar do begin
  21322. l_whence := SEEK_SET;
  21323. l_start := 0;
  21324. l_len := 0;
  21325. l_type := SHAREMODE[smode];
  21326. end;
  21327. Tvar := fcntl(FileHandle,F_SETLK,LockVar);
  21328. if Tvar=-1 then begin
  21329. __close(FileHandle);
  21330. exit;
  21331. end;
  21332. end;
  21333. result := FileHandle;
  21334. end;
  21335. end;
  21336. function GetTickCount64: Int64;
  21337. begin
  21338. result := SynKylix.GetTickCount64;
  21339. end;
  21340. {$endif KYLIX3}
  21341. {$ifdef FPC}
  21342. function GetTickCount64: Int64;
  21343. begin
  21344. result := SynFPCLinux.GetTickCount64;
  21345. end;
  21346. {$endif}
  21347. {$endif MSWINDOWS}
  21348. function FileOpenSequentialRead(const FileName: string): Integer;
  21349. begin
  21350. {$ifdef MSWINDOWS}
  21351. result := CreateFile(pointer(FileName),GENERIC_READ,
  21352. FILE_SHARE_READ or FILE_SHARE_WRITE,nil, // same as fmShareDenyNone
  21353. OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0);
  21354. {$else}
  21355. result := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
  21356. {$endif MSWINDOWS}
  21357. end;
  21358. function FileStreamSequentialRead(const FileName: string): TFileStream;
  21359. begin
  21360. {$ifdef DELPHI5ORFPC}
  21361. result := TFileStream.Create(FileName,fmOpenRead or fmShareDenyNone);
  21362. {$else}
  21363. result := TFileStream.Create(FileOpenSequentialRead(FileName));
  21364. {$endif}
  21365. end;
  21366. function Elapsed(var PreviousTix: Int64; Interval: Integer): Boolean;
  21367. var now: Int64;
  21368. begin
  21369. if Interval<=0 then
  21370. result := false else begin
  21371. now := GetTickCount64;
  21372. if now-PreviousTix>Interval then begin
  21373. PreviousTix := now;
  21374. result := true;
  21375. end else
  21376. result := false;
  21377. end;
  21378. end;
  21379. {$ifndef FPC} // FPC has its built-in InterlockedIncrement/InterlockedDecrement
  21380. {$ifdef PUREPASCAL}
  21381. function InterlockedIncrement(var I: Integer): Integer;
  21382. begin
  21383. {$ifdef MSWINDOWS} // AtomicIncrement() may not be available e.g. on Delphi XE2
  21384. result := Windows.InterlockedIncrement(I);
  21385. {$else}
  21386. result := AtomicIncrement(I);
  21387. {$endif}
  21388. end;
  21389. function InterlockedDecrement(var I: Integer): Integer;
  21390. begin
  21391. {$ifdef MSWINDOWS} // AtomicDecrement() may not be available e.g. on Delphi XE2
  21392. result := Windows.InterlockedDecrement(I);
  21393. {$else}
  21394. result := AtomicDecrement(I);
  21395. {$endif}
  21396. end;
  21397. {$else}
  21398. function InterlockedIncrement(var I: Integer): Integer;
  21399. asm
  21400. mov edx,1
  21401. xchg eax,edx
  21402. lock xadd [edx],eax
  21403. inc eax
  21404. end;
  21405. function InterlockedDecrement(var I: Integer): Integer;
  21406. asm
  21407. mov edx,-1
  21408. xchg eax,edx
  21409. lock xadd [edx],eax
  21410. dec eax
  21411. end;
  21412. {$endif}
  21413. {$endif}
  21414. procedure SoundExComputeAnsi(var p: PAnsiChar; var result: cardinal; Values: PSoundExValues);
  21415. var n,v,old: cardinal;
  21416. begin
  21417. n := 0;
  21418. old := 0;
  21419. if Values<>nil then
  21420. repeat
  21421. {$ifdef USENORMTOUPPER}
  21422. v := NormToUpperByte[ord(p^)]; // also handle 8 bit WinAnsi (1252 accents)
  21423. {$else}
  21424. v := NormToUpperAnsi7Byte[ord(p^)]; // 7 bit char uppercase
  21425. {$endif}
  21426. if not (v in IsWord) then break;
  21427. inc(p);
  21428. dec(v,ord('B'));
  21429. if v>high(TSoundExValues) then continue;
  21430. v := Values[v]; // get soundex value
  21431. if (v=0) or (v=old) then continue; // invalid or dopple value
  21432. old := v;
  21433. result := result shl SOUNDEX_BITS;
  21434. inc(result,v);
  21435. inc(n);
  21436. if n=((32-8)div SOUNDEX_BITS) then // first char use up to 8 bits
  21437. break; // result up to a cardinal size
  21438. until false;
  21439. end;
  21440. function SoundExComputeFirstCharAnsi(var p: PAnsiChar): cardinal;
  21441. label Err;
  21442. begin
  21443. if p=nil then begin
  21444. Err:result := 0;
  21445. exit;
  21446. end;
  21447. repeat
  21448. {$ifdef USENORMTOUPPER}
  21449. result := NormToUpperByte[ord(p^)]; // also handle 8 bit WinAnsi (CP 1252)
  21450. {$else}
  21451. result := NormToUpperAnsi7Byte[ord(p^)]; // 7 bit char uppercase
  21452. {$endif}
  21453. if result=0 then
  21454. goto Err; // end of input text, without a word
  21455. inc(p);
  21456. // trim initial spaces or 'H'
  21457. until AnsiChar(result) in ['A'..'G','I'..'Z'];
  21458. end;
  21459. function GetHighUTF8UCS4(var U: PUTF8Char): cardinal;
  21460. var extra,i: integer;
  21461. c: cardinal;
  21462. begin
  21463. result := 0;
  21464. c := byte(U^); // here U^>=#80
  21465. inc(U);
  21466. extra := UTF8_EXTRABYTES[c];
  21467. if extra=0 then exit else // invalid leading byte
  21468. for i := 1 to extra do begin
  21469. if byte(U^) and $c0<>$80 then
  21470. exit; // invalid input content
  21471. c := c shl 6+byte(U^);
  21472. inc(U);
  21473. end;
  21474. with UTF8_EXTRA[extra] do begin
  21475. dec(c,offset);
  21476. if c<minimum then
  21477. exit; // invalid input content
  21478. end;
  21479. result := c;
  21480. end;
  21481. function GetHighUTF8UCS4Inlined(var U: PUTF8Char): cardinal;
  21482. {$ifdef HASINLINE}inline;{$endif}
  21483. var extra,i: integer;
  21484. c: cardinal;
  21485. begin
  21486. result := 0;
  21487. c := byte(U^); // here U^>=#80
  21488. inc(U);
  21489. extra := UTF8_EXTRABYTES[c];
  21490. if extra=0 then exit else // invalid leading byte
  21491. for i := 1 to extra do begin
  21492. if byte(U^) and $c0<>$80 then
  21493. exit; // invalid input content
  21494. c := c shl 6+byte(U^);
  21495. inc(U);
  21496. end;
  21497. with UTF8_EXTRA[extra] do begin
  21498. dec(c,offset);
  21499. if c<minimum then
  21500. exit; // invalid input content
  21501. end;
  21502. result := c;
  21503. end;
  21504. function GetNextUTF8Upper(var U: PUTF8Char): cardinal;
  21505. begin
  21506. result := ord(U^);
  21507. if result=0 then
  21508. exit;
  21509. if result and $80=0 then begin
  21510. inc(U);
  21511. {$ifdef USENORMTOUPPER}
  21512. result := NormToUpperByte[result];
  21513. {$else}
  21514. result := NormToUpperAnsi7Byte[result];
  21515. {$endif}
  21516. exit;
  21517. end;
  21518. result := GetHighUTF8UCS4(U);
  21519. if (result<=255) and (WinAnsiConvert.AnsiToWide[result]<=255) then
  21520. {$ifdef USENORMTOUPPER}
  21521. result := NormToUpperByte[result];
  21522. {$else}
  21523. result := NormToUpperAnsi7Byte[result];
  21524. {$endif}
  21525. end;
  21526. procedure SoundExComputeUTF8(var U: PUTF8Char; var result: cardinal; Values: PSoundExValues);
  21527. var n,v,old: cardinal;
  21528. begin
  21529. n := 0;
  21530. old := 0;
  21531. if Values<>nil then
  21532. repeat
  21533. v := GetNextUTF8Upper(U);
  21534. if not (v in IsWord) then break;
  21535. dec(v,ord('B'));
  21536. if v>high(TSoundExValues) then continue;
  21537. v := Values[v]; // get soundex value
  21538. if (v=0) or (v=old) then continue; // invalid or dopple value
  21539. old := v;
  21540. result := result shl SOUNDEX_BITS;
  21541. inc(result,v);
  21542. inc(n);
  21543. if n=((32-8)div SOUNDEX_BITS) then // first char use up to 8 bits
  21544. break; // result up to a cardinal size
  21545. until false;
  21546. end;
  21547. function SoundExComputeFirstCharUTF8(var U: PUTF8Char): cardinal;
  21548. label Err;
  21549. begin
  21550. if U=nil then begin
  21551. Err:result := 0;
  21552. exit;
  21553. end;
  21554. repeat
  21555. result := GetNextUTF8Upper(U);
  21556. if result=0 then
  21557. goto Err; // end of input text, without a word
  21558. // trim initial spaces or 'H'
  21559. until AnsiChar(result) in ['A'..'G','I'..'Z'];
  21560. end;
  21561. function FindNextUTF8WordBegin(U: PUTF8Char): PUTF8Char;
  21562. var c: cardinal;
  21563. V: PUTF8Char;
  21564. begin
  21565. result := nil;
  21566. repeat
  21567. c := GetNextUTF8Upper(U);
  21568. if c=0 then
  21569. exit;
  21570. until not(c in IsWord);
  21571. repeat
  21572. V := U;
  21573. c := GetNextUTF8Upper(U);
  21574. if c=0 then
  21575. exit;
  21576. until c in IsWord;
  21577. result := V;
  21578. end;
  21579. { TSynSoundEx }
  21580. const
  21581. /// english Soundex pronunciation scores
  21582. // - defines the default values used for the SoundEx() function below
  21583. // (used if Values parameter is nil)
  21584. ValueEnglish: TSoundExValues =
  21585. // B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  21586. (1,2,3,0,1,2,0,0,2,2,4,5,5,0,1,2,6,2,3,0,1,0,2,0,2);
  21587. /// french Soundex pronunciation scores
  21588. // - can be used to override default values used for the SoundEx()
  21589. // function below
  21590. ValueFrench: TSoundExValues =
  21591. // B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  21592. (1,2,3,0,9,7,0,0,7,2,4,5,5,0,1,2,6,8,3,0,9,0,8,0,8);
  21593. /// spanish Soundex pronunciation scores
  21594. // - can be used to override default values used for the SoundEx()
  21595. // function below
  21596. ValueSpanish: TSoundExValues =
  21597. // B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  21598. (1,2,3,0,1,2,0,0,0,2,0,5,5,0,1,2,6,2,3,0,1,0,2,0,2);
  21599. SOUNDEXVALUES: array[TSynSoundExPronunciation] of PSoundExValues =
  21600. (@ValueEnglish,@ValueFrench,@ValueSpanish,@ValueEnglish);
  21601. function TSynSoundEx.Ansi(A: PAnsiChar): boolean;
  21602. var Value, c: cardinal;
  21603. begin
  21604. result := false;
  21605. if A=nil then exit;
  21606. repeat
  21607. // test beginning of word
  21608. c := SoundExComputeFirstCharAnsi(A);
  21609. if c=0 then exit else
  21610. if c=FirstChar then begin
  21611. // here we had the first char match -> check if word match UpperValue
  21612. Value := c-(ord('A')-1);
  21613. SoundExComputeAnsi(A,Value,fValues);
  21614. if Value=search then begin
  21615. result := true; // UpperValue found!
  21616. exit;
  21617. end;
  21618. end else
  21619. repeat
  21620. if A^=#0 then exit else
  21621. {$ifdef USENORMTOUPPER}
  21622. if not(NormToUpperByte[ord(A^)] in IsWord) then break else inc(A);
  21623. {$else} if not(ord(A^) in IsWord) then break else inc(A); {$endif}
  21624. until false;
  21625. // find beginning of next word
  21626. repeat
  21627. if A^=#0 then exit else
  21628. {$ifdef USENORMTOUPPER}
  21629. if NormToUpperByte[ord(A^)] in IsWord then break else inc(A);
  21630. {$else} if ord(A^) in IsWord then break else inc(A); {$endif}
  21631. until false;
  21632. until false;
  21633. end;
  21634. function TSynSoundEx.UTF8(U: PUTF8Char): boolean;
  21635. var Value, c: cardinal;
  21636. V: PUTF8Char;
  21637. begin
  21638. result := false;
  21639. if U=nil then exit;
  21640. repeat
  21641. // find beginning of word
  21642. c := SoundExComputeFirstCharUTF8(U);
  21643. if c=0 then exit else
  21644. if c=FirstChar then begin
  21645. // here we had the first char match -> check if word match UpperValue
  21646. Value := c-(ord('A')-1);
  21647. SoundExComputeUTF8(U,Value,fValues);
  21648. if Value=search then begin
  21649. result := true; // UpperValue found!
  21650. exit;
  21651. end;
  21652. end else
  21653. repeat
  21654. c := GetNextUTF8Upper(U);
  21655. if c=0 then
  21656. exit;
  21657. until not(c in IsWord);
  21658. // find beginning of next word
  21659. repeat
  21660. if U=nil then exit;
  21661. V := U;
  21662. c := GetNextUTF8Upper(U);
  21663. if c=0 then
  21664. exit;
  21665. until c in IsWord;
  21666. U := V;
  21667. until U=nil;
  21668. end;
  21669. function TSynSoundEx.Prepare(UpperValue: PAnsiChar; Lang: TSynSoundExPronunciation): boolean;
  21670. begin
  21671. fValues := SOUNDEXVALUES[Lang];
  21672. Search := SoundExAnsi(UpperValue,nil,Lang);
  21673. if Search=0 then
  21674. result := false else begin
  21675. FirstChar := SoundExComputeFirstCharAnsi(UpperValue);
  21676. result := true;
  21677. end;
  21678. end;
  21679. function SoundExAnsi(A: PAnsiChar; next: PPAnsiChar;
  21680. Lang: TSynSoundExPronunciation): cardinal;
  21681. begin
  21682. result := SoundExComputeFirstCharAnsi(A);
  21683. if result<>0 then begin
  21684. dec(result,ord('A')-1); // first Soundex char is first char
  21685. SoundExComputeAnsi(A,result,SOUNDEXVALUES[Lang]);
  21686. end;
  21687. if next<>nil then begin
  21688. {$ifdef USENORMTOUPPER}
  21689. while NormToUpperByte[ord(A^)] in IsWord do inc(A); // go to end of word
  21690. {$else}
  21691. while ord(A^) in IsWord do inc(A); // go to end of word
  21692. {$endif}
  21693. next^ := A;
  21694. end;
  21695. end;
  21696. function SoundExUTF8(U: PUTF8Char; next: PPUTF8Char;
  21697. Lang: TSynSoundExPronunciation): cardinal;
  21698. begin
  21699. result := SoundExComputeFirstCharUTF8(U);
  21700. if result<>0 then begin
  21701. dec(result,ord('A')-1); // first Soundex char is first char
  21702. SoundExComputeUTF8(U,result,SOUNDEXVALUES[Lang]);
  21703. end;
  21704. if next<>nil then
  21705. next^ := FindNextUTF8WordBegin(U);
  21706. end;
  21707. {$ifdef USENORMTOUPPER}
  21708. function AnsiICompW(u1, u2: PWideChar): PtrInt;
  21709. begin
  21710. if u1<>u2 then
  21711. if u1<>nil then
  21712. if u2<>nil then
  21713. repeat
  21714. result := PtrInt(u1^)-PtrInt(u2^);
  21715. if result<>0 then begin
  21716. if (PtrInt(u1^)>255) or (PtrInt(u2^)>255) then exit;
  21717. result := NormToUpperAnsi7Byte[PtrInt(u1^)]-NormToUpperAnsi7Byte[PtrInt(u2^)];
  21718. if result<>0 then exit;
  21719. end;
  21720. if (u1^=#0) or (u2^=#0) then break;
  21721. inc(u1);
  21722. inc(u2);
  21723. until false else
  21724. result := 1 else // u2=''
  21725. result := -1 else // u1=''
  21726. result := 0; // u1=u2
  21727. end;
  21728. {$ifdef PUREPASCAL}
  21729. function AnsiIComp(Str1, Str2: PWinAnsiChar): PtrInt;
  21730. begin
  21731. if Str1<>Str2 then
  21732. if Str1<>nil then
  21733. if Str2<>nil then
  21734. repeat
  21735. result := NormToUpperByte[ord(Str1^)]-NormToUpperByte[pByte(Str2)^];
  21736. if result<>0 then exit;
  21737. if (Str1^=#0) or (Str2^=#0) then break;
  21738. inc(Str1);
  21739. inc(Str2);
  21740. until false else
  21741. result := 1 else // Str2=''
  21742. result := -1 else // Str1=''
  21743. result := 0; // Str1=Str2
  21744. end;
  21745. {$else}
  21746. function AnsiIComp(Str1, Str2: PWinAnsiChar): PtrInt;
  21747. asm // fast 8 bits WinAnsi comparaison using the NormToUpper[] array
  21748. cmp eax,edx
  21749. je @2
  21750. test eax,edx // is either of the strings perhaps nil?
  21751. jz @3
  21752. @0: push ebx // compare the first character (faster quicksort)
  21753. movzx ebx,byte ptr [eax] // ebx=S1[1]
  21754. movzx ecx,byte ptr [edx] // ecx=S2[1]
  21755. test ebx,ebx
  21756. jz @z
  21757. cmp ebx,ecx
  21758. je @s
  21759. mov bl,byte ptr [NormToUpper+ebx]
  21760. mov cl,byte ptr [NormToUpper+ecx]
  21761. cmp ebx,ecx
  21762. je @s
  21763. mov eax,ebx
  21764. pop ebx
  21765. sub eax,ecx // return S1[1]-S2[1]
  21766. ret
  21767. @2: xor eax, eax
  21768. ret
  21769. @3: test eax,eax // S1=''
  21770. jz @4
  21771. test edx,edx // S2='' ?
  21772. jnz @0
  21773. mov eax,1 // return 1 (S1>S2)
  21774. ret
  21775. @s: inc eax
  21776. inc edx
  21777. mov bl,[eax] // ebx=S1[i]
  21778. mov cl,[edx] // ecx=S2[i]
  21779. test ebx,ebx
  21780. je @z // end of S1
  21781. cmp ebx,ecx
  21782. je @s
  21783. mov bl,byte ptr [NormToUpper+ebx]
  21784. mov cl,byte ptr [NormToUpper+ecx]
  21785. cmp ebx,ecx
  21786. je @s
  21787. mov eax,ebx
  21788. pop ebx
  21789. sub eax,ecx // return S1[i]-S2[i]
  21790. ret
  21791. @z: cmp ebx,ecx // S1=S2?
  21792. pop ebx
  21793. jz @2
  21794. @4: or eax,-1 // return -1 (S1<S2)
  21795. end;
  21796. {$endif}
  21797. function ConvertCaseUTF8(P: PUTF8Char; const Table: TNormTableByte): PtrInt;
  21798. var D,S: PUTF8Char;
  21799. c: PtrUInt;
  21800. extra,i: integer;
  21801. begin
  21802. result := 0;
  21803. if P=nil then
  21804. exit;
  21805. D := P;
  21806. repeat
  21807. c := byte(P[0]);
  21808. inc(P);
  21809. if c=0 then
  21810. break;
  21811. if c and $80=0 then begin
  21812. D[result] := AnsiChar(Table[c]);
  21813. inc(result);
  21814. end else begin
  21815. extra := UTF8_EXTRABYTES[c];
  21816. if extra=0 then exit else // invalid leading byte
  21817. for i := 0 to extra-1 do
  21818. if byte(P[i]) and $c0<>$80 then
  21819. exit else // invalid input content
  21820. c := c shl 6+byte(P[i]);
  21821. with UTF8_EXTRA[extra] do begin
  21822. dec(c,offset);
  21823. if c<minimum then
  21824. exit; // invalid input content
  21825. end;
  21826. if (c<=255) and (Table[c]<=127) then begin
  21827. D[result] := AnsiChar(Table[c]);
  21828. inc(result);
  21829. inc(P,extra);
  21830. continue;
  21831. end;
  21832. S := P-1;
  21833. inc(P,extra);
  21834. inc(extra);
  21835. MoveFast(S^,D[result],extra);
  21836. inc(result,extra);
  21837. end;
  21838. until false;
  21839. end;
  21840. function UpperCaseU(const S: RawUTF8): RawUTF8;
  21841. var LS,LD: integer;
  21842. begin
  21843. LS := length(S);
  21844. SetString(result,PAnsiChar(pointer(S)),LS);
  21845. LD := ConvertCaseUTF8(pointer(result),NormToUpperByte);
  21846. if LS<>LD then
  21847. SetLength(result,LD);
  21848. end;
  21849. function LowerCaseU(const S: RawUTF8): RawUTF8;
  21850. var LS,LD: integer;
  21851. begin
  21852. LS := length(S);
  21853. SetString(result,PAnsiChar(pointer(S)),LS);
  21854. LD := ConvertCaseUTF8(pointer(result),NormToLowerByte);
  21855. if LS<>LD then
  21856. SetLength(result,LD);
  21857. end;
  21858. function UTF8IComp(u1, u2: PUTF8Char): PtrInt;
  21859. var c2: PtrInt;
  21860. b: byte;
  21861. begin // fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values
  21862. if u1<>u2 then
  21863. if u1<>nil then
  21864. if u2<>nil then
  21865. repeat
  21866. result := ord(u1^);
  21867. if result and $80=0 then
  21868. if result<>0 then begin
  21869. result := NormToUpperByte[result];
  21870. inc(u1);
  21871. b := ord(u2^);
  21872. if b and $80=0 then begin
  21873. if b=0 then exit; // u1>u2 -> return u1^
  21874. dec(result,NormToUpperByte[b]);
  21875. inc(u2);
  21876. if result<>0 then exit;
  21877. continue;
  21878. end;
  21879. end else begin // u1^=#0 -> end of u1 reached
  21880. if u2^<>#0 then // end of u2 reached -> u1=u2 -> return 0
  21881. result := -1; // u1<u2
  21882. exit;
  21883. end else begin
  21884. result := GetHighUTF8UCS4Inlined(u1);
  21885. if result and $ffffff00=0 then
  21886. result := NormToUpperByte[result]; // 8 bits to upper, 32 bits as is
  21887. end;
  21888. c2 := ord(u2^);
  21889. if c2 and $80=0 then begin
  21890. inc(u2);
  21891. if c2=0 then exit; // u1>u2 -> return u1^
  21892. dec(result,NormToUpperByte[c2]);
  21893. if result<>0 then exit;
  21894. end else begin
  21895. c2 := GetHighUTF8UCS4Inlined(u2);
  21896. if c2 and $ffffff00=0 then
  21897. dec(result,NormToUpperByte[c2]) else // 8 bits to upper
  21898. dec(result,c2); // 32 bits widechar returns diff
  21899. if result<>0 then exit;
  21900. end;
  21901. until false else
  21902. result := 1 else // u2=''
  21903. result := -1 else // u1=''
  21904. result := 0; // u1=u2
  21905. end;
  21906. function UTF8ILComp(u1, u2: PUTF8Char; L1,L2: cardinal): PtrInt;
  21907. var c2: PtrInt;
  21908. extra,i: integer;
  21909. label neg,pos,eq;
  21910. begin // fast UTF-8 comparaison using the NormToUpper[] array for all 8 bits values
  21911. if u1<>u2 then
  21912. if (u1<>nil) and (L1<>0) then
  21913. if (u2<>nil) and (L2<>0) then
  21914. repeat
  21915. result := pByte(u1)^;
  21916. inc(u1);
  21917. dec(L1);
  21918. if result and $80=0 then begin
  21919. result := NormToUpperByte[result];
  21920. if pByte(u2)^ and $80=0 then begin
  21921. dec(result,NormToUpperByte[pByte(u2)^]);
  21922. dec(L2);
  21923. inc(u2);
  21924. if result<>0 then
  21925. exit else
  21926. if L1<>0 then
  21927. if L2<>0 then
  21928. continue else // L1>0 and L2>0 -> next char
  21929. goto pos else // L1>0 and L2=0 -> u1>u2
  21930. if L2<>0 then
  21931. goto neg else // L1=0 and L2>0 -> u1<u2
  21932. exit; // L1=0 and L2=0 -> u1=u2
  21933. end;
  21934. end else begin
  21935. extra := UTF8_EXTRABYTES[result];
  21936. if extra=0 then goto neg; // invalid leading byte
  21937. dec(L1,extra);
  21938. if Integer(L1)<0 then goto neg;
  21939. for i := 0 to extra-1 do
  21940. result := result shl 6+PByteArray(u1)[i];
  21941. dec(result,UTF8_EXTRA[extra].offset);
  21942. inc(u1,extra);
  21943. if result and $ffffff00=0 then
  21944. result := NormToUpperByte[result]; // 8 bits to upper, 32 bits as is
  21945. end;
  21946. // here result=NormToUpper[u1^]
  21947. c2 := pByte(u2)^;
  21948. inc(u2);
  21949. dec(L2);
  21950. if c2 and $80=0 then begin
  21951. dec(result,NormToUpperByte[c2]);
  21952. if result<>0 then exit;
  21953. end else begin
  21954. extra := UTF8_EXTRABYTES[c2];
  21955. if extra=0 then goto pos;
  21956. dec(L2,extra);
  21957. if integer(L2)<0 then goto pos;
  21958. for i := 0 to extra-1 do
  21959. c2 := c2 shl 6+PByteArray(u2)[i];
  21960. dec(c2,UTF8_EXTRA[extra].offset);
  21961. inc(u2,extra);
  21962. if c2 and $ffffff00=0 then
  21963. dec(result,NormToUpperByte[c2]) else // 8 bits to upper
  21964. dec(result,c2); // returns 32 bits diff
  21965. if result<>0 then exit;
  21966. end;
  21967. // here we have result=NormToUpper[u2^]-NormToUpper[u1^]=0
  21968. if L1=0 then // test if we reached end of u1 or end of u2
  21969. if L2=0 then exit // u1=u2
  21970. else goto neg else // u1<u2
  21971. if L2=0 then goto pos; // u1>u2
  21972. until false else
  21973. pos: result := 1 else // u2='' or u1>u2
  21974. neg: result := -1 else // u1='' or u1<u2
  21975. result := 0; // u1=u2
  21976. end;
  21977. function SameTextU(const S1, S2: RawUTF8): Boolean;
  21978. // checking UTF-8 lengths is not accurate: surrogates may be confusing
  21979. begin
  21980. result := UTF8IComp(pointer(S1),pointer(S2))=0;
  21981. end;
  21982. {$else} // no NormToUpper[]
  21983. function AnsiIComp(Str1, Str2: PWinAnsiChar): integer;
  21984. {$ifdef PUREPASCAL}
  21985. begin
  21986. result := StrIComp(Str1,Str2); // fast enough
  21987. end;
  21988. {$else}
  21989. asm
  21990. jmp StrIComp // LVCL without NormToUpper[]: use default SysUtils implementation
  21991. end;
  21992. {$endif}
  21993. {$endif}
  21994. function FindAnsi(A, UpperValue: PAnsiChar): boolean;
  21995. var ValueStart: PAnsiChar;
  21996. {$ifndef USENORMTOUPPER}
  21997. ch: AnsiChar;
  21998. {$endif}
  21999. begin
  22000. result := false;
  22001. if (A=nil) or (UpperValue=nil) then exit;
  22002. ValueStart := UpperValue;
  22003. repeat
  22004. // test beginning of word
  22005. repeat
  22006. if A^=#0 then exit else
  22007. {$ifdef USENORMTOUPPER}
  22008. if byte(NormToUpper[A^]) in IsWord then break else inc(A); {$else}
  22009. if byte(NormToUpperAnsi7[A^]) in IsWord then break else inc(A);
  22010. {$endif}
  22011. until false;
  22012. // check if this word is the UpperValue
  22013. UpperValue := ValueStart;
  22014. repeat
  22015. {$ifdef USENORMTOUPPER}
  22016. if NormToUpper[A^]<>UpperValue^ then break; {$else}
  22017. if NormToUpperAnsi7[A^]<>UpperValue^ then break;
  22018. {$endif}
  22019. inc(UpperValue);
  22020. if UpperValue^=#0 then begin
  22021. result := true; // UpperValue found!
  22022. exit;
  22023. end;
  22024. inc(A);
  22025. if A^=#0 then exit;
  22026. until false;
  22027. // find beginning of next word
  22028. repeat
  22029. if A^=#0 then exit else
  22030. {$ifdef USENORMTOUPPER}
  22031. if not (NormToUpperByte[ord(A^)] in IsWord) then break else inc(A);
  22032. {$else} if not (ord(A^) in IsWord) then break else inc(A); {$endif}
  22033. until false;
  22034. until false;
  22035. end;
  22036. function FindUnicode(PW, Upper: PWideChar; UpperLen: integer): boolean;
  22037. var Start: PWideChar;
  22038. begin
  22039. result := false;
  22040. if (PW=nil) or (Upper=nil) then exit;
  22041. repeat
  22042. // go to beginning of next word
  22043. repeat
  22044. if ord(PW^)=0 then exit else
  22045. if (ord(PW^)>126) or (ord(PW^) in IsWord) then
  22046. Break;
  22047. inc(PW);
  22048. until false;
  22049. Start := PW;
  22050. // search end of word matching UpperLen characters
  22051. repeat
  22052. inc(PW);
  22053. until (PW-Start>=UpperLen) or
  22054. (ord(PW^)=0) or ((ord(PW^)<126) and (not(ord(PW^) in IsWord)));
  22055. if PW-Start>=UpperLen then
  22056. if CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE,Start,UpperLen,Upper,UpperLen)=2 then begin
  22057. result := true; // match found
  22058. exit;
  22059. end;
  22060. // not found: go to end of current word
  22061. repeat
  22062. if PW^=#0 then exit else
  22063. if ((ord(PW^)<126) and (not(ord(PW^) in IsWord))) then Break;
  22064. inc(PW);
  22065. until false;
  22066. until false;
  22067. end;
  22068. function FindUTF8(U: PUTF8Char; UpperValue: PAnsiChar): boolean;
  22069. var ValueStart: PAnsiChar;
  22070. {$ifdef USENORMTOUPPER}
  22071. c: cardinal;
  22072. FirstChar: AnsiChar;
  22073. label Next;
  22074. {$else}
  22075. ch: AnsiChar;
  22076. {$endif}
  22077. begin
  22078. result := false;
  22079. if (U=nil) or (UpperValue=nil) then exit;
  22080. {$ifdef USENORMTOUPPER}
  22081. // handles 8-bits WinAnsi chars inside UTF-8 encoded data
  22082. FirstChar := UpperValue^;
  22083. ValueStart := UpperValue+1;
  22084. repeat
  22085. // test beginning of word
  22086. repeat
  22087. c := byte(U^);
  22088. inc(U);
  22089. if c=0 then exit else
  22090. if c and $80=0 then begin
  22091. if c in IsWord then
  22092. if PAnsiChar(@NormToUpper)[c]<>FirstChar then
  22093. goto Next else
  22094. break;
  22095. end else
  22096. if c and $20=0 then begin // fast direct process $0..$7ff
  22097. c := c shl 6+byte(U^)-$3080;
  22098. inc(U);
  22099. if c<=255 then begin
  22100. c := NormToUpperByte[c];
  22101. if c in IsWord then
  22102. if AnsiChar(c)<>FirstChar then
  22103. goto Next else
  22104. break;
  22105. end;
  22106. end else
  22107. if UTF8_EXTRABYTES[c]=0 then
  22108. exit else // invalid leading byte
  22109. inc(U,UTF8_EXTRABYTES[c]); // just ignore surrogates for soundex
  22110. until false;
  22111. // here we had the first char match -> check if this word match UpperValue
  22112. UpperValue := ValueStart;
  22113. repeat
  22114. if UpperValue^=#0 then begin
  22115. result := true; // UpperValue found!
  22116. exit;
  22117. end;
  22118. c := byte(U^); inc(U); // next chars
  22119. if c=0 then exit else
  22120. if c and $80=0 then begin
  22121. if PAnsiChar(@NormToUpper)[c]<>UpperValue^ then break;
  22122. end else
  22123. if c and $20=0 then begin
  22124. c := c shl 6+byte(U^)-$3080;
  22125. inc(U);
  22126. if (c>255) or (PAnsiChar(@NormToUpper)[c]<>UpperValue^) then break;
  22127. end else begin
  22128. if UTF8_EXTRABYTES[c]=0 then
  22129. exit else // invalid leading byte
  22130. inc(U,UTF8_EXTRABYTES[c]);
  22131. break;
  22132. end;
  22133. inc(UpperValue);
  22134. until false;
  22135. // find beginning of next word
  22136. Next:
  22137. {$else}
  22138. // this tiny version only handles 7-bits ansi chars and ignore all UTF-8 chars
  22139. ValueStart := UpperValue;
  22140. repeat
  22141. // find beginning of word
  22142. repeat
  22143. if byte(U^)=0 then exit else
  22144. if byte(U^) and $80=0 then
  22145. if byte(U^) in IsWord then
  22146. break else
  22147. inc(U) else
  22148. if byte(U^) and $20=0 then
  22149. inc(U,2) else
  22150. inc(U,3);
  22151. until false;
  22152. // check if this word is the UpperValue
  22153. UpperValue := ValueStart;
  22154. repeat
  22155. ch := NormToUpperAnsi7[U^];
  22156. if ch<>UpperValue^ then break;
  22157. inc(UpperValue);
  22158. if UpperValue^=#0 then begin
  22159. result := true; // UpperValue found!
  22160. exit;
  22161. end;
  22162. inc(U);
  22163. if byte(U^)=0 then exit else
  22164. if byte(U^) and $80<>0 then break; // 7 bits char check only
  22165. until false;
  22166. {$endif}
  22167. // find beginning of next word
  22168. U := FindNextUTF8WordBegin(U);
  22169. until U=nil;
  22170. end;
  22171. function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: integer): boolean;
  22172. var B,C: byte;
  22173. i: integer;
  22174. begin
  22175. result := false; // return false if any invalid char
  22176. if (Hex=nil) or (Bin=nil) then
  22177. exit;
  22178. inc(Bin,BinBytes-1);
  22179. for i := 1 to BinBytes do begin
  22180. B := ConvertHexToBin[Ord(Hex^)];
  22181. inc(Hex);
  22182. if B>15 then exit;
  22183. C := ConvertHexToBin[Ord(Hex^)];
  22184. Inc(Hex);
  22185. if C>15 then exit;
  22186. Bin^ := B shl 4+C;
  22187. Dec(Bin);
  22188. end;
  22189. result := true; // correct content in Hex
  22190. end;
  22191. function HexDisplayToCardinal(Hex: PAnsiChar; out aValue: cardinal): boolean;
  22192. begin
  22193. result := HexDisplayToBin(Hex,@aValue,sizeof(aValue));
  22194. end;
  22195. function HexDisplayToInt64(Hex: PAnsiChar; out aValue: Int64): boolean;
  22196. begin
  22197. result := HexDisplayToBin(Hex,@aValue,sizeof(aValue));
  22198. end;
  22199. function HexDisplayToInt64(const Hex: RawByteString): Int64;
  22200. begin
  22201. if not HexDisplayToBin(pointer(Hex),@result,sizeof(result)) then
  22202. result := 0;
  22203. end;
  22204. function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: Integer): boolean;
  22205. var I: Integer;
  22206. B,C: byte;
  22207. begin
  22208. result := false; // return false if any invalid char
  22209. if Hex=nil then
  22210. exit;
  22211. if Bin<>nil then
  22212. for I := 1 to BinBytes do begin
  22213. B := ConvertHexToBin[Ord(Hex^)];
  22214. inc(Hex);
  22215. if B>15 then exit;
  22216. C := ConvertHexToBin[Ord(Hex^)];
  22217. Inc(Hex);
  22218. if C>15 then exit;
  22219. Bin^ := B shl 4+C;
  22220. Inc(Bin);
  22221. end else
  22222. for I := 1 to BinBytes do begin // no Bin^ -> just validate Hex^ Stream format
  22223. B := ConvertHexToBin[Ord(Hex^)];
  22224. inc(Hex);
  22225. if B>15 then exit;
  22226. C := ConvertHexToBin[Ord(Hex^)];
  22227. Inc(Hex);
  22228. if C>15 then exit;
  22229. end;
  22230. result := true; // conversion OK
  22231. end;
  22232. function HexToCharValid(Hex: PAnsiChar): boolean;
  22233. begin
  22234. result := (ConvertHexToBin[Ord(Hex[0])]<=15) and
  22235. (ConvertHexToBin[Ord(Hex[1])]<=15);
  22236. end;
  22237. function HexToChar(Hex: PAnsiChar; Bin: PUTF8Char): boolean;
  22238. var B,C: byte;
  22239. begin
  22240. if Hex<>nil then begin
  22241. B := ConvertHexToBin[Ord(Hex[0])];
  22242. if B<=15 then begin
  22243. C := ConvertHexToBin[Ord(Hex[1])];
  22244. if C<=15 then begin
  22245. if Bin<>nil then
  22246. Bin^ := AnsiChar(B shl 4+C);
  22247. result := true;
  22248. exit;
  22249. end;
  22250. end;
  22251. end;
  22252. result := false; // return false if any invalid char
  22253. end;
  22254. function HexToWideChar(Hex: PAnsiChar): cardinal;
  22255. var B: cardinal;
  22256. begin
  22257. result := ConvertHexToBin[Ord(Hex[0])];
  22258. if result<=15 then begin
  22259. B := ConvertHexToBin[Ord(Hex[1])];
  22260. if B<=15 then begin
  22261. result := result shl 4+B;
  22262. B := ConvertHexToBin[Ord(Hex[2])];
  22263. if B<=15 then begin
  22264. result := result shl 4+B;
  22265. B := ConvertHexToBin[Ord(Hex[3])];
  22266. if B<=15 then begin
  22267. result := result shl 4+B;
  22268. exit;
  22269. end;
  22270. end;
  22271. end;
  22272. end;
  22273. result := 0;
  22274. end;
  22275. const
  22276. b64: array[0..63] of AnsiChar =
  22277. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  22278. function Base64EncodeMain(rp, sp: PAnsiChar; len: cardinal): integer;
  22279. {$ifdef PUREPASCAL}
  22280. {$ifdef HASINLINE}inline;{$endif}
  22281. var i: integer;
  22282. c: cardinal;
  22283. begin
  22284. result := len div 3;
  22285. for i := 1 to result do begin
  22286. c := ord(sp[0]) shl 16 + ord(sp[1]) shl 8 + ord(sp[2]);
  22287. rp[0] := b64[(c shr 18) and $3f];
  22288. rp[1] := b64[(c shr 12) and $3f];
  22289. rp[2] := b64[(c shr 6) and $3f];
  22290. rp[3] := b64[c and $3f];
  22291. inc(rp,4);
  22292. inc(sp,3);
  22293. end;
  22294. end;
  22295. {$else}
  22296. asm // eax=rp edx=sp ecx=len - pipeline optimized version by AB
  22297. push ebx
  22298. push esi
  22299. push edi
  22300. push ebp
  22301. mov ebx,edx
  22302. mov esi,eax
  22303. mov eax,ecx
  22304. mov edx,1431655766 // faster eax=len div 3 using reciprocal
  22305. sar ecx,31
  22306. imul edx
  22307. mov eax,edx
  22308. sub eax,ecx
  22309. mov edi,offset b64
  22310. mov ebp,eax
  22311. push eax
  22312. jz @z
  22313. // edi=b64 ebx=sp esi=rp ebp=len div 3
  22314. xor eax,eax
  22315. @1: // read 3 bytes from sp
  22316. movzx edx,byte ptr [ebx]
  22317. shl edx,16
  22318. mov al,[ebx+2]
  22319. mov ah,[ebx+1]
  22320. lea ebx,[ebx+3]
  22321. or eax,edx
  22322. // encode as Base64
  22323. mov ecx,eax
  22324. mov edx,eax
  22325. shr ecx,6
  22326. and edx,$3F
  22327. and ecx,$3F
  22328. mov dh,[edi+edx]
  22329. mov dl,[edi+ecx]
  22330. mov ecx,eax
  22331. shr eax,12
  22332. shr ecx,18
  22333. shl edx,16
  22334. and ecx,$3F
  22335. and eax,$3F
  22336. mov cl,[edi+ecx]
  22337. mov ch,[edi+eax]
  22338. or ecx,edx
  22339. // write the 4 encoded bytes into rp
  22340. dec ebp
  22341. mov [esi],ecx
  22342. lea esi,[esi+4]
  22343. jnz @1
  22344. @z: pop eax // result := len div 3
  22345. pop ebp
  22346. pop edi
  22347. pop esi
  22348. pop ebx
  22349. end;
  22350. {$endif}
  22351. procedure Base64EncodeTrailing(rp, sp: PAnsiChar; len: cardinal);
  22352. {$ifdef HASINLINE}inline;{$endif}
  22353. var c: cardinal;
  22354. begin
  22355. case len of
  22356. 1: begin
  22357. c := ord(sp[0]) shl 4;
  22358. rp[0] := b64[(c shr 6) and $3f];
  22359. rp[1] := b64[c and $3f];
  22360. rp[2] := '=';
  22361. rp[3] := '=';
  22362. end;
  22363. 2: begin
  22364. c := ord(sp[0]) shl 10 + ord(sp[1]) shl 2;
  22365. rp[0] := b64[(c shr 12) and $3f];
  22366. rp[1] := b64[(c shr 6) and $3f];
  22367. rp[2] := b64[c and $3f];
  22368. rp[3] := '=';
  22369. end;
  22370. end;
  22371. end;
  22372. procedure Base64Encode(rp, sp: PAnsiChar; len: cardinal);
  22373. var main: cardinal;
  22374. begin
  22375. main := Base64EncodeMain(rp,sp,len);
  22376. Base64EncodeTrailing(rp+main*4,sp+main*3,len-main*3);
  22377. end;
  22378. function BinToBase64Length(len: PtrUInt): PtrUInt;
  22379. begin
  22380. result := ((len+2)div 3)*4;
  22381. end;
  22382. function BinToBase64(const s: RawByteString): RawUTF8;
  22383. var len: integer;
  22384. begin
  22385. result := '';
  22386. len := length(s);
  22387. if len=0 then
  22388. exit;
  22389. SetLength(result,BinToBase64Length(len));
  22390. Base64Encode(pointer(result),pointer(s),len);
  22391. end;
  22392. function BinToBase64(Bin: PAnsiChar; BinBytes: integer): RawUTF8;
  22393. begin
  22394. result := '';
  22395. if BinBytes=0 then
  22396. exit;
  22397. SetLength(result,BinToBase64Length(BinBytes));
  22398. Base64Encode(pointer(result),Bin,BinBytes);
  22399. end;
  22400. procedure Base64ToURI(var base64: RawUTF8);
  22401. var P: PUTF8Char;
  22402. begin
  22403. {$ifdef FPC}
  22404. UniqueString(base64); // @base64[1] won't call UniqueString() under FPC :(
  22405. {$endif}
  22406. P := @base64[1];
  22407. if P<>nil then
  22408. repeat
  22409. case P^ of
  22410. #0: break;
  22411. '+': P^ := '-';
  22412. '/': P^ := '_';
  22413. '=': begin // trim unsignificant trailing '=' characters
  22414. SetLength(base64,P-pointer(base64));
  22415. break;
  22416. end;
  22417. end;
  22418. inc(P);
  22419. until false;
  22420. end;
  22421. procedure Base64FromURI(var base64: RawUTF8);
  22422. var P: PUTF8Char;
  22423. len,i,append: integer;
  22424. begin
  22425. len := length(base64);
  22426. if len=0 then
  22427. exit;
  22428. {$ifdef FPC}
  22429. UniqueString(base64); // @base64[1] won't call UniqueString() under FPC :(
  22430. {$endif}
  22431. P := @base64[1];
  22432. repeat
  22433. case P^ of
  22434. #0: break;
  22435. '-': P^ := '+';
  22436. '_': P^ := '/';
  22437. end;
  22438. inc(P);
  22439. until false;
  22440. append := 4-(len and 3);
  22441. if append<>4 then begin // add unsignificant trailing '=' characters
  22442. SetLength(base64,len+append);
  22443. for i := len+1 to len+append do
  22444. base64[i] := '=';
  22445. end;
  22446. end;
  22447. function BinToBase64URI(Bin: PAnsiChar; BinBytes: integer): RawUTF8;
  22448. begin
  22449. result := BinToBase64(Bin,BinBytes);
  22450. Base64ToURI(result);
  22451. end;
  22452. function BinToBase64WithMagic(const s: RawByteString): RawUTF8;
  22453. var len: integer;
  22454. begin
  22455. result:='';
  22456. len := length(s);
  22457. if len=0 then
  22458. exit;
  22459. SetLength(result,((len+2) div 3)*4+3);
  22460. PInteger(pointer(result))^ := JSON_BASE64_MAGIC;
  22461. Base64Encode(PAnsiChar(pointer(result))+3,pointer(s),len);
  22462. end;
  22463. function BinToBase64WithMagic(Data: pointer; DataLen: integer): RawUTF8; overload;
  22464. begin
  22465. result:='';
  22466. if DataLen<=0 then
  22467. exit;
  22468. SetLength(result,((DataLen+2) div 3)*4+3);
  22469. PInteger(pointer(result))^ := JSON_BASE64_MAGIC;
  22470. Base64Encode(PAnsiChar(pointer(result))+3,Data,DataLen);
  22471. end;
  22472. var
  22473. /// a conversion table from Base64 text into binary data
  22474. // - used by Base64ToBin/IsBase64 functions
  22475. ConvertBase64ToBin: array[AnsiChar] of shortint;
  22476. function IsBase64(sp: PAnsiChar; len: PtrInt): boolean;
  22477. var i: PtrInt;
  22478. begin
  22479. result := false;
  22480. if (len=0) or (len and 3<>0) then
  22481. exit;
  22482. for i := 0 to len-5 do
  22483. if ConvertBase64ToBin[sp[i]]<0 then
  22484. exit;
  22485. inc(sp,len-4);
  22486. if (ConvertBase64ToBin[sp[0]]=-1) or // -2 = '=' is allowed here
  22487. (ConvertBase64ToBin[sp[1]]=-1) or (ConvertBase64ToBin[sp[2]]=-1) or (ConvertBase64ToBin[sp[3]]=-1) then
  22488. exit;
  22489. result := true; // layout seems correct
  22490. end;
  22491. function IsBase64(const s: RawByteString): boolean;
  22492. begin
  22493. result := IsBase64(pointer(s),length(s));
  22494. end;
  22495. function Base64ToBinLength(sp: PAnsiChar; len: PtrInt): PtrInt;
  22496. begin
  22497. if (len=0) or (len and 3<>0) then begin
  22498. result := 0;
  22499. exit;
  22500. end;
  22501. if ConvertBase64ToBin[sp[len-2]]>=0 then
  22502. if ConvertBase64ToBin[sp[len-1]]>=0 then
  22503. result := 0 else
  22504. result := 1 else
  22505. result := 2;
  22506. result := (len shr 2)*3-result;
  22507. end;
  22508. procedure Base64Decode(sp,rp: PAnsiChar; len: PtrInt);
  22509. {$ifdef PUREPASCAL}
  22510. var i: PtrInt;
  22511. c, ch: PtrInt;
  22512. begin
  22513. for i := 1 to len do begin
  22514. c := ConvertBase64ToBin[sp[0]];
  22515. if c>=0 then begin
  22516. c := c shl 6;
  22517. ch := ConvertBase64ToBin[sp[1]];
  22518. if ch>=0 then begin
  22519. c := (c or ch) shl 6;
  22520. ch := ConvertBase64ToBin[sp[2]];
  22521. if ch>=0 then begin
  22522. c := (c or ch) shl 6;
  22523. ch := ConvertBase64ToBin[sp[3]];
  22524. if ch>=0 then begin
  22525. c := c or ch;
  22526. rp[2] := AnsiChar(c);
  22527. c := c shr 8;
  22528. rp[1] := AnsiChar(c);
  22529. c := c shr 8;
  22530. rp[0] := AnsiChar(c);
  22531. inc(rp,3);
  22532. inc(sp,4);
  22533. continue;
  22534. end else begin
  22535. c := c shr 8;
  22536. rp[1] := AnsiChar(c);
  22537. rp[0] := AnsiChar(c shr 8);
  22538. //assert(resultlen=len*3-1);
  22539. exit;
  22540. end;
  22541. end;
  22542. end;
  22543. end;
  22544. rp[0] := AnsiChar(c shr 10);
  22545. //assert(resultlen=len*3-2);
  22546. exit;
  22547. end;
  22548. end;
  22549. {$else}
  22550. asm // eax=sp edx=rp ecx=len - pipeline optimized version by AB
  22551. push ebx
  22552. push esi
  22553. push edi
  22554. push ebp
  22555. push eax
  22556. test ecx,ecx
  22557. mov ebp,edx
  22558. lea edi,[ConvertBase64ToBin]
  22559. mov [esp],ecx
  22560. jz @4
  22561. @0: movzx edx,byte ptr [eax]
  22562. movzx ebx,byte ptr [eax+$01]
  22563. movsx ecx,byte ptr [edi+edx]
  22564. movsx esi,byte ptr [edi+ebx]
  22565. test ecx,ecx
  22566. jl @1
  22567. shl ecx,$06
  22568. test esi,esi
  22569. jl @1
  22570. or ecx,esi
  22571. movzx edx,byte ptr [eax+$02]
  22572. movzx ebx,byte ptr [eax+$03]
  22573. shl ecx,$06
  22574. movsx esi,byte ptr [edi+edx]
  22575. movsx edx,byte ptr [edi+ebx]
  22576. test esi,esi
  22577. jl @1
  22578. or ecx,esi
  22579. shl ecx,$06
  22580. test edx,edx
  22581. jl @2
  22582. or ecx,edx
  22583. lea eax,[eax+4]
  22584. mov [ebp+2],cl
  22585. mov [ebp+1],ch
  22586. shr ecx,16
  22587. dec dword ptr [esp]
  22588. mov [ebp],cl
  22589. lea ebp,[ebp+3]
  22590. jnz @0
  22591. @4: pop eax
  22592. pop ebp
  22593. pop edi
  22594. pop esi
  22595. pop ebx
  22596. ret
  22597. @2: shr ecx,$08
  22598. mov [ebp+$01],cl
  22599. mov [ebp],ch
  22600. jmp @4
  22601. @1: shr ecx,$0a
  22602. mov [ebp],cl
  22603. jmp @4
  22604. end;
  22605. {$endif}
  22606. function Base64ToBin(const s: RawByteString): RawByteString;
  22607. var len, resultLen: PtrInt;
  22608. begin
  22609. len := length(s);
  22610. resultLen := Base64ToBinLength(pointer(s),len);
  22611. if resultLen=0 then
  22612. result := '' else begin
  22613. SetString(result,nil,resultLen);
  22614. Base64Decode(pointer(s),pointer(result),len shr 2);
  22615. end;
  22616. end;
  22617. function Base64ToBin(sp: PAnsiChar; len: PtrInt): RawByteString;
  22618. begin
  22619. Base64ToBin(sp,len,result);
  22620. end;
  22621. procedure Base64ToBin(sp: PAnsiChar; len: PtrInt; var result: RawByteString);
  22622. var resultLen: PtrInt;
  22623. begin
  22624. resultLen := Base64ToBinLength(sp,len);
  22625. if resultLen=0 then
  22626. result := '' else begin
  22627. SetString(result,nil,resultLen);
  22628. Base64Decode(sp,pointer(result),len shr 2);
  22629. end;
  22630. end;
  22631. procedure Base64ToBin(sp: PAnsiChar; len: PtrInt; var result: TSynTempBuffer); overload;
  22632. var resultLen: PtrInt;
  22633. begin
  22634. resultLen := Base64ToBinLength(sp,len);
  22635. if resultLen=0 then
  22636. result.Init(0) else begin
  22637. result.Init(resultLen);
  22638. Base64Decode(sp,result.buf,len shr 2);
  22639. end;
  22640. end;
  22641. function Base64ToBin(base64, bin: PAnsiChar; base64len, binlen: PtrInt;
  22642. nofullcheck: boolean): boolean;
  22643. begin
  22644. result := (bin<>nil) and (Base64ToBinLength(base64,base64len)=binlen) and
  22645. (nofullcheck or IsBase64(base64,base64len));
  22646. if result then
  22647. Base64Decode(base64,bin,base64len shr 2);
  22648. end;
  22649. function BinToSource(const ConstName, Comment: RawUTF8;
  22650. Data: pointer; Len, PerLine: integer; const Suffix: RawUTF8): RawUTF8;
  22651. var W: TTextWriter;
  22652. begin
  22653. if (Data=nil) or (Len<=0) or (PerLine<=0) then
  22654. result := '' else begin
  22655. W := TTextWriter.CreateOwnedStream(Len*5+50+length(Comment)+length(Suffix));
  22656. try
  22657. BinToSource(W,ConstName,Comment,Data,Len,PerLine);
  22658. if Suffix<>'' then begin
  22659. W.AddString(Suffix);
  22660. W.AddCR;
  22661. end;
  22662. W.SetText(result);
  22663. finally
  22664. W.Free;
  22665. end;
  22666. end;
  22667. end;
  22668. procedure BinToSource(Dest: TTextWriter; const ConstName, Comment: RawUTF8;
  22669. Data: pointer; Len, PerLine: integer);
  22670. var line,i: integer;
  22671. P: PByte;
  22672. begin
  22673. if (Dest=nil) or (Data=nil) or (Len<=0) or (PerLine<=0) then
  22674. exit;
  22675. Dest.AddShort('const');
  22676. if Comment<>'' then
  22677. Dest.Add(#13' // %',[Comment]);
  22678. Dest.Add(#13' %: array[0..%] of byte = (',[ConstName,Len-1]);
  22679. P := pointer(Data);
  22680. repeat
  22681. if len>PerLine then
  22682. line := PerLine else
  22683. line := Len;
  22684. Dest.AddShort(#13#10' ');
  22685. for i := 0 to line-1 do begin
  22686. Dest.Add('$');
  22687. Dest.AddByteToHex(P^);
  22688. inc(P);
  22689. Dest.Add(',');
  22690. end;
  22691. dec(Len,line);
  22692. until Len=0;
  22693. Dest.CancelLastComma;
  22694. Dest.Add(');'#13' %_LEN = SizeOf(%);'#13,[ConstName,ConstName]);
  22695. end;
  22696. function DateToSQL(Date: TDateTime): RawUTF8;
  22697. begin
  22698. if Date<=0 then
  22699. result := '' else begin
  22700. SetLength(result,13);
  22701. PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC;
  22702. DateToIso8601PChar(Date,PUTF8Char(pointer(result))+3,True);
  22703. end;
  22704. end;
  22705. function DateToSQL(Year,Month,Day: Cardinal): RawUTF8; overload;
  22706. begin
  22707. if (Year=0) or (Month-1>11) or (Day-1>30) then
  22708. result := '' else begin
  22709. SetLength(result,13);
  22710. PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC;
  22711. DateToIso8601PChar(PUTF8Char(pointer(result))+3,True,Year,Month,Day);
  22712. end;
  22713. end;
  22714. function DateTimeToSQL(DT: TDateTime): RawUTF8;
  22715. begin
  22716. if DT<=0 then
  22717. result := '' else begin
  22718. SetLength(result,3);
  22719. PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC;
  22720. if frac(DT)=0 then
  22721. result := result+DateToIso8601(DT,true) else
  22722. if trunc(DT)=0 then
  22723. result := result+TimeToIso8601(DT,true,'T') else
  22724. result := result+DateTimeToIso8601(DT,true,'T');
  22725. end;
  22726. end;
  22727. function TimeLogToSQL(const TimeStamp: TTimeLog): RawUTF8;
  22728. begin
  22729. if TimeStamp=0 then
  22730. result := '' else begin
  22731. SetLength(result,3);
  22732. PCardinal(pointer(result))^ := JSON_SQLDATE_MAGIC;
  22733. result := result+PTimeLogBits(@TimeStamp)^.Text(true);
  22734. end;
  22735. end;
  22736. function SQLToDateTime(const ParamValueWithMagic: RawUTF8): TDateTime;
  22737. begin
  22738. result := Iso8601ToDateTimePUTF8Char(PUTF8Char(pointer(ParamValueWithMagic))+3,
  22739. length(ParamValueWithMagic)-3);
  22740. end;
  22741. function UpperCaseUnicode(const S: RawUTF8): RawUTF8;
  22742. {$ifdef MSWINDOWS}
  22743. var tmp: RawUnicode;
  22744. TmpLen: integer;
  22745. {$endif}
  22746. begin
  22747. {$ifdef MSWINDOWS}
  22748. tmp := Utf8DecodeToRawUnicodeUI(S,@TmpLen);
  22749. TmpLen := TmpLen shr 1;
  22750. CharUpperBuffW(pointer(tmp),TmpLen);
  22751. RawUnicodeToUtf8(pointer(tmp),TmpLen,result);
  22752. {$endif}
  22753. {$ifdef POSIX}
  22754. result := WideStringToUTF8(WideUpperCase(UTF8ToWideString(S)));
  22755. {$endif}
  22756. end;
  22757. function LowerCaseUnicode(const S: RawUTF8): RawUTF8;
  22758. {$ifdef MSWINDOWS}
  22759. var tmp: RawUnicode;
  22760. TmpLen: integer;
  22761. {$endif}
  22762. begin
  22763. {$ifdef MSWINDOWS}
  22764. tmp := Utf8DecodeToRawUnicodeUI(S,@TmpLen);
  22765. TmpLen := TmpLen shr 1;
  22766. CharLowerBuffW(pointer(tmp),TmpLen);
  22767. RawUnicodeToUtf8(pointer(tmp),TmpLen,result);
  22768. {$endif}
  22769. {$ifdef POSIX}
  22770. result := WideStringToUTF8(WideLowerCase(UTF8ToWideString(S)));
  22771. {$endif}
  22772. end;
  22773. function UpperCase(const S: RawUTF8): RawUTF8;
  22774. var L, i: PtrInt;
  22775. begin
  22776. L := length(S);
  22777. SetString(Result,PAnsiChar(pointer(S)),L);
  22778. for i := 0 to L-1 do
  22779. if PByteArray(result)[i] in [ord('a')..ord('z')] then
  22780. dec(PByteArray(result)[i],32);
  22781. end;
  22782. procedure UpperCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8);
  22783. var i: integer;
  22784. begin
  22785. SetRawUTF8(result,PAnsiChar(Text),Len);
  22786. for i := 0 to Len-1 do
  22787. if PByteArray(result)[i] in [ord('a')..ord('z')] then
  22788. dec(PByteArray(result)[i],32);
  22789. end;
  22790. procedure UpperCaseCopy(const Source: RawUTF8; var Dest: RawUTF8);
  22791. var L, i: PtrInt;
  22792. begin
  22793. L := length(Source);
  22794. SetRawUTF8(Dest,PAnsiChar(pointer(Source)),L);
  22795. for i := 0 to L-1 do
  22796. if PByteArray(Dest)[i] in [ord('a')..ord('z')] then
  22797. dec(PByteArray(Dest)[i],32);
  22798. end;
  22799. function LowerCase(const S: RawUTF8): RawUTF8;
  22800. var L, i: PtrInt;
  22801. begin
  22802. L := length(S);
  22803. SetString(result,PAnsiChar(pointer(S)),L);
  22804. for i := 0 to L-1 do
  22805. if PByteArray(result)[i] in [ord('A')..ord('Z')] then
  22806. inc(PByteArray(result)[i],32);
  22807. end;
  22808. procedure LowerCaseCopy(Text: PUTF8Char; Len: integer; var result: RawUTF8);
  22809. var i: integer;
  22810. begin
  22811. SetRawUTF8(result,PAnsiChar(Text),Len);
  22812. for i := 0 to Len-1 do
  22813. if PByteArray(result)[i] in [ord('A')..ord('Z')] then
  22814. inc(PByteArray(result)[i],32);
  22815. end;
  22816. function TrimLeft(const S: RawUTF8): RawUTF8;
  22817. var i, l: Integer;
  22818. begin
  22819. l := Length(S);
  22820. i := 1;
  22821. while (i <= l) and (S[i] <= ' ') do
  22822. Inc(i);
  22823. Result := Copy(S, i, Maxint);
  22824. end;
  22825. function TrimRight(const S: RawUTF8): RawUTF8;
  22826. var i: Integer;
  22827. begin
  22828. i := Length(S);
  22829. while (i > 0) and (S[i] <= ' ') do
  22830. Dec(i);
  22831. SetString(result,PAnsiChar(pointer(S)),i);
  22832. end;
  22833. var
  22834. /// fast lookup table for converting hexadecimal numbers from 0 to 15
  22835. // into their ASCII equivalence
  22836. // - is local for better code generation
  22837. TwoDigitsHex: array[byte] of array[1..2] of AnsiChar;
  22838. TwoDigitsHexW: array[AnsiChar] of word absolute TwoDigitsHex;
  22839. TwoDigitsHexWB: array[byte] of word absolute TwoDigitsHex;
  22840. procedure BinToHex(Bin, Hex: PAnsiChar; BinBytes: integer);
  22841. var j: cardinal;
  22842. begin
  22843. for j := 1 to BinBytes do begin
  22844. PWord(Hex)^ := TwoDigitsHexW[Bin^];
  22845. inc(Hex,2);
  22846. inc(Bin);
  22847. end;
  22848. end;
  22849. function BinToHex(const Bin: RawByteString): RawUTF8; overload;
  22850. var L: integer;
  22851. begin
  22852. L := length(Bin);
  22853. FastNewRawUTF8(result,L*2);
  22854. SynCommons.BinToHex(pointer(Bin),pointer(Result),L);
  22855. end;
  22856. function BinToHex(Bin: PAnsiChar; BinBytes: integer): RawUTF8;
  22857. begin
  22858. FastNewRawUTF8(result,BinBytes*2);
  22859. SynCommons.BinToHex(Bin,pointer(Result),BinBytes);
  22860. end;
  22861. function HexToBin(const Hex: RawUTF8): RawByteString; overload;
  22862. var L: integer;
  22863. begin
  22864. L := length(Hex);
  22865. if L and 1<>0 then
  22866. L := 0 else // hexadecimal should be in char pairs
  22867. L := L shr 1;
  22868. SetLength(result,L);
  22869. if not SynCommons.HexToBin(pointer(Hex),pointer(result),L) then
  22870. result := '';
  22871. end;
  22872. procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: integer);
  22873. var j: integer;
  22874. begin
  22875. for j := BinBytes-1 downto 0 do begin
  22876. PWord(Hex+j*2)^ := TwoDigitsHexW[Bin^];
  22877. inc(Bin);
  22878. end;
  22879. end;
  22880. function BinToHexDisplay(Bin: PAnsiChar; BinBytes: integer): RawUTF8; overload;
  22881. begin
  22882. FastNewRawUTF8(result,BinBytes*2);
  22883. BinToHexDisplay(Bin,pointer(result),BinBytes);
  22884. end;
  22885. procedure PointerToHex(aPointer: Pointer; var result: RawUTF8);
  22886. begin
  22887. FastNewRawUTF8(result,sizeof(Pointer)*2);
  22888. BinToHexDisplay(aPointer,pointer(result),sizeof(Pointer));
  22889. end;
  22890. function PointerToHex(aPointer: Pointer): RawUTF8;
  22891. begin
  22892. FastNewRawUTF8(result,sizeof(Pointer)*2);
  22893. BinToHexDisplay(aPointer,pointer(result),sizeof(Pointer));
  22894. end;
  22895. function CardinalToHex(aCardinal: Cardinal): RawUTF8;
  22896. begin
  22897. FastNewRawUTF8(result,sizeof(Cardinal)*2);
  22898. BinToHexDisplay(@aCardinal,pointer(result),sizeof(Cardinal));
  22899. end;
  22900. function Int64ToHex(aInt64: Int64): RawUTF8;
  22901. begin
  22902. FastNewRawUTF8(result,sizeof(Int64)*2);
  22903. BinToHexDisplay(@AInt64,pointer(result),sizeof(Int64));
  22904. end;
  22905. procedure Int64ToHex(aInt64: Int64; var result: RawUTF8);
  22906. begin
  22907. FastNewRawUTF8(result,sizeof(Int64)*2);
  22908. BinToHexDisplay(@AInt64,pointer(result),sizeof(Int64));
  22909. end;
  22910. type TWordRec = packed record YDiv100, YMod100: byte; end;
  22911. {$ifdef FPC_OR_PUREPASCAL} // Alf reported asm below fails with FPC/Linux32
  22912. function Div100(Y: word): TWordRec; {$ifdef HASINLINE}inline;{$endif}
  22913. begin
  22914. result.YDiv100 := Y div 100;
  22915. result.YMod100 := Y-(result.YDiv100*100); // * is always faster than div
  22916. end;
  22917. {$else}
  22918. function Div100(Y: word): TWordRec;
  22919. asm
  22920. mov cl,100
  22921. div cl // ah=remainder=Y mod 100, al=quotient=Year div 100
  22922. end;
  22923. {$endif}
  22924. procedure YearToPChar(Y: cardinal; P: PUTF8Char);
  22925. {$ifdef PUREPASCAL}
  22926. var d100: cardinal;
  22927. begin
  22928. d100 := Y div 100;
  22929. PWordArray(P)[0] := TwoDigitLookupW[d100];
  22930. PWordArray(P)[1] := TwoDigitLookupW[Y-(d100*100)];
  22931. end;
  22932. {$else}
  22933. asm
  22934. mov cl,100
  22935. div cl // ah=remainder=Y mod 100, al=quotient=Year div 100
  22936. movzx ecx,al // al=quotient=Y div 100
  22937. mov cx,word ptr [TwoDigitLookup+ecx*2]
  22938. mov [edx],cx
  22939. movzx ecx,ah // ah=remainder=Y mod 100
  22940. mov cx,word ptr [TwoDigitLookup+ecx*2]
  22941. mov [edx+2],cx
  22942. end;
  22943. {$endif}
  22944. function UInt3DigitsToUTF8(Value: Cardinal): RawUTF8;
  22945. begin
  22946. SetString(result,nil,3);
  22947. PWordArray(result)[0] := TwoDigitLookupW[Value div 10];
  22948. PByteArray(result)[2] := (Value mod 10)+48;
  22949. end;
  22950. function UInt4DigitsToUTF8(Value: Cardinal): RawUTF8;
  22951. begin
  22952. SetString(result,nil,4);
  22953. YearToPChar(Value,pointer(result));
  22954. end;
  22955. function UInt4DigitsToShort(Value: Cardinal): Short4;
  22956. begin
  22957. result[0] := #4;
  22958. YearToPChar(Value,@result[1]);
  22959. end;
  22960. function UInt3DigitsToShort(Value: Cardinal): Short4;
  22961. begin
  22962. YearToPChar(Value,@result[0]);
  22963. result[0] := #3; // override first digit
  22964. end;
  22965. function UInt2DigitsToShort(Value: byte): Short4;
  22966. begin
  22967. result[0] := #2;
  22968. if Value>99 then
  22969. Value := 99;
  22970. PWord(@result[1])^ := TwoDigitLookupW[Value];
  22971. end;
  22972. function SameValue(const A, B: Double; DoublePrec: double): Boolean;
  22973. var AbsA,AbsB: double;
  22974. begin // faster than the Math unit version
  22975. AbsA := Abs(A);
  22976. AbsB := Abs(B);
  22977. if AbsA<AbsB then
  22978. AbsA := AbsA*DoublePrec else
  22979. AbsA := AbsB*DoublePrec; // AbsA := Min(Abs(A),Abs(B))*DoublePrec
  22980. // AbsA is the allowed Epsilon value
  22981. if AbsA<DoublePrec then
  22982. Result := Abs(A-B)<=DoublePrec else
  22983. Result := Abs(A-B)<=AbsA;
  22984. end;
  22985. function SameValueFloat(const A, B: TSynExtended; DoublePrec: TSynExtended): Boolean;
  22986. var AbsA,AbsB: TSynExtended;
  22987. begin // faster than the Math unit version
  22988. AbsA := Abs(A);
  22989. AbsB := Abs(B);
  22990. if AbsA<AbsB then
  22991. AbsA := AbsA*DoublePrec else
  22992. AbsA := AbsB*DoublePrec; // AbsA := Min(Abs(A),Abs(B))*DoublePrec
  22993. // AbsA is the allowed Epsilon value
  22994. if AbsA<DoublePrec then
  22995. Result := Abs(A-B)<=DoublePrec else
  22996. Result := Abs(A-B)<=AbsA;
  22997. end;
  22998. /// return the index of Value in Values[], -1 if not found
  22999. function FindRawUTF8(const Values: TRawUTF8DynArray; const Value: RawUTF8;
  23000. CaseSensitive: boolean=true): integer;
  23001. begin
  23002. if CaseSensitive then begin
  23003. for result := 0 to length(Values)-1 do
  23004. if Values[result]=Value then
  23005. exit;
  23006. end else
  23007. for result := 0 to length(Values)-1 do
  23008. if UTF8IComp(pointer(Values[result]),pointer(Value))=0 then
  23009. exit;
  23010. result := -1;
  23011. end;
  23012. function FindRawUTF8(const Values: array of RawUTF8; const Value: RawUTF8;
  23013. CaseSensitive: boolean=true): integer;
  23014. begin
  23015. if CaseSensitive then begin
  23016. for result := 0 to high(Values) do
  23017. if Values[result]=Value then
  23018. exit;
  23019. end else
  23020. for result := 0 to high(Values) do
  23021. if UTF8IComp(pointer(Values[result]),pointer(Value))=0 then
  23022. exit;
  23023. result := -1;
  23024. end;
  23025. function FindRawUTF8(const Values: TRawUTF8DynArray; ValuesCount: integer;
  23026. const Value: RawUTF8; SearchPropName: boolean): integer;
  23027. begin
  23028. if SearchPropName then begin
  23029. for result := 0 to ValuesCount-1 do
  23030. if IdemPropNameU(Values[result],Value) then
  23031. exit;
  23032. end else
  23033. for result := 0 to ValuesCount-1 do
  23034. if Values[result]=Value then
  23035. exit;
  23036. result := -1;
  23037. end;
  23038. function FindPropName(const Names: array of RawUTF8; const Name: RawUTF8): integer;
  23039. {$ifdef HASINLINE}
  23040. var NameLen: integer;
  23041. begin
  23042. NameLen := Length(Name);
  23043. for result := 0 to high(Names) do
  23044. if (Length(Names[result])=NameLen) and
  23045. IdemPropNameUSameLen(pointer(Names[result]),pointer(Name),NameLen) then
  23046. exit;
  23047. result := -1;
  23048. end;
  23049. {$else}
  23050. begin
  23051. for result := 0 to high(Names) do
  23052. if IdemPropNameU(Names[result],Name) then
  23053. exit;
  23054. result := -1;
  23055. end;
  23056. {$endif}
  23057. /// true if Value was added successfully in Values[]
  23058. function AddRawUTF8(var Values: TRawUTF8DynArray; const Value: RawUTF8;
  23059. NoDuplicates: boolean=false; CaseSensitive: boolean=true): boolean;
  23060. var i: integer;
  23061. begin
  23062. if NoDuplicates then begin
  23063. i := FindRawUTF8(Values,Value,CaseSensitive);
  23064. if i>=0 then begin
  23065. result := false;
  23066. exit;
  23067. end;
  23068. end;
  23069. i := length(Values);
  23070. SetLength(Values,i+1);
  23071. Values[i] := Value;
  23072. result := true;
  23073. end;
  23074. procedure AddRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  23075. const Value: RawUTF8);
  23076. var capacity: integer;
  23077. begin
  23078. capacity := Length(Values);
  23079. if ValuesCount=capacity then begin
  23080. inc(capacity,32+capacity shr 3);
  23081. SetLength(Values,capacity);
  23082. end;
  23083. Values[ValuesCount] := Value;
  23084. inc(ValuesCount);
  23085. end;
  23086. function RawUTF8DynArrayEquals(const A,B: TRawUTF8DynArray): boolean;
  23087. var i: integer;
  23088. begin
  23089. result := false;
  23090. if length(A)<>length(B) then
  23091. exit;
  23092. for i := 0 to high(A) do
  23093. if A[i]<>B[i] then
  23094. exit;
  23095. result := true;
  23096. end;
  23097. { TPropNameList }
  23098. procedure TPropNameList.Init;
  23099. begin
  23100. Count := 0;
  23101. end;
  23102. function TPropNameList.FindPropName(const Value: RawUTF8): Integer;
  23103. begin
  23104. for result := 0 to Count-1 do
  23105. if IdemPropNameU(Values[result],Value) then
  23106. exit;
  23107. result := -1;
  23108. end;
  23109. function TPropNameList.AddPropName(const Value: RawUTF8): Boolean;
  23110. begin
  23111. if FindPropName(Value)<0 then begin
  23112. if Count=length(Values) then
  23113. SetLength(Values,Count+16);
  23114. Values[Count] := Value;
  23115. inc(Count);
  23116. result := true;
  23117. end else
  23118. result := false;
  23119. end;
  23120. procedure StringDynArrayToRawUTF8DynArray(const Source: TStringDynArray;
  23121. var Result: TRawUTF8DynArray);
  23122. var i: Integer;
  23123. begin
  23124. SetLength(Result,length(Source));
  23125. for i := 0 to high(Source) do
  23126. StringToUTF8(Source[i],Result[i]);
  23127. end;
  23128. procedure StringListToRawUTF8DynArray(Source: TStringList; var Result: TRawUTF8DynArray);
  23129. var i: Integer;
  23130. begin
  23131. SetLength(Result,Source.Count);
  23132. for i := 0 to Source.Count-1 do
  23133. StringToUTF8(Source[i],Result[i]);
  23134. end;
  23135. /// find the position of the SEARCH] section in source
  23136. // - return true if SEARCH] was found, and store line after it in source
  23137. function FindSectionFirstLine(var source: PUTF8Char; search: PAnsiChar): boolean;
  23138. {$ifdef PUREPASCAL}
  23139. begin
  23140. result := false;
  23141. if source=nil then
  23142. exit;
  23143. repeat
  23144. if source^='[' then begin
  23145. inc(source);
  23146. result := IdemPChar(source,search);
  23147. end;
  23148. while source^ in ANSICHARNOT01310 do inc(source);
  23149. while source^ in [#10,#13] do inc(source);
  23150. if result then
  23151. exit; // found
  23152. until source^=#0;
  23153. source := nil;
  23154. end;
  23155. {$else}
  23156. asm // eax=source edx=search
  23157. push eax // save source var
  23158. mov eax,[eax] // eax=source
  23159. test eax,eax
  23160. jz @z
  23161. push ebx
  23162. mov ebx,edx // save search
  23163. cmp byte ptr [eax],'['
  23164. lea eax,[eax+1]
  23165. jne @s
  23166. @i: push eax
  23167. mov edx,ebx // edx=search
  23168. call IdemPChar
  23169. pop ecx // ecx=source
  23170. jmp @1
  23171. @s: mov ecx,eax
  23172. xor eax,eax // result := false
  23173. @1: mov dl,[ecx] // while not (source^ in [#0,#10,#13]) do inc(source);
  23174. inc ecx
  23175. cmp dl,13
  23176. ja @1
  23177. je @e
  23178. or dl,dl
  23179. jz @0
  23180. cmp dl,10
  23181. jne @1
  23182. cmp byte [ecx],13
  23183. jbe @1
  23184. jmp @4
  23185. @e: cmp byte ptr [ecx],10 // jump #13#10
  23186. jne @4
  23187. inc ecx
  23188. @4: test al,al
  23189. jnz @x // exit if IdemPChar returned true
  23190. cmp byte ptr [ecx],'['
  23191. lea ecx,[ecx+1]
  23192. jne @1
  23193. mov eax,ecx
  23194. jmp @i
  23195. @0: xor ecx,ecx // set source=nil
  23196. @x: pop ebx
  23197. pop edx // restore source var
  23198. mov [edx],ecx // update source var
  23199. ret
  23200. @z: pop edx // ignore source var, result := false
  23201. end;
  23202. {$endif}
  23203. {$ifdef USENORMTOUPPER}
  23204. {$ifdef PUREPASCAL}
  23205. function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean;
  23206. // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper)
  23207. begin
  23208. result := false;
  23209. if (p=nil) or (up=nil) then
  23210. exit;
  23211. while up^<>#0 do begin
  23212. if (p^>#255) or (up^<>AnsiChar(NormToUpperByte[ord(p^)])) then
  23213. exit;
  23214. inc(up);
  23215. inc(p);
  23216. end;
  23217. result := true;
  23218. end;
  23219. {$else}
  23220. function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean;
  23221. // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper)
  23222. // eax=p edx=up
  23223. asm
  23224. test eax,eax
  23225. jz @e // P=nil -> false
  23226. test edx,edx
  23227. push ebx
  23228. push esi
  23229. jz @z // up=nil -> true
  23230. mov esi,offset NormToUpper
  23231. xor ebx,ebx
  23232. xor ecx,ecx
  23233. @1: mov bx,[eax] // bl=p^
  23234. mov cl,[edx] // cl=up^
  23235. test bh,bh // p^ > #255 -> FALSE
  23236. jnz @n
  23237. test cl,cl
  23238. mov bl,[ebx+esi] // bl=NormToUpper[p^]
  23239. jz @z // up^=#0 -> OK
  23240. lea edx,[edx+1] // = inc edx without changing flags
  23241. cmp bl,cl
  23242. lea eax,[eax+2]
  23243. je @1
  23244. @n: pop esi
  23245. pop ebx
  23246. @e: xor eax,eax
  23247. ret
  23248. @z: mov al,1 // up^=#0 -> OK
  23249. pop esi
  23250. pop ebx
  23251. end;
  23252. {$endif}
  23253. {$else}
  23254. function IdemPCharW(p: PWideChar; up: PUTF8Char): boolean;
  23255. // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper)
  23256. begin
  23257. result := false;
  23258. if (p=nil) or (up=nil) then
  23259. exit;
  23260. while up^<>#0 do begin
  23261. if (p^>#255) or (up^<>AnsiChar(NormToUpperByteAnsi7[ord(p^)])) then
  23262. exit;
  23263. inc(up);
  23264. inc(p);
  23265. end;
  23266. result := true;
  23267. end;
  23268. {$endif}
  23269. function FindSectionFirstLineW(var source: PWideChar; search: PUTF8Char): boolean;
  23270. {$ifdef PUREPASCAL}
  23271. begin
  23272. result := false;
  23273. if source=nil then
  23274. exit;
  23275. repeat
  23276. if source^='[' then begin
  23277. inc(source);
  23278. result := IdemPCharW(source,search);
  23279. end;
  23280. while not (cardinal(source^) in [0,10,13]) do inc(source);
  23281. while cardinal(source^) in [10,13] do inc(source);
  23282. if result then
  23283. exit; // found
  23284. until source^=#0;
  23285. source := nil;
  23286. end;
  23287. {$else}
  23288. asm // eax=source edx=search
  23289. push eax // save source var
  23290. mov eax,[eax] // eax=source
  23291. test eax,eax
  23292. jz @z
  23293. push ebx
  23294. mov ebx,edx // save search
  23295. cmp word ptr [eax],'['
  23296. lea eax,[eax+2]
  23297. jne @s
  23298. @i: push eax
  23299. mov edx,ebx // edx=search
  23300. call IdemPCharW
  23301. pop ecx // ecx=source
  23302. jmp @1
  23303. @s: mov ecx,eax
  23304. xor eax,eax // result := false
  23305. @1: mov dx,[ecx] // while not (source^ in [#0,#10,#13]) do inc(source);
  23306. lea ecx,[ecx+2]
  23307. cmp dx,13
  23308. ja @1
  23309. je @e
  23310. or dx,dx
  23311. jz @0
  23312. cmp dx,10
  23313. jne @1
  23314. jmp @4
  23315. @e: cmp word ptr [ecx],10 // jump #13#10
  23316. jne @4
  23317. lea ecx,[ecx+2]
  23318. @4: test al,al
  23319. jnz @x // exit if IdemPChar returned true
  23320. cmp word ptr [ecx],'['
  23321. lea ecx,[ecx+2]
  23322. jne @1
  23323. mov eax,ecx
  23324. jmp @i
  23325. @0: xor ecx,ecx // set source=nil
  23326. @x: pop ebx
  23327. pop edx // restore source var
  23328. mov [edx],ecx // update source var
  23329. ret
  23330. @z: pop edx // ignore source var, result := false
  23331. end;
  23332. {$endif}
  23333. function FindIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
  23334. var PBeg: PUTF8Char;
  23335. i: integer;
  23336. begin // expect UpperName as 'NAME='
  23337. PBeg := nil;
  23338. if (P<>nil) and (P^<>'[') and (UpperName<>nil) then
  23339. repeat
  23340. if P^=' ' then repeat inc(P) until P^<>' '; // trim left ' '
  23341. if NormToUpperAnsi7[P[0]]=UpperName[0] then
  23342. PBeg := P;
  23343. repeat
  23344. if P[0]>#13 then
  23345. if P[1]>#13 then
  23346. if P[2]>#13 then
  23347. if P[3]>#13 then begin
  23348. inc(P,4);
  23349. continue;
  23350. end else
  23351. inc(P,3) else
  23352. inc(P,2) else
  23353. inc(P);
  23354. if P^ in [#0,#10,#13] then
  23355. break else
  23356. inc(P);
  23357. until false;
  23358. if PBeg<>nil then begin
  23359. i := 1;
  23360. repeat
  23361. if UpperName[i]<>#0 then
  23362. if NormToUpperAnsi7[PBeg[i]]<>UpperName[i] then
  23363. break else
  23364. inc(i) else begin
  23365. inc(PBeg,i);
  23366. SetString(result,PBeg,P-PBeg);
  23367. exit;
  23368. end;
  23369. until false;
  23370. PBeg := nil;
  23371. end;
  23372. if P^=#13 then inc(P);
  23373. if P^=#10 then inc(P);
  23374. until P^ in [#0,'['];
  23375. result := '';
  23376. end;
  23377. function ExistsIniName(P: PUTF8Char; UpperName: PAnsiChar): boolean;
  23378. var PBeg: PUTF8Char;
  23379. begin
  23380. result := true;
  23381. while (P<>nil) and (P^<>'[') do begin
  23382. PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
  23383. if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' '; // trim left ' '
  23384. if IdemPChar(PBeg,UpperName) then
  23385. exit;
  23386. end;
  23387. result := false;
  23388. end;
  23389. function ExistsIniNameValue(P: PUTF8Char; const UpperName: RawUTF8;
  23390. const UpperValues: array of PAnsiChar): boolean;
  23391. var PBeg: PUTF8Char;
  23392. begin
  23393. result := true;
  23394. if high(UpperValues)>=0 then
  23395. while (P<>nil) and (P^<>'[') do begin
  23396. PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
  23397. if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' '; // trim left ' '
  23398. if IdemPChar(PBeg,pointer(UpperName)) then begin
  23399. inc(PBeg,length(UpperName));
  23400. if IdemPCharArray(PBeg,UpperValues)>=0 then
  23401. exit; // found one value
  23402. break;
  23403. end;
  23404. end;
  23405. result := false;
  23406. end;
  23407. function FindWinAnsiIniNameValue(P: PUTF8Char; UpperName: PAnsiChar): RawUTF8;
  23408. begin
  23409. result := WinAnsiToUtf8(RawByteString(FindIniNameValue(P,UpperName)));
  23410. end;
  23411. function GetSectionContent(SectionFirstLine: PUTF8Char): RawUTF8;
  23412. var PBeg: PUTF8Char;
  23413. begin
  23414. PBeg := SectionFirstLine;
  23415. while (SectionFirstLine<>nil) and (SectionFirstLine^<>'[') do
  23416. GetNextLineBegin(SectionFirstLine,SectionFirstLine);
  23417. if SectionFirstLine=nil then
  23418. result := PBeg else
  23419. SetString(result,PBeg,SectionFirstLine-PBeg);
  23420. end;
  23421. function GetSectionContent(const Content, SectionName: RawUTF8): RawUTF8; overload;
  23422. var P: PUTF8Char;
  23423. UpperSection: array[byte] of AnsiChar;
  23424. begin
  23425. P := pointer(Content);
  23426. PWord(UpperCopy255(UpperSection,SectionName))^ := ord(']');
  23427. if FindSectionFirstLine(P,UpperSection) then
  23428. result := GetSectionContent(P) else
  23429. result := '';
  23430. end;
  23431. function DeleteSection(var Content: RawUTF8; const SectionName: RawUTF8;
  23432. EraseSectionHeader: boolean=true): boolean;
  23433. var P: PUTF8Char;
  23434. UpperSection: array[byte] of AnsiChar;
  23435. begin
  23436. result := false; // no modification
  23437. P := pointer(Content);
  23438. PWord(UpperCopy255(UpperSection,SectionName))^ := ord(']');
  23439. if FindSectionFirstLine(P,UpperSection) then
  23440. result := DeleteSection(P,Content,EraseSectionHeader);
  23441. end;
  23442. function DeleteSection(SectionFirstLine: PUTF8Char; var Content: RawUTF8;
  23443. EraseSectionHeader: boolean=true): boolean;
  23444. var PEnd: PUTF8Char;
  23445. IndexBegin: PtrInt;
  23446. begin
  23447. result := false;
  23448. PEnd := SectionFirstLine;
  23449. if EraseSectionHeader then // erase [Section] header line
  23450. while (PtrUInt(SectionFirstLine)>PtrUInt(Content)) and (SectionFirstLine^<>'[') do dec(SectionFirstLine);
  23451. while (PEnd<>nil) and (PEnd^<>'[') do
  23452. GetNextLineBegin(PEnd,PEnd);
  23453. IndexBegin := SectionFirstLine-pointer(Content);
  23454. if IndexBegin=0 then
  23455. exit; // no modification
  23456. if PEnd=nil then
  23457. SetLength(Content,IndexBegin) else
  23458. delete(Content,IndexBegin+1,PEnd-SectionFirstLine);
  23459. result := true; // Content was modified
  23460. end;
  23461. procedure ReplaceSection(SectionFirstLine: PUTF8Char;
  23462. var Content: RawUTF8; const NewSectionContent: RawUTF8); overload;
  23463. var PEnd: PUTF8Char;
  23464. IndexBegin: PtrInt;
  23465. begin
  23466. if SectionFirstLine=nil then
  23467. exit;
  23468. // delete existing [Section] content
  23469. PEnd := SectionFirstLine;
  23470. while (PEnd<>nil) and (PEnd^<>'[') do
  23471. GetNextLineBegin(PEnd,PEnd);
  23472. IndexBegin := SectionFirstLine-pointer(Content);
  23473. if PEnd=nil then
  23474. SetLength(Content,IndexBegin) else
  23475. delete(Content,IndexBegin+1,PEnd-SectionFirstLine);
  23476. // insert section content
  23477. insert(NewSectionContent,Content,IndexBegin+1);
  23478. end;
  23479. procedure ReplaceSection(var Content: RawUTF8; const SectionName,
  23480. NewSectionContent: RawUTF8);
  23481. var UpperSection: array[byte] of AnsiChar;
  23482. P: PUTF8Char;
  23483. begin
  23484. P := pointer(Content);
  23485. PWord(UpperCopy255(UpperSection,SectionName))^ := ord(']');
  23486. if FindSectionFirstLine(P,UpperSection) then
  23487. ReplaceSection(P,Content,NewSectionContent) else
  23488. Content := Content+'['+SectionName+']'#13#10+NewSectionContent;
  23489. end;
  23490. function FindIniNameValueInteger(P: PUTF8Char; UpperName: PAnsiChar): integer;
  23491. begin
  23492. result := GetInteger(pointer(FindIniNameValue(P,UpperName)));
  23493. end;
  23494. function FindIniEntry(const Content, Section, Name: RawUTF8): RawUTF8;
  23495. var P: PUTF8Char;
  23496. UpperSection, UpperName: array[byte] of AnsiChar;
  23497. // possible GPF if length(Section/Name)>255, but should const in code
  23498. begin
  23499. result := '';
  23500. P := pointer(Content);
  23501. if P=nil then exit;
  23502. // UpperName := UpperCase(Name)+'=';
  23503. PWord(UpperCopy255(UpperName,Name))^ := ord('=');
  23504. if Section='' then
  23505. // find the Name= entry before any [Section]
  23506. result := FindIniNameValue(P,UpperName) else begin
  23507. // find the Name= entry in the specified [Section]
  23508. PWord(UpperCopy255(UpperSection,Section))^ := ord(']');
  23509. if FindSectionFirstLine(P,UpperSection) then
  23510. result := FindIniNameValue(P,UpperName);
  23511. end;
  23512. end;
  23513. function FindWinAnsiIniEntry(const Content, Section,Name: RawUTF8): RawUTF8;
  23514. begin
  23515. result := WinAnsiToUtf8(WinAnsiString(FindIniEntry(Content,Section,Name)));
  23516. end;
  23517. function FindIniEntryInteger(const Content,Section,Name: RawUTF8): integer;
  23518. begin
  23519. result := GetInteger(pointer(FindIniEntry(Content,Section,Name)));
  23520. end;
  23521. function FindIniEntryFile(const FileName: TFileName; const Section,Name: RawUTF8): RawUTF8;
  23522. var Content: RawUTF8;
  23523. begin
  23524. Content := StringFromFile(FileName);
  23525. if Content='' then
  23526. result := '' else
  23527. result := FindIniEntry(Content,Section,Name);
  23528. end;
  23529. procedure UpdateIniEntry(var Content: RawUTF8; const Section,Name,Value: RawUTF8);
  23530. const CRLF = #13#10;
  23531. var P: PUTF8Char;
  23532. PBeg: PUTF8Char;
  23533. SectionFound: boolean;
  23534. i, UpperNameLength: PtrInt;
  23535. V: RawUTF8;
  23536. UpperSection, UpperName: array[byte] of AnsiChar;
  23537. // possible GPF if length(Section/Name)>255, but should be short const in code
  23538. label Sec;
  23539. begin
  23540. UpperNameLength := length(Name);
  23541. PWord(UpperCopy255Buf(UpperName,pointer(Name),UpperNameLength))^ := ord('=');
  23542. inc(UpperNameLength);
  23543. V := Value+CRLF;
  23544. P := pointer(Content);
  23545. // 1. find Section, and try update within it
  23546. if Section='' then
  23547. goto Sec; // find the Name= entry before any [Section]
  23548. SectionFound := false;
  23549. PWord(UpperCopy255(UpperSection,Section))^ := ord(']');
  23550. if FindSectionFirstLine(P,UpperSection) then begin
  23551. Sec:SectionFound := true;
  23552. while (P<>nil) and (P^<>'[') do begin
  23553. PBeg := GetNextLineBegin(P,P); // since PBeg=P, we have PBeg<>nil
  23554. while PBeg^=' ' do inc(PBeg); // trim left ' '
  23555. if IdemPChar(PBeg,UpperName) then begin
  23556. // update Name=Value entry
  23557. inc(PBeg,UpperNameLength);
  23558. i := (PBeg-pointer(Content))+1;
  23559. if (i=length(Value)) and CompareMem(PBeg,pointer(Value),i) then
  23560. exit; // new Value is identical to the old one -> no change
  23561. if P=nil then // avoid last line (P-PBeg) calculation error
  23562. SetLength(Content,i-1) else
  23563. delete(Content,i,P-PBeg); // delete old Value
  23564. insert(V,Content,i); // set new value
  23565. exit;
  23566. end;
  23567. end;
  23568. // we reached next [Section] without having found Name=
  23569. end;
  23570. // 2. section or Name= entry not found: add Name=Value
  23571. V := Name+'='+V;
  23572. if not SectionFound then
  23573. // create not existing [Section]
  23574. V := '['+Section+(']'+CRLF)+V;
  23575. // insert Name=Value at P^ (end of file or end of [Section])
  23576. if P=nil then
  23577. // insert at end of file
  23578. Content := Content+V else begin
  23579. // insert at end of [Section]
  23580. i := (P-pointer(Content))+1;
  23581. insert(V,Content,i);
  23582. end;
  23583. end;
  23584. procedure UpdateIniEntryFile(const FileName: TFileName; const Section,Name,Value: RawUTF8);
  23585. var Content: RawUTF8;
  23586. begin
  23587. Content := StringFromFile(FileName);
  23588. UpdateIniEntry(Content,Section,Name,Value);
  23589. FileFromString(Content,FileName);
  23590. end;
  23591. function StringFromFile(const FileName: TFileName): RawByteString;
  23592. var F: THandle;
  23593. Size: integer;
  23594. begin
  23595. result := '';
  23596. if FileName='' then
  23597. exit;
  23598. F := FileOpenSequentialRead(FileName);
  23599. if PtrInt(F)>=0 then begin
  23600. Size := GetFileSize(F,nil);
  23601. if Size>0 then begin
  23602. SetLength(result,Size);
  23603. if FileRead(F,pointer(result)^,Size)<>Size then
  23604. result := '';
  23605. end;
  23606. FileClose(F);
  23607. end;
  23608. end;
  23609. function FileFromString(const Content: RawByteString; const FileName: TFileName;
  23610. FlushOnDisk: boolean=false): boolean;
  23611. var F: THandle;
  23612. L: integer;
  23613. begin
  23614. result := false;
  23615. if FileName='' then
  23616. exit;
  23617. F := FileCreate(FileName);
  23618. if PtrInt(F)<0 then
  23619. exit;
  23620. if pointer(Content)<>nil then
  23621. L := FileWrite(F,pointer(Content)^,length(Content)) else
  23622. L := 0;
  23623. result := (L=length(Content));
  23624. {$ifdef MSWINDOWS}
  23625. if FlushOnDisk then
  23626. FlushFileBuffers(F);
  23627. {$endif}
  23628. FileClose(F);
  23629. end;
  23630. type
  23631. TTextFileKind = (isUnicode, isUTF8, isAnsi);
  23632. function TextFileKind(const Map: TMemoryMap): TTextFileKind;
  23633. begin
  23634. result := isAnsi;
  23635. if (Map.Buffer<>nil) and (Map.Size>3) then
  23636. if PWord(Map.Buffer)^=$FEFF then
  23637. result := isUnicode else
  23638. if (PWord(Map.Buffer)^=$BBEF) and (PByteArray(Map.Buffer)[2]=$BF) then
  23639. result := isUTF8;
  23640. end;
  23641. function AnyTextFileToSynUnicode(const FileName: TFileName; ForceUTF8: boolean): SynUnicode;
  23642. var Map: TMemoryMap;
  23643. begin
  23644. result := '';
  23645. if Map.Map(FileName) then
  23646. try
  23647. if ForceUTF8 then
  23648. UTF8ToSynUnicode(PUTF8Char(Map.Buffer),Map.Size,Result) else
  23649. case TextFileKind(Map) of
  23650. isUnicode:
  23651. SetString(result,PWideChar(PtrInt(Map.Buffer)+2),(Map.Size-2) shr 1);
  23652. isUTF8:
  23653. UTF8ToSynUnicode(PUTF8Char(pointer(PtrInt(Map.Buffer)+3)),Map.Size-3,Result);
  23654. isAnsi:
  23655. result := CurrentAnsiConvert.AnsiToUnicodeString(Map.Buffer, Map.Size);
  23656. end;
  23657. finally
  23658. Map.UnMap;
  23659. end;
  23660. end;
  23661. function AnyTextFileToRawUTF8(const FileName: TFileName; AssumeUTF8IfNoBOM: boolean): RawUTF8;
  23662. var Map: TMemoryMap;
  23663. begin
  23664. result := '';
  23665. if Map.Map(FileName) then
  23666. try
  23667. case TextFileKind(Map) of
  23668. isUnicode:
  23669. RawUnicodeToUtf8(PWideChar(PtrInt(Map.Buffer)+2),(Map.Size-2) shr 1,Result);
  23670. isUTF8:
  23671. SetString(result,PAnsiChar(pointer(PtrInt(Map.Buffer)+3)),Map.Size-3);
  23672. isAnsi:
  23673. if AssumeUTF8IfNoBOM then
  23674. SetString(result,PAnsiChar(Map.Buffer),Map.Size) else
  23675. result := CurrentAnsiConvert.AnsiBufferToRawUTF8(Map.Buffer, Map.Size);
  23676. end;
  23677. finally
  23678. Map.UnMap;
  23679. end;
  23680. end;
  23681. function AnyTextFileToString(const FileName: TFileName; ForceUTF8: boolean): string;
  23682. var Map: TMemoryMap;
  23683. begin
  23684. result := '';
  23685. if Map.Map(FileName) then
  23686. try
  23687. if ForceUTF8 then
  23688. {$ifdef UNICODE}
  23689. UTF8DecodeToString(PUTF8Char(Map.Buffer),Map.Size,result) {$else}
  23690. result := CurrentAnsiConvert.UTF8BufferToAnsi(PUTF8Char(Map.Buffer),Map.Size)
  23691. {$endif} else
  23692. case TextFileKind(Map) of
  23693. {$ifdef UNICODE}
  23694. isUnicode:
  23695. SetString(result,PWideChar(PtrInt(Map.Buffer)+2),(Map.Size-2) shr 1);
  23696. isUTF8:
  23697. UTF8DecodeToString(pointer(PtrInt(Map.Buffer)+3),Map.Size-3,result);
  23698. isAnsi:
  23699. result := CurrentAnsiConvert.AnsiToUnicodeString(Map.Buffer,Map.Size);
  23700. {$else}
  23701. isUnicode:
  23702. result := CurrentAnsiConvert.UnicodeBufferToAnsi(PWideChar(PtrInt(Map.Buffer)+2),(Map.Size-2) shr 1);
  23703. isUTF8:
  23704. result := CurrentAnsiConvert.UTF8BufferToAnsi(pointer(PtrInt(Map.Buffer)+3),Map.Size-3);
  23705. isAnsi:
  23706. SetString(result,PAnsiChar(Map.Buffer),Map.Size);
  23707. {$endif}
  23708. end;
  23709. finally
  23710. Map.UnMap;
  23711. end;
  23712. end;
  23713. function StreamToRawByteString(aStream: TStream): RawByteString;
  23714. var current, size: Int64;
  23715. begin
  23716. result := '';
  23717. if aStream=nil then
  23718. exit;
  23719. current := aStream.Position;
  23720. if (current=0) and aStream.InheritsFrom(TRawByteStringStream) then begin
  23721. result := TRawByteStringStream(aStream).DataString; // fast COW
  23722. exit;
  23723. end;
  23724. size := aStream.Size-current;
  23725. if (size=0) or (size>maxInt) then
  23726. exit;
  23727. SetLength(result,size);
  23728. aStream.Read(pointer(result)^,size);
  23729. aStream.Position := current;
  23730. end;
  23731. function RawByteStringToStream(const aString: RawByteString): TStream;
  23732. begin
  23733. result := TRawByteStringStream.Create(aString);
  23734. end;
  23735. function ReadStringFromStream(S: TStream; MaxAllowedSize: integer): RawUTF8;
  23736. var L: integer;
  23737. begin
  23738. result := '';
  23739. L := 0;
  23740. if (S.Read(L,4)<>4) or (L<=0) or (L>MaxAllowedSize) then
  23741. exit;
  23742. SetLength(result,L);
  23743. if S.Read(pointer(result)^,L)<>L then
  23744. result := '';
  23745. end;
  23746. function WriteStringToStream(S: TStream; const Text: RawUTF8): boolean;
  23747. var L: integer;
  23748. begin
  23749. L := length(Text);
  23750. if L=0 then
  23751. result := S.Write(L,4)=4 else
  23752. {$ifdef FPC}
  23753. result := (S.Write(L,4)=4) and (S.Write(pointer(Text)^,L)=L);
  23754. {$else}
  23755. result := S.Write(pointer(PtrInt(Text)-sizeof(integer))^,L+4)=L+4;
  23756. {$endif}
  23757. end;
  23758. function GetFileNameWithoutExt(const FileName: TFileName): TFileName;
  23759. var i, max: PtrInt;
  23760. begin
  23761. i := length(FileName);
  23762. max := i-8;
  23763. while (i>0) and not(cardinal(FileName[i]) in [ord('\'),ord('/'),ord('.')])
  23764. and (i>=max) do dec(i);
  23765. if (i=0) or (FileName[i]<>'.') then
  23766. result := FileName else
  23767. SetString(result,PChar(pointer(FileName)),i-1);
  23768. end;
  23769. function GetFileNameExtIndex(const FileName, CSVExt: TFileName): integer;
  23770. var Ext: TFileName;
  23771. P: PChar;
  23772. begin
  23773. result := -1;
  23774. P := pointer(CSVExt);
  23775. Ext := ExtractFileExt(FileName);
  23776. if (P=nil) or (Ext='') or (Ext[1]<>'.') then
  23777. exit;
  23778. delete(Ext,1,1);
  23779. repeat
  23780. inc(result);
  23781. if SameText(GetNextItemString(P),Ext) then
  23782. exit;
  23783. until P=nil;
  23784. result := -1;
  23785. end;
  23786. function FileSize(const FileName: TFileName): Int64;
  23787. {$ifdef MSWINDOWS}
  23788. var FA: WIN32_FILE_ATTRIBUTE_DATA;
  23789. begin // 5 times faster than CreateFile, GetFileSizeEx, CloseHandle
  23790. if GetFileAttributesEx(pointer(FileName),GetFileExInfoStandard,@FA) then begin
  23791. PInt64Rec(@result)^.Lo := FA.nFileSizeLow;
  23792. PInt64Rec(@result)^.Hi := FA.nFileSizeHigh;
  23793. end else
  23794. result := 0;
  23795. end;
  23796. {$else}
  23797. var f: THandle;
  23798. res: Int64Rec absolute result;
  23799. begin
  23800. result := 0;
  23801. f := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
  23802. if PtrInt(f)>0 then begin
  23803. res.Lo := GetFileSize(f,@res.Hi); // from SynKylix/SynFPCLinux
  23804. FileClose(f);
  23805. end;
  23806. end;
  23807. {$endif}
  23808. function FileAgeToDateTime(const FileName: TFileName): TDateTime;
  23809. {$ifdef MSWINDOWS}
  23810. var FA: WIN32_FILE_ATTRIBUTE_DATA;
  23811. ST,LT: TSystemTime;
  23812. begin // 5 times faster than CreateFile, GetFileSizeEx, CloseHandle
  23813. if GetFileAttributesEx(pointer(FileName),GetFileExInfoStandard,@FA) and
  23814. FileTimeToSystemTime(FA.ftLastWriteTime,ST) and
  23815. SystemTimeToTzSpecificLocalTime(nil,ST,LT) then
  23816. result := SystemTimeToDateTime(LT) else
  23817. result := 0;
  23818. end;
  23819. {$else}
  23820. {$ifdef HASNEWFILEAGE}
  23821. begin
  23822. if not FileAge(FileName,result) then
  23823. {$else}
  23824. var Age: integer;
  23825. begin
  23826. Age := FileAge(FileName);
  23827. if Age<>-1 then
  23828. result := FileDateToDateTime(Age) else
  23829. {$endif HASNEWFILEAGE}
  23830. result := 0;
  23831. end;
  23832. {$endif MSWINDOWS}
  23833. function CopyFile(const Source, Target: TFileName; FailIfExists: boolean): boolean;
  23834. {$ifdef MSWINDOWS}
  23835. begin
  23836. result := Windows.CopyFile(pointer(Source),pointer(Target),FailIfExists);
  23837. end;
  23838. {$else}
  23839. var SourceF, DestF: TFileStream;
  23840. begin
  23841. result := false;
  23842. if FailIfExists then
  23843. if FileExists(Target) then
  23844. exit else
  23845. DeleteFile(Target);
  23846. try
  23847. SourceF := TFileStream.Create(Source,fmOpenRead);
  23848. try
  23849. DestF := TFileStream.Create(Target,fmCreate);
  23850. try
  23851. DestF.CopyFrom(SourceF, SourceF.Size);
  23852. finally
  23853. DestF.Free;
  23854. end;
  23855. FileSetDateFrom(Target,SourceF.Handle);
  23856. finally
  23857. SourceF.Free;
  23858. end;
  23859. result := true;
  23860. except
  23861. result := false;
  23862. end;
  23863. end;
  23864. {$endif}
  23865. function SearchRecToDateTime(const F: TSearchRec): TDateTime;
  23866. begin
  23867. {$ifdef ISDELPHIXE}
  23868. result := F.TimeStamp;
  23869. {$else}
  23870. result := FileDateToDateTime(F.Time);
  23871. {$endif}
  23872. end;
  23873. function DirectoryDelete(const Directory: TFileName; const Mask: TFileName;
  23874. DeleteOnlyFilesNotDirectory: Boolean; DeletedCount: PInteger): Boolean;
  23875. var F: TSearchRec;
  23876. Dir: TFileName;
  23877. n: integer;
  23878. begin
  23879. n := 0;
  23880. result := true;
  23881. if DirectoryExists(Directory) then begin
  23882. Dir := IncludeTrailingPathDelimiter(Directory);
  23883. if FindFirst(Dir+Mask,faAnyFile-faDirectory,F)=0 then begin
  23884. repeat
  23885. {$ifndef DELPHI5OROLDER}
  23886. {$WARN SYMBOL_DEPRECATED OFF} // for faVolumeID
  23887. {$endif}
  23888. if (F.Attr and (faDirectory+faVolumeID+faSysFile+faHidden)=0) and
  23889. (F.Name[1]<>'.') then
  23890. if DeleteFile(Dir+F.Name) then
  23891. inc(n) else
  23892. result := false;
  23893. {$ifndef DELPHI5OROLDER}
  23894. {$WARN SYMBOL_DEPRECATED ON}
  23895. {$endif}
  23896. until FindNext(F)<>0;
  23897. FindClose(F);
  23898. end;
  23899. if (not DeleteOnlyFilesNotDirectory) and (not RemoveDir(Dir)) then
  23900. result := false;
  23901. end;
  23902. if DeletedCount<>nil then
  23903. DeletedCount^ := n;
  23904. end;
  23905. function DirectoryDeleteOlderFiles(const Directory: TFileName; TimePeriod: TDateTime;
  23906. const Mask: TFileName; Recursive: Boolean): Boolean;
  23907. var F: TSearchRec;
  23908. Dir: TFileName;
  23909. old: TDateTime;
  23910. begin
  23911. result := true;
  23912. if (Directory='') or not DirectoryExists(Directory) then
  23913. exit;
  23914. Dir := IncludeTrailingPathDelimiter(Directory);
  23915. if FindFirst(Dir+Mask,faAnyFile,F)=0 then begin
  23916. old := Now - TimePeriod;
  23917. repeat
  23918. if F.Name[1]<>'.' then
  23919. if Recursive and (F.Attr and faDirectory<>0) then
  23920. DirectoryDeleteOlderFiles(Dir+F.Name,TimePeriod,Mask,true) else
  23921. {$ifndef DELPHI5OROLDER}
  23922. {$WARN SYMBOL_DEPRECATED OFF} // for faVolumeID
  23923. {$endif}
  23924. if (F.Attr and (faDirectory+faVolumeID+faSysFile+faHidden)=0) then
  23925. if SearchRecToDateTime(F) < old then
  23926. if not DeleteFile(Dir+F.Name) then
  23927. result := false;
  23928. {$ifndef DELPHI5OROLDER}
  23929. {$WARN SYMBOL_DEPRECATED ON}
  23930. {$endif}
  23931. until FindNext(F)<>0;
  23932. FindClose(F);
  23933. end;
  23934. end;
  23935. procedure TFindFiles.FromSearchRec(const Directory: TFileName; const F: TSearchRec);
  23936. begin
  23937. Name := Directory+F.Name;
  23938. {$ifdef MSWINDOWS}
  23939. {$ifdef HASINLINE} // FPC or Delphi 2006+
  23940. Size := F.Size;
  23941. {$else} // F.Size was limited to 32 bits on older Delphi
  23942. Size := F.FindData.nFileSizeLow or Int64(F.FindData.nFileSizeHigh) shl 32;
  23943. {$endif}
  23944. {$else}
  23945. Size := F.Size;
  23946. {$endif}
  23947. Attr := F.Attr;
  23948. TimeStamp := SearchRecToDateTime(F);
  23949. end;
  23950. function FindFiles(const Directory,Mask,IgnoreFileName: TFileName;
  23951. SortByName, IncludesDir: boolean): TFindFilesDynArray;
  23952. var F: TSearchRec;
  23953. n: integer;
  23954. Dir: TFileName;
  23955. da: TDynArray;
  23956. masks: TRawUTF8DynArray;
  23957. masked: TFindFilesDynArray;
  23958. begin
  23959. result := nil;
  23960. n := 0;
  23961. da.Init(TypeInfo(TFindFilesDynArray),result);
  23962. if Pos(';',Mask)>0 then
  23963. CSVToRawUTF8DynArray(pointer(StringToUTF8(Mask)),masks,';');
  23964. if masks<>nil then begin
  23965. if SortByName then
  23966. QuickSortRawUTF8(masks,length(masks),nil,@StrIComp);
  23967. for n := 0 to high(masks) do begin
  23968. masked := FindFiles(Directory,UTF8ToString(masks[n]),
  23969. IgnoreFileName,SortByName,IncludesDir);
  23970. da.AddArray(masked);
  23971. end;
  23972. end else begin
  23973. Dir := IncludeTrailingPathDelimiter(Directory);
  23974. if FindFirst(Dir+Mask,faAnyfile-faDirectory,F)=0 then begin
  23975. repeat
  23976. {$ifndef DELPHI5OROLDER}
  23977. {$WARN SYMBOL_DEPRECATED OFF} // for faVolumeID
  23978. {$endif}
  23979. if (F.Attr and (faDirectory+faVolumeID+faSysFile+faHidden)=0) and
  23980. (F.Name[1]<>'.') and ((IgnoreFileName='') or
  23981. (AnsiCompareFileName(F.Name,IgnoreFileName)<>0)) then begin
  23982. if n=length(result) then
  23983. SetLength(result,n+n shr 3+8);
  23984. if IncludesDir then
  23985. result[n].FromSearchRec(Dir,F) else
  23986. result[n].FromSearchRec('',F);
  23987. inc(n);
  23988. end;
  23989. {$ifndef DELPHI5OROLDER}
  23990. {$WARN SYMBOL_DEPRECATED ON}
  23991. {$endif}
  23992. until FindNext(F)<>0;
  23993. FindClose(F);
  23994. if n=0 then
  23995. exit;
  23996. SetLength(result,n);
  23997. end;
  23998. if SortByName and (n>0) then
  23999. da.Sort(SortDynArrayStringI);
  24000. end;
  24001. end;
  24002. function EnsureDirectoryExists(const Directory: TFileName;
  24003. RaiseExceptionOnCreationFailure: boolean=false): TFileName;
  24004. begin
  24005. result := IncludeTrailingPathDelimiter(ExpandFileName(Directory));
  24006. if not DirectoryExists(result) then
  24007. if not CreateDir(result) then
  24008. if not RaiseExceptionOnCreationFailure then
  24009. result := '' else
  24010. raise ESynException.CreateUTF8('Impossible to create "%" folder',[Directory]);
  24011. end;
  24012. {$ifdef DELPHI5OROLDER}
  24013. /// DirectoryExists returns a boolean value that indicates whether the
  24014. // specified directory exists (and is actually a directory)
  24015. function DirectoryExists(const Directory: string): boolean;
  24016. var Code: Integer;
  24017. begin
  24018. Code := GetFileAttributes(pointer(Directory));
  24019. result := (Code<>-1) and (FILE_ATTRIBUTE_DIRECTORY and Code<>0);
  24020. end;
  24021. function GetEnvironmentVariable(const Name: string): string;
  24022. var Len: Integer;
  24023. Buffer: array[0..1023] of Char;
  24024. begin
  24025. Result := '';
  24026. Len := Windows.GetEnvironmentVariable(pointer(Name),@Buffer,SizeOf(Buffer));
  24027. if Len<SizeOf(Buffer) then
  24028. SetString(result,Buffer,Len) else begin
  24029. SetLength(result,Len-1);
  24030. Windows.GetEnvironmentVariable(pointer(Name),pointer(result),Len);
  24031. end;
  24032. end;
  24033. function GetModuleName(Module: HMODULE): TFileName;
  24034. var tmp: array[byte] of char;
  24035. begin
  24036. SetString(Result,tmp,GetModuleFileName(Module,tmp,SizeOf(tmp)));
  24037. end;
  24038. function TryEncodeTime(Hour, Min, Sec, MSec: Word; var Time: TDateTime): Boolean;
  24039. begin
  24040. if (Hour<24) and (Min<60) and (Sec<60) and (MSec<1000) then begin
  24041. Time := (Hour*3600000+Min*60000+Sec*1000+MSec)/MSecsPerDay;
  24042. result := true;
  24043. end else
  24044. result := false;
  24045. end;
  24046. function ExcludeTrailingPathDelimiter(const FileName: TFileName): TFileName;
  24047. begin
  24048. result := ExcludeTrailingBackslash(FileName);
  24049. end;
  24050. function IncludeTrailingPathDelimiter(const FileName: TFileName): TFileName;
  24051. begin
  24052. result := IncludeTrailingBackslash(FileName);
  24053. end;
  24054. procedure RaiseLastOSError;
  24055. var LastError: Integer;
  24056. Error: EOSError;
  24057. begin
  24058. LastError := GetLastError;
  24059. if LastError <> 0 then
  24060. Error := EOSError.CreateFmt('System Error. Code: %d.'#13#10'%s',
  24061. [LastError,SysErrorMessage(LastError)]) else
  24062. Error := EOSError.Create('A call to an OS function failed');
  24063. Error.ErrorCode := LastError;
  24064. raise Error;
  24065. end;
  24066. {$endif DELPHI5OROLDER}
  24067. function FileSetDateFrom(const Dest: TFileName; SourceHandle: integer): boolean;
  24068. {$ifdef MSWINDOWS}
  24069. var FileTime: TFileTime;
  24070. D: THandle;
  24071. begin
  24072. D := FileOpen(Dest,fmOpenWrite);
  24073. if D<>THandle(-1) then begin
  24074. result := GetFileTime(SourceHandle,nil,nil,@FileTime) and
  24075. SetFileTime(D,nil,nil,@FileTime);
  24076. FileClose(D);
  24077. end else
  24078. result := false;
  24079. end;
  24080. {$else}
  24081. begin
  24082. result := FileSetDate(Dest,FileGetDate(SourceHandle))=0;
  24083. end;
  24084. {$endif}
  24085. {$IFDEF PUREPASCAL}
  24086. {$IFNDEF HASCODEPAGE}
  24087. function Pos(const substr, str: RawUTF8): Integer; overload;
  24088. begin // the RawByteString version is fast enough
  24089. Result := PosEx(substr,str,1);
  24090. end;
  24091. {$ENDIF}
  24092. {$ENDIF}
  24093. function FindObjectEntry(const Content, Name: RawUTF8): RawUTF8;
  24094. var L: integer;
  24095. begin
  24096. result := Trim(FindIniEntry(Content,'',Name+' ')); // 'Name = Value' format
  24097. if (result<>'') and (result[1]='''') then begin
  24098. L := length(result);
  24099. if result[L]='''' then
  24100. result := copy(result,2,L-2); // 'testDI6322.IAS' -> testDI6322.IAS
  24101. end;
  24102. end;
  24103. function FindObjectEntryWithoutExt(const Content, Name: RawUTF8): RawUTF8;
  24104. begin
  24105. result := RawUTF8(GetFileNameWithoutExt(
  24106. ExtractFileName(TFileName(FindObjectEntry(Content,Name)))));
  24107. end;
  24108. function IntegerScanExists(P: PCardinalArray; Count: PtrInt; Value: cardinal): boolean;
  24109. {$ifdef PUREPASCAL}
  24110. var i: PtrInt; // very optimized code for speed
  24111. begin
  24112. if P<>nil then begin
  24113. result := true;
  24114. for i := 1 to (Count shr 2) do // 4 DWORD by loop - aligned read
  24115. if (P^[0]=Value) or (P^[1]=Value) or
  24116. (P^[2]=Value) or (P^[3]=Value) then
  24117. exit else
  24118. inc(PtrUInt(P),sizeof(P^[0])*4);
  24119. for i := 0 to (Count and 3)-1 do // last 0..3 DWORD
  24120. if P^[i]=Value then
  24121. exit;
  24122. end;
  24123. result := false;
  24124. end;
  24125. {$else}
  24126. asm // eax=P, edx=Count, Value=ecx
  24127. test eax,eax
  24128. jz @z // avoid GPF
  24129. cmp edx,8
  24130. jae @s1
  24131. jmp dword ptr [edx*4+@Table]
  24132. nop // align @Table
  24133. @Table: dd @z, @1, @2, @3, @4, @5, @6, @7
  24134. @s1: // fast search by 8 integers (pipelined instructions)
  24135. sub edx,8
  24136. cmp [eax],ecx; je @ok
  24137. cmp [eax+4],ecx; je @ok
  24138. cmp [eax+8],ecx; je @ok
  24139. cmp [eax+12],ecx; je @ok
  24140. cmp [eax+16],ecx; je @ok
  24141. cmp [eax+20],ecx; je @ok
  24142. cmp [eax+24],ecx; je @ok
  24143. cmp [eax+28],ecx; je @ok
  24144. cmp edx,8
  24145. lea eax,[eax+32] // preserve flags during 'cmp edx,8' computation
  24146. @s2: jae @s1
  24147. jmp dword ptr [edx*4+@Table]
  24148. @7: cmp [eax+24],ecx; je @ok
  24149. @6: cmp [eax+20],ecx; je @ok
  24150. @5: cmp [eax+16],ecx; je @ok
  24151. @4: cmp [eax+12],ecx; je @ok
  24152. @3: cmp [eax+8],ecx; je @ok
  24153. @2: cmp [eax+4],ecx; je @ok
  24154. @1: cmp [eax],ecx; je @ok
  24155. @z: xor eax,eax
  24156. ret
  24157. @ok: mov al,1
  24158. end;
  24159. {$endif}
  24160. function Int64ScanExists(P: PInt64Array; Count: PtrInt; const Value: Int64): boolean;
  24161. var i: PtrInt;
  24162. begin
  24163. if P<>nil then begin
  24164. result := true;
  24165. for i := 1 to (Count shr 2) do // 4 QWORD by loop - aligned read
  24166. if (P^[0]=Value) or (P^[1]=Value) or
  24167. (P^[2]=Value) or (P^[3]=Value) then
  24168. exit else
  24169. inc(PtrUInt(P),sizeof(P^[0])*4);
  24170. for i := 0 to (Count and 3)-1 do // last 0..3 QWORD
  24171. if P^[i]=Value then
  24172. exit;
  24173. end;
  24174. result := false;
  24175. end;
  24176. function IntegerScan(P: PCardinalArray; Count: PtrInt; Value: cardinal): PCardinal;
  24177. {$ifdef PUREPASCAL}
  24178. var i: PtrInt;
  24179. begin // very optimized code
  24180. if P<>nil then begin
  24181. for i := 1 to Count shr 2 do // 4 DWORD by loop - aligned read
  24182. if P^[0]<>Value then
  24183. if P^[1]<>Value then
  24184. if P^[2]<>Value then
  24185. if P^[3]=Value then begin
  24186. result := @P^[3];
  24187. exit;
  24188. end else
  24189. inc(PtrUInt(P),sizeof(P^[0])*4) else begin
  24190. result := @P^[2];
  24191. exit;
  24192. end else begin
  24193. result := @P^[1];
  24194. exit;
  24195. end else begin
  24196. result := pointer(P);
  24197. exit;
  24198. end;
  24199. for i := 0 to (Count and 3)-1 do // last 0..3 DWORD
  24200. if P^[i]=Value then begin
  24201. result := @P^[i];
  24202. exit;
  24203. end;
  24204. end;
  24205. result := nil;
  24206. end;
  24207. {$else}
  24208. asm // eax=P, edx=Count, Value=ecx
  24209. test eax,eax
  24210. jz @ok0 // avoid GPF
  24211. cmp edx,8
  24212. jb @s2
  24213. nop; nop; nop // @s1 loop align
  24214. @s1: sub edx,8
  24215. cmp [eax],ecx; je @ok0
  24216. cmp [eax+4],ecx; je @ok4
  24217. cmp [eax+8],ecx; je @ok8
  24218. cmp [eax+12],ecx; je @ok12
  24219. cmp [eax+16],ecx; je @ok16
  24220. cmp [eax+20],ecx; je @ok20
  24221. cmp [eax+24],ecx; je @ok24
  24222. cmp [eax+28],ecx; je @ok28
  24223. cmp edx,8
  24224. lea eax,[eax+32] // preserve flags during 'cmp edx,8' computation
  24225. jae @s1
  24226. @s2: test edx,edx; jz @z
  24227. cmp [eax],ecx; je @ok0; dec edx; jz @z
  24228. cmp [eax+4],ecx; je @ok4; dec edx; jz @z
  24229. cmp [eax+8],ecx; je @ok8; dec edx; jz @z
  24230. cmp [eax+12],ecx; je @ok12; dec edx; jz @z
  24231. cmp [eax+16],ecx; je @ok16; dec edx; jz @z
  24232. cmp [eax+20],ecx; je @ok20; dec edx; jz @z
  24233. cmp [eax+24],ecx; je @ok24
  24234. @z: xor eax,eax // return nil if not found
  24235. ret
  24236. @ok0: rep ret
  24237. @ok28: lea eax,[eax+28]; ret
  24238. @ok24: lea eax,[eax+24]; ret
  24239. @ok20: lea eax,[eax+20]; ret
  24240. @ok16: lea eax,[eax+16]; ret
  24241. @ok12: lea eax,[eax+12]; ret
  24242. @ok8: lea eax,[eax+8]; ret
  24243. @ok4: lea eax,[eax+4]
  24244. end;
  24245. {$endif}
  24246. function Int64Scan(P: PInt64Array; Count: PtrInt; const Value: Int64): PInt64;
  24247. var i: PtrInt;
  24248. begin
  24249. if P<>nil then begin
  24250. for i := 1 to Count shr 2 do // 4 QWORD by loop - aligned read
  24251. if P^[0]<>Value then
  24252. if P^[1]<>Value then
  24253. if P^[2]<>Value then
  24254. if P^[3]=Value then begin
  24255. result := @P^[3];
  24256. exit;
  24257. end else
  24258. inc(PtrUInt(P),sizeof(P^[0])*4) else begin
  24259. result := @P^[2];
  24260. exit;
  24261. end else begin
  24262. result := @P^[1];
  24263. exit;
  24264. end else begin
  24265. result := pointer(P);
  24266. exit;
  24267. end;
  24268. for i := 0 to (Count and 3)-1 do // last 0..3 QWORD
  24269. if P^[i]=Value then begin
  24270. result := @P^[i];
  24271. exit;
  24272. end;
  24273. end;
  24274. result := nil;
  24275. end;
  24276. function AddInteger(var Values: TIntegerDynArray; Value: integer;
  24277. NoDuplicates: boolean=false): boolean;
  24278. var n: PtrInt;
  24279. begin
  24280. n := Length(Values);
  24281. if NoDuplicates and IntegerScanExists(pointer(Values),n,Value) then begin
  24282. result := false;
  24283. exit;
  24284. end;
  24285. SetLength(Values,n+1);
  24286. Values[n] := Value;
  24287. result := true
  24288. end;
  24289. procedure AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  24290. Value: integer);
  24291. begin
  24292. if ValuesCount=length(Values) then
  24293. SetLength(Values,ValuesCount+256+ValuesCount shr 3);
  24294. Values[ValuesCount] := Value;
  24295. inc(ValuesCount);
  24296. end;
  24297. function AddInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  24298. Value: integer; NoDuplicates: boolean): boolean; overload;
  24299. begin
  24300. if NoDuplicates and IntegerScanExists(pointer(Values),ValuesCount,Value) then begin
  24301. result := false;
  24302. exit;
  24303. end;
  24304. if ValuesCount=length(Values) then
  24305. SetLength(Values,ValuesCount+256+ValuesCount shr 3);
  24306. Values[ValuesCount] := Value;
  24307. inc(ValuesCount);
  24308. result := true
  24309. end;
  24310. procedure AddInt64(var Values: TInt64DynArray; var ValuesCount: integer; Value: Int64);
  24311. begin
  24312. if ValuesCount=length(Values) then
  24313. SetLength(Values,ValuesCount+256+ValuesCount shr 3);
  24314. Values[ValuesCount] := Value;
  24315. inc(ValuesCount);
  24316. end;
  24317. procedure AddInt64(var Values: TInt64DynArray; Value: Int64);
  24318. var n: integer;
  24319. begin
  24320. n := length(Values);
  24321. SetLength(Values,n+1);
  24322. Values[n] := Value;
  24323. end;
  24324. procedure DeleteInteger(var Values: TIntegerDynArray; Index: PtrInt);
  24325. var n: PtrInt;
  24326. begin
  24327. n := Length(Values);
  24328. if PtrUInt(Index)>=PtrUInt(n) then
  24329. exit; // wrong Index
  24330. dec(n);
  24331. if n>Index then
  24332. MoveFast(Values[Index+1],Values[Index],(n-Index)*sizeof(Integer));
  24333. SetLength(Values,n);
  24334. end;
  24335. procedure DeleteInt64(var Values: TInt64DynArray; Index: PtrInt); overload;
  24336. var n: PtrInt;
  24337. begin
  24338. n := Length(Values);
  24339. if PtrUInt(Index)>=PtrUInt(n) then
  24340. exit; // wrong Index
  24341. dec(n);
  24342. if n>Index then
  24343. MoveFast(Values[Index+1],Values[Index],(n-Index)*sizeof(Int64));
  24344. SetLength(Values,n);
  24345. end;
  24346. procedure DeleteInteger(var Values: TIntegerDynArray; var ValuesCount: Integer; Index: PtrInt); overload;
  24347. var n: PtrInt;
  24348. begin
  24349. n := ValuesCount;
  24350. if PtrUInt(Index)>=PtrUInt(n) then
  24351. exit; // wrong Index
  24352. dec(n,Index+1);
  24353. if n>0 then
  24354. MoveFast(Values[Index+1],Values[Index],n*sizeof(Integer));
  24355. dec(ValuesCount);
  24356. end;
  24357. function MaxInteger(const Values: TIntegerDynArray; ValuesCount, MaxStart: integer): Integer;
  24358. var i: integer;
  24359. begin
  24360. result := MaxStart;
  24361. for i := 0 to ValuesCount-1 do
  24362. if Values[i]>result then
  24363. result := Values[i];
  24364. end;
  24365. procedure Reverse(const Values: TIntegerDynArray; ValuesCount: integer;
  24366. Reversed: PIntegerArray);
  24367. var i: integer;
  24368. begin
  24369. i := 0;
  24370. if ValuesCount>=4 then begin
  24371. dec(ValuesCount,4);
  24372. while i<ValuesCount do begin // faster pipelined version
  24373. Reversed[Values[i]] := i;
  24374. Reversed[Values[i+1]] := i+1;
  24375. Reversed[Values[i+2]] := i+2;
  24376. Reversed[Values[i+3]] := i+3;
  24377. inc(i,4);
  24378. end;
  24379. inc(ValuesCount,4);
  24380. end;
  24381. while i<ValuesCount do begin
  24382. Reversed[Values[i]] := i;
  24383. inc(i);
  24384. end;
  24385. //for i := 0 to Count-1 do Assert(Reverse[Orig[i]]=i);
  24386. end;
  24387. procedure FillIncreasing(Values: PIntegerArray; StartValue, Count: integer);
  24388. var i: integer;
  24389. begin
  24390. if StartValue=0 then
  24391. for i := 0 to Count-1 do
  24392. Values[i] := i else
  24393. for i := 0 to Count-1 do
  24394. Values[i] := StartValue+i;
  24395. end;
  24396. procedure Int64ToUInt32(Values64: PInt64Array; Values32: PCardinalArray; Count: integer);
  24397. var i: integer;
  24398. begin
  24399. for i := 0 to Count-1 do
  24400. Values32[i] := Values64[i];
  24401. end;
  24402. procedure CSVToIntegerDynArray(CSV: PUTF8Char; var Result: TIntegerDynArray);
  24403. begin
  24404. while CSV<>nil do begin
  24405. SetLength(Result,length(Result)+1);
  24406. Result[high(Result)] := GetNextItemInteger(CSV);
  24407. end;
  24408. end;
  24409. procedure CSVToInt64DynArray(CSV: PUTF8Char; var Result: TInt64DynArray);
  24410. begin
  24411. while CSV<>nil do begin
  24412. SetLength(Result,length(Result)+1);
  24413. Result[high(Result)] := GetNextItemInt64(CSV);
  24414. end;
  24415. end;
  24416. function IntegerDynArrayToCSV(const Values: array of integer; ValuesCount: integer;
  24417. const Prefix: RawUTF8=''; const Suffix: RawUTF8=''): RawUTF8;
  24418. type
  24419. TInts16 = packed array[word] of string[15]; // shortstring are faster (no heap allocation)
  24420. var i, L, Len: PtrInt;
  24421. tmp: array[0..15] of AnsiChar;
  24422. ints: ^TInts16;
  24423. P: PAnsiChar;
  24424. begin
  24425. result := '';
  24426. if ValuesCount=0 then
  24427. exit;
  24428. GetMem(ints,ValuesCount*sizeof(ints[0])); // getmem is faster than a dynamic array
  24429. try
  24430. // compute whole result length at once
  24431. dec(ValuesCount);
  24432. Len := length(Prefix)+length(Suffix);
  24433. tmp[15] := ',';
  24434. for i := 0 to ValuesCount do begin
  24435. P := StrInt32(@tmp[15],Values[i]);
  24436. L := @tmp[15]-P;
  24437. if i<ValuesCount then
  24438. inc(L); // append tmp[15]=','
  24439. inc(Len,L);
  24440. SetString(ints[i],P,L);
  24441. end;
  24442. // create result
  24443. SetLength(result,Len);
  24444. P := pointer(result);
  24445. if Prefix<>'' then begin
  24446. MoveFast(pointer(Prefix)^,P^,length(Prefix));
  24447. inc(P,length(Prefix));
  24448. end;
  24449. for i := 0 to ValuesCount do begin
  24450. MoveFast(ints[i][1],P^,ord(ints[i][0]));
  24451. inc(P,ord(ints[i][0]));
  24452. end;
  24453. if Suffix<>'' then
  24454. MoveFast(pointer(Suffix)^,P^,length(Suffix));
  24455. finally
  24456. FreeMem(ints);
  24457. end;
  24458. end;
  24459. function Int64DynArrayToCSV(const Values: array of Int64; ValuesCount: integer;
  24460. const Prefix: RawUTF8=''; const Suffix: RawUTF8=''): RawUTF8;
  24461. type
  24462. TInt = packed record
  24463. Len: byte;
  24464. Val: array[0..19] of AnsiChar; // Int64: 19 digits, then - sign
  24465. end;
  24466. var i, L, Len: PtrInt;
  24467. ints: pointer;
  24468. int: ^TInt;
  24469. P: PAnsiChar;
  24470. begin
  24471. result := '';
  24472. if ValuesCount=0 then
  24473. exit;
  24474. GetMem(ints,ValuesCount*sizeof(TInt)); // getmem is faster than a dynamic array
  24475. try
  24476. // compute whole result length at once
  24477. dec(ValuesCount);
  24478. Len := length(Prefix)+length(Suffix);
  24479. int := ints;
  24480. for i := 0 to ValuesCount do begin
  24481. P := StrInt64(PAnsiChar(int)+21,Values[i]);
  24482. L := PAnsiChar(int)+21-P;
  24483. int^.Len := L;
  24484. if i<ValuesCount then
  24485. inc(L); // for ,
  24486. inc(Len,L);
  24487. inc(int);
  24488. end;
  24489. // create result
  24490. SetLength(result,Len);
  24491. P := pointer(result);
  24492. if Prefix<>'' then begin
  24493. MoveFast(pointer(Prefix)^,P^,length(Prefix));
  24494. inc(P,length(Prefix));
  24495. end;
  24496. int := ints;
  24497. repeat
  24498. L := int^.Len;
  24499. MoveFast(PAnsiChar(int)[21-L],P^,L);
  24500. inc(P,L);
  24501. if ValuesCount=0 then
  24502. break;
  24503. inc(int);
  24504. P^ := ',';
  24505. inc(P);
  24506. dec(ValuesCount);
  24507. until false;
  24508. if Suffix<>'' then
  24509. MoveFast(pointer(Suffix)^,P^,length(Suffix));
  24510. finally
  24511. FreeMem(ints);
  24512. end;
  24513. end;
  24514. function IntegerScanIndex(P: PCardinalArray; Count: PtrInt; Value: cardinal): PtrInt;
  24515. {$ifdef PUREPASCAL}
  24516. var i: PtrInt; // very optimized code for speed
  24517. begin
  24518. if P<>nil then begin
  24519. result := 0;
  24520. for i := 1 to Count shr 2 do // 4 DWORD by loop - aligned read
  24521. if P^[0]<>Value then
  24522. if P^[1]<>Value then
  24523. if P^[2]<>Value then
  24524. if P^[3]<>Value then begin
  24525. inc(PtrUInt(P),sizeof(P^[0])*4);
  24526. inc(result,4);
  24527. end else begin
  24528. inc(result,3);
  24529. exit;
  24530. end else begin
  24531. inc(result,2);
  24532. exit;
  24533. end else begin
  24534. inc(result,1);
  24535. exit;
  24536. end else
  24537. exit;
  24538. for i := 0 to (Count and 3)-1 do // last 0..3 DWORD
  24539. if P^[i]=Value then
  24540. exit else
  24541. inc(result);
  24542. end;
  24543. result := -1;
  24544. end;
  24545. {$else}
  24546. asm
  24547. push eax
  24548. call IntegerScan
  24549. test eax,eax
  24550. pop edx
  24551. jnz @e
  24552. dec eax // returns -1
  24553. ret
  24554. @e: sub eax,edx
  24555. shr eax,2
  24556. end;
  24557. {$endif}
  24558. function PtrUIntScanExists(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): boolean;
  24559. {$ifdef PUREPASCAL}
  24560. begin
  24561. {$ifdef CPU64}
  24562. result := Int64ScanExists(pointer(P),Count,Value);
  24563. {$else}
  24564. result := IntegerScanExists(pointer(P),Count,Value);
  24565. {$endif}
  24566. end;
  24567. {$else}
  24568. asm
  24569. jmp IntegerScanExists;
  24570. end;
  24571. {$endif}
  24572. function PtrUIntScanIndex(P: PPtrUIntArray; Count: PtrInt; Value: PtrUInt): PtrInt;
  24573. {$ifdef PUREPASCAL}
  24574. var i: PtrInt; // optimized code for speed
  24575. begin
  24576. if P<>nil then begin
  24577. result := 0;
  24578. for i := 1 to Count shr 2 do // 4 PtrUInt by loop - aligned read
  24579. if P^[0]<>Value then
  24580. if P^[1]<>Value then
  24581. if P^[2]<>Value then
  24582. if P^[3]<>Value then begin
  24583. inc(PtrUInt(P),sizeof(P^[0])*4);
  24584. inc(result,4);
  24585. end else begin
  24586. inc(result,3);
  24587. exit;
  24588. end else begin
  24589. inc(result,2);
  24590. exit;
  24591. end else begin
  24592. inc(result,1);
  24593. exit;
  24594. end else
  24595. exit;
  24596. for i := 0 to (Count and 3)-1 do // last 0..3 PtrUInt
  24597. if P^[i]=Value then
  24598. exit else
  24599. inc(result);
  24600. end;
  24601. result := -1;
  24602. end;
  24603. {$else}
  24604. asm
  24605. push eax
  24606. call IntegerScan
  24607. test eax,eax
  24608. pop edx
  24609. jnz @e
  24610. dec eax // returns -1
  24611. ret
  24612. @e: sub eax,edx
  24613. shr eax,2
  24614. end;
  24615. {$endif}
  24616. function WordScanIndex(P: PWordArray; Count: PtrInt; Value: word): integer;
  24617. begin
  24618. for result := 0 to Count-1 do
  24619. if P^[result]=Value then
  24620. exit;
  24621. result := -1;
  24622. end;
  24623. procedure QuickSortInteger(ID: PIntegerArray; L,R: PtrInt);
  24624. var I, J, P: PtrInt;
  24625. pivot, Tmp: integer;
  24626. begin
  24627. if L<R then
  24628. repeat
  24629. I := L; J := R;
  24630. P := (L + R) shr 1;
  24631. repeat
  24632. pivot := ID^[P];
  24633. while ID[I]<pivot do inc(I);
  24634. while ID[J]>pivot do dec(J);
  24635. if I <= J then begin
  24636. Tmp := ID[J]; ID[J] := ID[I]; ID[I] := Tmp;
  24637. if P = I then P := J else if P = J then P := I;
  24638. inc(I); dec(J);
  24639. end;
  24640. until I > J;
  24641. if L < J then
  24642. QuickSortInteger(ID,L,J);
  24643. L := I;
  24644. until I >= R;
  24645. end;
  24646. procedure QuickSortInteger(var ID: TIntegerDynArray);
  24647. begin
  24648. QuickSortInteger(pointer(ID),0,high(ID));
  24649. end;
  24650. procedure QuickSortInteger(ID,CoValues: PIntegerArray; L,R: PtrInt);
  24651. var I, J, P: PtrInt;
  24652. pivot, Tmp: integer;
  24653. begin
  24654. if L<R then
  24655. repeat
  24656. I := L; J := R;
  24657. P := (L + R) shr 1;
  24658. repeat
  24659. pivot := ID^[P];
  24660. while ID[I]<pivot do inc(I);
  24661. while ID[J]>pivot do dec(J);
  24662. if I <= J then begin
  24663. Tmp := ID[J]; ID[J] := ID[I]; ID[I] := Tmp;
  24664. Tmp := CoValues[J]; CoValues[J] := CoValues[I]; CoValues[I] := Tmp;
  24665. if P = I then P := J else if P = J then P := I;
  24666. inc(I); dec(J);
  24667. end;
  24668. until I > J;
  24669. if L < J then
  24670. QuickSortInteger(ID,CoValues,L,J);
  24671. L := I;
  24672. until I >= R;
  24673. end;
  24674. procedure QuickSortInt64(ID: PInt64Array; L, R: PtrInt); overload;
  24675. var I, J, P: PtrInt;
  24676. pivot, Tmp: Int64;
  24677. begin
  24678. if L<R then
  24679. repeat
  24680. I := L; J := R;
  24681. P := (L + R) shr 1;
  24682. repeat
  24683. pivot := ID^[P];
  24684. while ID[I]<pivot do inc(I);
  24685. while ID[J]>pivot do dec(J);
  24686. if I <= J then begin
  24687. Tmp := ID[J]; ID[J] := ID[I]; ID[I] := Tmp;
  24688. if P = I then P := J else if P = J then P := I;
  24689. inc(I); dec(J);
  24690. end;
  24691. until I > J;
  24692. if L < J then
  24693. QuickSortInt64(ID,L,J);
  24694. L := I;
  24695. until I >= R;
  24696. end;
  24697. procedure QuickSortInt64(ID,CoValues: PInt64Array; L, R: PtrInt); overload;
  24698. var I, J, P: PtrInt;
  24699. pivot, Tmp: Int64;
  24700. begin
  24701. if L<R then
  24702. repeat
  24703. I := L; J := R;
  24704. P := (L + R) shr 1;
  24705. repeat
  24706. pivot := ID^[P];
  24707. while ID[I]<pivot do inc(I);
  24708. while ID[J]>pivot do dec(J);
  24709. if I <= J then begin
  24710. Tmp := ID[J]; ID[J] := ID[I]; ID[I] := Tmp;
  24711. Tmp := CoValues[J]; CoValues[J] := CoValues[I]; CoValues[I] := Tmp;
  24712. if P = I then P := J else if P = J then P := I;
  24713. inc(I); dec(J);
  24714. end;
  24715. until I > J;
  24716. if L < J then
  24717. QuickSortInt64(ID,L,J);
  24718. L := I;
  24719. until I >= R;
  24720. end;
  24721. procedure QuickSortPtrInt(P: PPtrIntArray; L, R: PtrInt);
  24722. begin
  24723. {$ifdef CPU64}
  24724. QuickSortInt64(PInt64Array(P),L,R);
  24725. {$else}
  24726. QuickSortInteger(PIntegerArray(P),L,R);
  24727. {$endif}
  24728. end;
  24729. function FastFindPtrIntSorted(P: PPtrIntArray; R: PtrInt; Value: PtrInt): PtrInt; overload;
  24730. begin
  24731. {$ifdef CPU64}
  24732. result := FastFindInt64Sorted(PInt64Array(P),R,Value);
  24733. {$else}
  24734. result := FastFindIntegerSorted(PIntegerArray(P),R,Value);
  24735. {$endif}
  24736. end;
  24737. procedure QuickSortPointer(P: PPointerArray; L, R: PtrInt);
  24738. begin
  24739. {$ifdef CPU64}
  24740. QuickSortInt64(PInt64Array(P),L,R);
  24741. {$else}
  24742. QuickSortInteger(PIntegerArray(P),L,R);
  24743. {$endif}
  24744. end;
  24745. function FastFindPointerSorted(P: PPointerArray; R: PtrInt; Value: pointer): PtrInt; overload;
  24746. begin
  24747. {$ifdef CPU64}
  24748. result := FastFindInt64Sorted(PInt64Array(P),R,Int64(Value));
  24749. {$else}
  24750. result := FastFindIntegerSorted(PIntegerArray(P),R,integer(Value));
  24751. {$endif}
  24752. end;
  24753. procedure CopyAndSortInteger(Values: PIntegerArray; ValuesCount: integer;
  24754. var Dest: TIntegerDynArray);
  24755. begin
  24756. if ValuesCount>length(Dest) then
  24757. SetLength(Dest,ValuesCount);
  24758. MoveFast(Values^[0],Dest[0],ValuesCount*sizeof(Integer));
  24759. QuickSortInteger(pointer(Dest),0,ValuesCount-1);
  24760. end;
  24761. procedure CopyAndSortInt64(Values: PInt64Array; ValuesCount: integer;
  24762. var Dest: TInt64DynArray);
  24763. begin
  24764. if ValuesCount>length(Dest) then
  24765. SetLength(Dest,ValuesCount);
  24766. MoveFast(Values^[0],Dest[0],ValuesCount*sizeof(Int64));
  24767. QuickSortInt64(pointer(Dest),0,ValuesCount-1);
  24768. end;
  24769. function FastFindIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt;
  24770. var L: PtrInt;
  24771. cmp: integer;
  24772. begin
  24773. L := 0;
  24774. if 0<=R then
  24775. repeat
  24776. result := (L + R) shr 1;
  24777. cmp := P^[result]-Value;
  24778. if cmp=0 then
  24779. exit;
  24780. if cmp<0 then
  24781. L := result + 1 else
  24782. R := result - 1;
  24783. until (L > R);
  24784. result := -1
  24785. end;
  24786. function FastFindInt64Sorted(P: PInt64Array; R: PtrInt; const Value: Int64): PtrInt; overload;
  24787. var L: PtrInt;
  24788. cmp: Int64;
  24789. begin
  24790. L := 0;
  24791. if 0<=R then
  24792. repeat
  24793. result := (L + R) shr 1;
  24794. cmp := P^[result]-Value;
  24795. if cmp=0 then
  24796. exit;
  24797. if cmp<0 then
  24798. L := result + 1 else
  24799. R := result - 1;
  24800. until (L > R);
  24801. result := -1
  24802. end;
  24803. function FastFindIntegerSorted(const Values: TIntegerDynArray; Value: integer): PtrInt;
  24804. begin
  24805. result := FastFindIntegerSorted(pointer(Values),length(Values)-1,Value);
  24806. end;
  24807. function FastLocateIntegerSorted(P: PIntegerArray; R: PtrInt; Value: integer): PtrInt;
  24808. var L,i: PtrInt;
  24809. cmp: integer;
  24810. begin
  24811. if R<0 then
  24812. result := 0 else begin
  24813. L := 0;
  24814. result := -1; // return -1 if found
  24815. repeat
  24816. i := (L + R) shr 1;
  24817. cmp := P^[i]-Value;
  24818. if cmp=0 then
  24819. exit;
  24820. if cmp<0 then
  24821. L := i + 1 else
  24822. R := i - 1;
  24823. until (L > R);
  24824. while (i>=0) and (P^[i]>=Value) do dec(i);
  24825. result := i+1; // return the index where to insert
  24826. end;
  24827. end;
  24828. function AddSortedInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  24829. Value: integer; CoValues: PIntegerDynArray=nil): PtrInt;
  24830. begin
  24831. result := FastLocateIntegerSorted(pointer(Values),ValuesCount-1,Value);
  24832. if result>=0 then // if Value exists -> fails
  24833. result := InsertInteger(Values,ValuesCount,Value,result,CoValues);
  24834. end;
  24835. function AddSortedInteger(var Values: TIntegerDynArray;
  24836. Value: integer; CoValues: PIntegerDynArray=nil): PtrInt;
  24837. var ValuesCount: integer;
  24838. begin
  24839. ValuesCount := length(Values);
  24840. result := FastLocateIntegerSorted(pointer(Values),ValuesCount-1,Value);
  24841. if result>=0 then begin // if Value exists -> fails
  24842. SetLength(Values,ValuesCount+1); // manual size increase
  24843. result := InsertInteger(Values,ValuesCount,Value,result,CoValues);
  24844. end;
  24845. end;
  24846. function InsertInteger(var Values: TIntegerDynArray; var ValuesCount: integer;
  24847. Value: Integer; Index: PtrInt; CoValues: PIntegerDynArray=nil): PtrInt;
  24848. var n: PtrInt;
  24849. begin
  24850. result := Index;
  24851. n := Length(Values);
  24852. if ValuesCount=n then begin
  24853. inc(n,256+n shr 3);
  24854. SetLength(Values,n);
  24855. if CoValues<>nil then
  24856. SetLength(CoValues^,n);
  24857. end;
  24858. n := ValuesCount;
  24859. if PtrUInt(result)<PtrUInt(n) then begin
  24860. n := (n-result)*sizeof(Integer);
  24861. MoveFast(Values[result],Values[result+1],n);
  24862. if CoValues<>nil then
  24863. MoveFast(CoValues^[result],CoValues^[result+1],n);
  24864. end else
  24865. result := n;
  24866. Values[result] := Value;
  24867. inc(ValuesCount);
  24868. end;
  24869. function TIntegerDynArrayFrom(const Values: array of integer): TIntegerDynArray;
  24870. var i: integer;
  24871. begin
  24872. SetLength(result,length(Values));
  24873. for i := 0 to high(Values) do
  24874. result[i] := Values[i];
  24875. end;
  24876. function TIntegerDynArrayFrom64(const Values: TInt64DynArray;
  24877. raiseExceptionOnOverflow: boolean=true): TIntegerDynArray;
  24878. var i: integer;
  24879. const MinInt = -MaxInt-1;
  24880. begin
  24881. SetLength(result,length(Values));
  24882. for i := 0 to high(Values) do
  24883. if Values[i]>MaxInt then
  24884. if raiseExceptionOnOverflow then
  24885. raise ESynException.CreateUTF8('TIntegerDynArrayFrom64: Values[%]=%>%',
  24886. [i,Values[i],MaxInt]) else
  24887. result[i] := MaxInt else
  24888. if Values[i]<MinInt then
  24889. if raiseExceptionOnOverflow then
  24890. raise ESynException.CreateUTF8('TIntegerDynArrayFrom64: Values[%]=%<%',
  24891. [i,Values[i],MinInt]) else
  24892. result[i] := MinInt else
  24893. result[i] := Values[i];
  24894. end;
  24895. function TInt64DynArrayFrom(const Values: TIntegerDynArray): TInt64DynArray;
  24896. var i: integer;
  24897. begin
  24898. SetLength(result,length(Values));
  24899. for i := 0 to high(Values) do
  24900. result[i] := Values[i];
  24901. end;
  24902. function GetInteger(P: PUTF8Char): PtrInt;
  24903. var c: PtrUInt;
  24904. minus: boolean;
  24905. begin
  24906. if P=nil then begin
  24907. result := 0;
  24908. exit;
  24909. end;
  24910. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  24911. if P^='-' then begin
  24912. minus := true;
  24913. repeat inc(P) until P^<>' ';
  24914. end else begin
  24915. minus := false;
  24916. if P^='+' then
  24917. repeat inc(P) until P^<>' ';
  24918. end;
  24919. c := byte(P^)-48;
  24920. if c>9 then
  24921. result := 0 else begin
  24922. result := c;
  24923. inc(P);
  24924. repeat
  24925. c := byte(P^)-48;
  24926. if c>9 then
  24927. break else
  24928. result := result*10+PtrInt(c);
  24929. inc(P);
  24930. until false;
  24931. end;
  24932. if minus then
  24933. result := -result;
  24934. end;
  24935. function GetInteger(P: PUTF8Char; var err: integer): PtrInt;
  24936. var c: PtrUInt;
  24937. minus: boolean;
  24938. begin
  24939. if P=nil then begin
  24940. result := 0;
  24941. err := 1;
  24942. exit;
  24943. end else
  24944. err := 0;
  24945. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  24946. if P^='-' then begin
  24947. minus := true;
  24948. repeat inc(P) until P^<>' ';
  24949. end else begin
  24950. minus := false;
  24951. if P^='+' then
  24952. repeat inc(P) until P^<>' ';
  24953. end;
  24954. c := byte(P^)-48;
  24955. if c>9 then begin
  24956. err := 1;
  24957. result := 0;
  24958. exit;
  24959. end else begin
  24960. result := c;
  24961. inc(P);
  24962. repeat
  24963. c := byte(P^)-48;
  24964. if c>9 then begin
  24965. if byte(P^)<>0 then
  24966. err := 1; // always return 1 as err code -> don't care about char index
  24967. break;
  24968. end else
  24969. result := result*10+PtrInt(c);
  24970. inc(P);
  24971. until false;
  24972. end;
  24973. if minus then
  24974. result := -result;
  24975. end;
  24976. function GetIntegerDef(P: PUTF8Char; Default: PtrInt): PtrInt;
  24977. var err: integer;
  24978. begin
  24979. result := GetInteger(P,err);
  24980. if err<>0 then
  24981. result := Default;
  24982. end;
  24983. function UTF8ToInteger(const value: RawUTF8; Default: PtrInt=0): PtrInt;
  24984. var err: integer;
  24985. begin
  24986. result := GetInteger(pointer(value),err);
  24987. if err<>0 then
  24988. result := Default;
  24989. end;
  24990. function UTF8ToInteger(const value: RawUTF8; Min,Max: PtrInt; Default: PtrInt=0): PtrInt;
  24991. var err: integer;
  24992. begin
  24993. result := GetInteger(pointer(value),err);
  24994. if (err<>0) or (result<Min) or (result>Max) then
  24995. result := Default;
  24996. end;
  24997. function GetBoolean(P: PUTF8Char): boolean;
  24998. begin
  24999. if (P<>nil) and (PInteger(P)^=TRUE_LOW) then
  25000. result := true else
  25001. result := GetInteger(P)<>0;
  25002. end;
  25003. function GetCardinalDef(P: PUTF8Char; Default: PtrUInt): PtrUInt;
  25004. var c: PtrUInt;
  25005. begin
  25006. if P=nil then begin
  25007. result := Default;
  25008. exit;
  25009. end;
  25010. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  25011. c := byte(P^)-48;
  25012. if c>9 then
  25013. result := Default else begin
  25014. result := c;
  25015. inc(P);
  25016. repeat
  25017. c := byte(P^)-48;
  25018. if c>9 then
  25019. break else
  25020. result := result*10+PtrUInt(c);
  25021. inc(P);
  25022. until false;
  25023. end;
  25024. end;
  25025. function GetCardinal(P: PUTF8Char): PtrUInt;
  25026. var c: PtrUInt;
  25027. begin
  25028. if P=nil then begin
  25029. result := 0;
  25030. exit;
  25031. end;
  25032. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  25033. c := byte(P^)-48;
  25034. if c>9 then
  25035. result := 0 else begin
  25036. result := c;
  25037. inc(P);
  25038. repeat
  25039. c := byte(P^)-48;
  25040. if c>9 then
  25041. break else
  25042. result := result*10+PtrUInt(c);
  25043. inc(P);
  25044. until false;
  25045. end;
  25046. end;
  25047. function GetCardinalW(P: PWideChar): PtrUInt;
  25048. var c: PtrUInt;
  25049. begin
  25050. if P=nil then begin
  25051. result := 0;
  25052. exit;
  25053. end;
  25054. if ord(P^) in [1..32] then repeat inc(P) until not(ord(P^) in [1..32]);
  25055. c := word(P^)-48;
  25056. if c>9 then
  25057. result := 0 else begin
  25058. result := c;
  25059. inc(P);
  25060. repeat
  25061. c := word(P^)-48;
  25062. if c>9 then
  25063. break else
  25064. result := result*10+c;
  25065. inc(P);
  25066. until false;
  25067. end;
  25068. end;
  25069. {$ifdef CPU64}
  25070. procedure SetInt64(P: PUTF8Char; var result: Int64);
  25071. begin // PtrInt is already int64 -> call PtrInt version
  25072. result := GetInteger(P);
  25073. end;
  25074. {$else}
  25075. procedure SetInt64(P: PUTF8Char; var result: Int64);
  25076. var c: cardinal;
  25077. minus: boolean;
  25078. begin
  25079. result := 0;
  25080. if P=nil then
  25081. exit;
  25082. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  25083. if P^='-' then begin
  25084. minus := true;
  25085. repeat inc(P) until P^<>' ';
  25086. end else begin
  25087. minus := false;
  25088. if P^='+' then
  25089. repeat inc(P) until P^<>' ';
  25090. end;
  25091. c := byte(P^)-48;
  25092. if c>9 then
  25093. exit;
  25094. Int64Rec(result).Lo := c;
  25095. inc(P);
  25096. repeat // fast 32 bit loop
  25097. c := byte(P^)-48;
  25098. if c>9 then
  25099. break else
  25100. Int64Rec(result).Lo := Int64Rec(result).Lo*10+c;
  25101. inc(P);
  25102. if Int64Rec(result).Lo>=high(cardinal)div 10 then begin
  25103. repeat // 64 bit loop
  25104. c := byte(P^)-48;
  25105. if c>9 then
  25106. break;
  25107. result := result shl 3+result+result; // fast result := result*10
  25108. inc(result,c);
  25109. inc(P);
  25110. until false;
  25111. break;
  25112. end;
  25113. until false;
  25114. if minus then
  25115. result := -result;
  25116. end;
  25117. {$endif}
  25118. {$ifdef CPU64}
  25119. function GetInt64(P: PUTF8Char): Int64;
  25120. begin // PtrInt is already int64 -> call previous version
  25121. result := GetInteger(P);
  25122. end;
  25123. {$else}
  25124. function GetInt64(P: PUTF8Char): Int64;
  25125. begin
  25126. SetInt64(P,result);
  25127. end;
  25128. {$endif}
  25129. {$ifdef CPU64}
  25130. function GetInt64(P: PUTF8Char; var err: integer): Int64;
  25131. begin // PtrInt is already int64 -> call previous version
  25132. result := GetInteger(P,err);
  25133. end;
  25134. {$else}
  25135. function GetInt64(P: PUTF8Char; var err: integer): Int64;
  25136. var c: cardinal;
  25137. minus: boolean;
  25138. begin
  25139. err := 0;
  25140. result := 0;
  25141. if P=nil then
  25142. exit;
  25143. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  25144. if P^='-' then begin
  25145. minus := true;
  25146. repeat inc(P) until P^<>' ';
  25147. end else begin
  25148. minus := false;
  25149. if P^='+' then
  25150. repeat inc(P) until P^<>' ';
  25151. end;
  25152. inc(err);
  25153. c := byte(P^)-48;
  25154. if c>9 then
  25155. exit;
  25156. Int64Rec(result).Lo := c;
  25157. inc(P);
  25158. repeat // fast 32 bit loop
  25159. c := byte(P^);
  25160. if c<>0 then begin
  25161. dec(c,48);
  25162. inc(err);
  25163. if c>9 then
  25164. exit;
  25165. Int64Rec(result).Lo := Int64Rec(result).Lo*10+c;
  25166. inc(P);
  25167. if Int64Rec(result).Lo>=high(cardinal)div 10 then begin
  25168. repeat // 64 bit loop
  25169. c := byte(P^);
  25170. if c=0 then begin
  25171. err := 0; // conversion success without error
  25172. break;
  25173. end;
  25174. dec(c,48);
  25175. inc(err);
  25176. if c>9 then
  25177. exit else
  25178. result := result shl 3+result+result; // fast result := result*10
  25179. inc(result,c);
  25180. if result<0 then
  25181. exit; // overflow (>$7FFFFFFFFFFFFFFF)
  25182. inc(P);
  25183. until false;
  25184. break;
  25185. end;
  25186. end else begin
  25187. err := 0; // reached P^=#0 -> conversion success without error
  25188. break;
  25189. end;
  25190. until false;
  25191. if minus then
  25192. result := -result;
  25193. end;
  25194. {$endif}
  25195. function GetExtended(P: PUTF8Char): TSynExtended;
  25196. var err: integer;
  25197. begin
  25198. result := GetExtended(P,err);
  25199. if err<>0 then
  25200. result := 0;
  25201. end;
  25202. {$ifdef PUREPASCAL}
  25203. {$define GETEXTENDEDPASCAL}
  25204. {$endif}
  25205. {$ifdef FPC}
  25206. {$define GETEXTENDEDPASCAL}
  25207. {$endif}
  25208. {$ifdef PIC}
  25209. {$define GETEXTENDEDPASCAL}
  25210. {$endif}
  25211. function GetExtended(P: PUTF8Char; out err: integer): TSynExtended;
  25212. // adapted from ValExt_JOH_PAS_8_a and ValExt_JOH_IA32_8_a by John O'Harrow
  25213. {$ifdef GETEXTENDEDPASCAL}
  25214. const POW10: array[0..31] of TSynExtended = (
  25215. 1E0,1E1,1E2,1E3,1E4,1E5,1E6,1E7,1E8,1E9,1E10,1E11,1E12,1E13,1E14,1E15,1E16,
  25216. 1E17,1E18,1E19,1E20,1E21,1E22,1E23,1E24,1E25,1E26,1E27,1E28,1E29,1E30,1E31);
  25217. function IntPower(Exponent: Integer): TSynExtended;
  25218. var Y: Cardinal;
  25219. LBase: Int64;
  25220. begin
  25221. Y := abs(Exponent);
  25222. LBase := 10;
  25223. result := 1.0;
  25224. repeat
  25225. while not odd(Y) do begin
  25226. Y := Y shr 1;
  25227. LBase := LBase*LBase;
  25228. end;
  25229. dec(Y);
  25230. result := result*LBase;
  25231. until Y=0;
  25232. if Exponent<0 then
  25233. result := 1.0/result;
  25234. end;
  25235. var Digits, ExpValue: Integer;
  25236. Ch: AnsiChar;
  25237. Neg, NegExp, Valid: Boolean;
  25238. begin
  25239. result := 0.0;
  25240. err := 0;
  25241. if P=nil then begin
  25242. inc(err);
  25243. exit;
  25244. end;
  25245. Neg := False;
  25246. NegExp := False;
  25247. Valid := False;
  25248. while P[err]=' ' do
  25249. inc(err);
  25250. Ch := P[err];
  25251. if Ch in ['+','-'] then begin
  25252. inc(err);
  25253. Neg := (Ch='-');
  25254. end;
  25255. while true do begin
  25256. Ch := P[err];
  25257. inc(err);
  25258. if not (Ch in ['0'..'9']) then
  25259. break;
  25260. result := (result*10.0)+Ord(Ch)-Ord('0');
  25261. Valid := True;
  25262. end;
  25263. Digits := 0;
  25264. if Ch='.' then begin
  25265. while true do begin
  25266. Ch := P[err];
  25267. inc(err);
  25268. if not (Ch in ['0'..'9']) then begin
  25269. if not valid then // starts with '.'
  25270. if Ch=#0 then
  25271. dec(err); // P='.'
  25272. break;
  25273. end;
  25274. result := (result*10.0)+Ord(Ch)-Ord('0');
  25275. dec(Digits);
  25276. Valid := true;
  25277. end;
  25278. end;
  25279. ExpValue := 0;
  25280. if Ch in ['E','e'] then begin
  25281. Valid := false;
  25282. Ch := P[err];
  25283. if Ch in ['+','-'] then begin
  25284. inc(err);
  25285. NegExp := (Ch='-');
  25286. end;
  25287. while true do begin
  25288. Ch := P[err];
  25289. inc(err);
  25290. if not (Ch in ['0'..'9']) then
  25291. break;
  25292. ExpValue := (ExpValue*10)+Ord(Ch)-Ord('0');
  25293. Valid := true;
  25294. end;
  25295. if NegExp then
  25296. ExpValue := -ExpValue;
  25297. end;
  25298. inc(Digits,ExpValue);
  25299. case Digits of
  25300. -high(POW10)..-1: result := result/POW10[-Digits];
  25301. 1..high(POW10): result := result*POW10[Digits];
  25302. 0: ;
  25303. else result := result*IntPower(Digits);
  25304. end;
  25305. if Neg then
  25306. result := -result;
  25307. if Valid and (ch=#0) then
  25308. err := 0;
  25309. end;
  25310. {$else}
  25311. const Ten: double = 10.0;
  25312. asm // in: eax=text, edx=@err out: st(0)=result
  25313. push ebx {Save Used Registers}
  25314. push esi
  25315. push edi
  25316. mov esi,eax {String Pointer}
  25317. push eax {Save for Error Condition}
  25318. xor ebx,ebx
  25319. push eax {Allocate Local Storage for Loading FPU}
  25320. test esi,esi
  25321. jz @@Nil {Nil String}
  25322. @@Trim: {Strip Leading Spaces}
  25323. movzx ebx,byte ptr [esi]
  25324. inc esi
  25325. cmp bl,' '
  25326. je @@Trim
  25327. xor ecx,ecx {Clear Sign Flag}
  25328. fld qword [Ten] {Load 10 into FPU}
  25329. xor eax,eax {Zero Number of Decimal Places}
  25330. fldz {Zero Result in FPU}
  25331. cmp bl,'0'
  25332. jl @@CheckSign {Check for Sign Character}
  25333. @@FirstDigit:
  25334. xor edi,edi {Zero Exponent Value}
  25335. @@DigitLoop:
  25336. sub bl,'0'
  25337. cmp bl,9
  25338. ja @@Fraction {Non-Digit}
  25339. mov cl,1 {Set Digit Found Flag}
  25340. mov [esp],ebx {Store for FPU Use}
  25341. fmul st(0),st(1) {Multply by 10}
  25342. fiadd dword ptr [esp] {Add Next Digit}
  25343. movzx ebx,byte ptr [esi] {Get Next Char}
  25344. inc esi
  25345. test bl,bl {End Reached?}
  25346. jnz @@DigitLoop {No,Get Next Digit}
  25347. jmp @@Finish {Yes,Finished}
  25348. @@CheckSign:
  25349. cmp bl,'-'
  25350. je @@Minus
  25351. cmp bl,'+'
  25352. je @@SignSet
  25353. @@GetFirstDigit:
  25354. test bl,bl
  25355. jz @@Error {No Digits Found}
  25356. jmp @@FirstDigit
  25357. @@Minus:
  25358. mov ch,1 {Set Sign Flag}
  25359. @@SignSet:
  25360. movzx ebx,byte ptr [esi] {Get Next Char}
  25361. inc esi
  25362. jmp @@GetFirstDigit
  25363. @@Fraction:
  25364. cmp bl,'.'-'0'
  25365. jne @@Exponent {No Decimal Point}
  25366. movzx ebx,byte ptr [esi] {Get Next Char}
  25367. test bl,bl
  25368. jz @@DotEnd {String Ends with '.'}
  25369. inc esi
  25370. @@FractionLoop:
  25371. sub bl,'0'
  25372. cmp bl,9
  25373. ja @@Exponent {Non-Digit}
  25374. mov [esp],ebx
  25375. dec eax {-(Number of Decimal Places)}
  25376. fmul st(0),st(1) {Multply by 10}
  25377. fiadd dword ptr [esp] {Add Next Digit}
  25378. movzx ebx,byte ptr [esi] {Get Next Char}
  25379. inc esi
  25380. test bl,bl {End Reached?}
  25381. jnz @@FractionLoop {No,Get Next Digit}
  25382. jmp @@Finish {Yes,Finished (No Exponent)}
  25383. @@DotEnd:
  25384. test cl,cl {Any Digits Found before '.'?}
  25385. jnz @@Finish {Yes,Valid}
  25386. jmp @@Error {No,Invalid}
  25387. @@Exponent:
  25388. or bl,$20
  25389. cmp bl,'e'-'0'
  25390. jne @@Error {Not 'e' or 'E'}
  25391. @@GetExponent:
  25392. movzx ebx,byte ptr [esi] {Get Next Char}
  25393. inc esi
  25394. mov cl,0 {Clear Exponent Sign Flag}
  25395. cmp bl,'-'
  25396. je @@MinusExp
  25397. cmp bl,'+'
  25398. je @@ExpSignSet
  25399. jmp @@ExpLoop
  25400. @@MinusExp:
  25401. mov cl,1 {Set Exponent Sign Flag}
  25402. @@ExpSignSet:
  25403. movzx ebx,byte ptr [esi] {Get Next Char}
  25404. inc esi
  25405. @@ExpLoop:
  25406. sub bl,'0'
  25407. cmp bl,9
  25408. ja @@Error {Non-Digit}
  25409. lea edi,[edi+edi*4] {Multiply by 10}
  25410. add edi,edi
  25411. add edi,ebx {Add Next Digit}
  25412. movzx ebx,byte ptr [esi] {Get Next Char}
  25413. inc esi
  25414. test bl,bl {End Reached?}
  25415. jnz @@ExpLoop {No,Get Next Digit}
  25416. @@EndExp:
  25417. test cl,cl {Positive Exponent?}
  25418. jz @@Finish {Yes,Keep Exponent Value}
  25419. neg edi {No,Negate Exponent Value}
  25420. @@Finish:
  25421. add eax,edi {Exponent Value - Number of Decimal Places}
  25422. mov [edx],ebx {Result Code = 0}
  25423. jz @@PowerDone {No call to _Pow10 Needed}
  25424. mov edi,ecx {Save Decimal Sign Flag}
  25425. call System.@Pow10 {Raise to Power of 10}
  25426. mov ecx,edi {Restore Decimal Sign Flag}
  25427. @@PowerDone:
  25428. test ch,ch {Decimal Sign Flag Set?}
  25429. jnz @@Negate {Yes,Negate Value}
  25430. @@Success:
  25431. add esp,8 {Dump Local Storage and String Pointer}
  25432. @@Exit:
  25433. ffree st(1) {Remove Ten Value from FPU}
  25434. pop edi {Restore Used Registers}
  25435. pop esi
  25436. pop ebx
  25437. ret {Finished}
  25438. @@Negate:
  25439. fchs {Negate Result in FPU}
  25440. jmp @@Success
  25441. @@Nil:
  25442. inc esi {Force Result Code = 1}
  25443. fldz {Result Value = 0}
  25444. @@Error:
  25445. pop ebx {Dump Local Storage}
  25446. pop eax {String Pointer}
  25447. sub esi,eax {Error Offset}
  25448. mov [edx],esi {Set Result Code}
  25449. test ch,ch {Decimal Sign Flag Set?}
  25450. jz @@Exit {No,exit}
  25451. fchs {Yes. Negate Result in FPU}
  25452. jmp @@Exit {Exit Setting Result Code}
  25453. end;
  25454. {$endif}
  25455. function GetUTF8Char(P: PUTF8Char): cardinal;
  25456. begin
  25457. if P<>nil then begin
  25458. result := ord(P[0]);
  25459. if result and $80<>0 then begin
  25460. result := GetHighUTF8UCS4(P);
  25461. if result>$ffff then
  25462. result := ord('?'); // do not handle surrogates now
  25463. end;
  25464. end else
  25465. result := PtrUInt(P);
  25466. end;
  25467. function NextUTF8UCS4(var P: PUTF8Char): cardinal;
  25468. begin
  25469. if P<>nil then begin
  25470. result := byte(P[0]);
  25471. if result and $80=0 then
  25472. inc(P) else begin
  25473. if result and $20=0 then begin
  25474. result := result shl 6+byte(P[1])-$3080; // fast direct process $0..$7ff
  25475. inc(P,2);
  25476. end else
  25477. result := GetHighUTF8UCS4(P); // handle even surrogates
  25478. end;
  25479. end else
  25480. result := 0;
  25481. end;
  25482. function ContainsUTF8(p, up: PUTF8Char): boolean;
  25483. var u: PByte;
  25484. begin
  25485. if (p<>nil) and (up<>nil) and (up^<>#0) then begin
  25486. result := true;
  25487. repeat
  25488. u := pointer(up);
  25489. repeat
  25490. if GetNextUTF8Upper(p)<>u^ then
  25491. break else
  25492. inc(u);
  25493. if u^=0 then
  25494. exit; // up^ was found inside p^
  25495. until false;
  25496. p := FindNextUTF8WordBegin(p);
  25497. until p=nil;
  25498. end;
  25499. result := false;
  25500. end;
  25501. function IdemFileExt(p: PUTF8Char; extup: PAnsiChar; sepChar: AnsiChar): Boolean;
  25502. var ext: PUTF8Char;
  25503. begin
  25504. if (p<>nil) and (extup<>nil) then begin
  25505. ext := nil;
  25506. repeat
  25507. if p^=sepChar then
  25508. ext := p; // get last '.' position from p into ext
  25509. inc(p);
  25510. until p^=#0;
  25511. if ext<>nil then
  25512. result := IdemPChar(ext,extup) else
  25513. result := false;
  25514. end else
  25515. result := false;
  25516. end;
  25517. function IdemPCharWithoutWhiteSpace(p: PUTF8Char; up: PAnsiChar): boolean;
  25518. begin
  25519. result := False;
  25520. if p=nil then
  25521. exit;
  25522. if up<>nil then
  25523. while up^<>#0 do begin
  25524. while p<=' ' do // trim white space
  25525. if p^=#0 then
  25526. exit else
  25527. inc(p);
  25528. if up^<>NormToUpperAnsi7[p^] then
  25529. exit;
  25530. inc(up);
  25531. inc(p);
  25532. end;
  25533. result := true;
  25534. end;
  25535. {$ifdef PUREPASCAL}
  25536. function IdemPChar(p: PUTF8Char; up: PAnsiChar): boolean;
  25537. // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper)
  25538. begin
  25539. result := false;
  25540. if p=nil then
  25541. exit;
  25542. if (up<>nil) and (up^<>#0) then
  25543. repeat
  25544. if up^<>NormToUpperAnsi7[p^] then
  25545. exit;
  25546. inc(up);
  25547. inc(p);
  25548. until up^=#0;
  25549. result := true;
  25550. end;
  25551. {$else}
  25552. function IdemPChar(p: PUTF8Char; up: PAnsiChar): boolean;
  25553. // if the beginning of p^ is same as up^ (ignore case - up^ must be already Upper)
  25554. // eax=p edx=up
  25555. asm
  25556. test eax,eax
  25557. jz @e // P=nil -> false
  25558. test edx,edx
  25559. push ebx
  25560. jz @t // up=nil -> true
  25561. mov ecx,[edx] // cl=up^[0]
  25562. test cl,cl
  25563. movzx ebx,byte ptr [eax] // bl=p^[0]
  25564. jz @t
  25565. cmp cl,byte ptr [ebx+NormToUpperAnsi7] // bl=NormToUpperAnsi7[p^[0]]
  25566. jnz @f // quick return in case of first invalid char
  25567. lea eax,[eax+1]
  25568. lea edx,[edx+1]
  25569. shr ecx,8 // cl=up^[1], ch=up^[2]
  25570. @1: mov bl,[eax] // bl=p^[0]
  25571. test cl,cl
  25572. jz @t // up^[0]=#0 -> OK
  25573. cmp cl,byte ptr [ebx+NormToUpperAnsi7] // bl=NormToUpperAnsi7[p^[0]]
  25574. mov bl,[eax+1] // bl=p^[1]
  25575. lea eax,[eax+2]
  25576. lea edx,[edx+2]
  25577. jne @f
  25578. test ch,ch
  25579. jz @t // up^[1]=#0 -> OK
  25580. cmp ch,byte ptr [ebx+NormToUpperAnsi7] // bl=NormToUpperAnsi7[p^[1]]
  25581. mov ecx,[edx] // cl=up^[0] ch=up^[1]
  25582. je @1
  25583. @f: pop ebx // NormToUpperAnsi7[p^]<>up^ -> FALSE
  25584. @e: xor eax,eax
  25585. ret
  25586. @t: pop ebx // up^=#0 -> TRUE
  25587. mov al,1
  25588. end;
  25589. {$endif}
  25590. function IdemPCharArray(p: PUTF8Char; const upArray: array of PAnsiChar): integer;
  25591. var W: word;
  25592. begin
  25593. if p<>nil then begin
  25594. w := NormToUpperAnsi7Byte[ord(p[0])]+NormToUpperAnsi7Byte[ord(p[1])]shl 8;
  25595. for result := 0 to high(upArray) do
  25596. if (PWord(upArray[result])^=w) and IdemPChar(p+2,upArray[result]+2) then
  25597. exit;
  25598. end;
  25599. result := -1;
  25600. end;
  25601. function IdemPCharU(p, up: PUTF8Char): boolean;
  25602. begin
  25603. result := false;
  25604. if (p=nil) or (up=nil) then
  25605. exit;
  25606. while up^<>#0 do begin
  25607. if GetNextUTF8Upper(p)<>ord(up^) then
  25608. exit;
  25609. inc(up);
  25610. end;
  25611. result := true;
  25612. end;
  25613. function UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;
  25614. begin
  25615. if source<>'' then
  25616. result := UpperCopy255Buf(dest,pointer(source),
  25617. {$ifdef HASINLINE}length(source){$else}PInteger(PtrInt(source)-4)^{$endif}) else
  25618. result := dest;
  25619. end;
  25620. function UpperCopy255BufPas(dest: PAnsiChar; source: PUTF8Char; sourceLen: integer): PAnsiChar;
  25621. var i: integer;
  25622. c,d: PtrUInt;
  25623. begin
  25624. if sourceLen>0 then begin
  25625. if sourceLen>248 then
  25626. sourceLen := 248; // avoid buffer overflow
  25627. // we allow to copy up to 3/7 more chars in Dest^ since its size is 255
  25628. {$ifdef CPU64} // unbranched uppercase conversion of 8 chars blocks
  25629. for i := 0 to sourceLen shr 3 do begin
  25630. c := PPtrUIntArray(source)^[i];
  25631. d := c or $8080808080808080;
  25632. PPtrUIntArray(dest)^[i] :=
  25633. c-((d-$6161616161616161) and not(d-$7b7b7b7b7b7b7b7b)) and
  25634. ((not c) and $8080808080808080)shr 2;
  25635. end;
  25636. {$else} // unbranched uppercase conversion of 4 chars blocks
  25637. for i := 0 to sourceLen shr 2 do begin
  25638. c := PPtrUIntArray(source)^[i];
  25639. d := c or $80808080;
  25640. PPtrUIntArray(dest)^[i] := c-((d-$61616161) and not(d-$7b7b7b7b)) and
  25641. ((not c) and $80808080)shr 2;
  25642. end;
  25643. {$endif}
  25644. result := dest+sourceLen; // but we always return the exact size
  25645. end else
  25646. result := dest;
  25647. end;
  25648. {$ifndef PUREPASCAL}
  25649. {$ifndef DELPHI5OROLDER}
  25650. const
  25651. CMP_RANGES = $44; // see https://msdn.microsoft.com/en-us/library/bb531425
  25652. function UpperCopy255BufSSE42(dest: PAnsiChar; source: PUTF8Char; sourceLen: integer): PAnsiChar;
  25653. asm // eax=dest edx=source ecx=sourceLen
  25654. test ecx,ecx
  25655. jz @z
  25656. cmp ecx,16
  25657. movdqu xmm1,dqword ptr [@az]
  25658. movdqu xmm3,dqword ptr [@bits]
  25659. ja @big
  25660. // optimize the common case of sourceLen<=16
  25661. movdqu xmm2,[edx]
  25662. {$ifdef HASAESNI}
  25663. pcmpistrm xmm1,xmm2,CMP_RANGES // find in range a-z, return mask in xmm0
  25664. {$else}
  25665. db $66,$0F,$3A,$62,$CA,CMP_RANGES
  25666. {$endif}
  25667. pand xmm0,xmm3
  25668. pxor xmm2,xmm0
  25669. movdqu [eax],xmm2
  25670. add eax,ecx
  25671. @z: ret
  25672. @big: cmp ecx,240
  25673. push eax
  25674. jb @ok
  25675. mov ecx,239
  25676. @ok: add [esp],ecx // save to return end position with the exact size
  25677. shr ecx,4
  25678. sub edx,eax
  25679. inc ecx
  25680. @s: movdqu xmm2,[edx+eax]
  25681. {$ifdef HASAESNI}
  25682. pcmpistrm xmm1,xmm2,CMP_RANGES
  25683. {$else}
  25684. db $66,$0F,$3A,$62,$CA,CMP_RANGES
  25685. {$endif}
  25686. pand xmm0,xmm3
  25687. pxor xmm2,xmm0
  25688. movdqu [eax],xmm2
  25689. dec ecx
  25690. lea eax,[eax+16]
  25691. jnz @s
  25692. pop eax
  25693. ret
  25694. @az: db 'azazazazazazazaz' // define range for upper case conversion
  25695. @bits: db ' ' // $20 = bit to change when changing case
  25696. end;
  25697. {$endif DELPHI5OROLDER}
  25698. {$endif PUREPASCAL}
  25699. function UpperCopyWin255(dest: PWinAnsiChar; const source: RawUTF8): PWinAnsiChar;
  25700. var i, L: integer;
  25701. begin
  25702. L := PStrRec(Pointer(PtrInt(source)-STRRECSIZE))^.length;
  25703. if L>0 then begin
  25704. if L>250 then
  25705. L := 250; // avoid buffer overflow
  25706. result := dest+L;
  25707. for i := 0 to L-1 do
  25708. dest[i] := AnsiChar(NormToUpperByte[PByteArray(source)[i]]);
  25709. end else
  25710. result := dest;
  25711. end;
  25712. function UTF8UpperCopy(Dest, Source: PUTF8Char; SourceChars: Cardinal): PUTF8Char;
  25713. var c: cardinal;
  25714. endSource, endSourceBy4, S: PUTF8Char;
  25715. extra,i: integer;
  25716. label By1, By4, set1; // ugly but faster
  25717. begin
  25718. if (Source<>nil) and (Dest<>nil) then begin
  25719. // first handle trailing 7 bit ASCII chars, by quad (Sha optimization)
  25720. endSource := Source+SourceChars;
  25721. endSourceBy4 := endSource-4;
  25722. if (PtrUInt(Source) and 3=0) and (Source<=endSourceBy4) then
  25723. repeat
  25724. By4:c := PCardinal(Source)^;
  25725. if c and $80808080<>0 then
  25726. goto By1; // break on first non ASCII quad
  25727. inc(Source,4);
  25728. Dest[0] := AnsiChar(NormToUpperByte[c and $ff]);
  25729. Dest[1] := AnsiChar(NormToUpperByte[(c shr 8) and $ff]);
  25730. Dest[2] := AnsiChar(NormToUpperByte[(c shr 16) and $ff]);
  25731. Dest[3] := AnsiChar(NormToUpperByte[c shr 24]);
  25732. inc(Dest,4);
  25733. until Source>endSourceBy4;
  25734. // generic loop, handling one UCS4 char per iteration
  25735. if Source<endSource then
  25736. repeat
  25737. By1:c := byte(Source^);
  25738. inc(Source);
  25739. if ord(c) and $80=0 then begin
  25740. Dest^ := AnsiChar(NormToUpperByte[c]);
  25741. Set1: inc(Dest);
  25742. if (PtrUInt(Source) and 3=0) and (Source<EndSourceBy4) then goto By4 else
  25743. if Source<endSource then continue else break;
  25744. end else begin
  25745. extra := UTF8_EXTRABYTES[c];
  25746. if (extra=0) or (Source+extra>endSource) then break;
  25747. for i := 0 to extra-1 do
  25748. c := c shl 6+byte(Source[i]);
  25749. with UTF8_EXTRA[extra] do begin
  25750. dec(c,offset);
  25751. if c<minimum then
  25752. break; // invalid input content
  25753. end;
  25754. if (c<=255) and (NormToUpperByte[c]<=127) then begin
  25755. Dest^ := AnsiChar(NormToUpperByte[c]);
  25756. inc(Source,extra);
  25757. goto set1;
  25758. end;
  25759. S := Source-1; // leave UTF-8 encoding untouched
  25760. inc(Source,extra);
  25761. inc(extra);
  25762. MoveFast(S^,Dest^,extra);
  25763. inc(Dest,extra);
  25764. if (PtrUInt(Source) and 3=0) and (Source<EndSourceBy4) then goto By4 else
  25765. if Source<endSource then continue else break;
  25766. end;
  25767. until false;
  25768. end;
  25769. result := Dest;
  25770. end;
  25771. function UTF8UpperCopy255(dest: PAnsiChar; const source: RawUTF8): PUTF8Char;
  25772. var L: integer;
  25773. begin
  25774. L := length(source);
  25775. if L>0 then begin
  25776. if L>250 then
  25777. L := 250; // avoid buffer overflow
  25778. result := UTF8UpperCopy(pointer(dest),pointer(source),L);
  25779. end else
  25780. result := pointer(dest);
  25781. end;
  25782. function UpperCopy255W(dest: PAnsiChar; const source: SynUnicode): PAnsiChar;
  25783. var c: cardinal;
  25784. i,L: integer;
  25785. begin
  25786. L := length(source);
  25787. if L>0 then begin
  25788. if L>250 then
  25789. L := 250; // avoid buffer overflow
  25790. result := dest+L;
  25791. for i := 0 to L-1 do begin
  25792. c := PWordArray(source)[i];
  25793. if c<255 then
  25794. dest[i] := AnsiChar(NormToUpperAnsi7Byte[c]) else
  25795. dest[i] := '?';
  25796. end;
  25797. end else
  25798. result := dest;
  25799. end;
  25800. function UpperCopy255W(dest: PAnsiChar; source: PWideChar; L: integer): PAnsiChar;
  25801. var c: cardinal;
  25802. i: integer;
  25803. begin
  25804. if L>0 then begin
  25805. if L>250 then
  25806. L := 250; // avoid buffer overflow
  25807. result := dest+L;
  25808. for i := 0 to L-1 do begin
  25809. c := PWordArray(source)[i];
  25810. if c<255 then
  25811. dest[i] := AnsiChar(NormToUpperAnsi7Byte[c]) else
  25812. dest[i] := '?';
  25813. end;
  25814. end else
  25815. result := dest;
  25816. end;
  25817. {$ifdef PUREPASCAL}
  25818. function UpperCopy(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;
  25819. var s: PAnsiChar;
  25820. c: cardinal;
  25821. begin
  25822. s := pointer(source);
  25823. if s<>nil then
  25824. repeat
  25825. c := ord(s^);
  25826. if c=0 then
  25827. break else
  25828. dest^ := AnsiChar(NormToUpperAnsi7Byte[c]);
  25829. inc(s);
  25830. inc(dest);
  25831. until false;
  25832. result := dest;
  25833. end;
  25834. {$else}
  25835. function UpperCopy(dest: PAnsiChar; const source: RawUTF8): PAnsiChar;
  25836. asm // eax=dest source=edx
  25837. test edx,edx
  25838. jz @z
  25839. push esi
  25840. mov esi,offset NormToUpperAnsi7
  25841. xor ecx,ecx
  25842. @1: mov cl,[edx]
  25843. inc edx
  25844. test cl,cl
  25845. mov cl,[esi+ecx]
  25846. jz @2
  25847. mov [eax],cl
  25848. inc eax
  25849. jmp @1
  25850. @2: pop esi
  25851. @z:
  25852. end;
  25853. {$endif}
  25854. {$ifdef PUREPASCAL}
  25855. function UpperCopyShort(dest: PAnsiChar; const source: shortstring): PAnsiChar;
  25856. var i: PtrInt;
  25857. begin
  25858. for i := 1 to ord(source[0]) do begin
  25859. dest^ := AnsiChar(NormToUpperAnsi7Byte[ord(source[i])]);
  25860. inc(dest);
  25861. end;
  25862. result := dest;
  25863. end;
  25864. {$else}
  25865. function UpperCopyShort(dest: PAnsiChar; const source: shortstring): PAnsiChar;
  25866. asm // eax=dest source=edx
  25867. push esi
  25868. push ebx
  25869. movzx ebx,byte ptr [edx] // ebx = length(source)
  25870. xor ecx,ecx
  25871. test ebx,ebx
  25872. mov esi,offset NormToUpperAnsi7
  25873. jz @2 // source=''
  25874. inc edx
  25875. @1: mov cl,[edx]
  25876. inc edx
  25877. dec ebx
  25878. mov cl,[esi+ecx]
  25879. mov [eax],cl
  25880. lea eax,[eax+1]
  25881. jnz @1
  25882. @2: pop ebx
  25883. pop esi
  25884. @z:
  25885. end;
  25886. {$endif}
  25887. function GetNextLine(source: PUTF8Char; out next: PUTF8Char): RawUTF8;
  25888. begin
  25889. next := source;
  25890. if source=nil then begin
  25891. result := '';
  25892. exit;
  25893. end;
  25894. while source^ in ANSICHARNOT01310 do inc(source);
  25895. SetString(result,PAnsiChar(next),source-next);
  25896. if source^=#13 then inc(source);
  25897. if source^=#10 then inc(source);
  25898. if source^=#0 then
  25899. next := nil else
  25900. next := source;
  25901. end;
  25902. {$ifdef UNICODE}
  25903. function GetNextLineW(source: PWideChar; out next: PWideChar): string;
  25904. begin
  25905. next := source;
  25906. if source=nil then begin
  25907. result := '';
  25908. exit;
  25909. end;
  25910. while not (cardinal(source^) in [0,10,13]) do inc(source);
  25911. SetString(result,PChar(next),source-next);
  25912. if source^=#13 then inc(source);
  25913. if source^=#10 then inc(source);
  25914. if source^=#0 then
  25915. next := nil else
  25916. next := source;
  25917. end;
  25918. function FindIniNameValueW(P: PWideChar; UpperName: PUTF8Char): string;
  25919. var PBeg: PWideChar;
  25920. L: PtrInt;
  25921. begin
  25922. while (P<>nil) and (P^<>'[') do begin
  25923. PBeg := P;
  25924. while not (cardinal(P^) in [0,10,13]) do inc(P);
  25925. while cardinal(P^) in [10,13] do inc(P);
  25926. if P^=#0 then P := nil;
  25927. if PBeg^=' ' then repeat inc(PBeg) until PBeg^<>' '; // trim left ' '
  25928. if IdemPCharW(PBeg,UpperName) then begin
  25929. inc(PBeg,StrLen(UpperName));
  25930. L := 0; while PBeg[L]>=' ' do inc(L); // get line length
  25931. SetString(result,PBeg,L);
  25932. exit;
  25933. end;
  25934. end;
  25935. result := '';
  25936. end;
  25937. function FindIniEntryW(const Content: string; const Section, Name: RawUTF8): string;
  25938. var P: PWideChar;
  25939. UpperSection, UpperName: array[byte] of AnsiChar;
  25940. // possible GPF if length(Section/Name)>255, but should const in code
  25941. begin
  25942. result := '';
  25943. P := pointer(Content);
  25944. if P=nil then exit;
  25945. // UpperName := UpperCase(Name)+'=';
  25946. PWord(UpperCopy255(UpperName,Name))^ := ord('=');
  25947. if Section='' then
  25948. // find the Name= entry before any [Section]
  25949. result := FindIniNameValueW(P,UpperName) else begin
  25950. // find the Name= entry in the specified [Section]
  25951. PWord(UpperCopy255(UpperSection,Section))^ := ord(']');
  25952. if FindSectionFirstLineW(P,UpperSection) then
  25953. result := FindIniNameValueW(P,UpperName);
  25954. end;
  25955. end;
  25956. {$endif}
  25957. function IdemPCharAndGetNextLine(var source: PUTF8Char; searchUp: PAnsiChar): boolean;
  25958. {$ifdef PUREPASCAL}
  25959. begin
  25960. if source=nil then
  25961. result := false else begin
  25962. result := IdemPChar(source,searchUp);
  25963. while source^ in ANSICHARNOT01310 do inc(source);
  25964. while source^ in [#13,#10] do inc(source);
  25965. if source^=#0 then
  25966. source := nil;
  25967. end;
  25968. end;
  25969. {$else}
  25970. asm // eax=source edx=searchUp
  25971. push eax // save source var
  25972. mov eax,[eax] // eax=source
  25973. test eax,eax
  25974. jz @z
  25975. push eax
  25976. call IdemPChar
  25977. pop ecx // ecx=source
  25978. push eax // save result
  25979. @1: mov dl,[ecx] // while not (source^ in [#0,#10,#13]) do inc(source);
  25980. inc ecx
  25981. cmp dl,13
  25982. ja @1
  25983. je @e
  25984. or dl,dl
  25985. jz @0
  25986. cmp dl,10
  25987. jne @1
  25988. jmp @4
  25989. @e: cmp byte ptr [ecx],10 // jump #13#10
  25990. jne @4
  25991. @3: inc ecx
  25992. @4: pop eax // restore result
  25993. pop edx // restore source var
  25994. mov [edx],ecx // update source var
  25995. ret
  25996. @0: xor ecx,ecx // set source=nil
  25997. jmp @4
  25998. @z: pop edx // ignore source var, result := false
  25999. end;
  26000. {$endif}
  26001. function IdemPCharAndGetNextItem(var source: PUTF8Char; const searchUp: RawUTF8;
  26002. var Item: RawUTF8; Sep: AnsiChar): boolean;
  26003. begin
  26004. if source=nil then
  26005. result := false else begin
  26006. result := IdemPChar(source,Pointer(searchUp));
  26007. if result then begin
  26008. inc(source,Length(searchUp));
  26009. Item := GetNextItem(source,Sep);
  26010. end;
  26011. end;
  26012. end;
  26013. function GetNextLineBegin(source: PUTF8Char; out next: PUTF8Char): PUTF8Char;
  26014. begin
  26015. result := pointer(source);
  26016. if source=nil then
  26017. exit;
  26018. while source^ in ANSICHARNOT01310 do inc(source);
  26019. if source^=#13 then inc(source);
  26020. if source^=#10 then inc(source);
  26021. if source^=#0 then
  26022. next := nil else
  26023. next := source;
  26024. end;
  26025. function GetLineSize(P,PEnd: PUTF8Char): PtrUInt;
  26026. begin
  26027. result := PtrUInt(P);
  26028. if P<>nil then
  26029. if PEnd=nil then
  26030. while P^ in ANSICHARNOT01310 do
  26031. inc(P) else
  26032. while (P<PEnd) and (P^ in ANSICHARNOT01310) do
  26033. inc(P);
  26034. result := PtrUInt(P)-result;
  26035. end;
  26036. function GetNextItem(var P: PUTF8Char; Sep: AnsiChar= ','): RawUTF8;
  26037. var S: PUTF8Char;
  26038. begin
  26039. if P=nil then
  26040. result := '' else begin
  26041. S := P;
  26042. while (S^<>#0) and (S^<>Sep) do
  26043. inc(S);
  26044. SetString(result,P,S-P);
  26045. if S^<>#0 then
  26046. P := S+1 else
  26047. P := nil;
  26048. end;
  26049. end;
  26050. function GetNextItemString(var P: PChar; Sep: Char= ','): string;
  26051. // this function will compile into AnsiString or UnicodeString, depending
  26052. // of the compiler version
  26053. var S: PChar;
  26054. begin
  26055. if P=nil then
  26056. result := '' else begin
  26057. S := P;
  26058. while (S^<>#0) and (S^<>Sep) do
  26059. inc(S);
  26060. SetString(result,P,S-P);
  26061. if S^<>#0 then
  26062. P := S+1 else
  26063. P := nil;
  26064. end;
  26065. end;
  26066. function GetNextStringLineToRawUnicode(var P: PChar): RawUnicode;
  26067. var S: PChar;
  26068. begin
  26069. if P=nil then
  26070. result := '' else begin
  26071. S := P;
  26072. while S^>=' ' do
  26073. inc(S);
  26074. result := StringToRawUnicode(P,S-P);
  26075. while (S^<>#0) and (S^<' ') do inc(S); // ignore e.g. #13 or #10
  26076. if S^<>#0 then
  26077. P := S else
  26078. P := nil;
  26079. end;
  26080. end;
  26081. procedure AppendCSVValues(const CSV: string; const Values: array of string;
  26082. var Result: string; const AppendBefore: string=#13#10);
  26083. var Caption: string;
  26084. i, bool: integer;
  26085. P: PChar;
  26086. first: Boolean;
  26087. begin
  26088. P := pointer(CSV);
  26089. if P=nil then
  26090. exit;
  26091. first := True;
  26092. for i := 0 to high(Values) do begin
  26093. Caption := GetNextItemString(P);
  26094. if Values[i]<>'' then begin
  26095. if first then begin
  26096. Result := Result+#13#10;
  26097. first := false;
  26098. end else
  26099. Result := Result+AppendBefore;
  26100. bool := FindCSVIndex('0,-1',RawUTF8(Values[i]));
  26101. Result := Result+Caption+': ';
  26102. if bool<0 then
  26103. Result := Result+Values[i] else
  26104. Result := Result+GetCSVItemString(pointer(GetNextItemString(P)),bool,'/');
  26105. end;
  26106. end;
  26107. end;
  26108. procedure GetNextItemShortString(var P: PUTF8Char; out Dest: ShortString; Sep: AnsiChar= ',');
  26109. var S: PUTF8Char;
  26110. len: integer;
  26111. begin
  26112. if P=nil then
  26113. Dest[0] := #0 else begin
  26114. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  26115. S := P;
  26116. while (S^<>#0) and (S^<>Sep) do
  26117. inc(S);
  26118. len := S-P;
  26119. while (P[len-1] in [#1..' ']) and (len>0) do dec(len); // trim right spaces
  26120. SetString(Dest,P,len);
  26121. if S^<>#0 then
  26122. P := S+1 else
  26123. P := nil;
  26124. end;
  26125. end;
  26126. function GetNextItemCardinal(var P: PUTF8Char; Sep: AnsiChar= ','): PtrUInt;
  26127. var c: PtrUInt;
  26128. begin
  26129. if P=nil then begin
  26130. result := 0;
  26131. exit;
  26132. end;
  26133. c := byte(P^)-48;
  26134. if c>9 then
  26135. result := 0 else begin
  26136. result := c;
  26137. inc(P);
  26138. repeat
  26139. c := byte(P^)-48;
  26140. if c>9 then
  26141. break else
  26142. result := result*10+c;
  26143. inc(P);
  26144. until false;
  26145. end;
  26146. while (P^<>#0) and (P^<>Sep) do // go to end of CSV item (ignore any decimal)
  26147. inc(P);
  26148. if P^=#0 then
  26149. P := nil else
  26150. inc(P);
  26151. end;
  26152. function GetNextItemCardinalStrict(var P: PUTF8Char): PtrUInt;
  26153. var c: PtrUInt;
  26154. begin
  26155. if P=nil then begin
  26156. result := 0;
  26157. exit;
  26158. end;
  26159. c := byte(P^)-48;
  26160. if c>9 then
  26161. result := 0 else begin
  26162. result := c;
  26163. inc(P);
  26164. repeat
  26165. c := byte(P^)-48;
  26166. if c>9 then
  26167. break else
  26168. result := result*10+c;
  26169. inc(P);
  26170. until false;
  26171. end;
  26172. if P^=#0 then
  26173. P := nil;
  26174. end;
  26175. function CSVOfValue(const Value: RawUTF8; Count: cardinal; const Sep: RawUTF8=','): RawUTF8;
  26176. var ValueLen, SepLen: cardinal;
  26177. i: cardinal;
  26178. P: PAnsiChar;
  26179. begin // CSVOfValue('?',3)='?,?,?'
  26180. if Count=0 then begin
  26181. result := '';
  26182. exit;
  26183. end;
  26184. ValueLen := length(Value);
  26185. SepLen := Length(Sep);
  26186. Setlength(result,ValueLen*Count+SepLen*pred(Count));
  26187. P := pointer(result);
  26188. i := 1;
  26189. repeat
  26190. MoveFast(Pointer(Value)^,P^,ValueLen);
  26191. inc(P,ValueLen);
  26192. if i=Count then
  26193. break;
  26194. MoveFast(Pointer(Sep)^,P^,SepLen);
  26195. inc(P,SepLen);
  26196. inc(i);
  26197. until false;
  26198. assert(P-pointer(result)=length(result));
  26199. end;
  26200. procedure SetBitCSV(var Bits; BitsCount: integer; var P: PUTF8Char);
  26201. var bit,last: cardinal;
  26202. begin
  26203. while P<>nil do begin
  26204. bit := GetNextItemCardinalStrict(P)-1; // '0' marks end of list
  26205. if bit>=cardinal(BitsCount) then
  26206. break; // avoid GPF
  26207. if (P=nil) or (P^=',') then
  26208. SetBit(Bits,bit) else
  26209. if P^='-' then begin
  26210. inc(P);
  26211. last := GetNextItemCardinalStrict(P)-1; // '0' marks end of list
  26212. if last>=Cardinal(BitsCount) then
  26213. exit;
  26214. while bit<=last do begin
  26215. SetBit(Bits,bit);
  26216. inc(bit);
  26217. end;
  26218. end;
  26219. if (P<>nil) and (P^=',') then
  26220. inc(P);
  26221. end;
  26222. if (P<>nil) and (P^=',') then
  26223. inc(P);
  26224. end;
  26225. function GetBitCSV(const Bits; BitsCount: integer): RawUTF8;
  26226. var i,j: integer;
  26227. begin
  26228. result := '';
  26229. i := 0;
  26230. while i<BitsCount do
  26231. if GetBit(Bits,i) then begin
  26232. j := i;
  26233. while (j+1<BitsCount) and GetBit(Bits,j+1) do
  26234. inc(j);
  26235. result := result+UInt32ToUtf8(i+1);
  26236. if j=i then
  26237. result := result+',' else
  26238. if j=i+1 then
  26239. result := result+','+UInt32ToUtf8(j+1)+',' else
  26240. result := result+'-'+UInt32ToUtf8(j+1)+',';
  26241. i := j+1;
  26242. end else
  26243. inc(i);
  26244. result := result+'0'; // '0' marks end of list
  26245. end;
  26246. function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar= ','): PtrUInt;
  26247. var c: PtrUInt;
  26248. begin
  26249. if P=nil then begin
  26250. result := 0;
  26251. exit;
  26252. end;
  26253. c := word(P^)-48;
  26254. if c>9 then
  26255. result := 0 else begin
  26256. result := c;
  26257. inc(P);
  26258. repeat
  26259. c := word(P^)-48;
  26260. if c>9 then
  26261. break else
  26262. result := result*10+c;
  26263. inc(P);
  26264. until false;
  26265. end;
  26266. while (P^<>#0) and (P^<>Sep) do // go to end of CSV item (ignore any decimal)
  26267. inc(P);
  26268. if P^=#0 then
  26269. P := nil else
  26270. inc(P);
  26271. end;
  26272. function GetNextItemInteger(var P: PUTF8Char; Sep: AnsiChar= ','): PtrInt;
  26273. var minus: boolean;
  26274. begin
  26275. if P=nil then begin
  26276. result := 0;
  26277. exit;
  26278. end;
  26279. if (P^ in ['+','-']) then begin
  26280. minus := P^='-';
  26281. inc(P);
  26282. end else
  26283. minus := false;
  26284. result := PtrInt(GetNextItemCardinal(P,Sep));
  26285. if minus then
  26286. result := -result;
  26287. end;
  26288. function GetNextItemInt64(var P: PUTF8Char; Sep: AnsiChar= ','): Int64;
  26289. {$ifdef CPU64}
  26290. begin
  26291. result := GetNextItemInteger(P,Sep);
  26292. end;
  26293. {$else}
  26294. var tmp: array[0..63] of AnsiChar;
  26295. i: integer;
  26296. begin
  26297. result := 0;
  26298. if P=nil then
  26299. exit;
  26300. i := 0;
  26301. while (P[i]<>#0) and (P[i]<>Sep) do begin
  26302. tmp[i] := P[i];
  26303. inc(i);
  26304. if i>=sizeof(tmp) then
  26305. exit;
  26306. end;
  26307. tmp[i] := #0;
  26308. inc(P,i); // P[i]=Sep or #0
  26309. if P^=#0 then
  26310. P := nil else
  26311. inc(P);
  26312. SetInt64(tmp,result);
  26313. end;
  26314. {$endif}
  26315. function GetNextItemDouble(var P: PUTF8Char; Sep: AnsiChar= ','): double;
  26316. var tmp: array[0..63] of AnsiChar;
  26317. i,err: integer;
  26318. begin
  26319. result := 0;
  26320. if P=nil then
  26321. exit;
  26322. i := 0;
  26323. while (P[i]<>#0) and (P[i]<>Sep) do begin
  26324. tmp[i] := P[i];
  26325. inc(i);
  26326. if i>=sizeof(tmp) then
  26327. exit;
  26328. end;
  26329. tmp[i] := #0;
  26330. inc(P,i); // P[i]=Sep or #0
  26331. if P^=#0 then
  26332. P := nil else
  26333. inc(P);
  26334. result := GetExtended(tmp,err);
  26335. if err<>0 then
  26336. result := 0;
  26337. end;
  26338. function GetCSVItem(P: PUTF8Char; Index: PtrUInt; Sep: AnsiChar = ','): RawUTF8;
  26339. var i: PtrUInt;
  26340. begin
  26341. if P=nil then
  26342. result := '' else
  26343. for i := 0 to Index do
  26344. result := GetNextItem(P,Sep);
  26345. end;
  26346. function GetLastCSVItem(const CSV: RawUTF8; Sep: AnsiChar=','): RawUTF8;
  26347. var i: integer;
  26348. begin
  26349. for i := length(CSV) downto 1 do
  26350. if CSV[i]=Sep then begin
  26351. result := copy(CSV,i+1,maxInt);
  26352. exit;
  26353. end;
  26354. result := CSV;
  26355. end;
  26356. function GetCSVItemString(P: PChar; Index: PtrUInt; Sep: Char = ','): string;
  26357. var i: PtrUInt;
  26358. begin
  26359. if P=nil then
  26360. result := '' else
  26361. for i := 0 to Index do
  26362. result := GetNextItemString(P,Sep);
  26363. end;
  26364. function FindCSVIndex(CSV: PUTF8Char; const Value: RawUTF8; Sep: AnsiChar;
  26365. CaseSensitive,TrimValue: boolean): integer;
  26366. var s: RawUTF8;
  26367. begin
  26368. result := 0;
  26369. while CSV<>nil do begin
  26370. s := GetNextItem(CSV,Sep);
  26371. if TrimValue then
  26372. s := trim(s);
  26373. if CaseSensitive then begin
  26374. if s=Value then
  26375. exit;
  26376. end else
  26377. if SameTextU(s,Value) then
  26378. exit;
  26379. inc(result);
  26380. end;
  26381. result := -1; // not found
  26382. end;
  26383. procedure CSVToRawUTF8DynArray(CSV: PUTF8Char; var Result: TRawUTF8DynArray;
  26384. Sep: AnsiChar);
  26385. var s: RawUTF8;
  26386. begin
  26387. while CSV<>nil do begin
  26388. s := GetNextItem(CSV,Sep);
  26389. if s<>'' then begin
  26390. SetLength(Result,length(Result)+1);
  26391. Result[high(Result)] := s;
  26392. end;
  26393. end;
  26394. end;
  26395. procedure CSVToRawUTF8DynArray(const CSV,Sep,SepEnd: RawUTF8; var Result: TRawUTF8DynArray);
  26396. var offs,i: integer;
  26397. begin
  26398. offs := 1;
  26399. while offs<length(CSV) do begin
  26400. SetLength(Result,length(Result)+1);
  26401. i := PosEx(Sep,CSV,offs);
  26402. if i=0 then begin
  26403. i := PosEx(SepEnd,CSV,offs);
  26404. if i=0 then
  26405. i := MaxInt else
  26406. dec(i,offs);
  26407. Result[high(Result)] := Copy(CSV,offs,i);
  26408. exit;
  26409. end;
  26410. Result[high(Result)] := Copy(CSV,offs,i-offs);
  26411. offs := i+length(sep);
  26412. end;
  26413. end;
  26414. function AddPrefixToCSV(CSV: PUTF8Char; const Prefix: RawUTF8; Sep: AnsiChar = ','): RawUTF8;
  26415. var s: RawUTF8;
  26416. begin
  26417. result := GetNextItem(CSV,Sep);
  26418. if result='' then
  26419. exit;
  26420. result := Prefix+result;
  26421. while CSV<>nil do begin
  26422. s := GetNextItem(CSV,Sep);
  26423. if s<>'' then
  26424. result := result+','+Prefix+s;
  26425. end;
  26426. end;
  26427. function RawUTF8ArrayToCSV(const Values: array of RawUTF8; const Sep: RawUTF8 = ','): RawUTF8;
  26428. var i, len, seplen, L: Integer;
  26429. P: PAnsiChar;
  26430. begin
  26431. result := '';
  26432. if high(Values)<0 then
  26433. exit;
  26434. seplen := length(Sep);
  26435. len := seplen*high(Values);
  26436. for i := 0 to high(Values) do
  26437. inc(len,length(Values[i]));
  26438. SetLength(result,len);
  26439. P := pointer(result);
  26440. i := 0;
  26441. repeat
  26442. L := length(Values[i]);
  26443. if L>0 then begin
  26444. MoveFast(pointer(Values[i])^,P^,L);
  26445. inc(P,L);
  26446. end;
  26447. if i=high(Values) then
  26448. Break;
  26449. if seplen>0 then begin
  26450. MoveFast(pointer(Sep)^,P^,seplen);
  26451. inc(P,seplen);
  26452. end;
  26453. inc(i);
  26454. until false;
  26455. Assert(P-pointer(result)=len);
  26456. end;
  26457. function RawUTF8ArrayToQuotedCSV(const Values: array of RawUTF8; const Sep: RawUTF8=',';
  26458. Quote: AnsiChar=''''): RawUTF8;
  26459. var i: integer;
  26460. tmp: TRawUTF8DynArray;
  26461. begin
  26462. SetLength(tmp,length(Values));
  26463. for i := 0 to High(Values) do
  26464. tmp[i] := QuotedStr(Values[i],Quote);
  26465. result := RawUTF8ArrayToCSV(tmp,Sep);
  26466. end;
  26467. function TRawUTF8DynArrayFrom(const Values: array of RawUTF8): TRawUTF8DynArray;
  26468. var i: integer;
  26469. begin
  26470. SetLength(result,length(Values));
  26471. for i := 0 to high(Values) do
  26472. result[i] := Values[i];
  26473. end;
  26474. procedure AddArrayOfConst(var Dest: TTVarRecDynArray; const Values: array of const);
  26475. var i,n: Integer;
  26476. begin
  26477. n := length(Dest);
  26478. SetLength(Dest,n+length(Values));
  26479. for i := 0 to high(Values) do
  26480. Dest[i+n] := Values[i];
  26481. end;
  26482. var
  26483. DefaultTextWriterJSONClass: TTextWriterClass = TTextWriter;
  26484. DefaultTextWriterTrimEnum: boolean;
  26485. function ObjectToJSON(Value: TObject; Options: TTextWriterWriteObjectOptions): RawUTF8;
  26486. begin
  26487. if Value=nil then
  26488. result := 'null' else
  26489. with DefaultTextWriterJSONClass.CreateOwnedStream do
  26490. try
  26491. include(fCustomOptions,twoForceJSONStandard);
  26492. WriteObject(Value,Options);
  26493. SetText(result);
  26494. finally
  26495. Free;
  26496. end;
  26497. end;
  26498. function ObjectsToJSON(const Names: array of RawUTF8; const Values: array of TObject;
  26499. Options: TTextWriterWriteObjectOptions=[woDontStoreDefault]): RawUTF8;
  26500. var i,n: integer;
  26501. begin
  26502. with DefaultTextWriterJSONClass.CreateOwnedStream do
  26503. try
  26504. n := length(Names);
  26505. Add('{');
  26506. for i := 0 to high(Values) do
  26507. if Values[i]<>nil then begin
  26508. if i<n then
  26509. AddFieldName(Names[i]) else
  26510. AddPropName(PShortString(PPointer(PPtrInt(Values[i])^+vmtClassName)^)^);
  26511. WriteObject(Values[i],Options);
  26512. Add(',');
  26513. end;
  26514. CancelLastComma;
  26515. Add('}');
  26516. SetText(result);
  26517. finally
  26518. Free;
  26519. end;
  26520. end;
  26521. function UrlEncode(const svar: RawUTF8): RawUTF8;
  26522. begin
  26523. result := UrlEncode(pointer(svar));
  26524. end;
  26525. function UrlEncode(Text: PUTF8Char): RawUTF8;
  26526. function Enc(s, p: PUTF8Char): PUTF8Char;
  26527. var c: PtrInt;
  26528. begin
  26529. repeat
  26530. c := ord(s^);
  26531. case c of
  26532. 0: break;
  26533. ord('0')..ord('9'),ord('a')..ord('z'),ord('A')..ord('Z'),
  26534. ord('_'),ord('-'),ord('.'),ord('~'): begin
  26535. // cf. rfc3986 2.3. Unreserved Characters
  26536. p^ := AnsiChar(c);
  26537. inc(p);
  26538. inc(s);
  26539. continue;
  26540. end;
  26541. ord(' '): p^ := '+';
  26542. else begin
  26543. p^ := '%'; inc(p);
  26544. PWord(p)^ := TwoDigitsHexWB[c]; inc(p);
  26545. end;
  26546. end; // case c of
  26547. inc(p);
  26548. inc(s);
  26549. until false;
  26550. result := p;
  26551. end;
  26552. function Size(s: PUTF8Char): PtrInt;
  26553. begin
  26554. result := 0;
  26555. if s<>nil then
  26556. repeat
  26557. case s^ of
  26558. #0: exit;
  26559. '0'..'9','a'..'z','A'..'Z','_','-','.','~',' ': begin
  26560. inc(result);
  26561. inc(s);
  26562. continue;
  26563. end;
  26564. else inc(result,3);
  26565. end;
  26566. inc(s);
  26567. until false;
  26568. end;
  26569. begin
  26570. result := '';
  26571. if Text=nil then
  26572. exit;
  26573. SetLength(result,Size(Text)); // reserve exact memory count
  26574. Enc(Text,pointer(result));
  26575. end;
  26576. function UrlEncode(const NameValuePairs: array of const): RawUTF8;
  26577. // (['select','*','where','ID=12','offset',23,'object',aObject]);
  26578. var A, n: PtrInt;
  26579. name, value: RawUTF8;
  26580. function Invalid(P: PAnsiChar): boolean;
  26581. begin
  26582. result := true;
  26583. if P<>nil then begin
  26584. repeat // cf. rfc3986 2.3. Unreserved Characters
  26585. if not (P^ in ['a'..'z','A'..'Z','0'..'9','_','.','~']) then
  26586. exit else
  26587. inc(P);
  26588. until P^=#0;
  26589. result := false;
  26590. end;
  26591. end;
  26592. begin
  26593. result := '';
  26594. n := high(NameValuePairs);
  26595. if n>0 then begin
  26596. for A := 0 to n shr 1 do begin
  26597. VarRecToUTF8(NameValuePairs[A*2],name);
  26598. if Invalid(pointer(name)) then
  26599. continue; // just skip invalid names
  26600. with NameValuePairs[A*2+1] do
  26601. if VType=vtObject then
  26602. value := ObjectToJSON(VObject,[]) else
  26603. VarRecToUTF8(NameValuePairs[A*2+1],value);
  26604. result := result+'&'+name+'='+UrlEncode(value);
  26605. end;
  26606. result[1] := '?';
  26607. end;
  26608. end;
  26609. function UrlEncodeJsonObject(const URIName: RawUTF8; ParametersJSON: PUTF8Char;
  26610. const PropNamesToIgnore: array of RawUTF8): RawUTF8;
  26611. var i,j, NameLen: integer;
  26612. sep: AnsiChar;
  26613. Params: TNameValuePUTF8CharDynArray;
  26614. begin
  26615. if ParametersJSON=nil then
  26616. result := URIName else
  26617. with TTextWriter.CreateOwnedStream do
  26618. try
  26619. AddString(URIName);
  26620. if (JSONDecode(ParametersJSON,Params,true)<>nil) and (Params<>nil) then begin
  26621. sep := '?';
  26622. for i := 0 to High(Params) do begin
  26623. NameLen := StrLen(Params[i].Name);
  26624. for j := 0 to high(PropNamesToIgnore) do
  26625. if IdemPropNameU(PropNamesToIgnore[j],Params[i].Name,NameLen) then begin
  26626. NameLen := 0;
  26627. break;
  26628. end;
  26629. if NameLen=0 then
  26630. continue;
  26631. Add(sep);
  26632. AddNoJSONEscape(Params[i].Name,NameLen);
  26633. Add('=');
  26634. AddString(UrlEncode(Params[i].Value));
  26635. sep := '&';
  26636. end;
  26637. end;
  26638. SetText(result);
  26639. finally
  26640. Free;
  26641. end;
  26642. end;
  26643. function UrlDecode(const s: RawUTF8; i: PtrInt = 1; len: PtrInt = -1): RawUTF8;
  26644. var L: PtrInt;
  26645. P: PUTF8Char;
  26646. begin
  26647. result := '';
  26648. if s='' then
  26649. exit;
  26650. L := PStrRec(Pointer(PtrInt(s)-STRRECSIZE))^.length;
  26651. if len<0 then
  26652. len := L;
  26653. if i>L then
  26654. exit;
  26655. dec(i);
  26656. if len=i then
  26657. exit;
  26658. Setlength(result,len-i); // reserve enough space for result
  26659. P := pointer(result);
  26660. while i<len do begin
  26661. case s[i+1] of
  26662. #0: break; // reached end of s
  26663. '%': if not HexToChar(PAnsiChar(pointer(s))+i+1,P) then
  26664. P^ := s[i+1] else
  26665. inc(i,2); // browsers may not follow the RFC (e.g. encode % as % !)
  26666. '+': P^ := ' ';
  26667. else
  26668. P^ := s[i+1];
  26669. end; // case s[i] of
  26670. inc(i);
  26671. inc(P);
  26672. end;
  26673. Setlength(result,P-pointer(Result)); // fast with FastMM4/SynScaleMM (in-place realloc)
  26674. end;
  26675. function UrlDecode(U: PUTF8Char): RawUTF8;
  26676. var P,Dest: PUTF8Char;
  26677. L: integer;
  26678. tmp: array[byte] of AnsiChar;
  26679. begin
  26680. L := StrLen(U);
  26681. if L=0 then begin
  26682. result := '';
  26683. exit;
  26684. end;
  26685. if L>sizeof(tmp) then begin
  26686. SetLength(result,L);
  26687. Dest := pointer(result);
  26688. end else
  26689. Dest := @tmp;
  26690. P := Dest;
  26691. repeat
  26692. case U^ of
  26693. #0: break; // reached end of URI
  26694. '%': if not HexToChar(PAnsiChar(U+1),P) then
  26695. P^ := U^ else
  26696. inc(U,2); // browsers may not follow the RFC (e.g. encode % as % !)
  26697. '+': P^ := ' ';
  26698. else
  26699. P^ := U^;
  26700. end; // case s[i] of
  26701. inc(U);
  26702. inc(P);
  26703. until false;
  26704. if Dest=@tmp then
  26705. SetString(result,PAnsiChar(@tmp),P-Dest) else
  26706. SetLength(result,P-Dest);
  26707. end;
  26708. function UrlDecodeNextValue(U: PUTF8Char; out Value: RawUTF8): PUTF8Char;
  26709. var Beg,V: PUTF8Char;
  26710. len, i: PtrInt;
  26711. begin
  26712. if U<>nil then begin
  26713. // compute resulting length of value
  26714. Beg := U;
  26715. len := 0;
  26716. while (U^<>#0) and (U^<>'&') do begin
  26717. if (U^='%') and HexToCharValid(PAnsiChar(U+1)) then
  26718. inc(U,3) else
  26719. inc(U);
  26720. inc(len);
  26721. end;
  26722. // decode value content
  26723. SetLength(Value,len);
  26724. V := pointer(Value);
  26725. U := Beg;
  26726. for i := 1 to len do
  26727. if (U^='%') and HexToChar(PAnsiChar(U+1),V) then begin
  26728. inc(V);
  26729. inc(U,3);
  26730. end else begin
  26731. if U^='+' then
  26732. V^ := ' ' else
  26733. V^ := U^;
  26734. inc(V);
  26735. inc(U);
  26736. end;
  26737. end;
  26738. result := U;
  26739. end;
  26740. function UrlDecodeNextName(U: PUTF8Char; out Name: RawUTF8): PUTF8Char;
  26741. var Beg, V: PUTF8Char;
  26742. len, i: PtrInt;
  26743. begin
  26744. result := nil;
  26745. if U=nil then
  26746. exit;
  26747. // compute resulting length of name
  26748. Beg := U;
  26749. len := 0;
  26750. repeat
  26751. case U^ of
  26752. #0: exit;
  26753. '=': begin
  26754. result := U+1;
  26755. break;
  26756. end;
  26757. '%': if (U[1]='3') and (U[2] in ['D','d']) then begin
  26758. result := U+3;
  26759. break; // %3d means = according to the RFC
  26760. end else
  26761. if HexToCharValid(PAnsiChar(U+1)) then
  26762. inc(U,3) else
  26763. inc(U);
  26764. else inc(U);
  26765. end;
  26766. inc(len);
  26767. until false;
  26768. // decode name content
  26769. SetLength(Name,len);
  26770. V := pointer(Name);
  26771. U := Beg;
  26772. for i := 1 to len do
  26773. if (U^='%') and HexToChar(PAnsiChar(U+1),V) then begin
  26774. inc(V);
  26775. inc(U,3);
  26776. end else begin
  26777. if U^='+' then
  26778. V^ := ' ' else
  26779. V^ := U^;
  26780. inc(V);
  26781. inc(U);
  26782. end;
  26783. end;
  26784. function UrlDecodeNextNameValue(U: PUTF8Char; var Name,Value: RawUTF8): PUTF8Char;
  26785. begin
  26786. result := nil;
  26787. if U=nil then
  26788. exit;
  26789. U := UrlDecodeNextName(U,Name);
  26790. if U=nil then
  26791. exit;
  26792. U := UrlDecodeNextValue(U,Value);
  26793. if U^=#0 then
  26794. result := U else
  26795. result := U+1; // jump '&' to let decode the next name=value pair
  26796. end;
  26797. function UrlDecodeValue(U: PUTF8Char; Upper: PAnsiChar; var Value: RawUTF8;
  26798. Next: PPUTF8Char=nil): boolean;
  26799. begin
  26800. // UrlDecodeValue('select=%2A&where=LastName%3D%27M%C3%B4net%27','SELECT=',V,@U)
  26801. // -> U^='where=...' and V='*'
  26802. result := false; // mark value not modified by default
  26803. if U=nil then begin
  26804. if Next<>nil then
  26805. Next^ := U;
  26806. exit;
  26807. end;
  26808. if IdemPChar(U,Upper) then begin
  26809. result := true;
  26810. inc(U,StrLen(PUTF8Char(Upper)));
  26811. U := UrlDecodeNextValue(U,Value);
  26812. end;
  26813. if Next=nil then
  26814. exit;
  26815. while not(U^ in [#0,'&']) do inc(U);
  26816. if U^=#0 then
  26817. Next^ := nil else
  26818. Next^ := U+1; // jump '&'
  26819. end;
  26820. function UrlDecodeInteger(U: PUTF8Char; Upper: PAnsiChar;
  26821. var Value: integer; Next: PPUTF8Char=nil): boolean;
  26822. var V: PtrInt;
  26823. SignNeg: boolean;
  26824. begin
  26825. // UrlDecodeInteger('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next)
  26826. // -> Next^='where=...' and O=20
  26827. result := false; // mark value not modified by default
  26828. if U=nil then begin
  26829. if Next<>nil then
  26830. Next^ := U;
  26831. exit;
  26832. end;
  26833. if IdemPChar(U,Upper) then begin
  26834. inc(U,StrLen(PUTF8Char(Upper)));
  26835. if U^='-' then begin
  26836. SignNeg := True;
  26837. Inc(U);
  26838. end else
  26839. SignNeg := false;
  26840. if U^ in ['0'..'9'] then begin
  26841. V := 0;
  26842. repeat
  26843. V := (V*10)+ord(U^)-48;
  26844. inc(U);
  26845. until not (U^ in ['0'..'9']);
  26846. if SignNeg then
  26847. Value := -V else
  26848. Value := V;
  26849. result := true;
  26850. end;
  26851. end;
  26852. if Next=nil then
  26853. exit;
  26854. while not(U^ in [#0,'&']) do inc(U);
  26855. if U^=#0 then
  26856. Next^ := nil else
  26857. Next^ := U+1; // jump '&'
  26858. end;
  26859. function UrlDecodeCardinal(U: PUTF8Char; Upper: PAnsiChar;var Value: Cardinal; Next: PPUTF8Char=nil): boolean;
  26860. var V: PtrInt;
  26861. begin
  26862. // UrlDecodeInteger('offset=20&where=LastName%3D%27M%C3%B4net%27','OFFSET=',O,@Next)
  26863. // -> Next^='where=...' and O=20
  26864. result := false; // mark value not modified by default
  26865. if U=nil then begin
  26866. if Next<>nil then
  26867. Next^ := U;
  26868. exit;
  26869. end;
  26870. if IdemPChar(U,Upper) then begin
  26871. inc(U,StrLen(PUTF8Char(Upper)));
  26872. if U^ in ['0'..'9'] then begin
  26873. V := 0;
  26874. repeat
  26875. V := (V*10)+ord(U^)-48;
  26876. inc(U);
  26877. until not (U^ in ['0'..'9']);
  26878. Value := V;
  26879. result := true;
  26880. end;
  26881. end;
  26882. if Next=nil then
  26883. exit;
  26884. while not(U^ in [#0,'&']) do inc(U);
  26885. if U^=#0 then
  26886. Next^ := nil else
  26887. Next^ := U+1; // jump '&'
  26888. end;
  26889. function UrlDecodeInt64(U: PUTF8Char; Upper: PAnsiChar;
  26890. var Value: Int64; Next: PPUTF8Char=nil): boolean;
  26891. var tmp: RawUTF8;
  26892. begin
  26893. result := UrlDecodeValue(U, Upper, tmp, Next);
  26894. if result then
  26895. SetInt64(pointer(tmp),Value);
  26896. end;
  26897. function UrlDecodeExtended(U: PUTF8Char; Upper: PAnsiChar;
  26898. var Value: TSynExtended; Next: PPUTF8Char=nil): boolean;
  26899. var tmp: RawUTF8;
  26900. err: integer;
  26901. begin
  26902. result := UrlDecodeValue(U, Upper, tmp, Next);
  26903. if result then begin
  26904. Value := GetExtended(pointer(tmp),err);
  26905. if err<>0 then
  26906. result := false;
  26907. end;
  26908. end;
  26909. function UrlDecodeDouble(U: PUTF8Char; Upper: PAnsiChar; var Value: double;
  26910. Next: PPUTF8Char=nil): boolean;
  26911. var tmp: RawUTF8;
  26912. err: integer;
  26913. begin
  26914. result := UrlDecodeValue(U, Upper, tmp, Next);
  26915. if result then begin
  26916. Value := GetExtended(pointer(tmp),err);
  26917. if err<>0 then
  26918. result := false;
  26919. end;
  26920. end;
  26921. function UrlDecodeNeedParameters(U, CSVNames: PUTF8Char): boolean;
  26922. var tmp: array[byte] of AnsiChar;
  26923. L: integer;
  26924. Beg: PUTF8Char;
  26925. // UrlDecodeNeedParameters('price=20.45&where=LastName%3D','price,where') will
  26926. // return TRUE
  26927. begin
  26928. result := (CSVNames=nil);
  26929. if result then
  26930. exit; // no parameter to check -> success
  26931. if U=nil then
  26932. exit; // no input data -> error
  26933. repeat
  26934. L := 0;
  26935. while (CSVNames^<>#0) and (CSVNames^<>',') do begin
  26936. tmp[L] := NormToUpper[CSVNames^];
  26937. if L=high(tmp) then
  26938. exit else // invalid CSV parameter
  26939. inc(L);
  26940. inc(CSVNames);
  26941. end;
  26942. if L=0 then
  26943. exit; // invalid CSV parameter
  26944. PWord(@tmp[L])^ := ord('=');
  26945. Beg := U;
  26946. repeat
  26947. if IdemPChar(U,tmp) then
  26948. break;
  26949. while not(U^ in [#0,'&']) do inc(U);
  26950. if U^=#0 then
  26951. exit else // didn't find tmp in U
  26952. inc(U); // Jump &
  26953. until false;
  26954. U := Beg;
  26955. if CSVNames^=#0 then
  26956. Break else // no more parameter to check
  26957. inc(CSVNames); // jump &
  26958. until false;
  26959. result := true; // all parameters found
  26960. end;
  26961. function CSVEncode(const NameValuePairs: array of const;
  26962. const KeySeparator, ValueSeparator: RawUTF8): RawUTF8;
  26963. var i: integer;
  26964. begin
  26965. if length(NameValuePairs)<2 then
  26966. result := '' else
  26967. with DefaultTextWriterJSONClass.CreateOwnedStream do
  26968. try
  26969. for i := 1 to length(NameValuePairs) shr 1 do begin
  26970. Add(NameValuePairs[i*2-2],twNone);
  26971. AddNoJSONEscape(pointer(KeySeparator),length(KeySeparator));
  26972. Add(NameValuePairs[i*2-1],twNone);
  26973. AddNoJSONEscape(pointer(ValueSeparator),length(ValueSeparator));
  26974. end;
  26975. SetText(result);
  26976. finally
  26977. Free;
  26978. end;
  26979. end;
  26980. function ArrayOfConstValueAsText(const NameValuePairs: array of const;
  26981. const aName: RawUTF8): RawUTF8;
  26982. var i: integer;
  26983. name: RawUTF8;
  26984. begin
  26985. for i := 1 to length(NameValuePairs) shr 1 do
  26986. if VarRecToUTF8IsString(NameValuePairs[i*2-2],name) and
  26987. IdemPropNameU(name,aName) then begin
  26988. VarRecToUTF8(NameValuePairs[i*2-1],result);
  26989. exit;
  26990. end;
  26991. result := '';
  26992. end;
  26993. function IsZero(P: pointer; Length: integer): boolean;
  26994. var i: integer;
  26995. begin
  26996. result := false;
  26997. for i := 1 to Length shr 4 do // 16 bytes (4 DWORD) by loop - aligned read
  26998. {$ifdef CPU64}
  26999. if (PInt64Array(P)^[0]<>0) or (PInt64Array(P)^[1]<>0) then
  27000. {$else}
  27001. if (PCardinalArray(P)^[0]<>0) or (PCardinalArray(P)^[1]<>0) or
  27002. (PCardinalArray(P)^[2]<>0) or (PCardinalArray(P)^[3]<>0) then
  27003. {$endif}
  27004. exit else
  27005. inc(PtrUInt(P),16);
  27006. for i := 1 to (Length shr 2)and 3 do // 4 bytes (1 DWORD) by loop
  27007. if PCardinal(P)^<>0 then
  27008. exit else
  27009. inc(PtrUInt(P),4);
  27010. for i := 1 to Length and 3 do // remaining content
  27011. if PByte(P)^<>0 then
  27012. exit else
  27013. inc(PtrUInt(P));
  27014. result := true;
  27015. end;
  27016. function IsZero(const Values: TRawUTF8DynArray): boolean;
  27017. var i: integer;
  27018. begin
  27019. result := false;
  27020. for i := 0 to length(Values)-1 do
  27021. if Values[i]<>'' then
  27022. exit;
  27023. result := true;
  27024. end;
  27025. function IsZero(const Values: TIntegerDynArray): boolean;
  27026. var i: integer;
  27027. begin
  27028. result := false;
  27029. for i := 0 to length(Values)-1 do
  27030. if Values[i]<>0 then
  27031. exit;
  27032. result := true;
  27033. end;
  27034. function IsZero(const Values: TInt64DynArray): boolean;
  27035. var i: integer;
  27036. begin
  27037. result := false;
  27038. for i := 0 to length(Values)-1 do
  27039. if Values[i]<>0 then
  27040. exit;
  27041. result := true;
  27042. end;
  27043. {$WARNINGS OFF} // yes, we know there will be dead code below: we rely on it ;)
  27044. function IsZero(const Fields: TSQLFieldBits): boolean; overload;
  27045. begin
  27046. if MAX_SQLFIELDS=64 then
  27047. result := (PInt64(@Fields)^=0) else
  27048. if MAX_SQLFields=128 then
  27049. result := (PInt64Array(@Fields)^[0]=0) and (PInt64Array(@Fields)^[1]=0) else
  27050. if MAX_SQLFields=192 then
  27051. result := (PInt64Array(@Fields)^[0]=0) and (PInt64Array(@Fields)^[1]=0) and
  27052. (PInt64Array(@Fields)^[2]=0) else
  27053. if MAX_SQLFields=256 then
  27054. result := (PInt64Array(@Fields)^[0]=0) and (PInt64Array(@Fields)^[1]=0) and
  27055. (PInt64Array(@Fields)^[2]=0) and (PInt64Array(@Fields)^[3]=0) else
  27056. result := IsZero(@Fields,sizeof(Fields))
  27057. end;
  27058. function IsEqual(const A,B: TSQLFieldBits): boolean;
  27059. begin
  27060. if MAX_SQLFIELDS=64 then
  27061. result := (PInt64(@A)^=PInt64(@B)^) else
  27062. if MAX_SQLFields=128 then
  27063. result := (PInt64Array(@A)^[0]=PInt64Array(@B)^[0]) and
  27064. (PInt64Array(@A)^[1]=PInt64Array(@B)^[1]) else
  27065. if MAX_SQLFields=192 then
  27066. result := (PInt64Array(@A)^[0]=PInt64Array(@B)^[0]) and
  27067. (PInt64Array(@A)^[1]=PInt64Array(@B)^[1]) and
  27068. (PInt64Array(@A)^[2]=PInt64Array(@B)^[2]) else
  27069. if MAX_SQLFields=256 then
  27070. result := (PInt64Array(@A)^[0]=PInt64Array(@B)^[0]) and
  27071. (PInt64Array(@A)^[1]=PInt64Array(@B)^[1]) and
  27072. (PInt64Array(@A)^[2]=PInt64Array(@B)^[2]) and
  27073. (PInt64Array(@A)^[3]=PInt64Array(@B)^[3]) else
  27074. result := CompareMem(@A,@B,sizeof(TSQLFieldBits))
  27075. end;
  27076. procedure FillZero(var Values: TRawUTF8DynArray);
  27077. var i: integer;
  27078. begin
  27079. for i := 0 to length(Values)-1 do
  27080. Values[i] := '';
  27081. end;
  27082. procedure FillZero(var Values: TIntegerDynArray);
  27083. begin
  27084. FillCharFast(Values[0],length(Values)*SizeOf(integer),0);
  27085. end;
  27086. procedure FillZero(var Values: TInt64DynArray); overload;
  27087. begin
  27088. FillCharFast(Values[0],length(Values)*SizeOf(Int64),0);
  27089. end;
  27090. procedure FillZero(var Fields: TSQLFieldBits);
  27091. begin
  27092. if MAX_SQLFIELDS=64 then
  27093. PInt64(@Fields)^ := 0 else
  27094. if MAX_SQLFields=128 then begin
  27095. PInt64Array(@Fields)^[0] := 0;
  27096. PInt64Array(@Fields)^[1] := 0;
  27097. end else
  27098. if MAX_SQLFields=192 then begin
  27099. PInt64Array(@Fields)^[0] := 0;
  27100. PInt64Array(@Fields)^[1] := 0;
  27101. PInt64Array(@Fields)^[2] := 0;
  27102. end else
  27103. if MAX_SQLFields=256 then begin
  27104. PInt64Array(@Fields)^[0] := 0;
  27105. PInt64Array(@Fields)^[1] := 0;
  27106. PInt64Array(@Fields)^[2] := 0;
  27107. PInt64Array(@Fields)^[3] := 0;
  27108. end else
  27109. FillcharFast(Fields,sizeof(Fields),0);
  27110. end;
  27111. {$WARNINGS ON}
  27112. procedure FieldBitsToIndex(const Fields: TSQLFieldBits; var Index: TSQLFieldIndexDynArray;
  27113. MaxLength,IndexStart: integer);
  27114. var i,n: integer;
  27115. sets: array[0..MAX_SQLFIELDS-1] of TSQLFieldIndex; // to avoid memory reallocation
  27116. begin
  27117. n := 0;
  27118. for i := 0 to MaxLength-1 do
  27119. if i in Fields then begin
  27120. sets[n] := i;
  27121. inc(n);
  27122. end;
  27123. SetLength(Index,IndexStart+n);
  27124. for i := 0 to n-1 do
  27125. Index[IndexStart+i] := sets[i];
  27126. end;
  27127. function FieldBitsToIndex(const Fields: TSQLFieldBits;
  27128. MaxLength: integer): TSQLFieldIndexDynArray;
  27129. begin
  27130. FieldBitsToIndex(Fields,result,MaxLength);
  27131. end;
  27132. function AddFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;
  27133. begin
  27134. result := length(Indexes);
  27135. SetLength(Indexes,result+1);
  27136. Indexes[result] := Field;
  27137. end;
  27138. function SearchFieldIndex(var Indexes: TSQLFieldIndexDynArray; Field: integer): integer;
  27139. begin
  27140. for result := 0 to length(Indexes)-1 do
  27141. if Indexes[result]=Field then
  27142. exit;
  27143. result := -1;
  27144. end;
  27145. procedure FieldIndexToBits(const Index: TSQLFieldIndexDynArray; var Fields: TSQLFieldBits);
  27146. var i: integer;
  27147. begin
  27148. FillcharFast(Fields,sizeof(Fields),0);
  27149. for i := 0 to Length(Index)-1 do
  27150. if Index[i]>=0 then
  27151. include(Fields,Index[i]);
  27152. end;
  27153. function FieldIndexToBits(const Index: TSQLFieldIndexDynArray): TSQLFieldBits;
  27154. begin
  27155. FieldIndexToBits(Index,result);
  27156. end;
  27157. function Hash32(const Text: RawByteString): cardinal;
  27158. begin
  27159. result := Hash32(pointer(Text),length(Text));
  27160. end;
  27161. function Hash32(Data: pointer; Len: integer): cardinal;
  27162. var s1,s2: cardinal;
  27163. i: PtrInt;
  27164. begin
  27165. if Data<>nil then begin
  27166. s1 := 0;
  27167. s2 := 0;
  27168. for i := 1 to Len shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read
  27169. inc(s1,PCardinalArray(Data)^[0]);
  27170. inc(s2,s1);
  27171. inc(s1,PCardinalArray(Data)^[1]);
  27172. inc(s2,s1);
  27173. inc(s1,PCardinalArray(Data)^[2]);
  27174. inc(s2,s1);
  27175. inc(s1,PCardinalArray(Data)^[3]);
  27176. inc(s2,s1);
  27177. inc(PtrUInt(Data),16);
  27178. end;
  27179. for i := 1 to (Len shr 2)and 3 do begin // 4 bytes (DWORD) by loop
  27180. inc(s1,PCardinalArray(Data)^[0]);
  27181. inc(s2,s1);
  27182. inc(PtrUInt(Data),4);
  27183. end;
  27184. case Len and 3 of // remaining 0..3 bytes
  27185. 1: inc(s1,PByte(Data)^);
  27186. 2: inc(s1,PWord(Data)^);
  27187. 3: inc(s1,PWord(Data)^ or (PByteArray(Data)^[2] shl 16));
  27188. end;
  27189. inc(s2,s1);
  27190. result := s1 xor (s2 shl 16);
  27191. end else
  27192. result := 0;
  27193. end;
  27194. function GetBit(const Bits; aIndex: PtrInt): boolean;
  27195. {$ifdef PUREPASCAL}
  27196. begin
  27197. {$ifdef CPU64}
  27198. result := PInt64Array(@Bits)^[aIndex shr 6] and (Int64(1) shl (aIndex and 63)) <> 0;
  27199. {$else}
  27200. result := PIntegerArray(@Bits)^[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0;
  27201. {$endif}
  27202. end;
  27203. {$else}
  27204. asm
  27205. bt [eax],edx // use very fast i386 bit statement
  27206. sbb eax,eax
  27207. and eax,1
  27208. end;
  27209. {$endif}
  27210. function GetAllBits(Bits: Cardinal; BitCount: Integer): boolean;
  27211. begin
  27212. if BitCount in [low(ALLBITS_CARDINAL)..high(ALLBITS_CARDINAL)] then
  27213. result := (Bits and ALLBITS_CARDINAL[BitCount])=ALLBITS_CARDINAL[BitCount] else
  27214. result := false;
  27215. end;
  27216. procedure SetBit(var Bits; aIndex: PtrInt);
  27217. {$ifdef PUREPASCAL}
  27218. begin
  27219. {$ifdef CPU64}
  27220. PInt64Array(@Bits)^[aIndex shr 6] := PInt64Array(@Bits)^[aIndex shr 6]
  27221. or (Int64(1) shl (aIndex and 63));
  27222. {$else}
  27223. PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5]
  27224. or (1 shl (aIndex and 31));
  27225. {$endif}
  27226. end;
  27227. {$else}
  27228. asm
  27229. bts [eax],edx // use very fast i386 bit statement
  27230. end;
  27231. {$endif}
  27232. procedure UnSetBit(var Bits; aIndex: PtrInt);
  27233. {$ifdef PUREPASCAL}
  27234. begin
  27235. PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5]
  27236. and not (1 shl (aIndex and 31));
  27237. end;
  27238. {$else}
  27239. asm
  27240. btr [eax],edx // use very fast i386 bit statement
  27241. end;
  27242. {$endif}
  27243. function GetBit64(const Bits; aIndex: PtrInt): boolean;
  27244. {$ifdef PUREPASCAL}
  27245. begin
  27246. if PtrUInt(aIndex)>63 then
  27247. result := false else
  27248. {$ifdef CPU64}
  27249. result := PInt64(@Bits)^ and (Int64(1) shl (aIndex and 63)) <> 0;
  27250. {$else}
  27251. result := PIntegerArray(@Bits)^[aIndex shr 5] and (1 shl (aIndex and 31)) <> 0;
  27252. {$endif}
  27253. end;
  27254. {$else}
  27255. asm
  27256. cmp edx,64
  27257. jae @z
  27258. bt [eax],edx // use very fast i386 bit statement
  27259. sbb eax,eax
  27260. and eax,1
  27261. ret
  27262. @z: xor eax,eax
  27263. end;
  27264. {$endif}
  27265. procedure SetBit64(var Bits: Int64; aIndex: PtrInt);
  27266. {$ifdef PUREPASCAL}
  27267. begin
  27268. if PtrUInt(aIndex)<=63 then
  27269. {$ifdef CPU64}
  27270. PInt64Array(@Bits)^[aIndex shr 6] := PInt64Array(@Bits)^[aIndex shr 6]
  27271. or (Int64(1) shl (aIndex and 63));
  27272. {$else}
  27273. PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5]
  27274. or (1 shl (aIndex and 31));
  27275. {$endif}
  27276. end;
  27277. {$else}
  27278. asm
  27279. cmp edx,64
  27280. jae @z
  27281. bts [eax],edx // use very fast i386 bit statement
  27282. @z:
  27283. end;
  27284. {$endif}
  27285. procedure UnSetBit64(var Bits: Int64; aIndex: PtrInt);
  27286. {$ifdef PUREPASCAL}
  27287. begin
  27288. if PtrUInt(aIndex)<=63 then
  27289. {$ifdef CPU64}
  27290. PInt64Array(@Bits)^[aIndex shr 6] := PInt64Array(@Bits)^[aIndex shr 6]
  27291. and not(Int64(1) shl (aIndex and 63));
  27292. {$else}
  27293. PIntegerArray(@Bits)^[aIndex shr 5] := PIntegerArray(@Bits)^[aIndex shr 5]
  27294. and not (1 shl (aIndex and 31));
  27295. {$endif}
  27296. end;
  27297. {$else}
  27298. asm
  27299. cmp edx,64
  27300. jae @z
  27301. btr [eax],edx // use very fast i386 bit statement
  27302. @z:
  27303. end;
  27304. {$endif}
  27305. function GetBitsCount(const Bits; Count: PtrInt): integer;
  27306. {$ifdef PUREPASCAL}
  27307. begin
  27308. result := 0;
  27309. while Count>0 do begin
  27310. dec(Count);
  27311. if GetBit(Bits,Count) then
  27312. inc(result);
  27313. end;
  27314. end;
  27315. {$else}
  27316. asm
  27317. xor ecx,ecx
  27318. @1: test edx,edx
  27319. jz @n
  27320. dec edx
  27321. bt [eax],edx
  27322. adc ecx,0
  27323. jmp @1
  27324. @n: mov eax,ecx
  27325. end;
  27326. {$endif}
  27327. procedure OrMemory(Dest,Source: PByteArray; size: integer);
  27328. begin
  27329. while size>=sizeof(PtrInt) do begin
  27330. dec(size,sizeof(PtrInt));
  27331. PPtrInt(Dest)^ := PPtrInt(Dest)^ or PPtrInt(Source)^;
  27332. inc(PPtrInt(Dest));
  27333. inc(PPtrInt(Source));
  27334. end;
  27335. while size>0 do begin
  27336. dec(size);
  27337. Dest[size] := Dest[size] or Source[size];
  27338. end;
  27339. end;
  27340. procedure XorMemory(Dest,Source: PByteArray; size: integer);
  27341. begin
  27342. while size>=sizeof(PtrInt) do begin
  27343. dec(size,sizeof(PtrInt));
  27344. PPtrInt(Dest)^ := PPtrInt(Dest)^ xor PPtrInt(Source)^;
  27345. inc(PPtrInt(Dest));
  27346. inc(PPtrInt(Source));
  27347. end;
  27348. while size>0 do begin
  27349. dec(size);
  27350. Dest[size] := Dest[size] xor Source[size];
  27351. end;
  27352. end;
  27353. procedure XorMemory(Dest,Source1,Source2: PByteArray; size: integer);
  27354. begin
  27355. while size>=sizeof(PtrInt) do begin
  27356. dec(size,sizeof(PtrInt));
  27357. PPtrInt(Dest)^ := PPtrInt(Source1)^ xor PPtrInt(Source2)^;
  27358. inc(PPtrInt(Dest));
  27359. inc(PPtrInt(Source1));
  27360. inc(PPtrInt(Source2));
  27361. end;
  27362. while size>0 do begin
  27363. dec(size);
  27364. Dest[size] := Source1[size] xor Source2[size];
  27365. end;
  27366. end;
  27367. procedure AndMemory(Dest,Source: PByteArray; size: integer);
  27368. begin
  27369. while size>=sizeof(PtrInt) do begin
  27370. dec(size,sizeof(PtrInt));
  27371. PPtrInt(Dest)^ := PPtrInt(Dest)^ and PPtrInt(Source)^;
  27372. inc(PPtrInt(Dest));
  27373. inc(PPtrInt(Source));
  27374. end;
  27375. while size>0 do begin
  27376. dec(size);
  27377. Dest[size] := Dest[size] and Source[size];
  27378. end;
  27379. end;
  27380. function fnv32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  27381. {$ifdef PUREPASCAL}
  27382. var i: integer;
  27383. begin
  27384. if buf<>nil then
  27385. for i := 0 to len-1 do
  27386. crc := (crc xor ord(buf[i]))*16777619;
  27387. result := crc;
  27388. end;
  27389. {$else}
  27390. asm // eax=crc, edx=buf, ecx=len
  27391. push ebx
  27392. test edx,edx; jz @0
  27393. neg ecx; jz @0
  27394. sub edx,ecx
  27395. @1: movzx ebx,byte ptr [edx+ecx]
  27396. xor eax,ebx
  27397. imul eax,eax,16777619
  27398. inc ecx
  27399. jnz @1
  27400. @0: pop ebx
  27401. end; // we tried an unrolled version, but it was slower on our Core i7!
  27402. {$endif}
  27403. function kr32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  27404. {$ifdef PUREPASCAL}
  27405. var i: integer;
  27406. begin
  27407. for i := 0 to len-1 do
  27408. crc := ord(buf[i])+crc*31;
  27409. result := crc;
  27410. end;
  27411. {$else}
  27412. asm // eax=crc, edx=buf, ecx=len
  27413. test ecx,ecx
  27414. push edi
  27415. push esi
  27416. push ebx
  27417. push ebp
  27418. jz @z
  27419. cmp ecx,4
  27420. jb @s
  27421. @8: mov ebx,[edx] // unrolled version reading per DWORD
  27422. lea edx,[edx+4]
  27423. mov esi,eax
  27424. movzx edi,bl
  27425. movzx ebp,bh
  27426. shr ebx,16
  27427. shl eax,5
  27428. sub eax,esi
  27429. lea eax,[eax+edi]
  27430. mov esi,eax
  27431. shl eax,5
  27432. sub eax,esi
  27433. lea esi,[eax+ebp]
  27434. lea eax,[eax+ebp]
  27435. movzx edi,bl
  27436. movzx ebx,bh
  27437. shl eax,5
  27438. sub eax,esi
  27439. lea ebp,[eax+edi]
  27440. lea eax,[eax+edi]
  27441. shl eax,5
  27442. sub eax,ebp
  27443. cmp ecx,8
  27444. lea eax,[eax+ebx]
  27445. lea ecx,[ecx-4]
  27446. jae @8
  27447. test ecx,ecx
  27448. jz @z
  27449. @s: mov esi,eax
  27450. @1: shl eax,5
  27451. movzx ebx,byte ptr [edx]
  27452. lea edx,[edx+1]
  27453. sub eax,esi
  27454. dec ecx
  27455. lea esi,[eax+ebx]
  27456. lea eax,[eax+ebx]
  27457. jnz @1
  27458. @z: pop ebp
  27459. pop ebx
  27460. pop esi
  27461. pop edi
  27462. end;
  27463. {$endif}
  27464. {$ifdef CPUINTEL}
  27465. type
  27466. TRegisters = record
  27467. eax,ebx,ecx,edx: cardinal;
  27468. end;
  27469. {$ifdef CPU64}
  27470. procedure GetCPUID(Param: Cardinal; var Registers: TRegisters);
  27471. {$ifdef FPC}nostackframe; assembler;
  27472. asm
  27473. {$else}
  27474. asm // ecx=param, rdx=Registers (Linux: edi,rsi)
  27475. .NOFRAME
  27476. {$endif FPC}
  27477. {$ifdef win64}
  27478. mov eax,ecx
  27479. mov r9,rdx
  27480. {$else}
  27481. mov eax,edi
  27482. mov r9,rsi
  27483. {$endif win64}
  27484. mov r10,rbx // preserve rbx
  27485. xor ebx,ebx
  27486. xor ecx,ecx
  27487. xor edx,edx
  27488. cpuid
  27489. mov TRegisters(r9).&eax,eax
  27490. mov TRegisters(r9).&ebx,ebx
  27491. mov TRegisters(r9).&ecx,ecx
  27492. mov TRegisters(r9).&edx,edx
  27493. mov rbx,r10
  27494. end;
  27495. function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  27496. {$ifdef FPC}nostackframe; assembler;
  27497. asm
  27498. {$else}
  27499. asm // ecx=crc, rdx=buf, r8=len (Linux: edi,rsi,rdx)
  27500. .NOFRAME
  27501. {$endif FPC}
  27502. {$ifdef win64}
  27503. mov eax,ecx
  27504. {$else}
  27505. mov eax,edi
  27506. mov r8,rdx
  27507. mov rdx,rsi
  27508. {$endif win64}
  27509. not eax
  27510. test r8,r8; jz @0
  27511. test rdx,rdx; jz @0
  27512. @7: test rdx,7; jz @8 // align to 8 bytes boundary
  27513. crc32 eax,byte ptr [rdx]
  27514. inc rdx
  27515. dec r8; jz @0
  27516. test rdx,7; jnz @7
  27517. @8: mov rcx,r8
  27518. shr r8,3
  27519. jz @2
  27520. @1: crc32 eax,dword ptr [rdx]
  27521. crc32 eax,dword ptr [rdx+4]
  27522. dec r8
  27523. lea rdx,[rdx+8]
  27524. jnz @1
  27525. @2: and rcx,7; jz @0
  27526. cmp rcx,4; jb @4
  27527. crc32 eax,dword ptr [rdx]
  27528. sub rcx,4
  27529. lea rdx,[rdx+4]
  27530. jz @0
  27531. @4: crc32 eax,byte ptr [rdx]
  27532. dec rcx; jz @0
  27533. crc32 eax,byte ptr [rdx+1]
  27534. dec rcx; jz @0
  27535. crc32 eax,byte ptr [rdx+2]
  27536. @0: not eax
  27537. end;
  27538. {$endif CPU64}
  27539. {$endif CPUINTEL}
  27540. function crc32cfast(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  27541. {$ifdef PUREPASCAL}
  27542. begin
  27543. result := not crc;
  27544. if (buf<>nil) and (len>0) then begin
  27545. repeat
  27546. if PtrUInt(buf) and 3=0 then // align to 4 bytes boundary
  27547. break;
  27548. result := crc32ctab[0,ToByte(result xor ord(buf^))] xor (result shr 8);
  27549. dec(len);
  27550. inc(buf);
  27551. until len=0;
  27552. while len>=4 do begin
  27553. result := result xor PCardinal(buf)^;
  27554. inc(buf,4);
  27555. result := crc32ctab[3,ToByte(result)] xor
  27556. crc32ctab[2,ToByte(result shr 8)] xor
  27557. crc32ctab[1,ToByte(result shr 16)] xor
  27558. crc32ctab[0,result shr 24];
  27559. dec(len,4);
  27560. end;
  27561. while len>0 do begin
  27562. result := crc32ctab[0,ToByte(result xor ord(buf^))] xor (result shr 8);
  27563. dec(len);
  27564. inc(buf);
  27565. end;
  27566. end;
  27567. result := not result;
  27568. end;
  27569. {$else}
  27570. // adapted from fast Aleksandr Sharahov version
  27571. asm
  27572. test edx, edx
  27573. jz @ret
  27574. neg ecx
  27575. jz @ret
  27576. not eax
  27577. push ebx
  27578. @head:
  27579. test dl,3
  27580. jz @aligned
  27581. movzx ebx, byte [edx]
  27582. inc edx
  27583. xor bl, al
  27584. shr eax, 8
  27585. xor eax,dword ptr [ebx*4 + crc32ctab]
  27586. inc ecx
  27587. jnz @head
  27588. pop ebx
  27589. not eax
  27590. ret
  27591. @ret:
  27592. rep ret
  27593. @aligned:
  27594. sub edx, ecx
  27595. add ecx, 8
  27596. jg @bodydone
  27597. push esi
  27598. push edi
  27599. mov edi, edx
  27600. mov edx, eax
  27601. @bodyloop:
  27602. mov ebx, [edi + ecx - 4]
  27603. xor edx, [edi + ecx - 8]
  27604. movzx esi, bl
  27605. mov eax,dword ptr [esi*4 + crc32ctab + 1024*3]
  27606. movzx esi, bh
  27607. xor eax,dword ptr [esi*4 + crc32ctab + 1024*2]
  27608. shr ebx, 16
  27609. movzx esi, bl
  27610. xor eax,dword ptr [esi*4 + crc32ctab + 1024*1]
  27611. movzx esi, bh
  27612. xor eax,dword ptr [esi*4 + crc32ctab + 1024*0]
  27613. movzx esi, dl
  27614. xor eax,dword ptr [esi*4 + crc32ctab + 1024*7]
  27615. movzx esi, dh
  27616. xor eax,dword ptr [esi*4 + crc32ctab + 1024*6]
  27617. shr edx, 16
  27618. movzx esi, dl
  27619. xor eax,dword ptr [esi*4 + crc32ctab + 1024*5]
  27620. movzx esi, dh
  27621. xor eax,dword ptr [esi*4 + crc32ctab + 1024*4]
  27622. add ecx, 8
  27623. jg @done
  27624. mov ebx, [edi + ecx - 4]
  27625. xor eax, [edi + ecx - 8]
  27626. movzx esi, bl
  27627. mov edx,dword ptr [esi*4 + crc32ctab + 1024*3]
  27628. movzx esi, bh
  27629. xor edx,dword ptr [esi*4 + crc32ctab + 1024*2]
  27630. shr ebx, 16
  27631. movzx esi, bl
  27632. xor edx,dword ptr [esi*4 + crc32ctab + 1024*1]
  27633. movzx esi, bh
  27634. xor edx,dword ptr [esi*4 + crc32ctab + 1024*0]
  27635. movzx esi, al
  27636. xor edx,dword ptr [esi*4 + crc32ctab + 1024*7]
  27637. movzx esi, ah
  27638. xor edx,dword ptr [esi*4 + crc32ctab + 1024*6]
  27639. shr eax, 16
  27640. movzx esi, al
  27641. xor edx,dword ptr [esi*4 + crc32ctab + 1024*5]
  27642. movzx esi, ah
  27643. xor edx,dword ptr [esi*4 + crc32ctab + 1024*4]
  27644. add ecx, 8
  27645. jle @bodyloop
  27646. mov eax, edx
  27647. @done:
  27648. mov edx, edi
  27649. pop edi
  27650. pop esi
  27651. @bodydone:
  27652. sub ecx, 8
  27653. jl @tail
  27654. pop ebx
  27655. not eax
  27656. ret
  27657. @tail:
  27658. movzx ebx, byte [edx + ecx]
  27659. xor bl,al
  27660. shr eax,8
  27661. xor eax,dword ptr [ebx*4 + crc32ctab]
  27662. inc ecx
  27663. jnz @tail
  27664. pop ebx
  27665. not eax
  27666. end;
  27667. procedure GetCPUID(Param: Cardinal; var Registers: TRegisters);
  27668. asm
  27669. push esi
  27670. push edi
  27671. mov esi,edx
  27672. mov edi,eax
  27673. pushfd
  27674. pop eax
  27675. mov edx,eax
  27676. xor eax,$200000
  27677. push eax
  27678. popfd
  27679. pushfd
  27680. pop eax
  27681. xor eax,edx
  27682. jz @nocpuid
  27683. push ebx
  27684. mov eax,edi
  27685. xor ecx,ecx
  27686. {$ifdef DELPHI5OROLDER}
  27687. db $0f,$a2
  27688. {$else}
  27689. cpuid
  27690. {$endif}
  27691. mov TRegisters(esi).&eax,eax
  27692. mov TRegisters(esi).&ebx,ebx
  27693. mov TRegisters(esi).&ecx,ecx
  27694. mov TRegisters(esi).&edx,edx
  27695. pop ebx
  27696. @nocpuid:
  27697. pop edi
  27698. pop esi
  27699. end;
  27700. function crc32csse42(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
  27701. asm // eax=crc, edx=buf, ecx=len
  27702. not eax
  27703. test ecx,ecx; jz @0
  27704. test edx,edx; jz @0
  27705. @3: test edx,3; jz @8 // align to 4 bytes boundary
  27706. {$ifdef ISDELPHI2010}
  27707. crc32 dword ptr eax,byte ptr [edx]
  27708. {$else}
  27709. db $F2,$0F,$38,$F0,$02
  27710. {$endif}
  27711. inc edx
  27712. dec ecx; jz @0
  27713. test edx,3; jnz @3
  27714. @8: push ecx
  27715. shr ecx,3
  27716. jz @2
  27717. @1: {$ifdef ISDELPHI2010}
  27718. crc32 dword ptr eax,dword ptr [edx]
  27719. crc32 dword ptr eax,dword ptr [edx+4]
  27720. {$else}
  27721. db $F2,$0F,$38,$F1,$02
  27722. db $F2,$0F,$38,$F1,$42,$04
  27723. {$endif}
  27724. dec ecx
  27725. lea edx,[edx+8]
  27726. jnz @1
  27727. @2: pop ecx
  27728. and ecx,7
  27729. jz @0
  27730. cmp ecx,4
  27731. jb @4
  27732. {$ifdef ISDELPHI2010}
  27733. crc32 dword ptr eax,dword ptr [edx]
  27734. {$else}
  27735. db $F2,$0F,$38,$F1,$02
  27736. {$endif}
  27737. sub ecx,4
  27738. lea edx,[edx+4]
  27739. jz @0
  27740. @4: {$ifdef ISDELPHI2010}
  27741. crc32 dword ptr eax,byte ptr [edx]
  27742. dec ecx; jz @0
  27743. crc32 dword ptr eax,byte ptr [edx+1]
  27744. dec ecx; jz @0
  27745. crc32 dword ptr eax,byte ptr [edx+2]
  27746. {$else}
  27747. db $F2,$0F,$38,$F0,$02
  27748. dec ecx; jz @0
  27749. db $F2,$0F,$38,$F0,$42,$01
  27750. dec ecx; jz @0
  27751. db $F2,$0F,$38,$F0,$42,$02
  27752. {$endif}
  27753. @0: not eax
  27754. end;
  27755. {$endif PUREPASCAL}
  27756. function crc32cUTF8ToHex(const str: RawUTF8): RawUTF8;
  27757. begin
  27758. result := CardinalToHex(crc32c(0,pointer(str),length(str)));
  27759. end;
  27760. function crc64c(buf: PAnsiChar; len: cardinal): Int64;
  27761. var hilo: Int64Rec absolute result;
  27762. begin
  27763. hilo.Lo := crc32c(0,buf,len);
  27764. hilo.Hi := crc32c(hilo.Lo,buf,len);
  27765. end;
  27766. function crc63c(buf: PAnsiChar; len: cardinal): Int64;
  27767. var hilo: Int64Rec absolute result;
  27768. begin
  27769. hilo.Lo := crc32c(0,buf,len);
  27770. hilo.Hi := crc32c(hilo.Lo,buf,len) and $7fffffff;
  27771. end;
  27772. procedure crc128c(buf: PAnsiChar; len: cardinal; out crc: THash128);
  27773. var h: array[0..3] of cardinal absolute crc;
  27774. begin // see http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
  27775. assert(sizeof(h)=sizeof(crc));
  27776. h[0] := crc32c(0,buf,len);
  27777. h[1] := crc32c(h[0],buf,len);
  27778. h[2] := h[0]+h[1];
  27779. h[3] := h[0]+2*h[1];
  27780. end;
  27781. function IsZero(const dig: THash128): boolean;
  27782. begin
  27783. {$ifdef CPU64}
  27784. result := (PInt64Array(@dig)^[0]=0) and (PInt64Array(@dig)^[1]=0);
  27785. {$else}
  27786. result := (PCardinalArray(@dig)^[0]=0) and (PCardinalArray(@dig)^[1]=0) and
  27787. (PCardinalArray(@dig)^[2]=0) and (PCardinalArray(@dig)^[3]=0);
  27788. {$endif}
  27789. end;
  27790. function IsEqual(const A,B: THash128): boolean;
  27791. begin
  27792. {$ifdef CPU64}
  27793. result := (PInt64Array(@A)^[0]=PInt64Array(@B)^[0]) and
  27794. (PInt64Array(@A)^[1]=PInt64Array(@B)^[1]);
  27795. {$else}
  27796. result := (PCardinalArray(@A)^[0]=PCardinalArray(@B)^[0]) and
  27797. (PCardinalArray(@A)^[1]=PCardinalArray(@B)^[1]) and
  27798. (PCardinalArray(@A)^[2]=PCardinalArray(@B)^[2]) and
  27799. (PCardinalArray(@A)^[3]=PCardinalArray(@B)^[3]);
  27800. {$endif}
  27801. end;
  27802. procedure FillZero(out dig: THash128);
  27803. begin
  27804. PInt64Array(@dig)^[0] := 0;
  27805. PInt64Array(@dig)^[1] := 0;
  27806. end;
  27807. procedure crc256c(buf: PAnsiChar; len: cardinal; out crc: THash256);
  27808. var h: array[0..7] of cardinal absolute crc;
  27809. i: cardinal;
  27810. begin // see http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
  27811. assert(sizeof(h)=sizeof(crc));
  27812. h[0] := crc32c(0,buf,len);
  27813. h[1] := crc32c(h[0],buf,len);
  27814. for i := 0 to 5 do
  27815. h[i+2] := h[0]+i*h[1];
  27816. end;
  27817. function IsZero(const dig: THash256): boolean;
  27818. begin
  27819. {$ifdef CPU64}
  27820. result := (PInt64Array(@dig)^[0]=0) and (PInt64Array(@dig)^[1]=0) and
  27821. (PInt64Array(@dig)^[2]=0) and (PInt64Array(@dig)^[3]=0);
  27822. {$else}
  27823. result := (PCardinalArray(@dig)^[0]=0) and (PCardinalArray(@dig)^[1]=0) and
  27824. (PCardinalArray(@dig)^[2]=0) and (PCardinalArray(@dig)^[3]=0) and
  27825. (PCardinalArray(@dig)^[4]=0) and (PCardinalArray(@dig)^[5]=0) and
  27826. (PCardinalArray(@dig)^[6]=0) and (PCardinalArray(@dig)^[7]=0);
  27827. {$endif}
  27828. end;
  27829. function IsEqual(const A,B: THash256): boolean;
  27830. begin
  27831. {$ifdef CPU64}
  27832. result := (PInt64Array(@A)^[0]=PInt64Array(@B)^[0]) and
  27833. (PInt64Array(@A)^[1]=PInt64Array(@B)^[1]) and
  27834. (PInt64Array(@A)^[2]=PInt64Array(@B)^[2]) and
  27835. (PInt64Array(@A)^[3]=PInt64Array(@B)^[3]);
  27836. {$else}
  27837. result := (PCardinalArray(@A)^[0]=PCardinalArray(@B)^[0]) and
  27838. (PCardinalArray(@A)^[1]=PCardinalArray(@B)^[1]) and
  27839. (PCardinalArray(@A)^[2]=PCardinalArray(@B)^[2]) and
  27840. (PCardinalArray(@A)^[3]=PCardinalArray(@B)^[3]) and
  27841. (PCardinalArray(@A)^[4]=PCardinalArray(@B)^[4]) and
  27842. (PCardinalArray(@A)^[5]=PCardinalArray(@B)^[5]) and
  27843. (PCardinalArray(@A)^[6]=PCardinalArray(@B)^[6]) and
  27844. (PCardinalArray(@A)^[7]=PCardinalArray(@B)^[7]);
  27845. {$endif}
  27846. end;
  27847. procedure FillZero(out dig: THash256);
  27848. begin
  27849. PInt64Array(@dig)^[0] := 0;
  27850. PInt64Array(@dig)^[1] := 0;
  27851. PInt64Array(@dig)^[2] := 0;
  27852. PInt64Array(@dig)^[3] := 0;
  27853. end;
  27854. procedure SymmetricEncrypt(key: cardinal; var data: RawByteString);
  27855. var i,len: integer;
  27856. d: PCardinal;
  27857. begin
  27858. UniqueString(AnsiString(data));
  27859. len := length(data);
  27860. d := pointer(data);
  27861. key := key xor cardinal(len);
  27862. for i := 0 to (len shr 2)-1 do begin
  27863. key := key xor crc32ctab[0,(cardinal(i) xor key)and 1023];
  27864. d^ := d^ xor key;
  27865. inc(d);
  27866. end;
  27867. for i := 0 to (len and 3)-1 do
  27868. PByteArray(d)^[i] := PByteArray(d)^[i] xor key xor crc32ctab[0,17 shl i];
  27869. end;
  27870. function UnixTimeToDateTime(const UnixTime: Int64): TDateTime;
  27871. begin
  27872. result := (UnixTime / SecsPerDay + UnixDateDelta);
  27873. end;
  27874. function DateTimeToUnixTime(const AValue: TDateTime): Int64;
  27875. begin
  27876. result := Round((AValue - UnixDateDelta) * SecsPerDay);
  27877. end;
  27878. function UnixMSTimeToDateTime(const UnixTime: Int64): TDateTime;
  27879. begin
  27880. result := (UnixTime / MSecsPerDay + UnixDateDelta);
  27881. end;
  27882. function DateTimeToUnixMSTime(const AValue: TDateTime): Int64;
  27883. begin
  27884. result := Round((AValue - UnixDateDelta) * MSecsPerDay);
  27885. end;
  27886. function NowUTC: TDateTime;
  27887. {$ifdef MSWINDOWS}
  27888. var SystemTime: TSystemTime;
  27889. time: TDateTime;
  27890. begin
  27891. GetSystemTime(SystemTime);
  27892. with SystemTime do
  27893. if TryEncodeDate(wYear,wMonth,wDay,result) and
  27894. TryEncodeTime(wHour,wMinute,wSecond,wMilliseconds,time) then
  27895. result := result+time else
  27896. result := 0;
  27897. end;
  27898. {$else}
  27899. begin
  27900. Result := GetNowUTC;
  27901. end;
  27902. {$endif}
  27903. function Iso8601ToDateTimePUTF8Char(P: PUTF8Char; L: integer): TDateTime;
  27904. var tmp: TDateTime;
  27905. begin
  27906. Iso8601ToDateTimePUTF8CharVar(P,L,tmp);
  27907. result := tmp;
  27908. end;
  27909. function Iso8601CheckAndDecode(P: PUTF8Char; L: integer; var Value: TDateTime): boolean;
  27910. // handle 'YYYY-MM-DDThh:mm:ss' or 'YYYY-MM-DD' or 'Thh:mm:ss'
  27911. begin
  27912. if P=nil then
  27913. result := false else
  27914. if ((L=9) and (P[0]='T') and (P[3]=':')) or // 'Thh:mm:ss'
  27915. ((L=10) and (P[4]='-') and (P[7]='-')) or // 'YYYY-MM-DD'
  27916. ((L=19) and (P[4]='-') and (P[10]='T')) then begin
  27917. Iso8601ToDateTimePUTF8CharVar(P,L,Value);
  27918. result := Value<>0;
  27919. end else
  27920. result := false;
  27921. end;
  27922. {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  27923. type
  27924. unaligned = Double;
  27925. {$endif}
  27926. function Char2ToByte(P: PUTF8Char; out Value: Cardinal): Boolean;
  27927. var B: cardinal;
  27928. begin
  27929. B := ConvertHexToBin[ord(P[0])];
  27930. if B<=9 then begin
  27931. Value := B;
  27932. B := ConvertHexToBin[ord(P[1])];
  27933. if B<=9 then begin
  27934. Value := Value*10+B;
  27935. result := false;
  27936. exit;
  27937. end;
  27938. end;
  27939. result := true; // error
  27940. end;
  27941. function Char3ToWord(P: PUTF8Char; out Value: Cardinal): Boolean;
  27942. var B: cardinal;
  27943. begin
  27944. B := ConvertHexToBin[ord(P[0])];
  27945. if B<=9 then begin
  27946. Value := B;
  27947. B := ConvertHexToBin[ord(P[1])];
  27948. if B<=9 then begin
  27949. Value := Value*10+B;
  27950. B := ConvertHexToBin[ord(P[2])];
  27951. if B<=9 then begin
  27952. Value := Value*10+B;
  27953. result := false;
  27954. exit;
  27955. end;
  27956. end;
  27957. end;
  27958. result := true; // error
  27959. end;
  27960. function Char4ToWord(P: PUTF8Char; out Value: Cardinal): Boolean;
  27961. var B: cardinal;
  27962. begin
  27963. B := ConvertHexToBin[ord(P[0])];
  27964. if B<=9 then begin
  27965. Value := B;
  27966. B := ConvertHexToBin[ord(P[1])];
  27967. if B<=9 then begin
  27968. Value := Value*10+B;
  27969. B := ConvertHexToBin[ord(P[2])];
  27970. if B<=9 then begin
  27971. Value := Value*10+B;
  27972. B := ConvertHexToBin[ord(P[3])];
  27973. if B<=9 then begin
  27974. Value := Value*10+B;
  27975. result := false;
  27976. exit;
  27977. end;
  27978. end;
  27979. end;
  27980. end;
  27981. result := true; // error
  27982. end;
  27983. procedure Iso8601ToDateTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TDateTime);
  27984. var i: integer;
  27985. B: cardinal;
  27986. Y,M,D, HH,MI,SS: cardinal;
  27987. // expect 'YYYYMMDDThhmmss' format but handle also 'YYYY-MM-DDThh:mm:ss'
  27988. begin
  27989. unaligned(result) := 0;
  27990. if P=nil then
  27991. exit;
  27992. if L=0 then
  27993. L := StrLen(P);
  27994. if L<4 then
  27995. exit; // we need 'YYYY' at least
  27996. if P[0]='T' then
  27997. dec(P,8) else begin
  27998. B := ConvertHexToBin[ord(P[0])]; // first digit
  27999. if B>9 then exit else Y := B; // fast check '0'..'9'
  28000. for i := 1 to 3 do begin
  28001. B := ConvertHexToBin[ord(P[i])]; // 3 other digits
  28002. if B>9 then exit else Y := Y*10+B;
  28003. end;
  28004. if P[4] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
  28005. D := 1;
  28006. if L>=6 then begin // YYYYMM
  28007. M := ord(P[4])*10+ord(P[5])-(48+480);
  28008. if (M=0) or (M>12) then exit;
  28009. if P[6] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
  28010. if L>=8 then begin // YYYYMMDD
  28011. if not(P[8] in [#0,' ','T']) then
  28012. exit; // invalid date format
  28013. D := ord(P[6])*10+ord(P[7])-(48+480);
  28014. if (D=0) or (D>MonthDays[true][M]) then exit; // worse is leap year=true
  28015. end;
  28016. end else
  28017. M := 1;
  28018. if M>2 then // inlined EncodeDate(Y,M,D)
  28019. dec(M,3) else
  28020. if M>0 then begin
  28021. inc(M,9);
  28022. dec(Y);
  28023. end;
  28024. if Y>9999 then
  28025. exit; // avoid integer overflow e.g. if '0000' is an invalid date
  28026. with Div100(Y) do
  28027. unaligned(result) := (146097*YDiv100) shr 2 + (1461*YMod100) shr 2 +
  28028. (153*M+2) div 5+D-693900;
  28029. if L<15 then
  28030. exit; // not enough space to retrieve the time
  28031. end;
  28032. HH := ord(P[9])*10+ord(P[10])-(48+480);
  28033. if HH<24 then begin
  28034. if P[11]=':' then inc(P);
  28035. MI := ord(P[11])*10+ord(P[12])-(48+480);
  28036. if MI<60 then begin
  28037. if P[13]=':' then inc(P);
  28038. SS := ord(P[13])*10+ord(P[14])-(48+480);
  28039. if SS<60 then // inlined EncodeTime()
  28040. result := result+(HH*(MinsPerHour*SecsPerMin*MSecsPerSec)+
  28041. MI*(SecsPerMin*MSecsPerSec)+SS*MSecsPerSec)/MSecsPerDay;
  28042. end;
  28043. end;
  28044. end;
  28045. function Iso8601ToTimePUTF8Char(P: PUTF8Char; L: integer=0): TDateTime;
  28046. begin
  28047. Iso8601ToTimePUTF8CharVar(P,L,result);
  28048. end;
  28049. procedure Iso8601ToTimePUTF8CharVar(P: PUTF8Char; L: integer; var result: TDateTime);
  28050. var H,MI,SS: cardinal;
  28051. begin
  28052. if Iso8601ToTimePUTF8Char(P,L,H,MI,SS) then
  28053. result := (H*(MinsPerHour*SecsPerMin*MSecsPerSec)+
  28054. MI*(SecsPerMin*MSecsPerSec)+SS*MSecsPerSec)/MSecsPerDay else
  28055. result := 0;
  28056. end;
  28057. function Iso8601ToTimePUTF8Char(P: PUTF8Char; L: integer; var H,M,S: cardinal): boolean;
  28058. begin
  28059. result := false; // error
  28060. if P=nil then
  28061. exit;
  28062. if L=0 then
  28063. L := StrLen(P);
  28064. if L<6 then
  28065. exit; // we need 'hhmmss' at least
  28066. H := ord(P[0])*10+ord(P[1])-(48+480);
  28067. if P[2]=':' then inc(P); // allow hh:mm:ss
  28068. M := ord(P[2])*10+ord(P[3])-(48+480);
  28069. if P[4]=':' then inc(P); // allow hh:mm:ss
  28070. S := ord(P[4])*10+ord(P[5])-(48+480);
  28071. if (H<24) and (M<60) and (S<60) then
  28072. result := true;
  28073. end;
  28074. function IntervalTextToDateTime(Text: PUTF8Char): TDateTime;
  28075. begin
  28076. IntervalTextToDateTimeVar(Text,result);
  28077. end;
  28078. procedure IntervalTextToDateTimeVar(Text: PUTF8Char; var result: TDateTime);
  28079. var negative: boolean;
  28080. Time: TDateTime;
  28081. begin // e.g. IntervalTextToDateTime('+0 06:03:20')
  28082. result := 0;
  28083. if Text=nil then
  28084. exit;
  28085. if Text^ in ['+','-'] then begin
  28086. negative := (Text^='-');
  28087. result := GetNextItemDouble(Text,' ');
  28088. end else
  28089. negative := false;
  28090. Iso8601ToTimePUTF8CharVar(Text,0,Time);
  28091. if negative then
  28092. result := result-Time else
  28093. result := result+Time;
  28094. end;
  28095. function Iso8601ToDateTime(const S: RawByteString): TDateTime;
  28096. begin
  28097. Iso8601ToDateTimePUTF8CharVar(pointer(S),length(S),result);
  28098. end;
  28099. function TimeLogToDateTime(const TimeStamp: TTimeLog): TDateTime;
  28100. begin
  28101. result := PTimeLogBits(@TimeStamp)^.ToDateTime;
  28102. end;
  28103. procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: cardinal); overload;
  28104. // use 'YYYYMMDD' format if not Expanded, 'YYYY-MM-DD' format if Expanded
  28105. begin
  28106. {$ifdef PUREPASCAL}
  28107. PWord(P )^ := TwoDigitLookupW[Y div 100];
  28108. PWord(P+2)^ := TwoDigitLookupW[Y mod 100];
  28109. {$else}
  28110. YearToPChar(Y,P);
  28111. {$endif}
  28112. inc(P,4);
  28113. if Expanded then begin
  28114. P^ := '-';
  28115. inc(P);
  28116. end;
  28117. PWord(P)^ := TwoDigitLookupW[M];
  28118. inc(P,2);
  28119. if Expanded then begin
  28120. P^ := '-';
  28121. inc(P);
  28122. end;
  28123. PWord(P)^ := TwoDigitLookupW[D];
  28124. end;
  28125. procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S: cardinal;
  28126. FirstChar: AnsiChar = 'T'); overload;
  28127. // use Thhmmss format
  28128. begin
  28129. if FirstChar<>#0 then begin
  28130. P^ := FirstChar;
  28131. inc(P);
  28132. end;
  28133. PWord(P)^ := TwoDigitLookupW[H];
  28134. inc(P,2);
  28135. if Expanded then begin
  28136. P^ := ':';
  28137. inc(P);
  28138. end;
  28139. PWord(P)^ := TwoDigitLookupW[M];
  28140. inc(P,2);
  28141. if Expanded then begin
  28142. P^ := ':';
  28143. inc(P);
  28144. end;
  28145. PWord(P)^ := TwoDigitLookupW[S];
  28146. end;
  28147. procedure DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean); overload;
  28148. // use YYYYMMDD date format
  28149. var Y,M,D: word;
  28150. begin
  28151. DecodeDate(Date,Y,M,D);
  28152. DateToIso8601PChar(P,Expanded,Y,M,D);
  28153. end;
  28154. function DateToIso8601Text(Date: TDateTime): RawUTF8;
  28155. begin // into 'YYYY-MM-DD' date format
  28156. SetLength(Result,10);
  28157. DateToIso8601PChar(Date,pointer(Result),True);
  28158. end;
  28159. procedure TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean;
  28160. FirstChar: AnsiChar = 'T'); overload;
  28161. // use Thhmmss format
  28162. var H,M,S,MS: word;
  28163. begin
  28164. DecodeTime(Time,H,M,S,MS);
  28165. TimeToIso8601PChar(P,Expanded,H,M,S,FirstChar);
  28166. end;
  28167. function DateTimeToIso8601(D: TDateTime; Expanded: boolean;
  28168. FirstChar: AnsiChar='T'): RawUTF8;
  28169. // use YYYYMMDDThhmmss format
  28170. const ISO8601_LEN: array[boolean,boolean] of integer = ((19,18),(15,14));
  28171. var tmp: array[0..31] of AnsiChar;
  28172. begin
  28173. if Expanded then begin
  28174. DateToIso8601PChar(D,tmp,true);
  28175. TimeToIso8601PChar(D,@tmp[10],true,FirstChar);
  28176. SetString(result,PAnsiChar(@tmp),ISO8601_LEN[false,FirstChar=#0]);
  28177. end else begin
  28178. DateToIso8601PChar(D,tmp,false);
  28179. TimeToIso8601PChar(D,@tmp[8],false,FirstChar);
  28180. SetString(result,PAnsiChar(@tmp),ISO8601_LEN[true,FirstChar=#0]);
  28181. end;
  28182. end;
  28183. function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUTF8;
  28184. // use YYYYMMDDTdate format
  28185. begin
  28186. FastNewRawUTF8(result,8+2*integer(Expanded));
  28187. DateToIso8601PChar(Date,pointer(result),Expanded);
  28188. end;
  28189. function DateToIso8601(Y,M,D: cardinal; Expanded: boolean): RawUTF8; overload;
  28190. // use 'YYYYMMDD' format if not Expanded, 'YYYY-MM-DD' format if Expanded
  28191. begin
  28192. FastNewRawUTF8(result,8+2*integer(Expanded));
  28193. DateToIso8601PChar(pointer(result),Expanded,Y,M,D);
  28194. end;
  28195. function TimeToIso8601(Time: TDateTime; Expanded: boolean; FirstChar: AnsiChar='T'): RawUTF8;
  28196. // use Thhmmss format
  28197. begin
  28198. FastNewRawUTF8(result,7+2*integer(Expanded));
  28199. TimeToIso8601PChar(Time,pointer(result),Expanded,FirstChar);
  28200. end;
  28201. function DateTimeToIso8601Text(DT: TDateTime; FirstChar: AnsiChar): RawUTF8;
  28202. begin
  28203. DateTimeToIso8601TextVar(DT,FirstChar,result);
  28204. end;
  28205. procedure DateTimeToIso8601TextVar(DT: TDateTime; FirstChar: AnsiChar; var result: RawUTF8);
  28206. begin
  28207. if DT=0 then
  28208. result := '' else
  28209. if frac(DT)=0 then
  28210. result := DateToIso8601(DT,true) else
  28211. if trunc(DT)=0 then
  28212. result := TimeToIso8601(DT,true,FirstChar) else
  28213. result := DateTimeToIso8601(DT,true,FirstChar);
  28214. end;
  28215. procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar; var result: string);
  28216. var tmp: RawUTF8;
  28217. begin
  28218. DateTimeToIso8601TextVar(DT,FirstChar,tmp);
  28219. Ansi7ToString(Pointer(tmp),length(tmp),result);
  28220. end;
  28221. procedure DateTimeToIso8601ExpandedPChar(const Value: TDateTime; Dest: PUTF8Char;
  28222. FirstChar: AnsiChar='T');
  28223. begin
  28224. if Value<>0 then begin
  28225. if trunc(Value)<>0 then begin
  28226. DateToIso8601PChar(Value,Dest,true);
  28227. inc(Dest,10);
  28228. end;
  28229. if frac(Value)<>0 then begin
  28230. TimeToIso8601PChar(Value,Dest,true,FirstChar);
  28231. inc(Dest,9);
  28232. end;
  28233. end;
  28234. Dest^ := #0;
  28235. end;
  28236. function Iso8601ToTimeLogPUTF8Char(P: PUTF8Char; L: integer; ContainsNoTime: PBoolean=nil): TTimeLog;
  28237. // bits: S=0..5 M=6..11 H=12..16 D=17..21 M=22..25 Y=26..38
  28238. // i.e. S<64 M<64 H<32 D<32 M<16 Y<4096: power of 2 -> use fast shl/shr
  28239. var V,B: PtrUInt;
  28240. i: integer;
  28241. begin
  28242. result := 0;
  28243. if P=nil then
  28244. exit;
  28245. if L=0 then
  28246. L := StrLen(P);
  28247. if L<4 then
  28248. exit; // we need 'YYYY' at least
  28249. if P[0]='T' then
  28250. dec(P,8) else begin // 'YYYY' -> year decode
  28251. V := ConvertHexToBin[ord(P[0])]; if V>9 then exit;
  28252. for i := 1 to 3 do begin
  28253. B := ConvertHexToBin[ord(P[i])]; if B>9 then exit else V := V*10+B; end;
  28254. result := Int64(V) shl 26; // store YYYY
  28255. if P[4] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
  28256. if L>=6 then begin // YYYYMM
  28257. V := ord(P[4])*10+ord(P[5])-(48+480+1); // Month 1..12 -> 0..11
  28258. if V<=11 then
  28259. inc(result,V shl 22) else begin
  28260. result := 0;
  28261. exit;
  28262. end;
  28263. if P[6] in ['-','/'] then begin inc(P); dec(L); end; // allow YYYY-MM-DD
  28264. if L>=8 then begin // YYYYMMDD
  28265. V := ord(P[6])*10+ord(P[7])-(48+480+1); // Day 1..31 -> 0..30
  28266. if (V<=30) and(P[8] in [#0,' ','T']) then
  28267. inc(result,V shl 17) else begin
  28268. result := 0;
  28269. exit;
  28270. end;
  28271. end;
  28272. end;
  28273. if L<15 then begin // not enough place to retrieve a time
  28274. if ContainsNoTime<>nil then
  28275. ContainsNoTime^ := true;
  28276. exit;
  28277. end;
  28278. end;
  28279. if ContainsNoTime<>nil then
  28280. ContainsNoTime^ := false;
  28281. B := ord(P[9])*10+ord(P[10])-(48+480);
  28282. if B<=23 then V := B shl 12 else exit;
  28283. if P[11]=':' then inc(P); // allow hh:mm:ss
  28284. B := ord(P[11])*10+ord(P[12])-(48+480);
  28285. if B<=59 then inc(V,B shl 6) else exit;
  28286. if P[13]=':' then inc(P); // allow hh:mm:ss
  28287. B := ord(P[13])*10+ord(P[14])-(48+480);
  28288. if B<=59 then inc(result,PtrUInt(V+B));
  28289. end;
  28290. function IsIso8601(P: PUTF8Char; L: integer): boolean;
  28291. begin
  28292. result := Iso8601ToTimeLogPUTF8Char(P,L)<>0;
  28293. end;
  28294. function Iso8601ToTimeLog(const S: RawByteString): TTimeLog;
  28295. {$ifdef PUREPASCAL}
  28296. begin
  28297. result := Iso8601ToTimeLogPUTF8Char(pointer(S),length(S));
  28298. end;
  28299. {$else}
  28300. asm
  28301. xor ecx,ecx // ContainsNoTime=nil
  28302. test eax,eax // if s='' -> p=nil -> will return 0, whatever L value is
  28303. jz Iso8601ToTimeLogPUTF8Char
  28304. mov edx,[eax-4] // edx=L
  28305. @1: jmp Iso8601ToTimeLogPUTF8Char
  28306. end;
  28307. {$endif}
  28308. { TTimeLogBits }
  28309. // bits: S=0..5 M=6..11 H=12..16 D=17..21 M=22..25 Y=26..38
  28310. // size: S=6 M=6 H=5 D=5 M=4 Y=12
  28311. // i.e. S<64 M<64 H<32 D<32 M<16 Y<4096: power of 2 -> use fast shl/shr
  28312. procedure TTimeLogBits.From(Y, M, D, HH, MM, SS: cardinal);
  28313. begin
  28314. inc(HH,D shl 5+M shl 10+Y shl 14-(1 shl 5+1 shl 10));
  28315. Value := SS+MM shl 6+Int64(HH) shl 12;
  28316. end;
  28317. procedure TTimeLogBits.From(P: PUTF8Char; L: integer);
  28318. begin
  28319. Value := Iso8601ToTimeLogPUTF8Char(P,L);
  28320. end;
  28321. procedure TTimeLogBits.Expand(out Date: TSystemTime);
  28322. begin
  28323. {$ifdef MSWINDOWS}
  28324. Date.wYear := (Value shr (6+6+5+5+4)) and 4095;
  28325. Date.wMonth := 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15;
  28326. Date.wDay := 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31;
  28327. Date.wDayOfWeek := 0;
  28328. Date.wHour := (Int64Rec(Value).Lo shr (6+6)) and 31;
  28329. Date.wMinute := (Int64Rec(Value).Lo shr 6) and 63;
  28330. Date.wSecond := Int64Rec(Value).Lo and 63;
  28331. {$else}
  28332. Date.Year := (Value shr (6+6+5+5+4)) and 4095;
  28333. Date.Month := 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15;
  28334. Date.Day := 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31;
  28335. Date.DayOfWeek := 0;
  28336. Date.Hour := (Int64Rec(Value).Lo shr (6+6)) and 31;
  28337. Date.Minute := (Int64Rec(Value).Lo shr 6) and 63;
  28338. Date.Second := Int64Rec(Value).Lo and 63;
  28339. {$endif}
  28340. end;
  28341. procedure TTimeLogBits.From(const S: RawUTF8);
  28342. begin
  28343. Value := Iso8601ToTimeLog(S);
  28344. end;
  28345. procedure TTimeLogBits.From(FileDate: integer);
  28346. begin
  28347. {$ifdef MSWINDOWS}
  28348. From(LongRec(FileDate).Hi shr 9+1980,
  28349. LongRec(FileDate).Hi shr 5 and 15,
  28350. LongRec(FileDate).Hi and 31,
  28351. LongRec(FileDate).Lo shr 11,
  28352. LongRec(FileDate).Lo shr 5 and 63,
  28353. LongRec(FileDate).Lo and 31 shl 1);
  28354. {$else} // FileDate depends on the running OS
  28355. From(FileDateToDateTime(FileDate));
  28356. {$endif}
  28357. end;
  28358. procedure TTimeLogBits.From(DateTime: TDateTime; DateOnly: Boolean=false);
  28359. var HH,MM,SS,MS,Y,M,D: word;
  28360. V: cardinal;
  28361. begin
  28362. if DateOnly then
  28363. HH := 0 else
  28364. DecodeTime(DateTime,HH,MM,SS,MS);
  28365. DecodeDate(DateTime,Y,M,D);
  28366. V := HH+D shl 5+M shl 10+Y shl 14-(1 shl 5+1 shl 10);
  28367. if DateOnly then
  28368. Value := Int64(V) shl 12 else
  28369. Value := SS+MM shl 6+Int64(V) shl 12;
  28370. end;
  28371. procedure TTimeLogBits.FromUnixTime(const UnixTime: Int64);
  28372. begin
  28373. From(UnixTimeToDateTime(UnixTime));
  28374. end;
  28375. procedure TTimeLogBits.FromUnixMSTime(const UnixMSTime: Int64);
  28376. begin
  28377. From(UnixMSTimeToDateTime(UnixMSTime));
  28378. end;
  28379. procedure TTimeLogBits.From(const Time: TSystemTime);
  28380. var V: cardinal;
  28381. begin
  28382. {$ifdef MSWINDOWS}
  28383. V := Time.wHour+Time.wDay shl 5+Time.wMonth shl 10+
  28384. Time.wYear shl 14-(1 shl 5+1 shl 10);
  28385. Value := Time.wSecond+Time.wMinute shl 6+Int64(V) shl 12;
  28386. {$else}
  28387. V := Time.Hour+Time.Day shl 5+Time.Month shl 10+
  28388. Time.Year shl 14-(1 shl 5+1 shl 10);
  28389. Value := Time.Second+Time.Minute shl 6+Int64(V) shl 12;
  28390. {$endif}
  28391. end;
  28392. var
  28393. UTCTimeCache: TTimeLog;
  28394. UTCTimeTicks: cardinal;
  28395. procedure TTimeLogBits.FromUTCTime;
  28396. var Ticks: cardinal;
  28397. Now: TSystemTime;
  28398. begin
  28399. Ticks := GetTickCount64 shr 8; // 256 ms resolution
  28400. if Ticks=UTCTimeTicks then begin
  28401. Value := UTCTimeCache;
  28402. exit;
  28403. end;
  28404. {$ifdef MSWINDOWS}
  28405. GetSystemTime(Now); // this API is fast enough for our purpose
  28406. {$else}
  28407. GetNowUTCSystem(Now);
  28408. {$endif}
  28409. From(Now);
  28410. UTCTimeCache := Value;
  28411. UTCTimeTicks := Ticks;
  28412. end;
  28413. procedure TTimeLogBits.FromNow;
  28414. var Now: TSystemTime;
  28415. begin
  28416. GetLocalTime(Now); // this API is fast enough for our purpose
  28417. From(Now);
  28418. end;
  28419. function TTimeLogBits.ToTime: TDateTime;
  28420. begin
  28421. if Value and (1 shl (6+6+5)-1)=0 then
  28422. result := 0 else
  28423. result := EncodeTime(
  28424. (Int64Rec(Value).Lo shr (6+6)) and 31,
  28425. (Int64Rec(Value).Lo shr 6) and 63,
  28426. Int64Rec(Value).Lo and 63, 0);
  28427. end;
  28428. function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean;
  28429. begin // faster version by AB
  28430. Result := False;
  28431. if (Month<1) or (Month>12) then exit;
  28432. if (Day <= MonthDays[
  28433. ((Year and 3)=0) and ((Year mod 100>0) or (Year mod 400=0))][Month]) and
  28434. (Year>=1) and (Year<10000) and
  28435. (Month<13) and (Day>0) then begin
  28436. if Month>2 then
  28437. dec(Month,3) else
  28438. if (Month>0) then begin
  28439. inc(Month,9);
  28440. dec(Year);
  28441. end
  28442. else exit; // Month <= 0
  28443. with Div100(Year) do
  28444. Date := (146097*YDiv100) shr 2+(1461*YMod100) shr 2+
  28445. (153*Month+2) div 5+Day-693900;
  28446. result := true;
  28447. end;
  28448. end;
  28449. function TTimeLogBits.ToDate: TDateTime;
  28450. var Y: cardinal;
  28451. begin
  28452. Y := (Value shr (6+6+5+5+4)) and 4095;
  28453. if (Y=0) or not TryEncodeDate(Y,
  28454. 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15,
  28455. 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31,result) then
  28456. result := 0;
  28457. end;
  28458. function TTimeLogBits.ToDateTime: TDateTime;
  28459. var Y: cardinal;
  28460. Time: TDateTime;
  28461. begin
  28462. Y := (Value shr (6+6+5+5+4)) and 4095;
  28463. if (Y=0) or not TryEncodeDate(Y,
  28464. 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15,
  28465. 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31,result) then
  28466. result := 0;
  28467. if (Value and (1 shl (6+6+5)-1)<>0) and TryEncodeTime(
  28468. (Int64Rec(Value).Lo shr (6+6)) and 31,
  28469. (Int64Rec(Value).Lo shr 6) and 63,
  28470. Int64Rec(Value).Lo and 63, 0, Time) then
  28471. result := result+Time;
  28472. end;
  28473. function TTimeLogBits.Year: Integer;
  28474. begin
  28475. result := (Value shr (6+6+5+5+4)) and 4095;
  28476. end;
  28477. function TTimeLogBits.Month: Integer;
  28478. begin
  28479. result := 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15;
  28480. end;
  28481. function TTimeLogBits.Day: Integer;
  28482. begin
  28483. result := 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31;
  28484. end;
  28485. function TTimeLogBits.Hour: Integer;
  28486. begin
  28487. result := (Int64Rec(Value).Lo shr (6+6)) and 31;
  28488. end;
  28489. function TTimeLogBits.Minute: Integer;
  28490. begin
  28491. result := (Int64Rec(Value).Lo shr 6) and 63;
  28492. end;
  28493. function TTimeLogBits.Second: Integer;
  28494. begin
  28495. result := Int64Rec(Value).Lo and 63;
  28496. end;
  28497. function TTimeLogBits.ToUnixTime: Int64;
  28498. begin
  28499. result := DateTimeToUnixTime(ToDateTime);
  28500. end;
  28501. function TTimeLogBits.ToUnixMSTime: Int64;
  28502. begin
  28503. result := DateTimeToUnixMSTime(ToDateTime);
  28504. end;
  28505. function TTimeLogBits.Text(Dest: PUTF8Char; Expanded: boolean; FirstTimeChar: AnsiChar): integer;
  28506. begin
  28507. if Value=0 then
  28508. result := 0 else
  28509. if Value and (1 shl (6+6+5)-1)=0 then begin
  28510. // no Time: just convert date
  28511. DateToIso8601PChar(Dest,Expanded,
  28512. (Value shr (6+6+5+5+4)) and 4095,
  28513. 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15,
  28514. 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31);
  28515. if Expanded then
  28516. result := 10 else
  28517. result := 8;
  28518. end else
  28519. if Value shr (6+6+5)=0 then begin
  28520. // no Date: just convert time
  28521. TimeToIso8601PChar(Dest,Expanded,
  28522. (Int64Rec(Value).Lo shr (6+6)) and 31,
  28523. (Int64Rec(Value).Lo shr 6) and 63,
  28524. Int64Rec(Value).Lo and 63, FirstTimeChar);
  28525. if Expanded then
  28526. result := 9 else
  28527. result := 7;
  28528. if FirstTimeChar=#0 then
  28529. dec(result);
  28530. end else begin
  28531. // convert time and date
  28532. DateToIso8601PChar(Dest,Expanded,
  28533. (Value shr (6+6+5+5+4)) and 4095,
  28534. 1+(Int64Rec(Value).Lo shr (6+6+5+5)) and 15,
  28535. 1+(Int64Rec(Value).Lo shr (6+6+5)) and 31);
  28536. if Expanded then
  28537. inc(Dest,10) else
  28538. inc(Dest,8);
  28539. TimeToIso8601PChar(Dest,Expanded,
  28540. (Int64Rec(Value).Lo shr (6+6)) and 31,
  28541. (Int64Rec(Value).Lo shr 6) and 63,
  28542. Int64Rec(Value).Lo and 63, FirstTimeChar);
  28543. if Expanded then
  28544. result := 15+4 else
  28545. result := 15;
  28546. if FirstTimeChar=#0 then
  28547. dec(result);
  28548. end;
  28549. end;
  28550. function TTimeLogBits.Text(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8;
  28551. var tmp: array[0..31] of AnsiChar;
  28552. begin
  28553. if Value=0 then
  28554. result := '' else
  28555. SetString(result,PAnsiChar(@tmp),Text(tmp,Expanded,FirstTimeChar));
  28556. end;
  28557. function TTimeLogBits.i18nText: string;
  28558. begin
  28559. if Assigned(i18nDateText) then
  28560. result := i18nDateText(Value) else
  28561. result := {$ifdef UNICODE}Ansi7ToString{$endif}(Text(true,' '));
  28562. end;
  28563. function TimeLogNow: TTimeLog;
  28564. begin
  28565. PTimeLogBits(@result)^.FromNow;
  28566. end;
  28567. function TimeLogNowUTC: TTimeLog;
  28568. begin
  28569. PTimeLogBits(@result)^.FromUTCTime;
  28570. end;
  28571. function NowToString(Expanded: boolean=true; FirstTimeChar: AnsiChar = ' '): RawUTF8;
  28572. var I: TTimeLogBits;
  28573. begin
  28574. I.FromNow;
  28575. result := I.Text(Expanded,FirstTimeChar);
  28576. end;
  28577. function NowUTCToString(Expanded: boolean=true; FirstTimeChar: AnsiChar = ' '): RawUTF8;
  28578. var I: TTimeLogBits;
  28579. begin
  28580. I.FromUTCTime;
  28581. result := I.Text(Expanded,FirstTimeChar);
  28582. end;
  28583. function DateTimeMSToString(DateTime: TDateTime; Expanded: boolean;
  28584. FirstTimeChar: AnsiChar; UTC: boolean): RawUTF8;
  28585. const FMT: array[boolean] of RawUTF8 = ('%%%%%%%%%', '%-%-%%:%:%:%.%%');
  28586. Z: array[boolean] of RawUTF8 = ('', 'Z');
  28587. var HH,MM,SS,MS,Y,M,D: word;
  28588. begin // 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
  28589. if DateTime=0 then begin
  28590. result := '';
  28591. exit;
  28592. end;
  28593. DecodeDate(DateTime,Y,M,D);
  28594. DecodeTime(DateTime,HH,MM,SS,MS);
  28595. FormatUTF8(FMT[Expanded], [UInt4DigitsToShort(Y),UInt2DigitsToShort(M),
  28596. UInt2DigitsToShort(D),FirstTimeChar,UInt2DigitsToShort(HH),UInt2DigitsToShort(MM),
  28597. UInt2DigitsToShort(SS),UInt3DigitsToShort(MS),Z[UTC]], result);
  28598. end;
  28599. const
  28600. HTML_WEEK_DAYS: array[1..7] of string[3] =
  28601. ('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); {do not localize}
  28602. HTML_MONTH_NAMES: array[1..12] of string[3] =
  28603. ('Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'); {do not localize}
  28604. function DateTimeToHTTPDate(UTCDateTime: TDateTime): RawUTF8;
  28605. var HH,MM,SS,MS,Y,M,D: word;
  28606. begin
  28607. if UTCDateTime=0 then begin
  28608. result := '';
  28609. exit;
  28610. end;
  28611. DecodeDate(UTCDateTime,Y,M,D);
  28612. DecodeTime(UTCDateTime,HH,MM,SS,MS);
  28613. FormatUTF8('%, % % % %:%:% GMT', [HTML_WEEK_DAYS[DayOfWeek(UTCDateTime)],
  28614. UInt2DigitsToShort(D), HTML_MONTH_NAMES[M],UInt4DigitsToShort(Y),
  28615. UInt2DigitsToShort(HH),UInt2DigitsToShort(MM),
  28616. UInt2DigitsToShort(SS)], result);
  28617. end;
  28618. function TimeToString: RawUTF8;
  28619. var I: TTimeLogBits;
  28620. begin
  28621. I.FromNow;
  28622. I.Value := I.Value and (1 shl (6+6+5)-1); // keep only time
  28623. result := I.Text(true,' ');
  28624. end;
  28625. function TimeLogFromFile(const FileName: TFileName): TTimeLog;
  28626. var Date: TDateTime;
  28627. begin
  28628. Date := FileAgeToDateTime(FileName);
  28629. if Date=0 then
  28630. result := 0 else
  28631. PTimeLogBits(@result)^.From(Date);
  28632. end;
  28633. function TimeLogFromDateTime(DateTime: TDateTime): TTimeLog;
  28634. begin
  28635. PTimeLogBits(@result)^.From(DateTime);
  28636. end;
  28637. { TTimeZoneValue }
  28638. function TryEncodeDayOfWeekInMonth(AYear, AMonth, ANthDayOfWeek, ADayOfWeek: integer;
  28639. out AValue: TDateTime): Boolean;
  28640. var LStartOfMonth, LDay: integer;
  28641. begin // adapted from DateUtils
  28642. LStartOfMonth := (DateTimeToTimeStamp(EncodeDate(AYear,AMonth,1)).Date-1)mod 7+1;
  28643. if LStartOfMonth<=ADayOfWeek then
  28644. dec(ANthDayOfWeek);
  28645. LDay := (ADayOfWeek-LStartOfMonth+1)+7*ANthDayOfWeek;
  28646. result := TryEncodeDate(AYear,AMonth,LDay,AValue);
  28647. end;
  28648. function TTimeZoneValue.EncodeForTimeChange(const year: word): TDateTime;
  28649. var dow,day: word;
  28650. begin
  28651. if wDayOfWeek=0 then
  28652. dow := 7 else // Delphi Sunday = 7
  28653. dow := wDayOfWeek;
  28654. // Encoding the day of change
  28655. day := wDay;
  28656. while not TryEncodeDayOfWeekInMonth(year,wMonth,day,dow,Result) do begin
  28657. // if wDay = 5 then try it and if needed decrement to find the last
  28658. // occurence of the day in this month
  28659. if day=0 then begin
  28660. TryEncodeDayOfWeekInMonth(year,wMonth,1,7,Result);
  28661. break;
  28662. end;
  28663. dec(day);
  28664. end;
  28665. // finally add the time when change is due
  28666. result := result+EncodeTime(wHour,wMinute,wSecond,wMilliseconds);
  28667. end;
  28668. function TTimeZoneValue.IsZero: boolean;
  28669. begin
  28670. result := (PInt64Array(@self)[0]=0) and (PInt64Array(@self)[1]=0);
  28671. end;
  28672. { TTimeZoneData }
  28673. function TTimeZoneData.GetTziFor(year: integer): PTimeZoneInfo;
  28674. var i,last: integer;
  28675. begin
  28676. if dyn=nil then
  28677. result := @tzi else
  28678. if year<=dyn[0].year then
  28679. result := @dyn[0].tzi else begin
  28680. last := high(dyn);
  28681. if year>=dyn[last].year then
  28682. result := @dyn[last].tzi else begin
  28683. for i := 1 to last do
  28684. if year<dyn[i].year then begin
  28685. result := @dyn[i-1].tzi;
  28686. exit;
  28687. end;
  28688. result := @tzi; // should never happen, but makes compiler happy
  28689. end;
  28690. end;
  28691. end;
  28692. { TTimeZoneInformation }
  28693. constructor TSynTimeZone.Create;
  28694. begin
  28695. fZones.InitSpecific(TypeInfo(TTimeZoneDataDynArray),fZone,djRawUTF8);
  28696. end;
  28697. constructor TSynTimeZone.CreateDefault;
  28698. begin
  28699. Create;
  28700. {$ifdef LVCL}
  28701. LoadFromFile;
  28702. {$else}
  28703. {$ifdef MSWINDOWS}
  28704. LoadFromRegistry;
  28705. {$else}
  28706. LoadFromFile;
  28707. if fZones.Count=0 then
  28708. LoadFromResource; // if no .tz file is available, try from registry
  28709. {$endif}
  28710. {$endif}
  28711. end;
  28712. destructor TSynTimeZone.Destroy;
  28713. begin
  28714. inherited Destroy;
  28715. fIds.Free;
  28716. fDisplays.Free;
  28717. end;
  28718. var
  28719. SharedSynTimeZone: TSynTimeZone;
  28720. class function TSynTimeZone.Default: TSynTimeZone;
  28721. begin
  28722. if SharedSynTimeZone=nil then
  28723. GarbageCollectorFreeAndNil(SharedSynTimeZone,TSynTimeZone.CreateDefault);
  28724. result := SharedSynTimeZone;
  28725. end;
  28726. function TSynTimeZone.SaveToBuffer: RawByteString;
  28727. begin
  28728. result := SynLZCompress(fZones.SaveTo);
  28729. end;
  28730. procedure TSynTimeZone.SaveToFile(const FileName: TFileName);
  28731. var FN: TFileName;
  28732. begin
  28733. if FileName='' then
  28734. FN := ChangeFileExt(ExeVersion.ProgramFileName,'.tz') else
  28735. FN := FileName;
  28736. FileFromString(SaveToBuffer,FN);
  28737. end;
  28738. procedure TSynTimeZone.LoadFromBuffer(const Buffer: RawByteString);
  28739. begin
  28740. fZones.LoadFrom(pointer(SynLZDecompress(Buffer)));
  28741. fZones.ReHash;
  28742. FreeAndNil(fIds);
  28743. FreeAndNil(fDisplays);
  28744. end;
  28745. procedure TSynTimeZone.LoadFromFile(const FileName: TFileName);
  28746. var FN: TFileName;
  28747. begin
  28748. if FileName='' then
  28749. FN := ChangeFileExt(ExeVersion.ProgramFileName,'.tz') else
  28750. FN := FileName;
  28751. LoadFromBuffer(StringFromFile(FN));
  28752. end;
  28753. procedure TSynTimeZone.LoadFromResource;
  28754. var buf: RawByteString;
  28755. begin
  28756. ResourceToRawByteString(ClassName,PChar(10),buf);
  28757. if buf<>'' then
  28758. LoadFromBuffer(buf);
  28759. end;
  28760. {$ifdef MSWINDOWS}
  28761. {$ifndef LVCL}
  28762. procedure TSynTimeZone.LoadFromRegistry;
  28763. const REGKEY = '\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\';
  28764. var Reg: TRegistry;
  28765. Keys: TStringList;
  28766. i,first,last,year,n: integer;
  28767. item: TTimeZoneData;
  28768. begin
  28769. fZones.Clear;
  28770. Keys := TStringList.Create;
  28771. Reg := TRegistry.Create;
  28772. try
  28773. Reg.RootKey := HKEY_LOCAL_MACHINE;
  28774. if Reg.OpenKeyReadOnly(REGKEY) then
  28775. try
  28776. Reg.GetKeyNames(Keys);
  28777. finally
  28778. Reg.CloseKey;
  28779. end;
  28780. for i := 0 to Keys.Count-1 do begin
  28781. Finalize(item);
  28782. FillcharFast(item.tzi,SizeOf(item.tzi),0);
  28783. if Reg.OpenKeyReadOnly(REGKEY+Keys[i]) then
  28784. try
  28785. StringToUTF8(Keys[i],RawUTF8(item.id));
  28786. StringToUTF8(Reg.ReadString('Display'),item.Display);
  28787. Reg.ReadBinaryData('TZI', item.tzi, SizeOf(item.tzi));
  28788. finally
  28789. Reg.CloseKey;
  28790. end;
  28791. if Reg.OpenKeyReadOnly(REGKEY+Keys[i]+'\Dynamic DST') then
  28792. try
  28793. first := Reg.ReadInteger('FirstEntry');
  28794. last := Reg.ReadInteger('LastEntry');
  28795. n := 0;
  28796. SetLength(item.dyn,last-first+1);
  28797. for year := first to last do
  28798. if Reg.ReadBinaryData(IntToStr(year),item.dyn[n].tzi,
  28799. SizeOf(TTimeZoneInfo))=SizeOf(TTimeZoneInfo) then begin
  28800. item.dyn[n].year := year;
  28801. inc(n);
  28802. end;
  28803. SetLength(item.dyn,n);
  28804. finally
  28805. Reg.CloseKey;
  28806. end;
  28807. fZones.Add(item);
  28808. end;
  28809. finally
  28810. Reg.Free;
  28811. Keys.Free;
  28812. end;
  28813. fZones.ReHash;
  28814. FreeAndNil(fIds);
  28815. FreeAndNil(fDisplays);
  28816. end;
  28817. {$endif LVCL}
  28818. {$endif MSWINDOWS}
  28819. function TSynTimeZone.GetDisplay(const TzId: TTimeZoneID): RawUTF8;
  28820. var ndx: integer;
  28821. begin
  28822. if self=nil then
  28823. ndx := -1 else
  28824. ndx := fZones.FindHashed(TzID);
  28825. if ndx<0 then
  28826. result := '' else
  28827. result := fZone[ndx].display;
  28828. end;
  28829. function TSynTimeZone.GetBiasForDateTime(const Value: TDateTime;
  28830. const TzId: TTimeZoneID; out Bias: integer; out HaveDaylight: boolean): boolean;
  28831. var ndx: integer;
  28832. y,m,d: word;
  28833. tzi: PTimeZoneInfo;
  28834. std,dlt: TDateTime;
  28835. begin
  28836. if (self=nil) or (TzId='') then
  28837. ndx := -1 else
  28838. if TzID=fLastZone then
  28839. ndx := fLastIndex else begin
  28840. ndx := fZones.FindHashed(TzID);
  28841. fLastZone := TzID;
  28842. flastIndex := ndx;
  28843. end;
  28844. if ndx<0 then begin
  28845. Bias := 0;
  28846. HaveDayLight := false;
  28847. result := false;
  28848. exit;
  28849. end;
  28850. DecodeDate(Value,y,m,d);
  28851. tzi := fZone[ndx].GetTziFor(y);
  28852. if tzi.change_time_std.IsZero then begin
  28853. HaveDaylight := false;
  28854. Bias := tzi.Bias+tzi.bias_std;
  28855. end else begin
  28856. HaveDaylight := true;
  28857. std := tzi.change_time_std.EncodeForTimeChange(y);
  28858. dlt := tzi.change_time_dlt.EncodeForTimeChange(y);
  28859. if std<dlt then
  28860. if (std<=Value) and (Value<dlt) then
  28861. Bias := tzi.Bias+tzi.bias_std else
  28862. Bias := tzi.Bias+tzi.bias_dlt else
  28863. if (dlt<=Value) and (Value<std) then
  28864. Bias := tzi.Bias+tzi.bias_dlt else
  28865. Bias := tzi.Bias+tzi.bias_std;
  28866. end;
  28867. result := true;
  28868. end;
  28869. function TSynTimeZone.UtcToLocal(const UtcDateTime: TDateTime;
  28870. const TzId: TTimeZoneID): TDateTime;
  28871. var Bias: integer;
  28872. HaveDaylight: boolean;
  28873. begin
  28874. if (self=nil) or (TzId='') then
  28875. result := UtcDateTime else begin
  28876. GetBiasForDateTime(UtcDateTime,TzId,Bias,HaveDaylight);
  28877. result := ((UtcDateTime*MinsPerDay)-Bias)/MinsPerDay;
  28878. end;
  28879. end;
  28880. function TSynTimeZone.NowToLocal(const TzId: TTimeZoneID): TDateTime;
  28881. begin
  28882. result := UtcToLocal(NowUtc,TzId);
  28883. end;
  28884. function TSynTimeZone.LocalToUtc(const LocalDateTime: TDateTime; const TzID: TTimeZoneID): TDateTime;
  28885. var Bias: integer;
  28886. HaveDaylight: boolean;
  28887. begin
  28888. if (self=nil) or (TzId='') then
  28889. result := LocalDateTime else begin
  28890. GetBiasForDateTime(LocalDateTime,TzId,Bias,HaveDaylight);
  28891. result := ((LocalDateTime*MinsPerDay)+Bias)/MinsPerDay;
  28892. end;
  28893. end;
  28894. function TSynTimeZone.Ids: TStrings;
  28895. var i: integer;
  28896. begin
  28897. if fIDs=nil then begin
  28898. fIDs := TStringList.Create;
  28899. for i := 0 to high(fZone) do
  28900. fIDs.Add(UTF8ToString(fZone[i].id));
  28901. end;
  28902. result := fIDs;
  28903. end;
  28904. function TSynTimeZone.Displays: TStrings;
  28905. var i: integer;
  28906. begin
  28907. if fDisplays=nil then begin
  28908. fDisplays := TStringList.Create;
  28909. for i := 0 to high(fZone) do
  28910. fDisplays.Add(UTF8ToString(fZone[i].Display));
  28911. end;
  28912. result := fDisplays;
  28913. end;
  28914. procedure AppendToTextFile(aLine: RawUTF8; const aFileName: TFileName);
  28915. var F: THandle;
  28916. Old: TFileName;
  28917. Date: array[1..22] of AnsiChar;
  28918. i: integer;
  28919. {$ifdef MSWINDOWS}
  28920. Now: TSystemTime; {$else}
  28921. D: TDateTime; {$endif}
  28922. begin
  28923. if aFileName='' then
  28924. exit;
  28925. F := FileOpen(aFileName,fmOpenWrite);
  28926. if PtrInt(F)<0 then begin
  28927. F := FileCreate(aFileName);
  28928. if PtrInt(F)<0 then
  28929. exit; // you may not have write access to this folder
  28930. end;
  28931. // append to end of file
  28932. if FileSeek64(F,0,soFromEnd)>MAXLOGSIZE then begin
  28933. // rotate log file if too big
  28934. FileClose(F);
  28935. Old := aFileName+'.bak'; // '.log.bak'
  28936. DeleteFile(Old); // rotate once
  28937. RenameFile(aFileName,Old);
  28938. F := FileCreate(aFileName);
  28939. if PtrInt(F)<0 then
  28940. exit;
  28941. end;
  28942. PWord(@Date)^ := 13+10 shl 8; // first go to next line
  28943. {$ifdef MSWINDOWS}
  28944. GetLocalTime(Now); // windows dedicated function
  28945. DateToIso8601PChar(@Date[3],true,Now.wYear,Now.wMonth,Now.wDay);
  28946. TimeToIso8601PChar(@Date[13],true,Now.wHour,Now.wMinute,Now.wSecond,' ');
  28947. {$else}
  28948. D := Now; // cross platform version
  28949. DateToIso8601PChar(D,@Date[3],true);
  28950. TimeToIso8601PChar(D,@Date[13],true);
  28951. {$endif}
  28952. Date[22] := ' ';
  28953. FileWrite(F,Date,sizeof(Date));
  28954. for i := 1 to length(aLine) do
  28955. if aLine[i]<' ' then
  28956. aLine[i] := ' '; // avoid line feed in text log file
  28957. FileWrite(F,pointer(aLine)^,length(aLine));
  28958. FileClose(F);
  28959. end;
  28960. procedure LogToTextFile(Msg: RawUTF8);
  28961. begin
  28962. if Msg='' then begin
  28963. StringToUTF8(SysErrorMessage(GetLastError),Msg);
  28964. if Msg='' then
  28965. exit;
  28966. end;
  28967. AppendToTextFile(Msg,{$ifndef MSWINDOWS}ExtractFileName{$endif}
  28968. (ChangeFileExt(ExeVersion.ProgramFileName,'.log')));
  28969. end;
  28970. function IsEqualGUID(const guid1, guid2: TGUID): Boolean;
  28971. {$ifdef CPU64}
  28972. var a: array[0..1] of Int64 absolute guid1;
  28973. b: array[0..1] of Int64 absolute guid2;
  28974. begin
  28975. result := (a[0]=b[0]) and (a[1]=b[1]);
  28976. end;
  28977. {$else}
  28978. var a: array[0..3] of integer absolute guid1;
  28979. b: array[0..3] of integer absolute guid2;
  28980. begin // slightly faster implementation than in SysUtils.pas
  28981. {$ifdef HASINLINE}
  28982. result := (a[0]=b[0]) and (a[1]=b[1]) and (a[2]=b[2]) and (a[3]=b[3]);
  28983. {$else}
  28984. if a[0]<>b[0] then
  28985. result := false else
  28986. result := (a[1]=b[1]) and (a[2]=b[2]) and (a[3]=b[3]);
  28987. {$endif}
  28988. end;
  28989. {$endif}
  28990. function IsEqualGUIDArray(const guid: TGUID; const guids: array of TGUID): integer;
  28991. begin
  28992. for result := 0 to high(guids) do
  28993. if IsEqualGUID(guid,guids[result]) then
  28994. exit;
  28995. result := -1;
  28996. end;
  28997. function IsNullGUID(const guid: TGUID): Boolean;
  28998. begin
  28999. {$ifdef CPU64}
  29000. result := (PInt64Array(@guid)^[0]=0) and (PInt64Array(@guid)^[1]=0);
  29001. {$else}
  29002. result := (PIntegerArray(@guid)^[0]=0) and (PIntegerArray(@guid)^[1]=0) and
  29003. (PIntegerArray(@guid)^[2]=0) and (PIntegerArray(@guid)^[3]=0);
  29004. {$endif}
  29005. end;
  29006. function AddGUID(var guids: TGUIDDynArray; const guid: TGUID;
  29007. NoDuplicates: boolean): integer;
  29008. begin
  29009. if NoDuplicates then
  29010. for result := 0 to length(guids)-1 do
  29011. if IsEqualGUID(guid,guids[result]) then
  29012. exit;
  29013. result := length(guids);
  29014. SetLength(guids,result+1);
  29015. guids[result] := guid;
  29016. end;
  29017. function GUIDToText(P: PUTF8Char; guid: PByteArray): PUTF8Char;
  29018. var i: integer;
  29019. begin // encode as '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
  29020. for i := 3 downto 0 do begin
  29021. PWord(P)^ := TwoDigitsHexWB[guid[i]];
  29022. inc(P,2);
  29023. end;
  29024. inc(PByte(guid),4);
  29025. for i := 1 to 2 do begin
  29026. P[0] := '-';
  29027. PWord(P+1)^ := TwoDigitsHexWB[guid[1]];
  29028. PWord(P+3)^ := TwoDigitsHexWB[guid[0]];
  29029. inc(PByte(guid),2);
  29030. inc(P,5);
  29031. end;
  29032. P[0] := '-';
  29033. PWord(P+1)^ := TwoDigitsHexWB[guid[0]];
  29034. PWord(P+3)^ := TwoDigitsHexWB[guid[1]];
  29035. P[5] := '-';
  29036. inc(PByte(guid),2);
  29037. inc(P,6);
  29038. for i := 0 to 5 do begin
  29039. PWord(P)^ := TwoDigitsHexWB[guid[i]];
  29040. inc(P,2);
  29041. end;
  29042. result := P;
  29043. end;
  29044. function HexaToByte(P: PUTF8Char; var Dest: byte): boolean; {$ifdef HASINLINE}inline;{$endif}
  29045. var B,C: byte;
  29046. begin
  29047. B := ConvertHexToBin[Ord(P[0])];
  29048. if B<=15 then begin
  29049. C := ConvertHexToBin[Ord(P[1])];
  29050. if C<=15 then begin
  29051. Dest := B shl 4+C;
  29052. result := true;
  29053. exit;
  29054. end;
  29055. end;
  29056. result := false; // mark error
  29057. end;
  29058. function TextToGUID(P: PUTF8Char; guid: PByteArray): PUTF8Char;
  29059. var i: integer;
  29060. begin // decode from '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
  29061. result := nil;
  29062. for i := 3 downto 0 do begin
  29063. if not HexaToByte(P,guid[i]) then
  29064. exit;
  29065. inc(P,2);
  29066. end;
  29067. inc(PByte(guid),4);
  29068. for i := 1 to 2 do begin
  29069. if (P^<>'-') or (not HexaToByte(P+1,guid[1])) or (not HexaToByte(P+3,guid[0])) then
  29070. exit;
  29071. inc(P,5);
  29072. inc(PByte(guid),2);
  29073. end;
  29074. if (P[0]<>'-') or (P[5]<>'-') or
  29075. (not HexaToByte(P+1,guid[0])) or (not HexaToByte(P+3,guid[1])) then
  29076. exit;
  29077. inc(PByte(guid),2);
  29078. inc(P,6);
  29079. for i := 0 to 5 do
  29080. if HexaToByte(P,guid[i]) then
  29081. inc(P,2) else
  29082. exit;
  29083. result := P;
  29084. end;
  29085. function GUIDToRawUTF8(const guid: TGUID): RawUTF8;
  29086. var P: PUTF8Char;
  29087. begin
  29088. FastNewRawUTF8(result,38);
  29089. P := pointer(result);
  29090. P^ := '{';
  29091. GUIDToText(P+1,@guid)^ := '}';
  29092. end;
  29093. function GUIDToShort(const guid: TGUID): TGUIDShortString;
  29094. begin
  29095. result[0] := #38;
  29096. result[1] := '{';
  29097. result[38] := '}';
  29098. GUIDToText(@result[2],@guid);
  29099. end;
  29100. function GUIDToString(const guid: TGUID): string;
  29101. {$ifdef UNICODE}
  29102. var tmp: array[0..35] of AnsiChar;
  29103. i: integer;
  29104. begin
  29105. GUIDToText(tmp,@guid);
  29106. SetString(result,nil,38);
  29107. PWordArray(result)[0] := ord('{');
  29108. for i := 1 to 36 do
  29109. PWordArray(result)[i] := ord(tmp[i-1]); // no conversion for 7 bit Ansi
  29110. PWordArray(result)[37] := ord('}');
  29111. end;
  29112. {$else}
  29113. begin
  29114. result := GUIDToRawUTF8(guid);
  29115. end;
  29116. {$endif}
  29117. {$ifdef CPUINTEL}
  29118. /// get 32-bit value from NIST SP 800-90A compliant RDRAND Intel x86/x64 opcode
  29119. function RdRand32: cardinal;
  29120. {$ifdef CPU64}
  29121. {$ifdef FPC}nostackframe; assembler;
  29122. asm
  29123. {$else}
  29124. asm
  29125. .noframe
  29126. {$endif FPC}
  29127. {$endif CPU64}
  29128. {$ifdef CPU32}
  29129. asm
  29130. {$endif}
  29131. // rdrand eax: same opcodes for x86 and x64
  29132. db $0f,$c7,$f0
  29133. // returns in eax, ignore carry flag (eax=0 won't hurt)
  29134. end;
  29135. {$endif}
  29136. procedure FillRandom(Dest: PCardinalArray; CardinalCount: integer);
  29137. var i: integer;
  29138. c: cardinal;
  29139. timenow: Int64;
  29140. begin
  29141. c := GetTickCount64+Random(maxInt)+GetCurrentThreadID;
  29142. QueryPerformanceCounter(timenow);
  29143. c := c xor crc32c(c,@timenow,sizeof(timenow));
  29144. for i := 0 to CardinalCount-1 do begin
  29145. c := c xor crc32ctab[0,(c+cardinal(i)) and 1023]
  29146. xor crc32c(c,pointer(Dest),CardinalCount*4);
  29147. {$ifdef CPUINTEL}
  29148. if cfRAND in CpuFeatures then
  29149. c := c xor RdRand32;
  29150. {$endif};
  29151. Dest^[i] := Dest^[i] xor c;
  29152. end;
  29153. end;
  29154. function RandomGUID: TGUID;
  29155. begin
  29156. FillRandom(@result,sizeof(TGUID) shr 2);
  29157. end;
  29158. procedure RandomGUID(out result: TGUID); overload;
  29159. begin
  29160. FillRandom(@result,sizeof(TGUID) shr 2);
  29161. end;
  29162. function RawUTF8ToGUID(const text: RawByteString): TGUID;
  29163. begin
  29164. if (length(text)<>38) or (text[1]<>'{') or (text[38]<>'}') or
  29165. (TextToGUID(@text[2],@result)=nil) then
  29166. FillcharFast(result,sizeof(result),0);
  29167. end;
  29168. function StringToGUID(const text: string): TGUID;
  29169. {$ifdef UNICODE}
  29170. var tmp: array[0..35] of byte;
  29171. i: integer;
  29172. {$endif}
  29173. begin
  29174. if (length(text)=38) and (text[1]='{') and (text[38]='}') then begin
  29175. {$ifdef UNICODE}
  29176. for i := 0 to 35 do
  29177. tmp[i] := PWordArray(text)[i+1];
  29178. if TextToGUID(@tmp,@result)<>nil then
  29179. {$else}
  29180. if TextToGUID(@text[2],@result)<>nil then
  29181. {$endif}
  29182. exit; // conversion OK
  29183. end;
  29184. FillcharFast(result,sizeof(result),0);
  29185. end;
  29186. function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar;
  29187. var c, c10: Int64;
  29188. c64: Int64Rec absolute c;
  29189. Lo: cardinal;
  29190. begin
  29191. if Value=0 then begin
  29192. result := P-1;
  29193. result^ := '0';
  29194. exit;
  29195. end;
  29196. if Value<0 then
  29197. c := -Value else
  29198. c := Value;
  29199. if (c64.Hi=0) and (c64.Lo<10000) then begin
  29200. Lo := c64.Lo; // only decimals
  29201. result := P;
  29202. end else begin
  29203. Lo := 10000;
  29204. result := P-1; // reserve space to insert '.'
  29205. end;
  29206. repeat
  29207. if c64.Hi=0 then begin
  29208. result := StrUInt32(result,c64.Lo);
  29209. break;
  29210. end;
  29211. c10 := c div 100; // one div by two digits
  29212. dec(c,c10*100); // fast c := c mod 100
  29213. dec(result,2);
  29214. PWord(result)^ := TwoDigitLookupW[c];
  29215. c := c10;
  29216. if c10=0 then break;
  29217. until false;
  29218. if Lo<10000 then begin
  29219. // only decimals -> append left '0.' to '0.000'
  29220. case Lo of
  29221. 1..9: begin // append left '0.000'
  29222. dec(result);
  29223. result^ := '0';
  29224. dec(result,2);
  29225. PWord(result)^ := ord('0')+ord('0')shl 8;
  29226. end;
  29227. 10..99: begin // append left '0.00'
  29228. dec(result,2);
  29229. PWord(result)^ := ord('0')+ord('0')shl 8;
  29230. end;
  29231. 100..999: begin // append left '0.0'
  29232. dec(result);
  29233. result^ := '0';
  29234. end;
  29235. end;
  29236. dec(result,2);
  29237. PWord(result)^ := ord('0')+ord('.')shl 8;
  29238. end else begin
  29239. // insert '.' just before last 4 decimals
  29240. P[-1] := P[-2];
  29241. P[-2] := P[-3];
  29242. P[-3] := P[-4];
  29243. P[-4] := P[-5];
  29244. P[-5] := '.';
  29245. end;
  29246. if Value<0 then begin
  29247. dec(result);
  29248. result^ := '-';
  29249. end;
  29250. end;
  29251. procedure Curr64ToStr(const Value: Int64; var result: RawUTF8); overload;
  29252. var tmp: array[0..31] of AnsiChar;
  29253. P: PAnsiChar;
  29254. Decim, L: Cardinal;
  29255. begin
  29256. P := StrCurr64(@tmp[31],Value);
  29257. L := @tmp[31]-P;
  29258. if L>4 then begin
  29259. Decim := PCardinal(P+L-sizeof(cardinal))^; // 4 last digits = 4 decimals
  29260. if Decim=ord('0')+ord('0')shl 8+ord('0')shl 16+ord('0')shl 24 then
  29261. dec(L,5) else // no decimal
  29262. if Decim and $ffff0000=ord('0')shl 16+ord('0')shl 24 then
  29263. dec(L,2); // 2 decimals
  29264. end;
  29265. SetRawUTF8(result,P,L);
  29266. end;
  29267. function Curr64ToStr(const Value: Int64): RawUTF8;
  29268. begin
  29269. Curr64ToStr(Value,result);
  29270. end;
  29271. function CurrencyToStr(Value: currency): RawUTF8;
  29272. begin
  29273. result := Curr64ToStr(PInt64(@Value)^);
  29274. end;
  29275. function Curr64ToPChar(const Value: Int64; Dest: PUTF8Char): PtrInt;
  29276. var tmp: array[0..31] of AnsiChar;
  29277. P: PAnsiChar;
  29278. Decim: Cardinal;
  29279. begin
  29280. P := StrCurr64(@tmp[31],Value);
  29281. result := @tmp[31]-P;
  29282. if result>4 then begin
  29283. Decim := PCardinal(P+result-sizeof(cardinal))^; // 4 last digits = 4 decimals
  29284. if Decim=ord('0')+ord('0')shl 8+ord('0')shl 16+ord('0')shl 24 then
  29285. dec(result,5) else // no decimal
  29286. if Decim and $ffff0000=ord('0')shl 16+ord('0')shl 24 then
  29287. dec(result,2); // 2 decimals
  29288. end;
  29289. MoveFast(P^,Dest^,result);
  29290. end;
  29291. function StrToCurr64(P: PUTF8Char; NoDecimal: PBoolean=nil): Int64;
  29292. var c: cardinal;
  29293. minus: boolean;
  29294. Dec: cardinal;
  29295. begin
  29296. result := 0;
  29297. if P=nil then
  29298. exit;
  29299. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  29300. if P^='-' then begin
  29301. minus := true;
  29302. repeat inc(P) until P^<>' ';
  29303. end else begin
  29304. minus := false;
  29305. if P^='+' then
  29306. repeat inc(P) until P^<>' ';
  29307. end;
  29308. if P^='.' then begin // '.5' -> 500
  29309. Dec := 2;
  29310. inc(P);
  29311. end else
  29312. Dec := 0;
  29313. c := byte(P^)-48;
  29314. if c>9 then
  29315. exit;
  29316. Int64Rec(result).Lo := c;
  29317. inc(P);
  29318. repeat
  29319. if P^<>'.' then begin
  29320. c := byte(P^)-48;
  29321. if c>9 then
  29322. break;
  29323. {$ifdef CPU64}
  29324. result := result*10;
  29325. {$else}
  29326. result := result shl 3+result+result;
  29327. {$endif}
  29328. inc(result,c);
  29329. inc(P);
  29330. if Dec<>0 then begin
  29331. inc(Dec);
  29332. if Dec<5 then continue else break;
  29333. end;
  29334. end else begin
  29335. inc(Dec);
  29336. inc(P);
  29337. end;
  29338. until false;
  29339. if NoDecimal<>nil then
  29340. if Dec=0 then begin
  29341. NoDecimal^ := true;
  29342. if minus then
  29343. result := -result;
  29344. exit;
  29345. end else
  29346. NoDecimal^ := false;
  29347. if Dec<>5 then // Dec=5 most of the time
  29348. case Dec of
  29349. 0,1: result := result*10000;
  29350. {$ifdef CPU64}
  29351. 2: result := result*1000;
  29352. 3: result := result*100;
  29353. 4: result := result*10;
  29354. {$else}
  29355. 2: result := result shl 10-result shl 4-result shl 3;
  29356. 3: result := result shl 6+result shl 5+result shl 2;
  29357. 4: result := result shl 3+result+result;
  29358. {$endif}
  29359. end;
  29360. if minus then
  29361. result := -result;
  29362. end;
  29363. function StrToCurrency(P: PUTF8Char): currency;
  29364. begin
  29365. PInt64(@result)^ := StrToCurr64(P,nil);
  29366. end;
  29367. function TruncTo2Digits(Value: Currency): Currency;
  29368. var V64: Int64 absolute Value; // to avoid any floating-point precision issues
  29369. Spare: integer;
  29370. begin
  29371. Spare := V64 mod 100;
  29372. if Spare<>0 then
  29373. dec(V64,Spare);
  29374. result := Value;
  29375. end;
  29376. function SimpleRoundTo2Digits(Value: Currency): Currency;
  29377. var V64: Int64 absolute Value; // to avoid any floating-point precision issues
  29378. Spare: integer;
  29379. begin
  29380. Spare := V64 mod 100;
  29381. if Spare<>0 then
  29382. if Spare>50 then
  29383. inc(V64,100-Spare) else
  29384. if Spare<-50 then
  29385. dec(V64,100+Spare) else
  29386. dec(V64,Spare);
  29387. result := Value;
  29388. end;
  29389. function TrimLeftLowerCase(const V: RawUTF8): PUTF8Char;
  29390. begin
  29391. result := Pointer(V);
  29392. if result<>nil then begin
  29393. while result^ in ['a'..'z'] do
  29394. inc(result);
  29395. if result^=#0 then
  29396. result := Pointer(V);
  29397. end;
  29398. end;
  29399. function TrimLeftLowerCaseToShort(V: PShortString): ShortString;
  29400. var P: PAnsiChar;
  29401. L: integer;
  29402. begin
  29403. L := length(V^);
  29404. P := @V^[1];
  29405. while (L>0) and (P^ in ['a'..'z']) do begin
  29406. inc(P);
  29407. dec(L);
  29408. end;
  29409. if L=0 then
  29410. result := V^ else
  29411. SetString(result,P,L);
  29412. end;
  29413. function TrimLeftLowerCaseShort(V: PShortString): RawUTF8;
  29414. {$ifdef NODELPHIASM}
  29415. var P: PAnsiChar;
  29416. L: integer;
  29417. begin
  29418. L := length(V^);
  29419. P := @V^[1];
  29420. while (L>0) and (P^ in ['a'..'z']) do begin
  29421. inc(P);
  29422. dec(L);
  29423. end;
  29424. if L=0 then
  29425. result := V^ else
  29426. SetString(result,P,L);
  29427. end;
  29428. {$else}
  29429. asm // eax=V
  29430. xor ecx,ecx
  29431. push edx // save result RawUTF8
  29432. test eax,eax
  29433. jz @2 // avoid GPF
  29434. lea edx,eax+1
  29435. mov cl,[eax]
  29436. @1: mov ch,[edx] // edx=source cl=length
  29437. sub ch,'a'
  29438. sub ch,'z'-'a'
  29439. ja @2 // not a lower char -> create a result string starting at edx
  29440. dec cl
  29441. lea edx,[edx+1]
  29442. jnz @1
  29443. mov cl,[eax]
  29444. lea edx,[eax+1] // no UpperCase -> retrieve full text (result := V^)
  29445. @2: pop eax
  29446. movzx ecx,cl
  29447. {$ifdef UNICODE}
  29448. push CP_UTF8 // UTF-8 code page for Delphi 2009+ + call below, not jump
  29449. call System.@LStrFromPCharLen // eax=Dest edx=Source ecx=Length
  29450. rep ret // we need a call just above for right push CP_UTF8 retrieval
  29451. {$else}
  29452. jmp System.@LStrFromPCharLen // eax=dest edx=source ecx=length(source)
  29453. {$endif}
  29454. end;
  29455. {$endif}
  29456. function UnCamelCase(const S: RawUTF8): RawUTF8; overload;
  29457. begin
  29458. result := '';
  29459. if S='' then
  29460. exit;
  29461. SetLength(result,length(S)*2); // max length
  29462. SetLength(result,UnCamelCase(pointer(result),pointer(S)));
  29463. end;
  29464. function UnCamelCase(D, P: PUTF8Char): integer; overload;
  29465. var Space, SpaceBeg, DBeg: PUTF8Char;
  29466. CapitalCount: integer;
  29467. Number: boolean;
  29468. label Next;
  29469. begin
  29470. Space := D;
  29471. DBeg := D;
  29472. SpaceBeg := D;
  29473. if (D<>nil) and (P<>nil) then // avoid GPF
  29474. repeat
  29475. CapitalCount := 0;
  29476. Number := P^ in ['0'..'9'];
  29477. if Number then
  29478. repeat
  29479. inc(CapitalCount);
  29480. D^ := P^;
  29481. inc(P);
  29482. inc(D);
  29483. until not (P^ in ['0'..'9']) else
  29484. repeat
  29485. inc(CapitalCount);
  29486. D^ := P^;
  29487. inc(P);
  29488. inc(D);
  29489. until not (P^ in ['A'..'Z']);
  29490. if P^=#0 then break; // no lowercase conversion of last fully uppercased word
  29491. if (CapitalCount > 1) and not Number then begin
  29492. dec(P);
  29493. dec(D);
  29494. end;
  29495. while P^ in ['a'..'z'] do begin
  29496. D^ := P^;
  29497. inc(D);
  29498. inc(P);
  29499. end;
  29500. if P^='_' then
  29501. if P[1]='_' then begin
  29502. D^ := ':';
  29503. inc(P);
  29504. inc(D);
  29505. goto Next;
  29506. end else begin
  29507. PWord(D)^ := ord(' ')+ord('-')shl 8;
  29508. inc(D,2);
  29509. Next: if Space=SpaceBeg then
  29510. SpaceBeg := D+1;
  29511. inc(P);
  29512. Space := D+1;
  29513. end else
  29514. Space := D;
  29515. if P^=#0 then break;
  29516. D^ := ' ';
  29517. inc(D);
  29518. until false;
  29519. while Space>SpaceBeg do begin
  29520. if Space^ in ['A'..'Z'] then
  29521. if not (Space[1] in ['A'..'Z',' ']) then
  29522. inc(Space^,32); // lowercase conversion of not last fully uppercased word
  29523. dec(Space);
  29524. end;
  29525. result := D-DBeg;
  29526. end;
  29527. procedure GetCaptionFromPCharLen(P: PUTF8Char; out result: string);
  29528. var Temp: array[byte] of AnsiChar;
  29529. begin // "out result" parameter definition already made result := ''
  29530. if P=nil then
  29531. exit;
  29532. {$ifdef UNICODE}
  29533. // property and enumeration names are UTF-8 encoded with Delphi 2009+
  29534. UTF8DecodeToUnicodeString(Temp,UnCamelCase(@Temp,P),result);
  29535. {$else}
  29536. SetString(result,Temp,UnCamelCase(@Temp,P));
  29537. {$endif}
  29538. {$ifndef LVCL} // LVCL system.pas doesn't implement LoadResStringTranslate()
  29539. if Assigned(LoadResStringTranslate) then
  29540. LoadResStringTranslate(result);
  29541. {$endif}
  29542. end;
  29543. function GetDisplayNameFromClass(C: TClass): RawUTF8;
  29544. var DelphiName: PShortString;
  29545. TrimLeft: integer;
  29546. begin
  29547. if C=nil then begin
  29548. result := '';
  29549. exit;
  29550. end;
  29551. // new TObject.ClassName is UnicodeString (since Delphi 2009) -> inline code
  29552. // with vmtClassName = UTF-8 encoded text stored in a shortstring = -44
  29553. DelphiName := PPointer(PtrInt(C)+vmtClassName)^;
  29554. TrimLeft := 0;
  29555. if DelphiName^[0]>#4 then
  29556. case PInteger(@DelphiName^[1])^ and $DFDFDFDF of
  29557. // fast case-insensitive compare
  29558. ord('T')+ord('S')shl 8+ord('Q')shl 16+ord('L')shl 24:
  29559. if (DelphiName^[0]<=#10) or
  29560. (PInteger(@DelphiName^[5])^ and $DFDFDFDF<> // fast case-insensitive compare
  29561. ord('R')+ord('E')shl 8+ord('C')shl 16+ord('O')shl 24) or
  29562. (PWord(@DelphiName^[9])^ and $DFDF<>ord('R')+ord('D')shl 8) then
  29563. TrimLeft := 4 else
  29564. TrimLeft := 10;
  29565. ord('T')+ord('S')shl 8+ord('Y')shl 16+ord('N')shl 24:
  29566. TrimLeft := 4;
  29567. end;
  29568. if (Trimleft=0) and (DelphiName^[1]='T') then
  29569. Trimleft := 1;
  29570. SetString(result,PAnsiChar(@DelphiName^[TrimLeft+1]),ord(DelphiName^[0])-TrimLeft);
  29571. end;
  29572. function GetCaptionFromClass(C: TClass): string;
  29573. var tmp: RawUTF8;
  29574. P: PUTF8Char;
  29575. begin
  29576. if C=nil then
  29577. result := '' else begin
  29578. tmp := RawUTF8(C.ClassName);
  29579. P := pointer(tmp);
  29580. if IdemPChar(P,'TSQL') or IdemPChar(P,'TSYN') then
  29581. inc(P,4) else
  29582. if P^='T' then
  29583. inc(P);
  29584. GetCaptionFromPCharLen(P,result);
  29585. end;
  29586. end;
  29587. function GetCaptionFromEnum(aTypeInfo: pointer; aIndex: integer): string;
  29588. var PS: PUTF8Char;
  29589. tmp: array[byte] of AnsiChar;
  29590. L: integer;
  29591. begin
  29592. PS := pointer(GetEnumName(aTypeInfo,aIndex));
  29593. L := ord(PS^);
  29594. inc(PS);
  29595. while (L>0) and (PS^ in ['a'..'z']) do begin inc(PS); dec(L); end;
  29596. tmp[L] := #0; // GetCaptionFromPCharLen expect
  29597. MoveFast(PS^,tmp,L);
  29598. GetCaptionFromPCharLen(tmp,result);
  29599. end;
  29600. function CharSetToCodePage(CharSet: integer): cardinal;
  29601. begin
  29602. case CharSet of
  29603. SHIFTJIS_CHARSET: result := 932;
  29604. HANGEUL_CHARSET: result := 949;
  29605. GB2312_CHARSET: result := 936;
  29606. HEBREW_CHARSET: result := 1255;
  29607. ARABIC_CHARSET: result := 1256;
  29608. GREEK_CHARSET: result := 1253;
  29609. TURKISH_CHARSET: result := 1254;
  29610. VIETNAMESE_CHARSET: result := 1258;
  29611. THAI_CHARSET: result := 874;
  29612. EASTEUROPE_CHARSET: result := 1250;
  29613. RUSSIAN_CHARSET: result := 1251;
  29614. BALTIC_CHARSET: result := 1257;
  29615. else result := CODEPAGE_US; // default is ANSI_CHARSET = iso-8859-1 = windows-1252
  29616. end;
  29617. end;
  29618. function CodePageToCharSet(CodePage: Cardinal): Integer;
  29619. begin
  29620. case CodePage of
  29621. 932: result := SHIFTJIS_CHARSET;
  29622. 949: result := HANGEUL_CHARSET;
  29623. 936: result := GB2312_CHARSET;
  29624. 1255: result := HEBREW_CHARSET;
  29625. 1256: result := ARABIC_CHARSET;
  29626. 1253: result := GREEK_CHARSET;
  29627. 1254: result := TURKISH_CHARSET;
  29628. 1258: result := VIETNAMESE_CHARSET;
  29629. 874: result := THAI_CHARSET;
  29630. 1250: result := EASTEUROPE_CHARSET;
  29631. 1251: result := RUSSIAN_CHARSET;
  29632. 1257: result := BALTIC_CHARSET;
  29633. else result := ANSI_CHARSET; // default is iso-8859-1 = windows-1252
  29634. end;
  29635. end;
  29636. function GetMimeContentTypeFromBuffer(Content: Pointer; Len: integer;
  29637. const DefaultContentType: RawUTF8): RawUTF8;
  29638. begin
  29639. result := DefaultContentType;
  29640. if (Content<>nil) and (Len>4) then
  29641. case PCardinal(Content)^ of
  29642. $04034B50: result := 'application/zip'; // 50 4B 03 04
  29643. $46445025: result := 'application/pdf'; // 25 50 44 46 2D 31 2E
  29644. $21726152: result := 'application/x-rar-compressed'; // 52 61 72 21 1A 07 00
  29645. $AFBC7A37: result := 'application/x-7z-compressed'; // 37 7A BC AF 27 1C
  29646. $75B22630: result := 'audio/x-ms-wma'; // 30 26 B2 75 8E 66
  29647. $9AC6CDD7: result := 'video/x-ms-wmv'; // D7 CD C6 9A 00 00
  29648. $474E5089: result := 'image/png'; // 89 50 4E 47 0D 0A 1A 0A
  29649. $38464947: result := 'image/gif'; // 47 49 46 38
  29650. $46464F77: result := 'application/font-woff'; // wOFF in BigEndian
  29651. $46464952: if Len>16 then // RIFF
  29652. case PCardinalArray(Content)^[2] of
  29653. $50424557: result := 'image/webp';
  29654. $20495641: if PCardinalArray(Content)^[3]=$5453494C then
  29655. result := 'video/x-msvideo'; // Windows Audio Video Interleave file
  29656. end;
  29657. $002A4949, $2A004D4D, $2B004D4D:
  29658. result := 'image/tiff'; // 49 49 2A 00 or 4D 4D 00 2A or 4D 4D 00 2B
  29659. $E011CFD0: // Microsoft Office applications D0 CF 11 E0=DOCFILE
  29660. if Len>600 then
  29661. case PWordArray(Content)^[256] of // at offset 512
  29662. $A5EC: result := 'application/msword'; // EC A5 C1 00
  29663. $FFFD: // FD FF FF
  29664. case PByteArray(Content)^[516] of
  29665. $0E,$1C,$43: result := 'application/vnd.ms-powerpoint';
  29666. $10,$1F,$20,$22,$23,$28,$29: result := 'application/vnd.ms-excel';
  29667. end;
  29668. end;
  29669. $5367674F:
  29670. if Len>14 then // OggS
  29671. if (PCardinalArray(Content)^[1]=$00000200) and
  29672. (PCardinalArray(Content)^[2]=$00000000) and
  29673. (PWordArray(Content)^[6]=$0000) then
  29674. result := 'video/ogg';
  29675. $1C000000:
  29676. if Len>12 then
  29677. if PCardinalArray(Content)^[1]=$70797466 then // ftyp
  29678. case PCardinalArray(Content)^[2] of
  29679. $6D6F7369, // isom: ISO Base Media file (MPEG-4) v1
  29680. $3234706D: // mp42: MPEG-4 video/QuickTime file
  29681. result := 'video/mp4';
  29682. $35706733: // 3gp5: MPEG-4 video files
  29683. result := 'video/3gpp';
  29684. end;
  29685. else
  29686. case PCardinal(Content)^ and $00ffffff of
  29687. $685A42: result := 'application/bzip2'; // 42 5A 68
  29688. $088B1F: result := 'application/gzip'; // 1F 8B 08
  29689. $492049: result := 'image/tiff'; // 49 20 49
  29690. $FFD8FF: result := 'image/jpeg'; // FF D8 FF DB/E0/E1/E2/E3/E8
  29691. else
  29692. case PWord(Content)^ of
  29693. $4D42: result := 'image/bmp'; // 42 4D
  29694. end;
  29695. end;
  29696. end;
  29697. end;
  29698. function GetMimeContentType(Content: Pointer; Len: integer;
  29699. const FileName: TFileName=''): RawUTF8;
  29700. begin // see http://www.garykessler.net/library/file_sigs.html for magic numbers
  29701. if Content<>nil then
  29702. result := GetMimeContentTypeFromBuffer(Content,Len,'') else
  29703. result := '';
  29704. if (result='') and (FileName<>'') then begin
  29705. result := LowerCase(StringToAnsi7(ExtractFileExt(FileName)));
  29706. case PosEx(copy(result,2,4),
  29707. 'png,gif,tiff,jpg,jpeg,bmp,doc,htm,html,css,js,ico,wof,txt,svg,'+
  29708. // 1 5 9 14 18 23 27 31 35 40 44 47 51 55 59
  29709. 'atom,rdf,rss,webp,appc,mani,docx,xml,json,woff,ogg,ogv,mp4,m2v,m2p,mp3,h264') of
  29710. // 63 68 72 76 81 86 91 96 100 105 110 114 118 122 126 130 134
  29711. 1: result := 'image/png';
  29712. 5: result := 'image/gif';
  29713. 9: result := 'image/tiff';
  29714. 14,18: result := 'image/jpeg';
  29715. 23: result := 'image/bmp';
  29716. 27,91: result := 'application/msword';
  29717. 31,35: result := HTML_CONTENT_TYPE;
  29718. 40: result := 'text/css';
  29719. 44: result := 'application/javascript';
  29720. // text/javascript and application/x-javascript are obsolete (RFC 4329)
  29721. 47: result := 'image/x-icon';
  29722. 51,105: result := 'application/font-woff';
  29723. 55: result := TEXT_CONTENT_TYPE;
  29724. 59: result := 'image/svg+xml';
  29725. 63,68,72,96: result := XML_CONTENT_TYPE;
  29726. 76: result := 'image/webp';
  29727. 81,86: result := 'text/cache-manifest';
  29728. 100: result := JSON_CONTENT_TYPE_VAR;
  29729. 110,114: result := 'video/ogg'; // RFC 5334
  29730. 118: result := 'video/mp4'; // RFC 4337 6381
  29731. 122,126: result := 'video/mp2';
  29732. 130: result := 'audio/mpeg'; // RFC 3003
  29733. 134: result := 'video/H264'; // RFC 6184
  29734. else
  29735. if result<>'' then
  29736. result := 'application/'+copy(result,2,10);
  29737. end;
  29738. end;
  29739. if result='' then
  29740. result := BINARY_CONTENT_TYPE;
  29741. end;
  29742. function GetMimeContentTypeHeader(const Content: RawByteString;
  29743. const FileName: TFileName): RawUTF8;
  29744. begin
  29745. result := HEADER_CONTENT_TYPE+
  29746. GetMimeContentType(Pointer(Content),length(Content),FileName);
  29747. end;
  29748. function IsContentCompressed(Content: Pointer; Len: integer): boolean;
  29749. begin
  29750. if (Content<>nil) and (Len>4) then
  29751. case PCardinal(Content)^ of
  29752. $04034B50, // 'application/zip' = 50 4B 03 04
  29753. $21726152, // 'application/x-rar-compressed' = 52 61 72 21 1A 07 00
  29754. $AFBC7A37, // 'application/x-7z-compressed' = 37 7A BC AF 27 1C
  29755. $75B22630, // 'audio/x-ms-wma' = 30 26 B2 75 8E 66
  29756. $9AC6CDD7, // 'video/x-ms-wmv' = D7 CD C6 9A 00 00
  29757. $474E5089, // 'image/png' = 89 50 4E 47 0D 0A 1A 0A
  29758. $38464947, // 'image/gif' = 47 49 46 38
  29759. $46464F77, // 'application/font-woff' = wOFF in BigEndian
  29760. $002A4949, $2A004D4D, $2B004D4D: // 'image/tiff'
  29761. result := true;
  29762. $46464952: if Len>16 then // RIFF
  29763. case PCardinalArray(Content)^[2] of
  29764. $50424557: // 'image/webp'
  29765. result := true;
  29766. else result := False;
  29767. end else
  29768. result := false;
  29769. else
  29770. case PCardinal(Content)^ and $00ffffff of
  29771. $685A42, // 'application/bzip2' = 42 5A 68
  29772. $088B1F, // 'application/gzip' = 1F 8B 08
  29773. $492049, // 'image/tiff' = 49 20 49
  29774. $FFD8FF: // 'image/jpeg' = FF D8 FF DB/E0/E1/E2/E3/E8
  29775. result := true;
  29776. else result := false;
  29777. end;
  29778. end else
  29779. result := false;
  29780. end;
  29781. function IsHTMLContentTypeTextual(Headers: PUTF8Char): Boolean;
  29782. begin
  29783. result := ExistsIniNameValue(Headers,HEADER_CONTENT_TYPE_UPPER,
  29784. [JSON_CONTENT_TYPE_UPPER,'TEXT/','APPLICATION/XML','APPLICATION/JAVASCRIPT',
  29785. 'APPLICATION/X-JAVASCRIPT','APPLICATION/JSON','IMAGE/SVG+XML']);
  29786. end;
  29787. function MultiPartFormDataDecode(const MimeType,Body: RawUTF8;
  29788. var MultiPart: TMultiPartDynArray): boolean;
  29789. var boundary,endBoundary: RawUTF8;
  29790. i,j: integer;
  29791. P: PUTF8Char;
  29792. part: TMultiPart;
  29793. begin
  29794. result := false;
  29795. i := PosEx('boundary=',MimeType);
  29796. if i=0 then
  29797. exit;
  29798. boundary := trim(copy(MimeType,i+9,200));
  29799. if (boundary<>'') and (boundary[1]='"') then
  29800. boundary := copy(boundary,2,length(boundary)-2); // "boundary" -> boundary
  29801. boundary := '--'+boundary;
  29802. endBoundary := boundary+'--'+#13#10;
  29803. boundary := boundary+#13#10;
  29804. i := PosEx(boundary,Body);
  29805. if i<>0 then
  29806. repeat
  29807. inc(i,length(boundary));
  29808. if i=length(body) then
  29809. exit; // reached the end
  29810. P := PUTF8Char(Pointer(Body))+i-1;
  29811. Finalize(part);
  29812. repeat
  29813. if IdemPChar(P,'CONTENT-DISPOSITION: ') then begin
  29814. inc(P,21);
  29815. if IdemPCharAndGetNextItem(P,'FORM-DATA; NAME="',part.Name,'"') then
  29816. IdemPCharAndGetNextItem(P,'; FILENAME="',part.FileName,'"') else
  29817. IdemPCharAndGetNextItem(P,'FILE; FILENAME="',part.FileName,'"')
  29818. end else
  29819. if not IdemPCharAndGetNextItem(P,'CONTENT-TYPE: ',part.ContentType) then
  29820. IdemPCharAndGetNextItem(P,'CONTENT-TRANSFER-ENCODING: ',part.Encoding);
  29821. GetNextLineBegin(P,P);
  29822. if P=nil then
  29823. exit;
  29824. until PWord(P)^=13+10 shl 8;
  29825. i := P-PUTF8Char(Pointer(Body))+3; // i = just after header
  29826. j := PosEx(boundary,Body,i);
  29827. if j=0 then begin
  29828. j := PosEx(endboundary,Body,i); // try last boundary
  29829. if j=0 then
  29830. exit;
  29831. end;
  29832. part.Content := copy(Body,i,j-i-2); // -2 to ignore latest #13#10
  29833. if (part.ContentType='') or (PosEx('-8',part.ContentType)>0) then begin
  29834. part.ContentType := TEXT_CONTENT_TYPE;
  29835. {$ifdef HASCODEPAGE}
  29836. SetCodePage(part.Content,CP_UTF8,false); // ensure raw field value is UTF-8
  29837. {$endif}
  29838. end else
  29839. if IdemPropNameU(part.Encoding,'base64') then
  29840. part.Content := Base64ToBin(part.Content);
  29841. // note: "quoted-printable" not yet handled here
  29842. SetLength(MultiPart,length(MultiPart)+1);
  29843. MultiPart[high(MultiPart)] := part;
  29844. result := true;
  29845. i := j;
  29846. until false;
  29847. end;
  29848. function MultiPartFormDataEncode(const MultiPart: TMultiPartDynArray;
  29849. var MultiPartContentType, MultiPartContent: RawUTF8): boolean;
  29850. var len, boundcount, filescount, i: integer;
  29851. boundaries: array of RawUTF8;
  29852. bound: RawUTF8;
  29853. W: TTextWriter;
  29854. procedure NewBound;
  29855. var random: array[1..3] of cardinal;
  29856. begin
  29857. FillRandom(@random,3);
  29858. bound := BinToBase64(@random,sizeof(Random));
  29859. SetLength(boundaries,boundcount+1);
  29860. boundaries[boundcount] := bound;
  29861. inc(boundcount);
  29862. end;
  29863. begin
  29864. result := false;
  29865. len := length(MultiPart);
  29866. if len=0 then
  29867. exit;
  29868. boundcount := 0;
  29869. filescount := 0;
  29870. W := TTextWriter.CreateOwnedStream;
  29871. try
  29872. // header multipart
  29873. NewBound;
  29874. MultiPartContentType := 'Content-Type: multipart/form-data; boundary='+bound;
  29875. for i := 0 to len-1 do
  29876. with MultiPart[i] do begin
  29877. if FileName='' then
  29878. W.Add('--%'#13'Content-Disposition: form-data; name="%"'#13+
  29879. 'Content-Type: %'#13#13'%'#13'--%'#13,
  29880. [bound,Name,ContentType,Content,bound]) else begin
  29881. // if this is the first file, create the header for files
  29882. if filescount=0 then begin
  29883. if i>0 then
  29884. NewBound;
  29885. W.Add('Content-Disposition: form-data; name="files"'#13+
  29886. 'Content-Type: multipart/mixed; boundary=%'#13#13,[bound]);
  29887. end;
  29888. inc(filescount);
  29889. W.Add('--%'#13'Content-Disposition: file; filename="%"'#13+
  29890. 'Content-Type: %'#13,[bound,FileName,ContentType]);
  29891. if Encoding<>'' then
  29892. W.Add('Content-Transfer-Encoding: %'#13,[Encoding]);
  29893. W.AddCR;
  29894. W.AddString(MultiPart[i].Content);
  29895. W.Add(#13'--%'#13,[bound]);
  29896. end;
  29897. end;
  29898. // footer multipart
  29899. for i := boundcount-1 downto 0 do
  29900. W.Add('--%--'#13, [boundaries[i]]);
  29901. W.SetText(MultiPartContent);
  29902. result := True;
  29903. finally
  29904. W.Free;
  29905. end;
  29906. end;
  29907. function MultiPartFormDataAddFile(const FileName: TFileName;
  29908. var MultiPart: TMultiPartDynArray; const Name: RawUTF8): boolean;
  29909. var part: TMultiPart;
  29910. newlen: integer;
  29911. content: RawByteString;
  29912. begin
  29913. result := false;
  29914. content := StringFromFile(FileName);
  29915. if content='' then
  29916. exit;
  29917. newlen := length(MultiPart)+1;
  29918. if Name='' then
  29919. FormatUTF8('File%',[newlen],part.Name) else
  29920. part.Name := Name;
  29921. part.FileName := StringToUTF8(ExtractFileName(FileName));
  29922. part.ContentType := GetMimeContentType(pointer(content),length(content),FileName);
  29923. part.Encoding := 'base64';
  29924. part.Content := BinToBase64(content);
  29925. SetLength(MultiPart,newlen);
  29926. MultiPart[newlen-1] := part;
  29927. result := true;
  29928. end;
  29929. function MultiPartFormDataAddField(const FieldName, FieldValue: RawUTF8;
  29930. var MultiPart: TMultiPartDynArray): boolean;
  29931. var
  29932. part: TMultiPart;
  29933. newlen: integer;
  29934. begin
  29935. result := false;
  29936. if FieldName='' then
  29937. exit;
  29938. newlen := length(MultiPart)+1;
  29939. part.Name := FieldName;
  29940. part.ContentType := GetMimeContentTypeFromBuffer(
  29941. pointer(FieldValue),length(FieldValue),'text/plain');
  29942. part.Content := FieldValue;
  29943. SetLength(MultiPart,newlen);
  29944. MultiPart[newlen-1] := part;
  29945. result := true;
  29946. end;
  29947. function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt;
  29948. var L,i,cmp: PtrInt;
  29949. begin // fast binary search
  29950. if R<0 then
  29951. result := 0 else begin
  29952. L := 0;
  29953. result := -1; // return -1 if found
  29954. repeat
  29955. i := (L + R) shr 1;
  29956. cmp := StrComp(P^[i],Value);
  29957. if cmp=0 then
  29958. exit;
  29959. if cmp<0 then
  29960. L := i + 1 else
  29961. R := i - 1;
  29962. until (L > R);
  29963. while (i>=0) and (StrComp(P^[i],Value)>=0) do dec(i);
  29964. result := i+1; // return the index where to insert
  29965. end;
  29966. end;
  29967. function FastLocatePUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char;
  29968. Compare: TUTF8Compare): PtrInt; overload;
  29969. var L,i,cmp: PtrInt;
  29970. begin // fast binary search
  29971. if not Assigned(Compare) or (R<0) then
  29972. result := 0 else begin
  29973. L := 0;
  29974. result := -1; // return -1 if found
  29975. repeat
  29976. i := (L + R) shr 1;
  29977. cmp := Compare(P^[i],Value);
  29978. if cmp=0 then
  29979. exit;
  29980. if cmp<0 then
  29981. L := i + 1 else
  29982. R := i - 1;
  29983. until (L > R);
  29984. while (i>=0) and (Compare(P^[i],Value)>=0) do dec(i);
  29985. result := i+1; // return the index where to insert
  29986. end;
  29987. end;
  29988. function FastFindPUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char;
  29989. Compare: TUTF8Compare): PtrInt; overload;
  29990. var L, cmp: PtrInt;
  29991. begin // fast binary search
  29992. L := 0;
  29993. if Assigned(Compare) and (0<=R) then
  29994. repeat
  29995. result := (L + R) shr 1;
  29996. cmp := Compare(P^[result],Value);
  29997. if cmp=0 then
  29998. exit;
  29999. if cmp<0 then
  30000. L := result + 1 else
  30001. R := result - 1;
  30002. until (L > R);
  30003. result := -1;
  30004. end;
  30005. function FastFindPUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char): PtrInt;
  30006. var L, cmp: PtrInt;
  30007. begin// fast binary search
  30008. L := 0;
  30009. if 0<=R then
  30010. repeat
  30011. result := (L + R) shr 1;
  30012. cmp := StrComp(P^[result],Value);
  30013. if cmp=0 then
  30014. exit;
  30015. if cmp<0 then
  30016. L := result + 1 else
  30017. R := result - 1;
  30018. until (L > R);
  30019. result := -1;
  30020. end;
  30021. function FastFindIndexedPUTF8Char(P: PPUTF8CharArray; R: PtrInt;
  30022. var SortedIndexes: TCardinalDynArray; Value: PUTF8Char;
  30023. ItemComp: TUTF8Compare): PtrInt;
  30024. var L, cmp: PtrInt;
  30025. begin // fast binary search
  30026. L := 0;
  30027. if 0<=R then
  30028. repeat
  30029. result := (L + R) shr 1;
  30030. cmp := ItemComp(P^[SortedIndexes[result]],Value);
  30031. if cmp=0 then begin
  30032. result := SortedIndexes[result];
  30033. exit;
  30034. end;
  30035. if cmp<0 then
  30036. L := result + 1 else
  30037. R := result - 1;
  30038. until (L > R);
  30039. result := -1;
  30040. end;
  30041. function AddSortedRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  30042. const Value: RawUTF8; CoValues: PIntegerDynArray=nil; ForcedIndex: PtrInt=-1;
  30043. Compare: TUTF8Compare=nil): PtrInt;
  30044. var n: PtrInt;
  30045. begin
  30046. if ForcedIndex>=0 then
  30047. result := ForcedIndex else begin
  30048. if Assigned(Compare) then
  30049. result := FastLocatePUTF8CharSorted(pointer(Values),ValuesCount-1,pointer(Value),Compare) else
  30050. result := FastLocatePUTF8CharSorted(pointer(Values),ValuesCount-1,pointer(Value));
  30051. if result<0 then
  30052. exit; // Value exists -> fails
  30053. end;
  30054. n := Length(Values);
  30055. if ValuesCount=n then begin
  30056. inc(n,256+n shr 3);
  30057. SetLength(Values,n);
  30058. if CoValues<>nil then
  30059. SetLength(CoValues^,n);
  30060. end;
  30061. n := ValuesCount;
  30062. if result<n then begin
  30063. n := (n-result)*sizeof(pointer);
  30064. MoveFast(Pointer(Values[result]),Pointer(Values[result+1]),n);
  30065. PtrInt(Values[result]) := 0; // avoid GPF
  30066. if CoValues<>nil then begin
  30067. {$ifdef CPU64}n := n shr 1;{$endif} // 64 bit pointer size is twice an integer
  30068. MoveFast(CoValues^[result],CoValues^[result+1],n);
  30069. end;
  30070. end else
  30071. result := n;
  30072. Values[result] := Value;
  30073. inc(ValuesCount);
  30074. end;
  30075. type
  30076. /// used internaly for faster quick sort
  30077. TQuickSortRawUTF8 = {$ifndef UNICODE}object{$else}record{$endif}
  30078. Values: PPointerArray;
  30079. Compare: TUTF8Compare;
  30080. CoValues: PIntegerArray;
  30081. Pivot: pointer;
  30082. procedure Sort(L,R: PtrInt);
  30083. end;
  30084. procedure TQuickSortRawUTF8.Sort(L, R: PtrInt);
  30085. var I, J, P: integer;
  30086. Tmp: Pointer;
  30087. TmpInt: integer;
  30088. begin
  30089. if L<R then
  30090. repeat
  30091. I := L; J := R;
  30092. P := (L + R) shr 1;
  30093. repeat
  30094. pivot := Values^[P];
  30095. while Compare(Values^[I],pivot)<0 do Inc(I);
  30096. while Compare(Values^[J],pivot)>0 do Dec(J);
  30097. if I <= J then begin
  30098. Tmp := Values^[J];
  30099. Values^[J] := Values^[I];
  30100. Values^[I] := Tmp;
  30101. if CoValues<>nil then begin
  30102. TmpInt := CoValues^[J];
  30103. CoValues^[J] := CoValues^[I];
  30104. CoValues^[I] := TmpInt;
  30105. end;
  30106. if P = I then P := J else if P = J then P := I;
  30107. Inc(I); Dec(J);
  30108. end;
  30109. until I > J;
  30110. if L < J then
  30111. Sort(L, J);
  30112. L := I;
  30113. until I >= R;
  30114. end;
  30115. procedure QuickSortRawUTF8(var Values: TRawUTF8DynArray; ValuesCount: integer;
  30116. CoValues: PIntegerDynArray=nil; Compare: TUTF8Compare=nil);
  30117. var QS: TQuickSortRawUTF8;
  30118. begin
  30119. QS.Values := pointer(Values);
  30120. if Assigned(Compare) then
  30121. QS.Compare := Compare else
  30122. QS.Compare := @StrComp;
  30123. if CoValues=nil then
  30124. QS.CoValues := nil else
  30125. QS.CoValues := pointer(CoValues^);
  30126. QS.Sort(0,ValuesCount-1);
  30127. end;
  30128. function DeleteRawUTF8(var Values: TRawUTF8DynArray; var ValuesCount: integer;
  30129. Index: integer; CoValues: PIntegerDynArray=nil): boolean;
  30130. var n: integer;
  30131. begin
  30132. n := ValuesCount;
  30133. if Cardinal(Index)>=Cardinal(n) then
  30134. result := false else begin
  30135. dec(n);
  30136. ValuesCount := n;
  30137. Values[Index] := ''; // avoid GPF
  30138. dec(n,Index);
  30139. if n>0 then begin
  30140. if CoValues<>nil then
  30141. MoveFast(CoValues^[Index+1],CoValues^[Index],n*sizeof(Integer));
  30142. MoveFast(pointer(Values[Index+1]),pointer(Values[Index]),n*sizeof(pointer));
  30143. PtrUInt(Values[ValuesCount]) := 0; // avoid GPF
  30144. end;
  30145. result := true;
  30146. end;
  30147. end;
  30148. function ToText(const aIntelCPUFeatures: TIntelCpuFeatures; const Sep: RawUTF8): RawUTF8;
  30149. var f: TIntelCpuFeature;
  30150. List: PShortString;
  30151. MaxValue: integer;
  30152. begin
  30153. result := '';
  30154. if GetEnumInfo(TypeInfo(TIntelCpuFeature),MaxValue,List) then
  30155. for f := low(f) to high(f) do begin
  30156. if (f in aIntelCPUFeatures) and (List^[3]<>'_') then begin
  30157. if result<>'' then
  30158. result := result+Sep;
  30159. result := result+RawUTF8(copy(List^,3,10));
  30160. end;
  30161. inc(PByte(List),ord(List^[0])+1); // next short string
  30162. end;
  30163. end;
  30164. function SystemInfoJson: RawUTF8;
  30165. begin
  30166. with SystemInfo do
  30167. result := JSONEncode([
  30168. 'host',ExeVersion.Host,'user',ExeVersion.User,'os',OSVersionText,
  30169. 'cpucount',
  30170. {$ifdef MSWINDOWS}
  30171. dwNumberOfProcessors,{$ifndef CPU64}'wow64',IsWow64,{$endif}
  30172. {$else MSWINDOWS}
  30173. nprocs,
  30174. {$endif MSWINDOWS}
  30175. {$ifndef PUREPASCAL}{$ifdef CPUINTEL}
  30176. 'cpufeatures', LowerCase(ToText(CpuFeatures, ' ')),
  30177. {$endif}{$endif}
  30178. 'freemem',TSynMonitorMemory.FreeAsText,'freedisk',TSynMonitorDisk.FreeAsText]);
  30179. end;
  30180. {$ifdef MSWINDOWS}
  30181. {$ifdef DELPHI6OROLDER}
  30182. function GetFileVersion(const FileName: TFileName): cardinal;
  30183. var Size, Size2: DWord;
  30184. Pt: Pointer;
  30185. Info: ^TVSFixedFileInfo;
  30186. tmp: TFileName;
  30187. begin
  30188. result := cardinal(-1);
  30189. if FileName='' then
  30190. exit;
  30191. // GetFileVersionInfo modifies the filename parameter data while parsing
  30192. // Copy the string const into a local variable to create a writeable copy
  30193. SetString(tmp,PChar(FileName),length(FileName));
  30194. Size := GetFileVersionInfoSize(pointer(tmp), Size2);
  30195. if Size>0 then begin
  30196. GetMem(Pt, Size);
  30197. try
  30198. GetFileVersionInfo(pointer(FileName), 0, Size, Pt);
  30199. if VerQueryValue(Pt, '\', pointer(Info), Size2) then
  30200. result := Info^.dwFileVersionMS;
  30201. finally
  30202. Freemem(Pt);
  30203. end;
  30204. end;
  30205. end;
  30206. {$endif DELPHI6OROLDER}
  30207. function WndProcMethod(Hwnd: HWND; Msg,wParam,lParam: integer): integer; stdcall;
  30208. var obj: TObject;
  30209. dsp: TMessage;
  30210. begin
  30211. {$ifdef CPU64}
  30212. obj := pointer(GetWindowLongPtr(HWnd,GWLP_USERDATA));
  30213. {$else}
  30214. obj := pointer(GetWindowLong(HWnd,GWL_USERDATA)); // faster than GetProp()
  30215. {$endif CPU64}
  30216. if not Assigned(obj) then
  30217. result := DefWindowProc(HWnd,Msg,wParam,lParam) else begin
  30218. dsp.msg := Msg;
  30219. dsp.wParam := WParam;
  30220. dsp.lParam := lParam;
  30221. dsp.result := 0;
  30222. obj.Dispatch(dsp);
  30223. result := dsp.result;
  30224. end;
  30225. end;
  30226. function CreateInternalWindow(const aWindowName: string; aObject: TObject): HWND;
  30227. var TempClass: TWndClass;
  30228. begin
  30229. result := 0;
  30230. if GetClassInfo(HInstance, pointer(aWindowName), TempClass) then
  30231. exit; // class name already registered -> fail
  30232. FillcharFast(TempClass,sizeof(TempClass),0);
  30233. TempClass.hInstance := HInstance;
  30234. TempClass.lpfnWndProc := @DefWindowProc;
  30235. TempClass.lpszClassName := pointer(aWindowName);
  30236. Windows.RegisterClass(TempClass);
  30237. result := CreateWindowEx(WS_EX_TOOLWINDOW, pointer(aWindowName),
  30238. '', WS_POPUP {!0}, 0, 0, 0, 0, 0, 0, HInstance, nil);
  30239. if result=0 then
  30240. exit; // impossible to create window -> fail
  30241. {$ifdef CPU64}
  30242. SetWindowLongPtr(result,GWLP_USERDATA,PtrInt(aObject));
  30243. SetWindowLongPtr(result,GWLP_WNDPROC,PtrInt(@WndProcMethod));
  30244. {$else}
  30245. SetWindowLong(result,GWL_USERDATA,PtrInt(aObject)); // faster than SetProp()
  30246. SetWindowLong(result,GWL_WNDPROC,PtrInt(@WndProcMethod));
  30247. {$endif CPU64}
  30248. end;
  30249. function ReleaseInternalWindow(var aWindowName: string; var aWindow: HWND): boolean;
  30250. begin
  30251. if (aWindow<>0) and (aWindowName<>'') then begin
  30252. {$ifdef CPU64}
  30253. SetWindowLongPtr(aWindow,GWLP_WNDPROC,PtrInt(@DefWindowProc));
  30254. {$else}
  30255. SetWindowLong(aWindow,GWL_WNDPROC,PtrInt(@DefWindowProc));
  30256. {$endif CPU64}
  30257. DestroyWindow(aWindow);
  30258. Windows.UnregisterClass(pointer(aWindowName),hInstance);
  30259. aWindow := 0;
  30260. aWindowName := '';
  30261. result := true;
  30262. end else
  30263. result := false;
  30264. end;
  30265. {$else}
  30266. const
  30267. _SC_PAGE_SIZE = $1000;
  30268. {$endif MSWINDOWS}
  30269. { TFileVersion }
  30270. constructor TFileVersion.Create(const aFileName: TFileName;
  30271. aMajor,aMinor,aRelease,aBuild: integer);
  30272. var M,D: word;
  30273. {$ifdef MSWINDOWS}
  30274. Size, Size2: DWord;
  30275. Pt: Pointer;
  30276. Info: ^TVSFixedFileInfo;
  30277. FileTime: TFILETIME;
  30278. SystemTime: TSYSTEMTIME;
  30279. tmp: TFileName;
  30280. {$endif}
  30281. begin
  30282. fFileName := aFileName;
  30283. {$ifdef MSWINDOWS}
  30284. if aFileName<>'' then begin
  30285. // GetFileVersionInfo modifies the filename parameter data while parsing.
  30286. // Copy the string const into a local variable to create a writeable copy.
  30287. SetString(tmp,PChar(aFileName),length(aFileName));
  30288. Size := GetFileVersionInfoSize(pointer(tmp), Size2);
  30289. if Size>0 then begin
  30290. GetMem(Pt, Size);
  30291. try
  30292. GetFileVersionInfo(pointer(aFileName), 0, Size, Pt);
  30293. VerQueryValue(Pt, '\', pointer(Info), Size2);
  30294. with Info^ do begin
  30295. if Version32=0 then begin
  30296. aMajor := dwFileVersionMS shr 16;
  30297. aMinor := word(dwFileVersionMS);
  30298. aRelease := dwFileVersionLS shr 16;
  30299. end;
  30300. aBuild := word(dwFileVersionLS);
  30301. BuildYear := 2010;
  30302. if (dwFileDateLS<>0) and (dwFileDateMS<>0) then begin
  30303. FileTime.dwLowDateTime:= dwFileDateLS; // built date from version info
  30304. FileTime.dwHighDateTime:= dwFileDateMS;
  30305. FileTimeToSystemTime(FileTime, SystemTime);
  30306. fBuildDateTime := EncodeDate(
  30307. SystemTime.wYear,SystemTime.wMonth,SystemTime.wDay);
  30308. end;
  30309. end;
  30310. finally
  30311. Freemem(Pt);
  30312. end;
  30313. end;
  30314. end;
  30315. {$endif}
  30316. SetVersion(aMajor,aMinor,aRelease,aBuild);
  30317. if fBuildDateTime=0 then // get build date from file age
  30318. fBuildDateTime := FileAgeToDateTime(aFileName);
  30319. if fBuildDateTime<>0 then
  30320. DecodeDate(fBuildDateTime,BuildYear,M,D);
  30321. end;
  30322. function TFileVersion.Version32: integer;
  30323. begin
  30324. result := Major shl 16+Minor shl 8+Release;
  30325. end;
  30326. procedure TFileVersion.SetVersion(aMajor,aMinor,aRelease,aBuild: integer);
  30327. begin
  30328. Major := aMajor;
  30329. Minor := aMinor;
  30330. Release := aRelease;
  30331. Build := aBuild;
  30332. Main := IntToString(Major)+'.'+IntToString(Minor);
  30333. fDetailed := Main+ '.'+IntToString(Release)+'.'+IntToString(Build);
  30334. end;
  30335. function TFileVersion.BuildDateTimeString: string;
  30336. begin
  30337. DateTimeToIso8601StringVar(fBuildDateTime,' ',result);
  30338. end;
  30339. function TFileVersion.VersionInfo: RawUTF8;
  30340. begin
  30341. FormatUTF8('% % %',[ExtractFileName(fFileName),fDetailed,BuildDateTimeString],result);
  30342. end;
  30343. class function TFileVersion.GetVersionInfo(const aFileName: TFileName): RawUTF8;
  30344. begin
  30345. with Create(aFileName,0,0,0,0) do
  30346. try
  30347. result := VersionInfo;
  30348. finally
  30349. Free;
  30350. end;
  30351. end;
  30352. procedure SetExecutableVersion(const aVersionText: RawUTF8);
  30353. var P: PUTF8Char;
  30354. i: integer;
  30355. ver: array[0..3] of integer;
  30356. begin
  30357. P := pointer(aVersionText);
  30358. for i := 0 to 3 do
  30359. ver[i] := GetNextItemCardinal(P,'.');
  30360. SetExecutableVersion(ver[0],ver[1],ver[2],ver[3]);
  30361. end;
  30362. procedure SetExecutableVersion(aMajor,aMinor,aRelease,aBuild: integer);
  30363. var i: integer;
  30364. {$ifdef MSWINDOWS}
  30365. Tmp: array[byte] of WideChar;
  30366. TmpSize: cardinal;
  30367. {$endif}
  30368. begin
  30369. with ExeVersion do begin
  30370. if Version=nil then begin
  30371. {$ifdef MSWINDOWS}
  30372. ProgramFileName := paramstr(0);
  30373. {$else}
  30374. ProgramFileName := GetModuleName(hInstance);
  30375. if ProgramFileName='' then
  30376. ProgramFileName := ExpandFileName(paramstr(0));
  30377. {$endif}
  30378. ProgramFilePath := ExtractFilePath(ProgramFileName);
  30379. if IsLibrary then
  30380. InstanceFileName := GetModuleName(HInstance) else
  30381. InstanceFileName := ProgramFileName;
  30382. ProgramName := StringToUTF8(ExtractFileName(ProgramFileName));
  30383. i := length(ProgramName);
  30384. while i>0 do
  30385. if ProgramName[i]='.' then begin
  30386. SetLength(ProgramName,i-1);
  30387. break;
  30388. end else
  30389. dec(i);
  30390. {$ifdef MSWINDOWS}
  30391. TmpSize := sizeof(Tmp);
  30392. GetComputerNameW(Tmp,TmpSize);
  30393. RawUnicodeToUtf8(@Tmp,StrLenW(Tmp),Host);
  30394. TmpSize := sizeof(Tmp);
  30395. GetUserNameW(Tmp,TmpSize);
  30396. RawUnicodeToUtf8(@Tmp,StrLenW(Tmp),User);
  30397. {$else}
  30398. Host := GetHostName;
  30399. {$ifdef KYLIX3}
  30400. User := LibC.getpwuid(LibC.getuid)^.pw_name;
  30401. {$endif}
  30402. {$endif}
  30403. if Host='' then
  30404. Host := 'unknown';
  30405. if User='' then
  30406. User := 'unknown';
  30407. GarbageCollectorFreeAndNil(Version,
  30408. TFileVersion.Create(InstanceFileName,aMajor,aMinor,aRelease,aBuild));
  30409. end else
  30410. Version.SetVersion(aMajor,aMinor,aRelease,aBuild);
  30411. FormatUTF8('% % (%)',[ProgramFileName,Version.Detailed,
  30412. DateTimeToIso8601(Version.BuildDateTime,True,' ')],ProgramFullSpec);
  30413. end;
  30414. end;
  30415. {$ifdef DARWIN}
  30416. function mprotect(Addr: Pointer; Len: size_t; Prot: Integer): Integer;
  30417. cdecl external 'libc.dylib' name 'mprotect';
  30418. {$define USEMPROTECT}
  30419. {$endif}
  30420. {$ifdef KYLIX3}
  30421. {$define USEMPROTECT}
  30422. {$endif}
  30423. procedure PatchCode(Old,New: pointer; Size: integer; Backup: pointer=nil;
  30424. LeaveUnprotected: boolean=false);
  30425. {$ifdef MSWINDOWS}
  30426. var RestoreProtection, Ignore: DWORD;
  30427. i: integer;
  30428. begin
  30429. if VirtualProtect(Old, Size, PAGE_EXECUTE_READWRITE, RestoreProtection) then
  30430. begin
  30431. if Backup<>nil then
  30432. for i := 0 to Size-1 do // do not use Move() here
  30433. PByteArray(Backup)^[i] := PByteArray(Old)^[i];
  30434. for i := 0 to Size-1 do // do not use Move() here
  30435. PByteArray(Old)^[i] := PByteArray(New)^[i];
  30436. if not LeaveUnprotected then
  30437. VirtualProtect(Old, Size, RestoreProtection, Ignore);
  30438. FlushInstructionCache(GetCurrentProcess, Old, Size);
  30439. if not CompareMem(Old,New,Size) then
  30440. raise ESynException.Create('PatchCode?');
  30441. end;
  30442. end;
  30443. {$else}
  30444. var PageSize, AlignedAddr: PtrInt;
  30445. i: integer;
  30446. begin
  30447. if Backup<>nil then
  30448. for i := 0 to Size-1 do // do not use Move() here
  30449. PByteArray(Backup)^[i] := PByteArray(Old)^[i];
  30450. PageSize := _SC_PAGE_SIZE;
  30451. AlignedAddr := PtrInt(Old) and not (PageSize - 1);
  30452. while PtrInt(Old) + Size >= AlignedAddr + PageSize do
  30453. Inc(PageSize,_SC_PAGE_SIZE);
  30454. {$ifdef USEMPROTECT}
  30455. if mprotect(Pointer(AlignedAddr),PageSize,PROT_READ or PROT_WRITE or PROT_EXEC)=0 then
  30456. {$else}
  30457. Do_SysCall(syscall_nr_mprotect,PtrUInt(AlignedAddr),PageSize,PROT_READ or PROT_WRITE or PROT_EXEC);
  30458. {$endif}
  30459. try
  30460. for i := 0 to Size-1 do // do not use Move() here
  30461. PByteArray(Old)^[i] := PByteArray(New)^[i];
  30462. except
  30463. end;
  30464. end;
  30465. {$endif}
  30466. procedure PatchCodePtrUInt(Code: PPtrUInt; Value: PtrUInt;
  30467. LeaveUnprotected: boolean=false);
  30468. begin
  30469. PatchCode(Code,@Value,SizeOf(Code^),nil,LeaveUnprotected);
  30470. end;
  30471. {$ifdef CPUINTEL}
  30472. procedure RedirectCode(Func, RedirectFunc: Pointer; Backup: PPatchCode=nil);
  30473. var NewJump: packed record
  30474. Code: byte; // $e9 = jmp {relative}
  30475. Distance: integer; // relative jump is 32 bit even on CPU64
  30476. end;
  30477. begin
  30478. if (Func=nil) or (RedirectFunc=nil) then
  30479. exit; // nothing to redirect to
  30480. assert(sizeof(TPatchCode)=sizeof(NewJump));
  30481. NewJump.Code := $e9;
  30482. NewJump.Distance := PtrInt(RedirectFunc)-PtrInt(Func)-sizeof(NewJump);
  30483. PatchCode(Func,@NewJump,sizeof(NewJump),Backup);
  30484. {$ifndef LVCL}
  30485. assert(pByte(Func)^=$e9);
  30486. {$endif}
  30487. end;
  30488. procedure RedirectCodeRestore(Func: pointer; const Backup: TPatchCode);
  30489. begin
  30490. PatchCode(Func,@Backup,sizeof(TPatchCode));
  30491. end;
  30492. {$endif CPUINTEL}
  30493. {$ifndef LVCL}
  30494. {$ifndef FPC}
  30495. {$ifdef MSWINDOWS}
  30496. { THeapMemoryStream = faster TMemoryStream using FastMM4/SynScaleMM heap,
  30497. not windows.GlobalAlloc() }
  30498. const
  30499. MemoryDelta = $8000; // 32 KB granularity (must be a power of 2)
  30500. function THeapMemoryStream.Realloc(var NewCapacity: longint): Pointer;
  30501. // allocates memory from Delphi heap (FastMM4/SynScaleMM) and not windows.Global*()
  30502. // and uses bigger growing size -> a lot faster
  30503. var i: PtrInt;
  30504. begin
  30505. if NewCapacity>0 then begin
  30506. i := Seek(0,soFromCurrent); // no direct access to fSize -> use Seek() trick
  30507. if NewCapacity=Seek(0,soFromEnd) then begin // avoid ReallocMem() if just truncate
  30508. result := Memory;
  30509. Seek(i,soBeginning);
  30510. exit;
  30511. end;
  30512. NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);
  30513. Seek(i,soBeginning);
  30514. end;
  30515. Result := Memory;
  30516. if NewCapacity <> Capacity then begin
  30517. if NewCapacity = 0 then begin
  30518. FreeMem(Memory);
  30519. Result := nil;
  30520. end else begin
  30521. if Capacity = 0 then
  30522. GetMem(Result, NewCapacity) else
  30523. if NewCapacity > Capacity then // only realloc if necessary (grow up)
  30524. ReallocMem(Result, NewCapacity) else
  30525. NewCapacity := Capacity; // same capacity as before
  30526. if Result = nil then
  30527. raise EStreamError.Create('THeapMemoryStream'); // memory allocation bug
  30528. end;
  30529. end;
  30530. end;
  30531. {$endif MSWINDOWS}
  30532. {$endif FPC}
  30533. {$endif LVCL}
  30534. { TSortedWordArray }
  30535. function FastLocateWordSorted(P: PWordArray; R: integer; Value: word): PtrInt;
  30536. var L,cmp: PtrInt;
  30537. begin
  30538. if R<0 then
  30539. result := 0 else begin
  30540. L := 0;
  30541. repeat
  30542. result := (L + R) shr 1;
  30543. cmp := P^[result]-Value;
  30544. if cmp=0 then begin
  30545. result := -result-1; // return -(foundindex+1) if already exists
  30546. exit;
  30547. end;
  30548. if cmp<0 then
  30549. L := result + 1 else
  30550. R := result - 1;
  30551. until (L > R);
  30552. while (result>=0) and (P^[result]>=Value) do dec(result);
  30553. result := result+1; // return the index where to insert
  30554. end;
  30555. end;
  30556. function TSortedWordArray.Add(aValue: Word): PtrInt;
  30557. begin
  30558. result := FastLocateWordSorted(pointer(Values),Count-1,aValue);
  30559. if result<0 then // aValue already exists in Values[] -> fails
  30560. exit;
  30561. if Count=length(Values) then
  30562. SetLength(Values,Count+100);
  30563. if result<Count then
  30564. MoveFast(Values[result],Values[result+1],(Count-result)*2) else
  30565. result := Count;
  30566. Values[result] := aValue;
  30567. inc(Count);
  30568. end;
  30569. function TSortedWordArray.IndexOf(aValue: Word): PtrInt;
  30570. var L,R: PtrInt;
  30571. cmp: integer;
  30572. begin
  30573. L := 0;
  30574. R := Count-1;
  30575. if 0<=R then
  30576. repeat
  30577. result := (L + R) shr 1;
  30578. cmp := Values[result]-aValue;
  30579. if cmp=0 then
  30580. exit else
  30581. if cmp<0 then
  30582. L := result + 1 else
  30583. R := result - 1;
  30584. until (L > R);
  30585. result := -1;
  30586. end;
  30587. {$ifdef PUREPASCAL}
  30588. function ToVarInt32(Value: PtrInt; Dest: PByte): PByte;
  30589. begin // 0=0,1=1,2=-1,3=2,4=-2...
  30590. if Value<0 then
  30591. // -1->2, -2->4..
  30592. Value := (-Value) shl 1 else
  30593. if Value>0 then
  30594. // 1->1, 2->3..
  30595. Value := (Value shl 1)-1;
  30596. // 0->0
  30597. result := ToVarUInt32(Value,Dest);
  30598. end;
  30599. {$else}
  30600. function ToVarInt32(Value: PtrInt; Dest: PByte): PByte;
  30601. asm
  30602. test eax,eax
  30603. jnl @pos
  30604. neg eax
  30605. add eax,eax
  30606. jmp ToVarUInt32
  30607. @pos: jz @zer
  30608. lea eax,[eax*2-1]
  30609. jmp ToVarUInt32
  30610. @zer: mov [edx],al
  30611. lea eax,[edx+1]
  30612. end;
  30613. {$endif}
  30614. function FromVarInt32(var Source: PByte): integer;
  30615. begin // 0=0,1=1,2=-1,3=2,4=-2...
  30616. result := integer(FromVarUInt32(Source));
  30617. if result and 1<>0 then
  30618. // 1->1, 3->2..
  30619. result := result shr 1+1 else
  30620. // 0->0, 2->-1, 4->-2..
  30621. result := -(result shr 1);
  30622. end;
  30623. function ToVarUInt32Length(Value: PtrUInt): PtrUInt;
  30624. begin
  30625. if Value<=$7f then
  30626. result := 1 else
  30627. if Value<$80 shl 7 then
  30628. result := 2 else
  30629. if Value<$80 shl 14 then
  30630. result := 3 else
  30631. if Value <$80 shl 21 then
  30632. result := 4 else
  30633. result := 5;
  30634. end;
  30635. function ToVarUInt32LengthWithData(Value: PtrUInt): PtrUInt;
  30636. begin
  30637. if Value<=$7f then
  30638. result := Value+1 else
  30639. if Value<$80 shl 7 then
  30640. result := Value+2 else
  30641. if Value<$80 shl 14 then
  30642. result := Value+3 else
  30643. if Value<$80 shl 21 then
  30644. result := Value+4 else
  30645. result := Value+5;
  30646. end;
  30647. {$ifdef PUREPASCAL}
  30648. function ToVarUInt32(Value: PtrUInt; Dest: PByte): PByte;
  30649. begin
  30650. if Value>$7f then
  30651. repeat
  30652. Dest^ := (Value and $7F) or $80;
  30653. Value := Value shr 7;
  30654. inc(Dest);
  30655. until Value<=$7f;
  30656. Dest^ := Value;
  30657. inc(Dest);
  30658. result := Dest;
  30659. end;
  30660. {$else}
  30661. function ToVarUInt32(Value: PtrUInt; Dest: PByte): PByte;
  30662. asm // eax=Value edx=Dest
  30663. cmp eax,$7F
  30664. ja @n
  30665. mov [edx],al
  30666. lea eax,[edx+1]
  30667. ret
  30668. @n: mov ecx,eax
  30669. @s: and cl,$7F // handle two bytes per loop
  30670. shr eax,7
  30671. or cl,$80
  30672. cmp eax,$7f
  30673. mov [edx],cl
  30674. lea edx,[edx+1]
  30675. mov ecx,eax
  30676. jbe @z
  30677. and cl,$7F
  30678. shr eax,7
  30679. or cl,$80
  30680. cmp eax,$7f
  30681. mov [edx],cl
  30682. mov ecx,eax
  30683. lea edx,[edx+1]
  30684. ja @s
  30685. @z: mov [edx],al
  30686. lea eax,[edx+1]
  30687. end;
  30688. {$endif}
  30689. {$ifdef HASINLINE}
  30690. function FromVarUInt32(var Source: PByte): cardinal;
  30691. begin
  30692. result := Source^;
  30693. inc(Source);
  30694. if result>$7f then
  30695. result := (result and $7F) or FromVarUInt32Up128(Source);
  30696. end;
  30697. {$else}
  30698. function FromVarUInt32(var Source: PByte): cardinal;
  30699. var c: PtrUInt;
  30700. begin
  30701. result := Source^;
  30702. inc(Source);
  30703. if result<=$7f then
  30704. exit;
  30705. c := Source^ shl 7;
  30706. inc(Source);
  30707. result := result and $7F or c;
  30708. if c<=$7f shl 7 then
  30709. exit; // Values between 128 and 16256
  30710. c := Source^ shl 14;
  30711. inc(Source);
  30712. result := result and $3FFF or c;
  30713. if c<=$7f shl 14 then
  30714. exit; // Values between 16257 and 2080768
  30715. c := Source^ shl 21;
  30716. inc(Source);
  30717. result := result and $1FFFFF or c;
  30718. if c<=$7f shl 21 then
  30719. exit; // Values between 2080769 and 266338304
  30720. c := Source^ shl 28;
  30721. inc(Source);
  30722. result := result and $FFFFFFF or c;
  30723. end;
  30724. {$endif}
  30725. function FromVarUInt32High(var Source: PByte): cardinal;
  30726. var c: PtrUInt;
  30727. begin
  30728. result := Source^;
  30729. inc(Source);
  30730. c := Source^ shl 7;
  30731. inc(Source);
  30732. result := result and $7F or c;
  30733. if c<=$7f shl 7 then
  30734. exit; // Values between 128 and 16256
  30735. c := Source^ shl 14;
  30736. inc(Source);
  30737. result := result and $3FFF or c;
  30738. if c<=$7f shl 14 then
  30739. exit; // Values between 16257 and 2080768
  30740. c := Source^ shl 21;
  30741. inc(Source);
  30742. result := result and $1FFFFF or c;
  30743. if c<=$7f shl 21 then
  30744. exit; // Values between 2080769 and 266338304
  30745. c := Source^ shl 28;
  30746. inc(Source);
  30747. result := result and $FFFFFFF or c;
  30748. end;
  30749. function FromVarUInt32up128(var Source: PByte): cardinal;
  30750. var c: PtrUInt;
  30751. begin
  30752. result := Source^ shl 7;
  30753. inc(Source);
  30754. if result<=$7f shl 7 then
  30755. exit; // Values between 128 and 16256
  30756. c := Source^ shl 14;
  30757. inc(Source);
  30758. result := result and $3FFF or c;
  30759. if c<=$7f shl 14 then
  30760. exit; // Values between 16257 and 2080768
  30761. c := Source^ shl 21;
  30762. inc(Source);
  30763. result := result and $1FFFFF or c;
  30764. if c<=$7f shl 21 then
  30765. exit; // Values between 2080769 and 266338304
  30766. c := Source^ shl 28;
  30767. inc(Source);
  30768. result := result and $FFFFFFF or c;
  30769. end;
  30770. function ToVarUInt64(Value: QWord; Dest: PByte): PByte;
  30771. begin
  30772. {$ifndef CPU64}
  30773. if Value<MaxInt then begin
  30774. result := ToVarUInt32(Int64Rec(Value).Lo,Dest);
  30775. exit;
  30776. end;
  30777. {$endif}
  30778. if Value>$7f then
  30779. repeat
  30780. Dest^ := (byte(Value) and $7F) or $80;
  30781. Value := Value shr 7;
  30782. inc(Dest);
  30783. until Value<=$7f;
  30784. Dest^ := Value;
  30785. inc(Dest);
  30786. result := Dest;
  30787. end;
  30788. function FromVarUInt64(var Source: PByte): QWord;
  30789. var c,n: PtrUInt;
  30790. begin
  30791. if Source^>$7f then begin
  30792. n := 0;
  30793. result := PtrUInt(Source^) and $7F;
  30794. inc(Source);
  30795. repeat
  30796. c := Source^;
  30797. inc(n,7);
  30798. if c<=$7f then
  30799. break;
  30800. result := result or (QWord(c and $7f) shl n);
  30801. inc(Source);
  30802. until false;
  30803. result := result or (QWord(c) shl n);
  30804. end else
  30805. result := Source^;
  30806. inc(Source);
  30807. end;
  30808. function ToVarInt64(Value: Int64; Dest: PByte): PByte;
  30809. begin // 0=0,1=1,2=-1,3=2,4=-2...
  30810. {$ifdef CPU64}
  30811. if Value<0 then
  30812. // -1->2, -2->4..
  30813. Value := (-Value) shl 1 else
  30814. if Value>0 then
  30815. // 1->1, 2->3..
  30816. Value := (Value shl 1)-1;
  30817. // 0->0
  30818. result := ToVarUInt64(Value,Dest);
  30819. {$else}
  30820. if Value<0 then
  30821. // -1->2, -2->4..
  30822. result := ToVarUInt64((-Value) shl 1,Dest) else
  30823. if Value>0 then
  30824. // 1->1, 2->3..
  30825. result := ToVarUInt64((Value shl 1)-1,Dest) else begin
  30826. // 0->0
  30827. Dest^ := 0;
  30828. inc(Dest);
  30829. result := Dest;
  30830. end;
  30831. {$endif}
  30832. end;
  30833. function FromVarInt64(var Source: PByte): Int64;
  30834. var c,n: PtrUInt;
  30835. begin // 0=0,1=1,2=-1,3=2,4=-2...
  30836. c := Source^;
  30837. if c>$7f then begin
  30838. result := c and $7F;
  30839. n := 0;
  30840. inc(Source);
  30841. repeat
  30842. c := Source^;
  30843. inc(n,7);
  30844. if c<=$7f then
  30845. break;
  30846. result := result or (Int64(c and $7f) shl n);
  30847. inc(Source);
  30848. until false;
  30849. result := result or (Int64(c) shl n);
  30850. if {$ifdef CPU64}result{$else}Int64Rec(result).Lo{$endif} and 1<>0 then
  30851. // 1->1, 3->2..
  30852. result := result shr 1+1 else
  30853. // 0->0, 2->-1, 4->-2..
  30854. result := -(result shr 1);
  30855. end else begin
  30856. if c=0 then
  30857. result := 0 else
  30858. if c and 1=0 then
  30859. // 0->0, 2->-1, 4->-2..
  30860. result := -(c shr 1) else
  30861. // 1->1, 3->2..
  30862. result := (c shr 1)+1;
  30863. end;
  30864. inc(Source);
  30865. end;
  30866. function FromVarInt64Value(Source: PByte): Int64;
  30867. var c,n: PtrUInt;
  30868. begin // 0=0,1=1,2=-1,3=2,4=-2...
  30869. c := Source^;
  30870. if c>$7f then begin
  30871. result := c and $7F;
  30872. n := 0;
  30873. inc(Source);
  30874. repeat
  30875. c := Source^;
  30876. inc(n,7);
  30877. if c<=$7f then
  30878. break;
  30879. result := result or (Int64(c and $7f) shl n);
  30880. inc(Source);
  30881. until false;
  30882. result := result or (Int64(c) shl n);
  30883. if {$ifdef CPU64}result{$else}Int64Rec(result).Lo{$endif} and 1<>0 then
  30884. // 1->1, 3->2..
  30885. result := result shr 1+1 else
  30886. // 0->0, 2->-1, 4->-2..
  30887. result := -(result shr 1);
  30888. end else
  30889. if c=0 then
  30890. result := 0 else
  30891. if c and 1<>0 then
  30892. // 1->1, 3->2..
  30893. result := (c shr 1)+1 else
  30894. // 0->0, 2->-1, 4->-2..
  30895. result := -(c shr 1);
  30896. end;
  30897. function GotoNextVarInt(Source: PByte): pointer;
  30898. begin
  30899. if Source<>nil then begin
  30900. while Source^>$7f do inc(Source);
  30901. inc(Source);
  30902. end;
  30903. result := Source;
  30904. end;
  30905. function ToVarString(const Value: RawUTF8; Dest: PByte): PByte;
  30906. var Len: integer;
  30907. begin
  30908. Len := Length(Value);
  30909. Dest := ToVarUInt32(Len,Dest);
  30910. if Len>0 then begin
  30911. MoveFast(pointer(Value)^,Dest^,Len);
  30912. result := pointer(PAnsiChar(Dest)+Len);
  30913. end else
  30914. result := Dest;
  30915. end;
  30916. function GotoNextVarString(Source: PByte): pointer;
  30917. begin
  30918. result := Pointer(PtrUInt(Source)+FromVarUInt32(Source));
  30919. end;
  30920. function FromVarString(var Source: PByte): RawUTF8;
  30921. var Len: PtrUInt;
  30922. begin
  30923. Len := FromVarUInt32(Source);
  30924. SetString(Result,PAnsiChar(Source),Len);
  30925. inc(Source,Len);
  30926. end;
  30927. procedure FromVarString(var Source: PByte; var Value: TSynTempBuffer);
  30928. var len: integer;
  30929. begin
  30930. len := FromVarUInt32(Source);
  30931. Value.Init(Source,len);
  30932. PByteArray(Value.buf)[len] := 0; // include trailing #0
  30933. inc(Source,len);
  30934. end;
  30935. procedure FromVarString(var Source: PByte; var Value: RawByteString; CodePage: integer);
  30936. var Len: PtrUInt;
  30937. begin
  30938. Len := FromVarUInt32(Source);
  30939. if Len=0 then
  30940. exit;
  30941. SetString(Value,PAnsiChar(Source),Len);
  30942. {$ifdef HASCODEPAGE}
  30943. SetCodePage(Value,CodePage,false);
  30944. {$endif}
  30945. inc(Source,Len);
  30946. end;
  30947. function FromVarBlob(Data: PByte): TValueResult;
  30948. begin
  30949. Result.Len := FromVarUInt32(Data);
  30950. Result.Ptr := pointer(Data);
  30951. end;
  30952. { ************ low-level RTTI types and conversion routines }
  30953. {$ifdef FPC}
  30954. function RTTIManagedSize(typeInfo: Pointer): SizeInt; inline;
  30955. begin
  30956. case PTypeKind(typeInfo)^ of
  30957. tkLString,tkLStringOld,tkWString,tkUString,
  30958. tkInterface,tkDynarray:
  30959. result := sizeof(Pointer);
  30960. {$ifndef NOVARIANTS}
  30961. tkVariant:
  30962. result := sizeof(TVarData);
  30963. {$endif}
  30964. tkArray:
  30965. with GetTypeInfo(typeInfo,tkArray)^ do
  30966. result := arraySize;
  30967. //result := (arraySize and $7FFFFFFF) * ElCount; // to be validated
  30968. tkObject,tkRecord:
  30969. result := GetTypeInfo(typeInfo,PTypeKind(typeInfo)^)^.recSize;
  30970. else
  30971. raise ESynException.CreateUTF8('RTTIManagedSize(%)',[PByte(typeInfo)^]);
  30972. end;
  30973. end;
  30974. procedure RecordClear(var Dest; TypeInfo: pointer);
  30975. [external name 'FPC_FINALIZE'];
  30976. procedure RecordAddRef(var Data; TypeInfo : pointer);
  30977. [external name 'FPC_ADDREF'];
  30978. procedure RecordCopy(var Dest; const Source; TypeInfo: pointer);
  30979. begin // external name 'FPC_COPY' does not work as we need
  30980. RecordClear(Dest,TypeInfo);
  30981. MoveFast(Source,Dest,RTTIManagedSize(TypeInfo));
  30982. RecordAddRef(Dest,TypeInfo);
  30983. end;
  30984. procedure CopyArray(dest, source, typeInfo: Pointer; cnt: PtrUInt);
  30985. var i, size: SizeInt;
  30986. begin
  30987. size := RTTIManagedSize(typeInfo);
  30988. if size>0 then
  30989. for i := 1 to cnt do begin
  30990. RecordClear(dest^,TypeInfo); // inlined RecordCopy()
  30991. MoveFast(source^,dest^,size);
  30992. RecordAddRef(dest^,TypeInfo);
  30993. inc(PByte(source),size);
  30994. inc(PByte(dest),size);
  30995. end;
  30996. end;
  30997. {$else}
  30998. procedure CopyArray(dest, source, typeInfo: Pointer; cnt: PtrUInt);
  30999. asm
  31000. {$ifdef CPU64}
  31001. .NOFRAME
  31002. jmp System.@CopyArray
  31003. {$else}
  31004. push dword ptr [EBP+8]
  31005. call System.@CopyArray // RTL is fast enough for this
  31006. {$endif}
  31007. end;
  31008. {$endif FPC}
  31009. function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
  31010. var info: PTypeInfo;
  31011. F: integer;
  31012. Field: ^TFieldInfo;
  31013. Diff: cardinal;
  31014. A, B: PAnsiChar;
  31015. {$ifndef DELPHI5OROLDER}
  31016. DynA, DynB: TDynArray;
  31017. {$endif}
  31018. begin
  31019. A := @RecA;
  31020. B := @RecB;
  31021. if A=B then begin // both nil or same pointer
  31022. result := true;
  31023. exit;
  31024. end;
  31025. result := false;
  31026. info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet);
  31027. if info=nil then
  31028. exit; // raise Exception.CreateUTF8('% is not a record',[Typ^.Name]);
  31029. Field := @info^.ManagedFields[0];
  31030. Diff := 0;
  31031. for F := 1 to info^.ManagedCount do begin
  31032. Diff := Field^.Offset-Diff;
  31033. if Diff<>0 then begin
  31034. if not CompareMem(A,B,Diff) then
  31035. exit; // binary block not equal
  31036. inc(A,Diff);
  31037. inc(B,Diff);
  31038. end;
  31039. case Field^.TypeInfo^.Kind of
  31040. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  31041. if PAnsiString(A)^=PAnsiString(B)^ then
  31042. Diff := sizeof(pointer) else
  31043. exit;
  31044. tkWString:
  31045. if PWideString(A)^=PWideString(B)^ then
  31046. Diff := sizeof(pointer) else
  31047. exit;
  31048. {$ifdef HASVARUSTRING}
  31049. tkUString:
  31050. if PUnicodeString(A)^=PUnicodeString(B)^ then
  31051. Diff := sizeof(pointer) else
  31052. exit;
  31053. {$endif}
  31054. tkRecord{$ifdef FPC},tkObject{$endif}:
  31055. if RecordEquals(A^,B^,Field^.TypeInfo{$ifndef HASDIRECTTYPEINFO}^{$endif}) then
  31056. Diff := RecordTypeInfoSize(Field^.TypeInfo{$ifndef HASDIRECTTYPEINFO}^{$endif}) else
  31057. exit;
  31058. {$ifndef NOVARIANTS}
  31059. tkVariant:
  31060. if PVariant(A)^=PVariant(B)^ then
  31061. Diff := sizeof(variant) else
  31062. exit;
  31063. {$endif}
  31064. {$ifndef DELPHI5OROLDER} // do not know why Delphi 5 compiler does not like it
  31065. tkDynArray: begin
  31066. DynA.Init(Deref(Field^.TypeInfo),A^);
  31067. DynB.Init(Deref(Field^.TypeInfo),B^);
  31068. if DynA.Equals(DynB) then
  31069. Diff := sizeof(pointer) else
  31070. exit;
  31071. end;
  31072. {$endif}
  31073. tkInterface:
  31074. if PPointer(A)^=PPointer(B)^ then
  31075. Diff := sizeof(pointer) else
  31076. exit;
  31077. {$ifdef FPC} // FPC does include RTTI for unmanaged fields! :)
  31078. else
  31079. if Field^.TypeInfo^.Kind in tkManagedTypes then
  31080. raise ESynException.CreateUTF8('RecordEquals(kind=%)',
  31081. [ord(Field^.TypeInfo^.Kind)]) else begin
  31082. if F=info^.ManagedCount then
  31083. Diff := info.recSize-Field^.Offset else
  31084. Diff := info^.ManagedFields[F].Offset-Field^.Offset;
  31085. if not CompareMem(A,B,Diff) then
  31086. exit; // binary block not equal
  31087. end;
  31088. {$else}
  31089. else raise ESynException.CreateUTF8('RecordEquals(kind=%)',
  31090. [ord(Field^.TypeInfo^.Kind)]);
  31091. {$endif}
  31092. end;
  31093. inc(A,Diff);
  31094. inc(B,Diff);
  31095. inc(Diff,Field^.Offset);
  31096. inc(Field);
  31097. end;
  31098. if CompareMem(A,B,info.recSize-Diff) then
  31099. result := true;
  31100. end;
  31101. function RecordSaveLength(const Rec; TypeInfo: pointer): integer;
  31102. var info, infoNested: PTypeInfo;
  31103. F, Len: integer;
  31104. Field: ^TFieldInfo;
  31105. P: PPtrUInt;
  31106. R: PAnsiChar;
  31107. DynArray: TDynArray;
  31108. begin
  31109. R := @Rec;
  31110. info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet);
  31111. if (R=nil) or (info=nil) then begin
  31112. result := 0; // should have been checked before
  31113. exit;
  31114. end;
  31115. Field := @info.ManagedFields[0];
  31116. result := info.recSize;
  31117. for F := 1 to info.ManagedCount do begin
  31118. P := pointer(R+Field.Offset);
  31119. case Field.TypeInfo^.Kind of
  31120. tkDynArray: begin
  31121. DynArray.Init(Deref(Field.TypeInfo),P^);
  31122. inc(result,DynArray.SaveToLength-sizeof(PtrUInt));
  31123. end;
  31124. tkLString,tkWString{$ifdef FPC},tkLStringOld{$endif}:
  31125. // length stored within WideString is in bytes
  31126. if P^=0 then
  31127. dec(result,sizeof(PtrUInt)-1) else
  31128. inc(result,ToVarUInt32LengthWithData(PStrRec(Pointer(P^-STRRECSIZE))^.length)-sizeof(PtrUInt));
  31129. {$ifdef HASVARUSTRING}
  31130. tkUString:
  31131. if P^=0 then
  31132. dec(result,sizeof(PtrUInt)-1) else
  31133. inc(result,ToVarUInt32LengthWithData(PStrRec(Pointer(P^-STRRECSIZE))^.length*2)-sizeof(PtrUInt));
  31134. {$endif}
  31135. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  31136. infoNested := Deref(Field.TypeInfo); // inlined GetTypeInfo()
  31137. Len := RecordSaveLength(P^,infoNested);
  31138. if Len=0 then begin
  31139. result := 0;
  31140. exit; // invalid/unhandled nested record content
  31141. end;
  31142. inc(result,Len);
  31143. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  31144. infoNested := GetFPCAlignPtr(infoNested);
  31145. {$else}
  31146. inc(PtrUInt(infoNested),infoNested^.NameLen);
  31147. {$endif}
  31148. dec(result,infoNested^.recSize);
  31149. end;
  31150. {$ifndef NOVARIANTS}
  31151. tkVariant: begin
  31152. Len := VariantSaveLength(PVariant(P)^);
  31153. if Len=0 then begin
  31154. result := 0;
  31155. exit; // invalid/unhandled variant content
  31156. end;
  31157. inc(result,Len-sizeof(variant));
  31158. end;
  31159. {$endif}
  31160. {$ifndef FPC} // FPC does include RTTI for unmanaged fields! :)
  31161. else begin
  31162. result := 0;
  31163. exit; // invalid/unhandled record content
  31164. end;
  31165. {$endif}
  31166. end;
  31167. inc(Field);
  31168. end;
  31169. end;
  31170. function RecordSave(const Rec; Dest: PAnsiChar; TypeInfo: pointer): PAnsiChar; overload;
  31171. var info, infoNested: PTypeInfo;
  31172. F, LenBytes: integer;
  31173. Diff: cardinal;
  31174. Field: ^TFieldInfo;
  31175. R: PAnsiChar;
  31176. Kind: TTypeKind;
  31177. DynArray: TDynArray;
  31178. begin
  31179. R := @Rec;
  31180. info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet);
  31181. if (R=nil) or (info=nil) then begin
  31182. result := nil; // should have been checked before
  31183. exit;
  31184. end;
  31185. Field := @info^.ManagedFields[0];
  31186. Diff := 0;
  31187. for F := 1 to info^.ManagedCount do begin
  31188. Diff := Field^.Offset-Diff;
  31189. if Diff<>0 then begin
  31190. MoveFast(R^,Dest^,Diff);
  31191. inc(R,Diff);
  31192. inc(Dest,Diff);
  31193. end;
  31194. Kind := Field.TypeInfo^.Kind;
  31195. case Kind of
  31196. tkDynArray: begin
  31197. DynArray.Init(Deref(Field.TypeInfo),R^);
  31198. Dest := DynArray.SaveTo(Dest);
  31199. Diff := sizeof(PtrUInt); // size of tkDynArray in record
  31200. end;
  31201. tkLString, tkWString {$ifdef HASVARUSTRING}, tkUString{$endif}
  31202. {$ifdef FPC}, tkLStringOld{$endif}: begin
  31203. if PPtrUInt(R)^=0 then
  31204. LenBytes := 0 else
  31205. LenBytes := PStrRec(Pointer(PPtrUInt(R)^-STRRECSIZE))^.length;
  31206. {$ifdef HASVARUSTRING} // WideString has length in bytes, UnicodeString in WideChars
  31207. if Kind=tkUString then
  31208. LenBytes := LenBytes*2;
  31209. {$endif}
  31210. Dest := pointer(ToVarUInt32(LenBytes,pointer(Dest)));
  31211. if LenBytes>0 then begin
  31212. MoveFast(pointer(PPtrUInt(R)^)^,Dest^,LenBytes);
  31213. inc(Dest,LenBytes);
  31214. end;
  31215. Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
  31216. end;
  31217. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  31218. infoNested := Deref(Field.TypeInfo); // inlined GetTypeInfo()
  31219. Dest := RecordSave(R^,Dest,infoNested);
  31220. if Dest=nil then begin
  31221. result := nil; // invalid/unhandled record content
  31222. exit;
  31223. end;
  31224. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  31225. infoNested := GetFPCAlignPtr(infoNested);
  31226. {$else}
  31227. inc(PtrUInt(infoNested),infoNested^.NameLen);
  31228. {$endif}
  31229. Diff := infoNested^.recSize;
  31230. end;
  31231. {$ifndef NOVARIANTS}
  31232. tkVariant: begin
  31233. Dest := VariantSave(PVariant(R)^,Dest);
  31234. if Dest=nil then begin
  31235. result := nil; // invalid/unhandled variant content
  31236. exit;
  31237. end;
  31238. Diff := sizeof(Variant); // size of tkVariant in record
  31239. end;
  31240. {$endif}
  31241. {$ifdef FPC} // FPC does include RTTI for unmanaged fields! :)
  31242. else
  31243. if Field^.TypeInfo^.Kind in tkManagedTypes then
  31244. raise ESynException.CreateUTF8('RecordSave(kind=%)',[ord(Field^.TypeInfo^.Kind)]) else begin
  31245. if F=info^.ManagedCount then
  31246. Diff := info.recSize-Field^.Offset else
  31247. Diff := info^.ManagedFields[F].Offset-Field^.Offset;
  31248. MoveFast(R^,Dest^,Diff);
  31249. inc(Dest,Diff);
  31250. end;
  31251. {$else}
  31252. else begin
  31253. result := nil;
  31254. exit; // invalid/unhandled record content
  31255. end;
  31256. {$endif}
  31257. end;
  31258. inc(R,Diff);
  31259. inc(Diff,Field.Offset);
  31260. inc(Field);
  31261. end;
  31262. Diff := info^.recSize-Diff;
  31263. if integer(Diff)<0 then
  31264. raise ESynException.Create('RecordSave diff') else
  31265. if Diff<>0 then begin
  31266. MoveFast(R^,Dest^,Diff);
  31267. result := Dest+Diff;
  31268. end else
  31269. result := Dest;
  31270. end;
  31271. function RecordSave(const Rec; TypeInfo: pointer): RawByteString; overload;
  31272. var Len: integer;
  31273. begin
  31274. Len := RecordSaveLength(Rec,TypeInfo);
  31275. SetString(result,nil,Len);
  31276. if Len<>0 then
  31277. RecordSave(Rec,pointer(result),TypeInfo);
  31278. end;
  31279. function RecordSaveBase64(const Rec; TypeInfo: pointer; UriCompatible: boolean): RawUTF8;
  31280. var len: integer;
  31281. data: RawByteString;
  31282. dat: PAnsiChar;
  31283. begin
  31284. result := '';
  31285. len := RecordSaveLength(Rec,TypeInfo);
  31286. if len=0 then
  31287. exit;
  31288. SetLength(data,len+4);
  31289. dat := PAnsiChar(pointer(data))+4;
  31290. RecordSave(Rec,dat,TypeInfo);
  31291. PCardinal(data)^ := crc32c(0,dat,len);
  31292. result := BinToBase64(data);
  31293. if UriCompatible then
  31294. Base64ToURI(result);
  31295. end;
  31296. function RecordLoadBase64(Source: PAnsiChar; Len: integer; var Rec;
  31297. TypeInfo: pointer; UriCompatible: boolean): boolean;
  31298. var data: RawByteString;
  31299. uri: RawUTF8;
  31300. begin
  31301. result := false;
  31302. if Len<=6 then
  31303. exit;
  31304. if UriCompatible then begin
  31305. SetString(uri,Source,Len);
  31306. Base64FromURI(uri);
  31307. data := Base64ToBin(uri);
  31308. end else
  31309. data := Base64ToBin(Source,Len);
  31310. Len := length(data);
  31311. if Len<=4 then
  31312. exit;
  31313. Source := PAnsiChar(pointer(data))+4;
  31314. if crc32c(0,Source,Len-4)=PCardinal(data)^ then
  31315. result := RecordLoad(Rec,Source,TypeInfo)<>nil;
  31316. end;
  31317. procedure _Finalize(Data: Pointer; TypeInfo: Pointer);
  31318. {$ifdef FPC}
  31319. [external name 'FPC_FINALIZE'];
  31320. {$else}
  31321. asm
  31322. {$ifdef CPU64}
  31323. .NOFRAME
  31324. mov r8,1 // rcx=p rdx=typeInfo r8=ElemCount
  31325. jmp System.@FinalizeArray
  31326. {$else} // much faster than FinalizeArray(Data,TypeInfo,1)
  31327. movzx ecx,byte ptr [edx] // eax=ptr edx=typeinfo ecx=datatype
  31328. sub cl,tkLString
  31329. {$ifdef UNICODE}
  31330. cmp cl,tkUString-tkLString+1
  31331. {$else} cmp cl,tkDynArray-tkLString+1
  31332. {$endif}jnb @@err
  31333. jmp dword ptr [@@Tab+ecx*4]
  31334. nop; nop // for @@Tab alignment
  31335. @@Tab: dd System.@LStrClr
  31336. {$IFDEF LINUX} // under Linux, WideString are refcounted as AnsiString
  31337. dd System.@LStrClr
  31338. {$else} dd System.@WStrClr
  31339. {$endif}
  31340. {$ifdef LVCL}dd @@err
  31341. {$else} dd System.@VarClr {$endif}
  31342. dd @@Array
  31343. dd RecordClear
  31344. dd System.@IntfClear
  31345. dd @@err
  31346. dd System.@DynArrayClear
  31347. {$ifdef UNICODE}
  31348. dd System.@UStrClr
  31349. {$endif}
  31350. @@err: mov al,reInvalidPtr
  31351. {$ifdef DELPHI5OROLDER}
  31352. jmp System.@RunError
  31353. {$else}
  31354. jmp System.Error
  31355. {$endif}
  31356. @@array:movzx ecx,[edx].TTypeInfo.NameLen
  31357. add ecx,edx
  31358. mov edx,dword ptr [ecx].TTypeInfo.ManagedFields[0] // Fields[0].TypeInfo^
  31359. mov ecx,[ecx].TTypeInfo.ManagedCount
  31360. mov edx,[edx]
  31361. jmp System.@FinalizeArray
  31362. {$endif CPU64}
  31363. end;
  31364. {$endif FPC}
  31365. function RecordLoad(var Rec; Source: PAnsiChar; TypeInfo: pointer): PAnsiChar;
  31366. var info, infoNested: PTypeInfo;
  31367. F, LenBytes: integer;
  31368. Diff: cardinal;
  31369. Field: ^TFieldInfo;
  31370. R: PAnsiChar;
  31371. Kind: TTypeKind;
  31372. DynArray: TDynArray;
  31373. begin
  31374. R := @Rec;
  31375. info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet);
  31376. if (R=nil) or (info=nil) then begin
  31377. result := nil; // should have been checked before
  31378. exit;
  31379. end;
  31380. Field := @info^.ManagedFields[0];
  31381. if Source=nil then begin // inline RecordClear() function
  31382. for F := 1 to info^.ManagedCount do begin
  31383. _Finalize(R+Field^.Offset,Deref(Field^.TypeInfo));
  31384. inc(Field);
  31385. end;
  31386. result := nil;
  31387. exit;
  31388. end;
  31389. Diff := 0;
  31390. for F := 1 to info^.ManagedCount do begin
  31391. Diff := Field^.Offset-Diff;
  31392. if Diff<>0 then begin
  31393. MoveFast(Source^,R^,Diff);
  31394. inc(Source,Diff);
  31395. inc(R,Diff);
  31396. end;
  31397. Kind := Field.TypeInfo^.Kind;
  31398. case Kind of
  31399. tkDynArray: begin
  31400. DynArray.Init(Deref(Field.TypeInfo),R^);
  31401. Source := DynArray.LoadFrom(Source);
  31402. Diff := sizeof(PtrUInt); // size of tkDynArray in record
  31403. end;
  31404. tkLString, tkWString {$ifdef HASVARUSTRING}, tkUString{$endif}
  31405. {$ifdef FPC}, tkLStringOld{$endif}: begin
  31406. LenBytes := FromVarUInt32(PByte(Source));
  31407. case Kind of
  31408. tkLString{$ifdef FPC},tkLStringOld{$endif}: begin
  31409. SetString(PRawByteString(R)^,Source,LenBytes);
  31410. {$ifdef HASCODEPAGE}
  31411. { Delphi 2009+: set Code page for this AnsiString }
  31412. if LenBytes<>0 then begin
  31413. infoNested := Deref(Field.TypeInfo);
  31414. SetCodePage(PRawByteString(R)^,
  31415. PWord(PtrUInt(infoNested)+infoNested^.NameLen+2)^,false);
  31416. end;
  31417. {$endif}
  31418. end;
  31419. tkWString:
  31420. SetString(PWideString(R)^,PWideChar(Source),LenBytes shr 1);
  31421. {$ifdef HASVARUSTRING}
  31422. tkUString:
  31423. SetString(PString(R)^,PWideChar(Source),LenBytes shr 1);
  31424. {$endif}
  31425. end;
  31426. inc(Source,LenBytes);
  31427. Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
  31428. end;
  31429. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  31430. infoNested := Deref(Field.TypeInfo); // inlined GetTypeInfo()
  31431. Source := RecordLoad(R^,Source,infoNested);
  31432. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  31433. infoNested := GetFPCAlignPtr(infoNested);
  31434. {$else}
  31435. inc(PtrUInt(infoNested),infoNested^.NameLen);
  31436. {$endif}
  31437. Diff := infoNested^.recSize;
  31438. end;
  31439. {$ifndef NOVARIANTS}
  31440. tkVariant: begin
  31441. Source := VariantLoad(PVariant(R)^,Source,@JSON_OPTIONS[true]);
  31442. Diff := sizeof(Variant); // size of tkVariant in record
  31443. end;
  31444. {$endif}
  31445. {$ifdef FPC} // FPC does include RTTI for unmanaged fields! :)
  31446. else
  31447. if Field^.TypeInfo^.Kind in tkManagedTypes then
  31448. raise ESynException.CreateUTF8('RecordLoad(kind=%)',[ord(Field^.TypeInfo^.Kind)]) else begin
  31449. if F=info^.ManagedCount then
  31450. Diff := info.recSize-Field^.Offset else
  31451. Diff := info^.ManagedFields[F].Offset-Field^.Offset;
  31452. MoveFast(Source^,R^,Diff);
  31453. inc(Source,Diff);
  31454. end;
  31455. {$else}
  31456. else begin
  31457. result := nil;
  31458. exit; // invalid/unhandled record content
  31459. end;
  31460. {$endif}
  31461. end;
  31462. inc(R,Diff);
  31463. inc(Diff,Field.Offset);
  31464. inc(Field);
  31465. end;
  31466. Diff := info^.recSize-Diff;
  31467. if integer(Diff)<0 then
  31468. raise ESynException.Create('RecordLoad diff') else
  31469. if Diff<>0 then begin
  31470. MoveFast(Source^,R^,Diff);
  31471. result := Source+Diff;
  31472. end else
  31473. result := Source;
  31474. end;
  31475. {$ifndef FPC}
  31476. {$ifdef USEPACKAGES}
  31477. {$define EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31478. {$endif}
  31479. {$ifdef DELPHI5OROLDER}
  31480. {$define EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31481. {$endif}
  31482. {$ifdef PUREPASCAL}
  31483. {$define EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31484. {$endif}
  31485. {$ifndef DOPATCHTRTL}
  31486. {$define EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31487. {$endif}
  31488. {$ifdef EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31489. procedure RecordCopy(var Dest; const Source; TypeInfo: pointer);
  31490. asm // same params than _CopyRecord{ dest, source, typeInfo: Pointer }
  31491. {$ifdef CPU64}
  31492. .NOFRAME
  31493. {$endif}
  31494. jmp System.@CopyRecord
  31495. end;
  31496. procedure RecordClear(var Dest; TypeInfo: pointer);
  31497. asm
  31498. {$ifdef CPU64}
  31499. .NOFRAME
  31500. {$endif}
  31501. jmp System.@FinalizeRecord
  31502. end;
  31503. {$endif EXPECTSDELPHIRTLRECORDCOPYCLEAR}
  31504. {$ifdef DOPATCHTRTL}
  31505. function SystemRecordCopyAddress: Pointer;
  31506. asm
  31507. {$ifdef CPU64}
  31508. mov rax,offset System.@CopyRecord
  31509. {$else}
  31510. mov eax,offset System.@CopyRecord
  31511. {$endif}
  31512. end;
  31513. function SystemFinalizeRecordAddress: Pointer;
  31514. asm
  31515. {$ifdef CPU64}
  31516. mov rax,offset System.@FinalizeRecord
  31517. {$else}
  31518. mov eax,offset System.@FinalizeRecord
  31519. {$endif}
  31520. end;
  31521. function SystemInitializeRecordAddress: Pointer;
  31522. asm
  31523. {$ifdef CPU64}
  31524. mov rax,offset System.@InitializeRecord
  31525. {$else}
  31526. mov eax,offset System.@InitializeRecord
  31527. {$endif}
  31528. end;
  31529. procedure _InitializeRecord(P: Pointer; TypeInfo: Pointer);
  31530. asm // faster version by AB
  31531. { -> EAX pointer to record to be finalized }
  31532. { EDX pointer to type info }
  31533. (* // this TObject.Create-like initialization sounds slower
  31534. movzx ecx,byte ptr [edx].TTypeInfo.NameLen
  31535. mov edx,[edx+ecx].TTypeInfo.Size
  31536. xor ecx,ecx
  31537. jmp dword ptr [FillCharFast] *)
  31538. movzx ecx,byte ptr [edx].TTypeInfo.NameLen
  31539. push ebx
  31540. mov ebx,eax
  31541. push esi
  31542. push edi
  31543. mov edi,[edx+ecx].TTypeInfo.ManagedCount
  31544. lea esi,[edx+ecx].TTypeInfo.ManagedFields
  31545. test edi,edi
  31546. jz @@end
  31547. @@loop: mov edx,[esi].TFieldInfo.TypeInfo
  31548. mov eax,[esi].TFieldInfo.&Offset
  31549. mov edx,[edx]
  31550. lea esi,[esi+8]
  31551. movzx ecx,[edx].TTypeInfo.Kind
  31552. lea eax,[eax+ebx] // eax=data to be initialized
  31553. jmp dword ptr [@@Tab+ecx*4-tkLString*4]
  31554. @@Tab: dd @@ptr, @@ptr, @@variant, @@array, @@array, @@ptr, @@ptr, @@ptr, @@ptr
  31555. @@ptr: dec edi
  31556. mov dword ptr [eax],0 // pointer initialization
  31557. jg @@loop
  31558. @@end: pop edi
  31559. pop esi
  31560. pop ebx
  31561. ret
  31562. @@variant:
  31563. xor ecx,ecx
  31564. dec edi
  31565. mov dword ptr [eax],ecx
  31566. mov dword ptr [eax+4],ecx
  31567. mov dword ptr [eax+8],ecx
  31568. mov dword ptr [eax+12],ecx
  31569. jg @@loop
  31570. pop edi
  31571. pop esi
  31572. pop ebx
  31573. ret
  31574. @@array:mov ecx,1 // here eax=data edx=typeinfo
  31575. call System.@InitializeArray
  31576. dec edi
  31577. jg @@loop
  31578. pop edi
  31579. pop esi
  31580. pop ebx
  31581. end;
  31582. {$ifndef UNICODE} // TMonitor.Destroy is not available ! -> apply to D2007 only
  31583. procedure TObjectCleanupInstance;
  31584. asm // faster version by AB
  31585. push ebx
  31586. mov ebx,eax
  31587. @@loop: mov ebx,[ebx] // handle three VMT levels per iteration
  31588. mov edx,[ebx].vmtInitTable
  31589. mov ebx,[ebx].vmtParent
  31590. test edx,edx
  31591. jnz @@clr
  31592. test ebx,ebx
  31593. jz @@end
  31594. mov ebx,[ebx]
  31595. mov edx,[ebx].vmtInitTable
  31596. mov ebx,[ebx].vmtParent
  31597. test edx,edx
  31598. jnz @@clr
  31599. test ebx,ebx
  31600. jz @@end
  31601. mov ebx,[ebx]
  31602. mov edx,[ebx].vmtInitTable
  31603. mov ebx,[ebx].vmtParent
  31604. test edx,edx
  31605. jnz @@clr
  31606. test ebx,ebx
  31607. jnz @@loop
  31608. @@end: pop ebx
  31609. ret
  31610. @@clr: push offset @@loop // TObject has no vmtInitTable -> safe
  31611. jmp RecordClear // eax=self edx=typeinfo
  31612. end;
  31613. {$endif}
  31614. procedure RecordClear(var Dest; TypeInfo: pointer);
  31615. asm // faster version by AB (direct call to finalization procedures)
  31616. { -> EAX pointer to record to be finalized }
  31617. { EDX pointer to type info }
  31618. { <- EAX pointer to record to be finalized }
  31619. movzx ecx,byte ptr [edx].TTypeInfo.NameLen
  31620. push ebx
  31621. mov ebx,eax
  31622. push esi
  31623. push edi
  31624. mov edi,[edx+ecx].TTypeInfo.ManagedCount
  31625. lea esi,[edx+ecx].TTypeInfo.ManagedFields
  31626. test edi,edi
  31627. jz @@end
  31628. @@loop: mov edx,[esi].TFieldInfo.TypeInfo
  31629. mov eax,[esi].TFieldInfo.&Offset
  31630. mov edx,[edx]
  31631. lea esi,[esi+8]
  31632. movzx ecx,[edx].TTypeInfo.Kind
  31633. lea eax,[eax+ebx] // eax=data to be initialized
  31634. sub cl,tkLString
  31635. {$ifdef UNICODE}
  31636. cmp cl,tkUString-tkLString+1
  31637. {$else} cmp cl,tkDynArray-tkLString+1
  31638. {$endif}jnb @@err
  31639. call dword ptr [@@Tab+ecx*4]
  31640. dec edi
  31641. jg @@loop
  31642. @@end: mov eax,ebx // keep eax at return (see e.g. TObject.CleanupInstance)
  31643. pop edi
  31644. pop esi
  31645. pop ebx
  31646. ret
  31647. nop; nop; nop // align @@Tab
  31648. @@Tab: dd System.@LStrClr
  31649. {$IFDEF LINUX} // under Linux, WideString are refcounted as AnsiString
  31650. dd System.@LStrClr
  31651. {$else} dd System.@WStrClr {$endif}
  31652. {$ifdef LVCL}
  31653. dd @@err
  31654. {$else} dd System.@VarClr {$endif}
  31655. dd @@Array
  31656. dd RecordClear
  31657. dd System.@IntfClear
  31658. dd @@err
  31659. dd System.@DynArrayClear
  31660. {$ifdef UNICODE}
  31661. dd System.@UStrClr
  31662. {$endif}
  31663. @@err: mov al,reInvalidPtr
  31664. pop edi
  31665. pop esi
  31666. pop ebx
  31667. jmp System.Error
  31668. @@array:movzx ecx,[edx].TTypeInfo.NameLen
  31669. add ecx,edx
  31670. mov edx,dword ptr [ecx].TTypeInfo.ManagedFields[0] // Fields[0].TypeInfo^
  31671. mov ecx,[ecx].TTypeInfo.ManagedCount
  31672. mov edx,[edx]
  31673. call System.@FinalizeArray
  31674. // we made Call @@Array -> ret to continue
  31675. end;
  31676. procedure RecordCopy(var Dest; const Source; TypeInfo: pointer);
  31677. asm // faster version of _CopyRecord{dest, source, typeInfo: Pointer} by AB
  31678. { -> EAX pointer to dest }
  31679. { EDX pointer to source }
  31680. { ECX pointer to typeInfo }
  31681. push ebp
  31682. push ebx
  31683. push esi
  31684. push edi
  31685. movzx ebx,byte ptr [ecx].TTypeInfo.NameLen
  31686. mov esi,edx // esi = source
  31687. mov edi,eax // edi = dest
  31688. add ebx,ecx // ebx = TFieldTable
  31689. xor eax,eax // eax = current offset
  31690. mov ebp,[ebx].TTypeInfo.ManagedCount // ebp = TFieldInfo count
  31691. mov ecx,[ebx].TTypeInfo.recSize
  31692. test ebp,ebp
  31693. jz @fullcopy
  31694. push ecx // sizeof(record) on stack
  31695. add ebx,offset TTypeInfo.ManagedFields[0] // ebx = first TFieldInfo
  31696. @next: mov ecx,[ebx].TFieldInfo.&Offset
  31697. mov edx,[ebx].TFieldInfo.TypeInfo
  31698. sub ecx,eax
  31699. mov edx,[edx]
  31700. jle @nomov
  31701. lea esi,[esi+ecx]
  31702. lea edi,[edi+ecx]
  31703. neg ecx
  31704. @mov1: mov al,[esi+ecx] // fast copy not destructable data
  31705. mov [edi+ecx],al
  31706. inc ecx
  31707. jnz @mov1
  31708. @nomov: mov eax,edi
  31709. movzx ecx,[edx].TTypeInfo.Kind
  31710. cmp ecx,tkLString
  31711. je @@LString
  31712. jb @@err
  31713. {$ifdef UNICODE}
  31714. cmp ecx,tkUString
  31715. je @@UString
  31716. {$else} cmp ecx,tkDynArray
  31717. je @@DynArray
  31718. {$endif}ja @@err
  31719. jmp dword ptr [ecx*4+@@tab-tkWString*4]
  31720. @@Tab: dd @@WString,@@Variant,@@Array,@@Record,@@Interface,@@err
  31721. {$ifdef UNICODE}dd @@DynArray{$endif}
  31722. @@errv: mov al,reVarInvalidOp
  31723. jmp @@err2
  31724. @@err: mov al,reInvalidPtr
  31725. @@err2: pop edi
  31726. pop esi
  31727. pop ebx
  31728. pop ebp
  31729. jmp System.Error
  31730. nop // all functions below have esi=source edi=dest
  31731. @@Array:
  31732. movzx ecx,byte ptr [edx].TTypeInfo.NameLen
  31733. push dword ptr [edx+ecx].TTypeInfo.recSize
  31734. push dword ptr [edx+ecx].TTypeInfo.ManagedCount
  31735. mov ecx,dword ptr [edx+ecx].TTypeInfo.ManagedFields[0] // Fields[0].TypeInfo^
  31736. mov ecx,[ecx]
  31737. mov edx,esi
  31738. call System.@CopyArray
  31739. pop eax // restore sizeof(Array)
  31740. jmp @@finish
  31741. @@Record:
  31742. movzx ecx,byte ptr [edx].TTypeInfo.NameLen
  31743. mov ecx,[edx+ecx].TTypeInfo.recSize
  31744. push ecx
  31745. mov ecx,edx
  31746. mov edx,esi
  31747. call RecordCopy
  31748. pop eax // restore sizeof(Record)
  31749. jmp @@finish
  31750. nop; nop; nop
  31751. @@Variant:
  31752. {$ifdef NOVARCOPYPROC}
  31753. mov edx,esi
  31754. call System.@VarCopy
  31755. {$else} cmp dword ptr [VarCopyProc],0
  31756. mov edx,esi
  31757. jz @@errv
  31758. call [VarCopyProc]
  31759. {$endif}mov eax,16
  31760. jmp @@finish
  31761. {$ifdef DELPHI6OROLDER} nop; nop; {$endif}
  31762. @@Interface:
  31763. mov edx,[esi]
  31764. call System.@IntfCopy
  31765. jmp @@fin4
  31766. nop; nop; nop
  31767. @@DynArray:
  31768. mov ecx,edx // ecx=TypeInfo
  31769. mov edx,[esi]
  31770. call System.@DynArrayAsg
  31771. jmp @@fin4
  31772. @@WString:
  31773. {$ifndef LINUX}
  31774. mov edx,[esi]
  31775. call System.@WStrAsg
  31776. jmp @@fin4
  31777. {$endif}
  31778. @@LString:
  31779. mov edx,[esi]
  31780. call System.@LStrAsg
  31781. {$ifdef UNICODE}
  31782. jmp @@fin4
  31783. nop; nop
  31784. @@UString:
  31785. mov edx,[esi]
  31786. call System.@UStrAsg
  31787. {$endif}
  31788. @@fin4: mov eax,4
  31789. @@finish:
  31790. add esi,eax
  31791. add edi,eax
  31792. add eax,[ebx].TFieldInfo.&Offset
  31793. dec ebp // any other TFieldInfo?
  31794. lea ebx,[ebx+8]
  31795. jnz @next
  31796. pop ecx // ecx= sizeof(record)
  31797. @fullcopy:
  31798. mov edx,edi
  31799. sub ecx,eax
  31800. mov eax,esi
  31801. jle @nomov2
  31802. call dword ptr [MoveFast]
  31803. @nomov2:pop edi
  31804. pop esi
  31805. pop ebx
  31806. pop ebp
  31807. end;
  31808. {$endif DOPATCHTRTL}
  31809. {$endif FPC}
  31810. {$ifndef FPC}
  31811. {$ifndef CPUARM}
  31812. function SystemFillCharAddress: Pointer;
  31813. asm
  31814. {$ifdef CPU64}
  31815. mov rax,offset System.@FillChar
  31816. {$else}
  31817. mov eax,offset System.@FillChar
  31818. {$endif}
  31819. end;
  31820. {$ifdef CPU64}
  31821. { Some notes about MOVNTI opcode use below:
  31822. - Delphi inline assembler is not able to compile the instruction -> so we
  31823. had to write some manual DB $... values instead :(
  31824. - The I in MOVNTI means "non-temporal hint". It is implemented by using a
  31825. write combining (WC) memory type protocol when writing the data to memory.
  31826. The processor does not write the data into the cache hierarchy, nor does
  31827. it fetch the corresponding cache line from memory into the cache hierarchy.
  31828. By-passing the cache should enhance move() speed of big memory blocks. }
  31829. procedure MoveSSE2; // Johan Bontes refactored revision
  31830. asm // rcx=Source, rdx=Dest, r8=Count
  31831. .noframe
  31832. .align 16
  31833. sub rcx,rdx
  31834. push rbx //allow better alignment of loops (saves a cycle).
  31835. mov rax,r8
  31836. mov r11d,128 //code shink in prefetch loop
  31837. je @done
  31838. jnc @MoveForwards
  31839. add rax,rcx
  31840. jc @MoveBackwards
  31841. @MoveForwards: cmp r8,8
  31842. jl @Below8
  31843. test dl,07H
  31844. jz @IsAbove32
  31845. test dl,01H
  31846. jz @TryMoveWord
  31847. mov al,[rcx+rdx]
  31848. dec r8
  31849. mov [rdx],al
  31850. dec rdx
  31851. @TryMoveWord: test dl,02H
  31852. jz @TryMoveDWord
  31853. mov ax,[rcx+rdx]
  31854. sub r8,2
  31855. mov [rdx],ax
  31856. add rdx,2
  31857. @TryMoveDWord: test dl,04H
  31858. jz @IsAbove32
  31859. mov eax,[rcx+rdx]
  31860. sub r8,4
  31861. mov [rdx],eax
  31862. add rdx,4
  31863. @IsAbove32: mov rbx,r8
  31864. shr rbx,5
  31865. jnz @Above32
  31866. @IsBelow8: mov rax,r8
  31867. shr eax,3
  31868. jz @Below8
  31869. @Loop8: dec eax
  31870. mov rbx,[rcx+rdx]
  31871. mov [rdx],rbx
  31872. lea rdx,[rdx+8]
  31873. jnz @Loop8
  31874. and r8d,7
  31875. @Below8: test r8,r8
  31876. jle @done
  31877. @MovePerByte: dec r8
  31878. mov al,[rcx+rdx]
  31879. mov [rdx],al
  31880. lea rdx,[rdx+1]
  31881. jnz @MovePerByte
  31882. @done: pop rbx
  31883. ret
  31884. nop
  31885. @Above32: cmp rbx,8192
  31886. jc @Below8192
  31887. mov eax,32
  31888. cmp rcx,4096
  31889. jnc @Prefetch_4K
  31890. @Below8192:
  31891. @loop8192: dec ebx
  31892. mov rax,[rcx+rdx]
  31893. mov r11,[rcx+rdx+08H]
  31894. mov [rdx],rax
  31895. mov [rdx+08H],r11
  31896. mov rax,[rcx+rdx+10H]
  31897. mov r11,[rcx+rdx+18H]
  31898. mov [rdx+10H],rax
  31899. mov [rdx+18H],r11
  31900. lea rdx,rdx+32
  31901. jnz @Loop8192
  31902. and r8d,1FH
  31903. jmp @IsBelow8
  31904. @Prefetch_4K: //assert eax=32
  31905. @PrefetchLoop: prefetchnta [rcx+rdx]
  31906. prefetchnta [rcx+rdx+40H]
  31907. add rdx,r11
  31908. dec eax
  31909. jnz @PrefetchLoop
  31910. sub rdx,4096
  31911. mov eax,64
  31912. db $0F,$1F,$40,$00 //nop4
  31913. @Loop64: mov rbx,[rcx+rdx]
  31914. mov r10,[rcx+rdx+08H]
  31915. db $48,$0F,$C3,$1A // movnti [rdx],rbx
  31916. db $4C,$0F,$C3,$52,$08 // movnti [rdx+08H],r10
  31917. mov rbx,[rcx+rdx+10H]
  31918. mov r10,[rcx+rdx+18H]
  31919. dec eax
  31920. db $48,$0F,$C3,$5A,$10 // movnti [rdx+10H],rbx
  31921. db $4C,$0F,$C3,$52,$18 // movnti [rdx+18],r10
  31922. mov rbx,[rcx+rdx+20H]
  31923. mov r10,[rcx+rdx+28H]
  31924. db $48,$0F,$C3,$5A,$20 // movnti [rdx+20H],rbx
  31925. db $4C,$0F,$C3,$52,$28 // movnti [rdx+28H],r10
  31926. mov rbx,[rcx+rdx+30H]
  31927. mov r10,[rcx+rdx+38H]
  31928. db $48,$0F,$C3,$5A,$30 // movnti [rdx+30H],rbx
  31929. db $4C,$0F,$C3,$52,$38 // movnti [rdx+38H],r10
  31930. lea rdx,rdx+64
  31931. jnz @Loop64
  31932. cmp r8,(4096*2)
  31933. lea r8,r8-4096
  31934. mov eax,32
  31935. jnc @Prefetch_4K
  31936. mfence
  31937. jmp @IsAbove32
  31938. @MoveBackwards:add rdx,r8
  31939. cmp r8,8
  31940. jl @IsEmpty
  31941. test dl,07H
  31942. jz @IsAbove32_2
  31943. test dl,01H
  31944. jz @TryMoveWord2
  31945. dec rdx
  31946. mov al,[rcx+rdx]
  31947. dec r8
  31948. mov [rdx],al
  31949. @TryMoveWord2: test dl,02H
  31950. jz @TryMoveDWord2
  31951. sub rdx,2
  31952. mov ax,[rcx+rdx]
  31953. sub r8,2
  31954. mov [rdx],ax
  31955. @TryMoveDWord2:test dl,04H
  31956. jz @IsAbove32_2
  31957. sub rdx,4
  31958. mov eax,[rcx+rdx]
  31959. sub r8,4
  31960. rep mov [rdx],eax
  31961. @IsAbove32_2: rep mov rbx,r8
  31962. shr rbx,5
  31963. jnz @Below8K
  31964. @IsBelow8_2: rep mov rbx,r8
  31965. shr rbx,3
  31966. jz @IsEmpty
  31967. @Loop8_2: sub rdx,8
  31968. mov rax,[rcx+rdx]
  31969. dec rbx
  31970. mov [rdx],rax
  31971. jnz @Loop8_2
  31972. and r8d,07H
  31973. @IsEmpty: test r8,r8
  31974. jle @Return
  31975. @MovePerByte2: dec rdx
  31976. mov al,[rcx+rdx]
  31977. dec r8
  31978. mov [rdx],al
  31979. jnz @MovePerByte2
  31980. @Return: pop rbx
  31981. ret
  31982. @Below8K: cmp rbx,8192
  31983. jc @Loop32
  31984. cmp rcx,-4096
  31985. jc @Prefetch_4K2
  31986. @Loop32: sub rdx,32
  31987. dec ebx
  31988. mov rax,[rcx+rdx+18H]
  31989. mov r10,[rcx+rdx+10H]
  31990. mov [rdx+18H],rax
  31991. mov [rdx+10H],r10
  31992. mov rax,[rcx+rdx+8H]
  31993. mov r10,[rcx+rdx]
  31994. mov [rdx+8H],rax
  31995. mov [rdx],r10
  31996. jnz @Loop32
  31997. and r8d,1FH
  31998. jmp @IsBelow8_2
  31999. @Prefetch_4K2: rep mov eax,32
  32000. @PrefetchLoop2:sub rdx,r11
  32001. prefetchnta [rcx+rdx]
  32002. prefetchnta [rcx+rdx+40H]
  32003. dec eax
  32004. jnz @PrefetchLoop2
  32005. add rdx,4096
  32006. mov eax,64 //eax is always zero at this point.
  32007. db $66,$0F,$1F,$00
  32008. @Loop64_2: sub rdx,64
  32009. dec eax
  32010. mov rbx,[rcx+rdx+38H]
  32011. mov r10,[rcx+rdx+30H]
  32012. db $48,$0F,$C3,$5A,$38 // movnti [rdx+38H],rbx
  32013. db $4C,$0F,$C3,$52,$30 // movnti [rdx+30H],r10
  32014. mov rbx,[rcx+rdx+28H]
  32015. mov r10,[rcx+rdx+20H]
  32016. db $48,$0F,$C3,$5A,$28 // movnti [rdx+28H],rbx
  32017. db $4C,$0F,$C3,$52,$20 // movnti [rdx+20H],r10
  32018. mov rbx,[rcx+rdx+18H]
  32019. mov r10,[rcx+rdx+10H]
  32020. db $48,$0F,$C3,$5A,$18 // movnti [rdx+18H],rbx
  32021. db $4C,$0F,$C3,$52,$10 // movnti [rdx+10H],r10
  32022. mov rbx,[rcx+rdx+8H]
  32023. mov r10,[rcx+rdx]
  32024. db $48,$0F,$C3,$5A,$08 // movnti [rdx+8H],rbx
  32025. db $4C,$0F,$C3,$12 // movnti [rdx],r10
  32026. jnz @Loop64_2
  32027. cmp r8,(4096*2)
  32028. lea r8,r8-4096
  32029. jnc @Prefetch_4K2
  32030. mfence
  32031. jmp @IsAbove32_2
  32032. end;
  32033. procedure FillCharSSE2; // Johan Bontes refactored revision
  32034. asm
  32035. .noframe
  32036. .align 16
  32037. movzx r8,r8b //There's no need to optimize for count <= 3
  32038. mov rax,$0101010101010101
  32039. mov r11d,edx
  32040. imul rax,r8 //fill rax with value.
  32041. cmp rdx,63 //fix: allow fills > 4GB
  32042. jg @Above32
  32043. @Below32: and r11d,not(3)
  32044. lea r10,[rip + @SmallFill + (15*4)]
  32045. jz @SizeIs3
  32046. sub r10,r11
  32047. jmp r10
  32048. db $66,$90
  32049. @SmallFill: rep mov [rcx+56], eax
  32050. rep mov [rcx+52], eax
  32051. rep mov [rcx+48], eax
  32052. rep mov [rcx+44], eax
  32053. rep mov [rcx+40], eax
  32054. rep mov [rcx+36], eax
  32055. rep mov [rcx+32], eax
  32056. rep mov [rcx+28], eax
  32057. rep mov [rcx+24], eax
  32058. rep mov [rcx+20], eax
  32059. rep mov [rcx+16], eax
  32060. rep mov [rcx+12], eax
  32061. rep mov [rcx+08], eax
  32062. rep mov [rcx+04], eax
  32063. mov [rcx],eax
  32064. @Fallthough: mov [rcx+rdx-4],eax //unaligned write to fix up tail
  32065. rep ret
  32066. @SizeIs3: lea edx,[edx*2+edx] //r9 <= 3 r9*4
  32067. add r10,$1B //reuse rip (saves 4 bytes)
  32068. sub r10,rdx
  32069. jmp r10
  32070. @do3: mov [rcx+2],al
  32071. @do2: mov [rcx+1],al
  32072. @do1: mov [rcx],al
  32073. ret
  32074. @do0: rep ret
  32075. @Above32: mov r11,rcx
  32076. mov r8b,7 //code shrink to help alignment.
  32077. lea r9,[rcx+rdx] //r9=end of array
  32078. sub rdx,8
  32079. mov [rcx],rax
  32080. add rcx,8
  32081. and r11,r8 //and 7 See if dest is aligned
  32082. add rdx,r11
  32083. mov [r9-8],rax //do a tail write to align.
  32084. @tail: and r9,r8 //and 7 is tail aligned?
  32085. @tailwrite: sub rdx,r9 //dec(count, tailcount)
  32086. @alignOK: mov r10,rdx
  32087. xor rcx,r11 //align dest
  32088. shr r10,6
  32089. and edx,(32+16+8) //count the partial iterations of the loop
  32090. mov r8b,64 //code shrink to help alignment.
  32091. mov r9,rdx
  32092. jz @Initloop64
  32093. @partialloop: lea r11,[rip + @partial +(4*7)] //start at the end of the loop
  32094. shr r9,1 //every instruction is 4 bytes
  32095. sub r11,r9 //step back as needed
  32096. add rcx,rdx //add the partial loop count to dest
  32097. test r10,r10 //do we need to do more loops after partial?
  32098. stc //CF=1: we're in a partial loop
  32099. jmp r11 //do a partial loop
  32100. @Initloop64: mov rdx,r10
  32101. shr r10,(19-6) //use non-temporal move for > 512kb
  32102. jnz @InitFillHuge
  32103. @Doloop64: add rcx,r8
  32104. dec edx
  32105. mov [rcx-64+00H],rax
  32106. @partial: mov [rcx-64+08H],rax
  32107. mov [rcx-64+10H],rax
  32108. mov [rcx-64+18H],rax
  32109. mov [rcx-64+20H],rax
  32110. mov [rcx-64+28H],rax
  32111. mov [rcx-64+30H],rax
  32112. mov [rcx-64+38H],rax
  32113. jnbe @DoLoop64 //repeat while not(partial) and not(done)
  32114. jnz @InitLoop64 //(re)start the loop if not done
  32115. @done: rep ret //rep ret saves 25! cycles.
  32116. db $66,$90 //nop
  32117. @InitFillHuge:
  32118. @FillHuge: add rcx,r8 // movdqnt is the same speed, but more hassle
  32119. dec rdx
  32120. db $48,$0F,$C3,$41,$C0 // movnti [rcx-64+00H],rax
  32121. db $48,$0F,$C3,$41,$C8 // movnti [rcx-64+08H],rax
  32122. db $48,$0F,$C3,$41,$D0 // movnti [rcx-64+10H],rax
  32123. db $48,$0F,$C3,$41,$D8 // movnti [rcx-64+18H],rax
  32124. db $48,$0F,$C3,$41,$E0 // movnti [rcx-64+20H],rax
  32125. db $48,$0F,$C3,$41,$E8 // movnti [rcx-64+28H],rax
  32126. db $48,$0F,$C3,$41,$F0 // movnti [rcx-64+30H],rax
  32127. db $48,$0F,$C3,$41,$F8 // movnti [rcx-64+38H],rax
  32128. jnz @FillHuge
  32129. @donefillhuge:mfence
  32130. end;
  32131. function StrLenSSE2(S: pointer): PtrInt;
  32132. asm // from GPL strlen64.asm by Agner Fog - www.agner.org/optimize
  32133. .NOFRAME
  32134. test rcx,rcx
  32135. mov rax,rcx // get pointer to string from rcx
  32136. mov r8,rcx // copy pointer
  32137. jz @null // returns 0 if S=nil
  32138. // rax = s,ecx = 32 bits of s
  32139. pxor xmm0,xmm0 // set to zero
  32140. and ecx,0FH // lower 4 bits indicate misalignment
  32141. and rax,-10H // align pointer by 16
  32142. movdqa xmm1,[rax] // read from nearest preceding boundary
  32143. pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
  32144. pmovmskb edx,xmm1 // get one bit for each byte result
  32145. shr edx,cl // shift out false bits
  32146. shl edx,cl // shift back again
  32147. bsf edx,edx // find first 1-bit
  32148. jnz @L2 // found
  32149. // Main loop, search 16 bytes at a time
  32150. @L1: add rax,10H // increment pointer by 16
  32151. movdqa xmm1,[rax] // read 16 bytes aligned
  32152. pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
  32153. pmovmskb edx,xmm1 // get one bit for each byte result
  32154. bsf edx,edx // find first 1-bit
  32155. // (moving the bsf out of the loop and using test here would be faster
  32156. // for long strings on old processors, but we are assuming that most
  32157. // strings are short, and newer processors have higher priority)
  32158. jz @L1 // loop if not found
  32159. @L2: // Zero-byte found. Compute string length
  32160. sub rax,r8 // subtract start address
  32161. add rax,rdx // add byte index
  32162. @null:
  32163. end;
  32164. const
  32165. EQUAL_EACH = 8; // see https://msdn.microsoft.com/en-us/library/bb531463
  32166. NEGATIVE_POLARITY = 16;
  32167. {$ifdef HASAESNI}
  32168. function StrLenSSE42(S: pointer): PtrInt;
  32169. asm // rcx=S
  32170. .NOFRAME
  32171. test rcx,rcx
  32172. mov rdx,rcx
  32173. mov rax,-16
  32174. jz @null
  32175. pxor xmm0,xmm0
  32176. @L: add rax,16 // add before comparison flag
  32177. pcmpistri xmm0,[rdx+rax],EQUAL_EACH
  32178. jnz @L
  32179. add rax,rcx
  32180. ret
  32181. @null: xor rax,rax
  32182. end;
  32183. {$endif}
  32184. {$else CPU64}
  32185. {$ifndef PUREPASCAL}
  32186. procedure FillCharX87;
  32187. asm // eax=Dest edx=Count cl=Value
  32188. // faster version by John O'Harrow (Code Size = 153 Bytes)
  32189. cmp edx,32
  32190. mov ch,cl // copy value into both bytes of cx
  32191. jl @small
  32192. mov [eax ],cx // fill first 8 bytes
  32193. mov [eax+2],cx
  32194. mov [eax+4],cx
  32195. mov [eax+6],cx
  32196. sub edx,16
  32197. fld qword ptr [eax]
  32198. fst qword ptr [eax+edx] // fill last 16 bytes
  32199. fst qword ptr [eax+edx+8]
  32200. mov ecx,eax
  32201. and ecx,7 // 8-byte align writes
  32202. sub ecx,8
  32203. sub eax,ecx
  32204. add edx,ecx
  32205. add eax,edx
  32206. neg edx
  32207. @loop: fst qword ptr [eax+edx] // fill 16 bytes per loop
  32208. fst qword ptr [eax+edx+8]
  32209. add edx,16
  32210. jl @loop
  32211. ffree st(0)
  32212. fincstp
  32213. ret
  32214. nop
  32215. @small: test edx,edx
  32216. jle @done
  32217. mov [eax+edx-1],cl // fill last byte
  32218. and edx,-2 // no. of words to fill
  32219. neg edx
  32220. lea edx,[@fill+60+edx*2]
  32221. jmp edx
  32222. nop // align jump destinations
  32223. nop
  32224. @fill: mov [eax+28],cx
  32225. mov [eax+26],cx
  32226. mov [eax+24],cx
  32227. mov [eax+22],cx
  32228. mov [eax+20],cx
  32229. mov [eax+18],cx
  32230. mov [eax+16],cx
  32231. mov [eax+14],cx
  32232. mov [eax+12],cx
  32233. mov [eax+10],cx
  32234. mov [eax+ 8],cx
  32235. mov [eax+ 6],cx
  32236. mov [eax+ 4],cx
  32237. mov [eax+ 2],cx
  32238. mov [eax ],cx
  32239. ret // for alignment
  32240. @done: db $f3 // rep ret AMD trick here
  32241. end;
  32242. /// faster implementation of Move() for Delphi versions with no FastCode inside
  32243. procedure MoveX87;
  32244. asm // eax=source edx=dest ecx=count
  32245. // original code by John O'Harrow - included since delphi 2007
  32246. cmp eax,edx
  32247. jz @exit // exit if source=dest
  32248. cmp ecx,32
  32249. ja @lrg // count > 32 or count < 0
  32250. sub ecx,8
  32251. jg @sml // 9..32 byte move
  32252. jmp dword ptr [@table+32+ecx*4] // 0..8 byte move
  32253. @sml: fild qword ptr [eax+ecx] // load last 8
  32254. fild qword ptr [eax] // load first 8
  32255. cmp ecx,8
  32256. jle @sml16
  32257. fild qword ptr [eax+8] // load second 8
  32258. cmp ecx,16
  32259. jle @sml24
  32260. fild qword ptr [eax+16] // load third 8
  32261. fistp qword ptr [edx+16] // save third 8
  32262. @sml24: fistp qword ptr [edx+8] // save second 8
  32263. @sml16: fistp qword ptr [edx] // save first 8
  32264. fistp qword ptr [edx+ecx] // save last 8
  32265. ret
  32266. @exit: rep ret
  32267. @table: dd @exit,@m01,@m02,@m03,@m04,@m05,@m06,@m07,@m08
  32268. @lrgfwd:push edx
  32269. fild qword ptr [eax] // first 8
  32270. lea eax,[eax+ecx-8]
  32271. lea ecx,[ecx+edx-8]
  32272. fild qword ptr [eax] // last 8
  32273. push ecx
  32274. neg ecx
  32275. and edx,-8 // 8-byte align writes
  32276. lea ecx,[ecx+edx+8]
  32277. pop edx
  32278. @fwd: fild qword ptr [eax+ecx]
  32279. fistp qword ptr [edx+ecx]
  32280. add ecx,8
  32281. jl @fwd
  32282. fistp qword ptr [edx] // last 8
  32283. pop edx
  32284. fistp qword ptr [edx] // first 8
  32285. ret
  32286. @lrg: jng @exit // count < 0
  32287. cmp eax,edx
  32288. ja @lrgfwd
  32289. sub edx,ecx
  32290. cmp eax,edx
  32291. lea edx,[edx+ecx]
  32292. jna @lrgfwd
  32293. sub ecx,8 // backward move
  32294. push ecx
  32295. fild qword ptr [eax+ecx] // last 8
  32296. fild qword ptr [eax] // first 8
  32297. add ecx,edx
  32298. and ecx,-8 // 8-byte align writes
  32299. sub ecx,edx
  32300. @bwd: fild qword ptr [eax+ecx]
  32301. fistp qword ptr [edx+ecx]
  32302. sub ecx,8
  32303. jg @bwd
  32304. pop ecx
  32305. fistp qword ptr [edx] // first 8
  32306. fistp qword ptr [edx+ecx] // last 8
  32307. ret
  32308. @m01: movzx ecx,byte ptr [eax]
  32309. mov [edx],cl
  32310. ret
  32311. @m02: movzx ecx,word ptr [eax]
  32312. mov [edx],cx
  32313. ret
  32314. @m03: mov cx,[eax]
  32315. mov al,[eax+2]
  32316. mov [edx],cx
  32317. mov [edx+2],al
  32318. ret
  32319. @m04: mov ecx,[eax]
  32320. mov [edx],ecx
  32321. ret
  32322. @m05: mov ecx,[eax]
  32323. mov al,[eax+4]
  32324. mov [edx],ecx
  32325. mov [edx+4],al
  32326. ret
  32327. @m06: mov ecx,[eax]
  32328. mov ax,[eax+4]
  32329. mov [edx],ecx
  32330. mov [edx+4],ax
  32331. ret
  32332. @m07: mov ecx,[eax]
  32333. mov eax,[eax+3]
  32334. mov [edx],ecx
  32335. mov [edx+3],eax
  32336. ret
  32337. @m08: mov ecx,[eax]
  32338. mov eax,[eax+4]
  32339. mov [edx],ecx
  32340. mov [edx+4],eax
  32341. end;
  32342. function StrLenX86(S: pointer): PtrInt;
  32343. // pure x86 function (if SSE2 not available) - faster than SysUtils' version
  32344. asm
  32345. test eax,eax
  32346. jz @@0
  32347. cmp byte ptr [eax+0],0; je @@0
  32348. cmp byte ptr [eax+1],0; je @@1
  32349. cmp byte ptr [eax+2],0; je @@2
  32350. cmp byte ptr [eax+3],0; je @@3
  32351. push eax
  32352. and eax,-4 { DWORD Align Reads }
  32353. @@Loop:
  32354. add eax,4
  32355. mov edx,[eax] { 4 Chars per Loop }
  32356. lea ecx,[edx-$01010101]
  32357. not edx
  32358. and edx,ecx
  32359. and edx,$80808080 { Set Byte to $80 at each #0 Position }
  32360. jz @@Loop { Loop until any #0 Found }
  32361. @@SetResult:
  32362. pop ecx
  32363. bsf edx,edx { Find First #0 Position }
  32364. shr edx,3 { Byte Offset of First #0 }
  32365. add eax,edx { Address of First #0 }
  32366. sub eax,ecx { Returns Length }
  32367. ret
  32368. @@0: xor eax,eax; ret
  32369. @@1: mov eax,1; ret
  32370. @@2: mov eax,2; ret
  32371. @@3: mov eax,3
  32372. end;
  32373. {$ifndef DELPHI5OROLDER} // need SSE2 asm instruction set
  32374. procedure FillCharSSE2;
  32375. asm // Dest=eax Count=edx Value=cl
  32376. cmp edx, 32
  32377. mov ch,cl {copy value into both bytes of cx}
  32378. jl @@small
  32379. sub edx,16
  32380. movd xmm0,ecx
  32381. pshuflw xmm0,xmm0,0
  32382. pshufd xmm0,xmm0,0
  32383. movups [eax],xmm0 {fill first 16 bytes}
  32384. movups [eax+edx],xmm0 {fill last 16 bytes}
  32385. mov ecx,eax {16-byte align writes}
  32386. and ecx,15
  32387. sub ecx,16
  32388. sub eax,ecx
  32389. add edx,ecx
  32390. add eax,edx
  32391. neg edx
  32392. cmp edx,-512*1024
  32393. jb @@large
  32394. @@loop:
  32395. movaps [eax+edx],xmm0 {fill 16 bytes per loop}
  32396. add edx,16
  32397. jl @@loop
  32398. ret
  32399. @@large:
  32400. movntdq [eax+edx],xmm0 {fill 16 bytes per loop}
  32401. add edx,16
  32402. jl @@large
  32403. ret
  32404. @@small:
  32405. test edx,edx
  32406. jle @@done
  32407. mov [eax+edx-1],cl {fill last byte}
  32408. and edx,-2 {no. of words to fill}
  32409. neg edx
  32410. lea edx,[@@smallfill+60+edx*2]
  32411. jmp edx
  32412. nop {align jump destinations}
  32413. nop
  32414. @@smallfill:
  32415. mov [eax+28],cx
  32416. mov [eax+26],cx
  32417. mov [eax+24],cx
  32418. mov [eax+22],cx
  32419. mov [eax+20],cx
  32420. mov [eax+18],cx
  32421. mov [eax+16],cx
  32422. mov [eax+14],cx
  32423. mov [eax+12],cx
  32424. mov [eax+10],cx
  32425. mov [eax+ 8],cx
  32426. mov [eax+ 6],cx
  32427. mov [eax+ 4],cx
  32428. mov [eax+ 2],cx
  32429. mov [eax ],cx
  32430. ret {do not remove - this is for alignment}
  32431. @@done:
  32432. end;
  32433. function StrLenSSE2(S: pointer): PtrInt;
  32434. asm // from GPL strlen32.asm by Agner Fog - www.agner.org/optimize
  32435. test eax,eax
  32436. mov ecx,eax // copy pointer
  32437. jz @null // returns 0 if S=nil
  32438. push eax // save start address
  32439. pxor xmm0,xmm0 // set to zero
  32440. and ecx,0FH // lower 4 bits indicate misalignment
  32441. and eax,-10H // align pointer by 16
  32442. movdqa xmm1,[eax] // read from nearest preceding boundary
  32443. pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
  32444. pmovmskb edx,xmm1 // get one bit for each byte result
  32445. shr edx,cl // shift out false bits
  32446. shl edx,cl // shift back again
  32447. bsf edx,edx // find first 1-bit
  32448. jnz @A200 // found
  32449. // Main loop, search 16 bytes at a time
  32450. @A100: add eax,10H // increment pointer by 16
  32451. movdqa xmm1,[eax] // read 16 bytes aligned
  32452. pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
  32453. pmovmskb edx,xmm1 // get one bit for each byte result
  32454. bsf edx,edx // find first 1-bit
  32455. // (moving the bsf out of the loop and using test here would be faster
  32456. // for long strings on old processors, but we are assuming that most
  32457. // strings are short, and newer processors have higher priority)
  32458. jz @A100 // loop if not found
  32459. @A200: // Zero-byte found. Compute string length
  32460. pop ecx // restore start address
  32461. sub eax,ecx // subtract start address
  32462. add eax,edx // add byte index
  32463. @null:
  32464. end;
  32465. function StrLenSSE42(S: pointer): PtrInt;
  32466. asm // warning: may read up to 15 bytes beyond the string itself
  32467. test eax,eax
  32468. mov edx,eax // copy pointer
  32469. jz @null // returns 0 if S=nil
  32470. xor eax,eax
  32471. pxor xmm0,xmm0
  32472. {$ifdef HASAESNI}
  32473. pcmpistri xmm0,dqword [edx],EQUAL_EACH // comparison result in ecx
  32474. {$else}
  32475. db $66,$0F,$3A,$63,$02,EQUAL_EACH
  32476. {$endif}
  32477. jnz @loop
  32478. mov eax,ecx
  32479. ret
  32480. nop // for @loop alignment
  32481. @loop: add eax,16
  32482. {$ifdef HASAESNI}
  32483. pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH // comparison result in ecx
  32484. {$else}
  32485. db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH
  32486. {$endif}
  32487. jnz @loop
  32488. @ok: add eax,ecx
  32489. ret
  32490. @null: db $f3 // rep ret
  32491. end;
  32492. {$endif DELPHI5OROLDER}
  32493. {$endif PUREPASCAL}
  32494. {$endif CPU64}
  32495. procedure InitRedirectCode;
  32496. begin
  32497. {$ifdef DELPHI5OROLDER}
  32498. StrLen := @StrLenX86;
  32499. FillcharFast := @FillCharX87;
  32500. MoveFast := @MoveX87;
  32501. {$else}
  32502. {$ifdef CPU64}
  32503. {$ifdef HASAESNI}
  32504. if cfSSE42 in CpuFeatures then
  32505. StrLen := @StrLenSSE42 else
  32506. {$endif}
  32507. StrLen := @StrLenSSE2;
  32508. FillcharFast := @FillCharSSE2;
  32509. //MoveFast := @MoveSSE2; // actually slower than RTL's for small blocks
  32510. {$else}
  32511. {$ifdef PUREPASCAL}
  32512. Pointer(@FillCharFast) := SystemFillCharAddress;
  32513. {$else}
  32514. if cfSSE2 in CpuFeatures then begin
  32515. if cfSSE42 in CpuFeatures then
  32516. StrLen := @StrLenSSE42 else
  32517. StrLen := @StrLenSSE2;
  32518. FillcharFast := @FillCharSSE2;
  32519. end else begin
  32520. StrLen := @StrLenX86;
  32521. FillcharFast := @FillCharX87;
  32522. end;
  32523. MoveFast := @MoveX87; // SSE2 is not faster than X87 version on 32 bit CPU
  32524. {$endif PUREPASCAL}
  32525. {$endif CPU64}
  32526. {$endif DELPHI5OROLDER}
  32527. // do redirection from RTL to our fastest version
  32528. {$ifdef DOPATCHTRTL}
  32529. if DebugHook=0 then begin // patch only outside debugging
  32530. RedirectCode(SystemFillCharAddress,@FillcharFast);
  32531. RedirectCode(@System.Move,@MoveFast);
  32532. RedirectCode(SystemRecordCopyAddress,@RecordCopy);
  32533. RedirectCode(SystemFinalizeRecordAddress,@RecordClear);
  32534. RedirectCode(SystemInitializeRecordAddress,@_InitializeRecord);
  32535. {$ifndef UNICODE} // buggy Delphi 2009+ RTL expects a TMonitor.Destroy call
  32536. RedirectCode(@TObject.CleanupInstance,@TObjectCleanupInstance);
  32537. {$endif UNICODE}
  32538. end;
  32539. {$endif DOPATCHTRTL}
  32540. end;
  32541. {$endif CPUARM}
  32542. {$endif FPC}
  32543. { ************ Custom record / dynamic array JSON serialization }
  32544. procedure SaveJSON(const Value; TypeInfo: pointer;
  32545. EnumSetsAsText: boolean; var result: RawUTF8);
  32546. begin
  32547. with DefaultTextWriterJSONClass.CreateOwnedStream do
  32548. try
  32549. if EnumSetsAsText then
  32550. CustomOptions := CustomOptions+[twoEnumSetsAsTextInRecord];
  32551. AddTypedJSON(TypeInfo,Value,EnumSetsAsText,true);
  32552. SetText(result);
  32553. finally
  32554. Free;
  32555. end;
  32556. end;
  32557. function SaveJSON(const Value; TypeInfo: pointer;
  32558. EnumSetsAsText: boolean=false): RawUTF8;
  32559. begin
  32560. SaveJSON(Value,TypeInfo,EnumSetsAsText,result);
  32561. end;
  32562. type
  32563. /// information about one customized JSON serialization
  32564. TJSONCustomParserRegistration = record
  32565. RecordTypeName: RawUTF8;
  32566. DynArrayTypeInfo: pointer;
  32567. RecordTypeInfo: pointer;
  32568. Reader: TDynArrayJSONCustomReader;
  32569. Writer: TDynArrayJSONCustomWriter;
  32570. RecordCustomParser: TJSONRecordAbstract;
  32571. end;
  32572. PJSONCustomParserRegistration = ^TJSONCustomParserRegistration;
  32573. TJSONCustomParserRegistrations = array of TJSONCustomParserRegistration;
  32574. PTJSONCustomParserAbstract = ^TJSONRecordAbstract;
  32575. /// used internally to manage custom record / dynamic array JSON serialization
  32576. // - e.g. used by TTextWriter.RegisterCustomJSONSerializer*()
  32577. TJSONCustomParsers = class
  32578. protected
  32579. fLastDynArrayIndex: integer;
  32580. fLastRecordIndex: integer;
  32581. fParser: TJSONCustomParserRegistrations;
  32582. fParsersCount: Integer;
  32583. fParsers: TDynArrayHashed;
  32584. function TryToGetFromRTTI(aDynArrayTypeInfo, aRecordTypeInfo: pointer): integer;
  32585. function Search(aTypeInfo: pointer; var Reg: TJSONCustomParserRegistration;
  32586. AddIfNotExisting: boolean): integer;
  32587. function DynArraySearch(aDynArrayTypeInfo, aRecordTypeInfo: pointer;
  32588. AddIfNotExisting: boolean=true): integer; overload;
  32589. function DynArraySearch(aDynArrayTypeInfo, aRecordTypeInfo: pointer;
  32590. out Reader: TDynArrayJSONCustomReader): boolean; overload;
  32591. function DynArraySearch(aDynArrayTypeInfo, aRecordTypeInfo: pointer;
  32592. out Writer: TDynArrayJSONCustomWriter; PParser: PTJSONCustomParserAbstract): boolean; overload;
  32593. function RecordSearch(aRecordTypeInfo: pointer;
  32594. AddIfNotExisting: boolean=true): integer; overload;
  32595. function RecordSearch(aRecordTypeInfo: pointer;
  32596. out Reader: TDynArrayJSONCustomReader): boolean; overload;
  32597. function RecordSearch(aRecordTypeInfo: pointer;
  32598. out Writer: TDynArrayJSONCustomWriter; PParser: PTJSONCustomParserAbstract): boolean; overload;
  32599. function RecordSearch(const aTypeName: RawUTF8): integer; overload;
  32600. public
  32601. constructor Create;
  32602. procedure RegisterCallbacks(aTypeInfo: pointer;
  32603. aReader: TDynArrayJSONCustomReader; aWriter: TDynArrayJSONCustomWriter);
  32604. function RegisterFromText(aTypeInfo: pointer;
  32605. const aRTTIDefinition: RawUTF8): TJSONRecordAbstract;
  32606. property Parser: TJSONCustomParserRegistrations read fParser;
  32607. property ParsersCount: Integer read fParsersCount;
  32608. end;
  32609. var
  32610. GlobalJSONCustomParsers: TJSONCustomParsers;
  32611. constructor TJSONCustomParsers.Create;
  32612. begin
  32613. fParsers.InitSpecific(TypeInfo(TJSONCustomParserRegistrations),
  32614. fParser,djRawUTF8,@fParsersCount,true);
  32615. GarbageCollectorFreeAndNil(GlobalJSONCustomParsers,self);
  32616. end;
  32617. function TJSONCustomParsers.TryToGetFromRTTI(aDynArrayTypeInfo,
  32618. aRecordTypeInfo: pointer): integer;
  32619. var Reg: TJSONCustomParserRegistration;
  32620. RegRoot: TJSONCustomParserRTTI;
  32621. info: PTypeInfo;
  32622. added: boolean;
  32623. ndx: integer;
  32624. begin
  32625. result := -1;
  32626. info := GetTypeInfo(aRecordTypeInfo,tkRecordTypeOrSet);
  32627. if info=nil then
  32628. exit; // not enough RTTI
  32629. Reg.RecordTypeInfo := aRecordTypeInfo;
  32630. Reg.DynArrayTypeInfo := aDynArrayTypeInfo;
  32631. TypeInfoToName(Reg.RecordTypeInfo,Reg.RecordTypeName);
  32632. if Reg.RecordTypeName='' then
  32633. exit; // we need a type name!
  32634. RegRoot := TJSONCustomParserRTTI.CreateFromTypeName('',Reg.RecordTypeName);
  32635. {$ifdef ISDELPHI2010}
  32636. if RegRoot=nil then begin
  32637. inc(PByte(info),info^.ManagedCount*sizeof(TFieldInfo)-sizeof(TFieldInfo));
  32638. inc(PByte(info),info^.NumOps*sizeof(pointer)); // jump RecOps[]
  32639. if info^.AllCount=0 then
  32640. exit; // not enough RTTI -> avoid exception in constructor below
  32641. end;
  32642. {$else}
  32643. if RegRoot=nil then
  32644. exit; // not enough RTTI for older versions of Delphi
  32645. {$endif}
  32646. Reg.RecordCustomParser := TJSONRecordRTTI.Create(Reg.RecordTypeInfo,RegRoot);
  32647. Reg.Reader := Reg.RecordCustomParser.CustomReader;
  32648. Reg.Writer := Reg.RecordCustomParser.CustomWriter;
  32649. if self=nil then
  32650. if GlobalJSONCustomParsers<>nil then // may have been set just above
  32651. self := GlobalJSONCustomParsers else
  32652. self := TJSONCustomParsers.Create;
  32653. ndx := fParsers.FindHashedForAdding(Reg.RecordTypeName,added);
  32654. if not added then
  32655. exit; // name should be unique
  32656. fParser[ndx] := Reg;
  32657. result := ndx;
  32658. end;
  32659. function TJSONCustomParsers.DynArraySearch(aDynArrayTypeInfo,aRecordTypeInfo: pointer;
  32660. AddIfNotExisting: boolean): Integer;
  32661. begin
  32662. if self<>nil then
  32663. if (aDynArrayTypeInfo<>nil) and (fParsersCount<>0) then
  32664. if fParser[fLastDynArrayIndex].DynArrayTypeInfo=aDynArrayTypeInfo then begin
  32665. result := fLastDynArrayIndex;
  32666. exit;
  32667. end else begin
  32668. if aRecordTypeInfo=nil then // record RTTI not specified: guess now
  32669. aRecordTypeInfo := DynArrayTypeInfoToRecordInfo(aDynArrayTypeInfo);
  32670. if aRecordTypeInfo=nil then begin
  32671. for result := 0 to fParsersCount-1 do
  32672. if fParser[result].DynArrayTypeInfo=aDynArrayTypeInfo then begin
  32673. fLastDynArrayIndex := result;
  32674. exit;
  32675. end;
  32676. end else
  32677. if (cardinal(fLastRecordIndex)<cardinal(fParsersCount)) and
  32678. (fParser[fLastRecordIndex].RecordTypeInfo=aRecordTypeInfo) then begin
  32679. result := fLastRecordIndex;
  32680. exit;
  32681. end else
  32682. for result := 0 to fParsersCount-1 do
  32683. with fParser[result] do
  32684. if (DynArrayTypeInfo=aDynArrayTypeInfo) or
  32685. (RecordTypeInfo=aRecordTypeInfo) then begin
  32686. fLastDynArrayIndex := result;
  32687. fLastRecordIndex := result;
  32688. exit;
  32689. end;
  32690. end;
  32691. if AddIfNotExisting then begin
  32692. result := TryToGetFromRTTI(aDynArrayTypeInfo,aRecordTypeInfo);
  32693. if result>=0 then
  32694. GlobalJSONCustomParsers.fLastRecordIndex := result;
  32695. end else
  32696. result := -1;
  32697. end;
  32698. function TJSONCustomParsers.DynArraySearch(aDynArrayTypeInfo,aRecordTypeInfo: pointer;
  32699. out Reader: TDynArrayJSONCustomReader): boolean;
  32700. var ndx: integer;
  32701. begin
  32702. ndx := DynArraySearch(aDynArrayTypeInfo,aRecordTypeInfo);
  32703. if (ndx>=0) and Assigned(GlobalJSONCustomParsers.fParser[ndx].Reader) then begin
  32704. Reader := GlobalJSONCustomParsers.fParser[ndx].Reader;
  32705. result := true;
  32706. end else
  32707. result := false;
  32708. end;
  32709. function TJSONCustomParsers.DynArraySearch(aDynArrayTypeInfo,aRecordTypeInfo: pointer;
  32710. out Writer: TDynArrayJSONCustomWriter; PParser: PTJSONCustomParserAbstract): boolean;
  32711. var ndx: integer;
  32712. begin
  32713. ndx := DynArraySearch(aDynArrayTypeInfo,aRecordTypeInfo);
  32714. if (ndx>=0) and Assigned(GlobalJSONCustomParsers.fParser[ndx].Writer) then begin
  32715. Writer := GlobalJSONCustomParsers.fParser[ndx].Writer;
  32716. if PParser<>nil then
  32717. PParser^ := GlobalJSONCustomParsers.fParser[ndx].RecordCustomParser;
  32718. result := true;
  32719. end else
  32720. result := false;
  32721. end;
  32722. function TJSONCustomParsers.RecordSearch(aRecordTypeInfo: pointer;
  32723. AddIfNotExisting: boolean): integer;
  32724. begin
  32725. if aRecordTypeInfo=nil then begin
  32726. result := -1;
  32727. exit;
  32728. end;
  32729. if self<>nil then
  32730. if (cardinal(fLastRecordIndex)<cardinal(fParsersCount)) and
  32731. (fParser[fLastRecordIndex].RecordTypeInfo=aRecordTypeInfo) then begin
  32732. result := fLastRecordIndex;
  32733. exit;
  32734. end else
  32735. for result := 0 to fParsersCount-1 do
  32736. if fParser[result].RecordTypeInfo=aRecordTypeInfo then begin
  32737. fLastRecordIndex := result;
  32738. exit;
  32739. end;
  32740. if AddIfNotExisting then begin
  32741. result := TryToGetFromRTTI(nil,aRecordTypeInfo);
  32742. if result>=0 then
  32743. GlobalJSONCustomParsers.fLastRecordIndex := result;
  32744. end else
  32745. result := -1;
  32746. end;
  32747. function TJSONCustomParsers.RecordSearch(const aTypeName: RawUTF8): integer;
  32748. begin
  32749. if self=nil then
  32750. result := -1 else
  32751. if (cardinal(fLastRecordIndex)<cardinal(fParsersCount)) and
  32752. IdemPropNameU(fParser[fLastRecordIndex].RecordTypeName,aTypeName) then
  32753. result := fLastRecordIndex else begin
  32754. result := fParsers.FindHashed(aTypeName);
  32755. if result>=0 then
  32756. fLastRecordIndex := result;
  32757. end;
  32758. end;
  32759. function TJSONCustomParsers.RecordSearch(aRecordTypeInfo: pointer;
  32760. out Reader: TDynArrayJSONCustomReader): boolean;
  32761. var ndx: integer;
  32762. begin
  32763. ndx := RecordSearch(aRecordTypeInfo);
  32764. if (ndx>=0) and Assigned(GlobalJSONCustomParsers.fParser[ndx].Reader) then begin
  32765. Reader := GlobalJSONCustomParsers.fParser[ndx].Reader;
  32766. result := true;
  32767. end else
  32768. result := false;
  32769. end;
  32770. function TJSONCustomParsers.RecordSearch(aRecordTypeInfo: pointer;
  32771. out Writer: TDynArrayJSONCustomWriter; PParser: PTJSONCustomParserAbstract): boolean;
  32772. var ndx: integer;
  32773. begin
  32774. result := false;
  32775. ndx := RecordSearch(aRecordTypeInfo);
  32776. if (ndx>=0) and Assigned(GlobalJSONCustomParsers.fParser[ndx].Writer) then begin
  32777. Writer := GlobalJSONCustomParsers.fParser[ndx].Writer;
  32778. if PParser<>nil then
  32779. PParser^ := GlobalJSONCustomParsers.fParser[ndx].RecordCustomParser;
  32780. result := true;
  32781. end;
  32782. end;
  32783. function TJSONCustomParsers.Search(aTypeInfo: pointer;
  32784. var Reg: TJSONCustomParserRegistration; AddIfNotExisting: boolean): integer;
  32785. var added: boolean;
  32786. begin
  32787. if (aTypeInfo=nil) or (self=nil) then
  32788. raise ESynException.CreateUTF8('%.Search(%)',[self,aTypeInfo]);
  32789. FillcharFast(Reg,sizeof(Reg),0);
  32790. case PTypeKind(aTypeInfo)^ of
  32791. tkDynArray: begin
  32792. Reg.DynArrayTypeInfo := aTypeInfo;
  32793. Reg.RecordTypeInfo := DynArrayTypeInfoToRecordInfo(aTypeInfo);
  32794. result := DynArraySearch(Reg.DynArrayTypeInfo,Reg.RecordTypeInfo,false);
  32795. end;
  32796. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  32797. Reg.DynArrayTypeInfo := nil;
  32798. Reg.RecordTypeInfo := aTypeInfo;
  32799. result := RecordSearch(Reg.RecordTypeInfo,false);
  32800. end;
  32801. else raise ESynException.CreateUTF8('%.Search(kind=%) not DynArray or Record',
  32802. [self,PByte(aTypeInfo)^]);
  32803. end;
  32804. if not AddIfNotExisting then
  32805. exit;
  32806. TypeInfoToName(Reg.RecordTypeInfo,Reg.RecordTypeName);
  32807. if Reg.RecordTypeName='' then
  32808. TypeInfoToName(Reg.DynArrayTypeInfo,Reg.RecordTypeName);
  32809. if Reg.RecordTypeName='' then
  32810. raise ESynException.CreateUTF8('%.Search(%) has no type name',[self,aTypeInfo]);
  32811. if result<0 then
  32812. result := fParsers.FindHashedForAdding(Reg.RecordTypeName,added);
  32813. end;
  32814. procedure TJSONCustomParsers.RegisterCallbacks(aTypeInfo: pointer;
  32815. aReader: TDynArrayJSONCustomReader; aWriter: TDynArrayJSONCustomWriter);
  32816. var Reg: TJSONCustomParserRegistration;
  32817. ForAdding: boolean;
  32818. ndx: integer;
  32819. begin
  32820. if self=nil then
  32821. self := TJSONCustomParsers.Create;
  32822. ForAdding := Assigned(aReader) or Assigned(aWriter);
  32823. ndx := Search(aTypeInfo,Reg,ForAdding);
  32824. if ForAdding then begin
  32825. Reg.Writer := aWriter;
  32826. Reg.Reader := aReader;
  32827. fParser[ndx] := Reg;
  32828. end else
  32829. if ndx>=0 then begin
  32830. fParsers.Delete(ndx);
  32831. fParsers.ReHash;
  32832. end;
  32833. end;
  32834. function TJSONCustomParsers.RegisterFromText(aTypeInfo: pointer;
  32835. const aRTTIDefinition: RawUTF8): TJSONRecordAbstract;
  32836. var Reg: TJSONCustomParserRegistration;
  32837. ForAdding: boolean;
  32838. ndx: integer;
  32839. begin
  32840. if self=nil then
  32841. self := TJSONCustomParsers.Create;
  32842. ForAdding := aRTTIDefinition<>'';
  32843. ndx := Search(aTypeInfo,Reg,ForAdding);
  32844. if ForAdding then begin
  32845. result := TJSONRecordTextDefinition.FromCache(aTypeInfo,aRTTIDefinition);
  32846. Reg.Reader := result.CustomReader;
  32847. Reg.Writer := result.CustomWriter;
  32848. Reg.RecordCustomParser := result;
  32849. fParser[ndx] := Reg;
  32850. end else begin
  32851. result := nil;
  32852. if ndx>=0 then begin
  32853. fParsers.Delete(ndx);
  32854. fParsers.ReHash;
  32855. end;
  32856. end;
  32857. end;
  32858. function RecordSaveJSON(const Rec; TypeInfo: pointer; EnumSetsAsText: boolean): RawUTF8;
  32859. begin
  32860. SaveJSON(Rec,TypeInfo,EnumSetsAsText,result);
  32861. end;
  32862. const
  32863. NULCHAR: AnsiChar = #0;
  32864. function RecordLoadJSON(var Rec; JSON: PUTF8Char; TypeInfo: pointer;
  32865. EndOfObject: PUTF8Char=nil): PUTF8Char;
  32866. var wasString, wasValid: boolean;
  32867. Reader: TDynArrayJSONCustomReader;
  32868. EndOfObj: AnsiChar;
  32869. Val: PUTF8Char;
  32870. begin // code below must match TTextWriter.AddRecordJSON
  32871. result := nil; // indicates error
  32872. if JSON=nil then
  32873. exit;
  32874. if (@Rec=nil) or (TypeInfo=nil) then
  32875. raise ESynException.CreateUTF8('Invalid RecordLoadJSON(%) call',[TypeInfo]);
  32876. if JSON^=' ' then repeat inc(JSON); if JSON^=#0 then exit; until JSON^<>' ';
  32877. if PCardinal(JSON)^=JSON_BASE64_MAGIC_QUOTE then begin
  32878. if not (PTypeKind(TypeInfo)^ in tkRecordTypes) then
  32879. raise ESynException.CreateUTF8('RecordLoadJSON(%/%)',
  32880. [PShortString(@PTypeInfo(TypeInfo).NameLen)^,PByte(TypeInfo)^]);
  32881. Val := GetJSONField(JSON,JSON,@wasString,@EndOfObj);
  32882. if (Val=nil) or (not wasString) or
  32883. (PInteger(Val)^ and $00ffffff<>JSON_BASE64_MAGIC) or
  32884. (RecordLoad(Rec,pointer(Base64ToBin(Val+3)),TypeInfo)=nil) then
  32885. exit; // invalid content
  32886. end else begin
  32887. if not GlobalJSONCustomParsers.RecordSearch(TypeInfo,Reader) then
  32888. exit;
  32889. JSON := Reader(JSON,Rec,wasValid);
  32890. if not wasValid then
  32891. exit;
  32892. if (JSON<>nil) and (JSON^ in [#1..' ']) then
  32893. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  32894. if (JSON<>nil) and (JSON^<>#0) then begin
  32895. EndOfObj := JSON^;
  32896. inc(JSON);
  32897. end else
  32898. EndOfObj := #0;
  32899. end;
  32900. if JSON=nil then
  32901. result := @NULCHAR else
  32902. result := JSON;
  32903. if EndOfObject<>nil then
  32904. EndOfObject^ := EndOfObj;
  32905. end;
  32906. function RecordLoadJSON(var Rec; const JSON: RawUTF8; TypeInfo: pointer): boolean;
  32907. var tmp: TSynTempBuffer;
  32908. begin
  32909. tmp.Init(JSON);
  32910. try
  32911. result := RecordLoadJSON(Rec,tmp.buf,TypeInfo)<>nil;
  32912. finally
  32913. tmp.Done;
  32914. end;
  32915. end;
  32916. { TJSONCustomParserCustom }
  32917. constructor TJSONCustomParserCustom.Create(const aPropertyName, aCustomTypeName: RawUTF8);
  32918. begin
  32919. inherited Create(aPropertyName,ptCustom);
  32920. fCustomTypeName := aCustomTypeName;
  32921. end;
  32922. procedure TJSONCustomParserCustom.FinalizeItem(Data: Pointer);
  32923. begin // nothing to be done by default
  32924. end;
  32925. { TJSONCustomParserCustomSimple }
  32926. constructor TJSONCustomParserCustomSimple.Create(
  32927. const aPropertyName, aCustomTypeName: RawUTF8; aCustomType: pointer);
  32928. var info: PTypeInfo;
  32929. kind: TTypeKind;
  32930. begin
  32931. inherited Create(aPropertyName,aCustomTypeName);
  32932. fCustomTypeInfo := aCustomType;
  32933. if IdemPropNameU(aCustomTypeName,'TGUID') then begin
  32934. fKnownType := ktGUID;
  32935. fDataSize := sizeof(TGUID);
  32936. end else
  32937. if fCustomTypeInfo<>nil then begin
  32938. TypeInfoToName(fCustomTypeInfo,fCustomTypeName,aCustomTypeName);
  32939. kind := PTypeKind(fCustomTypeInfo)^;
  32940. info := GetTypeInfo(fCustomTypeInfo,[tkEnumeration,tkSet,tkArray,tkDynArray]);
  32941. fTypeData := info;
  32942. if info<>nil then
  32943. case kind of
  32944. tkEnumeration, tkSet: begin
  32945. case info^.EnumType of
  32946. otSByte,otUByte: fDataSize := 1;
  32947. otSWord,otUWord: fDataSize := 2;
  32948. otSLong,otULong: fDataSize := 4;
  32949. end;
  32950. if kind=tkEnumeration then
  32951. fKnownType := ktEnumeration else
  32952. fKnownType := ktSet;
  32953. exit; // success
  32954. end;
  32955. tkArray: begin
  32956. if info^.dimCount<>1 then
  32957. raise ESynException.CreateUTF8(
  32958. '%.Create("%") supports only one dimension static array)',
  32959. [self,fCustomTypeName]);
  32960. fKnownType := ktStaticArray;
  32961. fDataSize := info^.arraySize;
  32962. fFixedSize := fDataSize div info^.elCount;
  32963. fNestedArray := TJSONCustomParserRTTI.CreateFromRTTI(
  32964. '',Deref(info^.arrayType),fFixedSize);
  32965. exit; // success
  32966. end;
  32967. tkDynArray: begin
  32968. fKnownType := ktDynamicArray;
  32969. exit; // success
  32970. end;
  32971. end;
  32972. raise ESynException.CreateUTF8('%.Create("%") unsupported type: % (%)',
  32973. [self,fCustomTypeName,ToText(kind)^,ord(kind)]);
  32974. end;
  32975. end;
  32976. constructor TJSONCustomParserCustomSimple.CreateFixedArray(
  32977. const aPropertyName: RawUTF8; aFixedSize: cardinal);
  32978. begin
  32979. inherited Create(aPropertyName,FormatUTF8('Fixed%Byte',[aFixedSize]));
  32980. fKnownType := ktFixedArray;
  32981. fFixedSize := aFixedSize;
  32982. fDataSize := aFixedSize;
  32983. end;
  32984. destructor TJSONCustomParserCustomSimple.Destroy;
  32985. begin
  32986. inherited;
  32987. fNestedArray.Free;
  32988. end;
  32989. procedure TJSONCustomParserCustomSimple.CustomWriter(
  32990. const aWriter: TTextWriter; const aValue);
  32991. var i: integer;
  32992. V: PByte;
  32993. begin
  32994. case fKnownType of
  32995. ktStaticArray: begin
  32996. aWriter.Add('[');
  32997. V := @aValue;
  32998. for i := 1 to PTypeInfo(fTypeData)^.elCount do begin
  32999. fNestedArray.WriteOneLevel(aWriter,V,[]);
  33000. aWriter.Add(',');
  33001. end;
  33002. aWriter.CancelLastComma;
  33003. aWriter.Add(']');
  33004. end;
  33005. ktEnumeration, ktSet:
  33006. aWriter.AddTypedJSON(fCustomTypeInfo,aValue,
  33007. twoEnumSetsAsTextInRecord in aWriter.CustomOptions, true);
  33008. ktDynamicArray:
  33009. raise ESynException.CreateUTF8('%.CustomWriter("%"): Unsupported',
  33010. [self,fCustomTypeName]);
  33011. else begin // encoded as JSON strings
  33012. aWriter.Add('"');
  33013. case fKnownType of
  33014. ktGUID:
  33015. aWriter.Add(TGUID(aValue));
  33016. ktFixedArray:
  33017. aWriter.AddBinToHex(@aValue,fFixedSize);
  33018. end;
  33019. aWriter.Add('"');
  33020. end;
  33021. end;
  33022. end;
  33023. function TJSONCustomParserCustomSimple.CustomReader(P: PUTF8Char;
  33024. var aValue; out EndOfObject: AnsiChar): PUTF8Char;
  33025. var PropValue: PUTF8Char;
  33026. i,i32: integer;
  33027. wasString: boolean;
  33028. Val: PByte;
  33029. begin
  33030. result := nil; // indicates error
  33031. case fKnownType of
  33032. ktStaticArray: begin
  33033. if P^<>'[' then
  33034. exit; // we expect a true array here
  33035. P := GotoNextNotSpace(P+1);
  33036. if JSONArrayCount(P)<>PTypeInfo(fTypeData)^.elCount then
  33037. exit; // invalid number of items
  33038. Val := @aValue;
  33039. for i := 1 to PTypeInfo(fTypeData)^.elCount do
  33040. if not fNestedArray.ReadOneLevel(P,Val,[]) then
  33041. exit else
  33042. if P=nil then
  33043. exit;
  33044. P := GotoNextNotSpace(P);
  33045. EndOfObject := P^;
  33046. if P^ in [',','}'] then
  33047. inc(P);
  33048. result := P;
  33049. end;
  33050. ktDynamicArray:
  33051. raise ESynException.CreateUTF8('%.CustomReader("%"): Unsupported',
  33052. [self,fCustomTypeName]);
  33053. ktSet: begin
  33054. i32 := GetSetNameValue(fCustomTypeInfo,P,EndOfObject);
  33055. MoveFast(i32,aValue,fDataSize);
  33056. result := P;
  33057. end;
  33058. else begin // encoded as JSON strings
  33059. PropValue := GetJSONField(P,P,@wasString,@EndOfObject);
  33060. if PropValue=nil then
  33061. exit;
  33062. if P=nil then
  33063. P := @NULCHAR; // result=nil indicates error
  33064. case fKnownType of
  33065. ktGUID:
  33066. if wasString and (TextToGUID(PropValue,@aValue)<>nil) then
  33067. result := P;
  33068. ktEnumeration: begin
  33069. if wasString then
  33070. i32 := GetEnumNameValue(fCustomTypeInfo,PropValue,StrLen(PropValue),true) else
  33071. i32 := GetCardinal(PropValue);
  33072. if i32<0 then
  33073. exit;
  33074. MoveFast(i32,aValue,fDataSize);
  33075. result := P;
  33076. end;
  33077. ktFixedArray:
  33078. if wasString and (StrLen(PropValue)=fFixedSize*2) and
  33079. SynCommons.HexToBin(PAnsiChar(PropValue),@aValue,fFixedSize) then
  33080. result := P;
  33081. end;
  33082. end;
  33083. end;
  33084. end;
  33085. { TJSONCustomParserCustomRecord }
  33086. {constructor TJSONCustomParserCustomRecord.Create(
  33087. const aPropertyName, aCustomTypeName: RawUTF8);
  33088. begin
  33089. inherited Create(aPropertyName,aCustomTypeName);
  33090. fCustomTypeIndex := GlobalJSONCustomParsers.RecordSearch(aCustomTypeName);
  33091. if fCustomTypeIndex<0 then
  33092. raise ESynException.CreateUTF8('%.Create(unknown "%" type)',
  33093. [self,aCustomTypeName]);
  33094. with GlobalJSONCustomParsers.fParser[fCustomTypeIndex] do begin
  33095. fCustomTypeInfo := RecordTypeInfo;
  33096. fCustomTypeName := RecordTypeName;
  33097. end;
  33098. fDataSize := RecordTypeInfoSize(fCustomTypeInfo);
  33099. if fDataSize=0 then
  33100. raise ESynException.CreateUTF8('%.Create("%" non record type)',
  33101. [self,aCustomTypeName]);
  33102. end;}
  33103. constructor TJSONCustomParserCustomRecord.Create(
  33104. const aPropertyName: RawUTF8; aCustomTypeIndex: integer);
  33105. begin
  33106. fCustomTypeIndex := aCustomTypeIndex;
  33107. with GlobalJSONCustomParsers.fParser[fCustomTypeIndex] do begin
  33108. inherited Create(aPropertyName,RecordTypeName);
  33109. fCustomTypeInfo := RecordTypeInfo;
  33110. fCustomTypeName := RecordTypeName;
  33111. end;
  33112. fDataSize := RecordTypeInfoSize(fCustomTypeInfo);
  33113. end;
  33114. function TJSONCustomParserCustomRecord.GetJSONCustomParserRegistration: pointer;
  33115. begin
  33116. result := nil;
  33117. if GlobalJSONCustomParsers<>nil then begin
  33118. if (Cardinal(fCustomTypeIndex)>=Cardinal(GlobalJSONCustomParsers.fParsersCount)) or
  33119. not IdemPropNameU(fCustomTypeName,
  33120. GlobalJSONCustomParsers.fParser[fCustomTypeIndex].RecordTypeName) then
  33121. fCustomTypeIndex := GlobalJSONCustomParsers.RecordSearch(fCustomTypeInfo);
  33122. if fCustomTypeIndex>=0 then
  33123. result := @GlobalJSONCustomParsers.fParser[fCustomTypeIndex];
  33124. end;
  33125. if result=nil then
  33126. raise ESynException.CreateUTF8(
  33127. '%: "%" type should not have been un-registered',[self,fCustomTypeName]);
  33128. end;
  33129. procedure TJSONCustomParserCustomRecord.CustomWriter(
  33130. const aWriter: TTextWriter; const aValue);
  33131. var parser: PJSONCustomParserRegistration;
  33132. begin
  33133. parser := GetJSONCustomParserRegistration;
  33134. parser^.Writer(aWriter,aValue);
  33135. end;
  33136. function TJSONCustomParserCustomRecord.CustomReader(P: PUTF8Char;
  33137. var aValue; out EndOfObject: AnsiChar): PUTF8Char;
  33138. var valid: boolean;
  33139. callback: PJSONCustomParserRegistration; // D5/D6 Internal error: C3890
  33140. begin
  33141. callback := GetJSONCustomParserRegistration;
  33142. result := callback^.Reader(P,aValue,valid);
  33143. if not valid then
  33144. result := nil;
  33145. if result=nil then
  33146. exit;
  33147. EndOfObject := result^;
  33148. if result^ in [',','}',']'] then
  33149. inc(result);
  33150. end;
  33151. procedure TJSONCustomParserCustomRecord.FinalizeItem(Data: Pointer);
  33152. begin
  33153. RecordClear(Data^,fCustomTypeInfo);
  33154. end;
  33155. { TJSONCustomParserRTTI }
  33156. var
  33157. GlobalCustomJSONSerializerFromTextSimpleType_: TRawUTF8ListHashed;
  33158. function GlobalCustomJSONSerializerFromTextSimpleType: TRawUTF8ListHashed;
  33159. begin
  33160. if GlobalCustomJSONSerializerFromTextSimpleType_=nil then begin
  33161. GarbageCollectorFreeAndNil(GlobalCustomJSONSerializerFromTextSimpleType_,
  33162. TRawUTF8ListHashed.Create(false));
  33163. GlobalCustomJSONSerializerFromTextSimpleType_.CaseSensitive := false;
  33164. GlobalCustomJSONSerializerFromTextSimpleType_.AddObjectIfNotExisting(
  33165. 'TGUID',{$ifdef ISDELPHI2010}TypeInfo(TGUID){$else}nil{$endif});
  33166. end;
  33167. result := GlobalCustomJSONSerializerFromTextSimpleType_;
  33168. end;
  33169. /// if defined, will try to mimic the default record alignment
  33170. // -> is buggy, and compiler revision specific -> we would rather use packed records
  33171. {.$define ALIGNCUSTOMREC}
  33172. constructor TJSONCustomParserRTTI.Create(const aPropertyName: RawUTF8;
  33173. aPropertyType: TJSONCustomParserRTTIType);
  33174. begin
  33175. fPropertyName := aPropertyName;
  33176. fPropertyType := aPropertyType;
  33177. end;
  33178. class function TJSONCustomParserRTTI.TypeNameToSimpleRTTIType(TypeName: PUTF8Char;
  33179. TypeNameLen: Integer; var ItemTypeName: RawUTF8): TJSONCustomParserRTTIType;
  33180. const
  33181. SORTEDMAX = {$ifdef NOVARIANTS}30{$else}31{$endif};
  33182. SORTEDNAMES: array[0..SORTEDMAX] of PUTF8Char =
  33183. ('ARRAY','BOOLEAN','BYTE','CARDINAL','CURRENCY',
  33184. 'DOUBLE','EXTENDED','INT64','INTEGER','PTRINT','PTRUINT',
  33185. 'RAWBYTESTRING','RAWJSON','RAWUTF8','RECORD','SINGLE',
  33186. 'STRING','SYNUNICODE','TCREATETIME','TDATETIME','TGUID',
  33187. 'TID','TMODTIME','TRECORDREFERENCE','TRECORDREFERENCETOBEDELETED',
  33188. 'TRECORDVERSION','TSQLRAWBLOB','TTIMELOG','UTF8STRING',
  33189. {$ifndef NOVARIANTS}'VARIANT',{$endif}
  33190. 'WIDESTRING','WORD');
  33191. // warning: recognized types should match at binary storage level!
  33192. SORTEDTYPES: array[0..SORTEDMAX] of TJSONCustomParserRTTIType =
  33193. (ptArray,ptBoolean,ptByte,ptCardinal,ptCurrency,
  33194. ptDouble,ptExtended,ptInt64,ptInteger,ptPtrInt,ptPtrUInt,
  33195. ptRawByteString,ptRawJSON,ptRawUTF8,ptRecord,ptSingle,
  33196. ptString,ptSynUnicode,ptTimeLog,ptDateTime,ptGUID,
  33197. ptID,ptTimeLog,ptInt64,ptInt64,
  33198. ptInt64,ptRawByteString,ptTimeLog,ptRawUTF8,
  33199. {$ifndef NOVARIANTS}ptVariant,{$endif}
  33200. ptWideString,ptWord);
  33201. var ndx: integer;
  33202. begin
  33203. UpperCaseCopy(TypeName,TypeNameLen,ItemTypeName);
  33204. //for ndx := 1 to SORTEDMAX do assert(StrComp(SORTEDNAMES[ndx],SORTEDNAMES[ndx-1])>0,SORTEDNAMES[ndx]);
  33205. ndx := FastFindPUTF8CharSorted(@SORTEDNAMES,SORTEDMAX,pointer(ItemTypeName));
  33206. if ndx>=0 then
  33207. result := SORTEDTYPES[ndx] else
  33208. result := ptCustom;
  33209. end;
  33210. class function TJSONCustomParserRTTI.TypeNameToSimpleRTTIType(
  33211. const TypeName: RawUTF8): TJSONCustomParserRTTIType;
  33212. var ItemTypeName: RawUTF8;
  33213. begin
  33214. if TypeName='' then
  33215. result := ptCustom else
  33216. result := TypeNameToSimpleRTTIType(Pointer(TypeName),length(TypeName),ItemTypeName);
  33217. end;
  33218. class function TJSONCustomParserRTTI.TypeNameToSimpleRTTIType(
  33219. TypeName: PShortString): TJSONCustomParserRTTIType;
  33220. var ItemTypeName: RawUTF8;
  33221. begin
  33222. if TypeName=nil then
  33223. result := ptCustom else
  33224. result := TypeNameToSimpleRTTIType(@TypeName^[1],Ord(TypeName^[0]),ItemTypeName);
  33225. end;
  33226. class function TJSONCustomParserRTTI.TypeInfoToSimpleRTTIType(Info: pointer;
  33227. ItemSize: integer): TJSONCustomParserRTTIType;
  33228. begin
  33229. result := ptCustom;
  33230. if Info=nil then
  33231. exit;
  33232. case PTypeKind(Info)^ of
  33233. tkLString{$ifdef FPC},tkLStringOld{$endif}: result := ptRawUTF8;
  33234. tkWString: result := ptWideString;
  33235. {$ifdef UNICODE}
  33236. tkUString: result := ptSynUnicode;
  33237. tkClassRef, tkPointer, tkProcedure:
  33238. case ItemSize of
  33239. 1: result := ptByte;
  33240. 2: result := ptWord;
  33241. 4: result := ptCardinal;
  33242. 8: result := ptInt64;
  33243. else result := ptPtrInt;
  33244. end;
  33245. {$endif}
  33246. {$ifndef NOVARIANTS}
  33247. tkVariant: result := ptVariant;
  33248. {$endif}
  33249. tkDynArray: result := ptArray;
  33250. tkChar: result := ptByte;
  33251. tkWChar: result := ptWord;
  33252. tkClass, tkMethod, tkInterface: result := ptPtrInt;
  33253. tkInteger, tkSet:
  33254. case GetTypeInfo(Info,[tkInteger,tkSet])^.IntegerType of
  33255. otSByte,otUByte: result := ptByte;
  33256. otSWord,otUWord: result := ptWord;
  33257. otSLong: result := ptInteger;
  33258. otULong: result := ptCardinal;
  33259. end;
  33260. tkInt64: result := ptInt64;
  33261. {$ifdef FPC}
  33262. tkBool: result := ptBoolean;
  33263. {$else}
  33264. tkEnumeration:
  33265. if Info=TypeInfo(boolean) then
  33266. result := ptBoolean;
  33267. // other enumerates will use TJSONCustomParserCustomSimple below
  33268. {$endif}
  33269. tkFloat:
  33270. case GetTypeInfo(Info,tkFloat)^.FloatType of
  33271. ftSingle: result := ptSingle;
  33272. ftDoub: result := ptDouble;
  33273. ftCurr: result := ptCurrency;
  33274. ftExtended: result := ptExtended;
  33275. // ftComp: not implemented yet
  33276. end;
  33277. end;
  33278. end;
  33279. class function TJSONCustomParserRTTI.CreateFromRTTI(
  33280. const PropertyName: RawUTF8; Info: pointer; ItemSize: integer): TJSONCustomParserRTTI;
  33281. var Item: PTypeInfo absolute Info;
  33282. ItemType: TJSONCustomParserRTTIType;
  33283. ItemTypeName: RawUTF8;
  33284. ndx: integer;
  33285. begin
  33286. if Item=nil then // no RTTI -> stored as hexa string
  33287. result := TJSONCustomParserCustomSimple.CreateFixedArray(PropertyName,ItemSize) else begin
  33288. ItemType := TypeNameToSimpleRTTIType(PUTF8Char(@Item.NameLen)+1,Item.NameLen,ItemTypeName);
  33289. if ItemType=ptCustom then
  33290. ItemType := TypeInfoToSimpleRTTIType(Info,ItemSize);
  33291. if ItemType=ptCustom then
  33292. if Item^.kind in [tkEnumeration,tkArray,tkDynArray] then
  33293. result := TJSONCustomParserCustomSimple.Create(
  33294. PropertyName,ItemTypeName,Item) else begin
  33295. ndx := GlobalJSONCustomParsers.RecordSearch(Item);
  33296. if ndx<0 then
  33297. ndx := GlobalJSONCustomParsers.RecordSearch(ItemTypeName);
  33298. if ndx<0 then
  33299. raise ESynException.CreateUTF8('%.CreateFromRTTI("%")',
  33300. [self,ItemTypeName]);
  33301. result := TJSONCustomParserCustomRecord.Create(PropertyName,ndx);
  33302. end else
  33303. result := TJSONCustomParserRTTI.Create(PropertyName,ItemType);
  33304. end;
  33305. if ItemSize<>0 then
  33306. result.fDataSize := ItemSize;
  33307. end;
  33308. class function TJSONCustomParserRTTI.CreateFromTypeName(
  33309. const aPropertyName, aCustomRecordTypeName: RawUTF8): TJSONCustomParserRTTI;
  33310. var ndx: integer;
  33311. begin
  33312. ndx := GlobalCustomJSONSerializerFromTextSimpleType.IndexOf(aCustomRecordTypeName);
  33313. if ndx>=0 then
  33314. result := TJSONCustomParserCustomSimple.Create(
  33315. aPropertyName,aCustomRecordTypeName,
  33316. GlobalCustomJSONSerializerFromTextSimpleType_.Objects[ndx]) else begin
  33317. ndx := GlobalJSONCustomParsers.RecordSearch(aCustomRecordTypeName);
  33318. if ndx<0 then
  33319. result := nil else
  33320. result := TJSONCustomParserCustomRecord.Create(aPropertyName,ndx);
  33321. end;
  33322. end;
  33323. procedure TJSONCustomParserRTTI.ComputeFullPropertyName;
  33324. var i: integer;
  33325. begin
  33326. for i := 0 to high(NestedProperty) do begin
  33327. NestedProperty[i].ComputeFullPropertyName;
  33328. if fFullPropertyName<>'' then
  33329. NestedProperty[i].fFullPropertyName :=
  33330. fFullPropertyName+'.'+NestedProperty[i].fPropertyName;
  33331. end;
  33332. end;
  33333. procedure TJSONCustomParserRTTI.ComputeNestedDataSize;
  33334. var i: integer;
  33335. begin
  33336. assert(fNestedDataSize=0);
  33337. fNestedDataSize := 0;
  33338. for i := 0 to high(NestedProperty) do begin
  33339. NestedProperty[i].ComputeDataSizeAfterAdd;
  33340. inc(fNestedDataSize,NestedProperty[i].fDataSize);
  33341. if fFullPropertyName<>'' then
  33342. NestedProperty[i].fFullPropertyName :=
  33343. fFullPropertyName+'.'+NestedProperty[i].fPropertyName;
  33344. end;
  33345. end;
  33346. procedure TJSONCustomParserRTTI.ComputeDataSizeAfterAdd;
  33347. const // binary size (in bytes) of each kind of property - 0 for ptRecord/ptCustom
  33348. JSONRTTI_SIZE: array[TJSONCustomParserRTTIType] of byte = (
  33349. SizeOf(PtrUInt),SizeOf(Boolean),SizeOf(Byte),SizeOf(Cardinal),SizeOf(Currency),
  33350. SizeOf(Double),SizeOf(Extended),SizeOf(Int64),SizeOf(Integer),SizeOf(RawByteString),
  33351. SizeOf(RawJSON),SizeOf(RawUTF8),0,SizeOf(Single),SizeOf(String),SizeOf(SynUnicode),
  33352. SizeOf(TDateTime),SizeOf(TGUID),SizeOf(Int64),SizeOf(TTimeLog),
  33353. {$ifndef NOVARIANTS}SizeOf(Variant),{$endif}
  33354. SizeOf(WideString),SizeOf(Word),0);
  33355. var i: integer;
  33356. begin
  33357. if fFullPropertyName='' then begin
  33358. fFullPropertyName := fPropertyName;
  33359. ComputeFullPropertyName;
  33360. end;
  33361. if fDataSize=0 then begin
  33362. ComputeNestedDataSize;
  33363. case PropertyType of
  33364. ptRecord:
  33365. for i := 0 to high(NestedProperty) do
  33366. inc(fDataSize,NestedProperty[i].fDataSize);
  33367. //ptCustom: fDataSize already set in TJSONCustomParserCustom.Create()
  33368. else
  33369. fDataSize := JSONRTTI_SIZE[PropertyType];
  33370. end;
  33371. {$ifdef ALIGNCUSTOMREC}
  33372. inc(fDataSize,fDataSize and 7);
  33373. {$endif}
  33374. end;
  33375. end;
  33376. procedure TJSONCustomParserRTTI.FinalizeNestedRecord(var Data: PByte);
  33377. var j: integer;
  33378. begin
  33379. for j := 0 to high(NestedProperty) do begin
  33380. case NestedProperty[j].PropertyType of
  33381. ptRawByteString,
  33382. ptRawJSON,
  33383. ptRawUTF8: PRawByteString(Data)^ := '';
  33384. ptString: PString(Data)^ := '';
  33385. ptSynUnicode: PSynUnicode(Data)^ := '';
  33386. ptWideString: PWideString(Data)^ := '';
  33387. ptArray: NestedProperty[j].FinalizeNestedArray(PPtrUInt(Data)^);
  33388. {$ifndef NOVARIANTS}
  33389. ptVariant: VarClear(PVariant(Data)^);
  33390. {$endif}
  33391. ptRecord: begin
  33392. NestedProperty[j].FinalizeNestedRecord(Data);
  33393. continue;
  33394. end;
  33395. ptCustom:
  33396. TJSONCustomParserCustom(NestedProperty[j]).FinalizeItem(Data);
  33397. end;
  33398. inc(Data,NestedProperty[j].fDataSize);
  33399. end;
  33400. end;
  33401. procedure TJSONCustomParserRTTI.FinalizeNestedArray(var Data: PtrUInt);
  33402. var i: integer;
  33403. Rec: ^TDynArrayRec;
  33404. ItemData: PByte;
  33405. begin
  33406. if Data=0 then
  33407. exit;
  33408. ItemData := pointer(Data);
  33409. Rec := pointer(Data);
  33410. dec(PtrUInt(Rec),sizeof(TDynArrayRec));
  33411. for i := 0 to Rec.length-1 do
  33412. FinalizeNestedRecord(ItemData);
  33413. FreeMem(Rec);
  33414. Data := 0;
  33415. end;
  33416. procedure TJSONCustomParserRTTI.AllocateNestedArray(var Data: PtrUInt;
  33417. NewLength: integer);
  33418. begin
  33419. FinalizeNestedArray(Data);
  33420. if NewLength<=0 then
  33421. exit;
  33422. pointer(Data) := AllocMem(sizeof(TDynArrayRec)+fNestedDataSize*NewLength);
  33423. PDynArrayRec(Data)^.refCnt := 1;
  33424. PDynArrayRec(Data)^.length := NewLength;
  33425. inc(Data,sizeof(TDynArrayRec));
  33426. end;
  33427. procedure TJSONCustomParserRTTI.ReAllocateNestedArray(var Data: PtrUInt;
  33428. NewLength: integer);
  33429. var OldLength: integer;
  33430. begin
  33431. if Data=0 then
  33432. raise ESynException.CreateUTF8('%.ReAllocateNestedArray(nil)',[self]);
  33433. dec(Data,sizeof(TDynArrayRec));
  33434. ReAllocMem(pointer(Data),sizeof(TDynArrayRec)+fNestedDataSize*NewLength);
  33435. OldLength := PDynArrayRec(Data)^.length;
  33436. if NewLength>OldLength then
  33437. FillcharFast(PByteArray(Data)[sizeof(TDynArrayRec)+fNestedDataSize*OldLength],
  33438. fNestedDataSize*(NewLength-OldLength),0);
  33439. PDynArrayRec(Data)^.length := NewLength;
  33440. inc(Data,sizeof(TDynArrayRec));
  33441. end;
  33442. function TJSONCustomParserRTTI.ReadOneLevel(var P: PUTF8Char; var Data: PByte;
  33443. Options: TJSONCustomParserSerializationOptions): boolean;
  33444. var EndOfObject: AnsiChar;
  33445. function ProcessValue(const Prop: TJSONCustomParserRTTI; var P: PUTF8Char;
  33446. var Data: PByte): boolean;
  33447. var DynArray: PByte;
  33448. ArrayLen, ArrayCapacity, n: integer;
  33449. wasString: boolean;
  33450. PropValue, ptr: PUTF8Char;
  33451. label Error;
  33452. begin
  33453. result := false;
  33454. P := GotoNextNotSpace(P);
  33455. case Prop.PropertyType of
  33456. ptRecord: begin
  33457. if not Prop.ReadOneLevel(P,Data,Options) then
  33458. exit;
  33459. EndOfObject := P^;
  33460. if P^ in [',','}'] then
  33461. inc(P);
  33462. result := true;
  33463. exit;
  33464. end;
  33465. ptArray:
  33466. if PInteger(P)^=NULL_LOW then begin // null -> void array
  33467. P := GotoNextNotSpace(P+4);
  33468. EndOfObject := P^;
  33469. if P^<>#0 then //if P^=',' then
  33470. inc(P);
  33471. Prop.FinalizeNestedArray(PPtrUInt(Data)^);
  33472. end else begin
  33473. if P^<>'[' then
  33474. exit; // we expect a true array here
  33475. repeat inc(P) until P^<>' ';
  33476. // try to allocate nested array at once (if not too slow)
  33477. ArrayLen := JSONArrayCount(P,P+131072); // parse up to 128 KB here
  33478. if ArrayLen<0 then // mostly JSONArrayCount()=nil due to PMax -> 512
  33479. ArrayCapacity := 512 else
  33480. ArrayCapacity := ArrayLen;
  33481. Prop.AllocateNestedArray(PPtrUInt(Data)^,ArrayCapacity);
  33482. // read array content
  33483. if ArrayLen=0 then begin
  33484. if not NextNotSpaceCharIs(P,']') then
  33485. exit;
  33486. end else begin
  33487. n := 0;
  33488. DynArray := PPointer(Data)^;
  33489. repeat
  33490. inc(n);
  33491. if (ArrayLen<0) and (n>ArrayCapacity) then begin
  33492. inc(ArrayCapacity,512+ArrayCapacity shr 3);
  33493. Prop.ReAllocateNestedArray(PPtrUInt(Data)^,ArrayCapacity);
  33494. DynArray := PPointer(Data)^;
  33495. inc(DynArray,pred(n)*Prop.fNestedDataSize);
  33496. end;
  33497. if Prop.NestedProperty[0].PropertyName='' then begin
  33498. // array of simple type
  33499. ptr := P;
  33500. if not ProcessValue(Prop.NestedProperty[0],ptr,DynArray) or (ptr=nil) then
  33501. goto Error;
  33502. P := ptr;
  33503. end else begin
  33504. // array of record
  33505. ptr := P;
  33506. if not Prop.ReadOneLevel(ptr,DynArray,Options) or (ptr=nil) then
  33507. goto Error;
  33508. P := GotoNextNotSpace(ptr);
  33509. EndOfObject := P^;
  33510. if not(P^ in [',',']']) then
  33511. goto Error;
  33512. inc(P);
  33513. end;
  33514. case EndOfObject of
  33515. ',': continue;
  33516. ']': begin
  33517. if ArrayLen<0 then
  33518. Prop.ReAllocateNestedArray(PPtrUInt(Data)^,n) else
  33519. if n<>ArrayLen then
  33520. goto Error;
  33521. break; // we reached end of array
  33522. end;
  33523. else begin
  33524. Error: Prop.FinalizeNestedArray(PPtrUInt(Data)^);
  33525. exit;
  33526. end;
  33527. end;
  33528. until false;
  33529. end;
  33530. if P=nil then
  33531. exit;
  33532. P := GotoNextNotSpace(P);
  33533. EndOfObject := P^;
  33534. if P^<>#0 then //if P^=',' then
  33535. inc(P);
  33536. end;
  33537. ptCustom: begin
  33538. ptr := TJSONCustomParserCustom(Prop).CustomReader(P,Data^,EndOfObject);
  33539. if ptr=nil then
  33540. exit;
  33541. P := ptr;
  33542. end;
  33543. {$ifndef NOVARIANTS}
  33544. ptVariant:
  33545. P := VariantLoadJSON(PVariant(Data)^,P,@EndOfObject,
  33546. @JSON_OPTIONS[soCustomVariantCopiedByReference in Options]);
  33547. {$endif}
  33548. ptRawByteString: begin
  33549. PropValue := GetJSONField(P,ptr,@wasString,@EndOfObject);
  33550. if PropValue=nil then // null -> Blob=''
  33551. PRawByteString(Data)^ := '' else
  33552. if not Base64MagicCheckAndDecode(PropValue,PRawByteString(Data)^) then
  33553. exit;
  33554. P := ptr;
  33555. end;
  33556. ptRawJSON:
  33557. GetJSONItemAsRawJSON(P,PRawJSON(Data)^,@EndOfObject);
  33558. else begin
  33559. PropValue := GetJSONField(P,ptr,@wasString,@EndOfObject);
  33560. if (PropValue<>nil) and // PropValue=nil for null
  33561. (wasString<>(Prop.PropertyType in [ptRawUTF8,ptString,
  33562. ptSynUnicode,ptDateTime,ptGUID,ptWideString])) then
  33563. exit;
  33564. P := ptr;
  33565. case Prop.PropertyType of
  33566. ptBoolean: PBoolean(Data)^ := GetBoolean(PropValue);
  33567. ptByte: PByte(Data)^ := GetCardinal(PropValue);
  33568. ptCardinal: PCardinal(Data)^ := GetCardinal(PropValue);
  33569. ptCurrency: PInt64(Data)^ := StrToCurr64(PropValue);
  33570. ptDouble: PDouble(Data)^ := GetExtended(PropValue);
  33571. ptExtended: PExtended(Data)^ := GetExtended(PropValue);
  33572. ptInt64,ptID,ptTimeLog: SetInt64(PropValue,PInt64(Data)^);
  33573. ptInteger: PInteger(Data)^ := GetInteger(PropValue);
  33574. ptSingle: PSingle(Data)^ := GetExtended(PropValue);
  33575. ptRawUTF8: PRawUTF8(Data)^ := PropValue;
  33576. ptString: UTF8DecodeToString(PropValue,StrLen(PropValue),PString(Data)^);
  33577. ptSynUnicode:UTF8ToSynUnicode(PropValue,StrLen(PropValue),PSynUnicode(Data)^);
  33578. ptDateTime: Iso8601ToDateTimePUTF8CharVar(PropValue,0,PDateTime(Data)^);
  33579. ptWideString:UTF8ToWideString(PropValue,StrLen(PropValue),PWideString(Data)^);
  33580. ptWord: PWord(Data)^ := GetCardinal(PropValue);
  33581. ptGUID: TextToGUID(PropValue,pointer(Data));
  33582. end;
  33583. end;
  33584. end;
  33585. inc(Data,Prop.fDataSize);
  33586. result := true;
  33587. end;
  33588. var i,j: integer;
  33589. PropName: shortstring;
  33590. ptr: PUTF8Char;
  33591. Values: array of PUTF8Char;
  33592. begin
  33593. result := false;
  33594. if P=nil then
  33595. exit;
  33596. P := GotoNextNotSpace(P);
  33597. if PInteger(P)^=NULL_LOW then begin // a record stored as null
  33598. P := GotoNextNotSpace(P+4);
  33599. inc(Data,fDataSize);
  33600. result := true;
  33601. exit;
  33602. end;
  33603. if not (PropertyType in [ptRecord,ptArray]) then begin
  33604. result := ProcessValue(Self,P,Data);
  33605. exit;
  33606. end;
  33607. if P^<>'{' then
  33608. exit; // we expect a true object here
  33609. repeat inc(P) until not(P^ in [#1..' ']);
  33610. if P^='}' then begin
  33611. inc(Data,fDataSize);
  33612. EndOfObject := '}';
  33613. inc(P);
  33614. end else
  33615. for i := 0 to High(NestedProperty) do begin
  33616. ptr := P;
  33617. GetJSONPropName(ptr,PropName);
  33618. if PropName='' then
  33619. exit; // invalid JSON content
  33620. P := ptr;
  33621. if IdemPropNameU(NestedProperty[i].PropertyName,@PropName[1],ord(PropName[0])) then begin
  33622. // O(1) optimistic search
  33623. if not ProcessValue(NestedProperty[i],P,Data) then
  33624. exit;
  33625. if EndOfObject='}' then begin // ignore missing properties
  33626. for j := i+1 to high(NestedProperty) do
  33627. inc(Data,NestedProperty[j].fDataSize);
  33628. break;
  33629. end;
  33630. end else begin
  33631. SetLength(Values,length(NestedProperty)); // pessimistic check through all properties
  33632. repeat
  33633. for j := i to High(NestedProperty) do
  33634. if (Values[j]=nil) and
  33635. IdemPropNameU(NestedProperty[j].PropertyName,@PropName[1],ord(PropName[0])) then begin
  33636. Values[j] := P;
  33637. PropName := '';
  33638. break;
  33639. end;
  33640. if (PropName<>'') and not(soReadIgnoreUnknownFields in Options) then
  33641. exit; // unexpected property
  33642. ptr := GotoNextJSONItem(P,1,@EndOfObject);
  33643. if ptr=nil then
  33644. exit;
  33645. P := ptr;
  33646. if EndOfObject='}' then
  33647. break;
  33648. GetJSONPropName(ptr,PropName); // next name
  33649. if PropName='' then
  33650. exit; // invalid JSON content
  33651. P := ptr;
  33652. until false;
  33653. for j := i to high(NestedProperty) do
  33654. if Values[j]=nil then // ignore missing properties
  33655. inc(Data,NestedProperty[j].fDataSize) else
  33656. if not ProcessValue(NestedProperty[j],Values[j],Data) then
  33657. exit;
  33658. EndOfObject := '}'; // ProcessValue() did update EndOfObject
  33659. break;
  33660. end;
  33661. end;
  33662. if (P<>nil) and (EndOfObject=',') and (soReadIgnoreUnknownFields in Options) then begin
  33663. ptr := GotoNextJSONObjectOrArray(P,'}');
  33664. if ptr=nil then
  33665. exit;
  33666. P := ptr;
  33667. end else
  33668. if EndOfObject<>'}' then
  33669. exit;
  33670. if P<>nil then
  33671. P := GotoNextNotSpace(P);
  33672. result := true;
  33673. end;
  33674. procedure JSONBoolean(value: boolean; var result: RawUTF8);
  33675. begin // defined as a function and not an array[boolean] of RawUTF8 for FPC
  33676. if value then
  33677. result := 'true' else
  33678. result := 'false';
  33679. end;
  33680. procedure TJSONCustomParserRTTI.WriteOneLevel(aWriter: TTextWriter; var P: PByte;
  33681. Options: TJSONCustomParserSerializationOptions);
  33682. procedure WriteOneValue(Prop: TJSONCustomParserRTTI; var Value: PByte);
  33683. var DynArray: PByte;
  33684. j: integer;
  33685. begin
  33686. case Prop.PropertyType of
  33687. ptBoolean: aWriter.Add(PBoolean(Value)^);
  33688. ptByte: aWriter.AddU(PByte(Value)^);
  33689. ptCardinal: aWriter.AddU(PCardinal(Value)^);
  33690. ptCurrency: aWriter.AddCurr64(PInt64(Value)^);
  33691. ptDouble: aWriter.AddDouble(unaligned(PDouble(Value)^));
  33692. ptExtended: aWriter.Add(PExtended(Value)^,EXTENDED_PRECISION);
  33693. ptInt64,ptID,ptTimeLog:
  33694. aWriter.Add(PInt64(Value)^);
  33695. ptInteger: aWriter.Add(PInteger(Value)^);
  33696. ptSingle: aWriter.AddSingle(PSingle(Value)^);
  33697. ptWord: aWriter.AddU(PWord(Value)^);
  33698. {$ifndef NOVARIANTS}
  33699. ptVariant: aWriter.AddVariant(PVariant(Value)^,twJSONEscape);
  33700. {$endif}
  33701. ptRawByteString:
  33702. aWriter.WrBase64(PPointer(Value)^,length(PRawByteString(Value)^),true);
  33703. ptRawJSON, ptRawUTF8, ptString, ptSynUnicode,
  33704. ptDateTime, ptGUID, ptWideString: begin
  33705. aWriter.Add('"');
  33706. case Prop.PropertyType of
  33707. ptRawJSON: aWriter.AddNoJSONEscape(PPointer(Value)^,length(PRawJSON(Value)^));
  33708. ptRawUTF8: aWriter.AddJSONEscape(PPointer(Value)^);
  33709. ptString: aWriter.AddJSONEscapeString(PString(Value)^);
  33710. ptSynUnicode,
  33711. ptWideString: aWriter.AddJSONEscapeW(PPointer(Value)^);
  33712. ptDateTime: aWriter.AddDateTime(unaligned(PDateTime(Value)^));
  33713. ptGUID: aWriter.Add(PGUID(Value)^);
  33714. end;
  33715. aWriter.Add('"');
  33716. end;
  33717. ptArray: begin
  33718. aWriter.Add('[');
  33719. inc(aWriter.fHumanReadableLevel);
  33720. DynArray := PPointer(Value)^;
  33721. if DynArray<>nil then
  33722. for j := 1 to DynArrayLength(DynArray) do begin
  33723. if soWriteHumanReadable in Options then
  33724. aWriter.AddCRAndIndent;
  33725. if Prop.NestedProperty[0].PropertyName='' then // array of simple
  33726. WriteOneValue(Prop.NestedProperty[0],DynArray) else
  33727. Prop.WriteOneLevel(aWriter,DynArray,Options); // array of record
  33728. aWriter.Add(',');
  33729. {$ifdef ALIGNCUSTOMREC}
  33730. if PtrUInt(DynArray)and 7<>0 then
  33731. inc(DynArray,8-(PtrUInt(DynArray)and 7));
  33732. {$endif}
  33733. end;
  33734. aWriter.CancelLastComma;
  33735. aWriter.Add(']');
  33736. dec(aWriter.fHumanReadableLevel);
  33737. end;
  33738. ptRecord: begin
  33739. Prop.WriteOneLevel(aWriter,Value,Options);
  33740. exit;
  33741. end;
  33742. ptCustom:
  33743. TJSONCustomParserCustom(Prop).CustomWriter(aWriter,Value^);
  33744. end;
  33745. inc(Value,Prop.fDataSize);
  33746. end;
  33747. var i: integer;
  33748. SubProp: TJSONCustomParserRTTI;
  33749. begin
  33750. if P=nil then begin
  33751. aWriter.AddShort('null');
  33752. exit;
  33753. end;
  33754. if not (PropertyType in [ptRecord,ptArray]) then begin
  33755. WriteOneValue(self,P);
  33756. exit;
  33757. end;
  33758. aWriter.Add('{');
  33759. Inc(aWriter.fHumanReadableLevel);
  33760. for i := 0 to high(NestedProperty) do begin
  33761. SubProp := NestedProperty[i];
  33762. if soWriteHumanReadable in Options then
  33763. aWriter.AddCRAndIndent;
  33764. aWriter.AddFieldName(SubProp.PropertyName);
  33765. if soWriteHumanReadable in Options then
  33766. aWriter.Add(' ');
  33767. WriteOneValue(SubProp,P);
  33768. aWriter.Add(',');
  33769. end;
  33770. aWriter.CancelLastComma;
  33771. dec(aWriter.fHumanReadableLevel);
  33772. if soWriteHumanReadable in Options then
  33773. aWriter.AddCRAndIndent;
  33774. aWriter.Add('}');
  33775. end;
  33776. { TJSONRecordAbstract }
  33777. constructor TJSONRecordAbstract.Create;
  33778. begin
  33779. fItems := TObjectList.Create;
  33780. end;
  33781. function TJSONRecordAbstract.AddItem(const aPropertyName: RawUTF8;
  33782. aPropertyType: TJSONCustomParserRTTIType;
  33783. const aCustomRecordTypeName: RawUTF8): TJSONCustomParserRTTI;
  33784. begin
  33785. if aPropertyType=ptCustom then begin
  33786. result := TJSONCustomParserRTTI.CreateFromTypeName(
  33787. aPropertyName,aCustomRecordTypeName);
  33788. if result=nil then
  33789. raise ESynException.CreateUTF8('Unregistered ptCustom for %.AddItem(%: %)',
  33790. [self,aPropertyName,aCustomRecordTypeName]);
  33791. end else
  33792. result := TJSONCustomParserRTTI.Create(aPropertyName,aPropertyType);
  33793. fItems.Add(result);
  33794. end;
  33795. function TJSONRecordAbstract.CustomReader(P: PUTF8Char; var aValue; out aValid: Boolean): PUTF8Char;
  33796. var Data: PByte;
  33797. begin
  33798. Data := @aValue;
  33799. aValid := Root.ReadOneLevel(P,Data,Options);
  33800. result := P;
  33801. end;
  33802. procedure TJSONRecordAbstract.CustomWriter(const aWriter: TTextWriter; const aValue);
  33803. var P: PByte;
  33804. begin
  33805. P := @aValue;
  33806. Root.WriteOneLevel(aWriter,P,Options);
  33807. end;
  33808. destructor TJSONRecordAbstract.Destroy;
  33809. begin
  33810. FreeAndNil(fItems);
  33811. inherited;
  33812. end;
  33813. { TJSONRecordTextDefinition }
  33814. var
  33815. JSONCustomParserCache: TRawUTF8ListHashed;
  33816. class function TJSONRecordTextDefinition.FromCache(aTypeInfo: pointer;
  33817. const aDefinition: RawUTF8): TJSONRecordTextDefinition;
  33818. var i: integer;
  33819. added: boolean;
  33820. begin
  33821. if JSONCustomParserCache=nil then
  33822. GarbageCollectorFreeAndNil(JSONCustomParserCache,TRawUTF8ListHashed.Create(True));
  33823. i := JSONCustomParserCache.AddObjectIfNotExisting(aDefinition,nil,@added);
  33824. if not added then begin
  33825. result := TJSONRecordTextDefinition(JSONCustomParserCache.fObjects[i]);
  33826. exit;
  33827. end;
  33828. result := TJSONRecordTextDefinition.Create(aTypeInfo,aDefinition);
  33829. JSONCustomParserCache.fObjects[i] := result;
  33830. end;
  33831. constructor TJSONRecordTextDefinition.Create(aRecordTypeInfo: pointer;
  33832. const aDefinition: RawUTF8);
  33833. var P: PUTF8Char;
  33834. recordInfoSize: integer;
  33835. begin
  33836. inherited Create;
  33837. fDefinition := aDefinition;
  33838. fRoot := TJSONCustomParserRTTI.Create('',ptRecord);
  33839. TypeInfoToName(aRecordTypeInfo,fRoot.fCustomTypeName);
  33840. fItems.Add(fRoot);
  33841. P := pointer(aDefinition);
  33842. Parse(fRoot,P,eeNothing);
  33843. fRoot.ComputeDataSizeAfterAdd;
  33844. recordInfoSize := RecordTypeInfoSize(aRecordTypeInfo);
  33845. if (recordInfoSize<>0) and (fRoot.fDataSize<>recordInfoSize) then
  33846. raise ESynException.CreateUTF8('%.Create: % text definition is not accurate,'+
  33847. ' or the type has not been defined as PACKED record: RTTI size is %'+
  33848. ' bytes but text definition generated % bytes',
  33849. [self,fRoot.fCustomTypeName,recordInfoSize,fRoot.fDataSize]);
  33850. end;
  33851. procedure TJSONRecordTextDefinition.Parse(Props: TJSONCustomParserRTTI;
  33852. var P: PUTF8Char; PEnd: TJSONCustomParserRTTIExpectedEnd);
  33853. const DYNARRAYTEXT: PUTF8Char = 'DynArray'; // make Delphi 5 compiler happy
  33854. function GetNextFieldType(var P: PUTF8Char;
  33855. var TypIdent: RawUTF8): TJSONCustomParserRTTIType;
  33856. begin
  33857. if GetNextFieldProp(P,TypIdent) then
  33858. result := TJSONCustomParserRTTI.TypeNameToSimpleRTTIType(
  33859. pointer(TypIdent),length(TypIdent),TypIdent) else
  33860. raise ESynException.CreateUTF8('%.Parse: missing field type',[self]);
  33861. end;
  33862. var PropsName: TRawUTF8DynArray;
  33863. PropsMax, ndx, len, firstNdx: cardinal;
  33864. Typ, ArrayTyp: TJSONCustomParserRTTIType;
  33865. TypIdent, ArrayTypIdent: RawUTF8;
  33866. Item: TJSONCustomParserRTTI;
  33867. ExpectedEnd: TJSONCustomParserRTTIExpectedEnd;
  33868. begin
  33869. SetLength(PropsName,16);
  33870. PropsMax := 0;
  33871. while (P<>nil) and (P^<>#0) do begin
  33872. // fill Props[]
  33873. if not GetNextFieldProp(P,PropsName[PropsMax]) then
  33874. break;
  33875. case P^ of
  33876. ',': begin
  33877. inc(P);
  33878. inc(PropsMax);
  33879. if PropsMax=cardinal(length(PropsName)) then
  33880. SetLength(PropsName,PropsMax+16);
  33881. continue; // several properties defined with the same type
  33882. end;
  33883. ':': P := GotoNextNotSpace(P+1);
  33884. end;
  33885. // identify type
  33886. ArrayTyp := ptRecord;
  33887. if P^='{' then begin
  33888. Typ := ptRecord;
  33889. ExpectedEnd := eeCurly;
  33890. repeat inc(P) until not(P^ in [#1..' ']);
  33891. end else
  33892. if P^='[' then begin
  33893. Typ := ptArray;
  33894. ExpectedEnd := eeSquare;
  33895. repeat inc(P) until not(P^ in [#1..' ']);
  33896. end else begin
  33897. Typ := GetNextFieldType(P,TypIdent);
  33898. case Typ of
  33899. ptArray: begin
  33900. if IdemPChar(P,'OF') then begin
  33901. P := GotoNextNotSpace(P+2);
  33902. ArrayTyp := GetNextFieldType(P,ArrayTypIdent);
  33903. if ArrayTyp=ptArray then
  33904. P := nil;
  33905. end else
  33906. P := nil;
  33907. if P=nil then
  33908. raise ESynException.CreateUTF8('%.Parse: expected syntax is '+
  33909. '"array of record" or "array of SimpleType"',[self]);
  33910. if ArrayTyp=ptRecord then
  33911. ExpectedEnd := eeEndKeyWord else
  33912. ExpectedEnd := eeNothing;
  33913. end;
  33914. ptRecord:
  33915. ExpectedEnd := eeEndKeyWord;
  33916. ptCustom: begin
  33917. len := length(TypIdent);
  33918. if (len>12) and (TypIdent[1]='T') and
  33919. IdemPropNameUSameLen(DYNARRAYTEXT,@PByteArray(TypIdent)[len-8],8) then begin
  33920. ArrayTyp := TJSONCustomParserRTTI.TypeNameToSimpleRTTIType(
  33921. @PByteArray(TypIdent)[1],len-9,ArrayTypIdent);
  33922. if ArrayTyp=ptCustom then
  33923. raise ESynException.CreatEUTF8('%.Parse: unknown %',[self,TypIdent]);
  33924. Typ := ptArray;
  33925. end;
  33926. ExpectedEnd := eeNothing;
  33927. end;
  33928. else ExpectedEnd := eeNothing;
  33929. end;
  33930. end;
  33931. // add elements
  33932. firstNdx := length(Props.fNestedProperty);
  33933. SetLength(Props.fNestedProperty,firstNdx+PropsMax+1);
  33934. for ndx := 0 to PropsMax do begin
  33935. Item := AddItem(PropsName[ndx],Typ,TypIdent);
  33936. Props.fNestedProperty[firstNdx+ndx] := Item;
  33937. if (Typ=ptArray) and (ArrayTyp<>ptRecord) then begin
  33938. SetLength(Item.fNestedProperty,1);
  33939. Item.fNestedProperty[0] := AddItem('',ArrayTyp,ArrayTypIdent);
  33940. end else
  33941. if Typ in [ptArray,ptRecord] then
  33942. if ndx=0 then // only parse once multiple fields nested type
  33943. Parse(Item,P,ExpectedEnd) else
  33944. Item.fNestedProperty := Props.fNestedProperty[firstNdx].fNestedProperty;
  33945. Item.ComputeDataSizeAfterAdd;
  33946. end;
  33947. // validate expected end
  33948. while P^ in [#1..' ',';'] do inc(P);
  33949. case PEnd of
  33950. eeEndKeyWord:
  33951. if IdemPChar(P,'END') then begin
  33952. inc(P,3);
  33953. while P^ in [#1..' ',';'] do inc(P);
  33954. break;
  33955. end;
  33956. eeSquare:
  33957. if P^=']' then begin
  33958. inc(P);
  33959. break;
  33960. end;
  33961. eeCurly:
  33962. if P^='}' then begin
  33963. inc(P);
  33964. break;
  33965. end;
  33966. end;
  33967. PropsMax := 0;
  33968. end;
  33969. end;
  33970. { TJSONRecordRTTI }
  33971. constructor TJSONRecordRTTI.Create(aRecordTypeInfo: pointer;
  33972. aRoot: TJSONCustomParserRTTI);
  33973. begin
  33974. inherited Create;
  33975. fRecordTypeInfo := aRecordTypeInfo;
  33976. fRoot := aRoot;
  33977. if fRoot=nil then begin
  33978. {$ifdef ISDELPHI2010}
  33979. fRoot := TJSONCustomParserRTTI.Create('',ptRecord);
  33980. FromEnhancedRTTI(fRoot,aRecordTypeInfo);
  33981. if fRoot.fNestedDataSize<>RecordTypeInfoSize(aRecordTypeInfo) then
  33982. raise ESynException.CreateUTF8(
  33983. '%.Create: error when retrieving enhanced RTTI for %',
  33984. [self,fRoot.CustomTypeName]);
  33985. {$else}
  33986. raise ESynException.CreateUTF8('%.Create with no enhanced RTTI for %',
  33987. [self,TypeInfoToName(aRecordTypeInfo)]);
  33988. {$endif}
  33989. end;
  33990. fItems.Add(fRoot);
  33991. GarbageCollector.Add(self);
  33992. end;
  33993. function TJSONRecordRTTI.AddItemFromRTTI(
  33994. const PropertyName: RawUTF8; Info: pointer; ItemSize: integer): TJSONCustomParserRTTI;
  33995. begin
  33996. result := TJSONCustomParserRTTI.CreateFromRTTI(PropertyName,Info,ItemSize);
  33997. fItems.Add(result);
  33998. end;
  33999. {$ifdef ISDELPHI2010}
  34000. procedure TJSONRecordRTTI.FromEnhancedRTTI(
  34001. Props: TJSONCustomParserRTTI; Info: pointer);
  34002. var FieldTable: PTypeInfo;
  34003. i: integer;
  34004. FieldSize: cardinal;
  34005. RecField: PEnhancedFieldInfo;
  34006. ItemFields: array of PEnhancedFieldInfo;
  34007. ItemField: PTypeInfo;
  34008. ItemFieldName: RawUTF8;
  34009. ItemFieldSize: cardinal;
  34010. Item, ItemArray: TJSONCustomParserRTTI;
  34011. begin // only tkRecord is needed here
  34012. FieldTable := GetTypeInfo(Info,tkRecord);
  34013. if FieldTable=nil then
  34014. raise ESynException.CreateUTF8('%.FromEnhancedRTTI(%=record?)',[self,Info]);
  34015. FieldSize := FieldTable^.recSize;
  34016. inc(PByte(FieldTable),FieldTable^.ManagedCount*sizeof(TFieldInfo)-sizeof(TFieldInfo));
  34017. inc(PByte(FieldTable),FieldTable^.NumOps*sizeof(pointer)); // jump RecOps[]
  34018. if FieldTable^.AllCount=0 then
  34019. exit; // not enough RTTI -> will raise an error in Create()
  34020. TypeInfoToName(Info,Props.fCustomTypeName);
  34021. RecField := @FieldTable^.AllFields[0];
  34022. SetLength(ItemFields,FieldTable^.AllCount);
  34023. for i := 0 to FieldTable^.AllCount-1 do begin
  34024. ItemFields[i] := RecField;
  34025. inc(PByte(RecField),RecField^.NameLen); // Delphi: no AlignPtr() needed
  34026. inc(RecField);
  34027. inc(PByte(RecField),PWord(RecField)^);
  34028. end;
  34029. SetLength(Props.fNestedProperty,FieldTable^.AllCount);
  34030. for i := 0 to FieldTable^.AllCount-1 do begin
  34031. if i=FieldTable^.AllCount-1 then
  34032. ItemFieldSize := FieldSize-ItemFields[i].Offset else
  34033. ItemFieldSize := ItemFields[i+1].Offset-ItemFields[i].Offset;
  34034. ItemField := Deref(ItemFields[i]^.TypeInfo);
  34035. SetRawUTF8(ItemFieldName,PAnsiChar(@ItemFields[i]^.NameLen)+1,ItemFields[i]^.NameLen);
  34036. Item := AddItemFromRTTI(ItemFieldName,ItemField,ItemFieldSize);
  34037. Props.fNestedProperty[i] := Item;
  34038. case Item.PropertyType of
  34039. ptArray: begin
  34040. inc(PByte(ItemField),ItemField^.NameLen);
  34041. ItemArray := AddItemFromRTTI('',Deref(ItemField^.elType2),
  34042. ItemField^.elSize {$ifdef FPC}and $7FFFFFFF{$endif});
  34043. if (ItemArray.PropertyType=ptCustom) and
  34044. (ItemArray.ClassType=TJSONCustomParserRTTI) then
  34045. FromEnhancedRTTI(Item,Deref(ItemField^.elType2)) else begin
  34046. SetLength(Item.fNestedProperty,1);
  34047. Item.fNestedProperty[0] := ItemArray;
  34048. Item.ComputeNestedDataSize;
  34049. end;
  34050. end;
  34051. ptCustom:
  34052. if (ItemField<>nil) and (Item.ClassType=TJSONCustomParserRTTI) then
  34053. FromEnhancedRTTI(Item,ItemField);
  34054. end;
  34055. end;
  34056. Props.ComputeNestedDataSize;
  34057. end;
  34058. {$endif ISDELPHI2010}
  34059. { ************ variant-based process, including JSON/BSON document content }
  34060. function SetVariantUnRefSimpleValue(const Source: variant; var Dest: TVarData): boolean;
  34061. var typ: word;
  34062. begin
  34063. if TVarData(Source).VType and varByRef<>0 then begin
  34064. typ := TVarData(Source).VType and not varByRef;
  34065. case typ of
  34066. varVariant:
  34067. if PVarData(TVarData(Source).VPointer)^.VType in
  34068. [varEmpty..varDate,varBoolean,varShortInt..varWord64] then begin
  34069. Dest := PVarData(TVarData(Source).VPointer)^;
  34070. result := true;
  34071. end else
  34072. result := false;
  34073. varEmpty..varDate,varBoolean,varShortInt..varWord64: begin
  34074. Dest.VType := typ;
  34075. Dest.VInt64 := PInt64(TVarData(Source).VAny)^;
  34076. result := true;
  34077. end;
  34078. else
  34079. result := false;
  34080. end;
  34081. end else
  34082. result := false;
  34083. end;
  34084. {$ifndef LVCL}
  34085. procedure RawByteStringToVariant(Data: PByte; DataLen: Integer; var Value: variant);
  34086. begin
  34087. with TVarData(Value) do begin
  34088. if VType and VTYPE_STATIC<>0 then
  34089. VarClear(Value);
  34090. if (Data=nil) or (DataLen<=0) then
  34091. VType := varNull else begin
  34092. VType := varString;
  34093. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  34094. SetString(RawByteString(VAny),PAnsiChar(Data),DataLen);
  34095. end;
  34096. end;
  34097. end;
  34098. procedure RawByteStringToVariant(const Data: RawByteString; var Value: variant);
  34099. begin
  34100. with TVarData(Value) do begin
  34101. if VType and VTYPE_STATIC<>0 then
  34102. VarClear(Value);
  34103. if Data='' then
  34104. VType := varNull else begin
  34105. VType := varString;
  34106. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  34107. RawByteString(VAny) := Data;
  34108. end;
  34109. end;
  34110. end;
  34111. procedure VariantToRawByteString(const Value: variant; var Dest: RawByteString);
  34112. begin
  34113. case TVarData(Value).VType of
  34114. varEmpty, varNull:
  34115. Dest := '';
  34116. varString:
  34117. Dest := RawByteString(TVarData(Value).VAny);
  34118. else // not from RawByteStringToVariant() -> conversion to string
  34119. Dest := {$ifdef UNICODE}RawByteString{$else}string{$endif}(Value);
  34120. end;
  34121. end;
  34122. procedure SetVariantNull(var Value: variant);
  34123. begin // slightly faster than Value := Null
  34124. VarClear(Value);
  34125. TVarData(Value).VType := varNull;
  34126. end;
  34127. {$endif LVCL}
  34128. function VarIsEmptyOrNull(const V: Variant): Boolean;
  34129. begin
  34130. result := VarDataIsEmptyOrNull(@V);
  34131. end;
  34132. function VarDataIsEmptyOrNull(VarData: pointer): Boolean;
  34133. begin
  34134. repeat
  34135. if PVarData(VarData)^.VType<>varVariant or varByRef then
  34136. break;
  34137. VarData := PVarData(VarData)^.VPointer;
  34138. if VarData=nil then begin
  34139. result := true;
  34140. exit;
  34141. end;
  34142. until false;
  34143. result := (PVarData(VarData)^.VType<=varNull) or
  34144. (PVarData(VarData)^.VType=varNull or varByRef);
  34145. end;
  34146. function VarIs(const V: Variant; const VTypes: TVarDataTypes): Boolean;
  34147. var VD: PVarData;
  34148. begin
  34149. VD := @V;
  34150. repeat
  34151. if VD^.VType<>varVariant or varByRef then
  34152. break;
  34153. VD := VD^.VPointer;
  34154. if VD=nil then begin
  34155. result := false;
  34156. exit;
  34157. end;
  34158. until false;
  34159. result := VD^.VType in VTypes;
  34160. end;
  34161. function VarIsVoid(const V: Variant): boolean;
  34162. begin
  34163. with TVarData(V) do
  34164. case VType of
  34165. varEmpty,varNull:
  34166. result := true;
  34167. varBoolean:
  34168. result := not VBoolean;
  34169. varString,varOleStr{$ifdef HASVARUSTRING},varUString{$endif}:
  34170. result := VAny=nil;
  34171. varDate:
  34172. result := VInt64=0;
  34173. else
  34174. if VType=varVariant or varByRef then
  34175. result := VarIsVoid(PVariant(VPointer)^) else
  34176. if (VType=varByRef or varString) or (VType=varByRef or varOleStr)
  34177. {$ifdef HASVARUSTRING} or (VType=varByRef or varUString) {$endif} then
  34178. result := PPointer(VAny)^=nil else
  34179. {$ifndef NOVARIANTS}
  34180. if VType=word(DocVariantVType) then
  34181. result := TDocVariantData(V).Count=0 else
  34182. {$endif}
  34183. result := false;
  34184. end;
  34185. end;
  34186. {$ifndef NOVARIANTS}
  34187. /// internal method used by VariantLoadJSON(), GetVariantFromJSON() and
  34188. // TDocVariantData.InitJSONInPlace()
  34189. procedure GetJSONToAnyVariant(var Value: variant; var JSON: PUTF8Char;
  34190. EndOfObject: PUTF8Char; Options: PDocVariantOptions); forward;
  34191. procedure SetVariantByRef(const Source: Variant; var Dest: Variant);
  34192. begin
  34193. if TVarData(Dest).VType and VTYPE_STATIC<>0 then
  34194. VarClear(Dest);
  34195. if (TVarData(Source).VType=varVariant or varByRef) or
  34196. (TVarData(Source).VType in // already byref or simple
  34197. [varEmpty..varDate,varBoolean,varShortInt..varWord64]) then
  34198. TVarData(Dest) := TVarData(Source) else
  34199. if not SetVariantUnRefSimpleValue(Source,TVarData(Dest)) then begin
  34200. TVarData(Dest).VType := varVariant or varByRef;
  34201. TVarData(Dest).VPointer := @Source;
  34202. end;
  34203. end;
  34204. procedure SetVariantByValue(const Source: Variant; var Dest: Variant);
  34205. var s: TVarData absolute Source;
  34206. d: TVarData absolute Dest;
  34207. begin
  34208. if d.VType and VTYPE_STATIC<>0 then
  34209. VarClear(Dest);
  34210. case s.VType of
  34211. varEmpty..varDate,varBoolean,varShortInt..varWord64: begin
  34212. d.VType := s.VType;
  34213. d.VInt64 := s.VInt64;
  34214. end;
  34215. varString: begin
  34216. d.VType := varString;
  34217. d.VAny := nil;
  34218. RawByteString(d.VAny) := RawByteString(s.VAny);
  34219. end;
  34220. varVariant or varByRef:
  34221. Dest := PVariant(s.VPointer)^;
  34222. varByRef or varString: begin
  34223. d.VType := varString;
  34224. d.VAny := nil;
  34225. RawByteString(d.VAny) := PRawByteString(s.VAny)^;
  34226. end;
  34227. {$ifdef HASVARUSTRING} varUString, varByRef or varUString, {$endif}
  34228. varOleStr, varByRef or varOleStr: begin
  34229. d.VType := varString;
  34230. d.VAny := nil;
  34231. VariantToUTF8(Source,RawUTF8(d.VAny)); // store a RawUTF8 instance
  34232. end;
  34233. else
  34234. if not SetVariantUnRefSimpleValue(Source,d) then
  34235. Dest := Source;
  34236. end;
  34237. end;
  34238. procedure ZeroFill(Value: PVarData);
  34239. begin // slightly faster than FillChar(Value,sizeof(Value),0);
  34240. PInt64Array(Value)^[0] := 0;
  34241. PInt64Array(Value)^[1] := 0;
  34242. {$ifdef CPU64}
  34243. //assert(SizeOf(TVarData)=24);
  34244. PInt64Array(Value)^[2] := 0;
  34245. {$endif}
  34246. end;
  34247. procedure RawUTF8ToVariant(Txt: PUTF8Char; TxtLen: integer; var Value: variant);
  34248. begin
  34249. with TVarData(Value) do begin
  34250. if VType<>varString then begin // in-place replacement of a RawUTF8 value
  34251. if VType and VTYPE_STATIC<>0 then
  34252. VarClear(Value);
  34253. VType := varString;
  34254. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  34255. end;
  34256. SetRawUTF8(RawUTF8(VAny),Txt,TxtLen);
  34257. end;
  34258. end;
  34259. procedure RawUTF8ToVariant(const Txt: RawUTF8; var Value: variant);
  34260. begin
  34261. with TVarData(Value) do begin
  34262. if VType<>varString then begin // in-place replacement of a RawUTF8 value
  34263. if VType and VTYPE_STATIC<>0 then
  34264. VarClear(Value);
  34265. VType := varString;
  34266. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  34267. if Txt='' then
  34268. exit;
  34269. end;
  34270. RawByteString(VAny) := Txt;
  34271. {$ifdef HASCODEPAGE}
  34272. if (Txt<>'') and (StringCodePage(Txt)=CP_RAWBYTESTRING) then
  34273. SetCodePage(RawByteString(VAny),CP_UTF8,false); // force explicit UTF-8
  34274. {$endif}
  34275. end;
  34276. end;
  34277. function RawUTF8ToVariant(const Txt: RawUTF8): variant;
  34278. begin
  34279. RawUTF8ToVariant(Txt,result);
  34280. end;
  34281. procedure RawUTF8ToVariant(const Txt: RawUTF8; var Value: TVarData;
  34282. ExpectedValueType: word);
  34283. begin
  34284. if Value.VType and VTYPE_STATIC<>0 then
  34285. VarClear(variant(Value));
  34286. Value.VType := ExpectedValueType;
  34287. Value.VAny := nil; // avoid GPF below
  34288. if Txt<>'' then
  34289. case ExpectedValueType of
  34290. varString: begin
  34291. RawByteString(Value.VAny) := Txt;
  34292. {$ifdef HASCODEPAGE}
  34293. if (Txt<>'') and (StringCodePage(Txt)=CP_RAWBYTESTRING) then
  34294. SetCodePage(RawByteString(Value.VAny),CP_UTF8,false); // force explicit UTF-8
  34295. {$endif}
  34296. end;
  34297. varOleStr:
  34298. UTF8ToWideString(Txt,WideString(Value.VAny));
  34299. {$ifdef HASVARUSTRING}
  34300. varUString:
  34301. UTF8DecodeToUnicodeString(pointer(Txt),length(Txt),UnicodeString(Value.VAny));
  34302. {$endif}
  34303. else raise ESynException.CreateUTF8('RawUTF8ToVariant(ExpectedValueType=%)',
  34304. [ExpectedValueType]);
  34305. end;
  34306. end;
  34307. function VariantSave(const Value: variant; Dest: PAnsiChar): PAnsiChar;
  34308. procedure ComplexType;
  34309. begin
  34310. try
  34311. Dest := pointer(ToVarString(VariantSaveJSON(Value),PByte(Dest)));
  34312. except
  34313. on Exception do
  34314. Dest := nil; // notify invalid/unhandled variant content
  34315. end;
  34316. end;
  34317. var LenBytes: integer;
  34318. tmp: TVarData;
  34319. begin
  34320. with TVarData(Value) do
  34321. if VType and varByRef<>0 then
  34322. if VType=varVariant or varByRef then begin
  34323. result := VariantSave(PVariant(VPointer)^,Dest);
  34324. exit;
  34325. end else
  34326. if SetVariantUnRefSimpleValue(Value,tmp) then begin
  34327. result := VariantSave(variant(tmp),Dest-sizeof(VType));
  34328. exit;
  34329. end;
  34330. with TVarData(Value) do begin
  34331. PWord(Dest)^ := VType;
  34332. inc(Dest,sizeof(VType));
  34333. case VType of
  34334. varShortInt, varByte: begin
  34335. Dest^ := AnsiChar(VByte);
  34336. inc(Dest);
  34337. end;
  34338. varSmallint, varWord, varBoolean: begin
  34339. PWord(Dest)^ := VWord;
  34340. inc(Dest,sizeof(VWord));
  34341. end;
  34342. varSingle, varLongWord, varInteger: begin
  34343. PInteger(Dest)^ := VInteger;
  34344. inc(Dest,sizeof(VInteger));
  34345. end;
  34346. varInt64, varWord64, varDouble, varDate, varCurrency:begin
  34347. PInt64(Dest)^ := VInt64;
  34348. inc(Dest,sizeof(VInt64));
  34349. end;
  34350. varString, varOleStr {$ifdef HASVARUSTRING}, varUString{$endif}: begin
  34351. if PtrUInt(VAny)=0 then
  34352. LenBytes := 0 else begin
  34353. LenBytes := PStrRec(Pointer(PtrUInt(VAny)-STRRECSIZE))^.length;
  34354. {$ifdef HASVARUSTRING}
  34355. if VType=varUString then
  34356. LenBytes := LenBytes*2; // stored length is in bytes, not (wide)chars
  34357. {$endif}
  34358. end;
  34359. Dest := pointer(ToVarUInt32(LenBytes,pointer(Dest)));
  34360. if LenBytes>0 then begin
  34361. MoveFast(PPtrUInt(VAny)^,Dest^,LenBytes); // direct raw copy
  34362. inc(Dest,LenBytes);
  34363. end;
  34364. end;
  34365. else ComplexType; // complex types are stored as JSON
  34366. end;
  34367. end;
  34368. result := Dest;
  34369. end;
  34370. function VariantSaveLength(const Value: variant): integer;
  34371. var tmp: TVarData;
  34372. begin // match VariantSave() storage
  34373. with TVarData(Value) do
  34374. if VType and varByRef<>0 then
  34375. if VType=varVariant or varByRef then begin
  34376. result := VariantSaveLength(PVariant(VPointer)^);
  34377. exit;
  34378. end else
  34379. if SetVariantUnRefSimpleValue(Value,tmp) then begin
  34380. result := VariantSaveLength(variant(tmp));
  34381. exit;
  34382. end;
  34383. with TVarData(Value) do
  34384. case VType of
  34385. varShortInt, varByte:
  34386. result := sizeof(VByte)+sizeof(VType);
  34387. varSmallint, varWord, varBoolean:
  34388. result := sizeof(VSmallint)+sizeof(VType);
  34389. varSingle, varLongWord, varInteger:
  34390. result := sizeof(VInteger)+sizeof(VType);
  34391. varInt64, varWord64, varDouble, varDate, varCurrency:
  34392. result := sizeof(VInt64)+sizeof(VType);
  34393. varString, varOleStr:
  34394. if PtrUInt(VAny)=0 then
  34395. result := 1+sizeof(VType) else
  34396. result := ToVarUInt32LengthWithData(PStrRec(Pointer(PtrUInt(VAny)-STRRECSIZE))^.length)
  34397. +sizeof(VType);
  34398. {$ifdef HASVARUSTRING}
  34399. varUString:
  34400. if PtrUInt(VAny)=0 then // stored length is in bytes, not (wide)chars
  34401. result := 1+sizeof(VType) else
  34402. result := ToVarUInt32LengthWithData(PStrRec(Pointer(PtrUInt(VAny)-STRRECSIZE))^.length*2)
  34403. +sizeof(VType);
  34404. {$endif}
  34405. else
  34406. try // complex types will be stored as JSON
  34407. result := ToVarUInt32LengthWithData(VariantSaveJSONLength(Value))+sizeof(VType);
  34408. except
  34409. on Exception do
  34410. result := 0; // notify invalid/unhandled variant content
  34411. end;
  34412. end;
  34413. end;
  34414. function VariantSave(const Value: variant): RawByteString;
  34415. var P: PAnsiChar;
  34416. begin
  34417. SetString(result,nil,VariantSaveLength(Value));
  34418. P := VariantSave(Value,pointer(result));
  34419. if P-pointer(result)<>length(result) then
  34420. raise ESynException.Create('VariantSave length');
  34421. end;
  34422. function VariantLoad(const Bin: RawByteString;
  34423. CustomVariantOptions: PDocVariantOptions): variant;
  34424. begin
  34425. if VariantLoad(result,Pointer(Bin),CustomVariantOptions)=nil then
  34426. VarClear(result);
  34427. end;
  34428. function VariantLoad(var Value: variant; Source: PAnsiChar;
  34429. CustomVariantOptions: PDocVariantOptions): PAnsiChar;
  34430. var JSON: PUTF8Char;
  34431. tmp: TSynTempBuffer; // GetJSON*() does in-place unescape -> private copy
  34432. begin
  34433. with TVarData(Value) do begin
  34434. if VType and VTYPE_STATIC<>0 then
  34435. VarClear(Value);
  34436. VType := PWord(Source)^;
  34437. inc(Source,SizeOf(VType));
  34438. case VType of
  34439. varShortInt, varByte: begin
  34440. VByte := byte(Source^);
  34441. inc(Source);
  34442. end;
  34443. varSmallint, varWord, varBoolean: begin
  34444. VWord := PWord(Source)^;
  34445. inc(Source,sizeof(VWord));
  34446. end;
  34447. varSingle, varLongWord, varInteger: begin
  34448. VInteger := PInteger(Source)^;
  34449. inc(Source,sizeof(VInteger));
  34450. end;
  34451. varInt64, varWord64, varDouble, varDate, varCurrency:begin
  34452. VInt64 := PInt64(Source)^;
  34453. inc(Source,sizeof(VInt64));
  34454. end;
  34455. varString, varOleStr {$ifdef HASVARUSTRING}, varUString{$endif}: begin
  34456. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  34457. tmp.Len := FromVarUInt32(PByte(Source));
  34458. case VType of
  34459. varString:
  34460. SetString(RawUTF8(VAny),Source,tmp.Len); // explicit RawUTF8
  34461. varOleStr:
  34462. SetString(WideString(VAny),PWideChar(Source),tmp.Len shr 1);
  34463. {$ifdef HASVARUSTRING}
  34464. varUString:
  34465. SetString(UnicodeString(VAny),PWideChar(Source),tmp.Len shr 1);
  34466. {$endif}
  34467. end;
  34468. inc(Source,tmp.Len);
  34469. end;
  34470. else
  34471. if CustomVariantOptions<>nil then begin
  34472. try // expected format for complex type is JSON (VType may differ)
  34473. FromVarString(PByte(Source),tmp);
  34474. try
  34475. JSON := tmp.buf;
  34476. VType := varEmpty; // avoid GPF below
  34477. GetJSONToAnyVariant(Value,JSON,nil,CustomVariantOptions);
  34478. finally
  34479. tmp.Done;
  34480. end;
  34481. except
  34482. on Exception do
  34483. Source := nil; // notify invalid/unhandled variant content
  34484. end;
  34485. end else
  34486. Source := nil; // notify unhandled type
  34487. end;
  34488. end;
  34489. result := Source;
  34490. end;
  34491. procedure FromVarVariant(var Source: PByte; var Value: variant;
  34492. CustomVariantOptions: PDocVariantOptions);
  34493. begin
  34494. Source := PByte(VariantLoad(Value,PAnsiChar(Source),CustomVariantOptions));
  34495. end;
  34496. function VariantLoadJSON(var Value: variant; JSON: PUTF8Char;
  34497. EndOfObject: PUTF8Char; TryCustomVariants: PDocVariantOptions): PUTF8Char;
  34498. var wasString: boolean;
  34499. Val: PUTF8Char;
  34500. begin
  34501. result := JSON;
  34502. if JSON=nil then
  34503. exit;
  34504. if TryCustomVariants<>nil then begin
  34505. if dvoJSONObjectParseWithinString in TryCustomVariants^ then begin
  34506. JSON := GotoNextNotSpace(JSON);
  34507. if JSON^='"' then begin
  34508. Val := GetJSONField(result,result,@wasString,EndOfObject);
  34509. GetJSONToAnyVariant(Value,Val,EndOfObject,TryCustomVariants);
  34510. end else
  34511. GetJSONToAnyVariant(Value,result,EndOfObject,TryCustomVariants);
  34512. end else
  34513. GetJSONToAnyVariant(Value,result,EndOfObject,TryCustomVariants);
  34514. end else begin
  34515. Val := GetJSONField(result,result,@wasString,EndOfObject);
  34516. GetVariantFromJSON(Val,wasString,Value);
  34517. end;
  34518. if result=nil then
  34519. result := @NULCHAR;
  34520. end;
  34521. procedure VariantLoadJSON(var Value: Variant; const JSON: RawUTF8;
  34522. TryCustomVariants: PDocVariantOptions);
  34523. var tmp: TSynTempBuffer;
  34524. begin
  34525. tmp.Init(JSON);
  34526. try
  34527. VariantLoadJSON(Value,tmp.buf,nil,TryCustomVariants);
  34528. finally
  34529. tmp.Done;
  34530. end;
  34531. end;
  34532. function VariantLoadJSON(const JSON: RawUTF8; TryCustomVariants: PDocVariantOptions): variant;
  34533. var tmp: TSynTempBuffer;
  34534. begin
  34535. tmp.Init(JSON);
  34536. try
  34537. VariantLoadJSON(result,tmp.buf,nil,TryCustomVariants);
  34538. finally
  34539. tmp.Done;
  34540. end;
  34541. end;
  34542. function VariantSaveJSON(const Value: variant; Escape: TTextWriterKind): RawUTF8;
  34543. begin
  34544. VariantSaveJSON(Value,Escape,result);
  34545. end;
  34546. procedure VariantSaveJSON(const Value: variant; Escape: TTextWriterKind;
  34547. var result: RawUTF8);
  34548. begin // not very optimized, but fast enough in practice, and creates valid JSON
  34549. with DefaultTextWriterJSONClass.CreateOwnedStream do
  34550. try
  34551. AddVariant(Value,Escape);
  34552. SetText(result);
  34553. finally
  34554. Free;
  34555. end;
  34556. end;
  34557. function VariantSaveJSONLength(const Value: variant; Escape: TTextWriterKind): integer;
  34558. var Fake: TFakeWriterStream;
  34559. begin // will avoid most memory allocations, except for one 2KB internal buffer
  34560. Fake := TFakeWriterStream.Create;
  34561. try
  34562. with DefaultTextWriterJSONClass.Create(Fake,2048) do
  34563. try
  34564. AddVariant(Value,Escape);
  34565. FlushFinal;
  34566. result := fTotalFileSize;
  34567. finally
  34568. Free;
  34569. end;
  34570. finally
  34571. Fake.Free;
  34572. end;
  34573. end;
  34574. procedure VariantToVarRec(const V: variant; var result: TVarRec);
  34575. begin
  34576. result.VType := vtVariant;
  34577. if TVarData(V).VType=varByRef or varVariant then
  34578. result.VVariant := TVarData(V).VPointer else
  34579. result.VVariant := @V;
  34580. end;
  34581. function VarRecToVariant(const V: TVarRec): variant;
  34582. begin
  34583. VarRecToVariant(V,result);
  34584. end;
  34585. procedure VarRecToVariant(const V: TVarRec; var result: variant);
  34586. begin
  34587. if TVarData(result).VType and VTYPE_STATIC=0 then
  34588. TVarData(result).VType := varEmpty else
  34589. VarClear(result);
  34590. with TVarData(result) do
  34591. case V.VType of
  34592. vtPointer:
  34593. VType := varNull;
  34594. vtBoolean: begin
  34595. VType := varBoolean;
  34596. VBoolean := V.VBoolean;
  34597. end;
  34598. vtInteger: begin
  34599. VType := varInteger;
  34600. VInteger := V.VInteger;
  34601. end;
  34602. vtInt64: begin
  34603. VType := varInt64;
  34604. VInt64 := V.VInt64^;
  34605. end;
  34606. vtCurrency: begin
  34607. VType := varCurrency;
  34608. VCurrency := V.VCurrency^;
  34609. end;
  34610. vtExtended: begin
  34611. VType := varDouble;
  34612. VDouble := V.VExtended^;
  34613. end;
  34614. vtVariant:
  34615. result := V.VVariant^;
  34616. vtAnsiString: begin
  34617. VType := varString;
  34618. VAny := nil;
  34619. RawByteString(VAny) := RawByteString(V.VAnsiString);
  34620. end;
  34621. vtString, {$ifdef HASVARUSTRING}vtUnicodeString,{$endif}
  34622. vtPChar, vtChar, vtWideChar, vtWideString, vtClass: begin
  34623. VType := varString;
  34624. VAny := nil; // avoid GPF on next line
  34625. VarRecToUTF8(V,RawUTF8(VAny));
  34626. end;
  34627. vtObject: // class instance will be serialized as a TDocVariant
  34628. ObjectToVariant(V.VObject,result,[woDontStoreDefault]);
  34629. else raise ESynException.CreateUTF8('Unhandled TVarRec.VType=%',[V.VType]);
  34630. end;
  34631. end;
  34632. function ObjectToVariant(Value: TObject; EnumSetsAsText: boolean): variant;
  34633. const OPTIONS: array[boolean] of TTextWriterWriteObjectOptions = (
  34634. [woDontStoreDefault],[woDontStoreDefault,woEnumSetsAsText]);
  34635. begin
  34636. VarClear(result);
  34637. ObjectToVariant(Value,result,OPTIONS[EnumSetsAsText]);
  34638. end;
  34639. procedure ObjectToVariant(Value: TObject; out Dest: variant);
  34640. begin
  34641. ObjectToVariant(Value,Dest,[woDontStoreDefault]);
  34642. end;
  34643. procedure ObjectToVariant(Value: TObject; var result: variant;
  34644. Options: TTextWriterWriteObjectOptions);
  34645. var json: RawUTF8;
  34646. begin
  34647. json := ObjectToJSON(Value,Options);
  34648. PDocVariantData(@result)^.InitJSONInPlace(pointer(json),JSON_OPTIONS_FAST);
  34649. end;
  34650. { TSynInvokeableVariantType }
  34651. procedure TSynInvokeableVariantType.Lookup(var Dest: TVarData; const V: TVarData;
  34652. FullName: PUTF8Char);
  34653. var itemName: RawUTF8;
  34654. Handler: TSynInvokeableVariantType;
  34655. DestVar,LookupVar: TVarData;
  34656. begin
  34657. Dest.VType := varEmpty; // left to Unassigned if not found
  34658. DestVar := V;
  34659. while DestVar.VType=varByRef or varVariant do
  34660. DestVar := PVarData(DestVar.VPointer)^;
  34661. repeat
  34662. itemName := GetNextItem(FullName,'.');
  34663. if itemName='' then
  34664. exit;
  34665. if DestVar.VType=DocVariantVType then begin
  34666. if not TDocVariantData(DestVar).GetVarData(itemName,DestVar) then
  34667. exit;
  34668. end else
  34669. if FindCustomVariantType(DestVar.VType,TCustomVariantType(Handler)) and
  34670. Handler.InheritsFrom(TSynInvokeableVariantType) then
  34671. try // handle any kind of document storage: TSynTableVariant,TBSONVariant...
  34672. LookupVar.VType := varEmpty;
  34673. Handler.IntGet(LookupVar,DestVar,pointer(itemName));
  34674. if LookupVar.VType<=varNull then
  34675. exit; // assume varNull means not found
  34676. DestVar := LookupVar;
  34677. except
  34678. on Exception do begin
  34679. DestVar.VType := varEmpty;
  34680. exit;
  34681. end;
  34682. end else
  34683. exit;
  34684. while DestVar.VType=varByRef or varVariant do
  34685. DestVar := PVarData(DestVar.VPointer)^;
  34686. if (DestVar.VType=DocVariantVType) and
  34687. (TDocVariantData(DestVar).VCount=0) then
  34688. DestVar.VType := varNull; // recognize void TDocVariant as null
  34689. if FullName=nil then begin // found full name scope
  34690. Dest := DestVar;
  34691. exit;
  34692. end;
  34693. // if we reached here, we should try for the next scope within Dest
  34694. if DestVar.VType=VarType then // most likely to be of the same exact type
  34695. continue;
  34696. if FindCustomVariantType(DestVar.VType,TCustomVariantType(Handler)) and
  34697. Handler.InheritsFrom(TSynInvokeableVariantType) then
  34698. Handler.Lookup(Dest,DestVar,FullName);
  34699. break;
  34700. until false;
  34701. end;
  34702. function TSynInvokeableVariantType.IterateCount(const V: TVarData): integer;
  34703. begin
  34704. result := -1; // this is not an array
  34705. end;
  34706. procedure TSynInvokeableVariantType.Iterate(var Dest: TVarData; const V: TVarData;
  34707. Index: integer);
  34708. begin // do nothing
  34709. end;
  34710. {$ifndef FPC}
  34711. {$ifndef DELPHI6OROLDER}
  34712. function TSynInvokeableVariantType.FixupIdent(const AText: string): string;
  34713. begin
  34714. result := AText; // NO uppercased identifier for our custom types!
  34715. end;
  34716. {$endif DELPHI6OROLDER}
  34717. {$endif FPC}
  34718. function TSynInvokeableVariantType.GetProperty(var Dest: TVarData;
  34719. const V: TVarData; const Name: String): Boolean;
  34720. {$ifdef UNICODE}
  34721. var Buf: array[byte] of AnsiChar; // to avoid heap allocation
  34722. {$endif}
  34723. begin
  34724. {$ifdef UNICODE}
  34725. RawUnicodeToUtf8(Buf,SizeOf(Buf),pointer(Name),length(Name));
  34726. IntGet(Dest,V,Buf);
  34727. {$else}
  34728. IntGet(Dest,V,pointer(Name));
  34729. {$endif}
  34730. result := True;
  34731. end;
  34732. {$ifdef FPC_VARIANTSETVAR} // see http://mantis.freepascal.org/view.php?id=26773
  34733. function TSynInvokeableVariantType.SetProperty(var V: TVarData;
  34734. const Name: string; const Value: TVarData): Boolean;
  34735. {$else}
  34736. function TSynInvokeableVariantType.SetProperty(const V: TVarData;
  34737. const Name: string; const Value: TVarData): Boolean;
  34738. {$endif}
  34739. var ValueSet: TVarData;
  34740. PropName: PAnsiChar;
  34741. {$ifdef UNICODE}
  34742. Buf: array[byte] of AnsiChar; // to avoid heap allocation
  34743. {$endif}
  34744. begin
  34745. {$ifdef UNICODE}
  34746. RawUnicodeToUtf8(Buf,SizeOf(Buf),pointer(Name),length(Name));
  34747. PropName := @Buf[0];
  34748. {$else}
  34749. PropName := pointer(Name);
  34750. {$endif}
  34751. ValueSet.VString := nil; // to avoid GPF in RawUTF8(ValueSet.VString) below
  34752. if Value.VType=varByRef or varOleStr then
  34753. RawUnicodeToUtf8(PPointer(Value.VAny)^,length(PWideString(Value.VAny)^),
  34754. RawUTF8(ValueSet.VString)) else
  34755. if Value.VType=varOleStr then
  34756. RawUnicodeToUtf8(Value.VAny,length(WideString(Value.VAny)),
  34757. RawUTF8(ValueSet.VString)) else
  34758. {$ifdef HASVARUSTRING}
  34759. if Value.VType=varByRef or varUString then
  34760. RawUnicodeToUtf8(PPointer(Value.VAny)^,length(PUnicodeString(Value.VAny)^),
  34761. RawUTF8(ValueSet.VString)) else
  34762. if Value.VType=varUString then
  34763. RawUnicodeToUtf8(Value.VAny,length(UnicodeString(Value.VAny)),
  34764. RawUTF8(ValueSet.VString)) else
  34765. {$endif}
  34766. if SetVariantUnRefSimpleValue(variant(Value),ValueSet) then begin
  34767. IntSet(V,ValueSet,PropName);
  34768. result := true;
  34769. exit;
  34770. end else begin
  34771. IntSet(V,Value,PropName);
  34772. result := true;
  34773. exit;
  34774. end;
  34775. try // unpatched RTL does not like Unicode values :( -> transmit a RawUTF8
  34776. ValueSet.VType := varString;
  34777. IntSet(V,ValueSet,PropName);
  34778. finally
  34779. RawUTF8(ValueSet.VString) := ''; // avoid memory leak
  34780. end;
  34781. result := True;
  34782. end;
  34783. procedure TSynInvokeableVariantType.Clear(var V: TVarData);
  34784. begin
  34785. ZeroFill(@V); // will set V.VType := varEmpty
  34786. end;
  34787. procedure TSynInvokeableVariantType.Copy(var Dest: TVarData;
  34788. const Source: TVarData; const Indirect: Boolean);
  34789. begin
  34790. if Indirect then
  34791. SimplisticCopy(Dest,Source,true) else begin
  34792. if Dest.VType and VTYPE_STATIC<>0 then
  34793. VarClear(variant(Dest)); // Dest may be a complex type
  34794. Dest := Source;
  34795. end;
  34796. end;
  34797. procedure TSynInvokeableVariantType.CopyByValue(var Dest: TVarData; const Source: TVarData);
  34798. begin
  34799. Copy(Dest,Source,false);
  34800. end;
  34801. function TSynInvokeableVariantType.TryJSONToVariant(var JSON: PUTF8Char;
  34802. var Value: variant; EndOfObject: PUTF8Char): boolean;
  34803. begin
  34804. result := false;
  34805. end;
  34806. procedure TSynInvokeableVariantType.ToJSON(W: TTextWriter; const Value: variant;
  34807. Escape: TTextWriterKind);
  34808. begin
  34809. raise ESynException.CreateUTF8('%.ToJSON: unimplemented variant type',[self]);
  34810. end;
  34811. function TSynInvokeableVariantType.IsOfType(const V: variant): boolean;
  34812. begin
  34813. if self=nil then
  34814. result := false else
  34815. if TVarData(V).VType=varByRef or varVariant then
  34816. result := IsOfType(PVariant(TVarData(V).VPointer)^) else
  34817. result := TVarData(V).VType=VarType;
  34818. end;
  34819. { TSynTableVariantType }
  34820. var
  34821. SynTableVariantType: TCustomVariantType = nil;
  34822. SynVariantTypes: TObjectList = nil;
  34823. function FindSynVariantType(aVarType: Word; out CustomType: TSynInvokeableVariantType): boolean;
  34824. var i: integer;
  34825. begin
  34826. if SynVariantTypes<>nil then begin
  34827. for i := 0 to SynVariantTypes.Count-1 do
  34828. if TSynInvokeableVariantType(SynVariantTypes.List[i]).VarType=aVarType then begin
  34829. CustomType := TSynInvokeableVariantType(SynVariantTypes.List[i]);
  34830. result := true;
  34831. exit;
  34832. end;
  34833. end;
  34834. result := false;
  34835. end;
  34836. procedure GetJSONToAnyVariant(var Value: variant; var JSON: PUTF8Char;
  34837. EndOfObject: PUTF8Char; Options: PDocVariantOptions);
  34838. // internal method used by VariantLoadJSON(), GetVariantFromJSON() and
  34839. // TDocVariantData.InitJSON()
  34840. var wasString: boolean;
  34841. procedure ProcessSimple(Val: PUTF8Char);
  34842. begin
  34843. GetVariantFromJSON(Val,wasString,Value);
  34844. if JSON=nil then
  34845. JSON := @NULCHAR;
  34846. end;
  34847. var i: integer;
  34848. VariantType: ^TSynInvokeableVariantType;
  34849. ToBeParsed: PUTF8Char;
  34850. wasParsedWithinString: boolean;
  34851. begin
  34852. if TVarData(Value).VType and VTYPE_STATIC<>0 then
  34853. VarClear(Value);
  34854. if EndOfObject<>nil then
  34855. EndOfObject^ := ' ';
  34856. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  34857. if (Options=nil) or (JSON^ in ['1'..'9']) then begin // obvious simple type
  34858. ProcessSimple(GetJSONField(JSON,JSON,@wasString,EndOfObject));
  34859. exit;
  34860. end;
  34861. if JSON^='"' then
  34862. if dvoJSONObjectParseWithinString in Options^ then begin
  34863. ToBeParsed := GetJSONField(JSON,JSON,@wasString,EndOfObject);
  34864. EndOfObject := nil; // already set just above
  34865. wasParsedWithinString := true;
  34866. end else begin
  34867. ProcessSimple(GetJSONField(JSON,JSON,@wasString,EndOfObject));
  34868. exit;
  34869. end else begin
  34870. ToBeParsed := JSON;
  34871. wasParsedWithinString := false;
  34872. end;
  34873. if (SynVariantTypes<>nil) and
  34874. not (dvoJSONParseDoNotTryCustomVariants in Options^) then begin
  34875. VariantType := pointer(SynVariantTypes.List);
  34876. for i := 1 to SynVariantTypes.Count do
  34877. if VariantType^.TryJSONToVariant(ToBeParsed,Value,EndOfObject) then begin
  34878. if not wasParsedWithinString then
  34879. JSON := ToBeParsed;
  34880. exit;
  34881. end else
  34882. inc(VariantType);
  34883. end;
  34884. if ToBeParsed^ in ['[','{'] then begin
  34885. // default JSON parsing and conversion to TDocVariant instance
  34886. ToBeParsed := TDocVariantData(Value).InitJSONInPlace(ToBeParsed,Options^,EndOfObject);
  34887. if not wasParsedWithinString then
  34888. JSON := ToBeParsed;
  34889. end else
  34890. // process to simple variant types
  34891. if wasParsedWithinString then
  34892. ProcessSimple(ToBeParsed) else
  34893. ProcessSimple(GetJSONField(JSON,JSON,@wasString,EndOfObject));
  34894. end;
  34895. function TextToVariantNumberType(json: PUTF8Char): word;
  34896. var start: PUTF8Char;
  34897. label exponent;
  34898. begin
  34899. start := json;
  34900. if (json[0] in ['1'..'9']) or // is first char numeric?
  34901. ((json[0]='0') and not (json[1] in ['0'..'9'])) or // '012' is invalid JSON
  34902. ((json[0]='-') and (json[1] in ['0'..'9'])) then begin
  34903. inc(json);
  34904. repeat
  34905. case json^ of
  34906. '0'..'9':
  34907. inc(json);
  34908. '.':
  34909. if (json[1] in ['0'..'9']) and (json[2] in [#0,'0'..'9']) then
  34910. if (json[2]=#0) or (json[3]=#0) or
  34911. ((json[3] in ['0'..'9']) and
  34912. (json[4]=#0) or
  34913. ((json[4] in ['0'..'9']) and (json[5]=#0))) then begin
  34914. result := varCurrency; // currency ###.1234 number
  34915. exit;
  34916. end else begin
  34917. repeat // more than 4 decimals
  34918. inc(json)
  34919. until not (json^ in ['0'..'9']);
  34920. if json^ in ['e','E'] then begin
  34921. exponent: inc(json);
  34922. if json^ in ['+','-'] then
  34923. inc(json);
  34924. if not (json^ in ['0'..'9']) then
  34925. break;
  34926. repeat
  34927. inc(json);
  34928. until not (json^ in ['0'..'9']);
  34929. end;
  34930. if json^=#0 then begin
  34931. result := varDouble; // (floating pointer number)
  34932. exit;
  34933. end;
  34934. break;
  34935. end else
  34936. break;
  34937. 'e','E':
  34938. goto exponent;
  34939. #0:
  34940. if json-start<=19 then begin // signed Int64 precision
  34941. result := varInt64;
  34942. exit;
  34943. end else begin
  34944. result := varDouble; // we may lost precision, but it is a number
  34945. exit;
  34946. end;
  34947. else break;
  34948. end;
  34949. until false;
  34950. end;
  34951. result := varString;
  34952. end;
  34953. function GetNumericVariantFromJSON(JSON: PUTF8Char; var Value: TVarData): boolean;
  34954. var err: integer;
  34955. label dbl;
  34956. begin
  34957. if JSON<>nil then
  34958. with Value do
  34959. case TextToVariantNumberType(JSON) of
  34960. varInt64: begin
  34961. VInt64 := GetInt64(JSON,err);
  34962. if err<>0 then
  34963. goto dbl; // overflow (>$7FFFFFFFFFFFFFFF) -> try floating point
  34964. if (VInt64<=high(integer)) and (VInt64>=low(integer)) then
  34965. VType := varInteger else
  34966. VType := varInt64;
  34967. result := true;
  34968. exit;
  34969. end;
  34970. varCurrency: begin
  34971. VInt64 := StrToCurr64(JSON);
  34972. VType := varCurrency;
  34973. result := true;
  34974. exit;
  34975. end;
  34976. varDouble: begin
  34977. dbl: VDouble := GetExtended(JSON,err);
  34978. if err=0 then begin
  34979. VType := varDouble;
  34980. result := true;
  34981. exit;
  34982. end;
  34983. end;
  34984. end;
  34985. result := false;
  34986. end;
  34987. procedure GetVariantFromJSON(JSON: PUTF8Char; wasString: Boolean; var Value: variant;
  34988. TryCustomVariants: PDocVariantOptions);
  34989. begin
  34990. // first handle any strict-JSON syntax objects or arrays into custom variants
  34991. // (e.g. when called directly from TSQLPropInfoRTTIVariant.SetValue)
  34992. if (TryCustomVariants<>nil) and (JSON<>nil) and (JSON^ in ['{','[']) then begin
  34993. GetJSONToAnyVariant(Value,JSON,nil,TryCustomVariants);
  34994. exit;
  34995. end;
  34996. // handle simple text or numerical values
  34997. with TVarData(Value) do begin
  34998. if VType and VTYPE_STATIC=0 then
  34999. VType := varEmpty else
  35000. VarClear(Value);
  35001. if (JSON=nil) or ((PInteger(JSON)^=NULL_LOW) and not wasString) then begin
  35002. VType := varNull;
  35003. exit;
  35004. end else
  35005. if (PInteger(JSON)^=FALSE_LOW) and (JSON[4]='e') and
  35006. (JSON[5] in EndOfJSONValueField) then begin
  35007. VType := varBoolean;
  35008. VBoolean := false;
  35009. exit;
  35010. end else
  35011. if (PInteger(JSON)^=TRUE_LOW) and (JSON[4] in EndOfJSONValueField) then begin
  35012. VType := varBoolean;
  35013. VBoolean := true;
  35014. exit;
  35015. end else
  35016. if not wasString then
  35017. if GetNumericVariantFromJSON(JSON,TVarData(Value)) then
  35018. exit;
  35019. // found no numerical value -> return a string in the expected format
  35020. VType := varString;
  35021. VAny := nil; // avoid GPF below when assigning a string variable to VAny
  35022. SetString(RawUTF8(VAny),PAnsiChar(JSON),StrLen(JSON));
  35023. end;
  35024. end;
  35025. procedure TSynTableVariantType.Clear(var V: TVarData);
  35026. begin
  35027. //Assert(V.VType=SynTableVariantType.VarType);
  35028. TSynTableData(V).VValue := ''; // clean memory release
  35029. PPtrUInt(@V)^ := 0; // will set V.VType := varEmpty
  35030. end;
  35031. procedure TSynTableVariantType.Copy(var Dest: TVarData;
  35032. const Source: TVarData; const Indirect: Boolean);
  35033. begin
  35034. //Assert(Source.VType=SynTableVariantType.VarType);
  35035. inherited Copy(Dest,Source,Indirect); // copy VType+VID+VTable
  35036. if not Indirect then
  35037. with TSynTableData(Dest) do begin
  35038. PtrInt(VValue) := 0; // avoid GPF
  35039. VValue := TSynTableData(Source).VValue; // copy by reference
  35040. end;
  35041. end;
  35042. procedure TSynTableVariantType.IntGet(var Dest: TVarData;
  35043. const V: TVarData; Name: PAnsiChar);
  35044. begin
  35045. TSynTableData(V).GetFieldVariant(RawByteString(Name),variant(Dest));
  35046. end;
  35047. procedure TSynTableVariantType.IntSet(const V, Value: TVarData;
  35048. Name: PAnsiChar);
  35049. begin
  35050. TSynTableData(V).SetFieldValue(RawByteString(Name),Variant(Value));
  35051. end;
  35052. class function TSynTableVariantType.ToID(const V: Variant): integer;
  35053. var Data: TSynTableData absolute V;
  35054. begin
  35055. if Data.VType<>SynTableVariantType.VarType then
  35056. result := 0 else
  35057. result := Data.VID;
  35058. end;
  35059. class function TSynTableVariantType.ToSBF(const V: Variant): TSBFString;
  35060. var Data: TSynTableData absolute V;
  35061. begin
  35062. if Data.VType<>SynTableVariantType.VarType then
  35063. result := '' else
  35064. result := Data.VValue;
  35065. end;
  35066. class function TSynTableVariantType.ToTable(const V: Variant): TSynTable;
  35067. var Data: TSynTableData absolute V;
  35068. begin
  35069. if Data.VType<>SynTableVariantType.VarType then
  35070. result := nil else
  35071. result := Data.VTable;
  35072. end;
  35073. {$ifndef FPC} // better not try it with FPC - rely on the current implementation
  35074. function ParseParamPointer(P: pointer; aType: cardinal; var Value: TVarData): pointer;
  35075. var Size: Cardinal;
  35076. ByRef: Boolean;
  35077. V: Variant absolute Value;
  35078. const TYPE_BYREF = 128;
  35079. TYPE_BYREF_MASK = TYPE_BYREF-1;
  35080. begin // this code should copy parameters without any reference count handling
  35081. ZeroFill(@Value); // TVarData is expected to be bulk stack: no VarClear needed
  35082. ByRef := (aType and TYPE_BYREF)<>0;
  35083. Size := sizeof(pointer);
  35084. case aType and TYPE_BYREF_MASK of
  35085. varShortInt, varSmallint, varInteger, varByte, varWord, varLongWord, varSingle: begin
  35086. if ByRef then
  35087. P := pointer(P^);
  35088. Value.VType := aType and TYPE_BYREF_MASK;
  35089. Value.VInteger := PInteger(P)^;
  35090. {$ifdef CPU64}
  35091. if not ByRef then
  35092. Size := sizeof(Integer);
  35093. {$endif}
  35094. end;
  35095. varDouble, varCurrency, varDate, varInt64, varWord64, varOleStr: begin
  35096. if ByRef then
  35097. P := pointer(P^);
  35098. Value.VType := aType and TYPE_BYREF_MASK;
  35099. Value.VInt64 := PInt64(P)^;
  35100. {$ifndef CPU64}
  35101. if not ByRef then
  35102. Size := sizeof(Int64);
  35103. {$endif}
  35104. end;
  35105. varStrArg: begin
  35106. if ByRef then
  35107. P := pointer(P^);
  35108. Value.VType := varString;
  35109. Value.VString := PPointer(P)^;
  35110. end;
  35111. {$ifdef HASVARUSTRARG}
  35112. varUStrArg: begin
  35113. if ByRef then
  35114. P := pointer(P^);
  35115. Value.VType := varUString;
  35116. Value.VUString := PPointer(P)^;
  35117. end;
  35118. {$endif}
  35119. varBoolean:
  35120. if ByRef then
  35121. V := PWordBool(pointer(P^))^ else
  35122. V := PWordBool(P)^;
  35123. varVariant:
  35124. {$ifdef CPU64} // circumvent Delphi x64 compiler oddiness
  35125. Value := PVarData(pointer(P^))^
  35126. {$else}
  35127. if ByRef then
  35128. Value := PVarData(pointer(P^))^ else begin
  35129. Value := PVarData(P)^;
  35130. Size := Sizeof(Value);
  35131. end;
  35132. {$endif}
  35133. else
  35134. raise EInvalidCast.CreateFmt('ParseParamPointer: Invalid VarType=%d',
  35135. [aType and TYPE_BYREF_MASK]);
  35136. end;
  35137. result := PAnsiChar(P)+Size;
  35138. end;
  35139. var
  35140. LastDispInvokeType: TSynInvokeableVariantType;
  35141. procedure SynVarDispProc(Result: PVarData; const Instance: TVarData;
  35142. CallDesc: PCallDesc; Params: Pointer); cdecl;
  35143. const DO_PROP = 1; GET_PROP = 2; SET_PROP = 4;
  35144. var Value: TVarData;
  35145. Handler: TSynInvokeableVariantType;
  35146. CacheDispInvokeType: TSynInvokeableVariantType; // to be thread-safe
  35147. begin
  35148. if Instance.VType=varByRef or varVariant then // handle By Ref variants
  35149. SynVarDispProc(Result,PVarData(Instance.VPointer)^,CallDesc,Params) else begin
  35150. if Result<>nil then
  35151. VarClear(Variant(Result^));
  35152. case Instance.VType of
  35153. varDispatch, varDispatch or varByRef,
  35154. varUnknown, varUnknown or varByRef, varAny:
  35155. // process Ole Automation variants
  35156. if Assigned(VarDispProc) then
  35157. VarDispProc(pointer(Result),Variant(Instance),CallDesc,@Params);
  35158. else begin
  35159. // first we check for our own TSynInvokeableVariantType types
  35160. if SynVariantTypes<>nil then begin
  35161. // simple cache for the latest type: most gets are grouped
  35162. CacheDispInvokeType := LastDispInvokeType;
  35163. if (CacheDispInvokeType<>nil) and
  35164. (CacheDispInvokeType.VarType=TVarData(Instance).VType) and
  35165. (CallDesc^.CallType in [GET_PROP, DO_PROP]) and
  35166. (Result<>nil) and (CallDesc^.ArgCount=0) then begin
  35167. CacheDispInvokeType.IntGet(Result^,Instance,@CallDesc^.ArgTypes[0]);
  35168. exit;
  35169. end;
  35170. end;
  35171. // handle any custom variant type
  35172. if FindCustomVariantType(Instance.VType,TCustomVariantType(Handler)) then begin
  35173. if Handler.InheritsFrom(TSynInvokeableVariantType) then
  35174. case CallDesc^.CallType of
  35175. GET_PROP, DO_PROP: // fast direct call of our IntGet() virtual method
  35176. if (Result<>nil) and (CallDesc^.ArgCount=0) then begin
  35177. Handler.IntGet(Result^,Instance,@CallDesc^.ArgTypes[0]);
  35178. LastDispInvokeType := Handler; // speed up in loop
  35179. exit;
  35180. end;
  35181. SET_PROP: // fast direct call of our IntSet() virtual method
  35182. if (Result=nil) and (CallDesc^.ArgCount=1) then begin
  35183. ParseParamPointer(@Params,CallDesc^.ArgTypes[0],Value);
  35184. Handler.IntSet(Instance,Value,@CallDesc^.ArgTypes[1]);
  35185. exit;
  35186. end;
  35187. end;
  35188. // here we call the default code handling custom types
  35189. Handler.DispInvoke({$ifdef DELPHI6OROLDER}Result^{$else}Result{$endif},
  35190. Instance,CallDesc,@Params)
  35191. end else
  35192. raise EInvalidOp.CreateFmt('Invalid variant type %d invoke',[Instance.VType]);
  35193. end;
  35194. end;
  35195. end;
  35196. end;
  35197. function VariantsDispInvokeAddress: pointer;
  35198. asm
  35199. {$ifdef CPU64}
  35200. mov rax,offset Variants.@DispInvoke
  35201. {$else}
  35202. mov eax,offset Variants.@DispInvoke
  35203. {$endif}
  35204. end;
  35205. {$ifdef DOPATCHTRTL}
  35206. {$define DOPATCHDISPINVOKE} // much faster late-binding process for our types
  35207. {$endif}
  35208. {$ifdef CPU64}
  35209. {$define DOPATCHDISPINVOKE}
  35210. // we NEED our patched DispInvoke to circumvent some Delphi bugs on Win64
  35211. {$endif}
  35212. {$ifdef DELPHI6OROLDER}
  35213. {$define DOPATCHDISPINVOKE}
  35214. // to circumvent LIdent := Uppercase() in TInvokeableVariantType.DispInvoke()
  35215. {$endif}
  35216. {$endif FPC}
  35217. function SynRegisterCustomVariantType(aClass: TSynInvokeableVariantTypeClass): TSynInvokeableVariantType;
  35218. var i: integer;
  35219. {$ifdef DOPATCHDISPINVOKE}
  35220. {$ifdef NOVARCOPYPROC}
  35221. VarMgr: TVariantManager;
  35222. {$endif}
  35223. {$endif}
  35224. begin
  35225. if SynVariantTypes=nil then begin
  35226. {$ifndef FPC}
  35227. {$ifdef DOPATCHDISPINVOKE}
  35228. {$ifndef CPU64} // we NEED our patched RTL on Win64
  35229. if DebugHook=0 then // patch VCL/RTL only outside debugging
  35230. {$endif} begin
  35231. {$ifdef NOVARCOPYPROC}
  35232. GetVariantManager(VarMgr);
  35233. VarMgr.DispInvoke := @SynVarDispProc;
  35234. SetVariantManager(VarMgr);
  35235. {$else}
  35236. RedirectCode(VariantsDispInvokeAddress,@SynVarDispProc);
  35237. {$endif NOVARCOPYPROC}
  35238. end;
  35239. {$endif DOPATCHDISPINVOKE}
  35240. {$endif FPC}
  35241. GarbageCollectorFreeAndNil(SynVariantTypes,TObjectList.Create);
  35242. end else
  35243. for i := 0 to SynVariantTypes.Count-1 do
  35244. if PPointer(SynVariantTypes.List[i])^=pointer(aClass) then begin
  35245. result := SynVariantTypes.List[i]; // returns already registered instance
  35246. exit;
  35247. end;
  35248. result := aClass.Create; // register variant type
  35249. SynVariantTypes.Add(result);
  35250. if aClass=TDocVariant then
  35251. DocVariantVType := result.VarType;
  35252. end;
  35253. function VariantDynArrayToJSON(const V: TVariantDynArray): RawUTF8;
  35254. var tmp: TDocVariantData;
  35255. begin
  35256. tmp.InitArrayFromVariants(V);
  35257. result := tmp.ToJSON;
  35258. end;
  35259. function JSONToVariantDynArray(const JSON: RawUTF8): TVariantDynArray;
  35260. var tmp: TDocVariantData;
  35261. begin
  35262. tmp.InitJSON(JSON,JSON_OPTIONS_FAST);
  35263. result := tmp.VValue;
  35264. end;
  35265. function ValuesToVariantDynArray(const items: array of const): TVariantDynArray;
  35266. var tmp: TDocVariantData;
  35267. begin
  35268. tmp.InitArray(items,JSON_OPTIONS_FAST);
  35269. result := tmp.VValue;
  35270. end;
  35271. function VariantTypeToSQLDBFieldType(const V: Variant): TSQLDBFieldType;
  35272. var tmp: TVarData;
  35273. begin
  35274. with TVarData(V) do
  35275. case VType of
  35276. varEmpty:
  35277. result := ftUnknown;
  35278. varNull:
  35279. result := ftNull;
  35280. {$ifndef DELPHI5OROLDER}varShortInt, varWord, varLongWord,{$endif}
  35281. varSmallInt, varByte, varBoolean, varInteger, varInt64, varWord64:
  35282. result := ftInt64;
  35283. varSingle,varDouble:
  35284. result := ftDouble;
  35285. varDate:
  35286. result := ftDate;
  35287. varCurrency:
  35288. result := ftCurrency;
  35289. varString:
  35290. if (VString<>nil) and (PCardinal(VString)^ and $ffffff=JSON_BASE64_MAGIC) then
  35291. result := ftBlob else
  35292. result := ftUTF8;
  35293. else
  35294. if SetVariantUnRefSimpleValue(V,tmp) then
  35295. result := VariantTypeToSQLDBFieldType(variant(tmp)) else
  35296. result := ftUTF8;
  35297. end;
  35298. end;
  35299. { TDocVariantData }
  35300. procedure TDocVariantData.Init(aOptions: TDocVariantOptions; aKind: TDocVariantKind);
  35301. begin
  35302. if DocVariantType=nil then
  35303. DocVariantType := SynRegisterCustomVariantType(TDocVariant);
  35304. ZeroFill(@self);
  35305. VType := DocVariantVType;
  35306. VOptions := aOptions;
  35307. VKind := aKind;
  35308. end;
  35309. procedure TDocVariantData.InitFast;
  35310. begin
  35311. if DocVariantType=nil then
  35312. DocVariantType := SynRegisterCustomVariantType(TDocVariant);
  35313. ZeroFill(@self);
  35314. VType := DocVariantVType;
  35315. VOptions := JSON_OPTIONS_FAST;
  35316. end;
  35317. procedure TDocVariantData.InitFast(InitialCapacity: integer; aKind: TDocVariantKind);
  35318. begin
  35319. Init(JSON_OPTIONS_FAST,aKind);
  35320. if aKind=dvObject then
  35321. SetLength(VName,InitialCapacity);
  35322. SetLength(VValue,InitialCapacity);
  35323. end;
  35324. procedure TDocVariantData.InitObject(const NameValuePairs: array of const;
  35325. aOptions: TDocVariantOptions=[]);
  35326. begin
  35327. Init(aOptions,dvObject);
  35328. AddNameValuesToObject(NameValuePairs);
  35329. end;
  35330. procedure TDocVariantData.AddNameValuesToObject(const NameValuePairs: array of const);
  35331. var n,arg: integer;
  35332. tmp: variant;
  35333. begin
  35334. n := length(NameValuePairs) shr 1;
  35335. if (n=0) or (VKind=dvArray) then
  35336. exit; // nothing to add
  35337. VKind := dvObject;
  35338. SetLength(VValue,VCount+n);
  35339. SetLength(VName,VCount+n);
  35340. for arg := 0 to n-1 do begin
  35341. VarRecToUTF8(NameValuePairs[arg*2],VName[arg+VCount]);
  35342. if dvoValueCopiedByReference in VOptions then
  35343. VarRecToVariant(NameValuePairs[arg*2+1],VValue[arg+VCount]) else begin
  35344. VarRecToVariant(NameValuePairs[arg*2+1],tmp);
  35345. SetVariantByValue(tmp,VValue[arg+VCount]);
  35346. end;
  35347. end;
  35348. inc(VCount,n);
  35349. end;
  35350. procedure TDocVariantData.AddOrUpdateNameValuesToObject(const NameValuePairs: array of const);
  35351. var n,arg: integer;
  35352. n2: RawUTF8;
  35353. v: Variant;
  35354. begin
  35355. n := length(NameValuePairs) shr 1;
  35356. if (n=0) or (VKind=dvArray) then
  35357. exit; // nothing to add
  35358. for arg := 0 to n-1 do begin
  35359. VarRecToUTF8(NameValuePairs[arg*2],n2);
  35360. VarRecToVariant(NameValuePairs[arg*2+1],v);
  35361. AddOrUpdateValue(n2,v)
  35362. end;
  35363. end;
  35364. procedure TDocVariantData.AddOrUpdateObject(const NewValues: variant;
  35365. OnlyAddMissing: boolean);
  35366. var n: integer;
  35367. new: PDocVariantData;
  35368. begin
  35369. new := _Safe(NewValues);
  35370. if (VKind<>dvArray) and (new^.VKind<>dvArray) then
  35371. for n := 0 to new^.Count-1 do
  35372. AddOrUpdateValue(new^.Names[n],new^.Values[n],nil,OnlyAddMissing);
  35373. end;
  35374. procedure TDocVariantData.InitArray(const Items: array of const;
  35375. aOptions: TDocVariantOptions=[]);
  35376. var arg: integer;
  35377. tmp: variant;
  35378. begin
  35379. Init(aOptions,dvArray);
  35380. if high(Items)>=0 then begin
  35381. VCount := length(Items);
  35382. SetLength(VValue,VCount);
  35383. if dvoValueCopiedByReference in aOptions then
  35384. for arg := 0 to high(Items) do
  35385. VarRecToVariant(Items[arg],VValue[arg]) else
  35386. for arg := 0 to high(Items) do begin
  35387. VarRecToVariant(Items[arg],tmp);
  35388. SetVariantByValue(tmp,VValue[arg]);
  35389. end;
  35390. end;
  35391. end;
  35392. procedure TDocVariantData.InitArrayFromVariants(const Items: TVariantDynArray;
  35393. aOptions: TDocVariantOptions; ItemsCopiedByReference: boolean);
  35394. begin
  35395. if Items=nil then
  35396. VType := varNull else begin
  35397. Init(aOptions,dvArray);
  35398. VCount := length(Items);
  35399. VValue := Items; // fast by-reference copy of VValue[]
  35400. if not ItemsCopiedByReference then
  35401. InitCopy(variant(self),aOptions);
  35402. end;
  35403. end;
  35404. procedure TDocVariantData.InitArrayFromObjArray(const ObjArray;
  35405. aOptions: TDocVariantOptions; aWriterOptions: TTextWriterWriteObjectOptions);
  35406. var ndx: integer;
  35407. Items: TObjectDynArray absolute ObjArray;
  35408. begin
  35409. if Items=nil then
  35410. VType := varNull else begin
  35411. Init(aOptions,dvArray);
  35412. VCount := length(Items);
  35413. SetLength(VValue,VCount);
  35414. for ndx := 0 to VCount-1 do
  35415. ObjectToVariant(Items[ndx],VValue[ndx],aWriterOptions);
  35416. end;
  35417. end;
  35418. procedure TDocVariantData.InitArrayFrom(const Items: TRawUTF8DynArray; aOptions: TDocVariantOptions);
  35419. var ndx: integer;
  35420. begin
  35421. if Items=nil then
  35422. VType := varNull else begin
  35423. Init(aOptions,dvArray);
  35424. VCount := length(Items);
  35425. SetLength(VValue,VCount);
  35426. for ndx := 0 to VCount-1 do
  35427. RawUTF8ToVariant(Items[ndx],VValue[ndx]);
  35428. end;
  35429. end;
  35430. procedure TDocVariantData.InitArrayFrom(const Items: TIntegerDynArray; aOptions: TDocVariantOptions);
  35431. var ndx: integer;
  35432. begin
  35433. if Items=nil then
  35434. VType := varNull else begin
  35435. Init(aOptions,dvArray);
  35436. VCount := length(Items);
  35437. SetLength(VValue,VCount);
  35438. for ndx := 0 to VCount-1 do
  35439. VValue[ndx] := Items[ndx];
  35440. end;
  35441. end;
  35442. procedure TDocVariantData.InitArrayFrom(const Items: TInt64DynArray; aOptions: TDocVariantOptions);
  35443. var ndx: integer;
  35444. begin
  35445. if Items=nil then
  35446. VType := varNull else begin
  35447. Init(aOptions,dvArray);
  35448. VCount := length(Items);
  35449. SetLength(VValue,VCount);
  35450. for ndx := 0 to VCount-1 do
  35451. VValue[ndx] := Items[ndx];
  35452. end;
  35453. end;
  35454. procedure TDocVariantData.InitFromTypeInfo(const aValue; aTypeInfo: pointer;
  35455. aEnumSetsAsText: boolean; aOptions: TDocVariantOptions);
  35456. var tmp: RawUTF8;
  35457. begin
  35458. SaveJSON(aValue,aTypeInfo,aEnumSetsAsText, tmp);
  35459. InitJSONInPlace(pointer(tmp),aOptions);
  35460. end;
  35461. procedure TDocVariantData.InitObjectFromVariants(const aNames: TRawUTF8DynArray;
  35462. const aValues: TVariantDynArray; aOptions: TDocVariantOptions=[]);
  35463. begin
  35464. if (aNames=nil) or (aValues=nil) or (length(aNames)<>length(aValues)) then
  35465. VType := varNull else begin
  35466. Init(aOptions,dvObject);
  35467. VCount := length(aNames);
  35468. VName := aNames; // fast by-reference copy of VName[] and VValue[]
  35469. VValue := aValues;
  35470. end;
  35471. end;
  35472. procedure TDocVariantData.InitObjectFromPath(const aPath: RawUTF8; const aValue: variant;
  35473. aOptions: TDocVariantOptions=[]);
  35474. var right: RawUTF8;
  35475. begin
  35476. if aPath='' then
  35477. VType := varNull else begin
  35478. Init(aOptions,dvObject);
  35479. VCount := 1;
  35480. SetLength(VName,1);
  35481. SetLength(VValue,1);
  35482. split(aPath,'.',VName[0],right);
  35483. if right='' then
  35484. VValue[0] := aValue else
  35485. PDocVariantData(@VValue[0])^.InitObjectFromPath(right,aValue,aOptions);
  35486. end;
  35487. end;
  35488. function TDocVariantData.InitJSONInPlace(JSON: PUTF8Char;
  35489. aOptions: TDocVariantOptions; aEndOfObject: PUTF8Char): PUTF8Char;
  35490. var EndOfObject: AnsiChar;
  35491. Name: PUTF8Char;
  35492. n: integer;
  35493. begin
  35494. Init(aOptions);
  35495. result := nil;
  35496. if JSON=nil then
  35497. exit;
  35498. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  35499. case JSON^ of
  35500. '[': begin
  35501. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  35502. n := JSONArrayCount(JSON); // may be slow if JSON is huge (not very common)
  35503. if n<0 then
  35504. exit; // invalid content
  35505. VKind := dvArray;
  35506. if n>0 then begin
  35507. SetLength(VValue,n);
  35508. repeat
  35509. if VCount>=n then
  35510. exit; // unexpected array size means invalid JSON
  35511. GetJSONToAnyVariant(VValue[VCount],JSON,@EndOfObject,@VOptions);
  35512. if JSON=nil then
  35513. exit;
  35514. inc(VCount);
  35515. until EndOfObject=']';
  35516. end else
  35517. if JSON^=']' then // n=0
  35518. repeat inc(JSON) until not(JSON^ in [#1..' ']) else
  35519. exit;
  35520. end;
  35521. '{': begin
  35522. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  35523. n := JSONObjectPropCount(JSON); // may be slow if JSON is huge (not very common)
  35524. if n<0 then
  35525. exit; // invalid content
  35526. VKind := dvObject;
  35527. if n>0 then begin
  35528. SetLength(VValue,n);
  35529. SetLength(VName,n);
  35530. repeat
  35531. if VCount>=n then
  35532. exit; // unexpected object size means invalid JSON
  35533. // see http://docs.mongodb.org/manual/reference/mongodb-extended-json
  35534. Name := GetJSONPropName(JSON);
  35535. if Name=nil then
  35536. exit;
  35537. SetString(VName[VCount],PAnsiChar(Name),StrLen(Name));
  35538. GetJSONToAnyVariant(VValue[VCount],JSON,@EndOfObject,@VOptions);
  35539. if JSON=nil then
  35540. exit;
  35541. inc(VCount);
  35542. until EndOfObject='}';
  35543. end else
  35544. if JSON^='}' then // n=0
  35545. repeat inc(JSON) until not(JSON^ in [#1..' ']) else
  35546. exit;
  35547. end;
  35548. 'n','N': begin
  35549. if IdemPChar(JSON+1,'ULL') then begin
  35550. VKind := dvObject;
  35551. result := GotoNextNotSpace(JSON+4);
  35552. end;
  35553. exit;
  35554. end;
  35555. else exit;
  35556. end;
  35557. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  35558. if aEndOfObject<>nil then
  35559. aEndOfObject^ := JSON^;
  35560. if JSON^<>#0 then
  35561. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  35562. result := JSON; // indicates successfully parsed
  35563. end;
  35564. function TDocVariantData.InitJSON(const JSON: RawUTF8;
  35565. aOptions: TDocVariantOptions): boolean;
  35566. var tmp: TSynTempBuffer;
  35567. begin
  35568. if JSON='' then
  35569. result := false else begin
  35570. tmp.Init(JSON);
  35571. try
  35572. result := InitJSONInPlace(tmp.buf,aOptions)<>nil;
  35573. finally
  35574. tmp.Done;
  35575. end;
  35576. end;
  35577. end;
  35578. function TDocVariantData.InitJSONFromFile(const JsonFile: TFileName;
  35579. aOptions: TDocVariantOptions): boolean;
  35580. begin
  35581. result := InitJSONInPlace(pointer(AnyTextFileToRawUTF8(JsonFile,true)),aOptions)<>nil;
  35582. end;
  35583. procedure TDocVariantData.InitCSV(CSV: PUTF8Char; aOptions: TDocVariantOptions;
  35584. NameValueSep, ItemSep: AnsiChar; DoTrim: boolean);
  35585. var n,v: RawUTF8;
  35586. val: variant;
  35587. begin
  35588. Init(aOptions,dvObject);
  35589. while CSV<>nil do begin
  35590. n := GetNextItem(CSV,NameValueSep);
  35591. v := GetNextItem(CSV,ItemSep);
  35592. if DoTrim then
  35593. v := trim(v);
  35594. if n='' then
  35595. break;
  35596. RawUTF8ToVariant(v,val);
  35597. AddValue(n,val);
  35598. end;
  35599. end;
  35600. procedure TDocVariantData.InitCSV(const CSV: RawUTF8; aOptions: TDocVariantOptions;
  35601. NameValueSep, ItemSep: AnsiChar; DoTrim: boolean);
  35602. begin
  35603. InitCSV(pointer(CSV),aOptions,NameValueSep,ItemSep,DoTrim);
  35604. end;
  35605. procedure TDocVariantData.InitCopy(const SourceDocVariant: variant;
  35606. aOptions: TDocVariantOptions);
  35607. var ndx: integer;
  35608. Source: PDocVariantData;
  35609. SourceVValue: TVariantDynArray;
  35610. Handler: TCustomVariantType;
  35611. t: word;
  35612. v: PVarData;
  35613. begin
  35614. with TVarData(SourceDocVariant) do
  35615. if VType=varByRef or varVariant then
  35616. Source := VPointer else
  35617. Source := @SourceDocVariant;
  35618. if (DocVariantType=nil) or (Source^.VType<>DocVariantVType) then
  35619. raise ESynException.CreateUTF8('No TDocVariant for InitCopy(%)',[Source.VType]);
  35620. SourceVValue := Source^.VValue; // local fast per-reference copy
  35621. if Source<>@self then begin
  35622. VType := DocVariantVType;
  35623. VKind := Source^.Kind;
  35624. VCount := Source^.VCount;
  35625. pointer(VName) := nil; // avoid GPF
  35626. VName := Source^.VName;
  35627. pointer(VValue) := nil;
  35628. end else
  35629. VariantDynArrayClear(VValue); // force re-create full copy of all values
  35630. if VCount>0 then begin
  35631. SetLength(VValue,VCount);
  35632. for ndx := 0 to VCount-1 do begin
  35633. v := @SourceVValue[ndx];
  35634. while v^.VType=varByRef or varVariant do
  35635. v := v^.VPointer;
  35636. t := v^.VType;
  35637. if t<=varNativeString then // simple string/number types copy
  35638. VValue[ndx] := variant(v^) else
  35639. if t=VType then // direct recursive copy for TDocVariant
  35640. TDocVariantData(VValue[ndx]).InitCopy(variant(v^),aOptions) else
  35641. if FindCustomVariantType(t,Handler) then
  35642. if Handler.InheritsFrom(TSynInvokeableVariantType) then
  35643. TSynInvokeableVariantType(Handler).CopyByValue(
  35644. TVarData(VValue[ndx]),v^) else
  35645. Handler.Copy(TVarData(VValue[ndx]),v^,false) else
  35646. VValue[ndx] := variant(v^); // default copy
  35647. end;
  35648. end;
  35649. VariantDynArrayClear(SourceVValue); // faster alternative
  35650. VOptions := aOptions; // may not be the same as in Source
  35651. end;
  35652. procedure TDocVariantData.Clear;
  35653. begin
  35654. if VType=DocVariantVType then
  35655. DocVariantType.Clear(TVarData(self)) else
  35656. VarClear(variant(self));
  35657. end;
  35658. procedure TDocVariantData.Reset;
  35659. var opt: TDocVariantOptions;
  35660. begin
  35661. opt := VOptions;
  35662. DocVariantType.Clear(TVarData(self));
  35663. VType := DocVariantVType;
  35664. VOptions := opt;
  35665. end;
  35666. procedure TDocVariantData.SetCount(aCount: integer);
  35667. begin
  35668. VCount := aCount;
  35669. end;
  35670. function TDocVariantData.InternalAdd(const aName: RawUTF8): integer;
  35671. var len: integer;
  35672. begin
  35673. if aName<>'' then
  35674. case Kind of // aName is set for an object
  35675. dvUndefined: begin
  35676. VType := DocVariantVType; // may not be set yet
  35677. VKind := dvObject;
  35678. end;
  35679. dvArray:
  35680. raise EDocVariant.CreateUTF8('Unexpected "%" property name in an array',[aName]);
  35681. end else
  35682. case Kind of // aName is not set for an array
  35683. dvUndefined: begin
  35684. VType := DocVariantVType; // may not be set yet
  35685. VKind := dvArray;
  35686. end;
  35687. dvObject:
  35688. raise EDocVariant.Create('Unexpected array item added to an object');
  35689. end;
  35690. if VValue=nil then
  35691. SetLength(VValue,16) else
  35692. if VCount>=length(VValue) then
  35693. SetLength(VValue,VCount+VCount shr 3+32);
  35694. if VKind=dvObject then begin
  35695. len := length(VValue);
  35696. if Length(VName)<>len then
  35697. SetLength(VName,len);
  35698. VName[VCount] := aName;
  35699. end;
  35700. result := VCount;
  35701. inc(VCount);
  35702. end;
  35703. procedure TDocVariantData.SetCapacity(aValue: integer);
  35704. begin
  35705. if VKind=dvObject then
  35706. SetLength(VName,aValue);
  35707. SetLength(VValue,aValue);
  35708. end;
  35709. function TDocVariantData.GetCapacity: integer;
  35710. begin
  35711. result := length(VValue);
  35712. end;
  35713. function TDocVariantData.AddValue(const aName: RawUTF8; const aValue: variant): integer;
  35714. var ndx: integer;
  35715. begin
  35716. if dvoCheckForDuplicatedNames in VOptions then begin
  35717. ndx := GetValueIndex(aName);
  35718. if ndx>=0 then
  35719. raise EDocVariant.CreateUTF8('Duplicated "%" name',[aName]);
  35720. end;
  35721. result := InternalAdd(aName); // FPC does not allow VValue[InternalAdd(aName)]
  35722. SetVariantByValue(aValue,VValue[result]);
  35723. end;
  35724. function TDocVariantData.AddValue(aName: PUTF8Char; aNameLen: integer; const aValue: variant): integer;
  35725. var tmp: RawUTF8;
  35726. begin
  35727. SetString(tmp,PAnsiChar(aName),aNameLen);
  35728. result := AddValue(tmp,aValue);
  35729. end;
  35730. function TDocVariantData.AddValueFromText(const aName,aValue: RawUTF8; Update: boolean): integer;
  35731. begin
  35732. if aName='' then begin
  35733. result := -1;
  35734. exit;
  35735. end;
  35736. result := GetValueIndex(aName);
  35737. if (not Update) and (dvoCheckForDuplicatedNames in VOptions) and (result>=0) then
  35738. raise EDocVariant.CreateUTF8('Duplicated "%" name',[aName]);
  35739. if result<0 then
  35740. result := InternalAdd(aName);
  35741. VarClear(VValue[result]);
  35742. if not GetNumericVariantFromJSON(pointer(aValue),TVarData(VValue[result])) then
  35743. RawUTF8ToVariant(aValue,VValue[result]);
  35744. end;
  35745. procedure TDocVariantData.AddByPath(const aSource: TDocVariantData; const aPaths: array of RawUTF8);
  35746. var p,added: integer;
  35747. v: TVarData;
  35748. begin
  35749. if (aSource.Count=0) or (aSource.VKind<>dvObject) or (VKind=dvArray) then
  35750. exit;
  35751. for p := 0 to High(aPaths) do begin
  35752. DocVariantType.Lookup(v,TVarData(aSource),pointer(aPaths[p]));
  35753. if v.VType<varNull then
  35754. continue; // path not found
  35755. added := InternalAdd(aPaths[p]);
  35756. PVarData(@VValue[added])^ := v;
  35757. end;
  35758. end;
  35759. procedure TDocVariantData.AddFrom(const aDocVariant: Variant);
  35760. var source: PDocVariantData;
  35761. ndx: integer;
  35762. begin
  35763. source := _Safe(aDocVariant);
  35764. if source^.Count=0 then
  35765. exit; // nothing to add
  35766. if (VKind<>dvUndefined) and (VKind<>source^.VKind) then
  35767. exit; // types should match
  35768. if source^.VKind=dvArray then
  35769. for ndx := 0 to source^.Count-1 do
  35770. AddItem(source^.VValue[ndx]) else
  35771. for ndx := 0 to source^.Count-1 do
  35772. AddValue(source^.VName[ndx],source^.VValue[ndx]);
  35773. end;
  35774. function TDocVariantData.AddItem(const aValue: variant): integer;
  35775. begin
  35776. result := InternalAdd(''); // FPC does not allow VValue[InternalAdd(aName)]
  35777. SetVariantByValue(aValue,VValue[result]);
  35778. end;
  35779. procedure TDocVariantData.AddItems(const aValue: array of const);
  35780. var ndx,added: integer;
  35781. begin
  35782. for ndx := 0 to high(aValue) do begin
  35783. added := InternalAdd(''); // FPC does not allow VValue[InternalAdd(aName)]
  35784. VarRecToVariant(aValue[ndx],VValue[added]);
  35785. end;
  35786. end;
  35787. function TDocVariantData.SearchItemByProp(const aPropName,aPropValue: RawUTF8;
  35788. aCaseSensitive: boolean): integer;
  35789. var ndx: integer;
  35790. tmp: RawUTF8;
  35791. wasString: boolean;
  35792. v: PVarData;
  35793. begin
  35794. if VKind=dvArray then
  35795. for result := 0 to VCount-1 do
  35796. with _Safe(VValue[result])^ do
  35797. if VKind=dvObject then begin
  35798. ndx := GetValueIndex(aPropName);
  35799. if ndx>=0 then begin
  35800. v := @VValue[ndx];
  35801. case v^.VType of
  35802. varEmpty,varNull:
  35803. if aPropValue='' then
  35804. exit; // VariantToUTF8(null)='null'
  35805. varString:
  35806. if aCaseSensitive then begin
  35807. if RawUTF8(v^.VAny)=aPropValue then
  35808. exit;
  35809. end else
  35810. if IdemPropNameU(RawUTF8(v^.VAny),aPropValue) then
  35811. exit;
  35812. else begin
  35813. VariantToUTF8(PVariant(v)^,tmp,wasString);
  35814. if aCaseSensitive then begin
  35815. if tmp=aPropValue then
  35816. exit;
  35817. end else
  35818. if IdemPropNameU(tmp,aPropValue) then
  35819. exit;
  35820. end;
  35821. end;
  35822. end;
  35823. end;
  35824. result := -1;
  35825. end;
  35826. function TDocVariantData.SearchItemByValue(const aValue: Variant;
  35827. CaseInsensitive: boolean; StartIndex: integer): integer;
  35828. begin
  35829. for result := StartIndex to VCount-1 do
  35830. if SortDynArrayVariantComp(TVarData(VValue[result]),TVarData(aValue),CaseInsensitive)=0 then
  35831. exit;
  35832. result := -1;
  35833. end;
  35834. procedure QuickSortDocVariant(names: PPointerArray; values: PVariantArray;
  35835. L, R: PtrInt; Compare: TUTF8Compare);
  35836. var I, J, P: PtrInt;
  35837. pivot, Tmp: pointer;
  35838. begin
  35839. if L<R then
  35840. repeat
  35841. I := L; J := R;
  35842. P := (L + R) shr 1;
  35843. repeat
  35844. pivot := names[P];
  35845. while Compare(names[I],pivot)<0 do Inc(I);
  35846. while Compare(names[J],pivot)>0 do Dec(J);
  35847. if I <= J then begin
  35848. Tmp := names[J]; names[J] := names[I]; names[I] := Tmp;
  35849. {$ifdef CPU64}
  35850. Exchg(@values[I],@values[J],sizeof(TVarData));
  35851. {$else}
  35852. Exchg16(@values[I],@values[J]);
  35853. {$endif}
  35854. if P = I then P := J else if P = J then P := I;
  35855. inc(I); dec(J);
  35856. end;
  35857. until I > J;
  35858. if L < J then
  35859. QuickSortDocVariant(names,values,L,J,Compare);
  35860. L := I;
  35861. until I >= R;
  35862. end;
  35863. procedure TDocVariantData.SortByName(Compare: TUTF8Compare=nil);
  35864. begin
  35865. if (VKind<>dvObject) or (VCount=0) then
  35866. exit;
  35867. if not Assigned(Compare) then
  35868. Compare := @StrIComp;
  35869. QuickSortDocVariant(pointer(VName),pointer(VValue),0,VCount-1,Compare);
  35870. end;
  35871. procedure TDocVariantData.ExchgValues(v1,v2: integer);
  35872. var n: pointer;
  35873. v: TVarData;
  35874. begin
  35875. if VName<>nil then begin // VName=[] for dvArray
  35876. n := pointer(VName[v2]);
  35877. pointer(VName[v2]) := pointer(VName[v1]);
  35878. PPointerArray(VName)[v1] := n;
  35879. end;
  35880. v := TVarData(VValue[v2]);
  35881. TVarData(VValue[v2]) := TVarData(VValue[v1]);
  35882. TVarData(VValue[v1]) := v;
  35883. end;
  35884. procedure QuickSortDocVariantValues(var Doc: TDocVariantData;
  35885. L, R: PtrInt; Compare: TVariantCompare);
  35886. var I, J, P: PtrInt;
  35887. pivot: PVariant;
  35888. begin
  35889. if L<R then
  35890. repeat
  35891. I := L; J := R;
  35892. P := (L + R) shr 1;
  35893. repeat
  35894. pivot := @Doc.VValue[P];
  35895. while Compare(Doc.VValue[I],pivot^)<0 do Inc(I);
  35896. while Compare(Doc.VValue[J],pivot^)>0 do Dec(J);
  35897. if I <= J then begin
  35898. Doc.ExchgValues(I,J);
  35899. if P = I then P := J else if P = J then P := I;
  35900. inc(I); dec(J);
  35901. end;
  35902. until I > J;
  35903. if L < J then
  35904. QuickSortDocVariantValues(Doc,L,J,Compare);
  35905. L := I;
  35906. until I >= R;
  35907. end;
  35908. procedure TDocVariantData.SortByValue(Compare: TVariantCompare);
  35909. begin
  35910. if VCount>0 then
  35911. QuickSortDocVariantValues(self,0,VCount-1,Compare);
  35912. end;
  35913. procedure TDocVariantData.Reverse;
  35914. var arr: TDynArray;
  35915. begin
  35916. if (VKind=dvUndefined) or (VCount=0) then
  35917. exit;
  35918. if VName<>nil then begin
  35919. SetLength(VName,VCount);
  35920. arr.Init(TypeInfo(TRawUTF8DynArray),VName);
  35921. arr.Reverse;
  35922. end;
  35923. if VValue<>nil then begin
  35924. SetLength(VValue,VCount);
  35925. arr.Init(TypeInfo(TVariantDynArray),VValue);
  35926. arr.Reverse;
  35927. end;
  35928. end;
  35929. function TDocVariantData.Reduce(const aPropNames: array of RawUTF8;
  35930. aCaseSensitive,aDoNotAddVoidProp: boolean): variant;
  35931. begin
  35932. VarClear(result);
  35933. Reduce(aPropNames,aCaseSensitive,PDocVariantData(@result)^,aDoNotAddVoidProp);
  35934. end;
  35935. procedure TDocVariantData.Reduce(const aPropNames: array of RawUTF8;
  35936. aCaseSensitive: boolean; out result: TDocVariantData; aDoNotAddVoidProp: boolean);
  35937. var ndx,j: integer;
  35938. reduced: TDocVariantData;
  35939. begin
  35940. result.InitFast;
  35941. if (VCount=0) or (high(aPropNames)<0) then
  35942. exit;
  35943. case VKind of
  35944. dvObject:
  35945. if aCaseSensitive then begin
  35946. for j := 0 to high(aPropNames) do
  35947. for ndx := 0 to VCount-1 do
  35948. if VName[ndx]=aPropNames[j] then begin
  35949. if (not aDoNotAddVoidProp) or (not VarIsVoid(VValue[ndx])) then
  35950. result.AddValue(VName[ndx],VValue[ndx]);
  35951. break;
  35952. end;
  35953. end else
  35954. for j := 0 to high(aPropNames) do
  35955. for ndx := 0 to VCount-1 do
  35956. if IdemPropNameU(VName[ndx],aPropNames[j]) then begin
  35957. if (not aDoNotAddVoidProp) or (not VarIsVoid(VValue[ndx])) then
  35958. result.AddValue(VName[ndx],VValue[ndx]);
  35959. break;
  35960. end;
  35961. dvArray:
  35962. for ndx := 0 to VCount-1 do begin
  35963. _Safe(VValue[ndx])^.Reduce(aPropNames,aCaseSensitive,reduced,aDoNotAddVoidProp);
  35964. if reduced.VKind=dvObject then
  35965. result.AddItem(variant(reduced));
  35966. end;
  35967. end;
  35968. end;
  35969. function TDocVariantData.ReduceAsArray(const aPropName: RawUTF8;
  35970. OnReduce: TOnReducePerItem): variant;
  35971. begin
  35972. VarClear(result);
  35973. ReduceAsArray(aPropName,PDocVariantData(@result)^,OnReduce);
  35974. end;
  35975. procedure TDocVariantData.ReduceAsArray(const aPropName: RawUTF8;
  35976. out result: TDocVariantData; OnReduce: TOnReducePerItem);
  35977. var ndx,j: integer;
  35978. item: PDocVariantData;
  35979. begin
  35980. result.InitFast;
  35981. if (VCount=0) or (aPropName='') or (VKind<>dvArray) then
  35982. exit;
  35983. for ndx := 0 to VCount-1 do begin
  35984. item := _Safe(VValue[ndx]);
  35985. j := item^.GetValueIndex(aPropName);
  35986. if j>=0 then
  35987. if (not Assigned(OnReduce)) or OnReduce(item) then
  35988. result.AddItem(item^.VValue[j]);
  35989. end;
  35990. end;
  35991. function TDocVariantData.ReduceAsArray(const aPropName: RawUTF8;
  35992. OnReduce: TOnReducePerValue): variant;
  35993. begin
  35994. VarClear(result);
  35995. ReduceAsArray(aPropName,PDocVariantData(@result)^,OnReduce);
  35996. end;
  35997. procedure TDocVariantData.ReduceAsArray(const aPropName: RawUTF8;
  35998. out result: TDocVariantData; OnReduce: TOnReducePerValue);
  35999. var ndx,j: integer;
  36000. item: PDocVariantData;
  36001. v: PVariant;
  36002. begin
  36003. result.InitFast;
  36004. if (VCount=0) or (aPropName='') or (VKind<>dvArray) then
  36005. exit;
  36006. for ndx := 0 to VCount-1 do begin
  36007. item := _Safe(VValue[ndx]);
  36008. j := item^.GetValueIndex(aPropName);
  36009. if j>=0 then begin
  36010. v := @item^.VValue[j];
  36011. if (not Assigned(OnReduce)) or OnReduce(v^) then
  36012. result.AddItem(v^);
  36013. end;
  36014. end;
  36015. end;
  36016. function TDocVariantData.FlattenAsNestedObject(const aObjectPropName: RawUTF8): boolean;
  36017. var ndx,len: integer;
  36018. Up: array[byte] of AnsiChar;
  36019. nested: TDocVariantData;
  36020. begin // {"p.a1":5,"p.a2":"dfasdfa"} -> {"p":{"a1":5,"a2":"dfasdfa"}}
  36021. result := false;
  36022. if (VCount=0) or (aObjectPropName='') or (VKind<>dvObject) then
  36023. exit;
  36024. PWord(UpperCopy255(Up,aObjectPropName))^ := ord('.'); // e.g. 'P.'
  36025. for ndx := 0 to Count-1 do
  36026. if not IdemPChar(pointer(VName[ndx]),Up) then
  36027. exit; // all fields should match "p.####"
  36028. len := length(aObjectPropName)+1;
  36029. for ndx := 0 to Count-1 do
  36030. system.delete(VName[ndx],1,len);
  36031. nested := self;
  36032. Clear;
  36033. InitObject([aObjectPropName,variant(nested)]);
  36034. result := true;
  36035. end;
  36036. function TDocVariantData.Delete(Index: integer): boolean;
  36037. begin
  36038. if cardinal(Index)>=cardinal(VCount) then
  36039. result := false else begin
  36040. dec(VCount);
  36041. if VName<>nil then
  36042. VName[Index] := '';
  36043. VarClear(VValue[Index]);
  36044. if Index<VCount then begin
  36045. if VName<>nil then begin
  36046. MoveFast(VName[Index+1],VName[Index],(VCount-Index)*sizeof(pointer));
  36047. PtrUInt(VName[VCount]) := 0; // avoid GPF
  36048. end;
  36049. MoveFast(VValue[Index+1],VValue[Index],(VCount-Index)*sizeof(variant));
  36050. TVarData(VValue[VCount]).VType := varEmpty; // avoid GPF
  36051. end;
  36052. result := true;
  36053. end;
  36054. end;
  36055. function TDocVariantData.Delete(const aName: RawUTF8): boolean;
  36056. begin
  36057. result := Delete(GetValueIndex(aName));
  36058. end;
  36059. function TDocVariantData.DeleteByProp(const aPropName,aPropValue: RawUTF8;
  36060. aCaseSensitive: boolean): boolean;
  36061. begin
  36062. result := Delete(SearchItemByProp(aPropName,aPropValue,aCaseSensitive));
  36063. end;
  36064. function TDocVariantData.DeleteByValue(const aValue: Variant;
  36065. CaseInsensitive: boolean=false): boolean;
  36066. var ndx: integer;
  36067. begin
  36068. result := false;
  36069. for ndx := VCount-1 downto 0 do
  36070. if SortDynArrayVariantComp(TVarData(VValue[ndx]),TVarData(aValue),CaseInsensitive)=0 then begin
  36071. Delete(ndx);
  36072. result := true;
  36073. end;
  36074. end;
  36075. function TDocVariantData.DeleteByStartName(aStartName: PUTF8Char; aStartNameLen: integer): integer;
  36076. var ndx: integer;
  36077. upname: array[byte] of AnsiChar;
  36078. begin
  36079. result := 0;
  36080. if aStartNameLen=0 then
  36081. aStartNameLen := StrLen(aStartName);
  36082. if (VCount=0) or (VKind<>dvObject) or (aStartNameLen=0) then
  36083. exit;
  36084. UpperCopy255Buf(upname,aStartName,aStartNameLen)^ := #0;
  36085. for ndx := Count-1 downto 0 do
  36086. if IdemPChar(pointer(Names[ndx]),upname) then begin
  36087. Delete(ndx);
  36088. inc(result);
  36089. end;
  36090. end;
  36091. function TDocVariantData.GetValueIndex(aName: PUTF8Char; aNameLen: integer;
  36092. aCaseSensitive: boolean): integer;
  36093. var err: integer;
  36094. begin
  36095. if Kind=dvArray then begin
  36096. result := GetInteger(aName,err);
  36097. if err<>0 then
  36098. raise EDocVariant.CreateUTF8('Impossible to find "%" property in an array',[aName]);
  36099. if cardinal(result)>=cardinal(VCount) then
  36100. raise EDocVariant.CreateUTF8('Out of range [%] property in an array',[aName]);
  36101. exit;
  36102. end;
  36103. // simple lookup for object names -> hashing may be needed for huge count
  36104. if aCaseSensitive then begin
  36105. for result := 0 to VCount-1 do
  36106. if (length(VName[result])=aNameLen) and
  36107. CompareMem(pointer(VName[result]),aName,aNameLen) then
  36108. exit;
  36109. end else
  36110. for result := 0 to VCount-1 do
  36111. if (length(VName[result])=aNameLen) and
  36112. IdemPropNameUSameLen(pointer(VName[result]),aName,aNameLen) then
  36113. exit;
  36114. result := -1;
  36115. end;
  36116. function TDocVariantData.GetValueIndex(const aName: RawUTF8): integer;
  36117. begin
  36118. result := GetValueIndex(Pointer(aName),Length(aName),dvoNameCaseSensitive in VOptions);
  36119. end;
  36120. function TDocVariantData.GetValueOrRaiseException(const aName: RawUTF8): variant;
  36121. begin
  36122. RetrieveValueOrRaiseException(pointer(aName),length(aName),
  36123. dvoNameCaseSensitive in VOptions,result,false);
  36124. end;
  36125. function TDocVariantData.GetValueOrDefault(const aName: RawUTF8;
  36126. const aDefault: variant): variant;
  36127. var ndx: integer;
  36128. begin
  36129. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36130. (Kind<>dvObject) then
  36131. result := aDefault else begin
  36132. ndx := GetValueIndex(aName);
  36133. if ndx>=0 then
  36134. result := VValue[ndx] else
  36135. result := aDefault;
  36136. end;
  36137. end;
  36138. function TDocVariantData.GetValueOrNull(const aName: RawUTF8): variant;
  36139. var ndx: integer;
  36140. begin
  36141. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36142. (Kind<>dvObject) then
  36143. SetVariantNull(result) else begin
  36144. ndx := GetValueIndex(aName);
  36145. if ndx>=0 then
  36146. result := VValue[ndx] else
  36147. SetVariantNull(result);
  36148. end;
  36149. end;
  36150. function TDocVariantData.GetValueOrEmpty(const aName: RawUTF8): variant;
  36151. var ndx: integer;
  36152. begin
  36153. VarClear(result);
  36154. if (DocVariantType<>nil) and (VType=DocVariantVType) and
  36155. (Kind=dvObject) then begin
  36156. ndx := GetValueIndex(aName);
  36157. if ndx>=0 then
  36158. result := VValue[ndx];
  36159. end;
  36160. end;
  36161. function TDocVariantData.GetAsBoolean(const aName: RawUTF8; out aValue: boolean;
  36162. aSortedCompare: TUTF8Compare): Boolean;
  36163. var found: PVarData;
  36164. begin
  36165. found := GetVarData(aName,aSortedCompare);
  36166. if found=nil then
  36167. result := false else
  36168. result := VariantToBoolean(PVariant(found)^,aValue)
  36169. end;
  36170. function TDocVariantData.GetAsInteger(const aName: RawUTF8; out aValue: integer;
  36171. aSortedCompare: TUTF8Compare): Boolean;
  36172. var found: PVarData;
  36173. begin
  36174. found := GetVarData(aName,aSortedCompare);
  36175. if found=nil then
  36176. result := false else
  36177. result := VariantToInteger(PVariant(found)^,aValue)
  36178. end;
  36179. function TDocVariantData.GetAsInt64(const aName: RawUTF8; out aValue: Int64;
  36180. aSortedCompare: TUTF8Compare): Boolean;
  36181. var found: PVarData;
  36182. begin
  36183. found := GetVarData(aName,aSortedCompare);
  36184. if found=nil then
  36185. result := false else
  36186. result := VariantToInt64(PVariant(found)^,aValue)
  36187. end;
  36188. function TDocVariantData.GetAsDouble(const aName: RawUTF8; out aValue: double;
  36189. aSortedCompare: TUTF8Compare): Boolean;
  36190. var found: PVarData;
  36191. begin
  36192. found := GetVarData(aName,aSortedCompare);
  36193. if found=nil then
  36194. result := false else
  36195. result := VariantToDouble(PVariant(found)^,aValue);
  36196. end;
  36197. function TDocVariantData.GetAsRawUTF8(const aName: RawUTF8; out aValue: RawUTF8;
  36198. aSortedCompare: TUTF8Compare): Boolean;
  36199. var found: PVarData;
  36200. wasString: boolean;
  36201. begin
  36202. found := GetVarData(aName,aSortedCompare);
  36203. if found=nil then
  36204. result := false else begin
  36205. VariantToUTF8(PVariant(found)^,aValue,wasString);
  36206. result := true;
  36207. end;
  36208. end;
  36209. function TDocVariantData.GetAsDocVariant(const aName: RawUTF8; out aValue: PDocVariantData;
  36210. aSortedCompare: TUTF8Compare=nil): boolean;
  36211. var found: PVarData;
  36212. begin
  36213. found := GetVarData(aName,aSortedCompare);
  36214. if found=nil then
  36215. result := false else begin
  36216. aValue := _Safe(PVariant(found)^);
  36217. result := aValue<>@DocVariantDataFake;
  36218. end;
  36219. end;
  36220. function TDocVariantData.GetAsDocVariantSafe(const aName: RawUTF8;
  36221. aSortedCompare: TUTF8Compare): PDocVariantData;
  36222. var found: PVarData;
  36223. begin
  36224. found := GetVarData(aName,aSortedCompare);
  36225. if found=nil then
  36226. result := @DocVariantDataFake else
  36227. result := _Safe(PVariant(found)^);
  36228. end;
  36229. function TDocVariantData.GetAsPVariant(const aName: RawUTF8; out aValue: PVariant;
  36230. aSortedCompare: TUTF8Compare=nil): boolean;
  36231. begin
  36232. aValue := pointer(GetVarData(aName,aSortedCompare));
  36233. result := aValue<>nil;
  36234. end;
  36235. function TDocVariantData.GetVarData(const aName: RawUTF8;
  36236. var aValue: TVarData; aSortedCompare: TUTF8Compare): boolean;
  36237. var found: PVarData;
  36238. begin
  36239. found := GetVarData(aName,aSortedCompare);
  36240. if found=nil then
  36241. result := false else begin
  36242. aValue := found^;
  36243. result := true;
  36244. end;
  36245. end;
  36246. function TDocVariantData.GetVarData(const aName: RawUTF8;
  36247. aSortedCompare: TUTF8Compare): PVarData;
  36248. var ndx: Integer;
  36249. begin
  36250. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36251. (Kind<>dvObject) or (VCount=0) then
  36252. result := nil else begin
  36253. if Assigned(aSortedCompare) then
  36254. ndx := FastFindPUTF8CharSorted(pointer(VName),VCount-1,pointer(aName),aSortedCompare) else
  36255. ndx := FindRawUTF8(VName,VCount,aName,not(dvoNameCaseSensitive in VOptions));
  36256. if ndx>=0 then
  36257. result := @VValue[ndx] else
  36258. result := nil;
  36259. end;
  36260. end;
  36261. function TDocVariantData.GetValueByPath(const aPath: RawUTF8): variant;
  36262. var Dest: TVarData;
  36263. begin
  36264. VarClear(result);
  36265. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36266. (Kind<>dvObject) then
  36267. exit;
  36268. DocVariantType.Lookup(Dest,TVarData(self),pointer(aPath));
  36269. if Dest.VType>=varNull then
  36270. result := variant(Dest); // copy
  36271. end;
  36272. function TDocVariantData.GetValueByPath(const aPath: RawUTF8; out aValue: variant): boolean;
  36273. var Dest: TVarData;
  36274. begin
  36275. result := false;
  36276. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36277. (Kind<>dvObject) then
  36278. exit;
  36279. DocVariantType.Lookup(Dest,TVarData(self),pointer(aPath));
  36280. if Dest.VType=varEmpty then
  36281. exit;
  36282. aValue := variant(Dest); // copy
  36283. result := true;
  36284. end;
  36285. function TDocVariantData.GetPVariantByPath(const aPath: RawUTF8;
  36286. addIfNotExisting: boolean=false): PVariant;
  36287. var p{,ppar}: integer;
  36288. path: TRawUTF8DynArray;
  36289. par: PVariant;
  36290. begin
  36291. result := nil;
  36292. if (DocVariantType=nil) or (VType<>DocVariantVType) or (aPath='') then
  36293. exit;
  36294. CSVToRawUTF8DynArray(pointer(aPath),path,'.');
  36295. par := @self;
  36296. // ppar := -1;
  36297. if (Kind=dvObject) and (Count>0) then
  36298. for p := 0 to length(path)-1 do begin
  36299. if _Safe(par^).GetAsPVariant(path[p],result) then
  36300. par := result else begin
  36301. result := nil;
  36302. //ppar := p;
  36303. break;
  36304. end;
  36305. end;
  36306. if par=result then // found
  36307. exit;
  36308. if not addIfNotExisting then begin
  36309. result := nil;
  36310. exit;
  36311. end;
  36312. { TODO: add if not existing }
  36313. result := nil;
  36314. end;
  36315. function TDocVariantData.GetValueByPath(const aDocVariantPath: array of RawUTF8): variant;
  36316. var found,res: PVarData;
  36317. P: integer;
  36318. begin
  36319. VarClear(result);
  36320. if (DocVariantType=nil) or (VType<>DocVariantVType) or
  36321. (Kind<>dvObject) or (high(aDocVariantPath)<0) then
  36322. exit;
  36323. found := @self;
  36324. P := 0;
  36325. repeat
  36326. found := PDocVariantData(found).GetVarData(aDocVariantPath[P]);
  36327. if found=nil then
  36328. exit;
  36329. if P=high(aDocVariantPath) then
  36330. break; // we found the item!
  36331. inc(P);
  36332. // if we reached here, we should try for the next scope within Dest
  36333. while found^.VType=varByRef or varVariant do
  36334. found := found^.VPointer;
  36335. if found^.VType=VType then
  36336. continue;
  36337. exit;
  36338. until false;
  36339. res := found;
  36340. while res^.VType=varByRef or varVariant do
  36341. res := res^.VPointer;
  36342. if (res^.VType=VType) and (PDocVariantData(res)^.VCount=0) then
  36343. // return void TDocVariant as null
  36344. TVarData(result).VType := varNull else
  36345. // copy found value
  36346. result := PVariant(found)^;
  36347. end;
  36348. function TDocVariantData.GetItemByProp(const aPropName,aPropValue: RawUTF8;
  36349. aCaseSensitive: boolean; var Dest: variant; DestByRef: boolean): boolean;
  36350. var ndx: integer;
  36351. begin
  36352. ndx := SearchItemByProp(aPropName,aPropValue,aCaseSensitive);
  36353. if ndx<0 then
  36354. result := false else begin
  36355. RetrieveValueOrRaiseException(ndx,Dest,DestByRef);
  36356. result := true;
  36357. end;
  36358. end;
  36359. function TDocVariantData.GetDocVariantByProp(const aPropName,aPropValue: RawUTF8;
  36360. aCaseSensitive: boolean; out Dest: PDocVariantData): boolean;
  36361. var ndx: integer;
  36362. begin
  36363. ndx := SearchItemByProp(aPropName,aPropValue,aCaseSensitive);
  36364. if ndx<0 then
  36365. result := false else begin
  36366. Dest := DocVariantData(VValue[ndx]);
  36367. result := true;
  36368. end;
  36369. end;
  36370. function TDocVariantData.GetJsonByStartName(const aStartName: RawUTF8): RawUTF8;
  36371. var Up: array[byte] of AnsiChar;
  36372. ndx: integer;
  36373. W: TTextWriter;
  36374. begin
  36375. if (Kind<>dvObject) or (VCount=0) then begin
  36376. result := 'null';
  36377. exit;
  36378. end;
  36379. UpperCopy255(Up,aStartName)^ := #0;
  36380. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  36381. try
  36382. W.Add('{');
  36383. for ndx := 0 to VCount-1 do
  36384. if IdemPChar(Pointer(VName[ndx]),Up) then begin
  36385. if (dvoSerializeAsExtendedJson in VOptions) and
  36386. JsonPropNameValid(pointer(VName[ndx])) then begin
  36387. W.AddNoJSONEscape(pointer(VName[ndx]),Length(VName[ndx]));
  36388. end else begin
  36389. W.Add('"');
  36390. W.AddJSONEscape(pointer(VName[ndx]),Length(VName[ndx]));
  36391. W.Add('"');
  36392. end;
  36393. W.Add(':');
  36394. W.AddVariant(VValue[ndx],twJSONEscape);
  36395. W.Add(',');
  36396. end;
  36397. W.CancelLastComma;
  36398. W.Add('}');
  36399. W.SetText(result);
  36400. finally
  36401. W.Free;
  36402. end;
  36403. end;
  36404. function TDocVariantData.GetValuesByStartName(const aStartName: RawUTF8;
  36405. TrimLeftStartName: boolean): variant;
  36406. var Up: array[byte] of AnsiChar;
  36407. ndx: integer;
  36408. name: RawUTF8;
  36409. begin
  36410. if aStartName='' then begin
  36411. result := Variant(self);
  36412. exit;
  36413. end;
  36414. if (Kind<>dvObject) or (VCount=0) then begin
  36415. SetVariantNull(result);
  36416. exit;
  36417. end;
  36418. TDocVariant.NewFast(result);
  36419. UpperCopy255(Up,aStartName)^ := #0;
  36420. for ndx := 0 to VCount-1 do
  36421. if IdemPChar(Pointer(VName[ndx]),Up) then begin
  36422. name := VName[ndx];
  36423. if TrimLeftStartName then
  36424. system.delete(name, 1, length(aStartName));
  36425. TDocVariantData(result).AddValue(name,VValue[ndx]);
  36426. end;
  36427. end;
  36428. procedure TDocVariantData.SetValueOrRaiseException(Index: integer; const NewValue: variant);
  36429. begin
  36430. if cardinal(Index)>=cardinal(VCount) then
  36431. raise EDocVariant.CreateUTF8('Out of range Values[%] (count=%)',[Index,VCount]) else
  36432. VValue[Index] := NewValue;
  36433. end;
  36434. procedure TDocVariantData.RetrieveNameOrRaiseException(Index: integer;
  36435. var Dest: RawUTF8);
  36436. begin
  36437. if (cardinal(Index)>=cardinal(VCount)) or (VName=nil) then
  36438. if dvoReturnNullForOutOfRangeIndex in VOptions then
  36439. Dest := '' else
  36440. raise EDocVariant.CreateUTF8('Out of range Names[%] (count=%)',[Index,VCount]) else
  36441. Dest := VName[Index];
  36442. end;
  36443. procedure TDocVariantData.RetrieveValueOrRaiseException(Index: integer;
  36444. var Dest: variant; DestByRef: boolean);
  36445. var Source: PVariant;
  36446. begin
  36447. if cardinal(Index)>=cardinal(VCount) then
  36448. if dvoReturnNullForOutOfRangeIndex in VOptions then
  36449. SetVariantNull(Dest) else
  36450. raise EDocVariant.CreateUTF8('Out of range Values[%] (count=%)',[Index,VCount]) else
  36451. if DestByRef then
  36452. SetVariantByRef(VValue[Index],Dest) else begin
  36453. Source := @VValue[Index];
  36454. while PVarData(Source)^.VType=varVariant or varByRef do
  36455. Source := PVarData(Source)^.VPointer;
  36456. Dest := Source^;
  36457. end;
  36458. end;
  36459. procedure TDocVariantData.RetrieveValueOrRaiseException(
  36460. aName: PUTF8Char; aNameLen: integer; aCaseSensitive: boolean;
  36461. var Dest: variant; DestByRef: boolean);
  36462. var ndx: Integer;
  36463. begin
  36464. ndx := GetValueIndex(aName,aNameLen,aCaseSensitive);
  36465. if ndx<0 then
  36466. if dvoReturnNullForUnknownProperty in VOptions then
  36467. SetVariantNull(Dest) else
  36468. raise EDocVariant.CreateUTF8('Unexpected "%" property',[aName]) else
  36469. RetrieveValueOrRaiseException(ndx,Dest,DestByRef);
  36470. end;
  36471. function TDocVariantData.GetValueOrItem(const aNameOrIndex: Variant): Variant;
  36472. var wasString: boolean;
  36473. Name: RawUTF8;
  36474. begin
  36475. if VKind=dvArray then // fast index lookup e.g. for Value[1]
  36476. RetrieveValueOrRaiseException(VariantToIntegerDef(aNameOrIndex,-1),result,true) else begin
  36477. VariantToUTF8(aNameOrIndex,Name,wasString); // by name lookup e.g. for Value['abc']
  36478. if wasString then
  36479. RetrieveValueOrRaiseException(pointer(Name),length(Name),
  36480. dvoNameCaseSensitive in VOptions,result,true) else
  36481. RetrieveValueOrRaiseException(GetIntegerDef(pointer(Name),-1),result,true);
  36482. end;
  36483. end;
  36484. procedure TDocVariantData.SetValueOrItem(const aNameOrIndex, aValue: variant);
  36485. var wasString: boolean;
  36486. ndx: integer;
  36487. Name: RawUTF8;
  36488. begin
  36489. if VKind=dvArray then // fast index lookup e.g. for Value[1]
  36490. SetValueOrRaiseException(VariantToIntegerDef(aNameOrIndex,-1),aValue) else begin
  36491. VariantToUTF8(aNameOrIndex,Name,wasString); // by name lookup e.g. for Value['abc']
  36492. if wasString then begin
  36493. ndx := GetValueIndex(Name);
  36494. if ndx<0 then
  36495. ndx := InternalAdd(Name);
  36496. SetVariantByValue(aValue,VValue[ndx]);
  36497. end else
  36498. SetValueOrRaiseException(VariantToIntegerDef(aNameOrIndex,-1),aValue);
  36499. end;
  36500. end;
  36501. function TDocVariantData.AddOrUpdateValue(const aName: RawUTF8;
  36502. const aValue: variant; wasAdded: PBoolean; OnlyAddMissing: boolean): integer;
  36503. begin
  36504. if VKind=dvArray then
  36505. raise EDocVariant.CreateUTF8('AddOrUpdateValue("%") on an array',[aName]);
  36506. result := GetValueIndex(aName);
  36507. if result<0 then begin
  36508. result := InternalAdd(aName);
  36509. if wasAdded<>nil then
  36510. wasAdded^ := true;
  36511. end else begin
  36512. if wasAdded<>nil then
  36513. wasAdded^ := false;
  36514. if OnlyAddMissing then
  36515. exit;
  36516. end;
  36517. SetVariantByValue(aValue,VValue[result]);
  36518. end;
  36519. function TDocVariantData.ToJSON(const Prefix, Suffix: RawUTF8;
  36520. Format: TTextWriterJSONFormat): RawUTF8;
  36521. var W: TTextWriter;
  36522. tmp: RawUTF8;
  36523. begin
  36524. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  36525. try
  36526. W.AddString(Prefix);
  36527. DocVariantType.ToJSON(W,variant(self),twJSONEscape);
  36528. W.AddString(Suffix);
  36529. W.SetText(result);
  36530. finally
  36531. W.Free;
  36532. end;
  36533. if Format=jsonCompact then
  36534. exit;
  36535. JSONBufferReformat(pointer(result),tmp,Format);
  36536. result := tmp;
  36537. end;
  36538. function TDocVariantData.ToNonExpandedJSON: RawUTF8;
  36539. var fields: TRawUTF8DynArray;
  36540. fieldsCount: integer;
  36541. W: TTextWriter;
  36542. r,f: integer;
  36543. row: PDocVariantData;
  36544. begin
  36545. fields := nil; // to please Kylix
  36546. fieldsCount := 0;
  36547. if VKind<>dvArray then begin
  36548. result := '';
  36549. exit;
  36550. end;
  36551. if VCount=0 then begin
  36552. result := '[]';
  36553. exit;
  36554. end;
  36555. with _Safe(VValue[0])^ do
  36556. if VKind=dvObject then begin
  36557. fields := VName;
  36558. fieldsCount := VCount;
  36559. end;
  36560. if fieldsCount=0 then
  36561. raise EDocVariant.Create('ToNonExpandedJSON: Value[0] is not an object');
  36562. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  36563. try
  36564. W.Add('{"fieldCount":%,"rowCount":%,"values":[',[fieldsCount,VCount]);
  36565. for f := 0 to fieldsCount-1 do begin
  36566. W.Add('"');
  36567. W.AddJSONEscape(pointer(fields[f]));
  36568. W.Add('"',',');
  36569. end;
  36570. for r := 0 to VCount-1 do begin
  36571. row := _Safe(VValue[r]);
  36572. if (r>0) and ((row^.VKind<>dvObject) or (row^.VCount<>fieldsCount)) then
  36573. raise EDocVariant.CreateUTF8('ToNonExpandedJSON: Value[%] not object',[r]);
  36574. for f := 0 to fieldsCount-1 do
  36575. if (r>0) and not IdemPropNameU(row^.VName[f],fields[f]) then
  36576. raise EDocVariant.CreateUTF8('ToNonExpandedJSON: Value[%] field=% expected=%',
  36577. [r,row^.VName[f],fields[f]]) else begin
  36578. W.AddVariant(row^.VValue[f],twJSONEscape);
  36579. W.Add(',');
  36580. end;
  36581. end;
  36582. W.CancelLastComma;
  36583. W.Add(']','}');
  36584. W.SetText(result);
  36585. finally
  36586. W.Free;
  36587. end;
  36588. end;
  36589. procedure TDocVariantData.ToRawUTF8DynArray(out Result: TRawUTF8DynArray);
  36590. var ndx: integer;
  36591. wasString: boolean;
  36592. begin
  36593. case VKind of
  36594. dvUndefined: exit; // leave Result=[]
  36595. dvArray: ;
  36596. else raise EDocVariant.Create('ToRawUTF8DynArray expects a dvArray');
  36597. end;
  36598. SetLength(Result,VCount);
  36599. for ndx := 0 to VCount-1 do
  36600. VariantToUTF8(VValue[ndx],Result[ndx],wasString);
  36601. end;
  36602. function TDocVariantData.ToRawUTF8DynArray: TRawUTF8DynArray;
  36603. begin
  36604. ToRawUTF8DynArray(result);
  36605. end;
  36606. function TDocVariantData.ToCSV(const Separator: RawUTF8=','): RawUTF8;
  36607. var tmp: TRawUTF8DynArray; // fast enough in practice
  36608. begin
  36609. ToRawUTF8DynArray(tmp);
  36610. result := RawUTF8ArrayToCSV(tmp,Separator);
  36611. end;
  36612. procedure TDocVariantData.ToTextPairsVar(out result: RawUTF8;
  36613. const NameValueSep, ItemSep: RawUTF8; escape: TTextWriterKind);
  36614. var ndx: integer;
  36615. begin
  36616. case VKind of
  36617. dvUndefined: exit; // leave result=''
  36618. dvObject: ;
  36619. else raise EDocVariant.Create('ToTextPairs expects a dvObject');
  36620. end;
  36621. if VCount=0 then
  36622. exit;
  36623. with DefaultTextWriterJSONClass.CreateOwnedStream(8192) do
  36624. try
  36625. ndx := 0;
  36626. repeat
  36627. AddString(VName[ndx]);
  36628. AddString(NameValueSep);
  36629. AddVariant(VValue[ndx],escape);
  36630. inc(ndx);
  36631. if ndx=VCount then
  36632. break;
  36633. AddString(ItemSep);
  36634. until false;
  36635. SetText(result);
  36636. finally
  36637. Free;
  36638. end;
  36639. end;
  36640. function TDocVariantData.ToTextPairs(const NameValueSep: RawUTF8='=';
  36641. const ItemSep: RawUTF8=#13#10; escape: TTextWriterKind=twJSONEscape): RawUTF8;
  36642. begin
  36643. ToTextPairsVar(result,NameValueSep,ItemSep,escape);
  36644. end;
  36645. procedure TDocVariantData.ToArrayOfConst(out Result: TTVarRecDynArray);
  36646. var ndx: integer;
  36647. begin
  36648. case VKind of
  36649. dvUndefined: exit; // leave Result=[]
  36650. dvArray: ;
  36651. else raise EDocVariant.Create('ToArrayOfConst expects a dvArray');
  36652. end;
  36653. SetLength(Result,VCount);
  36654. for ndx := 0 to VCount-1 do begin
  36655. Result[ndx].VType := vtVariant;
  36656. Result[ndx].VVariant := @VValue[ndx];
  36657. end;
  36658. end;
  36659. function TDocVariantData.ToArrayOfConst: TTVarRecDynArray;
  36660. begin
  36661. ToArrayOfConst(result);
  36662. end;
  36663. function TDocVariantData.ToUrlEncode(const UriRoot: RawUTF8): RawUTF8;
  36664. var json: RawUTF8;
  36665. begin
  36666. VariantSaveJSON(variant(self),twJSONEscape,json);
  36667. result := UrlEncodeJsonObject(UriRoot,Pointer(json),[]);
  36668. end;
  36669. function TDocVariantData.GetOrAddValueIndex(const aName: RawUTF8): integer;
  36670. begin
  36671. result := GetValueIndex(Pointer(aName),Length(aName),dvoNameCaseSensitive in VOptions);
  36672. if result<0 then
  36673. result := InternalAdd(aName);
  36674. end;
  36675. function TDocVariantData.GetVarDataByName(const aName: RawUTF8): PVariant;
  36676. var ndx: Integer;
  36677. begin
  36678. ndx := GetValueIndex(pointer(aName),length(aName),dvoNameCaseSensitive in VOptions);
  36679. if ndx<0 then
  36680. if dvoReturnNullForUnknownProperty in VOptions then
  36681. result := @DocVariantDataFake else
  36682. raise EDocVariant.CreateUTF8('Unexpected "%" property',[aName]) else
  36683. result := @VValue[ndx];
  36684. end;
  36685. function TDocVariantData.GetInt64ByName(const aName: RawUTF8): Int64;
  36686. begin
  36687. if not VariantToInt64(GetVarDataByName(aName)^,result) then
  36688. result := 0;
  36689. end;
  36690. function TDocVariantData.GetRawUTF8ByName(const aName: RawUTF8): RawUTF8;
  36691. var wasString: boolean;
  36692. v: PVariant;
  36693. begin
  36694. v := GetVarDataByName(aName);
  36695. if PVarData(v)^.VType<=varNull then
  36696. result := '' else
  36697. VariantToUTF8(v^,result,wasString);
  36698. end;
  36699. procedure TDocVariantData.SetInt64ByName(const aName: RawUTF8;
  36700. const aValue: Int64);
  36701. begin
  36702. VValue[GetOrAddValueIndex(aName)] := aValue;
  36703. end;
  36704. procedure TDocVariantData.SetRawUTF8ByName(const aName, aValue: RawUTF8);
  36705. begin
  36706. RawUTF8ToVariant(aValue,VValue[GetOrAddValueIndex(aName)]);
  36707. end;
  36708. function TDocVariantData.GetBooleanByName(const aName: RawUTF8): Boolean;
  36709. begin
  36710. if not VariantToBoolean(GetVarDataByName(aName)^,result) then
  36711. result := false;
  36712. end;
  36713. procedure TDocVariantData.SetBooleanByName(const aName: RawUTF8; aValue: Boolean);
  36714. begin
  36715. VValue[GetOrAddValueIndex(aName)] := aValue;
  36716. end;
  36717. function TDocVariantData.GetDoubleByName(const aName: RawUTF8): Double;
  36718. begin
  36719. if not VariantToDouble(GetVarDataByName(aName)^,result) then
  36720. result := 0;
  36721. end;
  36722. procedure TDocVariantData.SetDoubleByName(const aName: RawUTF8;
  36723. const aValue: Double);
  36724. begin
  36725. VValue[GetOrAddValueIndex(aName)] := aValue;
  36726. end;
  36727. function TDocVariantData.GetDocVariantExistingByName(const aName: RawUTF8;
  36728. aNotMatchingKind: TDocVariantKind): PDocVariantData;
  36729. begin
  36730. result := GetAsDocVariantSafe(aName);
  36731. if result^.Kind=aNotMatchingKind then
  36732. result := @DocVariantDataFake;
  36733. end;
  36734. function TDocVariantData.GetDocVariantOrAddByName(const aName: RawUTF8;
  36735. aKind: TDocVariantKind): PDocVariantData;
  36736. var ndx: integer;
  36737. begin
  36738. ndx := GetOrAddValueIndex(aName);
  36739. result := _Safe(VValue[ndx]);
  36740. if result^.Kind<>aKind then begin
  36741. result := @VValue[ndx];
  36742. VarClear(PVariant(result)^);
  36743. result^.Init(JSON_OPTIONS_FAST,aKind);
  36744. end;
  36745. end;
  36746. function TDocVariantData.GetObjectExistingByName(const aName: RawUTF8): PDocVariantData;
  36747. begin
  36748. result := GetDocVariantExistingByName(aName,dvArray);
  36749. end;
  36750. function TDocVariantData.GetObjectOrAddByName(const aName: RawUTF8): PDocVariantData;
  36751. begin
  36752. result := GetDocVariantOrAddByName(aName,dvObject);
  36753. end;
  36754. function TDocVariantData.GetArrayExistingByName(const aName: RawUTF8): PDocVariantData;
  36755. begin
  36756. result := GetDocVariantExistingByName(aName,dvObject);
  36757. end;
  36758. function TDocVariantData.GetArrayOrAddByName(const aName: RawUTF8): PDocVariantData;
  36759. begin
  36760. result := GetDocVariantOrAddByName(aName,dvArray);
  36761. end;
  36762. function TDocVariantData.GetAsDocVariantByIndex(aIndex: integer): PDocVariantData;
  36763. begin
  36764. if cardinal(aIndex)<cardinal(VCount) then
  36765. result := _Safe(VValue[aIndex]) else
  36766. if dvoReturnNullForOutOfRangeIndex in VOptions then
  36767. result := @DocVariantDataFake else
  36768. raise EDocVariant.CreateUTF8('Out of range _[%] (count=%)',[aIndex,VCount]);
  36769. end;
  36770. { TDocVariant }
  36771. procedure TDocVariant.IntGet(var Dest: TVarData;
  36772. const V: TVarData; Name: PAnsiChar);
  36773. procedure Execute(ndx: integer;
  36774. const source: TDocVariantData; var Dest: variant);
  36775. begin
  36776. case ndx of
  36777. 0: Dest := source.Count;
  36778. 1: Dest := ord(source.Kind);
  36779. 2: RawUTF8ToVariant(source.ToJSON,Dest);
  36780. end;
  36781. end;
  36782. var NameLen, ndx: integer;
  36783. begin
  36784. //Assert(V.VType=DocVariantVType);
  36785. NameLen := StrLen(PUTF8Char(Name));
  36786. // 1. search for any _* pseudo properties
  36787. if (NameLen>4) and (Name[0]='_') then begin
  36788. ndx := IdemPCharArray(@Name[1],['COUNT','KIND','JSON']);
  36789. if ndx>=0 then begin
  36790. Execute(ndx,TDocVariantData(V),variant(Dest));
  36791. exit;
  36792. end;
  36793. end;
  36794. // 2. case-insensitive search for aVariant.Name
  36795. TDocVariantData(V).RetrieveValueOrRaiseException(
  36796. PUTF8Char(Name),NameLen,false,variant(Dest),true);
  36797. end;
  36798. procedure TDocVariant.IntSet(const V, Value: TVarData; Name: PAnsiChar);
  36799. var ndx: Integer;
  36800. aName: RawUTF8;
  36801. Data: TDocVariantData absolute V;
  36802. begin
  36803. if (Data.Kind=dvArray) and (PWord(Name)^=ord('_')) then begin
  36804. ndx := Data.InternalAdd(''); // FPC does not allow VValue[InternalAdd(aName)]
  36805. SetVariantByValue(variant(Value),Data.VValue[ndx]);
  36806. exit;
  36807. end;
  36808. SetString(aName,Name,StrLen(PUTF8Char(Name)));
  36809. ndx := Data.GetValueIndex(aName);
  36810. if ndx<0 then
  36811. ndx := Data.InternalAdd(aName);
  36812. SetVariantByValue(variant(Value),Data.VValue[ndx]);
  36813. end;
  36814. function TDocVariant.IterateCount(const V: TVarData): integer;
  36815. var Data: TDocVariantData absolute V;
  36816. begin
  36817. if Data.Kind<>dvArray then
  36818. result := -1 else
  36819. result := Data.VCount;
  36820. end;
  36821. procedure TDocVariant.Iterate(var Dest: TVarData; const V: TVarData; Index: integer);
  36822. var Data: TDocVariantData absolute V;
  36823. begin
  36824. if (Data.Kind=dvArray) and (cardinal(Index)<cardinal(Data.VCount)) then
  36825. Dest := TVarData(Data.VValue[Index]) else
  36826. Dest.VType := varEmpty;
  36827. end;
  36828. function TDocVariant.DoFunction(var Dest: TVarData; const V: TVarData;
  36829. const Name: string; const Arguments: TVarDataArray): boolean;
  36830. var ndx: integer;
  36831. Data: TDocVariantData absolute V;
  36832. temp: RawUTF8;
  36833. procedure SetTempFromFirstArgument;
  36834. var wasString: boolean;
  36835. begin
  36836. VariantToUTF8(variant(Arguments[0]),temp,wasString);
  36837. end;
  36838. begin
  36839. result := true;
  36840. case length(Arguments) of
  36841. 0:if SameText(Name,'Clear') then begin
  36842. PDocVariantData(@V)^.VCount := 0;
  36843. PDocVariantData(@V)^.VKind := dvUndefined;
  36844. exit;
  36845. end;
  36846. 1:if SameText(Name,'Add') then begin
  36847. ndx := Data.InternalAdd(''); // FPC does not allow VValue[InternalAdd(aName)]
  36848. SetVariantByValue(variant(Arguments[0]),Data.VValue[ndx]);
  36849. exit;
  36850. end else
  36851. if SameText(Name,'Delete') then begin
  36852. SetTempFromFirstArgument;
  36853. Data.Delete(Data.GetValueIndex(temp));
  36854. exit;
  36855. end else
  36856. if SameText(Name,'Exists') then begin
  36857. SetTempFromFirstArgument;
  36858. variant(Dest) := Data.GetValueIndex(temp)>=0;
  36859. exit;
  36860. end else
  36861. if SameText(Name,'NameIndex') then begin
  36862. SetTempFromFirstArgument;
  36863. variant(Dest) := Data.GetValueIndex(temp);
  36864. exit;
  36865. end else
  36866. if VariantToInteger(variant(Arguments[0]),ndx) then begin
  36867. if (Name='_') or SameText(Name,'Value') then begin
  36868. Data.RetrieveValueOrRaiseException(ndx,variant(Dest),true);
  36869. exit;
  36870. end else
  36871. if SameText(Name,'Name') then begin
  36872. Data.RetrieveNameOrRaiseException(ndx,temp);
  36873. RawUTF8ToVariant(temp,variant(Dest));
  36874. exit;
  36875. end;
  36876. end else
  36877. if (Name='_') or SameText(Name,'Value') then begin
  36878. SetTempFromFirstArgument;
  36879. Data.RetrieveValueOrRaiseException(pointer(temp),length(temp),
  36880. dvoNameCaseSensitive in Data.VOptions,variant(Dest),true);
  36881. exit;
  36882. end;
  36883. 2:if SameText(Name,'Add') then begin
  36884. SetTempFromFirstArgument;
  36885. ndx := Data.InternalAdd(temp); // FPC does not allow VValue[InternalAdd(aName)]
  36886. SetVariantByValue(variant(Arguments[1]),Data.VValue[ndx]);
  36887. exit;
  36888. end;
  36889. end;
  36890. result := false;
  36891. end;
  36892. procedure TDocVariant.ToJSON(W: TTextWriter; const Value: variant;
  36893. escape: TTextWriterKind);
  36894. var ndx: integer;
  36895. backup: TTextWriterOptions;
  36896. checkExtendedPropName: boolean;
  36897. begin
  36898. with TDocVariantData(Value) do
  36899. if integer(VType)>varNull then
  36900. if integer(VType)=DocVariantVType then
  36901. if VKind=dvUndefined then
  36902. W.AddShort('null') else begin
  36903. backup := W.fCustomOptions;
  36904. if [twoForceJSONExtended,twoForceJSONStandard]*backup=[] then
  36905. if dvoSerializeAsExtendedJson in VOptions then
  36906. include(W.fCustomOptions,twoForceJSONExtended) else
  36907. include(W.fCustomOptions,twoForceJSONStandard);
  36908. case VKind of
  36909. dvObject: begin
  36910. checkExtendedPropName := twoForceJSONExtended in W.CustomOptions;
  36911. W.Add('{');
  36912. for ndx := 0 to VCount-1 do begin
  36913. if checkExtendedPropName and JsonPropNameValid(pointer(VName[ndx])) then begin
  36914. W.AddNoJSONEscape(pointer(VName[ndx]),Length(VName[ndx]));
  36915. end else begin
  36916. W.Add('"');
  36917. W.AddJSONEscape(pointer(VName[ndx]),Length(VName[ndx]));
  36918. W.Add('"');
  36919. end;
  36920. W.Add(':');
  36921. W.AddVariant(VValue[ndx],twJSONEscape);
  36922. W.Add(',');
  36923. end;
  36924. W.CancelLastComma;
  36925. W.Add('}');
  36926. end;
  36927. dvArray: begin
  36928. W.Add('[');
  36929. for ndx := 0 to VCount-1 do begin
  36930. W.AddVariant(VValue[ndx],twJSONEscape);
  36931. W.Add(',');
  36932. end;
  36933. W.CancelLastComma;
  36934. W.Add(']');
  36935. end;
  36936. end;
  36937. W.fCustomOptions := backup;
  36938. end else
  36939. raise ESynException.CreateUTF8('Unexpected variant type %',[VType]) else
  36940. W.AddShort('null');
  36941. end;
  36942. procedure TDocVariant.Clear(var V: TVarData);
  36943. begin
  36944. //Assert(V.VType=DocVariantVType);
  36945. VariantDynArrayClear(TDocVariantData(V).VValue);
  36946. TDocVariantData(V).VName := nil;
  36947. ZeroFill(@V); // will set V.VType := varEmpty and VCount=0
  36948. end;
  36949. procedure TDocVariant.Copy(var Dest: TVarData; const Source: TVarData;
  36950. const Indirect: Boolean);
  36951. begin
  36952. //Assert(Source.VType=DocVariantVType);
  36953. if Indirect then
  36954. SimplisticCopy(Dest,Source,true) else
  36955. if dvoValueCopiedByReference in TDocVariantData(Source).Options then begin
  36956. if Dest.VType and VTYPE_STATIC<>0 then
  36957. VarClear(variant(Dest)); // Dest may be a complex type
  36958. pointer(TDocVariantData(Dest).VName) := nil; // avoid GPF
  36959. pointer(TDocVariantData(Dest).VValue) := nil;
  36960. TDocVariantData(Dest) := TDocVariantData(Source); // copy whole record
  36961. end else
  36962. CopyByValue(Dest,Source);
  36963. end;
  36964. procedure TDocVariant.CopyByValue(var Dest: TVarData; const Source: TVarData);
  36965. var S: TDocVariantData absolute Source;
  36966. D: TDocVariantData absolute Dest;
  36967. i: integer;
  36968. begin
  36969. //Assert(Source.VType=DocVariantVType);
  36970. if Dest.VType and VTYPE_STATIC<>0 then
  36971. VarClear(variant(Dest)); // Dest may be a complex type
  36972. D.VType := S.VType;
  36973. D.VOptions := S.VOptions;
  36974. D.VKind := S.VKind;
  36975. D.VCount := S.VCount;
  36976. pointer(D.VName) := nil; // avoid GPF
  36977. pointer(D.VValue) := nil;
  36978. if S.VCount=0 then
  36979. exit; // no data to copy
  36980. D.VName := S.VName; // names can always be safely copied
  36981. // slower but safe by-value copy
  36982. SetLength(D.VValue,S.VCount);
  36983. for i := 0 to S.VCount-1 do
  36984. D.VValue[i] := S.VValue[i];
  36985. end;
  36986. procedure TDocVariant.Cast(var Dest: TVarData; const Source: TVarData);
  36987. begin
  36988. CastTo(Dest,Source,VarType);
  36989. end;
  36990. procedure TDocVariant.CastTo(var Dest: TVarData;
  36991. const Source: TVarData; const AVarType: TVarType);
  36992. var Tmp: RawUTF8;
  36993. wasString: boolean;
  36994. begin
  36995. if AVarType=VarType then begin
  36996. VariantToUTF8(Variant(Source),Tmp,wasString);
  36997. if wasString then begin
  36998. if Dest.VType and VTYPE_STATIC<>0 then
  36999. VarClear(variant(Dest));
  37000. variant(Dest) := _JSONFast(Tmp); // convert from JSON text
  37001. exit;
  37002. end;
  37003. RaiseCastError;
  37004. end else begin
  37005. if Source.VType<>VarType then
  37006. RaiseCastError;
  37007. VariantSaveJSON(variant(Source),twJSONEscape,tmp);
  37008. RawUTF8ToVariant(Tmp,Dest,AVarType); // convert to JSON text
  37009. end;
  37010. end;
  37011. procedure TDocVariant.Compare(const Left, Right: TVarData;
  37012. var Relationship: TVarCompareResult);
  37013. var res: integer;
  37014. LeftU,RightU: RawUTF8;
  37015. begin
  37016. VariantSaveJSON(variant(Left),twJSONEscape,LeftU);
  37017. VariantSaveJSON(variant(Right),twJSONEscape,RightU);
  37018. if LeftU=RightU then
  37019. Relationship := crEqual else begin
  37020. res := StrComp(pointer(LeftU),pointer(RightU));
  37021. if res<0 then
  37022. Relationship := crLessThan else
  37023. if res>0 then
  37024. Relationship := crGreaterThan else
  37025. Relationship := crEqual;
  37026. end;
  37027. end;
  37028. class procedure TDocVariant.New(out aValue: variant;
  37029. aOptions: TDocVariantOptions);
  37030. begin
  37031. TDocVariantData(aValue).Init(aOptions);
  37032. end;
  37033. class procedure TDocVariant.NewFast(out aValue: variant);
  37034. begin
  37035. TDocVariantData(aValue).InitFast;
  37036. end;
  37037. class procedure TDocVariant.IsOfTypeOrNewFast(var aValue: variant);
  37038. begin
  37039. if DocVariantType.IsOfType(aValue) then
  37040. exit;
  37041. VarClear(aValue);
  37042. TDocVariantData(aValue).InitFast;
  37043. end;
  37044. class procedure TDocVariant.NewFast(const aValues: array of PDocVariantData);
  37045. var i: integer;
  37046. begin
  37047. for i := 0 to high(aValues) do
  37048. aValues[i]^.InitFast;
  37049. end;
  37050. class function TDocVariant.New(Options: TDocVariantOptions): Variant;
  37051. begin
  37052. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37053. VarClear(result);
  37054. TDocVariantData(result).Init(Options);
  37055. end;
  37056. class function TDocVariant.NewObject(const NameValuePairs: array of const;
  37057. Options: TDocVariantOptions=[]): variant;
  37058. begin
  37059. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37060. VarClear(result);
  37061. TDocVariantData(result).InitObject(NameValuePairs,Options);
  37062. end;
  37063. class function TDocVariant.NewArray(const Items: array of const;
  37064. Options: TDocVariantOptions=[]): variant;
  37065. begin
  37066. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37067. VarClear(result);
  37068. TDocVariantData(result).InitArray(Items,Options);
  37069. end;
  37070. class function TDocVariant.NewArray(const Items: TVariantDynArray;
  37071. Options: TDocVariantOptions=[]): variant;
  37072. begin
  37073. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37074. VarClear(result);
  37075. TDocVariantData(result).InitArrayFromVariants(Items,Options);
  37076. end;
  37077. class function TDocVariant.NewJSON(const JSON: RawUTF8;
  37078. Options: TDocVariantOptions): variant;
  37079. begin
  37080. _Json(JSON,result,Options);
  37081. end;
  37082. class function TDocVariant.NewUnique(const SourceDocVariant: variant;
  37083. Options: TDocVariantOptions=[dvoReturnNullForUnknownProperty]): variant;
  37084. begin
  37085. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37086. VarClear(result);
  37087. TDocVariantData(result).InitCopy(SourceDocVariant,Options);
  37088. end;
  37089. class procedure TDocVariant.GetSingleOrDefault(const docVariantArray, default: variant;
  37090. var result: variant);
  37091. begin
  37092. if TVarData(DocVariantArray).VType=varByRef or varVariant then
  37093. GetSingleOrDefault(PVariant(TVarData(DocVariantArray).VPointer)^,default,result) else
  37094. if (DocVariantType=nil) or
  37095. (TVarData(DocVariantArray).VType<>DocVariantVType) or
  37096. (TDocVariantData(DocVariantArray).Kind<>dvArray) or
  37097. (TDocVariantData(DocVariantArray).Count<>1) then
  37098. result := default else
  37099. result := TDocVariantData(DocVariantArray).Values[0];
  37100. end;
  37101. function DocVariantData(const DocVariant: variant): PDocVariantData;
  37102. begin
  37103. with TVarData(DocVariant) do
  37104. if VType=word(DocVariantVType) then
  37105. result := @DocVariant else
  37106. if VType=varByRef or varVariant then
  37107. result := DocVariantData(PVariant(VPointer)^) else
  37108. raise EDocVariant.CreateUTF8('DocVariantType.Data(%<>TDocVariant)',[VType]);
  37109. end;
  37110. function _Safe(const DocVariant: variant): PDocVariantData;
  37111. {$ifndef HASINLINE}
  37112. asm
  37113. mov ecx,DocVariantVType
  37114. movzx edx,word ptr [eax].TVarData.VType
  37115. cmp edx,ecx
  37116. jne @by
  37117. ret
  37118. @ptr: mov eax,[eax].TVarData.VPointer
  37119. movzx edx,word ptr [eax].TVarData.VType
  37120. cmp edx,ecx
  37121. je @ok
  37122. @by: cmp edx,varByRef or varVariant
  37123. je @ptr
  37124. lea eax,[DocVariantDataFake]
  37125. @ok:
  37126. end;
  37127. {$else}
  37128. begin
  37129. with TVarData(DocVariant) do
  37130. if VType=word(DocVariantVType) then begin
  37131. result := @DocVariant;
  37132. exit;
  37133. end else
  37134. if VType=varByRef or varVariant then begin
  37135. result := _Safe(PVariant(VPointer)^);
  37136. exit;
  37137. end else begin
  37138. result := @DocVariantDataFake;
  37139. exit;
  37140. end;
  37141. end;
  37142. {$endif}
  37143. function _Safe(const DocVariant: variant; ExpectedKind: TDocVariantKind): PDocVariantData; overload;
  37144. begin
  37145. result := _Safe(DocVariant);
  37146. if result^.Kind<>ExpectedKind then
  37147. raise EDocVariant.CreateUTF8('_Safe(%)<>%',[ord(result^.Kind),ord(ExpectedKind)]);
  37148. end;
  37149. function _Obj(const NameValuePairs: array of const;
  37150. Options: TDocVariantOptions=[]): variant;
  37151. begin
  37152. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37153. VarClear(result);
  37154. TDocVariantData(result).InitObject(NameValuePairs,Options);
  37155. end;
  37156. function _Arr(const Items: array of const;
  37157. Options: TDocVariantOptions=[]): variant;
  37158. begin
  37159. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37160. VarClear(result);
  37161. TDocVariantData(result).InitArray(Items,Options);
  37162. end;
  37163. procedure _ObjAddProps(const NameValuePairs: array of const; var Obj: variant);
  37164. var o: PDocVariantData;
  37165. begin
  37166. o := _Safe(Obj);
  37167. if o^.VKind<>dvObject then begin
  37168. if TVarData(Obj).VType and VTYPE_STATIC<>0 then
  37169. VarClear(Obj);
  37170. TDocVariantData(Obj).InitObject(NameValuePairs,JSON_OPTIONS_FAST);
  37171. end else begin
  37172. TVarData(Obj) := PVarData(o)^; // ensure not stored by reference
  37173. o^.AddNameValuesToObject(NameValuePairs);
  37174. end;
  37175. end;
  37176. procedure _ObjAddProps(const Document: variant; var Obj: variant);
  37177. var ndx: integer;
  37178. d,o: PDocVariantData;
  37179. begin
  37180. d := _Safe(Document);
  37181. o := _Safe(Obj);
  37182. if d.VKind=dvObject then
  37183. if o.Kind<>dvObject then
  37184. Obj := Document else
  37185. for ndx := 0 to d^.VCount-1 do
  37186. o^.AddOrUpdateValue(d^.VName[ndx],d^.VValue[ndx]);
  37187. end;
  37188. function _ObjFast(const NameValuePairs: array of const): variant;
  37189. begin
  37190. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37191. VarClear(result);
  37192. TDocVariantData(result).InitObject(NameValuePairs,JSON_OPTIONS_FAST);
  37193. end;
  37194. function _ObjFast(aObject: TObject; aOptions: TTextWriterWriteObjectOptions): variant;
  37195. begin
  37196. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37197. VarClear(result);
  37198. if TDocVariantData(result).InitJSONInPlace(
  37199. pointer(ObjectToJson(aObject,aOptions)),JSON_OPTIONS_FAST)=nil then
  37200. VarClear(result);
  37201. end;
  37202. function _ArrFast(const Items: array of const): variant;
  37203. begin
  37204. if TVarData(result).VType and VTYPE_STATIC<>0 then
  37205. VarClear(result);
  37206. TDocVariantData(result).InitArray(Items,JSON_OPTIONS_FAST);
  37207. end;
  37208. function _Json(const JSON: RawUTF8; Options: TDocVariantOptions): variant;
  37209. begin
  37210. _Json(JSON,result,Options);
  37211. end;
  37212. function _JsonFast(const JSON: RawUTF8): variant;
  37213. begin
  37214. _Json(JSON,result,JSON_OPTIONS_FAST);
  37215. end;
  37216. function _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  37217. Options: TDocVariantOptions): variant;
  37218. begin
  37219. _JsonFmt(Format,Args,Params,Options,result);
  37220. end;
  37221. procedure _JsonFmt(const Format: RawUTF8; const Args,Params: array of const;
  37222. Options: TDocVariantOptions; out result: variant); overload;
  37223. begin
  37224. if TDocVariantData(result).InitJSONInPlace(
  37225. pointer(FormatUTF8(Format,Args,Params,true)),Options)=nil then
  37226. TDocVariantData(result).Clear;
  37227. end;
  37228. function _JsonFastFmt(const Format: RawUTF8; const Args,Params: array of const): variant;
  37229. begin
  37230. _JsonFmt(Format,Args,Params,JSON_OPTIONS_FAST,result);
  37231. end;
  37232. function _Json(const JSON: RawUTF8; var Value: variant;
  37233. Options: TDocVariantOptions): boolean;
  37234. begin
  37235. if TVarData(Value).VType and VTYPE_STATIC<>0 then
  37236. VarClear(Value);
  37237. if not TDocVariantData(Value).InitJSON(JSON,Options) then begin
  37238. VarClear(Value);
  37239. result := false;
  37240. end else
  37241. result := true;
  37242. end;
  37243. procedure _Unique(var DocVariant: variant);
  37244. begin // TDocVariantData(DocVariant): InitCopy() will check the DocVariant type
  37245. TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS[false]);
  37246. end;
  37247. procedure _UniqueFast(var DocVariant: variant);
  37248. begin // TDocVariantData(DocVariant): InitCopy() will check the DocVariant type
  37249. TDocVariantData(DocVariant).InitCopy(DocVariant,JSON_OPTIONS_FAST);
  37250. end;
  37251. function _Copy(const DocVariant: variant): variant;
  37252. begin
  37253. result := TDocVariant.NewUnique(DocVariant,JSON_OPTIONS[false]);
  37254. end;
  37255. function _CopyFast(const DocVariant: variant): variant;
  37256. begin
  37257. result := TDocVariant.NewUnique(DocVariant,JSON_OPTIONS_FAST);
  37258. end;
  37259. {$endif NOVARIANTS}
  37260. { ****************** TDynArray wrapper }
  37261. {$ifndef DELPHI5OROLDER} // do not know why Delphi 5 compiler does not like CopyFrom()
  37262. procedure DynArrayCopy(var Dest; const Source; SourceMaxElem: integer;
  37263. TypeInfo: pointer);
  37264. var DestDynArray: TDynArray;
  37265. begin
  37266. DestDynArray.Init(TypeInfo,Dest);
  37267. DestDynArray.CopyFrom(Source,SourceMaxElem);
  37268. end;
  37269. {$endif}
  37270. function DynArrayLoad(var Value; Source: PAnsiChar; TypeInfo: pointer): PAnsiChar;
  37271. var DynArray: TDynArray;
  37272. begin
  37273. DynArray.Init(TypeInfo,Value);
  37274. result := DynArray.LoadFrom(Source);
  37275. end;
  37276. function DynArraySave(var Value; TypeInfo: pointer): RawByteString;
  37277. var DynArray: TDynArray;
  37278. begin
  37279. DynArray.Init(TypeInfo,Value);
  37280. result := DynArray.SaveTo;
  37281. end;
  37282. function DynArrayLoadJSON(var Value; JSON: PUTF8Char; TypeInfo: pointer;
  37283. EndOfObject: PUTF8Char=nil): PUTF8Char;
  37284. var DynArray: TDynArray;
  37285. begin
  37286. DynArray.Init(TypeInfo,Value);
  37287. result := DynArray.LoadFromJSON(JSON,EndOfObject);
  37288. end;
  37289. function DynArraySaveJSON(const Value; TypeInfo: pointer;
  37290. EnumSetsAsText: boolean): RawUTF8;
  37291. begin
  37292. SaveJSON(Value,TypeInfo,EnumSetsAsText,result);
  37293. end;
  37294. function DynArraySaveJSON(TypeInfo: pointer; const BlobValue: RawByteString): RawUTF8;
  37295. var DynArray: TDynArray;
  37296. Value: pointer;
  37297. begin
  37298. Value := nil;
  37299. DynArray.Init(TypeInfo,Value);
  37300. try
  37301. if DynArray.LoadFrom(pointer(BlobValue))=nil then
  37302. result := '' else begin
  37303. with DefaultTextWriterJSONClass.CreateOwnedStream(8192) do
  37304. try
  37305. AddDynArrayJSON(TypeInfo,Value);
  37306. SetText(result);
  37307. finally
  37308. Free;
  37309. end;
  37310. end;
  37311. finally
  37312. DynArray.Clear;
  37313. end;
  37314. end;
  37315. function DynArrayElementTypeName(TypeInfo: pointer; ElemTypeInfo: PPointer): RawUTF8;
  37316. var DynArray: TDynArray;
  37317. VoidArray: pointer;
  37318. const KNOWNTYPE_ITEMNAME: array[TDynArrayKind] of RawUTF8 = ('',
  37319. 'boolean','byte','word','integer','cardinal','single','Int64','double','currency',
  37320. 'TTimeLog','TDateTime','RawUTF8','WinAnsiString','string','RawByteString',
  37321. 'WideString','SynUnicode','IInterface',{$ifndef NOVARIANTS}'variant',{$endif}'');
  37322. begin
  37323. VoidArray := nil;
  37324. DynArray.Init(TypeInfo,VoidArray);
  37325. result := '';
  37326. if ElemTypeInfo<>nil then
  37327. ElemTypeInfo^ := DynArray.ElemType;
  37328. if DynArray.ElemType<>nil then
  37329. TypeInfoToName(ElemTypeInfo,result) else
  37330. result := KNOWNTYPE_ITEMNAME[DynArray.ToKnownType];
  37331. end;
  37332. function SortDynArrayBoolean(const A,B): integer;
  37333. begin
  37334. if boolean(A)=boolean(B) then
  37335. result := 0 else
  37336. if boolean(A) then
  37337. result := 1 else
  37338. result := -1;
  37339. end;
  37340. function SortDynArrayByte(const A,B): integer;
  37341. begin
  37342. result := byte(A)-byte(B);
  37343. end;
  37344. function SortDynArrayWord(const A,B): integer;
  37345. begin
  37346. result := word(A)-word(B);
  37347. end;
  37348. function SortDynArrayInteger(const A,B): integer;
  37349. begin
  37350. result := Integer(A)-Integer(B);
  37351. end;
  37352. function SortDynArrayCardinal(const A,B): integer;
  37353. begin
  37354. if cardinal(A)<cardinal(B) then
  37355. result := -1 else
  37356. if cardinal(A)>cardinal(B) then
  37357. result := 1 else
  37358. result := 0;
  37359. end;
  37360. function SortDynArrayInt64(const A,B): integer;
  37361. var tmp: Int64;
  37362. begin
  37363. tmp := Int64(A)-Int64(B);
  37364. if tmp<0 then
  37365. result := -1 else
  37366. if tmp>0 then
  37367. result := 1 else
  37368. result := 0;
  37369. end;
  37370. function SortDynArrayPointer(const A,B): integer;
  37371. begin
  37372. {$ifdef CPU64}
  37373. if PtrInt(A)<PtrInt(B) then
  37374. result := -1 else
  37375. if PtrInt(A)>PtrInt(B) then
  37376. result := 1 else
  37377. result := 0;
  37378. {$else}
  37379. result := PtrInt(A)-PtrInt(B);
  37380. {$endif}
  37381. end;
  37382. function SortDynArraySingle(const A,B): integer;
  37383. begin
  37384. if Single(A)<Single(B) then
  37385. result := -1 else
  37386. if Single(A)>Single(B) then
  37387. result := 1 else
  37388. result := 0;
  37389. end;
  37390. function SortDynArrayDouble(const A,B): integer;
  37391. begin
  37392. if Double(A)<Double(B) then
  37393. result := -1 else
  37394. if Double(A)>Double(B) then
  37395. result := 1 else
  37396. result := 0;
  37397. end;
  37398. function SortDynArrayAnsiString(const A,B): integer;
  37399. {$ifdef PUREPASCAL}
  37400. begin
  37401. result := StrComp(pointer(A),pointer(B));
  37402. end;
  37403. {$else}
  37404. asm // x86 version optimized for RawByteString/AnsiString/RawUTF8 types
  37405. mov eax,[eax]
  37406. mov edx,[edx]
  37407. cmp eax,edx
  37408. je @0
  37409. test eax,edx
  37410. jz @n1
  37411. @n2:movzx ecx,byte ptr [eax] // first char comparison (QuickSort speedup)
  37412. sub cl,[edx]
  37413. jne @no
  37414. push ebx
  37415. mov ebx,[eax-4]
  37416. sub ebx,[edx-4]
  37417. push ebx
  37418. adc ecx,-1
  37419. and ecx,ebx
  37420. sub ecx,[eax-4]
  37421. sub eax,ecx
  37422. sub edx,ecx
  37423. @s: mov ebx,[eax+ecx] // compare by DWORD
  37424. xor ebx,[edx+ecx]
  37425. jnz @d
  37426. add ecx,4
  37427. js @s
  37428. @L: pop eax // all chars equal -> returns length(A)-length(B)
  37429. pop ebx
  37430. ret
  37431. @d: bsf ebx,ebx // char differs -> returns PByte(A)^-PByte(B)^
  37432. shr ebx,3
  37433. add ecx,ebx
  37434. jns @L
  37435. movzx eax,byte ptr [eax+ecx]
  37436. movzx edx,byte ptr [edx+ecx]
  37437. pop ebx
  37438. pop ebx
  37439. sub eax,edx
  37440. ret
  37441. @n1:test eax,eax // A or B may be ''
  37442. jz @n0
  37443. test edx,edx
  37444. jnz @n2
  37445. cmp [eax-4],edx
  37446. je @0
  37447. @no:jnc @1
  37448. or eax,-1
  37449. ret
  37450. @n0:cmp eax,[edx-4]
  37451. je @0
  37452. jnc @1
  37453. or eax,-1
  37454. ret
  37455. @0: xor eax,eax
  37456. ret
  37457. @1: mov eax,1
  37458. end;
  37459. {$endif}
  37460. function SortDynArrayAnsiStringI(const A,B): integer;
  37461. begin
  37462. result := StrIComp(PUTF8Char(A),PUTF8Char(B));
  37463. end;
  37464. function SortDynArrayPUTF8Char(const A,B): integer;
  37465. begin
  37466. result := StrComp(pointer(A),pointer(B));
  37467. end;
  37468. function SortDynArrayPUTF8CharI(const A,B): integer;
  37469. begin
  37470. result := StrIComp(PUTF8Char(A),PUTF8Char(B));
  37471. end;
  37472. function SortDynArrayString(const A,B): integer;
  37473. begin
  37474. {$ifdef UNICODE}
  37475. result := SysUtils.StrComp(PChar(A),PChar(B));
  37476. {$else}
  37477. result := StrComp(PUTF8Char(A),PUTF8Char(B));
  37478. {$endif}
  37479. end;
  37480. function SortDynArrayStringI(const A,B): integer;
  37481. begin
  37482. {$ifdef UNICODE}
  37483. result := AnsiICompW(PWideChar(A),PWideChar(B));
  37484. {$else}
  37485. result := StrIComp(PUTF8Char(A),PUTF8Char(B));
  37486. {$endif}
  37487. end;
  37488. function SortDynArrayUnicodeString(const A,B): integer;
  37489. begin
  37490. result := StrCompW(PWideChar(A),PWideChar(B));
  37491. end;
  37492. function SortDynArrayUnicodeStringI(const A,B): integer;
  37493. begin
  37494. result := AnsiICompW(PWideChar(A),PWideChar(B));
  37495. end;
  37496. {$ifndef NOVARIANTS}
  37497. function SortDynArrayVariantCompareAsString(const A,B: variant): integer;
  37498. var UA,UB: RawUTF8;
  37499. wasString: boolean;
  37500. begin
  37501. VariantToUTF8(A,UA,wasString);
  37502. VariantToUTF8(B,UB,wasString);
  37503. result := StrComp(pointer(UA),pointer(UB));
  37504. end;
  37505. function SortDynArrayVariantCompareAsStringI(const A,B: variant): integer;
  37506. var UA,UB: RawUTF8;
  37507. wasString: boolean;
  37508. begin
  37509. VariantToUTF8(A,UA,wasString);
  37510. VariantToUTF8(B,UB,wasString);
  37511. result := StrIComp(pointer(UA),pointer(UB));
  37512. end;
  37513. function SortDynArrayVariantComp(const A,B: TVarData; caseInsensitive: boolean): integer;
  37514. type
  37515. TSortDynArrayVariantComp = function(const A,B: variant): integer;
  37516. const
  37517. CMP: array[boolean] of TSortDynArrayVariantComp = (
  37518. SortDynArrayVariantCompareAsString,SortDynArrayVariantCompareAsStringI);
  37519. ICMP: array[TVariantRelationship] of integer = (0,-1,1,1);
  37520. begin
  37521. if A.VType=varVariant or varByRef then
  37522. result := SortDynArrayVariantComp(PVarData(A.VPointer)^,B,caseInsensitive) else
  37523. if B.VType=varVariant or varByRef then
  37524. result := SortDynArrayVariantComp(A,PVarData(B.VPointer)^,caseInsensitive) else
  37525. if A.VType=B.VType then
  37526. case A.VType of // optimized value comparison if A and B share the same type
  37527. varNull,varEmpty:
  37528. result := 0;
  37529. varString: // RawUTF8 most of the time (e.g. from TDocVariant)
  37530. if caseInsensitive then
  37531. result := StrIComp(A.VAny,B.VAny) else
  37532. result := StrComp(A.VAny,B.VAny);
  37533. varInteger:
  37534. result := A.VInteger-B.VInteger;
  37535. varInt64,varCurrency:
  37536. if A.VInt64<B.VInt64 then
  37537. result := -1 else
  37538. if A.VInt64>B.VInt64 then
  37539. result := 1 else
  37540. result := 0;
  37541. varDouble:
  37542. if A.VDouble<B.VDouble then
  37543. result := -1 else
  37544. if A.VDouble>B.VDouble then
  37545. result := 1 else
  37546. result := 0;
  37547. varBoolean:
  37548. result := ord(A.VBoolean)-ord(B.VBoolean);
  37549. varOleStr{$ifdef HASVARUSTRING},varUString{$endif}:
  37550. if caseInsensitive then
  37551. result := AnsiICompW(A.VAny,B.VAny) else
  37552. result := StrCompW(A.VAny,B.VAny);
  37553. else
  37554. if A.VType and VTYPE_STATIC=0 then
  37555. result := ICMP[VarCompareValue(variant(A),variant(B))] else
  37556. result := CMP[caseInsensitive](variant(A),variant(B));
  37557. end else
  37558. if (A.VType and VTYPE_STATIC=0) and
  37559. (B.VType and VTYPE_STATIC=0) then
  37560. result := ICMP[VarCompareValue(variant(A),variant(B))] else
  37561. result := CMP[caseInsensitive](variant(A),variant(B));
  37562. end;
  37563. function SortDynArrayVariant(const A,B): integer;
  37564. begin
  37565. result := SortDynArrayVariantComp(TVarData(A),TVarData(B),false);
  37566. end;
  37567. function SortDynArrayVariantI(const A,B): integer;
  37568. begin
  37569. result := SortDynArrayVariantComp(TVarData(A),TVarData(B),true);
  37570. end;
  37571. {$endif NOVARIANTS}
  37572. function TDynArray.Add(const Elem): integer;
  37573. begin
  37574. result := Count;
  37575. if fValue=nil then
  37576. exit; // avoid GPF if void
  37577. SetCount(result+1);
  37578. ElemCopy(Elem,pointer(PtrUInt(fValue^)+PtrUInt(result)*ElemSize)^);
  37579. end;
  37580. function TDynArray.New: integer;
  37581. begin
  37582. result := Count;
  37583. if fValue=nil then
  37584. exit; // avoid GPF if void
  37585. SetCount(result+1);
  37586. end;
  37587. procedure TDynArray.Insert(Index: Integer; const Elem);
  37588. var n: integer;
  37589. P: PByteArray;
  37590. begin
  37591. if fValue=nil then
  37592. exit; // avoid GPF if void
  37593. n := Count;
  37594. SetCount(n+1);
  37595. if cardinal(Index)<cardinal(n) then begin
  37596. P := pointer(PtrUInt(fValue^)+PtrUInt(Index)*ElemSize);
  37597. MoveFast(P[0],P[ElemSize],cardinal(n-Index)*ElemSize);
  37598. if ElemType<>nil then
  37599. FillcharFast(P[0],ElemSize,0); // avoid GPF in ElemCopy() below
  37600. end else
  37601. // Index>=Count -> add at the end
  37602. P := pointer(PtrUInt(fValue^)+PtrUInt(n)*ElemSize);
  37603. ElemCopy(Elem,P^);
  37604. end;
  37605. procedure TDynArray.Clear;
  37606. begin
  37607. SetCount(0);
  37608. end;
  37609. procedure TDynArray.Delete(aIndex: Integer);
  37610. var n, len: integer;
  37611. P: PAnsiChar;
  37612. zerolast: boolean;
  37613. begin
  37614. if fValue=nil then
  37615. exit; // avoid GPF if void
  37616. n := Count;
  37617. if cardinal(aIndex)>=cardinal(n) then
  37618. exit; // out of range
  37619. dec(n);
  37620. P := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize);
  37621. if ElemType<>nil then begin
  37622. _Finalize(P,ElemType);
  37623. zerolast := true;
  37624. end else
  37625. if GetIsObjArray then begin
  37626. FreeAndNil(PObject(P)^);
  37627. zerolast := true;
  37628. end else
  37629. zerolast := false;
  37630. if n>aIndex then begin
  37631. len := cardinal(n-aIndex)*ElemSize;
  37632. MoveFast(P[ElemSize],P[0],len);
  37633. if zerolast then // avoid GPF
  37634. FillcharFast(P[len],ElemSize,0);
  37635. end;
  37636. SetCount(n);
  37637. end;
  37638. function TDynArray.ElemPtr(aIndex: integer): pointer;
  37639. begin
  37640. result := nil;
  37641. if (fValue=nil) or (fValue^=nil) then
  37642. exit;
  37643. if fCountP<>nil then begin
  37644. if cardinal(aIndex)>=PCardinal(fCountP)^ then
  37645. exit;
  37646. end else
  37647. {$ifdef FPC}
  37648. if cardinal(aIndex)>=cardinal(PDynArrayRec(PtrUInt(fValue^)-SizeOf(TDynArrayRec))^.length) then
  37649. {$else}
  37650. if cardinal(aIndex)>=PCardinal(PtrUInt(fValue^)-sizeof(PtrInt))^ then
  37651. {$endif}
  37652. exit;
  37653. result := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize);
  37654. end;
  37655. function TDynArray.GetCount: integer;
  37656. begin
  37657. if fValue<>nil then
  37658. if fCountP=nil then
  37659. if PtrInt(fValue^)<>0 then begin
  37660. {$ifdef FPC}
  37661. result := PDynArrayRec(PtrUInt(fValue^)-SizeOf(TDynArrayRec))^.length;
  37662. {$else}
  37663. result := PInteger(PtrUInt(fValue^)-sizeof(PtrInt))^;
  37664. {$endif}
  37665. exit;
  37666. end else begin
  37667. result := 0;
  37668. exit;
  37669. end else begin
  37670. result := fCountP^;
  37671. exit;
  37672. end else begin
  37673. result := 0; // avoid GPF if void
  37674. exit;
  37675. end;
  37676. end;
  37677. procedure TDynArray.Reverse;
  37678. var i, siz, n, tmp: integer;
  37679. P1, P2: PAnsiChar;
  37680. c: AnsiChar;
  37681. i64: Int64;
  37682. begin
  37683. n := Count-1;
  37684. if n>0 then begin
  37685. siz := ElemSize;
  37686. P1 := fValue^;
  37687. case siz of
  37688. 1: begin
  37689. // optimized version for TByteDynArray and such
  37690. P2 := P1+n;
  37691. for i := 1 to n shr 1 do begin
  37692. c := P1^;
  37693. P1^ := P2^;
  37694. P2^ := c;
  37695. inc(P1);
  37696. dec(P2);
  37697. end;
  37698. end;
  37699. 4: begin
  37700. // optimized version for TIntegerDynArray + TRawUTF8DynArray and such
  37701. P2 := P1+n*sizeof(Integer);
  37702. for i := 1 to n shr 1 do begin
  37703. tmp := PInteger(P1)^;
  37704. PInteger(P1)^ := PInteger(P2)^;
  37705. PInteger(P2)^ := tmp;
  37706. inc(P1,4);
  37707. dec(P2,4);
  37708. end;
  37709. end;
  37710. 8: begin
  37711. // optimized version for TInt64DynArray + TDoubleDynArray and such
  37712. P2 := P1+n*sizeof(Int64);
  37713. for i := 1 to n shr 1 do begin
  37714. i64 := PInt64(P1)^;
  37715. PInt64(P1)^ := PInt64(P2)^;
  37716. PInt64(P2)^ := i64;
  37717. inc(P1,8);
  37718. dec(P2,8);
  37719. end;
  37720. end;
  37721. 16: begin
  37722. // optimized version for TVariantDynArray and such
  37723. P2 := P1+n*16;
  37724. for i := 1 to n shr 1 do begin
  37725. Exchg16(Pointer(P1),Pointer(P2));
  37726. inc(P1,16);
  37727. dec(P2,16);
  37728. end;
  37729. end;
  37730. else begin
  37731. // generic version
  37732. P2 := P1+n*siz;
  37733. for i := 1 to n shr 1 do begin
  37734. Exchg(P1,P2,siz);
  37735. inc(P1,siz);
  37736. dec(P2,siz);
  37737. end;
  37738. end;
  37739. end;
  37740. end;
  37741. end;
  37742. procedure TDynArray.SaveToStream(Stream: TStream);
  37743. var Posi, PosiEnd: Integer;
  37744. MemStream: TCustomMemoryStream absolute Stream;
  37745. tmp: RawByteString;
  37746. begin
  37747. if (fValue=nil) or (Stream=nil) then
  37748. exit; // avoid GPF if void
  37749. if Stream.InheritsFrom(TCustomMemoryStream) then begin
  37750. Posi := MemStream.Seek(0,soFromCurrent);
  37751. PosiEnd := Posi+SaveToLength;
  37752. if PosiEnd>MemStream.Size then
  37753. MemStream.Size := PosiEnd;
  37754. if SaveTo(PAnsiChar(MemStream.Memory)+Posi)-MemStream.Memory<>PosiEnd then
  37755. EStreamError.Create('TDynArray.SaveToStream');
  37756. MemStream.Seek(PosiEnd,soBeginning);
  37757. end else begin
  37758. tmp := SaveTo;
  37759. Stream.Write(pointer(tmp)^,length(tmp));
  37760. end;
  37761. end;
  37762. procedure TDynArray.LoadFromStream(Stream: TCustomMemoryStream);
  37763. var P: PAnsiChar;
  37764. begin
  37765. P := PAnsiChar(Stream.Memory)+Stream.Seek(0,soFromCurrent);
  37766. Stream.Seek(LoadFrom(P)-P,soCurrent);
  37767. end;
  37768. function TDynArray.SaveTo(Dest: PAnsiChar): PAnsiChar;
  37769. var i, n, LenBytes: integer;
  37770. P: PAnsiChar;
  37771. nested: PTypeInfo;
  37772. NestedArray: TDynArray;
  37773. begin
  37774. if fValue=nil then begin
  37775. result := Dest;
  37776. exit; // avoid GPF if void
  37777. end;
  37778. // first store the element size+type to check for the format (name='' mostly)
  37779. Dest := pointer(ToVarUInt32(ElemSize,pointer(Dest)));
  37780. if ElemType=nil then
  37781. Dest^ := #0 else
  37782. Dest^ := PAnsiChar(ElemType)^;
  37783. inc(Dest);
  37784. // then store dynamic array count
  37785. n := Count;
  37786. Dest := pointer(ToVarUInt32(n,pointer(Dest)));
  37787. if n=0 then begin
  37788. result := Dest;
  37789. exit;
  37790. end;
  37791. inc(Dest,sizeof(Cardinal)); // leave space for Hash32 checksum
  37792. result := Dest;
  37793. // store dynamic array elements content
  37794. P := fValue^;
  37795. if ElemType=nil then
  37796. if GetIsObjArray then
  37797. raise ESynException.CreateUTF8('TDynArray.SaveTo(%) is a T*ObjArray',
  37798. [PShortString(@PTypeInfo(ArrayType).NameLen)^]) else begin
  37799. // binary types: store as once
  37800. n := n*integer(ElemSize);
  37801. MoveFast(P^,Dest^,n);
  37802. inc(Dest,n);
  37803. end else
  37804. case PTypeKind(ElemType)^ of
  37805. tkLString, tkWString {$ifdef HASVARUSTRING}, tkUString{$endif}
  37806. {$ifdef FPC}, tkLStringOld{$endif}: begin
  37807. for i := 1 to n do begin
  37808. if PPtrUInt(P)^=0 then
  37809. LenBytes := 0 else begin
  37810. LenBytes := PStrRec(Pointer(PPtrUInt(P)^-STRRECSIZE))^.length;
  37811. {$ifdef HASVARUSTRING} // WideString length in bytes, UnicodeString in WideChars
  37812. if PTypeKind(ElemType)^=tkUString then
  37813. LenBytes := LenBytes*2;
  37814. {$endif}
  37815. end;
  37816. Dest := pointer(ToVarUInt32(LenBytes,pointer(Dest)));
  37817. if LenBytes>0 then begin
  37818. MoveFast(pointer(PPtrUInt(P)^)^,Dest^,LenBytes);
  37819. inc(Dest,LenBytes);
  37820. end;
  37821. inc(P,sizeof(PtrUInt));
  37822. end;
  37823. end;
  37824. {$ifndef NOVARIANTS}
  37825. tkVariant:
  37826. for i := 0 to n-1 do begin
  37827. Dest := VariantSave(PVariantArray(P)^[i],Dest);
  37828. if Dest=nil then
  37829. break; // invalid/unhandled variant content
  37830. end;
  37831. {$endif}
  37832. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  37833. nested := ElemType; // inlined GetTypeInfo()
  37834. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  37835. nested := GetFPCAlignPtr(nested);
  37836. {$else}
  37837. inc(PtrUInt(nested),nested^.NameLen);
  37838. {$endif}
  37839. if nested^.ManagedCount=0 then begin
  37840. {$ifndef LVCL}
  37841. assert(nested^.recSize=ElemSize);
  37842. {$endif}
  37843. n := n*integer(ElemSize);
  37844. MoveFast(P^,Dest^,n);
  37845. inc(Dest,n);
  37846. end else begin
  37847. for i := 1 to n do begin
  37848. Dest := RecordSave(P^,Dest,ElemType);
  37849. if Dest=nil then
  37850. break; // invalid record type (wrong field type)
  37851. inc(P,ElemSize);
  37852. end;
  37853. end;
  37854. end;
  37855. tkDynArray:
  37856. for i := 1 to n do begin
  37857. NestedArray.Init(ElemType,P^);
  37858. Dest := NestedArray.SaveTo(Dest);
  37859. inc(P,ElemSize);
  37860. end;
  37861. end;
  37862. // store Hash32 checksum
  37863. if Dest<>nil then // may be nil if RecordSave() failed
  37864. PCardinal(result-sizeof(Cardinal))^ := Hash32(result,Dest-result);
  37865. result := Dest;
  37866. end;
  37867. function TDynArray.SaveToLength: integer;
  37868. var i,n,L: integer;
  37869. P: PAnsiChar;
  37870. NestedArray: TDynArray;
  37871. begin
  37872. if fValue=nil then begin
  37873. result := 0;
  37874. exit; // avoid GPF if void
  37875. end;
  37876. n := Count;
  37877. result := ToVarUInt32Length(ElemSize)+ToVarUInt32Length(n)+1;
  37878. if n=0 then
  37879. exit;
  37880. if ElemType=nil then
  37881. if GetIsObjArray then
  37882. raise ESynException.CreateUTF8('TDynArray.SaveToLength(%) is a T*ObjArray',
  37883. [PShortString(@PTypeInfo(ArrayType).NameLen)^]) else
  37884. inc(result,integer(ElemSize)*n) else begin
  37885. P := fValue^;
  37886. case PTypeKind(ElemType)^ of
  37887. tkLString, tkWString{$ifdef FPC}, tkLStringOld{$endif}:
  37888. for i := 1 to n do begin
  37889. if PPtrUInt(P)^=0 then
  37890. inc(result) else
  37891. inc(result,ToVarUInt32LengthWithData(PStrRec(Pointer(PPtrUInt(P)^-STRRECSIZE))^.length));
  37892. inc(P,sizeof(PtrUInt));
  37893. end;
  37894. {$ifdef HASVARUSTRING}
  37895. tkUString: // WideString has length in bytes, UnicodeString in WideChars
  37896. for i := 1 to n do begin
  37897. if PPtrUInt(P)^=0 then
  37898. inc(result) else
  37899. inc(result,ToVarUInt32LengthWithData(PStrRec(Pointer(PPtrUInt(P)^-STRRECSIZE))^.length*2));
  37900. inc(P,sizeof(PtrUInt));
  37901. end;
  37902. {$endif}
  37903. {$ifndef NOVARIANTS}
  37904. tkVariant:
  37905. for i := 0 to n-1 do begin
  37906. L := VariantSaveLength(PVariantArray(P)^[i]);
  37907. if L=0 then
  37908. break; // invalid/unhandled variant content
  37909. inc(result,L);
  37910. end;
  37911. {$endif}
  37912. tkRecord{$ifdef FPC},tkObject{$endif}:
  37913. for i := 1 to n do begin
  37914. L := RecordSaveLength(P^,ElemType);
  37915. if L=0 then
  37916. break; // invalid record type (wrong field type)
  37917. inc(result,L);
  37918. inc(P,ElemSize);
  37919. end;
  37920. tkDynArray:
  37921. for i := 1 to n do begin
  37922. NestedArray.Init(ElemType,P^);
  37923. inc(result,NestedArray.SaveToLength);
  37924. inc(P,ElemSize);
  37925. end;
  37926. end;
  37927. end;
  37928. inc(result,sizeof(Cardinal)); // Hash32 checksum
  37929. end;
  37930. function TDynArray.SaveTo: RawByteString;
  37931. var Len: integer;
  37932. begin
  37933. Len := SaveToLength;
  37934. SetString(result,nil,Len);
  37935. if Len<>0 then
  37936. if SaveTo(pointer(result))-pointer(result)<>Len then
  37937. raise ESynException.Create('TDynArray.SaveTo len concern');
  37938. end;
  37939. function TDynArray.SaveToJSON(EnumSetsAsText: boolean): RawUTF8;
  37940. begin
  37941. with DefaultTextWriterJSONClass.CreateOwnedStream(8192) do
  37942. try
  37943. if EnumSetsAsText then
  37944. CustomOptions := CustomOptions+[twoEnumSetsAsTextInRecord];
  37945. AddDynArrayJSON(self);
  37946. SetText(result);
  37947. finally
  37948. Free;
  37949. end;
  37950. end;
  37951. function JSONArrayCount(P: PUTF8Char): integer;
  37952. var n: integer;
  37953. begin
  37954. result := -1;
  37955. n := 0;
  37956. P := GotoNextNotSpace(P);
  37957. if P^<>']' then
  37958. repeat
  37959. case P^ of
  37960. '"': begin
  37961. P := GotoEndOfJSONString(P);
  37962. if P^<>'"' then
  37963. exit;
  37964. inc(P);
  37965. end;
  37966. '{','[': begin
  37967. P := GotoNextJSONObjectOrArray(P);
  37968. if P=nil then
  37969. exit; // invalid content
  37970. end;
  37971. end;
  37972. while not (P^ in [#0,',',']']) do inc(P);
  37973. inc(n);
  37974. if P^<>',' then break;
  37975. repeat inc(P) until not(P^ in [#1..' ']);
  37976. until false;
  37977. if P^=']' then
  37978. result := n;
  37979. end;
  37980. function JSONArrayItem(P: PUTF8Char; Index: integer): PUTF8Char;
  37981. begin
  37982. if P<>nil then begin
  37983. P := GotoNextNotSpace(P);
  37984. if P^='[' then begin
  37985. P := GotoNextNotSpace(P+1);
  37986. while P^<>']' do begin
  37987. if Index<=0 then begin
  37988. result := P;
  37989. exit;
  37990. end;
  37991. case P^ of
  37992. '"': begin
  37993. P := GotoEndOfJSONString(P);
  37994. if P^<>'"' then
  37995. break; // invalid content
  37996. inc(P);
  37997. end;
  37998. '{','[': begin
  37999. P := GotoNextJSONObjectOrArray(P);
  38000. if P=nil then
  38001. break; // invalid content
  38002. end;
  38003. end;
  38004. while not (P^ in [#0,',',']']) do inc(P);
  38005. if P^<>',' then break;
  38006. repeat inc(P) until not(P^ in [#1..' ']);
  38007. dec(Index);
  38008. end;
  38009. end;
  38010. end;
  38011. result := nil;
  38012. end;
  38013. function JSONArrayCount(P,PMax: PUTF8Char): integer;
  38014. var n: integer;
  38015. begin
  38016. result := -1;
  38017. n := 0;
  38018. P := GotoNextNotSpace(P);
  38019. if P^<>']' then
  38020. while P<PMax do begin
  38021. case P^ of
  38022. '"': begin
  38023. P := GotoEndOfJSONString(P);
  38024. if P^<>'"' then
  38025. exit;
  38026. inc(P);
  38027. end;
  38028. '{','[': begin
  38029. P := GotoNextJSONObjectOrArrayMax(P,PMax);
  38030. if P=nil then
  38031. exit; // invalid content or PMax reached
  38032. end;
  38033. end;
  38034. while not (P^ in [#0,',',']']) do inc(P);
  38035. inc(n);
  38036. if P^<>',' then break;
  38037. repeat inc(P) until not(P^ in [#1..' ']);
  38038. end;
  38039. if P^=']' then
  38040. result := n;
  38041. end;
  38042. function JSONObjectPropCount(P: PUTF8Char): integer;
  38043. var n: integer;
  38044. begin
  38045. result := -1;
  38046. n := 0;
  38047. P := GotoNextNotSpace(P);
  38048. if P^<>'}' then
  38049. repeat
  38050. P := GotoNextJSONPropName(P);
  38051. if P=nil then
  38052. exit;
  38053. case P^ of
  38054. '"': begin
  38055. P := GotoEndOfJSONString(P);
  38056. if P^<>'"' then
  38057. exit;
  38058. inc(P);
  38059. end;
  38060. '{','[': begin
  38061. P := GotoNextJSONObjectOrArray(P);
  38062. if P=nil then
  38063. exit; // invalid content
  38064. end;
  38065. end;
  38066. while not (P^ in [#0,',','}']) do inc(P);
  38067. inc(n);
  38068. if P^<>',' then break;
  38069. repeat inc(P) until not(P^ in [#1..' ']);
  38070. until false;
  38071. if P^='}' then
  38072. result := n;
  38073. end;
  38074. function JsonObjectItem(P: PUTF8Char; const PropName: RawUTF8;
  38075. PropNameFound: PRawUTF8): PUTF8Char;
  38076. var name: shortstring; // no memory allocation nor P^ modification
  38077. PropNameLen: integer;
  38078. PropNameUpper: array[byte] of AnsiChar;
  38079. begin
  38080. if P<>nil then begin
  38081. P := GotoNextNotSpace(P);
  38082. PropNameLen := length(PropName);
  38083. if PropNameLen<>0 then begin
  38084. if PropName[PropNameLen]='*' then begin
  38085. UpperCopy255Buf(PropNameUpper,pointer(PropName),PropNameLen-1)^ := #0;
  38086. PropNameLen := 0;
  38087. end;
  38088. if P^='{' then
  38089. P := GotoNextNotSpace(P+1);
  38090. while P^<>'}' do begin
  38091. GetJSONPropName(P,name);
  38092. if (name[0]=#0) or (name[0]>#200) then
  38093. break;
  38094. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  38095. if PropNameLen=0 then begin
  38096. name[ord(name[0])+1] := #0; // make ASCIIZ
  38097. if IdemPChar(@name[1],PropNameUpper) then begin
  38098. if PropNameFound<>nil then
  38099. PropNameFound^ := name;
  38100. result := P;
  38101. exit;
  38102. end;
  38103. end else
  38104. if IdemPropName(name,pointer(PropName),PropNameLen) then begin
  38105. result := P;
  38106. exit;
  38107. end;
  38108. case P^ of
  38109. '"': begin
  38110. P := GotoEndOfJSONString(P);
  38111. if P^<>'"' then
  38112. break; // invalid content
  38113. inc(P);
  38114. end;
  38115. '{','[': begin
  38116. P := GotoNextJSONObjectOrArray(P);
  38117. if P=nil then
  38118. break; // invalid content
  38119. end;
  38120. end;
  38121. while not (P^ in [#0,',',']']) do inc(P);
  38122. if P^<>',' then break;
  38123. repeat inc(P) until not(P^ in [#1..' ']);
  38124. end;
  38125. end;
  38126. end;
  38127. result := nil;
  38128. end;
  38129. function JsonObjectByPath(JsonObject,PropPath: PUTF8Char): PUTF8Char;
  38130. var objName: RawUTF8;
  38131. begin
  38132. result := nil;
  38133. if (JsonObject=nil) or (PropPath=nil) then
  38134. exit;
  38135. repeat
  38136. objName := GetNextItem(PropPath,'.');
  38137. if objName='' then
  38138. exit;
  38139. JsonObject := JsonObjectItem(JsonObject,objName);
  38140. if JsonObject=nil then
  38141. exit;
  38142. if PropPath=nil then
  38143. break; // found full name scope
  38144. until false;
  38145. result := JsonObject;
  38146. end;
  38147. function JsonObjectsByPath(JsonObject,PropPath: PUTF8Char): RawUTF8;
  38148. var itemName,objName,propNameFound,objPath: RawUTF8;
  38149. start,ending,obj: PUTF8Char;
  38150. WR: TTextWriter;
  38151. procedure AddFromStart(const name: RaWUTF8);
  38152. begin
  38153. start := GotoNextNotSpace(start);
  38154. ending := GotoEndJSONItem(start);
  38155. if ending=nil then
  38156. exit;
  38157. if WR=nil then begin
  38158. WR := TTextWriter.CreateOwnedStream;
  38159. WR.Add('{');
  38160. end else
  38161. WR.Add(',');
  38162. WR.AddFieldName(name);
  38163. while (ending>start) and (ending[-1]<=' ') do dec(ending); // trim right
  38164. WR.AddNoJSONEscape(start,ending-start);
  38165. end;
  38166. begin
  38167. result := '';
  38168. if (JsonObject=nil) or (PropPath=nil) then
  38169. exit;
  38170. WR := nil;
  38171. try
  38172. repeat
  38173. itemName := GetNextItem(PropPath,',');
  38174. if itemName='' then
  38175. break;
  38176. if itemName[length(itemName)]<>'*' then begin
  38177. start := JsonObjectByPath(JsonObject,pointer(itemName));
  38178. if start<>nil then
  38179. AddFromStart(itemName);
  38180. end else begin
  38181. objPath := '';
  38182. obj := pointer(itemName);
  38183. repeat
  38184. objName := GetNextItem(obj,'.');
  38185. if objName='' then
  38186. exit;
  38187. propNameFound := '';
  38188. JsonObject := JsonObjectItem(JsonObject,objName,@propNameFound);
  38189. if JsonObject=nil then
  38190. exit;
  38191. if obj=nil then begin // found full name scope
  38192. start := JsonObject;
  38193. repeat
  38194. AddFromStart(objPath+propNameFound);
  38195. ending := GotoNextNotSpace(ending);
  38196. if ending^<>',' then
  38197. break;
  38198. propNameFound := '';
  38199. start := JsonObjectItem(GotoNextNotSpace(ending+1),objName,@propNameFound);
  38200. until start=nil;
  38201. break;
  38202. end else
  38203. objPath := objPath+objName+'.';
  38204. until false;
  38205. end;
  38206. until PropPath=nil;
  38207. if WR<>nil then begin
  38208. WR.Add('}');
  38209. WR.SetText(result);
  38210. end;
  38211. finally
  38212. WR.Free;
  38213. end;
  38214. end;
  38215. function JSONObjectAsJSONArrays(JSON: PUTF8Char; out keys,values: RawUTF8): boolean;
  38216. var wk,wv: TTextWriter;
  38217. kb,ke,vb,ve: PUTF8Char;
  38218. begin
  38219. result := false;
  38220. if (JSON=nil) or (JSON^<>'{') then
  38221. exit;
  38222. wk := TTextWriter.CreateOwnedStream(8192);
  38223. wv := TTextWriter.CreateOwnedStream(8192);
  38224. try
  38225. wk.Add('[');
  38226. wv.Add('[');
  38227. kb := JSON+1;
  38228. repeat
  38229. ke := GotoEndJSONItem(kb);
  38230. if (ke=nil) or (ke^<>':') then
  38231. exit; // invalid input content
  38232. vb := ke+1;
  38233. ve := GotoEndJSONItem(vb);
  38234. if (ve=nil) or not(ve^ in [',','}']) then
  38235. exit;
  38236. wk.AddNoJSONEscape(kb,ke-kb);
  38237. wk.Add(',');
  38238. wv.AddNoJSONEscape(vb,ve-vb);
  38239. wv.Add(',');
  38240. kb := ve+1;
  38241. until ve^='}';
  38242. wk.CancelLastComma;
  38243. wk.Add(']');
  38244. wk.SetText(keys);
  38245. wv.CancelLastComma;
  38246. wv.Add(']');
  38247. wv.SetText(values);
  38248. result := true;
  38249. finally
  38250. wv.Free;
  38251. wk.Free;
  38252. end;
  38253. end;
  38254. const
  38255. PTRSIZ = sizeof(Pointer);
  38256. KNOWNTYPE_SIZE: array[TDynArrayKind] of byte = (
  38257. 0, 1,1, 2, 4,4,4, 8,8,8,8,8, PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,PTRSIZ,
  38258. {$ifndef NOVARIANTS}sizeof(Variant),{$endif} 0);
  38259. function TDynArray.GetArrayTypeName: RawUTF8;
  38260. begin
  38261. TypeInfoToName(fTypeInfo,result);
  38262. end;
  38263. function TDynArray.ToKnownType(exactType: boolean): TDynArrayKind;
  38264. var nested: PTypeInfo;
  38265. label Bin, Rec;
  38266. begin
  38267. if fKnownType<>djNone then begin
  38268. result := fKnownType;
  38269. exit;
  38270. end;
  38271. case ElemSize of
  38272. 1: if fTypeInfo=TypeInfo(TBooleanDynArray) then
  38273. fKnownType := djBoolean;
  38274. 4: if fTypeInfo=TypeInfo(TCardinalDynArray) then
  38275. fKnownType := djCardinal else
  38276. if fTypeInfo=TypeInfo(TSingleDynArray) then
  38277. fKnownType := djSingle
  38278. {$ifdef CPU64} ; 8: {$else} else {$endif}
  38279. if fTypeInfo=TypeInfo(TRawUTF8DynArray) then
  38280. fKnownType := djRawUTF8 else
  38281. if fTypeInfo=TypeInfo(TStringDynArray) then
  38282. fKnownType := djString else
  38283. if fTypeInfo=TypeInfo(TWinAnsiDynArray) then
  38284. fKnownType := djWinAnsi else
  38285. if fTypeInfo=TypeInfo(TRawByteStringDynArray) then
  38286. fKnownType := djRawByteString else
  38287. if fTypeInfo=TypeInfo(TSynUnicodeDynArray) then
  38288. fKnownType := djSynUnicode else
  38289. {$ifndef DELPHI5OROLDER}
  38290. if fTypeInfo=TypeInfo(TInterfaceDynArray) then
  38291. fKnownType := djInterface
  38292. {$endif}
  38293. {$ifdef CPU64} else {$else} ; 8: {$endif}
  38294. if fTypeInfo=TypeInfo(TDoubleDynArray) then
  38295. fKnownType := djDouble else
  38296. if fTypeInfo=TypeInfo(TCurrencyDynArray) then
  38297. fKnownType := djCurrency else
  38298. if fTypeInfo=TypeInfo(TTimeLogDynArray) then
  38299. fKnownType := djTimeLog else
  38300. if fTypeInfo=TypeInfo(TDateTimeDynArray) then
  38301. fKnownType := djDateTime;
  38302. end;
  38303. if (fKnownType=djNone) and not exactType then begin
  38304. fKnownSize := 0;
  38305. if ElemType=nil then
  38306. Bin: case ElemSize of
  38307. 1: fKnownType := djByte;
  38308. 2: fKnownType := djWord;
  38309. 4: fKnownType := djInteger;
  38310. 8: fKnownType := djInt64;
  38311. else fKnownSize := ElemSize;
  38312. end else
  38313. case PTypeKind(ElemType)^ of
  38314. tkLString{$ifdef FPC},tkLStringOld{$endif}: fKnownType := djRawUTF8;
  38315. tkWString: fKnownType := djWideString;
  38316. {$ifdef UNICODE}
  38317. tkUString: fKnownType := djString;
  38318. {$endif}
  38319. {$ifndef NOVARIANTS}
  38320. tkVariant: fKnownType := djVariant;
  38321. {$endif}
  38322. tkInterface: fKnownType := djInterface;
  38323. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  38324. nested := ElemType; // inlined GetTypeInfo()
  38325. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  38326. rec: nested := GetFPCAlignPtr(nested);
  38327. {$else}
  38328. rec: inc(PtrUInt(nested),(nested^.NameLen));
  38329. {$endif}
  38330. if nested^.ManagedCount=0 then // only binary content -> full content
  38331. goto Bin;
  38332. with nested^.ManagedFields[0] do
  38333. case Offset of
  38334. 0: case TypeInfo^.Kind of
  38335. tkLString{$ifdef FPC},tkLStringOld{$endif}: fKnownType := djRawUTF8;
  38336. tkWString: fKnownType := djWideString;
  38337. {$ifdef UNICODE}
  38338. tkUString: fKnownType := djString;
  38339. {$endif}
  38340. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  38341. nested := Deref(TypeInfo);
  38342. goto Rec;
  38343. end;
  38344. {$ifndef NOVARIANTS}
  38345. tkVariant: fKnownType := djVariant;
  38346. {$endif}
  38347. else begin
  38348. {$ifdef FPC} // unmanaged fields have RTTI in newest FPC! :)
  38349. if (nested^.ManagedCount<>1) and // emulate Delphi behavior
  38350. (nested^.ManagedFields[1].TypeInfo^.Kind in tkManagedTypes) then
  38351. case nested^.ManagedFields[1].Offset of
  38352. 1: fKnownType := djByte;
  38353. 2: fKnownType := djWord;
  38354. 4: fKnownType := djInteger;
  38355. 8: fKnownType := djInt64;
  38356. else fKnownSize := nested^.ManagedFields[1].Offset;
  38357. end else
  38358. {$endif}
  38359. goto bin;
  38360. end;
  38361. end;
  38362. 1: fKnownType := djByte;
  38363. 2: fKnownType := djWord;
  38364. 4: fKnownType := djInteger;
  38365. 8: fKnownType := djInt64;
  38366. else fKnownSize := Offset;
  38367. end;
  38368. end;
  38369. end;
  38370. end;
  38371. if KNOWNTYPE_SIZE[fKnownType]<>0 then
  38372. fKnownSize := KNOWNTYPE_SIZE[fKnownType];
  38373. result := fKnownType;
  38374. end;
  38375. function TDynArray.LoadFromJSON(P: PUTF8Char; aEndOfObject: PUTF8Char=nil): PUTF8Char;
  38376. var n, i: integer;
  38377. T: TDynArrayKind;
  38378. wasString, expectedString, isValid: boolean;
  38379. EndOfObject: AnsiChar;
  38380. Val: PUTF8Char;
  38381. CustomReader: TDynArrayJSONCustomReader;
  38382. NestedDynArray: TDynArray;
  38383. begin // code below must match TTextWriter.AddDynArrayJSON()
  38384. result := nil;
  38385. if (P=nil) or (fValue=nil) then
  38386. exit;
  38387. if not NextNotSpaceCharIs(P,'[') then
  38388. exit;
  38389. n := JSONArrayCount(P);
  38390. if n<0 then
  38391. exit; // invalid array content
  38392. if n=0 then begin
  38393. if NextNotSpaceCharIs(P,']') then begin
  38394. Clear;
  38395. result := P;
  38396. end;
  38397. exit; // handle '[]' array
  38398. end;
  38399. if GlobalJSONCustomParsers.DynArraySearch(ArrayType,ElemType,CustomReader) then
  38400. T := djCustom else
  38401. T := ToKnownType;
  38402. if (T=djNone) and (P^='[') and (PTypeKind(ElemType)^=tkDynArray) then begin
  38403. Count := n; // fast allocation of the whole dynamic array memory at once
  38404. for i := 0 to n-1 do begin
  38405. NestedDynArray.Init(ElemType,PPointerArray(fValue^)^[i]);
  38406. P := NestedDynArray.LoadFromJSON(P,@EndOfObject);
  38407. if P=nil then
  38408. exit;
  38409. EndOfObject := P^; // ',' or ']' for the last item of the array
  38410. inc(P);
  38411. end;
  38412. end else
  38413. if (T=djNone) or
  38414. (PCardinal(P)^=JSON_BASE64_MAGIC_QUOTE) then begin
  38415. if n<>1 then
  38416. exit; // expect one Base64 encoded string value preceded by \uFFF0
  38417. Val := GetJSONField(P,P,@wasString,@EndOfObject);
  38418. if (Val=nil) or (not wasString) or
  38419. (PInteger(Val)^ and $00ffffff<>JSON_BASE64_MAGIC) or
  38420. (LoadFrom(pointer(Base64ToBin(Val+3)))=nil) then
  38421. exit; // invalid content
  38422. end else begin
  38423. if GetIsObjArray then
  38424. for i := 0 to Count-1 do // force release any previous instance
  38425. FreeAndNil(PObjectArray(fValue^)^[i]);
  38426. SetCount(n); // fast allocation of the whole dynamic array memory at once
  38427. case T of
  38428. {$ifndef NOVARIANTS}
  38429. djVariant:
  38430. for i := 0 to n-1 do
  38431. P := VariantLoadJSON(PVariantArray(fValue^)^[i],P,@EndOfObject,@JSON_OPTIONS[true]);
  38432. {$endif}
  38433. djCustom: begin
  38434. Val := fValue^;
  38435. for i := 1 to n do begin
  38436. P := CustomReader(P,Val^,isValid);
  38437. if not isValid then
  38438. exit;
  38439. EndOfObject := P^; // ',' or ']' for the last item of the array
  38440. inc(P);
  38441. inc(Val,ElemSize);
  38442. end;
  38443. end;
  38444. else begin
  38445. expectedString := (T in [djTimeLog..djSynUnicode]);
  38446. for i := 0 to n-1 do begin
  38447. Val := GetJSONField(P,P,@wasString,@EndOfObject);
  38448. if (Val=nil) or (wasString<>expectedString) then
  38449. exit;
  38450. case T of
  38451. djBoolean: PBooleanArray(fValue^)^[i] := GetBoolean(Val);
  38452. djByte: PByteArray(fValue^)^[i] := GetCardinal(Val);
  38453. djWord: PWordArray(fValue^)^[i] := GetCardinal(Val);
  38454. djInteger: PIntegerArray(fValue^)^[i] := GetInteger(Val);
  38455. djCardinal: PCardinalArray(fValue^)^[i] := GetCardinal(Val);
  38456. djSingle: PSingleArray(fValue^)^[i] := GetExtended(Val);
  38457. djInt64: SetInt64(Val,PInt64Array(fValue^)^[i]);
  38458. djTimeLog: PInt64Array(fValue^)^[i] := Iso8601ToTimeLogPUTF8Char(Val,0);
  38459. djDateTime: Iso8601ToDateTimePUTF8CharVar(Val,0,TDateTime(PDoubleArray(fValue^)^[i]));
  38460. djDouble: PDoubleArray(fValue^)^[i] := GetExtended(Val);
  38461. djCurrency: PInt64Array(fValue^)^[i] := StrToCurr64(Val);
  38462. djRawUTF8: RawUTF8(PPointerArray(fValue^)^[i]) := Val;
  38463. djRawByteString:
  38464. if not Base64MagicCheckAndDecode(Val,PRawByteStringArray(fValue^)^[i]) then
  38465. RawUTF8(PPointerArray(fValue^)^[i]) := Val;
  38466. djWinAnsi: WinAnsiConvert.UTF8BufferToAnsi(Val,StrLen(Val),PRawByteStringArray(fValue^)^[i]);
  38467. djString: UTF8DecodeToString(Val,StrLen(Val),string(PPointerArray(fValue^)^[i]));
  38468. djWideString: UTF8ToWideString(Val,StrLen(Val),WideString(PPointerArray(fValue^)^[i]));
  38469. djSynUnicode: UTF8ToSynUnicode(Val,StrLen(Val),SynUnicode(PPointerArray(fValue^)^[i]));
  38470. djInterface: raise ESynException.Create('djInterface not readable');
  38471. end;
  38472. end;
  38473. end;
  38474. end;
  38475. end;
  38476. if aEndOfObject<>nil then
  38477. aEndOfObject^ := EndOfObject;
  38478. if EndOfObject=']' then
  38479. if P=nil then
  38480. result := @NULCHAR else
  38481. result := P;
  38482. end;
  38483. function SimpleDynArrayLoadFrom(Source: PAnsiChar; aTypeInfo: pointer;
  38484. var Count, ElemSize: integer): pointer;
  38485. var Hash: PCardinalArray absolute Source;
  38486. info: PTypeInfo;
  38487. begin
  38488. result := nil;
  38489. info := GetTypeInfo(aTypeInfo,tkDynArray);
  38490. if info=nil then
  38491. exit; // invalid type information
  38492. if (info^.ElType<>nil) or (Source=nil) or
  38493. (Source[0]<>AnsiChar(info^.elSize)) or (Source[1]<>#0) then
  38494. exit; // invalid type information or Source content
  38495. ElemSize := info^.elSize {$ifdef FPC}and $7FFFFFFF{$endif};
  38496. inc(Source,2);
  38497. Count := FromVarUInt32(PByte(Source)); // dynamic array count
  38498. if (Count<>0) and (Hash32(@Hash[1],
  38499. Count*info^.elSize {$ifdef FPC}and $7FFFFFFF{$endif})=Hash[0]) then
  38500. result := @Hash[1]; // returns valid Source content
  38501. end;
  38502. function IntegerDynArrayLoadFrom(Source: PAnsiChar; var Count: integer): PIntegerArray;
  38503. var Hash: PCardinalArray absolute Source;
  38504. begin
  38505. result := nil;
  38506. if (Source=nil) or (Source[0]<>#4) or (Source[1]<>#0) then
  38507. exit; // invalid Source content
  38508. inc(Source,2);
  38509. Count := FromVarUInt32(PByte(Source)); // dynamic array count
  38510. if (Count<>0) and (Hash32(@Hash[1],Count*sizeof(Integer))=Hash[0]) then
  38511. result := @Hash[1]; // returns valid Source content
  38512. end;
  38513. function RawUTF8DynArrayLoadFromContains(Source: PAnsiChar;
  38514. Value: PUTF8Char; ValueLen: integer; CaseSensitive: boolean): integer;
  38515. var Count, Len: integer;
  38516. begin
  38517. if (Value=nil) or (ValueLen=0) or
  38518. (Source=nil) or (Source[0]<>AnsiChar(sizeof(PtrInt))) or
  38519. (Source[1]<>AnsiChar(tkLString)) then begin
  38520. result := -1;
  38521. exit; // invalid Source or Value content
  38522. end;
  38523. inc(Source,2);
  38524. Count := FromVarUInt32(PByte(Source)); // dynamic array count
  38525. inc(Source,sizeof(cardinal)); // ignore security checksum
  38526. for result := 0 to Count-1 do begin
  38527. Len := FromVarUInt32(PByte(Source));
  38528. if CaseSensitive then begin
  38529. if (Len=ValueLen) and CompareMem(Value,Source,Len) then
  38530. exit;
  38531. end else
  38532. if UTF8ILComp(Value,pointer(Source),ValueLen,Len)=0 then
  38533. exit;
  38534. inc(Source,Len);
  38535. end;
  38536. result := -1;
  38537. end;
  38538. function TDynArray.LoadFrom(Source: PAnsiChar): PAnsiChar;
  38539. var i, n, LenBytes: integer;
  38540. P: PAnsiChar;
  38541. infoNested: PTypeInfo;
  38542. Hash: PCardinalArray;
  38543. NestedArray: TDynArray;
  38544. begin
  38545. // check stored element size+type
  38546. if Source=nil then begin
  38547. Clear;
  38548. result := nil;
  38549. exit;
  38550. end;
  38551. FromVarUInt32(PByte(Source)); // ignore StoredElemSize to be Win32/64 compatible
  38552. if (fValue=nil) or
  38553. //((ElemSize<>sizeof(pointer)) and (StoredElemSize<>ElemSize)) or
  38554. ((ElemType=nil) and (Source^<>#0) or
  38555. ((ElemType<>nil) and (Source^=#0{<>PAnsiChar(ElemType)^}))) then begin
  38556. // ignore ElemType^ to be cross-FPC/Delphi compatible
  38557. result := nil; // invalid Source content
  38558. exit;
  38559. end;
  38560. inc(Source);
  38561. // retrieve dynamic array count
  38562. n := FromVarUInt32(PByte(Source));
  38563. SetCount(n);
  38564. if n=0 then begin
  38565. result := Source;
  38566. exit;
  38567. end;
  38568. // retrieve security checksum
  38569. Hash := pointer(Source);
  38570. inc(Source,sizeof(cardinal));
  38571. // retrieve dynamic array elements content
  38572. P := fValue^;
  38573. if ElemType=nil then
  38574. if GetIsObjArray then
  38575. raise ESynException.CreateUTF8('TDynArray.LoadFrom(%) is a T*ObjArray',
  38576. [PShortString(@PTypeInfo(ArrayType).NameLen)^]) else begin
  38577. // binary type was stored as once
  38578. n := n*integer(ElemSize);
  38579. MoveFast(Source^,P^,n);
  38580. inc(Source,n);
  38581. end else
  38582. case PTypeKind(ElemType)^ of
  38583. tkLString, tkWString {$ifdef HASVARUSTRING}, tkUString{$endif}
  38584. {$ifdef FPC}, tkLStringOld{$endif}:
  38585. for i := 1 to n do begin
  38586. LenBytes := FromVarUInt32(PByte(Source));
  38587. case PTypeKind(ElemType)^ of
  38588. tkLString{$ifdef FPC},tkLStringOld{$endif}: begin
  38589. SetString(PRawByteString(P)^,Source,LenBytes);
  38590. {$ifdef HASCODEPAGE}
  38591. { Delphi 2009+: set Code page for this AnsiString }
  38592. if LenBytes<>0 then
  38593. SetCodePage(PRawByteString(P)^,PWord(PtrUInt(ElemType)+
  38594. PTypeInfo(ElemType)^.NameLen+2)^,false);
  38595. {$endif}
  38596. end;
  38597. tkWString:
  38598. SetString(PWideString(P)^,PWideChar(Source),LenBytes shr 1);
  38599. {$ifdef HASVARUSTRING}
  38600. tkUString:
  38601. SetString(PString(P)^,PWideChar(Source),LenBytes shr 1);
  38602. {$endif}
  38603. end;
  38604. inc(Source,LenBytes);
  38605. inc(P,sizeof(PtrUInt));
  38606. end;
  38607. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  38608. infoNested := ElemType; // inlined GetTypeInfo()
  38609. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  38610. infoNested := GetFPCAlignPtr(infoNested);
  38611. {$else}
  38612. inc(PtrUInt(infoNested),infoNested^.NameLen);
  38613. {$endif}
  38614. if infoNested^.ManagedCount=0 then begin
  38615. {$ifndef LVCL}
  38616. assert(infoNested^.recSize=ElemSize);
  38617. {$endif}
  38618. n := n*integer(ElemSize);
  38619. MoveFast(Source^,P^,n);
  38620. inc(Source,n);
  38621. end else begin
  38622. for i := 1 to n do begin
  38623. Source := RecordLoad(P^,Source,ElemType);
  38624. if Source=nil then
  38625. break; // invalid content (e.g. wrong field type)
  38626. inc(P,ElemSize);
  38627. end;
  38628. end;
  38629. end;
  38630. {$ifndef NOVARIANTS}
  38631. tkVariant:
  38632. for i := 0 to n-1 do begin
  38633. Source := VariantLoad(PVariantArray(P)^[i],Source,@JSON_OPTIONS[true]);
  38634. if Source=nil then
  38635. break; // invalid/unhandled variant content
  38636. end;
  38637. {$endif}
  38638. tkDynArray:
  38639. for i := 1 to n do begin
  38640. NestedArray.Init(ElemType,P^);
  38641. Source := NestedArray.LoadFrom(Source);
  38642. if Source=nil then
  38643. break; // invalid content (e.g. wrong field type)
  38644. inc(P,ElemSize);
  38645. end;
  38646. end;
  38647. // check security checksum
  38648. if (Source=nil) or (Hash32(@Hash[1],Source-PAnsiChar(@Hash[1]))<>Hash[0]) then
  38649. result := nil else
  38650. result := Source;
  38651. end;
  38652. function TDynArray.Find(const Elem; const aIndex: TIntegerDynArray;
  38653. aCompare: TDynArraySortCompare): integer;
  38654. var n, L, cmp: integer;
  38655. P: PAnsiChar;
  38656. begin
  38657. n := Count;
  38658. if (@aCompare<>nil) and (n>0) then begin
  38659. dec(n);
  38660. P := fValue^;
  38661. if (n>10) and (length(aIndex)>=n) then begin
  38662. // array should be sorted via aIndex[] -> use fast binary search
  38663. L := 0;
  38664. repeat
  38665. result := (L+n) shr 1;
  38666. cmp := aCompare(P[cardinal(aIndex[result])*ElemSize],Elem);
  38667. if cmp=0 then begin
  38668. result := aIndex[result]; // returns index in TDynArray
  38669. exit;
  38670. end;
  38671. if cmp<0 then
  38672. L := result+1 else
  38673. n := result-1;
  38674. until L>n;
  38675. end else
  38676. // array is not sorted, or aIndex=nil -> use iterating search
  38677. for result := 0 to n do
  38678. if aCompare(P^,Elem)=0 then
  38679. exit else
  38680. inc(P,ElemSize);
  38681. end;
  38682. result := -1;
  38683. end;
  38684. function TDynArray.FindIndex(const Elem; aIndex: PIntegerDynArray;
  38685. aCompare: TDynArraySortCompare): integer;
  38686. begin
  38687. if aIndex<>nil then
  38688. result := Find(Elem,aIndex^,aCompare) else
  38689. if Assigned(aCompare) then
  38690. result := Find(Elem,nil,aCompare) else
  38691. result := Find(Elem);
  38692. end;
  38693. function TDynArray.FindAndFill(var Elem; aIndex: PIntegerDynArray=nil;
  38694. aCompare: TDynArraySortCompare=nil): integer;
  38695. begin
  38696. result := FindIndex(Elem,aIndex,aCompare);
  38697. if result>=0 then // if found, fill Elem with the matching item
  38698. ElemCopy(PAnsiChar(fValue^)[cardinal(result)*ElemSize],Elem);
  38699. end;
  38700. function TDynArray.FindAndDelete(const Elem; aIndex: PIntegerDynArray=nil;
  38701. aCompare: TDynArraySortCompare=nil): integer;
  38702. begin
  38703. result := FindIndex(Elem,aIndex,aCompare);
  38704. if result>=0 then
  38705. Delete(result);
  38706. end;
  38707. function TDynArray.FindAndUpdate(const Elem; aIndex: PIntegerDynArray=nil;
  38708. aCompare: TDynArraySortCompare=nil): integer;
  38709. begin
  38710. result := FindIndex(Elem,aIndex,aCompare);
  38711. if result>=0 then // if found, fill Elem with the matching item
  38712. ElemCopy(Elem,PAnsiChar(fValue^)[cardinal(result)*ElemSize]);
  38713. end;
  38714. function TDynArray.FindAndAddIfNotExisting(const Elem; aIndex: PIntegerDynArray=nil;
  38715. aCompare: TDynArraySortCompare=nil): integer;
  38716. begin
  38717. result := FindIndex(Elem,aIndex,aCompare);
  38718. if result<0 then
  38719. Add(Elem); // -1 will mark success
  38720. end;
  38721. function TDynArray.Find(const Elem): integer;
  38722. var n, L, cmp: integer;
  38723. P: PAnsiChar;
  38724. begin
  38725. n := Count;
  38726. if (@fCompare<>nil) and (n>0) then begin
  38727. dec(n);
  38728. P := fValue^;
  38729. if fSorted and (n>10) then begin
  38730. // array is sorted -> use fast binary search
  38731. L := 0;
  38732. repeat
  38733. result := (L+n) shr 1;
  38734. cmp := fCompare(P[cardinal(result)*ElemSize],Elem);
  38735. if cmp=0 then
  38736. exit;
  38737. if cmp<0 then
  38738. L := result+1 else
  38739. n := result-1;
  38740. until L>n;
  38741. end else
  38742. // array is very small, or not sorted -> use iterating search
  38743. for result := 0 to n do
  38744. if fCompare(P^,Elem)=0 then
  38745. exit else
  38746. inc(P,ElemSize);
  38747. end;
  38748. result := -1;
  38749. end;
  38750. function TDynArray.FastLocateSorted(const Elem; out Index: Integer): boolean;
  38751. var n, i, cmp: integer;
  38752. P: PAnsiChar;
  38753. begin
  38754. result := False;
  38755. n := Count;
  38756. if @fCompare<>nil then
  38757. if n=0 then // a void array is always sorted
  38758. Index := 0 else
  38759. if fSorted then begin
  38760. P := fValue^;
  38761. Index := 0;
  38762. dec(n);
  38763. while Index<=n do begin
  38764. i := (Index+n) shr 1;
  38765. cmp := fCompare(P[cardinal(i)*ElemSize],Elem);
  38766. if cmp=0 then begin
  38767. Index := i; // index of existing Elem
  38768. result := True;
  38769. exit;
  38770. end else
  38771. if cmp<0 then
  38772. Index := i+1 else
  38773. n := i-1;
  38774. end;
  38775. // Elem not found: returns false + the index where to insert
  38776. end else
  38777. Index := -1 else // not Sorted
  38778. Index := -1; // no fCompare()
  38779. end;
  38780. procedure TDynArray.FastAddSorted(Index: Integer; const Elem);
  38781. begin
  38782. Insert(Index,Elem);
  38783. fSorted := true; // Insert -> SetCount -> fSorted := false
  38784. end;
  38785. procedure TDynArray.FastDeleteSorted(Index: Integer);
  38786. begin
  38787. Delete(Index);
  38788. fSorted := true; // Delete -> SetCount -> fSorted := false
  38789. end;
  38790. function TDynArray.FastLocateOrAddSorted(const Elem; wasAdded: PBoolean): integer;
  38791. var toInsert: boolean;
  38792. begin
  38793. toInsert := (not FastLocateSorted(Elem,result)) and (result>=0);
  38794. if toInsert then
  38795. FastAddSorted(result,Elem);
  38796. if wasAdded<>nil then
  38797. wasAdded^ := toInsert;
  38798. end;
  38799. type
  38800. // internal structure used to make QuickSort faster & with less stack usage
  38801. TDynArrayQuickSort = {$ifndef UNICODE}object{$else}record{$endif}
  38802. Compare: TDynArraySortCompare;
  38803. Pivot: pointer;
  38804. Index: PCardinalArray;
  38805. ElemSize: cardinal;
  38806. P: integer;
  38807. Value: PAnsiChar;
  38808. IP, JP: PAnsiChar;
  38809. procedure QuickSort(L, R: PtrInt);
  38810. procedure QuickSortIndexed(L, R: PtrInt);
  38811. end;
  38812. procedure QuickSortIndexedPUTF8Char(Values: PPUtf8CharArray; Count: Integer;
  38813. var SortedIndexes: TCardinalDynArray; CaseSensitive: boolean);
  38814. var QS: TDynArrayQuickSort;
  38815. i: integer;
  38816. begin
  38817. if CaseSensitive then
  38818. QS.Compare := SortDynArrayPUTF8Char else
  38819. QS.Compare := SortDynArrayPUTF8CharI;
  38820. QS.Value := pointer(Values);
  38821. QS.ElemSize := sizeof(PUTF8Char);
  38822. SetLength(SortedIndexes,Count);
  38823. dec(Count);
  38824. for i := 0 to Count do
  38825. SortedIndexes[i] := i;
  38826. QS.Index := pointer(SortedIndexes);
  38827. QS.QuickSortIndexed(0,Count);
  38828. end;
  38829. procedure TDynArrayQuickSort.QuickSort(L, R: PtrInt);
  38830. var I, J: integer;
  38831. tmp: pointer;
  38832. begin
  38833. if L<R then
  38834. repeat
  38835. I := L; J := R;
  38836. P := (L + R) shr 1;
  38837. repeat
  38838. pivot := Value+cardinal(P)*ElemSize;
  38839. IP := Value+cardinal(I)*ElemSize;
  38840. JP := Value+cardinal(J)*ElemSize;
  38841. while Compare(IP^,pivot^)<0 do begin
  38842. inc(I);
  38843. inc(IP,ElemSize);
  38844. end;
  38845. while Compare(JP^,pivot^)>0 do begin
  38846. dec(J);
  38847. dec(JP,ElemSize);
  38848. end;
  38849. if I <= J then begin
  38850. if I<>J then
  38851. if ElemSize=SizeOf(pointer) then begin
  38852. // optimized version e.g. for TRawUTF8DynArray
  38853. tmp := PPointer(IP)^;
  38854. PPointer(IP)^ := PPointer(JP)^;
  38855. PPointer(JP)^ := tmp;
  38856. end else
  38857. // generic exchange of row element data
  38858. Exchg(IP,JP,ElemSize);
  38859. if P = I then P := J else
  38860. if P = J then P := I;
  38861. Inc(I); Dec(J);
  38862. end;
  38863. until I > J;
  38864. if L < J then
  38865. QuickSort(L, J);
  38866. L := I;
  38867. until I >= R;
  38868. end;
  38869. procedure TDynArrayQuickSort.QuickSortIndexed(L, R: PtrInt);
  38870. var I, J, tmp: integer;
  38871. begin
  38872. if L<R then
  38873. repeat
  38874. I := L; J := R;
  38875. P := (L + R) shr 1;
  38876. repeat
  38877. pivot := Value+Index[P]*ElemSize;
  38878. while Compare(Value[Index[I]*ElemSize],pivot^)<0 do inc(I);
  38879. while Compare(Value[Index[J]*ElemSize],pivot^)>0 do dec(J);
  38880. if I <= J then begin
  38881. if I<>J then begin
  38882. tmp := Index[I];
  38883. Index[I] := Index[J];
  38884. Index[J] := tmp;
  38885. end;
  38886. if P = I then P := J else
  38887. if P = J then P := I;
  38888. Inc(I); Dec(J);
  38889. end;
  38890. until I > J;
  38891. if L < J then
  38892. QuickSortIndexed(L, J);
  38893. L := I;
  38894. until I >= R;
  38895. end;
  38896. procedure TDynArray.Sort(aCompare: TDynArraySortCompare);
  38897. var QuickSort: TDynArrayQuickSort;
  38898. begin
  38899. if @aCompare=nil then
  38900. Quicksort.Compare := @fCompare else
  38901. Quicksort.Compare := aCompare;
  38902. if (@Quicksort.Compare<>nil) and (fValue<>nil) and (fValue^<>nil) then begin
  38903. Quicksort.Value := fValue^;
  38904. Quicksort.ElemSize := ElemSize;
  38905. Quicksort.QuickSort(0,Count-1);
  38906. fSorted := true;
  38907. end;
  38908. end;
  38909. procedure TDynArray.CreateOrderedIndex(var aIndex: TIntegerDynArray;
  38910. aCompare: TDynArraySortCompare);
  38911. var QuickSort: TDynArrayQuickSort;
  38912. n: integer;
  38913. begin
  38914. if @aCompare=nil then
  38915. Quicksort.Compare := @fCompare else
  38916. Quicksort.Compare := aCompare;
  38917. if (@QuickSort.Compare<>nil) and (fValue<>nil) and (fValue^<>nil) then begin
  38918. n := Count;
  38919. if length(aIndex)<n then begin
  38920. SetLength(aIndex,n);
  38921. FillIncreasing(pointer(aIndex),0,n);
  38922. end;
  38923. Quicksort.Value := fValue^;
  38924. Quicksort.ElemSize := ElemSize;
  38925. Quicksort.Index := pointer(aIndex);
  38926. Quicksort.QuickSortIndexed(0,n-1);
  38927. end;
  38928. end;
  38929. function TDynArray.ElemEquals(const A,B): boolean;
  38930. begin
  38931. if @fCompare<>nil then
  38932. result := fCompare(A,B)=0 else
  38933. if ElemType=nil then
  38934. case ElemSize of // optimized versions for arrays of common types
  38935. 1: result := byte(A)=byte(B);
  38936. 2: result := word(A)=word(B);
  38937. 4: result := cardinal(A)=cardinal(B);
  38938. 8: result := Int64(A)=Int64(B);
  38939. else result := CompareMem(@A,@B,ElemSize); // generic comparison
  38940. end else
  38941. case PTypeKind(ElemType)^ of
  38942. tkRecord{$ifdef FPC},tkObject{$endif}:
  38943. result := RecordEquals(A,B,ElemType);
  38944. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  38945. result := AnsiString(A)=AnsiString(B);
  38946. tkWString:
  38947. result := WideString(A)=WideString(B);
  38948. {$ifdef HASVARUSTRING}
  38949. tkUString:
  38950. result := UnicodeString(A)=UnicodeString(B);
  38951. {$endif}
  38952. tkInterface:
  38953. result := pointer(A)=pointer(B);
  38954. {$ifndef NOVARIANTS}
  38955. tkVariant:
  38956. result := Variant(A)=Variant(B);
  38957. {$endif}
  38958. else result := false;
  38959. end;
  38960. end;
  38961. {$ifndef DELPHI5OROLDER} // do not know why Delphi 5 compiler does not like it
  38962. function TDynArray.Equals(const B: TDynArray): boolean;
  38963. var i, n: integer;
  38964. P1,P2: PAnsiChar;
  38965. A1: PPointerArray absolute P1;
  38966. A2: PPointerArray absolute P2;
  38967. begin
  38968. result := false;
  38969. if ArrayType<>B.ArrayType then
  38970. exit; // array types shall match
  38971. n := Count;
  38972. if n<>B.Count then
  38973. exit;
  38974. P1 := fValue^;
  38975. P2 := B.fValue^;
  38976. if @fCompare<>nil then // if a customized comparison is available, use it
  38977. for i := 1 to n do
  38978. if fCompare(P1^,P2^)<>0 then
  38979. exit else begin
  38980. inc(P1,ElemSize);
  38981. inc(P2,ElemSize);
  38982. end else
  38983. if ElemType=nil then begin // binary type is compared as a whole
  38984. result := CompareMem(P1,P2,ElemSize*cardinal(n));
  38985. exit;
  38986. end else
  38987. case PTypeKind(ElemType)^ of
  38988. tkRecord{$ifdef FPC},tkObject{$endif}:
  38989. for i := 1 to n do
  38990. if not RecordEquals(P1^,P2^,ElemType) then
  38991. exit else begin
  38992. inc(P1,ElemSize);
  38993. inc(P2,ElemSize);
  38994. end;
  38995. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  38996. for i := 0 to n-1 do
  38997. if AnsiString(A1^[i])<>AnsiString(A2^[i]) then
  38998. exit;
  38999. tkWString:
  39000. for i := 0 to n-1 do
  39001. if WideString(A1^[i])<>WideString(A2^[i]) then
  39002. exit;
  39003. {$ifdef HASVARUSTRING}
  39004. tkUString:
  39005. for i := 0 to n-1 do
  39006. if UnicodeString(A1^[i])<>UnicodeString(A2^[i]) then
  39007. exit;
  39008. {$endif}
  39009. tkInterface:
  39010. if not CompareMem(P1,P2,SizeOf(pointer)*cardinal(n)) then
  39011. exit;
  39012. {$ifndef NOVARIANTS}
  39013. tkVariant:
  39014. for i := 0 to n-1 do
  39015. if PVariantArray(P1)^[i]<>PVariantArray(P2)^[i] then
  39016. exit;
  39017. {$endif}
  39018. else exit;
  39019. end;
  39020. result := true;
  39021. end;
  39022. procedure TDynArray.Copy(const Source: TDynArray);
  39023. var n: Cardinal;
  39024. begin
  39025. if (fValue=nil) or (ArrayType<>Source.ArrayType) then
  39026. exit;
  39027. SetCapacity(Source.Capacity);
  39028. n := Source.Count;
  39029. if n<>0 then
  39030. if ElemType=nil then
  39031. MoveFast(Source.fValue^^,fValue^^,n*ElemSize) else
  39032. CopyArray(fValue^,Source.fValue^,ElemType,n);
  39033. end;
  39034. procedure TDynArray.CopyFrom(const Source; MaxElem: integer);
  39035. var SourceDynArray: TDynArray;
  39036. begin
  39037. SourceDynArray.Init(fTypeInfo,pointer(@Source)^);
  39038. SourceDynArray.fCountP := @MaxElem; // would set Count=0 at Init()
  39039. Copy(SourceDynArray);
  39040. end;
  39041. {$endif DELPHI5OROLDER}
  39042. function TDynArray.IndexOf(const Elem): integer;
  39043. var P: pointer;
  39044. max: integer;
  39045. begin
  39046. if fValue=nil then begin
  39047. result := -1;
  39048. exit; // avoid GPF if void
  39049. end;
  39050. max := Count-1;
  39051. P := fValue^;
  39052. if @Elem<>nil then
  39053. if ElemType=nil then
  39054. case ElemSize of
  39055. // optimized versions for arrays of byte,word,integer,Int64,Currency,Double
  39056. 1: for result := 0 to max do
  39057. if PByteArray(P)^[result]=byte(Elem) then exit;
  39058. 2: for result := 0 to max do
  39059. if PWordArray(P)^[result]=word(Elem) then exit;
  39060. 4: for result := 0 to max do
  39061. if PIntegerArray(P)^[result]=integer(Elem) then exit;
  39062. 8: for result := 0 to max do // Int64,Currency,Double
  39063. if PInt64Array(P)^[result]=Int64(Elem) then exit;
  39064. else // generic binary comparison (fast with our overloaded CompareMem)
  39065. for result := 0 to max do
  39066. if CompareMem(P,@Elem,ElemSize) then
  39067. exit else
  39068. inc(PtrUInt(P),ElemSize);
  39069. end else
  39070. case PTypeKind(ElemType)^ of
  39071. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  39072. for result := 0 to max do
  39073. if AnsiString(PPtrIntArray(P)^[result])=AnsiString(Elem) then exit;
  39074. tkWString:
  39075. for result := 0 to max do
  39076. if WideString(PPtrIntArray(P)^[result])=WideString(Elem) then exit;
  39077. {$ifdef HASVARUSTRING}
  39078. tkUString:
  39079. for result := 0 to max do
  39080. if UnicodeString(PPtrIntArray(P)^[result])=UnicodeString(Elem) then exit;
  39081. {$endif}
  39082. {$ifndef NOVARIANTS}
  39083. tkVariant:
  39084. for result := 0 to max do
  39085. if PVariantArray(P)^[result]=variant(Elem) then exit;
  39086. {$endif}
  39087. tkRecord{$ifdef FPC},tkObject{$endif}:
  39088. // RecordEquals() works with packed records containing binary and string types
  39089. for result := 0 to max do
  39090. if RecordEquals(P^,Elem,ElemType) then
  39091. exit else
  39092. inc(PtrUInt(P),ElemSize);
  39093. tkInterface:
  39094. for result := 0 to max do
  39095. if PPtrIntArray(P)^[result]=PtrInt(Elem) then exit;
  39096. end;
  39097. result := -1;
  39098. end;
  39099. procedure TDynArray.Init(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil);
  39100. begin
  39101. fValue := @aValue;
  39102. fTypeInfo := aTypeInfo;
  39103. if PTypeKind(aTypeInfo)^<>tkDynArray then // inlined GetTypeInfo()
  39104. raise ESynException.CreateUTF8('TDynArray.Init(%): not a dynamic array',
  39105. [PShortString(@PTypeInfo(aTypeInfo)^.NameLen)^]);
  39106. {$ifdef FPC_REQUIRES_PROPER_ALIGNMENT}
  39107. aTypeInfo := GetFPCAlignPtr(aTypeInfo);
  39108. {$else}
  39109. inc(PtrUInt(aTypeInfo),PTypeInfo(aTypeInfo)^.NameLen);
  39110. {$endif}
  39111. fElemSize := PTypeInfo(aTypeInfo)^.elSize {$ifdef FPC}and $7FFFFFFF{$endif};
  39112. fElemType := PTypeInfo(aTypeInfo)^.elType;
  39113. if fElemType<>nil then begin
  39114. {$ifndef HASDIRECTTYPEINFO}
  39115. // FPC compatibility: if you have a GPF here at startup, your 3.1 trunk
  39116. // revision seems older than June 2016
  39117. // -> enable HASDIRECTTYPEINFO conditional below $ifdef VER3_1 in Synopse.inc
  39118. // or in your project's options
  39119. fElemType := PPointer(fElemType)^;
  39120. {$endif}
  39121. {$ifdef FPC}
  39122. if not (PTypeKind(fElemType)^ in tkManagedTypes) then
  39123. fElemType := nil; // as with Delphi
  39124. {$endif}
  39125. end;
  39126. fCountP := aCountPointer;
  39127. if fCountP<>nil then
  39128. fCountP^ := 0;
  39129. fCompare := nil;
  39130. fKnownSize := 0;
  39131. fSorted := false;
  39132. fKnownType := djNone;
  39133. fIsObjArray := oaUnknown;
  39134. end;
  39135. procedure TDynArray.InitSpecific(aTypeInfo: pointer; var aValue; aKind: TDynArrayKind;
  39136. aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  39137. var Comp: TDynArraySortCompare;
  39138. begin
  39139. Init(aTypeInfo,aValue,aCountPointer);
  39140. Comp := DYNARRAY_SORTFIRSTFIELD[aCaseInsensitive,aKind];
  39141. if @Comp=nil then
  39142. raise ESynException.CreateUTF8('TDynArray.InitSpecific(%) wrong aKind=%',
  39143. [PShortString(@PTypeInfo(aTypeInfo)^.NameLen)^,ord(aKind)]);
  39144. fCompare := Comp;
  39145. fKnownType := aKind;
  39146. fKnownSize := KNOWNTYPE_SIZE[aKind];
  39147. end;
  39148. procedure TDynArray.UseExternalCount(var aCountPointer: Integer);
  39149. begin
  39150. fCountP := @aCountPointer;
  39151. end;
  39152. procedure TDynArray.Void;
  39153. begin
  39154. fValue := nil;
  39155. end;
  39156. function TDynArray.IsVoid: boolean;
  39157. begin
  39158. result := fValue=nil;
  39159. end;
  39160. procedure _DynArrayClear(var a: Pointer; typeInfo: Pointer);
  39161. {$ifdef FPC}
  39162. [external name 'FPC_DYNARRAY_CLEAR'];
  39163. {$else}
  39164. asm
  39165. {$ifdef CPU64}
  39166. .NOFRAME
  39167. {$endif}
  39168. jmp System.@DynArrayClear
  39169. end;
  39170. {$endif}
  39171. procedure _FinalizeArray(p: Pointer; typeInfo: Pointer; elemCount: PtrUInt);
  39172. {$ifdef FPC}
  39173. [external name 'FPC_FINALIZE_ARRAY'];
  39174. {$else}
  39175. asm
  39176. {$ifdef CPU64}
  39177. .NOFRAME
  39178. {$endif}
  39179. jmp System.@FinalizeArray
  39180. end;
  39181. {$endif}
  39182. function TDynArray.GetIsObjArray: boolean;
  39183. begin
  39184. if fIsObjArray=oaUnknown then
  39185. if (fElemSize=sizeof(pointer)) and (fElemType=nil) and
  39186. Assigned(DynArrayIsObjArray) and DynArrayIsObjArray(fTypeInfo) then
  39187. fIsObjArray := oaTrue else
  39188. fIsObjArray := oaFalse;
  39189. result := fIsObjArray=oaTrue;
  39190. end;
  39191. procedure TDynArray.SetIsObjArray(aValue: boolean);
  39192. begin
  39193. if aValue then
  39194. fIsObjArray := oaTrue else
  39195. fIsObjArray := oaFalse;
  39196. end;
  39197. procedure TDynArray.InternalSetLength(NewLength: PtrUInt);
  39198. var p: PDynArrayRec;
  39199. pa: PAnsiChar absolute p;
  39200. OldLength, NeededSize, minLength: PtrUInt;
  39201. pp: pointer;
  39202. i: integer;
  39203. begin // this method is faster than default System.DynArraySetLength() function
  39204. // check that new array length is not just a hidden finalize
  39205. if NewLength=0 then begin
  39206. {$ifndef NOVARIANTS} // faster clear of custom variant uniformous array
  39207. if ArrayType=TypeInfo(TVariantDynArray) then begin
  39208. VariantDynArrayClear(TVariantDynArray(fValue^));
  39209. exit;
  39210. end;
  39211. {$endif}
  39212. if GetIsObjArray then
  39213. for i := 0 to Count-1 do
  39214. PObjectArray(fValue^)^[i].Free;
  39215. _DynArrayClear(fValue^,ArrayType);
  39216. exit;
  39217. end;
  39218. // retrieve old length
  39219. p := fValue^;
  39220. if p<>nil then begin
  39221. dec(PtrUInt(p),Sizeof(TDynArrayRec)); // p^ = start of heap object
  39222. OldLength := p^.length;
  39223. end else
  39224. OldLength := 0;
  39225. // calculate the needed size of the resulting memory structure on heap
  39226. NeededSize := NewLength*ElemSize+Sizeof(TDynArrayRec);
  39227. {$ifndef CPU64}
  39228. if NeededSize>1024*1024*1024 then // max workable memory block is 1 GB
  39229. raise ERangeError.CreateFmt('TDynArray SetLength(%s,%d) size concern',
  39230. [PShortString(@PTypeInfo(ArrayType).NameLen)^,NewLength]);
  39231. {$endif}
  39232. // if not shared (refCnt=1), resize; if shared, create copy (not thread safe)
  39233. if (p=nil) or (p^.refCnt=1) then begin
  39234. if NewLength<OldLength then
  39235. if ElemType<>nil then
  39236. _FinalizeArray(pa+NeededSize,ElemType,OldLength-NewLength) else
  39237. if GetIsObjArray then
  39238. for i := NewLength to OldLength-1 do
  39239. PObjectArray(fValue^)^[i].Free;
  39240. ReallocMem(p,neededSize);
  39241. end else begin
  39242. InterlockedDecrement(PInteger(@p^.refCnt)^); // FPC has refCnt: PtrInt
  39243. GetMem(p,neededSize);
  39244. minLength := oldLength;
  39245. if minLength>newLength then
  39246. minLength := newLength;
  39247. if ElemType<>nil then begin
  39248. pp := pa+Sizeof(TDynArrayRec);
  39249. FillcharFast(pp^,minLength*elemSize,0);
  39250. CopyArray(pp,fValue^,ElemType,minLength)
  39251. end else
  39252. MoveFast(fValue^,pa[Sizeof(TDynArrayRec)],minLength*elemSize);
  39253. end;
  39254. // set refCnt=1 and new length to the heap memory structure
  39255. with p^ do begin
  39256. refCnt := 1;
  39257. {$ifdef FPC}
  39258. high := newLength-1;
  39259. {$else}
  39260. length := newLength;
  39261. {$endif}
  39262. end;
  39263. inc(PtrUInt(p),Sizeof(p^));
  39264. // reset new allocated elements content to zero
  39265. if NewLength>OldLength then begin
  39266. OldLength := OldLength*elemSize;
  39267. FillcharFast(pa[OldLength],neededSize-OldLength-Sizeof(TDynArrayRec),0);
  39268. end;
  39269. fValue^ := p;
  39270. end;
  39271. procedure TDynArray.SetCount(aCount: integer);
  39272. const MINIMUM_SIZE = 64;
  39273. var capa, delta: integer;
  39274. begin
  39275. fSorted := false;
  39276. if fValue=nil then
  39277. exit; // avoid GPF if void
  39278. if fCountP<>nil then begin
  39279. delta := aCount-fCountP^;
  39280. if delta=0 then
  39281. exit;
  39282. fCountP^ := aCount;
  39283. if PtrInt(fValue^)=0 then begin
  39284. // no capa yet
  39285. if (delta>0) and (aCount<MINIMUM_SIZE) then
  39286. aCount := MINIMUM_SIZE; // reserve some minimal space for Add()
  39287. end else begin
  39288. {$ifdef FPC}
  39289. capa := PDynArrayRec(PtrUInt(fValue^)-SizeOf(TDynArrayRec))^.length;
  39290. {$else}
  39291. capa := PInteger(PtrInt(fValue^)-sizeof(PtrInt))^;
  39292. {$endif}
  39293. if delta>0 then begin
  39294. // size-up -> grow by chunks
  39295. if capa>=fCountP^ then
  39296. exit; // no need to grow
  39297. inc(capa,capa shr 2);
  39298. if capa<fCountP^ then
  39299. aCount := fCountP^ else
  39300. aCount := capa;
  39301. end else
  39302. if aCount>0 then // aCount=0 should release memory (e.g. TDynArray.Clear)
  39303. // size-down -> only if worth it (for faster Delete)
  39304. if (capa<=MINIMUM_SIZE) or (capa-aCount<capa shr 3) then
  39305. exit;
  39306. end;
  39307. end;
  39308. // no external Count, array size-down or array up-grow -> realloc
  39309. InternalSetLength(aCount);
  39310. end;
  39311. function TDynArray.GetCapacity: integer;
  39312. begin // capacity := length(DynArray)
  39313. if (fValue<>nil) and (PtrInt(fValue^)<>0) then
  39314. result := PDynArrayRec(PtrUInt(fValue^)-SizeOf(TDynArrayRec))^.length else
  39315. result := 0;
  39316. end;
  39317. procedure TDynArray.SetCapacity(aCapacity: integer);
  39318. begin
  39319. if fValue=nil then
  39320. exit; // avoid GPF if void
  39321. if fCountP<>nil then
  39322. if fCountP^>aCapacity then
  39323. fCountP^ := aCapacity;
  39324. InternalSetLength(aCapacity);
  39325. end;
  39326. procedure TDynArray.SetCompare(const aCompare: TDynArraySortCompare);
  39327. begin
  39328. if @aCompare<>@fCompare then begin
  39329. @fCompare := @aCompare;
  39330. fSorted := false;
  39331. end;
  39332. end;
  39333. procedure TDynArray.Slice(var Dest; aCount: Cardinal; aFirstIndex: cardinal=0);
  39334. var n: Cardinal;
  39335. D: PPointer;
  39336. P: PAnsiChar;
  39337. begin
  39338. if fValue=nil then
  39339. exit; // avoid GPF if void
  39340. n := Count;
  39341. if aFirstIndex>=n then
  39342. aCount := 0 else
  39343. if aCount>=n-aFirstIndex then
  39344. aCount := n-aFirstIndex;
  39345. DynArray(ArrayType,Dest).InternalSetLength(aCount);
  39346. D := @Dest;
  39347. if aCount>0 then begin
  39348. P := PAnsiChar(fValue^)+aFirstIndex*ElemSize;
  39349. if ElemType=nil then
  39350. MoveFast(P^,D^^,aCount*ElemSize) else
  39351. CopyArray(D^,P,ElemType,aCount);
  39352. end;
  39353. end;
  39354. procedure TDynArray.AddArray(const DynArrayVar; aStartIndex,aCount: integer);
  39355. var DynArrayCount, n: integer;
  39356. PS,PD: pointer;
  39357. begin
  39358. if fValue=nil then
  39359. exit; // avoid GPF if void
  39360. DynArrayCount := DynArrayLength(pointer(DynArrayVar));
  39361. if aStartIndex>=DynArrayCount then
  39362. exit; // nothing to copy
  39363. if (aCount<0) or (cardinal(aStartIndex+aCount)>cardinal(DynArrayCount)) then
  39364. aCount := DynArrayCount-aStartIndex;
  39365. if aCount<=0 then
  39366. exit;
  39367. n := Count;
  39368. SetCount(n+aCount);
  39369. PS := pointer(PtrUInt(DynArrayVar)+cardinal(aStartIndex)*ElemSize);
  39370. PD := pointer(PtrUInt(fValue^)+cardinal(n)*ElemSize);
  39371. if ElemType=nil then
  39372. MoveFast(PS^,PD^,cardinal(aCount)*ElemSize) else
  39373. CopyArray(PD,PS,ElemType,aCount);
  39374. end;
  39375. {$ifndef DELPHI5OROLDER} // don't know why Delphi 5 does not like this signature
  39376. procedure TDynArray.AddDynArray(const aSource: TDynArray; aStartIndex,aCount: integer);
  39377. var SourceCount: integer;
  39378. begin
  39379. if (aSource.fValue<>nil) and (ArrayType=aSource.ArrayType) then begin
  39380. SourceCount := aSource.Count;
  39381. if (aCount<0) or (aCount>SourceCount) then
  39382. aCount := SourceCount; // force use of external Source.Count, if any
  39383. AddArray(aSource.fValue^,aStartIndex,aCount);
  39384. end;
  39385. end;
  39386. {$endif DELPHI5OROLDER}
  39387. procedure TDynArray.ElemClear(var Elem);
  39388. begin
  39389. if ElemType<>nil then
  39390. case PTypeKind(ElemType)^ of // release reference counted
  39391. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  39392. RawByteString(Elem) := '';
  39393. tkWString:
  39394. WideString(Elem) := '';
  39395. tkInterface:
  39396. IUnknown(Elem) := nil;
  39397. {$ifdef HASVARUSTRING}
  39398. tkUString:
  39399. UnicodeString(Elem) := '';
  39400. {$endif}
  39401. tkRecord{$ifdef FPC},tkObject{$endif}:
  39402. RecordClear(Elem,ElemType);
  39403. tkDynArray:
  39404. _DynArrayClear(pointer(Elem),ElemType);
  39405. {$ifndef NOVARIANTS}
  39406. tkVariant:
  39407. VarClear(Variant(Elem));
  39408. {$endif}
  39409. else exit;
  39410. end;
  39411. FillcharFast(Elem,ElemSize,0); // always fill with zero binary content
  39412. end;
  39413. procedure TDynArray.ElemCopy(const A; var B);
  39414. begin
  39415. if ElemType=nil then begin
  39416. MoveFast(A,B,ElemSize);
  39417. exit;
  39418. end else begin
  39419. case PTypeKind(ElemType)^ of
  39420. tkLString{$ifdef FPC},tkLStringOld{$endif}: begin
  39421. RawByteString(B) := RawByteString(A);
  39422. exit;
  39423. end;
  39424. tkWString: begin
  39425. WideString(B) := WideString(A);
  39426. exit;
  39427. end;
  39428. tkInterface: begin
  39429. IUnknown(B) := IUnknown(A);
  39430. exit;
  39431. end;
  39432. {$ifdef HASVARUSTRING}
  39433. tkUString: begin
  39434. UnicodeString(B) := UnicodeString(A);
  39435. exit;
  39436. end;
  39437. {$endif}
  39438. tkRecord{$ifdef FPC},tkObject{$endif}: begin
  39439. RecordCopy(B,A,ElemType);
  39440. exit;
  39441. end;
  39442. {$ifndef NOVARIANTS}
  39443. tkVariant: begin
  39444. variant(B) := variant(A);
  39445. exit;
  39446. end;
  39447. {$endif}
  39448. else begin
  39449. {$ifdef FPC}
  39450. RecordClear(B,ElemType); // inlined CopyArray()
  39451. MoveFast(A,B,RTTIManagedSize(ElemType));
  39452. RecordAddRef(B,ElemType);
  39453. {$else}
  39454. CopyArray(@B,@A,ElemType,1);
  39455. {$endif}
  39456. exit;
  39457. end;
  39458. end;
  39459. end;
  39460. end;
  39461. function TDynArray.ElemLoad(Source: PAnsiChar): RawByteString;
  39462. begin
  39463. if (Source<>nil) and (ElemType=nil) then
  39464. SetString(result,Source,ElemSize) else begin
  39465. SetString(result,nil,ElemSize);
  39466. FillcharFast(pointer(result)^,ElemSize,0);
  39467. ElemLoad(Source,pointer(result)^);
  39468. end;
  39469. end;
  39470. procedure TDynArray.ElemLoad(Source: PAnsiChar; var Elem);
  39471. begin
  39472. if Source=nil then
  39473. exit; // avoid GPF
  39474. if ElemType=nil then
  39475. MoveFast(Source^,Elem,ElemSize) else
  39476. case PTypeKind(ElemType)^ of
  39477. tkLString{$ifdef FPC},tkLStringOld{$endif}: begin
  39478. SetString(RawByteString(Elem),Source+4,PInteger(Source)^);
  39479. {$ifdef HASCODEPAGE}
  39480. { Delphi 2009+: set Code page for this AnsiString }
  39481. if PPtrUInt(@Elem)^<>0 then
  39482. SetCodePage(RawByteString(Elem),PWord(PtrUInt(ElemType)+
  39483. PTypeInfo(ElemType)^.NameLen+2)^,false);
  39484. {$endif}
  39485. end;
  39486. tkWString: // WideString internal length is in bytes
  39487. SetString(WideString(Elem),PWideChar(Source+4),PInteger(Source)^ shr 1);
  39488. {$ifdef HASVARUSTRING}
  39489. tkUString:
  39490. SetString(UnicodeString(Elem),PWideChar(Source+4),PInteger(Source)^);
  39491. {$endif}
  39492. {$ifndef NOVARIANTS}
  39493. tkVariant:
  39494. VariantLoad(variant(Elem),Source,@JSON_OPTIONS[true]);
  39495. {$endif}
  39496. tkRecord{$ifdef FPC},tkObject{$endif}:
  39497. RecordLoad(Elem,Source,ElemType);
  39498. end;
  39499. end;
  39500. procedure TDynArray.ElemLoadClear(var ElemLoaded: RawByteString);
  39501. begin
  39502. if (ElemType<>nil) and (length(ElemLoaded)=integer(ElemSize)) then
  39503. case PTypeKind(ElemType)^ of
  39504. tkLString{$ifdef FPC},tkLStringOld{$endif}:
  39505. PRawByteString(pointer(ElemLoaded))^ := '';
  39506. tkWString:
  39507. PWideString(pointer(ElemLoaded))^ := '';
  39508. {$ifdef HASVARUSTRING}
  39509. tkUString:
  39510. PUnicodeString(pointer(ElemLoaded))^ := '';
  39511. {$endif}
  39512. {$ifndef NOVARIANTS}
  39513. tkVariant:
  39514. VarClear(PVariant(pointer(ElemLoaded))^);
  39515. {$endif}
  39516. tkRecord{$ifdef FPC},tkObject{$endif}:
  39517. RecordClear(pointer(ElemLoaded)^,ElemType);
  39518. end;
  39519. ElemLoaded := '';
  39520. end;
  39521. function TDynArray.ElemSave(const Elem): RawByteString;
  39522. {$ifdef FPC}
  39523. var LenBytes: integer;
  39524. {$endif}
  39525. begin
  39526. if ElemType=nil then
  39527. SetString(result,PAnsiChar(@Elem),ElemSize) else
  39528. case PTypeKind(ElemType)^ of
  39529. {$ifdef FPC}
  39530. tkLString, tkWString, tkLStringOld:
  39531. if PPtrInt(@Elem)^=0 then
  39532. SetString(result,PAnsiChar(@Elem),4) else begin
  39533. LenBytes := PStrRec(Pointer(PPtrInt(@Elem)^-STRRECSIZE))^.length;
  39534. SetString(result,PAnsiChar(PPtrInt(@Elem)^-sizeof(integer)),LenBytes+sizeof(integer));
  39535. PInteger(result)^ := LenBytes;
  39536. end;
  39537. {$ifdef HASVARUSTRING}
  39538. tkUString:
  39539. if PPtrInt(@Elem)^=0 then
  39540. SetString(result,PAnsiChar(@Elem),4) else begin
  39541. LenBytes := PStrRec(Pointer(PPtrInt(@Elem)^-STRRECSIZE))^.length;
  39542. SetString(result,PAnsiChar(PPtrInt(@Elem)^-sizeof(integer)),LenBytes*2+sizeof(integer));
  39543. PInteger(result)^ := LenBytes;
  39544. end;
  39545. {$endif}
  39546. {$else FPC}
  39547. tkLString, tkWString: // WideString internal length is in bytes
  39548. if PPtrInt(@Elem)^=0 then
  39549. SetString(result,PAnsiChar(@Elem),4) else
  39550. SetString(result,PAnsiChar(PPtrInt(@Elem)^-sizeof(integer)),
  39551. PInteger(PPtrInt(@Elem)^-sizeof(integer))^+sizeof(integer));
  39552. {$ifdef HASVARUSTRING}
  39553. tkUString:
  39554. if PPtrInt(@Elem)^=0 then
  39555. SetString(result,PAnsiChar(@Elem),4) else
  39556. SetString(result,PAnsiChar(PPtrInt(@Elem)^-sizeof(integer)),
  39557. PInteger(PPtrInt(@Elem)^-sizeof(integer))^*2+sizeof(integer));
  39558. {$endif}
  39559. {$endif FPC}
  39560. {$ifndef NOVARIANTS}
  39561. tkVariant:
  39562. result := VariantSave(variant(Elem));
  39563. {$endif}
  39564. tkRecord{$ifdef FPC},tkObject{$endif}:
  39565. result := RecordSave(Elem,ElemType);
  39566. else result := '';
  39567. end;
  39568. end;
  39569. function TDynArray.ElemLoadFind(Source: PAnsiChar): integer;
  39570. var tmp: RawByteString;
  39571. begin
  39572. tmp := ElemLoad(Source);
  39573. if tmp='' then
  39574. result := -1 else
  39575. try
  39576. if @fCompare=nil then
  39577. result := IndexOf(pointer(tmp)^) else
  39578. result := Find(pointer(tmp)^);
  39579. finally
  39580. ElemLoadClear(tmp);
  39581. end;
  39582. end;
  39583. function DynArray(aTypeInfo: pointer; var aValue; aCountPointer: PInteger=nil): TDynArray;
  39584. begin
  39585. result.Init(aTypeInfo,aValue,aCountPointer);
  39586. end;
  39587. { TDynArrayHashed }
  39588. const
  39589. // marks a void entry in the hash table
  39590. // -> code below will replace all hash value from 0 (HASH_VOID)
  39591. // to 1 (HASH_ONVOIDCOLISION)
  39592. HASH_VOID = 0;
  39593. // marks a hash colision with a void entry in the hash table
  39594. HASH_ONVOIDCOLISION = 1;
  39595. {$ifdef UNDIRECTDYNARRAY}
  39596. function TDynArrayHashed.Count: Integer;
  39597. begin
  39598. result := InternalDynArray.Count;
  39599. end;
  39600. procedure TDynArrayHashed.SetCount(aCount: Integer);
  39601. begin
  39602. InternalDynArray.Count := aCount;
  39603. end;
  39604. function TDynArrayHashed.GetCapacity: Integer;
  39605. begin
  39606. result := InternalDynArray.Capacity;
  39607. end;
  39608. procedure TDynArrayHashed.SetCapacity(aCapacity: Integer);
  39609. begin
  39610. InternalDynArray.SetCapacity(aCapacity);
  39611. end;
  39612. function TDynArrayHashed.fValue: PPointer;
  39613. begin
  39614. result := InternalDynArray.fValue;
  39615. end;
  39616. function TDynArrayHashed.ElemSize: PtrUInt;
  39617. begin
  39618. result := InternalDynArray.ElemSize;
  39619. end;
  39620. function TDynArrayHashed.ElemType: Pointer;
  39621. begin
  39622. result := InternalDynArray.ElemType;
  39623. end;
  39624. procedure TDynArrayHashed.ElemCopy(const A; var B);
  39625. begin
  39626. InternalDynArray.ElemCopy(A,B);
  39627. end;
  39628. function TDynArrayHashed.KnownType: TDynArrayKind;
  39629. begin
  39630. result := InternalDynArray.KnownType;
  39631. end;
  39632. procedure TDynArrayHashed.Clear;
  39633. begin
  39634. InternalDynArray.Clear;
  39635. end;
  39636. function TDynArrayHashed.Add(const Elem): integer;
  39637. begin
  39638. result := InternalDynArray.Add(Elem);
  39639. end;
  39640. procedure TDynArrayHashed.Delete(aIndex: Integer);
  39641. begin
  39642. InternalDynArray.Delete(aIndex);
  39643. end;
  39644. function TDynArrayHashed.SaveTo: RawByteString;
  39645. begin
  39646. result := InternalDynArray.SaveTo;
  39647. end;
  39648. function TDynArrayHashed.LoadFrom(Source: PAnsiChar): PAnsiChar;
  39649. begin
  39650. result := InternalDynArray.LoadFrom(Source);
  39651. end;
  39652. function TDynArrayHashed.SaveTo(Dest: PAnsiChar): PAnsiChar;
  39653. begin
  39654. result := InternalDynArray.SaveTo(Dest);
  39655. end;
  39656. function TDynArrayHashed.SaveToJSON(EnumSetsAsText: boolean=false): RawUTF8;
  39657. begin
  39658. result := InternalDynArray.SaveToJSON(EnumSetsAsText);
  39659. end;
  39660. function TDynArrayHashed.LoadFromJSON(P: PUTF8Char; aEndOfObject: PUTF8Char=nil): PUTF8Char;
  39661. begin
  39662. result := InternalDynArray.LoadFromJSON(P,aEndOfObject);
  39663. end;
  39664. function TDynArrayHashed.SaveToLength: integer;
  39665. begin
  39666. result := InternalDynArray.SaveToLength;
  39667. end;
  39668. {$endif UNDIRECTDYNARRAY}
  39669. function TDynArrayHashed.Scan(const Elem): integer;
  39670. var P: PAnsiChar;
  39671. begin
  39672. P := fValue^; // Count<fHashCountTrigger -> O(n) is faster than O(1)
  39673. if Assigned(fEventCompare) then begin
  39674. for result := 0 to Count-1 do
  39675. if fEventCompare(P^,Elem)=0 then
  39676. exit else
  39677. inc(P,ElemSize);
  39678. end else
  39679. for result := 0 to Count-1 do
  39680. if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare(P^,Elem)=0 then
  39681. exit else
  39682. inc(P,ElemSize);
  39683. result := -1;
  39684. end;
  39685. function TDynArrayHashed.FindHashed(const Elem): integer;
  39686. begin
  39687. if (fHashs<>nil) and Assigned(fHashElement) then begin
  39688. result := HashFind(fHashElement(Elem,fHasher),Elem);
  39689. if result<0 then
  39690. result := -1; // for coherency with most methods
  39691. end else begin
  39692. result := Scan(Elem); // Count<fHashCountTrigger
  39693. if (result>=0) and (fHashCountTrigger>0) then begin
  39694. inc(fHashFindCount);
  39695. if fHashFindCount>=fHashCountTrigger then begin
  39696. fHashCountTrigger := 0; // FindHashed() should use O(1) hash
  39697. ReHash;
  39698. end;
  39699. end;
  39700. end;
  39701. end;
  39702. procedure TDynArrayHashed.HashAdd(const Elem; aHashCode: Cardinal; var result: integer);
  39703. var n,cap: integer;
  39704. begin
  39705. n := Count;
  39706. SetCount(n+1); // reserve space for a void element in array
  39707. cap := Capacity;
  39708. if cap*2-cap shr 3>=fHashsCount then begin
  39709. // fHashs[] is too small -> recreate
  39710. ReHash;
  39711. result := HashFind(aHashCode,Elem); // fHashs[] has changed -> recompute
  39712. assert(result<0);
  39713. end;
  39714. with fHashs[-result-1] do begin // HashFind returned negative index in fHashs[]
  39715. Hash := aHashCode;
  39716. Index := n;
  39717. end;
  39718. result := n;
  39719. end;
  39720. function TDynArrayHashed.FindHashedForAdding(const Elem; out wasAdded: boolean;
  39721. aHashCode: cardinal): integer;
  39722. var n: integer;
  39723. begin
  39724. n := Count;
  39725. if n<fHashCountTrigger then begin
  39726. result := Scan(Elem);
  39727. if result<0 then begin
  39728. SetCount(n+1); // like HashAdd(): reserve space for added item
  39729. result := n;
  39730. wasadded := true;
  39731. end else
  39732. wasadded := false;
  39733. exit;
  39734. end;
  39735. if fHashs=nil then
  39736. ReHash; // compute hash of all previously added fHashCountTrigger items
  39737. if aHashCode=0 then
  39738. if Assigned(fHashElement) then
  39739. aHashCode := fHashElement(Elem,fHasher);
  39740. if aHashCode=HASH_VOID then
  39741. aHashCode := HASH_ONVOIDCOLISION; // as in HashFind() -> for HashAdd() below
  39742. result := HashFind(aHashCode,Elem);
  39743. if result>=0 then
  39744. // found matching existing item
  39745. wasAdded := false else begin
  39746. // create a void element
  39747. HashAdd(Elem,aHashCode,result);
  39748. wasAdded := true;
  39749. end;
  39750. end;
  39751. function TDynArrayHashed.AddAndMakeUniqueName(aName: RawUTF8): pointer;
  39752. var ndx,j: integer;
  39753. added: boolean;
  39754. aName_: RawUTF8;
  39755. begin
  39756. if aName='' then
  39757. aName := '_';
  39758. ndx := FindHashedForAdding(aName,added);
  39759. if not added then begin // force unique column name
  39760. aName_ := aName+'_';
  39761. j := 1;
  39762. repeat
  39763. aName := aName_+UInt32ToUTF8(j);
  39764. ndx := FindHashedForAdding(aName,added);
  39765. inc(j);
  39766. until added;
  39767. end;
  39768. assert(ndx=Count-1);
  39769. result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
  39770. PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  39771. end;
  39772. function TDynArrayHashed.AddUniqueName(const aName: RawUTF8;
  39773. const ExceptionMsg: RawUTF8; const ExceptionArgs: array of const): pointer;
  39774. var ndx: integer;
  39775. added: boolean;
  39776. begin
  39777. ndx := FindHashedForAdding(aName,added);
  39778. if added then begin
  39779. assert(ndx=Count-1);
  39780. result := PAnsiChar(fValue^)+cardinal(ndx)*ElemSize;
  39781. PRawUTF8(result)^ := aName; // store unique name at 1st elem position
  39782. end else
  39783. if ExceptionMsg='' then
  39784. raise ESynException.CreateUTF8('Duplicated "%" name',[aName]) else
  39785. raise ESynException.CreateUTF8(ExceptionMsg,ExceptionArgs);
  39786. end;
  39787. function TDynArrayHashed.FindHashedAndFill(var ElemToFill): integer;
  39788. begin
  39789. if fHashs=nil then // Count<fHashCountTrigger
  39790. result := Scan(ElemToFill) else
  39791. if Assigned(fHashElement) then begin
  39792. result := HashFind(fHashElement(ElemToFill,fHasher),ElemToFill);
  39793. if result<0 then
  39794. result := -1;
  39795. end else
  39796. result := -1;
  39797. if result>=0 then
  39798. ElemCopy((PAnsiChar(fValue^)+cardinal(result)*ElemSize)^,ElemToFill);
  39799. end;
  39800. function TDynArrayHashed.FindHashedAndUpdate(const Elem; AddIfNotExisting: boolean): integer;
  39801. var aHashCode: cardinal;
  39802. label h;
  39803. begin
  39804. if fHashs=nil then begin // Count<fHashCountTrigger
  39805. result := Scan(Elem);
  39806. if result<0 then
  39807. if AddIfNotExisting then
  39808. if Count<fHashCountTrigger then
  39809. result := Add(Elem) else begin
  39810. ReHash; // compute hash of all previously added fHashCountTrigger items
  39811. goto h;
  39812. end else
  39813. result := -1 else
  39814. ElemCopy(Elem,(PAnsiChar(fValue^)+cardinal(result)*ElemSize)^); // update
  39815. exit;
  39816. end;
  39817. h:if Assigned(fHashElement) then begin
  39818. aHashCode := fHashElement(Elem,fHasher);
  39819. if aHashCode=HASH_VOID then
  39820. aHashCode := HASH_ONVOIDCOLISION; // as in HashFind() -> for HashAdd() below
  39821. result := HashFind(aHashCode,Elem);
  39822. if result<0 then
  39823. if AddIfNotExisting then begin
  39824. // not existing -> add as new element
  39825. HashAdd(Elem,aHashCode,result); // ReHash only if necessary
  39826. ElemCopy(Elem,(PAnsiChar(fValue^)+cardinal(result)*ElemSize)^);
  39827. end else
  39828. result := -1 else begin
  39829. // copy from Elem into dynamic array found entry = Update
  39830. ElemCopy(Elem,(PAnsiChar(fValue^)+cardinal(result)*ElemSize)^);
  39831. ReHash; // whole hash table should be re-created for next search
  39832. end;
  39833. end else
  39834. result := -1;
  39835. end;
  39836. function TDynArrayHashed.FindHashedAndDelete(const Elem): integer;
  39837. begin
  39838. if fHashs=nil then begin // Count<fHashCountTrigger
  39839. result := Scan(Elem);
  39840. if result>=0 then
  39841. Delete(result);
  39842. end else
  39843. if Assigned(fHashElement) then begin
  39844. result := HashFind(fHashElement(Elem,fHasher),Elem);
  39845. if result<0 then
  39846. result := -1 else begin
  39847. Delete(result);
  39848. ReHash; // whole hash table should be re-created for next search
  39849. end;
  39850. end else
  39851. result := -1;
  39852. end;
  39853. function HashAnsiString(const Elem; Hasher: THasher): cardinal;
  39854. begin
  39855. if PtrUInt(Elem)<>0 then
  39856. result := Hasher(0,Pointer(PtrUInt(Elem)),
  39857. {$ifdef FPC}PStrRec(Pointer(PtrUInt(Elem)-STRRECSIZE))^.length
  39858. {$else}PInteger(PtrUInt(Elem)-sizeof(integer))^{$endif}) else
  39859. result := HASH_ONVOIDCOLISION;
  39860. end;
  39861. function HashAnsiStringI(const Elem; Hasher: THasher): cardinal;
  39862. var tmp: array[byte] of AnsiChar; // avoid slow heap allocation
  39863. begin
  39864. if PtrUInt(Elem)=0 then
  39865. result := HASH_ONVOIDCOLISION else
  39866. result := Hasher(0,tmp,UpperCopy255Buf(tmp,pointer(Elem),
  39867. {$ifdef FPC}PStrRec(Pointer(PtrUInt(Elem)-STRRECSIZE))^.length
  39868. {$else}PInteger(PtrUInt(Elem)-sizeof(integer))^{$endif})-tmp);
  39869. end;
  39870. {$ifdef UNICODE}
  39871. function HashUnicodeString(const Elem; Hasher: THasher): cardinal;
  39872. begin
  39873. if PtrUInt(Elem)=0 then
  39874. result := HASH_ONVOIDCOLISION else
  39875. result := Hasher(0,Pointer(Elem),length(UnicodeString(Elem))*2);
  39876. end;
  39877. function HashUnicodeStringI(const Elem; Hasher: THasher): cardinal;
  39878. var tmp: array[byte] of AnsiChar; // avoid slow heap allocation
  39879. begin
  39880. if PtrUInt(Elem)=0 then
  39881. result := HASH_ONVOIDCOLISION else
  39882. result := Hasher(0,tmp,UpperCopy255W(tmp,Pointer(Elem),length(UnicodeString(Elem)))-tmp);
  39883. end;
  39884. {$endif UNICODE}
  39885. function HashSynUnicode(const Elem; Hasher: THasher): cardinal;
  39886. begin
  39887. if PtrUInt(Elem)=0 then
  39888. result := HASH_ONVOIDCOLISION else
  39889. result := Hasher(0,Pointer(Elem),length(SynUnicode(Elem))*2);
  39890. end;
  39891. function HashSynUnicodeI(const Elem; Hasher: THasher): cardinal;
  39892. var tmp: array[byte] of AnsiChar; // avoid slow heap allocation
  39893. begin
  39894. if PtrUInt(Elem)=0 then
  39895. result := HASH_ONVOIDCOLISION else
  39896. result := Hasher(0,tmp,UpperCopy255W(tmp,SynUnicode(Elem))-tmp);
  39897. end;
  39898. function HashWideString(const Elem; Hasher: THasher): cardinal;
  39899. begin // WideString internal size is in bytes, not WideChar
  39900. if PtrUInt(Elem)=0 then
  39901. result := HASH_ONVOIDCOLISION else
  39902. result := Hasher(0,Pointer(Elem),Length(WideString(Elem))*2);
  39903. end;
  39904. function HashWideStringI(const Elem; Hasher: THasher): cardinal;
  39905. var tmp: array[byte] of AnsiChar; // avoid slow heap allocation
  39906. begin
  39907. if PtrUInt(Elem)=0 then
  39908. result := HASH_ONVOIDCOLISION else
  39909. result := Hasher(0,tmp,UpperCopy255W(tmp,pointer(Elem),Length(WideString(Elem)))-tmp);
  39910. end;
  39911. function HashPtrUInt(const Elem; Hasher: THasher): cardinal;
  39912. begin
  39913. {$ifdef CPU64}
  39914. result := Hasher(0,@Elem,sizeof(PtrUInt));
  39915. {$else}
  39916. result := (PtrUInt(Elem) shr 4)+1; // naive but optimal for TDynArrayHashed
  39917. {$endif}
  39918. end;
  39919. function HashPointer(const Elem; Hasher: THasher): cardinal;
  39920. begin
  39921. result := Hasher(0,@Elem,sizeof(pointer));
  39922. end;
  39923. function HashByte(const Elem; Hasher: THasher): cardinal;
  39924. begin
  39925. result := Byte(Elem);
  39926. end;
  39927. function HashWord(const Elem; Hasher: THasher): cardinal;
  39928. begin
  39929. result := Word(Elem);
  39930. end;
  39931. function HashInteger(const Elem; Hasher: THasher): cardinal;
  39932. begin
  39933. result := Integer(Elem);
  39934. end;
  39935. function HashCardinal(const Elem; Hasher: THasher): cardinal;
  39936. begin
  39937. result := Cardinal(Elem);
  39938. end;
  39939. function HashInt64(const Elem; Hasher: THasher): cardinal;
  39940. begin
  39941. result := Hasher(0,@Elem,sizeof(Int64)); // better than Int64Rec.(Lo xor Hi)
  39942. end;
  39943. {$ifndef NOVARIANTS}
  39944. function HashVariant(const Elem; Hasher: THasher): cardinal;
  39945. var U: RawUTF8;
  39946. wasString: boolean;
  39947. begin
  39948. VariantToUTF8(variant(Elem),U,wasString);
  39949. if PtrUInt(U)=0 then
  39950. result := HASH_ONVOIDCOLISION else
  39951. result := Hasher(0,Pointer(PtrUInt(U)),
  39952. {$ifdef FPC}PStrRec(Pointer(PtrUInt(U)-STRRECSIZE))^.length
  39953. {$else}PInteger(PtrUInt(U)-sizeof(integer))^{$endif});
  39954. end;
  39955. function HashVariantI(const Elem; Hasher: THasher): cardinal;
  39956. var U: RawUTF8;
  39957. wasString: boolean;
  39958. begin
  39959. VariantToUTF8(variant(Elem),U,wasString);
  39960. if pointer(U)=nil then
  39961. result := HASH_ONVOIDCOLISION else
  39962. result := Hasher(0,pointer(U),UpperCopy(pointer(U),U)-pointer(U));
  39963. end;
  39964. {$endif NOVARIANTS}
  39965. procedure TDynArrayHashed.InitSpecific(aTypeInfo: pointer; var aValue;
  39966. aKind: TDynArrayKind; aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  39967. var Comp: TDynArraySortCompare;
  39968. Hasher: TDynArrayHashOne;
  39969. begin
  39970. Comp := DYNARRAY_SORTFIRSTFIELD[aCaseInsensitive,aKind];
  39971. Hasher := DYNARRAY_HASHFIRSTFIELD[aCaseInsensitive,aKind];
  39972. if (@Hasher=nil) or (@Comp=nil) then
  39973. raise ESynException.Create('TDynArrayHashed.InitSpecific wrong aKind');
  39974. Init(aTypeInfo,aValue,Hasher,Comp,nil,aCountPointer,aCaseInsensitive);
  39975. {$ifdef UNDIRECTDYNARRAY}with InternalDynArray do{$endif} begin
  39976. fKnownType := aKind;
  39977. fKnownSize := KNOWNTYPE_SIZE[aKind];
  39978. end;
  39979. end;
  39980. procedure TDynArrayHashed.Init(aTypeInfo: pointer; var aValue;
  39981. aHashElement: TDynArrayHashOne=nil; aCompare: TDynArraySortCompare=nil;
  39982. aHasher: THasher=nil; aCountPointer: PInteger=nil; aCaseInsensitive: boolean=false);
  39983. var aKind: TDynArrayKind;
  39984. begin
  39985. {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$else}inherited{$endif}
  39986. Init(aTypeInfo,aValue,aCountPointer);
  39987. fEventCompare := nil;
  39988. if @aHasher=nil then
  39989. fHasher := DefaultHasher else
  39990. fHasher := aHasher;
  39991. if (@aHashElement=nil) or (@aCompare=nil) then begin
  39992. // it's faster to retrieve now the hashing/compare function than in HashOne
  39993. aKind := {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}ToKnownType;
  39994. if @aHashElement=nil then
  39995. aHashElement := DYNARRAY_HASHFIRSTFIELD[aCaseInsensitive,aKind];
  39996. if @aCompare=nil then
  39997. aCompare := DYNARRAY_SORTFIRSTFIELD[aCaseInsensitive,aKind];
  39998. end;
  39999. fHashElement := aHashElement;
  40000. {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := aCompare;
  40001. fHashs := nil;
  40002. fHashFindCount := 0;
  40003. fHashCountTrigger := 32;
  40004. end;
  40005. //var TDynArrayHashedCollisionCount: cardinal;
  40006. function TDynArrayHashed.HashFind(aHashCode: cardinal): integer;
  40007. var first,last: integer;
  40008. h: cardinal;
  40009. P: PAnsiChar;
  40010. begin
  40011. if fHashs=nil then begin // Count=0 or Count<fHashCountTrigger
  40012. if Assigned(fHashElement) then begin
  40013. P := fValue^;
  40014. for result := 0 to Count-1 do begin
  40015. h := fHashElement(P^,fHasher);
  40016. if h=HASH_VOID then
  40017. h := HASH_ONVOIDCOLISION;
  40018. if h=aHashCode then
  40019. exit else
  40020. inc(P,ElemSize);
  40021. end;
  40022. end;
  40023. result := -1;
  40024. exit;
  40025. end;
  40026. if aHashCode=HASH_VOID then
  40027. aHashCode := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  40028. result := (aHashCode-1) and (fHashsCount-1); // fHashs[] has a power of 2 length
  40029. last := fHashsCount;
  40030. first := result;
  40031. repeat
  40032. with fHashs[result] do
  40033. if Hash=aHashCode then begin
  40034. result := Index;
  40035. exit;
  40036. end else
  40037. if Hash=HASH_VOID then
  40038. break; // not found
  40039. inc(result);
  40040. if result=last then
  40041. // reached the end -> search once from fHash[0] to fHash[first-1]
  40042. if result=first then
  40043. break else begin
  40044. result := 0;
  40045. last := first;
  40046. end;
  40047. until false;
  40048. result := -1;
  40049. end;
  40050. function TDynArrayHashed.HashFind(aHashCode: cardinal; const Elem): integer;
  40051. var first,last: integer;
  40052. P: PAnsiChar;
  40053. begin
  40054. if fHashs=nil then begin // e.g. Count<fHashCountTrigger
  40055. result := Scan(Elem);
  40056. exit;
  40057. end;
  40058. if aHashCode=HASH_VOID then
  40059. aHashCode := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  40060. result := (aHashCode-1) and (fHashsCount-1); // fHashs[] has a power of 2 length
  40061. last := fHashsCount;
  40062. first := result;
  40063. repeat
  40064. with fHashs[result] do
  40065. if Hash=aHashCode then begin
  40066. P := PAnsiChar(fValue^)+Index*ElemSize;
  40067. if not Assigned(fEventCompare) then
  40068. if @{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare<>nil then begin
  40069. if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare(P^,Elem)=0 then begin
  40070. result := Index;
  40071. exit; // found -> returns index in dynamic array
  40072. end;
  40073. end else begin
  40074. if {$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}ElemEquals(P^,Elem) then begin
  40075. result := Index;
  40076. exit; // found -> returns index in dynamic array
  40077. end;
  40078. end else
  40079. if fEventCompare(P^,Elem)=0 then begin
  40080. result := Index;
  40081. exit; // found -> returns index in dynamic array
  40082. end;
  40083. end else
  40084. if Hash=HASH_VOID then begin
  40085. result := -(result+1);
  40086. exit; // not found -> returns void index in fHashs[] as negative
  40087. end;
  40088. // fHashs[Hash mod fHashsCount].Hash collision -> search next item
  40089. {$ifdef DYNARRAYHASHCOLLISIONCOUNT}
  40090. inc(fHashFindCollisions);
  40091. {$endif}
  40092. //inc(TDynArrayHashedCollisionCount);
  40093. inc(result);
  40094. if result=last then
  40095. // reached the end -> search once from fHash[0] to fHash[first-1]
  40096. if result=first then
  40097. break else begin
  40098. result := 0;
  40099. last := first;
  40100. end;
  40101. until false;
  40102. raise ESynException.Create('HashFind fatal collision'); // should never be here
  40103. end;
  40104. function TDynArrayHashed.GetHashFromIndex(aIndex: Integer): Cardinal;
  40105. var P: pointer;
  40106. begin
  40107. if (cardinal(aIndex)>=cardinal(Count)) or not Assigned(fHashElement) then
  40108. result := 0 else begin
  40109. // it's faster to rehash than to loop in fHashs[].Index values
  40110. // and it will also work with Count<fHashCountTrigger
  40111. P := PAnsiChar(fValue^)+cardinal(aIndex)*ElemSize;
  40112. result := fHashElement(P^,fHasher);
  40113. if result=HASH_VOID then
  40114. result := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  40115. end;
  40116. end;
  40117. function TDynArrayHashed.IsHashElementWithoutCollision: integer;
  40118. var i,j: integer;
  40119. h: cardinal;
  40120. begin
  40121. if Count>0 then begin
  40122. ReHash;
  40123. for i := 0 to fHashsCount-1 do begin
  40124. h := fHashs[i].Hash;
  40125. if h=HASH_VOID then
  40126. continue;
  40127. result := fHashs[i].Index;
  40128. for j := i+1 to fHashsCount-1 do
  40129. if fHashs[j].Hash=h then
  40130. exit; // found duplicate
  40131. end;
  40132. end;
  40133. result := -1;
  40134. end;
  40135. function TDynArrayHashed.ReHash(aHasher: TOnDynArrayHashOne=nil): boolean;
  40136. var i, n, cap, ndx: integer;
  40137. P: PAnsiChar;
  40138. aHashCode: cardinal;
  40139. begin
  40140. result := false;
  40141. fHashs := nil;
  40142. n := Count;
  40143. if (n=0) or (n<fHashCountTrigger) then
  40144. exit; // hash only if needed, and avoid GPF after TDynArray.Clear (Count=0)
  40145. if (not Assigned(aHasher)) and (not Assigned(fHashElement)) then
  40146. exit;
  40147. // find nearest power of two for new fHashs[] size
  40148. cap := Capacity*2; // Capacity sounds better than Count
  40149. fHashsCount := 256;
  40150. while fHashsCount<cap do
  40151. fHashsCount := fHashsCount shl 1;
  40152. SetLength(fHashs,fHashsCount); // fill all fHashs[]=HASH_VOID=0
  40153. // fill fHashs[] from all existing items
  40154. P := fValue^;
  40155. for i := 0 to n-1 do begin
  40156. if Assigned(aHasher) then
  40157. aHashCode := aHasher(P^) else
  40158. aHashCode := fHashElement(P^,fHasher);
  40159. if aHashCode=HASH_VOID then
  40160. aHashCode := HASH_ONVOIDCOLISION; // 0 means void slot in the loop below
  40161. ndx := HashFind(aHashCode,P^);
  40162. if ndx<0 then
  40163. // >=0 -> already found -> not necessary to add duplicated hash
  40164. with fHashs[-ndx-1] do begin
  40165. Hash := aHashCode;
  40166. Index := i;
  40167. end;
  40168. inc(P,ElemSize);
  40169. end;
  40170. result := true;
  40171. end;
  40172. { TObjectDynArrayWrapper }
  40173. constructor TObjectDynArrayWrapper.Create(var aValue);
  40174. begin
  40175. fValue := @aValue;
  40176. end;
  40177. destructor TObjectDynArrayWrapper.Destroy;
  40178. begin
  40179. Clear;
  40180. inherited;
  40181. end;
  40182. function TObjectDynArrayWrapper.Find(Instance: TObject): integer;
  40183. begin
  40184. for result := 0 to fCount-1 do
  40185. if TObjectDynArray(fValue^)[result]=Instance then
  40186. exit;
  40187. result := -1;
  40188. end;
  40189. function TObjectDynArrayWrapper.Add(Instance: TObject): integer;
  40190. var cap: integer;
  40191. begin
  40192. cap := length(TObjectDynArray(fValue^));
  40193. if cap<=fCount then begin
  40194. if cap<256 then
  40195. inc(cap,64) else
  40196. inc(cap,256+cap shr 3);
  40197. SetLength(TObjectDynArray(fValue^),cap);
  40198. end;
  40199. result := fCount;
  40200. TObjectDynArray(fValue^)[result] := Instance;
  40201. inc(fCount);
  40202. end;
  40203. procedure TObjectDynArrayWrapper.Delete(Index: integer);
  40204. begin
  40205. if cardinal(Index)>=cardinal(fCount) then
  40206. exit; // avoid Out of range
  40207. TObjectDynArray(fValue^)[Index].Free;
  40208. dec(fCount);
  40209. if fCount>Index then
  40210. MoveFast(TObjectDynArray(fValue^)[Index+1],TObjectDynArray(fValue^)[Index],
  40211. (fCount-Index)*sizeof(pointer));
  40212. end;
  40213. procedure TObjectDynArrayWrapper.Clear;
  40214. var i: integer;
  40215. begin
  40216. if fValue^<>nil then begin
  40217. for i := fCount-1 downto 0 do
  40218. try
  40219. TObjectDynArray(fValue^)[i].Free;
  40220. except
  40221. on Exception do;
  40222. end;
  40223. TObjectDynArray(fValue^) := nil; // set capacity to 0
  40224. fCount := 0;
  40225. end else
  40226. if fCount>0 then
  40227. raise ESynException.Create('You MUST define your IObjectDynArray field '+
  40228. 'BEFORE the corresponding dynamic array');
  40229. end;
  40230. function TObjectDynArrayWrapper.Count: integer;
  40231. begin
  40232. result := fCount;
  40233. end;
  40234. function TObjectDynArrayWrapper.Capacity: integer;
  40235. begin
  40236. result := length(TObjectDynArray(fValue^));
  40237. end;
  40238. procedure TObjectDynArrayWrapper.Sort(Compare: TDynArraySortCompare);
  40239. var QuickSort: TDynArrayQuickSort;
  40240. begin
  40241. if (@Compare<>nil) and (fCount>0) then begin
  40242. Quicksort.Compare := @Compare;
  40243. Quicksort.Value := fValue^;
  40244. Quicksort.ElemSize := sizeof(pointer);
  40245. Quicksort.QuickSort(0,fCount-1);
  40246. end;
  40247. end;
  40248. function PtrArrayAdd(var aPtrArray; aItem: pointer): integer;
  40249. var a: TPointerDynArray absolute aPtrArray;
  40250. begin
  40251. result := length(a);
  40252. SetLength(a,result+1);
  40253. a[result] := aItem;
  40254. end;
  40255. { wrapper functions to T*ObjArr types }
  40256. function ObjArrayAdd(var aObjArray; aItem: TObject): integer;
  40257. var a: TObjectDynArray absolute aObjArray;
  40258. begin
  40259. result := length(a);
  40260. SetLength(a,result+1);
  40261. a[result] := aItem;
  40262. end;
  40263. procedure ObjArrayAddOnce(var aObjArray; aItem: TObject);
  40264. begin
  40265. if not PtrUIntScanExists(pointer(aObjArray),
  40266. length(TObjectDynArray(aObjArray)),PtrUInt(aItem)) then
  40267. ObjArrayAdd(aObjArray,aItem);
  40268. end;
  40269. procedure ObjArraySetLength(var aObjArray; aLength: integer);
  40270. begin
  40271. SetLength(TObjectDynArray(aObjArray),aLength);
  40272. end;
  40273. function ObjArrayFind(const aObjArray; aItem: TObject): integer;
  40274. begin
  40275. result := PtrUIntScanIndex(pointer(aObjArray),
  40276. length(TObjectDynArray(aObjArray)),PtrUInt(aItem));
  40277. end;
  40278. procedure ObjArrayDelete(var aObjArray; aItemIndex: integer);
  40279. var n: integer;
  40280. a: TObjectDynArray absolute aObjArray;
  40281. begin
  40282. n := length(a);
  40283. if cardinal(aItemIndex)>=cardinal(n) then
  40284. exit; // out of range
  40285. a[aItemIndex].Free;
  40286. dec(n);
  40287. if n>aItemIndex then
  40288. MoveFast(a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*sizeof(TObject));
  40289. SetLength(a,n);
  40290. end;
  40291. function ObjArrayDelete(var aObjArray; aItem: TObject): integer;
  40292. begin
  40293. result := ObjArrayFind(aObjArray,aItem);
  40294. if result>=0 then
  40295. ObjArrayDelete(aObjArray,result);
  40296. end;
  40297. procedure ObjArraySort(var aObjArray; Compare: TDynArraySortCompare);
  40298. var QuickSort: TDynArrayQuickSort;
  40299. n: integer;
  40300. begin
  40301. n := length(TObjectDynArray(aObjArray));
  40302. if (@Compare<>nil) and (n>0) then begin
  40303. Quicksort.Compare := @Compare;
  40304. Quicksort.Value := pointer(aObjArray);
  40305. Quicksort.ElemSize := sizeof(pointer);
  40306. Quicksort.QuickSort(0,n-1);
  40307. end;
  40308. end;
  40309. procedure ObjArrayClear(var aObjArray);
  40310. var i: integer;
  40311. a: TObjectDynArray absolute aObjArray;
  40312. begin
  40313. if a<>nil then begin
  40314. for i := 0 to length(a)-1 do
  40315. a[i].Free;
  40316. a := nil;
  40317. end;
  40318. end;
  40319. function ObjArrayToJSON(const aObjArray; Options: TTextWriterWriteObjectOptions): RawUTF8;
  40320. begin
  40321. with DefaultTextWriterJSONClass.CreateOwnedStream do
  40322. try
  40323. if woEnumSetsAsText in Options then
  40324. CustomOptions := CustomOptions+[twoEnumSetsAsTextInRecord];
  40325. AddObjArrayJSON(aObjArray,Options);
  40326. SetText(result);
  40327. finally
  40328. Free;
  40329. end;
  40330. end;
  40331. procedure ObjArrayObjArrayClear(var aObjArray);
  40332. var i: integer;
  40333. a: TPointerDynArray absolute aObjArray;
  40334. begin
  40335. if a<>nil then begin
  40336. for i := 0 to length(a)-1 do
  40337. ObjArrayClear(a[i]);
  40338. a := nil;
  40339. end;
  40340. end;
  40341. procedure ObjArraysClear(const aObjArray: array of pointer);
  40342. var i: integer;
  40343. begin
  40344. for i := 0 to high(aObjArray) do
  40345. if aObjArray[i]<>nil then
  40346. ObjArrayClear(aObjArray[i]^);
  40347. end;
  40348. {$ifndef DELPHI5OROLDER}
  40349. function InterfaceArrayAdd(var aInterfaceArray; const aItem: IUnknown): integer;
  40350. var a: TInterfaceDynArray absolute aInterfaceArray;
  40351. begin
  40352. result := length(a);
  40353. SetLength(a,result+1);
  40354. a[result] := aItem;
  40355. end;
  40356. procedure InterfaceArrayAddOnce(var aInterfaceArray; const aItem: IUnknown);
  40357. var a: TInterfaceDynArray absolute aInterfaceArray;
  40358. n: integer;
  40359. begin
  40360. if PtrUIntScanExists(pointer(aInterfaceArray),
  40361. length(TInterfaceDynArray(aInterfaceArray)),PtrUInt(aItem)) then
  40362. exit;
  40363. n := length(a);
  40364. SetLength(a,n+1);
  40365. a[n] := aItem;
  40366. end;
  40367. function InterfaceArrayFind(const aInterfaceArray; const aItem: IUnknown): integer;
  40368. begin
  40369. result := PtrUIntScanIndex(pointer(aInterfaceArray),
  40370. length(TInterfaceDynArray(aInterfaceArray)),PtrUInt(aItem));
  40371. end;
  40372. procedure InterfaceArrayDelete(var aInterfaceArray; aItemIndex: integer);
  40373. var n: integer;
  40374. a: TInterfaceDynArray absolute aInterfaceArray;
  40375. begin
  40376. n := length(a);
  40377. if cardinal(aItemIndex)>=cardinal(n) then
  40378. exit; // out of range
  40379. a[aItemIndex] := nil;
  40380. dec(n);
  40381. if n>aItemIndex then
  40382. MoveFast(a[aItemIndex+1],a[aItemIndex],(n-aItemIndex)*sizeof(IInterface));
  40383. TPointerDynArray(aInterfaceArray)[n] := nil; // avoid GPF in SetLength()
  40384. SetLength(a,n);
  40385. end;
  40386. function InterfaceArrayDelete(var aInterfaceArray; const aItem: IUnknown): integer;
  40387. begin
  40388. result := InterfaceArrayFind(aInterfaceArray,aItem);
  40389. if result>=0 then
  40390. InterfaceArrayDelete(aInterfaceArray,result);
  40391. end;
  40392. {$endif DELPHI5OROLDER}
  40393. { TObjectHash }
  40394. const
  40395. COUNT_TO_START_HASHING = 32;
  40396. function TObjectHash.Find(Item: TObject): integer;
  40397. var n: integer;
  40398. begin
  40399. n := Count;
  40400. if n<=COUNT_TO_START_HASHING then
  40401. result := Scan(Item,n) else
  40402. result := HashFind(Hash(Item),Item);
  40403. end;
  40404. function TObjectHash.Scan(Item: TObject; ListCount: integer): integer;
  40405. begin
  40406. for result := 0 to ListCount-1 do
  40407. if Compare(Get(result),Item) then
  40408. exit;
  40409. result := -1;
  40410. end;
  40411. function TObjectHash.HashFind(aHashCode: cardinal; Item: TObject): integer;
  40412. var n, first: integer;
  40413. looped: boolean;
  40414. begin
  40415. looped := false;
  40416. if fHashs=nil then
  40417. HashInit(Count);
  40418. n := length(fHashs);
  40419. result := (aHashCode-1) and (n-1); // fHashs[] has a power of 2 length
  40420. first := result;
  40421. repeat
  40422. with fHashs[result] do
  40423. if Hash=aHashCode then begin
  40424. if Compare(Get(Index),Item) then begin
  40425. result := Index;
  40426. exit; // found -> returns index in list
  40427. end;
  40428. end else
  40429. if Hash=0 then begin
  40430. result := -(result+1);
  40431. exit; // not found -> returns void index in fHashs[] as negative
  40432. end;
  40433. // hash colision -> search next item
  40434. inc(result);
  40435. if result=n then
  40436. // reached the end -> search once from fHash[0] to fHash[first-1]
  40437. if looped then
  40438. Break else begin
  40439. result := 0;
  40440. n := first;
  40441. looped := true;
  40442. end;
  40443. until false;
  40444. raise ESynException.CreateUTF8('%.HashFind fatal collision',[self]);
  40445. end;
  40446. procedure TObjectHash.HashInit(aCountToHash: integer);
  40447. var PO2,i,ndx: integer;
  40448. H: cardinal;
  40449. O: TObject;
  40450. begin
  40451. assert(fHashs=nil);
  40452. // find nearest power of two for new fHashs[] size
  40453. PO2 := 256;
  40454. while PO2<aCountToHash*2 do
  40455. PO2 := PO2 shl 1;
  40456. SetLength(fHashs,PO2);
  40457. // hash all items
  40458. for i := 0 to aCountToHash-1 do begin
  40459. O := Get(i);
  40460. H := Hash(O);
  40461. ndx := HashFind(H,O);
  40462. if ndx>=0 then
  40463. raise ESynException.CreateUTF8('%.HashInit found dup at index %',[self,ndx]);
  40464. with fHashs[-ndx-1] do begin
  40465. Hash := H;
  40466. Index := i;
  40467. end;
  40468. end;
  40469. end;
  40470. procedure TObjectHash.Invalidate;
  40471. begin
  40472. fHashs := nil; // force HashInit call on next Find()
  40473. end;
  40474. function TObjectHash.EnsureJustAddedNotDuplicated: boolean;
  40475. var H: cardinal;
  40476. lastNdx,ndx: integer;
  40477. lastObject: TObject;
  40478. begin
  40479. lastNdx := Count-1;
  40480. lastObject := Get(lastNdx);
  40481. if lastObject=nil then
  40482. raise ESynException.CreateUTF8('Invalid %.EnsureJustAddedNotDuplicated call',[self]);
  40483. if lastNdx<COUNT_TO_START_HASHING then begin
  40484. result := Scan(lastObject,lastNdx)<0; // O(n) search if not worth it
  40485. exit;
  40486. end;
  40487. if lastNdx*2-lastNdx shr 3>length(fHashs) then begin
  40488. fHashs := nil;
  40489. HashInit(lastNdx); // re-compute fHashs up to Count-1 if not enough void positions
  40490. end;
  40491. H := Hash(lastObject);
  40492. ndx := HashFind(H,lastObject);
  40493. if ndx>=0 then begin
  40494. result := false; // duplicate found
  40495. exit;
  40496. end;
  40497. with fHashs[-ndx-1] do begin
  40498. Hash := H;
  40499. Index := lastNdx;
  40500. end;
  40501. result := true; // last inserted item is OK
  40502. end;
  40503. { TInterfacedObjectWithCustomCreate }
  40504. constructor TInterfacedObjectWithCustomCreate.Create;
  40505. begin // nothing to do by default - overridden constructor may add custom code
  40506. end;
  40507. procedure TInterfacedObjectWithCustomCreate.RefCountUpdate(Release: boolean);
  40508. begin
  40509. if Release then
  40510. _Release else
  40511. _AddRef;
  40512. end;
  40513. { TAutoLock }
  40514. type
  40515. /// used by TAutoLocker.ProtectMethod and TSynLocker.ProtectMethod
  40516. TAutoLock = class(TInterfacedObject)
  40517. protected
  40518. fLock: PSynLocker;
  40519. public
  40520. constructor Create(aLock: PSynLocker);
  40521. destructor Destroy; override;
  40522. end;
  40523. constructor TAutoLock.Create(aLock: PSynLocker);
  40524. begin
  40525. fLock := aLock;
  40526. fLock^.Lock;
  40527. end;
  40528. destructor TAutoLock.Destroy;
  40529. begin
  40530. fLock^.UnLock;
  40531. end;
  40532. { TSynLocker }
  40533. procedure TSynLocker.Init;
  40534. begin
  40535. InitializeCriticalSection(fSection);
  40536. PaddingMaxUsedIndex := -1;
  40537. end;
  40538. procedure TSynLocker.Done;
  40539. var i: integer;
  40540. begin
  40541. for i := 0 to PaddingMaxUsedIndex do
  40542. VarClear(variant(Padding[i]));
  40543. DeleteCriticalSection(fSection);
  40544. end;
  40545. procedure TSynLocker.Lock;
  40546. begin
  40547. EnterCriticalSection(fSection);
  40548. end;
  40549. procedure TSynLocker.UnLock;
  40550. begin
  40551. LeaveCriticalSection(fSection);
  40552. end;
  40553. function TSynLocker.TryLock: boolean;
  40554. begin
  40555. result := TryEnterCriticalSection(fSection){$ifdef LINUX}{$ifdef FPC}<>0{$endif}{$endif};
  40556. end;
  40557. function TSynLocker.ProtectMethod: IUnknown;
  40558. begin
  40559. result := TAutoLock.Create(@self);
  40560. end;
  40561. {$ifndef NOVARIANTS}
  40562. function TSynLocker.GetVariant(Index: integer): Variant;
  40563. begin
  40564. if (Index>=0) and (Index<=PaddingMaxUsedIndex) then // PaddingMaxUsedIndex may be -1
  40565. try
  40566. EnterCriticalSection(fSection);
  40567. result := variant(Padding[Index]);
  40568. finally
  40569. LeaveCriticalSection(fSection);
  40570. end else
  40571. VarClear(result);
  40572. end;
  40573. procedure TSynLocker.SetVariant(Index: integer; const Value: Variant);
  40574. begin
  40575. if cardinal(Index)<=high(Padding) then
  40576. try
  40577. EnterCriticalSection(fSection);
  40578. if Index>PaddingMaxUsedIndex then
  40579. PaddingMaxUsedIndex := Index;
  40580. variant(Padding[Index]) := Value;
  40581. finally
  40582. LeaveCriticalSection(fSection);
  40583. end;
  40584. end;
  40585. function TSynLocker.GetInt64(Index: integer): Int64;
  40586. begin
  40587. if (Index>=0) and (Index<=PaddingMaxUsedIndex) then
  40588. try
  40589. EnterCriticalSection(fSection);
  40590. if not VariantToInt64(variant(Padding[index]),result) then
  40591. result := 0;
  40592. finally
  40593. LeaveCriticalSection(fSection);
  40594. end else
  40595. result := 0;
  40596. end;
  40597. procedure TSynLocker.SetInt64(Index: integer; const Value: Int64);
  40598. begin
  40599. if cardinal(Index)<=high(Padding) then
  40600. try
  40601. EnterCriticalSection(fSection);
  40602. if Index>PaddingMaxUsedIndex then
  40603. PaddingMaxUsedIndex := Index;
  40604. variant(Padding[Index]) := Value;
  40605. finally
  40606. LeaveCriticalSection(fSection);
  40607. end;
  40608. end;
  40609. function TSynLocker.GetUnLockedInt64(Index: integer): Int64;
  40610. begin
  40611. if (Index<0) or (Index>PaddingMaxUsedIndex) or
  40612. not VariantToInt64(variant(Padding[index]),result) then
  40613. result := 0;
  40614. end;
  40615. procedure TSynLocker.SetUnlockedInt64(Index: integer; const Value: Int64);
  40616. begin
  40617. if cardinal(Index)<=high(Padding) then begin
  40618. if Index>PaddingMaxUsedIndex then
  40619. PaddingMaxUsedIndex := Index;
  40620. variant(Padding[Index]) := Value;
  40621. end;
  40622. end;
  40623. function TSynLocker.GetPointer(Index: integer): Pointer;
  40624. begin
  40625. if (Index>=0) and (Index<=PaddingMaxUsedIndex) then
  40626. try
  40627. EnterCriticalSection(fSection);
  40628. with Padding[index] do
  40629. if VType=varUnknown then
  40630. result := VUnknown else
  40631. result := nil;
  40632. finally
  40633. LeaveCriticalSection(fSection);
  40634. end else
  40635. result := nil;
  40636. end;
  40637. procedure TSynLocker.SetPointer(Index: integer; const Value: Pointer);
  40638. begin
  40639. if cardinal(Index)<=high(Padding) then
  40640. try
  40641. EnterCriticalSection(fSection);
  40642. if Index>PaddingMaxUsedIndex then
  40643. PaddingMaxUsedIndex := Index;
  40644. with Padding[index] do begin
  40645. if VType<>varUnknown then begin
  40646. VarClear(PVariant(@VType)^);
  40647. VType := varUnknown;
  40648. end;
  40649. VUnknown := Value;
  40650. end;
  40651. finally
  40652. LeaveCriticalSection(fSection);
  40653. end;
  40654. end;
  40655. function TSynLocker.GetUTF8(Index: integer): RawUTF8;
  40656. var wasString: Boolean;
  40657. begin
  40658. if (Index>=0) and (Index<=PaddingMaxUsedIndex) then
  40659. try
  40660. EnterCriticalSection(fSection);
  40661. VariantToUTF8(variant(Padding[Index]),result,wasString);
  40662. if not wasString then
  40663. result := '';
  40664. finally
  40665. LeaveCriticalSection(fSection);
  40666. end else
  40667. result := '';
  40668. end;
  40669. procedure TSynLocker.SetUTF8(Index: integer; const Value: RawUTF8);
  40670. begin
  40671. if cardinal(Index)<=high(Padding) then
  40672. try
  40673. EnterCriticalSection(fSection);
  40674. if Index>PaddingMaxUsedIndex then
  40675. PaddingMaxUsedIndex := Index;
  40676. RawUTF8ToVariant(Value,Padding[Index],varString);
  40677. finally
  40678. LeaveCriticalSection(fSection);
  40679. end;
  40680. end;
  40681. function TSynLocker.LockedInt64Increment(Index: integer; const Increment: Int64): Int64;
  40682. begin
  40683. if cardinal(Index)<=high(Padding) then
  40684. try
  40685. EnterCriticalSection(fSection);
  40686. result := 0;
  40687. if Index<=PaddingMaxUsedIndex then
  40688. VariantToInt64(variant(Padding[index]),result) else
  40689. PaddingMaxUsedIndex := Index;
  40690. variant(Padding[Index]) := Int64(result+Increment);
  40691. finally
  40692. LeaveCriticalSection(fSection);
  40693. end else
  40694. result := 0;
  40695. end;
  40696. function TSynLocker.LockedExchange(Index: integer; const Value: Variant): Variant;
  40697. begin
  40698. if cardinal(Index)<=high(Padding) then
  40699. try
  40700. EnterCriticalSection(fSection);
  40701. with Padding[index] do begin
  40702. if Index<=PaddingMaxUsedIndex then
  40703. result := PVariant(@VType)^ else begin
  40704. PaddingMaxUsedIndex := Index;
  40705. VarClear(result);
  40706. end;
  40707. PVariant(@VType)^ := Value;
  40708. end;
  40709. finally
  40710. LeaveCriticalSection(fSection);
  40711. end else
  40712. VarClear(result);
  40713. end;
  40714. function TSynLocker.LockedPointerExchange(Index: integer; Value: pointer): pointer;
  40715. begin
  40716. if cardinal(Index)<=high(Padding) then
  40717. try
  40718. EnterCriticalSection(fSection);
  40719. with Padding[index] do begin
  40720. if Index<=PaddingMaxUsedIndex then
  40721. if VType=varUnknown then
  40722. result := VUnknown else begin
  40723. VarClear(PVariant(@VType)^);
  40724. result := nil;
  40725. end else begin
  40726. PaddingMaxUsedIndex := Index;
  40727. result := nil;
  40728. end;
  40729. VType := varUnknown;
  40730. VUnknown := Value;
  40731. end;
  40732. finally
  40733. LeaveCriticalSection(fSection);
  40734. end else
  40735. result := nil;
  40736. end;
  40737. {$endif NOVARIANTS}
  40738. { TInterfacedObjectLocked }
  40739. constructor TInterfacedObjectLocked.Create;
  40740. begin
  40741. inherited Create;
  40742. fSafe.Init;
  40743. end;
  40744. destructor TInterfacedObjectLocked.Destroy;
  40745. begin
  40746. inherited Destroy;
  40747. fSafe.Done;
  40748. end;
  40749. { TPersistentWithCustomCreate }
  40750. constructor TPersistentWithCustomCreate.Create;
  40751. begin // nothing to do by default - overridden constructor may add custom code
  40752. end;
  40753. { TSynPersistent }
  40754. constructor TSynPersistent.Create;
  40755. begin // nothing to do by default - overridden constructor may add custom code
  40756. end;
  40757. procedure TSynPersistent.AssignError(Source: TSynPersistent);
  40758. var
  40759. SourceName: string;
  40760. begin
  40761. if Source <> nil then
  40762. SourceName := Source.ClassName else
  40763. SourceName := 'nil';
  40764. raise EConvertError.CreateFmt('Cannot assign a %s to a %s', [SourceName, ClassName]);
  40765. end;
  40766. procedure TSynPersistent.AssignTo(Dest: TSynPersistent);
  40767. begin
  40768. Dest.AssignError(Self);
  40769. end;
  40770. procedure TSynPersistent.Assign(Source: TSynPersistent);
  40771. begin
  40772. if Source<>nil then
  40773. Source.AssignTo(Self) else
  40774. AssignError(nil);
  40775. end;
  40776. {$ifndef FPC_OR_PUREPASCAL}
  40777. class function TSynPersistent.NewInstance: TObject;
  40778. asm
  40779. push eax // class
  40780. mov eax,[eax].vmtInstanceSize
  40781. push eax // size
  40782. call System.@GetMem
  40783. pop edx // size
  40784. push eax // self
  40785. mov cl,0
  40786. call dword ptr [FillcharFast]
  40787. pop eax // self
  40788. pop edx // class
  40789. mov [eax],edx // store VMT
  40790. end; // TSynPersistent has no interface -> bypass vmtIntfTable
  40791. procedure TSynPersistent.FreeInstance;
  40792. asm
  40793. push ebx
  40794. mov ebx,eax
  40795. @@loop: mov ebx,[ebx] // handle three VMT levels per iteration
  40796. mov edx,[ebx].vmtInitTable
  40797. mov ebx,[ebx].vmtParent
  40798. test edx,edx
  40799. jnz @@clr
  40800. test ebx,ebx
  40801. jz @@end
  40802. mov ebx,[ebx]
  40803. mov edx,[ebx].vmtInitTable
  40804. mov ebx,[ebx].vmtParent
  40805. test edx,edx
  40806. jnz @@clr
  40807. test ebx,ebx
  40808. jz @@end
  40809. mov ebx,[ebx]
  40810. mov edx,[ebx].vmtInitTable
  40811. mov ebx,[ebx].vmtParent
  40812. test edx,edx
  40813. jnz @@clr
  40814. test ebx,ebx
  40815. jnz @@loop
  40816. @@end: pop ebx
  40817. jmp System.@FreeMem
  40818. // TSynPersistent has no TMonitor -> bypass TMonitor.Destroy(self)
  40819. // BTW, TMonitor.Destroy is private, so unreachable
  40820. @@clr: push offset @@loop // parent has never any vmtInitTable -> @@loop
  40821. jmp RecordClear // eax=self edx=typeinfo
  40822. end;
  40823. {$endif FPC_OR_PUREPASCAL}
  40824. { TSynPersistentLocked }
  40825. constructor TSynPersistentLocked.Create;
  40826. begin
  40827. inherited Create;
  40828. fSafe.Init;
  40829. end;
  40830. destructor TSynPersistentLocked.Destroy;
  40831. begin
  40832. inherited Destroy;
  40833. fSafe.Done;
  40834. end;
  40835. { ****************** text buffer and JSON functions and classes ********* }
  40836. { TTextWriter }
  40837. procedure TTextWriter.Add(Value: PtrInt);
  40838. var tmp: array[0..23] of AnsiChar;
  40839. P: PAnsiChar;
  40840. Len: integer;
  40841. begin
  40842. if BEnd-B<=16 then
  40843. FlushToStream;
  40844. P := StrInt32(@tmp[23],value);
  40845. Len := @tmp[23]-P;
  40846. MoveFast(P[0],B[1],Len);
  40847. inc(B,Len);
  40848. end;
  40849. procedure TTextWriter.AddCurr64(const Value: Int64);
  40850. var tmp: array[0..31] of AnsiChar;
  40851. P: PAnsiChar;
  40852. Len: integer;
  40853. begin
  40854. if BEnd-B<=31 then
  40855. FlushToStream;
  40856. P := StrCurr64(@tmp[31],Value);
  40857. Len := @tmp[31]-P;
  40858. if Len>4 then
  40859. if P[Len-1]='0' then
  40860. if P[Len-2]='0' then
  40861. if P[Len-3]='0' then
  40862. if P[Len-4]='0' then
  40863. dec(Len,5) else
  40864. dec(Len,3) else
  40865. dec(Len,2) else
  40866. dec(Len);
  40867. MoveFast(P[0],B[1],Len);
  40868. inc(B,Len);
  40869. end;
  40870. procedure TTextWriter.AddCurr64(const Value: currency);
  40871. begin
  40872. AddCurr64(PInt64(@Value)^);
  40873. end;
  40874. procedure TTextWriter.AddTimeLog(Value: PInt64);
  40875. begin
  40876. if BEnd-B<=31 then
  40877. FlushToStream;
  40878. inc(B,PTimeLogBits(Value)^.Text(B+1,true,'T'));
  40879. end;
  40880. procedure TTextWriter.AddDateTime(Value: PDateTime; FirstChar: AnsiChar; QuoteChar: AnsiChar);
  40881. begin
  40882. if (Value^=0) and (QuoteChar=#0) then
  40883. exit;
  40884. if BEnd-B<=21 then
  40885. FlushToStream;
  40886. inc(B);
  40887. if QuoteChar<>#0 then
  40888. B^ := QuoteChar else
  40889. dec(B);
  40890. if Value^<>0 then begin
  40891. inc(B);
  40892. if trunc(Value^)<>0 then begin
  40893. DateToIso8601PChar(Value^,B,true);
  40894. inc(B,10);
  40895. end;
  40896. if frac(Value^)<>0 then begin
  40897. TimeToIso8601PChar(Value^,B,true,FirstChar);
  40898. inc(B,9);
  40899. end;
  40900. dec(B);
  40901. end;
  40902. if QuoteChar<>#0 then begin
  40903. inc(B);
  40904. B^ := QuoteChar;
  40905. end;
  40906. end;
  40907. procedure TTextWriter.AddDateTime(const Value: TDateTime);
  40908. begin
  40909. if Value=0 then
  40910. exit;
  40911. if BEnd-B<=19 then
  40912. FlushToStream;
  40913. inc(B);
  40914. if trunc(Value)<>0 then begin
  40915. DateToIso8601PChar(Value,B,true);
  40916. inc(B,10);
  40917. end;
  40918. if frac(Value)<>0 then begin
  40919. TimeToIso8601PChar(Value,B,true,'T');
  40920. inc(B,9);
  40921. end;
  40922. dec(B);
  40923. end;
  40924. procedure TTextWriter.AddU(Value: cardinal);
  40925. var tmp: array[0..15] of AnsiChar;
  40926. P: PAnsiChar;
  40927. Len: integer;
  40928. begin
  40929. if BEnd-B<=16 then
  40930. FlushToStream;
  40931. P := StrUInt32(@tmp[15],Value);
  40932. Len := @tmp[15]-P;
  40933. MoveFast(P[0],B[1],Len);
  40934. inc(B,Len);
  40935. end;
  40936. procedure TTextWriter.Add(Value: Extended; precision: integer);
  40937. var S: ShortString;
  40938. begin
  40939. if Value=0 then
  40940. Add('0') else
  40941. AddNoJSONEscape(@S[1],ExtendedToString(S,Value,precision));
  40942. end;
  40943. procedure TTextWriter.AddDouble(Value: double);
  40944. var S: ShortString;
  40945. begin
  40946. if Value=0 then
  40947. Add('0') else
  40948. AddNoJSONEscape(@S[1],ExtendedToString(S,Value,DOUBLE_PRECISION));
  40949. end;
  40950. procedure TTextWriter.AddSingle(Value: single);
  40951. var S: ShortString;
  40952. begin
  40953. if Value=0 then
  40954. Add('0') else
  40955. AddNoJSONEscape(@S[1],ExtendedToString(S,Value,SINGLE_PRECISION));
  40956. end;
  40957. {$ifndef CPU64} // Add(Value: PtrInt) already implemented it
  40958. procedure TTextWriter.Add(Value: Int64);
  40959. var tmp: array[0..23] of AnsiChar;
  40960. P: PAnsiChar;
  40961. Len: integer;
  40962. begin
  40963. if BEnd-B<=24 then
  40964. FlushToStream;
  40965. if Value<0 then begin
  40966. P := StrUInt64(@tmp[23],-Value)-1;
  40967. P^ := '-';
  40968. end else
  40969. P := StrUInt64(@tmp[23],Value);
  40970. Len := @tmp[23]-P;
  40971. MoveFast(P[0],B[1],Len);
  40972. inc(B,Len);
  40973. end;
  40974. {$endif}
  40975. procedure TTextWriter.Add(Value: boolean);
  40976. begin
  40977. if Value then
  40978. AddShort('true') else
  40979. AddShort('false');
  40980. end;
  40981. procedure TTextWriter.AddFloatStr(P: PUTF8Char);
  40982. var L: cardinal;
  40983. begin
  40984. L := StrLen(P);
  40985. if (L=0) or (L>30) then
  40986. Add('0') else begin
  40987. if BEnd-B<=31 then
  40988. FlushToStream;
  40989. inc(B);
  40990. if PWord(P)^=ord('-')+ord('.')shl 8 then begin
  40991. PWord(B)^ := ord('-')+ord('0')shl 8; // '-.3' -> '-0.3'
  40992. inc(B,2);
  40993. inc(P);
  40994. dec(L);
  40995. end else
  40996. if P^='.' then begin
  40997. B^ := '0'; // '.5' -> '0.5'
  40998. inc(B);
  40999. end;
  41000. MoveFast(P^,B^,L);
  41001. inc(B,L-1);
  41002. end;
  41003. end;
  41004. procedure TTextWriter.Add(c: AnsiChar);
  41005. begin
  41006. if B>=BEnd then
  41007. FlushToStream;
  41008. B[1] := c;
  41009. inc(B);
  41010. end;
  41011. procedure TTextWriter.Add(c1, c2: AnsiChar);
  41012. begin
  41013. if BEnd-B<=1 then
  41014. FlushToStream;
  41015. B[1] := c1;
  41016. B[2] := c2;
  41017. inc(B,2);
  41018. end;
  41019. procedure TTextWriter.Add(const guid: TGUID);
  41020. begin
  41021. if BEnd-B<=36 then
  41022. FlushToStream;
  41023. GUIDToText(B+1,@guid);
  41024. inc(B,36);
  41025. end;
  41026. procedure TTextWriter.AddCR;
  41027. begin
  41028. if BEnd-B<=1 then
  41029. FlushToStream;
  41030. PWord(B+1)^ := 13+10 shl 8; // CR + LF
  41031. inc(B,2);
  41032. end;
  41033. procedure TTextWriter.AddEndOfLine(aLevel: TSynLogInfo=sllNone);
  41034. var i: integer;
  41035. begin
  41036. if BEnd-B<=1 then
  41037. FlushToStream;
  41038. if twoEndOfLineCRLF in fCustomOptions then begin
  41039. PWord(B+1)^ := 13+10 shl 8; // CR + LF
  41040. inc(B,2);
  41041. end else begin
  41042. B[1] := #13; // CR
  41043. inc(B);
  41044. end;
  41045. if fEchos<>nil then begin
  41046. fEchoStart := EchoFlush;
  41047. for i := length(fEchos)-1 downto 0 do // for MultiEventRemove() below
  41048. try
  41049. fEchos[i](self,aLevel,fEchoBuf);
  41050. except // remove callback in case of exception during echoing in user code
  41051. MultiEventRemove(fEchos,i);
  41052. end;
  41053. fEchoBuf := '';
  41054. end;
  41055. end;
  41056. procedure TTextWriter.AddCRAndIndent;
  41057. var ntabs: cardinal;
  41058. begin
  41059. if B^=#9 then
  41060. exit; // we most probably just added an indentation level
  41061. ntabs := fHumanReadableLevel;
  41062. if ntabs>=cardinal(fTempBufSize) then
  41063. exit; // avoid buffer overflow
  41064. if BEnd-B<=Integer(ntabs)+1 then
  41065. FlushToStream;
  41066. PWord(B+1)^ := 13+10 shl 8; // CR + LF
  41067. FillcharFast(B[3],ntabs,9); // indentation using tabs
  41068. inc(B,ntabs+2);
  41069. end;
  41070. procedure TTextWriter.AddChars(aChar: AnsiChar; aCount: integer);
  41071. begin
  41072. if cardinal(aCount-1)>=cardinal(fTempBufSize) then
  41073. exit; // avoid buffer overflow
  41074. if BEnd-B<=aCount then
  41075. FlushToStream;
  41076. FillcharFast(B[1],aCount,ord(aChar));
  41077. inc(B,aCount);
  41078. end;
  41079. procedure TTextWriter.Add2(Value: integer);
  41080. begin
  41081. if BEnd-B<=3 then
  41082. FlushToStream;
  41083. if cardinal(Value)>99 then
  41084. PCardinal(B+1)^ := $3030+ord(',')shl 16 else // '00,' if overflow
  41085. PCardinal(B+1)^ := TwoDigitLookupW[Value]+ord(',')shl 16;
  41086. inc(B,3);
  41087. end;
  41088. procedure TTextWriter.Add4(Value: integer);
  41089. begin
  41090. if BEnd-B<=5 then
  41091. FlushToStream;
  41092. if cardinal(Value)>9999 then
  41093. PCardinal(B+1)^ := $30303030 else // '0000,' if overflow
  41094. YearToPChar(Value,B+1);
  41095. inc(B,5);
  41096. B^ := ',';
  41097. end;
  41098. var // can be safely made global since timing is multi-thread safe
  41099. GlobalLogTime: array[boolean] of record
  41100. time: TSystemTime;
  41101. clock: cardinal; // avoid slower API call
  41102. end;
  41103. procedure TTextWriter.AddCurrentLogTime(LocalTime, Use16msCache: boolean);
  41104. var Ticks: cardinal;
  41105. begin
  41106. if BEnd-B<=17 then
  41107. FlushToStream;
  41108. with GlobalLogTime[LocalTime] do begin
  41109. if Use16msCache then begin
  41110. Ticks := GetTickCount; // this call is very fast (just one integer mul)
  41111. if clock<>Ticks then begin // typically in range of 10-16 ms
  41112. clock := Ticks;
  41113. if LocalTime then
  41114. GetLocalTime(time) else
  41115. {$ifdef MSWINDOWS}
  41116. GetSystemTime(time);
  41117. {$else}
  41118. GetNowUTCSystem(time);
  41119. {$endif}
  41120. end;
  41121. end;
  41122. inc(B);
  41123. YearToPChar(time.{$ifdef MSWINDOWS}wYear{$else}Year{$endif},B);
  41124. PWord(B+4)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wMonth{$else}Month{$endif}];
  41125. PWord(B+6)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wDay{$else}Day{$endif}];
  41126. B[8] := ' ';
  41127. PWord(B+9)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wHour{$else}Hour{$endif}];
  41128. PWord(B+11)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wMinute{$else}Minute{$endif}];
  41129. PWord(B+13)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wSecond{$else}Second{$endif}];
  41130. PWord(B+15)^ := TwoDigitLookupW[time.{$ifdef MSWINDOWS}wMilliseconds{$else}Millisecond{$endif} shr 4];
  41131. B[17] := ' ';
  41132. inc(B,16);
  41133. end;
  41134. end;
  41135. procedure TTextWriter.AddMicroSec(MS: cardinal);
  41136. function Value3Digits(V: Cardinal; P: PUTF8Char): Cardinal;
  41137. begin
  41138. PWord(P+1)^ := TwoDigitLookupW[V mod 100];
  41139. P^ := AnsiChar((V div 100)mod 10+48);
  41140. result := V div 1000;
  41141. end;
  41142. begin // 00.000.000
  41143. if BEnd-B<=17 then
  41144. FlushToStream;
  41145. B[3] := '.';
  41146. B[7] := '.';
  41147. inc(B);
  41148. MS := Value3Digits(Value3Digits(MS,B+7),B+3);
  41149. if MS>99 then
  41150. MS := 99;
  41151. PWord(B)^:= TwoDigitLookupW[MS];
  41152. inc(B,9);
  41153. end;
  41154. procedure TTextWriter.Add3(Value: integer);
  41155. begin
  41156. if BEnd-B<=4 then
  41157. FlushToStream;
  41158. if cardinal(Value)>999 then
  41159. PCardinal(B+1)^ := $303030 else // '0000,' if overflow
  41160. PCardinal(B+1)^ := TwoDigitLookupW[Value div 10]+
  41161. ord(Value mod 10+48)shl 16;
  41162. inc(B,4);
  41163. B^ := ',';
  41164. end;
  41165. procedure TTextWriter.AddCSVInteger(const Integers: array of Integer);
  41166. var i: integer;
  41167. begin
  41168. if length(Integers)=0 then
  41169. exit;
  41170. for i := 0 to high(Integers) do begin
  41171. Add(Integers[i]);
  41172. Add(',');
  41173. end;
  41174. CancelLastComma;
  41175. end;
  41176. procedure TTextWriter.AddCSVDouble(const Doubles: array of double);
  41177. var i: integer;
  41178. begin
  41179. if length(Doubles)=0 then
  41180. exit;
  41181. for i := 0 to high(Doubles) do begin
  41182. AddDouble(Doubles[i]);
  41183. Add(',');
  41184. end;
  41185. CancelLastComma;
  41186. end;
  41187. procedure TTextWriter.AddCSVUTF8(const Values: array of RawUTF8);
  41188. var i: integer;
  41189. begin
  41190. if length(Values)=0 then
  41191. exit;
  41192. for i := 0 to high(Values) do begin
  41193. Add('"');
  41194. AddJSONEscape(pointer(Values[i]));
  41195. Add('"',',');
  41196. end;
  41197. CancelLastComma;
  41198. end;
  41199. procedure TTextWriter.AddCSVConst(const Values: array of const);
  41200. var i: integer;
  41201. begin
  41202. if length(Values)=0 then
  41203. exit;
  41204. for i := 0 to high(Values) do begin
  41205. AddJSONEscape(Values[i]);
  41206. Add(',');
  41207. end;
  41208. CancelLastComma;
  41209. end;
  41210. procedure TTextWriter.Add(const Values: array of const);
  41211. var i: Integer;
  41212. begin
  41213. for i := 0 to high(Values) do
  41214. AddJSONEscape(Values[i]);
  41215. end;
  41216. procedure TTextWriter.WriteObject(Value: TObject; Options: TTextWriterWriteObjectOptions);
  41217. var i: integer;
  41218. begin
  41219. if Value<>nil then
  41220. if Value.InheritsFrom(Exception) then
  41221. Add('{"%":"%"}',[Value.ClassType,Exception(Value).Message]) else
  41222. if Value.InheritsFrom(TRawUTF8List) then
  41223. with TRawUTF8List(Value) do begin
  41224. self.Add('[');
  41225. for i := 0 to Count-1 do begin
  41226. self.Add('"');
  41227. self.AddJSONEscape(pointer(fList[i]));
  41228. self.Add('"',',');
  41229. end;
  41230. self.CancelLastComma;
  41231. self.Add(']');
  41232. exit;
  41233. end else
  41234. if Value.InheritsFrom(TStrings) then
  41235. with TStrings(Value) do begin
  41236. self.Add('[');
  41237. for i := 0 to Count-1 do begin
  41238. self.Add('"');
  41239. {$ifdef UNICODE}
  41240. self.AddJSONEscapeW(pointer(Strings[i]),Length(Strings[i]));
  41241. {$else}
  41242. self.AddJSONEscapeAnsiString(Strings[i]);
  41243. {$endif}
  41244. self.Add('"',',');
  41245. end;
  41246. self.CancelLastComma;
  41247. self.Add(']');
  41248. exit;
  41249. end else
  41250. if not(woFullExpand in Options) or
  41251. not (Value.InheritsFrom(TList)
  41252. {$ifndef LVCL} or Value.InheritsFrom(TCollection){$endif}) then
  41253. Value := nil;
  41254. if Value=nil then begin
  41255. AddShort('null');
  41256. exit;
  41257. end;
  41258. Add('{');
  41259. AddInstanceName(Value,':');
  41260. Add('[');
  41261. if Value.InheritsFrom(TList) then
  41262. for i := 0 to TList(Value).Count-1 do
  41263. AddInstanceName(TList(Value).List[i],',')
  41264. {$ifndef LVCL} else
  41265. if Value.InheritsFrom(TCollection) then
  41266. for i := 0 to TCollection(Value).Count-1 do
  41267. AddInstanceName(TCollection(Value).Items[i],',') {$endif} ;
  41268. CancelLastComma;
  41269. Add(']','}');
  41270. end;
  41271. function TTextWriter.InternalJSONWriter: TTextWriter;
  41272. begin
  41273. if fInternalJSONWriter=nil then
  41274. fInternalJSONWriter := DefaultTextWriterJSONClass.CreateOwnedStream else
  41275. fInternalJSONWriter.CancelAll;
  41276. result := fInternalJSONWriter;
  41277. end;
  41278. procedure TTextWriter.AddJSONEscape(Source: TTextWriter);
  41279. begin
  41280. if Source.fTotalFileSize=0 then
  41281. AddJSONEscape(Source.fTempBuf,Source.B-Source.fTempBuf+1) else
  41282. AddJSONEscape(Pointer(Source.Text),0);
  41283. end;
  41284. procedure TTextWriter.AddNoJSONEscape(Source: TTextWriter);
  41285. begin
  41286. if Source.fTotalFileSize=0 then
  41287. AddNoJSONEscape(Source.fTempBuf,Source.B-Source.fTempBuf+1) else
  41288. AddNoJSONEscapeUTF8(Source.Text);
  41289. end;
  41290. procedure TTextWriter.WriteObjectAsString(Value: TObject;
  41291. Options: TTextWriterWriteObjectOptions);
  41292. begin
  41293. Add('"');
  41294. InternalJSONWriter.WriteObject(Value,Options);
  41295. AddJSONEscape(fInternalJSONWriter);
  41296. Add('"');
  41297. end;
  41298. class procedure TTextWriter.RegisterCustomJSONSerializer(aTypeInfo: pointer;
  41299. aReader: TDynArrayJSONCustomReader; aWriter: TDynArrayJSONCustomWriter);
  41300. begin
  41301. GlobalJSONCustomParsers.RegisterCallbacks(aTypeInfo,aReader,aWriter);
  41302. end;
  41303. class procedure TTextWriter.UnRegisterCustomJSONSerializer(aTypeInfo: pointer);
  41304. begin
  41305. GlobalJSONCustomParsers.RegisterCallbacks(aTypeInfo,nil,nil);
  41306. end;
  41307. class function TTextWriter.RegisterCustomJSONSerializerFromText(aTypeInfo: pointer;
  41308. const aRTTIDefinition: RawUTF8): TJSONRecordAbstract;
  41309. begin
  41310. result := GlobalJSONCustomParsers.RegisterFromText(aTypeInfo,aRTTIDefinition);
  41311. end;
  41312. class procedure TTextWriter.RegisterCustomJSONSerializerFromText(
  41313. const aTypeInfoTextDefinitionPairs: array of const);
  41314. var n,i: integer;
  41315. def: RawUTF8;
  41316. begin
  41317. n := length(aTypeInfoTextDefinitionPairs);
  41318. if (n=0) or (n and 1=1) then
  41319. exit;
  41320. n := n shr 1;
  41321. if n=0 then
  41322. exit;
  41323. for i := 0 to n-1 do
  41324. if (aTypeInfoTextDefinitionPairs[i*2].VType<>vtPointer) or
  41325. not VarRecToUTF8IsString(aTypeInfoTextDefinitionPairs[i*2+1],def) then
  41326. raise ESynException.Create('RegisterCustomJSONSerializerFromText[?]') else
  41327. GlobalJSONCustomParsers.RegisterFromText(
  41328. aTypeInfoTextDefinitionPairs[i*2].VPointer,def);
  41329. end;
  41330. class function TTextWriter.RegisterCustomJSONSerializerSetOptions(aTypeInfo: pointer;
  41331. aOptions: TJSONCustomParserSerializationOptions; aAddIfNotExisting: boolean): boolean;
  41332. var ndx: integer;
  41333. begin
  41334. result := false;
  41335. if aTypeInfo=nil then
  41336. exit;
  41337. case PTypeKind(aTypeInfo)^ of
  41338. tkRecord{$ifdef FPC},tkObject{$endif}:
  41339. ndx := GlobalJSONCustomParsers.RecordSearch(aTypeInfo,aAddIfNotExisting);
  41340. tkDynArray:
  41341. ndx := GlobalJSONCustomParsers.DynArraySearch(aTypeInfo,nil,aAddIfNotExisting);
  41342. else
  41343. exit;
  41344. end;
  41345. if (ndx>=0) and
  41346. (GlobalJSONCustomParsers.fParser[ndx].RecordCustomParser<>nil) then begin
  41347. GlobalJSONCustomParsers.fParser[ndx].RecordCustomParser.Options := aOptions;
  41348. result := true;
  41349. end;
  41350. end;
  41351. class function TTextWriter.RegisterCustomJSONSerializerFindParser(
  41352. aTypeInfo: pointer; aAddIfNotExisting: boolean=false): TJSONRecordAbstract;
  41353. var ndx: integer;
  41354. begin
  41355. result := nil;
  41356. if aTypeInfo=nil then
  41357. exit;
  41358. case PTypeKind(aTypeInfo)^ of
  41359. tkRecord{$ifdef FPC},tkObject{$endif}:
  41360. ndx := GlobalJSONCustomParsers.RecordSearch(aTypeInfo,aAddIfNotExisting);
  41361. tkDynArray:
  41362. ndx := GlobalJSONCustomParsers.DynArraySearch(aTypeInfo,nil,aAddIfNotExisting);
  41363. else
  41364. exit;
  41365. end;
  41366. if ndx>=0 then
  41367. result := GlobalJSONCustomParsers.fParser[ndx].RecordCustomParser;
  41368. end;
  41369. class procedure TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType(
  41370. aTypeInfo: pointer; aTypeName: RawUTF8='');
  41371. begin
  41372. if aTypeName='' then
  41373. TypeInfoToName(aTypeInfo,aTypeName);
  41374. GlobalCustomJSONSerializerFromTextSimpleType.AddObjectIfNotExisting(aTypeName,aTypeInfo);
  41375. end;
  41376. class procedure TTextWriter.RegisterCustomJSONSerializerFromTextSimpleType(
  41377. const aTypeInfos: array of pointer);
  41378. var i: integer;
  41379. begin
  41380. for i := 0 to high(aTypeInfos) do
  41381. RegisterCustomJSONSerializerFromTextSimpleType(aTypeInfos[i],'');
  41382. end;
  41383. procedure TTextWriter.AddRecordJSON(const Rec; TypeInfo: pointer);
  41384. var customWriter: TDynArrayJSONCustomWriter;
  41385. begin
  41386. if (self=nil) or (@Rec=nil) or (TypeInfo=nil) or
  41387. not(PTypeKind(TypeInfo)^ in tkRecordTypes) then
  41388. raise ESynException.CreateUTF8('Invalid %.AddRecordJSON(%)',[self,TypeInfo]);
  41389. if GlobalJSONCustomParsers.RecordSearch(TypeInfo,customWriter,nil) then
  41390. customWriter(self,Rec) else
  41391. WrRecord(Rec,TypeInfo);
  41392. end;
  41393. procedure TTextWriter.AddVoidRecordJSON(TypeInfo: pointer);
  41394. var tmp: TBytes;
  41395. info: PTypeInfo;
  41396. begin
  41397. info := GetTypeInfo(TypeInfo,tkRecordTypeOrSet);
  41398. if (self=nil) or (info=nil) then
  41399. raise ESynException.CreateUTF8('Invalid %.AddVoidRecordJSON(%)',[self,TypeInfo]);
  41400. SetLength(tmp,info^.recSize {$ifdef FPC}and $7FFFFFFF{$endif});
  41401. AddRecordJSON(tmp[0],TypeInfo);
  41402. end;
  41403. {$ifndef NOVARIANTS}
  41404. procedure TTextWriter.AddVariant(const Value: variant; Escape: TTextWriterKind);
  41405. var CustomVariantType: TCustomVariantType;
  41406. begin
  41407. with TVarData(Value) do
  41408. case VType of
  41409. varEmpty,
  41410. varNull: AddShort('null');
  41411. varSmallint: Add(VSmallint);
  41412. varShortInt: Add(VShortInt);
  41413. varByte: AddU(VByte);
  41414. varWord: AddU(VWord);
  41415. varLongWord: AddU(VLongWord);
  41416. varInteger: Add(VInteger);
  41417. varInt64: Add(VInt64);
  41418. varWord64: Add(VInt64);
  41419. varSingle: AddSingle(VSingle);
  41420. varDouble: AddDouble(VDouble);
  41421. varDate: AddDateTime(@VDate,'T','"');
  41422. varCurrency: AddCurr64(VInt64);
  41423. varBoolean: Add(VBoolean);
  41424. varVariant: AddVariant(PVariant(VPointer)^,Escape);
  41425. varString: begin
  41426. if Escape=twJSONEscape then
  41427. Add('"');
  41428. {$ifdef HASCODEPAGE}
  41429. AddAnyAnsiString(RawByteString(VString),Escape);
  41430. {$else} // VString is expected to be a RawUTF8
  41431. Add(VAny,length(RawUTF8(VAny)),Escape);
  41432. {$endif}
  41433. if Escape=twJSONEscape then
  41434. Add('"');
  41435. end;
  41436. varOleStr {$ifdef HASVARUSTRING}, varUString{$endif}: begin
  41437. if Escape=twJSONEscape then
  41438. Add('"');
  41439. AddW(VAny,0,Escape);
  41440. if Escape=twJSONEscape then
  41441. Add('"');
  41442. end;
  41443. else
  41444. if VType=varVariant or varByRef then
  41445. AddVariant(PVariant(VPointer)^,Escape) else
  41446. if VType=varByRef or varString then begin
  41447. if Escape=twJSONEscape then
  41448. Add('"');
  41449. {$ifdef HASCODEPAGE}
  41450. AddAnyAnsiString(PRawByteString(VAny)^,Escape);
  41451. {$else} // VString is expected to be a RawUTF8
  41452. Add(PPointer(VAny)^,length(PRawUTF8(VAny)^),Escape);
  41453. {$endif}
  41454. if Escape=twJSONEscape then
  41455. Add('"');
  41456. end else
  41457. if {$ifdef HASVARUSTRING}(VType=varByRef or varUString) or {$endif}
  41458. (VType=varByRef or varOleStr) then begin
  41459. if Escape=twJSONEscape then
  41460. Add('"');
  41461. AddW(PPointer(VAny)^,0,Escape);
  41462. if Escape=twJSONEscape then
  41463. Add('"');
  41464. end else
  41465. if FindCustomVariantType(VType,CustomVariantType) and
  41466. CustomVariantType.InheritsFrom(TSynInvokeableVariantType) then
  41467. TSynInvokeableVariantType(CustomVariantType).ToJson(self,Value,Escape) else
  41468. raise ESynException.CreateUTF8('%.AddVariant(VType=%)',[self,VType]);
  41469. end;
  41470. end;
  41471. {$endif NOVARIANTS}
  41472. procedure TTextWriter.AddDynArrayJSON(aTypeInfo: pointer; const aValue);
  41473. var DynArray: TDynArray;
  41474. begin
  41475. DynArray.Init(aTypeInfo,pointer(@aValue)^);
  41476. AddDynArrayJSON(DynArray);
  41477. end;
  41478. {$ifdef UNDIRECTDYNARRAY}
  41479. procedure TTextWriter.AddDynArrayJSON(const aDynArray: TDynArrayHashed);
  41480. begin
  41481. AddDynArrayJson(aDynArray.InternalDynArray);
  41482. end;
  41483. {$endif}
  41484. procedure TTextWriter.AddDynArrayJSONAsString(aTypeInfo: pointer; var aValue);
  41485. begin
  41486. Add('"');
  41487. InternalJSONWriter.AddDynArrayJSON(aTypeInfo,aValue);
  41488. AddJSONEscape(fInternalJSONWriter);
  41489. Add('"');
  41490. end;
  41491. procedure TTextWriter.AddObjArrayJSON(const aObjArray;
  41492. Options: TTextWriterWriteObjectOptions);
  41493. var i: integer;
  41494. a: TObjectDynArray absolute aObjArray;
  41495. begin
  41496. Add('[');
  41497. for i := 0 to length(a)-1 do begin
  41498. WriteObject(a[i],Options);
  41499. Add(',');
  41500. end;
  41501. CancelLastComma;
  41502. Add(']');
  41503. end;
  41504. procedure TTextWriter.AddTypedJSON(aTypeInfo: pointer; const aValue;
  41505. EnumSetsAsText,FullSetsAsStar: boolean);
  41506. var max, i: Integer;
  41507. PS: PShortString;
  41508. begin
  41509. case PTypeKind(aTypeInfo)^ of
  41510. tkClass:
  41511. WriteObject(TObject(aValue),[woFullExpand]);
  41512. tkEnumeration:
  41513. if EnumSetsAsText then begin
  41514. Add('"');
  41515. PS := GetEnumName(aTypeInfo,byte(aValue));
  41516. if twoTrimLeftEnumSets in fCustomOptions then
  41517. AddTrimLeftLowerCase(PS) else
  41518. AddShort(PS^);
  41519. Add('"');
  41520. end else
  41521. AddU(byte(aValue));
  41522. tkSet:
  41523. if GetSetInfo(aTypeInfo,max,PS) then
  41524. if EnumSetsAsText then begin
  41525. Add('[');
  41526. if FullSetsAsStar and GetAllBits(cardinal(aValue),max+1) then
  41527. AddShort('"*"') else begin
  41528. for i := 0 to max do begin
  41529. if GetBit(aValue,i) then begin
  41530. Add('"');
  41531. if twoTrimLeftEnumSets in fCustomOptions then
  41532. AddTrimLeftLowerCase(PS) else
  41533. AddShort(PS^);
  41534. Add('"',',');
  41535. end;
  41536. inc(PByte(PS),ord(PS^[0])+1); // next short string
  41537. end;
  41538. CancelLastComma;
  41539. end;
  41540. Add(']');
  41541. end else
  41542. if max<8 then
  41543. AddU(byte(aValue)) else
  41544. if max<16 then
  41545. AddU(word(aValue)) else
  41546. if max<32 then
  41547. AddU(cardinal(aValue)) else
  41548. Add(Int64(aValue))
  41549. else AddShort('null');
  41550. tkRecord{$ifdef FPC},tkObject{$endif}:
  41551. AddRecordJSON(aValue,aTypeInfo);
  41552. tkDynArray:
  41553. AddDynArrayJSON(DynArray(aTypeInfo,(@aValue)^));
  41554. {$ifndef NOVARIANTS}
  41555. tkVariant:
  41556. AddVariant(variant(aValue),twJSONEscape);
  41557. {$endif}
  41558. else
  41559. AddShort('null');
  41560. end;
  41561. end;
  41562. function TTextWriter.AddJSONReformat(JSON: PUTF8Char;
  41563. Format: TTextWriterJSONFormat; EndOfObject: PUTF8Char): PUTF8Char;
  41564. var objEnd: AnsiChar;
  41565. Name,Value: PUTF8Char;
  41566. ValueLen: integer;
  41567. begin
  41568. result := nil;
  41569. if JSON=nil then
  41570. exit;
  41571. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41572. case JSON^ of
  41573. '[': begin // array
  41574. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41575. if JSON^=']' then begin
  41576. Add('[');
  41577. inc(JSON);
  41578. end else begin
  41579. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41580. AddCRAndIndent;
  41581. inc(fHumanReadableLevel);
  41582. Add('[');
  41583. repeat
  41584. if JSON=nil then
  41585. exit;
  41586. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41587. AddCRAndIndent;
  41588. JSON := AddJSONReformat(JSON,Format,@objEnd);
  41589. if objEnd=']' then
  41590. break;
  41591. Add(objEnd);
  41592. until false;
  41593. dec(fHumanReadableLevel);
  41594. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41595. AddCRAndIndent;
  41596. end;
  41597. Add(']');
  41598. end;
  41599. '{': begin // object
  41600. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41601. Add('{');
  41602. inc(fHumanReadableLevel);
  41603. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41604. AddCRAndIndent;
  41605. if JSON^='}' then
  41606. repeat inc(JSON) until not(JSON^ in [#1..' ']) else
  41607. repeat
  41608. Name := GetJSONPropName(JSON);
  41609. if Name=nil then
  41610. exit;
  41611. if (Format in [jsonUnquotedPropName,jsonUnquotedPropNameCompact]) and
  41612. JsonPropNameValid(Name) then
  41613. AddNoJSONEscape(Name,StrLen(Name)) else begin
  41614. Add('"');
  41615. AddJSONEscape(Name);
  41616. Add('"');
  41617. end;
  41618. if Format in [jsonCompact,jsonUnquotedPropNameCompact] then
  41619. Add(':') else
  41620. Add(':',' ');
  41621. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41622. JSON := AddJSONReformat(JSON,Format,@objEnd);
  41623. if objEnd='}' then
  41624. break;
  41625. Add(objEnd);
  41626. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41627. AddCRAndIndent;
  41628. until false;
  41629. dec(fHumanReadableLevel);
  41630. if not (Format in [jsonCompact,jsonUnquotedPropNameCompact]) then
  41631. AddCRAndIndent;
  41632. Add('}');
  41633. end;
  41634. '"': begin // string
  41635. Value := JSON;
  41636. JSON := GotoEndOfJSONString(JSON);
  41637. if JSON^<>'"' then
  41638. exit;
  41639. inc(JSON);
  41640. AddNoJSONEscape(Value,JSON-Value);
  41641. end;
  41642. else begin // numeric or true/false/null
  41643. Value := GetJSONField(JSON,result,nil,EndOfObject); // let wasString=nil
  41644. if Value=nil then
  41645. AddShort('null') else begin
  41646. ValueLen := StrLen(Value);
  41647. while (ValueLen>0) and (Value[ValueLen-1]<=' ') do dec(ValueLen);
  41648. AddNoJSONEscape(Value,ValueLen);
  41649. end;
  41650. exit;
  41651. end;
  41652. end;
  41653. if JSON<>nil then begin
  41654. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41655. if EndOfObject<>nil then
  41656. EndOfObject^ := JSON^;
  41657. if JSON^<>#0 then
  41658. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41659. end;
  41660. result := JSON;
  41661. end;
  41662. function TTextWriter.AddJSONToXML(JSON: PUTF8Char; ArrayName: PUTF8Char=nil;
  41663. EndOfObject: PUTF8Char=nil): PUTF8Char;
  41664. var objEnd: AnsiChar;
  41665. Name,Value: PUTF8Char;
  41666. n,c: integer;
  41667. begin
  41668. result := nil;
  41669. if JSON=nil then
  41670. exit;
  41671. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41672. case JSON^ of
  41673. '[': begin
  41674. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41675. if JSON^=']' then
  41676. JSON := GotoNextNotSpace(JSON+1) else begin
  41677. n := 0;
  41678. repeat
  41679. if JSON=nil then
  41680. exit;
  41681. Add('<');
  41682. if ArrayName=nil then
  41683. Add(n) else
  41684. AddXmlEscape(ArrayName);
  41685. Add('>');
  41686. JSON := AddJSONToXML(JSON,nil,@objEnd);
  41687. Add('<','/');
  41688. if ArrayName=nil then
  41689. Add(n) else
  41690. AddXmlEscape(ArrayName);
  41691. Add('>');
  41692. inc(n);
  41693. until objEnd=']';
  41694. end;
  41695. end;
  41696. '{': begin
  41697. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41698. if JSON^='}' then
  41699. repeat inc(JSON) until not(JSON^ in [#1..' ']) else
  41700. repeat
  41701. Name := GetJSONPropName(JSON);
  41702. if Name=nil then
  41703. exit;
  41704. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41705. if JSON^='[' then // arrays are written as list of items, without root
  41706. JSON := AddJSONToXML(JSON,Name,@objEnd) else begin
  41707. Add('<');
  41708. AddXmlEscape(Name);
  41709. Add('>');
  41710. JSON := AddJSONToXML(JSON,Name,@objEnd);
  41711. Add('<','/');
  41712. AddXmlEscape(Name);
  41713. Add('>');
  41714. end;
  41715. until objEnd='}';
  41716. end;
  41717. else begin
  41718. Value := GetJSONField(JSON,result,nil,EndOfObject); // let wasString=nil
  41719. if Value=nil then
  41720. AddShort('null') else begin
  41721. c := PInteger(Value)^ and $ffffff;
  41722. if (c=JSON_BASE64_MAGIC) or (c=JSON_SQLDATE_MAGIC) then
  41723. inc(Value,3); // just ignore the Magic codepoint encoded as UTF-8
  41724. AddXmlEscape(Value);
  41725. end;
  41726. exit;
  41727. end;
  41728. end;
  41729. if JSON<>nil then begin
  41730. if JSON^ in [#1..' '] then repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41731. if EndOfObject<>nil then
  41732. EndOfObject^ := JSON^;
  41733. if JSON^<>#0 then
  41734. repeat inc(JSON) until not(JSON^ in [#1..' ']);
  41735. end;
  41736. result := JSON;
  41737. end;
  41738. procedure TTextWriter.AddDynArrayJSON(const aDynArray: TDynArray);
  41739. var i,n: integer;
  41740. P: Pointer;
  41741. T: TDynArrayKind;
  41742. tmp: RawByteString;
  41743. customWriter: TDynArrayJSONCustomWriter;
  41744. customParser: TJSONRecordAbstract;
  41745. Options: TJSONCustomParserSerializationOptions;
  41746. NestedDynArray: TDynArray;
  41747. begin // code below must match TDynArray.LoadFromJSON
  41748. n := aDynArray.Count-1;
  41749. if n<0 then begin
  41750. Add('[',']');
  41751. exit;
  41752. end;
  41753. if GlobalJSONCustomParsers.DynArraySearch(
  41754. aDynArray.ArrayType,aDynArray.ElemType,customWriter,@customParser) then
  41755. T := djCustom else
  41756. T := aDynArray.ToKnownType;
  41757. P := aDynArray.fValue^;
  41758. Add('[');
  41759. case T of
  41760. djNone:
  41761. if (aDynArray.ElemType<>nil) and
  41762. (PTypeKind(aDynArray.ElemType)^=tkDynArray) then begin
  41763. for i := 0 to n do begin
  41764. NestedDynArray.Init(aDynArray.ElemType,P^);
  41765. AddDynArrayJSON(NestedDynArray);
  41766. Add(',');
  41767. inc(PtrUInt(P),aDynArray.ElemSize);
  41768. end;
  41769. end else begin
  41770. tmp := aDynArray.SaveTo;
  41771. WrBase64(pointer(tmp),length(tmp),true); // magic=true
  41772. end;
  41773. djCustom: begin
  41774. if customParser=nil then
  41775. byte(Options) := 0 else
  41776. Options := customParser.Options;
  41777. if soWriteHumanReadable in Options then
  41778. Inc(fHumanReadableLevel);
  41779. for i := 0 to n do begin
  41780. customWriter(self,P^);
  41781. Add(',');
  41782. inc(PtrUInt(P),aDynArray.ElemSize);
  41783. end;
  41784. if soWriteHumanReadable in Options then begin
  41785. dec(fHumanReadableLevel);
  41786. CancelLastComma;
  41787. AddCRAndIndent;
  41788. end;
  41789. end;
  41790. {$ifndef NOVARIANTS}
  41791. djVariant:
  41792. for i := 0 to n do begin
  41793. AddVariant(PVariantArray(P)^[i],twJSONEscape);
  41794. Add(',');
  41795. end;
  41796. {$endif}
  41797. djRawByteString:
  41798. for i := 0 to n do begin
  41799. WrBase64(PPointerArray(P)^[i],Length(PRawByteStringArray(P)^[i]),true);
  41800. Add(',');
  41801. end;
  41802. djTimeLog..djString,djWideString..djInterface: // add textual JSON content
  41803. for i := 0 to n do begin
  41804. Add('"');
  41805. case T of
  41806. djTimeLog: AddTimeLog(@PInt64Array(P)^[i]);
  41807. djDateTime: AddDateTime(@PDoubleArray(P)^[i]);
  41808. djRawUTF8: AddJSONEscape(PPointerArray(P)^[i]);
  41809. djWideString, djSynUnicode: AddJSONEscapeW(PPointerArray(P)^[i]);
  41810. djWinAnsi: AddAnyAnsiString(PRawByteStringArray(P)^[i],twJSONEscape,CODEPAGE_US);
  41811. djString:
  41812. {$ifdef UNICODE}
  41813. AddJSONEscapeW(PPointerArray(P)^[i]);
  41814. {$else}
  41815. AddAnyAnsiString(PRawByteStringArray(P)^[i],twJSONEscape,0);
  41816. {$endif}
  41817. djInterface: AddPointer(PPtrIntArray(P)^[i]);
  41818. end;
  41819. Add('"',',');
  41820. end;
  41821. else // numerical JSON
  41822. for i := 0 to n do begin
  41823. case T of
  41824. djBoolean: Add(PBooleanArray(P)^[i]);
  41825. djByte: AddU(PByteArray(P)^[i]);
  41826. djWord: AddU(PWordArray(P)^[i]);
  41827. djInteger: Add(PIntegerArray(P)^[i]);
  41828. djCardinal: AddU(PCardinalArray(P)^[i]);
  41829. djSingle: AddSingle(PSingleArray(P)^[i]);
  41830. djInt64: Add(PInt64Array(P)^[i]);
  41831. djDouble: AddDouble(PDoubleArray(P)^[i]);
  41832. djCurrency: AddCurr64(PInt64Array(P)^[i]);
  41833. end;
  41834. Add(',');
  41835. end;
  41836. end;
  41837. CancelLastComma;
  41838. Add(']');
  41839. end;
  41840. procedure TTextWriter.Add(const Format: RawUTF8; const Values: array of const;
  41841. Escape: TTextWriterKind=twNone);
  41842. var ValuesIndex: integer;
  41843. F: PUTF8Char;
  41844. label write;
  41845. begin // we put const char > #127 as #??? -> asiatic MBCS codepage OK
  41846. if Format='' then
  41847. exit;
  41848. if (Format='%') and (high(Values)>=0) then begin
  41849. Add(Values[0],Escape);
  41850. exit;
  41851. end;
  41852. ValuesIndex := 0;
  41853. F := pointer(Format);
  41854. repeat
  41855. repeat
  41856. case ord(F^) of
  41857. 0: exit;
  41858. 13: AddCR;
  41859. ord('%'): break;
  41860. {$ifdef OLDTEXTWRITERFORMAT}
  41861. 164: AddCR; // ¤ -> add CR,LF
  41862. 167: if B^=',' then dec(B); // §
  41863. ord('|'): begin
  41864. inc(F); // |% -> %
  41865. goto write;
  41866. end;
  41867. ord('$'),163,181: // $,£,µ
  41868. break; // process command value
  41869. {$endif}
  41870. else begin
  41871. write: if B>=BEnd then
  41872. FlushToStream;
  41873. B[1] := F^;
  41874. inc(B);
  41875. end;
  41876. end;
  41877. inc(F);
  41878. until false;
  41879. // add next value as text
  41880. if ValuesIndex<=high(Values) then // missing value will display nothing
  41881. case ord(F^) of
  41882. ord('%'):
  41883. Add(Values[ValuesIndex],Escape);
  41884. {$ifdef OLDTEXTWRITERFORMAT}
  41885. ord('$'): with Values[ValuesIndex] do
  41886. if Vtype=vtInteger then Add2(VInteger);
  41887. 163: with Values[ValuesIndex] do // £
  41888. if Vtype=vtInteger then Add4(VInteger);
  41889. 181: with Values[ValuesIndex] do // µ
  41890. if Vtype=vtInteger then Add3(VInteger);
  41891. {$endif}
  41892. end;
  41893. inc(F);
  41894. inc(ValuesIndex);
  41895. until false;
  41896. end;
  41897. procedure TTextWriter.AddLine(const Text: shortstring);
  41898. begin
  41899. if BEnd-B<=ord(Text[0])+2 then
  41900. FlushToStream;
  41901. inc(B);
  41902. MoveFast(Text[1],B[0],ord(Text[0]));
  41903. inc(B,ord(Text[0]));
  41904. PWord(B)^ := 13+10 shl 8; // CR + LF
  41905. inc(B);
  41906. end;
  41907. procedure TTextWriter.AddPointer(P: PtrUInt);
  41908. procedure Pointer4ToHex(B: PWordArray; P: PtrUInt);
  41909. begin
  41910. B[3] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8;
  41911. B[2] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8;
  41912. B[1] := TwoDigitsHexWB[ToByte(P)]; P := P shr 8;
  41913. B[0] := TwoDigitsHexWB[P];
  41914. end;
  41915. begin
  41916. if BEnd-B<=sizeof(P)*2 then
  41917. FlushToStream;
  41918. {$ifdef CPU64}
  41919. if P and $ffffffff00000000<>0 then begin
  41920. BinToHexDisplay(@P,PAnsiChar(B+1),8);
  41921. inc(B,16);
  41922. exit;
  41923. end;
  41924. // truncate to 8 hexa chars for most heap-allocated pointers
  41925. {$endif}
  41926. Pointer4ToHex(@B[1],P);
  41927. inc(B,8);
  41928. end;
  41929. procedure TTextWriter.AddBinToHexDisplay(Bin: pointer; BinBytes: integer);
  41930. begin
  41931. if cardinal(BinBytes*2-1)>=cardinal(fTempBufSize) then
  41932. exit;
  41933. if BEnd-B<=BinBytes*2 then
  41934. FlushToStream;
  41935. BinToHexDisplay(Bin,PAnsiChar(B+1),BinBytes);
  41936. inc(B,BinBytes*2);
  41937. end;
  41938. procedure TTextWriter.AddBinToHex(Bin: Pointer; BinBytes: integer);
  41939. var ChunkBytes: integer;
  41940. begin
  41941. if BinBytes<=0 then
  41942. exit;
  41943. if B>=BEnd then
  41944. FlushToStream;
  41945. inc(B);
  41946. repeat
  41947. // guess biggest size to be added into buf^ at once
  41948. ChunkBytes := (BEnd-B) shr 1; // div 2, *2 -> two hexa chars per byte
  41949. if BinBytes<ChunkBytes then
  41950. ChunkBytes := BinBytes;
  41951. // add hexa characters
  41952. SynCommons.BinToHex(PAnsiChar(Bin),PAnsiChar(B),ChunkBytes);
  41953. inc(B,ChunkBytes*2);
  41954. inc(PByte(Bin),ChunkBytes);
  41955. dec(BinBytes,ChunkBytes);
  41956. if BinBytes=0 then break;
  41957. // Flush writes B-buf+1 -> special one below:
  41958. inc(fTotalFileSize,fStream.Write(fTempBuf^,B-fTempBuf));
  41959. B := fTempBuf;
  41960. until false;
  41961. dec(B); // allow CancelLastChar
  41962. end;
  41963. procedure TTextWriter.AddQuotedStr(Text: PUTF8Char; Quote: AnsiChar;
  41964. TextLen: integer=0);
  41965. var BMax: PUTF8Char;
  41966. begin
  41967. BMax := BEnd-3;
  41968. if B>=BMax then begin
  41969. FlushToStream;
  41970. BMax := BEnd-3;
  41971. end;
  41972. B[1] := Quote;
  41973. inc(B);
  41974. if Text<>nil then
  41975. repeat
  41976. if B<BMax then begin
  41977. if Text^=#0 then
  41978. break;
  41979. if TextLen>0 then begin
  41980. if TextLen=3 then begin
  41981. B[1] := '.'; // indicates truncated
  41982. B[2] := '.';
  41983. B[3] := '.';
  41984. inc(B,3);
  41985. break;
  41986. end else
  41987. dec(TextLen);
  41988. end;
  41989. if Text^<>Quote then begin
  41990. B[1] := Text^;
  41991. inc(Text);
  41992. inc(B);
  41993. end else begin
  41994. B[1] := Quote;
  41995. B[2] := Quote;
  41996. inc(B,2);
  41997. inc(Text);
  41998. end;
  41999. end else begin
  42000. FlushToStream;
  42001. BMax := BEnd-2;
  42002. end;
  42003. until false;
  42004. B[1] := Quote;
  42005. inc(B);
  42006. end;
  42007. procedure TTextWriter.AddHtmlEscape(Text: PUTF8Char; Fmt: TTextWriterHTMLFormat);
  42008. var i,beg: PtrInt;
  42009. begin
  42010. if Text=nil then
  42011. exit;
  42012. i := 0;
  42013. repeat
  42014. beg := i;
  42015. case Fmt of
  42016. hfAnyWhere:
  42017. while true do
  42018. if Text[i] in [#0,'&','"','<','>'] then
  42019. break else
  42020. inc(i);
  42021. hfOutsideAttributes:
  42022. while true do
  42023. if Text[i] in [#0,'&','<','>'] then
  42024. break else
  42025. inc(i);
  42026. hfWithinAttributes:
  42027. while true do
  42028. if Text[i] in [#0,'&','"'] then
  42029. break else
  42030. inc(i);
  42031. end;
  42032. AddNoJSONEscape(Text+beg,i-beg);
  42033. repeat
  42034. case Text[i] of
  42035. #0: exit;
  42036. '<': AddShort('&lt;');
  42037. '>': AddShort('&gt;');
  42038. '&': AddShort('&amp;');
  42039. '"': AddShort('&quot;');
  42040. else break;
  42041. end;
  42042. inc(i);
  42043. until false;
  42044. until false;
  42045. end;
  42046. procedure TTextWriter.AddHtmlEscape(Text: PUTF8Char; TextLen: integer;
  42047. Fmt: TTextWriterHTMLFormat);
  42048. var i,beg: PtrInt;
  42049. begin
  42050. if (Text=nil) or (TextLen<=0) then
  42051. exit;
  42052. i := 0;
  42053. repeat
  42054. beg := i;
  42055. case Fmt of
  42056. hfAnyWhere:
  42057. while i<TextLen do
  42058. if Text[i] in [#0,'&','"','<','>'] then
  42059. break else
  42060. inc(i);
  42061. hfOutsideAttributes:
  42062. while i<TextLen do
  42063. if Text[i] in [#0,'&','<','>'] then
  42064. break else
  42065. inc(i);
  42066. hfWithinAttributes:
  42067. while i<TextLen do
  42068. if Text[i] in [#0,'&','"'] then
  42069. break else
  42070. inc(i);
  42071. end;
  42072. AddNoJSONEscape(Text+beg,i-beg);
  42073. repeat
  42074. if i=TextLen then
  42075. exit;
  42076. case Text[i] of
  42077. #0: exit;
  42078. '<': AddShort('&lt;');
  42079. '>': AddShort('&gt;');
  42080. '&': AddShort('&amp;');
  42081. '"': AddShort('&quot;');
  42082. else break;
  42083. end;
  42084. inc(i);
  42085. until false;
  42086. until false;
  42087. end;
  42088. procedure TTextWriter.AddHtmlEscapeString(const Text: string; Fmt: TTextWriterHTMLFormat);
  42089. begin
  42090. AddHtmlEscape(pointer(StringToUTF8(Text)),Fmt);
  42091. end;
  42092. procedure TTextWriter.AddHtmlEscapeUTF8(const Text: RawUTF8; Fmt: TTextWriterHTMLFormat);
  42093. begin
  42094. AddHtmlEscape(pointer(Text),length(Text),Fmt);
  42095. end;
  42096. procedure TTextWriter.AddHtmlEscapeWiki(P: PUTF8Char);
  42097. var B: PUTF8Char;
  42098. bold,italic: boolean;
  42099. procedure Toggle(var value: Boolean; HtmlChar: AnsiChar);
  42100. begin
  42101. Add('<');
  42102. if value then
  42103. Add('/');
  42104. Add(HtmlChar,'>');
  42105. value := not value;
  42106. end;
  42107. procedure EndOfParagraph;
  42108. begin
  42109. if bold then
  42110. Toggle(bold,'B');
  42111. if italic then
  42112. Toggle(italic,'I');
  42113. AddShort('</p>');
  42114. end;
  42115. begin
  42116. bold := false;
  42117. italic := false;
  42118. AddShort('<p>');
  42119. if P<>nil then
  42120. repeat
  42121. B := P;
  42122. while not (ord(P^) in [0,13,10,ord('*'),ord('+')]) do
  42123. if (P^='h') and IdemPChar(P+1,'TTP://') then
  42124. break else
  42125. inc(P);
  42126. AddHtmlEscape(B,P-B,hfOutsideAttributes);
  42127. case ord(P^) of
  42128. 0: break;
  42129. 10,13: begin
  42130. EndOfParagraph;
  42131. AddShort('<p>');
  42132. while P[1] in [#10,#13] do inc(P);
  42133. end;
  42134. ord('*'):
  42135. Toggle(italic,'I');
  42136. ord('+'):
  42137. Toggle(bold,'B');
  42138. ord('h'): begin
  42139. B := P;
  42140. while P^>' ' do inc(P);
  42141. AddShort('<a href=');
  42142. AddHtmlEscape(B,P-B);
  42143. Add('>');
  42144. AddHtmlEscape(B,P-B);
  42145. AddShort('</a>');
  42146. continue;
  42147. end;
  42148. end;
  42149. inc(P);
  42150. until P^=#0;
  42151. EndOfParagraph;
  42152. end;
  42153. procedure TTextWriter.AddXmlEscape(Text: PUTF8Char);
  42154. const XML_ESCAPE: set of byte = [0..31,ord('<'),ord('>'),ord('&'),ord('"'),ord('''')];
  42155. var i,beg: PtrInt;
  42156. begin
  42157. if Text=nil then
  42158. exit;
  42159. i := 0;
  42160. repeat
  42161. beg := i;
  42162. if not(ord(Text[i]) in XML_ESCAPE) then begin
  42163. repeat // it is faster to handle all not-escaped chars at once
  42164. inc(i);
  42165. until ord(Text[i]) in XML_ESCAPE;
  42166. AddNoJSONEscape(Text+beg,i-beg);
  42167. end;
  42168. repeat
  42169. case Text[i] of
  42170. #0: exit;
  42171. #1..#8,#11,#12,#14..#31:
  42172. ; // ignore invalid character - see http://www.w3.org/TR/xml/#NT-Char
  42173. #9,#10,#13: begin // characters below ' ', #9 e.g. -> // '&#x09;'
  42174. AddShort('&#x');
  42175. AddByteToHex(ord(Text[i]));
  42176. Add(';');
  42177. end;
  42178. '<': AddShort('&lt;');
  42179. '>': AddShort('&gt;');
  42180. '&': AddShort('&amp;');
  42181. '"': AddShort('&quot;');
  42182. '''': AddShort('&apos;');
  42183. else break; // should match XML_ESCAPE[] constant above
  42184. end;
  42185. inc(i);
  42186. until false;
  42187. until false;
  42188. end;
  42189. procedure TTextWriter.AddReplace(Text: PUTF8Char; Orig,Replaced: AnsiChar);
  42190. begin
  42191. if Text<>nil then
  42192. while Text^<>#0 do begin
  42193. if Text^=Orig then
  42194. Add(Replaced) else
  42195. Add(Text^);
  42196. inc(Text);
  42197. end;
  42198. end;
  42199. procedure TTextWriter.AddByteToHex(Value: byte);
  42200. begin
  42201. if BEnd-B<=1 then
  42202. FlushToStream;
  42203. PWord(B+1)^ := TwoDigitsHexWB[Value];
  42204. inc(B,2);
  42205. end;
  42206. procedure TTextWriter.AddInt18ToChars3(Value: cardinal);
  42207. begin
  42208. if BEnd-B<=3 then
  42209. FlushToStream;
  42210. PCardinal(B+1)^ := ((Value shr 12) and $3f)+
  42211. ((Value shr 6) and $3f)shl 8+
  42212. (Value and $3f)shl 16+$202020;
  42213. //assert(Chars3ToInt18(B)=Value);
  42214. inc(B,3);
  42215. end;
  42216. function Chars3ToInt18(P: pointer): cardinal;
  42217. begin
  42218. result := PCardinal(P)^-$202020;
  42219. result := ((result shr 16)and $3f)+
  42220. ((result shr 8) and $3f)shl 6+
  42221. (result and $3f)shl 12;
  42222. end;
  42223. procedure TTextWriter.AddNoJSONEscape(P: Pointer);
  42224. begin
  42225. AddNoJSONEscape(P,StrLen(PUTF8Char(P)));
  42226. end;
  42227. procedure TTextWriter.AddNoJSONEscape(P: Pointer; Len: integer);
  42228. var i: integer;
  42229. begin
  42230. if (P<>nil) and (Len>0) then begin
  42231. inc(B); // allow CancelLastChar
  42232. repeat
  42233. i := BEnd-B+1; // guess biggest size to be added into buf^ at once
  42234. if Len<i then
  42235. i := Len;
  42236. // add UTF-8 bytes
  42237. MoveFast(P^,B^,i);
  42238. inc(B,i);
  42239. if i=Len then
  42240. break;
  42241. inc(PByte(P),i);
  42242. dec(Len,i);
  42243. // FlushInc writes B-buf+1 -> special one below:
  42244. inc(fTotalFileSize,fStream.Write(fTempBuf^,B-fTempBuf));
  42245. B := fTempBuf;
  42246. until false;
  42247. dec(B); // allow CancelLastChar
  42248. end;
  42249. end;
  42250. procedure TTextWriter.AddNoJSONEscapeUTF8(const text: RawByteString);
  42251. begin
  42252. AddNoJSONEscape(pointer(text),length(text));
  42253. end;
  42254. procedure TTextWriter.AddNoJSONEscapeW(WideChar: PWord; WideCharCount: integer);
  42255. var PEnd: PtrUInt;
  42256. BMax: PUTF8Char;
  42257. begin
  42258. if WideChar=nil then
  42259. exit;
  42260. BMax := BEnd-7; // ensure enough space for biggest Unicode glyph as UTF-8
  42261. if WideCharCount=0 then
  42262. repeat
  42263. if B>=BMax then begin
  42264. FlushToStream;
  42265. BMax := BEnd-7; // B may have been resized -> recompute BMax
  42266. end;
  42267. if WideChar^=0 then
  42268. break;
  42269. if WideChar^<=126 then begin
  42270. B[1] := AnsiChar(ord(WideChar^));
  42271. inc(WideChar);
  42272. inc(B);
  42273. end else
  42274. inc(B,UTF16CharToUtf8(B+1,WideChar));
  42275. until false else begin
  42276. PEnd := PtrUInt(WideChar)+PtrUInt(WideCharCount)*sizeof(WideChar^);
  42277. repeat
  42278. if B>=BMax then begin
  42279. FlushToStream;
  42280. BMax := BEnd-7;
  42281. end;
  42282. if WideChar^=0 then
  42283. break;
  42284. if WideChar^<=126 then begin
  42285. B[1] := AnsiChar(ord(WideChar^));
  42286. inc(WideChar);
  42287. inc(B);
  42288. if PtrUInt(WideChar)<PEnd then continue else break;
  42289. end;
  42290. inc(B,UTF16CharToUtf8(B+1,WideChar));
  42291. if PtrUInt(WideChar)<PEnd then continue else break;
  42292. until false;
  42293. end;
  42294. end;
  42295. procedure TTextWriter.Add(P: PUTF8Char; Escape: TTextWriterKind);
  42296. begin
  42297. if P<>nil then
  42298. case Escape of
  42299. twNone: AddNoJSONEscape(P,StrLen(P));
  42300. twJSONEscape: AddJSONEscape(P);
  42301. twOnSameLine: AddOnSameLine(P);
  42302. end;
  42303. end;
  42304. procedure TTextWriter.Add(P: PUTF8Char; Len: PtrInt; Escape: TTextWriterKind);
  42305. begin
  42306. if P<>nil then
  42307. case Escape of
  42308. twNone: AddNoJSONEscape(P,Len);
  42309. twJSONEscape: AddJSONEscape(P,Len);
  42310. twOnSameLine: AddOnSameLine(P,Len);
  42311. end;
  42312. end;
  42313. procedure TTextWriter.AddW(P: PWord; Len: PtrInt; Escape: TTextWriterKind);
  42314. begin
  42315. if P<>nil then
  42316. case Escape of
  42317. twNone: AddNoJSONEscapeW(P,Len);
  42318. twJSONEscape: AddJSONEScapeW(P,Len);
  42319. twOnSameLine: AddOnSameLineW(P,Len);
  42320. end;
  42321. end;
  42322. procedure TTextWriter.AddAnsiString(const s: AnsiString; Escape: TTextWriterKind);
  42323. begin
  42324. AddAnyAnsiBuffer(pointer(s),length(s),Escape,0);
  42325. end;
  42326. procedure TTextWriter.AddAnyAnsiString(const s: RawByteString;
  42327. Escape: TTextWriterKind; CodePage: Integer);
  42328. var L: integer;
  42329. begin
  42330. L := length(s);
  42331. if L=0 then
  42332. exit;
  42333. if PInteger(s)^ and $ffffff=JSON_BASE64_MAGIC then begin
  42334. AddNoJSONEscape(pointer(s),L); // identified as a BLOB content
  42335. exit;
  42336. end;
  42337. if CodePage<0 then
  42338. {$ifdef HASCODEPAGE}
  42339. CodePage := StringCodePage(s);
  42340. {$else}
  42341. CodePage := 0; // TSynAnsiConvert.Engine(0)=CurrentAnsiConvert
  42342. {$endif}
  42343. AddAnyAnsiBuffer(pointer(s),L,Escape,CodePage);
  42344. end;
  42345. procedure TTextWriter.AddAnyAnsiBuffer(P: PAnsiChar; Len: integer;
  42346. Escape: TTextWriterKind; CodePage: Integer);
  42347. var B: PUTF8Char;
  42348. begin
  42349. if Len>0 then
  42350. case CodePage of
  42351. CP_UTF8, CP_RAWBYTESTRING:
  42352. Add(PUTF8Char(P),Len,Escape); // direct write of RawUTF8/RawByteString content
  42353. CP_UTF16:
  42354. AddW(PWord(P),0,Escape); // direct write of UTF-16 content
  42355. CP_SQLRAWBLOB: begin
  42356. AddNoJSONEscape(@PByteArray(@JSON_BASE64_MAGIC_QUOTE_VAR)[1],3);
  42357. WrBase64(P,Len,false);
  42358. end;
  42359. else begin
  42360. // first handle trailing 7 bit ASCII chars, by quad
  42361. B := pointer(P);
  42362. if Len>=4 then
  42363. repeat
  42364. if PCardinal(P)^ and $80808080<>0 then
  42365. break; // break on first non ASCII quad
  42366. inc(P,4);
  42367. dec(Len,4);
  42368. until Len<4;
  42369. if (Len>0) and (P^<#128) then
  42370. repeat
  42371. inc(P);
  42372. dec(Len);
  42373. until (Len=0) or (P^>=#127);
  42374. Add(B,P-B,Escape);
  42375. if Len=0 then
  42376. exit;
  42377. // rely on explicit conversion for all remaining ASCII characters
  42378. TSynAnsiConvert.Engine(CodePage).InternalAppendUTF8(P,Len,self,Escape);
  42379. end;
  42380. end;
  42381. end;
  42382. const // see http://www.ietf.org/rfc/rfc4627.txt
  42383. JSON_ESCAPE: set of byte = [0..31,ord('\'),ord('"')];
  42384. function NeedsJsonEscape(const Text: RawUTF8): boolean;
  42385. var i: integer;
  42386. begin
  42387. result := true;
  42388. for i := 1 to length(Text) do
  42389. if byte(Text[i]) in JSON_ESCAPE then
  42390. exit;
  42391. result := false;
  42392. end;
  42393. procedure TTextWriter.InternalAddFixedAnsi(Source: PAnsiChar; SourceChars: Cardinal;
  42394. const AnsiToWide: TWordDynArray; Escape: TTextWriterKind);
  42395. var c: cardinal;
  42396. begin
  42397. while SourceChars>0 do begin
  42398. c := byte(Source^);
  42399. if c<=$7F then begin
  42400. if B>=BEnd then
  42401. FlushToStream;
  42402. case Escape of
  42403. twNone: begin
  42404. inc(B);
  42405. B^ := AnsiChar(c);
  42406. end;
  42407. twJSONEscape:
  42408. if c in JSON_ESCAPE then
  42409. AddJsonEscape(Source,1) else begin
  42410. inc(B);
  42411. B^ := AnsiChar(c);
  42412. end;
  42413. twOnSameLine: begin
  42414. inc(B);
  42415. if c<32 then
  42416. B^ := ' ' else
  42417. B^ := AnsiChar(c);
  42418. end;
  42419. end
  42420. end else begin // no surrogate is expected in TSynAnsiFixedWidth charsets
  42421. if BEnd-B<=3 then
  42422. FlushToStream;
  42423. c := AnsiToWide[c]; // convert FixedAnsi char into Unicode char
  42424. if c>$7ff then begin
  42425. B[1] := AnsiChar($E0 or (c shr 12));
  42426. B[2] := AnsiChar($80 or ((c shr 6) and $3F));
  42427. B[3] := AnsiChar($80 or (c and $3F));
  42428. inc(B,3);
  42429. end else begin
  42430. B[1] := AnsiChar($C0 or (c shr 6));
  42431. B[2] := AnsiChar($80 or (c and $3F));
  42432. inc(B,2);
  42433. end;
  42434. end;
  42435. dec(SourceChars);
  42436. inc(Source);
  42437. end;
  42438. end;
  42439. procedure TTextWriter.AddOnSameLine(P: PUTF8Char);
  42440. begin
  42441. if P<>nil then
  42442. while P^<>#0 do begin
  42443. if B>=BEnd then
  42444. FlushToStream;
  42445. if P^<' ' then
  42446. B[1] := ' ' else
  42447. B[1] := P^;
  42448. inc(P);
  42449. inc(B);
  42450. end;
  42451. end;
  42452. procedure TTextWriter.AddOnSameLine(P: PUTF8Char; Len: PtrInt);
  42453. var i: PtrInt;
  42454. begin
  42455. if P<>nil then
  42456. for i := 0 to Len-1 do begin
  42457. if B>=BEnd then
  42458. FlushToStream;
  42459. if P[i]<' ' then
  42460. B[1] := ' ' else
  42461. B[1] := P[i];
  42462. inc(B);
  42463. end;
  42464. end;
  42465. procedure TTextWriter.AddOnSameLineW(P: PWord; Len: PtrInt);
  42466. var PEnd: PtrUInt;
  42467. begin
  42468. if P=nil then exit;
  42469. if Len=0 then
  42470. PEnd := 0 else
  42471. PEnd := PtrUInt(P)+PtrUInt(Len)*sizeof(WideChar);
  42472. while (Len=0) or (PtrUInt(P)<PEnd) do begin
  42473. if BEnd-B<=7 then
  42474. FlushToStream;
  42475. // escape chars, so that all content will stay on the same text line
  42476. case P^ of
  42477. 0: break;
  42478. 1..32: begin
  42479. B[1] := ' ';
  42480. inc(B);
  42481. inc(P);
  42482. end;
  42483. 33..126: begin
  42484. B[1] := AnsiChar(ord(P^)); // direct store 7 bits ASCII
  42485. inc(B);
  42486. inc(P);
  42487. end;
  42488. else // characters higher than #126 -> UTF-8 encode
  42489. inc(B,UTF16CharToUtf8(B+1,P));
  42490. end;
  42491. end;
  42492. end;
  42493. procedure TTextWriter.AddJSONEscape(P: Pointer; Len: PtrInt);
  42494. var i,c: integer;
  42495. label noesc;
  42496. begin
  42497. if P=nil then
  42498. exit;
  42499. if Len=0 then
  42500. Len := MaxInt;
  42501. i := 0;
  42502. while i<Len do begin
  42503. if not(PByteArray(P)[i] in JSON_ESCAPE) then begin
  42504. noesc:c := i;
  42505. repeat
  42506. inc(i);
  42507. until (i>=Len) or (PByteArray(P)[i] in JSON_ESCAPE);
  42508. inc(PByte(P),c);
  42509. dec(i,c);
  42510. dec(Len,c);
  42511. if BEnd-B<=i then
  42512. AddNoJSONEscape(P,i) else begin
  42513. MoveFast(P^,B[1],i);
  42514. inc(B,i);
  42515. end;
  42516. end;
  42517. while i<Len do begin
  42518. c := PByteArray(P)[i];
  42519. case c of
  42520. 0: exit;
  42521. 8: c := ord('\')+ord('b')shl 8;
  42522. 9: c := ord('\')+ord('t')shl 8;
  42523. 10: c := ord('\')+ord('n')shl 8;
  42524. 12: c := ord('\')+ord('f')shl 8;
  42525. 13: c := ord('\')+ord('r')shl 8;
  42526. ord('\'): c := ord('\')+ord('\')shl 8;
  42527. ord('"'): c := ord('\')+ord('"')shl 8;
  42528. 1..7,11,14..31: begin // characters below ' ', #7 e.g. -> // 'u0007'
  42529. AddShort('\u00');
  42530. c := TwoDigitsHexWB[c];
  42531. end;
  42532. else goto noesc;
  42533. end;
  42534. if BEnd-B<=1 then
  42535. FlushToStream;
  42536. PWord(B+1)^ := c;
  42537. inc(B,2);
  42538. inc(i);
  42539. end;
  42540. end;
  42541. end;
  42542. procedure TTextWriter.AddJSONEscapeW(P: PWord; Len: PtrInt);
  42543. var i,c: PtrInt;
  42544. begin
  42545. if P=nil then
  42546. exit;
  42547. if Len=0 then
  42548. Len := MaxInt;
  42549. i := 0;
  42550. while i<Len do begin
  42551. c := i;
  42552. if not(PWordArray(P)[i] in JSON_ESCAPE) then begin
  42553. repeat
  42554. inc(i);
  42555. until (i>=Len) or (PWordArray(P)[i] in JSON_ESCAPE);
  42556. AddNoJSONEscapeW(@PWordArray(P)[c],i-c);
  42557. end;
  42558. while i<Len do begin
  42559. c := PWordArray(P)[i];
  42560. case c of
  42561. 0: exit;
  42562. 8: Add('\','b');
  42563. 9: Add('\','t');
  42564. 10: Add('\','n');
  42565. 12: Add('\','f');
  42566. 13: Add('\','r');
  42567. ord('\'),ord('"'): Add('\',AnsiChar(c));
  42568. 1..7,11,14..31: begin // characters below ' ', #7 e.g. -> // 'u0007'
  42569. AddShort('\u00');
  42570. AddByteToHex(c);
  42571. end;
  42572. else break;
  42573. end;
  42574. inc(i);
  42575. end;
  42576. end;
  42577. end;
  42578. procedure TTextWriter.AddJSONEscape(const V: TVarRec);
  42579. begin
  42580. with V do
  42581. case VType of
  42582. vtPointer: AddShort('null');
  42583. vtString, vtAnsiString,{$ifdef HASVARUSTRING}vtUnicodeString,{$endif}
  42584. vtPChar, vtChar, vtWideChar, vtWideString, vtClass: begin
  42585. Add('"');
  42586. case VType of
  42587. vtString: AddJSONEscape(@VString^[1],ord(VString^[0]));
  42588. vtAnsiString: AddJSONEscape(pointer(RawUTF8(VAnsiString)));
  42589. {$ifdef HASVARUSTRING}
  42590. vtUnicodeString: AddJSONEscapeW(
  42591. pointer(UnicodeString(VUnicodeString)),length(UnicodeString(VUnicodeString)));
  42592. {$endif}
  42593. vtPChar: AddJSONEscape(VPChar);
  42594. vtChar: AddJSONEscape(@VChar,1);
  42595. vtWideChar: AddJSONEscapeW(@VWideChar,1);
  42596. vtWideString: AddJSONEscapeW(VWideString);
  42597. vtClass: AddClassName(VClass);
  42598. end;
  42599. Add('"');
  42600. end;
  42601. vtBoolean: Add(VBoolean);
  42602. vtInteger: Add(VInteger);
  42603. vtInt64: Add(VInt64^);
  42604. vtExtended: Add(VExtended^,DOUBLE_PRECISION);
  42605. vtCurrency: AddCurr64(VInt64^);
  42606. vtObject: WriteObject(VObject);
  42607. {$ifndef NOVARIANTS}
  42608. vtVariant: AddVariant(VVariant^,twJSONEscape);
  42609. {$endif}
  42610. end;
  42611. end;
  42612. procedure TTextWriter.Add(const V: TVarRec; Escape: TTextWriterKind);
  42613. begin
  42614. with V do
  42615. case Vtype of
  42616. vtInteger: Add(VInteger);
  42617. vtBoolean: AddU(byte(VBoolean));
  42618. vtChar: Add(@VChar,1,Escape);
  42619. vtExtended: Add(VExtended^,DOUBLE_PRECISION);
  42620. vtString: Add(@VString^[1],ord(VString^[0]),Escape);
  42621. vtInterface,
  42622. vtPointer: AddPointer(PtrUInt(VPointer));
  42623. vtPChar: Add(PUTF8Char(VPChar),Escape);
  42624. vtObject: WriteObject(VObject,[woFullExpand]);
  42625. vtClass: AddClassName(VClass);
  42626. vtWideChar:
  42627. AddW(@VWideChar,1,Escape);
  42628. vtPWideChar:
  42629. AddW(pointer(VPWideChar),StrLenW(VPWideChar),Escape);
  42630. vtAnsiString:
  42631. Add(VAnsiString,length(RawUTF8(VAnsiString)),Escape); // expect RawUTF8
  42632. vtCurrency:
  42633. AddCurr64(VInt64^);
  42634. vtWideString:
  42635. if VWideString<>nil then
  42636. AddW(VWideString,length(WideString(VWideString)),Escape);
  42637. vtInt64:
  42638. Add(VInt64^);
  42639. {$ifndef NOVARIANTS}
  42640. vtVariant:
  42641. AddVariant(VVariant^,Escape);
  42642. {$endif}
  42643. {$ifdef HASVARUSTRING}
  42644. vtUnicodeString:
  42645. if VUnicodeString<>nil then // convert to UTF-8
  42646. AddW(VUnicodeString,length(UnicodeString(VUnicodeString)),Escape);
  42647. {$endif} end;
  42648. end;
  42649. {$ifndef NOVARIANTS}
  42650. procedure TTextWriter.AddJSON(const Format: RawUTF8; const Args,Params: array of const);
  42651. begin
  42652. AddVariant(_JsonFastFmt(Format,Args,Params),twJSONEscape);
  42653. end;
  42654. {$endif}
  42655. procedure TTextWriter.AddJSONArraysAsJSONObject(keys,values: PUTF8Char);
  42656. var k,v: PUTF8Char;
  42657. begin
  42658. if (keys=nil) or (keys^<>'[') or (values=nil) or (values^<>'[') then begin
  42659. AddShort('null');
  42660. exit;
  42661. end;
  42662. inc(keys); // jump initial [
  42663. inc(values);
  42664. Add('{');
  42665. repeat
  42666. k := GotoEndJSONItem(keys);
  42667. v := GotoEndJSONItem(values);
  42668. if (k=nil) or (v=nil) then
  42669. break; // invalid JSON input
  42670. AddNoJSONEscape(keys,k-keys);
  42671. Add(':');
  42672. AddNoJSONEscape(values,v-values);
  42673. Add(',');
  42674. if (k^<>',') or (v^<>',') then
  42675. break; // reached the end of the input JSON arrays
  42676. keys := k+1;
  42677. values := v+1;
  42678. until false;
  42679. CancelLastComma;
  42680. Add('}');
  42681. end;
  42682. procedure TTextWriter.AddJSONEscape(const NameValuePairs: array of const);
  42683. var a: integer;
  42684. procedure WriteValue;
  42685. begin
  42686. case VarRecAsChar(NameValuePairs[a]) of
  42687. ord('['): begin
  42688. Add('[');
  42689. while a<high(NameValuePairs) do begin
  42690. inc(a);
  42691. if VarRecAsChar(NameValuePairs[a])=ord(']') then
  42692. break;
  42693. WriteValue;
  42694. end;
  42695. CancelLastComma;
  42696. Add(']');
  42697. end;
  42698. ord('{'): begin
  42699. Add('{');
  42700. while a<high(NameValuePairs) do begin
  42701. inc(a);
  42702. if VarRecAsChar(NameValuePairs[a])=ord('}') then
  42703. break;
  42704. AddJSONEscape(NameValuePairs[a]);
  42705. Add(':');
  42706. inc(a);
  42707. WriteValue;
  42708. end;
  42709. CancelLastComma;
  42710. Add('}');
  42711. end else
  42712. AddJSONEscape(NameValuePairs[a]);
  42713. end;
  42714. Add(',');
  42715. end;
  42716. begin
  42717. Add('{');
  42718. a := 0;
  42719. while a<high(NameValuePairs) do begin
  42720. AddJSONEscape(NameValuePairs[a]);
  42721. inc(a);
  42722. Add(':');
  42723. WriteValue;
  42724. inc(a);
  42725. end;
  42726. CancelLastComma;
  42727. Add('}');
  42728. end;
  42729. procedure TTextWriter.AddNoJSONEscapeString(const s: string);
  42730. begin
  42731. if s<>'' then
  42732. {$ifdef UNICODE}
  42733. AddNoJSONEscapeW(pointer(s),length(s));
  42734. {$else}
  42735. AddAnsiString(s,twNone);
  42736. {$endif}
  42737. end;
  42738. procedure TTextWriter.AddJSONEscapeString(const s: string);
  42739. begin
  42740. if s<>'' then
  42741. {$ifdef UNICODE}
  42742. AddJSONEscapeW(pointer(s),Length(s));
  42743. {$else}
  42744. AddAnyAnsiString(s,twJSONEscape,0);
  42745. {$endif}
  42746. end;
  42747. procedure TTextWriter.AddJSONEscapeAnsiString(const s: AnsiString);
  42748. begin
  42749. AddAnyAnsiString(s,twJSONEscape,0);
  42750. end;
  42751. procedure TTextWriter.AddPropName(const PropName: ShortString);
  42752. begin
  42753. if ord(PropName[0])=0 then
  42754. exit;
  42755. if BEnd-B<=ord(PropName[0])+3 then
  42756. FlushToStream;
  42757. if twoForceJSONExtended in CustomOptions then begin
  42758. MoveFast(PropName[1],B[1],ord(PropName[0]));
  42759. inc(B,ord(PropName[0])+1);
  42760. B^ := ':';
  42761. end else begin
  42762. B[1] := '"';
  42763. MoveFast(PropName[1],B[2],ord(PropName[0]));
  42764. inc(B,ord(PropName[0])+2);
  42765. PWord(B)^ := ord('"')+ord(':')shl 8;
  42766. inc(B);
  42767. end;
  42768. end;
  42769. procedure TTextWriter.AddFieldName(const FieldName: RawUTF8);
  42770. begin
  42771. AddFieldName(Pointer(FieldName),length(FieldName));
  42772. end;
  42773. procedure TTextWriter.AddFieldName(FieldName: PUTF8Char; FieldNameLen: integer);
  42774. begin
  42775. if BEnd-B<=FieldNameLen+3 then
  42776. FlushToStream;
  42777. B[1] := '"';
  42778. MoveFast(FieldName^,B[2],FieldNameLen);
  42779. inc(B,FieldNameLen+2);
  42780. PWord(B)^ := ord('"')+ord(':')shl 8;
  42781. inc(B);
  42782. end;
  42783. procedure TTextWriter.AddClassName(aClass: TClass);
  42784. begin
  42785. if aClass<>nil then
  42786. AddShort(PShortString(PPointer(PtrInt(aClass)+vmtClassName)^)^);
  42787. end;
  42788. procedure TTextWriter.AddInstanceName(Instance: TObject; SepChar: AnsiChar);
  42789. begin
  42790. Add('"');
  42791. if Instance=nil then
  42792. AddShort('void') else
  42793. AddShort(PShortString(PPointer(PPtrInt(Instance)^+vmtClassName)^)^);
  42794. Add('(');
  42795. AddPointer(PtrUInt(Instance));
  42796. Add(')','"');
  42797. if SepChar<>#0 then
  42798. Add(SepChar);
  42799. end;
  42800. procedure TTextWriter.AddInstancePointer(Instance: TObject; SepChar: AnsiChar;
  42801. IncludeUnitName: boolean);
  42802. begin
  42803. AddShort(PShortString(PPointer(PPtrInt(Instance)^+vmtClassName)^)^);
  42804. Add('(');
  42805. AddPointer(PtrUInt(Instance));
  42806. Add(')');
  42807. if SepChar<>#0 then
  42808. Add(SepChar);
  42809. end;
  42810. procedure TTextWriter.AddShort(const Text: ShortString);
  42811. begin
  42812. if ord(Text[0])=0 then
  42813. exit;
  42814. if BEnd-B<=ord(Text[0]) then
  42815. FlushToStream;
  42816. MoveFast(Text[1],B[1],ord(Text[0]));
  42817. inc(B,ord(Text[0]));
  42818. end;
  42819. procedure TTextWriter.AddQuotedStringAsJSON(const QuotedString: RawUTF8);
  42820. var L: integer;
  42821. P,B: PUTF8Char;
  42822. quote: AnsiChar;
  42823. begin
  42824. L := length(QuotedString);
  42825. if L>0 then begin
  42826. quote := QuotedString[1];
  42827. if (quote in ['''','"']) and (QuotedString[L]=quote) then begin
  42828. Add('"');
  42829. P := pointer(QuotedString);
  42830. inc(P);
  42831. repeat
  42832. B := P;
  42833. while P[0]<>quote do inc(P);
  42834. if P[1]<>quote then
  42835. break; // end quote
  42836. inc(P);
  42837. AddJSONEscape(B,P-B);
  42838. inc(P); // ignore double quote
  42839. until false;
  42840. if P-B<>0 then
  42841. AddJSONEscape(B,P-B);
  42842. Add('"');
  42843. end else
  42844. AddNoJSONEscape(pointer(QuotedString),length(QuotedString));
  42845. end;
  42846. end;
  42847. procedure TTextWriter.AddTrimLeftLowerCase(Text: PShortString);
  42848. var P: PAnsiChar;
  42849. L: integer;
  42850. begin
  42851. L := length(Text^);
  42852. P := @Text^[1];
  42853. while (L>0) and (P^ in ['a'..'z']) do begin
  42854. inc(P);
  42855. dec(L);
  42856. end;
  42857. if L=0 then
  42858. AddShort(Text^) else
  42859. AddNoJSONEscape(P,L);
  42860. end;
  42861. procedure TTextWriter.AddString(const Text: RawUTF8);
  42862. var L: integer;
  42863. begin
  42864. if PtrInt(Text)=0 then
  42865. exit;
  42866. {$ifdef FPC}
  42867. L := PStrRec(Pointer(PtrInt(Text)-STRRECSIZE))^.length;
  42868. {$else}
  42869. L := PInteger(PtrInt(Text)-sizeof(integer))^;
  42870. {$endif}
  42871. if L<fTempBufSize then begin
  42872. if BEnd-B<=L then
  42873. FlushToStream;
  42874. MoveFast(pointer(Text)^,B[1],L);
  42875. inc(B,L);
  42876. end else
  42877. AddNoJSONEscape(pointer(Text),L);
  42878. end;
  42879. procedure TTextWriter.AddStringCopy(const Text: RawUTF8; start,len: integer);
  42880. var L: integer;
  42881. begin
  42882. if (len<=0) or (PtrInt(Text)=0) then
  42883. exit;
  42884. if start<0 then
  42885. start := 0 else
  42886. dec(start);
  42887. {$ifdef FPC}
  42888. L := PStrRec(Pointer(PtrInt(Text)-STRRECSIZE))^.length;
  42889. {$else}
  42890. L := PInteger(PtrInt(Text)-sizeof(integer))^;
  42891. {$endif}
  42892. dec(L,start);
  42893. if L>0 then begin
  42894. if len<L then
  42895. L := len;
  42896. AddNoJSONEscape(@PByteArray(Text)[start],L);
  42897. end;
  42898. end;
  42899. procedure TTextWriter.AddStrings(const Text: array of RawUTF8);
  42900. var i: integer;
  42901. begin
  42902. for i := 0 to high(Text) do
  42903. AddString(Text[i]);
  42904. end;
  42905. procedure TTextWriter.AddStrings(const Text: RawUTF8; count: integer);
  42906. var i,L: integer;
  42907. begin
  42908. L := length(Text);
  42909. if L*count>fTempBufSize then
  42910. for i := 1 to count do
  42911. AddString(Text) else begin
  42912. if BEnd-B<=L*count then
  42913. FlushToStream;
  42914. for i := 1 to count do begin
  42915. MoveFast(pointer(Text)^,B[1],L);
  42916. inc(B,L);
  42917. end;
  42918. end;
  42919. end;
  42920. procedure TTextWriter.CancelAll;
  42921. begin
  42922. if self=nil then
  42923. exit; // avoid GPF
  42924. if fTotalFileSize<>0 then
  42925. fTotalFileSize := fStream.Seek(fInitialStreamPosition,soBeginning);
  42926. B := fTempBuf-1;
  42927. end;
  42928. procedure TTextWriter.CancelLastChar;
  42929. begin
  42930. dec(B);
  42931. end;
  42932. function TTextWriter.LastChar: AnsiChar;
  42933. begin
  42934. result := B^;
  42935. end;
  42936. procedure TTextWriter.CancelLastChar(aCharToCancel: AnsiChar);
  42937. begin
  42938. if B^=aCharToCancel then
  42939. dec(B);
  42940. end;
  42941. function TTextWriter.PendingBytes: PtrUInt;
  42942. begin
  42943. result := B-fTempBuf;
  42944. end;
  42945. procedure TTextWriter.CancelLastComma;
  42946. begin
  42947. if B^=',' then
  42948. dec(B);
  42949. end;
  42950. constructor TTextWriter.Create(aStream: TStream; aBufSize: integer);
  42951. begin
  42952. SetStream(aStream);
  42953. if aBufSize<256 then
  42954. aBufSize := 256;
  42955. fTempBufSize := aBufSize;
  42956. GetMem(fTempBuf,aBufSize);
  42957. B := fTempBuf-1; // Add() methods will append at B+1
  42958. BEnd := fTempBuf+fTempBufSize-2;
  42959. if DefaultTextWriterTrimEnum then
  42960. Include(fCustomOptions,twoTrimLeftEnumSets);
  42961. end;
  42962. constructor TTextWriter.CreateOwnedStream(aBufSize: integer);
  42963. begin
  42964. Create(TRawByteStringStream.Create,aBufSize);
  42965. Include(fCustomOptions,twoStreamIsOwned);
  42966. end;
  42967. constructor TTextWriter.CreateOwnedFileStream(const aFileName: TFileName;
  42968. aBufSize: integer);
  42969. begin
  42970. DeleteFile(aFileName);
  42971. Create(TFileStream.Create(aFileName,fmCreate),aBufSize);
  42972. Include(fCustomOptions,twoStreamIsOwned);
  42973. end;
  42974. destructor TTextWriter.Destroy;
  42975. begin
  42976. if twoStreamIsOwned in fCustomOptions then
  42977. fStream.Free;
  42978. FreeMem(fTempBuf);
  42979. fInternalJSONWriter.Free;
  42980. inherited;
  42981. end;
  42982. class procedure TTextWriter.SetDefaultJSONClass(aClass: TTextWriterClass);
  42983. begin
  42984. DefaultTextWriterJSONClass := aClass;
  42985. end;
  42986. class procedure TTextWriter.SetDefaultEnumTrim(aShouldTrimEnumsAsText: boolean);
  42987. begin
  42988. DefaultTextWriterTrimEnum := aShouldTrimEnumsAsText;
  42989. end;
  42990. procedure TTextWriter.SetStream(aStream: TStream);
  42991. begin
  42992. if fStream<>nil then
  42993. if twoStreamIsOwned in fCustomOptions then begin
  42994. FreeAndNil(fStream);
  42995. Exclude(fCustomOptions,twoStreamIsOwned);
  42996. end;
  42997. if aStream<>nil then begin
  42998. fStream := aStream;
  42999. fInitialStreamPosition := fStream.Seek(0,soFromCurrent);
  43000. fTotalFileSize := fInitialStreamPosition;
  43001. end;
  43002. end;
  43003. procedure TTextWriter.FlushToStream;
  43004. begin
  43005. if fEchos<>nil then begin
  43006. EchoFlush;
  43007. fEchoStart := 0;
  43008. end;
  43009. inc(fTotalFileSize,fStream.Write(fTempBuf^,B-fTempBuf+1));
  43010. if (not (twoFlushToStreamNoAutoResize in fCustomOptions)) and
  43011. (fTempBufSize<49152) and
  43012. (fTotalFileSize-fInitialStreamPosition>1 shl 18) then begin
  43013. FreeMem(fTempBuf); // with big content (256KB) comes bigger buffer (64KB)
  43014. fTempBufSize := 65536;
  43015. GetMem(fTempBuf,65536);
  43016. BEnd := fTempBuf+(65536-2);
  43017. end;
  43018. B := fTempBuf-1;
  43019. end;
  43020. function TTextWriter.GetEndOfLineCRLF: boolean;
  43021. begin
  43022. result := twoEndOfLineCRLF in fCustomOptions;
  43023. end;
  43024. procedure TTextWriter.SetEndOfLineCRLF(aEndOfLineCRLF: boolean);
  43025. begin
  43026. Include(fCustomOptions,twoEndOfLineCRLF);
  43027. end;
  43028. function TTextWriter.GetLength: cardinal;
  43029. begin
  43030. if self=nil then
  43031. result := 0 else
  43032. result := cardinal(B-fTempBuf+1)+fTotalFileSize-fInitialStreamPosition;
  43033. end;
  43034. function TTextWriter.Text: RawUTF8;
  43035. begin
  43036. SetText(result);
  43037. end;
  43038. procedure TTextWriter.ForceContent(const text: RawUTF8);
  43039. begin
  43040. CancelAll;
  43041. if (fInitialStreamPosition=0) and fStream.InheritsFrom(TRawByteStringStream) then
  43042. TRawByteStringStream(fStream).fDataString := text else
  43043. fStream.Write(pointer(text)^,length(text));
  43044. fTotalFileSize := fInitialStreamPosition+cardinal(length(text));
  43045. end;
  43046. procedure TTextWriter.FlushFinal;
  43047. begin
  43048. Include(fCustomOptions,twoFlushToStreamNoAutoResize);
  43049. FlushToStream;
  43050. end;
  43051. procedure TTextWriter.SetText(var result: RawUTF8);
  43052. var Len: cardinal;
  43053. begin
  43054. FlushFinal;
  43055. Len := fTotalFileSize-fInitialStreamPosition;
  43056. if Len=0 then
  43057. result := '' else
  43058. if fStream.InheritsFrom(TRawByteStringStream) then
  43059. with TRawByteStringStream(fStream) do
  43060. if fInitialStreamPosition=0 then begin
  43061. {$ifdef HASCODEPAGE} // FPC expects this
  43062. SetCodePage(fDataString,CP_UTF8,false);
  43063. {$endif}
  43064. result := fDataString;
  43065. end else
  43066. SetRawUTF8(result,PAnsiChar(pointer(DataString))+fInitialStreamPosition,Len) else
  43067. if fStream.InheritsFrom(TCustomMemoryStream) then
  43068. with TCustomMemoryStream(fStream) do
  43069. SetRawUTF8(result,PAnsiChar(Memory)+fInitialStreamPosition,Len) else begin
  43070. FastNewRawUTF8(result,Len);
  43071. fStream.Seek(fInitialStreamPosition,soBeginning);
  43072. fStream.Read(pointer(result)^,Len);
  43073. end;
  43074. end;
  43075. procedure TTextWriter.WrRecord(const Rec; TypeInfo: pointer);
  43076. var L: integer;
  43077. tmp: RawByteString;
  43078. begin
  43079. L := RecordSaveLength(Rec,TypeInfo);
  43080. SetString(tmp,nil,L);
  43081. if L<>0 then
  43082. RecordSave(Rec,pointer(tmp),TypeInfo);
  43083. WrBase64(pointer(tmp),L,true);
  43084. end;
  43085. procedure TTextWriter.WrBase64(P: PAnsiChar; Len: cardinal; withMagic: boolean);
  43086. var trailing, main, n: cardinal;
  43087. begin
  43088. if withMagic then
  43089. if len<=0 then begin
  43090. AddShort('null'); // JSON null is better than "" for BLOBs
  43091. exit;
  43092. end else
  43093. AddNoJSONEscape(@JSON_BASE64_MAGIC_QUOTE_VAR,4);
  43094. if len>0 then begin
  43095. n := Len div 3;
  43096. trailing := Len-n*3;
  43097. dec(Len,trailing);
  43098. if BEnd-B>integer(n+1) shl 2 then begin
  43099. // will fit in available space in Buf -> fast in-buffer Base64 encoding
  43100. n := Base64EncodeMain(@B[1],P,Len);
  43101. inc(B,n*4);
  43102. inc(P,n*3);
  43103. end else begin
  43104. // bigger than available space in Buf -> do it per chunk
  43105. FlushToStream;
  43106. while Len>0 do begin // length(buf) const -> so is ((length(buf)-4)shr2 )*3
  43107. n := ((fTempBufSize-4)shr 2)*3;
  43108. if Len<n then
  43109. n := Len;
  43110. main := Base64EncodeMain(PAnsiChar(fTempBuf),P,n);
  43111. n := main*4;
  43112. if n<cardinal(fTempBufSize)-4 then
  43113. inc(B,n) else
  43114. inc(fTotalFileSize,fStream.Write(fTempBuf^,n));
  43115. n := main*3;
  43116. inc(P,n);
  43117. dec(Len,n);
  43118. end;
  43119. end;
  43120. if trailing>0 then begin
  43121. Base64EncodeTrailing(@B[1],P,trailing);
  43122. inc(B,4);
  43123. end;
  43124. end;
  43125. if withMagic then
  43126. Add('"');
  43127. end;
  43128. procedure TTextWriter.EchoAdd(const aEcho: TOnTextWriterEcho);
  43129. begin
  43130. if self<>nil then
  43131. if MultiEventAdd(fEchos,TMethod(aEcho)) then
  43132. if fEchos<>nil then
  43133. fEchoStart := B-fTempBuf+1; // ignore any previous buffer
  43134. end;
  43135. procedure TTextWriter.EchoRemove(const aEcho: TOnTextWriterEcho);
  43136. begin
  43137. if self<>nil then
  43138. MultiEventRemove(fEchos,TMethod(aEcho));
  43139. end;
  43140. function TTextWriter.EchoFlush: integer;
  43141. var L,LI: Integer;
  43142. P: PByteArray;
  43143. begin
  43144. result := B-fTempBuf+1;
  43145. L := result-fEchoStart;
  43146. P := @PByteArray(fTempBuf)[fEchoStart];
  43147. while (L>0) and (P[L-1] in [10,13]) do // trim right CR/LF chars
  43148. dec(L);
  43149. LI := length(fEchoBuf); // faster append to fEchoBuf
  43150. SetLength(fEchoBuf,LI+L);
  43151. MoveFast(P^,PByteArray(fEchoBuf)[LI],L);
  43152. end;
  43153. procedure TTextWriter.EchoReset;
  43154. begin
  43155. fEchoBuf := '';
  43156. end;
  43157. { TJSONWriter }
  43158. procedure TJSONWriter.CancelAllVoid;
  43159. const VOIDARRAY: PAnsiChar = '[]'#10;
  43160. VOIDFIELD: PAnsiChar = '{"FieldCount":0}';
  43161. begin
  43162. CancelAll; // rewind JSON
  43163. if fExpand then // same as sqlite3_get_table()
  43164. inc(fTotalFileSize,fStream.Write(VOIDARRAY^,3)) else
  43165. inc(fTotalFileSize,fStream.Write(VOIDFIELD^,16));
  43166. end;
  43167. constructor TJSONWriter.Create(aStream: TStream; Expand, withID: boolean;
  43168. const Fields: TSQLFieldBits);
  43169. begin
  43170. Create(aStream,Expand,withID,FieldBitsToIndex(Fields));
  43171. end;
  43172. constructor TJSONWriter.Create(aStream: TStream; Expand, withID: boolean;
  43173. const Fields: TSQLFieldIndexDynArray);
  43174. begin
  43175. if aStream=nil then
  43176. CreateOwnedStream else
  43177. inherited Create(aStream);
  43178. fExpand := Expand;
  43179. fWithID := withID;
  43180. fFields := Fields;
  43181. end;
  43182. procedure TJSONWriter.AddColumns(aKnownRowsCount: integer);
  43183. var i: integer;
  43184. begin
  43185. if fExpand then begin
  43186. if twoForceJSONExtended in CustomOptions then
  43187. for i := 0 to High(ColNames) do
  43188. ColNames[i] := ColNames[i]+':' else
  43189. for i := 0 to High(ColNames) do
  43190. ColNames[i] := '"'+ColNames[i]+'":';
  43191. end else begin
  43192. AddShort('{"fieldCount":');
  43193. Add(length(ColNames));
  43194. if aKnownRowsCount>0 then begin
  43195. AddShort(',"rowCount":');
  43196. Add(aKnownRowsCount);
  43197. end;
  43198. AddShort(',"values":["');
  43199. // first row is FieldNames
  43200. for i := 0 to High(ColNames) do begin
  43201. AddString(ColNames[i]);
  43202. AddNoJSONEscape(PAnsiChar('","'),3);
  43203. end;
  43204. CancelLastChar('"');
  43205. fStartDataPosition := fStream.Position+(B-fTempBuf);
  43206. // B := buf-1 at startup -> need ',val11' position in
  43207. // "values":["col1","col2",val11,' i.e. current pos without the ','
  43208. end;
  43209. end;
  43210. procedure TJSONWriter.ChangeExpandedFields(aWithID: boolean;
  43211. const aFields: TSQLFieldIndexDynArray);
  43212. begin
  43213. if not Expand then
  43214. raise ESynException.CreateUTF8(
  43215. '%.ChangeExpandedFields() called with Expanded=false',[self]);
  43216. fWithID := aWithID;
  43217. fFields := aFields;
  43218. end;
  43219. procedure TJSONWriter.EndJSONObject(aKnownRowsCount,aRowsCount: integer);
  43220. begin
  43221. CancelLastComma; // cancel last ','
  43222. Add(']');
  43223. if not fExpand then begin
  43224. if aKnownRowsCount=0 then begin
  43225. AddShort(',"rowCount":');
  43226. Add(aRowsCount);
  43227. end;
  43228. Add('}');
  43229. end;
  43230. Add(#10);
  43231. FlushFinal;
  43232. end;
  43233. procedure TJSONWriter.TrimFirstRow;
  43234. var P, PBegin, PEnd: PUTF8Char;
  43235. begin
  43236. if (self=nil) or not fStream.InheritsFrom(TMemoryStream) or
  43237. fExpand or (fStartDataPosition=0) then
  43238. exit;
  43239. // go to begin of first row
  43240. FlushToStream; // we need the data to be in fStream memory
  43241. // PBegin^=val11 in { "fieldCount":1,"values":["col1","col2",val11,"val12",val21,..] }
  43242. PBegin := TMemoryStream(fStream).Memory;
  43243. PEnd := PBegin+fStream.Position;
  43244. PEnd^ := #0; // mark end of current values
  43245. inc(PBegin,fStartDataPosition+1); // +1 to include ',' of ',val11'
  43246. // jump to end of first row
  43247. P := GotoNextJSONItem(PBegin,length(ColNames));
  43248. if P=nil then exit; // unexpected end
  43249. // trim first row data
  43250. if P^<>#0 then
  43251. MoveFast(P^,PBegin^,PEnd-P); // erase content
  43252. fStream.Seek(PBegin-P,soCurrent); // adjust current stream position
  43253. end;
  43254. function JSONEncode(const NameValuePairs: array of const): RawUTF8;
  43255. begin
  43256. if high(NameValuePairs)<1 then
  43257. result := '{}' else // return void JSON object on error
  43258. with DefaultTextWriterJSONClass.CreateOwnedStream do
  43259. try
  43260. AddJSONEscape(NameValuePairs);
  43261. SetText(result);
  43262. finally
  43263. Free
  43264. end;
  43265. end;
  43266. {$ifndef NOVARIANTS}
  43267. function JSONEncode(const Format: RawUTF8; const Args,Params: array of const): RawUTF8; overload;
  43268. begin
  43269. with DefaultTextWriterJSONClass.CreateOwnedStream do
  43270. try
  43271. AddJSON(Format,Args,Params);
  43272. SetText(result);
  43273. finally
  43274. Free
  43275. end;
  43276. end;
  43277. {$endif}
  43278. function JSONEncodeArrayDouble(const Values: array of double): RawUTF8;
  43279. var W: TTextWriter;
  43280. begin
  43281. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  43282. try
  43283. W.Add('[');
  43284. W.AddCSVDouble(Values);
  43285. W.Add(']');
  43286. W.SetText(result);
  43287. finally
  43288. W.Free
  43289. end;
  43290. end;
  43291. function JSONEncodeArrayUTF8(const Values: array of RawUTF8): RawUTF8;
  43292. var W: TTextWriter;
  43293. begin
  43294. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  43295. try
  43296. W.Add('[');
  43297. W.AddCSVUTF8(Values);
  43298. W.Add(']');
  43299. W.SetText(result);
  43300. finally
  43301. W.Free
  43302. end;
  43303. end;
  43304. function JSONEncodeArrayInteger(const Values: array of integer): RawUTF8;
  43305. var W: TTextWriter;
  43306. begin
  43307. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  43308. try
  43309. W.Add('[');
  43310. W.AddCSVInteger(Values);
  43311. W.Add(']');
  43312. W.SetText(result);
  43313. finally
  43314. W.Free
  43315. end;
  43316. end;
  43317. function JSONEncodeArrayOfConst(const Values: array of const;
  43318. WithoutBraces: boolean): RawUTF8;
  43319. begin
  43320. JSONEncodeArrayOfConst(Values,WithoutBraces,result);
  43321. end;
  43322. procedure JSONEncodeArrayOfConst(const Values: array of const;
  43323. WithoutBraces: boolean; var result: RawUTF8);
  43324. begin
  43325. if length(Values)=0 then
  43326. if WithoutBraces then
  43327. result := '' else
  43328. result := '[]' else
  43329. with DefaultTextWriterJSONClass.CreateOwnedStream do
  43330. try
  43331. if not WithoutBraces then
  43332. Add('[');
  43333. AddCSVConst(Values);
  43334. if not WithoutBraces then
  43335. Add(']');
  43336. SetText(result);
  43337. finally
  43338. Free
  43339. end;
  43340. end;
  43341. procedure JSONEncodeNameSQLValue(const Name,SQLValue: RawUTF8;
  43342. var result: RawUTF8);
  43343. begin
  43344. if (SQLValue<>'') and (SQLValue[1] in ['''','"']) then
  43345. // unescape SQL quoted string value into a valid JSON string
  43346. with DefaultTextWriterJSONClass.CreateOwnedStream do
  43347. try
  43348. Add('{','"');
  43349. AddNoJSONEscapeUTF8(Name);
  43350. Add('"',':');
  43351. AddQuotedStringAsJSON(SQLValue);
  43352. Add('}');
  43353. SetText(result);
  43354. finally
  43355. Free;
  43356. end else
  43357. // Value is a number or null/true/false
  43358. result := '{"'+Name+'":'+SQLValue+'}';
  43359. end;
  43360. procedure JSONDecode(var JSON: RawUTF8;
  43361. const Names: array of PUTF8Char; var Values: TPUtf8CharDynArray;
  43362. HandleValuesAsObjectOrArray: Boolean=false);
  43363. begin
  43364. JSONDecode(UniqueRawUTF8(JSON),Names,Values,HandleValuesAsObjectOrArray);
  43365. end;
  43366. function JSONDecode(P: PUTF8Char; const Names: array of PUTF8Char;
  43367. var Values: TPUtf8CharDynArray; HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char;
  43368. var n, i: PtrInt;
  43369. Name, Value: PUTF8Char;
  43370. EndOfObject: AnsiChar;
  43371. NewValues: boolean;
  43372. begin
  43373. result := nil;
  43374. n := length(Names);
  43375. NewValues := pointer(Values)=nil;
  43376. SetLength(Values,n);
  43377. if not NewValues then
  43378. FillcharFast(Values[0],n*sizeof(PUTF8Char),0); // SetLength() could leave it not void
  43379. dec(n);
  43380. if P=nil then
  43381. exit;
  43382. while P^<>'{' do
  43383. if P^=#0 then
  43384. exit else
  43385. inc(P);
  43386. inc(P); // jump {
  43387. repeat
  43388. Name := GetJSONPropName(P);
  43389. if Name=nil then
  43390. exit; // invalid JSON content
  43391. Value := GetJSONFieldOrObjectOrArray(P,nil,@EndOfObject,HandleValuesAsObjectOrArray);
  43392. if not(EndOfObject in [',','}']) then
  43393. exit; // invalid item separator
  43394. for i := 0 to n do
  43395. if StrIComp(Name,Names[i])=0 then begin
  43396. Values[i] := Value;
  43397. break;
  43398. end;
  43399. until (P=nil) or (EndOfObject='}');
  43400. if P=nil then // result=nil indicates failure -> points to #0 for end of text
  43401. result := @NULCHAR else
  43402. result := P;
  43403. end;
  43404. function JSONDecode(var JSON: RawUTF8; const aName: RawUTF8;
  43405. wasString: PBoolean; HandleValuesAsObjectOrArray: Boolean): RawUTF8;
  43406. var P, Name, Value: PUTF8Char;
  43407. EndOfObject: AnsiChar;
  43408. begin
  43409. result := '';
  43410. P := pointer(JSON);
  43411. if P=nil then
  43412. exit;
  43413. while P^<>'{' do
  43414. if P^=#0 then
  43415. exit else
  43416. inc(P);
  43417. inc(P); // jump {
  43418. repeat
  43419. Name := GetJSONPropName(P);
  43420. if Name=nil then
  43421. exit; // invalid JSON content
  43422. Value := GetJSONFieldOrObjectOrArray(
  43423. P,wasString,@EndOfObject,HandleValuesAsObjectOrArray);
  43424. if not(EndOfObject in [',','}']) then
  43425. exit; // invalid item separator
  43426. if StrIComp(Name,pointer(aName))=0 then begin
  43427. Result := RawUTF8(Value);
  43428. exit;
  43429. end;
  43430. until (P=nil) or (EndOfObject='}');
  43431. end;
  43432. function JSONDecode(P: PUTF8Char; out Values: TNameValuePUTF8CharDynArray;
  43433. HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char;
  43434. var n: PtrInt;
  43435. Name, Value: PUTF8Char;
  43436. EndOfObject: AnsiChar;
  43437. begin
  43438. result := nil;
  43439. n := 0;
  43440. if P<>nil then begin
  43441. while P^<>'{' do
  43442. if P^=#0 then
  43443. exit else
  43444. inc(P);
  43445. inc(P); // jump {
  43446. repeat
  43447. Name := GetJSONPropName(P);
  43448. if Name=nil then
  43449. exit; // invalid JSON content
  43450. Value := GetJSONFieldOrObjectOrArray(P,nil,@EndOfObject,HandleValuesAsObjectOrArray);
  43451. if not(EndOfObject in [',','}']) then
  43452. exit; // invalid item separator
  43453. if n=length(Values) then
  43454. SetLength(Values,n+32);
  43455. Values[n].Name := Name;
  43456. Values[n].Value := Value;
  43457. inc(n);
  43458. until (P=nil) or (EndOfObject='}');
  43459. end;
  43460. SetLength(Values,n);
  43461. if P=nil then // result=nil indicates failure -> points to #0 for end of text
  43462. result := @NULCHAR else
  43463. result := P;
  43464. end;
  43465. function JSONRetrieveStringField(P: PUTF8Char; out Field: PUTF8Char;
  43466. out FieldLen: integer; ExpectNameField: boolean): PUTF8Char;
  43467. begin
  43468. result := nil;
  43469. // retrieve string field
  43470. if P=nil then
  43471. exit;
  43472. while P^ in [#1..' '] do inc(P);
  43473. if P^<>'"' then exit;
  43474. Field := P+1;
  43475. P := GotoEndOfJSONString(P);
  43476. if P^<>'"' then
  43477. exit; // here P^ should be '"'
  43478. FieldLen := P-Field;
  43479. // check valid JSON delimiter
  43480. repeat inc(P) until not(P^ in [#1..' ']);
  43481. if ExpectNameField then begin
  43482. if P^<>':' then
  43483. exit; // invalid name field
  43484. end else
  43485. if not (P^ in ['}',',']) then
  43486. exit; // invalid value field
  43487. result := P; // return either ':' for name field, either '}',',' for value
  43488. end;
  43489. /// decode a JSON field into an UTF-8 encoded buffer, stored inplace of JSON data
  43490. function GetJSONField(P: PUTF8Char; out PDest: PUTF8Char;
  43491. wasString: PBoolean=nil; EndOfObject: PUTF8Char=nil): PUTF8Char;
  43492. // this code is very fast
  43493. var D: PUTF8Char;
  43494. b,c4,surrogate,j: integer;
  43495. label slash,num;
  43496. begin
  43497. if wasString<>nil then
  43498. wasString^ := false; // default is 'no string'
  43499. PDest := nil; // PDest=nil indicates error or unexpected end (#0)
  43500. result := nil;
  43501. if P=nil then exit;
  43502. if P^<=' ' then repeat inc(P); if P^=#0 then exit; until P^>' ';
  43503. case P^ of
  43504. 'n':
  43505. if (PInteger(P)^=NULL_LOW) and (P[4] in EndOfJSONValueField) then begin
  43506. result := nil; // null -> returns nil and wasString=false
  43507. inc(P,3);
  43508. end else
  43509. exit; // PDest=nil to indicate error
  43510. 'f':
  43511. if (PInteger(P+1)^=ord('a')+ord('l')shl 8+ord('s')shl 16+ord('e')shl 24) and
  43512. (P[5] in EndOfJSONValueField) then begin
  43513. result := P; // false -> returns 'false' and wasString=false
  43514. inc(P,4);
  43515. end else
  43516. exit; // PDest=nil to indicate error
  43517. 't':
  43518. if (PInteger(P)^=TRUE_LOW) and (P[4] in EndOfJSONValueField) then begin
  43519. result := P; // true -> returns 'true' and wasString=false
  43520. inc(P,3);
  43521. end else
  43522. exit; // PDest=nil to indicate error
  43523. '"': begin
  43524. // '"string \"\\field"' -> 'string "\field'
  43525. if wasString<>nil then
  43526. wasString^ := true;
  43527. inc(P);
  43528. result := P;
  43529. D := P;
  43530. repeat // unescape P^ into U^ (cf. http://www.ietf.org/rfc/rfc4627.txt)
  43531. case P^ of
  43532. #0: exit; // leave PDest=nil for unexpected end
  43533. '"': break; // end of string
  43534. '\': goto slash;
  43535. else begin
  43536. D^ := P^; // 3 stages pipelined process of unescaped chars
  43537. inc(P);
  43538. inc(D);
  43539. case P^ of
  43540. #0: exit;
  43541. '"': break;
  43542. '\': goto slash;
  43543. else begin
  43544. D^ := P^;
  43545. inc(P);
  43546. inc(D);
  43547. case P^ of
  43548. #0: exit;
  43549. '"': break;
  43550. '\': goto slash;
  43551. else begin
  43552. D^ := P^;
  43553. inc(P);
  43554. inc(D);
  43555. continue;
  43556. end;
  43557. end;
  43558. end;
  43559. end;
  43560. end;
  43561. end;
  43562. slash:inc(P);
  43563. case P^ of // unescape JSON string
  43564. #0: exit; // to avoid potential buffer overflow issue for \#0
  43565. 'b': D^ := #08;
  43566. 't': D^ := #09;
  43567. 'n': D^ := #$0a;
  43568. 'f': D^ := #$0c;
  43569. 'r': D^ := #$0d;
  43570. 'u': begin // inlined decoding of '\u0123' UTF-16 codepoint into UTF-8
  43571. c4 := ConvertHexToBin[ord(P[1])];
  43572. if c4<=15 then begin
  43573. b := ConvertHexToBin[ord(P[2])];
  43574. if b<=15 then begin
  43575. c4 := c4 shl 4+b;
  43576. b := ConvertHexToBin[ord(P[3])];
  43577. if b<=15 then begin
  43578. c4 := c4 shl 4+b;
  43579. b := ConvertHexToBin[ord(P[4])];
  43580. if b<=15 then begin
  43581. c4 := c4 shl 4+b;
  43582. case c4 of
  43583. 0: begin
  43584. D^ := '?'; // \u0000 is an invalid value
  43585. inc(D);
  43586. end;
  43587. 1..$7f: begin
  43588. D^ := AnsiChar(c4);
  43589. inc(D);
  43590. end;
  43591. $80..$7ff: begin
  43592. D[0] := AnsiChar($C0 or (c4 shr 6));
  43593. D[1] := AnsiChar($80 or (c4 and $3F));
  43594. inc(D,2);
  43595. end;
  43596. UTF16_HISURROGATE_MIN..UTF16_LOSURROGATE_MAX:
  43597. if PWord(P+5)^=ord('\')+ord('u') shl 8 then begin
  43598. inc(P,6);
  43599. surrogate := (ConvertHexToBin[ord(P[1])] shl 12)+
  43600. (ConvertHexToBin[ord(P[2])] shl 8)+
  43601. (ConvertHexToBin[ord(P[3])] shl 4)+
  43602. ConvertHexToBin[ord(P[4])]; // optimistic approach
  43603. case c4 of // inlined UTF16CharToUtf8()
  43604. UTF16_HISURROGATE_MIN..UTF16_HISURROGATE_MAX:
  43605. c4 := ((c4-$D7C0)shl 10)+(surrogate xor UTF16_LOSURROGATE_MIN);
  43606. UTF16_LOSURROGATE_MIN..UTF16_LOSURROGATE_MAX:
  43607. c4 := ((surrogate-$D7C0)shl 10)+(c4 xor UTF16_LOSURROGATE_MIN);
  43608. end;
  43609. case c4 of
  43610. 0..$7ff: b := 2;
  43611. $800..$ffff: b := 3;
  43612. $10000..$1FFFFF: b := 4;
  43613. $200000..$3FFFFFF: b := 5;
  43614. else b := 6;
  43615. end;
  43616. for j := b-1 downto 1 do begin
  43617. D[j] := AnsiChar((c4 and $3f)+$80);
  43618. c4 := c4 shr 6;
  43619. end;
  43620. D^ := AnsiChar(Byte(c4) or UTF8_FIRSTBYTE[b]);
  43621. inc(D,b);
  43622. end else begin
  43623. D^ := '?'; // unexpected surrogate without its pair
  43624. inc(D);
  43625. end;
  43626. else begin
  43627. D[0] := AnsiChar($E0 or (c4 shr 12));
  43628. D[1] := AnsiChar($80 or ((c4 shr 6) and $3F));
  43629. D[2] := AnsiChar($80 or (c4 and $3F));
  43630. inc(D,3);
  43631. end;
  43632. end;
  43633. inc(P,5);
  43634. continue;
  43635. end;
  43636. end;
  43637. end;
  43638. end;
  43639. D^ := '?'; // bad formated hexa number -> '?0123'
  43640. end;
  43641. else D^ := P^; // litterals: '\"' -> '"'
  43642. end;
  43643. inc(P);
  43644. inc(D);
  43645. until false;
  43646. // here P^='"'
  43647. D^ := #0; // make zero-terminated
  43648. inc(P);
  43649. if P^=#0 then
  43650. exit;
  43651. end;
  43652. '0':
  43653. if P[1] in ['0'..'9'] then // 0123 excluded by JSON!
  43654. exit else // leave PDest=nil for unexpected end
  43655. goto num;
  43656. '-','1'..'9': begin
  43657. num:// numerical field: all chars before end of field
  43658. result := P;
  43659. repeat
  43660. if not (P^ in DigitFloatChars) then
  43661. break;
  43662. inc(P);
  43663. until false;
  43664. if P^=#0 then
  43665. exit;
  43666. if P^<=' ' then
  43667. P^ := #0; // force numerical field with no trailing ' '
  43668. end;
  43669. else exit; // PDest=nil to indicate error
  43670. end;
  43671. if not (P^ in EndOfJSONField) then begin
  43672. inc(P);
  43673. while not (P^ in EndOfJSONField) do begin
  43674. inc(P);
  43675. if P^=#0 then
  43676. exit; // leave PDest=nil for unexpected end
  43677. end;
  43678. end;
  43679. if EndOfObject<>nil then
  43680. EndOfObject^ := P^;
  43681. P^ := #0; // make zero-terminated
  43682. PDest := @P[1];
  43683. if P[1]=#0 then
  43684. PDest := nil;
  43685. end;
  43686. function GetJSONPropName(var P: PUTF8Char): PUTF8Char;
  43687. var Name: PUTF8Char;
  43688. wasString: boolean;
  43689. EndOfObject: AnsiChar;
  43690. begin // should match GotoNextJSONObjectOrArray() and JsonPropNameValid()
  43691. result := nil;
  43692. if P=nil then
  43693. exit;
  43694. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43695. Name := P; // put here to please some versions of Delphi compiler
  43696. case P^ of
  43697. '_','A'..'Z','a'..'z','0'..'9','$': begin // e.g. '{age:{$gt:18}}'
  43698. repeat
  43699. inc(P);
  43700. until not (ord(P[0]) in IsJsonIdentifier);
  43701. if P^ in [#1..' '] then begin
  43702. P^ := #0;
  43703. inc(P);
  43704. end;
  43705. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43706. if not (P^ in [':','=']) then // allow both age:18 and age=18 pairs
  43707. exit;
  43708. P^ := #0;
  43709. inc(P);
  43710. end;
  43711. '''': begin // single quotes won't handle nested quote character
  43712. inc(P);
  43713. Name := P;
  43714. while P^<>'''' do
  43715. if P^<' ' then
  43716. exit else
  43717. inc(P);
  43718. P^ := #0;
  43719. repeat inc(P) until not(P^ in [#1..' ']);
  43720. if P^<>':' then
  43721. exit;
  43722. inc(P);
  43723. end;
  43724. '"': begin
  43725. Name := GetJSONField(P,P,@wasString,@EndOfObject);
  43726. if (Name=nil) or (not wasString) or (EndOfObject<>':') then
  43727. exit;
  43728. end else
  43729. exit;
  43730. end;
  43731. result := Name;
  43732. end;
  43733. procedure GetJSONPropName(var P: PUTF8Char; out PropName: shortstring);
  43734. var Name: PAnsiChar;
  43735. begin // match GotoNextJSONObjectOrArray() and overloaded GetJSONPropName()
  43736. PropName[0] := #0;
  43737. if P=nil then
  43738. exit;
  43739. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43740. Name := pointer(P);
  43741. case P^ of
  43742. '_','A'..'Z','a'..'z','0'..'9','$': begin // e.g. '{age:{$gt:18}}'
  43743. repeat
  43744. inc(P);
  43745. until not (ord(P^) in IsJsonIdentifier);
  43746. SetString(PropName,Name,P-Name);
  43747. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43748. if not (P^ in [':','=']) then begin // allow both age:18 and age=18 pairs
  43749. PropName[0] := #0;
  43750. exit;
  43751. end;
  43752. inc(P);
  43753. end;
  43754. '''': begin // single quotes won't handle nested quote character
  43755. inc(P);
  43756. inc(Name);
  43757. while P^<>'''' do
  43758. if P^<' ' then
  43759. exit else
  43760. inc(P);
  43761. SetString(PropName,Name,P-Name);
  43762. repeat inc(P) until not(P^ in [#1..' ']);
  43763. if P^<>':' then begin
  43764. PropName[0] := #0;
  43765. exit;
  43766. end;
  43767. inc(P);
  43768. end;
  43769. '"': begin
  43770. inc(Name);
  43771. P := GotoEndOfJSONString(P); // won't unescape JSON strings
  43772. if P^<>'"' then
  43773. exit;
  43774. SetString(PropName,Name,P-Name);
  43775. repeat inc(P) until not(P^ in [#1..' ']);
  43776. if P^<>':' then begin
  43777. PropName[0] := #0;
  43778. exit;
  43779. end;
  43780. inc(P);
  43781. end else
  43782. exit;
  43783. end;
  43784. end;
  43785. function GotoNextJSONPropName(P: PUTF8Char): PUTF8Char;
  43786. label s;
  43787. begin // should match GotoNextJSONObjectOrArray()
  43788. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43789. result := nil;
  43790. if P=nil then
  43791. exit;
  43792. case P^ of
  43793. '_','A'..'Z','a'..'z','0'..'9','$': begin // e.g. '{age:{$gt:18}}'
  43794. repeat
  43795. inc(P);
  43796. until not (ord(P^) in IsJsonIdentifier);
  43797. if P^ in [#1..' '] then
  43798. inc(P);
  43799. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43800. if not (P^ in [':','=']) then // allow both age:18 and age=18 pairs
  43801. exit;
  43802. end;
  43803. '''': begin // single quotes won't handle nested quote character
  43804. inc(P);
  43805. while P^<>'''' do
  43806. if P^<' ' then
  43807. exit else
  43808. inc(P);
  43809. goto s;
  43810. end;
  43811. '"': begin
  43812. P := GotoEndOfJSONString(P);
  43813. if P^<>'"' then
  43814. exit;
  43815. s: repeat inc(P) until not(P^ in [#1..' ']);
  43816. if P^<>':' then
  43817. exit;
  43818. end else
  43819. exit;
  43820. end;
  43821. repeat inc(P) until not(P^ in [#1..' ']);
  43822. result := P;
  43823. end;
  43824. function GetJSONFieldOrObjectOrArray(var P: PUTF8Char; wasString: PBoolean=nil;
  43825. EndOfObject: PUTF8Char=nil; HandleValuesAsObjectOrArray: Boolean=false): PUTF8Char;
  43826. var Value: PUTF8Char;
  43827. wStr: boolean;
  43828. begin
  43829. result := nil;
  43830. if P=nil then
  43831. exit;
  43832. while ord(P^) in [1..32] do inc(P);
  43833. if HandleValuesAsObjectOrArray and (P^ in ['{','[']) then begin
  43834. Value := P;
  43835. P := GotoNextJSONObjectOrArray(P);
  43836. if P=nil then
  43837. exit; // invalid content
  43838. if wasString<>nil then
  43839. wasString^ := false; // was object or array
  43840. while ord(P^) in [1..32] do inc(P);
  43841. if EndOfObject<>nil then
  43842. EndOfObject^ := P^;
  43843. P^ := #0; // make zero-terminated
  43844. if P[1]=#0 then
  43845. P := nil else
  43846. inc(P);
  43847. result := Value;
  43848. end else begin
  43849. result := GetJSONField(P,P,@wStr,EndOfObject);
  43850. if (result<>nil) and (not wStr) and (result^>='f') then
  43851. if PInteger(result)^=TRUE_LOW then
  43852. result := '1' else // normalize true -> 1
  43853. if PInteger(result)^=FALSE_LOW then
  43854. result := '0'; // normalize false -> 0
  43855. if wasString<>nil then
  43856. wasString^ := wStr;
  43857. end;
  43858. end;
  43859. function IsString(P: PUTF8Char): boolean; // test if P^ is a "string" value
  43860. begin
  43861. if P=nil then begin
  43862. result := false;
  43863. exit;
  43864. end;
  43865. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43866. if (P[0] in ['0'..'9']) or // is first char numeric?
  43867. ((P[0] in ['-','+']) and (P[1] in ['0'..'9'])) then begin
  43868. // check if P^ is a true numerical value
  43869. repeat inc(P) until not (P^ in ['0'..'9']); // check digits
  43870. if P^='.' then
  43871. repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits
  43872. if (P^ in ['e','E']) and (P[1] in DigitChars) then begin
  43873. inc(P);
  43874. if P^='+' then inc(P) else
  43875. if P^='-' then inc(P);
  43876. while P^ in ['0'..'9'] do inc(P);
  43877. end;
  43878. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43879. result := (P^<>#0);
  43880. exit;
  43881. end else
  43882. result := true; // don't begin with a numerical value -> must be a string
  43883. end;
  43884. function IsStringJSON(P: PUTF8Char): boolean; // test if P^ is a "string" value
  43885. var c4: integer;
  43886. begin
  43887. if P=nil then begin
  43888. result := false;
  43889. exit;
  43890. end;
  43891. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43892. c4 := PInteger(P)^;
  43893. if (((c4=NULL_LOW)or(c4=TRUE_LOW)) and (P[4] in EndOfJSONValueField)) or
  43894. ((c4=FALSE_LOW) and (P[4]='e') and (P[5] in EndOfJSONValueField)) then begin
  43895. result := false; // constants are no string
  43896. exit;
  43897. end else
  43898. if (P[0] in ['1'..'9']) or // is first char numeric?
  43899. ((P[0]='0') and not (P[1] in ['0'..'9'])) or // '012' excluded by JSON
  43900. ((P[0]='-') and (P[1] in ['0'..'9'])) then begin
  43901. // check if P^ is a true numerical value
  43902. repeat inc(P) until not (P^ in ['0'..'9']); // check digits
  43903. if P^='.' then
  43904. repeat inc(P) until not (P^ in ['0'..'9']); // check fractional digits
  43905. if (P^ in ['e','E']) and (P[1] in DigitChars) then begin
  43906. inc(P);
  43907. if P^='+' then inc(P) else
  43908. if P^='-' then inc(P);
  43909. while P^ in ['0'..'9'] do inc(P);
  43910. end;
  43911. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43912. result := (P^<>#0);
  43913. exit;
  43914. end else
  43915. result := true; // don't begin with a numerical value -> must be a string
  43916. end;
  43917. function GotoEndJSONItem(P: PUTF8Char): PUTF8Char;
  43918. label next;
  43919. begin
  43920. result := nil; // to notify unexpected end
  43921. if P=nil then
  43922. exit;
  43923. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43924. // get a field
  43925. case P^ of
  43926. #0: exit;
  43927. '"': begin
  43928. P := GotoEndOfJSONString(P);
  43929. if P^<>'"' then
  43930. exit; // P^ should be '"' here -> execute repeat.. below
  43931. end;
  43932. '[','{': begin
  43933. P := GotoNextJSONObjectOrArray(P);
  43934. if P=nil then
  43935. exit;
  43936. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43937. goto next;
  43938. end;
  43939. end;
  43940. repeat // numeric or true/false/null or MongoDB extended {age:{$gt:18}}
  43941. inc(P);
  43942. if P^=#0 then exit; // unexpected end
  43943. until P^ in [':',',',']','}'];
  43944. next:
  43945. if P^=#0 then
  43946. exit;
  43947. result := P;
  43948. end;
  43949. procedure GetJSONItemAsRawJSON(var P: PUTF8Char; var result: RawJSON; EndOfObject: PAnsiChar);
  43950. var B: PUTF8Char;
  43951. begin
  43952. result := '';
  43953. if P=nil then
  43954. exit;
  43955. B := P;
  43956. P := GotoEndJSONItem(B);
  43957. if P=nil then
  43958. exit;
  43959. SetString(result,PAnsiChar(B),P-B);
  43960. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43961. if EndOfObject<>nil then
  43962. EndOfObject^ := P^;
  43963. if P^<>#0 then //if P^=',' then
  43964. repeat inc(P) until not(P^ in [#1..' ']);
  43965. end;
  43966. function GotoNextJSONItem(P: PUTF8Char; NumberOfItemsToJump: cardinal;
  43967. EndOfObject: PAnsiChar): PUTF8Char;
  43968. label next;
  43969. begin
  43970. result := nil; // to notify unexpected end
  43971. while NumberOfItemsToJump>0 do begin
  43972. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43973. // get a field
  43974. case P^ of
  43975. #0: exit;
  43976. '"': begin
  43977. P := GotoEndOfJSONString(P);
  43978. if P^<>'"' then
  43979. exit; // P^ should be '"' here
  43980. end;
  43981. '[','{': begin
  43982. P := GotoNextJSONObjectOrArray(P);
  43983. if P=nil then
  43984. exit;
  43985. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  43986. goto next;
  43987. end;
  43988. end;
  43989. repeat // numeric or true/false/null or MongoDB extended {age:{$gt:18}}
  43990. inc(P);
  43991. if P^=#0 then exit; // unexpected end
  43992. until P^ in [':',',',']','}'];
  43993. next:
  43994. if P^=#0 then
  43995. exit;
  43996. if EndOfObject<>nil then
  43997. EndOfObject^ := P^;
  43998. inc(P);
  43999. dec(NumberOfItemsToJump);
  44000. end;
  44001. result := P;
  44002. end;
  44003. function GotoNextJSONObjectOrArrayInternal(P,PMax: PUTF8Char; EndChar: AnsiChar): PUTF8Char;
  44004. label Prop;
  44005. begin // should match GetJSONPropName()
  44006. result := nil;
  44007. repeat
  44008. case P^ of
  44009. '{','[': begin
  44010. if PMax=nil then
  44011. P := GotoNextJSONObjectOrArray(P) else
  44012. P := GotoNextJSONObjectOrArrayMax(P,PMax);
  44013. if P=nil then exit;
  44014. end;
  44015. ':': if EndChar<>'}' then exit else inc(P); // syntax for JSON object only
  44016. ',': inc(P); // comma appears in both JSON objects and arrays
  44017. '}': if EndChar='}' then break else exit;
  44018. ']': if EndChar=']' then break else exit;
  44019. '"': begin
  44020. P := GotoEndOfJSONString(P);
  44021. if P^<>'"' then
  44022. exit;
  44023. inc(P);
  44024. end;
  44025. '-','+','0'..'9': // '0123' excluded by JSON, but not here
  44026. repeat
  44027. inc(P);
  44028. until not (P^ in DigitFloatChars);
  44029. 't': if PInteger(P)^=TRUE_LOW then inc(P,4) else goto Prop;
  44030. 'f': if PInteger(P)^=FALSE_LOW then inc(P,5) else goto Prop;
  44031. 'n': if PInteger(P)^=NULL_LOW then inc(P,4) else goto Prop;
  44032. '''': begin
  44033. repeat inc(P); if P^<=' ' then exit; until P^='''';
  44034. repeat inc(P) until not(P^ in [#1..' ']);
  44035. if P^<>':' then exit;
  44036. end;
  44037. '/': begin
  44038. repeat // allow extended /regex/ syntax
  44039. inc(P);
  44040. if P^=#0 then
  44041. exit;
  44042. until P^='/';
  44043. repeat inc(P) until not(P^ in [#1..' ']);
  44044. end;
  44045. else begin
  44046. Prop: if not (ord(P^) in IsJsonIdentifierFirstChar) then
  44047. exit; // expect e.g. '{age:{$gt:18}}'
  44048. repeat
  44049. inc(P);
  44050. until not (ord(P^) in IsJsonIdentifier);
  44051. while P^ in [#1..' '] do inc(P);
  44052. if P^='(' then begin // handle e.g. "born":isodate("1969-12-31")
  44053. inc(P);
  44054. while P^ in [#1..' '] do inc(P);
  44055. if P^='"' then begin
  44056. P := GotoEndOfJSONString(P);
  44057. if P^<>'"' then
  44058. exit;
  44059. end;
  44060. inc(P);
  44061. while P^ in [#1..' '] do inc(P);
  44062. if P^<>')' then
  44063. exit;
  44064. inc(P);
  44065. end
  44066. else
  44067. if P^<>':' then exit;
  44068. end;
  44069. end;
  44070. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  44071. if (PMax<>nil) and (P>=PMax) then
  44072. exit;
  44073. until P^=EndChar;
  44074. result := P+1;
  44075. end;
  44076. function GotoNextJSONObjectOrArray(P: PUTF8Char; EndChar: AnsiChar=#0): PUTF8Char;
  44077. label Prop;
  44078. begin // should match GetJSONPropName()
  44079. result := nil; // mark error or unexpected end (#0)
  44080. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  44081. if EndChar=#0 then begin
  44082. case P^ of
  44083. '[': EndChar := ']';
  44084. '{': EndChar := '}';
  44085. else exit;
  44086. end;
  44087. repeat inc(P) until not(P^ in [#1..' ']);
  44088. end;
  44089. result := GotoNextJSONObjectOrArrayInternal(P,nil,EndChar);
  44090. end;
  44091. function GotoNextJSONObjectOrArrayMax(P,PMax: PUTF8Char): PUTF8Char;
  44092. var EndChar: AnsiChar;
  44093. begin // should match GetJSONPropName()
  44094. result := nil; // mark error or unexpected end (#0)
  44095. if P^ in [#1..' '] then repeat inc(P) until not(P^ in [#1..' ']);
  44096. case P^ of
  44097. '[': EndChar := ']';
  44098. '{': EndChar := '}';
  44099. else exit;
  44100. end;
  44101. repeat inc(P) until not(P^ in [#1..' ']);
  44102. result := GotoNextJSONObjectOrArrayInternal(P,PMax,EndChar);
  44103. end;
  44104. procedure RemoveCommentsFromJSON(P: PUTF8Char);
  44105. begin // replace comments by ' ' characters which will be ignored by parser
  44106. if P<>nil then
  44107. while P^<>#0 do begin
  44108. case P^ of
  44109. '"': begin
  44110. P := GotoEndOfJSONString(P);
  44111. if P^<>'"' then
  44112. exit;
  44113. end;
  44114. '/': begin
  44115. inc(P);
  44116. case P^ of
  44117. '/': begin // this is // comment - replace by ' '
  44118. dec(P);
  44119. repeat
  44120. P^ := ' ';
  44121. inc(P)
  44122. until P^ in [#0, #10, #13];
  44123. end;
  44124. '*': begin // this is /* comment - replace by ' ' but keep CRLF
  44125. P[-1] := ' ';
  44126. repeat
  44127. if not(P^ in [#10, #13]) then
  44128. P^ := ' '; // keep CRLF for correct line numbering (e.g. for error)
  44129. inc(P);
  44130. if PWord(P)^=ord('*')+ord('/')shl 8 then begin
  44131. PWord(P)^ := $2020;
  44132. inc(P,2);
  44133. break;
  44134. end;
  44135. until P^=#0;
  44136. end;
  44137. end;
  44138. end;
  44139. end;
  44140. inc(P);
  44141. end;
  44142. end;
  44143. procedure JSONBufferToXML(P: PUTF8Char; const Header,NameSpace: RawUTF8;
  44144. out result: RawUTF8);
  44145. var i,j,L: integer;
  44146. begin
  44147. if P=nil then
  44148. result := Header else
  44149. with TTextWriter.CreateOwnedStream do
  44150. try
  44151. AddNoJSONEscape(pointer(Header),length(Header));
  44152. L := length(NameSpace);
  44153. if L<>0 then
  44154. AddNoJSONEscape(pointer(NameSpace),L);
  44155. AddJSONToXML(P);
  44156. if L<>0 then
  44157. for i := 1 to L do
  44158. if NameSpace[i]='<' then begin
  44159. for j := i+1 to L do
  44160. if NameSpace[j] in [' ','>'] then begin
  44161. Add('<','/');
  44162. AddStringCopy(NameSpace,i+1,j-i-1);
  44163. Add('>');
  44164. break;
  44165. end;
  44166. break;
  44167. end;
  44168. SetText(result);
  44169. finally
  44170. Free;
  44171. end;
  44172. end;
  44173. function JSONToXML(const JSON: RawUTF8; const Header,NameSpace: RawUTF8): RawUTF8;
  44174. var tmp: TSynTempBuffer;
  44175. begin
  44176. tmp.Init(JSON);
  44177. try
  44178. JSONBufferToXML(tmp.buf,Header,NameSpace,result);
  44179. finally
  44180. tmp.Done;
  44181. end;
  44182. end;
  44183. procedure JSONBufferReformat(P: PUTF8Char; out result: RawUTF8;
  44184. Format: TTextWriterJSONFormat);
  44185. begin
  44186. if P<>nil then
  44187. with TTextWriter.CreateOwnedStream(65536) do
  44188. try
  44189. AddJSONReformat(P,Format,nil);
  44190. SetText(result);
  44191. finally
  44192. Free;
  44193. end;
  44194. end;
  44195. function JSONReformat(const JSON: RawUTF8; Format: TTextWriterJSONFormat): RawUTF8;
  44196. var n: integer;
  44197. tmp: TSynTempBuffer;
  44198. begin
  44199. tmp.Init(JSON);
  44200. if tmp.len<4096 then
  44201. n := 4096 else // minimal rough estimation of the output buffer size
  44202. n := tmp.Len shr 2;
  44203. with TTextWriter.CreateOwnedStream(n) do
  44204. try
  44205. AddJSONReformat(tmp.buf,Format,nil);
  44206. SetText(result);
  44207. finally
  44208. Free;
  44209. tmp.Done;
  44210. end;
  44211. end;
  44212. function JSONBufferReformatToFile(P: PUTF8Char; const Dest: TFileName;
  44213. Format: TTextWriterJSONFormat=jsonHumanReadable): boolean;
  44214. var F: TFileStream;
  44215. begin
  44216. try
  44217. F := TFileStream.Create(Dest,fmCreate);
  44218. try
  44219. with TTextWriter.Create(F,256*1024) do
  44220. try
  44221. AddJSONReformat(P,Format,nil);
  44222. FlushFinal;
  44223. finally
  44224. Free;
  44225. end;
  44226. result := true;
  44227. finally
  44228. F.Free;
  44229. end;
  44230. except
  44231. on Exception do
  44232. result := false;
  44233. end;
  44234. end;
  44235. function JSONReformatToFile(const JSON: RawUTF8; const Dest: TFileName;
  44236. Format: TTextWriterJSONFormat=jsonHumanReadable): boolean;
  44237. var tmp: TSynTempBuffer;
  44238. begin
  44239. tmp.Init(JSON);
  44240. try
  44241. result := JSONBufferReformatToFile(tmp.buf,Dest,Format);
  44242. finally
  44243. tmp.Done;
  44244. end;
  44245. end;
  44246. { TSynPersistentWithPassword }
  44247. class function TSynPersistentWithPassword.ComputePassword(const PlainPassword: RawUTF8;
  44248. CustomKey: cardinal): RawUTF8;
  44249. var instance: TSynPersistentWithPassword;
  44250. begin
  44251. instance := TSynPersistentWithPassword.Create;
  44252. try
  44253. instance.Key := CustomKey;
  44254. instance.SetPassWordPlain(PlainPassword);
  44255. result := instance.fPassWord;
  44256. finally
  44257. instance.Free;
  44258. end;
  44259. end;
  44260. class function TSynPersistentWithPassword.ComputePlainPassword(const CypheredPassword: RawUTF8;
  44261. CustomKey: cardinal): RawUTF8;
  44262. var instance: TSynPersistentWithPassword;
  44263. begin
  44264. instance := TSynPersistentWithPassword.Create;
  44265. try
  44266. instance.Key := CustomKey;
  44267. instance.fPassWord := CypheredPassword;
  44268. result := instance.GetPassWordPlain;
  44269. finally
  44270. instance.Free;
  44271. end;
  44272. end;
  44273. function TSynPersistentWithPassword.GetPasswordFieldAddress: pointer;
  44274. begin
  44275. result := @fPassword;
  44276. end;
  44277. function TSynPersistentWithPassword.GetKey: cardinal;
  44278. begin
  44279. if self=nil then
  44280. result := 0 else
  44281. result := fKey xor $A5abba5A;
  44282. end;
  44283. function TSynPersistentWithPassword.GetPassWordPlain: RawUTF8;
  44284. begin
  44285. if (self=nil) or (fPassWord='') then
  44286. result := '' else begin
  44287. result := Base64ToBin(fPassWord);
  44288. SymmetricEncrypt(GetKey,RawByteString(result));
  44289. end;
  44290. end;
  44291. procedure TSynPersistentWithPassword.SetPassWordPlain(const Value: RawUTF8);
  44292. var tmp: RawByteString;
  44293. begin
  44294. if self=nil then
  44295. exit;
  44296. if Value='' then begin
  44297. fPassWord := '';
  44298. exit;
  44299. end;
  44300. SetString(tmp,PAnsiChar(Value),Length(Value)); // private copy
  44301. SymmetricEncrypt(GetKey,tmp);
  44302. fPassWord := BinToBase64(tmp);
  44303. end;
  44304. { TSynConnectionDefinition }
  44305. constructor TSynConnectionDefinition.CreateFromJSON(const JSON: RawUTF8;
  44306. Key: cardinal);
  44307. var privateCopy: RawUTF8;
  44308. values: TPUtf8CharDynArray;
  44309. begin
  44310. fKey := Key;
  44311. privateCopy := JSON;
  44312. JSONDecode(privateCopy,['Kind','ServerName','DatabaseName','User','Password'],values);
  44313. UTF8DecodeToString(values[0],StrLen(values[0]),fKind);
  44314. fServerName := values[1];
  44315. fDatabaseName := values[2];
  44316. fUser := values[3];
  44317. fPassWord := values[4];
  44318. end;
  44319. function TSynConnectionDefinition.SaveToJSON: RawUTF8;
  44320. begin
  44321. result := JSONEncode(['Kind',fKind,'ServerName',fServerName,
  44322. 'DatabaseName',fDatabaseName,'User',fUser,'Password',fPassword]);
  44323. end;
  44324. { ************ filtering and validation classes and functions }
  44325. function IsValidIP4Address(P: PUTF8Char): boolean;
  44326. var ndot: PtrInt;
  44327. V: PtrUInt;
  44328. begin
  44329. result := false;
  44330. if (P=nil) or not (P^ in ['0'..'9']) then
  44331. exit;
  44332. V := 0;
  44333. ndot := 0;
  44334. repeat
  44335. case P^ of
  44336. #0: break;
  44337. '.': if (P[-1]='.') or (V>255) then
  44338. exit else begin
  44339. inc(ndot);
  44340. V := 0;
  44341. end;
  44342. '0'..'9': V := (V*10)+ord(P^)-48;
  44343. else exit;
  44344. end;
  44345. inc(P);
  44346. until false;
  44347. if (ndot=3) and (V<=255) and (P[-1]<>'.') then
  44348. result := true;
  44349. end;
  44350. function IsValidEmail(P: PUTF8Char): boolean;
  44351. // Initial Author: Ernesto D'Spirito - UTF-8 version by AB
  44352. // http://www.howtodothings.com/computers/a1169-validating-email-addresses-in-delphi.html
  44353. const
  44354. // Valid characters in an "atom"
  44355. atom_chars: set of AnsiChar = [#33..#255] -
  44356. ['(', ')', '<', '>', '@', ',', ';', ':', '\', '/', '"', '.', '[', ']', #127];
  44357. // Valid characters in a "quoted-string"
  44358. quoted_string_chars: set of AnsiChar = [#0..#255] - ['"', #13, '\'];
  44359. // Valid characters in a subdomain
  44360. letters: set of AnsiChar = ['A'..'Z', 'a'..'z'];
  44361. letters_digits: set of AnsiChar = ['0'..'9', 'A'..'Z', 'a'..'z'];
  44362. type
  44363. States = (STATE_BEGIN, STATE_ATOM, STATE_QTEXT, STATE_QCHAR,
  44364. STATE_QUOTE, STATE_LOCAL_PERIOD, STATE_EXPECTING_SUBDOMAIN,
  44365. STATE_SUBDOMAIN, STATE_HYPHEN);
  44366. var
  44367. State: States;
  44368. subdomains: integer;
  44369. c: AnsiChar;
  44370. ch: PtrInt;
  44371. begin
  44372. State := STATE_BEGIN;
  44373. subdomains := 1;
  44374. if P<>nil then
  44375. repeat
  44376. ch := ord(P^);
  44377. if ch and $80=0 then
  44378. inc(P) else
  44379. ch := GetHighUTF8UCS4Inlined(P);
  44380. if (ch<=255) and (WinAnsiConvert.AnsiToWide[ch]<=255) then
  44381. // convert into WinAnsi char
  44382. c := AnsiChar(ch) else
  44383. // invalid char
  44384. c := #127;
  44385. case State of
  44386. STATE_BEGIN:
  44387. if c in atom_chars then
  44388. State := STATE_ATOM else
  44389. if c='"' then
  44390. State := STATE_QTEXT else
  44391. break;
  44392. STATE_ATOM:
  44393. if c='@' then
  44394. State := STATE_EXPECTING_SUBDOMAIN else
  44395. if c='.' then
  44396. State := STATE_LOCAL_PERIOD else
  44397. if not (c in atom_chars) then
  44398. break;
  44399. STATE_QTEXT:
  44400. if c='\' then
  44401. State := STATE_QCHAR else
  44402. if c='"' then
  44403. State := STATE_QUOTE else
  44404. if not (c in quoted_string_chars) then
  44405. break;
  44406. STATE_QCHAR:
  44407. State := STATE_QTEXT;
  44408. STATE_QUOTE:
  44409. if c='@' then
  44410. State := STATE_EXPECTING_SUBDOMAIN else
  44411. if c='.' then
  44412. State := STATE_LOCAL_PERIOD else
  44413. break;
  44414. STATE_LOCAL_PERIOD:
  44415. if c in atom_chars then
  44416. State := STATE_ATOM else
  44417. if c='"' then
  44418. State := STATE_QTEXT else
  44419. break;
  44420. STATE_EXPECTING_SUBDOMAIN:
  44421. if c in letters_digits then
  44422. State := STATE_SUBDOMAIN else
  44423. break;
  44424. STATE_SUBDOMAIN:
  44425. if c='.' then begin
  44426. inc(subdomains);
  44427. State := STATE_EXPECTING_SUBDOMAIN
  44428. end else
  44429. if c='-' then
  44430. State := STATE_HYPHEN else
  44431. if not (c in letters_digits) then
  44432. break;
  44433. STATE_HYPHEN:
  44434. if c in letters_digits then
  44435. State := STATE_SUBDOMAIN else
  44436. if c<>'-' then
  44437. break;
  44438. end;
  44439. if P^=#0 then begin
  44440. P := nil;
  44441. break;
  44442. end;
  44443. until false;
  44444. result := (State = STATE_SUBDOMAIN) and (subdomains >= 2);
  44445. end;
  44446. function IsMatch(const Pattern, Text: RawUTF8; CaseInsensitive: boolean): boolean;
  44447. // code below adapted from ZMatchPattern.pas - http://www.zeoslib.sourceforge.net
  44448. type
  44449. TMatch = (mNONE, mABORT, mEND, mLITERAL, mPATTERN, mRANGE, mVALID);
  44450. const
  44451. SINGLE = '?';
  44452. KLEENE_STAR = '*';
  44453. RANGE_OPEN = '[';
  44454. RANGE = '-';
  44455. RANGE_CLOSE = ']';
  44456. CARET_NEGATE = '^';
  44457. EXCLAMATION_NEGATE = '!';
  44458. function MatchAfterStar(Pattern, Text: RawUTF8): TMatch; forward;
  44459. function Matche(const Pattern, Text: RawUTF8): TMatch;
  44460. var RangeStart, RangeEnd, P, T, PLen, TLen: Integer;
  44461. Invert, MemberMatch: Boolean;
  44462. begin
  44463. P := 1;
  44464. T := 1;
  44465. PLen := Length(pattern);
  44466. TLen := Length(text);
  44467. result := mNONE;
  44468. while ((result = mNONE) and (P <= PLen)) do begin
  44469. if T > TLen then begin
  44470. if (Pattern[P] = KLEENE_STAR) and (P+1 > PLen) then
  44471. result := mVALID else
  44472. result := mABORT;
  44473. exit;
  44474. end else
  44475. case Pattern[P] of
  44476. KLEENE_STAR:
  44477. result := MatchAfterStar(Copy(Pattern,P,PLen),Copy(Text,T,TLen));
  44478. RANGE_OPEN: begin
  44479. inc(P);
  44480. Invert := False;
  44481. if (Pattern[P] = EXCLAMATION_NEGATE) or
  44482. (Pattern[P] = CARET_NEGATE) then begin
  44483. Invert := True;
  44484. inc(P);
  44485. end;
  44486. if (Pattern[P] = RANGE_CLOSE) then begin
  44487. result := mPATTERN;
  44488. exit;
  44489. end;
  44490. MemberMatch := False;
  44491. while Pattern[P] <> RANGE_CLOSE do begin
  44492. RangeStart := P;
  44493. RangeEnd := P;
  44494. inc(P);
  44495. if P > PLen then begin
  44496. result := mPATTERN;
  44497. exit;
  44498. end;
  44499. if Pattern[P] = RANGE then begin
  44500. inc(P);
  44501. RangeEnd := P;
  44502. if (P > PLen) or (Pattern[RangeEnd] = RANGE_CLOSE) then begin
  44503. result := mPATTERN;
  44504. exit;
  44505. end;
  44506. inc(P);
  44507. end;
  44508. if P > PLen then begin
  44509. result := mPATTERN;
  44510. exit;
  44511. end;
  44512. if RangeStart < RangeEnd then begin
  44513. if (Text[T] >= Pattern[RangeStart]) and
  44514. (Text[T] <= Pattern[RangeEnd]) then begin
  44515. MemberMatch := True;
  44516. break;
  44517. end;
  44518. end
  44519. else begin
  44520. if (Text[T] >= Pattern[RangeEnd]) and
  44521. (Text[T] <= Pattern[RangeStart]) then begin
  44522. MemberMatch := True;
  44523. break;
  44524. end;
  44525. end;
  44526. end;
  44527. if (Invert and MemberMatch) or (not (Invert or MemberMatch)) then begin
  44528. result := mRANGE;
  44529. exit;
  44530. end;
  44531. if MemberMatch then
  44532. while (P <= PLen) and (Pattern[P] <> RANGE_CLOSE) do
  44533. inc(P);
  44534. if P > PLen then begin
  44535. result := mPATTERN;
  44536. exit;
  44537. end;
  44538. end;
  44539. else
  44540. if Pattern[P] <> SINGLE then
  44541. if Pattern[P] <> Text[T] then
  44542. result := mLITERAL;
  44543. end;
  44544. inc(P);
  44545. inc(T);
  44546. end;
  44547. if result = mNONE then
  44548. if T <= TLen then
  44549. result := mEND else
  44550. result := mVALID;
  44551. end;
  44552. function MatchAfterStar(Pattern, Text: RawUTF8): TMatch;
  44553. var P, T, PLen, TLen: Integer;
  44554. begin
  44555. result := mNONE;
  44556. P := 1;
  44557. T := 1;
  44558. PLen := Length(Pattern);
  44559. TLen := Length(Text);
  44560. if TLen = 1 then begin
  44561. result := mVALID;
  44562. exit;
  44563. end else
  44564. if (PLen = 0) or (TLen = 0) then begin
  44565. result := mABORT;
  44566. exit;
  44567. end;
  44568. while ((T <= TLen) and (P < PLen)) and ((Pattern[P] = SINGLE) or
  44569. (Pattern[P] = KLEENE_STAR)) do begin
  44570. if Pattern[P] = SINGLE then
  44571. inc(T);
  44572. inc(P);
  44573. end;
  44574. if T >= TLen then begin
  44575. result := mABORT;
  44576. exit;
  44577. end else
  44578. if P >= PLen then begin
  44579. result := mVALID;
  44580. exit;
  44581. end;
  44582. repeat
  44583. if (Pattern[P] = Text[T]) or (Pattern[P] = RANGE_OPEN) then begin
  44584. Pattern := Copy(Pattern, P, PLen);
  44585. Text := Copy(Text, T, TLen);
  44586. PLen := Length(Pattern);
  44587. TLen := Length(Text);
  44588. p := 1;
  44589. t := 1;
  44590. result := Matche(Pattern, Text);
  44591. if result <> mVALID then
  44592. result := mNONE; // retry until end of Text, (check below) or result valid
  44593. end;
  44594. inc(T);
  44595. if (T > TLen) or (P > PLen) then begin
  44596. result := mABORT;
  44597. exit;
  44598. end;
  44599. until result <> mNONE;
  44600. end;
  44601. begin // IsMatch() main block
  44602. if CaseInsensitive then
  44603. result := (Matche(LowerCase(Pattern), LowerCase(Text)) = mVALID) else
  44604. result := (Matche(Pattern, Text) = mVALID);
  44605. end;
  44606. { TSynFilterOrValidate }
  44607. constructor TSynFilterOrValidate.Create(const aParameters: RawUTF8);
  44608. begin
  44609. inherited Create;
  44610. SetParameters(aParameters); // should parse the JSON-encoded parameters
  44611. end;
  44612. constructor TSynFilterOrValidate.CreateUTF8(const Format: RawUTF8;
  44613. const Args, Params: array of const);
  44614. begin
  44615. Create(FormatUTF8(Format,Args,Params,true));
  44616. end;
  44617. procedure TSynFilterOrValidate.SetParameters(const Value: RawUTF8);
  44618. begin
  44619. fParameters := Value;
  44620. end;
  44621. function TSynFilterOrValidate.AddOnce(var aObjArray: TSynFilterOrValidateObjArray;
  44622. aFreeIfAlreadyThere: boolean): TSynFilterOrValidate;
  44623. var i: integer;
  44624. begin
  44625. if self<>nil then begin
  44626. for i := 0 to length(aObjArray)-1 do
  44627. if (PPointer(aObjArray[i])^=PPointer(self)^) and
  44628. (aObjArray[i].fParameters=fParameters) then begin
  44629. if aFreeIfAlreadyThere then
  44630. Free;
  44631. result := aObjArray[i];
  44632. exit;
  44633. end;
  44634. ObjArrayAdd(aObjArray,self);
  44635. end;
  44636. result := self;
  44637. end;
  44638. { TSynFilterUpperCase }
  44639. procedure TSynFilterUpperCase.Process(aFieldIndex: integer; var Value: RawUTF8);
  44640. begin
  44641. Value := SynCommons.UpperCase(Value);
  44642. end;
  44643. { TSynFilterUpperCaseU }
  44644. procedure TSynFilterUpperCaseU.Process(aFieldIndex: integer; var Value: RawUTF8);
  44645. begin
  44646. Value := UpperCaseU(Value);
  44647. end;
  44648. { TSynFilterLowerCase }
  44649. procedure TSynFilterLowerCase.Process(aFieldIndex: integer; var Value: RawUTF8);
  44650. begin
  44651. Value := LowerCase(Value);
  44652. end;
  44653. { TSynFilterLowerCaseU }
  44654. procedure TSynFilterLowerCaseU.Process(aFieldIndex: integer; var Value: RawUTF8);
  44655. begin
  44656. Value := LowerCaseU(Value);
  44657. end;
  44658. { TSynFilterTrim }
  44659. procedure TSynFilterTrim.Process(aFieldIndex: integer; var Value: RawUTF8);
  44660. begin
  44661. Value := Trim(Value);
  44662. end;
  44663. { TSynFilterTruncate}
  44664. procedure TSynFilterTruncate.SetParameters(const Value: RawUTF8);
  44665. var V: TPUtf8CharDynArray;
  44666. tmp: TSynTempBuffer;
  44667. begin
  44668. tmp.Init(Value);
  44669. JSONDecode(tmp.buf,['MaxLength','UTF8Length'],V);
  44670. fMaxLength := GetCardinalDef(V[0],0);
  44671. fUTF8Length := IdemPChar(V[1],'1') or IdemPChar(V[1],'TRUE');
  44672. tmp.Done;
  44673. end;
  44674. procedure TSynFilterTruncate.Process(aFieldIndex: integer; var Value: RawUTF8);
  44675. begin
  44676. if fMaxLength-1<cardinal(maxInt) then
  44677. if fUTF8Length then
  44678. Utf8TruncateToLength(Value,fMaxLength) else
  44679. Utf8TruncateToUnicodeLength(Value,fMaxLength);
  44680. end;
  44681. { TSynValidateIPAddress }
  44682. function TSynValidateIPAddress.Process(aFieldIndex: integer; const Value: RawUTF8;
  44683. var ErrorMsg: string): boolean;
  44684. begin
  44685. result := IsValidIP4Address(pointer(Value));
  44686. if not result then
  44687. ErrorMsg := Format(sInvalidIPAddress,[UTF8ToString(Value)]);
  44688. end;
  44689. { TSynValidateEmail }
  44690. function TSynValidateEmail.Process(aFieldIndex: integer; const Value: RawUTF8;
  44691. var ErrorMsg: string): boolean;
  44692. var TLD,DOM: RawUTF8;
  44693. i: integer;
  44694. const TopLevelTLD: array[0..19] of PUTF8Char = (
  44695. // see http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
  44696. 'aero','asia','biz','cat','com','coop','edu','gov','info','int','jobs',
  44697. 'mil','mobi','museum','name','net','org','pro','tel','travel'); // no xxx !
  44698. begin
  44699. if IsValidEmail(pointer(Value)) then
  44700. repeat
  44701. DOM := lowercase(copy(Value,PosEx('@',Value)+1,100));
  44702. if length(DOM)>63 then
  44703. break; // exceeded 63-character limit of a DNS name
  44704. if (ForbiddenDomains<>'') and (FindCSVIndex(pointer(ForbiddenDomains),DOM)>=0) then
  44705. break;
  44706. i := length(Value);
  44707. while (i>0) and (Value[i]<>'.') do dec(i);
  44708. TLD := lowercase(copy(Value,i+1,100));
  44709. if (AllowedTLD<>'') and (FindCSVIndex(pointer(AllowedTLD),TLD)<0) then
  44710. break;
  44711. if (ForbiddenTLD<>'') and (FindCSVIndex(pointer(ForbiddenTLD),TLD)>=0) then
  44712. break;
  44713. if not fAnyTLD then
  44714. if FastFindPUTF8CharSorted(@TopLevelTLD,high(TopLevelTLD),pointer(TLD))<0 then
  44715. if length(TLD)<>2 then
  44716. break; // assume a two chars string is a ISO 3166-1 alpha-2 code
  44717. result := true;
  44718. exit;
  44719. until true;
  44720. ErrorMsg := Format(sInvalidEmailAddress,[UTF8ToString(Value)]);
  44721. result := false;
  44722. end;
  44723. procedure TSynValidateEmail.SetParameters(const Value: RawUTF8);
  44724. var V: TPUtf8CharDynArray;
  44725. tmp: TSynTempBuffer;
  44726. begin
  44727. inherited;
  44728. tmp.Init(Value);
  44729. JSONDecode(tmp.buf,['AllowedTLD','ForbiddenTLD','ForbiddenDomains','AnyTLD'],V);
  44730. LowerCaseCopy(V[0],StrLen(V[0]),fAllowedTLD);
  44731. LowerCaseCopy(V[1],StrLen(V[1]),fForbiddenTLD);
  44732. LowerCaseCopy(V[2],StrLen(V[2]),fForbiddenDomains);
  44733. AnyTLD := IdemPChar(V[3],'1') or IdemPChar(V[3],'TRUE');
  44734. tmp.Done;
  44735. end;
  44736. { TSynValidatePattern }
  44737. function TSynValidatePattern.Process(aFieldIndex: integer; const Value: RawUTF8;
  44738. var ErrorMsg: string): boolean;
  44739. begin
  44740. result := IsMatch(fParameters,Value,ClassType=TSynValidatePatternI);
  44741. if not result then
  44742. ErrorMsg := Format(sInvalidPattern,[UTF8ToString(Value)]);
  44743. end;
  44744. { TSynValidateNonVoidText }
  44745. function Character01n(n: integer): string;
  44746. begin
  44747. if n<0 then
  44748. n := 0 else
  44749. if n>1 then
  44750. n := 2;
  44751. result := GetCSVItemString(pointer(string(sCharacter01n)),n);
  44752. end;
  44753. procedure InvalidTextLengthMin(min: integer; var result: string);
  44754. begin
  44755. result := Format(sInvalidTextLengthMin,[min,Character01n(min)]);
  44756. end;
  44757. function TSynValidateNonVoidText.Process(aFieldIndex: integer; const Value: RawUTF8;
  44758. var ErrorMsg: string): boolean;
  44759. begin
  44760. if Value='' then begin
  44761. InvalidTextLengthMin(1,ErrorMsg);
  44762. result := false;
  44763. end else
  44764. result := true;
  44765. end;
  44766. { TSynValidateText }
  44767. procedure TSynValidateText.SetErrorMsg(fPropsIndex, InvalidTextIndex,
  44768. MainIndex: integer; var result: string);
  44769. var P: PChar;
  44770. begin
  44771. P := pointer(string(sInvalidTextChar));
  44772. result := GetCSVItemString(P,MainIndex);
  44773. if fPropsIndex>0 then
  44774. result := Format(result,
  44775. [fProps[fPropsIndex],GetCSVItemString(P,InvalidTextIndex),
  44776. Character01n(fProps[fPropsIndex])]);
  44777. end;
  44778. function TSynValidateText.Process(aFieldIndex: integer; const Value: RawUTF8;
  44779. var ErrorMsg: string): boolean;
  44780. var i, L: cardinal;
  44781. Min: array[2..7] of cardinal;
  44782. begin
  44783. result := false;
  44784. if fUTF8Length then
  44785. L := length(Value) else
  44786. L := Utf8ToUnicodeLength(pointer(Value));
  44787. if L<MinLength then
  44788. InvalidTextLengthMin(MinLength,ErrorMsg) else
  44789. if L>MaxLength then
  44790. ErrorMsg := Format(sInvalidTextLengthMax,[MaxLength,Character01n(MaxLength)]) else begin
  44791. FillcharFast(Min,sizeof(Min),0);
  44792. L := length(Value);
  44793. for i := 1 to L do
  44794. case Value[i] of
  44795. ' ':
  44796. inc(Min[7]);
  44797. 'a'..'z': begin
  44798. inc(Min[2]);
  44799. inc(Min[5]);
  44800. end;
  44801. 'A'..'Z': begin
  44802. inc(Min[2]);
  44803. inc(Min[6]);
  44804. end;
  44805. '0'..'9':
  44806. inc(Min[3]);
  44807. '_','!',';','.',',','/',':','?','%','$','=','"','#','@','(',')','{','}',
  44808. '+','''','-','*':
  44809. inc(Min[4]);
  44810. end;
  44811. for i := 2 to 7 do
  44812. if Min[i]<fProps[i] then begin
  44813. SetErrorMsg(i,i,0,ErrorMsg);
  44814. exit;
  44815. end else
  44816. if Min[i]>fProps[i+8] then begin
  44817. SetErrorMsg(i+8,i,1,ErrorMsg);
  44818. exit;
  44819. end;
  44820. if Value<>'' then begin
  44821. if MaxLeftTrimCount<cardinal(maxInt) then begin
  44822. // if MaxLeftTrimCount is set, check against Value
  44823. i := 0;
  44824. while (i<L) and (Value[i+1]=' ') do inc(i);
  44825. if i>MaxLeftTrimCount then begin
  44826. SetErrorMsg(0,0,8,ErrorMsg);
  44827. exit;
  44828. end;
  44829. end;
  44830. if MaxRightTrimCount<cardinal(maxInt) then begin
  44831. // if MaxRightTrimCount is set, check against Value
  44832. i := 0;
  44833. while (i<L) and (Value[L-i]=' ') do dec(i);
  44834. if i>MaxRightTrimCount then begin
  44835. SetErrorMsg(0,0,9,ErrorMsg);
  44836. exit;
  44837. end;
  44838. end;
  44839. end;
  44840. result := true;
  44841. end;
  44842. end;
  44843. procedure TSynValidateText.SetParameters(const Value: RawUTF8);
  44844. var V: TPUtf8CharDynArray;
  44845. i: integer;
  44846. tmp: TSynTempBuffer;
  44847. const DEFAULT: TSynValidateTextProps = (
  44848. 1,maxInt,0,0,0,0,0,0,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt);
  44849. begin
  44850. if (MinLength=0) and (MaxLength=0) then // if not previously set
  44851. fProps := DEFAULT;
  44852. inherited SetParameters(Value);
  44853. if Value='' then
  44854. exit;
  44855. tmp.Init(Value);
  44856. try
  44857. JSONDecode(tmp.buf,['MinLength','MaxLength',
  44858. 'MinAlphaCount','MinDigitCount','MinPunctCount',
  44859. 'MinLowerCount','MinUpperCount','MinSpaceCount',
  44860. 'MaxLeftTrimCount','MaxRightTrimCount',
  44861. 'MaxAlphaCount','MaxDigitCount','MaxPunctCount',
  44862. 'MaxLowerCount','MaxUpperCount','MaxSpaceCount',
  44863. 'UTF8Length'],V);
  44864. if length(V)<>length(fProps)+1 then
  44865. exit;
  44866. for i := 0 to high(fProps) do
  44867. fProps[i] := GetCardinalDef(V[i],fProps[i]);
  44868. fUTF8Length := IdemPChar(V[length(fProps)],'1') or
  44869. IdemPChar(V[length(fProps)],'TRUE');
  44870. finally
  44871. tmp.Done;
  44872. end;
  44873. end;
  44874. { TSynValidatePassWord }
  44875. procedure TSynValidatePassWord.SetParameters(const Value: RawUTF8);
  44876. const DEFAULT: TSynValidateTextProps = (
  44877. 5,20,1,1,1,1,1,0,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,maxInt,0);
  44878. begin
  44879. // set default values for validating a strong password
  44880. fProps := DEFAULT;
  44881. // read custom parameters
  44882. inherited;
  44883. end;
  44884. { ************ some console functions }
  44885. var
  44886. TextAttr: integer = ord(ccDarkGray);
  44887. {$ifdef MSWINDOWS}
  44888. procedure InitConsole;
  44889. begin
  44890. if StdOut=0 then begin
  44891. StdOut := GetStdHandle(STD_OUTPUT_HANDLE);
  44892. if StdOut=INVALID_HANDLE_VALUE then
  44893. StdOut := 0;
  44894. end;
  44895. end;
  44896. procedure TextColor(Color: TConsoleColor);
  44897. var oldAttr: integer;
  44898. begin
  44899. InitConsole;
  44900. oldAttr := TextAttr;
  44901. TextAttr := (TextAttr and $F0) or ord(Color);
  44902. if TextAttr<>oldAttr then
  44903. SetConsoleTextAttribute(StdOut,TextAttr);
  44904. end;
  44905. procedure TextBackground(Color: TConsoleColor);
  44906. var oldAttr: integer;
  44907. begin
  44908. InitConsole;
  44909. oldAttr := TextAttr;
  44910. TextAttr := (TextAttr and $0F) or (ord(Color) shl 4);
  44911. if TextAttr<>oldAttr then
  44912. SetConsoleTextAttribute(StdOut,TextAttr);
  44913. end;
  44914. function ConsoleKeyPressed(ExpectedKey: Word): Boolean;
  44915. var lpNumberOfEvents: DWORD;
  44916. lpBuffer: TInputRecord;
  44917. lpNumberOfEventsRead : DWORD;
  44918. nStdHandle: THandle;
  44919. begin
  44920. result := false;
  44921. nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  44922. lpNumberOfEvents := 0;
  44923. GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
  44924. if lpNumberOfEvents<>0 then begin
  44925. PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
  44926. if lpNumberOfEventsRead<>0 then
  44927. if lpBuffer.EventType=KEY_EVENT then
  44928. if lpBuffer.Event.KeyEvent.bKeyDown and
  44929. ((ExpectedKey=0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode=ExpectedKey)) then
  44930. result := true else
  44931. FlushConsoleInputBuffer(nStdHandle) else
  44932. FlushConsoleInputBuffer(nStdHandle);
  44933. end;
  44934. end;
  44935. procedure ConsoleWaitForEnterKey;
  44936. {$ifdef DELPHI5OROLDER}
  44937. begin
  44938. readln;
  44939. end;
  44940. {$else}
  44941. var msg: TMsg;
  44942. begin
  44943. while not ConsoleKeyPressed(VK_RETURN) do begin
  44944. {$ifndef LVCL}
  44945. if GetCurrentThreadID=MainThreadID then
  44946. CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif} else
  44947. {$endif}
  44948. WaitMessage;
  44949. while PeekMessage(msg,0,0,0,PM_REMOVE) do
  44950. if Msg.Message=WM_QUIT then
  44951. exit else begin
  44952. TranslateMessage(Msg);
  44953. DispatchMessage(Msg);
  44954. end;
  44955. end;
  44956. end;
  44957. {$endif DELPHI5OROLDER}
  44958. {$else MSWINDOWS}
  44959. // we by-pass crt.pp since this unit cancels the SIGINT signal
  44960. {$I-}
  44961. procedure TextColor(Color: TConsoleColor);
  44962. const AnsiTbl : string[8]='04261537';
  44963. begin
  44964. if ord(color)=TextAttr then
  44965. exit;
  44966. TextAttr := ord(color);
  44967. if ord(color)>=8 then
  44968. write(#27'[1;3') else
  44969. write(#27'[0;3');
  44970. write(AnsiTbl[(ord(color) and 7)+1],'m');
  44971. ioresult;
  44972. end;
  44973. {$I+}
  44974. procedure TextBackground(Color: TConsoleColor);
  44975. begin // not implemented yet - but not needed either
  44976. end;
  44977. procedure ConsoleWaitForEnterKey;
  44978. begin
  44979. Readln;
  44980. end;
  44981. {$endif MSWINDOWS}
  44982. function Utf8ToConsole(const S: RawUTF8): RawByteString;
  44983. begin
  44984. {$ifdef MSWINDOWS}
  44985. result := TSynAnsiConvert.Engine(CP_OEMCP).UTF8ToAnsi(S);
  44986. {$else}
  44987. result := S;
  44988. {$endif}
  44989. end;
  44990. {$I-}
  44991. procedure ConsoleShowFatalException(E: Exception);
  44992. begin
  44993. ioresult;
  44994. TextColor(ccLightRed);
  44995. write(#13#10'Fatal exception ');
  44996. TextColor(ccWhite);
  44997. write(E.ClassName);
  44998. TextColor(ccLightRed);
  44999. Writeln(' raised with message:'#13#10' ',UTF8ToConsole(StringToUTF8(E.Message)));
  45000. TextColor(ccLightGray);
  45001. writeln(#13#10'Program will now abort');
  45002. {$ifndef LINUX}
  45003. writeln('Press [Enter] to quit');
  45004. if ioresult=0 then
  45005. Readln;
  45006. {$endif}
  45007. ioresult;
  45008. end;
  45009. {$I+}
  45010. { ************ Unit-Testing classes and functions }
  45011. function KB(bytes: Int64): RawUTF8;
  45012. var hi,rem: cardinal;
  45013. begin
  45014. if bytes>=1 shl 20 then begin
  45015. if bytes>=Int64(1) shl 40 then begin
  45016. bytes := bytes shr 20;
  45017. result := ' TB';
  45018. end else
  45019. if bytes>=1 shl 30 then begin
  45020. bytes := bytes shr 10;
  45021. result := ' GB';
  45022. end else
  45023. result := ' MB';
  45024. rem := (PtrUInt(bytes) and pred(1 shl 20))div (102*1024);
  45025. hi := bytes shr 20;
  45026. if rem=10 then begin
  45027. rem := 0;
  45028. inc(hi);
  45029. end;
  45030. if rem<>0 then
  45031. result := FormatUTF8('%.%%',[hi,rem,result]) else
  45032. result := FormatUTF8('%%',[hi,result]);
  45033. end else
  45034. if bytes>1023*9 then
  45035. result := UInt32ToUtf8(PtrUInt(bytes) shr 10)+' KB' else
  45036. result := UInt32ToUtf8(PtrUInt(bytes))+' B';
  45037. end;
  45038. function IntToThousandString(Value: integer; const ThousandSep: RawUTF8=','): RawUTF8;
  45039. var i,L,Len: cardinal;
  45040. begin
  45041. Int32ToUtf8(value,result);
  45042. L := length(Result);
  45043. Len := L+1;
  45044. if Value<0 then
  45045. dec(L,2) else // ignore '-' sign
  45046. dec(L);
  45047. for i := 1 to L div 3 do
  45048. insert(ThousandSep,Result,Len-i*3);
  45049. end;
  45050. function MicroSecToString(Micro: QWord): RawUTF8;
  45051. function TwoDigitToString(value: cardinal): RawUTF8;
  45052. var L: integer;
  45053. begin
  45054. UInt32ToUtf8(value,result);
  45055. L := length(result);
  45056. if L=1 then
  45057. result := '0.0'+result else // '3' -> '0.03'
  45058. if L=2 then
  45059. result := '0.'+result else // '35' -> '0.35'
  45060. insert('.',result,L-1); // '103' -> '1.03'
  45061. end;
  45062. begin
  45063. if Micro<=0 then
  45064. result := '0us' else
  45065. if Micro<1000 then
  45066. result := UInt32ToUtf8(Int64Rec(Micro).Lo)+'us' else
  45067. if Micro<1000*1000 then
  45068. result := TwoDigitToString(Micro div 10)+'ms' else
  45069. result := TwoDigitToString(Micro div (10*1000))+'s';
  45070. end;
  45071. function IsInitializedCriticalSection(const CS: TRTLCriticalSection): Boolean;
  45072. begin
  45073. result := not IsZero(@CS,sizeof(CS));
  45074. end;
  45075. procedure InitializeCriticalSectionIfNeededAndEnter(var CS: TRTLCriticalSection);
  45076. begin
  45077. if IsZero(@CS,sizeof(CS)) then
  45078. InitializeCriticalSection(CS);
  45079. EnterCriticalSection(CS);
  45080. end;
  45081. procedure DeleteCriticalSectionIfNeeded(var CS: TRTLCriticalSection);
  45082. begin
  45083. if not IsZero(@CS,sizeof(CS)) then
  45084. DeleteCriticalSection(CS);
  45085. end;
  45086. { ******************* process monitoring / statistics ********************** }
  45087. { TPrecisionTimer }
  45088. function TPrecisionTimer.ByCount(Count: QWord): RawUTF8;
  45089. begin
  45090. if Count=0 then
  45091. result := '0' else // avoid div per 0 exception
  45092. result := MicroSecToString(iTime div Count);
  45093. end;
  45094. function TPrecisionTimer.PerSec(const Count: QWord): QWord;
  45095. begin
  45096. if iTime<=0 then // avoid negative value in case of incorrect Start/Stop sequence
  45097. result := 0 else // avoid div per 0 exception
  45098. result := (Count*QWord(1000*1000)) div iTime;
  45099. end;
  45100. procedure TPrecisionTimer.Init;
  45101. begin
  45102. FillcharFast(self,sizeof(self),0);
  45103. end;
  45104. procedure TPrecisionTimer.Start;
  45105. begin
  45106. FillcharFast(self,sizeof(self),0);
  45107. QueryPerformanceCounter(iStart);
  45108. iLast := iStart;
  45109. end;
  45110. procedure TPrecisionTimer.ComputeTime;
  45111. begin
  45112. QueryPerformanceCounter(iStop);
  45113. if iFreq=0 then begin
  45114. QueryPerformanceFrequency(iFreq);
  45115. if iFreq=0 then begin
  45116. iTime := 0;
  45117. iLastTime := 0;
  45118. exit;
  45119. end;
  45120. end;
  45121. iTime := ((iStop-iStart)*QWord(1000*1000))div iFreq;
  45122. iLastTime := ((iStop-iLast)*QWord(1000*1000))div iFreq;
  45123. end;
  45124. procedure TPrecisionTimer.FromExternalMicroSeconds(const MicroSeconds: QWord);
  45125. begin
  45126. iLastTime := MicroSeconds;
  45127. inc(iTime,MicroSeconds);
  45128. end;
  45129. function TPrecisionTimer.FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord;
  45130. begin // very close to ComputeTime
  45131. if iFreq=0 then begin
  45132. iTime := 0;
  45133. QueryPerformanceFrequency(iFreq);
  45134. end;
  45135. if iFreq=0 then
  45136. iLastTime := 0 else
  45137. FromExternalMicroSeconds((CounterDiff*QWord(1000*1000))div iFreq);
  45138. result := iLastTime;
  45139. end;
  45140. function TPrecisionTimer.Stop: RawUTF8;
  45141. begin
  45142. ComputeTime;
  45143. result := Time;
  45144. end;
  45145. procedure TPrecisionTimer.Pause;
  45146. begin
  45147. QueryPerformanceCounter(iResume);
  45148. dec(iResume,iStart);
  45149. inc(fPauseCount);
  45150. end;
  45151. procedure TPrecisionTimer.Resume;
  45152. begin
  45153. QueryPerformanceCounter(iStart);
  45154. iLast := iStart;
  45155. dec(iStart,iResume);
  45156. iResume := 0;
  45157. end;
  45158. function TPrecisionTimer.Time: RawUTF8;
  45159. begin
  45160. result := MicroSecToString(iTime);
  45161. end;
  45162. function TPrecisionTimer.LastTime: RawUTF8;
  45163. begin
  45164. result := MicroSecToString(iLastTime);
  45165. end;
  45166. type
  45167. /// a class used internaly by TPrecisionTimer.ProfileMethod
  45168. TPrecisionTimerProfiler = class(TInterfacedObject)
  45169. protected
  45170. fTimer: PPrecisionTimer;
  45171. public
  45172. constructor Create(aTimer: PPrecisionTimer);
  45173. destructor Destroy; override;
  45174. end;
  45175. constructor TPrecisionTimerProfiler.Create(aTimer: PPrecisionTimer);
  45176. begin
  45177. fTimer := aTimer;
  45178. end;
  45179. destructor TPrecisionTimerProfiler.Destroy;
  45180. begin
  45181. if fTimer<>nil then
  45182. fTimer^.Pause;
  45183. inherited;
  45184. end;
  45185. function TPrecisionTimer.ProfileCurrentMethod: IUnknown;
  45186. begin
  45187. if iStart=0 then
  45188. Start else
  45189. Resume;
  45190. result := TPrecisionTimerProfiler.Create(@self);
  45191. end;
  45192. { TLocalPrecisionTimer }
  45193. function TLocalPrecisionTimer.ByCount(Count: cardinal): RawUTF8;
  45194. begin
  45195. result := fTimer.ByCount(Count);
  45196. end;
  45197. procedure TLocalPrecisionTimer.Pause;
  45198. begin
  45199. fTimer.Pause;
  45200. end;
  45201. function TLocalPrecisionTimer.PerSec(Count: cardinal): cardinal;
  45202. begin
  45203. result := fTimer.PerSec(Count);
  45204. end;
  45205. procedure TLocalPrecisionTimer.Resume;
  45206. begin
  45207. fTimer.Resume;
  45208. end;
  45209. procedure TLocalPrecisionTimer.Start;
  45210. begin
  45211. fTimer.Start;
  45212. end;
  45213. function TLocalPrecisionTimer.Stop: RawUTF8;
  45214. begin
  45215. result := fTimer.Stop;
  45216. end;
  45217. constructor TLocalPrecisionTimer.CreateAndStart;
  45218. begin
  45219. inherited;
  45220. fTimer.Start;
  45221. end;
  45222. { TSynMonitorTime }
  45223. function TSynMonitorTime.GetAsText: RawUTF8;
  45224. begin
  45225. result := MicroSecToString(fMicroSeconds);
  45226. end;
  45227. function TSynMonitorTime.PerSecond(const Count: QWord): QWord;
  45228. begin
  45229. if PInt64(@fMicroSeconds)^<=0 then // avoid negative or div per 0
  45230. result := 0 else
  45231. result := (Count*QWord(1000*1000)) div fMicroSeconds;
  45232. end;
  45233. { TSynMonitorOneTime }
  45234. function TSynMonitorOneTime.GetAsText: RawUTF8;
  45235. begin
  45236. result := MicroSecToString(fMicroSeconds);
  45237. end;
  45238. function TSynMonitorOneTime.PerSecond(const Count: QWord): QWord;
  45239. begin
  45240. if PInt64(@fMicroSeconds)^<=0 then // avoid negative or div per 0
  45241. result := 0 else
  45242. result := (Count*QWord(1000*1000)) div fMicroSeconds;
  45243. end;
  45244. { TSynMonitorSize }
  45245. function TSynMonitorSize.GetAsText: RawUTF8;
  45246. begin
  45247. result := KB(fBytes);
  45248. end;
  45249. { TSynMonitorOneSize }
  45250. function TSynMonitorOneSize.GetAsText: RawUTF8;
  45251. begin
  45252. result := KB(fBytes);
  45253. end;
  45254. { TSynMonitorThroughput }
  45255. function TSynMonitorThroughput.GetAsText: RawUTF8;
  45256. begin
  45257. result := KB(fBytesPerSec)+'/s';
  45258. end;
  45259. { TSynMonitor }
  45260. constructor TSynMonitor.Create;
  45261. begin
  45262. inherited Create;
  45263. fTotalTime := TSynMonitorTime.Create;
  45264. fLastTime := TSynMonitorOneTime.Create;
  45265. fMinimalTime := TSynMonitorOneTime.Create;
  45266. fAverageTime := TSynMonitorOneTime.Create;
  45267. fMaximalTime := TSynMonitorOneTime.Create;
  45268. InitializeCriticalSection(fLock);
  45269. end;
  45270. constructor TSynMonitor.Create(const aName: RawUTF8);
  45271. begin
  45272. Create;
  45273. fName := aName;
  45274. end;
  45275. destructor TSynMonitor.Destroy;
  45276. begin
  45277. fMaximalTime.Free;
  45278. fAverageTime.Free;
  45279. fMinimalTime.Free;
  45280. fLastTime.Free;
  45281. fTotalTime.Free;
  45282. DeleteCriticalSection(fLock);
  45283. inherited Destroy;
  45284. end;
  45285. procedure TSynMonitor.Lock;
  45286. begin
  45287. EnterCriticalSection(fLock);
  45288. end;
  45289. procedure TSynMonitor.UnLock;
  45290. begin
  45291. LeaveCriticalSection(fLock);
  45292. end;
  45293. procedure TSynMonitor.Changed;
  45294. begin // do nothing by default - overriden classes may track modified changes
  45295. end;
  45296. procedure TSynMonitor.ProcessStart;
  45297. begin
  45298. if fProcessing then
  45299. raise ESynException.CreateUTF8('Reentrant %.ProcessStart',[self]);
  45300. EnterCriticalSection(fLock);
  45301. try
  45302. InternalTimer.Resume;
  45303. fTaskStatus := taskNotStarted;
  45304. fProcessing := true;
  45305. finally
  45306. LeaveCriticalSection(fLock);
  45307. end;
  45308. end;
  45309. procedure TSynMonitor.ProcessDoTask;
  45310. begin
  45311. EnterCriticalSection(fLock);
  45312. try
  45313. inc(fTaskCount);
  45314. fTaskStatus := taskStarted;
  45315. Changed;
  45316. finally
  45317. LeaveCriticalSection(fLock);
  45318. end;
  45319. end;
  45320. procedure TSynMonitor.ProcessStartTask;
  45321. begin
  45322. if fProcessing then
  45323. raise ESynException.CreateUTF8('Reentrant %.ProcessStart',[self]);
  45324. EnterCriticalSection(fLock);
  45325. try
  45326. InternalTimer.Resume;
  45327. fProcessing := true;
  45328. inc(fTaskCount);
  45329. fTaskStatus := taskStarted;
  45330. Changed;
  45331. finally
  45332. LeaveCriticalSection(fLock);
  45333. end;
  45334. end;
  45335. procedure TSynMonitor.ProcessEnd;
  45336. begin
  45337. EnterCriticalSection(fLock);
  45338. try
  45339. InternalTimer.Pause;
  45340. InternalTimer.ComputeTime;
  45341. LockedFromProcessTimer;
  45342. finally
  45343. LeaveCriticalSection(fLock);
  45344. end;
  45345. end;
  45346. procedure TSynMonitor.LockedFromProcessTimer;
  45347. begin
  45348. fTotalTime.MicroSec := InternalTimer.TimeInMicroSec;
  45349. if fTaskStatus=taskStarted then begin
  45350. fLastTime.MicroSec := InternalTimer.LastTimeInMicroSec;
  45351. if (fMinimalTime.MicroSec=0) or
  45352. (InternalTimer.LastTimeInMicroSec<fMinimalTime.MicroSec) then
  45353. fMinimalTime.MicroSec := InternalTimer.LastTimeInMicroSec;
  45354. if InternalTimer.LastTimeInMicroSec>fMaximalTime.MicroSec then
  45355. fMaximalTime.MicroSec := InternalTimer.LastTimeInMicroSec;
  45356. fTaskStatus := taskNotStarted;
  45357. end;
  45358. LockedPerSecProperties;
  45359. fProcessing := false;
  45360. Changed;
  45361. end;
  45362. function TSynMonitor.FromExternalQueryPerformanceCounters(const CounterDiff: QWord): QWord;
  45363. begin
  45364. EnterCriticalSection(fLock);
  45365. try // thread-safe ProcessStart+ProcessDoTask+ProcessEnd
  45366. inc(fTaskCount);
  45367. fTaskStatus := taskStarted;
  45368. result := InternalTimer.FromExternalQueryPerformanceCounters(CounterDiff);
  45369. LockedFromProcessTimer;
  45370. finally
  45371. LeaveCriticalSection(fLock);
  45372. end;
  45373. end;
  45374. procedure TSynMonitor.FromExternalMicroSeconds(const MicroSecondsElapsed: QWord);
  45375. begin
  45376. EnterCriticalSection(fLock);
  45377. try // thread-safe ProcessStart+ProcessDoTask+ProcessEnd
  45378. inc(fTaskCount);
  45379. fTaskStatus := taskStarted;
  45380. InternalTimer.FromExternalMicroSeconds(MicroSecondsElapsed);
  45381. LockedFromProcessTimer;
  45382. finally
  45383. LeaveCriticalSection(fLock);
  45384. end;
  45385. end;
  45386. class procedure TSynMonitor.InitializeObjArray(var ObjArr; Count: integer);
  45387. var i: integer;
  45388. begin
  45389. ObjArrayClear(ObjArr);
  45390. SetLength(TPointerDynArray(ObjArr),Count);
  45391. for i := 0 to Count-1 do
  45392. TPointerDynArray(ObjArr)[i] := Create;
  45393. end;
  45394. procedure TSynMonitor.ProcessError(const info: variant);
  45395. begin
  45396. EnterCriticalSection(fLock);
  45397. try
  45398. if not VarIsEmptyOrNull(info) then
  45399. inc(fInternalErrors);
  45400. fLastInternalError := info;
  45401. Changed;
  45402. finally
  45403. LeaveCriticalSection(fLock);
  45404. end;
  45405. end;
  45406. procedure TSynMonitor.ProcessErrorNumber(info: integer);
  45407. begin
  45408. ProcessError(info);
  45409. end;
  45410. procedure TSynMonitor.LockedPerSecProperties;
  45411. begin
  45412. if fTaskCount=0 then
  45413. exit; // avoid division per zero
  45414. fPerSec := fTotalTime.PerSecond(fTaskCount);
  45415. fAverageTime.MicroSec := Round(fTotalTime.MicroSec/fTaskCount);
  45416. end;
  45417. procedure TSynMonitor.Sum(another: TSynMonitor);
  45418. begin
  45419. if (self=nil) or (another=nil) then
  45420. exit;
  45421. EnterCriticalSection(fLock);
  45422. EnterCriticalSection(another.fLock);
  45423. try
  45424. LockedSum(another);
  45425. finally
  45426. LeaveCriticalSection(another.fLock);
  45427. LeaveCriticalSection(fLock);
  45428. end;
  45429. end;
  45430. procedure TSynMonitor.LockedSum(another: TSynMonitor);
  45431. begin
  45432. fTotalTime.MicroSec := fTotalTime.MicroSec+another.fTotalTime.MicroSec;
  45433. if (fMinimalTime.MicroSec=0) or
  45434. (another.fMinimalTime.MicroSec<fMinimalTime.MicroSec) then
  45435. fMinimalTime.MicroSec := another.fMinimalTime.MicroSec;
  45436. if another.fMaximalTime.MicroSec>fMaximalTime.MicroSec then
  45437. fMaximalTime.MicroSec := another.fMaximalTime.MicroSec;
  45438. inc(fTaskCount,another.fTaskCount);
  45439. if another.Processing then
  45440. fProcessing := true; // if any thread is active, whole daemon is active
  45441. inc(fInternalErrors,another.Errors);
  45442. end;
  45443. procedure TSynMonitor.WriteDetailsTo(W: TTextWriter);
  45444. begin
  45445. EnterCriticalSection(fLock);
  45446. try
  45447. W.WriteObject(self);
  45448. finally
  45449. LeaveCriticalSection(fLock);
  45450. end;
  45451. end;
  45452. procedure TSynMonitor.ComputeDetailsTo(W: TTextWriter);
  45453. begin
  45454. EnterCriticalSection(fLock);
  45455. try
  45456. LockedPerSecProperties; // may not have been calculated after Sum()
  45457. WriteDetailsTo(W);
  45458. finally
  45459. LeaveCriticalSection(fLock);
  45460. end;
  45461. end;
  45462. function TSynMonitor.ComputeDetailsJSON: RawUTF8;
  45463. var W: TTextWriter;
  45464. begin
  45465. W := DefaultTextWriterJSONClass.CreateOwnedStream;
  45466. try
  45467. ComputeDetailsTo(W);
  45468. W.SetText(result);
  45469. finally
  45470. W.Free;
  45471. end;
  45472. end;
  45473. {$ifndef NOVARIANTS}
  45474. function TSynMonitor.ComputeDetails: variant;
  45475. begin
  45476. _Json(ComputeDetailsJSON,result,JSON_OPTIONS_FAST);
  45477. end;
  45478. {$endif}
  45479. { TSynMonitorWithSize}
  45480. constructor TSynMonitorWithSize.Create;
  45481. begin
  45482. inherited Create;
  45483. fSize := TSynMonitorSize.Create;
  45484. fThroughput := TSynMonitorThroughput.Create;
  45485. end;
  45486. destructor TSynMonitorWithSize.Destroy;
  45487. begin
  45488. inherited Destroy;
  45489. fThroughput.Free;
  45490. fSize.Free;
  45491. end;
  45492. procedure TSynMonitorWithSize.LockedPerSecProperties;
  45493. begin
  45494. inherited LockedPerSecProperties;
  45495. fThroughput.BytesPerSec := fTotalTime.PerSecond(fSize.Bytes);
  45496. end;
  45497. procedure TSynMonitorWithSize.AddSize(const Bytes: QWord);
  45498. begin
  45499. EnterCriticalSection(fLock);
  45500. try
  45501. fSize.Bytes := fSize.Bytes+Bytes;
  45502. finally
  45503. LeaveCriticalSection(fLock);
  45504. end;
  45505. end;
  45506. procedure TSynMonitorWithSize.LockedSum(another: TSynMonitor);
  45507. begin
  45508. inherited LockedSum(another);
  45509. if another.InheritsFrom(TSynMonitorWithSize) then
  45510. AddSize(TSynMonitorWithSize(another).Size.Bytes);
  45511. end;
  45512. { TSynMonitorInputOutput }
  45513. constructor TSynMonitorInputOutput.Create;
  45514. begin
  45515. inherited Create;
  45516. fInput := TSynMonitorSize.Create;
  45517. fOutput := TSynMonitorSize.Create;
  45518. fInputThroughput := TSynMonitorThroughput.Create;
  45519. fOutputThroughput := TSynMonitorThroughput.Create;
  45520. end;
  45521. destructor TSynMonitorInputOutput.Destroy;
  45522. begin
  45523. fOutputThroughput.Free;
  45524. fOutput.Free;
  45525. fInputThroughput.Free;
  45526. fInput.Free;
  45527. inherited Destroy;
  45528. end;
  45529. procedure TSynMonitorInputOutput.LockedPerSecProperties;
  45530. begin
  45531. inherited LockedPerSecProperties;
  45532. fInputThroughput.BytesPerSec := fTotalTime.PerSecond(fInput.Bytes);
  45533. fOutputThroughput.BytesPerSec := fTotalTime.PerSecond(fOutput.Bytes);
  45534. end;
  45535. procedure TSynMonitorInputOutput.AddSize(const Incoming, Outgoing: QWord);
  45536. begin
  45537. EnterCriticalSection(fLock);
  45538. try
  45539. fInput.Bytes := fInput.Bytes+Incoming;
  45540. fOutput.Bytes := fOutput.Bytes+Outgoing;
  45541. finally
  45542. LeaveCriticalSection(fLock);
  45543. end;
  45544. end;
  45545. procedure TSynMonitorInputOutput.LockedSum(another: TSynMonitor);
  45546. begin
  45547. inherited LockedSum(another);
  45548. if another.InheritsFrom(TSynMonitorInputOutput) then begin
  45549. fInput.Bytes := fInput.Bytes+TSynMonitorInputOutput(another).Input.Bytes;
  45550. fOutput.Bytes := fOutput.Bytes+TSynMonitorInputOutput(another).Output.Bytes;
  45551. end;
  45552. end;
  45553. { TSynMonitorServer }
  45554. procedure TSynMonitorServer.ClientConnect;
  45555. begin
  45556. EnterCriticalSection(fLock);
  45557. try
  45558. inc(fClientsCurrent);
  45559. if fClientsCurrent>fClientsMax then
  45560. fClientsMax := fClientsCurrent;
  45561. Changed;
  45562. finally
  45563. LeaveCriticalSection(fLock);
  45564. end;
  45565. end;
  45566. procedure TSynMonitorServer.ClientDisconnect;
  45567. begin
  45568. EnterCriticalSection(fLock);
  45569. try
  45570. if fClientsCurrent>0 then
  45571. dec(fClientsCurrent);
  45572. Changed;
  45573. finally
  45574. LeaveCriticalSection(fLock);
  45575. end;
  45576. end;
  45577. procedure TSynMonitorServer.ClientDisconnectAll;
  45578. begin
  45579. EnterCriticalSection(fLock);
  45580. try
  45581. fClientsCurrent := 0;
  45582. Changed;
  45583. finally
  45584. LeaveCriticalSection(fLock);
  45585. end;
  45586. end;
  45587. function TSynMonitorServer.GetClientsCurrent: TSynMonitorOneCount;
  45588. begin
  45589. EnterCriticalSection(fLock);
  45590. try
  45591. result := fClientsCurrent;
  45592. finally
  45593. LeaveCriticalSection(fLock);
  45594. end;
  45595. end;
  45596. function TSynMonitorServer.AddCurrentRequestCount(diff: integer): integer;
  45597. begin
  45598. EnterCriticalSection(fLock);
  45599. try
  45600. inc(fCurrentRequestCount,diff);
  45601. result := fCurrentRequestCount;
  45602. finally
  45603. LeaveCriticalSection(fLock);
  45604. end;
  45605. end;
  45606. { TSynMonitorMemory }
  45607. constructor TSynMonitorMemory.Create;
  45608. begin
  45609. FAllocatedUsed := TSynMonitorOneSize.create;
  45610. FAllocatedReserved := TSynMonitorOneSize.create;
  45611. FPhysicalMemoryFree := TSynMonitorOneSize.Create;
  45612. FVirtualMemoryFree := TSynMonitorOneSize.Create;
  45613. FPagingFileTotal := TSynMonitorOneSize.Create;
  45614. FPhysicalMemoryTotal := TSynMonitorOneSize.Create;
  45615. FVirtualMemoryTotal := TSynMonitorOneSize.Create;
  45616. FPagingFileFree := TSynMonitorOneSize.Create;
  45617. end;
  45618. destructor TSynMonitorMemory.Destroy;
  45619. begin
  45620. FAllocatedReserved.Free;
  45621. FAllocatedUsed.Free;
  45622. FPhysicalMemoryFree.Free;
  45623. FVirtualMemoryFree.Free;
  45624. FPagingFileTotal.Free;
  45625. FPhysicalMemoryTotal.Free;
  45626. FVirtualMemoryTotal.Free;
  45627. FPagingFileFree.Free;
  45628. inherited Destroy;
  45629. end;
  45630. class function TSynMonitorMemory.FreeAsText: RawUTF8;
  45631. begin
  45632. with TSynMonitorMemory.Create do
  45633. try
  45634. FormatUTF8('% / %',[PhysicalMemoryFree.Text,PhysicalMemoryTotal.Text],result);
  45635. finally
  45636. Free;
  45637. end;
  45638. end;
  45639. {$ifndef NOVARIANTS}
  45640. class function TSynMonitorMemory.ToVariant: variant;
  45641. begin
  45642. with TSynMonitorMemory.Create do
  45643. try
  45644. result := _JsonFastFmt('{Allocated:{reserved:%,used:%},Physical:{total:%,free:%},'+
  45645. 'Virtual:{total:%,free:%},Paged:{total:%,free:%}}',
  45646. [AllocatedReserved.Bytes shr 10,AllocatedUsed.Bytes shr 10,
  45647. PhysicalMemoryTotal.Bytes shr 10,PhysicalMemoryFree.Bytes shr 10,
  45648. {$ifdef MSWINDOWS}
  45649. VirtualMemoryTotal.Bytes shr 10,VirtualMemoryFree.Bytes shr 10,
  45650. {$endif}
  45651. PagingFileTotal.Bytes shr 10,PagingFileFree.Bytes shr 10],[]);
  45652. finally
  45653. Free;
  45654. end;
  45655. end;
  45656. {$endif}
  45657. function TSynMonitorMemory.GetAllocatedUsed: TSynMonitorOneSize;
  45658. begin
  45659. RetrieveMemoryInfo;
  45660. result := FAllocatedUsed;
  45661. end;
  45662. function TSynMonitorMemory.GetAllocatedReserved: TSynMonitorOneSize;
  45663. begin
  45664. RetrieveMemoryInfo;
  45665. result := FAllocatedReserved;
  45666. end;
  45667. function TSynMonitorMemory.GetMemoryLoadPercent: integer;
  45668. begin
  45669. RetrieveMemoryInfo;
  45670. result := FMemoryLoadPercent;
  45671. end;
  45672. function TSynMonitorMemory.GetPagingFileFree: TSynMonitorOneSize;
  45673. begin
  45674. RetrieveMemoryInfo;
  45675. result := FPagingFileFree;
  45676. end;
  45677. function TSynMonitorMemory.GetPagingFileTotal: TSynMonitorOneSize;
  45678. begin
  45679. RetrieveMemoryInfo;
  45680. result := FPagingFileTotal;
  45681. end;
  45682. function TSynMonitorMemory.GetPhysicalMemoryFree: TSynMonitorOneSize;
  45683. begin
  45684. RetrieveMemoryInfo;
  45685. result := FPhysicalMemoryFree;
  45686. end;
  45687. function TSynMonitorMemory.GetPhysicalMemoryTotal: TSynMonitorOneSize;
  45688. begin
  45689. RetrieveMemoryInfo;
  45690. result := FPhysicalMemoryTotal;
  45691. end;
  45692. function TSynMonitorMemory.GetVirtualMemoryFree: TSynMonitorOneSize;
  45693. begin
  45694. RetrieveMemoryInfo;
  45695. result := FVirtualMemoryFree;
  45696. end;
  45697. function TSynMonitorMemory.GetVirtualMemoryTotal: TSynMonitorOneSize;
  45698. begin
  45699. RetrieveMemoryInfo;
  45700. result := FVirtualMemoryTotal;
  45701. end;
  45702. {$ifdef MSWINDOWS}
  45703. {$ifndef UNICODE} // missing API for oldest Delphi
  45704. type
  45705. DWORDLONG = Int64;
  45706. TMemoryStatusEx = record
  45707. dwLength: DWORD;
  45708. dwMemoryLoad: DWORD;
  45709. ullTotalPhys: DWORDLONG;
  45710. ullAvailPhys: DWORDLONG;
  45711. ullTotalPageFile: DWORDLONG;
  45712. ullAvailPageFile: DWORDLONG;
  45713. ullTotalVirtual: DWORDLONG;
  45714. ullAvailVirtual: DWORDLONG;
  45715. ullAvailExtendedVirtual: DWORDLONG;
  45716. end;
  45717. // information about the system's current usage of both physical and virtual memory
  45718. function GlobalMemoryStatusEx(var lpBuffer: TMemoryStatusEx): BOOL;
  45719. stdcall; external kernel32;
  45720. {$endif}
  45721. {$endif}
  45722. procedure TSynMonitorMemory.RetrieveMemoryInfo;
  45723. procedure RetrieveInfo;
  45724. {$ifndef FPC}
  45725. var Heap: TMemoryManagerState;
  45726. sb: integer;
  45727. tot,res: QWord;
  45728. {$endif}
  45729. {$ifdef MSWINDOWS}
  45730. var MemoryStatus: TMemoryStatusEx;
  45731. begin
  45732. FillcharFast(MemoryStatus,SizeOf(MemoryStatus),0);
  45733. MemoryStatus.dwLength := SizeOf(MemoryStatus);
  45734. GlobalMemoryStatusEx(MemoryStatus);
  45735. FMemoryLoadPercent := MemoryStatus.dwMemoryLoad;
  45736. FPhysicalMemoryTotal.fBytes := MemoryStatus.ullTotalPhys;
  45737. FPhysicalMemoryFree.fBytes := MemoryStatus.ullAvailPhys;
  45738. FPagingFileTotal.fBytes := MemoryStatus.ullTotalPageFile;
  45739. FPagingFileFree.fBytes := MemoryStatus.ullAvailPageFile;
  45740. FVirtualMemoryTotal.fBytes := MemoryStatus.ullTotalVirtual;
  45741. FVirtualMemoryFree.fBytes := MemoryStatus.ullAvailVirtual;
  45742. {$else}
  45743. {$ifdef LINUX}
  45744. var si: TSysInfo;
  45745. begin
  45746. {$ifdef FPC}
  45747. SysInfo(@si);
  45748. {$else}
  45749. SysInfo(si);
  45750. {$endif}
  45751. if si.totalram<>0 then // avoid div per 0 exception
  45752. FMemoryLoadPercent := ((si.totalram-si.freeram)*100)div si.totalram;
  45753. FPhysicalMemoryTotal.fBytes := si.totalram;
  45754. FPhysicalMemoryFree.fBytes := si.freeram;
  45755. FPagingFileTotal.fBytes := si.totalswap;
  45756. FPagingFileFree.fBytes := si.freeswap;
  45757. // virtual memory information is not available under Linux
  45758. {$else}
  45759. begin // e.g. Darwin
  45760. {$endif LINUX}
  45761. {$endif MSWINDOWS}
  45762. {$ifdef FPC}
  45763. with GetHeapStatus do begin
  45764. FAllocatedUsed.fBytes := TotalAllocated;
  45765. FAllocatedReserved.fBytes := TotalAllocated+TotalFree;
  45766. end;
  45767. {$else}
  45768. {$ifdef LVCL}
  45769. tot := 0;
  45770. res := 0;
  45771. {$else}
  45772. GetMemoryManagerState(Heap); // direct access to FastMM4 statistics
  45773. tot := Heap.TotalAllocatedMediumBlockSize+Heap.TotalAllocatedLargeBlockSize;
  45774. res := Heap.ReservedMediumBlockAddressSpace+Heap.ReservedLargeBlockAddressSpace;
  45775. for sb := 0 to high(Heap.SmallBlockTypeStates) do
  45776. with Heap.SmallBlockTypeStates[sb] do begin
  45777. inc(tot,UseableBlockSize*AllocatedBlockCount);
  45778. inc(res,ReservedAddressSpace);
  45779. end;
  45780. {$endif LVCL}
  45781. FAllocatedUsed.fBytes := tot;
  45782. FAllocatedReserved.fBytes := res;
  45783. {$endif FPC}
  45784. end;
  45785. var tix: cardinal;
  45786. begin
  45787. tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates
  45788. if fLastMemoryInfoRetrievedTix<>tix then begin
  45789. fLastMemoryInfoRetrievedTix := tix;
  45790. RetrieveInfo;
  45791. end;
  45792. end;
  45793. { TSynMonitorDisk }
  45794. constructor TSynMonitorDisk.Create;
  45795. begin
  45796. fAvailableSize := TSynMonitorOneSize.Create;
  45797. fFreeSize := TSynMonitorOneSize.Create;
  45798. fTotalSize := TSynMonitorOneSize.Create;
  45799. end;
  45800. destructor TSynMonitorDisk.Destroy;
  45801. begin
  45802. fAvailableSize.Free;
  45803. fFreeSize.Free;
  45804. fTotalSize.Free;
  45805. inherited;
  45806. end;
  45807. function TSynMonitorDisk.GetName: RawUTF8;
  45808. begin
  45809. RetrieveDiskInfo;
  45810. result := fName;
  45811. end;
  45812. function TSynMonitorDisk.GetAvailable: TSynMonitorOneSize;
  45813. begin
  45814. RetrieveDiskInfo;
  45815. result := fAvailableSize;
  45816. end;
  45817. function TSynMonitorDisk.GetFree: TSynMonitorOneSize;
  45818. begin
  45819. RetrieveDiskInfo;
  45820. result := fFreeSize;
  45821. end;
  45822. function TSynMonitorDisk.GetTotal: TSynMonitorOneSize;
  45823. begin
  45824. RetrieveDiskInfo;
  45825. result := fTotalSize;
  45826. end;
  45827. class function TSynMonitorDisk.FreeAsText: RawUTF8;
  45828. begin
  45829. with TSynMonitorDisk.Create do
  45830. try
  45831. FormatUTF8('% % / %',[Name,FreeSize.Text,TotalSize.Text],result);
  45832. finally
  45833. Free;
  45834. end;
  45835. end;
  45836. {$ifdef MSWINDOWS}
  45837. function GetDiskFreeSpaceExA(lpDirectoryName: PAnsiChar;
  45838. var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes,
  45839. lpTotalNumberOfFreeBytes: QWord): LongBool; stdcall; external kernel32;
  45840. {$endif}
  45841. procedure TSynMonitorDisk.RetrieveDiskInfo;
  45842. procedure RetrieveInfo;
  45843. {$ifdef MSWINDOWS}
  45844. var tmp: array[byte] of AnsiChar;
  45845. dummy,flags: DWORD;
  45846. dn: RawUTF8;
  45847. begin
  45848. if fName='' then
  45849. fName := UpperCase(StringToUTF8(ExtractFileDrive(GetCurrentDir)));
  45850. dn := fName;
  45851. if (dn<>'') and (dn[2]=':') and (dn[3]=#0) then
  45852. dn := dn+'\';
  45853. if fVolumeName='' then begin
  45854. tmp[0] := #0;
  45855. GetVolumeInformationA(pointer(dn),tmp,sizeof(tmp),nil,dummy,flags,nil,0);
  45856. SetString(fVolumeName,PAnsiChar(@tmp),StrLen(@tmp));
  45857. end;
  45858. GetDiskFreeSpaceExA(pointer(dn),PQWord(@fAvailableSize.fBytes)^,
  45859. PQWord(@fTotalSize.fBytes)^,PQWord(@fFreeSize.fBytes)^);
  45860. {$else}
  45861. {$ifdef KYLIX3}
  45862. var fs: TStatFs64;
  45863. h: THandle;
  45864. begin
  45865. if fName='' then
  45866. fName := '.';
  45867. h := FileOpen(fName,fmShareDenyNone);
  45868. fstatfs64(h,fs);
  45869. FileClose(h);
  45870. fAvailableSize.fBytes := fs.f_bavail*fs.f_bsize;
  45871. fFreeSize.fBytes := fAvailableSize.fBytes;
  45872. fTotalSize.fBytes := fs.f_blocks*fs.f_bsize;
  45873. {$endif}
  45874. {$ifdef FPC}
  45875. var fs: pstatfs;
  45876. begin
  45877. if fName='' then
  45878. fName := '.';
  45879. fpStatFS(fName,fs);
  45880. fAvailableSize.fBytes := QWord(fs.bavail)*QWord(fs.bsize);
  45881. fFreeSize.fBytes := fAvailableSize.fBytes;
  45882. fTotalSize.fBytes := QWord(fs.blocks)*QWord(fs.bsize);
  45883. {$endif}
  45884. {$endif}
  45885. end;
  45886. var tix: cardinal;
  45887. begin
  45888. tix := GetTickCount64 shr 7; // allow 128 ms resolution for updates
  45889. if fLastDiskInfoRetrievedTix<>tix then begin
  45890. fLastDiskInfoRetrievedTix := tix;
  45891. RetrieveInfo;
  45892. end;
  45893. end;
  45894. { ******************* cross-cutting classes and functions ***************** }
  45895. { TSynInterfacedObject }
  45896. function TSynInterfacedObject._AddRef: {$ifdef FPC}longint{$else}integer{$endif};
  45897. begin
  45898. result := VirtualAddRef;
  45899. end;
  45900. function TSynInterfacedObject._Release: {$ifdef FPC}longint{$else}integer{$endif};
  45901. begin
  45902. result := VirtualRelease;
  45903. end;
  45904. {$ifdef FPC}
  45905. function TSynInterfacedObject.QueryInterface(
  45906. {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID;
  45907. out Obj): longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  45908. {$else}
  45909. function TSynInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
  45910. {$endif}
  45911. begin
  45912. result := VirtualQueryInterface(IID,Obj);
  45913. end;
  45914. function TSynInterfacedObject.VirtualQueryInterface(const IID: TGUID; out Obj): HResult;
  45915. begin
  45916. result := E_NOINTERFACE;
  45917. end;
  45918. {$ifdef MSWINDOWS}
  45919. {$ifndef DELPHI5OROLDER}
  45920. { TSynFPUException }
  45921. function TSynFPUException.VirtualAddRef: integer;
  45922. begin
  45923. if fRefCount=0 then begin
  45924. fSaved8087 := Get8087CW;
  45925. Set8087CW(fExpected8087); // set FPU exceptions mask
  45926. end;
  45927. inc(fRefCount);
  45928. result := 1; // should never be 0 (mark release of TSynFPUException instance)
  45929. end;
  45930. function TSynFPUException.VirtualRelease: integer;
  45931. begin
  45932. dec(fRefCount);
  45933. if fRefCount=0 then
  45934. Set8087CW(fSaved8087);
  45935. result := 1; // should never be 0 (mark release of TSynFPUException instance)
  45936. end;
  45937. threadvar
  45938. GlobalSynFPUExceptionDelphi,
  45939. GlobalSynFPUExceptionLibrary: TSynFPUException;
  45940. constructor TSynFPUException.Create(Expected8087Flag: word);
  45941. begin // $1332=Delphi $133F=library (mask all exceptions)
  45942. inherited Create;
  45943. fExpected8087 := Expected8087Flag;
  45944. end;
  45945. class function TSynFPUException.ForLibraryCode: IUnknown;
  45946. begin
  45947. if GlobalSynFPUExceptionLibrary=nil then begin
  45948. GlobalSynFPUExceptionLibrary := TSynFPUException.Create($133F);
  45949. GarbageCollector.Add(GlobalSynFPUExceptionLibrary);
  45950. end;
  45951. result := GlobalSynFPUExceptionLibrary;
  45952. end;
  45953. class function TSynFPUException.ForDelphiCode: IUnknown;
  45954. begin
  45955. if GlobalSynFPUExceptionDelphi=nil then begin
  45956. GlobalSynFPUExceptionDelphi := TSynFPUException.Create($1332);
  45957. GarbageCollector.Add(GlobalSynFPUExceptionDelphi);
  45958. end;
  45959. result := GlobalSynFPUExceptionDelphi;
  45960. end;
  45961. {$endif DELPHI5OROLDER}
  45962. {$endif MSWINDOWS}
  45963. { TAutoFree }
  45964. constructor TAutoFree.Create(var localVariable; obj: TObject);
  45965. begin
  45966. fObject := obj;
  45967. TObject(localVariable) := obj;
  45968. end;
  45969. class function TAutoFree.One(var localVariable; obj: TObject): IAutoFree;
  45970. begin
  45971. result := Create(localVariable,obj);
  45972. end;
  45973. class function TAutoFree.Several(const varObjPairs: array of pointer): IAutoFree;
  45974. begin
  45975. result := Create(varObjPairs);
  45976. end;
  45977. constructor TAutoFree.Create(const varObjPairs: array of pointer);
  45978. var n,i: integer;
  45979. begin
  45980. n := length(varObjPairs);
  45981. if (n=0) or (n and 1=1) then
  45982. exit;
  45983. n := n shr 1;
  45984. if n=0 then
  45985. exit;
  45986. SetLength(fObjectList,n);
  45987. for i := 0 to n-1 do begin
  45988. fObjectList[i] := varObjPairs[i*2+1];
  45989. PPointer(varObjPairs[i*2])^ := fObjectList[i];
  45990. end;
  45991. end;
  45992. procedure TAutoFree.Another(var localVariable; obj: TObject);
  45993. var n: integer;
  45994. begin
  45995. n := length(fObjectList);
  45996. SetLength(fObjectList,n+1);
  45997. fObjectList[n] := obj;
  45998. TObject(localVariable) := obj;
  45999. end;
  46000. destructor TAutoFree.Destroy;
  46001. var i: integer;
  46002. begin
  46003. if fObjectList<>nil then
  46004. for i := high(fObjectList) downto 0 do // release FILO
  46005. fObjectList[i].Free;
  46006. fObject.Free;
  46007. inherited;
  46008. end;
  46009. { TAutoLocker }
  46010. constructor TAutoLocker.Create;
  46011. begin
  46012. fSafe.Init;
  46013. end;
  46014. destructor TAutoLocker.Destroy;
  46015. begin
  46016. fSafe.Done;
  46017. inherited;
  46018. end;
  46019. function TAutoLocker.ProtectMethod: IUnknown;
  46020. begin
  46021. result := TAutoLock.Create(@fSafe);
  46022. end;
  46023. procedure TAutoLocker.Enter;
  46024. begin
  46025. EnterCriticalSection(fSafe.fSection);
  46026. end;
  46027. procedure TAutoLocker.Leave;
  46028. begin
  46029. LeaveCriticalSection(fSafe.fSection);
  46030. end;
  46031. function TAutoLocker.Safe: PSynLocker;
  46032. begin
  46033. result := @fSafe;
  46034. end;
  46035. {$ifndef DELPHI5OROLDER} // internal error C3517 under Delphi 5 :(
  46036. {$ifndef NOVARIANTS}
  46037. { TLockedDocVariant }
  46038. constructor TLockedDocVariant.Create;
  46039. begin
  46040. Create(JSON_OPTIONS_FAST);
  46041. end;
  46042. constructor TLockedDocVariant.Create(FastStorage: boolean);
  46043. begin
  46044. Create(JSON_OPTIONS[FastStorage]);
  46045. end;
  46046. constructor TLockedDocVariant.Create(options: TDocVariantOptions);
  46047. begin
  46048. fLock := TAutoLocker.Create;
  46049. fValue.Init(options);
  46050. end;
  46051. destructor TLockedDocVariant.Destroy;
  46052. begin
  46053. inherited;
  46054. fLock.Free;
  46055. end;
  46056. function TLockedDocVariant.Exists(const Name: RawUTF8; out Value: Variant): boolean;
  46057. var i: integer;
  46058. begin
  46059. fLock.Enter;
  46060. try
  46061. i := fValue.GetValueIndex(Name);
  46062. if i<0 then
  46063. result := false else begin
  46064. Value := fValue.Values[i];
  46065. result := true;
  46066. end;
  46067. finally
  46068. fLock.Leave;
  46069. end;
  46070. end;
  46071. function TLockedDocVariant.ExistsOrLock(const Name: RawUTF8; out Value: Variant): boolean;
  46072. var i: integer;
  46073. begin
  46074. result := true;
  46075. fLock.Enter;
  46076. try
  46077. i := fValue.GetValueIndex(Name);
  46078. if i<0 then
  46079. result := false else
  46080. Value := fValue.Values[i];
  46081. finally
  46082. if result then
  46083. fLock.Leave;
  46084. end;
  46085. end;
  46086. procedure TLockedDocVariant.ReplaceAndUnlock(
  46087. const Name: RawUTF8; const Value: Variant; out LocalValue: Variant);
  46088. begin
  46089. try
  46090. SetValue(Name,Value);
  46091. LocalValue := Value;
  46092. finally
  46093. fLock.Leave;
  46094. end;
  46095. end;
  46096. function TLockedDocVariant.AddExistingPropOrLock(const Name: RawUTF8;
  46097. var Obj: variant): boolean;
  46098. var i: integer;
  46099. begin
  46100. result := true;
  46101. fLock.Enter;
  46102. try
  46103. i := fValue.GetValueIndex(Name);
  46104. if i<0 then
  46105. result := false else
  46106. _ObjAddProps([Name,fValue.Values[i]],Obj);
  46107. finally
  46108. if result then
  46109. fLock.Leave;
  46110. end;
  46111. end;
  46112. procedure TLockedDocVariant.AddNewPropAndUnlock(const Name: RawUTF8;
  46113. const Value: variant;
  46114. var Obj: variant);
  46115. begin
  46116. try
  46117. SetValue(Name,Value);
  46118. _ObjAddProps([Name,Value],Obj);
  46119. finally
  46120. fLock.Leave;
  46121. end;
  46122. end;
  46123. function TLockedDocVariant.AddExistingProp(const Name: RawUTF8;
  46124. var Obj: variant): boolean;
  46125. var i: integer;
  46126. begin
  46127. result := true;
  46128. fLock.Enter;
  46129. try
  46130. i := fValue.GetValueIndex(Name);
  46131. if i<0 then
  46132. result := false else
  46133. _ObjAddProps([Name,fValue.Values[i]],Obj);
  46134. finally
  46135. fLock.Leave;
  46136. end;
  46137. end;
  46138. procedure TLockedDocVariant.AddNewProp(const Name: RawUTF8;
  46139. const Value: variant;
  46140. var Obj: variant);
  46141. begin
  46142. fLock.Enter;
  46143. try
  46144. SetValue(Name,Value);
  46145. _ObjAddProps([Name,Value],Obj);
  46146. finally
  46147. fLock.Leave;
  46148. end;
  46149. end;
  46150. function TLockedDocVariant.GetValue(const Name: RawUTF8): Variant;
  46151. begin
  46152. fLock.Enter;
  46153. try
  46154. fValue.RetrieveValueOrRaiseException(pointer(Name),length(Name),
  46155. dvoNameCaseSensitive in fValue.Options,result,false);
  46156. finally
  46157. fLock.Leave;
  46158. end;
  46159. end;
  46160. procedure TLockedDocVariant.SetValue(const Name: RawUTF8;
  46161. const Value: Variant);
  46162. begin
  46163. fLock.Enter;
  46164. try
  46165. fValue.AddOrUpdateValue(Name,Value);
  46166. finally
  46167. fLock.Leave;
  46168. end;
  46169. end;
  46170. procedure TLockedDocVariant.AddItem(const Value: variant);
  46171. begin
  46172. fLock.Enter;
  46173. try
  46174. fValue.AddItem(Value);
  46175. finally
  46176. fLock.Leave;
  46177. end;
  46178. end;
  46179. function TLockedDocVariant.Copy: variant;
  46180. begin
  46181. VarClear(result);
  46182. fLock.Enter;
  46183. try
  46184. TDocVariantData(result).InitCopy(variant(fValue),JSON_OPTIONS_FAST);
  46185. finally
  46186. fLock.Leave;
  46187. end;
  46188. end;
  46189. procedure TLockedDocVariant.Clear;
  46190. var opt: TDocVariantOptions;
  46191. begin
  46192. fLock.Enter;
  46193. try
  46194. opt := fValue.Options;
  46195. fValue.Clear;
  46196. fValue.Init(opt);
  46197. finally
  46198. fLock.Leave;
  46199. end;
  46200. end;
  46201. function TLockedDocVariant.ToJSON(HumanReadable: boolean): RawUTF8;
  46202. var tmp: RawUTF8;
  46203. begin
  46204. fLock.Enter;
  46205. try
  46206. VariantSaveJSON(variant(fValue),twJSONEscape,tmp);
  46207. finally
  46208. fLock.Leave;
  46209. end;
  46210. if HumanReadable then
  46211. JSONBufferReformat(pointer(tmp),result) else
  46212. result := tmp;
  46213. end;
  46214. {$endif NOVARIANTS}
  46215. {$endif DELPHI5OROLDER}
  46216. function GetDelphiCompilerVersion: RawUTF8;
  46217. begin
  46218. result :=
  46219. {$ifdef FPC}
  46220. 'Free Pascal'
  46221. {$ifdef VER2_6_4}+' 2.6.4'{$endif}
  46222. {$ifdef VER2_7_0}+' 2.7.0'{$endif}
  46223. {$ifdef VER2_7_1}+' 2.7.1'{$endif}
  46224. {$ifdef VER3_0_1}+' 3.0.1'{$endif}
  46225. {$ifdef VER3_1_1}+' 3.1.1'{$endif}
  46226. {$else}
  46227. {$ifdef VER130} 'Delphi 5'{$endif}
  46228. {$ifdef CONDITIONALEXPRESSIONS} // Delphi 6 or newer
  46229. {$if defined(KYLIX3)}'Kylix 3'
  46230. {$elseif defined(VER140)}'Delphi 6'
  46231. {$elseif defined(VER150)}'Delphi 7'
  46232. {$elseif defined(VER160)}'Delphi 8'
  46233. {$elseif defined(VER170)}'Delphi 2005'
  46234. {$elseif defined(VER185)}'Delphi 2007'
  46235. {$elseif defined(VER180)}'Delphi 2006'
  46236. {$elseif defined(VER200)}'Delphi 2009'
  46237. {$elseif defined(VER210)}'Delphi 2010'
  46238. {$elseif defined(VER220)}'Delphi XE'
  46239. {$elseif defined(VER230)}'Delphi XE2'
  46240. {$elseif defined(VER240)}'Delphi XE3'
  46241. {$elseif defined(VER250)}'Delphi XE4'
  46242. {$elseif defined(VER260)}'Delphi XE5'
  46243. {$elseif defined(VER265)}'AppMethod 1'
  46244. {$elseif defined(VER270)}'Delphi XE6'
  46245. {$elseif defined(VER280)}'Delphi XE7'
  46246. {$elseif defined(VER290)}'Delphi XE8'
  46247. {$elseif defined(VER300)}'Delphi 10 Seattle'
  46248. {$elseif defined(VER310)}'Delphi 10.1 Berlin'
  46249. {$elseif defined(VER320)}'Delphi 10.2 Tokyo'
  46250. {$ifend}
  46251. {$endif CONDITIONALEXPRESSIONS}
  46252. {$endif}
  46253. {$ifdef CPU64}
  46254. +' 64 bit'
  46255. {$endif}
  46256. end;
  46257. { TSynCache }
  46258. procedure TSynCache.Add(const aValue: RawUTF8; aTag: PtrInt);
  46259. begin
  46260. if (self=nil) or (fFindLastAddedIndex<0) then
  46261. // fFindLastAddedIndex should have been set by a previous call to Find()
  46262. exit;
  46263. inc(fValueSize,length(aValue));
  46264. if fValueSize>fMaxCacheRamUsed then begin
  46265. // if tends to consume too much memory, restart caching (fast in practice)
  46266. Reset;
  46267. exit;
  46268. end;
  46269. // add the cache entry values (text+integer)
  46270. with fNameValue.List[fFindLastAddedIndex] do begin
  46271. Tag := aTag;
  46272. Value := aValue;
  46273. end;
  46274. fFindLastAddedIndex := -1;
  46275. end;
  46276. constructor TSynCache.Create(aMaxCacheRamUsed: cardinal=16384*1024; aCaseSensitive: boolean=false);
  46277. begin
  46278. fNameValue.Init(aCaseSensitive);
  46279. fNameValue.fDynArray.Capacity := 200; // some space for future cached entries
  46280. fMaxCacheRamUsed := aMaxCacheRamUsed;
  46281. fFindLastAddedIndex := -1;
  46282. end;
  46283. function TSynCache.Find(const aKey: RawUTF8; aResultTag: PPtrInt): RawUTF8;
  46284. var added: boolean;
  46285. begin
  46286. result := '';
  46287. if Self<>nil then
  46288. if aKey='' then
  46289. fFindLastAddedIndex := -1 else begin
  46290. fFindLastAddedIndex := fNameValue.fDynArray.FindHashedForAdding(aKey,added);
  46291. if added then
  46292. fNameValue.List[fFindLastAddedIndex].Name := aKey else
  46293. // match key found
  46294. with fNameValue.List[fFindLastAddedIndex] do begin
  46295. result := Value;
  46296. if aResultTag<>nil then
  46297. aResultTag^ := Tag;
  46298. fFindLastAddedIndex := -1;
  46299. end;
  46300. end;
  46301. end;
  46302. function TSynCache.Reset: boolean;
  46303. begin
  46304. result := false;
  46305. if self=nil then
  46306. exit; // avoid GPF
  46307. if Count<>0 then begin
  46308. if fValueSize<131072 then // no capacity change for small cache content
  46309. fNameValue.Count := 0 else
  46310. with fNameValue.fDynArray{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif} do begin
  46311. Capacity := 0; // force free all fNameValue.List[] key/value pairs
  46312. Capacity := 200; // then reserve some space for future cached entries
  46313. end;
  46314. fNameValue.fDynArray.fHashs := nil; // will force reset all hash content
  46315. result := true; // mark something was flushed
  46316. end;
  46317. fFindLastAddedIndex := -1;
  46318. fValueSize := 0;
  46319. end;
  46320. {$ifdef VER220}
  46321. function TSynCache.Count: integer;
  46322. begin
  46323. result := fNameValue.Count;
  46324. end;
  46325. {$endif}
  46326. { TRawUTF8List }
  46327. function TRawUTF8List.Add(const aText: RawUTF8): PtrInt;
  46328. var capacity: PtrInt;
  46329. begin
  46330. if self=nil then
  46331. result := -1 else
  46332. if fObjects=nil then begin
  46333. capacity := length(fList);
  46334. result := fCount;
  46335. if result>=capacity then begin
  46336. inc(capacity,256+fCount shr 3);
  46337. SetLength(fList,capacity);
  46338. end;
  46339. fList[result] := aText;
  46340. inc(fCount);
  46341. Changed;
  46342. end else
  46343. result := AddObject(aText,nil);
  46344. end;
  46345. function TRawUTF8List.AddIfNotExisting(const aText: RawUTF8; wasAdded: PBoolean): PtrInt;
  46346. begin
  46347. result := IndexOf(aText);
  46348. if result<0 then begin
  46349. result := Add(aText);
  46350. if wasAdded<>nil then
  46351. wasAdded^ := true;
  46352. end else
  46353. if wasAdded<>nil then
  46354. wasAdded^ := false;
  46355. end;
  46356. function TRawUTF8List.AddObjectIfNotExisting(const aText: RawUTF8; aObject: TObject;
  46357. wasAdded: PBoolean): PtrInt;
  46358. begin
  46359. result := IndexOf(aText);
  46360. if result<0 then begin
  46361. result := AddObject(aText,aObject);
  46362. if wasAdded<>nil then
  46363. wasAdded^ := true;
  46364. end else
  46365. if wasAdded<>nil then
  46366. wasAdded^ := false;
  46367. end;
  46368. function TRawUTF8List.AddObject(const aText: RawUTF8; aObject: TObject): PtrInt;
  46369. var capacity: PtrInt;
  46370. begin
  46371. if self=nil then begin
  46372. result := -1;
  46373. exit;
  46374. end;
  46375. capacity := length(fList);
  46376. result := fCount;
  46377. if result>=capacity then begin
  46378. inc(capacity,256+fCount shr 3);
  46379. SetLength(fList,capacity);
  46380. if (fObjects<>nil) or (aObject<>nil) then
  46381. SetLength(fObjects,capacity);
  46382. end else
  46383. if (aObject<>nil) and (fObjects=nil) then
  46384. SetLength(fObjects,capacity);
  46385. fList[result] := aText;
  46386. if aObject<>nil then
  46387. fObjects[result] := aObject;
  46388. inc(fCount);
  46389. Changed;
  46390. end;
  46391. procedure TRawUTF8List.AddRawUTF8List(List: TRawUTF8List);
  46392. var i: PtrInt;
  46393. begin
  46394. if List<>nil then begin
  46395. BeginUpdate;
  46396. if List.fObjects=nil then
  46397. for i := 0 to List.fCount-1 do
  46398. Add(List.fList[i]) else
  46399. for i := 0 to List.fCount-1 do
  46400. AddObject(List.fList[i],List.fObjects[i]);
  46401. EndUpdate;
  46402. end;
  46403. end;
  46404. procedure TRawUTF8List.BeginUpdate;
  46405. begin
  46406. inc(fOnChangeLevel);
  46407. if fOnChangeLevel>1 then
  46408. exit;
  46409. fOnChangeHidden := fOnChange;
  46410. fOnChange := OnChangeHidden;
  46411. fOnChangeTrigerred := false;
  46412. end;
  46413. procedure TRawUTF8List.Changed;
  46414. begin
  46415. if (self<>nil) and Assigned(fOnChange) then
  46416. fOnChange(self);
  46417. end;
  46418. procedure TRawUTF8List.Clear;
  46419. begin
  46420. Capacity := 0;
  46421. Changed;
  46422. end;
  46423. constructor TRawUTF8List.Create(aOwnObjects: boolean);
  46424. begin
  46425. fNameValueSep := '=';
  46426. fObjectsOwned := aOwnObjects;
  46427. fCaseSensitive := true;
  46428. end;
  46429. destructor TRawUTF8List.Destroy;
  46430. begin
  46431. Capacity := 0;
  46432. inherited;
  46433. end;
  46434. procedure TRawUTF8List.Delete(Index: PtrInt);
  46435. begin
  46436. if (self=nil) or (PtrUInt(Index)>=PtrUInt(fCount)) then
  46437. exit;
  46438. // release string/object instances
  46439. fList[Index] := '';
  46440. if (fObjects<>nil) and fObjectsOwned then
  46441. FreeAndNil(fObjects[Index]);
  46442. // swap the string/object arrays
  46443. dec(fCount);
  46444. if Index<fCount then begin
  46445. MoveFast(fList[Index+1],fList[Index],(fCount-Index)*sizeof(fList[0]));
  46446. PPointer(@fList[fCount])^ := nil; // avoid GPF
  46447. if fObjects<>nil then begin
  46448. MoveFast(fObjects[Index+1],fObjects[Index],(fCount-Index)*sizeof(fObjects[0]));
  46449. fObjects[fCount] := nil; // avoid GPF if fObjectsOwned is set
  46450. end;
  46451. end;
  46452. Changed;
  46453. end;
  46454. function TRawUTF8List.Delete(const aText: RawUTF8): PtrInt;
  46455. begin
  46456. Result := IndexOf(aText);
  46457. if Result>=0 then
  46458. Delete(Result);
  46459. end;
  46460. function TRawUTF8List.DeleteFromName(const Name: RawUTF8): PtrInt;
  46461. begin
  46462. Result := IndexOfName(Name);
  46463. if Result>=0 then
  46464. Delete(Result);
  46465. end;
  46466. procedure TRawUTF8List.EndUpdate;
  46467. begin
  46468. if fOnChangeLevel<=0 then
  46469. exit;
  46470. dec(fOnChangeLevel);
  46471. if fOnChangeLevel>0 then
  46472. exit; // allows nested BeginUpdate..EndUpdate calls
  46473. fOnChange := fOnChangeHidden;
  46474. if fOnChangeTrigerred and Assigned(fOnChange) then
  46475. fOnChange(self);
  46476. fOnChangeTrigerred := false;
  46477. end;
  46478. function TRawUTF8List.Get(Index: PtrInt): RawUTF8;
  46479. begin
  46480. if (self=nil) or (PtrUInt(Index)>=PtrUInt(fCount)) then
  46481. result := '' else
  46482. result := fList[Index];
  46483. end;
  46484. function TRawUTF8List.GetCapacity: PtrInt;
  46485. begin
  46486. if self=nil then
  46487. result := 0 else
  46488. result := length(fList);
  46489. end;
  46490. function TRawUTF8List.GetCount: PtrInt;
  46491. begin
  46492. if self=nil then
  46493. result := 0 else
  46494. result := fCount;
  46495. end;
  46496. function TRawUTF8List.GetListPtr: PPUtf8CharArray;
  46497. begin
  46498. if self=nil then
  46499. result := nil else
  46500. result := pointer(fList);
  46501. end;
  46502. function TRawUTF8List.GetObjectPtr: PPointerArray;
  46503. begin
  46504. if self=nil then
  46505. result := nil else
  46506. result := pointer(fObjects);
  46507. end;
  46508. function TRawUTF8List.GetName(Index: PtrInt): RawUTF8;
  46509. var Sep: PUTF8Char;
  46510. begin
  46511. result := Get(Index);
  46512. if result='' then
  46513. exit;
  46514. Sep := PosChar(pointer(result),NameValueSep);
  46515. if Sep=nil then
  46516. result := '' else
  46517. SetLength(result,Sep-pointer(result));
  46518. end;
  46519. function TRawUTF8List.GetObject(Index: PtrInt): TObject;
  46520. begin
  46521. if (self<>nil) and (PtrUInt(Index)<PtrUInt(fCount)) and (fObjects<>nil) then
  46522. result := fObjects[Index] else
  46523. result := nil;
  46524. end;
  46525. function TRawUTF8List.GetObjectByName(const Name: RawUTF8): TObject;
  46526. var ndx: PtrUInt;
  46527. begin
  46528. if (self<>nil) and (fObjects<>nil) then begin
  46529. ndx := IndexOf(Name);
  46530. if ndx<PtrUInt(fCount) then begin
  46531. result := fObjects[ndx];
  46532. exit;
  46533. end else begin
  46534. result := nil;
  46535. exit;
  46536. end;
  46537. end else begin
  46538. result := nil;
  46539. exit;
  46540. end;
  46541. end;
  46542. function TRawUTF8List.GetText(const Delimiter: RawUTF8): RawUTF8;
  46543. var DelimLen, i, Len: PtrInt;
  46544. P: PUTF8Char;
  46545. begin
  46546. result := '';
  46547. if (self=nil) or (fCount=0) then
  46548. exit;
  46549. DelimLen := length(Delimiter);
  46550. Len := DelimLen*(fCount-1);
  46551. for i := 0 to fCount-1 do
  46552. inc(Len,length(fList[i]));
  46553. SetLength(result,len);
  46554. P := pointer(result);
  46555. i := 0;
  46556. repeat
  46557. Len := length(fList[i]);
  46558. if Len>0 then begin
  46559. MoveFast(pointer(fList[i])^,P^,Len);
  46560. inc(P,Len);
  46561. end;
  46562. inc(i);
  46563. if i>=fCount then
  46564. Break;
  46565. MoveFast(pointer(Delimiter)^,P^,DelimLen);
  46566. inc(P,DelimLen);
  46567. until false;
  46568. end;
  46569. procedure TRawUTF8List.SaveToStream(Dest: TStream; const Delimiter: RawUTF8);
  46570. var W: TTextWriter;
  46571. i: integer;
  46572. begin
  46573. if (self=nil) or (fCount=0) then
  46574. exit;
  46575. W := TTextWriter.Create(Dest,8192); // faster with a 8KB intermediate buffer
  46576. try
  46577. i := 0;
  46578. repeat
  46579. W.AddString(fList[i]);
  46580. inc(i);
  46581. if i>=fCount then
  46582. Break;
  46583. W.AddString(Delimiter);
  46584. until false;
  46585. W.FlushFinal;
  46586. finally
  46587. W.Free;
  46588. end;
  46589. end;
  46590. procedure TRawUTF8List.SaveToFile(const FileName: TFileName; const Delimiter: RawUTF8);
  46591. var FS: TFileStream;
  46592. begin
  46593. FS := TFileStream.Create(FileName,fmCreate);
  46594. try
  46595. SaveToStream(FS,Delimiter);
  46596. finally
  46597. FS.Free;
  46598. end;
  46599. end;
  46600. function TRawUTF8List.GetTextCRLF: RawUTF8;
  46601. begin
  46602. result := GetText;
  46603. end;
  46604. function TRawUTF8List.GetValue(const Name: RawUTF8): RawUTF8;
  46605. begin
  46606. Result := GetValueAt(IndexOfName(Name));
  46607. end;
  46608. function TRawUTF8List.GetValueAt(Index: PtrInt): RawUTF8;
  46609. var Sep: PUTF8Char;
  46610. begin
  46611. if (self=nil) or (PtrUInt(Index)>=PtrUInt(fCount)) then
  46612. result := '' else
  46613. result := Get(Index);
  46614. if result='' then
  46615. exit;
  46616. Sep := PosChar(pointer(result),NameValueSep);
  46617. if Sep=nil then
  46618. result := '' else
  46619. result := Sep+1; // get 'Value' from 'Name=Value'
  46620. end;
  46621. function TRawUTF8List.IndexOf(const aText: RawUTF8): PtrInt;
  46622. begin
  46623. if self<>nil then
  46624. if fCaseSensitive then begin
  46625. for result := 0 to fCount-1 do
  46626. if fList[result]=aText then
  46627. exit;
  46628. end else
  46629. for result := 0 to fCount-1 do
  46630. if UTF8IComp(pointer(fList[result]),pointer(aText))=0 then
  46631. exit;
  46632. result := -1;
  46633. end;
  46634. function TRawUTF8List.IndexOfName(const Name: RawUTF8): PtrInt;
  46635. var UpperName: array[byte] of AnsiChar;
  46636. begin
  46637. if self<>nil then begin
  46638. PWord(UpperCopy255(UpperName,Name))^ := ord(NameValueSep);
  46639. for result := 0 to fCount-1 do
  46640. if IdemPChar(Pointer(fList[result]),UpperName) then
  46641. exit;
  46642. end;
  46643. result := -1;
  46644. end;
  46645. function TRawUTF8List.IndexOfObject(aObject: TObject): PtrInt;
  46646. begin
  46647. if (self<>nil) and (fObjects<>nil) then
  46648. result := PtrUIntScanIndex(pointer(fObjects),fCount,PtrUInt(aObject)) else
  46649. result := -1;
  46650. end;
  46651. procedure TRawUTF8List.OnChangeHidden(Sender: TObject);
  46652. begin
  46653. if self<>nil then
  46654. fOnChangeTrigerred := true;
  46655. end;
  46656. procedure TRawUTF8List.Put(Index: PtrInt; const Value: RawUTF8);
  46657. begin
  46658. if (self<>nil) and (PtrUInt(Index)<PtrUInt(fCount)) then begin
  46659. fList[Index] := Value;
  46660. Changed;
  46661. end;
  46662. end;
  46663. procedure TRawUTF8List.PutObject(Index: PtrInt; const Value: TObject);
  46664. begin
  46665. if (self<>nil) and (PtrUInt(Index)<PtrUInt(fCount)) then begin
  46666. if fObjects=nil then
  46667. SetLength(fObjects,Length(fList));
  46668. fObjects[Index] := Value;
  46669. Changed;
  46670. end;
  46671. end;
  46672. procedure TRawUTF8List.SetCapacity(const Value: PtrInt);
  46673. var i: integer;
  46674. begin
  46675. if self<>nil then begin
  46676. if Value<=0 then begin
  46677. fList := nil;
  46678. if fObjects<>nil then begin
  46679. if fObjectsOwned then
  46680. for i := 0 to fCount-1 do
  46681. fObjects[i].Free;
  46682. fObjects := nil;
  46683. end;
  46684. fCount := 0;
  46685. end else begin
  46686. if Value<fCount then begin
  46687. if (fObjects<>nil) and fObjectsOwned then
  46688. for i := Value to fCount-1 do
  46689. FreeAndNil(fObjects[i]);
  46690. fCount := Value;
  46691. end;
  46692. if Value>length(fList) then begin // increase capacity
  46693. SetLength(fList,Value);
  46694. if pointer(fObjects)<>nil then
  46695. SetLength(fObjects,Value);
  46696. end;
  46697. end;
  46698. end;
  46699. end;
  46700. procedure TRawUTF8List.SetText(const aText, Delimiter: RawUTF8);
  46701. begin
  46702. SetTextPtr(pointer(aText),Delimiter);
  46703. end;
  46704. procedure TRawUTF8List.LoadFromFile(const FileName: TFileName);
  46705. var Map: TMemoryMap;
  46706. P: pointer;
  46707. begin
  46708. if Map.Map(FileName) then
  46709. try
  46710. if Map.Size<>0 then begin
  46711. if TextFileKind(Map)=isUTF8 then // ignore UTF-8 BOM
  46712. P := Map.Buffer+3 else
  46713. P := Map.Buffer;
  46714. SetTextPtr(P,#13#10);
  46715. end;
  46716. finally
  46717. Map.UnMap;
  46718. end;
  46719. end;
  46720. procedure TRawUTF8List.SetTextPtr(P: PUTF8Char; const Delimiter: RawUTF8);
  46721. var DelimLen: PtrInt;
  46722. DelimFirst: AnsiChar;
  46723. PBeg, DelimNext: PUTF8Char;
  46724. Line: RawUTF8;
  46725. begin
  46726. DelimLen := length(Delimiter);
  46727. BeginUpdate;
  46728. Clear;
  46729. if (P<>nil) and (DelimLen>0) then begin
  46730. DelimFirst := Delimiter[1];
  46731. DelimNext := PUTF8Char(pointer(Delimiter))+1;
  46732. repeat
  46733. PBeg := P;
  46734. while P^<>#0 do begin
  46735. if (P^=DelimFirst) and CompareMem(P+1,DelimNext,DelimLen-1) then
  46736. break;
  46737. inc(P);
  46738. end;
  46739. SetString(Line,PBeg,P-PBeg);
  46740. AddObject(Line,nil);
  46741. if P^=#0 then
  46742. break;
  46743. inc(P,DelimLen);
  46744. until P^=#0;
  46745. end;
  46746. EndUpdate;
  46747. end;
  46748. procedure TRawUTF8List.SetTextCRLF(const Value: RawUTF8);
  46749. begin
  46750. SetTextPtr(pointer(Value),#13#10);
  46751. end;
  46752. procedure TRawUTF8List.SetValue(const Name, Value: RawUTF8);
  46753. var i: PtrInt;
  46754. begin
  46755. i := IndexOfName(Name);
  46756. if i<0 then
  46757. Add(Name+RawUTF8(NameValueSep)+Value) else
  46758. fList[i] := Name+RawUTF8(NameValueSep)+Value;
  46759. end;
  46760. procedure TRawUTF8List.SetCaseSensitive(Value: boolean);
  46761. begin
  46762. fCaseSensitive := Value;
  46763. end;
  46764. procedure TRawUTF8List.UpdateValue(const Name: RawUTF8; var Value: RawUTF8;
  46765. ThenDelete: boolean);
  46766. var i: PtrInt;
  46767. begin
  46768. i := IndexOfName(Name);
  46769. if i>=0 then begin
  46770. Value := GetValueAt(i); // update value
  46771. if ThenDelete then
  46772. Delete(i); // optionally delete
  46773. end;
  46774. end;
  46775. function TRawUTF8List.PopFirst(out aText: RawUTF8; aObject: PObject=nil): boolean;
  46776. begin
  46777. result := fCount>0;
  46778. if not result then
  46779. exit;
  46780. aText := fList[0];
  46781. if aObject<>nil then
  46782. if fObjects<>nil then
  46783. aObject^ := fObjects[0] else
  46784. aObject^ := nil;
  46785. Delete(0);
  46786. end;
  46787. function TRawUTF8List.PopLast(out aText: RawUTF8; aObject: PObject=nil): boolean;
  46788. var ndx: integer;
  46789. begin
  46790. result := fCount>0;
  46791. if not result then
  46792. exit;
  46793. ndx := fCount-1;
  46794. aText := fList[ndx];
  46795. if aObject<>nil then
  46796. if fObjects<>nil then
  46797. aObject^ := fObjects[ndx] else
  46798. aObject^ := nil;
  46799. Delete(ndx);
  46800. end;
  46801. { TRawUTF8ListLocked }
  46802. constructor TRawUTF8ListLocked.Create(aOwnObjects: boolean);
  46803. begin
  46804. inherited Create(aOwnObjects);
  46805. fSafe.Init;
  46806. end;
  46807. destructor TRawUTF8ListLocked.Destroy;
  46808. begin
  46809. inherited;
  46810. fSafe.Done;
  46811. end;
  46812. { TObjectListHashedAbstract}
  46813. constructor TObjectListHashedAbstract.Create(aFreeItems: boolean);
  46814. begin
  46815. inherited Create;
  46816. fFreeItems := aFreeItems;
  46817. fHash.Init(TypeInfo(TObjectDynArray),fList,@HashPtrUInt,@SortDynArrayPointer,nil,@fCount);
  46818. fHash.fHashCountTrigger := 0; // has dedicated inherited process of small lists
  46819. end;
  46820. destructor TObjectListHashedAbstract.Destroy;
  46821. var i: integer;
  46822. begin
  46823. if fFreeItems then
  46824. for i := 0 to fCount-1 do
  46825. List[i].Free;
  46826. inherited;
  46827. end;
  46828. procedure TObjectListHashedAbstract.Delete(aIndex: integer);
  46829. begin
  46830. if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
  46831. exit;
  46832. if fFreeItems then
  46833. FreeAndNil(List[aIndex]);
  46834. fHash.Delete(aIndex);
  46835. fHashValid := false;
  46836. end;
  46837. procedure TObjectListHashedAbstract.Delete(aObject: TObject);
  46838. begin
  46839. Delete(IndexOf(aObject));
  46840. end;
  46841. { TObjectListHashed }
  46842. const
  46843. // hashing will start only when List[] reaches 32 items (not worth it before)
  46844. TOBJECTLISTHASHED_START_HASHING_COUNT = 32;
  46845. function TObjectListHashed.Add(aObject: TObject; out wasAdded: boolean): integer;
  46846. begin
  46847. wasAdded := false;
  46848. if self<>nil then
  46849. if fHashed then begin
  46850. if not fHashValid then
  46851. fHashValid := fHash.ReHash;
  46852. result := fHash.FindHashedForAdding(aObject,wasAdded);
  46853. if wasAdded then
  46854. fList[result] := aObject;
  46855. end else begin
  46856. for result := 0 to fCount-1 do
  46857. if fList[result]=aObject then
  46858. exit;
  46859. wasAdded := true;
  46860. result := fHash.Add(aObject);
  46861. if fCount>=TOBJECTLISTHASHED_START_HASHING_COUNT then
  46862. fHashed := true;
  46863. end
  46864. else
  46865. result := -1;
  46866. end;
  46867. function TObjectListHashed.IndexOf(aObject: TObject): integer;
  46868. begin
  46869. if (self<>nil) and (fCount>0) then
  46870. if fHashed then begin
  46871. if not fHashValid then begin
  46872. fHash.ReHash;
  46873. fHashValid := true;
  46874. end;
  46875. result := fHash.FindHashed(aObject);
  46876. exit;
  46877. end else
  46878. for result := 0 to fCount-1 do
  46879. if fList[result]=aObject then
  46880. exit;
  46881. result := -1;
  46882. end;
  46883. { TObjectListPropertyHashed }
  46884. constructor TObjectListPropertyHashed.Create(
  46885. aSubPropAccess: TObjectListPropertyHashedAccessProp;
  46886. aHashElement: TDynArrayHashOne; aCompare: TDynArraySortCompare;
  46887. aFreeItems: boolean);
  46888. begin
  46889. inherited Create(aFreeItems);
  46890. fSubPropAccess := aSubPropAccess;
  46891. if Assigned(aHashElement) then
  46892. fHash.fHashElement := aHashElement;
  46893. if Assigned(aCompare) then
  46894. fHash.{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare := aCompare;
  46895. fHash.EventCompare := IntComp;
  46896. end;
  46897. function TObjectListPropertyHashed.IntHash(const Elem): cardinal;
  46898. var O: TObject;
  46899. begin
  46900. O := fSubPropAccess(TObject(Elem));
  46901. result := fHash.fHashElement(O,fHash.fHasher);
  46902. end;
  46903. function TObjectListPropertyHashed.IntComp(const A,B): integer;
  46904. var O: TObject;
  46905. begin
  46906. O := fSubPropAccess(TObject(A));
  46907. result := fHash.{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare(O,B);
  46908. end;
  46909. procedure TObjectListPropertyHashed.IntHashValid;
  46910. begin
  46911. fHash.ReHash(IntHash);
  46912. fHashValid := true;
  46913. end;
  46914. function TObjectListPropertyHashed.Add(aObject: TObject; out wasAdded: boolean): integer;
  46915. begin
  46916. wasAdded := false;
  46917. if self<>nil then
  46918. if fHashed then begin
  46919. if not fHashValid then
  46920. IntHashValid;
  46921. result := fHash.FindHashedForAdding(aObject,wasAdded,
  46922. fHash.fHashElement(aObject,fHash.fHasher));
  46923. if wasAdded then
  46924. fList[result] := aObject;
  46925. end else begin
  46926. for result := 0 to fCount-1 do
  46927. if IntComp(fList[result],aObject)=0 then
  46928. exit;
  46929. wasAdded := true;
  46930. result := fHash.Add(aObject);
  46931. if fCount>=TOBJECTLISTHASHED_START_HASHING_COUNT then
  46932. fHashed := true;
  46933. end
  46934. else
  46935. result := -1;
  46936. end;
  46937. function TObjectListPropertyHashed.IndexOf(aObject: TObject): integer;
  46938. begin
  46939. if fCount>0 then
  46940. if fHashed then begin
  46941. if not fHashValid then
  46942. IntHashValid;
  46943. result := fHash.HashFind(fHash.fHashElement(aObject,fHash.fHasher),PtrInt(aObject));
  46944. if result>=0 then
  46945. exit; // if found
  46946. end else
  46947. for result := 0 to fCount-1 do
  46948. if IntComp(fList[result],aObject)=0 then
  46949. exit;
  46950. result := -1;
  46951. end;
  46952. { TPointerClassHashed }
  46953. constructor TPointerClassHashed.Create(aInfo: pointer);
  46954. begin
  46955. fInfo := aInfo;
  46956. end;
  46957. { TPointerClassHash }
  46958. function PointerClassHashProcess(aObject: TPointerClassHashed): pointer;
  46959. begin
  46960. result := aObject.Info;
  46961. end;
  46962. constructor TPointerClassHash.Create;
  46963. begin
  46964. inherited Create(@PointerClassHashProcess);
  46965. end;
  46966. function TPointerClassHash.TryAdd(aInfo: pointer): PPointerClassHashed;
  46967. var wasAdded: boolean;
  46968. i: integer;
  46969. begin
  46970. i := inherited Add(aInfo,wasAdded);
  46971. if wasAdded then
  46972. result := @List[i] else
  46973. result := nil;
  46974. end;
  46975. function TPointerClassHash.Find(aInfo: pointer): TPointerClassHashed;
  46976. var i: integer;
  46977. begin
  46978. if self<>nil then begin
  46979. if not fHashed then begin // Count<TOBJECTLISTHASHED_START_HASHING_COUNT
  46980. for i := 0 to fCount-1 do begin
  46981. result := TPointerClassHashed(List[i]);
  46982. if result.Info=aInfo then
  46983. exit;
  46984. end;
  46985. result := nil;
  46986. end else begin
  46987. i := IndexOf(aInfo);
  46988. if i>=0 then
  46989. result := TPointerClassHashed(List[i]) else
  46990. result := nil;
  46991. end;
  46992. end else
  46993. result := nil;
  46994. end;
  46995. { TPointerClassHashLocked }
  46996. constructor TPointerClassHashLocked.Create;
  46997. begin
  46998. inherited Create;
  46999. fSafe.Init;
  47000. end;
  47001. destructor TPointerClassHashLocked.Destroy;
  47002. begin
  47003. fSafe.Done;
  47004. inherited Destroy;
  47005. end;
  47006. function TPointerClassHashLocked.FindLocked(aInfo: pointer): TPointerClassHashed;
  47007. begin
  47008. if self=nil then
  47009. result := nil else begin
  47010. fSafe.Lock;
  47011. try
  47012. result := inherited Find(aInfo);
  47013. finally
  47014. fSafe.UnLock;
  47015. end;
  47016. end;
  47017. end;
  47018. function TPointerClassHashLocked.TryAddLocked(aInfo: pointer;
  47019. out aNewEntry: PPointerClassHashed): boolean;
  47020. var wasAdded: boolean;
  47021. i: integer;
  47022. begin
  47023. fSafe.Lock;
  47024. i := inherited Add(aInfo,wasAdded);
  47025. if wasAdded then begin
  47026. aNewEntry := @List[i];
  47027. result := true; // caller should call Unlock
  47028. end else begin
  47029. fSafe.UnLock;
  47030. result := false;
  47031. end;
  47032. end;
  47033. procedure TPointerClassHashLocked.Unlock;
  47034. begin
  47035. fSafe.UnLock;
  47036. end;
  47037. { TObjectListLocked }
  47038. constructor TObjectListLocked.Create(AOwnsObjects: Boolean=true);
  47039. begin
  47040. inherited Create(AOwnsObjects);
  47041. fSafe.Init;
  47042. end;
  47043. destructor TObjectListLocked.Destroy;
  47044. begin
  47045. inherited Destroy;
  47046. fSafe.Done;
  47047. end;
  47048. { TRawUTF8ListHashed }
  47049. {$ifdef PUREPASCAL}
  47050. function SortDynArrayAnsiStringHashOnly(const A,B): integer;
  47051. begin
  47052. if RawByteString(A)=RawByteString(B) then // faster than StrCmp
  47053. result := 0 else
  47054. result := 1; // fake comparison, but fHash only use equality
  47055. end;
  47056. {$endif}
  47057. var
  47058. DYNARRAY_SORTFIRSTFIELDHASHONLY: array[boolean] of TDynArraySortCompare = (
  47059. SortDynArrayAnsiStringI,
  47060. {$ifdef PUREPASCAL}SortDynArrayAnsiStringHashOnly
  47061. {$else}SortDynArrayAnsiString{$endif});
  47062. constructor TRawUTF8ListHashed.Create(aOwnObjects: boolean);
  47063. begin
  47064. inherited Create(aOwnObjects);
  47065. fHash.Init(TypeInfo(TRawUTF8DynArray),fList,@HashAnsiString,
  47066. DYNARRAY_SORTFIRSTFIELDHASHONLY[true],nil,@fCount);
  47067. end;
  47068. procedure TRawUTF8ListHashed.Changed;
  47069. begin
  47070. fChanged := true;
  47071. inherited;
  47072. end;
  47073. procedure TRawUTF8ListHashed.SetCaseSensitive(Value: boolean);
  47074. begin
  47075. if fCaseSensitive=Value then
  47076. exit;
  47077. inherited;
  47078. fHash.fHashElement := DYNARRAY_HASHFIRSTFIELD[not Value,djRawUTF8];
  47079. fHash.{$ifdef UNDIRECTDYNARRAY}InternalDynArray.{$endif}fCompare :=
  47080. DYNARRAY_SORTFIRSTFIELDHASHONLY[Value];
  47081. if not fChanged then
  47082. fChanged := Count>0; // force re-hash next IndexOf() call
  47083. end;
  47084. function TRawUTF8ListHashed.IndexOf(const aText: RawUTF8): PtrInt;
  47085. begin
  47086. if fChanged then
  47087. fChanged := not fHash.ReHash; // rough, but working implementation
  47088. result := fHash.FindHashed(aText);
  47089. end;
  47090. function TRawUTF8ListHashed.AddIfNotExisting(const aText: RawUTF8;
  47091. wasAdded: PBoolean): PtrInt;
  47092. var added: boolean;
  47093. begin
  47094. if fChanged then
  47095. fChanged := not fHash.ReHash; // rough, but working implementation
  47096. result := fHash.FindHashedForAdding(aText,added);
  47097. if added then begin
  47098. fList[result] := aText;
  47099. if (fObjects<>nil) and (length(fObjects)<>length(fList)) then
  47100. SetLength(fObjects,length(fList));
  47101. end;
  47102. if wasAdded<>nil then
  47103. wasAdded^ := added;
  47104. end;
  47105. function TRawUTF8ListHashed.AddObjectIfNotExisting(
  47106. const aText: RawUTF8; aObject: TObject; wasAdded: PBoolean): PtrInt;
  47107. var added: boolean;
  47108. begin
  47109. if fChanged then
  47110. fChanged := not fHash.ReHash; // rough, but working implementation
  47111. result := fHash.FindHashedForAdding(aText,added);
  47112. if added then begin
  47113. fList[result] := aText;
  47114. if length(fObjects)<>length(fList) then
  47115. SetLength(fObjects,length(fList));
  47116. fObjects[result] := aObject;
  47117. end;
  47118. if wasAdded<>nil then
  47119. wasAdded^ := added;
  47120. end;
  47121. function TRawUTF8ListHashed.HashFind(aHashCode: cardinal): integer;
  47122. begin
  47123. result := fHash.HashFind(aHashCode);
  47124. end;
  47125. { TRawUTF8ListHashedLocked }
  47126. constructor TRawUTF8ListHashedLocked.Create(aOwnObjects: boolean);
  47127. begin
  47128. inherited Create(aOwnObjects);
  47129. fSafe.Init;
  47130. end;
  47131. destructor TRawUTF8ListHashedLocked.Destroy;
  47132. begin
  47133. fSafe.Done;
  47134. inherited;
  47135. end;
  47136. function TRawUTF8ListHashedLocked.LockedAdd(const aText: RawUTF8): PtrInt;
  47137. begin
  47138. fSafe.Lock;
  47139. try
  47140. result := inherited Add(aText);
  47141. finally
  47142. fSafe.UnLock;
  47143. end;
  47144. end;
  47145. function TRawUTF8ListHashedLocked.IndexOf(const aText: RawUTF8): PtrInt;
  47146. begin
  47147. fSafe.Lock;
  47148. try
  47149. result := inherited IndexOf(aText);
  47150. finally
  47151. fSafe.UnLock;
  47152. end;
  47153. end;
  47154. function TRawUTF8ListHashedLocked.LockedGetObjectByName(const aText: RawUTF8): TObject;
  47155. begin
  47156. fSafe.Lock;
  47157. try
  47158. result := inherited GetObjectByName(aText);
  47159. finally
  47160. fSafe.UnLock;
  47161. end;
  47162. end;
  47163. function TRawUTF8ListHashedLocked.AddIfNotExisting(const aText: RawUTF8;
  47164. wasAdded: PBoolean): PtrInt;
  47165. begin
  47166. fSafe.Lock;
  47167. try
  47168. result := inherited AddIfNotExisting(aText,wasAdded);
  47169. finally
  47170. fSafe.UnLock;
  47171. end;
  47172. end;
  47173. function TRawUTF8ListHashedLocked.AddObjectIfNotExisting(const aText: RawUTF8;
  47174. aObject: TObject; wasAdded: PBoolean): PtrInt;
  47175. begin
  47176. fSafe.Lock;
  47177. try
  47178. result := inherited AddObjectIfNotExisting(aText,aObject,wasAdded);
  47179. finally
  47180. fSafe.UnLock;
  47181. end;
  47182. end;
  47183. function TRawUTF8ListHashedLocked.Delete(const aText: RawUTF8): PtrInt;
  47184. begin
  47185. fSafe.Lock;
  47186. try
  47187. result := inherited IndexOf(aText);
  47188. if result>=0 then
  47189. inherited Delete(result);
  47190. finally
  47191. fSafe.UnLock;
  47192. end;
  47193. end;
  47194. function TRawUTF8ListHashedLocked.DeleteFromName(const Name: RawUTF8): PtrInt;
  47195. begin
  47196. fSafe.Lock;
  47197. try
  47198. result := inherited IndexOfName(Name);
  47199. if result>=0 then
  47200. inherited Delete(result);
  47201. finally
  47202. fSafe.UnLock;
  47203. end;
  47204. end;
  47205. function TRawUTF8ListHashedLocked.PopFirst(out aText: RawUTF8; aObject: PObject=nil): boolean;
  47206. begin
  47207. fSafe.Lock;
  47208. try
  47209. result := inherited PopFirst(aText,aObject);
  47210. finally
  47211. fSafe.UnLock;
  47212. end;
  47213. end;
  47214. function TRawUTF8ListHashedLocked.PopLast(out aText: RawUTF8; aObject: PObject=nil): boolean;
  47215. begin
  47216. fSafe.Lock;
  47217. try
  47218. result := inherited PopLast(aText,aObject);
  47219. finally
  47220. fSafe.UnLock;
  47221. end;
  47222. end;
  47223. procedure TRawUTF8ListHashedLocked.Clear;
  47224. begin
  47225. fSafe.Lock;
  47226. try
  47227. inherited Clear;
  47228. finally
  47229. fSafe.UnLock;
  47230. end;
  47231. end;
  47232. { TRawUTF8MethodList }
  47233. function TRawUTF8MethodList.AddEvent(const aName: RawUTF8;
  47234. const aEvent: TMethod): PtrInt;
  47235. begin
  47236. result := Add(aName);
  47237. if result>=length(fEvents) then
  47238. SetLength(fEvents,result+256);
  47239. fEvents[result] := aEvent;
  47240. end;
  47241. procedure TRawUTF8MethodList.Clear;
  47242. begin
  47243. inherited Clear;
  47244. fEvents := nil;
  47245. end;
  47246. procedure TRawUTF8MethodList.Delete(Index: PtrInt);
  47247. begin
  47248. inherited Delete(Index);
  47249. if Index<length(fEvents) then
  47250. MoveFast(fEvents[Index+1],fEvents[Index],(length(fEvents)-Index)*sizeof(TMethod));
  47251. end;
  47252. function TRawUTF8MethodList.GetEvent(aIndex: PtrInt;
  47253. out aEvent: TMethod): boolean;
  47254. begin
  47255. result := aIndex<length(fEvents);
  47256. if result then
  47257. aEvent := fEvents[aIndex];
  47258. end;
  47259. function TRawUTF8MethodList.GetEventByName(const aText: RawUTF8;
  47260. out aEvent: TMethod): boolean;
  47261. var i: integer;
  47262. begin
  47263. result := false;
  47264. if self=nil then
  47265. exit;
  47266. i := IndexOf(aText);
  47267. if (i>=0) and (i<length(fEvents)) then begin
  47268. result := true;
  47269. aEvent := fEvents[i];
  47270. end;
  47271. end;
  47272. { TSynDictionary }
  47273. const
  47274. DIC_KEYCOUNT = 0;
  47275. DIC_KEY = 1;
  47276. DIC_VALUECOUNT = 2;
  47277. DIC_VALUE = 3;
  47278. constructor TSynDictionary.Create(aKeyTypeInfo,aValueTypeInfo: pointer;
  47279. aKeyCaseInsensitive: boolean);
  47280. begin
  47281. inherited Create;
  47282. fSafe.Padding[DIC_KEYCOUNT].VType := varInteger;
  47283. fSafe.Padding[DIC_KEY].VType := varUnknown;
  47284. fSafe.Padding[DIC_VALUECOUNT].VType := varInteger;
  47285. fSafe.Padding[DIC_VALUE].VType := varUnknown;
  47286. fSafe.PaddingMaxUsedIndex := DIC_VALUE;
  47287. fKeys.Init(aKeyTypeInfo,fSafe.Padding[DIC_KEY].VAny,nil,nil,nil,
  47288. @fSafe.Padding[DIC_KEYCOUNT].VInteger,aKeyCaseInsensitive);
  47289. fValues.Init(aValueTypeInfo,fSafe.Padding[DIC_VALUE].VAny,
  47290. @fSafe.Padding[DIC_VALUECOUNT].VInteger);
  47291. end;
  47292. procedure TSynDictionary.DeleteAll;
  47293. begin
  47294. fSafe.Lock;
  47295. try
  47296. fKeys.Clear;
  47297. fKeys.ReHash; // mandatory to avoid GPF
  47298. fValues.Clear;
  47299. finally
  47300. fSafe.UnLock;
  47301. end;
  47302. end;
  47303. destructor TSynDictionary.Destroy;
  47304. begin
  47305. fKeys.Clear;
  47306. fValues.Clear;
  47307. inherited Destroy;
  47308. end;
  47309. function TSynDictionary.Add(const aKey, aValue): integer;
  47310. var added: boolean;
  47311. begin
  47312. fSafe.Lock;
  47313. try
  47314. result := fKeys.FindHashedForAdding(aKey,added);
  47315. if added then begin
  47316. with fKeys{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif} do
  47317. ElemCopy(aKey,ElemPtr(result)^); // fKey[result] := aKey;
  47318. if fValues.Add(aValue)<>result then
  47319. raise ESynException.CreateUTF8('%.Add fValues.Add',[self]);
  47320. end else
  47321. result := -1;
  47322. finally
  47323. fSafe.UnLock;
  47324. end;
  47325. end;
  47326. function TSynDictionary.AddOrUpdate(const aKey, aValue): integer;
  47327. var added: boolean;
  47328. begin
  47329. fSafe.Lock;
  47330. try
  47331. result := fKeys.FindHashedForAdding(aKey,added);
  47332. if added then begin
  47333. with fKeys{$ifdef UNDIRECTDYNARRAY}.InternalDynArray{$endif} do
  47334. ElemCopy(aKey,ElemPtr(result)^); // fKey[result] := aKey;
  47335. if fValues.Add(aValue)<>result then
  47336. raise ESynException.CreateUTF8('%.AddOrUpdate fValues.Add',[self]);
  47337. end else
  47338. fValues.ElemCopy(aValue,fValues.ElemPtr(result)^);
  47339. finally
  47340. fSafe.UnLock;
  47341. end;
  47342. end;
  47343. function TSynDictionary.Clear(const aKey): integer;
  47344. begin
  47345. fSafe.Lock;
  47346. try
  47347. result := fKeys.FindHashed(aKey);
  47348. if result>=0 then
  47349. fValues.ElemClear(fValues.ElemPtr(result)^);
  47350. finally
  47351. fSafe.UnLock;
  47352. end;
  47353. end;
  47354. function TSynDictionary.Delete(const aKey): integer;
  47355. begin
  47356. fSafe.Lock;
  47357. try
  47358. result := fKeys.FindHashedAndDelete(aKey);
  47359. if result>=0 then
  47360. fValues.Delete(result);
  47361. finally
  47362. fSafe.UnLock;
  47363. end;
  47364. end;
  47365. function TSynDictionary.InArray(const aKey, aArrayValue; aAction: TSynDictionaryInArray): Boolean;
  47366. var nested: TDynArray;
  47367. ndx: integer;
  47368. begin
  47369. result := false;
  47370. if (fValues.ElemType=nil) or (PTypeKind(fValues.ElemType)^<>tkDynArray) then
  47371. raise ESynException.CreateUTF8('%.Values: % items are not dynamic arrays',
  47372. [self,PShortString(@PTypeInfo(fValues.ArrayType)^.NameLen)^]);
  47373. fSafe.Lock;
  47374. try
  47375. ndx := fKeys.FindHashed(aKey);
  47376. if ndx<0 then
  47377. exit;
  47378. nested.Init(fValues.ElemType, fValues.ElemPtr(ndx)^);
  47379. case aAction of
  47380. iaFind:
  47381. result := nested.Find(aArrayValue)>=0;
  47382. iaFindAndDelete:
  47383. result := nested.FindAndDelete(aArrayValue)>=0;
  47384. iaFindAndUpdate:
  47385. result := nested.FindAndUpdate(aArrayValue)>=0;
  47386. iaFindAndAddIfNotExisting:
  47387. result := nested.FindAndAddIfNotExisting(aArrayValue)>=0;
  47388. iaAdd:
  47389. result := nested.Add(aArrayValue)>=0;
  47390. end;
  47391. finally
  47392. fSafe.UnLock;
  47393. end;
  47394. end;
  47395. function TSynDictionary.FindInArray(const aKey, aArrayValue): boolean;
  47396. begin
  47397. result := InArray(aKey,aArrayValue,iaFind);
  47398. end;
  47399. function TSynDictionary.DeleteInArray(const aKey, aArrayValue): boolean;
  47400. begin
  47401. result := InArray(aKey,aArrayValue,iaFindAndDelete);
  47402. end;
  47403. function TSynDictionary.UpdateInArray(const aKey, aArrayValue): boolean;
  47404. begin
  47405. result := InArray(aKey,aArrayValue,iaFindAndUpdate);
  47406. end;
  47407. function TSynDictionary.AddInArray(const aKey, aArrayValue): boolean;
  47408. begin
  47409. result := InArray(aKey,aArrayValue,iaAdd);
  47410. end;
  47411. function TSynDictionary.AddOnceInArray(const aKey, aArrayValue): boolean;
  47412. begin
  47413. result := InArray(aKey,aArrayValue,iaFindAndAddIfNotExisting);
  47414. end;
  47415. function TSynDictionary.Find(const aKey): integer;
  47416. begin // caller is expected to call fSafe.Lock/Unlock
  47417. result := fKeys.FindHashed(aKey);
  47418. end;
  47419. function TSynDictionary.FindAndCopy(const aKey; out aValue): boolean;
  47420. var ndx: integer;
  47421. begin
  47422. fSafe.Lock;
  47423. try
  47424. ndx := fKeys.FindHashed(aKey);
  47425. if ndx>=0 then begin
  47426. fValues.ElemCopy(fValues.ElemPtr(ndx)^,aValue);
  47427. result := true;
  47428. end else
  47429. result := false;
  47430. finally
  47431. fSafe.UnLock;
  47432. end;
  47433. end;
  47434. function TSynDictionary.Exists(const aKey): boolean;
  47435. begin
  47436. fSafe.Lock;
  47437. try
  47438. result := fKeys.FindHashed(aKey)>=0;
  47439. finally
  47440. fSafe.UnLock;
  47441. end;
  47442. end;
  47443. function TSynDictionary.ForEach(const OnEach: TSynDictionaryEvent): integer;
  47444. var k,v: PAnsiChar;
  47445. i,n,ks,vs: integer;
  47446. begin
  47447. fSafe.Lock;
  47448. try
  47449. result := 0;
  47450. n := fSafe.Padding[DIC_KEYCOUNT].VInteger;
  47451. if (n=0) or not Assigned(OnEach) then
  47452. exit;
  47453. k := fKeys.fValue^;
  47454. ks := fKeys.ElemSize;
  47455. v := fValues.fValue^;
  47456. vs := fValues.ElemSize;
  47457. for i := 0 to n-1 do begin
  47458. inc(result);
  47459. if not OnEach(k^,v^,i,n) then
  47460. break;
  47461. inc(k,ks);
  47462. inc(v,vs);
  47463. end;
  47464. finally
  47465. fSafe.UnLock;
  47466. end;
  47467. end;
  47468. function TSynDictionary.ForEach(const OnMatch: TSynDictionaryEvent;
  47469. KeyCompare,ValueCompare: TDynArraySortCompare; const aKey,aValue): integer;
  47470. var k,v: PAnsiChar;
  47471. i,n,ks,vs: integer;
  47472. begin
  47473. fSafe.Lock;
  47474. try
  47475. result := 0;
  47476. if (not Assigned(OnMatch)) or
  47477. (not Assigned(KeyCompare)) and (not Assigned(ValueCompare)) then
  47478. exit;
  47479. n := fSafe.Padding[DIC_KEYCOUNT].VInteger;
  47480. k := fKeys.fValue^;
  47481. ks := fKeys.ElemSize;
  47482. v := fValues.fValue^;
  47483. vs := fValues.ElemSize;
  47484. for i := 0 to n-1 do begin
  47485. if (Assigned(KeyCompare) and (KeyCompare(k^,aKey)=0)) or
  47486. (Assigned(ValueCompare) and (ValueCompare(v^,aValue)=0)) then begin
  47487. inc(result);
  47488. if not OnMatch(k^,v^,i,n) then
  47489. break;
  47490. end;
  47491. inc(k,ks);
  47492. inc(v,vs);
  47493. end;
  47494. finally
  47495. fSafe.UnLock;
  47496. end;
  47497. end;
  47498. function TSynDictionary.Count: integer;
  47499. begin
  47500. {$ifdef NOVARIANTS}
  47501. result := fSafe.Padding[DIC_KEYCOUNT].VInteger;
  47502. {$else}
  47503. result := fSafe.LockedInt64[DIC_KEYCOUNT];
  47504. {$endif}
  47505. end;
  47506. procedure TSynDictionary.SaveToJSON(W: TTextWriter; EnumSetsAsText: boolean);
  47507. var k,v: RawUTF8;
  47508. begin
  47509. fSafe.Lock;
  47510. try
  47511. k := fKeys.SaveToJSON(EnumSetsAsText);
  47512. v := fValues.SaveToJSON(EnumSetsAsText);
  47513. finally
  47514. fSafe.UnLock;
  47515. end;
  47516. W.AddJSONArraysAsJSONObject(pointer(k),pointer(v));
  47517. end;
  47518. function TSynDictionary.SaveToJSON(EnumSetsAsText: boolean): RawUTF8;
  47519. var W: TTextWriter;
  47520. begin
  47521. W := TTextWriter.CreateOwnedStream;
  47522. try
  47523. SaveToJSON(W,EnumSetsAsText);
  47524. W.SetText(result);
  47525. finally
  47526. W.Free;
  47527. end;
  47528. end;
  47529. function TSynDictionary.LoadFromJSON(const JSON: RawUTF8;
  47530. EnsureNoKeyCollision: boolean): boolean;
  47531. begin
  47532. result := LoadFromJSON(pointer(JSON),EnsureNoKeyCollision);
  47533. end;
  47534. function TSynDictionary.LoadFromJSON(JSON: PUTF8Char; EnsureNoKeyCollision: boolean): boolean;
  47535. var k,v: RawUTF8;
  47536. begin
  47537. result := false;
  47538. if not JSONObjectAsJSONArrays(JSON,k,v) then
  47539. exit;
  47540. fSafe.Lock;
  47541. try
  47542. if fKeys.LoadFromJSON(pointer(k))<>nil then
  47543. if fValues.LoadFromJSON(pointer(v))<>nil then
  47544. if fKeys.Count=fValues.Count then
  47545. if EnsureNoKeyCollision then
  47546. // fKeys.Rehash is not enough, since input JSON may be invalid
  47547. result := fKeys.IsHashElementWithoutCollision<0 else begin
  47548. // optimistic approach
  47549. fKeys.Rehash;
  47550. result := true;
  47551. end;
  47552. finally
  47553. fSafe.UnLock;
  47554. end;
  47555. end;
  47556. function TSynDictionary.LoadFromBinary(const binary: RawByteString): boolean;
  47557. var P: PAnsiChar;
  47558. begin
  47559. result := false;
  47560. P := pointer(SynLZDecompress(binary));
  47561. if P=nil then
  47562. exit;
  47563. fSafe.Lock;
  47564. try
  47565. P := fKeys.LoadFrom(P);
  47566. if P<>nil then
  47567. P := fValues.LoadFrom(P);
  47568. if (P<>nil) and (fKeys.Count=fValues.Count) then begin
  47569. fKeys.ReHash; // optimistic: input from safe TSynDictionary.SaveToBinary
  47570. result := true;
  47571. end;
  47572. finally
  47573. fSafe.UnLock;
  47574. end;
  47575. end;
  47576. function TSynDictionary.SaveToBinary: RawByteString;
  47577. var tmp: TSynTempBuffer;
  47578. begin
  47579. fSafe.Lock;
  47580. try
  47581. tmp.Init(fKeys.SaveToLength+fValues.SaveToLength);
  47582. if fValues.SaveTo(fKeys.SaveTo(tmp.buf))-tmp.buf<>tmp.len then
  47583. result := '' else
  47584. SynLZCompress(tmp.buf,tmp.len,result);
  47585. tmp.Done;
  47586. finally
  47587. fSafe.UnLock;
  47588. end;
  47589. end;
  47590. { TMemoryMap }
  47591. function TMemoryMap.Map(aFile: THandle; aCustomSize: cardinal; aCustomOffset: Int64): boolean;
  47592. var Available: Int64;
  47593. begin
  47594. fBuf := nil;
  47595. fBufSize := 0;
  47596. {$ifdef MSWINDOWS}
  47597. fMap := 0;
  47598. {$endif}
  47599. fFileLocal := false;
  47600. fFile := aFile;
  47601. fFileSize := FileSeek64(fFile,0,soFromEnd);
  47602. if fFileSize=0 then begin
  47603. result := true; // handle 0 byte file without error (but no memory map)
  47604. exit;
  47605. end;
  47606. result := false;
  47607. if (fFileSize<=0) or (fFileSize>maxInt) then
  47608. /// maxInt = $7FFFFFFF = 1.999 GB (2GB would induce PtrInt errors)
  47609. exit;
  47610. if aCustomSize=0 then
  47611. fBufSize := Int64Rec(fFileSize).Lo else begin
  47612. Available := fFileSize-aCustomOffset;
  47613. if Available<0 then
  47614. exit;
  47615. if aCustomSize>Available then
  47616. fBufSize := Int64Rec(Available).Lo;
  47617. fBufSize := aCustomSize;
  47618. end;
  47619. {$ifdef MSWINDOWS}
  47620. with Int64Rec(fFileSize) do
  47621. fMap := CreateFileMapping(fFile,nil,PAGE_READONLY,Hi,Lo,nil);
  47622. if fMap=0 then
  47623. raise ESynException.Create('TMemoryMap.Map: CreateFileMapping()=0');
  47624. with Int64Rec(aCustomOffset) do
  47625. fBuf := MapViewOfFile(fMap,FILE_MAP_READ,Hi,Lo,fBufSize);
  47626. if fBuf=nil then begin
  47627. // Windows failed to find a contiguous VA space -> fall back on direct read
  47628. CloseHandle(fMap);
  47629. fMap := 0;
  47630. {$else}
  47631. if aCustomOffset<>0 then
  47632. if aCustomOffset and (_SC_PAGE_SIZE-1)<>0 then
  47633. raise ESynException.CreateUTF8('fpmmap(aCustomOffset=%) with pagesize=%',
  47634. [aCustomOffset,_SC_PAGE_SIZE]) else
  47635. aCustomOffset := aCustomOffset div _SC_PAGE_SIZE;
  47636. fBuf := {$ifdef KYLIX3}mmap{$else}fpmmap{$endif}(
  47637. nil,fBufSize,PROT_READ,MAP_SHARED,fFile,aCustomOffset);
  47638. if fBuf=MAP_FAILED then begin
  47639. fBuf := nil;
  47640. {$endif}
  47641. end else
  47642. result := true;
  47643. end;
  47644. procedure TMemoryMap.Map(aBuffer: pointer; aBufferSize: cardinal);
  47645. begin
  47646. fBuf := aBuffer;
  47647. fFileSize := aBufferSize;
  47648. fBufSize := aBufferSize;
  47649. {$ifdef MSWINDOWS}
  47650. fMap := 0;
  47651. {$endif}
  47652. fFile := 0;
  47653. fFileLocal := false;
  47654. end;
  47655. function TMemoryMap.Map(const aFileName: TFileName): boolean;
  47656. var F: THandle;
  47657. begin
  47658. result := false;
  47659. // Memory-mapped file access does not go through the cache manager so
  47660. // using FileOpenSequentialRead() is pointless here
  47661. F := FileOpen(aFileName,fmOpenRead or fmShareDenyNone);
  47662. if PtrInt(F)<0 then
  47663. exit;
  47664. if Map(F) then
  47665. result := true else
  47666. FileClose(F);
  47667. fFileLocal := result;
  47668. end;
  47669. procedure TMemoryMap.UnMap;
  47670. begin
  47671. {$ifdef MSWINDOWS}
  47672. if fMap<>0 then begin
  47673. UnmapViewOfFile(fBuf);
  47674. CloseHandle(fMap);
  47675. fMap := 0;
  47676. end;
  47677. {$else}
  47678. if fBuf<>nil then
  47679. {$ifdef KYLIX3}munmap{$else}fpmunmap{$endif}(fBuf,fBufSize);
  47680. {$endif}
  47681. fBuf := nil;
  47682. fBufSize := 0;
  47683. if fFile<>0 then begin
  47684. if fFileLocal then
  47685. FileClose(fFile);
  47686. fFile := 0;
  47687. end;
  47688. end;
  47689. { TSynMemoryStream }
  47690. constructor TSynMemoryStream.Create(const aText: RawByteString);
  47691. begin
  47692. inherited Create;
  47693. SetPointer(pointer(aText),length(aText));
  47694. end;
  47695. constructor TSynMemoryStream.Create(Data: pointer; DataLen: integer);
  47696. begin
  47697. inherited Create;
  47698. SetPointer(Data,DataLen);
  47699. end;
  47700. function TSynMemoryStream.Write(const Buffer; Count: Integer): Longint;
  47701. begin
  47702. raise EStreamError.Create('TSynMemoryStream.Write');
  47703. end;
  47704. { TSynMemoryStreamMapped }
  47705. constructor TSynMemoryStreamMapped.Create(const aFileName: TFileName;
  47706. aCustomSize: cardinal; aCustomOffset: Int64);
  47707. begin
  47708. fFileName := aFileName;
  47709. // Memory-mapped file access does not go through the cache manager so
  47710. // using FileOpenSequentialRead() is pointless here
  47711. fFileStream := TFileStream.Create(aFileName,fmOpenRead or fmShareDenyNone);
  47712. Create(fFileStream.Handle,aCustomSize,aCustomOffset);
  47713. end;
  47714. constructor TSynMemoryStreamMapped.Create(aFile: THandle;
  47715. aCustomSize: cardinal; aCustomOffset: Int64);
  47716. begin
  47717. if not fMap.Map(aFile,aCustomSize,aCustomOffset) then
  47718. raise ESynException.CreateUTF8('%.Create(%) mapping error',[self,fFileName]);
  47719. inherited Create(fMap.fBuf,fMap.fBufSize);
  47720. end;
  47721. destructor TSynMemoryStreamMapped.Destroy;
  47722. begin
  47723. fMap.UnMap;
  47724. fFileStream.Free;
  47725. inherited;
  47726. end;
  47727. function FileSeek64(Handle: THandle; const Offset: Int64; Origin: DWORD): Int64;
  47728. {$ifdef MSWINDOWS}
  47729. var R64: packed record Lo, Hi: integer; end absolute Result;
  47730. begin
  47731. Result := Offset;
  47732. R64.Lo := integer(SetFilePointer(Handle,R64.Lo,@R64.Hi,Origin));
  47733. if (R64.Lo=-1) and (GetLastError<>0) then
  47734. R64.Hi := -1; // so result=-1
  47735. end;
  47736. {$else}
  47737. begin
  47738. {$ifdef FPC}
  47739. result := FPLSeek(Handle,Offset,Origin);
  47740. {$else}
  47741. {$ifdef KYLIX3}
  47742. result := LibC.lseek64(Handle,Offset,Origin);
  47743. {$else}
  47744. // warning: this won't handle file size > 2 GB :(
  47745. result := FileSeek(Handle,Offset,Origin);
  47746. {$endif}
  47747. {$endif}
  47748. end;
  47749. {$endif}
  47750. { TFileBufferWriter }
  47751. constructor TFileBufferWriter.Create(aFile: THandle; BufLen: integer);
  47752. begin
  47753. Create(THandleStream.Create(aFile),BufLen);
  47754. fInternalStream := true;
  47755. end;
  47756. constructor TFileBufferWriter.Create(const aFileName: TFileName; BufLen: integer;
  47757. Append: boolean);
  47758. var s: TStream;
  47759. begin
  47760. if Append and FileExists(aFileName) then begin
  47761. s := TFileStream.Create(aFileName,fmOpenWrite);
  47762. s.Seek(0,soFromEnd);
  47763. end else
  47764. s := TFileStream.Create(aFileName,fmCreate);
  47765. Create(s,BufLen);
  47766. fInternalStream := true;
  47767. end;
  47768. constructor TFileBufferWriter.Create(aStream: TStream; BufLen: integer);
  47769. begin
  47770. if BufLen>1 shl 22 then
  47771. fBufLen := 1 shl 22 else // 4 MB sounds right enough
  47772. if BufLen<32 then
  47773. fBufLen := 32;
  47774. fBufLen := BufLen;
  47775. fStream := aStream;
  47776. SetLength(fBuf,fBufLen);
  47777. end;
  47778. constructor TFileBufferWriter.Create(aClass: TStreamClass; BufLen: integer);
  47779. begin
  47780. Create(aClass.Create,BufLen);
  47781. fInternalStream := true;
  47782. end;
  47783. destructor TFileBufferWriter.Destroy;
  47784. begin
  47785. if fInternalStream then
  47786. fStream.Free;
  47787. inherited;
  47788. end;
  47789. function TFileBufferWriter.Flush: Int64;
  47790. begin
  47791. if fPos>0 then begin
  47792. fStream.Write(pointer(fBuf)^,fPos);
  47793. fPos := 0;
  47794. end;
  47795. result := fTotalWritten;
  47796. fTotalWritten := 0;
  47797. end;
  47798. procedure TFileBufferWriter.CancelAll;
  47799. begin
  47800. fTotalWritten := 0;
  47801. fPos := 0;
  47802. fStream.Seek(0,soBeginning);
  47803. end;
  47804. procedure TFileBufferWriter.Write(Data: pointer; DataLen: integer);
  47805. begin
  47806. if (DataLen<=0) or (Data=nil) then
  47807. exit;
  47808. inc(fTotalWritten,PtrUInt(DataLen));
  47809. if fPos+DataLen>fBufLen then begin
  47810. if fPos>0 then begin
  47811. fStream.Write(pointer(fBuf)^,fPos);
  47812. fPos := 0;
  47813. end;
  47814. if DataLen>fBufLen then begin
  47815. fStream.Write(Data^,DataLen);
  47816. exit;
  47817. end;
  47818. end;
  47819. MoveFast(Data^,PByteArray(fBuf)^[fPos],DataLen);
  47820. inc(fPos,DataLen);
  47821. end;
  47822. procedure TFileBufferWriter.WriteN(Data: Byte; Count: integer);
  47823. var len: integer;
  47824. begin
  47825. inc(fTotalWritten,Count);
  47826. while Count>0 do begin
  47827. if Count>fBufLen then
  47828. len := fBufLen else
  47829. len := Count;
  47830. if fPos+len>fBufLen then begin
  47831. fStream.Write(pointer(fBuf)^,fPos);
  47832. fPos := 0;
  47833. end;
  47834. FillcharFast(PByteArray(fBuf)^[fPos],len,Data);
  47835. inc(fPos,len);
  47836. dec(Count,len);
  47837. end;
  47838. end;
  47839. procedure TFileBufferWriter.Write1(Data: byte);
  47840. begin
  47841. if fPos+1>fBufLen then begin
  47842. fStream.Write(pointer(fBuf)^,fPos);
  47843. fPos := 0;
  47844. end;
  47845. PByteArray(fBuf)^[fPos] := Data;
  47846. inc(fPos);
  47847. inc(fTotalWritten);
  47848. end;
  47849. procedure TFileBufferWriter.Write4(Data: integer);
  47850. begin
  47851. if fPos+sizeof(integer)>fBufLen then begin
  47852. fStream.Write(pointer(fBuf)^,fPos);
  47853. fPos := 0;
  47854. end;
  47855. PInteger(@PByteArray(fBuf)^[fPos])^ := Data;
  47856. inc(fPos,sizeof(integer));
  47857. inc(fTotalWritten,sizeof(integer));
  47858. end;
  47859. procedure TFileBufferWriter.Write4BigEndian(Data: integer);
  47860. begin
  47861. Write4(bswap32(Data));
  47862. end;
  47863. procedure TFileBufferWriter.Write8(const Data8Bytes);
  47864. begin
  47865. if fPos+sizeof(Int64)>fBufLen then begin
  47866. fStream.Write(pointer(fBuf)^,fPos);
  47867. fPos := 0;
  47868. end;
  47869. PInt64(@PByteArray(fBuf)^[fPos])^ := Int64(Data8Bytes);
  47870. inc(fPos,sizeof(Int64));
  47871. inc(fTotalWritten,sizeof(Int64));
  47872. end;
  47873. procedure TFileBufferWriter.Write(const Text: RawByteString);
  47874. var L: integer;
  47875. begin
  47876. L := length(Text);
  47877. WriteVarUInt32(L);
  47878. if L=0 then
  47879. exit;
  47880. Write(pointer(Text),L);
  47881. end;
  47882. procedure TFileBufferWriter.WriteShort(const Text: ShortString);
  47883. begin
  47884. Write1(ord(Text[0]));
  47885. Write(@Text[1],ord(Text[0]));
  47886. end;
  47887. procedure TFileBufferWriter.WriteBinary(const Data: RawByteString);
  47888. begin
  47889. Write(pointer(Data),Length(Data));
  47890. end;
  47891. {$ifndef NOVARIANTS}
  47892. procedure TFileBufferWriter.Write(const Value: variant);
  47893. procedure CustomType; // same code as VariantSave/VariantSaveLen
  47894. begin
  47895. Write(@TVarData(Value).VType,SizeOf(TVarData(Value).VType));
  47896. Write(VariantSaveJSON(Value));
  47897. end;
  47898. var tmp,buf: PAnsiChar;
  47899. len: integer;
  47900. begin
  47901. if TVarData(Value).VType>varAny then begin
  47902. CustomType; // faster process without calling VariantSaveLength() for JSON
  47903. exit;
  47904. end;
  47905. tmp := nil;
  47906. len := VariantSaveLength(Value);
  47907. if len=0 then
  47908. raise ESynException.CreateUTF8('%.Write(VType=%) VariantSaveLength=0',
  47909. [self,TVarData(Value).VType]);
  47910. if fPos+len>fBufLen then begin
  47911. fStream.Write(pointer(fBuf)^,fPos);
  47912. fPos := 0;
  47913. if len>fBufLen then begin
  47914. GetMem(tmp,len);
  47915. buf := tmp;
  47916. end else
  47917. buf := pointer(fBuf);
  47918. end else
  47919. buf := @PByteArray(fBuf)^[fPos];
  47920. if VariantSave(Value,buf)=nil then
  47921. raise ESynException.CreateUTF8('%.Write(VType=%) VariantSave=nil',
  47922. [self,TVarData(Value).VType]);
  47923. inc(fTotalWritten,len);
  47924. if tmp=nil then
  47925. inc(fPos,len) else begin
  47926. fStream.Write(tmp^,len);
  47927. FreeMem(tmp);
  47928. end;
  47929. end;
  47930. {$endif}
  47931. procedure TFileBufferWriter.WriteXor(New,Old: PAnsiChar; Len: integer; crc: PCardinal);
  47932. var L: integer;
  47933. Dest: PAnsiChar;
  47934. begin
  47935. if (New=nil) or (Old=nil) then
  47936. exit;
  47937. inc(fTotalWritten,Len);
  47938. while Len>0 do begin
  47939. Dest := pointer(fBuf);
  47940. if fPos+Len>fBufLen then begin
  47941. fStream.Write(pointer(fBuf)^,fPos);
  47942. fPos := 0;
  47943. end else
  47944. inc(Dest,fPos);
  47945. if Len>fBufLen then
  47946. L := fBufLen else
  47947. L := Len;
  47948. XorMemory(pointer(Dest),pointer(New),pointer(Old),L);
  47949. if crc<>nil then
  47950. crc^ := crc32c(crc^,Dest,L);
  47951. inc(Old,L);
  47952. inc(New,L);
  47953. dec(Len,L);
  47954. inc(fPos,L);
  47955. end;
  47956. end;
  47957. procedure TFileBufferWriter.WriteRawUTF8DynArray(const Values: TRawUTF8DynArray;
  47958. ValuesCount: integer);
  47959. var PI: PPtrUIntArray;
  47960. n, i, fixedsize: integer;
  47961. len: PtrUInt;
  47962. P, PEnd: PByte;
  47963. PBeg: PAnsiChar;
  47964. begin
  47965. WriteVarUInt32(ValuesCount);
  47966. PI := pointer(Values);
  47967. if ValuesCount=0 then
  47968. exit;
  47969. fixedsize := length(Values[0]);
  47970. if fixedsize>0 then
  47971. for i := 1 to ValuesCount-1 do
  47972. if (PI^[i]=0) or
  47973. ({$ifdef FPC}PStrRec(Pointer(PI^[i]-STRRECSIZE))^.length
  47974. {$else}PInteger(PI^[i]-sizeof(integer))^{$endif}<>fixedsize) then begin
  47975. fixedsize := 0;
  47976. break;
  47977. end;
  47978. WriteVarUInt32(fixedsize);
  47979. repeat
  47980. P := @PByteArray(fBuf)^[fPos];
  47981. PEnd := @PByteArray(fBuf)^[fBufLen-8];
  47982. if PtrUInt(P)<PtrUInt(PEnd) then begin
  47983. n := ValuesCount;
  47984. PBeg := PAnsiChar(P); // leave space for chunk size
  47985. inc(P,4);
  47986. if fixedsize=0 then
  47987. for i := 0 to ValuesCount-1 do
  47988. if PI^[i]=0 then begin
  47989. P^ := 0; // store length=0
  47990. inc(P);
  47991. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  47992. n := i+1;
  47993. break; // avoid buffer overflow
  47994. end;
  47995. end else begin
  47996. len := {$ifdef FPC}PStrRec(Pointer(PI^[i]-STRRECSIZE))^.length
  47997. {$else}PInteger(PI^[i]-sizeof(integer))^{$endif};
  47998. if PtrUInt(PEnd)-PtrUInt(P)<=len then begin
  47999. n := i;
  48000. break; // avoid buffer overflow
  48001. end;
  48002. P := ToVarUInt32(len,P);
  48003. MoveFast(pointer(PI^[i])^,P^,len);
  48004. inc(P,len);
  48005. end else
  48006. // fixed size strings case
  48007. for i := 0 to ValuesCount-1 do begin
  48008. if PtrInt(PEnd)-PtrInt(P)<=fixedsize then begin
  48009. n := i;
  48010. break; // avoid buffer overflow
  48011. end;
  48012. MoveFast(pointer(PI^[i])^,P^,fixedsize);
  48013. inc(P,fixedsize);
  48014. end;
  48015. len := PAnsiChar(P)-PBeg; // format: Isize+varUInt32s*strings
  48016. PInteger(PBeg)^ := len-4;
  48017. inc(fTotalWritten,len);
  48018. inc(fPos,len);
  48019. inc(PtrUInt(PI),n*sizeof(PtrInt));
  48020. dec(ValuesCount,n);
  48021. if ValuesCount=0 then
  48022. break;
  48023. end;
  48024. fStream.Write(pointer(fBuf)^,fPos);
  48025. fPos := 0;
  48026. until false;
  48027. end;
  48028. procedure TFileBufferWriter.WriteRawUTF8List(List: TRawUTF8List;
  48029. StoreObjectsAsVarUInt32: Boolean);
  48030. var i: integer;
  48031. begin
  48032. if List=nil then
  48033. WriteVarUInt32(0) else begin
  48034. WriteRawUTF8DynArray(List.fList,List.Count);
  48035. if List.fObjects=nil then
  48036. StoreObjectsAsVarUInt32 := false; // no Objects[] values
  48037. Write(@StoreObjectsAsVarUInt32,1);
  48038. if StoreObjectsAsVarUInt32 then
  48039. for i := 0 to List.fCount-1 do
  48040. WriteVarUInt32(PtrUInt(List.fObjects[i]));
  48041. end;
  48042. end;
  48043. procedure TFileBufferWriter.WriteStream(aStream: TCustomMemoryStream;
  48044. aStreamSize: Integer);
  48045. begin
  48046. if aStreamSize<0 then
  48047. if aStream=nil then
  48048. aStreamSize := 0 else
  48049. aStreamSize := aStream.Size;
  48050. WriteVarUInt32(aStreamSize);
  48051. if aStreamSize>0 then
  48052. Write(aStream.Memory,aStreamSize);
  48053. end;
  48054. procedure TFileBufferWriter.WriteVarInt32(Value: PtrInt);
  48055. begin
  48056. if Value<=0 then
  48057. // 0->0, -1->2, -2->4..
  48058. Value := (-Value) shl 1 else
  48059. // 1->1, 2->3..
  48060. Value := (Value shl 1)-1;
  48061. WriteVarUInt32(Value);
  48062. end;
  48063. procedure TFileBufferWriter.WriteVarUInt32(Value: PtrUInt);
  48064. var pos: integer;
  48065. begin
  48066. if fPos+16>fBufLen then begin
  48067. fStream.Write(pointer(fBuf)^,fPos);
  48068. fPos := 0;
  48069. end;
  48070. pos := fPos;
  48071. fPos := PtrUInt(ToVarUInt32(Value,@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48072. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48073. end;
  48074. procedure TFileBufferWriter.WriteVarInt64(Value: Int64);
  48075. var pos: integer;
  48076. begin
  48077. if fPos+16>fBufLen then begin
  48078. fStream.Write(pointer(fBuf)^,fPos);
  48079. fPos := 0;
  48080. end;
  48081. pos := fPos;
  48082. fPos := PtrUInt(ToVarInt64(Value,@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48083. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48084. end;
  48085. procedure TFileBufferWriter.WriteVarUInt64(Value: QWord);
  48086. var pos: integer;
  48087. begin
  48088. if fPos+16>fBufLen then begin
  48089. fStream.Write(pointer(fBuf)^,fPos);
  48090. fPos := 0;
  48091. end;
  48092. pos := fPos;
  48093. fPos := PtrUInt(ToVarUInt64(Value,@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48094. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48095. end;
  48096. function CleverStoreInteger(p: PInteger; V, VEnd: PAnsiChar; pCount: integer;
  48097. var StoredCount: integer): PAnsiChar;
  48098. // Clever = store Values[i+1]-Values[i] (with special diff=1 count)
  48099. // format: Integer: firstValue, then:
  48100. // B:0 W:difference with previous
  48101. // B:1..253 = difference with previous
  48102. // B:254 W:byOne
  48103. // B:255 B:byOne
  48104. var i, d, byOne: integer;
  48105. begin
  48106. StoredCount := pCount;
  48107. if pCount<=0 then begin
  48108. result := V;
  48109. exit;
  48110. end;
  48111. i := p^;
  48112. PInteger(V)^ := p^;
  48113. inc(V,4);
  48114. dec(pCount);
  48115. inc(p);
  48116. byOne := 0;
  48117. if pCount>0 then
  48118. repeat
  48119. d := p^-i;
  48120. i := p^;
  48121. inc(p);
  48122. if d=1 then begin
  48123. dec(pCount);
  48124. inc(byOne);
  48125. if pCount>0 then continue;
  48126. end else
  48127. if d<0 then begin
  48128. result:= nil;
  48129. exit;
  48130. end;
  48131. if byOne<>0 then begin
  48132. case byOne of
  48133. 1: begin V^ := #1; inc(V); end; // B:1..253 = difference with previous
  48134. 2: begin PWord(V)^ := $0101; inc(V,2); end; // B:1..253 = difference
  48135. else
  48136. if byOne>255 then begin
  48137. while byOne>65535 do begin
  48138. PInteger(V)^ := $fffffe; inc(V,3); // store as many len=$ffff as necessary
  48139. dec(byOne,$ffff);
  48140. end;
  48141. PInteger(V)^ := byOne shl 8+$fe; inc(V,3); // B:254 W:byOne
  48142. end else begin
  48143. PWord(V)^ := byOne shl 8+$ff; inc(V,2); // B:255 B:byOne
  48144. end;
  48145. end; // case byOne of
  48146. if pCount=0 then break;
  48147. byOne := 0;
  48148. end;
  48149. if (d=0) or (d>253) then begin
  48150. while cardinal(d)>65535 do begin
  48151. PInteger(V)^ := $ffff00; inc(V,3); // store as many len=$ffff as necessary
  48152. dec(cardinal(d),$ffff);
  48153. end;
  48154. dec(pCount);
  48155. PInteger(V)^ := d shl 8; inc(V,3); // B:0 W:difference with previous
  48156. if (V<VEnd) and (pCount>0) then continue else break;
  48157. end else begin
  48158. dec(pCount);
  48159. V^ := AnsiChar(d); inc(V); // B:1..253 = difference with previous
  48160. if (V<VEnd) and (pCount>0) then continue else break;
  48161. end;
  48162. if V>=VEnd then
  48163. break; // avoid GPF
  48164. until false;
  48165. dec(StoredCount,pCount);
  48166. result := V;
  48167. end;
  48168. procedure TFileBufferWriter.WriteVarUInt32Array(const Values: TIntegerDynArray;
  48169. ValuesCount: integer; DataLayout: TFileBufferWriterKind);
  48170. var n, i, pos, diff: integer;
  48171. P: PByte;
  48172. PI: PIntegerArray;
  48173. PBeg, PEnd: PAnsiChar;
  48174. begin
  48175. WriteVarUInt32(ValuesCount);
  48176. if ValuesCount=0 then
  48177. exit;
  48178. PI := pointer(Values);
  48179. PByteArray(fBuf)^[fPos] := ord(DataLayout);
  48180. inc(fPos);
  48181. inc(fTotalWritten);
  48182. if DataLayout in [wkOffsetU, wkOffsetI] then begin
  48183. pos := fPos;
  48184. fPos := PtrUInt(ToVarUInt32(PI^[0],@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48185. diff := PI^[1]-PI^[0];
  48186. inc(PtrUInt(PI),4);
  48187. dec(ValuesCount);
  48188. if ValuesCount=0 then begin
  48189. inc(fTotalWritten,PtrUInt(fPos-pos));
  48190. exit;
  48191. end;
  48192. if diff>0 then begin
  48193. for i := 1 to ValuesCount-1 do
  48194. if PI^[i]-PI^[i-1]<>diff then begin
  48195. diff := 0; // not always the same offset
  48196. break;
  48197. end;
  48198. end else
  48199. diff := 0;
  48200. fPos := PtrUInt(ToVarUInt32(diff,@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48201. inc(fTotalWritten,PtrUInt(fPos-pos));
  48202. if diff<>0 then
  48203. exit; // same offset for all items (fixed sized records) -> quit now
  48204. end;
  48205. repeat
  48206. P := @PByteArray(fBuf)^[fPos];
  48207. PEnd := @PByteArray(fBuf)^[fBufLen-32];
  48208. if PtrUInt(P)<PtrUInt(PEnd) then begin
  48209. pos := fPos;
  48210. case DataLayout of
  48211. wkUInt32: begin
  48212. n := (fBufLen-fPos)shr 2;
  48213. if ValuesCount<n then
  48214. n := ValuesCount;
  48215. MoveFast(PI^,P^,n*4);
  48216. inc(P,n*4);
  48217. end;
  48218. wkVarInt32, wkVarUInt32, wkOffsetU, wkOffsetI: begin
  48219. PBeg := PAnsiChar(P); // leave space for chunk size
  48220. inc(P,4);
  48221. n := ValuesCount;
  48222. case DataLayout of
  48223. wkVarInt32:
  48224. for i := 0 to ValuesCount-1 do begin
  48225. P := ToVarInt32(PI^[i],P);
  48226. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48227. n := i+1;
  48228. break; // avoid buffer overflow
  48229. end;
  48230. end;
  48231. wkVarUInt32:
  48232. for i := 0 to ValuesCount-1 do begin
  48233. P := ToVarUInt32(PI^[i],P);
  48234. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48235. n := i+1;
  48236. break; // avoid buffer overflow
  48237. end;
  48238. end;
  48239. wkOffsetU:
  48240. for i := 0 to ValuesCount-1 do begin
  48241. P := ToVarUInt32(PI^[i]-PI^[i-1],P);
  48242. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48243. n := i+1;
  48244. break; // avoid buffer overflow
  48245. end;
  48246. end;
  48247. wkOffsetI:
  48248. for i := 0 to ValuesCount-1 do begin
  48249. P := ToVarInt32(PI^[i]-PI^[i-1],P);
  48250. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48251. n := i+1;
  48252. break; // avoid buffer overflow
  48253. end;
  48254. end;
  48255. end;
  48256. PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32s
  48257. end;
  48258. wkSorted: begin
  48259. PBeg := PAnsiChar(P)+4; // leave space for chunk size
  48260. P := PByte(CleverStoreInteger(pointer(PI),PBeg,PEnd,ValuesCount,n));
  48261. if P=nil then
  48262. raise ESynException.CreateUTF8('%.WriteVarUInt32Array: data not sorted',[self]);
  48263. PInteger(PBeg-4)^ := PAnsiChar(P)-PBeg; // format: Isize+cleverStorage
  48264. end;
  48265. end;
  48266. inc(PtrUInt(PI),n*4);
  48267. fPos := PtrUInt(P)-PtrUInt(fBuf);
  48268. inc(fTotalWritten,PtrUInt(fPos-pos));
  48269. dec(ValuesCount,n);
  48270. if ValuesCount=0 then
  48271. break;
  48272. end;
  48273. fStream.Write(pointer(fBuf)^,fPos);
  48274. fPos := 0;
  48275. until false;
  48276. end;
  48277. procedure TFileBufferWriter.WriteVarUInt64DynArray(
  48278. const Values: TInt64DynArray; ValuesCount: integer; Offset: Boolean);
  48279. var n, i, pos: integer;
  48280. diff: Int64;
  48281. P, PEnd: PByte;
  48282. PI: PInt64Array;
  48283. PBeg: PAnsiChar;
  48284. begin
  48285. WriteVarUInt32(ValuesCount);
  48286. if ValuesCount=0 then
  48287. exit;
  48288. PI := pointer(Values);
  48289. pos := fPos;
  48290. if Offset then begin
  48291. PByteArray(fBuf)^[fPos] := 1;
  48292. fPos := PtrUInt(ToVarUInt64(PI^[0],@PByteArray(fBuf)^[fPos+1]))-PtrUInt(fBuf);
  48293. diff := PI^[1]-PI^[0];
  48294. inc(PtrUInt(PI),8);
  48295. dec(ValuesCount);
  48296. if ValuesCount=0 then begin
  48297. inc(fTotalWritten,PtrUInt(fPos-pos));
  48298. exit;
  48299. end;
  48300. if (diff>0) and (diff<MaxInt) then begin
  48301. for i := 1 to ValuesCount-1 do
  48302. if PI^[i]-PI^[i-1]<>diff then begin
  48303. diff := 0; // not always the same offset
  48304. break;
  48305. end;
  48306. end else
  48307. diff := 0;
  48308. fPos := PtrUInt(ToVarUInt32(diff,@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf);
  48309. if diff<>0 then begin
  48310. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48311. exit; // same offset for all items (fixed sized records) -> quit now
  48312. end;
  48313. end else begin
  48314. PByteArray(fBuf)^[fPos] := 0;
  48315. inc(fPos);
  48316. end;
  48317. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48318. repeat
  48319. P := @PByteArray(fBuf)^[fPos];
  48320. PEnd := @PByteArray(fBuf)^[fBufLen-32];
  48321. if PtrUInt(P)<PtrUInt(PEnd) then begin
  48322. pos := fPos;
  48323. PBeg := PAnsiChar(P); // leave space for chunk size
  48324. inc(P,4);
  48325. n := ValuesCount;
  48326. if Offset then
  48327. for i := 0 to ValuesCount-1 do begin
  48328. P := ToVarUInt32(PI^[i]-PI^[i-1],P); // store diffs
  48329. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48330. n := i+1;
  48331. break; // avoid buffer overflow
  48332. end;
  48333. end
  48334. else
  48335. for i := 0 to ValuesCount-1 do begin
  48336. P := ToVarUInt64(PI^[i],P);
  48337. if PtrUInt(P)>=PtrUInt(PEnd) then begin
  48338. n := i+1;
  48339. break; // avoid buffer overflow
  48340. end;
  48341. end;
  48342. PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32/64s
  48343. inc(PtrUInt(PI),n*8);
  48344. fPos := PtrUInt(P)-PtrUInt(fBuf);
  48345. inc(fTotalWritten,PtrUInt(fPos-Pos));
  48346. dec(ValuesCount,n);
  48347. if ValuesCount=0 then
  48348. break;
  48349. end;
  48350. fStream.Write(pointer(fBuf)^,fPos);
  48351. fPos := 0;
  48352. until false;
  48353. end;
  48354. function TFileBufferWriter.WriteDirectStart(maxSize: integer;
  48355. const TooBigMessage: RawUTF8): PByte;
  48356. begin
  48357. inc(maxSize,fPos);
  48358. if maxSize>fBufLen then begin
  48359. fTotalWritten := Flush;
  48360. if maxSize>fBufLen then begin
  48361. if maxSize>100 shl 20 then
  48362. raise ESynException.CreateUTF8('%.WriteDirectStart: too big % - '+
  48363. 'we allow up to 100 MB block',[self,TooBigMessage]);
  48364. fBufLen := maxSize+1024;
  48365. SetString(fBuf,nil,fBufLen);
  48366. end;
  48367. end;
  48368. result := @PByteArray(fBuf)^[fPos];
  48369. end;
  48370. procedure TFileBufferWriter.WriteDirectEnd(realSize: integer);
  48371. begin
  48372. if fPos+realSize>fBufLen then
  48373. raise ESynException.CreateUTF8(
  48374. '%.WriteDirectEnd: too big %',[self,realSize]);
  48375. inc(fPos,realSize);
  48376. inc(fTotalWritten,realSize);
  48377. end;
  48378. { TFileBufferReader }
  48379. procedure TFileBufferReader.Close;
  48380. begin
  48381. fMap.UnMap;
  48382. end;
  48383. procedure TFileBufferReader.ErrorInvalidContent;
  48384. begin
  48385. raise ESynException.Create('TFileBufferReader: invalid content');
  48386. end;
  48387. procedure TFileBufferReader.OpenFrom(aBuffer: pointer; aBufferSize: cardinal);
  48388. begin
  48389. fCurrentPos := 0;
  48390. fMap.Map(aBuffer,aBufferSize);
  48391. end;
  48392. function TFileBufferReader.OpenFrom(Stream: TStream): boolean;
  48393. begin
  48394. result := false;
  48395. if Stream=nil then
  48396. exit;
  48397. if Stream.InheritsFrom(TFileStream) then
  48398. Open(TFileStream(Stream).Handle) else
  48399. if Stream.InheritsFrom(TCustomMemoryStream) then
  48400. with TCustomMemoryStream(Stream) do
  48401. OpenFrom(Memory,Size) else
  48402. exit;
  48403. result := true
  48404. end;
  48405. procedure TFileBufferReader.Open(aFile: THandle);
  48406. begin
  48407. fCurrentPos := 0;
  48408. fMap.Map(aFile)
  48409. // if Windows failed to find a contiguous VA space -> fall back on direct read
  48410. end;
  48411. function TFileBufferReader.Read(Data: pointer; DataLen: integer): integer;
  48412. var len: integer;
  48413. begin
  48414. if DataLen>0 then
  48415. if fMap.fBuf<>nil then begin
  48416. // file up to 2 GB: use fast memory map
  48417. len := fMap.fBufSize-fCurrentPos;
  48418. if len>DataLen then
  48419. len := DataLen;
  48420. if Data<>nil then
  48421. MoveFast(fMap.fBuf[fCurrentPos],Data^,len);
  48422. inc(fCurrentPos,len);
  48423. result := len;
  48424. end else
  48425. // file bigger than 2 GB: slower but accurate reading from file
  48426. if Data=nil then begin
  48427. FileSeek(fMap.fFile,soFromCurrent,DataLen);
  48428. result := DataLen;
  48429. end else
  48430. result := FileRead(fMap.fFile,Data^,DataLen) else
  48431. // DataLen=0
  48432. result := 0;
  48433. end;
  48434. function TFileBufferReader.Read(out Text: RawByteString): integer;
  48435. begin
  48436. result := ReadVarUInt32;
  48437. if result=0 then
  48438. exit;
  48439. SetLength(Text,result);
  48440. if Read(pointer(Text),result)<>result then
  48441. ErrorInvalidContent;
  48442. end;
  48443. function TFileBufferReader.Read(out Text: RawUTF8): integer;
  48444. begin
  48445. result := ReadVarUInt32;
  48446. if result=0 then
  48447. exit;
  48448. SetLength(Text,result);
  48449. if Read(pointer(Text),result)<>result then
  48450. ErrorInvalidContent;
  48451. end;
  48452. function TFileBufferReader.ReadRawUTF8: RawUTF8;
  48453. begin
  48454. Read(result);
  48455. end;
  48456. procedure TFileBufferReader.ReadChunk(var P, PEnd: PByte; var BufTemp: RawByteString);
  48457. var len: integer;
  48458. begin // read Isize + buffer in P,PEnd
  48459. if (Read(@len,4)<>4) or (len<0) then
  48460. ErrorInvalidContent;
  48461. P := ReadPointer(len,BufTemp);
  48462. if P=nil then
  48463. ErrorInvalidContent;
  48464. PEnd := pointer(PtrUInt(P)+PtrUInt(len));
  48465. end;
  48466. function TFileBufferReader.CurrentMemory: pointer;
  48467. begin
  48468. if (fMap.fBuf=nil) or (fCurrentPos>=fMap.fBufSize) then
  48469. result := nil else
  48470. result := @fMap.fBuf[fCurrentPos];
  48471. end;
  48472. function TFileBufferReader.CurrentPosition: integer;
  48473. begin
  48474. if (fMap.fBuf=nil) or (fCurrentPos>=fMap.fBufSize) then
  48475. result := -1 else
  48476. result := fCurrentPos;
  48477. end;
  48478. function TFileBufferReader.ReadPointer(DataLen: PtrUInt;
  48479. var aTempData: RawByteString): pointer;
  48480. begin
  48481. if fMap.fBuf=nil then begin
  48482. // read from file
  48483. if DataLen>PtrUInt(Length(aTempData)) then begin
  48484. aTempData := ''; // so no move() call in SetLength() below
  48485. SetLength(aTempData,DataLen);
  48486. end;
  48487. if PtrUInt(FileRead(fMap.fFile,pointer(aTempData)^,DataLen))<>DataLen then
  48488. result := nil else // invalid content
  48489. result := pointer(aTempData);
  48490. end else
  48491. if DataLen+fCurrentPos>fMap.fBufSize then
  48492. // invalid request
  48493. result := nil else begin
  48494. // get pointer to data from current memory map (no data copy)
  48495. result := @fMap.fBuf[fCurrentPos];
  48496. inc(fCurrentPos,DataLen);
  48497. end;
  48498. end;
  48499. function TFileBufferReader.ReadStream(DataLen: PtrInt): TCustomMemoryStream;
  48500. var FileCurrentPos: Int64;
  48501. begin
  48502. if DataLen<0 then
  48503. DataLen := ReadVarUInt32;
  48504. if DataLen<>0 then
  48505. if fMap.fBuf=nil then begin
  48506. FileCurrentPos := FileSeek64(fMap.fFile,0,soFromCurrent);
  48507. if FileCurrentPos+DataLen>fMap.fFileSize then
  48508. // invalid content
  48509. result := nil else begin
  48510. // create a temporary memory map buffer stream
  48511. result := TSynMemoryStreamMapped.Create(fMap.fFile,DataLen,FileCurrentPos);
  48512. FileSeek64(fMap.fFile,DataLen,soFromCurrent);
  48513. end;
  48514. end else
  48515. if PtrUInt(DataLen)+fCurrentPos>fMap.fBufSize then
  48516. // invalid content
  48517. result := nil else begin
  48518. // get pointer to data from current memory map (no data copy)
  48519. result := TSynMemoryStream.Create(@fMap.fBuf[fCurrentPos],DataLen);
  48520. inc(fCurrentPos,DataLen);
  48521. end else
  48522. // DataLen=0 -> invalid content
  48523. result := nil;
  48524. end;
  48525. function TFileBufferReader.ReadByte: PtrUInt;
  48526. begin
  48527. if fMap.fBuf<>nil then
  48528. if fCurrentPos>=fMap.fBufSize then
  48529. // invalid request
  48530. result := 0 else begin
  48531. // read one byte from current memory map
  48532. result := ord(fMap.fBuf[fCurrentPos]);
  48533. inc(fCurrentPos);
  48534. end else begin
  48535. // read from file if >= 2 GB (slow, but works)
  48536. result := 0;
  48537. if FileRead(fMap.fFile,result,1)<>1 then
  48538. result := 0;
  48539. end;
  48540. end;
  48541. function TFileBufferReader.ReadCardinal: cardinal;
  48542. begin
  48543. if fMap.fBuf<>nil then
  48544. if fCurrentPos+3>=fMap.fBufSize then
  48545. // invalid request
  48546. result := 0 else begin
  48547. // read one byte from current memory map
  48548. result := PCardinal(fMap.fBuf+fCurrentPos)^;
  48549. inc(fCurrentPos,4);
  48550. end else begin
  48551. // read from file if >= 2 GB (slow, but works)
  48552. result := 0;
  48553. if FileRead(fMap.fFile,result,4)<>4 then
  48554. result := 0;
  48555. end;
  48556. end;
  48557. function TFileBufferReader.ReadVarUInt32: PtrUInt;
  48558. var c, n: PtrUInt;
  48559. begin
  48560. result := ReadByte;
  48561. if result>$7f then begin
  48562. n := 0;
  48563. result := result and $7F;
  48564. repeat
  48565. c := ReadByte;
  48566. inc(n,7);
  48567. if c<=$7f then break;
  48568. result := result or ((c and $7f) shl n);
  48569. until false;
  48570. result := result or (c shl n);
  48571. end;
  48572. end;
  48573. function TFileBufferReader.ReadVarInt32: PtrInt;
  48574. begin
  48575. result := ReadVarUInt32;
  48576. if result and 1<>0 then
  48577. // 1->1, 3->2..
  48578. result := result shr 1+1 else
  48579. // 0->0, 2->-1, 4->-2..
  48580. result := -(result shr 1);
  48581. end;
  48582. function TFileBufferReader.ReadVarUInt64: QWord;
  48583. var c, n: PtrUInt;
  48584. begin
  48585. result := ReadByte;
  48586. if result>$7f then begin
  48587. n := 0;
  48588. result := result and $7F;
  48589. repeat
  48590. c := ReadByte;
  48591. inc(n,7);
  48592. if c<=$7f then break;
  48593. result := result or (QWord(c and $7f) shl n);
  48594. until false;
  48595. result := result or (QWord(c) shl n);
  48596. end;
  48597. end;
  48598. function TFileBufferReader.ReadVarInt64: Int64;
  48599. begin
  48600. result := ReadVarUInt64;
  48601. if result and 1<>0 then
  48602. // 1->1, 3->2..
  48603. result := result shr 1+1 else
  48604. // 0->0, 2->-1, 4->-2..
  48605. result := -(result shr 1);
  48606. end;
  48607. function CleverReadInteger(p, pEnd: PAnsiChar; V: PInteger): PtrUInt;
  48608. // Clever = decode Values[i+1]-Values[i] storage (with special diff=1 count)
  48609. var i, n: PtrUInt;
  48610. begin
  48611. result := PtrUInt(V);
  48612. i := PInteger(p)^; inc(p,4); // Integer: firstValue
  48613. V^ := i; inc(V);
  48614. if PtrUInt(p)<PtrUInt(pEnd) then
  48615. repeat
  48616. case p^ of
  48617. #0: begin // B:0 W:difference with previous
  48618. inc(i,PWord(p+1)^); inc(p,3);
  48619. V^ := i; inc(V);
  48620. if PtrUInt(p)<PtrUInt(pEnd) then continue else break;
  48621. end;
  48622. #254: begin // B:254 W:byOne
  48623. for n := 1 to PWord(p+1)^ do begin
  48624. inc(i);
  48625. V^ := i; inc(V);
  48626. end;
  48627. inc(p,3);
  48628. if PtrUInt(p)<PtrUInt(pEnd) then continue else break;
  48629. end;
  48630. #255: begin // B:255 B:byOne
  48631. for n := 1 to pByte(p+1)^ do begin
  48632. inc(i);
  48633. V^ := i; inc(V);
  48634. end;
  48635. inc(p,2);
  48636. if PtrUInt(p)<PtrUInt(pEnd) then continue else break;
  48637. end else begin // B:1..253 = difference with previous
  48638. inc(i,ord(p^)); inc(p);
  48639. V^ := i; inc(V);
  48640. if PtrUInt(p)<PtrUInt(pEnd) then continue else break;
  48641. end;
  48642. end; // case p^ of
  48643. until false;
  48644. result := (PtrUInt(V)-result) shr 2; // returns count of stored integer
  48645. end;
  48646. function TFileBufferReader.ReadVarUInt32Array(var Values: TIntegerDynArray): PtrInt;
  48647. var count, n, i, diff: integer;
  48648. DataLayout: TFileBufferWriterKind;
  48649. P, PEnd: PByte;
  48650. PI: PInteger;
  48651. PIA: PIntegerArray absolute PI;
  48652. BufTemp: RawByteString;
  48653. begin
  48654. result := ReadVarUInt32;
  48655. if result=0 then
  48656. exit;
  48657. DataLayout := TFileBufferWriterKind(ReadByte);
  48658. if DataLayout=wkFakeMarker then begin
  48659. result := -result;
  48660. exit;
  48661. end;
  48662. count := result;
  48663. if count>length(Values) then // only set length is not big enough
  48664. SetLength(Values,count);
  48665. PI := pointer(Values);
  48666. if DataLayout in [wkOffsetU, wkOffsetI] then begin
  48667. PI^ := ReadVarUInt32;
  48668. dec(count);
  48669. if count=0 then
  48670. exit;
  48671. diff := ReadVarUInt32;
  48672. if diff<>0 then begin
  48673. for i := 0 to count-1 do
  48674. PIA^[i+1] := PIA^[i]+diff;
  48675. exit;
  48676. end;
  48677. end;
  48678. if DataLayout=wkUint32 then
  48679. Read(@Values[0],count*4) else begin
  48680. repeat
  48681. ReadChunk(P,PEnd,BufTemp); // raise ErrorInvalidContent on error
  48682. case DataLayout of
  48683. wkVarInt32:
  48684. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48685. PI^ := FromVarInt32(P);
  48686. dec(count);
  48687. inc(PI);
  48688. end;
  48689. wkVarUInt32:
  48690. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48691. PI^ := FromVarUInt32(P);
  48692. dec(count);
  48693. inc(PI);
  48694. end;
  48695. wkSorted: begin
  48696. n := CleverReadInteger(pointer(P),pointer(PEnd),PI);
  48697. dec(count,n);
  48698. inc(PtrUInt(PI),n*4);
  48699. end;
  48700. wkOffsetU: begin
  48701. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48702. PIA^[1] := PIA^[0]+integer(FromVarUInt32(P));
  48703. dec(count);
  48704. inc(PI);
  48705. end;
  48706. if count<=0 then
  48707. inc(PI); // make sure PI=@Values[result]
  48708. end;
  48709. wkOffsetI: begin
  48710. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48711. PIA^[1] := PIA^[0]+FromVarInt32(P);
  48712. dec(count);
  48713. inc(PI);
  48714. end;
  48715. if count<=0 then
  48716. inc(PI); // make sure PI=@Values[result]
  48717. end;
  48718. else
  48719. ErrorInvalidContent;
  48720. end;
  48721. until count<=0;
  48722. if PI<>@Values[result] then
  48723. ErrorInvalidContent;
  48724. end;
  48725. end;
  48726. function TFileBufferReader.ReadVarUInt64Array(var Values: TInt64DynArray): PtrInt;
  48727. var count, diff, i: integer;
  48728. Offset: boolean;
  48729. P, PEnd: PByte;
  48730. PI: PInt64;
  48731. PIA: PInt64Array absolute PI;
  48732. BufTemp: RawByteString;
  48733. begin
  48734. result := ReadVarUInt32;
  48735. if result=0 then
  48736. exit;
  48737. count := result;
  48738. if count>length(Values) then // only set length is not big enough
  48739. SetLength(Values,count);
  48740. Offset := boolean(ReadByte);
  48741. PI := pointer(Values);
  48742. if Offset then begin
  48743. PI^ := ReadVarUInt64; // read first value
  48744. dec(count);
  48745. diff := ReadVarUInt32;
  48746. if diff=0 then begin
  48747. // read all offsets, and compute (not fixed sized records)
  48748. repeat
  48749. ReadChunk(P,PEnd,BufTemp); // raise ErrorInvalidContent on error
  48750. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48751. PIA^[1] := PIA^[0]+integer(FromVarUInt32(P));
  48752. dec(count);
  48753. inc(PI);
  48754. end;
  48755. until count<=0;
  48756. {$ifopt C+} inc(PI); {$endif} // to make assert() below work
  48757. end else begin
  48758. // same offset for all items (fixed sized records)
  48759. for i := 0 to count-1 do
  48760. PIA^[i+1] := PIA^[i]+diff;
  48761. {$ifopt C+} inc(PI,count+1); count := 0; {$endif} // for assert() below
  48762. end;
  48763. end else
  48764. repeat
  48765. ReadChunk(P,PEnd,BufTemp); // raise ErrorInvalidContent on error
  48766. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48767. PI^ := FromVarUInt64(P);
  48768. dec(count);
  48769. inc(PI);
  48770. end;
  48771. until count<=0;
  48772. assert((count=0)and(PI=@Values[result]));
  48773. end;
  48774. function TFileBufferReader.ReadRawUTF8List(List: TRawUTF8List): boolean;
  48775. var i: integer;
  48776. StoreObjectsAsVarUInt32: Boolean;
  48777. begin
  48778. if (fMap.fBuf<>nil) and (List<>nil) then
  48779. with List do begin
  48780. BeginUpdate;
  48781. try
  48782. Capacity := 0; // finalize both fObjects[] and fList[]
  48783. fCount := ReadVarRawUTF8DynArray(List.fList);
  48784. result := true;
  48785. if fCount=0 then
  48786. exit;
  48787. Read(@StoreObjectsAsVarUInt32,1);
  48788. if StoreObjectsAsVarUInt32 then begin
  48789. fObjectsOwned := false; // Int32 here, not instances
  48790. SetLength(fObjects,Capacity);
  48791. for i := 0 to fCount-1 do
  48792. fObjects[i] := TObject(ReadVarUInt32);
  48793. end;
  48794. finally
  48795. EndUpdate;
  48796. end;
  48797. end else
  48798. result := false;
  48799. end;
  48800. function TFileBufferReader.ReadVarRawUTF8DynArray(var Values: TRawUTF8DynArray): PtrInt;
  48801. var count, len, fixedsize: integer;
  48802. P, PEnd: PByte;
  48803. PI: PRawUTF8;
  48804. BufTemp: RawByteString;
  48805. begin
  48806. result := ReadVarUInt32;
  48807. if result=0 then
  48808. exit;
  48809. count := result;
  48810. if count>length(Values) then // only set length is not big enough
  48811. SetLength(Values,count);
  48812. PI := pointer(Values);
  48813. fixedsize := ReadVarUInt32;
  48814. repeat
  48815. ReadChunk(P,PEnd,BufTemp); // raise ErrorInvalidContent on error
  48816. if fixedsize=0 then
  48817. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48818. len := FromVarUInt32(P);
  48819. if len>0 then begin
  48820. SetString(PI^,PAnsiChar(P),len);
  48821. inc(P,len);
  48822. end else
  48823. if PI^<>'' then
  48824. PI^ := '';
  48825. dec(count);
  48826. inc(PI);
  48827. end else
  48828. // fixed size strings case
  48829. while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin
  48830. SetString(PI^,PAnsiChar(P),fixedsize);
  48831. inc(P,fixedsize);
  48832. dec(count);
  48833. inc(PI);
  48834. end;
  48835. until count<=0;
  48836. if PI<>@Values[result] then
  48837. ErrorInvalidContent;
  48838. end;
  48839. {$ifndef CPU64}
  48840. function TFileBufferReader.Seek(Offset: Int64): boolean;
  48841. begin
  48842. if (Offset<0) or (Offset>fMap.fFileSize) then
  48843. result := False else
  48844. if fMap.fBuf=nil then
  48845. result := FileSeek64(fMap.fFile,Offset,soFromBeginning)=Offset else begin
  48846. fCurrentPos := Int64Rec(Offset).Lo;
  48847. result := true;
  48848. end;
  48849. end;
  48850. {$endif CPU64}
  48851. function TFileBufferReader.Seek(Offset: PtrInt): boolean;
  48852. begin
  48853. // we don't need to handle fMap=0 here
  48854. if fMap.fBuf=nil then
  48855. Result := FileSeek(fMap.fFile,Offset,0)=Offset else
  48856. if (fMap.fBuf<>nil) and (PtrUInt(Offset)<PPtrUInt(@fMap.fFileSize)^) then begin
  48857. fCurrentPos := Offset;
  48858. result := true;
  48859. end else
  48860. result := false;
  48861. end;
  48862. { TSynTable }
  48863. {$ifndef SORTCOMPAREMETHOD}
  48864. function SortU8(P1,P2: PUTF8Char): PtrInt;
  48865. begin
  48866. if P1<>P2 then
  48867. if P1<>nil then
  48868. if P2<>nil then begin
  48869. result := PByte(P1)^-PByte(P2)^;
  48870. exit;
  48871. end else
  48872. result := 1 else // P2=nil
  48873. result := -1 else // P1=nil
  48874. result := 0; // P1=P2
  48875. end;
  48876. function SortU16(P1,P2: PUTF8Char): PtrInt;
  48877. begin
  48878. if P1<>P2 then
  48879. if P1<>nil then
  48880. if P2<>nil then begin
  48881. result := PWord(P1)^-PWord(P2)^;
  48882. exit;
  48883. end else
  48884. result := 1 else // P2=nil
  48885. result := -1 else // P1=nil
  48886. result := 0; // P1=P2
  48887. end;
  48888. function SortI32(P1,P2: PUTF8Char): PtrInt;
  48889. begin
  48890. if P1<>P2 then
  48891. if P1<>nil then
  48892. if P2<>nil then begin
  48893. result := PInteger(P1)^-PInteger(P2)^;
  48894. exit;
  48895. end else
  48896. result := 1 else // P2=nil
  48897. result := -1 else // P1=nil
  48898. result := 0; // P1=P2
  48899. end;
  48900. function SortI64(P1,P2: PUTF8Char): PtrInt;
  48901. var V: Int64;
  48902. begin
  48903. if P1<>P2 then
  48904. if P1<>nil then
  48905. if P2<>nil then begin
  48906. V := PInt64(P1)^-PInt64(P2)^;
  48907. if V<0 then
  48908. result := -1 else
  48909. if V>0 then
  48910. result := 1 else
  48911. result := 0;
  48912. end else
  48913. result := 1 else // P2=nil
  48914. result := -1 else // P1=nil
  48915. result := 0; // P1=P2
  48916. end;
  48917. function SortDouble(P1,P2: PUTF8Char): PtrInt;
  48918. var V: Double;
  48919. begin
  48920. if P1<>P2 then
  48921. if P1<>nil then
  48922. if P2<>nil then begin
  48923. V := PDouble(P1)^-PDouble(P2)^;
  48924. if V<0 then
  48925. result := -1 else
  48926. if V=0 then
  48927. result := 0 else
  48928. result := 1;
  48929. end else
  48930. result := 1 else // P2=nil
  48931. result := -1 else // P1=nil
  48932. result := 0; // P1=P2
  48933. end;
  48934. function SortU24(P1,P2: PUTF8Char): PtrInt;
  48935. begin
  48936. if P1<>P2 then
  48937. if P1<>nil then
  48938. if P2<>nil then begin
  48939. result := PtrInt(PWord(P1)^)+PtrInt(P1[2])shl 16
  48940. -PtrInt(PWord(P2)^)-PtrInt(P2[2]) shl 16;
  48941. exit;
  48942. end else
  48943. result := 1 else // P2=nil
  48944. result := -1 else // P1=nil
  48945. result := 0; // P1=P2
  48946. end;
  48947. function SortVarUInt32(P1,P2: PUTF8Char): PtrInt;
  48948. begin
  48949. if P1<>P2 then
  48950. if P1<>nil then
  48951. if P2<>nil then begin
  48952. result := FromVarUInt32(PByte(P1))-FromVarUInt32(PByte(P2));
  48953. exit;
  48954. end else
  48955. result := 1 else // P2=nil
  48956. result := -1 else // P1=nil
  48957. result := 0; // P1=P2
  48958. end;
  48959. function SortVarInt32(P1,P2: PUTF8Char): PtrInt;
  48960. begin
  48961. if P1<>P2 then
  48962. if P1<>nil then
  48963. if P2<>nil then begin
  48964. result := FromVarInt32(PByte(P1))-FromVarInt32(PByte(P2));
  48965. exit;
  48966. end else
  48967. result := 1 else // P2=nil
  48968. result := -1 else // P1=nil
  48969. result := 0; // P1=P2
  48970. end;
  48971. function SortVarUInt64(P1,P2: PUTF8Char): PtrInt;
  48972. begin
  48973. if P1<>P2 then
  48974. if P1<>nil then
  48975. if P2<>nil then
  48976. result := FromVarUInt64(PByte(P1))-FromVarUInt64(PByte(P2)) else
  48977. result := 1 else // P2=nil
  48978. result := -1 else // P1=nil
  48979. result := 0; // P1=P2
  48980. end;
  48981. function SortVarInt64(P1,P2: PUTF8Char): PtrInt;
  48982. begin
  48983. if P1<>P2 then
  48984. if P1<>nil then
  48985. if P2<>nil then
  48986. result := FromVarInt64(PByte(P1))-FromVarInt64(PByte(P2)) else
  48987. result := 1 else // P2=nil
  48988. result := -1 else // P1=nil
  48989. result := 0; // P1=P2
  48990. end;
  48991. function SortStr(P1,P2: PUTF8Char): PtrInt;
  48992. var L1, L2, L, i: PtrInt;
  48993. PB1, PB2: PByte;
  48994. begin
  48995. if P1<>P2 then
  48996. if P1<>nil then
  48997. if P2<>nil then begin
  48998. if PtrInt(P1^)<=$7F then begin
  48999. L1 := PtrInt(P1^);
  49000. inc(P1);
  49001. end else begin
  49002. PB1 := pointer(P1);
  49003. L1 := FromVarUInt32High(PB1);
  49004. P1 := pointer(PB1);
  49005. end;
  49006. if PtrInt(P2^)<=$7F then begin
  49007. L2 := PtrInt(P2^);
  49008. inc(P2);
  49009. end else begin
  49010. PB2 := pointer(P2);
  49011. L2 := FromVarUInt32High(PB2);
  49012. P2 := pointer(PB2);
  49013. end;
  49014. L := L1;
  49015. if L2>L then
  49016. L := L2;
  49017. for i := 0 to L-1 do begin
  49018. result := PtrInt(P1[i])-PtrInt(P2[i]);
  49019. if Result<>0 then
  49020. exit;
  49021. end;
  49022. result := L1-L2;
  49023. end else
  49024. result := 1 else // P2=nil
  49025. result := -1 else // P1=nil
  49026. result := 0; // P1=P2
  49027. end;
  49028. function SortIStr(P1,P2: PUTF8Char): PtrInt;
  49029. var L1, L2, L, i: PtrInt;
  49030. PB1, PB2: PByte;
  49031. begin
  49032. if P1<>P2 then
  49033. if P1<>nil then
  49034. if P2<>nil then begin
  49035. if PtrInt(P1^)<=$7F then begin
  49036. L1 := PtrInt(P1^);
  49037. inc(P1);
  49038. end else begin
  49039. PB1 := pointer(P1);
  49040. L1 := FromVarUInt32High(PB1);
  49041. P1 := pointer(PB1);
  49042. end;
  49043. if PtrInt(P2^)<=$7F then begin
  49044. L2 := PtrInt(P2^);
  49045. inc(P2);
  49046. end else begin
  49047. PB2 := pointer(P2);
  49048. L2 := FromVarUInt32High(PB2);
  49049. P2 := pointer(PB2);
  49050. end;
  49051. if L2>L1 then
  49052. L := L2 else
  49053. L := L1;
  49054. for i := 0 to L-1 do // NormToUpperAnsi7 works for both WinAnsi & UTF-8
  49055. if NormToUpperAnsi7[P1[i]]<>NormToUpperAnsi7[P2[i]] then begin
  49056. result := PtrInt(P1[i])-PtrInt(P2[i]);
  49057. exit;
  49058. end;
  49059. result := L1-L2;
  49060. end else
  49061. result := 1 else // P2=nil
  49062. result := -1 else // P1=nil
  49063. result := 0; // P1=P2
  49064. end;
  49065. const
  49066. FIELD_SORT: array[TSynTableFieldType] of TUTF8Compare = (
  49067. nil, // tftUnknown,
  49068. SortU8, SortU8, SortU16, SortU24, SortI32, SortI64,
  49069. // tftBoolean,tftUInt8,tftUInt16,tftUInt24,tftInt32,tftInt64,
  49070. SortI64, SortDouble, SortVarUInt32,SortVarInt32,SortVarUInt64,
  49071. // tftCurrency,tftDouble, tftVarUInt32, tftVarInt32,tftVarUInt64,
  49072. SortStr, SortStr, SortStr, nil, SortVarInt64);
  49073. // tftWinAnsi,tftUTF8, tftBlobInternal,tftBlobExternal,tftVarInt64);
  49074. {$endif SORTCOMPAREMETHOD}
  49075. const
  49076. FIELD_FIXEDSIZE: array[TSynTableFieldType] of Integer = (
  49077. 0, // tftUnknown,
  49078. 1, 1, 2, 3, 4, 8, 8, 8,
  49079. // tftBoolean, tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64, tftCurrency, tftDouble
  49080. -1, -1, -1, // tftVarUInt32, tftVarInt32, tftVarUInt64 have -1 as size
  49081. -2, -2, -2, // tftWinAnsi, tftUTF8, tftBlobInternal have -2 as size
  49082. -3, // tftBlobExternal has -3 as size
  49083. -1); //tftVarInt64
  49084. // note: boolean is not in this set, because it can be 'true' or 'false'
  49085. FIELD_INTEGER: TSynTableFieldTypes = [
  49086. tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64,
  49087. tftVarUInt32, tftVarInt32, tftVarUInt64, tftVarInt64];
  49088. function PropNameValid(P: PUTF8Char): boolean;
  49089. begin
  49090. result := false;
  49091. if (P=nil) or not (P^ in ['a'..'z','A'..'Z','_']) then
  49092. exit; // first char must be alphabetical
  49093. inc(P);
  49094. while P^<>#0 do
  49095. if not (ord(P^) in IsIdentifier) then
  49096. exit else // following chars can be alphanumerical
  49097. inc(P);
  49098. result := true;
  49099. end;
  49100. function JsonPropNameValid(P: PUTF8Char): boolean;
  49101. {$ifdef HASINLINE}
  49102. begin
  49103. if (P<>nil) and (ord(P^) in IsJsonIdentifierFirstChar) then begin
  49104. repeat
  49105. inc(P);
  49106. until not(ord(P^) in IsJsonIdentifier);
  49107. if P^=#0 then begin
  49108. result := true;
  49109. exit;
  49110. end else begin
  49111. result := false;
  49112. exit;
  49113. end;
  49114. end else
  49115. result := false;
  49116. end;
  49117. {$else}
  49118. asm
  49119. test eax,eax
  49120. jz @z
  49121. movzx edx,byte ptr [eax]
  49122. bt [offset @f],edx
  49123. mov ecx,offset @c
  49124. jb @2
  49125. @z: xor eax,eax
  49126. ret
  49127. @f: dd 0,$03FF0010,$87FFFFFE,$07FFFFFE,0,0,0,0 // IsJsonIdentifierFirstChar
  49128. @c: dd 0,$03FF4000,$AFFFFFFE,$07FFFFFE,0,0,0,0 // IsJsonIdentifier
  49129. @s: mov dl,[eax]
  49130. bt [ecx],edx
  49131. jnb @1
  49132. @2: mov dl,[eax+1]
  49133. bt [ecx],edx
  49134. jnb @1
  49135. mov dl,[eax+2]
  49136. bt [ecx],edx
  49137. jnb @1
  49138. mov dl,[eax+3]
  49139. bt [ecx],edx
  49140. lea eax,[eax+4]
  49141. jb @s
  49142. @1: test dl,dl
  49143. setz al
  49144. end;
  49145. {$endif}
  49146. function TSynTable.AddField(const aName: RawUTF8;
  49147. aType: TSynTableFieldType; aOptions: TSynTableFieldOptions): TSynTableFieldProperties;
  49148. var aSize: Integer;
  49149. begin
  49150. result := nil;
  49151. aSize := FIELD_FIXEDSIZE[aType];
  49152. if (self=nil) or (aSize=0) or IsRowID(pointer(aName)) or
  49153. not PropNameValid(pointer(aName)) or (GetFieldFromName(aName)<>nil) then
  49154. exit;
  49155. result := TSynTableFieldProperties.Create;
  49156. if fAddedField=nil then
  49157. fAddedField := TList.Create;
  49158. fAddedField.Add(result);
  49159. result.Name := aName;
  49160. result.FieldType := aType;
  49161. if tfoUnique in aOptions then
  49162. Include(aOptions,tfoIndex); // create an index for faster Unique field
  49163. if aSize=-3 then // external field has no index available
  49164. aOptions := aOptions-[tfoIndex,tfoUnique];
  49165. result.Options := aOptions;
  49166. if aSize>0 then begin
  49167. // fixed-size field should be inserted left-side of the stream
  49168. if (tfoIndex in aOptions) or (aSize and 3=0) then begin
  49169. // indexed field or size is alignment friendly: put left side
  49170. if not ((tfoIndex in aOptions) and (aSize and 3=0)) then
  49171. // indexed+aligned field -> set first, otherwise at variable or not indexed
  49172. while result.FieldNumber<fField.Count do
  49173. with TSynTableFieldProperties(fField.List[result.FieldNumber]) do
  49174. if (Offset<0) or not (tfoIndex in Options) then
  49175. break else
  49176. Inc(result.FieldNumber);
  49177. end else
  49178. // not indexed field: insert after previous fixed-sized fields
  49179. if fFieldVariableIndex>=0 then
  49180. result.FieldNumber := fFieldVariableIndex else
  49181. result.FieldNumber := fField.Count;
  49182. fField.Insert(result.FieldNumber,result);
  49183. end else begin
  49184. if (tfoIndex in aOptions) and (fFieldVariableIndex>=0) then begin
  49185. // indexed field should be added left side (faster access for sort)
  49186. result.FieldNumber := fFieldVariableIndex;
  49187. while result.FieldNumber<fField.Count do
  49188. with TSynTableFieldProperties(fField.List[result.FieldNumber]) do
  49189. if not (tfoIndex in Options) then
  49190. break else
  49191. Inc(result.FieldNumber);
  49192. fField.Insert(result.FieldNumber,result);
  49193. end else
  49194. // not indexed field: just add at the end of the field list
  49195. result.FieldNumber := fField.Add(result);
  49196. end;
  49197. if tfoUnique in aOptions then begin
  49198. fFieldHasUniqueIndexes := true;
  49199. result.AddFilterOrValidate(TSynValidateTableUniqueField.Create);
  49200. end;
  49201. AfterFieldModif; // set Offset,FieldNumber,FieldSize fFieldVariableIndex/Offset
  49202. end;
  49203. procedure TSynTable.UpdateFieldData(RecordBuffer: PUTF8Char; RecordBufferLen,
  49204. FieldIndex: integer; var result: TSBFString; const NewFieldData: TSBFString='');
  49205. var NewSize, DestOffset, OldSize: integer;
  49206. F: TSynTableFieldProperties;
  49207. NewData, Dest: PAnsiChar;
  49208. begin
  49209. if (self<>nil) and ((RecordBuffer=nil) or (RecordBufferLen=0)) then begin
  49210. // no data yet -> use default
  49211. RecordBuffer := pointer(fDefaultRecordData);
  49212. RecordBufferLen := fDefaultRecordLength;
  49213. end;
  49214. if RecordBuffer=pointer(result) then
  49215. // update content code below will fail -> please correct calling code
  49216. raise ETableDataException.CreateUTF8('In-place call of %.UpdateFieldData',[self]);
  49217. if (self=nil) or (cardinal(FieldIndex)>=cardinal(fField.Count)) then begin
  49218. SetString(result,PAnsiChar(RecordBuffer),RecordBufferLen);
  49219. exit;
  49220. end;
  49221. F := TSynTableFieldProperties(fField.List[FieldIndex]);
  49222. NewSize := length(NewFieldData);
  49223. if NewSize=0 then begin
  49224. // no NewFieldData specified -> use default field data to be inserted
  49225. NewData := pointer(F.fDefaultFieldData);
  49226. NewSize := F.fDefaultFieldLength;
  49227. end else
  49228. NewData := pointer(NewFieldData);
  49229. Dest := GetData(RecordBuffer,F);
  49230. DestOffset := Dest-RecordBuffer;
  49231. // update content
  49232. OldSize := F.GetLength(Dest);
  49233. dec(RecordBufferLen,OldSize);
  49234. SetLength(Result,RecordBufferLen+NewSize);
  49235. MoveFast(RecordBuffer^,PByteArray(result)[0],DestOffset);
  49236. MoveFast(NewData^,PByteArray(result)[DestOffset],NewSize);
  49237. MoveFast(Dest[OldSize],PByteArray(result)[DestOffset+NewSize],RecordBufferLen-DestOffset);
  49238. end;
  49239. constructor TSynTable.Create(const aTableName: RawUTF8);
  49240. begin
  49241. if not PropNameValid(pointer(aTableName)) then
  49242. raise ETableDataException.CreateUTF8('Invalid %.Create(%)',[self,aTableName]);
  49243. fTableName := aTableName;
  49244. fField := TObjectList.Create;
  49245. fFieldVariableIndex := -1;
  49246. end;
  49247. procedure TSynTable.LoadFrom(var RD: TFileBufferReader);
  49248. var n, i: integer;
  49249. aTableName: RawUTF8;
  49250. begin
  49251. fField.Clear;
  49252. RD.Read(aTableName);
  49253. if not PropNameValid(pointer(aTableName)) then
  49254. RD.ErrorInvalidContent;
  49255. fTableName := aTableName;
  49256. n := RD.ReadVarUInt32;
  49257. if cardinal(n)>=MAX_SQLFIELDS then
  49258. RD.ErrorInvalidContent;
  49259. for i := 0 to n-1 do
  49260. fField.Add(TSynTableFieldProperties.CreateFrom(RD));
  49261. AfterFieldModif;
  49262. end;
  49263. destructor TSynTable.Destroy;
  49264. begin
  49265. fField.Free;
  49266. fAddedField.Free;
  49267. inherited;
  49268. end;
  49269. function TSynTable.GetFieldCount: integer;
  49270. begin
  49271. if self=nil then
  49272. result := 0 else
  49273. result := fField.Count;
  49274. end;
  49275. function TSynTable.GetFieldFromName(const aName: RawUTF8): TSynTableFieldProperties;
  49276. var i: integer;
  49277. begin
  49278. if self<>nil then
  49279. for i := 0 to fField.Count-1 do begin
  49280. result := TSynTableFieldProperties(fField.List[i]);
  49281. if IdemPropNameU(result.Name,aName) then
  49282. exit;
  49283. end;
  49284. result := nil;
  49285. end;
  49286. function TSynTable.GetFieldIndexFromName(const aName: RawUTF8): integer;
  49287. begin
  49288. if self<>nil then
  49289. for result := 0 to fField.Count-1 do
  49290. if IdemPropNameU(TSynTableFieldProperties(fField.List[result]).Name,aName) then
  49291. exit;
  49292. result := -1;
  49293. end;
  49294. function TSynTable.GetFieldIndexFromShortName(const aName: ShortString): integer;
  49295. begin
  49296. if self<>nil then
  49297. for result := 0 to fField.Count-1 do
  49298. with TSynTableFieldProperties(fField.List[result]) do
  49299. if IdemPropName(aName,pointer(Name),length(Name)) then
  49300. exit;
  49301. result := -1;
  49302. end;
  49303. function TSynTable.GetFieldType(Index: integer): TSynTableFieldProperties;
  49304. begin
  49305. if (self=nil) or (cardinal(Index)>=cardinal(fField.Count)) then
  49306. result := nil else // avoid GPF
  49307. result := fField.List[Index];
  49308. end;
  49309. {$ifndef DELPHI5OROLDER}
  49310. function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  49311. const Fields: TSQLFieldBits): TJSONWriter;
  49312. begin
  49313. result := CreateJSONWriter(JSON,Expand,withID,FieldBitsToIndex(Fields,fField.Count));
  49314. end;
  49315. function TSynTable.CreateJSONWriter(JSON: TStream; Expand, withID: boolean;
  49316. const Fields: TSQLFieldIndexDynArray): TJSONWriter;
  49317. var i,nf,n: integer;
  49318. begin
  49319. if (self=nil) or ((Fields=nil) and not withID) then begin
  49320. result := nil; // no data to retrieve
  49321. exit;
  49322. end;
  49323. result := TJSONWriter.Create(JSON,Expand,withID,Fields);
  49324. // set col names
  49325. if withID then
  49326. n := 1 else
  49327. n := 0;
  49328. nf := length(Fields);
  49329. SetLength(result.ColNames,nf+n);
  49330. if withID then
  49331. result.ColNames[0] := 'ID';
  49332. for i := 0 to nf-1 do
  49333. result.ColNames[i+n] := TSynTableFieldProperties(fField.List[Fields[i]]).Name;
  49334. result.AddColumns; // write or init field names for appropriate JSON Expand
  49335. end;
  49336. procedure TSynTable.GetJSONValues(aID: integer; RecordBuffer: PUTF8Char;
  49337. W: TJSONWriter);
  49338. var i,n: integer;
  49339. buf: array[0..MAX_SQLFIELDS-1] of PUTF8Char;
  49340. begin
  49341. if (self=nil) or (RecordBuffer=nil) or (W=nil) then
  49342. exit; // avoid GPF
  49343. if W.Expand then begin
  49344. W.Add('{');
  49345. if W.WithID then
  49346. W.AddString(W.ColNames[0]);
  49347. end;
  49348. if W.WithID then begin
  49349. W.Add(aID);
  49350. W.Add(',');
  49351. n := 1;
  49352. end else
  49353. n := 0;
  49354. for i := 0 to fField.Count-1 do begin
  49355. buf[i] := RecordBuffer;
  49356. inc(RecordBuffer,TSynTableFieldProperties(fField.List[i]).GetLength(RecordBuffer));
  49357. end;
  49358. for i := 0 to length(W.Fields)-1 do begin
  49359. if W.Expand then begin
  49360. W.AddString(W.ColNames[n]); // '"'+ColNames[]+'":'
  49361. inc(n);
  49362. end;
  49363. TSynTableFieldProperties(fField.List[W.Fields[i]]).GetJSON(buf[i],W);
  49364. W.Add(',');
  49365. end;
  49366. W.CancelLastComma; // cancel last ','
  49367. if W.Expand then
  49368. W.Add('}');
  49369. end;
  49370. function TSynTable.IterateJSONValues(Sender: TObject; Opaque: pointer;
  49371. ID: integer; Data: pointer; DataLen: integer): boolean;
  49372. var Statement: TSynTableStatement absolute Opaque;
  49373. F: TSynTableFieldProperties;
  49374. nWhere,fIndex: cardinal;
  49375. begin // note: we should have handled -2 (=COUNT) case already
  49376. nWhere := length(Statement.Where);
  49377. if (self=nil) or (Statement=nil) or (Data=nil) or
  49378. (Statement.Select=nil) or (nWhere>1) or
  49379. ((nWhere=1)and(Statement.Where[0].ValueSBF='')) then begin
  49380. result := false;
  49381. exit;
  49382. end;
  49383. result := true;
  49384. if nWhere=1 then begin // Where=nil -> all rows
  49385. fIndex := Statement.Where[0].Field;
  49386. if fIndex=SYNTABLESTATEMENTWHEREID then begin
  49387. if ID<>Statement.Where[0].ValueInteger then
  49388. exit;
  49389. end else begin
  49390. dec(fIndex); // 0 is ID, 1 for field # 0, 2 for field #1, and so on...
  49391. if fIndex<cardinal(fField.Count) then begin
  49392. F := TSynTableFieldProperties(fField.List[fIndex]);
  49393. if F.SortCompare(GetData(Data,F),pointer(Statement.Where[0].ValueSBF))<>0 then
  49394. exit;
  49395. end;
  49396. end;
  49397. end;
  49398. GetJSONValues(ID,Data,Statement.Writer);
  49399. end;
  49400. {$endif DELPHI5OROLDER}
  49401. function TSynTable.GetData(RecordBuffer: PUTF8Char; Field: TSynTableFieldProperties): pointer;
  49402. var i: integer;
  49403. PB: PByte;
  49404. begin
  49405. if Field.Offset>=0 then
  49406. result := RecordBuffer+Field.Offset else begin
  49407. result := RecordBuffer+fFieldVariableOffset;
  49408. for i := fFieldVariableIndex to Field.FieldNumber-1 do
  49409. if i in fFieldIsVarString then begin
  49410. // inlined result := GotoNextVarString(result);
  49411. if PByte(result)^<=$7f then
  49412. inc(PtrUInt(result),PByte(result)^+1) else begin
  49413. PB := result;
  49414. inc(PtrUInt(result),FromVarUInt32High(PB)+PtrUInt(PB)-PtrUInt(result));
  49415. end;
  49416. end else
  49417. if not (i in fFieldIsExternal) then begin
  49418. // inlined result := GotoNextVarInt(result)
  49419. while PByte(result)^>$7f do inc(PtrUInt(result));
  49420. inc(PtrUInt(result));
  49421. end;
  49422. end;
  49423. end;
  49424. procedure TSynTable.SaveTo(WR: TFileBufferWriter);
  49425. var i: Integer;
  49426. begin
  49427. WR.Write(fTableName);
  49428. WR.WriteVarUInt32(fField.Count);
  49429. for i := 0 to fField.Count-1 do
  49430. TSynTableFieldProperties(fField.List[i]).SaveTo(WR);
  49431. end;
  49432. procedure TSynTable.AfterFieldModif;
  49433. var i, Offs: integer;
  49434. begin
  49435. Int64(fFieldIsVarString) := 0;
  49436. Int64(fFieldIsExternal) := 0;
  49437. fFieldVariableIndex := -1;
  49438. fDefaultRecordLength := 0;
  49439. fFieldHasUniqueIndexes := false;
  49440. Offs := 0;
  49441. for i := 0 to fField.Count-1 do
  49442. with TSynTableFieldProperties(fField.List[i]) do begin
  49443. FieldNumber := i;
  49444. {$ifndef SORTCOMPAREMETHOD}
  49445. SortCompare := FIELD_SORT[FieldType];
  49446. {$endif}
  49447. Owner := self;
  49448. FieldSize := FIELD_FIXEDSIZE[FieldType];
  49449. if FieldSize>=0 then begin
  49450. assert(Offs>=0);
  49451. Offset := Offs;
  49452. inc(Offs,FieldSize);
  49453. inc(fDefaultRecordLength,FieldSize);
  49454. fDefaultFieldLength := FieldSize;
  49455. end else begin
  49456. if FieldSize=-3 then
  49457. Include(fFieldIsExternal,i) else begin
  49458. fDefaultFieldLength := 1;
  49459. inc(fDefaultRecordLength);
  49460. if FieldSize=-2 then
  49461. Include(fFieldIsVarString,i);
  49462. {$ifndef SORTCOMPAREMETHOD}
  49463. if (FieldType in [tftWinAnsi,tftUTF8]) and
  49464. (tfoCaseInsensitive in Options) then
  49465. SortCompare := SortIStr; // works for both WinAnsi and UTF-8 encodings
  49466. {$endif}
  49467. end;
  49468. // we need the Offset even for tftBlobExternal (FieldSize=-3)
  49469. if fFieldVariableIndex<0 then begin
  49470. fFieldVariableIndex := i;
  49471. fFieldVariableOffset := Offs;
  49472. Offs := -1;
  49473. end;
  49474. Offset := Offs;
  49475. dec(Offs);
  49476. end;
  49477. SetLength(fDefaultFieldData,fDefaultFieldLength);
  49478. FillcharFast(pointer(fDefaultFieldData)^,fDefaultFieldLength,0);
  49479. end;
  49480. SetLength(fDefaultRecordData,fDefaultRecordLength);
  49481. FillcharFast(pointer(fDefaultRecordData)^,fDefaultRecordLength,0);
  49482. end;
  49483. procedure TSynTable.FieldIndexModify(aOldIndex, aNewIndex: integer;
  49484. aOldRecordData, aNewRecordData: pointer);
  49485. var F: integer;
  49486. begin
  49487. for F := 0 to fField.Count-1 do
  49488. with TSynTableFieldProperties(fField.List[F]) do
  49489. if tfoIndex in Options then
  49490. OrderedIndexUpdate(aOldIndex,aNewIndex,aOldRecordData,aNewRecordData);
  49491. end;
  49492. procedure TSynTable.Filter(var RecordBuffer: TSBFString);
  49493. var Old, New: RawUTF8;
  49494. NewRecord: TSBFString; // UpdateFieldData update result in-place
  49495. F, i: integer;
  49496. begin
  49497. for F := 0 to fField.Count-1 do
  49498. with TSynTableFieldProperties(fField.List[F]) do
  49499. if Filters<>nil then begin
  49500. Old := GetRawUTF8(pointer(RecordBuffer));
  49501. New := Old;
  49502. for i := 0 to Filters.Count-1 do
  49503. TSynFilter(Filters.List[i]).Process(F,New);
  49504. if Old<>New then begin
  49505. // value was changed -> store modified
  49506. UpdateFieldData(pointer(RecordBuffer),length(RecordBuffer),F,
  49507. NewRecord,SBFFromRawUTF8(New));
  49508. RecordBuffer := NewRecord;
  49509. end;
  49510. end;
  49511. end;
  49512. {$ifndef NOVARIANTS}
  49513. function TSynTable.Data(aID: integer; RecordBuffer: pointer; RecordBufferLen: Integer): Variant;
  49514. var data: TSynTableData absolute result;
  49515. begin
  49516. if SynTableVariantType=nil then
  49517. SynTableVariantType := SynRegisterCustomVariantType(TSynTableVariantType);
  49518. if data.VType and VTYPE_STATIC<>0 then
  49519. VarClear(result);
  49520. data.VType := SynTableVariantType.VarType;
  49521. data.VID := aID;
  49522. data.VTable := self;
  49523. pointer(data.VValue) := nil; // avoid GPF
  49524. if RecordBuffer=nil then
  49525. data.VValue := DefaultRecordData else begin
  49526. if RecordBufferLen=0 then
  49527. RecordBufferLen := DataLength(RecordBuffer);
  49528. SetString(data.VValue,PAnsiChar(RecordBuffer),RecordBufferLen);
  49529. end;
  49530. end;
  49531. {$endif NOVARIANTS}
  49532. function TSynTable.DataLength(RecordBuffer: pointer): integer;
  49533. var F: Integer;
  49534. PC: PUTF8Char;
  49535. begin
  49536. if (Self<>nil) and (RecordBuffer<>nil) then begin
  49537. PC := RecordBuffer;
  49538. for F := 0 to fField.Count-1 do
  49539. inc(PC,TSynTableFieldProperties(fField.List[F]).GetLength(PC));
  49540. result := PC-RecordBuffer;
  49541. end else
  49542. result := 0;
  49543. end;
  49544. function TSynTable.UpdateFieldEvent(Sender: TObject; Opaque: pointer;
  49545. ID, Index: integer; Data: pointer; DataLen: integer): boolean;
  49546. var Added: PUpdateFieldEvent absolute Opaque;
  49547. F, aSize: integer;
  49548. begin // in practice, this data processing is very fast (thanks to WR speed)
  49549. with Added^ do begin
  49550. result := Count<length(IDs);
  49551. if not result then
  49552. exit;
  49553. for F := 0 to fField.Count-1 do
  49554. with TSynTableFieldProperties(fField.List[F]) do
  49555. if F in AvailableFields then begin
  49556. // add previous field content: will handle any field offset change in record
  49557. aSize := Getlength(Data);
  49558. WR.Write(Data,aSize);
  49559. Inc(PtrUInt(Data),aSize);
  49560. end else
  49561. // add default field content for a newly added field
  49562. WR.Write(Pointer(fDefaultFieldData),fDefaultFieldLength);
  49563. if WR.fTotalWritten>1 shl 30 then
  49564. raise ETableDataException.CreateUTF8('%: File size too big (>1GB)',[self]) else
  49565. Offsets64[Count] := WR.fTotalWritten;
  49566. IDs[Count] := ID;
  49567. NewIndexs[Index] := Count;
  49568. inc(Count);
  49569. end;
  49570. end;
  49571. function TSynTable.UpdateFieldRecord(RecordBuffer: PUTF8Char;
  49572. var AvailableFields: TSQLFieldBits): TSBFString;
  49573. var Lens: array[0..MAX_SQLFIELDS-1] of Integer;
  49574. F, Len, TotalLen: integer;
  49575. P: PUTF8Char;
  49576. Dest: PByte;
  49577. begin
  49578. // retrieve all field buffer lengths, to speed up record content creation
  49579. TotalLen := 0;
  49580. P := RecordBuffer;
  49581. for F := 0 to fField.Count-1 do
  49582. with TSynTableFieldProperties(fField.List[F]) do
  49583. if F in AvailableFields then begin
  49584. Len := GetLength(P);
  49585. inc(P,Len);
  49586. inc(TotalLen,Len);
  49587. Lens[F] := Len;
  49588. end else
  49589. inc(TotalLen,fDefaultFieldLength);
  49590. // create new record content
  49591. P := RecordBuffer;
  49592. SetString(Result,nil,TotalLen);
  49593. Dest := pointer(Result);
  49594. for F := 0 to fField.Count-1 do
  49595. with TSynTableFieldProperties(fField.List[F]) do
  49596. if F in AvailableFields then begin
  49597. Len := Lens[F];
  49598. MoveFast(P^,Dest^,Len);
  49599. inc(P,Len);
  49600. inc(Dest,Len);
  49601. end else begin
  49602. FillcharFast(Dest^,fDefaultFieldLength,0);
  49603. inc(Dest,fDefaultFieldLength);
  49604. end;
  49605. Assert(PtrUInt(Dest)-PtrUInt(result)=PtrUInt(TotalLen));
  49606. end;
  49607. function TSynTable.Validate(RecordBuffer: pointer; RecordIndex: integer): string;
  49608. var F: integer;
  49609. begin
  49610. result := '';
  49611. for F := 0 to fField.Count-1 do
  49612. with TSynTableFieldProperties(fField.List[F]) do
  49613. if Validates<>nil then begin
  49614. result := Validate(RecordBuffer,RecordIndex);
  49615. if result<>'' then
  49616. exit;
  49617. end;
  49618. end;
  49619. { TSynTableFieldProperties }
  49620. constructor TSynTableFieldProperties.CreateFrom(var RD: TFileBufferReader);
  49621. begin
  49622. fOrderedIndexFindAdd := -1;
  49623. RD.Read(Name);
  49624. if not PropNameValid(pointer(Name)) then
  49625. RD.ErrorInvalidContent;
  49626. RD.Read(@FieldType,SizeOf(FieldType));
  49627. RD.Read(@Options,SizeOf(Options));
  49628. if (FieldType>high(FieldType)) then
  49629. RD.ErrorInvalidContent;
  49630. OrderedIndexCount := RD.ReadVarUInt32Array(OrderedIndex);
  49631. if OrderedIndexCount>0 then begin
  49632. if tfoIndex in Options then begin
  49633. assert(OrderedIndexReverse=nil);
  49634. OrderedIndexReverseSet(-1); // compute whole OrderedIndexReverse[] array
  49635. end else
  49636. RD.ErrorInvalidContent;
  49637. end;
  49638. // we allow a void OrderedIndex[] array from disk
  49639. end;
  49640. destructor TSynTableFieldProperties.Destroy;
  49641. begin
  49642. Filters.Free;
  49643. Validates.Free;
  49644. inherited;
  49645. end;
  49646. {$ifndef DELPHI5OROLDER} // Delphi 5 does not like this code, do not know why :(
  49647. function TSynTableFieldProperties.GetJSON(FieldBuffer: pointer;
  49648. W: TTextWriter): pointer;
  49649. var len: integer;
  49650. tmp: RawUTF8;
  49651. begin
  49652. case FieldType of
  49653. // fixed-sized field value
  49654. tftBoolean:
  49655. W.Add(PBoolean(FieldBuffer)^);
  49656. tftUInt8:
  49657. W.Add(PByte(FieldBuffer)^);
  49658. tftUInt16:
  49659. W.Add(PWord(FieldBuffer)^);
  49660. tftUInt24:
  49661. // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
  49662. W.Add(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16);
  49663. tftInt32:
  49664. W.Add(PInteger(FieldBuffer)^);
  49665. tftInt64:
  49666. W.Add(PInt64(FieldBuffer)^);
  49667. tftCurrency:
  49668. W.AddCurr64(PInt64(FieldBuffer)^);
  49669. tftDouble:
  49670. W.AddDouble(PDouble(FieldBuffer)^);
  49671. // some variable-size field value
  49672. tftVarUInt32:
  49673. W.Add(FromVarUInt32(PByte(FieldBuffer)));
  49674. tftVarInt32:
  49675. W.Add(FromVarInt32(PByte(FieldBuffer)));
  49676. tftVarUInt64:
  49677. W.Add(Int64(FromVarUInt64(PByte(FieldBuffer))));
  49678. tftVarInt64:
  49679. W.Add(FromVarInt64(PByte(FieldBuffer)));
  49680. // text storage - WinAnsi could use less space than UTF-8
  49681. tftWinAnsi, tftUTF8: begin
  49682. W.Add('"');
  49683. len := FromVarUInt32(PByte(FieldBuffer));
  49684. if len>0 then
  49685. if FieldType=tftUTF8 then
  49686. W.AddJSONEscape(PAnsiChar(FieldBuffer),len) else begin
  49687. SetLength(tmp,len*3); // in-place decoding and appending
  49688. W.AddJSONEscape(pointer(tmp),WinAnsiBufferToUtf8(pointer(tmp),PAnsiChar(FieldBuffer),len)-pointer(tmp));
  49689. end;
  49690. W.Add('"');
  49691. result := PAnsiChar(FieldBuffer)+len;
  49692. exit;
  49693. end;
  49694. tftBlobInternal: begin
  49695. W.AddShort('"X''');
  49696. len := FromVarUInt32(PByte(FieldBuffer));
  49697. W.AddBinToHex(PByte(FieldBuffer),len);
  49698. W.Add('''','"');
  49699. end;
  49700. tftBlobExternal:
  49701. ; // BLOB fields are not handled here, but must be directly accessed
  49702. end;
  49703. result := PAnsiChar(FieldBuffer)+FieldSize; // // tftWinAnsi,tftUTF8 already done
  49704. end;
  49705. {$endif}
  49706. function TSynTableFieldProperties.GetLength(FieldBuffer: pointer): Integer;
  49707. var PB: PByte;
  49708. begin
  49709. if FieldSize>=0 then
  49710. result := FieldSize else
  49711. case FieldSize of
  49712. -1: begin // variable-length data
  49713. result := 0;
  49714. while PByteArray(FieldBuffer)^[result]>$7f do inc(result);
  49715. inc(result);
  49716. end;
  49717. -2: begin // tftWinAnsi, tftUTF8, tftBlobInternal records
  49718. result := PByte(FieldBuffer)^;
  49719. if result<=$7F then
  49720. inc(Result) else begin
  49721. PB := FieldBuffer;
  49722. result := FromVarUInt32High(PB)+PtrUInt(PB)-PtrUInt(FieldBuffer);
  49723. end;
  49724. end;
  49725. else
  49726. result := 0; // tftBlobExternal is not stored in FieldBuffer
  49727. end;
  49728. end;
  49729. {$ifndef NOVARIANTS}
  49730. function TSynTableFieldProperties.GetVariant(FieldBuffer: pointer): Variant;
  49731. begin
  49732. GetVariant(FieldBuffer,result);
  49733. end;
  49734. procedure TSynTableFieldProperties.GetVariant(FieldBuffer: pointer; var result: Variant);
  49735. var len: integer;
  49736. PB: PByte absolute FieldBuffer;
  49737. PA: PAnsiChar absolute FieldBuffer;
  49738. PU: PUTF8Char absolute FieldBuffer;
  49739. tmp: RawByteString;
  49740. {$ifndef UNICODE}
  49741. WS: WideString;
  49742. {$endif}
  49743. begin
  49744. case FieldType of
  49745. // fixed-sized field value
  49746. tftBoolean:
  49747. result := PBoolean(FieldBuffer)^;
  49748. tftUInt8:
  49749. result := PB^;
  49750. tftUInt16:
  49751. result := PWord(FieldBuffer)^;
  49752. tftUInt24:
  49753. // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
  49754. result := PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16;
  49755. tftInt32:
  49756. result := PInteger(FieldBuffer)^;
  49757. tftInt64:
  49758. result := PInt64(FieldBuffer)^;
  49759. tftCurrency:
  49760. result := PCurrency(FieldBuffer)^;
  49761. tftDouble:
  49762. result := PDouble(FieldBuffer)^;
  49763. // some variable-size field value
  49764. tftVarUInt32:
  49765. result := FromVarUInt32(PB);
  49766. tftVarInt32:
  49767. result := FromVarInt32(PB);
  49768. tftVarUInt64:
  49769. result := FromVarUInt64(PB);
  49770. tftVarInt64:
  49771. result := FromVarInt64(PB);
  49772. // text storage - WinAnsi could use less space than UTF-8
  49773. tftWinAnsi: begin
  49774. len := FromVarUInt32(PB);
  49775. if len>0 then
  49776. {$ifdef UNICODE}
  49777. result := WinAnsiToUnicodeString(PA,len)
  49778. {$else}
  49779. result := CurrentAnsiConvert.AnsiToAnsi(WinAnsiConvert,PA,len)
  49780. {$endif} else
  49781. result := '';
  49782. end;
  49783. tftUTF8: begin
  49784. len := FromVarUInt32(PB);
  49785. if len>0 then
  49786. {$ifdef UNICODE}
  49787. result := UTF8DecodeToUnicodeString(PU,len)
  49788. {$else} begin
  49789. UTF8ToSynUnicode(PU,len,WS);
  49790. result := WS;
  49791. end
  49792. {$endif} else
  49793. result := '';
  49794. end;
  49795. tftBlobInternal: begin
  49796. len := FromVarUInt32(PB);
  49797. SetString(tmp,PA,len);
  49798. result := tmp; // return internal BLOB content as string
  49799. end
  49800. else
  49801. result := ''; // tftBlobExternal fields e.g. must be directly accessed
  49802. end;
  49803. end;
  49804. {$endif}
  49805. function TSynTableFieldProperties.GetValue(FieldBuffer: pointer): RawUTF8;
  49806. var len: integer;
  49807. PB: PByte absolute FieldBuffer;
  49808. PC: PAnsiChar absolute FieldBuffer;
  49809. begin
  49810. case FieldType of
  49811. // fixed-sized field value
  49812. tftBoolean:
  49813. JSONBoolean(PBoolean(FieldBuffer)^,result);
  49814. tftUInt8:
  49815. UInt32ToUtf8(PB^,result);
  49816. tftUInt16:
  49817. UInt32ToUtf8(PWord(FieldBuffer)^,result);
  49818. tftUInt24:
  49819. // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
  49820. UInt32ToUtf8(PWord(FieldBuffer)^+integer(PByteArray(FieldBuffer)^[2])shl 16,result);
  49821. tftInt32:
  49822. Int32ToUtf8(PInteger(FieldBuffer)^,result);
  49823. tftInt64:
  49824. Int64ToUtf8(PInt64(FieldBuffer)^,result);
  49825. tftCurrency:
  49826. Curr64ToStr(PInt64(FieldBuffer)^,result);
  49827. tftDouble:
  49828. ExtendedToStr(PDouble(FieldBuffer)^,DOUBLE_PRECISION,result);
  49829. // some variable-size field value
  49830. tftVarUInt32:
  49831. UInt32ToUtf8(FromVarUInt32(PB),result);
  49832. tftVarInt32:
  49833. Int32ToUtf8(FromVarInt32(PB),result);
  49834. tftVarUInt64:
  49835. Int64ToUtf8(FromVarUInt64(PB),result);
  49836. tftVarInt64:
  49837. Int64ToUtf8(FromVarInt64(PB),result);
  49838. // text storage - WinAnsi could use less space than UTF-8
  49839. tftWinAnsi, tftUTF8, tftBlobInternal: begin
  49840. len := FromVarUInt32(PB);
  49841. if len>0 then
  49842. if FieldType<>tftWinAnsi then
  49843. SetString(result,PC,len) else
  49844. result := WinAnsiConvert.AnsiBufferToRawUTF8(PC,len) else
  49845. result := '';
  49846. end;
  49847. else
  49848. result := ''; // tftBlobExternal fields e.g. must be directly accessed
  49849. end;
  49850. end;
  49851. procedure TSynTableFieldProperties.OrderedIndexReverseSet(aOrderedIndex: integer);
  49852. var nrev, ndx, n: PtrInt;
  49853. begin
  49854. n := length(OrderedIndex);
  49855. nrev := length(OrderedIndexReverse);
  49856. if nrev=0 then
  49857. if n=0 then
  49858. exit else begin
  49859. // void OrderedIndexReverse[]
  49860. nrev := MaxInteger(OrderedIndex,OrderedIndexCount,n)+1;
  49861. SetLength(OrderedIndexReverse,nrev);
  49862. FillcharFast(OrderedIndexReverse[0],nrev*4,255); // all to -1
  49863. Reverse(OrderedIndex,OrderedIndexCount,pointer(OrderedIndexReverse));
  49864. end;
  49865. if PtrUInt(aOrderedIndex)>=PtrUInt(OrderedIndexCount) then
  49866. exit; // e.g. CreateFrom() will call OrderedIndexReverseSet(-1)
  49867. if nrev<n then begin
  49868. SetLength(OrderedIndexReverse,n); // resize if needed
  49869. nrev := n;
  49870. end;
  49871. ndx := OrderedIndex[aOrderedIndex];
  49872. if ndx>=nrev then
  49873. SetLength(OrderedIndexReverse,ndx+256) else
  49874. OrderedIndexReverse[ndx] := aOrderedIndex;
  49875. end;
  49876. procedure TSynTableFieldProperties.OrderedIndexSort(L, R: PtrInt);
  49877. var I, J, P: PtrInt;
  49878. TmpI, TmpJ: integer;
  49879. begin
  49880. if (L<R) and Assigned(Owner.GetRecordData) then
  49881. repeat
  49882. I := L; J := R;
  49883. P := (L + R) shr 1;
  49884. repeat
  49885. with Owner do begin
  49886. SortPivot := GetData(GetRecordData(OrderedIndex[P],DataTemp1),self);
  49887. while SortCompare(GetData(GetRecordData(OrderedIndex[I],DataTemp2),self),
  49888. SortPivot)<0 do Inc(I);
  49889. while SortCompare(GetData(GetRecordData(OrderedIndex[J],DataTemp2),self),
  49890. SortPivot)>0 do Dec(J);
  49891. end;
  49892. if I <= J then begin
  49893. if I < J then begin
  49894. TmpJ := OrderedIndex[J];
  49895. TmpI := OrderedIndex[I];
  49896. OrderedIndex[J] := TmpI;
  49897. OrderedIndex[I] := TmpJ;
  49898. // keep OrderedIndexReverse[OrderedIndex[i]]=i
  49899. OrderedIndexReverse[TmpJ] := I;
  49900. OrderedIndexReverse[TmpI] := J;
  49901. end;
  49902. if P = I then P := J else if P = J then P := I;
  49903. Inc(I); Dec(J);
  49904. end;
  49905. until I > J;
  49906. if L < J then
  49907. OrderedIndexSort(L, J);
  49908. L := I;
  49909. until I >= R;
  49910. end;
  49911. procedure TSynTableFieldProperties.OrderedIndexRefresh;
  49912. begin
  49913. if (self=nil) or not OrderedIndexNotSorted then
  49914. exit; // already sorted
  49915. OrderedIndexSort(0,OrderedIndexCount-1);
  49916. OrderedIndexNotSorted := false;
  49917. end;
  49918. function TSynTableFieldProperties.OrderedIndexFind(Value: pointer): PtrInt;
  49919. var L,R: PtrInt;
  49920. cmp: PtrInt;
  49921. begin
  49922. if OrderedIndexNotSorted then
  49923. OrderedIndexRefresh;
  49924. L := 0;
  49925. R := OrderedIndexCount-1;
  49926. with Owner do
  49927. if (R>=0) and Assigned(GetRecordData) then
  49928. repeat
  49929. result := (L + R) shr 1;
  49930. cmp := SortCompare(GetData(GetRecordData(OrderedIndex[result],DataTemp1),self),Value);
  49931. if cmp=0 then
  49932. exit;
  49933. if cmp<0 then
  49934. L := result + 1 else
  49935. R := result - 1;
  49936. until (L > R);
  49937. result := -1
  49938. end;
  49939. function TSynTableFieldProperties.OrderedIndexFindAdd(Value: pointer): PtrInt;
  49940. var L,R,i: PtrInt;
  49941. cmp: PtrInt;
  49942. begin
  49943. if OrderedIndexNotSorted then
  49944. OrderedIndexRefresh;
  49945. R := OrderedIndexCount-1;
  49946. if R<0 then
  49947. result := 0 else
  49948. with Owner do begin
  49949. fOrderedIndexFindAdd := -1;
  49950. L := 0;
  49951. result := -1; // return -1 if found
  49952. repeat
  49953. i := (L + R) shr 1;
  49954. cmp := SortCompare(GetData(GetRecordData(OrderedIndex[i],DataTemp1),self),Value);
  49955. if cmp=0 then
  49956. exit;
  49957. if cmp<0 then
  49958. L := i + 1 else
  49959. R := i - 1;
  49960. until (L > R);
  49961. while (i>=0) and
  49962. (SortCompare(GetData(GetRecordData(OrderedIndex[i],DataTemp1),self),Value)>=0) do
  49963. dec(i);
  49964. result := i+1; // return the index where to insert
  49965. end;
  49966. fOrderedIndexFindAdd := result; // store inserting index for OrderedIndexUpdate
  49967. end;
  49968. function TSynTableFieldProperties.OrderedIndexMatch(WhereSBFValue: pointer;
  49969. var MatchIndex: TIntegerDynArray; var MatchIndexCount: integer; Limit: Integer=0): Boolean;
  49970. var i, L,R: PtrInt;
  49971. begin
  49972. result := false;
  49973. if (self=nil) or (WhereSBFValue=nil) or not Assigned(Owner.GetRecordData) or
  49974. (OrderedIndex=nil) or not (tfoIndex in Options) then
  49975. exit;
  49976. i := OrderedIndexFind(WhereSBFValue);
  49977. if i<0 then
  49978. exit; // WHERE value not found
  49979. if (tfoUnique in Options) or (Limit=1) then begin
  49980. // unique index: direct fastest binary search
  49981. AddSortedInteger(MatchIndex,MatchIndexCount,OrderedIndex[i]);
  49982. // AddSortedInteger() will fail if OrderedIndex[i] already exists
  49983. end else
  49984. with Owner do begin
  49985. // multiple index matches possible: add matching range
  49986. L := i;
  49987. repeat
  49988. dec(L);
  49989. until (L<0) or (SortCompare(GetData(GetRecordData(
  49990. OrderedIndex[L],DataTemp1),self),WhereSBFValue)<>0);
  49991. R := i;
  49992. repeat
  49993. inc(R);
  49994. until (R>=OrderedIndexCount) or
  49995. (SortCompare(GetData(GetRecordData(OrderedIndex[R],DataTemp1),self),WhereSBFValue)<>0);
  49996. if Limit=0 then
  49997. Limit := MaxInt; // no LIMIT set -> retrieve all rows
  49998. for i := L+1 to R-1 do begin
  49999. AddSortedInteger(MatchIndex,MatchIndexCount,OrderedIndex[i]);
  50000. dec(Limit);
  50001. if Limit=0 then
  50002. Break; // reach LIMIT upperbound result count
  50003. end;
  50004. end;
  50005. result := true;
  50006. end;
  50007. function TSynTableFieldProperties.OrderedIndexUpdate(aOldIndex, aNewIndex: integer;
  50008. aOldRecordData, aNewRecordData: pointer): boolean;
  50009. var aOldIndexIndex: integer;
  50010. begin
  50011. result := false;
  50012. if (self=nil) or not Assigned(Owner.GetRecordData) then
  50013. exit; // avoid GPF
  50014. // update content
  50015. if aOldIndex<0 then
  50016. if aNewIndex<0 then begin
  50017. // both indexes equal -1 -> force sort
  50018. OrderedIndexSort(0,OrderedIndexCount-1);
  50019. OrderedIndexNotSorted := false;
  50020. end else begin
  50021. // added record
  50022. if tfoUnique in Options then begin
  50023. if fOrderedIndexFindAdd<0 then
  50024. raise ETableDataException.CreateUTF8(
  50025. '%.CheckConstraint call needed before %.OrderedIndexUpdate',[self,Name]);
  50026. OrderedIndexReverseSet(InsertInteger(OrderedIndex,OrderedIndexCount,
  50027. aNewIndex,fOrderedIndexFindAdd));
  50028. end else begin
  50029. AddInteger(OrderedIndex,OrderedIndexCount,aNewIndex);
  50030. OrderedIndexReverseSet(OrderedIndexCount-1);
  50031. OrderedIndexNotSorted := true; // -> OrderedIndexSort() call on purpose
  50032. end;
  50033. end else begin
  50034. // aOldIndex>=0: update a value
  50035. // retrieve position in OrderedIndex[] to be deleted/updated
  50036. if OrderedIndexReverse=nil then
  50037. OrderedIndexReverseSet(0) else // do OrderedIndexReverse[OrderedIndex[i]] := i
  50038. assert(aOldIndex<length(OrderedIndexReverse));
  50039. //assert(IntegerScanIndex(Pointer(OrderedIndex),OrderedIndexCount,aOldIndex)=OrderedIndexReverse[aOldIndex]);
  50040. aOldIndexIndex := OrderedIndexReverse[aOldIndex]; // use FAST reverse array
  50041. if aOldIndexIndex<0 then
  50042. exit; // invalid Old index
  50043. if aNewIndex<0 then begin
  50044. // deleted record
  50045. DeleteInteger(OrderedIndex,OrderedIndexCount,aOldIndexIndex);
  50046. Reverse(OrderedIndex,OrderedIndexCount,pointer(OrderedIndexReverse));
  50047. // no need to refresh OrderedIndex[], since data will remain sorted
  50048. end else begin
  50049. // updated record
  50050. OrderedIndex[aOldIndexIndex] := aNewIndex;
  50051. OrderedIndexReverseSet(aOldIndexIndex);
  50052. if (aOldRecordData<>nil) or (aOldIndex<>aNewIndex) then // not in-place update
  50053. with Owner do begin
  50054. if aOldRecordData=nil then
  50055. aOldRecordData := GetRecordData(aOldIndex,DataTemp1);
  50056. if aNewRecordData=nil then
  50057. aNewRecordData := GetRecordData(aNewIndex,DataTemp2);
  50058. if SortCompare(GetData(aOldRecordData,self),GetData(aNewRecordData,self))=0 then begin
  50059. // only sort if field content was modified -> MUCH faster in most case
  50060. result := true;
  50061. exit;
  50062. end;
  50063. end;
  50064. if tfoUnique in Options then begin
  50065. if fOrderedIndexFindAdd>=0 then begin
  50066. // we know which OrderedIndex[] has to be changed -> manual update
  50067. // - this is still a bottleneck in the current implementation, but
  50068. // I was not able to find out how to make it faster, and still
  50069. // being able to check unique field constraints without changing the
  50070. // OrderedIndex[] content from a simple list into e.g. a red-black
  50071. // tree: such a structure performs better, but uses much more memory
  50072. // and is to be implemented
  50073. // - it's still fast, faster than any DB AFAIK, around 500 updates
  50074. // per second with 1,000,000 records on a Core i7
  50075. // - it's still faster to refresh OrderedIndex[] than iterating
  50076. // through all items to validate the unique constraint
  50077. DeleteInteger(OrderedIndex,OrderedIndexCount,aOldIndexIndex);
  50078. if fOrderedIndexFindAdd>aOldIndexIndex then
  50079. dec(fOrderedIndexFindAdd);
  50080. InsertInteger(OrderedIndex,OrderedIndexCount,aNewIndex,fOrderedIndexFindAdd);
  50081. Reverse(OrderedIndex,OrderedIndexCount,pointer(OrderedIndexReverse));
  50082. end else
  50083. // slow full sort - with 1,000,000 items it's about 100 times slower
  50084. // (never called with common usage in SynBigTable unit)
  50085. OrderedIndexSort(0,OrderedIndexCount-1);
  50086. end else
  50087. OrderedIndexNotSorted := true; // will call OrderedIndexSort() on purpose
  50088. end;
  50089. end;
  50090. fOrderedIndexFindAdd := -1; // consume this value
  50091. result := true;
  50092. end;
  50093. procedure TSynTableFieldProperties.SaveTo(WR: TFileBufferWriter);
  50094. begin
  50095. WR.Write(Name);
  50096. WR.Write(@FieldType,SizeOf(FieldType));
  50097. WR.Write(@Options,SizeOf(Options));
  50098. WR.WriteVarUInt32Array(OrderedIndex,OrderedIndexCount,wkVarUInt32);
  50099. end;
  50100. function TSynTableFieldProperties.SBF(const Value: Int64): TSBFString;
  50101. var tmp: array[0..15] of AnsiChar;
  50102. begin
  50103. case FieldType of
  50104. tftInt32: begin // special version for handling negative values
  50105. PInteger(@tmp)^ := Value;
  50106. SetString(Result,tmp,sizeof(Integer));
  50107. end;
  50108. tftUInt8, tftUInt16, tftUInt24, tftInt64:
  50109. SetString(Result,PAnsiChar(@Value),FieldSize);
  50110. tftVarUInt32:
  50111. SetString(Result,tmp,PAnsiChar(ToVarUInt32(Value,@tmp))-tmp);
  50112. tftVarInt32:
  50113. SetString(Result,tmp,PAnsiChar(ToVarInt32(Value,@tmp))-tmp);
  50114. tftVarUInt64:
  50115. SetString(Result,tmp,PAnsiChar(ToVarUInt64(Value,@tmp))-tmp);
  50116. tftVarInt64:
  50117. SetString(Result,tmp,PAnsiChar(ToVarInt64(Value,@tmp))-tmp);
  50118. else
  50119. result := '';
  50120. end;
  50121. end;
  50122. function TSynTableFieldProperties.SBF(const Value: Integer): TSBFString;
  50123. var tmp: array[0..15] of AnsiChar;
  50124. begin
  50125. case FieldType of
  50126. tftUInt8, tftUInt16, tftUInt24, tftInt32:
  50127. SetString(Result,PAnsiChar(@Value),FieldSize);
  50128. tftInt64: begin // special version for handling negative values
  50129. PInt64(@tmp)^ := Value;
  50130. SetString(Result,tmp,sizeof(Int64));
  50131. end;
  50132. tftVarUInt32:
  50133. if Value<0 then // expect an unsigned integer
  50134. result := '' else
  50135. SetString(Result,tmp,PAnsiChar(ToVarUInt32(Value,@tmp))-tmp);
  50136. tftVarInt32:
  50137. SetString(Result,tmp,PAnsiChar(ToVarInt32(Value,@tmp))-tmp);
  50138. tftVarUInt64:
  50139. if cardinal(Value)>cardinal(maxInt) then
  50140. result := '' else // expect a 32 bit integer
  50141. SetString(Result,tmp,PAnsiChar(ToVarUInt64(Value,@tmp))-tmp);
  50142. tftVarInt64:
  50143. SetString(Result,tmp,PAnsiChar(ToVarInt64(Value,@tmp))-tmp);
  50144. else
  50145. result := '';
  50146. end;
  50147. end;
  50148. const
  50149. SBF_BOOL: array[boolean] of TSBFString =
  50150. (#0,#1);
  50151. {$ifndef NOVARIANTS}
  50152. function TSynTableFieldProperties.SBF(const Value: Variant): TSBFString;
  50153. var V64: Int64;
  50154. VC: Currency absolute V64;
  50155. VD: Double absolute V64;
  50156. begin // VarIsOrdinal/VarIsFloat/VarIsStr are buggy -> use field type
  50157. case FieldType of
  50158. tftBoolean:
  50159. result := SBF_BOOL[boolean(Value)];
  50160. tftUInt8, tftUInt16, tftUInt24, tftInt32, tftInt64,
  50161. tftVarUInt32, tftVarInt32, tftVarUInt64, tftVarInt64: begin
  50162. if not VariantToInt64(Value,V64) then
  50163. V64 := 0;
  50164. result := SBF(V64);
  50165. end;
  50166. tftCurrency: begin
  50167. VC := Value;
  50168. SetString(result,PAnsiChar(@VC),sizeof(VC));
  50169. end;
  50170. tftDouble: begin
  50171. VD := Value;
  50172. SetString(result,PAnsiChar(@VD),sizeof(VD));
  50173. end;
  50174. tftWinAnsi:
  50175. ToSBFStr(WinAnsiConvert.UTF8ToAnsi(VariantToUTF8(Value)),result);
  50176. tftUTF8:
  50177. ToSBFStr(VariantToUTF8(Value),result);
  50178. else
  50179. result := '';
  50180. end;
  50181. if result='' then
  50182. result := SBFDefault;
  50183. end;
  50184. {$endif}
  50185. function TSynTableFieldProperties.SBF(const Value: Boolean): TSBFString;
  50186. begin
  50187. if FieldType<>tftBoolean then
  50188. result := '' else
  50189. result := SBF_BOOL[Value];
  50190. end;
  50191. function TSynTableFieldProperties.SBFCurr(const Value: Currency): TSBFString;
  50192. begin
  50193. if FieldType<>tftCurrency then
  50194. result := '' else
  50195. SetString(Result,PAnsiChar(@Value),sizeof(Value));
  50196. end;
  50197. procedure ToSBFStr(const Value: RawByteString; out Result: TSBFString);
  50198. var tmp: array[0..15] of AnsiChar;
  50199. Len, Head: integer;
  50200. begin
  50201. if PtrUInt(Value)=0 then
  50202. Result := #0 else begin
  50203. Len := {$ifdef FPC}PStrRec(Pointer(PtrUInt(Value)-STRRECSIZE))^.length
  50204. {$else}PInteger(PtrUInt(Value)-sizeof(integer))^{$endif};
  50205. Head := PAnsiChar(ToVarUInt32(Len,@tmp))-tmp;
  50206. SetLength(Result,Len+Head);
  50207. MoveFast(tmp,PByteArray(Result)[0],Head);
  50208. MoveFast(pointer(Value)^,PByteArray(Result)[Head],Len);
  50209. end;
  50210. end;
  50211. function TSynTableFieldProperties.SBF(const Value: RawUTF8): TSBFString;
  50212. begin
  50213. case FieldType of
  50214. tftUTF8:
  50215. ToSBFStr(Value,Result);
  50216. tftWinAnsi:
  50217. ToSBFStr(Utf8ToWinAnsi(Value),Result);
  50218. else
  50219. result := '';
  50220. end;
  50221. end;
  50222. function TSynTableFieldProperties.SBF(Value: pointer; ValueLen: integer): TSBFString;
  50223. var tmp: array[0..15] of AnsiChar;
  50224. Head: integer;
  50225. begin
  50226. if FieldType<>tftBlobInternal then
  50227. result := '' else
  50228. if (Value=nil) or (ValueLen=0) then
  50229. result := #0 else begin // inlined ToSBFStr() code
  50230. Head := PAnsiChar(ToVarUInt32(ValueLen,@tmp))-tmp;
  50231. SetString(Result,nil,ValueLen+Head);
  50232. MoveFast(tmp,PByteArray(Result)[0],Head);
  50233. MoveFast(Value^,PByteArray(Result)[Head],ValueLen);
  50234. end;
  50235. end;
  50236. function TSynTableFieldProperties.SBFFloat(const Value: Double): TSBFString;
  50237. begin
  50238. if FieldType<>tftDouble then
  50239. result := '' else
  50240. SetString(Result,PAnsiChar(@Value),sizeof(Value));
  50241. end;
  50242. function TSynTableFieldProperties.SBFFromRawUTF8(const aValue: RawUTF8): TSBFString;
  50243. var Curr: Currency;
  50244. begin
  50245. case FieldType of
  50246. tftBoolean:
  50247. if (SynCommons.GetInteger(pointer(aValue))<>0) or IdemPropNameU(aValue,'true') then
  50248. result := #1 else
  50249. result := #0; // store false by default
  50250. tftUInt8, tftUInt16, tftUInt24, tftInt32, tftVarInt32:
  50251. result := SBF(SynCommons.GetInteger(pointer(aValue)));
  50252. tftVarUInt32, tftInt64, tftVarUInt64, tftVarInt64:
  50253. result := SBF(SynCommons.GetInt64(pointer(aValue)));
  50254. tftCurrency: begin
  50255. PInt64(@Curr)^ := StrToCurr64(pointer(aValue));
  50256. result := SBFCurr(Curr);
  50257. end;
  50258. tftDouble:
  50259. result := SBFFloat(GetExtended(pointer(aValue)));
  50260. // text storage - WinAnsi could use less space than UTF-8
  50261. tftUTF8, tftWinAnsi:
  50262. result := SBF(aValue);
  50263. else
  50264. result := ''; // tftBlob* fields e.g. must be handled directly
  50265. end;
  50266. end;
  50267. function TSynTableFieldProperties.GetInteger(RecordBuffer: pointer): Integer;
  50268. begin
  50269. if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then
  50270. result := 0 else begin
  50271. RecordBuffer := Owner.GetData(RecordBuffer,self);
  50272. case FieldType of
  50273. tftBoolean, tftUInt8:
  50274. result := PByte(RecordBuffer)^;
  50275. tftUInt16:
  50276. result := PWord(RecordBuffer)^;
  50277. tftUInt24:
  50278. // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
  50279. result := PWord(RecordBuffer)^+integer(PByteArray(RecordBuffer)^[2])shl 16;
  50280. tftInt32:
  50281. result := PInteger(RecordBuffer)^;
  50282. tftInt64:
  50283. result := PInt64(RecordBuffer)^;
  50284. // some variable-size field value
  50285. tftVarUInt32:
  50286. result := FromVarUInt32(PByte(RecordBuffer));
  50287. tftVarInt32:
  50288. result := FromVarInt32(PByte(RecordBuffer));
  50289. tftVarUInt64:
  50290. result := FromVarUInt64(PByte(RecordBuffer));
  50291. tftVarInt64:
  50292. result := FromVarInt64(PByte(RecordBuffer));
  50293. else
  50294. result := 0;
  50295. end;
  50296. end;
  50297. end;
  50298. function TSynTableFieldProperties.GetInt64(RecordBuffer: pointer): Int64;
  50299. var PB: PByte;
  50300. begin
  50301. if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then
  50302. result := 0 else begin
  50303. PB := Owner.GetData(RecordBuffer,self);
  50304. case FieldType of
  50305. tftInt64:
  50306. result := PInt64(PB)^;
  50307. tftVarUInt64:
  50308. result := FromVarUInt64(PB);
  50309. tftVarInt64:
  50310. result := FromVarInt64(PB);
  50311. else
  50312. result := GetInteger(RecordBuffer);
  50313. end;
  50314. end;
  50315. end;
  50316. function TSynTableFieldProperties.GetBoolean(RecordBuffer: pointer): Boolean;
  50317. begin
  50318. result := boolean(GetInteger(RecordBuffer));
  50319. end;
  50320. function TSynTableFieldProperties.GetCurrency(RecordBuffer: pointer): Currency;
  50321. begin
  50322. if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then
  50323. result := 0 else
  50324. case FieldType of
  50325. tftCurrency:
  50326. result := PCurrency(Owner.GetData(RecordBuffer,self))^;
  50327. else
  50328. result := GetInt64(RecordBuffer);
  50329. end;
  50330. end;
  50331. function TSynTableFieldProperties.GetDouble(RecordBuffer: pointer): Double;
  50332. begin
  50333. if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then
  50334. result := 0 else
  50335. case FieldType of
  50336. tftDouble:
  50337. result := PDouble(Owner.GetData(RecordBuffer,self))^;
  50338. else
  50339. result := GetInt64(RecordBuffer);
  50340. end;
  50341. end;
  50342. function TSynTableFieldProperties.GetRawUTF8(RecordBuffer: pointer): RawUTF8;
  50343. begin
  50344. if (self=nil) or (RecordBuffer=nil) or (Owner=nil) then
  50345. result := '' else begin
  50346. RecordBuffer := Owner.GetData(RecordBuffer,self);
  50347. if RecordBuffer<>nil then
  50348. result := GetValue(RecordBuffer) else // will do conversion to text
  50349. result := '';
  50350. end;
  50351. end;
  50352. function TSynTableFieldProperties.AddFilterOrValidate(aFilter: TSynFilterOrValidate): TSynFilterOrValidate;
  50353. procedure Add(var List: TObjectList);
  50354. begin
  50355. if List=nil then
  50356. List := TObjectList.Create;
  50357. List.Add(result);
  50358. end;
  50359. begin
  50360. result := aFilter;
  50361. if (self=nil) or (result=nil) then
  50362. result := nil else
  50363. if aFilter.InheritsFrom(TSynFilter) then
  50364. Add(Filters) else
  50365. if aFilter.InheritsFrom(TSynValidate) then
  50366. Add(Validates) else
  50367. result := nil;
  50368. end;
  50369. function TSynTableFieldProperties.Validate(RecordBuffer: pointer;
  50370. RecordIndex: integer): string;
  50371. var i: integer;
  50372. Value: RawUTF8;
  50373. aValidate: TSynValidate;
  50374. aValidateTable: TSynValidateTable absolute aValidate;
  50375. begin
  50376. result := '';
  50377. if (self=nil) or (Validates=nil) then
  50378. exit;
  50379. Value := GetRawUTF8(RecordBuffer); // TSynTableValidate needs RawUTF8 text
  50380. for i := 0 to Validates.Count-1 do begin
  50381. aValidate := Validates.List[i];
  50382. if aValidate.InheritsFrom(TSynValidateTable) then begin
  50383. aValidateTable.ProcessField := self;
  50384. aValidateTable.ProcessRecordIndex := RecordIndex;
  50385. end;
  50386. if not aValidate.Process(FieldNumber,Value,result) then begin
  50387. if result='' then
  50388. // no custom message -> show a default message
  50389. result := format(sValidationFailed,[
  50390. GetCaptionFromClass(aValidate.ClassType)]);
  50391. break;
  50392. end;
  50393. end;
  50394. end;
  50395. {$ifdef SORTCOMPAREMETHOD}
  50396. function TSynTableFieldProperties.SortCompare(P1, P2: PUTF8Char): PtrInt;
  50397. var i, L: integer;
  50398. label minus,plus,zer;
  50399. begin
  50400. if P1<>P2 then
  50401. if P1<>nil then
  50402. if P2<>nil then
  50403. case FieldType of
  50404. tftBoolean, tftUInt8:
  50405. result := PByte(P1)^-PByte(P2)^;
  50406. tftUInt16:
  50407. result := PWord(P1)^-PWord(P2)^;
  50408. tftUInt24:
  50409. result := PtrInt(PWord(P1)^)+PtrInt(P1[2])shl 16
  50410. -PtrInt(PWord(P2)^)-PtrInt(P2[2]) shl 16;
  50411. tftInt32:
  50412. result := PInteger(P1)^-PInteger(P2)^;
  50413. tftInt64, tftCurrency: begin
  50414. PInt64(@SortCompareTmp)^ := PInt64(P1)^-PInt64(P2)^;
  50415. if PInt64(@SortCompareTmp)^<0 then
  50416. goto minus else
  50417. if PInt64(@SortCompareTmp)^>0 then
  50418. goto plus else
  50419. goto zer;
  50420. end;
  50421. tftDouble: begin
  50422. PDouble(@SortCompareTmp)^ := PDouble(P1)^-PDouble(P2)^;
  50423. if PDouble(@SortCompareTmp)^<0 then
  50424. goto minus else
  50425. if PDouble(@SortCompareTmp)^>0 then
  50426. goto plus else
  50427. goto zer;
  50428. end;
  50429. tftVarUInt32:
  50430. with SortCompareTmp do begin
  50431. PB1 := Pointer(P1);
  50432. PB2 := Pointer(P2);
  50433. result := FromVarUInt32(PB1)-FromVarUInt32(PB2);
  50434. end;
  50435. tftVarInt32:
  50436. with SortCompareTmp do begin
  50437. PB1 := Pointer(P1);
  50438. PB2 := Pointer(P2);
  50439. result := FromVarInt32(PB1)-FromVarInt32(PB2);
  50440. end;
  50441. tftVarUInt64:
  50442. with SortCompareTmp do begin
  50443. PB1 := Pointer(P1);
  50444. PB2 := Pointer(P2);
  50445. result := FromVarUInt64(PB1)-FromVarUInt64(PB2);
  50446. end;
  50447. tftVarInt64:
  50448. with SortCompareTmp do begin
  50449. PB1 := Pointer(P1);
  50450. PB2 := Pointer(P2);
  50451. result := FromVarInt64(PB1)-FromVarInt64(PB2);
  50452. end;
  50453. tftWinAnsi, tftUTF8, tftBlobInternal:
  50454. begin
  50455. with SortCompareTmp do begin
  50456. if PtrInt(P1^)<=$7F then begin
  50457. L1 := PtrInt(P1^);
  50458. inc(P1);
  50459. end else begin
  50460. PB1 := pointer(P1);
  50461. L1 := FromVarUInt32High(PB1);
  50462. P1 := pointer(PB1);
  50463. end;
  50464. if PtrInt(P2^)<=$7F then begin
  50465. L2 := PtrInt(P2^);
  50466. inc(P2);
  50467. end else begin
  50468. PB2 := pointer(P2);
  50469. L2 := FromVarUInt32High(PB2);
  50470. P2 := pointer(PB2);
  50471. end;
  50472. end;
  50473. with SortCompareTmp do begin
  50474. L := L1;
  50475. if L2>L then
  50476. L := L2;
  50477. end;
  50478. if tfoCaseInsensitive in Options then begin
  50479. i := 0;
  50480. while i<L do begin
  50481. result := PtrInt(NormToUpperAnsi7[P1[i]])-PtrInt(NormToUpperAnsi7[P2[i]]);
  50482. if result<>0 then
  50483. exit else
  50484. inc(i);
  50485. end;
  50486. end else begin
  50487. i := 0;
  50488. while i<L do begin
  50489. result := PtrInt(P1[i])-PtrInt(P2[i]);
  50490. if result<>0 then
  50491. exit else
  50492. inc(i);
  50493. end;
  50494. end;
  50495. with SortCompareTmp do
  50496. result := L1-L2;
  50497. end;
  50498. else
  50499. goto zer;
  50500. end else
  50501. plus: result := 1 else // P2=nil
  50502. minus:result := -1 else // P1=nil
  50503. zer:result := 0; // P1=P2
  50504. end;
  50505. {$endif}
  50506. function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char;
  50507. Value: Int64; Oper: TCompareOperator): boolean; overload;
  50508. var V: Int64;
  50509. PB: PByte absolute SBF;
  50510. begin
  50511. result := true;
  50512. if PB<>nil then
  50513. repeat
  50514. case FieldType of
  50515. tftBoolean, tftUInt8:
  50516. V := PB^;
  50517. tftUInt16:
  50518. V := PWord(PB)^;
  50519. tftUInt24:
  50520. // PInteger()^ and $ffffff -> possible GPF on Memory Mapped file
  50521. V := PWord(PB)^+integer(PByteArray(PB)^[2])shl 16;
  50522. tftInt32:
  50523. V := PInteger(PB)^;
  50524. tftInt64:
  50525. V := PInt64(PB)^;
  50526. // some variable-size field value
  50527. tftVarUInt32:
  50528. V := FromVarUInt32(PB);
  50529. tftVarInt32:
  50530. V := FromVarInt32(PB);
  50531. tftVarUInt64:
  50532. V := FromVarUInt64(PB);
  50533. tftVarInt64:
  50534. V := FromVarInt64(PB);
  50535. else V := 0; // makes compiler happy
  50536. end;
  50537. case Oper of
  50538. soEqualTo: if V=Value then exit;
  50539. soNotEqualTo: if V<>Value then exit;
  50540. soLessThan: if V<Value then exit;
  50541. soLessThanOrEqualTo: if V<=Value then exit;
  50542. soGreaterThan: if V>Value then exit;
  50543. soGreaterThanOrEqualTo: if V>=Value then exit;
  50544. else break;
  50545. end;
  50546. // not found: go to next value
  50547. if SBFEnd=nil then
  50548. break; // only one value to be checked
  50549. if FIELD_FIXEDSIZE[FieldType]>0 then
  50550. inc(SBF,FIELD_FIXEDSIZE[FieldType]); // FromVar*() already updated PB/SBF
  50551. until SBF>=SBFEnd;
  50552. result := false; // not found
  50553. end;
  50554. function CompareOperator(SBF, SBFEnd: PUTF8Char;
  50555. Value: double; Oper: TCompareOperator): boolean; overload;
  50556. begin
  50557. result := true;
  50558. if SBF<>nil then
  50559. repeat
  50560. case Oper of
  50561. soEqualTo: if PDouble(SBF)^=Value then exit;
  50562. soNotEqualTo: if PDouble(SBF)^<>Value then exit;
  50563. soLessThan: if PDouble(SBF)^<Value then exit;
  50564. soLessThanOrEqualTo: if PDouble(SBF)^<=Value then exit;
  50565. soGreaterThan: if PDouble(SBF)^>Value then exit;
  50566. soGreaterThanOrEqualTo: if PDouble(SBF)^>=Value then exit;
  50567. else break;
  50568. end;
  50569. // not found: go to next value
  50570. if SBFEnd=nil then
  50571. break; // only one value to be checked
  50572. Inc(SBF,sizeof(Value));
  50573. until SBF>=SBFEnd;
  50574. result := false; // not found
  50575. end;
  50576. function StrCompL(P1,P2: PUTF8Char; L, Default: Integer): PtrInt;
  50577. var i: PtrInt;
  50578. begin
  50579. i := 0;
  50580. repeat
  50581. result := PtrInt(P1[i])-PtrInt(P2[i]);
  50582. if result=0 then begin
  50583. inc(i);
  50584. if i<L then continue else break;
  50585. end;
  50586. exit;
  50587. until false;
  50588. result := Default;
  50589. end;
  50590. function StrCompIL(P1,P2: PUTF8Char; L, Default: Integer): PtrInt;
  50591. var i: PtrInt;
  50592. begin
  50593. i := 0;
  50594. repeat
  50595. if NormToUpperAnsi7[P1[i]]=NormToUpperAnsi7[P2[i]] then begin
  50596. inc(i);
  50597. if i<L then continue else break;
  50598. end;
  50599. result := PtrInt(P1[i])-PtrInt(P2[i]);
  50600. exit;
  50601. until false;
  50602. result := Default;
  50603. end;
  50604. {$ifdef FPC}
  50605. function BooleanNormalize(value: boolean): integer; inline;
  50606. begin
  50607. if value then
  50608. result := 1 else
  50609. result := 0;
  50610. end;
  50611. {$endif}
  50612. var
  50613. /// a temporary buffer, big enough for using the SoundEx algorithm
  50614. SoundExtTmp: array[byte] of AnsiChar;
  50615. function CompareOperator(FieldType: TSynTableFieldType; SBF, SBFEnd: PUTF8Char;
  50616. Value: PUTF8Char; ValueLen: integer; Oper: TCompareOperator;
  50617. CaseSensitive: boolean): boolean; overload;
  50618. var L, Cmp: PtrInt;
  50619. PB: PByte;
  50620. begin
  50621. result := true;
  50622. if SBF<>nil then
  50623. repeat
  50624. // get length of text in the SBF encoded buffer
  50625. if integer(SBF^)<=$7f then begin
  50626. L := integer(SBF^);
  50627. inc(SBF);
  50628. end else begin
  50629. PB := Pointer(SBF);
  50630. L := FromVarUInt32(PB);
  50631. SBF := pointer(PB);
  50632. end;
  50633. // perform comparison: returns nil on match
  50634. case Oper of
  50635. soEqualTo..soGreaterThanOrEqualTo: begin
  50636. Cmp := L-ValueLen;
  50637. if Cmp<0 then
  50638. L := ValueLen;
  50639. if CaseSensitive then
  50640. Cmp := StrCompL(SBF,Value,L,Cmp) else
  50641. Cmp := StrCompIL(SBF,Value,L,Cmp);
  50642. case Oper of
  50643. soEqualTo: if Cmp=0 then exit;
  50644. soNotEqualTo: if Cmp<>0 then exit;
  50645. soLessThan: if Cmp<0 then exit;
  50646. soLessThanOrEqualTo: if Cmp<=0 then exit;
  50647. soGreaterThan: if Cmp>0 then exit;
  50648. soGreaterThanOrEqualTo: if Cmp>=0 then exit;
  50649. end;
  50650. end;
  50651. soBeginWith:
  50652. if ValueLen>=L then
  50653. if CaseSensitive then begin
  50654. if StrCompL(SBF,Value,ValueLen,0)=0 then
  50655. exit;
  50656. end else
  50657. if StrCompIL(SBF,Value,ValueLen,0)=0 then
  50658. exit;
  50659. soContains: begin
  50660. dec(L,ValueLen);
  50661. while L>=0 do begin
  50662. while (L>=0) and not(byte(SBF^) in IsWord) do begin
  50663. dec(L);
  50664. inc(SBF);
  50665. end; // begin of next word reached
  50666. if L<0 then
  50667. Break; // not enough chars to contain the Value
  50668. if CaseSensitive then begin
  50669. if StrCompL(SBF,Value,ValueLen,0)=0 then
  50670. exit;
  50671. end else
  50672. if StrCompIL(SBF,Value,ValueLen,0)=0 then
  50673. exit;
  50674. while (L>=0) and (byte(SBF^) in IsWord) do begin
  50675. dec(L);
  50676. inc(SBF);
  50677. end; // end of word reached
  50678. end;
  50679. if SBFEnd=nil then
  50680. break; // only one value to be checked
  50681. inc(SBF,ValueLen); // custom inc(SBF,L);
  50682. if SBF<SBFEnd then
  50683. continue else break;
  50684. end;
  50685. soSoundsLikeEnglish,
  50686. soSoundsLikeFrench,
  50687. soSoundsLikeSpanish: begin
  50688. if L>high(SoundExtTmp) then
  50689. cmp := high(SoundExtTmp) else
  50690. cmp := L;
  50691. SoundExtTmp[cmp] := #0; // TSynSoundEx expect the buffer to be #0 terminated
  50692. MoveFast(SBF^,SoundExtTmp,cmp);
  50693. case FieldType of
  50694. tftWinAnsi:
  50695. if PSynSoundEx(Value)^.Ansi(SoundExtTmp) then
  50696. exit;
  50697. tftUTF8:
  50698. if PSynSoundEx(Value)^.UTF8(SoundExtTmp) then
  50699. exit;
  50700. else break;
  50701. end;
  50702. end;
  50703. else break;
  50704. end;
  50705. // no match -> go to the end of the SBF buffer
  50706. if SBFEnd=nil then
  50707. exit; // only one value to be checked
  50708. inc(SBF,L);
  50709. if SBF>=SBFEnd then
  50710. break;
  50711. until false;
  50712. end;
  50713. { TSynValidateTableUniqueField }
  50714. function TSynValidateTableUniqueField.Process(aFieldIndex: integer;
  50715. const Value: RawUTF8; var ErrorMsg: string): boolean;
  50716. var S: TSBFString;
  50717. begin
  50718. result := false;
  50719. if (self=nil) or (Value='') or (ProcessField=nil) then
  50720. exit; // void field can't be unique
  50721. if not (tfoIndex in ProcessField.Options) then
  50722. exit; // index should be always created by TSynTable.AfterFieldModif
  50723. S := ProcessField.SBFFromRawUTF8(Value);
  50724. if S='' then
  50725. exit; // void field can't be unique
  50726. if ProcessField.OrderedIndexFindAdd(Pointer(S))>=0 then
  50727. // there is some place to insert the Value -> not existing yet -> OK
  50728. result := true else begin
  50729. // RecordIndex=-1 in case of adding, or the physical index of the updated record
  50730. if (ProcessRecordIndex>=0) and
  50731. (ProcessField.OrderedIndex[ProcessField.OrderedIndexFind(Pointer(S))]=
  50732. ProcessRecordIndex) then
  50733. // allow update of the record
  50734. result := true else
  50735. // found a dupplicated value
  50736. ErrorMsg := sValidationFieldDuplicate;
  50737. end;
  50738. end;
  50739. { TSynTableStatement }
  50740. function IsRowID(FieldName: PUTF8Char): boolean;
  50741. begin
  50742. if FieldName=nil then
  50743. result := false else
  50744. result := (PInteger(FieldName)^ and $ffdfdf=ord('I')+ord('D')shl 8) or
  50745. ((PIntegerArray(FieldName)^[0] and $dfdfdfdf=
  50746. ord('R')+ord('O')shl 8+ord('W')shl 16+ord('I')shl 24) and
  50747. (PIntegerArray(FieldName)^[1] and $ffdf=ord('D')));
  50748. end;
  50749. function IsRowID(FieldName: PUTF8Char; FieldLen: integer): boolean;
  50750. begin
  50751. case FieldLen of
  50752. 2: result :=
  50753. PWord(FieldName)^ and $dfdf=ord('I')+ord('D')shl 8;
  50754. 5: result :=
  50755. (PInteger(FieldName)^ and $dfdfdfdf=
  50756. ord('R')+ord('O')shl 8+ord('W')shl 16+ord('I')shl 24) and
  50757. (ord(FieldName[4]) and $df=ord('D'));
  50758. else result := false;
  50759. end;
  50760. end;
  50761. function IsRowIDShort(const FieldName: shortstring): boolean;
  50762. begin
  50763. result :=
  50764. (PInteger(@FieldName)^ and $DFDFFF=
  50765. 2+ord('I')shl 8+ord('D')shl 16) or
  50766. ((PIntegerArray(@FieldName)^[0] and $dfdfdfff=
  50767. 5+ord('R')shl 8+ord('O')shl 16+ord('W')shl 24) and
  50768. (PIntegerArray(@FieldName)^[1] and $dfdf=
  50769. ord('I')+ord('D')shl 8));
  50770. end;
  50771. function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean;
  50772. var B: PUTF8Char;
  50773. begin
  50774. while P^ in [#1..' ',';'] do inc(P);
  50775. B := P;
  50776. while ord(P^) in IsIdentifier do inc(P); // go to end of field name
  50777. SetRawUTF8(Prop,B,P-B);
  50778. while P^ in [#1..' ',';'] do inc(P);
  50779. result := Prop<>'';
  50780. end;
  50781. constructor TSynTableStatement.Create(const SQL: RawUTF8;
  50782. GetFieldIndex: TSynTableFieldIndex; SimpleFieldsBits: TSQLFieldBits;
  50783. FieldProp: TSynTableFieldProperties);
  50784. var Prop, whereBefore: RawUTF8;
  50785. P, B: PUTF8Char;
  50786. ndx,err,len,selectCount,whereCount: integer;
  50787. whereWithOR,whereNotClause: boolean;
  50788. function GetPropIndex: integer;
  50789. begin
  50790. if not GetNextFieldProp(P,Prop) then
  50791. result := -1 else
  50792. if IsRowID(pointer(Prop)) then
  50793. result := 0 else begin // 0 = ID field
  50794. result := GetFieldIndex(Prop);
  50795. if result>=0 then // -1 = no valid field name
  50796. inc(result); // otherwise: PropertyIndex+1
  50797. end;
  50798. end;
  50799. function SetFields: boolean;
  50800. var select: TSynTableStatementSelect;
  50801. begin
  50802. result := false;
  50803. FillcharFast(select,sizeof(select),0);
  50804. select.Field := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1
  50805. if select.Field<0 then begin
  50806. if P^<>'(' then // Field not found -> try function(field)
  50807. exit;
  50808. P := GotoNextNotSpace(P+1);
  50809. select.FunctionName := Prop;
  50810. inc(fSelectFunctionCount);
  50811. if IdemPropNameU(Prop,'COUNT') and (P^='*') then begin
  50812. select.Field := 0; // count(*) -> count(ID)
  50813. select.FunctionKnown := funcCountStar;
  50814. P := GotoNextNotSpace(P+1);
  50815. end else begin
  50816. if IdemPropNameU(Prop,'DISTINCT') then
  50817. select.FunctionKnown := funcDistinct else
  50818. if IdemPropNameU(Prop,'MAX') then
  50819. select.FunctionKnown := funcMax;
  50820. select.Field := GetPropIndex;
  50821. if select.Field<0 then
  50822. exit;
  50823. end;
  50824. if P^<>')' then
  50825. exit;
  50826. P := GotoNextNotSpace(P+1);
  50827. end;
  50828. if P^ in ['+','-'] then begin
  50829. select.ToBeAdded := GetNextItemInteger(P,' ');
  50830. if select.ToBeAdded=0 then
  50831. exit;
  50832. P := GotoNextNotSpace(P);
  50833. end;
  50834. if IdemPChar(P,'AS ') then begin
  50835. inc(P,3);
  50836. if not GetNextFieldProp(P,select.Alias) then
  50837. exit;
  50838. end;
  50839. SetLength(fSelect,selectCount+1);
  50840. fSelect[selectCount] := select;
  50841. inc(selectCount);
  50842. result := true;
  50843. end;
  50844. function GetWhereValue(var Where: TSynTableStatementWhere): boolean;
  50845. var B: PUTF8Char;
  50846. begin
  50847. result := false;
  50848. P := GotoNextNotSpace(P);
  50849. Where.ValueSQL := P;
  50850. if PWord(P)^=ord(':')+ord('(') shl 8 then
  50851. inc(P,2); // ignore :(...): parameter (no prepared statements here)
  50852. if P^ in ['''','"'] then begin
  50853. // SQL String statement
  50854. P := UnQuoteSQLStringVar(P,Where.Value);
  50855. if P=nil then
  50856. exit; // end of string before end quote -> incorrect
  50857. {$ifndef NOVARIANTS}
  50858. RawUTF8ToVariant(Where.Value,Where.ValueVariant);
  50859. {$endif}
  50860. if FieldProp<>nil then
  50861. // create a SBF formatted version of the WHERE value
  50862. Where.ValueSBF := FieldProp.SBFFromRawUTF8(Where.Value);
  50863. end else
  50864. if (PInteger(P)^ and $DFDFDFDF=NULL_UPP) and (P[4] in [#0..' ',';']) then begin
  50865. // NULL statement
  50866. Where.Value := 'null'; // not void
  50867. {$ifndef NOVARIANTS}
  50868. SetVariantNull(Where.ValueVariant);
  50869. {$endif}
  50870. end else begin
  50871. // numeric statement or 'true' or 'false' (OK for NormalizeValue)
  50872. B := P;
  50873. repeat
  50874. inc(P);
  50875. until P^ in [#0..' ',';',')',','];
  50876. SetString(Where.Value,B,P-B);
  50877. {$ifndef NOVARIANTS}
  50878. Where.ValueVariant := VariantLoadJSON(Where.Value);
  50879. {$endif}
  50880. Where.ValueInteger := GetInteger(pointer(Where.Value),err);
  50881. if FieldProp<>nil then
  50882. if Where.Value<>'?' then
  50883. if (FieldProp.FieldType in FIELD_INTEGER) and (err<>0) then
  50884. // we expect a true INTEGER value here
  50885. Where.Value := '' else
  50886. // create a SBF formatted version of the WHERE value
  50887. Where.ValueSBF := FieldProp.SBFFromRawUTF8(Where.Value);
  50888. end;
  50889. if PWord(P)^=ord(')')+ord(':')shl 8 then
  50890. inc(P,2); // ignore :(...): parameter
  50891. Where.ValueSQLLen := P-Where.ValueSQL;
  50892. P := GotoNextNotSpace(P);
  50893. if (P^=')') and (Where.FunctionName='') then begin
  50894. B := P;
  50895. repeat
  50896. inc(P);
  50897. until not (P^ in [#1..' ',')']);
  50898. while P[-1]=' ' do dec(P); // trim right space
  50899. SetString(Where.ParenthesisAfter,B,P-B);
  50900. P := GotoNextNotSpace(P);
  50901. end;
  50902. result := true;
  50903. end;
  50904. {$ifndef NOVARIANTS}
  50905. function GetWhereValues(var Where: TSynTableStatementWhere): boolean;
  50906. var v: TSynTableStatementWhereDynArray;
  50907. n, w: integer;
  50908. tmp: RawUTF8;
  50909. begin
  50910. result := false;
  50911. if Where.ValueSQLLen<2 then
  50912. exit;
  50913. SetString(tmp,PAnsiChar(Where.ValueSQL)+1,Where.ValueSQLLen-2);
  50914. P := pointer(tmp); // parse again the IN (...,...,... ) expression
  50915. n := 0;
  50916. try
  50917. repeat
  50918. if n=length(v) then
  50919. SetLength(v,n+n shr 3+8);
  50920. if not GetWhereValue(v[n]) then
  50921. exit;
  50922. inc(n);
  50923. if P^=#0 then
  50924. break;
  50925. if P^<>',' then
  50926. exit;
  50927. inc(P);
  50928. until false;
  50929. finally
  50930. P := Where.ValueSQL+Where.ValueSQLLen; // continue parsing as usual
  50931. end;
  50932. with TDocVariantData(Where.ValueVariant) do begin
  50933. InitFast(n,dvArray);
  50934. for w := 0 to n-1 do
  50935. AddItem(v[w].ValueVariant);
  50936. Where.Value := ToJSON;
  50937. end;
  50938. result := true;
  50939. end;
  50940. {$endif}
  50941. function GetWhereExpression(FieldIndex: integer; var Where: TSynTableStatementWhere): boolean;
  50942. begin
  50943. result := false;
  50944. Where.ParenthesisBefore := whereBefore;
  50945. Where.JoinedOR := whereWithOR;
  50946. Where.NotClause := whereNotClause;
  50947. Where.Field := FieldIndex; // 0 = ID, otherwise PropertyIndex+1
  50948. case P^ of
  50949. '=': Where.Operator := opEqualTo;
  50950. '>': if P[1]='=' then begin
  50951. inc(P);
  50952. Where.Operator := opGreaterThanOrEqualTo;
  50953. end else
  50954. Where.Operator := opGreaterThan;
  50955. '<': case P[1] of
  50956. '=': begin
  50957. inc(P);
  50958. Where.Operator := opLessThanOrEqualTo;
  50959. end;
  50960. '>': begin
  50961. inc(P);
  50962. Where.Operator := opNotEqualTo;
  50963. end;
  50964. else
  50965. Where.Operator := opLessThan;
  50966. end;
  50967. 'i','I':
  50968. case P[1] of
  50969. 's','S': begin
  50970. P := GotoNextNotSpace(P+2);
  50971. if IdemPChar(P,'NULL') then begin
  50972. Where.Value := 'null';
  50973. Where.Operator := opIsNull;
  50974. Where.ValueSQL := P;
  50975. Where.ValueSQLLen := 4;
  50976. {$ifndef NOVARIANTS}
  50977. TVarData(Where.ValueVariant).VType := varNull;
  50978. {$endif}
  50979. inc(P,4);
  50980. result := true;
  50981. end else
  50982. if IdemPChar(P,'NOT NULL') then begin
  50983. Where.Value := 'not null';
  50984. Where.Operator := opIsNotNull;
  50985. Where.ValueSQL := P;
  50986. Where.ValueSQLLen := 8;
  50987. {$ifndef NOVARIANTS}
  50988. TVarData(Where.ValueVariant).VType := varNull;
  50989. {$endif}
  50990. inc(P,8);
  50991. result := true; // leave ValueVariant=unassigned
  50992. end;
  50993. exit;
  50994. end;
  50995. {$ifndef NOVARIANTS}
  50996. 'n','N': begin
  50997. Where.Operator := opIn;
  50998. P := GotoNextNotSpace(P+2);
  50999. if P^<>'(' then
  51000. exit; // incorrect SQL statement
  51001. B := P; // get the IN() clause as JSON - without :(...): by now
  51002. inc(P);
  51003. while P^<>')' do
  51004. if P^=#0 then
  51005. exit else
  51006. inc(P);
  51007. inc(P);
  51008. SetString(Where.Value,PAnsiChar(B),P-B);
  51009. Where.ValueSQL := B;
  51010. Where.ValueSQLLen := P-B;
  51011. result := GetWhereValues(Where);
  51012. exit;
  51013. end;
  51014. {$endif}
  51015. end; // 'i','I':
  51016. 'l','L':
  51017. if IdemPChar(P+1,'IKE') then begin
  51018. inc(P,3);
  51019. Where.Operator := opLike;
  51020. end else
  51021. exit;
  51022. else exit; // unknown operator
  51023. end;
  51024. // we got 'WHERE FieldName operator ' -> handle value
  51025. inc(P);
  51026. result := GetWhereValue(Where);
  51027. end;
  51028. label lim,lim2;
  51029. begin
  51030. P := pointer(SQL);
  51031. if (P=nil) or (self=nil) then
  51032. exit; // avoid GPF
  51033. P := GotoNextNotSpace(P); // trim left
  51034. if not IdemPChar(P,'SELECT ') then
  51035. exit else // handle only SELECT statement
  51036. inc(P,7);
  51037. // 1. get SELECT clause: set bits in Fields from CSV field IDs in SQL
  51038. selectCount := 0;
  51039. P := GotoNextNotSpace(P); // trim left
  51040. if P^=#0 then
  51041. exit; // no SQL statement
  51042. if P^='*' then begin // all simple (not TSQLRawBlob/TSQLRecordMany) fields
  51043. inc(P);
  51044. SetLength(fSelect,GetBitsCount(SimpleFieldsBits,MAX_SQLFIELDS)+1);
  51045. selectCount := 1; // Select[0].Field := 0 -> ID
  51046. for ndx := 0 to MAX_SQLFIELDS-1 do
  51047. if ndx in SimpleFieldsBits then begin
  51048. fSelect[selectCount].Field := ndx+1;
  51049. inc(selectCount);
  51050. end;
  51051. GetNextFieldProp(P,Prop);
  51052. end else
  51053. if not SetFields then
  51054. exit else // we need at least one field name
  51055. if P^<>',' then
  51056. GetNextFieldProp(P,Prop) else
  51057. repeat
  51058. while P^ in [',',#1..' '] do inc(P); // trim left
  51059. until not SetFields; // add other CSV field names
  51060. // 2. get FROM clause
  51061. if not IdemPropNameU(Prop,'FROM') then exit; // incorrect SQL statement
  51062. GetNextFieldProp(P,Prop);
  51063. fTableName := Prop;
  51064. // 3. get WHERE clause
  51065. whereCount := 0;
  51066. whereWithOR := false;
  51067. whereNotClause := false;
  51068. whereBefore := '';
  51069. GetNextFieldProp(P,Prop);
  51070. if IdemPropNameU(Prop,'WHERE') then begin
  51071. repeat
  51072. B := P;
  51073. if P^='(' then begin
  51074. fWhereHasParenthesis := true;
  51075. repeat
  51076. inc(P);
  51077. until not (P^ in [#1..' ','(']);
  51078. while P[-1]=' ' do dec(P); // trim right space
  51079. SetString(whereBefore,B,P-B);
  51080. B := P;
  51081. end;
  51082. ndx := GetPropIndex;
  51083. if ndx<0 then begin
  51084. if IdemPropNameU(Prop,'NOT') then begin
  51085. whereNotClause := true;
  51086. continue;
  51087. end;
  51088. if P^='(' then begin
  51089. inc(P);
  51090. SetLength(fWhere,whereCount+1);
  51091. with fWhere[whereCount] do begin
  51092. ParenthesisBefore := whereBefore;
  51093. JoinedOR := whereWithOR;
  51094. NotClause := whereNotClause;
  51095. FunctionName := UpperCase(Prop);
  51096. // Byte/Word/Integer/Cardinal/Int64/CurrencyDynArrayContains(BlobField,I64)
  51097. len := length(Prop);
  51098. if (len>16) and
  51099. IdemPropName('DynArrayContains',PUTF8Char(@PByteArray(Prop)[len-16]),16) then
  51100. Operator := opContains else
  51101. Operator := opFunction;
  51102. B := P;
  51103. Field := GetPropIndex;
  51104. if Field<0 then
  51105. P := B else
  51106. if P^<>',' then
  51107. break else
  51108. P := GotoNextNotSpace(P+1);
  51109. if (P^=')') or
  51110. (GetWhereValue(fWhere[whereCount]) and (P^=')')) then begin
  51111. inc(P);
  51112. break;
  51113. end;
  51114. end;
  51115. end;
  51116. P := B;
  51117. break;
  51118. end;
  51119. SetLength(fWhere,whereCount+1);
  51120. if not GetWhereExpression(ndx,fWhere[whereCount]) then
  51121. exit; // invalid SQL statement
  51122. inc(whereCount);
  51123. GetNextFieldProp(P,Prop);
  51124. if IdemPropNameU(Prop,'OR') then
  51125. whereWithOR := true else
  51126. if IdemPropNameU(Prop,'AND') then
  51127. whereWithOR := false else
  51128. goto lim2;
  51129. whereNotClause := false;
  51130. whereBefore := '';
  51131. until false;
  51132. // 4. get optional LIMIT/OFFSET/ORDER clause
  51133. lim:P := GotoNextNotSpace(P);
  51134. while (P<>nil) and not(P^ in [#0,';']) do begin
  51135. GetNextFieldProp(P,Prop);
  51136. lim2: if IdemPropNameU(Prop,'LIMIT') then
  51137. fLimit := GetNextItemCardinal(P,' ') else
  51138. if IdemPropNameU(Prop,'OFFSET') then
  51139. fOffset := GetNextItemCardinal(P,' ') else
  51140. if IdemPropNameU(Prop,'ORDER') then begin
  51141. GetNextFieldProp(P,Prop);
  51142. if IdemPropNameU(Prop,'BY') then begin
  51143. repeat
  51144. ndx := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1
  51145. if ndx<0 then
  51146. exit; // incorrect SQL statement
  51147. AddFieldIndex(fOrderByField,ndx);
  51148. if P^<>',' then begin // check ORDER BY ... ASC/DESC
  51149. B := P;
  51150. if GetNextFieldProp(P,Prop) then
  51151. if IdemPropNameU(Prop,'DESC') then
  51152. fOrderByDesc := true else
  51153. if not IdemPropNameU(Prop,'ASC') then
  51154. P := B;
  51155. break;
  51156. end;
  51157. P := GotoNextNotSpace(P+1);
  51158. until P^ in [#0,';'];
  51159. end else
  51160. exit; // incorrect SQL statement
  51161. end else
  51162. if IdemPropNameU(Prop,'GROUP') then begin
  51163. GetNextFieldProp(P,Prop);
  51164. if IdemPropNameU(Prop,'BY') then begin
  51165. repeat
  51166. ndx := GetPropIndex; // 0 = ID, otherwise PropertyIndex+1
  51167. if ndx<0 then
  51168. exit; // incorrect SQL statement
  51169. AddFieldIndex(fGroupByField,ndx);
  51170. if P^<>',' then
  51171. break;
  51172. P := GotoNextNotSpace(P+1);
  51173. until P^ in [#0,';'];
  51174. end else
  51175. exit; // incorrect SQL statement
  51176. end else
  51177. if Prop<>'' then
  51178. exit else // incorrect SQL statement
  51179. break; // reached the end of the statement
  51180. end;
  51181. end else
  51182. if Prop<>'' then
  51183. goto lim2; // handle LIMIT OFFSET ORDER
  51184. fSQLStatement := SQL; // make a private copy e.g. for Where[].ValueSQL
  51185. end;
  51186. procedure TSynTableStatement.SelectFieldBits(var Fields: TSQLFieldBits; var withID: boolean);
  51187. var i: integer;
  51188. begin
  51189. FillcharFast(Fields,sizeof(Fields),0);
  51190. withID := false;
  51191. for i := 0 to Length(Select)-1 do
  51192. if Select[i].Field=0 then
  51193. withID := true else
  51194. include(Fields,Select[i].Field-1);
  51195. end;
  51196. {$ifndef DELPHI5OROLDER}
  51197. { TSynTableData }
  51198. procedure TSynTableData.CheckVTableInitialized;
  51199. begin
  51200. if VTable=nil then
  51201. raise ETableDataException.Create('TSynTableData non initialized');
  51202. end;
  51203. {$ifndef NOVARIANTS}
  51204. function TSynTableData.GetFieldValue(const FieldName: RawUTF8): Variant;
  51205. begin
  51206. GetFieldVariant(FieldName,result);
  51207. end;
  51208. procedure TSynTableData.GetFieldVariant(const FieldName: RawUTF8; var result: Variant);
  51209. var aField: TSynTableFieldProperties;
  51210. begin
  51211. if IsRowID(Pointer(FieldName)) then
  51212. result := VID else begin
  51213. CheckVTableInitialized;
  51214. aField := VTable.FieldFromName[FieldName];
  51215. if aField=nil then
  51216. raise ETableDataException.CreateUTF8('Unknown % property',[FieldName]) else
  51217. aField.GetVariant(VTable.GetData(pointer(VValue),aField),result);
  51218. end;
  51219. end;
  51220. function TSynTableData.GetFieldValue(aField: TSynTableFieldProperties): Variant;
  51221. begin
  51222. CheckVTableInitialized;
  51223. aField.GetVariant(VTable.GetData(pointer(VValue),aField),result);
  51224. end;
  51225. {$endif NOVARIANTS}
  51226. procedure TSynTableData.FilterSBFValue;
  51227. begin
  51228. CheckVTableInitialized;
  51229. VTable.Filter(VValue);
  51230. end;
  51231. function TSynTableData.GetFieldSBFValue(aField: TSynTableFieldProperties): TSBFString;
  51232. var FieldBuffer: PAnsiChar;
  51233. begin
  51234. CheckVTableInitialized;
  51235. FieldBuffer := VTable.GetData(pointer(VValue),aField);
  51236. SetString(Result,FieldBuffer,aField.GetLength(FieldBuffer));
  51237. end;
  51238. procedure TSynTableData.Init(aTable: TSynTable; aID: Integer);
  51239. begin
  51240. VTable := aTable;
  51241. VID := aID;
  51242. VValue := VTable.DefaultRecordData;
  51243. {$ifdef UNICODE}FillcharFast(Filler,sizeof(Filler),0);{$endif}
  51244. end;
  51245. procedure TSynTableData.Init(aTable: TSynTable; aID: Integer;
  51246. RecordBuffer: pointer; RecordBufferLen: integer);
  51247. begin
  51248. VTable := aTable;
  51249. if (RecordBufferLen=0) or (RecordBuffer=nil) then begin
  51250. VID := 0;
  51251. VValue := VTable.DefaultRecordData;
  51252. end else begin
  51253. VID := aID;
  51254. SetString(VValue,PAnsiChar(RecordBuffer),RecordBufferLen);
  51255. end;
  51256. end;
  51257. {$ifndef NOVARIANTS}
  51258. procedure TSynTableData.SetFieldValue(const FieldName: RawUTF8;
  51259. const Value: Variant);
  51260. var F: TSynTableFieldProperties;
  51261. begin
  51262. CheckVTableInitialized;
  51263. if IsRowID(Pointer(FieldName)) then
  51264. VID := Value else begin
  51265. F := VTable.FieldFromName[FieldName];
  51266. if F=nil then
  51267. raise ETableDataException.CreateUTF8('Unknown % property',[FieldName]) else
  51268. SetFieldValue(F,Value);
  51269. end;
  51270. end;
  51271. procedure TSynTableData.SetFieldValue(aField: TSynTableFieldProperties; const Value: Variant);
  51272. begin
  51273. SetFieldSBFValue(aField,aField.SBF(Value));
  51274. end;
  51275. {$endif}
  51276. procedure TSynTableData.SetFieldSBFValue(aField: TSynTableFieldProperties;
  51277. const Value: TSBFString);
  51278. var NewValue: TSBFString;
  51279. begin
  51280. CheckVTableInitialized;
  51281. if (aField.FieldSize>0) and (VValue<>'') then begin
  51282. // fixed size content: fast in-place update
  51283. MoveFast(pointer(Value)^,VValue[aField.Offset+1],aField.FieldSize)
  51284. // VValue[F.Offset+1] above will call UniqueString(VValue), even under FPC
  51285. end else begin
  51286. // variable-length update
  51287. VTable.UpdateFieldData(pointer(VValue),length(VValue),
  51288. aField.FieldNumber,NewValue,Value);
  51289. VValue := NewValue;
  51290. end;
  51291. end;
  51292. function TSynTableData.ValidateSBFValue(RecordIndex: integer): string;
  51293. begin
  51294. CheckVTableInitialized;
  51295. Result := VTable.Validate(Pointer(VValue),RecordIndex);
  51296. end;
  51297. {$endif DELPHI5OROLDER}
  51298. type
  51299. TSynLZHead = packed record
  51300. Magic: cardinal;
  51301. CompressedSize: integer;
  51302. HashCompressed: cardinal;
  51303. UnCompressedSize: integer;
  51304. HashUncompressed: cardinal;
  51305. end;
  51306. PSynLZHead = ^TSynLZHead;
  51307. TSynLZTrailer = packed record
  51308. HeaderRelativeOffset: cardinal;
  51309. Magic: cardinal;
  51310. end;
  51311. PSynLZTrailer = ^TSynLZTrailer;
  51312. function StreamSynLZComputeLen(P: PAnsiChar; Len, aMagic: cardinal): integer;
  51313. begin
  51314. inc(P,Len);
  51315. with PSynLZTrailer(P-sizeof(TSynLZTrailer))^ do
  51316. if (Magic=aMagic) and (HeaderRelativeOffset<Len) and
  51317. (PSynLZHead(P-HeaderRelativeOffset)^.Magic=aMagic) then
  51318. // trim existing content
  51319. result := Len-HeaderRelativeOffset else
  51320. result := Len;
  51321. end;
  51322. function CompressSynLZ(var DataRawByteString; Compress: boolean): AnsiString;
  51323. var DataLen, len: integer;
  51324. P: PAnsiChar;
  51325. Data: RawByteString absolute DataRawByteString;
  51326. begin
  51327. DataLen := length(Data);
  51328. if DataLen<>0 then // '' is compressed and uncompressed to ''
  51329. if Compress then begin
  51330. len := SynLZcompressdestlen(DataLen)+8;
  51331. SetString(result,nil,len);
  51332. P := pointer(result);
  51333. PCardinal(P)^ := Hash32(pointer(Data),DataLen);
  51334. len := SynLZcompress1(pointer(Data),DataLen,P+8);
  51335. PCardinal(P+4)^ := Hash32(pointer(P+8),len);
  51336. SetString(Data,P,len+8);
  51337. end else begin
  51338. result := '';
  51339. P := pointer(Data);
  51340. if (DataLen<=8) or (Hash32(pointer(P+8),DataLen-8)<>PCardinal(P+4)^) then
  51341. exit;
  51342. len := SynLZdecompressdestlen(P+8);
  51343. SetLength(result,len);
  51344. if (len<>0) and ((SynLZDecompress1(P+8,DataLen-8,pointer(result))<>len) or
  51345. (Hash32(pointer(result),len)<>PCardinal(P)^)) then begin
  51346. result := '';
  51347. exit;
  51348. end else
  51349. SetString(Data,PAnsiChar(pointer(result)),len);
  51350. end;
  51351. result := 'synlz';
  51352. end;
  51353. function StreamSynLZ(Source: TCustomMemoryStream; Dest: TStream; Magic: cardinal): integer;
  51354. var DataLen: integer;
  51355. S,D: pointer;
  51356. Head: TSynLZHead;
  51357. Trailer: TSynLZTrailer;
  51358. tmp: TSynTempBuffer;
  51359. begin
  51360. if Dest=nil then begin
  51361. result := 0;
  51362. exit;
  51363. end;
  51364. if Source<>nil then begin
  51365. S := Source.Memory;
  51366. DataLen := Source.Size;
  51367. end else begin
  51368. S := nil;
  51369. DataLen := 0;
  51370. end;
  51371. tmp.Init(SynLZcompressdestlen(DataLen));
  51372. try
  51373. Head.Magic := Magic;
  51374. Head.UnCompressedSize := DataLen;
  51375. Head.HashUncompressed := Hash32(S,DataLen);
  51376. result := SynLZcompress1(S,DataLen,tmp.buf);
  51377. if result>tmp.len then
  51378. raise ESynException.Create('StreamLZ: SynLZ compression overflow');
  51379. if result>DataLen then begin
  51380. result := DataLen; // compression not worth it
  51381. D := S;
  51382. end else
  51383. D := tmp.buf;
  51384. Head.CompressedSize := result;
  51385. Head.HashCompressed := Hash32(D,result);
  51386. Dest.Write(Head,sizeof(Head));
  51387. Dest.Write(D^,Head.CompressedSize);
  51388. Trailer.HeaderRelativeOffset := result+(sizeof(Head)+sizeof(Trailer));
  51389. Trailer.Magic := Magic;
  51390. Dest.Write(Trailer,sizeof(Trailer));
  51391. result := Head.CompressedSize+(sizeof(Head)+sizeof(Trailer));
  51392. finally
  51393. tmp.Done;
  51394. end;
  51395. end;
  51396. function StreamSynLZ(Source: TCustomMemoryStream; const DestFile: TFileName;
  51397. Magic: cardinal): integer;
  51398. var F: TFileStream;
  51399. begin
  51400. F := TFileStream.Create(DestFile,fmCreate);
  51401. try
  51402. result := StreamSynLZ(Source,F,Magic);
  51403. finally
  51404. F.Free;
  51405. end;
  51406. end;
  51407. const
  51408. /// 128 MB default buffer
  51409. FILESYNLZ_BLOCKSIZE = 128*1024*1024;
  51410. function FileSynLZ(const Source, Dest: TFileName; Magic: Cardinal): boolean;
  51411. var src,dst: RawByteString;
  51412. S,D: TFileStream;
  51413. Head: TSynLZHead;
  51414. Count: Int64;
  51415. begin
  51416. result := false;
  51417. if FileExists(Source) then
  51418. try
  51419. S := FileStreamSequentialRead(Source);
  51420. try
  51421. DeleteFile(Dest);
  51422. D := TFileStream.Create(Dest,fmCreate);
  51423. try
  51424. Head.Magic := Magic;
  51425. Count := S.Size;
  51426. while Count>0 do begin
  51427. if Count>FILESYNLZ_BLOCKSIZE then
  51428. Head.UnCompressedSize := FILESYNLZ_BLOCKSIZE else
  51429. Head.UnCompressedSize := Count;
  51430. if src='' then
  51431. SetString(src,nil,Head.UnCompressedSize);
  51432. if dst='' then
  51433. SetString(dst,nil,SynLZcompressdestlen(Head.UnCompressedSize));
  51434. S.Read(pointer(src)^,Head.UnCompressedSize);
  51435. Head.HashUncompressed := Hash32(pointer(src),Head.UnCompressedSize);
  51436. Head.CompressedSize :=
  51437. SynLZcompress1(pointer(src),Head.UnCompressedSize,pointer(dst));
  51438. Head.HashCompressed := Hash32(pointer(dst),Head.CompressedSize);
  51439. if (D.Write(Head,sizeof(Head))<>sizeof(Head)) or
  51440. (D.Write(pointer(dst)^,Head.CompressedSize)<>Head.CompressedSize) then
  51441. exit;
  51442. dec(Count,Head.UnCompressedSize);
  51443. end;
  51444. finally
  51445. D.Free;
  51446. end;
  51447. result := FileSetDateFrom(Dest,S.Handle);
  51448. finally
  51449. S.Free;
  51450. end;
  51451. except
  51452. on Exception do
  51453. result := false;
  51454. end;
  51455. end;
  51456. function FileUnSynLZ(const Source, Dest: TFileName; Magic: Cardinal): boolean;
  51457. var src,dst: RawByteString;
  51458. S,D: TFileStream;
  51459. Count: Int64;
  51460. Head: TSynLZHead;
  51461. begin
  51462. result := false;
  51463. if FileExists(Source) then
  51464. try
  51465. S := FileStreamSequentialRead(Source);
  51466. try
  51467. DeleteFile(Dest);
  51468. D := TFileStream.Create(Dest,fmCreate);
  51469. try
  51470. Count := S.Size;
  51471. while Count>0 do begin
  51472. if S.Read(Head,sizeof(Head))<>Sizeof(Head) then
  51473. exit;
  51474. dec(Count,sizeof(Head));
  51475. if (Head.Magic<>Magic) or
  51476. (Head.CompressedSize>Count) then
  51477. exit;
  51478. if Head.CompressedSize>length(src) then
  51479. SetString(src,nil,Head.CompressedSize);
  51480. if S.Read(pointer(src)^,Head.CompressedSize)<>Head.CompressedSize then
  51481. exit;
  51482. dec(Count,Head.CompressedSize);
  51483. if (Hash32(pointer(src),Head.CompressedSize)<>Head.HashCompressed) or
  51484. (SynLZdecompressdestlen(pointer(src))<>Head.UnCompressedSize) then
  51485. exit;
  51486. if Head.UnCompressedSize>length(dst) then
  51487. SetString(dst,nil,Head.UnCompressedSize);
  51488. if (SynLZDecompress1(pointer(src),Head.CompressedSize,pointer(dst))<>Head.UnCompressedSize) or
  51489. (Hash32(pointer(dst),Head.UnCompressedSize)<>Head.HashUncompressed) then
  51490. exit;
  51491. if D.Write(pointer(dst)^,Head.UncompressedSize)<>Head.UncompressedSize then
  51492. exit;
  51493. end;
  51494. finally
  51495. D.Free;
  51496. end;
  51497. result := FileSetDateFrom(Dest,S.Handle);
  51498. finally
  51499. S.Free;
  51500. end;
  51501. except
  51502. on Exception do
  51503. result := false;
  51504. end;
  51505. end;
  51506. function FileIsZynLZ(const Name: TFileName; Magic: Cardinal): boolean;
  51507. var S: TFileStream;
  51508. Head: TSynLZHead;
  51509. begin
  51510. result := false;
  51511. if FileExists(Name) then
  51512. try
  51513. S := TFileStream.Create(Name,fmOpenRead or fmShareDenyNone);
  51514. try
  51515. if S.Read(Head,sizeof(Head))=Sizeof(Head) then
  51516. if Head.Magic=Magic then
  51517. if Head.CompressedSize+SizeOf(Head)=S.Size then
  51518. result := true;
  51519. finally
  51520. S.Free;
  51521. end;
  51522. except
  51523. on Exception do
  51524. result := false;
  51525. end;
  51526. end;
  51527. function StreamUnSynLZ(const Source: TFileName; Magic: cardinal): TMemoryStream; overload;
  51528. var S: TStream;
  51529. begin
  51530. try
  51531. S := TSynMemoryStreamMapped.Create(Source);
  51532. try
  51533. result := StreamUnSynLZ(S,Magic);
  51534. finally
  51535. S.Free;
  51536. end;
  51537. except
  51538. on E: Exception do
  51539. result := nil;
  51540. end;
  51541. end;
  51542. function StreamUnSynLZ(Source: TStream; Magic: cardinal): TMemoryStream;
  51543. var S,D: PAnsiChar;
  51544. sourcePosition,resultSize: PtrInt;
  51545. sourceSize: Int64;
  51546. Head: TSynLZHead;
  51547. Trailer: TSynLZTrailer;
  51548. buf: RawByteString;
  51549. stored: boolean;
  51550. begin
  51551. result := nil;
  51552. if Source=nil then
  51553. exit;
  51554. sourceSize := Source.Size;
  51555. {$ifndef CPU64}
  51556. if sourceSize>maxInt then
  51557. exit; // result TMemoryStream should stay in memory!
  51558. {$endif}
  51559. sourcePosition := Source.Position;
  51560. if sourceSize-sourcePosition<sizeof(head) then
  51561. exit;
  51562. resultSize := 0;
  51563. repeat
  51564. Source.Read(Head,sizeof(Head));
  51565. if Head.Magic<>Magic then begin
  51566. // Source not positioned as expected -> try from the end
  51567. Source.Position := sourceSize-sizeof(Trailer);
  51568. Source.Read(Trailer,sizeof(Trailer));
  51569. if Trailer.Magic<>Magic then
  51570. exit;
  51571. sourcePosition := sourceSize-Trailer.HeaderRelativeOffset;
  51572. Source.Position := sourcePosition;
  51573. Source.Read(Head,sizeof(Head));
  51574. if Head.Magic<>Magic then
  51575. exit;
  51576. end;
  51577. inc(sourcePosition,sizeof(Head));
  51578. if sourcePosition+Head.CompressedSize>sourceSize then
  51579. exit;
  51580. if Source.InheritsFrom(TCustomMemoryStream) then begin
  51581. S := PAnsiChar(TCustomMemoryStream(Source).Memory)+PtrUInt(sourcePosition);
  51582. Source.Seek(Head.CompressedSize,soFromCurrent);
  51583. end else begin
  51584. if Head.CompressedSize>length(Buf) then
  51585. SetString(Buf,nil,Head.CompressedSize);
  51586. S := pointer(Buf);
  51587. Source.Read(S^,Head.CompressedSize);
  51588. end;
  51589. inc(sourcePosition,Head.CompressedSize);
  51590. if (Source.Read(Trailer,sizeof(Trailer))<>sizeof(Trailer)) or
  51591. (Trailer.Magic<>Magic) then
  51592. // trailer not available in old .synlz layout, or in FileSynLZ multiblocks
  51593. Source.Position := sourcePosition else
  51594. sourceSize := 0; // should be monoblock
  51595. // Source stream will now point after all data
  51596. stored := (Head.CompressedSize=Head.UnCompressedSize) and
  51597. (Head.HashCompressed=Head.HashUncompressed);
  51598. if not stored then
  51599. if SynLZdecompressdestlen(S)<>Head.UnCompressedSize then
  51600. exit;
  51601. if Hash32(S,Head.CompressedSize)<>Head.HashCompressed then
  51602. exit;
  51603. if result=nil then
  51604. result := THeapMemoryStream.Create else begin
  51605. {$ifndef CPU64}
  51606. if Int64(resultSize)+Head.UnCompressedSize>maxInt then begin
  51607. FreeAndNil(result); // result TMemoryStream should stay in memory!
  51608. break;
  51609. end;
  51610. {$endif}
  51611. end;
  51612. result.Size := resultSize+Head.UnCompressedSize;
  51613. D := PAnsiChar(result.Memory)+resultSize;
  51614. inc(resultSize,Head.UnCompressedSize);
  51615. if stored then
  51616. MoveFast(S^,D^,Head.CompressedSize) else
  51617. if SynLZDecompress1(S,Head.CompressedSize,D)<>Head.UnCompressedSize then
  51618. FreeAndNil(result) else
  51619. if Hash32(D,Head.UnCompressedSize)<>Head.HashUncompressed then
  51620. FreeAndNil(result);
  51621. until (result=nil) or (sourcePosition>=sourceSize);
  51622. end;
  51623. const
  51624. SYNLZCOMPRESS_STORED = #0;
  51625. SYNLZCOMPRESS_SYNLZ = #1;
  51626. function SynLZCompress(const Data: RawByteString; CompressionSizeTrigger: integer): RawByteString;
  51627. begin
  51628. SynLZCompress(pointer(Data),length(Data),result,CompressionSizeTrigger);
  51629. end;
  51630. procedure SynLZCompress(P: PAnsiChar; PLen: integer; out Result: RawByteString;
  51631. CompressionSizeTrigger: integer);
  51632. var len: integer;
  51633. R: PAnsiChar;
  51634. crc: cardinal;
  51635. tmp: array[0..4095] of AnsiChar; // resize Result instead of TSynTempBuffer
  51636. begin
  51637. if PLen=0 then
  51638. exit;
  51639. crc := crc32c(0,P,PLen);
  51640. if PLen<CompressionSizeTrigger then begin
  51641. SetString(result,nil,PLen+9);
  51642. R := pointer(result);
  51643. PCardinal(R)^ := crc;
  51644. R[4] := SYNLZCOMPRESS_STORED;
  51645. PCardinal(R+5)^ := crc;
  51646. MoveFast(P^,R[9],PLen);
  51647. end else begin
  51648. len := SynLZcompressdestlen(PLen)+9;
  51649. if len>sizeof(tmp) then begin
  51650. SetString(result,nil,len);
  51651. R := pointer(result);
  51652. end else
  51653. R := @tmp;
  51654. PCardinal(R)^ := crc;
  51655. len := SynLZcompress1(P,PLen,R+9);
  51656. if len>PLen then begin // store if compression not worth it
  51657. R[4] := SYNLZCOMPRESS_STORED;
  51658. PCardinal(R+5)^ := crc;
  51659. MoveFast(P^,R[9],PLen);
  51660. len := PLen;
  51661. end else begin
  51662. R[4] := SYNLZCOMPRESS_SYNLZ;
  51663. PCardinal(R+5)^ := crc32c(0,pointer(R+9),len);
  51664. end;
  51665. if R=@tmp then
  51666. SetString(result,tmp,len+9) else
  51667. SetLength(result,len+9); // resize in-place may not move any data
  51668. end;
  51669. end;
  51670. function SynLZDecompress(const Data: RawByteString): RawByteString;
  51671. begin
  51672. SynLZDecompress(pointer(Data),Length(Data),result);
  51673. end;
  51674. procedure SynLZDecompress(P: PAnsiChar; PLen: integer; out Result: RawByteString);
  51675. var len: integer;
  51676. begin
  51677. if (PLen<=9) or (P=nil) or (crc32c(0,pointer(P+9),PLen-9)<>PCardinal(P+5)^) then
  51678. exit;
  51679. case P[4] of
  51680. SYNLZCOMPRESS_STORED:
  51681. if PCardinal(P)^=PCardinal(P+5)^ then
  51682. SetString(result,P+9,PLen-9);
  51683. SYNLZCOMPRESS_SYNLZ: begin
  51684. len := SynLZdecompressdestlen(P+9);
  51685. SetLength(result,len);
  51686. if (len<>0) and
  51687. ((SynLZDecompress1(P+9,PLen-9,pointer(result))<>len) or
  51688. (crc32c(0,pointer(result),len)<>PCardinal(P)^)) then
  51689. result := '';
  51690. end;
  51691. end;
  51692. end;
  51693. function SynLZDecompress(const Data: RawByteString; out Len: integer;
  51694. var tmp: RawByteString): pointer;
  51695. begin
  51696. result := SynLZDecompress(pointer(Data),length(Data),Len,tmp);
  51697. end;
  51698. function SynLZDecompress(P: PAnsiChar; PLen: integer; out Len: integer;
  51699. var tmp: RawByteString): pointer;
  51700. begin
  51701. result := nil;
  51702. if (PLen<=9) or (P=nil) or (crc32c(0,pointer(P+9),PLen-9)<>PCardinal(P+5)^) then
  51703. exit;
  51704. case P[4] of
  51705. SYNLZCOMPRESS_STORED:
  51706. if PCardinal(P)^=PCardinal(P+5)^ then begin
  51707. result := P+9;
  51708. Len := PLen-9;
  51709. end;
  51710. SYNLZCOMPRESS_SYNLZ: begin
  51711. Len := SynLZdecompressdestlen(P+9);
  51712. SetString(tmp,nil,Len);
  51713. if (Len<>0) and
  51714. (SynLZDecompress1(P+9,PLen-9,pointer(tmp))=Len) and
  51715. (crc32c(0,pointer(tmp),Len)=PCardinal(P)^) then
  51716. result := pointer(tmp);
  51717. end;
  51718. end;
  51719. end;
  51720. function SynLZCompressToBytes(const Data: RawByteString;
  51721. CompressionSizeTrigger: integer): TByteDynArray;
  51722. begin
  51723. result := SynLZCompressToBytes(pointer(Data),length(Data),CompressionSizeTrigger);
  51724. end;
  51725. function SynLZCompressToBytes(P: PAnsiChar; PLen,CompressionSizeTrigger: integer): TByteDynArray;
  51726. var len: integer;
  51727. R: PAnsiChar;
  51728. crc: cardinal;
  51729. begin
  51730. if PLen=0 then
  51731. exit;
  51732. crc := crc32c(0,P,PLen);
  51733. if PLen<CompressionSizeTrigger then begin
  51734. SetLength(result,PLen+9);
  51735. R := pointer(result);
  51736. PCardinal(R)^ := crc;
  51737. R[4] := SYNLZCOMPRESS_STORED;
  51738. PCardinal(R+5)^ := crc;
  51739. MoveFast(P^,R[9],PLen);
  51740. end else begin
  51741. SetLength(result,SynLZcompressdestlen(PLen)+9);
  51742. R := pointer(result);
  51743. PCardinal(R)^ := crc;
  51744. len := SynLZcompress1(P,PLen,R+9);
  51745. if len>PLen then begin // store if compression not worth it
  51746. R[4] := SYNLZCOMPRESS_STORED;
  51747. PCardinal(R+5)^ := crc;
  51748. MoveFast(P^,R[9],PLen);
  51749. end else begin
  51750. R[4] := SYNLZCOMPRESS_SYNLZ;
  51751. PCardinal(R+5)^ := crc32c(0,pointer(R+9),len);
  51752. end;
  51753. SetLength(result,len+9);
  51754. end;
  51755. end;
  51756. function SynLZDecompress(const Data: TByteDynArray): RawByteString; overload;
  51757. begin
  51758. SynLZDecompress(pointer(Data),length(Data),result);
  51759. end;
  51760. procedure ZeroCompress(P: PAnsiChar; Len: integer; Dest: TFileBufferWriter);
  51761. var PEnd,beg,zero: PAnsiChar;
  51762. crc: cardinal;
  51763. begin
  51764. Dest.WriteVarUInt32(Len);
  51765. PEnd := P+Len;
  51766. beg := P;
  51767. crc := 0;
  51768. while P<PEnd do begin
  51769. while (P^<>#0) and (P<PEnd) do inc(P);
  51770. zero := P;
  51771. while (P^=#0) and (P<PEnd) do inc(P);
  51772. if P-zero>3 then begin
  51773. Len := zero-beg;
  51774. crc := crc32c(crc,beg,Len);
  51775. Dest.WriteVarUInt32(Len);
  51776. Dest.Write(beg,Len);
  51777. Len := P-zero;
  51778. crc := crc32c(crc,@Len,sizeof(Len));
  51779. Dest.WriteVarUInt32(Len-3);
  51780. beg := P;
  51781. end;
  51782. end;
  51783. Len := P-beg;
  51784. if Len>0 then begin
  51785. crc := crc32c(crc,beg,Len);
  51786. Dest.WriteVarUInt32(Len);
  51787. Dest.Write(beg,Len);
  51788. end;
  51789. Dest.Write4(crc);
  51790. end;
  51791. procedure ZeroCompressXor(New,Old: PAnsiChar; Len: cardinal; Dest: TFileBufferWriter);
  51792. var beg,same,index,crc,L: cardinal;
  51793. begin
  51794. Dest.WriteVarUInt32(Len);
  51795. beg := 0;
  51796. index := 0;
  51797. crc := 0;
  51798. while index<Len do begin
  51799. while (New[index]<>Old[index]) and (index<Len) do inc(index);
  51800. same := index;
  51801. while (New[index]=Old[index]) and (index<Len) do inc(index);
  51802. L := index-same;
  51803. if L>3 then begin
  51804. Dest.WriteVarUInt32(same-beg);
  51805. Dest.WriteXor(New+beg,Old+beg,same-beg,@crc);
  51806. crc := crc32c(crc,@L,sizeof(L));
  51807. Dest.WriteVarUInt32(L-3);
  51808. beg := index;
  51809. end;
  51810. end;
  51811. L := index-beg;
  51812. if L>0 then begin
  51813. Dest.WriteVarUInt32(L);
  51814. Dest.WriteXor(New+beg,Old+beg,L,@crc);
  51815. end;
  51816. Dest.Write4(crc);
  51817. end;
  51818. procedure ZeroDecompress(P: PByte; Len: integer; out Dest: RawByteString);
  51819. var PEnd,D,DEnd: PAnsiChar;
  51820. DestLen,crc: cardinal;
  51821. begin
  51822. PEnd := PAnsiChar(P)+Len-4;
  51823. DestLen := FromVarUInt32(P);
  51824. SetLength(Dest,DestLen);
  51825. D := pointer(Dest);
  51826. DEnd := D+DestLen;
  51827. crc := 0;
  51828. while PAnsiChar(P)<PEnd do begin
  51829. Len := FromVarUInt32(P);
  51830. if D+Len>DEnd then
  51831. break;
  51832. MoveFast(P^,D^,Len);
  51833. crc := crc32c(crc,D,Len);
  51834. inc(P,Len);
  51835. inc(D,Len);
  51836. if PAnsiChar(P)>=PEnd then
  51837. break;
  51838. Len := FromVarUInt32(P)+3;
  51839. if D+Len>DEnd then
  51840. break;
  51841. FillCharFast(D^,Len,0);
  51842. crc := crc32c(crc,@Len,sizeof(Len));
  51843. inc(D,Len);
  51844. end;
  51845. if crc<>PCardinal(P)^ then
  51846. Dest := '';
  51847. end;
  51848. function ZeroDecompressOr(P,Dest: PAnsiChar; Len,DestLen: integer): boolean;
  51849. var PEnd,DEnd: PAnsiChar;
  51850. crc: cardinal;
  51851. begin
  51852. PEnd := P+Len-4;
  51853. if cardinal(DestLen)<>FromVarUInt32(PByte(P)) then begin
  51854. result := false;
  51855. exit;
  51856. end;
  51857. DEnd := Dest+DestLen;
  51858. crc := 0;
  51859. while (P<PEnd) and (Dest<DEnd) do begin
  51860. Len := FromVarUInt32(PByte(P));
  51861. if Dest+Len>DEnd then
  51862. break;
  51863. crc := crc32c(crc,P,Len);
  51864. OrMemory(pointer(Dest),pointer(P),Len);
  51865. inc(P,Len);
  51866. inc(Dest,Len);
  51867. if P>=PEnd then
  51868. break;
  51869. Len := FromVarUInt32(PByte(P))+3;
  51870. crc := crc32c(crc,@Len,sizeof(Len));
  51871. inc(Dest,Len);
  51872. end;
  51873. result := crc=PCardinal(P)^;
  51874. end;
  51875. { ESynException }
  51876. constructor ESynException.CreateUTF8(const Format: RawUTF8; const Args: array of const);
  51877. begin
  51878. inherited Create(UTF8ToString(FormatUTF8(Format,Args)));
  51879. end;
  51880. {$ifndef NOEXCEPTIONINTERCEPT}
  51881. function ESynException.CustomLog(WR: TTextWriter;
  51882. const Context: TSynLogExceptionContext): boolean;
  51883. begin
  51884. if Assigned(TSynLogExceptionToStrCustom) then
  51885. result := TSynLogExceptionToStrCustom(WR,Context) else
  51886. if Assigned(DefaultSynLogExceptionToStr) then
  51887. result := DefaultSynLogExceptionToStr(WR,Context) else
  51888. result := false;
  51889. end;
  51890. {$endif}
  51891. { TMemoryMapText }
  51892. constructor TMemoryMapText.Create;
  51893. begin
  51894. end;
  51895. constructor TMemoryMapText.Create(aFileContent: PUTF8Char; aFileSize: integer);
  51896. begin
  51897. Create;
  51898. fMap.Map(aFileContent,aFileSize);
  51899. LoadFromMap;
  51900. end;
  51901. constructor TMemoryMapText.Create(const aFileName: TFileName);
  51902. begin
  51903. Create;
  51904. fFileName := aFileName;
  51905. if fMap.Map(aFileName) then
  51906. LoadFromMap;
  51907. end; // invalid file or unable to memory map its content -> Count := 0
  51908. destructor TMemoryMapText.Destroy;
  51909. begin
  51910. Freemem(fLines);
  51911. fMap.UnMap;
  51912. inherited;
  51913. end;
  51914. procedure TMemoryMapText.SaveToStream(Dest: TStream; const Header: RawUTF8);
  51915. var i: integer;
  51916. W: TTextWriter;
  51917. begin
  51918. i := length(Header);
  51919. if i>0 then
  51920. Dest.Write(pointer(Header)^,i);
  51921. if fMap.Size>0 then
  51922. Dest.Write(fMap.Buffer^,fMap.Size);
  51923. if fAppendedLinesCount=0 then
  51924. exit;
  51925. W := TTextWriter.Create(Dest);
  51926. try
  51927. if (fMap.Size>0) and (fMap.Buffer[fMap.Size-1]>=' ') then
  51928. W.Add(#13);
  51929. for i := 0 to fAppendedLinesCount-1 do begin
  51930. W.AddString(fAppendedLines[i]);
  51931. W.Add(#13);
  51932. end;
  51933. W.FlushFinal;
  51934. finally
  51935. W.Free;
  51936. end;
  51937. end;
  51938. procedure TMemoryMapText.SaveToFile(FileName: TFileName; const Header: RawUTF8);
  51939. var FS: TFileStream;
  51940. begin
  51941. FS := TFileStream.Create(FileName,fmCreate);
  51942. try
  51943. SaveToStream(FS,Header);
  51944. finally
  51945. FS.Free;
  51946. end;
  51947. end;
  51948. function TMemoryMapText.GetLine(aIndex: integer): RawUTF8;
  51949. begin
  51950. if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
  51951. result := '' else
  51952. SetString(result,PAnsiChar(fLines[aIndex]),GetLineSize(fLines[aIndex],fMapEnd));
  51953. end;
  51954. function TMemoryMapText.GetString(aIndex: integer): string;
  51955. begin
  51956. if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
  51957. result := '' else
  51958. UTF8DecodeToString(fLines[aIndex],GetLineSize(fLines[aIndex],fMapEnd),result);
  51959. end;
  51960. function GetLineContains(p,pEnd, up: PUTF8Char): boolean;
  51961. var i: integer;
  51962. label Fnd;
  51963. begin
  51964. if (p<>nil) and (up<>nil) then
  51965. if pEnd=nil then
  51966. repeat
  51967. i := ord(p^);
  51968. if not (AnsiChar(i) in ANSICHARNOT01310) then break;
  51969. inc(p);
  51970. if (NormToUpperAnsi7Byte[i]=ord(up^)) and IdemPChar(p,@up[1]) then begin
  51971. result := true;
  51972. exit;
  51973. end;
  51974. until false
  51975. else
  51976. repeat // fast unrolled search
  51977. if p>=pEnd then break;
  51978. i := ord(p^);
  51979. if not (AnsiChar(i) in ANSICHARNOT01310) then break;
  51980. if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd;
  51981. inc(p);
  51982. if p>=pEnd then break;
  51983. i := ord(p^);
  51984. if not (AnsiChar(i) in ANSICHARNOT01310) then break;
  51985. if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd;
  51986. inc(p);
  51987. if p>=pEnd then break;
  51988. i := ord(p^);
  51989. if not (AnsiChar(i) in ANSICHARNOT01310) then break;
  51990. if NormToUpperAnsi7Byte[i]=ord(up^) then goto Fnd;
  51991. inc(p);
  51992. if p>=pEnd then break;
  51993. i := ord(p^);
  51994. if not (AnsiChar(i) in ANSICHARNOT01310) then break;
  51995. if NormToUpperAnsi7Byte[i]<>ord(up^) then begin
  51996. inc(p);
  51997. continue;
  51998. end;
  51999. Fnd:i := 0;
  52000. repeat
  52001. inc(i);
  52002. if up[i]=#0 then begin
  52003. result := true; // found
  52004. exit;
  52005. end;
  52006. until NormToUpperAnsi7[p[i]]<>up[i];
  52007. inc(p);
  52008. until false;
  52009. result := false;
  52010. end;
  52011. function TMemoryMapText.LineContains(const aUpperSearch: RawUTF8;
  52012. aIndex: Integer): Boolean;
  52013. begin
  52014. if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) or (aUpperSearch='') then
  52015. result := false else
  52016. result := GetLineContains(fLines[aIndex],fMapEnd,pointer(aUpperSearch));
  52017. end;
  52018. function TMemoryMapText.LineSize(aIndex: integer): integer;
  52019. begin
  52020. result := GetLineSize(fLines[aIndex],fMapEnd);
  52021. end;
  52022. function GetLineSizeSmallerThan(P,PEnd: PUTF8Char; aMinimalCount: integer): boolean;
  52023. begin
  52024. if P<>nil then
  52025. while (P<PEnd) and (P^ in ANSICHARNOT01310) do
  52026. if aMinimalCount=0 then begin
  52027. result := false;
  52028. exit;
  52029. end else begin
  52030. dec(aMinimalCount);
  52031. inc(P);
  52032. end;
  52033. result := true;
  52034. end;
  52035. function TMemoryMapText.LineSizeSmallerThan(aIndex, aMinimalCount: integer): boolean;
  52036. begin
  52037. result := GetLineSizeSmallerThan(fLines[aIndex],fMapEnd,aMinimalCount);
  52038. end;
  52039. procedure TMemoryMapText.ProcessOneLine(LineBeg, LineEnd: PUTF8Char);
  52040. begin
  52041. if fCount=fLinesMax then begin
  52042. inc(fLinesMax,256+fLinesMax shr 3);
  52043. Reallocmem(fLines,fLinesMax*sizeof(pointer));
  52044. end;
  52045. fLines[fCount] := LineBeg;
  52046. inc(fCount);
  52047. end;
  52048. procedure TMemoryMapText.LoadFromMap(AverageLineLength: integer=32);
  52049. procedure ParseLines(P,PEnd: PUTF8Char);
  52050. var PBeg: PUTF8Char;
  52051. begin // generated asm is much better with a local proc
  52052. while P<PEnd do begin
  52053. PBeg := P;
  52054. while (P<PEnd) and (P^<>#13) and (P^<>#10) do
  52055. inc(P);
  52056. ProcessOneLine(PBeg,P);
  52057. if P+1>=PEnd then
  52058. break;
  52059. if P[0]=#13 then
  52060. if P[1]=#10 then
  52061. inc(P,2) else // ignore #13#10
  52062. inc(P) else // ignore #13
  52063. inc(P); // ignore #10
  52064. end;
  52065. end;
  52066. var P: PUTF8Char;
  52067. begin
  52068. fLinesMax := fMap.fFileSize div AverageLineLength+8;
  52069. Getmem(fLines,fLinesMax*sizeof(pointer));
  52070. P := pointer(fMap.Buffer);
  52071. fMapEnd := P+fMap.Size;
  52072. if TextFileKind(Map)=isUTF8 then
  52073. inc(PByte(P),3); // ignore UTF-8 BOM
  52074. ParseLines(P,fMapEnd);
  52075. if fLinesMax>fCount+16384 then
  52076. Reallocmem(fLines,fCount*sizeof(pointer)); // size down only if worth it
  52077. end;
  52078. procedure TMemoryMapText.AddInMemoryLine(const aNewLine: RawUTF8);
  52079. var P: PUTF8Char;
  52080. begin
  52081. if aNewLine='' then
  52082. exit;
  52083. AddRawUTF8(fAppendedLines,fAppendedLinesCount,aNewLine);
  52084. P := pointer(fAppendedLines[fAppendedLinesCount-1]);
  52085. ProcessOneLine(P,P+StrLen(P));
  52086. end;
  52087. procedure TMemoryMapText.AddInMemoryLinesClear;
  52088. begin
  52089. dec(fCount,fAppendedLinesCount);
  52090. fAppendedLinesCount := 0;
  52091. fAppendedLines := nil;
  52092. end;
  52093. { TRawByteStringStream }
  52094. constructor TRawByteStringStream.Create(const aString: RawByteString);
  52095. begin
  52096. fDataString := aString;
  52097. end;
  52098. function TRawByteStringStream.Read(var Buffer; Count: Integer): Longint;
  52099. begin
  52100. if Count<=0 then
  52101. Result := 0 else begin
  52102. Result := Length(fDataString)-fPosition;
  52103. if Result>Count then
  52104. Result := Count;
  52105. MoveFast(PByteArray(fDataString)[fPosition],Buffer,Result);
  52106. inc(fPosition, Result);
  52107. end;
  52108. end;
  52109. function TRawByteStringStream.Seek(Offset: Integer; Origin: Word): Longint;
  52110. begin
  52111. case Origin of
  52112. soFromBeginning: fPosition := Offset;
  52113. soFromCurrent: fPosition := fPosition+Offset;
  52114. soFromEnd: fPosition := Length(fDataString)-Offset;
  52115. end;
  52116. if fPosition>Length(fDataString) then
  52117. fPosition := Length(fDataString) else
  52118. if fPosition<0 then
  52119. fPosition := 0;
  52120. result := fPosition;
  52121. end;
  52122. procedure TRawByteStringStream.SetSize(NewSize: Integer);
  52123. begin
  52124. SetLength(fDataString, NewSize);
  52125. if fPosition>NewSize then
  52126. fPosition := NewSize;
  52127. end;
  52128. function TRawByteStringStream.Write(const Buffer; Count: Integer): Longint;
  52129. begin
  52130. if Count<=0 then
  52131. Result := 0 else begin
  52132. Result := Count;
  52133. SetLength(fDataString,(fPosition+Result));
  52134. MoveFast(Buffer,PByteArray(fDataString)[fPosition],Result);
  52135. inc(FPosition,Result);
  52136. end;
  52137. end;
  52138. { TFakeWriterStream }
  52139. function TFakeWriterStream.Read(var Buffer; Count: Longint): Longint;
  52140. begin // do nothing
  52141. result := Count;
  52142. end;
  52143. function TFakeWriterStream.Write(const Buffer; Count: Longint): Longint;
  52144. begin // do nothing
  52145. result := Count;
  52146. end;
  52147. function TFakeWriterStream.Seek(Offset: Longint; Origin: Word): Longint;
  52148. begin
  52149. result := Offset;
  52150. end;
  52151. { TSynBloomFilter }
  52152. const
  52153. BLOOM_VERSION = 0;
  52154. BLOOM_MAXHASH = 32; // only 7 is needed for 1% false positive ratio
  52155. constructor TSynBloomFilter.Create(aSize: integer; aFalsePositivePercent: double);
  52156. const LN2 = 0.69314718056;
  52157. begin
  52158. inherited Create;
  52159. if aSize < 0 then
  52160. fSize := 1000 else
  52161. fSize := aSize;
  52162. if aFalsePositivePercent<=0 then
  52163. fFalsePositivePercent := 1 else
  52164. if aFalsePositivePercent>100 then
  52165. fFalsePositivePercent := 100 else
  52166. fFalsePositivePercent := aFalsePositivePercent;
  52167. // see http://stackoverflow.com/a/22467497/458259
  52168. fBits := Round(-ln(fFalsePositivePercent/100)*aSize/(LN2*LN2));
  52169. fHashFunctions := Round(fBits/fSize*LN2);
  52170. if fHashFunctions=0 then
  52171. fHashFunctions := 1 else
  52172. if fHashFunctions>BLOOM_MAXHASH then
  52173. fHashFunctions := BLOOM_MAXHASH;
  52174. Reset;
  52175. end;
  52176. constructor TSynBloomFilter.Create(const aSaved: RawByteString; aMagic: cardinal);
  52177. begin
  52178. inherited Create;
  52179. if not LoadFrom(aSaved,aMagic) then
  52180. raise ESynException.CreateUTF8('%.Create with invalid aSaved content',[self]);
  52181. end;
  52182. procedure TSynBloomFilter.Insert(const aValue: RawByteString);
  52183. begin
  52184. Insert(pointer(aValue),length(aValue));
  52185. end;
  52186. procedure TSynBloomFilter.Insert(aValue: pointer; aValueLen: integer);
  52187. var h: integer;
  52188. h1,h2: cardinal; // http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
  52189. begin
  52190. if (self=nil) or (aValueLen<=0) and (fBits=0) then
  52191. exit;
  52192. h1 := crc32c(0,aValue,aValueLen);
  52193. if fHashFunctions=1 then
  52194. h2 := 0 else
  52195. h2 := crc32c(h1,aValue,aValueLen);
  52196. Safe.Lock;
  52197. try
  52198. for h := 0 to fHashFunctions-1 do
  52199. SetBit(pointer(fStore)^,(h1+cardinal(h)*h2) mod fBits);
  52200. inc(fInserted);
  52201. finally
  52202. Safe.UnLock;
  52203. end;
  52204. end;
  52205. function TSynBloomFilter.GetInserted: cardinal;
  52206. begin
  52207. Safe.Lock;
  52208. try
  52209. result := fInserted; // Delphi 5 does not support LockedInt64[]
  52210. finally
  52211. Safe.UnLock;
  52212. end;
  52213. end;
  52214. function TSynBloomFilter.MayExist(const aValue: RawByteString): boolean;
  52215. begin
  52216. result := MayExist(pointer(aValue),length(aValue));
  52217. end;
  52218. function TSynBloomFilter.MayExist(aValue: pointer; aValueLen: integer): boolean;
  52219. var h: integer;
  52220. h1,h2: cardinal; // http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
  52221. begin
  52222. result := false;
  52223. if (self=nil) or (aValueLen<=0) and (fBits=0) then
  52224. exit;
  52225. h1 := crc32c(0,aValue,aValueLen);
  52226. if fHashFunctions=1 then
  52227. h2 := 0 else
  52228. h2 := crc32c(h1,aValue,aValueLen);
  52229. Safe.Lock;
  52230. try
  52231. for h := 0 to fHashFunctions-1 do
  52232. if not GetBit(pointer(fStore)^,(h1+cardinal(h)*h2) mod fBits) then
  52233. exit;
  52234. finally
  52235. Safe.UnLock;
  52236. end;
  52237. result := true;
  52238. end;
  52239. procedure TSynBloomFilter.Reset;
  52240. begin
  52241. Safe.Lock;
  52242. try
  52243. if fStore='' then
  52244. SetLength(fStore,(fBits shr 3)+1);
  52245. FillcharFast(pointer(fStore)^,length(fStore),0);
  52246. fInserted := 0;
  52247. finally
  52248. Safe.UnLock;
  52249. end;
  52250. end;
  52251. function TSynBloomFilter.SaveTo(aMagic: cardinal): RawByteString;
  52252. var W: TFileBufferWriter;
  52253. begin
  52254. W := TFileBufferWriter.Create(TRawByteStringStream,length(fStore)+100);
  52255. try
  52256. SaveTo(W,aMagic);
  52257. W.Flush;
  52258. result := TRawByteStringStream(W.Stream).DataString;
  52259. finally
  52260. W.Free;
  52261. end;
  52262. end;
  52263. procedure TSynBloomFilter.SaveTo(aDest: TFileBufferWriter; aMagic: cardinal=$B1003F11);
  52264. begin
  52265. aDest.Write4(aMagic);
  52266. aDest.Write1(BLOOM_VERSION);
  52267. Safe.Lock;
  52268. try
  52269. aDest.Write8(fFalsePositivePercent);
  52270. aDest.Write4(fSize);
  52271. aDest.Write4(fBits);
  52272. aDest.Write1(fHashFunctions);
  52273. aDest.Write4(fInserted);
  52274. ZeroCompress(pointer(fStore),Length(fStore),aDest);
  52275. finally
  52276. Safe.UnLock;
  52277. end;
  52278. end;
  52279. function TSynBloomFilter.LoadFrom(const aSaved: RawByteString; aMagic: cardinal): boolean;
  52280. begin
  52281. result := LoadFrom(pointer(aSaved),length(aSaved));
  52282. end;
  52283. function TSynBloomFilter.LoadFrom(P: PByte; PLen: integer; aMagic: cardinal): boolean;
  52284. var start: PByte;
  52285. version: integer;
  52286. begin
  52287. result := false;
  52288. start := P;
  52289. if (P=nil) or (PLen<32) or (PCardinal(P)^<>aMagic) then
  52290. exit;
  52291. inc(P,4);
  52292. version := P^; inc(P);
  52293. if version>BLOOM_VERSION then
  52294. exit;
  52295. Safe.Lock;
  52296. try
  52297. fFalsePositivePercent := PDouble(P)^; inc(P,8);
  52298. if (fFalsePositivePercent<=0) or (fFalsePositivePercent>100) then
  52299. exit;
  52300. fSize := PCardinal(P)^; inc(P,4);
  52301. fBits := PCardinal(P)^; inc(P,4);
  52302. if fBits<fSize then
  52303. exit;
  52304. fHashFunctions := P^; inc(P);
  52305. if fHashFunctions-1>=BLOOM_MAXHASH then
  52306. exit;
  52307. Reset;
  52308. fInserted := PCardinal(P)^; inc(P,4);
  52309. ZeroDecompress(P,PLen-(PAnsiChar(P)-PAnsiChar(start)),fStore);
  52310. result := length(fStore)=integer(fBits shr 3)+1;
  52311. finally
  52312. Safe.UnLock;
  52313. end;
  52314. end;
  52315. { TSynBloomFilterDiff }
  52316. type
  52317. TBloomDiffHeader = packed record
  52318. kind: (bdDiff,bdFull,bdUpToDate);
  52319. size: cardinal;
  52320. inserted: cardinal;
  52321. revision: Int64;
  52322. crc: cardinal;
  52323. end;
  52324. procedure TSynBloomFilterDiff.Insert(aValue: pointer; aValueLen: integer);
  52325. begin
  52326. Safe.Lock;
  52327. try
  52328. inherited Insert(aValue,aValueLen);
  52329. inc(fRevision);
  52330. inc(fSnapshotInsertCount);
  52331. finally
  52332. Safe.UnLock;
  52333. end;
  52334. end;
  52335. procedure TSynBloomFilterDiff.Reset;
  52336. begin
  52337. Safe.Lock;
  52338. try
  52339. inherited Reset;
  52340. fSnapshotAfterInsertCount := fSize shr 5;
  52341. fSnapShotAfterMinutes := 30;
  52342. fSnapshotTimeStamp := 0;
  52343. fSnapshotInsertCount := 0;
  52344. fRevision := DateTimeToUnixTime(NowUTC) shl 31;
  52345. fKnownRevision := 0;
  52346. fKnownStore := '';
  52347. finally
  52348. Safe.UnLock;
  52349. end;
  52350. end;
  52351. procedure TSynBloomFilterDiff.DiffSnapshot;
  52352. begin
  52353. Safe.Lock;
  52354. try
  52355. fKnownRevision := fRevision;
  52356. fSnapshotInsertCount := 0;
  52357. SetString(fKnownStore,PAnsiChar(pointer(fStore)),length(fStore));
  52358. if fSnapShotAfterMinutes=0 then
  52359. fSnapshotTimeStamp := 0 else
  52360. fSnapshotTimeStamp := GetTickCount64+fSnapShotAfterMinutes*60000;
  52361. finally
  52362. Safe.UnLock;
  52363. end;
  52364. end;
  52365. function TSynBloomFilterDiff.SaveToDiff(const aKnownRevision: Int64): RawByteString;
  52366. var head: TBloomDiffHeader;
  52367. W: TFileBufferWriter;
  52368. begin
  52369. Safe.Lock;
  52370. try
  52371. if aKnownRevision=fRevision then
  52372. head.kind := bdUpToDate else
  52373. if (fKnownRevision=0) or
  52374. (fSnapshotInsertCount>fSnapshotAfterInsertCount) or
  52375. ((fSnapshotInsertCount>0) and (fSnapshotTimeStamp<>0) and
  52376. (GetTickCount64>fSnapshotTimeStamp)) then begin
  52377. DiffSnapshot;
  52378. head.kind := bdFull;
  52379. end else
  52380. if (aKnownRevision<fKnownRevision) or (aKnownRevision>fRevision) then
  52381. head.kind := bdFull else
  52382. head.kind := bdDiff;
  52383. head.size := length(fStore);
  52384. head.inserted := fInserted;
  52385. head.revision := fRevision;
  52386. head.crc := crc32c(0,@head,sizeof(head)-sizeof(head.crc));
  52387. if head.kind=bdUpToDate then begin
  52388. SetString(result,PAnsiChar(@head),sizeof(head));
  52389. exit;
  52390. end;
  52391. W := TFileBufferWriter.Create(TRawByteStringStream,head.size+100);
  52392. try
  52393. W.Write(@head,sizeof(head));
  52394. case head.kind of
  52395. bdFull:
  52396. SaveTo(W);
  52397. bdDiff:
  52398. ZeroCompressXor(pointer(fStore),pointer(fKnownStore),head.size,W);
  52399. end;
  52400. W.Flush;
  52401. result := TRawByteStringStream(W.Stream).DataString;
  52402. finally
  52403. W.Free;
  52404. end;
  52405. finally
  52406. Safe.UnLock;
  52407. end;
  52408. end;
  52409. function TSynBloomFilterDiff.DiffKnownRevision(const aDiff: RawByteString): Int64;
  52410. var head: ^TBloomDiffHeader absolute aDiff;
  52411. begin
  52412. if (length(aDiff)<sizeof(head^)) or (head.kind>high(head.kind)) or
  52413. (head.size<>cardinal(length(fStore))) or
  52414. (head.crc<>crc32c(0,pointer(head),sizeof(head^)-sizeof(head.crc))) then
  52415. result := 0 else
  52416. result := head.Revision;
  52417. end;
  52418. function TSynBloomFilterDiff.LoadFromDiff(const aDiff: RawByteString): boolean;
  52419. var head: ^TBloomDiffHeader absolute aDiff;
  52420. P: PByte;
  52421. PLen: integer;
  52422. begin
  52423. result := false;
  52424. P := pointer(aDiff);
  52425. PLen := length(aDiff);
  52426. if (PLen<sizeof(head^)) or (head.kind>high(head.kind)) or
  52427. (head.crc<>crc32c(0,pointer(head),sizeof(head^)-sizeof(head.crc))) then
  52428. exit;
  52429. if (fStore<>'') and (head.size<>cardinal(length(fStore))) then
  52430. exit;
  52431. inc(P,sizeof(head^));
  52432. dec(PLen,sizeof(head^));
  52433. Safe.Lock;
  52434. try
  52435. case head.kind of
  52436. bdFull:
  52437. result := LoadFrom(P,PLen);
  52438. bdDiff:
  52439. if fStore<>'' then
  52440. result := ZeroDecompressOr(pointer(P),Pointer(fStore),PLen,head.size);
  52441. bdUpToDate:
  52442. result := true;
  52443. end;
  52444. if result then begin
  52445. fRevision := head.revision;
  52446. fInserted := head.inserted;
  52447. end;
  52448. finally
  52449. Safe.UnLock;
  52450. end;
  52451. end;
  52452. { TPendingTaskList }
  52453. constructor TPendingTaskList.Create;
  52454. begin
  52455. fSafe.Init;
  52456. fTasks.InitSpecific(TypeInfo(TPendingTaskListItemDynArray),fTask,djInt64,@fCount);
  52457. end;
  52458. destructor TPendingTaskList.Destroy;
  52459. begin
  52460. fSafe.Done;
  52461. inherited Destroy;
  52462. end;
  52463. function TPendingTaskList.GetTimeStamp: Int64;
  52464. begin
  52465. result := GetTickCount64;
  52466. end;
  52467. procedure TPendingTaskList.AddTask(aMilliSecondsDelayFromNow: integer;
  52468. const aTask: RawByteString);
  52469. var item: TPendingTaskListItem;
  52470. ndx: integer;
  52471. begin
  52472. item.TimeStamp := GetTimeStamp+aMilliSecondsDelayFromNow;
  52473. item.Task := aTask;
  52474. fSafe.Lock;
  52475. try
  52476. if fTasks.FastLocateSorted(item,ndx) then
  52477. inc(ndx); // always insert just after an existing timestamp
  52478. fTasks.FastAddSorted(ndx,item);
  52479. finally
  52480. fSafe.UnLock;
  52481. end;
  52482. end;
  52483. procedure TPendingTaskList.AddTasks(
  52484. const aMilliSecondsDelays: array of integer;
  52485. const aTasks: array of RawByteString);
  52486. var item: TPendingTaskListItem;
  52487. i,ndx: integer;
  52488. begin
  52489. if length(aTasks)<>length(aMilliSecondsDelays) then
  52490. exit;
  52491. item.TimeStamp := GetTimeStamp;
  52492. fSafe.Lock;
  52493. try
  52494. for i := 0 to High(aTasks) do begin
  52495. inc(item.TimeStamp,aMilliSecondsDelays[i]);
  52496. item.Task := aTasks[i];
  52497. if fTasks.FastLocateSorted(item,ndx) then
  52498. inc(ndx); // always insert just after an existing timestamp
  52499. fTasks.FastAddSorted(ndx,item);
  52500. end;
  52501. finally
  52502. fSafe.UnLock;
  52503. end;
  52504. end;
  52505. function TPendingTaskList.GetCount: integer;
  52506. begin
  52507. if self=nil then
  52508. result := 0 else begin
  52509. fSafe.Lock;
  52510. try
  52511. result := fCount;
  52512. finally
  52513. fSafe.UnLock;
  52514. end;
  52515. end;
  52516. end;
  52517. function TPendingTaskList.NextPendingTask: RawByteString;
  52518. begin
  52519. result := '';
  52520. if (self=nil) or (fCount=0) then
  52521. exit;
  52522. fSafe.Lock;
  52523. try
  52524. if fCount>0 then
  52525. if GetTimeStamp>=fTask[0].TimeStamp then begin
  52526. result := fTask[0].Task;
  52527. fTasks.FastDeleteSorted(0);
  52528. end;
  52529. finally
  52530. fSafe.UnLock;
  52531. end;
  52532. end;
  52533. procedure TPendingTaskList.Clear;
  52534. begin
  52535. if (self=nil) or (fCount=0) then
  52536. exit;
  52537. fSafe.Lock;
  52538. try
  52539. fTasks.Clear;
  52540. finally
  52541. fSafe.UnLock;
  52542. end;
  52543. end;
  52544. { TSynNameValue }
  52545. procedure TSynNameValue.Add(const aName, aValue: RawUTF8; aTag: PtrInt);
  52546. var added: boolean;
  52547. i: Integer;
  52548. begin
  52549. i := fDynArray.FindHashedForAdding(aName,added);
  52550. with List[i] do begin
  52551. if added then
  52552. Name := aName;
  52553. Value := aValue;
  52554. Tag := aTag;
  52555. end;
  52556. if Assigned(fOnAdd) then
  52557. fOnAdd(List[i],i);
  52558. end;
  52559. procedure TSynNameValue.InitFromIniSection(Section: PUTF8Char;
  52560. OnTheFlyConvert: TConvertRawUTF8; OnAdd: TSynNameValueNotify);
  52561. var s: RawUTF8;
  52562. i: integer;
  52563. begin
  52564. Init(false);
  52565. fOnAdd := OnAdd;
  52566. while (Section<>nil) and (Section^<>'[') do begin
  52567. s := GetNextLine(Section,Section);
  52568. i := PosEx('=',s);
  52569. if (i>1) and not(s[1] in [';','[']) then
  52570. if Assigned(OnTheFlyConvert) then
  52571. Add(copy(s,1,i-1),OnTheFlyConvert(copy(s,i+1,1000))) else
  52572. Add(copy(s,1,i-1),copy(s,i+1,1000));
  52573. end;
  52574. end;
  52575. procedure TSynNameValue.InitFromCSV(CSV: PUTF8Char; NameValueSep,ItemSep: AnsiChar);
  52576. var n,v: RawUTF8;
  52577. begin
  52578. Init(false);
  52579. while CSV<>nil do begin
  52580. n := GetNextItem(CSV,NameValueSep);
  52581. v := GetNextItem(CSV,ItemSep);
  52582. if n='' then
  52583. break;
  52584. Add(n,v);
  52585. end;
  52586. end;
  52587. procedure TSynNameValue.Init(aCaseSensitive: boolean);
  52588. begin
  52589. List := nil; // release dynamic arrays memory before Fillchar()
  52590. fDynArray.fHashs := nil;
  52591. FillcharFast(self,sizeof(self),0);
  52592. fDynArray.InitSpecific(TypeInfo(TSynNameValueItemDynArray),List,
  52593. djRawUTF8,@Count,not aCaseSensitive);
  52594. end;
  52595. function TSynNameValue.Find(const aName: RawUTF8): integer;
  52596. begin
  52597. result := fDynArray.FindHashed(aName);
  52598. end;
  52599. function TSynNameValue.FindStart(const aUpperName: RawUTF8): integer;
  52600. begin
  52601. for result := 0 to Count-1 do
  52602. if IdemPChar(pointer(List[result].Name),pointer(aUpperName)) then
  52603. exit;
  52604. result := -1;
  52605. end;
  52606. function TSynNameValue.FindByValue(const aValue: RawUTF8): integer;
  52607. begin
  52608. for result := 0 to Count-1 do
  52609. if List[result].Value=aValue then
  52610. exit;
  52611. result := -1;
  52612. end;
  52613. function TSynNameValue.Delete(const aName: RawUTF8): boolean;
  52614. var ndx: integer;
  52615. begin
  52616. ndx := fDynArray.FindHashed(aName);
  52617. if ndx>=0 then begin
  52618. fDynArray.Delete(ndx);
  52619. fDynArray.ReHash;
  52620. result := true;
  52621. end else
  52622. result := false;
  52623. end;
  52624. function TSynNameValue.DeleteByValue(const aValue: RawUTF8; Limit: integer): integer;
  52625. var ndx: integer;
  52626. begin
  52627. result := 0;
  52628. if Limit<1 then
  52629. exit;
  52630. for ndx := Count-1 downto 0 do
  52631. if List[ndx].Value=aValue then begin
  52632. fDynArray.Delete(ndx);
  52633. inc(result);
  52634. if result>=Limit then
  52635. break;
  52636. end;
  52637. if result>0 then
  52638. fDynArray.ReHash;
  52639. end;
  52640. function TSynNameValue.Value(const aName: RawUTF8; const aDefaultValue: RawUTF8): RawUTF8;
  52641. var i: integer;
  52642. begin
  52643. if @self=nil then
  52644. i := -1 else
  52645. i := fDynArray.FindHashed(aName);
  52646. if i<0 then
  52647. result := aDefaultValue else
  52648. result := List[i].Value;
  52649. end;
  52650. function TSynNameValue.ValueInt(const aName: RawUTF8; const aDefaultValue: Int64): Int64;
  52651. var i,err: integer;
  52652. begin
  52653. i := fDynArray.FindHashed(aName);
  52654. if i<0 then
  52655. result := aDefaultValue else begin
  52656. result := GetInt64(pointer(List[i].Value),err);
  52657. if err<>0 then
  52658. result := aDefaultValue;
  52659. end;
  52660. end;
  52661. function TSynNameValue.ValueBool(const aName: RawUTF8): Boolean;
  52662. begin
  52663. result := Value(aName)='1';
  52664. end;
  52665. function TSynNameValue.Initialized: boolean;
  52666. begin
  52667. result := fDynArray.fValue=@List;
  52668. end;
  52669. function TSynNameValue.GetBlobData: RawByteString;
  52670. begin
  52671. result := fDynArray.SaveTo;
  52672. end;
  52673. procedure TSynNameValue.SetBlobDataPtr(aValue: pointer);
  52674. begin
  52675. fDynArray.LoadFrom(aValue);
  52676. fDynArray.ReHash;
  52677. end;
  52678. procedure TSynNameValue.SetBlobData(const aValue: RawByteString);
  52679. begin
  52680. SetBlobDataPtr(pointer(aValue));
  52681. end;
  52682. function TSynNameValue.GetStr(const aName: RawUTF8): RawUTF8;
  52683. begin
  52684. result := Value(aName,'');
  52685. end;
  52686. function TSynNameValue.GetInt(const aName: RawUTF8): Int64;
  52687. begin
  52688. result := ValueInt(aName,0);
  52689. end;
  52690. function TSynNameValue.GetBool(const aName: RawUTF8): Boolean;
  52691. begin
  52692. result := Value(aName)='1';
  52693. end;
  52694. function TSynNameValue.AsCSV(const KeySeparator,ValueSeparator,IgnoreKey: RawUTF8): RawUTF8;
  52695. var i: integer;
  52696. begin
  52697. with TTextWriter.CreateOwnedStream do
  52698. try
  52699. for i := 0 to Count-1 do
  52700. if (IgnoreKey='') or (List[i].Name<>IgnoreKey) then begin
  52701. AddNoJSONEscapeUTF8(List[i].Name);
  52702. AddNoJSONEscapeUTF8(KeySeparator);
  52703. AddNoJSONEscapeUTF8(List[i].Value);
  52704. AddNoJSONEscapeUTF8(ValueSeparator);
  52705. end;
  52706. SetText(result);
  52707. finally
  52708. Free;
  52709. end;
  52710. end;
  52711. procedure TSynNameValue.AsNameValues(out Names,Values: TRawUTF8DynArray);
  52712. var i: integer;
  52713. begin
  52714. SetLength(Names,Count);
  52715. SetLength(Values,Count);
  52716. for i := 0 to Count-1 do begin
  52717. Names[i] := List[i].Name;
  52718. Values[i] := List[i].Value;
  52719. end;
  52720. end;
  52721. {$ifndef NOVARIANTS}
  52722. function TSynNameValue.ValueVariantOrNull(const aName: RawUTF8): variant;
  52723. var i: integer;
  52724. begin
  52725. i := Find(aName);
  52726. if i<0 then
  52727. SetVariantNull(result) else
  52728. RawUTF8ToVariant(List[i].Value,result);
  52729. end;
  52730. procedure TSynNameValue.AsDocVariant(out DocVariant: variant;
  52731. ExtendedJson,ValueAsString: boolean);
  52732. var ndx: integer;
  52733. begin
  52734. if Count>0 then
  52735. with TDocVariantData(DocVariant) do begin
  52736. Init(JSON_OPTIONS_NAMEVALUE[ExtendedJson],dvObject);
  52737. VCount := self.Count;
  52738. SetLength(VName,VCount);
  52739. SetLength(VValue,VCount);
  52740. for ndx := 0 to VCount-1 do begin
  52741. VName[ndx] := List[ndx].Name;
  52742. if ValueAsString or
  52743. not GetNumericVariantFromJSON(pointer(List[ndx].Value),TVarData(VValue[ndx])) then
  52744. RawUTF8ToVariant(List[ndx].Value,VValue[ndx]);
  52745. end;
  52746. end else
  52747. TVarData(DocVariant).VType := varNull;
  52748. end;
  52749. function TSynNameValue.AsDocVariant(ExtendedJson,ValueAsString: boolean): variant;
  52750. begin
  52751. AsDocVariant(result,ExtendedJson,ValueAsString);
  52752. end;
  52753. function TSynNameValue.MergeDocVariant(var DocVariant: variant;
  52754. ValueAsString: boolean; ChangedProps: PVariant; ExtendedJson: Boolean): integer;
  52755. var DV: TDocVariantData absolute DocVariant;
  52756. i,ndx: integer;
  52757. v: variant;
  52758. begin
  52759. if DV.VType<>DocVariantVType then
  52760. TDocVariant.New(DocVariant,JSON_OPTIONS_NAMEVALUE[ExtendedJson]);
  52761. if ChangedProps<>nil then
  52762. TDocVariant.New(ChangedProps^,DV.Options);
  52763. result := 0; // returns number of changed values
  52764. for i := 0 to Count-1 do begin
  52765. VarClear(v);
  52766. if ValueAsString or
  52767. not GetNumericVariantFromJSON(pointer(List[i].Value),TVarData(v)) then
  52768. RawUTF8ToVariant(List[i].Value,v);
  52769. ndx := DV.GetValueIndex(List[i].Name);
  52770. if ndx<0 then
  52771. ndx := DV.InternalAdd(List[i].Name) else
  52772. if SortDynArrayVariantComp(TVarData(v),TVarData(DV.Values[ndx]),false)=0 then
  52773. continue; // value not changed -> skip
  52774. if ChangedProps<>nil then
  52775. PDocVariantData(ChangedProps)^.AddValue(List[i].Name,v);
  52776. SetVariantByValue(v,DV.VValue[ndx]);
  52777. inc(result);
  52778. end;
  52779. end;
  52780. {$endif}
  52781. { TSynAuthenticationAbstract }
  52782. constructor TSynAuthenticationAbstract.Create;
  52783. begin
  52784. fSafe.Init;
  52785. fTokenSeed := GetTickCount64*PtrUInt(self)*Random(maxInt);
  52786. fSessionGenerator := abs(fTokenSeed*PtrUInt(ClassType));
  52787. end;
  52788. destructor TSynAuthenticationAbstract.Destroy;
  52789. begin
  52790. fSafe.Done;
  52791. inherited;
  52792. end;
  52793. class function TSynAuthenticationAbstract.ComputeHash(Token: Int64;
  52794. const UserName,PassWord: RawUTF8): cardinal;
  52795. begin // rough authentication - better than nothing
  52796. result := length(UserName);
  52797. result := crc32c(crc32c(crc32c(result,@Token,sizeof(Token)),
  52798. pointer(UserName),result),pointer(Password),length(PassWord));
  52799. end;
  52800. function TSynAuthenticationAbstract.ComputeCredential(previous: boolean;
  52801. const UserName,PassWord: RawUTF8): cardinal;
  52802. var tok: Int64;
  52803. begin
  52804. tok := GetTickCount64 div 10000;
  52805. if previous then
  52806. dec(tok);
  52807. result := ComputeHash(tok xor fTokenSeed,UserName,PassWord);
  52808. end;
  52809. function TSynAuthenticationAbstract.CurrentToken: Int64;
  52810. begin
  52811. result := (GetTickCount64 div 10000) xor fTokenSeed;
  52812. end;
  52813. procedure TSynAuthenticationAbstract.AuthenticateUser(const aName, aPassword: RawUTF8);
  52814. begin
  52815. raise ESynException.CreateFmt('%.AuthenticateUser() is not implemented',[self]);
  52816. end;
  52817. procedure TSynAuthenticationAbstract.DisauthenticateUser(const aName: RawUTF8);
  52818. begin
  52819. raise ESynException.CreateFmt('%.DisauthenticateUser() is not implemented',[self]);
  52820. end;
  52821. function TSynAuthenticationAbstract.CreateSession(const User: RawUTF8; Hash: cardinal): integer;
  52822. var password: RawUTF8;
  52823. begin
  52824. result := 0;
  52825. fSafe.Lock;
  52826. try
  52827. // check the credentials
  52828. if not GetPassword(User,password) then
  52829. exit;
  52830. if (ComputeCredential(false,User,password)<>Hash) and
  52831. (ComputeCredential(true,User,password)<>Hash) then
  52832. exit;
  52833. // create the new session
  52834. repeat
  52835. result := fSessionGenerator;
  52836. inc(fSessionGenerator);
  52837. until result<>0;
  52838. AddSortedInteger(fSessions,fSessionsCount,result);
  52839. finally
  52840. fSafe.UnLock;
  52841. end;
  52842. end;
  52843. function TSynAuthenticationAbstract.SessionExists(aID: integer): boolean;
  52844. begin
  52845. fSafe.Lock;
  52846. try
  52847. result := FastFindIntegerSorted(pointer(fSessions),fSessionsCount-1,aID)>=0;
  52848. finally
  52849. fSafe.UnLock;
  52850. end;
  52851. end;
  52852. procedure TSynAuthenticationAbstract.RemoveSession(aID: integer);
  52853. var i: integer;
  52854. begin
  52855. fSafe.Lock;
  52856. try
  52857. i := FastFindIntegerSorted(pointer(fSessions),fSessionsCount-1,aID);
  52858. if i>=0 then
  52859. DeleteInteger(fSessions,fSessionsCount,i);
  52860. finally
  52861. fSafe.UnLock;
  52862. end;
  52863. end;
  52864. { TSynAuthentication }
  52865. constructor TSynAuthentication.Create(const aUserName,aPassword: RawUTF8);
  52866. begin
  52867. inherited Create;
  52868. fCredentials.Init(true);
  52869. if aUserName<>'' then
  52870. AuthenticateUser(aUserName,aPassword);
  52871. end;
  52872. function TSynAuthentication.GetPassword(const UserName: RawUTF8;
  52873. out Password: RawUTF8): boolean;
  52874. var i: integer;
  52875. begin // caller did protect this method via fSafe.Lock
  52876. i := fCredentials.Find(UserName);
  52877. if i<0 then begin
  52878. result := false;
  52879. exit;
  52880. end;
  52881. password := fCredentials.List[i].Value;
  52882. result := true;
  52883. end;
  52884. function TSynAuthentication.GetUsersCount: integer;
  52885. begin
  52886. fSafe.Lock;
  52887. try
  52888. result := fCredentials.Count;
  52889. finally
  52890. fSafe.UnLock;
  52891. end;
  52892. end;
  52893. procedure TSynAuthentication.AuthenticateUser(const aName, aPassword: RawUTF8);
  52894. begin
  52895. fSafe.Lock;
  52896. try
  52897. fCredentials.Add(aName,aPassword);
  52898. finally
  52899. fSafe.UnLock;
  52900. end;
  52901. end;
  52902. procedure TSynAuthentication.DisauthenticateUser(const aName: RawUTF8);
  52903. begin
  52904. fSafe.Lock;
  52905. try
  52906. fCredentials.Delete(aName);
  52907. finally
  52908. fSafe.UnLock;
  52909. end;
  52910. end;
  52911. { TSynUniqueIdentifierBits }
  52912. function TSynUniqueIdentifierBits.Counter: word;
  52913. begin
  52914. result := PWord(@Value)^ and $7fff;
  52915. end;
  52916. function TSynUniqueIdentifierBits.ProcessID: TSynUniqueIdentifierProcess;
  52917. begin
  52918. result := (PCardinal(@Value)^ shr 15) and $ffff;
  52919. end;
  52920. function TSynUniqueIdentifierBits.CreateTimeUnix: cardinal;
  52921. begin
  52922. result := Value shr 31;
  52923. end;
  52924. {$ifndef NOVARIANTS}
  52925. function TSynUniqueIdentifierBits.AsVariant: variant;
  52926. begin
  52927. result := _ObjFast(['Created',DateTimeToIso8601Text(CreateDateTime),
  52928. 'Identifier',ProcessID,'Counter',Counter,'Value',Value,
  52929. 'Hex',Int64ToHex(Value)]);
  52930. end;
  52931. {$endif NOVARIANTS}
  52932. {$ifndef DELPHI5OROLDER}
  52933. function TSynUniqueIdentifierBits.Equal(const Another: TSynUniqueIdentifierBits): boolean;
  52934. begin
  52935. result := Value=Another.Value;
  52936. end;
  52937. {$endif}
  52938. procedure TSynUniqueIdentifierBits.From(const AID: TSynUniqueIdentifier);
  52939. begin
  52940. Value := AID;
  52941. end;
  52942. function TSynUniqueIdentifierBits.CreateTimeLog: TTimeLog;
  52943. begin
  52944. PTimeLogBits(@result)^.From(UnixTimeToDateTime(Value shr 31));
  52945. end;
  52946. function TSynUniqueIdentifierBits.CreateDateTime: TDateTime;
  52947. begin
  52948. result := UnixTimeToDateTime(Value shr 31);
  52949. end;
  52950. function TSynUniqueIdentifierBits.ToHexa: RawUTF8;
  52951. begin
  52952. Int64ToHex(Value,result);
  52953. end;
  52954. function TSynUniqueIdentifierBits.FromHexa(const hexa: RawUTF8): boolean;
  52955. begin
  52956. result := (Length(hexa)=16) and HexDisplayToBin(pointer(hexa),@Value,sizeof(Value));
  52957. end;
  52958. procedure TSynUniqueIdentifierBits.FromDateTime(aDateTime: TDateTime);
  52959. begin
  52960. Value := DateTimeToUnixTime(aDateTime) shl 31;
  52961. end;
  52962. { TSynUniqueIdentifierGenerator }
  52963. const // fSafe.Padding[] slots
  52964. SYNUNIQUEGEN_COMPUTECOUNT = 0;
  52965. procedure TSynUniqueIdentifierGenerator.ComputeNew(
  52966. out result: TSynUniqueIdentifierBits);
  52967. var tix, currentTime: cardinal;
  52968. begin
  52969. tix := GetTickCount64 shr 8; // retrieve time every 256 ms
  52970. fSafe.Lock;
  52971. try
  52972. if tix<>fLastTix then begin
  52973. fLastTix := tix;
  52974. currentTime := DateTimeToUnixTime(NowUTC);
  52975. if currentTime>fUnixCreateTime then begin
  52976. fUnixCreateTime := currentTime;
  52977. fLastCounter := 0; // reset
  52978. end;
  52979. end;
  52980. if fLastCounter=$7fff then begin // collision (unlikely) -> cheat on timestamp
  52981. inc(fUnixCreateTime);
  52982. fLastCounter := 0;
  52983. end else
  52984. inc(fLastCounter);
  52985. result.Value := Int64(fLastCounter or fIdentifierShifted) or
  52986. (Int64(fUnixCreateTime) shl 31);
  52987. inc(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64);
  52988. finally
  52989. fSafe.UnLock;
  52990. end;
  52991. end;
  52992. function TSynUniqueIdentifierGenerator.ComputeNew: Int64;
  52993. begin
  52994. ComputeNew(PSynUniqueIdentifierBits(@result)^);
  52995. end;
  52996. function TSynUniqueIdentifierGenerator.GetComputedCount: Int64;
  52997. begin
  52998. {$ifdef NOVARIANTS}
  52999. fSafe.Lock;
  53000. result := fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT].VInt64;
  53001. fSafe.Unlock;
  53002. {$else}
  53003. result := fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT];
  53004. {$endif}
  53005. end;
  53006. procedure TSynUniqueIdentifierGenerator.ComputeFromDateTime(aDateTime: TDateTime;
  53007. out result: TSynUniqueIdentifierBits);
  53008. begin // assume fLastCounter=0
  53009. result.Value := (DateTimeToUnixTime(aDateTime) shl 31) or fIdentifierShifted;
  53010. end;
  53011. procedure TSynUniqueIdentifierGenerator.ComputeFromUnixTime(aUnixTime: Int64;
  53012. out result: TSynUniqueIdentifierBits);
  53013. begin // assume fLastCounter=0
  53014. result.Value := (aUnixTime shl 31) or fIdentifierShifted;
  53015. end;
  53016. constructor TSynUniqueIdentifierGenerator.Create(aIdentifier: TSynUniqueIdentifierProcess;
  53017. const aSharedObfuscationKey: RawUTF8);
  53018. var i, len: integer;
  53019. crc: cardinal;
  53020. begin
  53021. fIdentifier := aIdentifier;
  53022. fIdentifierShifted := aIdentifier shl 15;
  53023. fSafe.Init;
  53024. {$ifdef NOVARIANTS}
  53025. variant(fSafe.Padding[SYNUNIQUEGEN_COMPUTECOUNT]) := 0;
  53026. {$else}
  53027. fSafe.LockedInt64[SYNUNIQUEGEN_COMPUTECOUNT] := 0;
  53028. {$endif}
  53029. // compute obfuscation key using hash diffusion of the supplied text
  53030. len := length(aSharedObfuscationKey);
  53031. crc := crc32ctab[0,len and 1023];
  53032. for i := 0 to high(fCrypto)+1 do begin
  53033. crc := crc32ctab[0,crc and 1023] xor crc32ctab[3,i] xor
  53034. kr32(crc,pointer(aSharedObfuscationKey),len) xor
  53035. crc32c(crc,pointer(aSharedObfuscationKey),len) xor
  53036. fnv32(crc,pointer(aSharedObfuscationKey),len);
  53037. // do not modify those hashes above or you will break obfuscation pattern!
  53038. if i<=high(fCrypto) then
  53039. fCrypto[i] := crc else
  53040. fCryptoCRC := crc;
  53041. end;
  53042. // due to the weakness of the hash algorithms used, this approach is a bit
  53043. // naive and would be broken easily with brute force - but point here is to
  53044. // hide/obfuscate public values at end-user level (e.g. when publishing URIs),
  53045. // not implement strong security, so it sounds good enough for our purpose
  53046. end;
  53047. destructor TSynUniqueIdentifierGenerator.Destroy;
  53048. begin
  53049. fSafe.Done;
  53050. inherited Destroy;
  53051. end;
  53052. type // used to compute a 24 hexadecimal chars obfuscated pseudo file name
  53053. TSynUniqueIdentifierObfuscatedBits = packed record
  53054. crc: cardinal;
  53055. id: TSynUniqueIdentifierBits;
  53056. end;
  53057. function TSynUniqueIdentifierGenerator.ToObfuscated(
  53058. const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated;
  53059. var bits: TSynUniqueIdentifierObfuscatedBits;
  53060. key: cardinal;
  53061. begin
  53062. result := '';
  53063. if aIdentifier=0 then
  53064. exit;
  53065. bits.id.Value := aIdentifier;
  53066. if self=nil then
  53067. key := 0 else
  53068. key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC;
  53069. bits.crc := crc32c(bits.id.ProcessID,@bits.id,sizeof(bits.id)) xor key;
  53070. if self<>nil then
  53071. bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^;
  53072. result := BinToHex(@bits,SizeOf(bits));
  53073. end;
  53074. function TSynUniqueIdentifierGenerator.FromObfuscated(
  53075. const aObfuscated: TSynUniqueIdentifierObfuscated;
  53076. out aIdentifier: TSynUniqueIdentifier): boolean;
  53077. var bits: TSynUniqueIdentifierObfuscatedBits;
  53078. len: integer;
  53079. key: cardinal;
  53080. begin
  53081. result := false;
  53082. len := PosEx('.',aObfuscated);
  53083. if len=0 then
  53084. len := Length(aObfuscated) else
  53085. dec(len); // trim right '.jpg'
  53086. if (len<>sizeof(bits)*2) or
  53087. not SynCommons.HexToBin(pointer(aObfuscated),@bits,sizeof(bits)) then
  53088. exit;
  53089. if self=nil then
  53090. key := 0 else begin
  53091. bits.id.Value := bits.id.Value xor PInt64(@fCrypto[high(fCrypto)-1])^;
  53092. key := crc32ctab[0,bits.id.ProcessID and 1023] xor fCryptoCRC;
  53093. end;
  53094. if crc32c(bits.id.ProcessID,@bits.id,SizeOf(bits.id)) xor key=bits.crc then begin
  53095. aIdentifier := bits.id.Value;
  53096. result := true;
  53097. end;
  53098. end;
  53099. { TSynBackgroundThreadAbstract }
  53100. {$ifdef MSWINDOWS}
  53101. function IsDebuggerPresent: BOOL; stdcall; external kernel32; // since XP
  53102. {$endif}
  53103. procedure SetCurrentThreadName(const Format: RawUTF8; const Args: array of const);
  53104. begin
  53105. SetThreadName(GetCurrentThreadId,Format,Args);
  53106. end;
  53107. procedure SetThreadName(ThreadID: TThreadID; const Format: RawUTF8;
  53108. const Args: array of const);
  53109. var name: RawUTF8;
  53110. begin
  53111. FormatUTF8(Format,Args,name);
  53112. SetThreadNameInternal(ThreadID,name);
  53113. end;
  53114. procedure SetThreadNameDefault(ThreadID: TThreadID; const Name: RawUTF8);
  53115. var s: RawByteString;
  53116. {$ifndef ISDELPHIXE2}
  53117. {$ifdef MSWINDOWS}
  53118. info: record
  53119. FType: LongWord; // must be 0x1000
  53120. FName: PAnsiChar; // pointer to name (in user address space)
  53121. FThreadID: LongWord; // thread ID (-1 indicates caller thread)
  53122. FFlags: LongWord; // reserved for future use, must be zero
  53123. end;
  53124. {$endif}
  53125. {$endif}
  53126. begin
  53127. {$ifdef FPC}
  53128. exit;
  53129. {$endif}
  53130. {$ifdef NOSETTHREADNAME}
  53131. exit;
  53132. {$endif}
  53133. {$ifdef MSWINDOWS}
  53134. if not IsDebuggerPresent then
  53135. exit;
  53136. {$endif}
  53137. s := CurrentAnsiConvert.UTF8ToAnsi(Name);
  53138. {$ifdef ISDELPHIXE2}
  53139. TThread.NameThreadForDebugging(s,ThreadID);
  53140. {$else}
  53141. {$ifdef MSWINDOWS}
  53142. info.FType := $1000;
  53143. info.FName := pointer(s);
  53144. info.FThreadID := ThreadID;
  53145. info.FFlags := 0;
  53146. try
  53147. RaiseException($406D1388,0,SizeOf(info) div SizeOf(LongWord),@info);
  53148. except {ignore} end;
  53149. {$endif}
  53150. {$endif}
  53151. end;
  53152. constructor TSynBackgroundThreadAbstract.Create(const aThreadName: RawUTF8;
  53153. OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent);
  53154. begin
  53155. fProcessEvent := TEvent.Create(nil,false,false,'');
  53156. fThreadName := aThreadName;
  53157. fOnBeforeExecute := OnBeforeExecute;
  53158. fOnAfterExecute := OnAfterExecute;
  53159. InitializeCriticalSection(fPendingProcessLock);
  53160. inherited Create(false{$ifdef FPC},512*1024{$endif}); // DefaultStackSize=512KB
  53161. end;
  53162. {$ifdef KYLIX3}
  53163. type
  53164. // see http://stackoverflow.com/a/3085509/458259 about the Kylix only bug
  53165. TEventHack = class(THandleObject) // should match EXACTLY SyncObjs.pas source!
  53166. private
  53167. FEvent: TSemaphore;
  53168. FManualReset: Boolean;
  53169. end;
  53170. function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult;
  53171. var E: TEventHack absolute Event;
  53172. procedure SetResult(res: integer);
  53173. begin
  53174. if res=0 then
  53175. result := wrSignaled else
  53176. if errno in [EAGAIN,ETIMEDOUT] then
  53177. result := wrTimeOut else begin
  53178. write(TimeOut,':',errno,' ');
  53179. result := wrError;
  53180. end;
  53181. end;
  53182. {.$define USESEMTRYWAIT}
  53183. // sem_timedwait() is slower than sem_trywait(), but consuming much less CPU
  53184. {$ifdef USESEMTRYWAIT}
  53185. var time: timespec;
  53186. {$else}
  53187. var start,current: Int64;
  53188. elapsed: LongWord;
  53189. {$endif}
  53190. begin
  53191. if Timeout=INFINITE then begin
  53192. SetResult(sem_wait(E.FEvent));
  53193. exit;
  53194. end;
  53195. if TimeOut=0 then begin
  53196. SetResult(sem_trywait(E.FEvent));
  53197. exit;
  53198. end;
  53199. {$ifdef USESEMTRYWAIT}
  53200. clock_gettime(CLOCK_REALTIME,time);
  53201. inc(time.tv_sec,TimeOut div 1000);
  53202. inc(time.tv_nsec,(TimeOut mod 1000)*1000000);
  53203. while time.tv_nsec>1000000000 do begin
  53204. inc(time.tv_sec);
  53205. dec(time.tv_nsec,1000000000);
  53206. end;
  53207. SetResult(sem_timedwait(E.FEvent,time));
  53208. {$else}
  53209. start := GetTickCount64;
  53210. repeat
  53211. if sem_trywait(E.FEvent)=0 then begin
  53212. result := wrSignaled;
  53213. break;
  53214. end;
  53215. current := GetTickCount64;
  53216. elapsed := current-start;
  53217. if elapsed=0 then
  53218. sched_yield else
  53219. if elapsed>TimeOut then begin
  53220. result := wrTimeOut;
  53221. break;
  53222. end else
  53223. if elapsed<5 then
  53224. usleep(50) else
  53225. usleep(1000);
  53226. until false;
  53227. {$endif}
  53228. if E.FManualReset then begin
  53229. repeat until sem_trywait(E.FEvent)<>0; // reset semaphore state
  53230. sem_post(E.FEvent);
  53231. end;
  53232. end;
  53233. {$else KYLIX3} // original FPC or Windows is OK:
  53234. function FixedWaitFor(Event: TEvent; Timeout: LongWord): TWaitResult;
  53235. begin
  53236. result := Event.WaitFor(TimeOut);
  53237. end;
  53238. {$endif KYLIX3}
  53239. procedure FixedWaitForever(Event: TEvent);
  53240. begin
  53241. FixedWaitFor(Event,INFINITE);
  53242. end;
  53243. destructor TSynBackgroundThreadAbstract.Destroy;
  53244. begin
  53245. FreeAndNil(fProcessEvent);
  53246. DeleteCriticalSection(fPendingProcessLock);
  53247. inherited Destroy;
  53248. end;
  53249. procedure TSynBackgroundThreadAbstract.Execute;
  53250. begin
  53251. try
  53252. if fThreadName='' then
  53253. SetCurrentThreadName('%(%)',[self,pointer(self)]) else
  53254. SetCurrentThreadName('%',[fThreadName]);
  53255. if Assigned(fOnBeforeExecute) then
  53256. fOnBeforeExecute(self);
  53257. try
  53258. while not Terminated do
  53259. ExecuteLoop;
  53260. finally
  53261. if Assigned(fOnAfterExecute) then
  53262. fOnAfterExecute(self);
  53263. end;
  53264. finally
  53265. fExecuteFinished := true;
  53266. end;
  53267. end;
  53268. { TSynBackgroundThreadMethodAbstract }
  53269. constructor TSynBackgroundThreadMethodAbstract.Create(aOnIdle: TOnIdleSynBackgroundThread;
  53270. const aThreadName: RawUTF8; OnBeforeExecute,OnAfterExecute: TNotifyThreadEvent);
  53271. begin
  53272. fOnIdle := aOnIdle; // cross-platform may run Execute as soon as Create is called
  53273. fCallerEvent := TEvent.Create(nil,false,false,'');
  53274. inherited Create(aThreadName,OnBeforeExecute,OnAfterExecute);
  53275. end;
  53276. destructor TSynBackgroundThreadMethodAbstract.Destroy;
  53277. begin
  53278. SetPendingProcess(flagDestroying);
  53279. fProcessEvent.SetEvent; // notify terminated
  53280. FixedWaitForever(fCallerEvent);
  53281. FreeAndNil(fCallerEvent);
  53282. inherited Destroy;
  53283. end;
  53284. function TSynBackgroundThreadMethodAbstract.GetPendingProcess: TSynBackgroundThreadProcessStep;
  53285. begin
  53286. EnterCriticalSection(fPendingProcessLock);
  53287. result := fPendingProcessFlag;
  53288. LeaveCriticalSection(fPendingProcessLock);
  53289. end;
  53290. procedure TSynBackgroundThreadMethodAbstract.SetPendingProcess(State: TSynBackgroundThreadProcessStep);
  53291. begin
  53292. EnterCriticalSection(fPendingProcessLock);
  53293. fPendingProcessFlag := State;
  53294. LeaveCriticalSection(fPendingProcessLock);
  53295. end;
  53296. procedure TSynBackgroundThreadMethodAbstract.ExecuteLoop;
  53297. begin
  53298. case FixedWaitFor(fProcessEvent,INFINITE) of
  53299. wrSignaled:
  53300. case GetPendingProcess of
  53301. flagDestroying: begin
  53302. fCallerEvent.SetEvent; // abort caller thread process
  53303. Terminate; // forces Execute loop ending
  53304. exit;
  53305. end;
  53306. flagStarted:
  53307. if not Terminated then
  53308. try
  53309. fBackgroundException := nil;
  53310. try
  53311. if Assigned(fOnBeforeProcess) then
  53312. fOnBeforeProcess(self);
  53313. try
  53314. Process;
  53315. finally
  53316. if Assigned(fOnAfterProcess) then
  53317. fOnAfterProcess(self);
  53318. end;
  53319. except
  53320. {$ifdef DELPHI5OROLDER}
  53321. on E: Exception do
  53322. fBackgroundException := ESynException.CreateUTF8(
  53323. 'Redirected %: "%"',[E,E.Message]);
  53324. {$else}
  53325. fBackgroundException := AcquireExceptionObject;
  53326. {$endif}
  53327. end;
  53328. finally
  53329. SetPendingProcess(flagFinished);
  53330. fCallerEvent.SetEvent;
  53331. end;
  53332. end;
  53333. end;
  53334. end;
  53335. { TSynBackgroundThreadEvent }
  53336. constructor TSynBackgroundThreadEvent.Create(aOnProcess: TOnProcessSynBackgroundThread;
  53337. aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8);
  53338. begin
  53339. inherited Create(aOnIdle,aThreadName);
  53340. fOnProcess := aOnProcess;
  53341. end;
  53342. procedure TSynBackgroundThreadEvent.Process;
  53343. begin
  53344. if not Assigned(fOnProcess) then
  53345. raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]);
  53346. fOnProcess(self,fParam);
  53347. end;
  53348. function TSynBackgroundThreadMethodAbstract.AcquireThread: TSynBackgroundThreadProcessStep;
  53349. begin
  53350. EnterCriticalSection(fPendingProcessLock);
  53351. try
  53352. result := fPendingProcessFlag;
  53353. if result=flagIdle then begin // we just acquired the thread! congrats!
  53354. fPendingProcessFlag := flagStarted; // atomic set "started" flag
  53355. fCallerThreadID := ThreadID;
  53356. end;
  53357. finally
  53358. LeaveCriticalSection(fPendingProcessLock);
  53359. end;
  53360. end;
  53361. function TSynBackgroundThreadMethodAbstract.OnIdleProcessNotify(start: Int64): integer;
  53362. begin
  53363. result := GetTickCount64-start;
  53364. if result<0 then
  53365. result := MaxInt; // should happen only under XP -> ignore
  53366. if Assigned(fOnIdle) then
  53367. fOnIdle(self,result) ;
  53368. end;
  53369. procedure TSynBackgroundThreadMethodAbstract.WaitForFinished(start: Int64);
  53370. var E: Exception;
  53371. begin
  53372. if (self=nil) or not (fPendingProcessFlag in [flagStarted, flagFinished]) then
  53373. exit; // nothing to wait for
  53374. try
  53375. {$ifdef MSWINDOWS} // do process the OnIdle only if UI
  53376. if Assigned(fOnIdle) then begin
  53377. while FixedWaitFor(fCallerEvent,100)=wrTimeout do
  53378. OnIdleProcessNotify(start);
  53379. end else
  53380. {$endif}
  53381. FixedWaitForever(fCallerEvent);
  53382. if fPendingProcessFlag<>flagFinished then
  53383. ESynException.CreateUTF8('%.WaitForFinished: flagFinished?',[self]);
  53384. if fBackgroundException<>nil then begin
  53385. E := fBackgroundException;
  53386. fBackgroundException := nil;
  53387. raise E; // raise background exception in the calling scope
  53388. end;
  53389. finally
  53390. fParam := nil;
  53391. fCallerThreadID := 0;
  53392. FreeAndNil(fBackgroundException);
  53393. SetPendingProcess(flagIdle);
  53394. if Assigned(fOnIdle) then
  53395. fOnIdle(self,-1); // notify finished
  53396. end;
  53397. end;
  53398. function TSynBackgroundThreadMethodAbstract.RunAndWait(OpaqueParam: pointer): boolean;
  53399. var start: Int64;
  53400. ThreadID: TThreadID;
  53401. begin
  53402. result := false;
  53403. ThreadID := GetCurrentThreadId;
  53404. if (self=nil) or (ThreadID=fCallerThreadID) then
  53405. // avoid endless loop when waiting in same thread (e.g. UI + OnIdle)
  53406. exit;
  53407. // 1. wait for any previous request to be finished (should not happen often)
  53408. if Assigned(fOnIdle) then
  53409. fOnIdle(self,0); // notify started
  53410. start := GetTickCount64;
  53411. repeat
  53412. case AcquireThread of
  53413. flagDestroying:
  53414. exit;
  53415. flagIdle:
  53416. break; // we acquired the background thread
  53417. end;
  53418. case OnIdleProcessNotify(start) of // Windows.GetTickCount64 res is 10-16 ms
  53419. 0..20: SleepHiRes(0);
  53420. 21..100: SleepHiRes(1);
  53421. 101..900: SleepHiRes(5);
  53422. else SleepHiRes(50);
  53423. end;
  53424. until false;
  53425. // 2. process execution in the background thread
  53426. fParam := OpaqueParam;
  53427. fProcessEvent.SetEvent; // notify background thread for Call pending process
  53428. WaitForFinished(start); // wait for flagFinished, then set flagIdle
  53429. result := true;
  53430. end;
  53431. function TSynBackgroundThreadMethodAbstract.GetOnIdleBackgroundThreadActive: boolean;
  53432. begin
  53433. result := (self<>nil) and Assigned(fOnIdle) and (GetPendingProcess<>flagIdle);
  53434. end;
  53435. { TSynBackgroundThreadMethod }
  53436. procedure TSynBackgroundThreadMethod.Process;
  53437. var Method: ^TThreadMethod;
  53438. begin
  53439. if fParam=nil then
  53440. raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]);
  53441. Method := fParam;
  53442. Method^();
  53443. end;
  53444. procedure TSynBackgroundThreadMethod.RunAndWait(Method: TThreadMethod);
  53445. var Met: TMethod absolute Method;
  53446. begin
  53447. inherited RunAndWait(@Met);
  53448. end;
  53449. { TSynBackgroundThreadProcedure }
  53450. constructor TSynBackgroundThreadProcedure.Create(aOnProcess: TOnProcessSynBackgroundThreadProc;
  53451. aOnIdle: TOnIdleSynBackgroundThread; const aThreadName: RawUTF8);
  53452. begin
  53453. inherited Create(aOnIdle,aThreadName);
  53454. fOnProcess := aOnProcess;
  53455. end;
  53456. procedure TSynBackgroundThreadProcedure.Process;
  53457. begin
  53458. if not Assigned(fOnProcess) then
  53459. raise ESynException.CreateUTF8('Invalid %.RunAndWait() call',[self]);
  53460. fOnProcess(fParam);
  53461. end;
  53462. { TSynParallelProcessThread }
  53463. procedure TSynParallelProcessThread.Process;
  53464. begin
  53465. if not Assigned(fMethod) then
  53466. exit;
  53467. fMethod(fIndexStart,fIndexStop);
  53468. fMethod := nil;
  53469. end;
  53470. procedure TSynParallelProcessThread.Start(
  53471. Method: TSynParallelProcessMethod; IndexStart, IndexStop: integer);
  53472. begin
  53473. fMethod := Method;
  53474. fIndexStart := IndexStart;
  53475. fIndexStop := IndexStop;
  53476. fProcessEvent.SetEvent; // notify execution
  53477. end;
  53478. { TSynBackgroundThreadProcess }
  53479. constructor TSynBackgroundThreadProcess.Create(const aThreadName: RawUTF8;
  53480. aOnProcess: TOnSynBackgroundThreadProcess; aOnProcessMS: cardinal;
  53481. aOnBeforeExecute, aOnAfterExecute: TNotifyThreadEvent; aStats: TSynMonitorClass);
  53482. begin
  53483. if not Assigned(aOnProcess) then
  53484. raise ESynException.CreateUTF8('%.Create(aOnProcess=nil)',[self]);
  53485. if aStats<>nil then
  53486. fStats := aStats.Create(aThreadName) else
  53487. fStats := TSynMonitor.Create(aThreadName);
  53488. fOnProcess := aOnProcess;
  53489. fOnProcessMS := aOnProcessMS;
  53490. if fOnProcessMS=0 then
  53491. fOnProcessMS := INFINITE; // wait until ProcessEvent.SetEvent or Terminated
  53492. inherited Create(aThreadName,aOnBeforeExecute,aOnAfterExecute);
  53493. end;
  53494. destructor TSynBackgroundThreadProcess.Destroy;
  53495. begin
  53496. if not fExecuteFinished then begin
  53497. Terminate;
  53498. fProcessEvent.SetEvent; // notify terminated
  53499. while not fExecuteFinished do
  53500. Sleep(1);
  53501. end;
  53502. inherited Destroy;
  53503. fStats.Free;
  53504. end;
  53505. procedure TSynBackgroundThreadProcess.ExecuteLoop;
  53506. var wait: TWaitResult;
  53507. begin
  53508. wait := FixedWaitFor(fProcessEvent,fOnProcessMS);
  53509. if not Terminated and (wait in [wrSignaled,wrTimeout]) then
  53510. try
  53511. fStats.ProcessStartTask;
  53512. try
  53513. fOnProcess(self,wait);
  53514. finally
  53515. fStats.ProcessEnd;
  53516. end;
  53517. except
  53518. on E: Exception do begin
  53519. fStats.ProcessError({$ifdef NOVARIANTS}E.ClassName{$else}ObjectToVariant(E){$endif});
  53520. if Assigned(fOnException) then
  53521. fOnException(E);
  53522. end;
  53523. end;
  53524. end;
  53525. { TSynParallelProcess }
  53526. constructor TSynParallelProcess.Create(ThreadPoolCount: integer; const ThreadName: RawUTF8;
  53527. OnBeforeExecute, OnAfterExecute: TNotifyThreadEvent);
  53528. var i: integer;
  53529. begin
  53530. inherited Create;
  53531. if ThreadPoolCount<0 then
  53532. raise ESynParallelProcess.CreateUTF8('%.Create(%,%)',[Self,ThreadPoolCount,ThreadName]);
  53533. if ThreadPoolCount>32 then
  53534. ThreadPoolCount := 32;
  53535. fThreadPoolCount := ThreadPoolCount;
  53536. fThreadName := ThreadName;
  53537. SetLength(fPool,fThreadPoolCount);
  53538. for i := 0 to fThreadPoolCount-1 do
  53539. fPool[i] := TSynParallelProcessThread.Create(nil,FormatUTF8('%#%/%',
  53540. [fThreadName,i+1,fThreadPoolCount]),OnBeforeExecute,OnAfterExecute);
  53541. end;
  53542. destructor TSynParallelProcess.Destroy;
  53543. begin
  53544. ObjArrayClear(fPool);
  53545. inherited;
  53546. end;
  53547. procedure TSynParallelProcess.ParallelRunAndWait(Method: TSynParallelProcessMethod;
  53548. MethodCount: integer);
  53549. var use,t,n,perthread: integer;
  53550. error: RawUTF8;
  53551. begin
  53552. if (MethodCount<=0) or not Assigned(Method) then
  53553. exit;
  53554. if (self=nil) or (MethodCount=1) or (fThreadPoolCount=0) then begin
  53555. Method(0,0); // no need to use a background thread here
  53556. exit;
  53557. end;
  53558. use := MethodCount;
  53559. if use>fThreadPoolCount+1 then // +1 to include current thread
  53560. use := fThreadPoolCount+1;
  53561. try
  53562. // start secondary threads
  53563. perthread := MethodCount div use;
  53564. if perthread=0 then
  53565. use := 1;
  53566. n := 0;
  53567. for t := 0 to use-2 do begin
  53568. repeat
  53569. case fPool[t].AcquireThread of
  53570. flagDestroying: // should not happen
  53571. raise ESynParallelProcess.CreateUTF8(
  53572. '%.ParallelRunAndWait [%] destroying',[self,fPool[t].fThreadName]);
  53573. flagIdle:
  53574. break; // acquired (should always be the case)
  53575. end;
  53576. Sleep(1);
  53577. until false;
  53578. fPool[t].Start(Method,n,n+perthread-1);
  53579. inc(n,perthread);
  53580. inc(fParallelRunCount);
  53581. end;
  53582. // run remaining items in the current thread
  53583. if n<MethodCount then begin
  53584. Method(n,MethodCount-1);
  53585. inc(fParallelRunCount);
  53586. end;
  53587. finally
  53588. // wait for the process to finish
  53589. for t := 0 to use-2 do
  53590. try
  53591. fPool[t].WaitForFinished(0);
  53592. except
  53593. on E: Exception do
  53594. error := FormatUTF8('% % on thread % [%]',[error,E,fPool[t].fThreadName,E.Message]);
  53595. end;
  53596. if error<>'' then
  53597. raise ESynParallelProcess.CreateUTF8('%.ParallelRunAndWait: %',[self,error]);
  53598. end;
  53599. end;
  53600. { TBlockingProcess }
  53601. constructor TBlockingProcess.Create(aTimeOutMs: integer; const aSafe: TSynLocker);
  53602. begin
  53603. inherited Create(nil,false,false,'');
  53604. if aTimeOutMs<=0 then
  53605. fTimeOutMs := 3000 else // never wait for ever
  53606. fTimeOutMs := aTimeOutMs;
  53607. fSafe := @aSafe;
  53608. end;
  53609. constructor TBlockingProcess.Create(aTimeOutMs: integer);
  53610. begin
  53611. fOwnedSafe := TAutoLocker.Create;
  53612. Create(aTimeOutMS,fOwnedSafe.fSafe);
  53613. end;
  53614. destructor TBlockingProcess.Destroy;
  53615. begin
  53616. fOwnedSafe.Free;
  53617. inherited Destroy;
  53618. end;
  53619. function TBlockingProcess.WaitFor: TBlockingEvent;
  53620. begin
  53621. fSafe^.Lock;
  53622. try
  53623. result := fEvent;
  53624. if fEvent in [evRaised,evTimeOut] then
  53625. exit;
  53626. fEvent := evWaiting;
  53627. finally
  53628. fSafe^.UnLock;
  53629. end;
  53630. FixedWaitFor(self,fTimeOutMs);
  53631. fSafe^.Lock;
  53632. try
  53633. if fEvent<>evRaised then
  53634. fEvent := evTimeOut;
  53635. result := fEvent;
  53636. finally
  53637. fSafe^.UnLock;
  53638. end;
  53639. end;
  53640. function TBlockingProcess.WaitFor(TimeOutMS: integer): TBlockingEvent;
  53641. begin
  53642. if TimeOutMS <= 0 then
  53643. fTimeOutMs := 3000 // never wait for ever
  53644. else
  53645. fTimeOutMs := TimeOutMS;
  53646. result := WaitFor;
  53647. end;
  53648. function TBlockingProcess.NotifyFinished(alreadyLocked: boolean): boolean;
  53649. begin
  53650. result := false;
  53651. if not alreadyLocked then
  53652. fSafe^.Lock;
  53653. try
  53654. if fEvent in [evRaised,evTimeOut] then
  53655. exit; // ignore if already notified
  53656. fEvent := evRaised;
  53657. SetEvent; // notify caller to unlock "WaitFor" method
  53658. result := true;
  53659. finally
  53660. fSafe^.UnLock;
  53661. end;
  53662. end;
  53663. procedure TBlockingProcess.ResetInternal;
  53664. begin
  53665. ResetEvent;
  53666. fEvent := evNone;
  53667. end;
  53668. function TBlockingProcess.Reset: boolean;
  53669. begin
  53670. fSafe^.Lock;
  53671. try
  53672. result := fEvent<>evWaiting;
  53673. if result then
  53674. ResetInternal;
  53675. finally
  53676. fSafe^.UnLock;
  53677. end;
  53678. end;
  53679. procedure TBlockingProcess.Lock;
  53680. begin
  53681. fSafe^.Lock;
  53682. end;
  53683. procedure TBlockingProcess.Unlock;
  53684. begin
  53685. fSafe^.Unlock;
  53686. end;
  53687. { TBlockingProcessPoolItem }
  53688. procedure TBlockingProcessPoolItem.ResetInternal;
  53689. begin
  53690. inherited ResetInternal; // set fEvent := evNone
  53691. fCall := 0;
  53692. end;
  53693. { TBlockingProcessPool }
  53694. constructor TBlockingProcessPool.Create(aClass: TBlockingProcessPoolItemClass);
  53695. begin
  53696. inherited Create;
  53697. if aClass=nil then
  53698. fClass := TBlockingProcessPoolItem else
  53699. fClass := aClass;
  53700. fPool := TObjectListLocked.Create(true);
  53701. end;
  53702. const
  53703. CALL_DESTROYING = -1;
  53704. destructor TBlockingProcessPool.Destroy;
  53705. var i: integer;
  53706. someWaiting: boolean;
  53707. begin
  53708. fCallCounter := CALL_DESTROYING;
  53709. someWaiting := false;
  53710. for i := 0 to fPool.Count-1 do
  53711. with TBlockingProcessPoolItem(fPool.List[i]) do
  53712. if Event=evWaiting then begin
  53713. SetEvent; // release WaitFor (with evTimeOut)
  53714. someWaiting := true;
  53715. end;
  53716. if someWaiting then
  53717. sleep(10); // propagate the pending evTimeOut to the WaitFor threads
  53718. fPool.Free;
  53719. inherited;
  53720. end;
  53721. function TBlockingProcessPool.NewProcess(aTimeOutMs: integer): TBlockingProcessPoolItem;
  53722. var i: integer;
  53723. p: ^TBlockingProcessPoolItem;
  53724. begin
  53725. result := nil;
  53726. if fCallCounter=CALL_DESTROYING then
  53727. exit;
  53728. if aTimeOutMs<=0 then
  53729. aTimeOutMs := 3000; // never wait for ever
  53730. fPool.Safe.Lock;
  53731. try
  53732. p := pointer(fPool.List);
  53733. for i := 1 to fPool.Count do
  53734. if p^.Call=0 then begin
  53735. result := p^; // found a non-used entry
  53736. result.fTimeOutMs := aTimeOutMS;
  53737. break;
  53738. end else
  53739. inc(p);
  53740. if result=nil then begin
  53741. result := fClass.Create(aTimeOutMS);
  53742. fPool.Add(result);
  53743. end;
  53744. inc(fCallCounter); // 1,2,3,...
  53745. result.fCall := fCallCounter;
  53746. finally
  53747. fPool.Safe.UnLock;
  53748. end;
  53749. end;
  53750. function TBlockingProcessPool.FromCall(call: TBlockingProcessPoolCall;
  53751. locked: boolean): TBlockingProcessPoolItem;
  53752. var i: integer;
  53753. p: ^TBlockingProcessPoolItem;
  53754. begin
  53755. result := nil;
  53756. if (fCallCounter=CALL_DESTROYING) or (call<=0) then
  53757. exit;
  53758. fPool.Safe.Lock;
  53759. try
  53760. p := pointer(fPool.List);
  53761. for i := 1 to fPool.Count do
  53762. if p^.Call=call then begin
  53763. result := p^;
  53764. if locked then
  53765. result.Lock;
  53766. exit;
  53767. end else
  53768. inc(p);
  53769. finally
  53770. fPool.Safe.UnLock;
  53771. end;
  53772. end;
  53773. { MultiEvent* functions }
  53774. function MultiEventFind(const EventList; const Event: TMethod): integer;
  53775. var Events: TMethodDynArray absolute EventList;
  53776. begin
  53777. if Event.Code<>nil then // callback assigned
  53778. for result := 0 to length(Events)-1 do
  53779. if (Events[result].Code=Event.Code) and
  53780. (Events[result].Data=Event.Data) then
  53781. exit;
  53782. result := -1;
  53783. end;
  53784. function MultiEventAdd(var EventList; const Event: TMethod): boolean;
  53785. var Events: TMethodDynArray absolute EventList;
  53786. n: integer;
  53787. begin
  53788. result := false;
  53789. n := MultiEventFind(EventList,Event);
  53790. if n>=0 then
  53791. exit; // already registered
  53792. result := true;
  53793. n := length(Events);
  53794. SetLength(Events,n+1);
  53795. Events[n] := Event;
  53796. end;
  53797. procedure MultiEventRemove(var EventList; const Event: TMethod);
  53798. begin
  53799. MultiEventRemove(EventList,MultiEventFind(EventList,Event));
  53800. end;
  53801. procedure MultiEventRemove(var EventList; Index: Integer);
  53802. var Events: TMethodDynArray absolute EventList;
  53803. max: integer;
  53804. begin
  53805. max := length(Events);
  53806. if cardinal(index)<cardinal(max) then begin
  53807. dec(max);
  53808. MoveFast(Events[index+1],Events[index],(max-index)*sizeof(Events[index]));
  53809. SetLength(Events,max);
  53810. end;
  53811. end;
  53812. procedure MultiEventMerge(var DestList; const ToBeAddedList);
  53813. var Dest: TMethodDynArray absolute DestList;
  53814. New: TMethodDynArray absolute ToBeAddedList;
  53815. d,n: integer;
  53816. begin
  53817. d := length(Dest);
  53818. n := length(New);
  53819. if n=0 then
  53820. exit;
  53821. SetLength(Dest,d+n);
  53822. MoveFast(New[0],Dest[d],n*sizeof(TMethod));
  53823. end;
  53824. var
  53825. GarbageCollectorFreeAndNilList: TList;
  53826. procedure GarbageCollectorFree;
  53827. var i: integer;
  53828. begin
  53829. if GarbageCollectorFreeing then
  53830. exit; // when already called before finalization
  53831. GarbageCollectorFreeing := true;
  53832. for i := GarbageCollector.Count-1 downto 0 do // last in, first out
  53833. try
  53834. GarbageCollector.Delete(i); // will call GarbageCollector[i].Free
  53835. except
  53836. on Exception do
  53837. ; // just ignore exceptions in client code destructors
  53838. end;
  53839. for i := GarbageCollectorFreeAndNilList.Count-1 downto 0 do // LIFO
  53840. try
  53841. if PObject(GarbageCollectorFreeAndNilList.List[i])^<>nil then
  53842. FreeAndNil(PObject(GarbageCollectorFreeAndNilList.List[i])^);
  53843. except
  53844. on E: Exception do
  53845. ; // just ignore exceptions in client code destructors
  53846. end;
  53847. FreeAndNil(GarbageCollectorFreeAndNilList);
  53848. end;
  53849. procedure GarbageCollectorFreeAndNil(var InstanceVariable; Instance: TObject);
  53850. begin
  53851. TObject(InstanceVariable) := Instance;
  53852. GarbageCollectorFreeAndNilList.Add(@InstanceVariable);
  53853. end;
  53854. var
  53855. GlobalCriticalSection: TRTLCriticalSection;
  53856. procedure GlobalLock;
  53857. begin
  53858. EnterCriticalSection(GlobalCriticalSection);
  53859. end;
  53860. procedure GlobalUnLock;
  53861. begin
  53862. LeaveCriticalSection(GlobalCriticalSection);
  53863. end;
  53864. {$ifdef CPUINTEL}
  53865. procedure TestIntelCpuFeatures;
  53866. var regs: TRegisters;
  53867. begin
  53868. regs.edx := 0;
  53869. regs.ecx := 0;
  53870. GetCPUID(1,regs);
  53871. PIntegerArray(@CpuFeatures)^[0] := regs.edx;
  53872. PIntegerArray(@CpuFeatures)^[1] := regs.ecx;
  53873. GetCPUID(7,regs);
  53874. PIntegerArray(@CpuFeatures)^[2] := regs.ebx;
  53875. PByteArray(@CpuFeatures)^[12] := regs.ecx;
  53876. end;
  53877. {$endif CPUINTEL}
  53878. procedure InitSynCommonsConversionTables;
  53879. var i,n: integer;
  53880. v: byte;
  53881. crc: cardinal;
  53882. {$ifdef OWNNORMTOUPPER}
  53883. d: integer;
  53884. const n2u: array[138..255] of byte =
  53885. (83,139,140,141,90,143,144,145,146,147,148,149,150,151,152,153,83,155,140,
  53886. 157,90,89,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
  53887. 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,65,65,65,
  53888. 65,65,65,198,67,69,69,69,69,73,73,73,73,68,78,79,79,79,79,79,215,79,85,85,
  53889. 85,85,89,222,223,65,65,65,65,65,65,198,67,69,69,69,69,73,73,73,73,68,78,79,
  53890. 79,79,79,79,247,79,85,85,85,85,89,222,89);
  53891. {$endif OWNNORMTOUPPER}
  53892. const HexChars: array[0..15] of AnsiChar = '0123456789ABCDEF';
  53893. begin
  53894. JSON_CONTENT_TYPE_VAR := JSON_CONTENT_TYPE;
  53895. JSON_CONTENT_TYPE_HEADER_VAR := JSON_CONTENT_TYPE_HEADER;
  53896. {$ifdef FPC}
  53897. {$ifdef ISFPC27}
  53898. SetMultiByteConversionCodePage(CP_UTF8);
  53899. SetMultiByteRTLFileSystemCodePage(CP_UTF8);
  53900. {$endif}
  53901. {$endif FPC}
  53902. {$ifdef KYLIX3}
  53903. // if default locale is set to *.UTF-8, which is the case in most modern
  53904. // linux default configuration, unicode decode will fail in SysUtils.CheckLocale
  53905. setlocale(LC_CTYPE,'en_US'); // force locale for a UTF-8 server
  53906. {$endif}
  53907. {$ifndef EXTENDEDTOSTRING_USESTR}
  53908. {$ifdef ISDELPHIXE}
  53909. SettingsUS := TFormatSettings.Create($0409);
  53910. {$else}
  53911. GetLocaleFormatSettings($0409,SettingsUS);
  53912. {$endif}
  53913. SettingsUS.DecimalSeparator := '.'; // value may have been overriden :(
  53914. {$endif}
  53915. for i := 0 to 255 do
  53916. NormToUpperAnsi7Byte[i] := i;
  53917. for i := ord('a') to ord('z') do
  53918. dec(NormToUpperAnsi7Byte[i],32);
  53919. {$ifdef OWNNORMTOUPPER}
  53920. // initialize custom NormToUpper[] and NormToLower[] arrays
  53921. MoveFast(NormToUpperAnsi7,NormToUpper,138);
  53922. MoveFast(n2u,NormToUpperByte[138],sizeof(n2u));
  53923. for i := 0 to 255 do begin
  53924. d := NormToUpperByte[i];
  53925. if d in [ord('A')..ord('Z')] then
  53926. inc(d,32);
  53927. NormToLowerByte[i] := d;
  53928. end;
  53929. {$endif OWNNORMTOUPPER}
  53930. // code below is 55 bytes long, therefore shorter than a const array
  53931. FillcharFast(ConvertHexToBin[0],sizeof(ConvertHexToBin),255); // all to 255
  53932. v := 0;
  53933. for i := ord('0') to ord('9') do begin
  53934. ConvertHexToBin[i] := v;
  53935. inc(v);
  53936. end;
  53937. for i := ord('A') to ord('F') do begin
  53938. ConvertHexToBin[i] := v;
  53939. ConvertHexToBin[i+(ord('a')-ord('A'))] := v;
  53940. inc(v);
  53941. end;
  53942. for i := 0 to 255 do begin
  53943. TwoDigitsHex[i][1] := HexChars[i shr 4];
  53944. TwoDigitsHex[i][2] := HexChars[i and $f];
  53945. end;
  53946. FillcharFast(ConvertBase64ToBin,256,255); // invalid value set to -1
  53947. for i := 0 to high(b64) do
  53948. ConvertBase64ToBin[b64[i]] := i;
  53949. ConvertBase64ToBin['='] := -2; // special value for '='
  53950. for i := high(B2A) downto 0 do
  53951. if B2A[i]<#128 then
  53952. A2B[B2A[i]] := i;
  53953. for i := ord('a') to ord('z') do
  53954. A2B[AnsiChar(i-32)] := A2B[AnsiChar(i)]; // A-Z -> a-z
  53955. // initialize our internaly used TSynAnsiConvert engines
  53956. TSynAnsiConvert.Engine(0);
  53957. // initialize tables for crc32cfast() and SymmetricEncrypt/FillRandom
  53958. for i := 0 to 255 do begin
  53959. crc := i;
  53960. for n := 1 to 8 do
  53961. if (crc and 1)<>0 then // polynom is not the same as with zlib's crc32()
  53962. crc := (crc shr 1) xor $82f63b78 else
  53963. crc := crc shr 1;
  53964. crc32ctab[0,i] := crc;
  53965. end;
  53966. for i := 0 to 255 do begin
  53967. crc := crc32ctab[0,i];
  53968. for n := 1 to high(crc32ctab) do begin
  53969. crc := (crc shr 8) xor crc32ctab[0,ToByte(crc)];
  53970. crc32ctab[n,i] := crc;
  53971. end;
  53972. end;
  53973. UpperCopy255Buf := @UpperCopy255BufPas;
  53974. {$ifdef CPUINTEL}
  53975. if cfSSE42 in CpuFeatures then begin
  53976. crc32c := @crc32csse42;
  53977. {$ifndef PUREPASCAL}
  53978. StrComp := @StrCompSSE42;
  53979. DYNARRAY_SORTFIRSTFIELD[false,djRawUTF8] := @SortDynArrayAnsiStringSSE42;
  53980. DYNARRAY_SORTFIRSTFIELD[false,djWinAnsi] := @SortDynArrayAnsiStringSSE42;
  53981. DYNARRAY_SORTFIRSTFIELD[false,djRawByteString] := @SortDynArrayAnsiStringSSE42;
  53982. {$ifndef UNICODE}
  53983. DYNARRAY_SORTFIRSTFIELD[false,djString] := @SortDynArrayAnsiStringSSE42;
  53984. {$endif}
  53985. DYNARRAY_SORTFIRSTFIELDHASHONLY[true] := @SortDynArrayAnsiStringSSE42;
  53986. {$ifndef DELPHI5OROLDER}
  53987. UpperCopy255Buf := @UpperCopy255BufSSE42;
  53988. {$endif}
  53989. {$endif PUREPASCAL}
  53990. end else
  53991. {$endif CPUINTEL}
  53992. crc32c := @crc32cfast;
  53993. DefaultHasher := crc32c;
  53994. end;
  53995. initialization
  53996. // initialization of global variables
  53997. GarbageCollectorFreeAndNilList := TList.Create;
  53998. GarbageCollectorFreeAndNil(GarbageCollector,TObjectList.Create);
  53999. InitializeCriticalSection(GlobalCriticalSection);
  54000. {$ifdef CPUINTEL}
  54001. TestIntelCpuFeatures;
  54002. {$endif}
  54003. MoveFast := @System.Move;
  54004. {$ifdef FPC}
  54005. FillCharFast := @System.FillChar;
  54006. {$else}
  54007. {$ifdef CPUARM}
  54008. FillCharFast := @System.FillChar;
  54009. {$else}
  54010. {$ifdef USEPACKAGES}
  54011. Pointer(@FillCharFast) := SystemFillCharAddress;
  54012. {$else}
  54013. InitRedirectCode;
  54014. {$endif USEPACKAGES}
  54015. {$endif CPUARM}
  54016. {$endif FPC}
  54017. InitSynCommonsConversionTables;
  54018. RetrieveSystemInfo;
  54019. SetExecutableVersion(0,0,0,0);
  54020. TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TFindFilesDynArray),
  54021. 'Name string Attr Integer Size Int64 TimeStamp TDateTime');
  54022. // some type definition assertions
  54023. Assert(SizeOf(TSynTableFieldType)=1); // as expected by TSynTableFieldProperties
  54024. Assert(SizeOf(TSynTableFieldOptions)=1);
  54025. {$ifndef NOVARIANTS}
  54026. Assert(SizeOf(TSynTableData)=sizeof(TVarData));
  54027. Assert(SizeOf(TDocVariantData)=sizeof(TVarData));
  54028. {$endif NOVARIANTS}
  54029. {$warnings OFF}
  54030. Assert((MAX_SQLFIELDS>=64)and(MAX_SQLFIELDS<=256));
  54031. {$warnings ON}
  54032. Assert(sizeof(TSynUniqueIdentifierBits)=sizeof(TSynUniqueIdentifier));
  54033. { TypeInfoSaveRegisterKnown([
  54034. TypeInfo(boolean),TypeInfo(byte),TypeInfo(word),TypeInfo(cardinal),TypeInfo(Int64),
  54035. TypeInfo(single),TypeInfo(double),TypeInfo(currency),TypeInfo(extended),TypeInfo(TDateTime),
  54036. TypeInfo(RawByteString),TypeInfo(RawJSON),TypeInfo(RawUTF8),TypeInfo(string),
  54037. TypeInfo(SynUnicode),TypeInfo(WideString)]); }
  54038. finalization
  54039. GarbageCollectorFree;
  54040. DeleteCriticalSection(GlobalCriticalSection);
  54041. //writeln('TDynArrayHashedCollisionCount=',TDynArrayHashedCollisionCount); readln;
  54042. end.