lxgui
Loading...
Searching...
No Matches
gui_addon_registry_parser.cpp
1#include "lxgui/gui_addon_registry.hpp"
2#include "lxgui/gui_event_emitter.hpp"
3#include "lxgui/gui_frame.hpp"
4#include "lxgui/gui_layout_node.hpp"
5#include "lxgui/gui_out.hpp"
6#include "lxgui/gui_parser_common.hpp"
7#include "lxgui/gui_root.hpp"
8#include "lxgui/gui_virtual_root.hpp"
9#include "lxgui/utils_file_system.hpp"
10#include "lxgui/utils_string.hpp"
11
12#include <lxgui/extern_sol2_state.hpp>
13#if defined(LXGUI_ENABLE_XML_PARSER)
14# include <lxgui/extern_pugixml.hpp>
15#endif
16#if defined(LXGUI_ENABLE_YAML_PARSER)
17# include <lxgui/extern_ryml.hpp>
18#endif
19
20#include <fstream>
21
22namespace lxgui::gui {
23
24class file_line_mappings {
25public:
26 explicit file_line_mappings(const std::string& file_name) : file_name_(file_name) {
27 std::ifstream stream(file_name);
28 if (!stream.is_open())
29 return;
30
31 std::string line;
32 std::size_t prev_pos = 0u;
33 while (std::getline(stream, line)) {
34 file_content_ += '\n' + line;
35 line_offsets_.push_back(prev_pos);
36 prev_pos += line.size() + 1u;
37 }
38
39 line_offsets_.push_back(prev_pos);
40
41 is_open_ = true;
42 }
43
44 bool is_open() const {
45 return is_open_;
46 }
47
48 const std::string& get_content() const {
49 return file_content_;
50 }
51
52 std::pair<std::size_t, std::size_t> get_line_info(std::size_t offset) const {
53 auto iter = std::lower_bound(line_offsets_.begin(), line_offsets_.end(), offset);
54 if (iter == line_offsets_.end())
55 return std::make_pair(0, 0);
56
57 std::size_t line_nbr = iter - line_offsets_.begin();
58 std::size_t char_offset = offset - *iter + 1u;
59
60 return std::make_pair(line_nbr, char_offset);
61 }
62
63 std::string get_location(std::size_t offset) const {
64 auto location = get_line_info(offset);
65 if (location.first == 0)
66 return file_name_ + ":?";
67 else
68 return file_name_ + ":" + utils::to_string(location.first);
69 }
70
71private:
72 bool is_open_ = false;
73 std::string file_name_;
74 std::string file_content_;
75 std::vector<std::size_t> line_offsets_;
76};
77
78std::string normalize_node_name(const std::string& name, bool capital_first) {
79 std::string normalized;
80 bool next_capitalize = capital_first;
81 for (auto c : name) {
82 if (next_capitalize)
83 c = std::toupper(c);
84
85 next_capitalize = c == '_';
86 if (next_capitalize)
87 continue;
88
89 normalized.push_back(c);
90 }
91
92 return normalized;
93}
94
95#if defined(LXGUI_ENABLE_XML_PARSER)
96void set_node(const file_line_mappings& file, layout_node& node, const pugi::xml_node& xml_node) {
97 auto location = file.get_location(xml_node.offset_debug());
98 node.set_location(location);
99 node.set_value_location(location);
100 node.set_name(normalize_node_name(xml_node.name(), true));
101
102 for (const auto& attr : xml_node.attributes()) {
103 std::string name = normalize_node_name(attr.name(), false);
104 if (const auto* node_attr = node.try_get_attribute(name)) {
105 gui::out << gui::warning << location << ": attribute '" << name
106 << "' duplicated; only first value will be used." << std::endl;
107 node_attr->mark_as_not_accessed();
108 continue;
109 }
110
111 auto& attrib = node.add_attribute();
112 attrib.set_location(location);
113 attrib.set_value_location(location);
114 attrib.set_name(std::move(name));
115 attrib.set_value(attr.value());
116 }
117
118 std::string value;
119 for (const auto& elem_node : xml_node.children()) {
120 if (elem_node.type() == pugi::node_pcdata || elem_node.type() == pugi::node_cdata) {
121 value += elem_node.value();
122 } else {
123 auto& child = node.add_child();
124 set_node(file, child, elem_node);
125 }
126 }
127
128 node.set_value(value);
129}
130#endif
131
132#if defined(LXGUI_ENABLE_YAML_PARSER)
133std::string to_string(const c4::csubstr& c_string) {
134 return std::string(c_string.data(), c_string.size());
135}
136
137void set_node(
138 const file_line_mappings& file,
139 const ryml::Tree& tree,
140 layout_node& node,
141 const ryml::ConstNodeRef& yaml_node) {
142 std::string location;
143 if (yaml_node.has_key())
144 location = file.get_location(yaml_node.key().data() - tree.arena().data());
145 else if (yaml_node.has_val())
146 location = file.get_location(yaml_node.val().data() - tree.arena().data());
147 node.set_location(location);
148 node.set_value_location(location);
149
150 if (yaml_node.has_key())
151 node.set_name(normalize_node_name(to_string(yaml_node.key()), true));
152
153 for (auto elem_node : yaml_node.children()) {
154 switch (elem_node.type()) {
155 case ryml::KEYVAL: {
156 std::string name = normalize_node_name(to_string(elem_node.key()), false);
157 std::string attr_location =
158 file.get_location(elem_node.key().data() - tree.arena().data());
159 if (const auto* node_attr = node.try_get_attribute(name)) {
160 gui::out << gui::warning << attr_location << ": attribute '" << name
161 << "' duplicated; only first value will be used." << std::endl;
162 gui::out << gui::warning << std::string(attr_location.size(), ' ')
163 << " first occurence at: '" << std::endl;
164 gui::out << gui::warning << std::string(attr_location.size(), ' ') << " "
165 << node_attr->get_location() << std::endl;
166 node_attr->mark_as_not_accessed();
167 continue;
168 }
169
170 auto& attrib = node.add_attribute();
171 attrib.set_location(std::move(attr_location));
172 attrib.set_value_location(
173 file.get_location(elem_node.val().data() - tree.arena().data()));
174 attrib.set_name(std::move(name));
175 attrib.set_value(to_string(elem_node.val()));
176 break;
177 }
178 case ryml::KEYMAP: [[fallthrough]];
179 case ryml::MAP: [[fallthrough]];
180 case ryml::KEYSEQ: {
181 auto& child = node.add_child();
182 set_node(file, tree, child, elem_node);
183 break;
184 }
185 default: {
186 gui::out << gui::warning << location << ": unsupported YAML node type: '"
187 << elem_node.type_str() << "'." << std::endl;
188 break;
189 }
190 }
191 }
192}
193#endif
194
195void addon_registry::parse_layout_file_(const std::string& file_name, const addon& add_on) {
196 file_line_mappings file(file_name);
197 if (!file.is_open()) {
198 gui::out << gui::error << file_name << ": could not open file for parsing." << std::endl;
199 return;
200 }
201
202 layout_node root;
203 bool parsed = false;
204
205 const std::string extension = utils::get_file_extension(file_name);
206
207#if defined(LXGUI_ENABLE_XML_PARSER)
208 if (extension == ".xml") {
209 const unsigned int options = pugi::parse_ws_pcdata_single;
210
211 pugi::xml_document doc;
212 pugi::xml_parse_result result =
213 doc.load_buffer(file.get_content().c_str(), file.get_content().size(), options);
214
215 if (!result) {
216 gui::out << gui::error << file.get_location(result.offset) << ": "
217 << result.description() << std::endl;
218 return;
219 }
220
221 set_node(file, root, doc.first_child());
222 parsed = true;
223 }
224#endif
225
226#if defined(LXGUI_ENABLE_YAML_PARSER)
227 if (extension == ".yml" || extension == ".yaml") {
228 ryml::Tree tree;
229 ryml::parse_in_arena(ryml::to_csubstr(file.get_content()), &tree);
230 set_node(file, tree, root, tree.rootref().first_child());
231 parsed = true;
232 }
233#endif
234
235 if (!parsed) {
236 gui::out << gui::error << file_name
237 << ": no parser registered for extension '" + extension + "'." << std::endl;
238 return;
239 }
240
241 for (const auto& node : root.get_children()) {
242 if (node.get_name() == "Script") {
243 std::string script_file =
244 add_on.directory + "/" + node.get_attribute_value<std::string>("file");
245
246 auto result = lua_.do_file(script_file);
247 if (!result.valid()) {
248 std::string err = result.get<sol::error>().what();
249 gui::out << gui::error << err << std::endl;
250 event_emitter_.fire_event("LUA_ERROR", {err});
251 }
252 } else if (node.get_name() == "Include") {
253 parse_layout_file_(
254 add_on.directory + "/" + node.get_attribute_value<std::string>("file"), add_on);
255 } else {
256 try {
257 auto attr = frame_core_attributes{parse_core_attributes(
258 root_.get_registry(), virtual_root_.get_registry(), node, nullptr)};
259
260 utils::observer_ptr<frame> obj;
261 auto parent = attr.parent; // copy here to prevent use-after-move
262 if (parent) {
263 obj = parent->create_child(std::move(attr));
264 } else {
265 if (attr.is_virtual) {
266 obj = virtual_root_.create_root_frame(std::move(attr));
267 } else {
268 obj = root_.create_root_frame(std::move(attr));
269 }
270 }
271
272 if (!obj)
273 continue;
274
275 obj->set_addon(&add_on);
276 obj->parse_layout(node);
277 obj->notify_loaded();
278 } catch (const utils::exception& e) {
279 gui::out << gui::error << e.get_description() << std::endl;
280 }
281 }
282 }
283
285}
286
287} // namespace lxgui::gui
void fire_event(const std::string &event_name, event_data data=event_data{})
Emmit a new event.
utils::observer_ptr< frame > create_root_frame(frame_core_attributes attr)
Creates a new frame, ready for use, and owned by this frame_container.
registry & get_registry()
Returns the UI object registry, which keeps track of all objects in the UI.
Definition gui_root.hpp:261
virtual_registry & get_registry()
Returns the UI object registry, which keeps track of all objects in the UI.
std::ostream out
region_core_attributes parse_core_attributes(registry &reg, virtual_registry &vreg, const layout_node &node, utils::observer_ptr< frame > parent)
Parse "core" attributes from a layout node, before creating a frame.
std::string normalize_node_name(const std::string &name, bool capital_first)
const std::string warning
Definition gui_out.cpp:6
const std::string error
Definition gui_out.cpp:7
void warn_for_not_accessed_node(const layout_node &node)
Emit a warning if this node (or any of its attributes/children) was not read.
range_impl::value_range< T > value(T &container)
Expose the value rather than the (key,value) pair.
std::string get_file_extension(const std::string &file)