Browse Source

Add support for displaying JSON values

Support for displaying JSON values as strings, in a valid JSON format.
pull/8/head
Anton Tarasenko 4 years ago
parent
commit
07fa4c42b7
  1. 9
      config/AcediaAliases_Colors.ini
  2. 41
      sources/Data/JSON/JArray.uc
  3. 81
      sources/Data/JSON/JObject.uc
  4. 193
      sources/Data/JSON/JSON.uc
  5. 12
      sources/Text/Parser.uc
  6. BIN
      sources/Text/Tests/TEST_Parser.uc

9
config/AcediaAliases_Colors.ini

@ -11,6 +11,15 @@ record=(alias="type_boolean",value="rgb(38,139,210)")
record=(alias="type_string",value="rgb(211,54,130)") record=(alias="type_string",value="rgb(211,54,130)")
record=(alias="type_literal",value="rgb(42,161,152)") record=(alias="type_literal",value="rgb(42,161,152)")
record=(alias="type_class",value="rgb(108,113,196)") record=(alias="type_class",value="rgb(108,113,196)")
record=(alias="json_propertyName",value="rgb(255,255,255)")
record=(alias="json_objectBraces",value="rgb(128,128,128)")
record=(alias="json_arrayBraces",value="rgb(128,128,128)")
record=(alias="json_comma",value="rgb(128,128,128)")
record=(alias="json_colon",value="rgb(128,128,128)")
record=(alias="json_number",value="rgb(181,137,0)")
record=(alias="json_boolean",value="rgb(38,139,210)")
record=(alias="json_string",value="rgb(211,54,130)")
record=(alias="json_null",value="rgb(42,161,152)")
; Pink colors ; Pink colors
record=(alias="Pink",value="rgb(255,192,203)") record=(alias="Pink",value="rgb(255,192,203)")
record=(alias="LightPink",value="rgb(255,182,193)") record=(alias="LightPink",value="rgb(255,182,193)")

41
sources/Data/JSON/JArray.uc

@ -179,6 +179,7 @@ public final function JArray SetNumber(
newStorageValue.type = JSON_Number; newStorageValue.type = JSON_Number;
newStorageValue.numberValue = value; newStorageValue.numberValue = value;
newStorageValue.numberValueAsInt = int(value); newStorageValue.numberValueAsInt = int(value);
newStorageValue.preferIntegerValue = false;
data[index] = newStorageValue; data[index] = newStorageValue;
return self; return self;
} }
@ -206,6 +207,7 @@ public final function JArray SetInteger(
newStorageValue.type = JSON_Number; newStorageValue.type = JSON_Number;
newStorageValue.numberValue = float(value); newStorageValue.numberValue = float(value);
newStorageValue.numberValueAsInt = value; newStorageValue.numberValueAsInt = value;
newStorageValue.preferIntegerValue = true;
data[index] = newStorageValue; data[index] = newStorageValue;
return self; return self;
} }
@ -531,6 +533,45 @@ public function JSON Clone()
return clonedArray; return clonedArray;
} }
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
local int i;
local bool isntFirstElement;
local string contents;
local string openingBraces, closingBraces;
local string elementsSeparator;
local JSONDisplaySettings innerSettings;
if (displaySettings.stackIndentation) {
innerSettings = IndentSettings(displaySettings, true);
}
else {
innerSettings = displaySettings;
}
// Prepare delimiters using appropriate indentation rules
// We only use inner settings for the part right after '[',
// as the rest is supposed to be aligned with outer objects
openingBraces = displaySettings.beforeArrayOpening
$ "[" $ innerSettings.afterArrayOpening;
closingBraces = displaySettings.beforeArrayEnding
$ "]" $ displaySettings.afterArrayEnding;
elementsSeparator = "," $ innerSettings.afterArrayComma;
if (innerSettings.colored) {
elementsSeparator = "{$json_comma" $ elementsSeparator $ "}";
openingBraces = "{$json_arrayBraces" $ openingBraces $ "}";
closingBraces = "{$json_arrayBraces" $ closingBraces $ "}";
}
// Display inner properties
for (i = 0; i < data.length; i += 1)
{
if (isntFirstElement) {
contents $= elementsSeparator;
}
contents $= DisplayAtom(data[i], innerSettings);
isntFirstElement = true;
}
return openingBraces $ contents $ closingBraces;
}
defaultproperties defaultproperties
{ {
} }

