123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 |
- /*! \file xml.hpp
- \brief XML input and output archives */
- /*
- Copyright (c) 2014, Randolph Voorhies, Shane Grant
- All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the copyright holder nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- #ifndef CEREAL_ARCHIVES_XML_HPP_
- #define CEREAL_ARCHIVES_XML_HPP_
- #include "cereal/cereal.hpp"
- #include "cereal/details/util.hpp"
- #include "cereal/external/rapidxml/rapidxml.hpp"
- #include "cereal/external/rapidxml/rapidxml_print.hpp"
- #include "cereal/external/base64.hpp"
- #include <sstream>
- #include <stack>
- #include <vector>
- #include <limits>
- #include <string>
- #include <cstring>
- #include <cmath>
- namespace cereal
- {
- namespace xml_detail
- {
- #ifndef CEREAL_XML_STRING_VALUE
- //! The default name for the root node in a cereal xml archive.
- /*! You can define CEREAL_XML_STRING_VALUE to be different assuming you do so
- before this file is included. */
- #define CEREAL_XML_STRING_VALUE "cereal"
- #endif // CEREAL_XML_STRING_VALUE
- //! The name given to the root node in a cereal xml archive
- static const char * CEREAL_XML_STRING = CEREAL_XML_STRING_VALUE;
- //! Returns true if the character is whitespace
- inline bool isWhitespace( char c )
- {
- return c == ' ' || c == '\t' || c == '\n' || c == '\r';
- }
- }
- // ######################################################################
- //! An output archive designed to save data to XML
- /*! This archive uses RapidXML to build an in memory XML tree of the
- data it serializes before outputting it to its stream upon destruction.
- This archive should be used in an RAII fashion, letting
- the automatic destruction of the object cause the flush to its stream.
- XML archives provides a human readable output but at decreased
- performance (both in time and space) compared to binary archives.
- XML benefits greatly from name-value pairs, which if present, will
- name the nodes in the output. If these are not present, each level
- of the output tree will be given an automatically generated delimited name.
- The precision of the output archive controls the number of decimals output
- for floating point numbers and should be sufficiently large (i.e. at least 20)
- if there is a desire to have binary equality between the numbers output and
- those read in. In general you should expect a loss of precision when going
- from floating point to text and back.
- XML archives can optionally print the type of everything they serialize, which
- adds an attribute to each node.
- XML archives do not output the size information for any dynamically sized structure
- and instead infer it from the number of children for a node. This means that data
- can be hand edited for dynamic sized structures and will still be readable. This
- is accomplished through the cereal::SizeTag object, which will also add an attribute
- to its parent field.
- \ingroup Archives */
- class XMLOutputArchive : public OutputArchive<XMLOutputArchive>, public traits::TextArchive
- {
- public:
- /*! @name Common Functionality
- Common use cases for directly interacting with an XMLOutputArchive */
- //! @{
- //! A class containing various advanced options for the XML archive
- /*! Options can either be directly passed to the constructor, or chained using the
- modifier functions for an interface analogous to named parameters */
- class Options
- {
- public:
- //! Default options
- static Options Default(){ return Options(); }
- //! Specify specific options for the XMLOutputArchive
- /*! @param precision_ The precision used for floating point numbers
- @param indent_ Whether to indent each line of XML
- @param outputType_ Whether to output the type of each serialized object as an attribute
- @param sizeAttributes_ Whether dynamically sized containers output the size=dynamic attribute */
- explicit Options( int precision_ = std::numeric_limits<double>::max_digits10,
- bool indent_ = true,
- bool outputType_ = false,
- bool sizeAttributes_ = true ) :
- itsPrecision( precision_ ),
- itsIndent( indent_ ),
- itsOutputType( outputType_ ),
- itsSizeAttributes( sizeAttributes_ )
- { }
- /*! @name Option Modifiers
- An interface for setting option settings analogous to named parameters.
- @code{cpp}
- cereal::XMLOutputArchive ar( myStream,
- cereal::XMLOutputArchive::Options()
- .indent(true)
- .sizeAttributes(false) );
- @endcode
- */
- //! @{
- //! Sets the precision used for floating point numbers
- Options & precision( int value ){ itsPrecision = value; return * this; }
- //! Whether to indent each line of XML
- Options & indent( bool enable ){ itsIndent = enable; return *this; }
- //! Whether to output the type of each serialized object as an attribute
- Options & outputType( bool enable ){ itsOutputType = enable; return *this; }
- //! Whether dynamically sized containers (e.g. vector) output the size=dynamic attribute
- Options & sizeAttributes( bool enable ){ itsSizeAttributes = enable; return *this; }
- //! @}
- private:
- friend class XMLOutputArchive;
- int itsPrecision;
- bool itsIndent;
- bool itsOutputType;
- bool itsSizeAttributes;
- };
- //! Construct, outputting to the provided stream upon destruction
- /*! @param stream The stream to output to. Note that XML is only guaranteed to flush
- its output to the stream upon destruction.
- @param options The XML specific options to use. See the Options struct
- for the values of default parameters */
- XMLOutputArchive( std::ostream & stream, Options const & options = Options::Default() ) :
- OutputArchive<XMLOutputArchive>(this),
- itsStream(stream),
- itsOutputType( options.itsOutputType ),
- itsIndent( options.itsIndent ),
- itsSizeAttributes(options.itsSizeAttributes)
- {
- // rapidxml will delete all allocations when xml_document is cleared
- auto node = itsXML.allocate_node( rapidxml::node_declaration );
- node->append_attribute( itsXML.allocate_attribute( "version", "1.0" ) );
- node->append_attribute( itsXML.allocate_attribute( "encoding", "utf-8" ) );
- itsXML.append_node( node );
- // allocate root node
- auto root = itsXML.allocate_node( rapidxml::node_element, xml_detail::CEREAL_XML_STRING );
- itsXML.append_node( root );
- itsNodes.emplace( root );
- // set attributes on the streams
- itsStream << std::boolalpha;
- itsStream.precision( options.itsPrecision );
- itsOS << std::boolalpha;
- itsOS.precision( options.itsPrecision );
- }
- //! Destructor, flushes the XML
- ~XMLOutputArchive() CEREAL_NOEXCEPT
- {
- const int flags = itsIndent ? 0x0 : rapidxml::print_no_indenting;
- rapidxml::print( itsStream, itsXML, flags );
- itsXML.clear();
- }
- //! Saves some binary data, encoded as a base64 string, with an optional name
- /*! This can be called directly by users and it will automatically create a child node for
- the current XML node, populate it with a base64 encoded string, and optionally name
- it. The node will be finished after it has been populated. */
- void saveBinaryValue( const void * data, size_t size, const char * name = nullptr )
- {
- itsNodes.top().name = name;
- startNode();
- auto base64string = base64::encode( reinterpret_cast<const unsigned char *>( data ), size );
- saveValue( base64string );
- if( itsOutputType )
- itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", "cereal binary data" ) );
- finishNode();
- }
- //! @}
- /*! @name Internal Functionality
- Functionality designed for use by those requiring control over the inner mechanisms of
- the XMLOutputArchive */
- //! @{
- //! Creates a new node that is a child of the node at the top of the stack
- /*! Nodes will be given a name that has either been pre-set by a name value pair,
- or generated based upon a counter unique to the parent node. If you want to
- give a node a specific name, use setNextName prior to calling startNode.
- The node will then be pushed onto the node stack. */
- void startNode()
- {
- // generate a name for this new node
- const auto nameString = itsNodes.top().getValueName();
- // allocate strings for all of the data in the XML object
- auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 );
- // insert into the XML
- auto node = itsXML.allocate_node( rapidxml::node_element, namePtr, nullptr, nameString.size() );
- itsNodes.top().node->append_node( node );
- itsNodes.emplace( node );
- }
- //! Designates the most recently added node as finished
- void finishNode()
- {
- itsNodes.pop();
- }
- //! Sets the name for the next node created with startNode
- void setNextName( const char * name )
- {
- itsNodes.top().name = name;
- }
- //! Saves some data, encoded as a string, into the current top level node
- /*! The data will be be named with the most recent name if one exists,
- otherwise it will be given some default delimited value that depends upon
- the parent node */
- template <class T> inline
- void saveValue( T const & value )
- {
- itsOS.clear(); itsOS.seekp( 0, std::ios::beg );
- itsOS << value << std::ends;
- auto strValue = itsOS.str();
- // itsOS.str() may contain data from previous calls after the first '\0' that was just inserted
- // and this data is counted in the length call. We make sure to remove that section so that the
- // whitespace validation is done properly
- strValue.resize(std::strlen(strValue.c_str()));
- // If the first or last character is a whitespace, add xml:space attribute
- const auto len = strValue.length();
- if ( len > 0 && ( xml_detail::isWhitespace( strValue[0] ) || xml_detail::isWhitespace( strValue[len - 1] ) ) )
- {
- itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "xml:space", "preserve" ) );
- }
- // allocate strings for all of the data in the XML object
- auto dataPtr = itsXML.allocate_string(strValue.c_str(), strValue.length() + 1 );
- // insert into the XML
- itsNodes.top().node->append_node( itsXML.allocate_node( rapidxml::node_data, nullptr, dataPtr ) );
- }
- //! Overload for uint8_t prevents them from being serialized as characters
- void saveValue( uint8_t const & value )
- {
- saveValue( static_cast<uint32_t>( value ) );
- }
- //! Overload for int8_t prevents them from being serialized as characters
- void saveValue( int8_t const & value )
- {
- saveValue( static_cast<int32_t>( value ) );
- }
- //! Causes the type to be appended as an attribute to the most recently made node if output type is set to true
- template <class T> inline
- void insertType()
- {
- if( !itsOutputType )
- return;
- // generate a name for this new node
- const auto nameString = util::demangledName<T>();
- // allocate strings for all of the data in the XML object
- auto namePtr = itsXML.allocate_string( nameString.data(), nameString.length() + 1 );
- itsNodes.top().node->append_attribute( itsXML.allocate_attribute( "type", namePtr ) );
- }
- //! Appends an attribute to the current top level node
- void appendAttribute( const char * name, const char * value )
- {
- auto namePtr = itsXML.allocate_string( name );
- auto valuePtr = itsXML.allocate_string( value );
- itsNodes.top().node->append_attribute( itsXML.allocate_attribute( namePtr, valuePtr ) );
- }
- bool hasSizeAttributes() const { return itsSizeAttributes; }
- protected:
- //! A struct that contains metadata about a node
- struct NodeInfo
- {
- NodeInfo( rapidxml::xml_node<> * n = nullptr,
- const char * nm = nullptr ) :
- node( n ),
- counter( 0 ),
- name( nm )
- { }
- rapidxml::xml_node<> * node; //!< A pointer to this node
- size_t counter; //!< The counter for naming child nodes
- const char * name; //!< The name for the next child node
- //! Gets the name for the next child node created from this node
- /*! The name will be automatically generated using the counter if
- a name has not been previously set. If a name has been previously
- set, that name will be returned only once */
- std::string getValueName()
- {
- if( name )
- {
- auto n = name;
- name = nullptr;
- return {n};
- }
- else
- return "value" + std::to_string( counter++ ) + "\0";
- }
- }; // NodeInfo
- //! @}
- private:
- std::ostream & itsStream; //!< The output stream
- rapidxml::xml_document<> itsXML; //!< The XML document
- std::stack<NodeInfo> itsNodes; //!< A stack of nodes added to the document
- std::ostringstream itsOS; //!< Used to format strings internally
- bool itsOutputType; //!< Controls whether type information is printed
- bool itsIndent; //!< Controls whether indenting is used
- bool itsSizeAttributes; //!< Controls whether lists have a size attribute
- }; // XMLOutputArchive
- // ######################################################################
- //! An output archive designed to load data from XML
- /*! This archive uses RapidXML to build an in memory XML tree of the
- data in the stream it is given before loading any types serialized.
- As with the output XML archive, the preferred way to use this archive is in
- an RAII fashion, ensuring its destruction after all data has been read.
- Input XML should have been produced by the XMLOutputArchive. Data can
- only be added to dynamically sized containers - the input archive will
- determine their size by looking at the number of child nodes. Data that
- did not originate from an XMLOutputArchive is not officially supported,
- but may be possible to use if properly formatted.
- The XMLInputArchive does not require that nodes are loaded in the same
- order they were saved by XMLOutputArchive. Using name value pairs (NVPs),
- it is possible to load in an out of order fashion or otherwise skip/select
- specific nodes to load.
- The default behavior of the input archive is to read sequentially starting
- with the first node and exploring its children. When a given NVP does
- not match the read in name for a node, the archive will search for that
- node at the current level and load it if it exists. After loading an out of
- order node, the archive will then proceed back to loading sequentially from
- its new position.
- Consider this simple example where loading of some data is skipped:
- @code{cpp}
- // imagine the input file has someData(1-9) saved in order at the top level node
- ar( someData1, someData2, someData3 ); // XML loads in the order it sees in the file
- ar( cereal::make_nvp( "hello", someData6 ) ); // NVP given does not
- // match expected NVP name, so we search
- // for the given NVP and load that value
- ar( someData7, someData8, someData9 ); // with no NVP given, loading resumes at its
- // current location, proceeding sequentially
- @endcode
- \ingroup Archives */
- class XMLInputArchive : public InputArchive<XMLInputArchive>, public traits::TextArchive
- {
- public:
- /*! @name Common Functionality
- Common use cases for directly interacting with an XMLInputArchive */
- //! @{
- //! Construct, reading in from the provided stream
- /*! Reads in an entire XML document from some stream and parses it as soon
- as serialization starts
- @param stream The stream to read from. Can be a stringstream or a file. */
- XMLInputArchive( std::istream & stream ) :
- InputArchive<XMLInputArchive>( this ),
- itsData( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() )
- {
- try
- {
- itsData.push_back('\0'); // rapidxml will do terrible things without the data being null terminated
- itsXML.parse<rapidxml::parse_trim_whitespace | rapidxml::parse_no_data_nodes | rapidxml::parse_declaration_node>( reinterpret_cast<char *>( itsData.data() ) );
- }
- catch( rapidxml::parse_error const & )
- {
- //std::cerr << "-----Original-----" << std::endl;
- //stream.seekg(0);
- //std::cout << std::string( std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>() ) << std::endl;
- //std::cerr << "-----Error-----" << std::endl;
- //std::cerr << e.what() << std::endl;
- //std::cerr << e.where<char>() << std::endl;
- throw Exception("XML Parsing failed - likely due to invalid characters or invalid naming");
- }
- // Parse the root
- auto root = itsXML.first_node( xml_detail::CEREAL_XML_STRING );
- if( root == nullptr )
- throw Exception("Could not detect cereal root node - likely due to empty or invalid input");
- else
- itsNodes.emplace( root );
- }
- ~XMLInputArchive() CEREAL_NOEXCEPT = default;
- //! Loads some binary data, encoded as a base64 string, optionally specified by some name
- /*! This will automatically start and finish a node to load the data, and can be called directly by
- users.
- Note that this follows the same ordering rules specified in the class description in regards
- to loading in/out of order */
- void loadBinaryValue( void * data, size_t size, const char * name = nullptr )
- {
- setNextName( name );
- startNode();
- std::string encoded;
- loadValue( encoded );
- auto decoded = base64::decode( encoded );
- if( size != decoded.size() )
- throw Exception("Decoded binary data size does not match specified size");
- std::memcpy( data, decoded.data(), decoded.size() );
- finishNode();
- }
- //! @}
- /*! @name Internal Functionality
- Functionality designed for use by those requiring control over the inner mechanisms of
- the XMLInputArchive */
- //! @{
- //! Prepares to start reading the next node
- /*! This places the next node to be parsed onto the nodes stack.
- By default our strategy is to start with the document root node and then
- recursively iterate through all children in the order they show up in the document.
- We don't need to know NVPs do to this; we'll just blindly load in the order things appear in.
- We check to see if the specified NVP matches what the next automatically loaded node is. If they
- match, we just continue as normal, going in order. If they don't match, we attempt to find a node
- named after the NVP that is being loaded. If that NVP does not exist, we throw an exception. */
- void startNode()
- {
- auto next = itsNodes.top().child; // By default we would move to the next child node
- auto const expectedName = itsNodes.top().name; // this is the expected name from the NVP, if provided
- // If we were given an NVP name, look for it in the current level of the document.
- // We only need to do this if either we have exhausted the siblings of the current level or
- // the NVP name does not match the name of the node we would normally read next
- if( expectedName && ( next == nullptr || std::strcmp( next->name(), expectedName ) != 0 ) )
- {
- next = itsNodes.top().search( expectedName );
- if( next == nullptr )
- throw Exception("XML Parsing failed - provided NVP (" + std::string(expectedName) + ") not found");
- }
- itsNodes.emplace( next );
- }
- //! Finishes reading the current node
- void finishNode()
- {
- // remove current
- itsNodes.pop();
- // advance parent
- itsNodes.top().advance();
- // Reset name
- itsNodes.top().name = nullptr;
- }
- //! Retrieves the current node name
- //! will return @c nullptr if the node does not have a name
- const char * getNodeName() const
- {
- return itsNodes.top().getChildName();
- }
- //! Sets the name for the next node created with startNode
- void setNextName( const char * name )
- {
- itsNodes.top().name = name;
- }
- //! Loads a bool from the current top node
- template <class T, traits::EnableIf<std::is_unsigned<T>::value,
- std::is_same<T, bool>::value> = traits::sfinae> inline
- void loadValue( T & value )
- {
- std::istringstream is( itsNodes.top().node->value() );
- is.setf( std::ios::boolalpha );
- is >> value;
- }
- //! Loads a char (signed or unsigned) from the current top node
- template <class T, traits::EnableIf<std::is_integral<T>::value,
- !std::is_same<T, bool>::value,
- sizeof(T) == sizeof(char)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = *reinterpret_cast<T*>( itsNodes.top().node->value() );
- }
- //! Load an int8_t from the current top node (ensures we parse entire number)
- void loadValue( int8_t & value )
- {
- int32_t val; loadValue( val ); value = static_cast<int8_t>( val );
- }
- //! Load a uint8_t from the current top node (ensures we parse entire number)
- void loadValue( uint8_t & value )
- {
- uint32_t val; loadValue( val ); value = static_cast<uint8_t>( val );
- }
- //! Loads a type best represented as an unsigned long from the current top node
- template <class T, traits::EnableIf<std::is_unsigned<T>::value,
- !std::is_same<T, bool>::value,
- !std::is_same<T, char>::value,
- !std::is_same<T, unsigned char>::value,
- sizeof(T) < sizeof(long long)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = static_cast<T>( std::stoul( itsNodes.top().node->value() ) );
- }
- //! Loads a type best represented as an unsigned long long from the current top node
- template <class T, traits::EnableIf<std::is_unsigned<T>::value,
- !std::is_same<T, bool>::value,
- sizeof(T) >= sizeof(long long)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = static_cast<T>( std::stoull( itsNodes.top().node->value() ) );
- }
- //! Loads a type best represented as an int from the current top node
- template <class T, traits::EnableIf<std::is_signed<T>::value,
- !std::is_same<T, char>::value,
- sizeof(T) <= sizeof(int)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = static_cast<T>( std::stoi( itsNodes.top().node->value() ) );
- }
- //! Loads a type best represented as a long from the current top node
- template <class T, traits::EnableIf<std::is_signed<T>::value,
- (sizeof(T) > sizeof(int)),
- sizeof(T) <= sizeof(long)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = static_cast<T>( std::stol( itsNodes.top().node->value() ) );
- }
- //! Loads a type best represented as a long long from the current top node
- template <class T, traits::EnableIf<std::is_signed<T>::value,
- (sizeof(T) > sizeof(long)),
- sizeof(T) <= sizeof(long long)> = traits::sfinae> inline
- void loadValue( T & value )
- {
- value = static_cast<T>( std::stoll( itsNodes.top().node->value() ) );
- }
- //! Loads a type best represented as a float from the current top node
- void loadValue( float & value )
- {
- try
- {
- value = std::stof( itsNodes.top().node->value() );
- }
- catch( std::out_of_range const & )
- {
- // special case for denormalized values
- std::istringstream is( itsNodes.top().node->value() );
- is >> value;
- if( std::fpclassify( value ) != FP_SUBNORMAL )
- throw;
- }
- }
- //! Loads a type best represented as a double from the current top node
- void loadValue( double & value )
- {
- try
- {
- value = std::stod( itsNodes.top().node->value() );
- }
- catch( std::out_of_range const & )
- {
- // special case for denormalized values
- std::istringstream is( itsNodes.top().node->value() );
- is >> value;
- if( std::fpclassify( value ) != FP_SUBNORMAL )
- throw;
- }
- }
- //! Loads a type best represented as a long double from the current top node
- void loadValue( long double & value )
- {
- try
- {
- value = std::stold( itsNodes.top().node->value() );
- }
- catch( std::out_of_range const & )
- {
- // special case for denormalized values
- std::istringstream is( itsNodes.top().node->value() );
- is >> value;
- if( std::fpclassify( value ) != FP_SUBNORMAL )
- throw;
- }
- }
- //! Loads a string from the current node from the current top node
- template<class CharT, class Traits, class Alloc> inline
- void loadValue( std::basic_string<CharT, Traits, Alloc> & str )
- {
- std::basic_istringstream<CharT, Traits> is( itsNodes.top().node->value() );
- str.assign( std::istreambuf_iterator<CharT, Traits>( is ),
- std::istreambuf_iterator<CharT, Traits>() );
- }
- //! Loads the size of the current top node
- template <class T> inline
- void loadSize( T & value )
- {
- value = getNumChildren( itsNodes.top().node );
- }
- protected:
- //! Gets the number of children (usually interpreted as size) for the specified node
- static size_t getNumChildren( rapidxml::xml_node<> * node )
- {
- size_t size = 0;
- node = node->first_node(); // get first child
- while( node != nullptr )
- {
- ++size;
- node = node->next_sibling();
- }
- return size;
- }
- //! A struct that contains metadata about a node
- /*! Keeps track of some top level node, its number of
- remaining children, and the current active child node */
- struct NodeInfo
- {
- NodeInfo( rapidxml::xml_node<> * n = nullptr ) :
- node( n ),
- child( n->first_node() ),
- size( XMLInputArchive::getNumChildren( n ) ),
- name( nullptr )
- { }
- //! Advances to the next sibling node of the child
- /*! If this is the last sibling child will be null after calling */
- void advance()
- {
- if( size > 0 )
- {
- --size;
- child = child->next_sibling();
- }
- }
- //! Searches for a child with the given name in this node
- /*! @param searchName The name to search for (must be null terminated)
- @return The node if found, nullptr otherwise */
- rapidxml::xml_node<> * search( const char * searchName )
- {
- if( searchName )
- {
- size_t new_size = XMLInputArchive::getNumChildren( node );
- const size_t name_size = rapidxml::internal::measure( searchName );
- for( auto new_child = node->first_node(); new_child != nullptr; new_child = new_child->next_sibling() )
- {
- if( rapidxml::internal::compare( new_child->name(), new_child->name_size(), searchName, name_size, true ) )
- {
- size = new_size;
- child = new_child;
- return new_child;
- }
- --new_size;
- }
- }
- return nullptr;
- }
- //! Returns the actual name of the next child node, if it exists
- const char * getChildName() const
- {
- return child ? child->name() : nullptr;
- }
- rapidxml::xml_node<> * node; //!< A pointer to this node
- rapidxml::xml_node<> * child; //!< A pointer to its current child
- size_t size; //!< The remaining number of children for this node
- const char * name; //!< The NVP name for next child node
- }; // NodeInfo
- //! @}
- private:
- std::vector<char> itsData; //!< The raw data loaded
- rapidxml::xml_document<> itsXML; //!< The XML document
- std::stack<NodeInfo> itsNodes; //!< A stack of nodes read from the document
- };
- // ######################################################################
- // XMLArchive prologue and epilogue functions
- // ######################################################################
- // ######################################################################
- //! Prologue for NVPs for XML output archives
- /*! NVPs do not start or finish nodes - they just set up the names */
- template <class T> inline
- void prologue( XMLOutputArchive &, NameValuePair<T> const & )
- { }
- //! Prologue for NVPs for XML input archives
- template <class T> inline
- void prologue( XMLInputArchive &, NameValuePair<T> const & )
- { }
- // ######################################################################
- //! Epilogue for NVPs for XML output archives
- /*! NVPs do not start or finish nodes - they just set up the names */
- template <class T> inline
- void epilogue( XMLOutputArchive &, NameValuePair<T> const & )
- { }
- //! Epilogue for NVPs for XML input archives
- template <class T> inline
- void epilogue( XMLInputArchive &, NameValuePair<T> const & )
- { }
- // ######################################################################
- //! Prologue for deferred data for XML archives
- /*! Do nothing for the defer wrapper */
- template <class T> inline
- void prologue( XMLOutputArchive &, DeferredData<T> const & )
- { }
- //! Prologue for deferred data for XML archives
- template <class T> inline
- void prologue( XMLInputArchive &, DeferredData<T> const & )
- { }
- // ######################################################################
- //! Epilogue for deferred for XML archives
- /*! NVPs do not start or finish nodes - they just set up the names */
- template <class T> inline
- void epilogue( XMLOutputArchive &, DeferredData<T> const & )
- { }
- //! Epilogue for deferred for XML archives
- /*! Do nothing for the defer wrapper */
- template <class T> inline
- void epilogue( XMLInputArchive &, DeferredData<T> const & )
- { }
- // ######################################################################
- //! Prologue for SizeTags for XML output archives
- /*! SizeTags do not start or finish nodes */
- template <class T> inline
- void prologue( XMLOutputArchive & ar, SizeTag<T> const & )
- {
- if (ar.hasSizeAttributes())
- {
- ar.appendAttribute("size", "dynamic");
- }
- }
- template <class T> inline
- void prologue( XMLInputArchive &, SizeTag<T> const & )
- { }
- //! Epilogue for SizeTags for XML output archives
- /*! SizeTags do not start or finish nodes */
- template <class T> inline
- void epilogue( XMLOutputArchive &, SizeTag<T> const & )
- { }
- template <class T> inline
- void epilogue( XMLInputArchive &, SizeTag<T> const & )
- { }
- // ######################################################################
- //! Prologue for all other types for XML output archives (except minimal types)
- /*! Starts a new node, named either automatically or by some NVP,
- that may be given data by the type about to be archived
- Minimal types do not start or end nodes */
- template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value ||
- traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline
- void prologue( XMLOutputArchive & ar, T const & )
- {
- ar.startNode();
- ar.insertType<T>();
- }
- //! Prologue for all other types for XML input archives (except minimal types)
- template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value ||
- traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline
- void prologue( XMLInputArchive & ar, T const & )
- {
- ar.startNode();
- }
- // ######################################################################
- //! Epilogue for all other types other for XML output archives (except minimal types)
- /*! Finishes the node created in the prologue
- Minimal types do not start or end nodes */
- template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_output_serialization, XMLOutputArchive>::value ||
- traits::has_minimal_output_serialization<T, XMLOutputArchive>::value> = traits::sfinae> inline
- void epilogue( XMLOutputArchive & ar, T const & )
- {
- ar.finishNode();
- }
- //! Epilogue for all other types other for XML output archives (except minimal types)
- template <class T, traits::DisableIf<traits::has_minimal_base_class_serialization<T, traits::has_minimal_input_serialization, XMLInputArchive>::value ||
- traits::has_minimal_input_serialization<T, XMLInputArchive>::value> = traits::sfinae> inline
- void epilogue( XMLInputArchive & ar, T const & )
- {
- ar.finishNode();
- }
- // ######################################################################
- // Common XMLArchive serialization functions
- // ######################################################################
- //! Saving NVP types to XML
- template <class T> inline
- void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive & ar, NameValuePair<T> const & t )
- {
- ar.setNextName( t.name );
- ar( t.value );
- }
- //! Loading NVP types from XML
- template <class T> inline
- void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, NameValuePair<T> & t )
- {
- ar.setNextName( t.name );
- ar( t.value );
- }
- // ######################################################################
- //! Saving SizeTags to XML
- template <class T> inline
- void CEREAL_SAVE_FUNCTION_NAME( XMLOutputArchive &, SizeTag<T> const & )
- { }
- //! Loading SizeTags from XML
- template <class T> inline
- void CEREAL_LOAD_FUNCTION_NAME( XMLInputArchive & ar, SizeTag<T> & st )
- {
- ar.loadSize( st.size );
- }
- // ######################################################################
- //! Saving for POD types to xml
- template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline
- void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, T const & t)
- {
- ar.saveValue( t );
- }
- //! Loading for POD types from xml
- template <class T, traits::EnableIf<std::is_arithmetic<T>::value> = traits::sfinae> inline
- void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, T & t)
- {
- ar.loadValue( t );
- }
- // ######################################################################
- //! saving string to xml
- template<class CharT, class Traits, class Alloc> inline
- void CEREAL_SAVE_FUNCTION_NAME(XMLOutputArchive & ar, std::basic_string<CharT, Traits, Alloc> const & str)
- {
- ar.saveValue( str );
- }
- //! loading string from xml
- template<class CharT, class Traits, class Alloc> inline
- void CEREAL_LOAD_FUNCTION_NAME(XMLInputArchive & ar, std::basic_string<CharT, Traits, Alloc> & str)
- {
- ar.loadValue( str );
- }
- } // namespace cereal
- // register archives for polymorphic support
- CEREAL_REGISTER_ARCHIVE(cereal::XMLOutputArchive)
- CEREAL_REGISTER_ARCHIVE(cereal::XMLInputArchive)
- // tie input and output archives together
- CEREAL_SETUP_ARCHIVE_TRAITS(cereal::XMLInputArchive, cereal::XMLOutputArchive)
- #endif // CEREAL_ARCHIVES_XML_HPP_
|