Add Dom::Path class for representing positions in a Dom (#7008)

* Add Dom::Path class for representing positions in a Dom

This also adds Value support for doing a path-based lookup.

The serialized representation is presently compliant with the JSON-pointer spec but the implementation supports Node types and may be later expanded if we require additional functionality (e.g. XPath style conditional querying).

Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com>
This commit is contained in:
Nicholas Van Sickle
2022-01-20 16:53:33 -08:00
committed by GitHub
parent 858a92f394
commit 32e2ba754b
8 changed files with 1036 additions and 0 deletions
@@ -0,0 +1,478 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/DOM/DomPath.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Console/ConsoleTypeHelpers.h>
namespace AZ::Dom
{
PathEntry::PathEntry(size_t value)
: m_value(value)
{
}
PathEntry::PathEntry(AZ::Name value)
: m_value(AZStd::move(value))
{
}
PathEntry::PathEntry(AZStd::string_view value)
: m_value(AZ::Name(value))
{
}
PathEntry& PathEntry::operator=(size_t value)
{
m_value = value;
return *this;
}
PathEntry& PathEntry::operator=(AZ::Name value)
{
m_value = AZStd::move(value);
return *this;
}
PathEntry& PathEntry::operator=(AZStd::string_view value)
{
m_value = AZ::Name(value);
return *this;
}
bool PathEntry::operator==(const PathEntry& other) const
{
return m_value == other.m_value;
}
bool PathEntry::operator==(size_t value) const
{
return IsIndex() && GetIndex() == value;
}
bool PathEntry::operator==(const AZ::Name& key) const
{
return IsKey() && GetKey() == key;
}
bool PathEntry::operator==(AZStd::string_view key) const
{
return IsKey() && GetKey() == AZ::Name(key);
}
bool PathEntry::operator!=(const PathEntry& other) const
{
return m_value != other.m_value;
}
bool PathEntry::operator!=(size_t value) const
{
return !IsIndex() || GetIndex() != value;
}
bool PathEntry::operator!=(const AZ::Name& key) const
{
return !IsKey() || GetKey() != key;
}
bool PathEntry::operator!=(AZStd::string_view key) const
{
return !IsKey() || GetKey() != AZ::Name(key);
}
void PathEntry::SetEndOfArray()
{
m_value = EndOfArrayIndex;
}
bool PathEntry::IsEndOfArray() const
{
const size_t* result = AZStd::get_if<size_t>(&m_value);
return result == nullptr ? false : ((*result) == EndOfArrayIndex);
}
bool PathEntry::IsIndex() const
{
const size_t* result = AZStd::get_if<size_t>(&m_value);
return result == nullptr ? false : ((*result) != EndOfArrayIndex);
}
bool PathEntry::IsKey() const
{
return AZStd::holds_alternative<AZ::Name>(m_value);
}
size_t PathEntry::GetIndex() const
{
AZ_Assert(IsIndex(), "GetIndex called on PathEntry that is not an index");
return AZStd::get<size_t>(m_value);
}
const AZ::Name& PathEntry::GetKey() const
{
AZ_Assert(IsKey(), "Key called on PathEntry that is not a key");
return AZStd::get<AZ::Name>(m_value);
}
Path::Path(AZStd::initializer_list<PathEntry> init)
: m_entries(init)
{
}
Path::Path(AZStd::string_view pathString)
{
FromString(pathString);
}
Path Path::operator/(const PathEntry& entry) const
{
Path newPath(*this);
newPath /= entry;
return newPath;
}
Path Path::operator/(size_t index) const
{
return *this / PathEntry(index);
}
Path Path::operator/(AZ::Name key) const
{
return *this / PathEntry(key);
}
Path Path::operator/(AZStd::string_view key) const
{
return *this / PathEntry(key);
}
Path Path::operator/(const Path& other) const
{
Path newPath(*this);
newPath /= other;
return newPath;
}
Path& Path::operator/=(const PathEntry& entry)
{
Push(entry);
return *this;
}
Path& Path::operator/=(size_t index)
{
return *this /= PathEntry(index);
}
Path& Path::operator/=(AZ::Name key)
{
return *this /= PathEntry(key);
}
Path& Path::operator/=(AZStd::string_view key)
{
return *this /= PathEntry(key);
}
Path& Path::operator/=(const Path& other)
{
for (const PathEntry& entry : other)
{
Push(entry);
}
return *this;
}
bool Path::operator==(const Path& other) const
{
return m_entries == other.m_entries;
}
const Path::ContainerType& Path::GetEntries() const
{
return m_entries;
}
void Path::Push(PathEntry entry)
{
m_entries.push_back(AZStd::move(entry));
}
void Path::Push(size_t entry)
{
Push(PathEntry(entry));
}
void Path::Push(AZ::Name entry)
{
Push(PathEntry(AZStd::move(entry)));
}
void Path::Push(AZStd::string_view entry)
{
Push(AZ::Name(entry));
}
void Path::Pop()
{
m_entries.pop_back();
}
void Path::Clear()
{
m_entries.clear();
}
PathEntry Path::At(size_t index) const
{
if (index < m_entries.size())
{
return m_entries[index];
}
return {};
}
size_t Path::Size() const
{
return m_entries.size();
}
PathEntry& Path::operator[](size_t index)
{
return m_entries[index];
}
const PathEntry& Path::operator[](size_t index) const
{
return m_entries[index];
}
Path::ContainerType::iterator Path::begin()
{
return m_entries.begin();
}
Path::ContainerType::iterator Path::end()
{
return m_entries.end();
}
Path::ContainerType::const_iterator Path::begin() const
{
return m_entries.cbegin();
}
Path::ContainerType::const_iterator Path::end() const
{
return m_entries.cend();
}
Path::ContainerType::const_iterator Path::cbegin() const
{
return m_entries.cbegin();
}
Path::ContainerType::const_iterator Path::cend() const
{
return m_entries.cend();
}
size_t Path::size() const
{
return m_entries.size();
}
size_t Path::GetStringLength() const
{
size_t size = 0;
for (const PathEntry& entry : m_entries)
{
++size;
if (entry.IsEndOfArray())
{
size += 1;
}
else if (entry.IsIndex())
{
const size_t index = entry.GetIndex();
const double digitCount = index > 0 ? log10(aznumeric_cast<double>(index + 1)) : 1.0;
size += aznumeric_cast<size_t>(ceil(digitCount));
}
else
{
const char* nameBuffer = entry.GetKey().GetCStr();
for (size_t i = 0; nameBuffer[i]; ++i)
{
if (nameBuffer[i] == EscapeCharacter || nameBuffer[i] == PathSeparator)
{
++size;
}
++size;
}
}
}
return size;
}
void Path::FormatString(char* stringBuffer, size_t bufferSize) const
{
size_t bufferIndex = 0;
auto putChar = [&](char c)
{
if (bufferIndex == bufferSize)
{
return;
}
stringBuffer[bufferIndex++] = c;
};
auto writeToBuffer = [&](const char* key)
{
for (size_t keyIndex = 0; key[keyIndex]; ++keyIndex)
{
const char c = key[keyIndex];
if (c == EscapeCharacter)
{
putChar(EscapeCharacter);
putChar(TildeSequence);
}
else if (c == PathSeparator)
{
putChar(EscapeCharacter);
putChar(ForwardSlashSequence);
}
else
{
putChar(c);
}
}
};
for (const PathEntry& entry : m_entries)
{
putChar(PathSeparator);
if (entry.IsEndOfArray())
{
putChar(EndOfArrayCharacter);
}
else if (entry.IsIndex())
{
bufferIndex += azsnprintf(&stringBuffer[bufferIndex], bufferSize - bufferIndex, "%zu", entry.GetIndex());
}
else
{
writeToBuffer(entry.GetKey().GetCStr());
}
}
putChar('\0');
}
AZStd::string Path::ToString() const
{
AZStd::string formattedString;
const size_t size = GetStringLength();
formattedString.resize_no_construct(size);
FormatString(formattedString.data(), size + 1);
return formattedString;
}
void Path::AppendToString(AZStd::string& output) const
{
const size_t startIndex = output.length();
const size_t stringLength = GetStringLength();
output.resize_no_construct(startIndex + stringLength);
FormatString(output.data() + startIndex, stringLength + 1);
}
void Path::FromString(AZStd::string_view pathString)
{
m_entries.clear();
if (pathString.empty())
{
return;
}
size_t pathEntryCount = 0;
for (size_t i = 1; i <= pathString.size(); ++i)
{
if (pathString[i] == PathSeparator)
{
++pathEntryCount;
}
}
m_entries.reserve(pathEntryCount);
// Ignore a preceeding path separator and start processing after it
size_t pathIndex = pathString[0] == PathSeparator ? 1 : 0;
bool isNumber = true;
AZStd::string convertedSection;
for (size_t i = pathIndex; i <= pathString.size(); ++i)
{
if (i == pathString.size() || pathString[i] == PathSeparator)
{
AZStd::string_view section = pathString.substr(pathIndex, i - pathIndex);
if (section.size() == 1 && section[0] == EndOfArrayCharacter)
{
PathEntry entry;
entry.SetEndOfArray();
m_entries.push_back(AZStd::move(entry));
}
else if (isNumber && !section.empty())
{
size_t index = 0;
ConsoleTypeHelpers::StringToValue(index, section);
m_entries.push_back(PathEntry{ index });
}
else
{
convertedSection.clear();
size_t lastPos = 0;
size_t posToEscape = section.find(EscapeCharacter);
while (posToEscape != AZStd::string_view::npos)
{
if (convertedSection.empty())
{
convertedSection.reserve(section.size() - 1);
}
convertedSection += section.substr(lastPos, posToEscape - lastPos);
if (section[posToEscape + 1] == ForwardSlashSequence)
{
convertedSection += '/';
}
else
{
convertedSection += '~';
}
lastPos = posToEscape + 2;
posToEscape = section.find(EscapeCharacter, posToEscape + 2);
}
if (!convertedSection.empty())
{
convertedSection += section.substr(lastPos);
m_entries.emplace_back(convertedSection);
}
else
{
m_entries.emplace_back(section);
}
}
pathIndex = i + 1;
isNumber = true;
continue;
}
const char c = pathString[i];
isNumber = isNumber && c >= '0' && c <= '9';
}
}
} // namespace AZ::Dom
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
namespace AZ::Dom
{
//! Represents the path to a direct descendant of a Value.
//! PathEntry may be one of the following:
//! - Index, a numerical index for indexing within Arrays and Nodes
//! - Key, a name for indexing within Objects and Nodes
//! - EndOfArray, a special-case indicator for representing the end of an array
//! used by the patching system to represent push / pop back operations.
class PathEntry final
{
public:
static constexpr size_t EndOfArrayIndex = size_t(-1);
PathEntry() = default;
PathEntry(const PathEntry&) = default;
PathEntry(PathEntry&&) = default;
explicit PathEntry(size_t value);
explicit PathEntry(AZ::Name value);
explicit PathEntry(AZStd::string_view value);
PathEntry& operator=(const PathEntry&) = default;
PathEntry& operator=(PathEntry&&) = default;
PathEntry& operator=(size_t value);
PathEntry& operator=(AZ::Name value);
PathEntry& operator=(AZStd::string_view value);
bool operator==(const PathEntry& other) const;
bool operator==(size_t index) const;
bool operator==(const AZ::Name& key) const;
bool operator==(AZStd::string_view key) const;
bool operator!=(const PathEntry& other) const;
bool operator!=(size_t index) const;
bool operator!=(const AZ::Name& key) const;
bool operator!=(AZStd::string_view key) const;
void SetEndOfArray();
bool IsEndOfArray() const;
bool IsIndex() const;
bool IsKey() const;
size_t GetIndex() const;
const AZ::Name& GetKey() const;
private:
AZStd::variant<size_t, AZ::Name> m_value;
};
//! Represents a path, represented as a series of PathEntry values, to a position in a Value.
class Path final
{
public:
using ContainerType = AZStd::vector<PathEntry>;
static constexpr char PathSeparator = '/';
static constexpr char EscapeCharacter = '~';
static constexpr char TildeSequence = '0';
static constexpr char ForwardSlashSequence = '1';
static constexpr char EndOfArrayCharacter = '-';
Path() = default;
Path(const Path&) = default;
Path(Path&&) = default;
explicit Path(AZStd::initializer_list<PathEntry> init);
//! Creates a Path from a path string, a path string is formatted per the JSON pointer specification
//! and looks like "/path/to/value/0"
explicit Path(AZStd::string_view pathString);
template<class InputIterator>
explicit Path(InputIterator first, InputIterator last)
: m_entries(first, last)
{
}
Path& operator=(const Path&) = default;
Path& operator=(Path&&) = default;
Path operator/(const PathEntry&) const;
Path operator/(size_t) const;
Path operator/(AZ::Name) const;
Path operator/(AZStd::string_view) const;
Path operator/(const Path&) const;
Path& operator/=(const PathEntry&);
Path& operator/=(size_t);
Path& operator/=(AZ::Name);
Path& operator/=(AZStd::string_view);
Path& operator/=(const Path&);
bool operator==(const Path&) const;
const ContainerType& GetEntries() const;
void Push(PathEntry entry);
void Push(size_t entry);
void Push(AZ::Name entry);
void Push(AZStd::string_view key);
void Pop();
void Clear();
PathEntry At(size_t index) const;
size_t Size() const;
PathEntry& operator[](size_t index);
const PathEntry& operator[](size_t index) const;
ContainerType::iterator begin();
ContainerType::iterator end();
ContainerType::const_iterator begin() const;
ContainerType::const_iterator end() const;
ContainerType::const_iterator cbegin() const;
ContainerType::const_iterator cend() const;
size_t size() const;
//! Gets the length this path would require, if string-formatted.
//! The length includes the contents of the string but not a null terminator.
size_t GetStringLength() const;
//! Formats a JSON-pointer style path string into the target buffer.
//! This operation will fail if bufferSize < GetStringLength() + 1
void FormatString(char* stringBuffer, size_t bufferSize) const;
//! Returns a JSON-pointer style path string for this path.
AZStd::string ToString() const;
void AppendToString(AZStd::string& output) const;
//! Reads a JSON-pointer style path from pathString and replaces this path's contents.
//! Paths are accepted in the following forms:
//! "/path/to/foo/0"
//! "path/to/foo/0"
void FromString(AZStd::string_view pathString);
private:
ContainerType m_entries;
};
} // namespace AZ::Dom
@@ -6,6 +6,7 @@
*
*/
#include <AzCore/DOM/DomPath.h>
#include <AzCore/DOM/DomValue.h>
#include <AzCore/DOM/DomValueWriter.h>
#include <AzCore/std/smart_ptr/make_shared.h>
@@ -1177,4 +1178,124 @@ namespace AZ::Dom
{
return m_value;
}
Value& Value::operator[](const PathEntry& entry)
{
if (entry.IsEndOfArray())
{
Array::ContainerType& array = GetArrayInternal();
array.push_back();
return array[array.size() - 1];
}
return entry.IsIndex() ? operator[](entry.GetIndex()) : operator[](entry.GetKey());
}
const Value& Value::operator[](const PathEntry& entry) const
{
return entry.IsIndex() ? operator[](entry.GetIndex()) : operator[](entry.GetKey());
}
Value& Value::operator[](const Path& path)
{
Value* value = this;
for (const PathEntry& entry : path)
{
value = &value->operator[](entry);
}
return *value;
}
const Value& Value::operator[](const Path& path) const
{
const Value* value = this;
for (const PathEntry& entry : path)
{
value = &value->operator[](entry);
}
return *value;
}
const Value* Value::FindChild(const PathEntry& entry) const
{
if (entry.IsEndOfArray())
{
return nullptr;
}
else if (entry.IsIndex())
{
const Array::ContainerType& array = GetArrayInternal();
const size_t index = entry.GetIndex();
if (index < array.size())
{
return &array[index];
}
}
else
{
const Object::ContainerType& obj = GetObjectInternal();
auto memberIt = FindMember(entry.GetKey());
if (memberIt != obj.end())
{
return &memberIt->second;
}
}
return nullptr;
}
Value* Value::FindMutableChild(const PathEntry& entry)
{
if (entry.IsEndOfArray())
{
Array::ContainerType& array = GetArrayInternal();
array.push_back();
return &array[array.size() - 1];
}
else if (entry.IsIndex())
{
Array::ContainerType& array = GetArrayInternal();
const size_t index = entry.GetIndex();
if (index < array.size())
{
return &array[index];
}
}
else
{
Object::ContainerType& obj = GetObjectInternal();
auto memberIt = FindMutableMember(entry.GetKey());
if (memberIt != obj.end())
{
return &memberIt->second;
}
}
return nullptr;
}
const Value* Value::FindChild(const Path& path) const
{
const Value* value = this;
for (const PathEntry& entry : path)
{
value = value->FindChild(entry);
if (value == nullptr)
{
return nullptr;
}
}
return value;
}
Value* Value::FindMutableChild(const Path& path)
{
Value* value = this;
for (const PathEntry& entry : path)
{
value = value->FindMutableChild(entry);
if (value == nullptr)
{
return nullptr;
}
}
return value;
}
} // namespace AZ::Dom
@@ -22,6 +22,8 @@
namespace AZ::Dom
{
class PathEntry;
class Path;
using KeyType = AZ::Name;
//! The type of underlying value stored in a value. \see Value
@@ -380,6 +382,17 @@ namespace AZ::Dom
Visitor::Result Accept(Visitor& visitor, bool copyStrings) const;
AZStd::unique_ptr<Visitor> GetWriteHandler();
// Path API...
Value& operator[](const PathEntry& entry);
const Value& operator[](const PathEntry& entry) const;
Value& operator[](const Path& path);
const Value& operator[](const Path& path) const;
const Value* FindChild(const PathEntry& entry) const;
Value* FindMutableChild(const PathEntry& entry);
const Value* FindChild(const Path& path) const;
Value* FindMutableChild(const Path& path);
//! Gets the internal value of this Value. Note that this value's types may not correspond one-to-one with the Type enumeration,
//! as internally the same type might have different storage mechanisms. Where possible, prefer using the typed API.
const ValueType& GetInternalValue() const;
@@ -116,6 +116,8 @@ set(FILES
Debug/TraceReflection.h
DOM/DomBackend.cpp
DOM/DomBackend.h
DOM/DomPath.cpp
DOM/DomPath.h
DOM/DomUtils.cpp
DOM/DomUtils.h
DOM/DomValue.cpp
@@ -0,0 +1,99 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Tests/DOM/DomFixtures.h>
#include <AzCore/DOM/DomPath.h>
namespace AZ::Dom::Benchmark
{
using DomPathBenchmark = Tests::DomBenchmarkFixture;
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPath_Concatenate_InPlace)(benchmark::State& state)
{
AZ::Name entry1("entry1");
AZ::Name entry2("entry2");
PathEntry end;
end.SetEndOfArray();
for (auto _ : state)
{
Path p;
p /= entry1;
p /= entry2;
p /= 0;
p /= end;
}
state.SetItemsProcessed(4 * state.iterations());
}
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPath_Concatenate_InPlace);
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPath_Concatenate_Copy)(benchmark::State& state)
{
AZ::Name entry1("entry1");
AZ::Name entry2("entry2");
PathEntry end;
end.SetEndOfArray();
for (auto _ : state)
{
Path p = Path() / entry1 / entry2 / 0 / end;
}
state.SetItemsProcessed(4 * state.iterations());
}
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPath_Concatenate_Copy);
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPath_ToString)(benchmark::State& state)
{
Path p("/path/with/multiple/0/different/components/65536/999/-");
AZStd::string s;
s.resize_no_construct(p.GetStringLength());
for (auto _ : state)
{
p.GetStringLength();
p.FormatString(s.data(), s.size());
}
state.SetBytesProcessed(s.size() * state.iterations());
}
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPath_ToString);
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPath_FromString)(benchmark::State& state)
{
AZStd::string pathString = "/path/with/multiple/0/different/components/including-long-strings-like-this/65536/999/-";
for (auto _ : state)
{
Path p(pathString);
benchmark::DoNotOptimize(p);
}
state.SetBytesProcessed(pathString.size() * state.iterations());
}
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPath_FromString);
BENCHMARK_DEFINE_F(DomPathBenchmark, DomPathEntry_IsEndOfArray)(benchmark::State& state)
{
PathEntry name("name");
PathEntry index(0);
PathEntry endOfArray;
endOfArray.SetEndOfArray();
for (auto _ : state)
{
name.IsEndOfArray();
index.IsEndOfArray();
endOfArray.IsEndOfArray();
}
state.SetItemsProcessed(3 * state.iterations());
}
BENCHMARK_REGISTER_F(DomPathBenchmark, DomPathEntry_IsEndOfArray);
}
@@ -0,0 +1,177 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomPath.h>
#include <AzCore/Name/NameDictionary.h>
#include <Tests/DOM/DomFixtures.h>
namespace AZ::Dom::Tests
{
class DomPathTests : public DomTestFixture
{
};
TEST_F(DomPathTests, EmptyPath_IsEmpty)
{
Path path("");
EXPECT_EQ(path.GetEntries().size(), 0);
}
TEST_F(DomPathTests, EmptyPath_IsEqualToDefault)
{
EXPECT_EQ(Path(""), Path());
}
TEST_F(DomPathTests, Index_IsNumeric)
{
EXPECT_EQ(Path("/0")[0], 0);
EXPECT_EQ(Path("/20")[0], 20);
EXPECT_EQ(Path("/9999")[0], 9999);
EXPECT_EQ(Path("/0/4/5/1")[3], 1);
}
TEST_F(DomPathTests, Index_ConvertsToString)
{
Path p;
EXPECT_EQ(p.ToString(), "");
p.Push(0);
EXPECT_EQ(p.ToString(), "/0");
p.Push(1);
EXPECT_EQ(p.ToString(), "/0/1");
p.Push(10);
EXPECT_EQ(p.ToString(), "/0/1/10");
p.Push(9999);
EXPECT_EQ(p.ToString(), "/0/1/10/9999");
p.Pop();
EXPECT_EQ(p.ToString(), "/0/1/10");
}
TEST_F(DomPathTests, Key_IsString)
{
EXPECT_EQ(Path("/foo")[0], "foo");
EXPECT_EQ(Path("/bar")[0], "bar");
EXPECT_EQ(Path("/foo/bar/baz12345")[0], "foo");
EXPECT_EQ(Path("/foo/bar/baz12345")[2], "baz12345");
EXPECT_EQ(Path("//foo")[0], "");
}
TEST_F(DomPathTests, Key_ConvertsToString)
{
Path p;
p.Push("foo");
EXPECT_EQ(p.ToString(), "/foo");
p.Push("bar");
EXPECT_EQ(p.ToString(), "/foo/bar");
p.Push("another_key");
EXPECT_EQ(p.ToString(), "/foo/bar/another_key");
p.Pop();
EXPECT_EQ(p.ToString(), "/foo/bar");
}
TEST_F(DomPathTests, Key_ConvertsFromEscaped)
{
EXPECT_EQ(Path("/foo~0")[0], "foo~");
EXPECT_EQ(Path("/foo~0bar~0~0")[0], "foo~bar~~");
EXPECT_EQ(Path("/foo~1bar/baz")[0], "foo/bar");
EXPECT_EQ(Path("/~1foo~1")[0], "/foo/");
}
TEST_F(DomPathTests, Key_ConvertsToEscaped)
{
Path p;
p.Push("with~tilde");
EXPECT_EQ(p.ToString(), "/with~0tilde");
p.Push("with/slash");
EXPECT_EQ(p.ToString(), "/with~0tilde/with~1slash");
p.Clear();
p.Push("/~with/mixed/characters~");
EXPECT_EQ(p.ToString(), "/~1~0with~1mixed~1characters~0");
}
TEST_F(DomPathTests, MixedPath_Resolves)
{
EXPECT_EQ(Path("/foo/0")[0], "foo");
EXPECT_EQ(Path("/foo/0")[1], 0);
EXPECT_EQ(Path("/42/foo/bar/0")[0], 42);
EXPECT_EQ(Path("/42/foo/bar/0")[1], "foo");
EXPECT_EQ(Path("/42/foo/bar/0")[2], "bar");
EXPECT_EQ(Path("/42/foo/bar/0")[3], 0);
}
TEST_F(DomPathTests, MixedPath_ConvertsToString)
{
Path p;
p.Push("foo");
EXPECT_EQ(p.ToString(), "/foo");
p.Push(0);
EXPECT_EQ(p.ToString(), "/foo/0");
p.Push("another_key");
EXPECT_EQ(p.ToString(), "/foo/0/another_key");
p.Push(100);
EXPECT_EQ(p.ToString(), "/foo/0/another_key/100");
p.Pop();
EXPECT_EQ(p.ToString(), "/foo/0/another_key");
}
TEST_F(DomPathTests, OperatorOverloads_Append)
{
EXPECT_EQ(Path("/foo/bar"), Path("/foo") / Path("/bar"));
EXPECT_EQ(Path("/foo"), Path("/foo") / Path());
EXPECT_EQ(Path("/foo/1/bar/0"), Path("/foo/1") / Path("/bar/0"));
EXPECT_EQ(Path("/foo") / 0, Path("/foo/0"));
EXPECT_EQ(Path() / "foo" / "bar", Path("/foo/bar"));
EXPECT_EQ(Path("/foo") / "bar" / "baz", Path("/foo/bar/baz"));
Path p("/foo/bar");
p /= "baz";
EXPECT_EQ(p, Path("/foo/bar/baz"));
p /= Path("0/1");
EXPECT_EQ(p, Path("/foo/bar/baz/0/1"));
}
TEST_F(DomPathTests, EndOfArray_FromString)
{
EXPECT_FALSE(Path("/foo/-")[0].IsEndOfArray());
EXPECT_TRUE(Path("/foo/-")[1].IsEndOfArray());
EXPECT_TRUE(Path("/foo/-/-")[2].IsEndOfArray());
}
TEST_F(DomPathTests, EndOfArray_ToString)
{
EXPECT_EQ(Path("/-").ToString(), "/-");
EXPECT_EQ(Path("/0/-").ToString(), "/0/-");
}
TEST_F(DomPathTests, MixedPath_AppendToString)
{
Path p("/foo/0");
AZStd::string s;
p.AppendToString(s);
EXPECT_EQ(s, "/foo/0");
p.AppendToString(s);
EXPECT_EQ(s, "/foo/0/foo/0");
}
} // namespace AZ::Dom::Tests
@@ -219,6 +219,8 @@ set(FILES
DOM/DomFixtures.h
DOM/DomJsonTests.cpp
DOM/DomJsonBenchmarks.cpp
DOM/DomPathTests.cpp
DOM/DomPathBenchmarks.cpp
DOM/DomValueTests.cpp
DOM/DomValueBenchmarks.cpp
)