12 #include <unordered_map>
16 #include "pugixml.hpp"
23 static Config& getInstance(
const std::string& xmlFile =
"") {
24 static Config instance(xmlFile);
28 std::string getXMLpath()
const {
34 rebuildExpandedDocument();
40 T getValue(
const std::string& key)
const {
41 pugi::xml_node node = resolveKeyPath(key);
43 std::string parentKey = key.substr(0, key.find_last_of(
'.'));
44 std::string finalKey = key.substr(key.find_last_of(
'.') + 1);
45 pugi::xml_node parent = resolveKeyPath(parentKey);
47 node = parent.child(finalKey.c_str());
51 throw std::runtime_error(
"Key not found: " + key);
53 std::istringstream iss(node.text().get());
57 throw std::runtime_error(
"Invalid value or type for key: " + key);
64 void setValue(
const std::string& key,
const T& value) {
65 std::ostringstream oss;
67 std::string strValue = oss.str();
70 auto fileAndLocal = getFileAndLocalKeyForFullKey(key);
72 std::cerr <<
"Patching file: " << fileAndLocal.first <<
" with local key: " << fileAndLocal.second <<
"\n";
75 pugi::xml_document targetDoc;
76 if (!targetDoc.load_file(fileAndLocal.first.c_str()))
77 throw std::runtime_error(
"Failed to load: " + fileAndLocal.first);
80 pugi::xml_node targetNode = targetDoc.document_element();
81 std::istringstream ss(fileAndLocal.second);
83 while (std::getline(ss, token,
'.')) {
84 if (token == targetNode.name())
continue;
85 targetNode = targetNode.child(token.c_str());
87 throw std::runtime_error(
"Node not found in file: " + token);
91 targetNode.text().set(strValue.c_str());
94 if (!targetDoc.save_file(fileAndLocal.first.c_str()))
95 throw std::runtime_error(
"Failed to save: " + fileAndLocal.first);
98 rebuildExpandedDocument();
102 void printAllKeys()
const {
103 printKeysRecursive(doc.document_element(),
"");
107 std::vector<std::pair<std::string, std::string>> extractKeysAndValues(
const std::string& key)
const {
108 std::vector<std::pair<std::string, std::string>> result;
109 pugi::xml_node node = resolveKeyPath(key);
110 if (!node)
throw std::runtime_error(
"Key not found: " + key);
112 for (pugi::xml_node child : node.children()) {
113 std::string name = child.name();
114 if (name.empty())
continue;
115 result.emplace_back(name, child.text().as_string());
126 Config(
const std::string& xmlFile) : xml_path(xmlFile) {
127 pugi::xml_parse_result result = doc.load_file(xml_path.c_str());
128 if (!result)
throw std::runtime_error(
"Failed to load XML file: " + std::string(result.description()));
131 rebuildExpandedDocument();
135 pugi::xml_node resolveKeyPath(
const std::string& key)
const {
136 std::string adjustedKey = key;
138 std::string rootName = doc.document_element().name();
139 if (adjustedKey.find(rootName +
".") == 0) {
140 adjustedKey = adjustedKey.substr(rootName.length() + 1);
142 std::istringstream ss(adjustedKey);
144 pugi::xml_node node = doc.document_element();
145 while (std::getline(ss, token,
'.')) {
146 node = node.child(token.c_str());
153 void printKeysRecursive(pugi::xml_node node,
const std::string& prefix)
const {
154 for (pugi::xml_node child : node.children()) {
155 std::string fullKey = prefix.empty() ? child.name() : prefix +
"." + child.name();
156 std::string value = child.text().as_string();
157 std::cout << fullKey <<
": " << value << std::endl;
158 printKeysRecursive(child, fullKey);
163 void rebuildExpandedDocument() {
164 include_key_map.clear();
165 base_dir = std::filesystem::path(xml_path).parent_path();
168 pugi::xml_parse_result result = doc.load_file(xml_path.c_str());
169 if (!result)
throw std::runtime_error(
"Failed to load XML file: " + std::string(result.description()));
172 expandIncludesRecursive(doc.document_element());
175 registerKeysForSubtree(doc.document_element(), doc.document_element().name(), doc.document_element().name(), xml_path,
false);
180 void expandIncludesRecursive(pugi::xml_node node) {
181 for (pugi::xml_node child = node.first_child(); child; ) {
182 pugi::xml_node next = child.next_sibling();
185 if (child.type() == pugi::node_element && std::string(child.name()) !=
"include") {
186 expandIncludesRecursive(child);
190 if (child.type() == pugi::node_element && std::string(child.name()) ==
"include") {
191 const char* fileAttr = child.attribute(
"file").value();
192 if (!fileAttr || std::string(fileAttr).empty()) {
193 throw std::runtime_error(
"Include tag missing 'file' attribute.");
196 std::filesystem::path includePath = base_dir / std::filesystem::path(fileAttr);
198 pugi::xml_document subdoc;
199 pugi::xml_parse_result subres = subdoc.load_file(includePath.string().c_str());
201 throw std::runtime_error(
"Failed to load included XML file: " + includePath.string() +
" (" + std::string(subres.description()) +
")");
204 pugi::xml_node subroot = subdoc.document_element();
206 throw std::runtime_error(
"Included XML file has no document element: " + includePath.string());
210 pugi::xml_node inserted = node.insert_copy_before(subroot, child);
213 std::string parentFull = computeNodeFullKey(node);
214 std::string insertedFull = parentFull.empty() ? inserted.name() : parentFull +
"." + inserted.name();
215 std::string insertedLocal = inserted.name();
217 registerKeysForSubtree(inserted, insertedFull, insertedLocal, includePath.string(),
true);
221 node.remove_child(child);
229 std::string computeNodeFullKey(pugi::xml_node node)
const {
230 if (!node)
return "";
231 std::vector<std::string> parts;
232 pugi::xml_node cur = node;
233 while (cur && cur.type() == pugi::node_element) {
234 parts.push_back(cur.name());
235 if (cur == doc.document_element())
break;
238 std::ostringstream oss;
239 for (
auto it = parts.rbegin(); it != parts.rend(); ++it) {
240 if (it != parts.rbegin()) oss <<
".";
247 void registerKeysForSubtree(pugi::xml_node node,
const std::string& fullPrefix,
const std::string& localPrefix,
248 const std::string& sourceFile,
const bool overwriteExisting =
true) {
252 if (overwriteExisting) {
253 include_key_map[fullPrefix] = std::make_pair(sourceFile, localPrefix);
255 include_key_map.emplace(fullPrefix, std::make_pair(sourceFile, localPrefix));
259 for (pugi::xml_node child : node.children()) {
260 if (child.type() != pugi::node_element)
continue;
261 std::string childName = child.name();
262 std::string childFull = fullPrefix.empty() ? childName : fullPrefix +
"." + childName;
263 std::string childLocal = localPrefix.empty() ? childName : localPrefix +
"." + childName;
264 registerKeysForSubtree(child, childFull, childLocal, sourceFile, overwriteExisting);
270 std::pair<std::string, std::string> getFileAndLocalKeyForFullKey(
const std::string& fullKey)
const {
272 std::string rootName = doc.document_element().name();
273 std::string normalized = fullKey;
274 if (normalized.find(rootName +
".") != 0 && normalized != rootName) {
276 normalized = rootName +
"." + normalized;
280 auto it = include_key_map.find(normalized);
281 if (it != include_key_map.end()) {
287 return std::make_pair(xml_path, fullKey);
295 std::string xml_path;
297 pugi::xml_document doc;
300 std::filesystem::path base_dir;
303 std::unordered_map<std::string, std::pair<std::string, std::string>> include_key_map;