Skip to content

Commit 2499b75

Browse files
committed
fix(xml): keep whitespace-only element content when reading
tinyxml2 dropped a text node made only of whitespace, so <Tab> </Tab> read as an empty string where .NET reads a tab. The reader now parses in PEDANTIC_WHITESPACE mode, reports such content as a Whitespace node, and includes Whitespace and SignificantWhitespace in ReadElementContentAsString(). Indentation between elements is still not reported, which is what tinyxml2's pedantic mode preserves. Needed by CNA's XNA IntermediateSerializer corpus (string_edges: a tab, a space).
1 parent c8fadba commit 2499b75

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

modules/xml/src/System/Xml/XmlReader.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ struct XmlEvent {
3333
// ---------------------------------------------------------------------------
3434

3535
struct XmlReaderState {
36-
tinyxml2::XMLDocument doc;
36+
// PEDANTIC_WHITESPACE keeps whitespace-only text nodes, which .NET reports as Whitespace
37+
// nodes and includes in ReadElementContentAsString(): `<Tab>\t</Tab>` reads a tab, not "".
38+
tinyxml2::XMLDocument doc{true, tinyxml2::PEDANTIC_WHITESPACE};
3739
std::vector<XmlEvent> events;
3840
int pos = -1; // before first Read()
3941
int attrIndex = -1; // attribute cursor (-1 = on element)
@@ -103,8 +105,11 @@ static void buildEvents(tinyxml2::XMLNode* node, std::vector<XmlEvent>& out,
103105
}
104106
if (auto* txt = node->ToText()) {
105107
XmlEvent e;
106-
e.type = txt->CData() ? XmlNodeType::CDATA : XmlNodeType::Text;
107108
e.value = txt->Value() ? txt->Value() : "";
109+
const bool whitespaceOnly = !txt->CData() &&
110+
e.value.find_first_not_of(" \t\r\n") == std::string::npos;
111+
e.type = txt->CData() ? XmlNodeType::CDATA
112+
: whitespaceOnly ? XmlNodeType::Whitespace : XmlNodeType::Text;
108113
e.scope = scope; e.depth = depth; e.lineNumber = node->GetLineNum();
109114
out.push_back(std::move(e));
110115
return;
@@ -400,7 +405,9 @@ std::string XmlReader::ReadElementContentAsString() {
400405
--depth;
401406
} else if (ev.type == XmlNodeType::Element) {
402407
if (!ev.isEmptyElement) ++depth; // self-closing elements have no matching EndElement
403-
} else if (depth == 0 && (ev.type == XmlNodeType::Text || ev.type == XmlNodeType::CDATA)) {
408+
} else if (depth == 0 && (ev.type == XmlNodeType::Text || ev.type == XmlNodeType::CDATA ||
409+
ev.type == XmlNodeType::Whitespace ||
410+
ev.type == XmlNodeType::SignificantWhitespace)) {
404411
result += ev.value;
405412
}
406413
Read();

modules/xml/tests/System/Xml/XmlReaderNavigationTests.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,28 @@ TEST(XmlReaderNavigationTests, LineInfoReportsTheStartLineOfEveryNode) {
148148
EXPECT_EQ(r->getLineNumberProperty(), 6);
149149
}
150150

151+
TEST(XmlReaderNavigationTests, WhitespaceOnlyTextIsReportedAndReadAsContent) {
152+
// .NET keeps whitespace-only element content: `<Tab>\t</Tab>` reads a tab. tinyxml2 dropped
153+
// such nodes before PEDANTIC_WHITESPACE; in that mode it keeps an element's whitespace-only
154+
// content (reported here as a Whitespace node) while still dropping indentation between
155+
// elements, so the reader never sees a node between `<r>` and `<Tab>`.
156+
auto r = Open("<r>\n <Tab>\t</Tab>\n <Space> </Space>\n <Empty></Empty>\n</r>");
157+
r->ReadStartElement("r");
158+
ASSERT_TRUE(r->IsStartElement("Tab"));
159+
r->Read();
160+
EXPECT_EQ(r->getNodeTypeProperty(), XmlNodeType::Whitespace);
161+
EXPECT_EQ(r->getValueProperty(), "\t");
162+
r = Open("<r>\n <Tab>\t</Tab>\n <Space> </Space>\n <Empty></Empty>\n</r>");
163+
r->ReadStartElement("r");
164+
ASSERT_TRUE(r->IsStartElement("Tab"));
165+
EXPECT_EQ(r->ReadElementContentAsString(), "\t");
166+
ASSERT_TRUE(r->IsStartElement("Space"));
167+
EXPECT_EQ(r->ReadElementContentAsString(), " ");
168+
ASSERT_TRUE(r->IsStartElement("Empty"));
169+
EXPECT_EQ(r->ReadElementContentAsString(), "");
170+
EXPECT_EQ(r->MoveToContent(), XmlNodeType::EndElement);
171+
}
172+
151173
TEST(XmlReaderNavigationTests, SettingsProhibitDtdByDefault) {
152174
XmlReaderSettings settings;
153175
EXPECT_EQ(settings.DtdProcessing, DtdProcessing::Prohibit);

0 commit comments

Comments
 (0)