1 /*! \file xml.hpp 2 \brief XML input and output archives */ 3 /* 4 Copyright (c) 2014, Randolph Voorhies, Shane Grant 5 All rights reserved. 6 7 Redistribution and use in source and binary forms, with or without 8 modification, are permitted provided that the following conditions are met: 9 * Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 * Neither the name of cereal nor the 15 names of its contributors may be used to endorse or promote products 16 derived from this software without specific prior written permission. 17 18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY 22 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 #ifndef CEREAL_ARCHIVES_XML_HPP_ 30 #define CEREAL_ARCHIVES_XML_HPP_ 31 #include "cereal/cereal.hpp" 32 #include "cereal/details/util.hpp" 33 34 #include "cereal/external/rapidxml/rapidxml.hpp" 35 #include "cereal/external/rapidxml/rapidxml_print.hpp" 36 #include "cereal/external/base64.hpp" 37 38 #include <sstream> 39 #include <stack> 40 #include <vector> 41 #include <limits> 42 #include <string> 43 #include <cstring> 44 #include <cmath> 45 46 namespace cereal 47 { 48 namespace xml_detail 49 { 50 #ifndef CEREAL_XML_STRING_VALUE 51 //! The default name for the root node in a cereal xml archive. 52 /*! You can define CEREAL_XML_STRING_VALUE to be different assuming you do so 53 before this file is included. */ 54 #define CEREAL_XML_STRING_VALUE "cereal" 55 #endif // CEREAL_XML_STRING_VALUE 56 57 //! The name given to the root node in a cereal xml archive 58 static const char * CEREAL_XML_STRING = CEREAL_XML_STRING_VALUE; 59 60 //! Returns true if the character is whitespace isWhitespace(char c)61 inline bool isWhitespace( char c ) 62 { 63 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 64 } 65 } 66 67 // ###################################################################### 68 //! An output archive designed to save data to XML 69 /*! This archive uses RapidXML to build an in memory XML tree of the 70 data it serializes before outputting it to its stream upon destruction. 71 This archive should be used in an RAII fashion, letting 72 the automatic destruction of the object cause the flush to its stream. 73 74 XML archives provides a human readable output but at decreased 75 performance (both in time and space) compared to binary archives. 76 77 XML benefits greatly from name-value pairs, which if present, will 78 name the nodes in the output. If these are not present, each level 79 of the output tree will be given an automatically generated delimited name. 80 81 The precision of the output archive controls the number of decimals output 82 for floating point numbers and should be sufficiently large (i.e. at least 20) 83 if there is a desire to have binary equality between the numbers output and 84 those read in. In general you should expect a loss of precision when going 85 from floating point to text and back. 86 87 XML archives can optionally print the type of everything they serialize, which 88 adds an attribute to each node. 89 90 XML archives do not output the size information for any dynamically sized structure 91 and instead infer it from the number of children for a node. This means that data 92 can be hand edited for dynamic sized structures and will still be readable. This 93 is accomplished through the cereal::SizeTag object, which will also add an attribute 94 to its parent field. 95 \ingroup Archives */ 96 class XMLOutputArchive : public OutputArchive<XMLOutputArchive>, public traits::TextArchive 97 { 98 public: 99 /*! @name Common Functionality 100 Common use cases for directly interacting with an XMLOutputArchive */ 101 //! @{ 102 103 //! A class containing various advanced options for the XML archive 104 class Options 105 { 106 public: 107 //! Default options Default()108 static Options Default(){ return Options(); } 109 110 //! Default options with no indentation NoIndent()111 static Options NoIndent(){ return Options( std::numeric_limits<double>::max_digits10, false ); } 112 113 //! Specify specific options for the XMLOutputArchive 114 /*! @param precision The precision used for floating point numbers 115 @param indent Whether to indent each line of XML 116 @param outputType Whether to output the type of each serialized object as an attribute */ Options(int precision=std::numeric_limits<double>::max_digits10,bool indent=true,bool outputType=false)117 explicit Options( int precision = std::numeric_limits<double>::max_digits10, 118 bool indent = true, 119 bool outputType = false ) : 120 itsPrecision( precision ), 121 itsIndent( indent ), 122 itsOutputType( outputType ) { } 123 124 private: 125 friend class XMLOutputArchive; 126 int itsPrecision; 127 bool itsIndent; 128 bool itsOutputType; 129 }; 130 131 //! Construct, outputting to the provided stream upon destruction 132 /*! @param stream The stream to output to. Note that XML is only guaranteed to flush 133 its output to the stream upon destruction. 134 @param options The XML specific options to use. See the Options struct 135 for the values of default parameters */ XMLOutputArchive(std::ostream & stream,Options const & options=Options::Default ())136 XMLOutputArchive( std::ostream & stream, Options const & options = Options::Default() ) : 137 OutputArchive<XMLOutputArchive>(this), 138 itsStream(stream), 139 itsOutputType( options.itsOutputType ), 140 itsIndent( options.itsIndent ) 141 { 142 // rapidxml will delete all allocations when xml_document is cleared 143 auto node = itsXML.allocate_node( rapidxml::node_declaration ); 144 node->append_attribute( itsXML.allocate_attribute( "version", "1.0" ) ); 145 node->append_attribute( itsXML.allocate_attribute( "encoding", "utf-8" ) ); 146 itsXML.append_node( node ); 147 148 // allocate root node 149 auto root = itsXML.allocate_node( rapidxml::node_element, xml_detail::CEREAL_XML_STRING ); 150 itsXML.append_node( root ); 151 itsNodes.emplace( root ); 152 153 // set attributes on the streams 154 itsStream << std::boolalpha; 155 itsStream.precision( options.itsPrecision ); 156 itsOS << std::boolalpha; 157 itsOS.precision( options.itsPrecision ); 158 } 159 160 //! Destructor, flushes the XML ~XMLOutputArchive()161 ~XMLOutputArchive() CEREAL_NOEXCEPT 162 { 163 const int flags = itsIndent ? 0x0 : rapidxml::print_no_indenting; 164 rapidxml::print( itsStream, itsXML, flags ); 165 itsXML.clear(); 166 } 167 168 //! Saves some binary data, encoded as a base64 string, with an optional name 169 /*! This can be called directly by users and it will automatically create a child node for 170 the current XML node, populate it with a base64 encoded string, and optionally name 171 it. The node will be finished after it has been populated. */ saveBinaryValue(const void * data,size_t size,const char * name=nullptr)172 void saveBinaryValue( const void * data, size_t size, const char * name = nullptr ) 173 { 174 itsNodes.top().name = name; 175 176 startNode(); 177 178 auto base64string = base64::encode( reinterpret_cast<const unsigned char *>( data ), size ); 179 saveValue( base64string ); 180 181 if( itsOutputType ) 182 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", "cereal binary data" ) ); 183 184 finishNode(); 185 }; 186 187 //! @} 188 /*! @name Internal Functionality 189 Functionality designed for use by those requiring control over the inner mechanisms of 190 the XMLOutputArchive */ 191 //! @{ 192 193 //! Creates a new node that is a child of the node at the top of the stack 194 /*! Nodes will be given a name that has either been pre-set by a name value pair, 195 or generated based upon a counter unique to the parent node. If you want to 196 give a node a specific name, use setNextName prior to calling startNode. 197 198 The node will then be pushed onto the node stack. */ startNode()199 void startNode() 200 { 201 // generate a name for this new node 202 const auto nameString = itsNodes.top().getValueName(); 203 204 // allocate strings for all of the data in the XML object 205 auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 ); 206 207 // insert into the XML 208 auto node = itsXML.allocate_node( rapidxml::node_element, namePtr, nullptr, nameString.size() ); 209 itsNodes.top().node->append_node( node ); 210 itsNodes.emplace( node ); 211 } 212 213 //! Designates the most recently added node as finished finishNode()214 void finishNode() 215 { 216 itsNodes.pop(); 217 } 218 219 //! Sets the name for the next node created with startNode setNextName(const char * name)220 void setNextName( const char * name ) 221 { 222 itsNodes.top().name = name; 223 } 224 225 //! Saves some data, encoded as a string, into the current top level node 226 /*! The data will be be named with the most recent name if one exists, 227 otherwise it will be given some default delimited value that depends upon 228 the parent node */ 229 template <class T> inline saveValue(T const & value)230 void saveValue( T const & value ) 231 { 232 itsOS.clear(); itsOS.seekp( 0, std::ios::beg ); 233 itsOS << value << std::ends; 234 235 auto strValue = itsOS.str(); 236 237 // itsOS.str() may contain data from previous calls after the first '\0' that was just inserted 238 // and this data is counted in the length call. We make sure to remove that section so that the 239 // whitespace validation is done properly 240 strValue.resize(std::strlen(strValue.c_str())); 241 242 // If the first or last character is a whitespace, add xml:space attribute 243 const auto len = strValue.length(); 244 if ( len > 0 && ( xml_detail::isWhitespace( strValue[0] ) || xml_detail::isWhitespace( strValue[len - 1] ) ) ) 245 { 246 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "xml:space", "preserve" ) ); 247 } 248 249 // allocate strings for all of the data in the XML object 250 auto dataPtr = itsXML.allocate_string(strValue.c_str(), strValue.length() + 1 ); 251 252 // insert into the XML 253 itsNodes.top().node->append_node( itsXML.allocate_node( rapidxml::node_data, nullptr, dataPtr ) ); 254 } 255 256 //! Overload for uint8_t prevents them from being serialized as characters saveValue(uint8_t const & value)257 void saveValue( uint8_t const & value ) 258 { 259 saveValue( static_cast<uint32_t>( value ) ); 260 } 261 262 //! Overload for int8_t prevents them from being serialized as characters saveValue(int8_t const & value)263 void saveValue( int8_t const & value ) 264 { 265 saveValue( static_cast<int32_t>( value ) ); 266 } 267 268 //! Causes the type to be appended as an attribute to the most recently made node if output type is set to true 269 template <class T> inline insertType()270 void insertType() 271 { 272 if( !itsOutputType ) 273 return; 274 275 // generate a name for this new node 276 const auto nameString = util::demangledName<T>(); 277 278 // allocate strings for all of the data in the XML object 279 auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 ); 280 281 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", namePtr ) ); 282 } 283 284 //! Appends an attribute to the current top level node appendAttribute(const char * name,const char * value)285 void appendAttribute( const char * name, const char * value ) 286 { 287 auto namePtr = itsXML.allocate_string( name ); 288 auto valuePtr = itsXML.allocate_string( value ); 289 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( namePtr, valuePtr ) ); 290 } 291 292 protected: 293 //! A struct that contains metadata about a node 294 struct NodeInfo 295 { NodeInfocereal::XMLOutputArchive::NodeInfo296 NodeInfo( rapidxml::xml_node<> * n = nullptr, 297 const char * nm = nullptr ) : 298 node( n ), 299 counter( 0 ), 300 name( nm ) 301 { } 302 303 rapidxml::xml_node<> * node; //!< A pointer to this node 304 size_t counter; //!< The counter for naming child nodes 305 const char * name; //!< The name for the next child node 306 307 //! Gets the name for the next child node created from this node 308 /*! The name will be automatically generated using the counter if 309 a name has not been previously set. If a name has been previously 310 set, that name will be returned only once */ getValueNamecereal::XMLOutputArchive::NodeInfo311 std::string getValueName() 312 { 313 if( name ) 314 { 315 auto n = name; 316 name = nullptr; 317 return {n}; 318 } 319 else 320 return "value" + std::to_string( counter++ ) + "\0"; 321 } 322 }; // NodeInfo 323 324 //! @} 325 326 private: 327 std::ostream & itsStream; //!< The output stream 328 rapidxml::xml_document<> itsXML; //!< The XML document 329 std::stack<NodeInfo> itsNodes; //!< A stack of nodes added to the document 330 std::ostringstream itsOS; //!< Used to format strings internally 331 bool itsOutputType; //!< Controls whether type information is printed 332 bool itsIndent; //!< Controls whether indenting is used 333 }; // XMLOutputArchive 334 335 // ###################################################################### 336 //! An output archive designed to load data from XML 337 /*! This archive uses RapidXML to build an in memory XML tree of the 338 data in the stream it is given before loading any types serialized. 339 340 As with the output XML archive, the preferred way to use this archive is in 341 an RAII fashion, ensuring its destruction after all data has been read. 342 343 Input XML should have been produced by the XMLOutputArchive. Data can 344 only be added to dynamically sized containers - the input archive will 345 determine their size by looking at the number of child nodes. Data that 346 did not originate from an XMLOutputArchive is not officially supported, 347 but may be possible to use if properly formatted. 348 349 The XMLInputArchive does not require that nodes are loaded in the same 350 order they were saved by XMLOutputArchive. Using name value pairs (NVPs), 351 it is possible to load in an out of order fashion or otherwise skip/select 352 specific nodes to load. 353 354 The default behavior of the input archive is to read sequentially starting 355 with the first node and exploring its children. When a given NVP does 356 not match the read in name for a node, the archive will search for that 357 node at the current level and load it if it exists. After loading an out of 358 order node, the archive will then proceed back to loading sequentially from 359 its new position. 360 361 Consider this simple example where loading of some data is skipped: 362 363 @code{cpp} 364 // imagine the input file has someData(1-9) saved in order at the top level node 365 ar( someData1, someData2, someData3 ); // XML loads in the order it sees in the file 366 ar( cereal::make_nvp( "hello", someData6 ) ); // NVP given does not 367 // match expected NVP name, so we search 368 // for the given NVP and load that value 369 ar( someData7, someData8, someData9 ); // with no NVP given, loading resumes at its 370 // current location, proceeding sequentially 371 @endcode 372 373 \ingroup Archives */ 374 class XMLInputArchive : public InputArchive<XMLInputArchive>, public traits::TextArchive 375 { 376 public: 377 /*! @name Common Functionality 378 Common use cases for directly interacting with an XMLInputArchive */ 379 //! @{ 380 381 //! Construct, reading in from the provided stream 382 /*! Reads in an entire XML document from some stream and parses it as soon 383 as serialization starts 384 385 @param stream The stream to read from. Can be a stringstream or a file. */ XMLInputArchive(std::istream & stream)386 XMLInputArchive( std::istream & stream ) : 387 InputArchive<XMLInputArchive>( this ), 388 itsData( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) 389 { 390 try 391 { 392 itsData.push_back('\0'); // rapidxml will do terrible things without the data being null terminated 393 itsXML.parse<rapidxml::parse_trim_whitespace | rapidxml::parse_no_data_nodes | rapidxml::parse_declaration_node>( reinterpret_cast<char *>( itsData.data() ) ); 394 } 395 catch( rapidxml::parse_error const & ) 396 { 397 //std::cerr << "-----Original-----" << std::endl; 398 //stream.seekg(0); 399 //std::cout << std::string( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) << std::endl; 400 401 //std::cerr << "-----Error-----" << std::endl; 402 //std::cerr << e.what() << std::endl; 403 //std::cerr << e.where<char>() << std::endl; 404 throw Exception("XML Parsing failed - likely due to invalid characters or invalid naming"); 405 } 406 407 // Parse the root 408 auto root = itsXML.first_node( xml_detail::CEREAL_XML_STRING ); 409 if( root == nullptr ) 410 throw Exception("Could not detect cereal root node - likely due to empty or invalid input"); 411 else 412 itsNodes.emplace( root ); 413 } 414 415 ~XMLInputArchive() CEREAL_NOEXCEPT = default; 416 417 //! Loads some binary data, encoded as a base64 string, optionally specified by some name 418 /*! This will automatically start and finish a node to load the data, and can be called directly by 419 users. 420 421 Note that this follows the same ordering rules specified in the class description in regards 422 to loading in/out of order */ loadBinaryValue(void * data,size_t size,const char * name=nullptr)423 void loadBinaryValue( void * data, size_t size, const char * name = nullptr ) 424 { 425 setNextName( name ); 426 startNode(); 427 428 std::string encoded; 429 loadValue( encoded ); 430 431 auto decoded = base64::decode( encoded ); 432 433 if( size != decoded.size() ) 434 throw Exception("Decoded binary data size does not match specified size"); 435 436 std::memcpy( data, decoded.data(), decoded.size() ); 437 438 finishNode(); 439 }; 440 441 //! @} 442 /*! @name Internal Functionality 443 Functionality designed for use by those requiring control over the inner mechanisms of 444 the XMLInputArchive */ 445 //! @{ 446 447 //! Prepares to start reading the next node 448 /*! This places the next node to be parsed onto the nodes stack. 449 450 By default our strategy is to start with the document root node and then 451 recursively iterate through all children in the order they show up in the document. 452 We don't need to know NVPs do to this; we'll just blindly load in the order things appear in. 453 454 We check to see if the specified NVP matches what the next automatically loaded node is. If they 455 match, we just continue as normal, going in order. If they don't match, we attempt to find a node 456 named after the NVP that is being loaded. If that NVP does not exist, we throw an exception. */ startNode()457 void startNode() 458 { 459 auto next = itsNodes.top().child; // By default we would move to the next child node 460 auto const expectedName = itsNodes.top().name; // this is the expected name from the NVP, if provided 461 462 // If we were given an NVP name, look for it in the current level of the document. 463 // We only need to do this if either we have exhausted the siblings of the current level or 464 // the NVP name does not match the name of the node we would normally read next 465 if( expectedName && ( next == nullptr || std::strcmp( next->name(), expectedName ) != 0 ) ) 466 { 467 next = itsNodes.top().search( expectedName ); 468 469 if( next == nullptr ) 470 throw Exception("XML Parsing failed - provided NVP (" + std::string(expectedName) + ") not found"); 471 } 472 473 itsNodes.emplace( next ); 474 } 475 476 //! Finishes reading the current node finishNode()477 void finishNode() 478 { 479 // remove current 480 itsNodes.pop(); 481 482 // advance parent 483 itsNodes.top().advance(); 484 485 // Reset name 486 itsNodes.top().name = nullptr; 487 } 488 489 //! Retrieves the current node name 490 //! will return @c nullptr if the node does not have a name getNodeName() const491 const char * getNodeName() const 492 { 493 return itsNodes.top().getChildName(); 494 } 495 496 //! Sets the name for the next node created with startNode setNextName(const char * name)497 void setNextName( const char * name ) 498 { 499 itsNodes.top().name = name; 500 } 501 502 //! Loads a bool from the current top node 503 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 504 std::is_same<T, bool>::value> = traits::sfinae> inline loadValue(T & value)505 void loadValue( T & value ) 506 { 507 std::istringstream is( itsNodes.top().node->value() ); 508 is.setf( std::ios::boolalpha ); 509 is >> value; 510 } 511 512 //! Loads a char (signed or unsigned) from the current top node 513 template <class T, traits::EnableIf<std::is_integral<T>::value, 514 !std::is_same<T, bool>::value, 515 sizeof(T) == sizeof(char)> = traits::sfinae> inline loadValue(T & value)516 void loadValue( T & value ) 517 { 518 value = *reinterpret_cast<T*>( itsNodes.top().node->value() ); 519 } 520 521 //! Load an int8_t from the current top node (ensures we parse entire number) loadValue(int8_t & value)522 void loadValue( int8_t & value ) 523 { 524 int32_t val; loadValue( val ); value = static_cast<int8_t>( val ); 525 } 526 527 //! Load a uint8_t from the current top node (ensures we parse entire number) loadValue(uint8_t & value)528 void loadValue( uint8_t & value ) 529 { 530 uint32_t val; loadValue( val ); value = static_cast<uint8_t>( val ); 531 } 532 533 //! Loads a type best represented as an unsigned long from the current top node 534 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 535 !std::is_same<T, bool>::value, 536 !std::is_same<T, char>::value, 537 !std::is_same<T, unsigned char>::value, 538 sizeof(T) < sizeof(long long)> = traits::sfinae> inline loadValue(T & value)539 void loadValue( T & value ) 540 { 541 value = static_cast<T>( std::stoul( itsNodes.top().node->value() ) ); 542 } 543 544 //! Loads a type best represented as an unsigned long long from the current top node 545 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 546 !std::is_same<T, bool>::value, 547 sizeof(T) >= sizeof(long long)> = traits::sfinae> inline 548 void loadValue( T & value ) 549 { 550 value = static_cast<T>( std::stoull( itsNodes.top().node->value() ) ); 551 } 552 553 //! Loads a type best represented as an int from the current top node 554 template <class T, traits::EnableIf<std::is_signed<T>::value, 555 !std::is_same<T, char>::value, 556 sizeof(T) <= sizeof(int)> = traits::sfinae> inline loadValue(T & value)557 void loadValue( T & value ) 558 { 559 value = static_cast<T>( std::stoi( itsNodes.top().node->value() ) ); 560 } 561 562 //! Loads a type best represented as a long from the current top node 563 template <class T, traits::EnableIf<std::is_signed<T>::value, 564 (sizeof(T) > sizeof(int)), 565 sizeof(T) <= sizeof(long)> = traits::sfinae> inline loadValue(T & value)566 void loadValue( T & value ) 567 { 568 value = static_cast<T>( std::stol( itsNodes.top().node->value() ) ); 569 } 570 571 //! Loads a type best represented as a long long from the current top node 572 template <class T, traits::EnableIf<std::is_signed<T>::value, 573 (sizeof(T) > sizeof(long)), 574 sizeof(T) <= sizeof(long long)> = traits::sfinae> inline loadValue(T & value)575 void loadValue( T & value ) 576 { 577 value = static_cast<T>( std::stoll( itsNodes.top().node->value() ) ); 578 } 579 580 //! Loads a type best represented as a float from the current top node loadValue(float & value)581 void loadValue( float & value ) 582 { 583 try 584 { 585 value = std::stof( itsNodes.top().node->value() ); 586 } 587 catch( std::out_of_range const & ) 588 { 589 // special case for denormalized values 590 std::istringstream is( itsNodes.top().node->value() ); 591 is >> value; 592 if( std::fpclassify( value ) != FP_SUBNORMAL ) 593 throw; 594 } 595 } 596 597 //! Loads a type best represented as a double from the current top node loadValue(double & value)598 void loadValue( double & value ) 599 { 600 try 601 { 602 value = std::stod( itsNodes.top().node->value() ); 603 } 604 catch( std::out_of_range const & ) 605 { 606 // special case for denormalized values 607 std::istringstream is( itsNodes.top().node->value() ); 608 is >> value; 609 if( std::fpclassify( value ) != FP_SUBNORMAL ) 610 throw; 611 } 612 } 613 614 //! Loads a type best represented as a long double from the current top node loadValue(long double & value)615 void loadValue( long double & value ) 616 { 617 try 618 { 619 value = std::stold( itsNodes.top().node->value() ); 620 } 621 catch( std::out_of_range const & ) 622 { 623 // special case for denormalized values 624 std::istringstream is( itsNodes.top().node->value() ); 625 is >> value; 626 if( std::fpclassify( value ) != FP_SUBNORMAL ) 627 throw; 628 } 629 } 630 631 //! Loads a string from the current node from the current top node 632 template<class CharT, class Traits, class Alloc> inline loadValue(std::basic_string<CharT,Traits,Alloc> & str)633 void loadValue( std::basic_string<CharT, Traits, Alloc> & str ) 634 { 635 std::basic_istringstream<CharT, Traits> is( itsNodes.top().node->value() ); 636 637 str.assign( std::istreambuf_iterator<CharT, Traits>( is ), 638 std::istreambuf_iterator<CharT, Traits>() ); 639 } 640 641 //! Loads the size of the current top node 642 template <class T> inline loadSize(T & value)643 void loadSize( T & value ) 644 { 645 value = getNumChildren( itsNodes.top().node ); 646 } 647 648 protected: 649 //! Gets the number of children (usually interpreted as size) for the specified node getNumChildren(rapidxml::xml_node<> * node)650 static size_t getNumChildren( rapidxml::xml_node<> * node ) 651 { 652 size_t size = 0; 653 node = node->first_node(); // get first child 654 655 while( node != nullptr ) 656 { 657 ++size; 658 node = node->next_sibling(); 659 } 660 661 return size; 662 } 663 664 //! A struct that contains metadata about a node 665 /*! Keeps track of some top level node, its number of 666 remaining children, and the current active child node */ 667 struct NodeInfo 668 { NodeInfocereal::XMLInputArchive::NodeInfo669 NodeInfo( rapidxml::xml_node<> * n = nullptr ) : 670 node( n ), 671 child( n->first_node() ), 672 size( XMLInputArchive::getNumChildren( n ) ), 673 name( nullptr ) 674 { } 675 676 //! Advances to the next sibling node of the child 677 /*! If this is the last sibling child will be null after calling */ advancecereal::XMLInputArchive::NodeInfo678 void advance() 679 { 680 if( size > 0 ) 681 { 682 --size; 683 child = child->next_sibling(); 684 } 685 } 686 687 //! Searches for a child with the given name in this node 688 /*! @param searchName The name to search for (must be null terminated) 689 @return The node if found, nullptr otherwise */ searchcereal::XMLInputArchive::NodeInfo690 rapidxml::xml_node<> * search( const char * searchName ) 691 { 692 if( searchName ) 693 { 694 size_t new_size = XMLInputArchive::getNumChildren( node ); 695 const size_t name_size = rapidxml::internal::measure( searchName ); 696 697 for( auto new_child = node->first_node(); new_child != nullptr; new_child = new_child->next_sibling() ) 698 { 699 if( rapidxml::internal::compare( new_child->name(), new_child->name_size(), searchName, name_size, true ) ) 700 { 701 size = new_size; 702 child = new_child; 703 704 return new_child; 705 } 706 --new_size; 707 } 708 } 709 710 return nullptr; 711 } 712 713 //! Returns the actual name of the next child node, if it exists getChildNamecereal::XMLInputArchive::NodeInfo714 const char * getChildName() const 715 { 716 return child ? child->name() : nullptr; 717 } 718 719 rapidxml::xml_node<> * node; //!< A pointer to this node 720 rapidxml::xml_node<> * child; //!< A pointer to its current child 721 size_t size; //!< The remaining number of children for this node 722 const char * name; //!< The NVP name for next child node 723 }; // NodeInfo 724 725 //! @} 726 727 private: 728 std::vector<char> itsData; //!< The raw data loaded 729 rapidxml::xml_document<> itsXML; //!< The XML document 730 std::stack<NodeInfo> itsNodes; //!< A stack of nodes read from the document 731 }; 732 733 // ###################################################################### 734 // XMLArchive prologue and epilogue functions 735 // ###################################################################### 736 737 // ###################################################################### 738 //! Prologue for NVPs for XML output archives 739 /*! NVPs do not start or finish nodes - they just set up the names */ 740 template <class T> inline prologue(XMLOutputArchive &,NameValuePair<T> const &)741 void prologue( XMLOutputArchive &, NameValuePair<T> const & ) 742 { } 743 744 //! Prologue for NVPs for XML input archives 745 template <class T> inline prologue(XMLInputArchive &,NameValuePair<T> const &)746 void prologue( XMLInputArchive &, NameValuePair<T> const & ) 747 { } 748 749 // ###################################################################### 750 //! Epilogue for NVPs for XML output archives 751 /*! NVPs do not start or finish nodes - they just set up the names */ 752 template <class T> inline epilogue(XMLOutputArchive &,NameValuePair<T> const &)753 void epilogue( XMLOutputArchive &, NameValuePair<T> const & ) 754 { } 755 756 //! Epilogue for NVPs for XML input archives 757 template <class T> inline epilogue(XMLInputArchive &,NameValuePair<T> const &)758 void epilogue( XMLInputArchive &, NameValuePair<T> const & ) 759 { } 760 761 // ###################################################################### 762 //! Prologue for SizeTags for XML output archives 763 /*! SizeTags do not start or finish nodes */ 764 template <class T> inline prologue(XMLOutputArchive & ar,SizeTag<T> const &)765 void prologue( XMLOutputArchive & ar, SizeTag<T> const & ) 766 { 767 ar.appendAttribute( "size", "dynamic" ); 768 } 769 770 template <class T> inline prologue(XMLInputArchive &,SizeTag<T> const &)771 void prologue( XMLInputArchive &, SizeTag<T> const & ) 772 { } 773 774 //! Epilogue for SizeTags for XML output archives 775 /*! SizeTags do not start or finish nodes */ 776 template <class T> inline epilogue(XMLOutputArchive &,SizeTag<T> const &)777 void epilogue( XMLOutputArchive &, SizeTag<T> const & ) 778 { } 779 780 template <class T> inline epilogue(XMLInputArchive &,SizeTag<T> const &)781 void epilogue( XMLInputArchive &, SizeTag<T> const & ) 782 { } 783 784 // ###################################################################### 785 //! Prologue for all other types for XML output archives (except minimal types) 786 /*! Starts a new node, named either automatically or by some NVP, 787 that may be given data by the type about to be archived 788 789 Minimal types do not start or end nodes */ 790 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value || 791 traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline prologue(XMLOutputArchive & ar,T const &)792 void prologue( XMLOutputArchive & ar, T const & ) 793 { 794 ar.startNode(); 795 ar.insertType<T>(); 796 } 797 798 //! Prologue for all other types for XML input archives (except minimal types) 799 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value || 800 traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline prologue(XMLInputArchive & ar,T const &)801 void prologue( XMLInputArchive & ar, T const & ) 802 { 803 ar.startNode(); 804 } 805 806 // ###################################################################### 807 //! Epilogue for all other types other for XML output archives (except minimal types) 808 /*! Finishes the node created in the prologue 809 810 Minimal types do not start or end nodes */ 811 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value || 812 traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline epilogue(XMLOutputArchive & ar,T const &)813 void epilogue( XMLOutputArchive & ar, T const & ) 814 { 815 ar.finishNode(); 816 } 817 818 //! Epilogue for all other types other for XML output archives (except minimal types) 819 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value || 820 traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline epilogue(XMLInputArchive & ar,T const &)821 void epilogue( XMLInputArchive & ar, T const & ) 822 { 823 ar.finishNode(); 824 } 825 826 // ###################################################################### 827 // Common XMLArchive serialization functions 828 // ###################################################################### 829 830 //! Saving NVP types to XML 831 template <class T> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,NameValuePair<T> const & t)832 void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive & ar, NameValuePair<T> const & t ) 833 { 834 ar.setNextName( t.name ); 835 ar( t.value ); 836 } 837 838 //! Loading NVP types from XML 839 template <class T> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,NameValuePair<T> & t)840 void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, NameValuePair<T> & t ) 841 { 842 ar.setNextName( t.name ); 843 ar( t.value ); 844 } 845 846 // ###################################################################### 847 //! Saving SizeTags to XML 848 template <class T> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive &,SizeTag<T> const &)849 void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive &, SizeTag<T> const & ) 850 { } 851 852 //! Loading SizeTags from XML 853 template <class T> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,SizeTag<T> & st)854 void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, SizeTag<T> & st ) 855 { 856 ar.loadSize( st.size ); 857 } 858 859 // ###################################################################### 860 //! Saving for POD types to xml 861 template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,T const & t)862 void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, T const & t) 863 { 864 ar.saveValue( t ); 865 } 866 867 //! Loading for POD types from xml 868 template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,T & t)869 void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, T & t) 870 { 871 ar.loadValue( t ); 872 } 873 874 // ###################################################################### 875 //! saving string to xml 876 template<class CharT, class Traits, class Alloc> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,std::basic_string<CharT,Traits,Alloc> const & str)877 void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str) 878 { 879 ar.saveValue( str ); 880 } 881 882 //! loading string from xml 883 template<class CharT, class Traits, class Alloc> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,std::basic_string<CharT,Traits,Alloc> & str)884 void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str) 885 { 886 ar.loadValue( str ); 887 } 888 } // namespace cereal 889 890 // register archives for polymorphic support 891 CEREAL_REGISTER_ARCHIVE(cereal::XMLOutputArchive) 892 CEREAL_REGISTER_ARCHIVE(cereal::XMLInputArchive) 893 894 // tie input and output archives together 895 CEREAL_SETUP_ARCHIVE_TRAITS(cereal::XMLInputArchive, cereal::XMLOutputArchive) 896 897 #endif // CEREAL_ARCHIVES_XML_HPP_ 898