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:
committed by
GitHub
parent
858a92f394
commit
32e2ba754b
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user