81
sources/Data/JSON/JObject.uc

@ -322,10 +322,11 @@ public final function JObject SetNumber(string name, float value)
{ {
local JProperty property; local JProperty property;
FindProperty(name, property); FindProperty(name, property);
property.value.type = JSON_Number; property.value.type = JSON_Number;
property.value.numberValue = value; property.value.numberValue = value;
property.value.numberValueAsInt = int(value); property.value.numberValueAsInt = int(value);
property.value.complexValue = none; property.value.complexValue = none;
property.value.preferIntegerValue = false;
UpdateProperty(property); UpdateProperty(property);
return self; return self;
} }
@ -334,10 +335,11 @@ public final function JObject SetInteger(string name, int value)
{ {
local JProperty property; local JProperty property;
FindProperty(name, property); FindProperty(name, property);
property.value.type = JSON_Number; property.value.type = JSON_Number;
property.value.numberValue = float(value); property.value.numberValue = float(value);
property.value.numberValueAsInt = value; property.value.numberValueAsInt = value;
property.value.complexValue = none; property.value.complexValue = none;
property.value.preferIntegerValue = true;
UpdateProperty(property); UpdateProperty(property);
return self; return self;
} }
@ -527,6 +529,69 @@ public function JSON Clone()
return clonedObject; return clonedObject;
} }
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
local int i, j;
local bool isntFirstProperty;
local string contents;
local string openingBraces, closingBraces;
local string propertiesSeparator;
local array<JProperty> nextProperties;
local JSONDisplaySettings innerSettings;
if (displaySettings.stackIndentation) {
innerSettings = IndentSettings(displaySettings);
}
else {
innerSettings = displaySettings;
}
// Prepare delimiters using appropriate indentation rules
// We only use inner settings for the part right after '{',
// as the rest is supposed to be aligned with outer objects
openingBraces = displaySettings.beforeObjectOpening
$ "{" $ innerSettings.afterObjectOpening;
closingBraces = displaySettings.beforeObjectEnding
$ "}" $ displaySettings.afterObjectEnding;
propertiesSeparator = "," $ innerSettings.afterObjectComma;
if (innerSettings.colored) {
propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}";
openingBraces = "{$json_objectBraces &" $ openingBraces $ "}";
closingBraces = "{$json_objectBraces &" $ closingBraces $ "}";
}
// Display inner properties
for (i = 0; i < hashTable.length; i += 1)
{
nextProperties = hashTable[i].properties;
for (j = 0; j < nextProperties.length; j += 1)
{
if (isntFirstProperty) {
contents $= propertiesSeparator;
}
contents $= DisplayProperty(nextProperties[j], innerSettings);
isntFirstProperty = true;
}
}
return openingBraces $ contents $ closingBraces;
}
protected function string DisplayProperty(
JProperty toDisplay,
JSONDisplaySettings displaySettings)
{
local string result;
result = displaySettings.beforePropertyName
$ DisplayJSONString(toDisplay.name)
$ displaySettings.afterPropertyName;
if (displaySettings.colored) {
result = "{$json_propertyName" @ result $ "}{$json_colon :}";
}
else {
result $= ":";
}
return (result $ displaySettings.beforePropertyValue
$ DisplayAtom(toDisplay.value, displaySettings)
$ displaySettings.afterPropertyValue);
}
defaultproperties defaultproperties
{ {
ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 ABSOLUTE_LOWER_CAPACITY_LIMIT = 10

193
sources/Data/JSON/JSON.uc

@ -68,6 +68,7 @@ struct JStorageAtom
// them as both `float` and `integer` and allow user to request any version // them as both `float` and `integer` and allow user to request any version
// of them // of them
var protected int numberValueAsInt; var protected int numberValueAsInt;
var protected bool preferIntegerValue;
// Some `string` values might be actually used to represent classes, // Some `string` values might be actually used to represent classes,
// so we will give users an ability to request `string` value as a class. // so we will give users an ability to request `string` value as a class.
var protected class<Object> stringValueAsClass; var protected class<Object> stringValueAsClass;
@ -84,6 +85,22 @@ enum JComparisonResult
JCR_Equal JCR_Equal
}; };
struct JSONDisplaySettings
{
var bool colored;
var bool stackIndentation;
var string subObjectIndentation, subArrayIndentation;
var string beforeObjectOpening, afterObjectOpening;
var string beforeObjectEnding, afterObjectEnding;
var string beforePropertyName, afterPropertyName;
var string beforePropertyValue, afterPropertyValue;
var string afterObjectComma;
var string beforeArrayOpening, afterArrayOpening;
var string beforeArrayEnding, afterArrayEnding;
var string beforeElement, afterElement;
var string afterArrayComma;
};
public function JSON Clone() public function JSON Clone()
{ {
return none; return none;
@ -158,6 +175,182 @@ protected final function TryLoadingStringAsClass(out JStorageAtom atom)
class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true)); class<Object>(DynamicLoadObject(atom.stringValue, class'Class', true));
} }
public final function string Display(
optional bool fancyPrinting,
optional bool colorSettings)
{
local JSONDisplaySettings settingsToUse;
// Settings are minimal by default
if (fancyPrinting) {
settingsToUse = GetFancySettings();
}
if (colorSettings) {
settingsToUse.colored = true;
}
return DisplayWith(settingsToUse);
}
public function string DisplayWith(JSONDisplaySettings displaySettings)
{
return "";
}
protected final function string DisplayAtom(
JStorageAtom atom,
JSONDisplaySettings displaySettings)
{
local string colorTag;
local string result;
if ( atom.complexValue != none
&& (atom.type == JSON_Object || atom.type == JSON_Array) ) {
return atom.complexValue.DisplayWith(displaySettings);
}
if (atom.type == JSON_Null) {
result = "null";
colorTag = "$json_null";
}
else if (atom.type == JSON_Number) {
if (atom.preferIntegerValue) {
result = string(atom.numberValueAsInt);
}
else {
result = DisplayFloat(atom.numberValue);
}
colorTag = "$json_number";
}
else if (atom.type == JSON_String) {
result = DisplayJSONString(atom.stringValue);
colorTag = "$json_string";
}
else if (atom.type == JSON_Boolean) {
if (atom.booleanValue) {
result = "true";
}
else {
result = "false";
}
colorTag = "$json_boolean";
}
if (displaySettings.colored) {
return "{" $ colorTag @ result $ "}";
}
return result;
}
protected final function string DisplayFloat(float number)
{
local int integerPart, fractionalPart;
local int precision;
local int howManyZeroes;
local string zeroes;
local string result;
precision = Clamp(MAX_FLOAT_PRECISION, 0, 10);
if (number < 0) {
number *= -1;
result = "-";
}
integerPart = number;
result $= string(integerPart);
number = (number - integerPart);
fractionalPart = Round(number * (10 ** precision));
if (fractionalPart <= 0) {
return result;
}
result $= ".";
howManyZeroes = precision - CountDigits(fractionalPart);
while (fractionalPart > 0 && fractionalPart % 10 == 0) {
fractionalPart /= 10;
}
while (howManyZeroes > 0) {
zeroes $= "0";
howManyZeroes -= 1;
}
return result $ zeroes $ string(fractionalPart);
}
protected final function int CountDigits(int number)
{
local int digitCounter;
while (number > 0)
{
number -= (number % 10);
number /= 10;
digitCounter += 1;
}
return digitCounter;
}
protected final function JSONDisplaySettings GetFancySettings()
{
local string lineFeed;
local JSONDisplaySettings fancySettings;
lineFeed = Chr(10);
fancySettings.stackIndentation = true;
fancySettings.subObjectIndentation = " ";
fancySettings.subArrayIndentation = "";
fancySettings.afterObjectOpening = lineFeed;
fancySettings.beforeObjectEnding = lineFeed;
fancySettings.beforePropertyValue = " ";
fancySettings.afterObjectComma = lineFeed;
fancySettings.beforeElement = " ";
fancySettings.afterArrayComma = " ";
return fancySettings;
}
protected final function string DisplayJSONString(string input)
{
// Convert control characters (+ other, specified by JSON)
// into escaped sequences
ReplaceText(input, "\"", "\\\"");
ReplaceText(input, "/", "\\/");
ReplaceText(input, "\\", "\\\\");
ReplaceText(input, Chr(0x08), "\\b");
ReplaceText(input, Chr(0x0c), "\\f");
ReplaceText(input, Chr(0x0a), "\\n");
ReplaceText(input, Chr(0x0d), "\\r");
ReplaceText(input, Chr(0x09), "\\t");
// TODO: test if there are control characters and render them as "\u...."
return ("\"" $ input $ "\"");
}
protected final function JSONDisplaySettings IndentSettings(
JSONDisplaySettings inputSettings,
optional bool indentingArray)
{
local string lineFeed;
local string lineFeedIndent;
local JSONDisplaySettings indentedSettings;
indentedSettings = inputSettings;
lineFeed = Chr(0x0a);
if (indentingArray) {
lineFeedIndent = lineFeed $ inputSettings.subArrayIndentation;
}
else {
lineFeedIndent = lineFeed $ inputSettings.subObjectIndentation;
}
if (lineFeedIndent == lineFeed) {
return indentedSettings;
}
ReplaceText(indentedSettings.afterObjectEnding, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterPropertyName, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterObjectComma, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterArrayOpening, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforeArrayEnding, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterArrayEnding, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforeElement, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterElement, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterArrayComma, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforeObjectOpening, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforePropertyValue, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterObjectOpening, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforeObjectEnding, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforeArrayOpening, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.afterPropertyValue, lineFeed, lineFeedIndent);
ReplaceText(indentedSettings.beforePropertyName, lineFeed, lineFeedIndent);
return indentedSettings;
}
defaultproperties defaultproperties
{ {
MAX_FLOAT_PRECISION = 4
} }

