From 16bf75dedffbc768dca2c94e55babff8d7a18111 Mon Sep 17 00:00:00 2001 From: Volo Zyko Date: Thu, 31 Jul 2014 15:45:05 +0300 Subject: [PATCH] Extended the fix for issue #109 and added a test. --- include/cereal/archives/xml.hpp | 75 +++++++++++++++++++++++++++++++-- unittests/basic_string.cpp | 40 ++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/include/cereal/archives/xml.hpp b/include/cereal/archives/xml.hpp index 8f97e7c0..1c2ae6f5 100644 --- a/include/cereal/archives/xml.hpp +++ b/include/cereal/archives/xml.hpp @@ -238,8 +238,11 @@ namespace cereal itsOS.clear(); itsOS.seekp( 0, std::ios::beg ); itsOS << value << std::ends; + // workaround rapidxml restriction on whitespace handling. + auto encoded = encode_cdata( itsOS.str() ); + // allocate strings for all of the data in the XML object - auto dataPtr = itsXML.allocate_string( itsOS.str().c_str() ); + auto dataPtr = itsXML.allocate_string( encoded.c_str() ); // allocate cdata and set its value auto cdata = itsXML.allocate_node( rapidxml::node_cdata ); @@ -317,6 +320,34 @@ namespace cereal } }; // NodeInfo + //! Encode only & and > in cdata text + /* Normally CDATA when saved to XML should not be changed in any way + but since rapidxml doesn't handle xml:space attribute in XML elements then + we use CDATA and do escaping of non-allowed characters manually. */ + std::string encode_cdata( std::string const & text ) + { + std::string res; + res.reserve( text.size() ); + + for( auto c : text ) + { + if( c == '&' ) + { + res.append( "&" ); + } + else if( c == '>' ) + { + res.append( ">" ); + } + else + { + res.append( 1, c ); + } + } + + return res; + } + //! @} private: @@ -594,8 +625,8 @@ namespace cereal std::basic_istringstream is( first_node->value() ); - str.assign( std::istreambuf_iterator( is ), - std::istreambuf_iterator() ); + str = decode_cdata( std::istreambuf_iterator( is ), + std::istreambuf_iterator() ); } //! Loads the size of the current top node @@ -676,6 +707,44 @@ namespace cereal const char * name; //!< The NVP name for next next child node }; // NodeInfo + //! Decode & and > characters in cdata + /* For details see explanation for XMLOutputArchive::encode_cdata(). */ + template + std::basic_string decode_cdata( std::istreambuf_iterator start, + const std::istreambuf_iterator& end ) + { + std::basic_string res; + + for( ; start != end; ++start ) + { + if( *start == '&' ) + { + const auto first = *( ++start ); + const auto second = *( ++start ); + const auto third = *( ++start ); + + if( first == 'g' && second == 't' && third == ';' ) + { + res.append( 1, '>' ); + } + else if( first == 'a' && second == 'm' && third == 'p' && *( ++start ) == ';' ) + { + res.append( 1, '&' ); + } + else + { + throw Exception("Cannot decode cdata value"); + } + } + else + { + res.append( 1, *start ); + } + } + + return res; + } + //! @} private: diff --git a/unittests/basic_string.cpp b/unittests/basic_string.cpp index 25ec20b5..20ca9fab 100644 --- a/unittests/basic_string.cpp +++ b/unittests/basic_string.cpp @@ -125,3 +125,43 @@ BOOST_AUTO_TEST_CASE( json_string_basic ) test_string_basic(); } +template +void test_string_for_array(T (&strings)[N][M]) +{ + for(size_t i=0; i o_string = strings[i]; + + std::ostringstream os; + { + OArchive oar(os); + oar(o_string); + } + + std::basic_string i_string; + + std::istringstream is(os.str()); + { + IArchive iar(is); + iar(i_string); + } + + BOOST_CHECK_EQUAL(i_string, o_string); + } +} + +BOOST_AUTO_TEST_CASE( xml_string_issue109 ) +{ + char strings[][20] = { + "some text", + " some text ", + " ", + " text ", + " ]]> ", + " > > ]]> ", + " < <]>] < ", + " & & " + }; + + test_string_for_array(strings); +}