You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1390 lines
52 KiB
1390 lines
52 KiB
/** |
|
* Provides convenient access to JSON-related functions. |
|
* Printing method produce correct JSON. |
|
* Parsing methods do not provide validity checks guarantees and will parse |
|
* both valid and invalid JSON. However only correctly parsing valid JSON |
|
* is guaranteed. This means that you should not rely on these methods to parse |
|
* any JSON extensions or validate JSON for you. |
|
* Copyright 2021 Anton Tarasenko |
|
*------------------------------------------------------------------------------ |
|
* This file is part of Acedia. |
|
* |
|
* Acedia is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Acedia is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
|
*/ |
|
class JSONAPI extends AcediaObject |
|
config(AcediaSystem); |
|
|
|
var private bool formattingInitialized; |
|
// Variables used in json pretty printing for defining used colors; |
|
// Colors are taken from `ColorAPI`. |
|
var private Text.Formatting jPropertyName, jObjectBraces, jArrayBraces, jComma; |
|
var private Text.Formatting jColon, jNumber, jBoolean, jString, jNull; |
|
|
|
var const int TNULL, TTRUE, TFALSE, TDOT, TEXPONENT; |
|
var const int TOPEN_BRACKET, TCLOSE_BRACKET, TOPEN_BRACE, TCLOSE_BRACE; |
|
var const int TCOMMA, TCOLON, TQUOTE, TJSON_INDENT, TSPACE, TCOLON_SPACE; |
|
|
|
var const int CODEPOINT_BACKSPACE, CODEPOINT_TAB, CODEPOINT_LINE_FEED; |
|
var const int CODEPOINT_FORM_FEED, CODEPOINT_CARRIAGE_RETURN; |
|
var const int CODEPOINT_QUOTATION_MARK, CODEPOINT_SOLIDUS; |
|
var const int CODEPOINT_REVERSE_SOLIDUS, CODEPOINT_SMALL_B, CODEPOINT_SMALL_F; |
|
var const int CODEPOINT_SMALL_N, CODEPOINT_SMALL_R, CODEPOINT_SMALL_T; |
|
|
|
// Max precision that will be used when outputting JSON values as a string. |
|
// Hardcoded to force this value between 0 and 10, inclusively. |
|
var private const config int MAX_FLOAT_PRECISION; |
|
|
|
// Method for initializing json formatting variables |
|
private final function InitFormatting() |
|
{ |
|
if (formattingInitialized) { |
|
return; |
|
} |
|
formattingInitialized = true; |
|
jPropertyName = _.text.FormattingFromColor(_.color.jPropertyName); |
|
jObjectBraces = _.text.FormattingFromColor(_.color.jObjectBraces); |
|
jArrayBraces = _.text.FormattingFromColor(_.color.jArrayBraces); |
|
jComma = _.text.FormattingFromColor(_.color.jComma); |
|
jColon = _.text.FormattingFromColor(_.color.jColon); |
|
jNumber = _.text.FormattingFromColor(_.color.jNumber); |
|
jBoolean = _.text.FormattingFromColor(_.color.jBoolean); |
|
jString = _.text.FormattingFromColor(_.color.jString); |
|
jNull = _.text.FormattingFromColor(_.color.jNull); |
|
} |
|
|
|
/** |
|
* Creates new `JSONPointer`, corresponding to a given path in |
|
* JSON pointer format (https://tools.ietf.org/html/rfc6901). |
|
* |
|
* If provided `Text` value is an incorrect pointer, then it will be |
|
* treated like an empty pointer. |
|
* However, if given pointer can be fixed by prepending "/" - it will be |
|
* done automatically. This means that "foo/bar" is treated like "/foo/bar", |
|
* "path" like "/path", but empty `Text` "" is treated like itself. |
|
* |
|
* @param pointerAsText `Text` representation of the JSON pointer. |
|
* @return New `JSONPointer`, corresponding to the given `pointerAsText`. |
|
* Guaranteed to not be `none`. If provided `pointerAsText` is |
|
* an incorrect JSON pointer or `none`, - empty `JSONPointer` will be |
|
* returned. |
|
*/ |
|
public final function JSONPointer Pointer(optional Text pointerAsText) |
|
{ |
|
return JSONPointer(_.memory.Allocate(class'JSONPointer')) |
|
.Set(pointerAsText); |
|
} |
|
|
|
/** |
|
* Checks whether passed `AcediaObject` can be converted into JSON by this API. |
|
* |
|
* Compatible objects are `none` and any object that has one of the following |
|
* classes: `BoolBox`, `BoolRef`, `ByteBox`, `ByteRef`, `IntBox`, `IntRef`, |
|
* `FloatBox`, `FloatRef`, `Text`, `MutableText`, `DynamicArray`, |
|
* `AssociativeArray`. |
|
* |
|
* This method does not check whether objects stored inside `DynamicArray`, |
|
* `AssociativeArray` are compatible. If they are not, they will normally be |
|
* defaulted to JSON null upon any conversion. |
|
*/ |
|
public function bool IsCompatible(AcediaObject data) |
|
{ |
|
local class<AcediaObject> dataClass; |
|
if (data == none) { |
|
return true; |
|
} |
|
dataClass = data.class; |
|
return dataClass == class'BoolBox' || dataClass == class'BoolRef' |
|
|| dataClass == class'ByteBox' || dataClass == class'ByteRef' |
|
|| dataClass == class'IntBox' || dataClass == class'IntRef' |
|
|| dataClass == class'FloatBox' || dataClass == class'FloatRef' |
|
|| dataClass == class'Text' || dataClass == class'MutableText' |
|
|| dataClass == class'DynamicArray' |
|
|| dataClass == class'AssociativeArray'; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a null JSON value ("null" in arbitrary case). |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON null term (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value from |
|
* it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
*/ |
|
public final function TryNullWith(Parser parser) |
|
{ |
|
if (parser != none) { |
|
parser.Match(T(default.TNULL), SCASE_INSENSITIVE); |
|
} |
|
} |
|
|
|
/** |
|
* Tries to parse null JSON value ("null" in arbitrary case) and reports |
|
* whether parsing succeeded. |
|
* |
|
* `source` must contain precisely a null JSON value and nothing else for this |
|
* method to succeed. For example, even having leading/trailing whitespace |
|
* symbols (" null" or "null ") is enough to fail parsing. |
|
* |
|
* @param source `Text` instance to parse JSON null value from. |
|
* @return `true` if parsing succeeded and `false` otherwise. |
|
*/ |
|
public final function bool IsNull(Text source) |
|
{ |
|
local bool parsingSucceeded; |
|
local Parser parser; |
|
if (source == none) return false; |
|
|
|
parser = _.text.Parse(source); |
|
parser.Match(T(default.TNULL), SCASE_INSENSITIVE); |
|
parsingSucceeded = parser.Ok() && parser.HasFinished(); |
|
parser.FreeSelf(); |
|
return parsingSucceeded; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON boolean ("true" or "false" with |
|
* arbitrary case). |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON boolean (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value from |
|
* it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @return Parsed boolean value if parsing was successful and |
|
* `false` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function bool ParseBooleanVariableWith(Parser parser) |
|
{ |
|
local Parser.ParserState initState; |
|
if (parser == none) return false; |
|
if (!parser.Ok()) return false; |
|
|
|
initState = parser.GetCurrentState(); |
|
// Check if we should return `true` |
|
if (parser.Match(T(default.TTRUE), SCASE_INSENSITIVE).Ok()) { |
|
return true; |
|
} |
|
// We need to try parsing "false", so that we can use `parser`'s state |
|
// to report about success of parsing; but we return `false` anyway. |
|
parser.RestoreState(initState).Match(T(default.TFALSE), SCASE_INSENSITIVE); |
|
return false; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON boolean ("true" or "false" |
|
* with arbitrary case) into either `BoolBox` or `BoolRef`. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON boolean (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`BoolRef`) and `false` if immutable (`BoolBox`). |
|
* @return Parsed boolean value as an `AcediaObject` if parsing was successful |
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to |
|
* be not `none` and have correct class, determined by |
|
* `parseAsMutable` parameter. |
|
* Returns `none` iff parsing has failed. |
|
*/ |
|
public final function AcediaObject ParseBooleanWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local bool result; |
|
if (parser == none) return none; |
|
|
|
result = ParseBooleanVariableWith(parser); |
|
if (!parser.Ok()) { |
|
return none; |
|
} |
|
if (parseAsMutable) { |
|
return _.ref.bool(result); |
|
} |
|
else { |
|
return _.box.bool(result); |
|
} |
|
} |
|
|
|
/** |
|
* Parses a JSON boolean ("true" or "false" with arbitrary case) from |
|
* a given `source` into either `BoolBox` or `BoolRef`. |
|
* |
|
* `source` must contain precisely a boolean value and nothing else for this |
|
* method to succeed. For example, even having leading/trailing whitespace |
|
* symbols (" true" or "false ") is enough to fail parsing. |
|
* |
|
* @param source `Text` instance to parse JSON boolean value from. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`BoolRef`) and `false` if immutable (`BoolBox`). |
|
* @return Parsed boolean value as an `AcediaObject` if parsing was successful |
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to |
|
* be not `none` and have correct class, determined by |
|
* `parseAsMutable` parameter. |
|
* Returns `none` iff parsing has failed. |
|
*/ |
|
public final function AcediaObject ParseBoolean( |
|
Text source, |
|
optional bool parseAsMutable) |
|
{ |
|
local bool result; |
|
local bool parsingFailed; |
|
local Parser parser; |
|
if (source == none) return none; |
|
|
|
parser = _.text.Parse(source); |
|
result = ParseBooleanVariableWith(parser); |
|
parsingFailed = !parser.Ok() || !parser.HasFinished(); |
|
parser.FreeSelf(); |
|
if (parsingFailed) { |
|
return none; |
|
} |
|
if (parseAsMutable) { |
|
return _.ref.bool(result); |
|
} |
|
else { |
|
return _.box.bool(result); |
|
} |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON number into an integer. |
|
* |
|
* If number is written in an "integer form" (not dot "." or exponent "e"), |
|
* then it will be directly be parsed as an `int`. Otherwise it will be |
|
* parsed as a `float` and the converted into `int`, with appropriate loss |
|
* of precision. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse a JSON number |
|
* (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* To parse a JSON number into a `float` use `ParseFloatVariableWith()` method. |
|
* |
|
* @param parser Parser that method would use to parse JSON value from |
|
* it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param integerOnly Setting this parameter to `true` will prevent method |
|
* from parsing number as a `float` (and possibly losing precision): |
|
* in case it is written in a `float`, parsing will be considered failed. |
|
* @return Parsed integer value if parsing was successful and |
|
* `0` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function int ParseIntegerVariableWith( |
|
Parser parser, |
|
optional bool integerOnly) |
|
{ |
|
local int integerValue; |
|
local bool isInFloatForm; |
|
local float floatValue; |
|
local Parser.ParserState initState, integerParsedState; |
|
if (parser == none) return 0; |
|
|
|
initState = parser.GetCurrentState(); |
|
if (!parser.MInteger(integerValue, 10).Ok()) { |
|
return 0; |
|
} |
|
// `integerParsedState` is guaranteed to be a successful state |
|
integerParsedState = parser.GetCurrentState(); |
|
// JSON number recorded as float form will have either dot or exponent |
|
// after the integer part. |
|
isInFloatForm = parser.Match(T(default.TDOT)).Ok(); |
|
parser.RestoreState(integerParsedState); |
|
if (parser.Match(T(default.TEXPONENT), SCASE_INSENSITIVE).Ok()) { |
|
isInFloatForm = true; |
|
} |
|
// For a number check if it can be parsed as a float specifically. |
|
// If not - use parsed integer. |
|
parser.RestoreState(initState); |
|
if (isInFloatForm && parser.MNumber(floatValue).Ok()) |
|
{ |
|
if (integerOnly) |
|
{ |
|
parser.Fail(); |
|
return 0; |
|
} |
|
return int(floatValue); |
|
} |
|
parser.RestoreState(integerParsedState); |
|
return integerValue; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON number. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON number (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* To parse a JSON number into an `int` use `ParseIntegerVariableWith()` method. |
|
* |
|
* @param parser Parser that method would use to parse JSON value from |
|
* it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @return Parsed number value if parsing was successful and |
|
* `0.0` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function float ParseFloatVariableWith(Parser parser) |
|
{ |
|
local float floatValue; |
|
if (parser == none) return 0.0; |
|
if (!parser.MNumber(floatValue).Ok()) return 0.0; |
|
|
|
return floatValue; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON number into one of the following |
|
* object classes: `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, depending on |
|
* parameters and how numeric value is recorded. |
|
* |
|
* To improve precision, this method will try to parse JSON number as |
|
* an integer (`IntBox` or `IntRef`) if possible (if number does not include |
|
* fractional or exponent parts: "." or "e"). |
|
* Otherwise it will parse number as a floating point value |
|
* (`FloatBox` or `FloatRef`). |
|
* The choice between box and reference is made depending on the method's |
|
* parameter `parseAsMutable`. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON number (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`IntRef` or `FloatRef`) and `false` if immutable |
|
* (`IntBox` or `FloatBox`). |
|
* @return Parsed number value as an `AcediaObject` if parsing was successful |
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to |
|
* be not `none` and have correct class, determined partly by |
|
* `parseAsMutable` parameter. |
|
* Returns `none` iff parsing has failed. |
|
*/ |
|
public final function AcediaObject ParseNumberWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local int integerResult; |
|
local float floatResult; |
|
local Parser.ParserState initState; |
|
if (parser == none) return none; |
|
|
|
initState = parser.GetCurrentState(); |
|
// Try parsing into `int`; |
|
// this will fail if number recorded in floating format. |
|
integerResult = ParseIntegerVariableWith(parser, true); |
|
if (parser.Ok()) |
|
{ |
|
if (parseAsMutable) { |
|
return _.ref.int(integerResult); |
|
} |
|
else { |
|
return _.box.int(integerResult); |
|
} |
|
} |
|
// If simple integer does not work - try to parse it as `float` |
|
floatResult = ParseFloatVariableWith(parser.RestoreState(initState)); |
|
if (!parser.Ok()) { |
|
return none; |
|
} |
|
if (parseAsMutable) { |
|
return _.ref.float(floatResult); |
|
} |
|
else { |
|
return _.box.float(floatResult); |
|
} |
|
} |
|
|
|
/** |
|
* Parses a JSON number from `source` into one of the following |
|
* object classes: `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, depending on |
|
* parameters and how numeric value is recorded. |
|
* |
|
* To improve precision, this method will try to parse JSON number as |
|
* an integer (`IntBox` or `IntRef`) if possible (if number does not include |
|
* fractional or exponent parts: "." or "e"). |
|
* Otherwise it will parse number as a floating point value |
|
* (`FloatBox` or `FloatRef`). |
|
* The choice between box and reference is made depending on the method's |
|
* parameter `parseAsMutable`. |
|
* |
|
* `source` must contain precisely a numeric value and nothing else for this |
|
* method to succeed. For example, even having leading/trailing whitespace |
|
* symbols (" 75.3" or "9 ") is enough to fail parsing. |
|
* |
|
* @param source `Text` instance to parse JSON number from. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`IntRef` or `FloatRef`) and `false` if immutable |
|
* (`IntBox` or `FloatBox`). |
|
* @return Parsed number value as an `AcediaObject` if parsing was successful |
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to |
|
* be not `none` and have correct class, determined partly by |
|
* `parseAsMutable` parameter. |
|
* Returns `none` iff parsing has failed. |
|
*/ |
|
public final function AcediaObject ParseNumber( |
|
Text source, |
|
optional bool parseAsMutable) |
|
{ |
|
local int integerResult; |
|
local float floatResult; |
|
local Parser parser; |
|
parser = _.text.Parse(source); |
|
// Try parsing into `int`; |
|
// this will fail if number recorded in floating format. |
|
integerResult = ParseIntegerVariableWith(parser, true); |
|
if (parser.Ok() && parser.HasFinished()) |
|
{ |
|
parser.FreeSelf(); |
|
if (parseAsMutable) { |
|
return _.ref.int(integerResult); |
|
} |
|
else { |
|
return _.box.int(integerResult); |
|
} |
|
} |
|
// If simple integer does not work - try to parse it as `float` |
|
floatResult = ParseFloatVariableWith(parser.R()); |
|
if (!parser.Ok() || !parser.HasFinished()) |
|
{ |
|
parser.FreeSelf(); |
|
return none; |
|
} |
|
parser.FreeSelf(); |
|
if (parseAsMutable) { |
|
return _.ref.float(floatResult); |
|
} |
|
else { |
|
return _.box.float(floatResult); |
|
} |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON string. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON string (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`MutableText`) and `false` if immutable (`Text`). |
|
* @return Parsed string value as `Text` or `MutableText` (depending on |
|
* `parseAsMutable` parameter) if parsing was successful and |
|
* `none` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function Text ParseStringWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local Text immutableTextValue; |
|
local MutableText mutableTextValue; |
|
if (parser == none) { |
|
return none; |
|
} |
|
parser.MStringLiteral(mutableTextValue); |
|
if (!parser.Ok()) { |
|
mutableTextValue.FreeSelf(); |
|
return none; |
|
} |
|
if (parseAsMutable) { |
|
return mutableTextValue; |
|
} |
|
immutableTextValue = mutableTextValue.Copy(); |
|
mutableTextValue.FreeSelf(); |
|
return immutableTextValue; |
|
} |
|
|
|
/** |
|
* Parses a JSON string from `source` into either `Text` or `MutableText`, |
|
* depending on parameters. |
|
* |
|
* `source` must contain precisely a JSON string value and nothing else for |
|
* this method to succeed. For example, even having leading/trailing whitespace |
|
* symbols (" \"string!\"" or "\"another!\" ") is enough to fail parsing. |
|
* |
|
* @param source `Text` instance to parse JSON string from. |
|
* @param parseAsMutable `true` if you want this method to return mutable |
|
* object (`MutableText`) and `false` if immutable (`Text`). |
|
* @return Parsed string value as `Text` or `MutableText` (depending on |
|
* `parseAsMutable` parameter) if parsing was successful and |
|
* `none` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function Text ParseString( |
|
Text source, |
|
optional bool parseAsMutable) |
|
{ |
|
local bool parsingSuccessful; |
|
local Parser parser; |
|
local Text immutableTextValue; |
|
local MutableText mutableTextValue; |
|
parser = _.text.Parse(source); |
|
parsingSuccessful = |
|
parser.MStringLiteral(mutableTextValue).Ok() && parser.HasFinished(); |
|
parser.FreeSelf(); |
|
if (!parsingSuccessful) { |
|
mutableTextValue.FreeSelf(); |
|
return none; |
|
} |
|
if (parseAsMutable) { |
|
return mutableTextValue; |
|
} |
|
immutableTextValue = mutableTextValue.Copy(); |
|
mutableTextValue.FreeSelf(); |
|
return immutableTextValue; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON array. |
|
* |
|
* This method will parse JSON values that are contained in parsed JSON array |
|
* according to description given for `ParseWith()` method. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON array (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON array |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to parse array's |
|
* items as mutable values and `false` otherwise (as immutable ones). |
|
* @return Parsed JSON array as `DynamicArray` if parsing was successful and |
|
* `none` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public final function DynamicArray ParseArrayWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local bool parsingSucceeded; |
|
local Parser.ParserState confirmedState; |
|
local AcediaObject nextValue; |
|
local array<AcediaObject> parsedValues; |
|
if (parser == none) return none; |
|
|
|
confirmedState = |
|
parser.Skip().Match(T(default.TOPEN_BRACKET)).GetCurrentState(); |
|
while (parser.Ok() && !parser.HasFinished()) |
|
{ |
|
confirmedState = parser.Skip().GetCurrentState(); |
|
// Check for JSON array ending and ONLY THEN declare parsing |
|
// is successful, not encountering '}' implies bad JSON format. |
|
if (parser.Match(T(default.TCLOSE_BRACKET)).Ok()) { |
|
parsingSucceeded = true; |
|
break; |
|
} |
|
parser.RestoreState(confirmedState); |
|
// Look for comma after each element |
|
if (parsedValues.length > 0) |
|
{ |
|
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) { |
|
break; |
|
} |
|
confirmedState = parser.GetCurrentState(); |
|
} |
|
// Parse next value |
|
nextValue = ParseWith(parser, parseAsMutable); |
|
parsedValues[parsedValues.length] = nextValue; |
|
if (!parser.Ok()) { |
|
break; |
|
} |
|
} |
|
if (parsingSucceeded) { |
|
return _.collections.NewDynamicArray(parsedValues, true); |
|
} |
|
_.memory.FreeMany(parsedValues); |
|
parser.Fail(); |
|
return none; |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON object. |
|
* |
|
* This method will parse JSON values that are contained in parsed JSON object |
|
* according to description given for `ParseWith()` method. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON object (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON object |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to parse object's |
|
* items as mutable values and `false` otherwise (as immutable ones). |
|
* @return Parsed JSON object as `AssociativeArray` if parsing was successful |
|
* and `none` otherwise. To check for parsing success check the state of |
|
* the `parser`. |
|
*/ |
|
public function AssociativeArray ParseObjectWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local bool parsingSucceeded; |
|
local Parser.ParserState confirmedState; |
|
local array<AssociativeArray.Entry> parsedEntries; |
|
if (parser == none) return none; |
|
|
|
// Ensure that parser starts pointing at what looks like a JSON object |
|
confirmedState = |
|
parser.Skip().Match(T(default.TOPEN_BRACE)).GetCurrentState(); |
|
if (!parser.Ok()) { |
|
return none; |
|
} |
|
while (parser.Ok() && !parser.HasFinished()) |
|
{ |
|
confirmedState = parser.Skip().GetCurrentState(); |
|
// Check for JSON object ending and ONLY THEN declare parsing |
|
// is successful, not encountering '}' implies bad JSON format. |
|
if (parser.Match(T(default.TCLOSE_BRACE)).Ok()) |
|
{ |
|
parsingSucceeded = true; |
|
break; |
|
} |
|
parser.RestoreState(confirmedState); |
|
// Look for comma after each key-value pair |
|
if (parsedEntries.length > 0) |
|
{ |
|
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) { |
|
break; |
|
} |
|
confirmedState = parser.GetCurrentState(); |
|
} |
|
// Parse property |
|
parsedEntries[parsedEntries.length] = |
|
ParseProperty(parser, parseAsMutable); |
|
if (!parser.Ok()) { |
|
break; |
|
} |
|
} |
|
if (parsingSucceeded) { |
|
return _.collections.NewAssociativeArray(parsedEntries, true); |
|
} |
|
FreeEntries(parsedEntries); |
|
parser.Fail(); |
|
return none; |
|
} |
|
|
|
// Parses a JSON key-value pair (there must not be any leading spaces). |
|
private function AssociativeArray.Entry ParseProperty( |
|
Parser parser, |
|
bool parseAsMutable) |
|
{ |
|
local MutableText nextKey; |
|
local AssociativeArray.Entry entry; |
|
parser.MStringLiteral(nextKey).Skip().Match(T(default.TCOLON)).Skip(); |
|
entry.key = nextKey.Copy(); |
|
nextKey.FreeSelf(); |
|
entry.value = ParseWith(parser, parseAsMutable); |
|
return entry; |
|
} |
|
|
|
// Auxiliary method for deallocating unneeded objects in entry pairs. |
|
private function FreeEntries(array<AssociativeArray.Entry> entries) |
|
{ |
|
local int i; |
|
for (i = 0; i < entries.length; i += 1) |
|
{ |
|
_.memory.Free(entries[i].key); |
|
_.memory.Free(entries[i].value); |
|
} |
|
} |
|
|
|
/** |
|
* Uses given parser to parse a JSON value. |
|
* |
|
* This method will parse JSON values that are contained in parsed JSON object |
|
* according to description given for `ParseWith()` method. |
|
* |
|
* Rules for determining types into which JSON value will be parsed: |
|
* 1. Null values will be returned as `none`; |
|
* 2. Number values will be return as an `IntBox`/`IntRef` if they consist |
|
* of only digits (and optionally a sign) and `FloatBox`/`FloatRef` |
|
* otherwise. Choice between box and ref is made based on |
|
* `parseAsMutable` parameter (boxes are immutable, refs are mutable); |
|
* 3. String values will be parsed as `Text`/`MutableText`, based on |
|
* `parseAsMutable` parameter; |
|
* 4. Array values will be parsed as a `DynamicArray`, it's items parsed |
|
* according to these rules (`parseAsMutable` parameter is propagated). |
|
* 5. Object values will be parsed as a `AssociativeArray`, it's items |
|
* parsed according to these rules (`parseAsMutable` parameter is |
|
* propagated) and recorded under the keys parsed into `Text`. |
|
* |
|
* It does not matter what content follows parsed value in the `parser`, |
|
* method will be successful as long as it manages to parse correct |
|
* JSON value (from the current `parser`'s position). |
|
* |
|
* To check whether parsing have failed, simply check if `parser` is in |
|
* a failed state after the method call. |
|
* |
|
* @param parser Parser that method would use to parse JSON value |
|
* from it's current position. It's confirmed state will not be changed. |
|
* If parsing was successful it will point at the next available character. |
|
* Parser will be in a failed state after this method iff |
|
* parsing has failed. |
|
* @param parseAsMutable `true` if you want this method to parse value |
|
* (and it's sub-items, if applicable) as mutable values and |
|
* `false` if you want them to be immutable. |
|
* @return Parsed JSON value as `AcediaObject` that has one of the classes |
|
* described in parsing rules, `none` otherwise. To check for parsing |
|
* success check the state of the `parser`. Note that method can also |
|
* return `none` if parsed JSON value was "null". |
|
*/ |
|
public final function AcediaObject ParseWith( |
|
Parser parser, |
|
optional bool parseAsMutable) |
|
{ |
|
local AcediaObject result; |
|
local Parser.ParserState initState; |
|
if (parser == none) return none; |
|
if (!parser.Ok()) return none; |
|
|
|
initState = parser.GetCurrentState(); |
|
TryNullWith(parser); |
|
if (parser.Ok()) { |
|
return none; |
|
} |
|
result = ParseBooleanWith(parser.RestoreState(initState), parseAsMutable); |
|
if (parser.Ok()) { |
|
return result; |
|
} |
|
result = ParseNumberWith(parser.RestoreState(initState), parseAsMutable); |
|
if (parser.Ok()) { |
|
return result; |
|
} |
|
result = ParseStringWith(parser.RestoreState(initState), parseAsMutable); |
|
if (parser.Ok()) { |
|
return result; |
|
} |
|
result = ParseArrayWith(parser.RestoreState(initState), parseAsMutable); |
|
if (parser.Ok()) { |
|
return result; |
|
} |
|
result = ParseObjectWith(parser.RestoreState(initState), parseAsMutable); |
|
if (parser.Ok()) { |
|
return result; |
|
} |
|
return none; |
|
} |
|
|
|
/** |
|
* "Prints" given `AcediaObject` value, saving it in JSON format. |
|
* |
|
* "Prints" given `AcediaObject` in a minimal way, for a human-readable output |
|
* use `PrettyPrint()` method. |
|
* |
|
* Only certain classes (the same as the ones that can be parsed from JSON |
|
* via this API) are supported: |
|
* 1. `none` is printed into "null"; |
|
* 2. Boolean types (`BoolBox`/`BoolRef`) are printed into JSON bool value; |
|
* 3. Integer (`IntBox`/`IntRef`) and float (`FloatBox`/`FloatRef`) types |
|
* are printed into JSON number value; |
|
* 4. `Text` and `MutableText` are printed into JSON string value; |
|
* 5. `DynamicArray` is printed into JSON array with `Print()` method |
|
* applied to each of it's items. If some of them have not printable |
|
* types - "none" will be used as a fallback. |
|
* 6. `AssociativeArray` is printed into JSON object with `Print()` method |
|
* applied to each of it's items. Only items with `Text` keys are |
|
* printed, the rest is omitted. If some of them have not printable |
|
* types - "none" will be used as a fallback. |
|
* |
|
* @param toPrint Object to "print" into `MutableText`. |
|
* @return Text version of given `toDisplay`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText Print(AcediaObject toPrint) |
|
{ |
|
if (toPrint == none) { |
|
return T(default.TNULL).MutableCopy(); |
|
} |
|
if (toPrint.class == class'IntBox') { |
|
return _.text.FromIntM(IntBox(toPrint).Get()); |
|
} |
|
if (toPrint.class == class'IntRef') { |
|
return _.text.FromIntM(IntRef(toPrint).Get()); |
|
} |
|
if (toPrint.class == class'BoolBox') { |
|
return _.text.FromBoolM(BoolBox(toPrint).Get()); |
|
} |
|
if (toPrint.class == class'BoolRef') { |
|
return _.text.FromBoolM(BoolRef(toPrint).Get()); |
|
} |
|
if (toPrint.class == class'FloatBox') { |
|
return _.text.FromFloatM(FloatBox(toPrint).Get(), MAX_FLOAT_PRECISION); |
|
} |
|
if (toPrint.class == class'FloatRef') { |
|
return _.text.FromFloatM(FloatRef(toPrint).Get(), MAX_FLOAT_PRECISION); |
|
} |
|
if ( toPrint.class == class'Text' |
|
|| toPrint.class == class'MutableText') { |
|
return DisplayText(Text(toPrint)); |
|
} |
|
if (toPrint.class == class'DynamicArray') { |
|
return PrintArray(DynamicArray(toPrint)); |
|
} |
|
if (toPrint.class == class'AssociativeArray') { |
|
return PrintObject(AssociativeArray(toPrint)); |
|
} |
|
return none; |
|
} |
|
|
|
/** |
|
* "Prints" given `DynamicArray` value, saving it as a JSON array in |
|
* `MutableText`. |
|
* |
|
* "Prints" given `DynamicArray` in a minimal way, for a human-readable output |
|
* use `PrettyPrintArray()` method. |
|
* |
|
* It's items must either be equal to `none` or have one of the following |
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, |
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`. |
|
* Otherwise items will be printed as "null" values. |
|
* Also see `Print()` method. |
|
* |
|
* @param toPrint Array to "print" into `MutableText`. |
|
* @return Text version of given `toPrint`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText PrintArray(DynamicArray toPrint) |
|
{ |
|
local int i, length; |
|
local MutableText result, printedItem; |
|
if (toPrint == none) return none; |
|
|
|
length = toPrint.GetLength(); |
|
result = T(default.TOPEN_BRACKET).MutableCopy(); |
|
for (i = 0; i < length; i += 1) |
|
{ |
|
if (i > 0) { |
|
result.Append(T(default.TCOMMA)); |
|
} |
|
printedItem = Print(toPrint.GetItem(i)); |
|
if (printedItem != none) |
|
{ |
|
result.Append(printedItem); |
|
printedItem.FreeSelf(); |
|
} |
|
else { |
|
result.Append(T(default.TNULL)); |
|
} |
|
} |
|
result.Append(T(default.TCLOSE_BRACKET)); |
|
return result; |
|
} |
|
|
|
/** |
|
* "Prints" given `AssociativeArray` value, saving it as a JSON object in |
|
* `MutableText`. |
|
* |
|
* "Prints" given `AssociativeArray` in a minimal way, for |
|
* a human-readable output use `PrettyPrintObject()` method. |
|
* |
|
* Only prints items recorded with `Text` key, the rest is omitted. |
|
* |
|
* It's items must either be equal to `none` or have one of the following |
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, |
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`. |
|
* Otherwise items will be printed as "null" values. |
|
* Also see `Print()` method. |
|
* |
|
* @param toPrint Array to "print" into `MutableText`. |
|
* @return Text version of given `toPrint`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText PrintObject(AssociativeArray toPrint) |
|
{ |
|
local bool printedKeyValuePair; |
|
local Iter iter; |
|
local Text nextKey; |
|
local AcediaObject nextValue; |
|
local MutableText result, printedKey, printedValue; |
|
if (toPrint == none) return none; |
|
|
|
result = T(default.TOPEN_BRACE).MutableCopy(); |
|
iter = toPrint.Iterate(); |
|
for (iter = toPrint.Iterate(); !iter.HasFinished(); iter.Next()) |
|
{ |
|
if (printedKeyValuePair) { |
|
result.Append(T(default.TCOMMA)); |
|
} |
|
nextKey = Text(iter.GetKey()); |
|
nextValue = iter.Get(); |
|
if (nextKey == none) continue; |
|
if (nextKey.class != class'Text') continue; |
|
printedKey = DisplayText(nextKey); |
|
printedValue = Print(nextValue); |
|
result.Append(printedKey).Append(T(default.TCOLON)); |
|
printedKey.FreeSelf(); |
|
if (printedValue != none) |
|
{ |
|
result.Append(printedValue); |
|
printedValue.FreeSelf(); |
|
} |
|
else { |
|
result.Append(T(default.TNULL)); |
|
} |
|
printedKeyValuePair = true; |
|
} |
|
iter.FreeSelf(); |
|
result.Append(T(default.TCLOSE_BRACE)); |
|
return result; |
|
} |
|
|
|
/** |
|
* "Prints" given `AcediaObject` value, saving it in JSON format. |
|
* |
|
* "Prints" given `AcediaObject` in a human-readable way. For a minimal output |
|
* use `Print()` method. |
|
* |
|
* Only certain classes (the same as the ones that can be parsed from JSON |
|
* via this API) are supported: |
|
* 1. `none` is printed into "null"; |
|
* 2. Boolean types (`BoolBox`/`BoolRef`) are printed into JSON bool value; |
|
* 3. Integer (`IntBox`/`IntRef`) and float (`FloatBox`/`FloatRef`) types |
|
* are printed into JSON number value; |
|
* 4. `Text` and `MutableText` are printed into JSON string value; |
|
* 5. `DynamicArray` is printed into JSON array with `Print()` method |
|
* applied to each of it's items. If some of them have not printable |
|
* types - "none" will be used as a fallback. |
|
* 6. `AssociativeArray` is printed into JSON object with `Print()` method |
|
* applied to each of it's items. Only items with `Text` keys are |
|
* printed, the rest is omitted. If some of them have not printable |
|
* types - "none" will be used as a fallback. |
|
* |
|
* @param toPrint Object to "print" into `MutableText`. |
|
* @return Text version of given `toDisplay`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText PrettyPrint(AcediaObject toPrint) |
|
{ |
|
local MutableText result; |
|
local MutableText accumulatedIndent; |
|
InitFormatting(); |
|
accumulatedIndent = _.text.Empty(); |
|
result = PrettyPrintWithIndent(toPrint, accumulatedIndent); |
|
accumulatedIndent.FreeSelf(); |
|
return result; |
|
} |
|
|
|
/** |
|
* "Prints" given `DynamicArray` value, saving it as a JSON array in |
|
* `MutableText`. |
|
* |
|
* "Prints" given `DynamicArray` in human-readable way, for minimal output |
|
* use `PrintArray()` method. |
|
* |
|
* It's items must either be equal to `none` or have one of the following |
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, |
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`. |
|
* Otherwise items will be printed as "null" values. |
|
* Also see `Print()` method. |
|
* |
|
* @param toPrint Array to "print" into `MutableText`. |
|
* @return Text version of given `toPrint`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText PrettyPrintArray(DynamicArray toPrint) |
|
{ |
|
local MutableText result; |
|
local MutableText accumulatedIndent; |
|
InitFormatting(); |
|
accumulatedIndent = _.text.Empty(); |
|
result = PrettyPrintArrayWithIndent(toPrint, accumulatedIndent); |
|
accumulatedIndent.FreeSelf(); |
|
return result; |
|
} |
|
|
|
/** |
|
* "Prints" given `AssociativeArray` value, saving it as a JSON object in |
|
* `MutableText`. |
|
* |
|
* "Prints" given `AssociativeArray` in a human readable way, for |
|
* a minimal output use `PrintObject()` method. |
|
* |
|
* Only prints items recorded with `Text` key, the rest is omitted. |
|
* |
|
* It's items must either be equal to `none` or have one of the following |
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, |
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`. |
|
* Otherwise items will be printed as "null" values. |
|
* Also see `Print()` method. |
|
* |
|
* @param toPrint Array to "print" into `MutableText`. |
|
* @return Text version of given `toPrint`, if it has one of the printable |
|
* classes. Otherwise returns `none`. |
|
* Note that `none` is considered printable and will produce "null". |
|
*/ |
|
public final function MutableText PrettyPrintObject(AssociativeArray toPrint) |
|
{ |
|
local MutableText result; |
|
local MutableText accumulatedIndent; |
|
InitFormatting(); |
|
accumulatedIndent = _.text.Empty(); |
|
result = PrettyPrintObjectWithIndent(toPrint, accumulatedIndent); |
|
accumulatedIndent.FreeSelf(); |
|
return result; |
|
} |
|
|
|
// Does the actual job for `PrettyPrint()` method. |
|
// Separated to hide `accumulatedIndent` parameter that is necessary for |
|
// pretty printing. |
|
// Assumes `InitFormatting()` was made and json formatting variables are |
|
// initialized. |
|
private final function MutableText PrettyPrintWithIndent( |
|
AcediaObject toPrint, |
|
optional MutableText accumulatedIndent) |
|
{ |
|
if (toPrint == none) { |
|
return T(default.TNULL).MutableCopy().ChangeFormatting(jNull); |
|
} |
|
if (toPrint.class == class'IntBox') { |
|
return _.text.FromIntM(IntBox(toPrint).Get()).ChangeFormatting(jNumber); |
|
} |
|
if (toPrint.class == class'IntRef') { |
|
return _.text.FromIntM(IntRef(toPrint).Get()).ChangeFormatting(jNumber); |
|
} |
|
if (toPrint.class == class'BoolBox') |
|
{ |
|
return _.text.FromBoolM(BoolBox(toPrint).Get()) |
|
.ChangeFormatting(jBoolean); |
|
} |
|
if (toPrint.class == class'BoolRef') |
|
{ |
|
return _.text.FromBoolM(BoolRef(toPrint).Get()) |
|
.ChangeFormatting(jBoolean); |
|
} |
|
if (toPrint.class == class'FloatBox') |
|
{ |
|
return _.text.FromFloatM(FloatBox(toPrint).Get(), MAX_FLOAT_PRECISION) |
|
.ChangeFormatting(jNumber); |
|
} |
|
if (toPrint.class == class'FloatRef') |
|
{ |
|
return _.text.FromFloatM(FloatRef(toPrint).Get(), MAX_FLOAT_PRECISION) |
|
.ChangeFormatting(jNumber); |
|
} |
|
if ( toPrint.class == class'Text' |
|
|| toPrint.class == class'MutableText') |
|
{ |
|
return DisplayText(Text(toPrint)).ChangeFormatting(jString); |
|
} |
|
if (toPrint.class == class'DynamicArray') |
|
{ |
|
return PrettyPrintArrayWithIndent( DynamicArray(toPrint), |
|
accumulatedIndent); |
|
} |
|
if (toPrint.class == class'AssociativeArray') { |
|
return PrettyPrintObjectWithIndent( AssociativeArray(toPrint), |
|
accumulatedIndent); |
|
} |
|
return none; |
|
} |
|
|
|
// Does the actual job for `PrettyPrintArray()` method. |
|
// Separated to hide `accumulatedIndent` parameter that is necessary for |
|
// pretty printing. |
|
// Assumes `InitFormatting()` was made and json formatting variables are |
|
// initialized. |
|
private final function MutableText PrettyPrintArrayWithIndent( |
|
DynamicArray toPrint, |
|
MutableText accumulatedIndent) |
|
{ |
|
local int i, length; |
|
local MutableText extendedIndent; |
|
local MutableText result, printedItem; |
|
if (toPrint == none) { |
|
return none; |
|
} |
|
length = toPrint.GetLength(); |
|
extendedIndent = accumulatedIndent.MutableCopy().Append(T(TJSON_INDENT)); |
|
result = T(default.TOPEN_BRACKET).MutableCopy() |
|
.ChangeFormatting(jArrayBraces); |
|
for (i = 0; i < length; i += 1) |
|
{ |
|
if (i > 0) { |
|
result.Append(T(default.TCOMMA), jComma); |
|
} |
|
printedItem = PrettyPrintWithIndent(toPrint.GetItem(i), extendedIndent); |
|
if (printedItem != none) |
|
{ |
|
result.AppendLineBreak().Append(extendedIndent).Append(printedItem); |
|
printedItem.FreeSelf(); |
|
} |
|
else { |
|
result.Append(T(default.TNULL), jNull); |
|
} |
|
} |
|
if (i > 0) { |
|
result.AppendLineBreak().Append(accumulatedIndent); |
|
} |
|
result.Append(T(default.TCLOSE_BRACKET), jArrayBraces); |
|
extendedIndent.FreeSelf(); |
|
return result; |
|
} |
|
|
|
// Does the actual job for `PrettyPrintObject()` method. |
|
// Separated to hide `accumulatedIndent` parameter that is necessary for |
|
// pretty printing. |
|
// Assumes `InitFormatting()` was made and json formatting variables are |
|
// initialized. |
|
private final function MutableText PrettyPrintObjectWithIndent( |
|
AssociativeArray toPrint, |
|
MutableText accumulatedIndent) |
|
{ |
|
local bool printedKeyValuePair; |
|
local Iter iter; |
|
local Text nextKey; |
|
local AcediaObject nextValue; |
|
local MutableText extendedIndent; |
|
local MutableText result; |
|
if (toPrint == none) { |
|
return none; |
|
} |
|
extendedIndent = accumulatedIndent.MutableCopy().Append(T(TJSON_INDENT)); |
|
result = T(default.TOPEN_BRACE).MutableCopy() |
|
.ChangeFormatting(jObjectBraces); |
|
iter = toPrint.Iterate(); |
|
for (iter = toPrint.Iterate(); !iter.HasFinished(); iter.Next()) |
|
{ |
|
if (printedKeyValuePair) { |
|
result.Append(T(default.TCOMMA), jComma); |
|
} |
|
nextKey = Text(iter.GetKey()); |
|
nextValue = iter.Get(); |
|
if (nextKey == none) continue; |
|
if (nextKey.class != class'Text') continue; |
|
PrettyPrintKeyValue(result, nextKey, nextValue, extendedIndent); |
|
printedKeyValuePair = true; |
|
} |
|
if (printedKeyValuePair) { |
|
result.AppendLineBreak().Append(accumulatedIndent); |
|
} |
|
iter.FreeSelf(); |
|
result.Append(T(default.TCLOSE_BRACE), jObjectBraces); |
|
extendedIndent.FreeSelf(); |
|
return result; |
|
} |
|
|
|
// Auxiliary method for printing key-value pair into the `builder`. |
|
// `accumulatedIndent` is necessary in case passed `value` is |
|
// an object or array. |
|
// Assumes `InitFormatting()` was made and json formatting variables are |
|
// initialized. |
|
private final function PrettyPrintKeyValue( |
|
MutableText builder, |
|
Text nextKey, |
|
AcediaObject nextValue, |
|
MutableText accumulatedIndent) |
|
{ |
|
local MutableText printedKey, printedValue; |
|
printedKey = DisplayText(nextKey).ChangeFormatting(jPropertyName); |
|
printedValue = PrettyPrintWithIndent(nextValue, accumulatedIndent); |
|
builder.AppendLineBreak() |
|
.Append(accumulatedIndent) |
|
.Append(printedKey) |
|
.Append(T(default.TCOLON_SPACE), jColon); |
|
printedKey.FreeSelf(); |
|
if (printedValue != none) |
|
{ |
|
builder.Append(printedValue); |
|
printedValue.FreeSelf(); |
|
} |
|
else { |
|
builder.Append(T(default.TNULL), jNull); |
|
} |
|
} |
|
|
|
// Auxiliary method to convert `Text` into it's JSON "string" |
|
// representation. |
|
// We can't just dump `original`'s contents into JSON output as is, |
|
// since we have to replace several special characters with escaped sequences. |
|
private final function MutableText DisplayText(Text original) |
|
{ |
|
local int i, length; |
|
local MutableText result; |
|
local Text.Character nextCharacter; |
|
local Text.Character reverseSolidus; |
|
reverseSolidus = _.text.CharacterFromCodePoint(CODEPOINT_REVERSE_SOLIDUS); |
|
result = T(TQUOTE).MutableCopy(); |
|
length = original.GetLength(); |
|
for (i = 0; i < length; i += 1) |
|
{ |
|
nextCharacter = original.GetCharacter(i); |
|
if (DoesNeedEscaping(nextCharacter.codePoint)) |
|
{ |
|
result.AppendCharacter(reverseSolidus); |
|
nextCharacter.codePoint = |
|
GetEscapedVersion(nextCharacter.codePoint); |
|
result.AppendCharacter(nextCharacter); |
|
} |
|
else { |
|
result.AppendCharacter(nextCharacter); |
|
} |
|
} |
|
result.Append(T(TQUOTE)); |
|
return result; |
|
} |
|
|
|
// Checks whether a certain character (code point) needs to be replaced for |
|
// JSON printing. |
|
private final function bool DoesNeedEscaping(int codePoint) |
|
{ |
|
if (codePoint == CODEPOINT_REVERSE_SOLIDUS) return true; |
|
if (codePoint == CODEPOINT_CARRIAGE_RETURN) return true; |
|
if (codePoint == CODEPOINT_QUOTATION_MARK) return true; |
|
if (codePoint == CODEPOINT_BACKSPACE) return true; |
|
if (codePoint == CODEPOINT_FORM_FEED) return true; |
|
if (codePoint == CODEPOINT_LINE_FEED) return true; |
|
if (codePoint == CODEPOINT_SOLIDUS) return true; |
|
if (codePoint == CODEPOINT_TAB) return true; |
|
return false; |
|
} |
|
|
|
// Replaces code point with it's escaped letter. |
|
// When printing text into JSON some characters need to be escaped |
|
// (see `DoesNeedEscaping()`), but while some can use themselves in escaped |
|
// sequence ("\""), some need to be replaced with a different character ("\n"). |
|
private final function int GetEscapedVersion(int codePoint) |
|
{ |
|
if (codePoint == CODEPOINT_BACKSPACE) { |
|
return CODEPOINT_SMALL_B; |
|
} |
|
else if (codePoint == CODEPOINT_FORM_FEED) { |
|
return CODEPOINT_SMALL_F; |
|
} |
|
else if (codePoint == CODEPOINT_LINE_FEED) { |
|
return CODEPOINT_SMALL_N; |
|
} |
|
else if (codePoint == CODEPOINT_CARRIAGE_RETURN) { |
|
return CODEPOINT_SMALL_R; |
|
} |
|
else if (codePoint == CODEPOINT_TAB) { |
|
return CODEPOINT_SMALL_T; |
|
} |
|
return codePoint; |
|
} |
|
|
|
defaultproperties |
|
{ |
|
MAX_FLOAT_PRECISION = 4 |
|
TNULL = 0 |
|
stringConstants(0) = "null" |
|
TTRUE = 1 |
|
stringConstants(1) = "true" |
|
TFALSE = 2 |
|
stringConstants(2) = "false" |
|
TDOT = 3 |
|
stringConstants(3) = "." |
|
TEXPONENT = 4 |
|
stringConstants(4) = "e" |
|
TOPEN_BRACKET = 5 |
|
stringConstants(5) = "[" |
|
TCLOSE_BRACKET = 6 |
|
stringConstants(6) = "]" |
|
TOPEN_BRACE = 7 |
|
stringConstants(7) = "&{" |
|
TCLOSE_BRACE = 8 |
|
stringConstants(8) = "&}" |
|
TCOMMA = 9 |
|
stringConstants(9) = "," |
|
TCOLON = 10 |
|
stringConstants(10) = ":" |
|
TQUOTE = 11 |
|
stringConstants(11) = "\"" |
|
TJSON_INDENT = 12 |
|
stringConstants(12) = " " |
|
TCOLON_SPACE = 13 |
|
stringConstants(13) = ": " |
|
|
|
CODEPOINT_BACKSPACE = 8 |
|
CODEPOINT_TAB = 9 |
|
CODEPOINT_LINE_FEED = 10 |
|
CODEPOINT_FORM_FEED = 12 |
|
CODEPOINT_CARRIAGE_RETURN = 13 |
|
CODEPOINT_QUOTATION_MARK = 34 |
|
CODEPOINT_SOLIDUS = 47 |
|
CODEPOINT_REVERSE_SOLIDUS = 92 |
|
CODEPOINT_SMALL_B = 98 |
|
CODEPOINT_SMALL_F = 102 |
|
CODEPOINT_SMALL_N = 110 |
|
CODEPOINT_SMALL_R = 114 |
|
CODEPOINT_SMALL_T = 116 |
|
} |