diff --git a/src/pugixml.cpp b/src/pugixml.cpp
index 4340c2d..4592ae7 100644
--- a/src/pugixml.cpp
+++ b/src/pugixml.cpp
@@ -1861,7 +1861,7 @@ PUGI__NS_BEGIN
enum chartypex_t
{
ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, >
- ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, "
+ ctx_special_attr = 2, // Any symbol >= 0 and < 32, &, <, >, "
ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _
ctx_digit = 8, // 0-9
ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, .
@@ -1869,7 +1869,7 @@ PUGI__NS_BEGIN
static const unsigned char chartypex_table[256] =
{
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, // 0-15
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31
0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63
diff --git a/tests/test_write.cpp b/tests/test_write.cpp
index 95cc566..57fa7fb 100644
--- a/tests/test_write.cpp
+++ b/tests/test_write.cpp
@@ -193,7 +193,21 @@ TEST_XML(write_escape, "text")
doc.child(STR("node")).attribute(STR("attr")) = STR("<>'\"&\x04\r\n\t");
doc.child(STR("node")).first_child().set_value(STR("<>'\"&\x04\r\n\t"));
- CHECK_NODE(doc, STR("<>'\"&\r\n\t"));
+ CHECK_NODE(doc, STR("<>'\"&\r\n\t"));
+}
+
+TEST_XML(write_escape_roundtrip, "text")
+{
+ doc.child(STR("node")).attribute(STR("attr")) = STR("<>'\"&\x04\r\n\t");
+ doc.child(STR("node")).first_child().set_value(STR("<>'\"&\x04\r\n\t"));
+
+ std::string contents = write_narrow(doc, format_raw, encoding_utf8);
+
+ CHECK(doc.load_buffer(contents.c_str(), contents.size()));
+
+ // Note: this string is almost identical to the string from write_escape with the exception of \r
+ // \r in PCDATA doesn't roundtrip because it has to go through newline conversion (which could be disabled, but is active by default)
+ CHECK_NODE(doc, STR("<>'\"&\n\t"));
}
TEST_XML(write_escape_unicode, "")