From e9deac2f38b8d014c470b49ee00c32f2131b167f Mon Sep 17 00:00:00 2001 From: shweet Date: Fri, 24 Apr 2026 13:44:33 -0400 Subject: [PATCH] anm2 experiments --- CMakeLists.txt | 2 + compile_commands.json | 2 +- src/anm2_new/anm2.cpp | 474 ++++++++++++++++++++++++++++++++++++++++++ src/anm2_new/anm2.hpp | 233 +++++++++++++++++++++ src/util/xml_.cpp | 87 +++++++- src/util/xml_.hpp | 15 ++ 6 files changed, 806 insertions(+), 7 deletions(-) create mode 100644 src/anm2_new/anm2.cpp create mode 100644 src/anm2_new/anm2.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a299249..121cf19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,8 @@ set(TINYXML2_SRC external/tinyxml2/tinyxml2.cpp) file(GLOB PROJECT_SRC CONFIGURE_DEPENDS src/anm2/*.cpp src/anm2/*.hpp + src/anm2_new/*.cpp + src/anm2_new/*.hpp src/resource/*.cpp src/resource/*.hpp src/imgui/*.cpp diff --git a/compile_commands.json b/compile_commands.json index bc0dce8..e2be980 120000 --- a/compile_commands.json +++ b/compile_commands.json @@ -1 +1 @@ -/home/anon/sda/Personal/Repos/anm2ed/out/build/linux-debug/compile_commands.json \ No newline at end of file +/home/anon/sda/Personal/Repos/anm2ed/out/build/linux-release/compile_commands.json \ No newline at end of file diff --git a/src/anm2_new/anm2.cpp b/src/anm2_new/anm2.cpp new file mode 100644 index 0000000..911c206 --- /dev/null +++ b/src/anm2_new/anm2.cpp @@ -0,0 +1,474 @@ +#include "anm2.hpp" + +#include + +#include "file_.hpp" +#include "path_.hpp" +#include "xml_.hpp" + +namespace anm2ed::anm2_new +{ + using namespace tinyxml2; + + template + constexpr std::string_view enum_string_get(const std::array, N>& mappings, + Enum value) + { + for (const auto& [mappedValue, mappedString] : mappings) + if (mappedValue == value) return mappedString; + return {}; + } + + template + constexpr Enum string_enum_get(const std::array, N>& mappings, + std::string_view value, Enum fallback) + { + for (const auto& [mappedValue, mappedString] : mappings) + if (mappedString == value) return mappedValue; + return fallback; + } + + constexpr bool is_flag_set(Anm2::Flags flags, Anm2::Flag flag) { return (flags & flag) != 0; } + + std::filesystem::path asset_path_resolve(const std::filesystem::path& directory, + const std::filesystem::path& assetPath) + { + if (assetPath.empty()) return {}; + + auto resolved = assetPath.is_absolute() ? assetPath : directory / assetPath; + resolved = util::path::lower_case_backslash_handle(resolved); + if (util::path::is_exist(resolved)) return resolved; + return util::path::lower_case_backslash_handle(assetPath); + } + + std::string_view Anm2::Element::tag_get(Type type) { return enum_string_get(TYPE_TAGS, type); } + + Anm2::Element::Type Anm2::Element::type_get(std::string_view tag, Type parentType) + { + (void)parentType; + return string_enum_get(TYPE_TAGS, tag, UNKNOWN); + } + + Anm2::Element::Element(const std::filesystem::path& path, std::string* errorString) + { + XMLDocument document{}; + if (!document_load(document, path, errorString)) return; + if (auto* root = document.RootElement()) *this = from_xml(root); + } + + Anm2::Element::Element(std::string_view xml, Source, std::string* errorString) + { + XMLDocument document{}; + if (!document_parse(document, xml, errorString)) return; + if (auto* root = document.RootElement()) *this = from_xml(root); + } + + bool Anm2::Element::document_load(XMLDocument& document, const std::filesystem::path& path, std::string* errorString) + { + util::File file(path, "rb"); + if (!file) + { + if (errorString) *errorString = "Could not open file."; + return false; + } + + if (document.LoadFile(file.get()) != XML_SUCCESS) + { + if (errorString) *errorString = document.ErrorStr(); + return false; + } + + return true; + } + + bool Anm2::Element::document_parse(XMLDocument& document, std::string_view xml, std::string* errorString) + { + if (document.Parse(xml.data(), xml.size()) != XML_SUCCESS) + { + if (errorString) *errorString = document.ErrorStr(); + return false; + } + + return true; + } + + void Anm2::Element::attributes_read(const XMLElement* element, Type parentType) + { + if (!element) return; + + util::xml::query_string_attribute(element, "CreatedBy", createdBy); + util::xml::query_string_attribute(element, "CreatedOn", createdOn); + util::xml::query_string_attribute(element, "DefaultAnimation", defaultAnimation); + util::xml::query_string_attribute(element, "Name", name); + util::xml::query_path_attribute(element, "Path", path); + + util::xml::query_int_attribute(element, "Id", id); + util::xml::query_int_attribute(element, "LayerId", layerId); + util::xml::query_int_attribute(element, "NullId", nullId); + util::xml::query_int_attribute(element, "SpritesheetId", spritesheetId); + util::xml::query_int_attribute(element, "EventId", eventId); + util::xml::query_int_attribute(element, "RegionId", regionId); + util::xml::query_int_attribute(element, "AtFrame", atFrame); + util::xml::query_int_attribute(element, "FrameNum", frameNum); + util::xml::query_int_attribute(element, "Fps", fps); + util::xml::query_int_attribute(element, "Version", version); + util::xml::query_int_attribute(element, "Delay", delay); + + util::xml::query_float_attribute(element, "XCrop", crop.x); + util::xml::query_float_attribute(element, "YCrop", crop.y); + util::xml::query_float_attribute(element, "Width", size.x); + util::xml::query_float_attribute(element, "Height", size.y); + util::xml::query_float_attribute(element, "XPivot", pivot.x); + util::xml::query_float_attribute(element, "YPivot", pivot.y); + util::xml::query_float_attribute(element, "XPosition", position.x); + util::xml::query_float_attribute(element, "YPosition", position.y); + util::xml::query_float_attribute(element, "XScale", scale.x); + util::xml::query_float_attribute(element, "YScale", scale.y); + util::xml::query_float_attribute(element, "Rotation", rotation); + + util::xml::query_color_attribute(element, "RedTint", tint.r); + util::xml::query_color_attribute(element, "GreenTint", tint.g); + util::xml::query_color_attribute(element, "BlueTint", tint.b); + util::xml::query_color_attribute(element, "AlphaTint", tint.a); + util::xml::query_color_attribute(element, "RedOffset", colorOffset.r); + util::xml::query_color_attribute(element, "GreenOffset", colorOffset.g); + util::xml::query_color_attribute(element, "BlueOffset", colorOffset.b); + + util::xml::query_bool_attribute(element, "Visible", isVisible); + util::xml::query_bool_attribute(element, "ShowRect", isShowRect); + util::xml::query_bool_attribute(element, "Loop", isLoop); + + if (const char* value = element->Attribute("Origin")) origin = string_enum_get(ORIGIN_STRINGS, value, CUSTOM); + + if (const char* value = element->Attribute("Interpolated")) + { + auto view = std::string_view(value); + if (view == "true" || view == "1") + interpolation = LINEAR; + else if (view == "false" || view == "0") + interpolation = NONE; + else + interpolation = string_enum_get(INTERPOLATION_STRINGS, view, NONE); + } + + if (type == SOUND && parentType == TRIGGER) path.clear(); + } + + void Anm2::Element::attributes_write(XMLElement* element, Type parentType, Flags flags) const + { + if (!element) return; + + switch (type) + { + case INFO: + util::xml::set_string_attribute(element, "CreatedBy", createdBy); + util::xml::set_string_attribute(element, "CreatedOn", createdOn); + util::xml::set_int_attribute(element, "Fps", fps); + util::xml::set_int_attribute(element, "Version", version); + break; + + case SPRITESHEET: + if (id > -1) util::xml::set_int_attribute(element, "Id", id); + util::xml::set_path_attribute(element, "Path", path); + break; + + case REGION: + if (id > -1) util::xml::set_int_attribute(element, "Id", id); + util::xml::set_string_attribute(element, "Name", name); + util::xml::set_float_attribute(element, "XCrop", crop.x); + util::xml::set_float_attribute(element, "YCrop", crop.y); + util::xml::set_float_attribute(element, "Width", size.x); + util::xml::set_float_attribute(element, "Height", size.y); + + if (origin == CUSTOM) + { + util::xml::set_float_attribute(element, "XPivot", pivot.x); + util::xml::set_float_attribute(element, "YPivot", pivot.y); + } + else if (auto value = enum_string_get(ORIGIN_STRINGS, origin); !value.empty()) + element->SetAttribute("Origin", value.data()); + break; + + case LAYER: + if (id > -1) util::xml::set_int_attribute(element, "Id", id); + util::xml::set_string_attribute(element, "Name", name); + if (spritesheetId > -1) util::xml::set_int_attribute(element, "SpritesheetId", spritesheetId); + break; + + case NULL_ELEMENT: + if (id > -1) util::xml::set_int_attribute(element, "Id", id); + util::xml::set_string_attribute(element, "Name", name); + util::xml::set_bool_attribute(element, "ShowRect", isShowRect); + break; + + case EVENT: + if (id > -1) util::xml::set_int_attribute(element, "Id", id); + util::xml::set_string_attribute(element, "Name", name); + break; + + case SOUND: + if (id > -1) + { + if (parentType == TRIGGER) + util::xml::set_int_attribute(element, "Id", id); + else + { + util::xml::set_int_attribute(element, "Id", id); + util::xml::set_path_attribute(element, "Path", path); + } + } + break; + case ANIMATIONS: + util::xml::set_string_attribute(element, "DefaultAnimation", defaultAnimation); + break; + case ANIMATION: + util::xml::set_string_attribute(element, "Name", name); + util::xml::set_int_attribute(element, "FrameNum", frameNum); + util::xml::set_bool_attribute(element, "Loop", isLoop); + break; + + case LAYER_ANIMATION: + if (layerId > -1) util::xml::set_int_attribute(element, "LayerId", layerId); + util::xml::set_bool_attribute(element, "Visible", isVisible); + break; + + case NULL_ANIMATION: + if (nullId > -1) util::xml::set_int_attribute(element, "NullId", nullId); + util::xml::set_bool_attribute(element, "Visible", isVisible); + break; + + case TRIGGER: + if (eventId > -1) util::xml::set_int_attribute(element, "EventId", eventId); + util::xml::set_int_attribute(element, "AtFrame", atFrame); + break; + + case FRAME: + { + bool isNoRegions = is_flag_set(flags, NO_REGIONS); + bool isFrameNoRegionValues = is_flag_set(flags, FRAME_NO_REGION_VALUES); + bool isRegionValid = parentType == LAYER_ANIMATION && !isNoRegions && regionId != -1; + bool isWriteRegionValues = parentType == LAYER_ANIMATION && (!isFrameNoRegionValues || !isRegionValid); + + if (isRegionValid) util::xml::set_int_attribute(element, "RegionId", regionId); + util::xml::set_float_attribute(element, "XPosition", position.x); + util::xml::set_float_attribute(element, "YPosition", position.y); + + if (isWriteRegionValues) + { + util::xml::set_float_attribute(element, "XPivot", pivot.x); + util::xml::set_float_attribute(element, "YPivot", pivot.y); + util::xml::set_float_attribute(element, "XCrop", crop.x); + util::xml::set_float_attribute(element, "YCrop", crop.y); + util::xml::set_float_attribute(element, "Width", size.x); + util::xml::set_float_attribute(element, "Height", size.y); + } + + util::xml::set_float_attribute(element, "XScale", scale.x); + util::xml::set_float_attribute(element, "YScale", scale.y); + util::xml::set_int_attribute(element, "Delay", delay); + util::xml::set_bool_attribute(element, "Visible", isVisible); + util::xml::set_color_attribute(element, "RedTint", tint.r); + util::xml::set_color_attribute(element, "GreenTint", tint.g); + util::xml::set_color_attribute(element, "BlueTint", tint.b); + util::xml::set_color_attribute(element, "AlphaTint", tint.a); + util::xml::set_color_attribute(element, "RedOffset", colorOffset.r); + util::xml::set_color_attribute(element, "GreenOffset", colorOffset.g); + util::xml::set_color_attribute(element, "BlueOffset", colorOffset.b); + util::xml::set_float_attribute(element, "Rotation", rotation); + + if (is_flag_set(flags, INTERPOLATION_BOOL_ONLY) || interpolation == LINEAR || interpolation == NONE) + element->SetAttribute("Interpolated", interpolation == LINEAR); + else if (auto value = enum_string_get(INTERPOLATION_STRINGS, interpolation); !value.empty()) + element->SetAttribute("Interpolated", value.data()); + break; + } + + case ACTOR: + case CONTENT: + case SPRITESHEETS: + case LAYERS: + case NULLS: + case EVENTS: + case SOUNDS: + case ROOT_ANIMATION: + case LAYER_ANIMATIONS: + case NULL_ANIMATIONS: + case TRIGGERS: + case UNKNOWN: + case TYPE_COUNT: + break; + } + } + + Anm2::Element Anm2::Element::from_xml(const XMLElement* element, Type parentType) + { + Element parsed{}; + if (!element) return parsed; + + parsed.type = type_get(element->Name(), parentType); + if (parsed.type == UNKNOWN) return parsed; + + parsed.attributes_read(element, parentType); + + for (const XMLElement* child = element->FirstChildElement(); child; child = child->NextSiblingElement()) + if (auto parsedChild = from_xml(child, parsed.type); parsedChild.type != UNKNOWN) + parsed.children.push_back(std::move(parsedChild)); + + return parsed; + } + + XMLElement* Anm2::Element::to_xml(XMLDocument& document, Type parentType, Flags flags) const + { + auto tagName = tag_get(type); + if (tagName.empty()) return nullptr; + + auto* element = document.NewElement(tagName.data()); + attributes_write(element, parentType, flags); + + for (const auto& child : children) + { + if (is_flag_set(flags, NO_SOUNDS) && (child.type == SOUNDS || child.type == SOUND)) continue; + if (is_flag_set(flags, NO_REGIONS) && child.type == REGION) continue; + if (auto* childElement = child.to_xml(document, type, flags)) element->InsertEndChild(childElement); + } + + return element; + } + + bool Anm2::Element::serialize(const std::filesystem::path& path, std::string* errorString, + Compatibility compatibility) const + { + XMLDocument document{}; + if (auto* element = to_xml(document, UNKNOWN, Anm2::flags_get(compatibility))) + document.InsertFirstChild(element); + else + { + if (errorString) *errorString = "No supported element to serialize."; + return false; + } + + util::File file(path, "wb"); + if (!file) + { + if (errorString) *errorString = "Could not open file for writing."; + return false; + } + + if (document.SaveFile(file.get()) != XML_SUCCESS) + { + if (errorString) *errorString = document.ErrorStr(); + return false; + } + + return true; + } + + std::string Anm2::Element::to_string(Compatibility compatibility) const + { + XMLDocument document{}; + if (auto* element = to_xml(document, UNKNOWN, Anm2::flags_get(compatibility))) document.InsertFirstChild(element); + return util::xml::document_to_string(document); + } + + Anm2::Element* Anm2::Element::child_get(Type childType, std::size_t index) + { + std::size_t matchIndex{}; + for (auto& child : children) + if (child.type == childType && matchIndex++ == index) return &child; + return nullptr; + } + + const Anm2::Element* Anm2::Element::child_get(Type childType, std::size_t index) const + { + std::size_t matchIndex{}; + for (const auto& child : children) + if (child.type == childType && matchIndex++ == index) return &child; + return nullptr; + } + + std::vector Anm2::Element::children_get(Type childType) + { + std::vector matches{}; + for (auto& child : children) + if (child.type == childType) matches.push_back(&child); + return matches; + } + + std::vector Anm2::Element::children_get(Type childType) const + { + std::vector matches{}; + for (const auto& child : children) + if (child.type == childType) matches.push_back(&child); + return matches; + } + + Anm2::Anm2(const std::filesystem::path& filepath, std::string* errorString) + : path(filepath), root(filepath, errorString) + { + isValid = root.type == Element::ACTOR; + assets_reload(); + } + + bool Anm2::serialize(const std::filesystem::path& filepath, std::string* errorString, + Compatibility compatibility) const + { + return root.serialize(filepath, errorString, compatibility); + } + + std::string Anm2::to_string(Compatibility compatibility) const { return root.to_string(compatibility); } + + void Anm2::assets_reload() + { + spritesheets.clear(); + sounds.clear(); + + auto directory = path.parent_path(); + for (auto* element : elements_get(Element::SPRITESHEET)) + if (element->id > -1 && !element->path.empty()) + spritesheets[element->id] = resource::Texture(asset_path_resolve(directory, element->path)); + + for (auto* element : elements_get(Element::SOUND)) + if (element->id > -1 && !element->path.empty()) + sounds[element->id] = resource::Audio(asset_path_resolve(directory, element->path)); + } + + Anm2::Element* Anm2::element_get(Element::Type type, std::size_t index) + { + auto matches = elements_get(type); + return index < matches.size() ? matches[index] : nullptr; + } + + const Anm2::Element* Anm2::element_get(Element::Type type, std::size_t index) const + { + auto matches = elements_get(type); + return index < matches.size() ? matches[index] : nullptr; + } + + std::vector Anm2::elements_get(Element::Type type) + { + std::vector matches{}; + elements_collect(root, type, matches); + return matches; + } + + std::vector Anm2::elements_get(Element::Type type) const + { + std::vector matches{}; + elements_collect(root, type, matches); + return matches; + } + + void Anm2::elements_collect(Element& element, Element::Type type, std::vector& out) + { + if (element.type == type) out.push_back(&element); + for (auto& child : element.children) + elements_collect(child, type, out); + } + + void Anm2::elements_collect(const Element& element, Element::Type type, std::vector& out) + { + if (element.type == type) out.push_back(&element); + for (const auto& child : element.children) + elements_collect(child, type, out); + } +} diff --git a/src/anm2_new/anm2.hpp b/src/anm2_new/anm2.hpp new file mode 100644 index 0000000..6ac76d0 --- /dev/null +++ b/src/anm2_new/anm2.hpp @@ -0,0 +1,233 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "audio.hpp" +#include "texture.hpp" + +namespace tinyxml2 +{ + class XMLDocument; + class XMLElement; +} + +namespace anm2ed::anm2_new +{ + class Anm2 + { + public: + enum Compatibility + { + ISAAC, + ANM2ED, + ANM2ED_LIMITED, + COMPATIBILITY_COUNT + }; + + enum Flag + { + NO_SOUNDS = 1 << 0, + NO_REGIONS = 1 << 1, + FRAME_NO_REGION_VALUES = 1 << 2, + INTERPOLATION_BOOL_ONLY = 1 << 3 + }; + + using Flags = int; + + inline static constexpr std::array COMPATIBILITY_FLAGS = {{ + NO_SOUNDS | NO_REGIONS | FRAME_NO_REGION_VALUES | INTERPOLATION_BOOL_ONLY, + 0, + FRAME_NO_REGION_VALUES, + }}; + + class Element + { + public: + enum Source + { + STRING + }; + + enum Type + { + UNKNOWN, + ACTOR, + INFO, + CONTENT, + SPRITESHEETS, + SPRITESHEET, + REGION, + LAYERS, + LAYER, + NULLS, + NULL_ELEMENT, + EVENTS, + EVENT, + SOUNDS, + SOUND, + ANIMATIONS, + ANIMATION, + ROOT_ANIMATION, + LAYER_ANIMATIONS, + LAYER_ANIMATION, + NULL_ANIMATIONS, + NULL_ANIMATION, + TRIGGERS, + TRIGGER, + FRAME, + TYPE_COUNT + }; + + enum Origin + { + TOP_LEFT, + ORIGIN_CENTER, + CUSTOM + }; + + enum Interpolation + { + NONE, + LINEAR, + EASE_IN, + EASE_OUT, + EASE_IN_OUT + }; + + inline static constexpr std::array, TYPE_COUNT - 1> TYPE_TAGS = {{ + {ACTOR, "AnimatedActor"}, + {INFO, "Info"}, + {CONTENT, "Content"}, + {SPRITESHEETS, "Spritesheets"}, + {SPRITESHEET, "Spritesheet"}, + {REGION, "Region"}, + {LAYERS, "Layers"}, + {LAYER, "Layer"}, + {NULLS, "Nulls"}, + {NULL_ELEMENT, "Null"}, + {EVENTS, "Events"}, + {EVENT, "Event"}, + {SOUNDS, "Sounds"}, + {SOUND, "Sound"}, + {ANIMATIONS, "Animations"}, + {ANIMATION, "Animation"}, + {ROOT_ANIMATION, "RootAnimation"}, + {LAYER_ANIMATIONS, "LayerAnimations"}, + {LAYER_ANIMATION, "LayerAnimation"}, + {NULL_ANIMATIONS, "NullAnimations"}, + {NULL_ANIMATION, "NullAnimation"}, + {TRIGGERS, "Triggers"}, + {TRIGGER, "Trigger"}, + {FRAME, "Frame"}, + }}; + + inline static constexpr std::array, 2> ORIGIN_STRINGS = {{ + {TOP_LEFT, "TopLeft"}, + {ORIGIN_CENTER, "Center"}, + }}; + + inline static constexpr std::array, 3> INTERPOLATION_STRINGS = {{ + {EASE_IN, "EaseIn"}, + {EASE_OUT, "EaseOut"}, + {EASE_IN_OUT, "EaseInOut"}, + }}; + + Type type{UNKNOWN}; + std::vector children{}; + + std::string createdBy{}; + std::string createdOn{}; + std::string defaultAnimation{}; + std::string name{}; + std::filesystem::path path{}; + + int id{-1}; + int layerId{-1}; + int nullId{-1}; + int spritesheetId{-1}; + int eventId{-1}; + int regionId{-1}; + int atFrame{-1}; + int frameNum{}; + int fps{30}; + int version{}; + int delay{1}; + + glm::vec2 crop{}; + glm::vec2 size{}; + glm::vec2 pivot{}; + glm::vec2 position{}; + glm::vec2 scale{100.0f, 100.0f}; + glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f}; + glm::vec3 colorOffset{}; + float rotation{}; + + bool isVisible{true}; + bool isShowRect{false}; + bool isLoop{false}; + Origin origin{CUSTOM}; + Interpolation interpolation{NONE}; + + Element() = default; + explicit Element(const std::filesystem::path&, std::string* = nullptr); + Element(std::string_view, Source, std::string* = nullptr); + + bool serialize(const std::filesystem::path&, std::string* = nullptr, Compatibility = ANM2ED) const; + std::string to_string(Compatibility = ANM2ED) const; + + Element* child_get(Type, std::size_t index = 0); + const Element* child_get(Type, std::size_t index = 0) const; + std::vector children_get(Type); + std::vector children_get(Type) const; + + static std::string_view tag_get(Type); + static Type type_get(std::string_view, Type = UNKNOWN); + static Element from_xml(const tinyxml2::XMLElement*, Type = UNKNOWN); + static bool document_load(tinyxml2::XMLDocument&, const std::filesystem::path&, std::string*); + static bool document_parse(tinyxml2::XMLDocument&, std::string_view, std::string*); + + tinyxml2::XMLElement* to_xml(tinyxml2::XMLDocument&, Type = UNKNOWN, Flags = 0) const; + + private: + void attributes_read(const tinyxml2::XMLElement*, Type = UNKNOWN); + void attributes_write(tinyxml2::XMLElement*, Type = UNKNOWN, Flags = 0) const; + }; + + bool isValid{true}; + std::filesystem::path path{}; + Element root{}; + std::unordered_map spritesheets{}; + std::unordered_map sounds{}; + + Anm2() = default; + explicit Anm2(const std::filesystem::path&, std::string* = nullptr); + + bool serialize(const std::filesystem::path&, std::string* = nullptr, Compatibility = ANM2ED) const; + std::string to_string(Compatibility = ANM2ED) const; + void assets_reload(); + + static constexpr Flags flags_get(Compatibility compatibility) + { + return COMPATIBILITY_FLAGS[static_cast(compatibility)]; + } + + Element* element_get(Element::Type, std::size_t index = 0); + const Element* element_get(Element::Type, std::size_t index = 0) const; + std::vector elements_get(Element::Type); + std::vector elements_get(Element::Type) const; + + private: + static void elements_collect(Element&, Element::Type, std::vector&); + static void elements_collect(const Element&, Element::Type, std::vector&); + }; +} diff --git a/src/util/xml_.cpp b/src/util/xml_.cpp index 24d709e..4c6f4b1 100644 --- a/src/util/xml_.cpp +++ b/src/util/xml_.cpp @@ -15,16 +15,93 @@ namespace anm2ed::util::xml return std::string(printer.CStr()); } + bool query_string_attribute(const XMLElement* element, const char* attribute, std::string& out) + { + if (!element || !attribute) return false; + + const char* value = nullptr; + if (element->QueryStringAttribute(attribute, &value) != XML_SUCCESS || !value) return false; + out = value; + return true; + } + + bool query_path_attribute(const XMLElement* element, const char* attribute, std::filesystem::path& out) + { + std::string temp{}; + if (!query_string_attribute(element, attribute, temp)) return false; + out = path::from_utf8(temp); + return true; + } + + bool query_int_attribute(const XMLElement* element, const char* attribute, int& out) + { + return element && attribute && element->QueryIntAttribute(attribute, &out) == XML_SUCCESS; + } + + bool query_float_attribute(const XMLElement* element, const char* attribute, float& out) + { + return element && attribute && element->QueryFloatAttribute(attribute, &out) == XML_SUCCESS; + } + + bool query_bool_attribute(const XMLElement* element, const char* attribute, bool& out) + { + return element && attribute && element->QueryBoolAttribute(attribute, &out) == XML_SUCCESS; + } + + bool query_color_attribute(const XMLElement* element, const char* attribute, float& out) + { + int value{}; + if (!query_int_attribute(element, attribute, value)) return false; + out = math::uint8_to_float(value); + return true; + } + + void set_string_attribute(XMLElement* element, const char* attribute, const std::string& value) + { + if (element && attribute && !value.empty()) element->SetAttribute(attribute, value.c_str()); + } + + void set_path_attribute(XMLElement* element, const char* attribute, const std::filesystem::path& value) + { + if (!element || !attribute || value.empty()) return; + auto utf8 = path::to_utf8(value); + element->SetAttribute(attribute, utf8.c_str()); + } + + void set_int_attribute(XMLElement* element, const char* attribute, int value) + { + if (element && attribute) element->SetAttribute(attribute, value); + } + + void set_float_attribute(XMLElement* element, const char* attribute, float value) + { + if (element && attribute) element->SetAttribute(attribute, value); + } + + void set_bool_attribute(XMLElement* element, const char* attribute, bool value) + { + if (element && attribute) element->SetAttribute(attribute, value); + } + + void set_color_attribute(XMLElement* element, const char* attribute, float value) + { + if (element && attribute) element->SetAttribute(attribute, math::float_to_uint8(value)); + } + XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* out) { - const char* temp = nullptr; - auto result = element->QueryStringAttribute(attribute, &temp); - if (result == XML_SUCCESS && temp) *out = temp; + if (!element || !attribute || !out) return XML_NO_ATTRIBUTE; + + const char* value = nullptr; + auto result = element->QueryStringAttribute(attribute, &value); + if (result == XML_SUCCESS && value) *out = value; return result; } XMLError query_path_attribute(XMLElement* element, const char* attribute, std::filesystem::path* out) { + if (!out) return XML_NO_ATTRIBUTE; + std::string temp{}; auto result = query_string_attribute(element, attribute, &temp); if (result == XML_SUCCESS) *out = path::from_utf8(temp); @@ -33,8 +110,6 @@ namespace anm2ed::util::xml void query_color_attribute(XMLElement* element, const char* attribute, float& out) { - int value{}; - element->QueryIntAttribute(attribute, &value); - out = math::uint8_to_float(value); + query_color_attribute(static_cast(element), attribute, out); } } diff --git a/src/util/xml_.hpp b/src/util/xml_.hpp index 0a4cf2b..b4ec4b6 100644 --- a/src/util/xml_.hpp +++ b/src/util/xml_.hpp @@ -8,6 +8,21 @@ namespace anm2ed::util::xml { std::string document_to_string(tinyxml2::XMLDocument&); + + bool query_string_attribute(const tinyxml2::XMLElement*, const char*, std::string&); + bool query_path_attribute(const tinyxml2::XMLElement*, const char*, std::filesystem::path&); + bool query_int_attribute(const tinyxml2::XMLElement*, const char*, int&); + bool query_float_attribute(const tinyxml2::XMLElement*, const char*, float&); + bool query_bool_attribute(const tinyxml2::XMLElement*, const char*, bool&); + bool query_color_attribute(const tinyxml2::XMLElement*, const char*, float&); + + void set_string_attribute(tinyxml2::XMLElement*, const char*, const std::string&); + void set_path_attribute(tinyxml2::XMLElement*, const char*, const std::filesystem::path&); + void set_int_attribute(tinyxml2::XMLElement*, const char*, int); + void set_float_attribute(tinyxml2::XMLElement*, const char*, float); + void set_bool_attribute(tinyxml2::XMLElement*, const char*, bool); + void set_color_attribute(tinyxml2::XMLElement*, const char*, float); + tinyxml2::XMLError query_string_attribute(tinyxml2::XMLElement*, const char*, std::string*); tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement*, const char*, std::filesystem::path*); void query_color_attribute(tinyxml2::XMLElement*, const char*, float&);