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