mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 01:15:33 +00:00
Fixes #10161. Includes transition of existing options from registry to options.json, and once options.json exists, will always read from that and ignore registry. Was able to eliminate some of the legacy datatype usage for several options. Reader/writer includes naive file lock retry to avoid contention, which I am hopeful will be sufficient for our needs, given the low chance of contention.
177 lines
5 KiB
ObjectPascal
177 lines
5 KiB
ObjectPascal
(*
|
|
Name: JsonUtil
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 10 Oct 2014
|
|
|
|
Modified Date: 30 May 2015
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 10 Oct 2014 - mcdurdin - I4440 - V9.0 - If more than one language listed for a keyboard, the JSON file becomes invalid
|
|
30 May 2015 - mcdurdin - I4727 - Changing font while touch layout editor in Code mode results in broken \ rules
|
|
*)
|
|
unit JsonUtil;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.JSON,
|
|
System.Classes,
|
|
System.Generics.Collections;
|
|
|
|
function JSONToString(obj: TJSONAncestor; ReplaceSlashes: Boolean = False): string;
|
|
|
|
// Formats JSONValue to an indented structure and adds it to OutputStrings
|
|
procedure PrettyPrintJSON(JSONValue: TJSONValue; OutputStrings: TStrings; indent: integer = 0);
|
|
function ParseJSONValue(const Data: string; var Offset: Integer): TJSONObject; // I4035 // I4260
|
|
function JSONDateToDateTime(const Value: string; var DateTime: TDateTime): Boolean;
|
|
function DateTimeToJSONDate(ADateTime: TDateTime): string;
|
|
function LoadJSONFromFile(const Filename: string; var Offset: Integer): TJSONObject;
|
|
procedure SaveJSONToFile(const Filename: string; const JSON: TJSONObject);
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.DateUtils,
|
|
System.StrUtils,
|
|
System.SysUtils;
|
|
|
|
function JSONToString(obj: TJSONAncestor; ReplaceSlashes: Boolean = False): string;
|
|
var
|
|
bytes: TBytes;
|
|
len: Integer;
|
|
begin
|
|
SetLength(bytes, obj.EstimatedByteSize);
|
|
len := obj.ToBytes(bytes, 0);
|
|
Result := TEncoding.UTF8.GetString(bytes, 0, len);
|
|
if ReplaceSlashes then
|
|
Result := ReplaceStr(Result, '\/', '/');
|
|
end;
|
|
|
|
const INDENT_SIZE = 2;
|
|
|
|
procedure PrettyPrintPair(JSONValue: TJSONPair; OutputStrings: TStrings; last: boolean; indent: integer);
|
|
const TEMPLATE = '%s: %s';
|
|
var
|
|
line: string;
|
|
newList: TStringList;
|
|
begin
|
|
newList := TStringList.Create;
|
|
try
|
|
PrettyPrintJSON(JSONValue.JsonValue, newList, indent);
|
|
line := format(TEMPLATE, [JSONValue.JsonString.ToString, Trim(newList.Text)]);
|
|
finally
|
|
newList.Free;
|
|
end;
|
|
|
|
line := StringOfChar(' ', indent * INDENT_SIZE) + line;
|
|
if not last then
|
|
line := line + ',';
|
|
OutputStrings.add(line);
|
|
end;
|
|
|
|
procedure PrettyPrintArray(JSONValue: TJSONArray; OutputStrings: TStrings; last: boolean; indent: integer);
|
|
var i: integer;
|
|
begin
|
|
OutputStrings.add(StringOfChar(' ', indent * INDENT_SIZE) + '[');
|
|
for i := 0 to JSONValue.Count - 1 do
|
|
begin
|
|
PrettyPrintJSON(JSONValue.Items[i], OutputStrings, indent);
|
|
if i < JSONValue.Count - 1 then
|
|
OutputStrings[OutputStrings.Count-1] := OutputStrings[OutputStrings.Count-1] + ','; // I4440
|
|
end;
|
|
OutputStrings.add(StringOfChar(' ', (indent-1) * INDENT_SIZE) + ']');
|
|
end;
|
|
|
|
procedure PrettyPrintJSON(JSONValue: TJSONValue; OutputStrings: TStrings; indent: integer = 0);
|
|
var
|
|
i: integer;
|
|
begin
|
|
if JSONValue is TJSONObject then
|
|
begin
|
|
OutputStrings.add(StringOfChar(' ', indent * INDENT_SIZE) + '{');
|
|
for i := 0 to TJSONObject(JSONValue).Count - 1 do
|
|
PrettyPrintPair(TJSONObject(JSONValue).Pairs[i], OutputStrings, i = TJSONObject(JSONValue).Count - 1, indent + 1);
|
|
OutputStrings.add(StringOfChar(' ', indent * INDENT_SIZE) + '}');
|
|
end
|
|
else if JSONValue is TJSONArray then
|
|
PrettyPrintArray(TJSONArray(JSONValue), OutputStrings, True, indent + 1)
|
|
else
|
|
OutputStrings.add(StringOfChar(' ', indent * INDENT_SIZE) + JSONToString(JSONValue, True)); // I4727
|
|
end;
|
|
|
|
function RemoveWhites(const str1: string): string;
|
|
var
|
|
ch: char;
|
|
inQuotes: Boolean;
|
|
begin
|
|
Result := '';
|
|
inQuotes := False;
|
|
for ch in str1 do
|
|
begin
|
|
if ch = '"' then
|
|
inQuotes := not inQuotes;
|
|
if InQuotes or not CharInSet(ch, [' ',#9,#10,#13]) then
|
|
Result := Result + ch;
|
|
end;
|
|
end;
|
|
|
|
function ParseJSONValue(const Data: string; var Offset: Integer): TJSONObject; // I4035 // I4260
|
|
var
|
|
UTF8Data: TBytes;
|
|
begin
|
|
UTF8Data := TEncoding.UTF8.GetBytes(RemoveWhites(Data));
|
|
Result := TJSONObject.ParseJSONValue(UTF8Data, Offset, True) as TJSONObject; // I4083
|
|
end;
|
|
|
|
function JSONDateToDateTime(const Value: string; var DateTime: TDateTime): Boolean;
|
|
begin
|
|
Result := TryISO8601ToDate(Value, DateTime, False);
|
|
end;
|
|
|
|
function DateTimeToJSONDate(ADateTime: TDateTime): string;
|
|
begin
|
|
Result := DateToISO8601(ADateTime, False);
|
|
end;
|
|
|
|
function LoadJSONFromFile(const Filename: string; var Offset: Integer): TJSONObject;
|
|
var
|
|
ss: TStringStream;
|
|
begin
|
|
ss := TStringStream.Create('', TEncoding.UTF8);
|
|
try
|
|
ss.LoadFromFile(Filename);
|
|
offset := 0;
|
|
Result := ParseJSONValue(ss.DataString, offset);
|
|
finally
|
|
ss.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure SaveJSONToFile(const Filename: string; const JSON: TJSONObject);
|
|
var
|
|
s: TStringList;
|
|
ss: TStringStream;
|
|
begin
|
|
s := TStringList.Create;
|
|
try
|
|
PrettyPrintJSON(JSON, s, 2);
|
|
ss := TStringStream.Create(s.Text, TEncoding.UTF8);
|
|
try
|
|
ss.SaveToFile(Filename);
|
|
finally
|
|
ss.Free;
|
|
end;
|
|
finally
|
|
s.Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|