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 /*! Options can either be directly passed to the constructor, or chained using the 105 modifier functions for an interface analogous to named parameters */ 106 class Options 107 { 108 public: 109 //! Default options Default()110 static Options Default(){ return Options(); } 111 112 //! Specify specific options for the XMLOutputArchive 113 /*! @param precision_ The precision used for floating point numbers 114 @param indent_ Whether to indent each line of XML 115 @param outputType_ Whether to output the type of each serialized object as an attribute 116 @param sizeAttributes_ Whether dynamically sized containers output the size=dynamic attribute */ Options(int precision_=std::numeric_limits<double>::max_digits10,bool indent_=true,bool outputType_=false,bool sizeAttributes_=true)117 explicit Options( int precision_ = std::numeric_limits<double>::max_digits10, 118 bool indent_ = true, 119 bool outputType_ = false, 120 bool sizeAttributes_ = true ) : 121 itsPrecision( precision_ ), 122 itsIndent( indent_ ), 123 itsOutputType( outputType_ ), 124 itsSizeAttributes( sizeAttributes_ ) 125 { } 126 127 /*! @name Option Modifiers 128 An interface for setting option settings analogous to named parameters. 129 130 @code{cpp} 131 cereal::XMLOutputArchive ar( myStream, 132 cereal::XMLOutputArchive::Options() 133 .indent(true) 134 .sizeAttributes(false) ); 135 @endcode 136 */ 137 //! @{ 138 139 //! Sets the precision used for floaing point numbers precision(int value)140 Options & precision( int value ){ itsPrecision = value; return * this; } 141 //! Whether to indent each line of XML indent(bool enable)142 Options & indent( bool enable ){ itsIndent = enable; return *this; } 143 //! Whether to output the type of each serialized object as an attribute outputType(bool enable)144 Options & outputType( bool enable ){ itsOutputType = enable; return *this; } 145 //! Whether dynamically sized containers (e.g. vector) output the size=dynamic attribute sizeAttributes(bool enable)146 Options & sizeAttributes( bool enable ){ itsSizeAttributes = enable; return *this; } 147 148 //! @} 149 150 private: 151 friend class XMLOutputArchive; 152 int itsPrecision; 153 bool itsIndent; 154 bool itsOutputType; 155 bool itsSizeAttributes; 156 }; 157 158 //! Construct, outputting to the provided stream upon destruction 159 /*! @param stream The stream to output to. Note that XML is only guaranteed to flush 160 its output to the stream upon destruction. 161 @param options The XML specific options to use. See the Options struct 162 for the values of default parameters */ XMLOutputArchive(std::ostream & stream,Options const & options=Options::Default ())163 XMLOutputArchive( std::ostream & stream, Options const & options = Options::Default() ) : 164 OutputArchive<XMLOutputArchive>(this), 165 itsStream(stream), 166 itsOutputType( options.itsOutputType ), 167 itsIndent( options.itsIndent ), 168 itsSizeAttributes(options.itsSizeAttributes) 169 { 170 // rapidxml will delete all allocations when xml_document is cleared 171 auto node = itsXML.allocate_node( rapidxml::node_declaration ); 172 node->append_attribute( itsXML.allocate_attribute( "version", "1.0" ) ); 173 node->append_attribute( itsXML.allocate_attribute( "encoding", "utf-8" ) ); 174 itsXML.append_node( node ); 175 176 // allocate root node 177 auto root = itsXML.allocate_node( rapidxml::node_element, xml_detail::CEREAL_XML_STRING ); 178 itsXML.append_node( root ); 179 itsNodes.emplace( root ); 180 181 // set attributes on the streams 182 itsStream << std::boolalpha; 183 itsStream.precision( options.itsPrecision ); 184 itsOS << std::boolalpha; 185 itsOS.precision( options.itsPrecision ); 186 } 187 188 //! Destructor, flushes the XML ~XMLOutputArchive()189 ~XMLOutputArchive() CEREAL_NOEXCEPT 190 { 191 const int flags = itsIndent ? 0x0 : rapidxml::print_no_indenting; 192 rapidxml::print( itsStream, itsXML, flags ); 193 itsXML.clear(); 194 } 195 196 //! Saves some binary data, encoded as a base64 string, with an optional name 197 /*! This can be called directly by users and it will automatically create a child node for 198 the current XML node, populate it with a base64 encoded string, and optionally name 199 it. The node will be finished after it has been populated. */ saveBinaryValue(const void * data,size_t size,const char * name=nullptr)200 void saveBinaryValue( const void * data, size_t size, const char * name = nullptr ) 201 { 202 itsNodes.top().name = name; 203 204 startNode(); 205 206 auto base64string = base64::encode( reinterpret_cast<const unsigned char *>( data ), size ); 207 saveValue( base64string ); 208 209 if( itsOutputType ) 210 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", "cereal binary data" ) ); 211 212 finishNode(); 213 } 214 215 //! @} 216 /*! @name Internal Functionality 217 Functionality designed for use by those requiring control over the inner mechanisms of 218 the XMLOutputArchive */ 219 //! @{ 220 221 //! Creates a new node that is a child of the node at the top of the stack 222 /*! Nodes will be given a name that has either been pre-set by a name value pair, 223 or generated based upon a counter unique to the parent node. If you want to 224 give a node a specific name, use setNextName prior to calling startNode. 225 226 The node will then be pushed onto the node stack. */ startNode()227 void startNode() 228 { 229 // generate a name for this new node 230 const auto nameString = itsNodes.top().getValueName(); 231 232 // allocate strings for all of the data in the XML object 233 auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 ); 234 235 // insert into the XML 236 auto node = itsXML.allocate_node( rapidxml::node_element, namePtr, nullptr, nameString.size() ); 237 itsNodes.top().node->append_node( node ); 238 itsNodes.emplace( node ); 239 } 240 241 //! Designates the most recently added node as finished finishNode()242 void finishNode() 243 { 244 itsNodes.pop(); 245 } 246 247 //! Sets the name for the next node created with startNode setNextName(const char * name)248 void setNextName( const char * name ) 249 { 250 itsNodes.top().name = name; 251 } 252 253 //! Saves some data, encoded as a string, into the current top level node 254 /*! The data will be be named with the most recent name if one exists, 255 otherwise it will be given some default delimited value that depends upon 256 the parent node */ 257 template <class T> inline saveValue(T const & value)258 void saveValue( T const & value ) 259 { 260 itsOS.clear(); itsOS.seekp( 0, std::ios::beg ); 261 itsOS << value << std::ends; 262 263 auto strValue = itsOS.str(); 264 265 // itsOS.str() may contain data from previous calls after the first '\0' that was just inserted 266 // and this data is counted in the length call. We make sure to remove that section so that the 267 // whitespace validation is done properly 268 strValue.resize(std::strlen(strValue.c_str())); 269 270 // If the first or last character is a whitespace, add xml:space attribute 271 const auto len = strValue.length(); 272 if ( len > 0 && ( xml_detail::isWhitespace( strValue[0] ) || xml_detail::isWhitespace( strValue[len - 1] ) ) ) 273 { 274 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "xml:space", "preserve" ) ); 275 } 276 277 // allocate strings for all of the data in the XML object 278 auto dataPtr = itsXML.allocate_string(strValue.c_str(), strValue.length() + 1 ); 279 280 // insert into the XML 281 itsNodes.top().node->append_node( itsXML.allocate_node( rapidxml::node_data, nullptr, dataPtr ) ); 282 } 283 284 //! Overload for uint8_t prevents them from being serialized as characters saveValue(uint8_t const & value)285 void saveValue( uint8_t const & value ) 286 { 287 saveValue( static_cast<uint32_t>( value ) ); 288 } 289 290 //! Overload for int8_t prevents them from being serialized as characters saveValue(int8_t const & value)291 void saveValue( int8_t const & value ) 292 { 293 saveValue( static_cast<int32_t>( value ) ); 294 } 295 296 //! Causes the type to be appended as an attribute to the most recently made node if output type is set to true 297 template <class T> inline insertType()298 void insertType() 299 { 300 if( !itsOutputType ) 301 return; 302 303 // generate a name for this new node 304 const auto nameString = util::demangledName<T>(); 305 306 // allocate strings for all of the data in the XML object 307 auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 ); 308 309 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", namePtr ) ); 310 } 311 312 //! Appends an attribute to the current top level node appendAttribute(const char * name,const char * value)313 void appendAttribute( const char * name, const char * value ) 314 { 315 auto namePtr = itsXML.allocate_string( name ); 316 auto valuePtr = itsXML.allocate_string( value ); 317 itsNodes.top().node->append_attribute( itsXML.allocate_attribute( namePtr, valuePtr ) ); 318 } 319 hasSizeAttributes() const320 bool hasSizeAttributes() const { return itsSizeAttributes; } 321 322 protected: 323 //! A struct that contains metadata about a node 324 struct NodeInfo 325 { NodeInfocereal::XMLOutputArchive::NodeInfo326 NodeInfo( rapidxml::xml_node<> * n = nullptr, 327 const char * nm = nullptr ) : 328 node( n ), 329 counter( 0 ), 330 name( nm ) 331 { } 332 333 rapidxml::xml_node<> * node; //!< A pointer to this node 334 size_t counter; //!< The counter for naming child nodes 335 const char * name; //!< The name for the next child node 336 337 //! Gets the name for the next child node created from this node 338 /*! The name will be automatically generated using the counter if 339 a name has not been previously set. If a name has been previously 340 set, that name will be returned only once */ getValueNamecereal::XMLOutputArchive::NodeInfo341 std::string getValueName() 342 { 343 if( name ) 344 { 345 auto n = name; 346 name = nullptr; 347 return {n}; 348 } 349 else 350 return "value" + std::to_string( counter++ ) + "\0"; 351 } 352 }; // NodeInfo 353 354 //! @} 355 356 private: 357 std::ostream & itsStream; //!< The output stream 358 rapidxml::xml_document<> itsXML; //!< The XML document 359 std::stack<NodeInfo> itsNodes; //!< A stack of nodes added to the document 360 std::ostringstream itsOS; //!< Used to format strings internally 361 bool itsOutputType; //!< Controls whether type information is printed 362 bool itsIndent; //!< Controls whether indenting is used 363 bool itsSizeAttributes; //!< Controls whether lists have a size attribute 364 }; // XMLOutputArchive 365 366 // ###################################################################### 367 //! An output archive designed to load data from XML 368 /*! This archive uses RapidXML to build an in memory XML tree of the 369 data in the stream it is given before loading any types serialized. 370 371 As with the output XML archive, the preferred way to use this archive is in 372 an RAII fashion, ensuring its destruction after all data has been read. 373 374 Input XML should have been produced by the XMLOutputArchive. Data can 375 only be added to dynamically sized containers - the input archive will 376 determine their size by looking at the number of child nodes. Data that 377 did not originate from an XMLOutputArchive is not officially supported, 378 but may be possible to use if properly formatted. 379 380 The XMLInputArchive does not require that nodes are loaded in the same 381 order they were saved by XMLOutputArchive. Using name value pairs (NVPs), 382 it is possible to load in an out of order fashion or otherwise skip/select 383 specific nodes to load. 384 385 The default behavior of the input archive is to read sequentially starting 386 with the first node and exploring its children. When a given NVP does 387 not match the read in name for a node, the archive will search for that 388 node at the current level and load it if it exists. After loading an out of 389 order node, the archive will then proceed back to loading sequentially from 390 its new position. 391 392 Consider this simple example where loading of some data is skipped: 393 394 @code{cpp} 395 // imagine the input file has someData(1-9) saved in order at the top level node 396 ar( someData1, someData2, someData3 ); // XML loads in the order it sees in the file 397 ar( cereal::make_nvp( "hello", someData6 ) ); // NVP given does not 398 // match expected NVP name, so we search 399 // for the given NVP and load that value 400 ar( someData7, someData8, someData9 ); // with no NVP given, loading resumes at its 401 // current location, proceeding sequentially 402 @endcode 403 404 \ingroup Archives */ 405 class XMLInputArchive : public InputArchive<XMLInputArchive>, public traits::TextArchive 406 { 407 public: 408 /*! @name Common Functionality 409 Common use cases for directly interacting with an XMLInputArchive */ 410 //! @{ 411 412 //! Construct, reading in from the provided stream 413 /*! Reads in an entire XML document from some stream and parses it as soon 414 as serialization starts 415 416 @param stream The stream to read from. Can be a stringstream or a file. */ XMLInputArchive(std::istream & stream)417 XMLInputArchive( std::istream & stream ) : 418 InputArchive<XMLInputArchive>( this ), 419 itsData( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) 420 { 421 try 422 { 423 itsData.push_back('\0'); // rapidxml will do terrible things without the data being null terminated 424 itsXML.parse<rapidxml::parse_trim_whitespace | rapidxml::parse_no_data_nodes | rapidxml::parse_declaration_node>( reinterpret_cast<char *>( itsData.data() ) ); 425 } 426 catch( rapidxml::parse_error const & ) 427 { 428 //std::cerr << "-----Original-----" << std::endl; 429 //stream.seekg(0); 430 //std::cout << std::string( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) << std::endl; 431 432 //std::cerr << "-----Error-----" << std::endl; 433 //std::cerr << e.what() << std::endl; 434 //std::cerr << e.where<char>() << std::endl; 435 throw Exception("XML Parsing failed - likely due to invalid characters or invalid naming"); 436 } 437 438 // Parse the root 439 auto root = itsXML.first_node( xml_detail::CEREAL_XML_STRING ); 440 if( root == nullptr ) 441 throw Exception("Could not detect cereal root node - likely due to empty or invalid input"); 442 else 443 itsNodes.emplace( root ); 444 } 445 446 ~XMLInputArchive() CEREAL_NOEXCEPT = default; 447 448 //! Loads some binary data, encoded as a base64 string, optionally specified by some name 449 /*! This will automatically start and finish a node to load the data, and can be called directly by 450 users. 451 452 Note that this follows the same ordering rules specified in the class description in regards 453 to loading in/out of order */ loadBinaryValue(void * data,size_t size,const char * name=nullptr)454 void loadBinaryValue( void * data, size_t size, const char * name = nullptr ) 455 { 456 setNextName( name ); 457 startNode(); 458 459 std::string encoded; 460 loadValue( encoded ); 461 462 auto decoded = base64::decode( encoded ); 463 464 if( size != decoded.size() ) 465 throw Exception("Decoded binary data size does not match specified size"); 466 467 std::memcpy( data, decoded.data(), decoded.size() ); 468 469 finishNode(); 470 } 471 472 //! @} 473 /*! @name Internal Functionality 474 Functionality designed for use by those requiring control over the inner mechanisms of 475 the XMLInputArchive */ 476 //! @{ 477 478 //! Prepares to start reading the next node 479 /*! This places the next node to be parsed onto the nodes stack. 480 481 By default our strategy is to start with the document root node and then 482 recursively iterate through all children in the order they show up in the document. 483 We don't need to know NVPs do to this; we'll just blindly load in the order things appear in. 484 485 We check to see if the specified NVP matches what the next automatically loaded node is. If they 486 match, we just continue as normal, going in order. If they don't match, we attempt to find a node 487 named after the NVP that is being loaded. If that NVP does not exist, we throw an exception. */ startNode()488 void startNode() 489 { 490 auto next = itsNodes.top().child; // By default we would move to the next child node 491 auto const expectedName = itsNodes.top().name; // this is the expected name from the NVP, if provided 492 493 // If we were given an NVP name, look for it in the current level of the document. 494 // We only need to do this if either we have exhausted the siblings of the current level or 495 // the NVP name does not match the name of the node we would normally read next 496 if( expectedName && ( next == nullptr || std::strcmp( next->name(), expectedName ) != 0 ) ) 497 { 498 next = itsNodes.top().search( expectedName ); 499 500 if( next == nullptr ) 501 throw Exception("XML Parsing failed - provided NVP (" + std::string(expectedName) + ") not found"); 502 } 503 504 itsNodes.emplace( next ); 505 } 506 507 //! Finishes reading the current node finishNode()508 void finishNode() 509 { 510 // remove current 511 itsNodes.pop(); 512 513 // advance parent 514 itsNodes.top().advance(); 515 516 // Reset name 517 itsNodes.top().name = nullptr; 518 } 519 520 //! Retrieves the current node name 521 //! will return @c nullptr if the node does not have a name getNodeName() const522 const char * getNodeName() const 523 { 524 return itsNodes.top().getChildName(); 525 } 526 527 //! Sets the name for the next node created with startNode setNextName(const char * name)528 void setNextName( const char * name ) 529 { 530 itsNodes.top().name = name; 531 } 532 533 //! Loads a bool from the current top node 534 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 535 std::is_same<T, bool>::value> = traits::sfinae> inline loadValue(T & value)536 void loadValue( T & value ) 537 { 538 std::istringstream is( itsNodes.top().node->value() ); 539 is.setf( std::ios::boolalpha ); 540 is >> value; 541 } 542 543 //! Loads a char (signed or unsigned) from the current top node 544 template <class T, traits::EnableIf<std::is_integral<T>::value, 545 !std::is_same<T, bool>::value, 546 sizeof(T) == sizeof(char)> = traits::sfinae> inline loadValue(T & value)547 void loadValue( T & value ) 548 { 549 value = *reinterpret_cast<T*>( itsNodes.top().node->value() ); 550 } 551 552 //! Load an int8_t from the current top node (ensures we parse entire number) loadValue(int8_t & value)553 void loadValue( int8_t & value ) 554 { 555 int32_t val; loadValue( val ); value = static_cast<int8_t>( val ); 556 } 557 558 //! Load a uint8_t from the current top node (ensures we parse entire number) loadValue(uint8_t & value)559 void loadValue( uint8_t & value ) 560 { 561 uint32_t val; loadValue( val ); value = static_cast<uint8_t>( val ); 562 } 563 564 //! Loads a type best represented as an unsigned long from the current top node 565 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 566 !std::is_same<T, bool>::value, 567 !std::is_same<T, char>::value, 568 !std::is_same<T, unsigned char>::value, 569 sizeof(T) < sizeof(long long)> = traits::sfinae> inline loadValue(T & value)570 void loadValue( T & value ) 571 { 572 value = static_cast<T>( std::stoul( itsNodes.top().node->value() ) ); 573 } 574 575 //! Loads a type best represented as an unsigned long long from the current top node 576 template <class T, traits::EnableIf<std::is_unsigned<T>::value, 577 !std::is_same<T, bool>::value, 578 sizeof(T) >= sizeof(long long)> = traits::sfinae> inline 579 void loadValue( T & value ) 580 { 581 value = static_cast<T>( std::stoull( itsNodes.top().node->value() ) ); 582 } 583 584 //! Loads a type best represented as an int from the current top node 585 template <class T, traits::EnableIf<std::is_signed<T>::value, 586 !std::is_same<T, char>::value, 587 sizeof(T) <= sizeof(int)> = traits::sfinae> inline loadValue(T & value)588 void loadValue( T & value ) 589 { 590 value = static_cast<T>( std::stoi( itsNodes.top().node->value() ) ); 591 } 592 593 //! Loads a type best represented as a long from the current top node 594 template <class T, traits::EnableIf<std::is_signed<T>::value, 595 (sizeof(T) > sizeof(int)), 596 sizeof(T) <= sizeof(long)> = traits::sfinae> inline loadValue(T & value)597 void loadValue( T & value ) 598 { 599 value = static_cast<T>( std::stol( itsNodes.top().node->value() ) ); 600 } 601 602 //! Loads a type best represented as a long long from the current top node 603 template <class T, traits::EnableIf<std::is_signed<T>::value, 604 (sizeof(T) > sizeof(long)), 605 sizeof(T) <= sizeof(long long)> = traits::sfinae> inline loadValue(T & value)606 void loadValue( T & value ) 607 { 608 value = static_cast<T>( std::stoll( itsNodes.top().node->value() ) ); 609 } 610 611 //! Loads a type best represented as a float from the current top node loadValue(float & value)612 void loadValue( float & value ) 613 { 614 try 615 { 616 value = std::stof( itsNodes.top().node->value() ); 617 } 618 catch( std::out_of_range const & ) 619 { 620 // special case for denormalized values 621 std::istringstream is( itsNodes.top().node->value() ); 622 is >> value; 623 if( std::fpclassify( value ) != FP_SUBNORMAL ) 624 throw; 625 } 626 } 627 628 //! Loads a type best represented as a double from the current top node loadValue(double & value)629 void loadValue( double & value ) 630 { 631 try 632 { 633 value = std::stod( itsNodes.top().node->value() ); 634 } 635 catch( std::out_of_range const & ) 636 { 637 // special case for denormalized values 638 std::istringstream is( itsNodes.top().node->value() ); 639 is >> value; 640 if( std::fpclassify( value ) != FP_SUBNORMAL ) 641 throw; 642 } 643 } 644 645 //! Loads a type best represented as a long double from the current top node loadValue(long double & value)646 void loadValue( long double & value ) 647 { 648 try 649 { 650 value = std::stold( itsNodes.top().node->value() ); 651 } 652 catch( std::out_of_range const & ) 653 { 654 // special case for denormalized values 655 std::istringstream is( itsNodes.top().node->value() ); 656 is >> value; 657 if( std::fpclassify( value ) != FP_SUBNORMAL ) 658 throw; 659 } 660 } 661 662 //! Loads a string from the current node from the current top node 663 template<class CharT, class Traits, class Alloc> inline loadValue(std::basic_string<CharT,Traits,Alloc> & str)664 void loadValue( std::basic_string<CharT, Traits, Alloc> & str ) 665 { 666 std::basic_istringstream<CharT, Traits> is( itsNodes.top().node->value() ); 667 668 str.assign( std::istreambuf_iterator<CharT, Traits>( is ), 669 std::istreambuf_iterator<CharT, Traits>() ); 670 } 671 672 //! Loads the size of the current top node 673 template <class T> inline loadSize(T & value)674 void loadSize( T & value ) 675 { 676 value = getNumChildren( itsNodes.top().node ); 677 } 678 679 protected: 680 //! Gets the number of children (usually interpreted as size) for the specified node getNumChildren(rapidxml::xml_node<> * node)681 static size_t getNumChildren( rapidxml::xml_node<> * node ) 682 { 683 size_t size = 0; 684 node = node->first_node(); // get first child 685 686 while( node != nullptr ) 687 { 688 ++size; 689 node = node->next_sibling(); 690 } 691 692 return size; 693 } 694 695 //! A struct that contains metadata about a node 696 /*! Keeps track of some top level node, its number of 697 remaining children, and the current active child node */ 698 struct NodeInfo 699 { NodeInfocereal::XMLInputArchive::NodeInfo700 NodeInfo( rapidxml::xml_node<> * n = nullptr ) : 701 node( n ), 702 child( n->first_node() ), 703 size( XMLInputArchive::getNumChildren( n ) ), 704 name( nullptr ) 705 { } 706 707 //! Advances to the next sibling node of the child 708 /*! If this is the last sibling child will be null after calling */ advancecereal::XMLInputArchive::NodeInfo709 void advance() 710 { 711 if( size > 0 ) 712 { 713 --size; 714 child = child->next_sibling(); 715 } 716 } 717 718 //! Searches for a child with the given name in this node 719 /*! @param searchName The name to search for (must be null terminated) 720 @return The node if found, nullptr otherwise */ searchcereal::XMLInputArchive::NodeInfo721 rapidxml::xml_node<> * search( const char * searchName ) 722 { 723 if( searchName ) 724 { 725 size_t new_size = XMLInputArchive::getNumChildren( node ); 726 const size_t name_size = rapidxml::internal::measure( searchName ); 727 728 for( auto new_child = node->first_node(); new_child != nullptr; new_child = new_child->next_sibling() ) 729 { 730 if( rapidxml::internal::compare( new_child->name(), new_child->name_size(), searchName, name_size, true ) ) 731 { 732 size = new_size; 733 child = new_child; 734 735 return new_child; 736 } 737 --new_size; 738 } 739 } 740 741 return nullptr; 742 } 743 744 //! Returns the actual name of the next child node, if it exists getChildNamecereal::XMLInputArchive::NodeInfo745 const char * getChildName() const 746 { 747 return child ? child->name() : nullptr; 748 } 749 750 rapidxml::xml_node<> * node; //!< A pointer to this node 751 rapidxml::xml_node<> * child; //!< A pointer to its current child 752 size_t size; //!< The remaining number of children for this node 753 const char * name; //!< The NVP name for next child node 754 }; // NodeInfo 755 756 //! @} 757 758 private: 759 std::vector<char> itsData; //!< The raw data loaded 760 rapidxml::xml_document<> itsXML; //!< The XML document 761 std::stack<NodeInfo> itsNodes; //!< A stack of nodes read from the document 762 }; 763 764 // ###################################################################### 765 // XMLArchive prologue and epilogue functions 766 // ###################################################################### 767 768 // ###################################################################### 769 //! Prologue for NVPs for XML output archives 770 /*! NVPs do not start or finish nodes - they just set up the names */ 771 template <class T> inline prologue(XMLOutputArchive &,NameValuePair<T> const &)772 void prologue( XMLOutputArchive &, NameValuePair<T> const & ) 773 { } 774 775 //! Prologue for NVPs for XML input archives 776 template <class T> inline prologue(XMLInputArchive &,NameValuePair<T> const &)777 void prologue( XMLInputArchive &, NameValuePair<T> const & ) 778 { } 779 780 // ###################################################################### 781 //! Epilogue for NVPs for XML output archives 782 /*! NVPs do not start or finish nodes - they just set up the names */ 783 template <class T> inline epilogue(XMLOutputArchive &,NameValuePair<T> const &)784 void epilogue( XMLOutputArchive &, NameValuePair<T> const & ) 785 { } 786 787 //! Epilogue for NVPs for XML input archives 788 template <class T> inline epilogue(XMLInputArchive &,NameValuePair<T> const &)789 void epilogue( XMLInputArchive &, NameValuePair<T> const & ) 790 { } 791 792 // ###################################################################### 793 //! Prologue for deferred data for XML archives 794 /*! Do nothing for the defer wrapper */ 795 template <class T> inline prologue(XMLOutputArchive &,DeferredData<T> const &)796 void prologue( XMLOutputArchive &, DeferredData<T> const & ) 797 { } 798 799 //! Prologue for deferred data for XML archives 800 template <class T> inline prologue(XMLInputArchive &,DeferredData<T> const &)801 void prologue( XMLInputArchive &, DeferredData<T> const & ) 802 { } 803 804 // ###################################################################### 805 //! Epilogue for deferred for XML archives 806 /*! NVPs do not start or finish nodes - they just set up the names */ 807 template <class T> inline epilogue(XMLOutputArchive &,DeferredData<T> const &)808 void epilogue( XMLOutputArchive &, DeferredData<T> const & ) 809 { } 810 811 //! Epilogue for deferred for XML archives 812 /*! Do nothing for the defer wrapper */ 813 template <class T> inline epilogue(XMLInputArchive &,DeferredData<T> const &)814 void epilogue( XMLInputArchive &, DeferredData<T> const & ) 815 { } 816 817 // ###################################################################### 818 //! Prologue for SizeTags for XML output archives 819 /*! SizeTags do not start or finish nodes */ 820 template <class T> inline prologue(XMLOutputArchive & ar,SizeTag<T> const &)821 void prologue( XMLOutputArchive & ar, SizeTag<T> const & ) 822 { 823 if (ar.hasSizeAttributes()) 824 { 825 ar.appendAttribute("size", "dynamic"); 826 } 827 } 828 829 template <class T> inline prologue(XMLInputArchive &,SizeTag<T> const &)830 void prologue( XMLInputArchive &, SizeTag<T> const & ) 831 { } 832 833 //! Epilogue for SizeTags for XML output archives 834 /*! SizeTags do not start or finish nodes */ 835 template <class T> inline epilogue(XMLOutputArchive &,SizeTag<T> const &)836 void epilogue( XMLOutputArchive &, SizeTag<T> const & ) 837 { } 838 839 template <class T> inline epilogue(XMLInputArchive &,SizeTag<T> const &)840 void epilogue( XMLInputArchive &, SizeTag<T> const & ) 841 { } 842 843 // ###################################################################### 844 //! Prologue for all other types for XML output archives (except minimal types) 845 /*! Starts a new node, named either automatically or by some NVP, 846 that may be given data by the type about to be archived 847 848 Minimal types do not start or end nodes */ 849 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value || 850 traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline prologue(XMLOutputArchive & ar,T const &)851 void prologue( XMLOutputArchive & ar, T const & ) 852 { 853 ar.startNode(); 854 ar.insertType<T>(); 855 } 856 857 //! Prologue for all other types for XML input archives (except minimal types) 858 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value || 859 traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline prologue(XMLInputArchive & ar,T const &)860 void prologue( XMLInputArchive & ar, T const & ) 861 { 862 ar.startNode(); 863 } 864 865 // ###################################################################### 866 //! Epilogue for all other types other for XML output archives (except minimal types) 867 /*! Finishes the node created in the prologue 868 869 Minimal types do not start or end nodes */ 870 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value || 871 traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline epilogue(XMLOutputArchive & ar,T const &)872 void epilogue( XMLOutputArchive & ar, T const & ) 873 { 874 ar.finishNode(); 875 } 876 877 //! Epilogue for all other types other for XML output archives (except minimal types) 878 template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value || 879 traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline epilogue(XMLInputArchive & ar,T const &)880 void epilogue( XMLInputArchive & ar, T const & ) 881 { 882 ar.finishNode(); 883 } 884 885 // ###################################################################### 886 // Common XMLArchive serialization functions 887 // ###################################################################### 888 889 //! Saving NVP types to XML 890 template <class T> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,NameValuePair<T> const & t)891 void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive & ar, NameValuePair<T> const & t ) 892 { 893 ar.setNextName( t.name ); 894 ar( t.value ); 895 } 896 897 //! Loading NVP types from XML 898 template <class T> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,NameValuePair<T> & t)899 void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, NameValuePair<T> & t ) 900 { 901 ar.setNextName( t.name ); 902 ar( t.value ); 903 } 904 905 // ###################################################################### 906 //! Saving SizeTags to XML 907 template <class T> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive &,SizeTag<T> const &)908 void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive &, SizeTag<T> const & ) 909 { } 910 911 //! Loading SizeTags from XML 912 template <class T> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,SizeTag<T> & st)913 void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, SizeTag<T> & st ) 914 { 915 ar.loadSize( st.size ); 916 } 917 918 // ###################################################################### 919 //! Saving for POD types to xml 920 template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,T const & t)921 void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, T const & t) 922 { 923 ar.saveValue( t ); 924 } 925 926 //! Loading for POD types from xml 927 template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,T & t)928 void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, T & t) 929 { 930 ar.loadValue( t ); 931 } 932 933 // ###################################################################### 934 //! saving string to xml 935 template<class CharT, class Traits, class Alloc> inline CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar,std::basic_string<CharT,Traits,Alloc> const & str)936 void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str) 937 { 938 ar.saveValue( str ); 939 } 940 941 //! loading string from xml 942 template<class CharT, class Traits, class Alloc> inline CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar,std::basic_string<CharT,Traits,Alloc> & str)943 void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str) 944 { 945 ar.loadValue( str ); 946 } 947 } // namespace cereal 948 949 // register archives for polymorphic support 950 CEREAL_REGISTER_ARCHIVE(cereal::XMLOutputArchive) 951 CEREAL_REGISTER_ARCHIVE(cereal::XMLInputArchive) 952 953 // tie input and output archives together 954 CEREAL_SETUP_ARCHIVE_TRAITS(cereal::XMLInputArchive, cereal::XMLOutputArchive) 955 956 #endif // CEREAL_ARCHIVES_XML_HPP_ 957