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