C++ RapidXml - 使用 first_node() 遍历以修改 XML 文件中节点的值
C++ RapidXml - Traversing with first_node() to modify the value of a node in an XML file
我一定要疯了...这是我的 XML 文件,名为 Original.xml
:
<root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>
我正在尝试修改 metadata
标签中的内容。
这是我拥有的全部代码 WORKS:
#include <iostream>
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
int main()
{
// Open 'Original.xml' to read from
rapidxml::file<> xmlFile("Original.xml");
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile.data());
// Get to <metadata> tag
// <root> <metadata> ???
rapidxml::xml_node<>* metadataNode = doc.first_node()->first_node()->first_node();
// Always correctly prints: 'Trying to change this'
std::cout << "Before: " << metadataNode->value() << std::endl;
// Modify the contents within <metadata>
const std::string newMetadataValue = "Did the changing";
metadataNode->value(newMetadataValue.c_str());
// Always correctly prints: 'Did the changing'
std::cout << "After: " << metadataNode->value() << std::endl;
// Save output to 'New.xml'
std::ofstream newXmlFile("New.xml");
newXmlFile << doc;
newXmlFile.close();
doc.clear();
return 0;
}
New.xml
现在看起来像这样:
<root>
<metadata>Did the changing</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>
这就是我想要的行为。
我不明白的是为什么我需要third first_node()
调用SAVE里面的信息metadata
.
如果我删除上面的 ???
标记的 third first_node()
调用,New.xml
将保留旧的 <metadata>
字符串:“正在尝试更改此设置”。
然而,在这种情况下,std::cout
对 metadataNode->value()
的两次调用仍将正确打印预期的字符串;意思是,第一个将打印 "Trying to change this",第二个将 correctly 打印 "Did the changing"。 =64=].
为什么我需要使用 n+1
调用 first_node()
来 SAVE 所需节点的新值 n
是遍历(从根)到达所需节点的节点数?为什么如果我有 n
first_node()
调用,我只能在 RAM 中成功修改所需节点的值?
可能的错误?在谁的一边?
在XML树模型中,文本元素也是节点。当您有混合内容元素时,这是有意义的:<a>some<b/>text<c/>nodes</a>
.
基本上:
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
int main() {
rapidxml::file<> xmlFile("Original.xml");
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile.data());
auto root = doc.first_node();
auto metadata = root->first_node();
auto text_node = metadata->first_node();
text_node->value("Did the changing");
std::ofstream newXmlFile("New.xml");
newXmlFile << doc;
}
等等,还有更多
遗憾的是,除非您的输入具有完全预期的属性,否则这是一个问题。
假设这个 ok 样本:
char sample1[] = R"(<root><metadata>Trying to change this</metadata></root>)";
如果元数据元素为空,您会崩溃:
char sample2[] = R"(<root><metadata></metadata></root>)";
char sample3[] = R"(<root><metadata/></root>)";
确实这会触发 ASAN 故障:
/home/sehe/Projects/Whosebug/test.cpp:17:25: runtime error: member access within null pointer of type 'struct xml_node'
/home/sehe/Projects/Whosebug/test.cpp:17:25: runtime error: member call on null pointer of type 'struct xml_base'
/usr/include/rapidxml/rapidxml.hpp:762:24: runtime error: member call on null pointer of type 'struct xml_base'
/usr/include/rapidxml/rapidxml.hpp:753:21: runtime error: member access within null pointer of type 'struct xml_base'
AddressSanitizer:DEADLYSIGNAL
如果有惊喜,它会....做令人惊讶的事情!
char sample4[] = R"(<root><metadata><surprise/></metadata></root>)";
最终错误地生成:
<root>
<metadata>
<surprise>changed</surprise>
</metadata>
</root>
这还没有结束:
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
#include <iostream>
namespace {
char sample1[] = R"(<root><metadata>Trying to change this</metadata></root>)";
char sample2[] = R"(<root><metadata><surprise/></metadata></root>)";
char sample3[] = R"(<root><metadata>mixed<surprise/>bag</metadata></root>)";
char sample4[] = R"(<root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root>)";
char sample5[] = R"(<root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root>)";
//These crash:
//char sampleX[] = R"(<root><metadata></metadata></root>)";
//char sampleY[] = R"(<root><metadata/></root>)";
}
int main() {
for (char* xml : {sample1, sample2, sample3, sample4, sample5}) {
std::cout << "\n=== " << xml << " ===\n";
rapidxml::xml_document<> doc;
doc.parse<0>(xml);
auto root = doc.first_node();
auto metadata = root->first_node();
auto text_node = metadata->first_node();
text_node->value("changed");
print(std::cout << " --> ", doc, rapidxml::print_no_indenting);
std::cout << "\n";
}
}
版画
=== <root><metadata>Trying to change this</metadata></root> ===
--> <root><metadata>changed</metadata></root>
=== <root><metadata><surprise/></metadata></root> ===
--> <root><metadata><surprise>changed</surprise></metadata></root>
=== <root><metadata>mixed<surprise/>bag</metadata></root> ===
--> <root><metadata>changed<surprise/>bag</metadata></root>
=== <root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root> ===
--> <root><metadata><![CDATA[changed]]></metadata></root>
=== <root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root> ===
--> <root><metadata>changed</metadata></root>
如何让它稳健?
首先,使用查询来找到您的目标。遗憾的是 rapidxml 不支持这个;参见 What XML parser should I use in C++?
其次,编辑前检查节点类型
第三,如果可以的话,替换整个节点,这样你就可以独立于以前的节点
最后,请务必从文档中实际分配您的新节点,以免出现生命周期问题。
auto root = doc.first_node();
if (auto* old_meta = root->first_node()) {
assert(old_meta->name() == std::string("metadata"));
print(std::cout << "Removing metadata node: ", *old_meta, fmt);
std::cout << "\n";
root->remove_first_node();
}
auto newmeta = doc.allocate_node(rapidxml::node_element, "metadata", "changed");
root->prepend_node(newmeta);
综合起来:
#include <rapidxml/rapidxml.hpp>
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
#include <iostream>
namespace {
std::string cases[] = {
R"(<root><metadata>Trying to change this</metadata></root>)",
R"(<root><metadata><surprise/></metadata></root>)",
R"(<root><metadata>mixed<surprise/>bag</metadata></root>)",
R"(<root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root>)",
R"(<root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root>)",
R"(<root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>)",
//These no longer crash:
R"(<root><metadata></metadata></root>)",
R"(<root><metadata/></root>)",
// more edge-cases in the predecessor chain
R"(<root></root>)",
R"(<root><no-metadata/></root>)",
R"(<bogus/>)",
};
}
int main() {
auto const fmt = rapidxml::print_no_indenting;
for (auto& xml : cases) {
std::cout << "Input: " << xml << "\n";
rapidxml::xml_document<> doc;
doc.parse<0>(xml.data());
if (auto root = doc.first_node()) {
if (root->name() == std::string("root")) {
if (auto* old_meta = root->first_node()) {
if (old_meta->name() == std::string("metadata")) {
root->remove_first_node();
} else {
std::cout << "WARNING: Not removing '" << old_meta->name() << "' element where 'metadata' expected\n";
}
}
auto newmeta = doc.allocate_node(rapidxml::node_element, "metadata", "changed");
root->prepend_node(newmeta);
} else {
std::cout << "WARNING: '" << root->name() << "' found where 'root' expected\n";
}
}
print(std::cout << "Output: ", doc, fmt);
std::cout << "\n--\n";
}
}
版画
Input: <root><metadata>Trying to change this</metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><surprise/></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata>mixed<surprise/>bag</metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root> ===
Output: <root><metadata>changed</metadata><body><salad>Greek Caesar</salad></body></root>
--
Input: <root><metadata></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata/></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><no-metadata/></root> ===
WARNING: Not removing 'no-metadata' element where 'metadata' expected
Output: <root><metadata>changed</metadata><no-metadata/></root>
--
Input: <bogus/> ===
WARNING: 'bogus' found where 'root' expected
Output: <bogus/>
--
摘要
XML 是可扩展的。这是标记。是语言。不简单:)
我一定要疯了...这是我的 XML 文件,名为 Original.xml
:
<root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>
我正在尝试修改 metadata
标签中的内容。
这是我拥有的全部代码 WORKS:
#include <iostream>
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
int main()
{
// Open 'Original.xml' to read from
rapidxml::file<> xmlFile("Original.xml");
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile.data());
// Get to <metadata> tag
// <root> <metadata> ???
rapidxml::xml_node<>* metadataNode = doc.first_node()->first_node()->first_node();
// Always correctly prints: 'Trying to change this'
std::cout << "Before: " << metadataNode->value() << std::endl;
// Modify the contents within <metadata>
const std::string newMetadataValue = "Did the changing";
metadataNode->value(newMetadataValue.c_str());
// Always correctly prints: 'Did the changing'
std::cout << "After: " << metadataNode->value() << std::endl;
// Save output to 'New.xml'
std::ofstream newXmlFile("New.xml");
newXmlFile << doc;
newXmlFile.close();
doc.clear();
return 0;
}
New.xml
现在看起来像这样:
<root>
<metadata>Did the changing</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>
这就是我想要的行为。
我不明白的是为什么我需要third first_node()
调用SAVE里面的信息metadata
.
如果我删除上面的 ???
标记的 third first_node()
调用,New.xml
将保留旧的 <metadata>
字符串:“正在尝试更改此设置”。
然而,在这种情况下,std::cout
对 metadataNode->value()
的两次调用仍将正确打印预期的字符串;意思是,第一个将打印 "Trying to change this",第二个将 correctly 打印 "Did the changing"。 =64=].
为什么我需要使用 n+1
调用 first_node()
来 SAVE 所需节点的新值 n
是遍历(从根)到达所需节点的节点数?为什么如果我有 n
first_node()
调用,我只能在 RAM 中成功修改所需节点的值?
可能的错误?在谁的一边?
在XML树模型中,文本元素也是节点。当您有混合内容元素时,这是有意义的:<a>some<b/>text<c/>nodes</a>
.
基本上:
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
int main() {
rapidxml::file<> xmlFile("Original.xml");
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile.data());
auto root = doc.first_node();
auto metadata = root->first_node();
auto text_node = metadata->first_node();
text_node->value("Did the changing");
std::ofstream newXmlFile("New.xml");
newXmlFile << doc;
}
等等,还有更多
遗憾的是,除非您的输入具有完全预期的属性,否则这是一个问题。
假设这个 ok 样本:
char sample1[] = R"(<root><metadata>Trying to change this</metadata></root>)";
如果元数据元素为空,您会崩溃:
char sample2[] = R"(<root><metadata></metadata></root>)";
char sample3[] = R"(<root><metadata/></root>)";
确实这会触发 ASAN 故障:
/home/sehe/Projects/Whosebug/test.cpp:17:25: runtime error: member access within null pointer of type 'struct xml_node'
/home/sehe/Projects/Whosebug/test.cpp:17:25: runtime error: member call on null pointer of type 'struct xml_base'
/usr/include/rapidxml/rapidxml.hpp:762:24: runtime error: member call on null pointer of type 'struct xml_base'
/usr/include/rapidxml/rapidxml.hpp:753:21: runtime error: member access within null pointer of type 'struct xml_base'
AddressSanitizer:DEADLYSIGNAL
如果有惊喜,它会....做令人惊讶的事情!
char sample4[] = R"(<root><metadata><surprise/></metadata></root>)";
最终错误地生成:
<root>
<metadata>
<surprise>changed</surprise>
</metadata>
</root>
这还没有结束:
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
#include <iostream>
namespace {
char sample1[] = R"(<root><metadata>Trying to change this</metadata></root>)";
char sample2[] = R"(<root><metadata><surprise/></metadata></root>)";
char sample3[] = R"(<root><metadata>mixed<surprise/>bag</metadata></root>)";
char sample4[] = R"(<root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root>)";
char sample5[] = R"(<root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root>)";
//These crash:
//char sampleX[] = R"(<root><metadata></metadata></root>)";
//char sampleY[] = R"(<root><metadata/></root>)";
}
int main() {
for (char* xml : {sample1, sample2, sample3, sample4, sample5}) {
std::cout << "\n=== " << xml << " ===\n";
rapidxml::xml_document<> doc;
doc.parse<0>(xml);
auto root = doc.first_node();
auto metadata = root->first_node();
auto text_node = metadata->first_node();
text_node->value("changed");
print(std::cout << " --> ", doc, rapidxml::print_no_indenting);
std::cout << "\n";
}
}
版画
=== <root><metadata>Trying to change this</metadata></root> ===
--> <root><metadata>changed</metadata></root>
=== <root><metadata><surprise/></metadata></root> ===
--> <root><metadata><surprise>changed</surprise></metadata></root>
=== <root><metadata>mixed<surprise/>bag</metadata></root> ===
--> <root><metadata>changed<surprise/>bag</metadata></root>
=== <root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root> ===
--> <root><metadata><![CDATA[changed]]></metadata></root>
=== <root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root> ===
--> <root><metadata>changed</metadata></root>
如何让它稳健?
首先,使用查询来找到您的目标。遗憾的是 rapidxml 不支持这个;参见 What XML parser should I use in C++?
其次,编辑前检查节点类型
第三,如果可以的话,替换整个节点,这样你就可以独立于以前的节点
最后,请务必从文档中实际分配您的新节点,以免出现生命周期问题。
auto root = doc.first_node(); if (auto* old_meta = root->first_node()) { assert(old_meta->name() == std::string("metadata")); print(std::cout << "Removing metadata node: ", *old_meta, fmt); std::cout << "\n"; root->remove_first_node(); } auto newmeta = doc.allocate_node(rapidxml::node_element, "metadata", "changed"); root->prepend_node(newmeta);
综合起来:
#include <rapidxml/rapidxml.hpp>
#include <rapidxml/rapidxml_print.hpp>
#include <rapidxml/rapidxml_utils.hpp>
#include <iostream>
namespace {
std::string cases[] = {
R"(<root><metadata>Trying to change this</metadata></root>)",
R"(<root><metadata><surprise/></metadata></root>)",
R"(<root><metadata>mixed<surprise/>bag</metadata></root>)",
R"(<root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root>)",
R"(<root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root>)",
R"(<root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root>)",
//These no longer crash:
R"(<root><metadata></metadata></root>)",
R"(<root><metadata/></root>)",
// more edge-cases in the predecessor chain
R"(<root></root>)",
R"(<root><no-metadata/></root>)",
R"(<bogus/>)",
};
}
int main() {
auto const fmt = rapidxml::print_no_indenting;
for (auto& xml : cases) {
std::cout << "Input: " << xml << "\n";
rapidxml::xml_document<> doc;
doc.parse<0>(xml.data());
if (auto root = doc.first_node()) {
if (root->name() == std::string("root")) {
if (auto* old_meta = root->first_node()) {
if (old_meta->name() == std::string("metadata")) {
root->remove_first_node();
} else {
std::cout << "WARNING: Not removing '" << old_meta->name() << "' element where 'metadata' expected\n";
}
}
auto newmeta = doc.allocate_node(rapidxml::node_element, "metadata", "changed");
root->prepend_node(newmeta);
} else {
std::cout << "WARNING: '" << root->name() << "' found where 'root' expected\n";
}
}
print(std::cout << "Output: ", doc, fmt);
std::cout << "\n--\n";
}
}
版画
Input: <root><metadata>Trying to change this</metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><surprise/></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata>mixed<surprise/>bag</metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><![CDATA[mixed<surprise/>bag]]></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata><!-- comment please -->outloud<!-- hidden --></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root>
<metadata>Trying to change this</metadata>
<body>
<salad>Greek Caesar</salad>
</body>
</root> ===
Output: <root><metadata>changed</metadata><body><salad>Greek Caesar</salad></body></root>
--
Input: <root><metadata></metadata></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><metadata/></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root></root> ===
Output: <root><metadata>changed</metadata></root>
--
Input: <root><no-metadata/></root> ===
WARNING: Not removing 'no-metadata' element where 'metadata' expected
Output: <root><metadata>changed</metadata><no-metadata/></root>
--
Input: <bogus/> ===
WARNING: 'bogus' found where 'root' expected
Output: <bogus/>
--
摘要
XML 是可扩展的。这是标记。是语言。不简单:)