diff --git a/sources/Text/JSON/JSONPointer.uc b/sources/Text/JSON/JSONPointer.uc index 8e365f3..63ed173 100644 --- a/sources/Text/JSON/JSONPointer.uc +++ b/sources/Text/JSON/JSONPointer.uc @@ -5,7 +5,7 @@ * Path "/a/b/c" will be stored as a sequence of components "a", "b" and "c", * path "/" will be stored as a singular empty component "" * and empty path "" would mean that there is not components at all. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2023 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -293,6 +293,30 @@ public final function int GetNumericComponent(int index) return components[index].asNumber; } +/** + * Checks whether component at given index can be used to index array. + * + * This method accepts numeric components plus component equal to "-", that can + * be used to point at the element after the last on in the `JSONArray`. + * + * @param index Index of the component to check. + * @param `true` if component with given index exists and it either positive + * number or "-". + */ +public final function bool IsComponentArrayApplicable(int index) +{ + local bool isAddElementAlias; + local Text component; + + if (GetNumericComponent(index) >= 0) { + return true; + } + component = GetComponent(index); + isAddElementAlias = P("-").IsEqual(component); + _.memory.Free(component); + return isAddElementAlias; +} + /** * Converts caller `JSONPointer` into it's `Text` representation. * @@ -418,6 +442,85 @@ public final function JSONPointer Copy( return newPointer; } +/** + * Appends path, contained in JSON pointer `other` to the caller JSON pointer. + * Appending "/A/B/7/C" to "/object/hey/1/there/" produces + * "/object/hey/1/there//A/B/7/C". + * + * @param other Pointer to append. If `none` - caller `JSONPointer` will + * not change. + * @return Reference to the caller `JSONPointer` to allow for method chaining. + */ +public final function JSONPointer Append(JSONPointer other) +{ + local int i; + local array otherComponents; + + if (other == none) { + return self; + } + otherComponents = other.components; + for (i = 0; i < otherComponents.length; i += 1) + { + if (otherComponents[i].asText != none) { + otherComponents[i].asText = otherComponents[i].asText.MutableCopy(); + } + components[components.length] = otherComponents[i]; + } + return self; +} + +/** + * Checks if given pointer corresponds with the beginning of the caller one. + * + * Pointer starts with another one if it includes all of its fields from + * the beginning and in order + * E.g. "/A/B/C" starts with "/A/B", but not with "/A/B/C/D", "/D/A/B/C" or + * "/A/B/CD". + * + * @param other Candidate into being caller pointer's prefix. + * @return `true` if `other` is prefix and `false` otherwise. `none` is + * considered to be an empty pointer and, therefore, prefix to any other + * pointer. + */ +public final function bool StartsWith(JSONPointer other) +{ + local int i; + local array otherComponents; + + // `none` is same as empty + if (other == none) return true; + otherComponents = other.components; + // Not enough length + if (components.length < otherComponents.length) return false; + + for (i = 0; i < otherComponents.length; i += 1) + { + // Compare numeric components if at least one is such + if ( components[i].testedForBeingNumeric + || otherComponents[i].testedForBeingNumeric) + { + if (GetNumericComponent(i) != other.GetNumericComponent(i)) { + return false; + } + // End this iteration for numeric component, but continue for + // text ones + if (GetNumericComponent(i) >= 0) { + continue; + } + } + // We can reach here if: + // 1. Neither components have `testedForBeingNumeric` set to + // `true`, neither `asText` fields are `none` by the invariant; + // 2. At least one had `testedForBeingNumeric`, but they tested + // negative for being numeric. + if (!components[i].asText.Compare(otherComponents[i].asText)) { + return false; + } + } + return true; +} + defaultproperties { TSLASH = 0