Line data Source code
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 the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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
61 5026293 : inline bool isWhitespace( char c )
62 : {
63 5026293 : 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
110 3748 : 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 */
117 3748 : explicit Options( int precision_ = std::numeric_limits<double>::max_digits10,
118 : bool indent_ = true,
119 : bool outputType_ = false,
120 3748 : bool sizeAttributes_ = true ) :
121 : itsPrecision( precision_ ),
122 : itsIndent( indent_ ),
123 : itsOutputType( outputType_ ),
124 3748 : itsSizeAttributes( sizeAttributes_ )
125 3748 : { }
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
140 : Options & precision( int value ){ itsPrecision = value; return * this; }
141 : //! Whether to indent each line of XML
142 : Options & indent( bool enable ){ itsIndent = enable; return *this; }
143 : //! Whether to output the type of each serialized object as an attribute
144 : Options & outputType( bool enable ){ itsOutputType = enable; return *this; }
145 : //! Whether dynamically sized containers (e.g. vector) output the size=dynamic attribute
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 */
163 3748 : XMLOutputArchive( std::ostream & stream, Options const & options = Options::Default() ) :
164 : OutputArchive<XMLOutputArchive>(this),
165 : itsStream(stream),
166 3748 : itsOutputType( options.itsOutputType ),
167 3748 : itsIndent( options.itsIndent ),
168 3748 : itsSizeAttributes(options.itsSizeAttributes)
169 : {
170 : // rapidxml will delete all allocations when xml_document is cleared
171 3748 : auto node = itsXML.allocate_node( rapidxml::node_declaration );
172 3748 : node->append_attribute( itsXML.allocate_attribute( "version", "1.0" ) );
173 3748 : node->append_attribute( itsXML.allocate_attribute( "encoding", "utf-8" ) );
174 3748 : itsXML.append_node( node );
175 :
176 : // allocate root node
177 3748 : auto root = itsXML.allocate_node( rapidxml::node_element, xml_detail::CEREAL_XML_STRING );
178 3748 : itsXML.append_node( root );
179 3748 : itsNodes.emplace( root );
180 :
181 : // set attributes on the streams
182 3748 : itsStream << std::boolalpha;
183 3748 : itsStream.precision( options.itsPrecision );
184 3748 : itsOS << std::boolalpha;
185 3748 : itsOS.precision( options.itsPrecision );
186 3748 : }
187 :
188 : //! Destructor, flushes the XML
189 3748 : ~XMLOutputArchive() CEREAL_NOEXCEPT
190 3748 : {
191 3748 : const int flags = itsIndent ? 0x0 : rapidxml::print_no_indenting;
192 3748 : rapidxml::print( itsStream, itsXML, flags );
193 3748 : itsXML.clear();
194 3748 : }
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. */
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. */
227 3874234 : void startNode()
228 : {
229 : // generate a name for this new node
230 7748468 : const auto nameString = itsNodes.top().getValueName();
231 :
232 : // allocate strings for all of the data in the XML object
233 3874234 : auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 );
234 :
235 : // insert into the XML
236 3874234 : auto node = itsXML.allocate_node( rapidxml::node_element, namePtr, nullptr, nameString.size() );
237 3874234 : itsNodes.top().node->append_node( node );
238 3874234 : itsNodes.emplace( node );
239 3874234 : }
240 :
241 : //! Designates the most recently added node as finished
242 3874234 : void finishNode()
243 : {
244 3874234 : itsNodes.pop();
245 3874234 : }
246 :
247 : //! Sets the name for the next node created with startNode
248 742617 : void setNextName( const char * name )
249 : {
250 742617 : itsNodes.top().name = name;
251 742617 : }
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
258 2513356 : void saveValue( T const & value )
259 : {
260 2513356 : itsOS.clear(); itsOS.seekp( 0, std::ios::beg );
261 2513356 : itsOS << value << std::ends;
262 :
263 5026712 : 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 2513356 : strValue.resize(std::strlen(strValue.c_str()));
269 :
270 : // If the first or last character is a whitespace, add xml:space attribute
271 2513356 : const auto len = strValue.length();
272 2513356 : if ( len > 0 && ( xml_detail::isWhitespace( strValue[0] ) || xml_detail::isWhitespace( strValue[len - 1] ) ) )
273 : {
274 21 : 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 2513356 : auto dataPtr = itsXML.allocate_string(strValue.c_str(), strValue.length() + 1 );
279 :
280 : // insert into the XML
281 2513356 : itsNodes.top().node->append_node( itsXML.allocate_node( rapidxml::node_data, nullptr, dataPtr ) );
282 2513356 : }
283 :
284 : //! Overload for uint8_t prevents them from being serialized as characters
285 32011 : void saveValue( uint8_t const & value )
286 : {
287 32011 : saveValue( static_cast<uint32_t>( value ) );
288 32011 : }
289 :
290 : //! Overload for int8_t prevents them from being serialized as characters
291 56747 : void saveValue( int8_t const & value )
292 : {
293 56747 : saveValue( static_cast<int32_t>( value ) );
294 56747 : }
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
298 3874234 : void insertType()
299 : {
300 3874234 : if( !itsOutputType )
301 3874234 : return;
302 :
303 : // generate a name for this new node
304 0 : const auto nameString = util::demangledName<T>();
305 :
306 : // allocate strings for all of the data in the XML object
307 0 : auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 );
308 :
309 0 : itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", namePtr ) );
310 : }
311 :
312 : //! Appends an attribute to the current top level node
313 10300 : void appendAttribute( const char * name, const char * value )
314 : {
315 10300 : auto namePtr = itsXML.allocate_string( name );
316 10300 : auto valuePtr = itsXML.allocate_string( value );
317 10300 : itsNodes.top().node->append_attribute( itsXML.allocate_attribute( namePtr, valuePtr ) );
318 10300 : }
319 :
320 10300 : bool hasSizeAttributes() const { return itsSizeAttributes; }
321 :
322 : protected:
323 : //! A struct that contains metadata about a node
324 : struct NodeInfo
325 : {
326 3877982 : NodeInfo( rapidxml::xml_node<> * n = nullptr,
327 3877982 : const char * nm = nullptr ) :
328 : node( n ),
329 : counter( 0 ),
330 3877982 : name( nm )
331 3877982 : { }
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 */
341 3874234 : std::string getValueName()
342 : {
343 3874234 : if( name )
344 : {
345 742617 : auto n = name;
346 742617 : name = nullptr;
347 742617 : return {n};
348 : }
349 : else
350 6263234 : 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. */
417 3848 : XMLInputArchive( std::istream & stream ) :
418 : InputArchive<XMLInputArchive>( this ),
419 3848 : itsData( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() )
420 : {
421 : try
422 : {
423 3848 : itsData.push_back('\0'); // rapidxml will do terrible things without the data being null terminated
424 3848 : itsXML.parse<rapidxml::parse_trim_whitespace | rapidxml::parse_no_data_nodes | rapidxml::parse_declaration_node>( reinterpret_cast<char *>( itsData.data() ) );
425 : }
426 0 : 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 0 : throw Exception("XML Parsing failed - likely due to invalid characters or invalid naming");
436 : }
437 :
438 : // Parse the root
439 3848 : auto root = itsXML.first_node( xml_detail::CEREAL_XML_STRING );
440 3848 : if( root == nullptr )
441 0 : throw Exception("Could not detect cereal root node - likely due to empty or invalid input");
442 : else
443 3848 : itsNodes.emplace( root );
444 3848 : }
445 :
446 3848 : ~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 */
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. */
488 3874734 : void startNode()
489 : {
490 3874734 : auto next = itsNodes.top().child; // By default we would move to the next child node
491 3874734 : 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 3874734 : if( expectedName && ( next == nullptr || std::strcmp( next->name(), expectedName ) != 0 ) )
497 : {
498 900 : next = itsNodes.top().search( expectedName );
499 :
500 900 : if( next == nullptr )
501 0 : throw Exception("XML Parsing failed - provided NVP (" + std::string(expectedName) + ") not found");
502 : }
503 :
504 3874734 : itsNodes.emplace( next );
505 3874734 : }
506 :
507 : //! Finishes reading the current node
508 3874434 : void finishNode()
509 : {
510 : // remove current
511 3874434 : itsNodes.pop();
512 :
513 : // advance parent
514 3874434 : itsNodes.top().advance();
515 :
516 : // Reset name
517 3874434 : itsNodes.top().name = nullptr;
518 3874434 : }
519 :
520 : //! Retrieves the current node name
521 : //! will return @c nullptr if the node does not have a name
522 : const char * getNodeName() const
523 : {
524 : return itsNodes.top().getChildName();
525 : }
526 :
527 : //! Sets the name for the next node created with startNode
528 742717 : void setNextName( const char * name )
529 : {
530 742717 : itsNodes.top().name = name;
531 742717 : }
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
536 11408 : void loadValue( T & value )
537 : {
538 34224 : std::istringstream is( itsNodes.top().node->value() );
539 11408 : is.setf( std::ios::boolalpha );
540 11408 : is >> value;
541 11408 : }
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
547 311 : void loadValue( T & value )
548 : {
549 311 : value = *reinterpret_cast<T*>( itsNodes.top().node->value() );
550 311 : }
551 :
552 : //! Load an int8_t from the current top node (ensures we parse entire number)
553 56747 : void loadValue( int8_t & value )
554 : {
555 56747 : int32_t val; loadValue( val ); value = static_cast<int8_t>( val );
556 56747 : }
557 :
558 : //! Load a uint8_t from the current top node (ensures we parse entire number)
559 32111 : void loadValue( uint8_t & value )
560 : {
561 32111 : uint32_t val; loadValue( val ); value = static_cast<uint8_t>( val );
562 32111 : }
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
570 138969 : void loadValue( T & value )
571 : {
572 138969 : value = static_cast<T>( std::stoul( itsNodes.top().node->value() ) );
573 138969 : }
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 1853 : void loadValue( T & value )
580 : {
581 1853 : value = static_cast<T>( std::stoull( itsNodes.top().node->value() ) );
582 1853 : }
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
588 2285272 : void loadValue( T & value )
589 : {
590 2285272 : value = static_cast<T>( std::stoi( itsNodes.top().node->value() ) );
591 2285272 : }
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
597 1401 : void loadValue( T & value )
598 : {
599 1401 : value = static_cast<T>( std::stol( itsNodes.top().node->value() ) );
600 1401 : }
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
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
612 800 : void loadValue( float & value )
613 : {
614 : try
615 : {
616 800 : value = std::stof( itsNodes.top().node->value() );
617 : }
618 0 : catch( std::out_of_range const & )
619 : {
620 : // special case for denormalized values
621 0 : std::istringstream is( itsNodes.top().node->value() );
622 0 : is >> value;
623 0 : if( std::fpclassify( value ) != FP_SUBNORMAL )
624 0 : throw;
625 : }
626 800 : }
627 :
628 : //! Loads a type best represented as a double from the current top node
629 1403 : void loadValue( double & value )
630 : {
631 : try
632 : {
633 1403 : value = std::stod( itsNodes.top().node->value() );
634 : }
635 0 : catch( std::out_of_range const & )
636 : {
637 : // special case for denormalized values
638 0 : std::istringstream is( itsNodes.top().node->value() );
639 0 : is >> value;
640 0 : if( std::fpclassify( value ) != FP_SUBNORMAL )
641 0 : throw;
642 : }
643 1403 : }
644 :
645 : //! Loads a type best represented as a long double from the current top node
646 300 : void loadValue( long double & value )
647 : {
648 : try
649 : {
650 300 : value = std::stold( itsNodes.top().node->value() );
651 : }
652 0 : catch( std::out_of_range const & )
653 : {
654 : // special case for denormalized values
655 0 : std::istringstream is( itsNodes.top().node->value() );
656 0 : is >> value;
657 0 : if( std::fpclassify( value ) != FP_SUBNORMAL )
658 0 : throw;
659 : }
660 300 : }
661 :
662 : //! Loads a string from the current node from the current top node
663 : template<class CharT, class Traits, class Alloc> inline
664 71839 : void loadValue( std::basic_string<CharT, Traits, Alloc> & str )
665 : {
666 143678 : std::basic_istringstream<CharT, Traits> is( itsNodes.top().node->value() );
667 :
668 71839 : str.assign( std::istreambuf_iterator<CharT, Traits>( is ),
669 : std::istreambuf_iterator<CharT, Traits>() );
670 71839 : }
671 :
672 : //! Loads the size of the current top node
673 : template <class T> inline
674 10300 : void loadSize( T & value )
675 : {
676 10300 : value = getNumChildren( itsNodes.top().node );
677 10300 : }
678 :
679 : protected:
680 : //! Gets the number of children (usually interpreted as size) for the specified node
681 3889782 : static size_t getNumChildren( rapidxml::xml_node<> * node )
682 : {
683 3889782 : size_t size = 0;
684 3889782 : node = node->first_node(); // get first child
685 :
686 8908705 : while( node != nullptr )
687 : {
688 5018926 : ++size;
689 5018926 : node = node->next_sibling();
690 : }
691 :
692 3889782 : 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 : {
700 3878582 : NodeInfo( rapidxml::xml_node<> * n = nullptr ) :
701 : node( n ),
702 7757164 : child( n->first_node() ),
703 7757164 : size( XMLInputArchive::getNumChildren( n ) ),
704 3878582 : name( nullptr )
705 3878582 : { }
706 :
707 : //! Advances to the next sibling node of the child
708 : /*! If this is the last sibling child will be null after calling */
709 3874434 : void advance()
710 : {
711 3874434 : if( size > 0 )
712 : {
713 3874434 : --size;
714 3874434 : child = child->next_sibling();
715 : }
716 3874434 : }
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 */
721 900 : rapidxml::xml_node<> * search( const char * searchName )
722 : {
723 900 : if( searchName )
724 : {
725 900 : size_t new_size = XMLInputArchive::getNumChildren( node );
726 900 : const size_t name_size = rapidxml::internal::measure( searchName );
727 :
728 3100 : for( auto new_child = node->first_node(); new_child != nullptr; new_child = new_child->next_sibling() )
729 : {
730 3100 : if( rapidxml::internal::compare( new_child->name(), new_child->name_size(), searchName, name_size, true ) )
731 : {
732 900 : size = new_size;
733 900 : child = new_child;
734 :
735 900 : return new_child;
736 : }
737 2200 : --new_size;
738 : }
739 : }
740 :
741 0 : return nullptr;
742 : }
743 :
744 : //! Returns the actual name of the next child node, if it exists
745 : 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
772 742617 : void prologue( XMLOutputArchive &, NameValuePair<T> const & )
773 742617 : { }
774 :
775 : //! Prologue for NVPs for XML input archives
776 : template <class T> inline
777 742717 : void prologue( XMLInputArchive &, NameValuePair<T> const & )
778 742717 : { }
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
784 742617 : void epilogue( XMLOutputArchive &, NameValuePair<T> const & )
785 742617 : { }
786 :
787 : //! Epilogue for NVPs for XML input archives
788 : template <class T> inline
789 742517 : void epilogue( XMLInputArchive &, NameValuePair<T> const & )
790 742517 : { }
791 :
792 : // ######################################################################
793 : //! Prologue for deferred data for XML archives
794 : /*! Do nothing for the defer wrapper */
795 : template <class T> inline
796 1000 : void prologue( XMLOutputArchive &, DeferredData<T> const & )
797 1000 : { }
798 :
799 : //! Prologue for deferred data for XML archives
800 : template <class T> inline
801 1000 : void prologue( XMLInputArchive &, DeferredData<T> const & )
802 1000 : { }
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
808 1000 : void epilogue( XMLOutputArchive &, DeferredData<T> const & )
809 1000 : { }
810 :
811 : //! Epilogue for deferred for XML archives
812 : /*! Do nothing for the defer wrapper */
813 : template <class T> inline
814 1000 : void epilogue( XMLInputArchive &, DeferredData<T> const & )
815 1000 : { }
816 :
817 : // ######################################################################
818 : //! Prologue for SizeTags for XML output archives
819 : /*! SizeTags do not start or finish nodes */
820 : template <class T> inline
821 10300 : void prologue( XMLOutputArchive & ar, SizeTag<T> const & )
822 : {
823 10300 : if (ar.hasSizeAttributes())
824 : {
825 10300 : ar.appendAttribute("size", "dynamic");
826 : }
827 10300 : }
828 :
829 : template <class T> inline
830 10300 : void prologue( XMLInputArchive &, SizeTag<T> const & )
831 10300 : { }
832 :
833 : //! Epilogue for SizeTags for XML output archives
834 : /*! SizeTags do not start or finish nodes */
835 : template <class T> inline
836 10300 : void epilogue( XMLOutputArchive &, SizeTag<T> const & )
837 10300 : { }
838 :
839 : template <class T> inline
840 10300 : void epilogue( XMLInputArchive &, SizeTag<T> const & )
841 10300 : { }
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
851 3874234 : void prologue( XMLOutputArchive & ar, T const & )
852 : {
853 3874234 : ar.startNode();
854 3874234 : ar.insertType<T>();
855 3874234 : }
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
860 3874734 : void prologue( XMLInputArchive & ar, T const & )
861 : {
862 3874734 : ar.startNode();
863 3874734 : }
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
872 3874234 : void epilogue( XMLOutputArchive & ar, T const & )
873 : {
874 3874234 : ar.finishNode();
875 3874234 : }
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
880 3874434 : void epilogue( XMLInputArchive & ar, T const & )
881 : {
882 3874434 : ar.finishNode();
883 3874434 : }
884 :
885 : // ######################################################################
886 : // Common XMLArchive serialization functions
887 : // ######################################################################
888 :
889 : //! Saving NVP types to XML
890 : template <class T> inline
891 742617 : void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive & ar, NameValuePair<T> const & t )
892 : {
893 742617 : ar.setNextName( t.name );
894 742617 : ar( t.value );
895 742617 : }
896 :
897 : //! Loading NVP types from XML
898 : template <class T> inline
899 742717 : void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, NameValuePair<T> & t )
900 : {
901 742717 : ar.setNextName( t.name );
902 742717 : ar( t.value );
903 742517 : }
904 :
905 : // ######################################################################
906 : //! Saving SizeTags to XML
907 : template <class T> inline
908 10300 : void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive &, SizeTag<T> const & )
909 10300 : { }
910 :
911 : //! Loading SizeTags from XML
912 : template <class T> inline
913 10300 : void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, SizeTag<T> & st )
914 : {
915 10300 : ar.loadSize( st.size );
916 10300 : }
917 :
918 : // ######################################################################
919 : //! Saving for POD types to xml
920 : template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline
921 2441517 : void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, T const & t)
922 : {
923 2441517 : ar.saveValue( t );
924 2441517 : }
925 :
926 : //! Loading for POD types from xml
927 : template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline
928 2441717 : void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, T & t)
929 : {
930 2441717 : ar.loadValue( t );
931 2441717 : }
932 :
933 : // ######################################################################
934 : //! saving string to xml
935 : template<class CharT, class Traits, class Alloc> inline
936 71839 : void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str)
937 : {
938 71839 : ar.saveValue( str );
939 71839 : }
940 :
941 : //! loading string from xml
942 : template<class CharT, class Traits, class Alloc> inline
943 71839 : void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str)
944 : {
945 71839 : ar.loadValue( str );
946 71839 : }
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_
|