12
sources/Text/Parser.uc

@ -598,10 +598,10 @@ public final function Parser MUnsignedInteger
* *
* A Unicode code point can also be directly entered with either of the two * A Unicode code point can also be directly entered with either of the two
* commands: * commands:
* \U0056 * \u0056
* \u56 * \U56
* The difference is that `\U` allows you to enter two-byte code point, while * The difference is that `\u` allows you to enter two-byte code point, while
* `\u` only allows to define code points that fit into 1 byte, * `\U` only allows to define code points that fit into 1 byte,
* but is more compact. * but is more compact.
* *
* @param denotedCodePoint If parsing is successful, parameter will contain * @param denotedCodePoint If parsing is successful, parameter will contain
@ -633,11 +633,11 @@ public final function Parser MEscapedSequence
} }
} }
// Escaped character denotes declaration of arbitrary Unicode code point // Escaped character denotes declaration of arbitrary Unicode code point
if (denotedCharacter.codePoint == CODEPOINT_ULARGE) if (denotedCharacter.codePoint == CODEPOINT_USMALL)
{ {
MUnsignedInteger(denotedCharacter.codePoint, 16, 4); MUnsignedInteger(denotedCharacter.codePoint, 16, 4);
} }
else if (denotedCharacter.codePoint == CODEPOINT_USMALL) else if (denotedCharacter.codePoint == CODEPOINT_ULARGE)
{ {
MUnsignedInteger(denotedCharacter.codePoint, 16, 2); MUnsignedInteger(denotedCharacter.codePoint, 16, 2);
} }

BIN
sources/Text/Tests/TEST_Parser.uc

Binary file not shown.
Loading…
Cancel
Save