lxgui
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 
22 namespace lxgui::gui {
23 
24 class file_line_mappings {
25 public:
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 
71 private:
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 
78 std::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)
96 void 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)
133 std::string to_string(const c4::csubstr& c_string) {
134  return std::string(c_string.data(), c_string.size());
135 }
136 
137 void set_node(
138  const file_line_mappings& file,
139  const ryml::Tree& tree,
140  layout_node& node,
141  const ryml::NodeRef& 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 
195 void 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 = ryml::parse(ryml::to_csubstr(file.get_content()));
229  set_node(file, tree, root, tree.rootref().first_child());
230  parsed = true;
231  }
232 #endif
233 
234  if (!parsed) {
235  gui::out << gui::error << file_name
236  << ": no parser registered for extension '" + extension + "'." << std::endl;
237  return;
238  }
239 
240  for (const auto& node : root.get_children()) {
241  if (node.get_name() == "Script") {
242  std::string script_file =
243  add_on.directory + "/" + node.get_attribute_value<std::string>("file");
244 
245  auto result = lua_.do_file(script_file);
246  if (!result.valid()) {
247  std::string err = result.get<sol::error>().what();
248  gui::out << gui::error << err << std::endl;
249  event_emitter_.fire_event("LUA_ERROR", {err});
250  }
251  } else if (node.get_name() == "Include") {
252  parse_layout_file_(
253  add_on.directory + "/" + node.get_attribute_value<std::string>("file"), add_on);
254  } else {
255  try {
256  auto attr = frame_core_attributes{parse_core_attributes(
257  root_.get_registry(), virtual_root_.get_registry(), node, nullptr)};
258 
259  utils::observer_ptr<frame> obj;
260  auto parent = attr.parent; // copy here to prevent use-after-move
261  if (parent) {
262  obj = parent->create_child(std::move(attr));
263  } else {
264  if (attr.is_virtual) {
265  obj = virtual_root_.create_root_frame(std::move(attr));
266  } else {
267  obj = root_.create_root_frame(std::move(attr));
268  }
269  }
270 
271  if (!obj)
272  continue;
273 
274  obj->set_addon(&add_on);
275  obj->parse_layout(node);
276  obj->notify_loaded();
277  } catch (const utils::exception& e) {
278  gui::out << gui::error << e.get_description() << std::endl;
279  }
280  }
281  }
282 
284 }
285 
286 } // 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)