How to change or delete tags in boost serializatio

2019-04-02 01:26发布

I'm trying to serialize my classes to xml. My classes;

class HardwareDto{
    friend class boost::serialization::access;
    template<class Archive> void serialize(Archive & ar, const unsigned int version) {
        ar & BOOST_SERIALIZATION_NVP(HardwareID);
        ar & BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar & BOOST_SERIALIZATION_NVP(HardwareFriendlyName);
    }
public:
    int HardwareID;
    int HardwareHostID;
    string HardwareFriendlyName;
    inline HardwareDto(int HardwareHostID, int HardwareID, string HardwareFriendlyName) {
        this->HardwareHostID = HardwareHostID;
        this->HardwareID = HardwareID;
        this->HardwareFriendlyName = HardwareFriendlyName;
    }
};

And a class which contains a HardwareDto list.

class HardwareHostDto {
private:
    friend class boost::serialization::access;
    template<class Archive> void serialize(Archive & ar, const unsigned int version) {
        ar & BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar & BOOST_SERIALIZATION_NVP(BranchID);
        ar & BOOST_SERIALIZATION_NVP(HardwareHostFriendlyName);
        ar & BOOST_SERIALIZATION_NVP(HardwareList);
    }

public:
    int HardwareHostID;
    int BranchID;
    string HardwareHostFriendlyName ;
    HardwareDto* HardwareList[20];

    inline HardwareHostDto(int HardwareHostID, int BranchID, string HardwareHostFriendlyName, HardwareDto* HardwareList[20]) {
        this->HardwareHostID = HardwareHostID;
        this->BranchID = BranchID;
        this->HardwareHostFriendlyName = HardwareHostFriendlyName;
        this->HardwareList[0] = HardwareList[0];
    }
};

And

HardwareDto *HardwareList[20]; 

is my global hardwaredto list. In this example I only inserted one hardwarehostdto object into this list.

I'm trying to serialize this via boost function:

std::ofstream ofs("filename.xml");

unsigned int flags = boost::archive::no_header;
boost::archive::xml_iarchive ia(is, boost::archive::no_header);
boost::archive::xml_oarchive oa(ofs, flags);

HardwareHostDto* HardwareHost = new HardwareHostDto(1, 1, "kiosk", HardwareList);

oa << BOOST_SERIALIZATION_NVP(HardwareHost);

After this code executed, i got this filename.xml:

<HardwareHost class_id="0">
    <HardwareHostID>1</HardwareHostID>
    <BranchID>1</BranchID>
    <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
    <HardwareList>
        <count>20</count>
        <item class_id="1">
            <HardwareID>2</HardwareID>
            <HardwareHostID>2</HardwareHostID>
            <HardwareFriendlyName>Ankara</HardwareFriendlyName>
        </item>
    </HardwareList>
</HardwareHost>

<item> tag should be <Hardware> but i cant change it. My question is: is there any way to change <item> tag, or actullay customize this xml structure, like no <count> tag or flags? I found a few ways to do it in boost website but couldnt handle it.

Thank you.

2条回答
萌系小妹纸
2楼-- · 2019-04-02 01:57

Yes, you can hack it. Maybe. To an extent. See:

And no, you shouldn't. Use an XML library to write arbitrary XML.

Boost Serialization only does serialization. The archive format is implementation detail.


Bad Example

What not to do (it breaks non-default constructible types, it breaks versioning).

On the bright side, this code doesn't leak memory.

Live On Coliru

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>

class HardwareDto {
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar &BOOST_SERIALIZATION_NVP(HardwareID);
        ar &BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar &BOOST_SERIALIZATION_NVP(HardwareFriendlyName);
    }

  public:
    int HardwareHostID;
    int HardwareID;
    std::string HardwareFriendlyName;

    HardwareDto(int HardwareHostID = -1, int HardwareID = -1, std::string HardwareFriendlyName = {})
       : HardwareHostID(HardwareHostID),
         HardwareID(HardwareID),
         HardwareFriendlyName(HardwareFriendlyName)
    { }
};

using HardwareDtoList = std::vector<HardwareDto>;

class HardwareHostDto {
  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar &BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar &BOOST_SERIALIZATION_NVP(BranchID);
        ar &BOOST_SERIALIZATION_NVP(HardwareHostFriendlyName);
        ar &BOOST_SERIALIZATION_NVP(HardwareList);
    }

  public:
    int HardwareHostID;
    int BranchID;
    std::string HardwareHostFriendlyName;
    HardwareDtoList HardwareList;

    HardwareHostDto(int HardwareHostID, int BranchID, std::string HardwareHostFriendlyName, HardwareDtoList HardwareList) 
        : HardwareHostID(HardwareHostID),
          BranchID(BranchID),
          HardwareHostFriendlyName(HardwareHostFriendlyName),
          HardwareList(HardwareList)
    { }
};

namespace boost { namespace serialization {
    template <typename Ar>
        void serialize(Ar& ar, std::vector<HardwareDto>& v, unsigned) {
            size_t count = v.size();
            ar & BOOST_SERIALIZATION_NVP(count);
            v.resize(count);

            for (auto& el : v)
                ar & boost::serialization::make_nvp("Hardware", el);
        }
} }

#include <fstream>
#include <iostream>

int main() {
    unsigned int flags = boost::archive::no_header;

    {
        HardwareDtoList HardwareList;
        HardwareList.emplace_back(1, 2, "friendly");

        std::ofstream ofs("filename.xml");
        boost::archive::xml_oarchive oa(ofs, flags);

        HardwareHostDto host(1, 1, "kiosk", HardwareList);

        oa << boost::serialization::make_nvp("HardwareHost", host);
    }

    {
        HardwareHostDto roundtrip(-1, -1, "", {});
        std::ifstream ifs("filename.xml");
        boost::archive::xml_iarchive ia(ifs, flags);

        ia >> boost::serialization::make_nvp("HardwareHost", roundtrip);

        std::cout << "Read back: " << roundtrip.HardwareHostID << "\n";
        std::cout << "Read back: " << roundtrip.BranchID << "\n";
        std::cout << "Read back: " << roundtrip.HardwareHostFriendlyName << "\n";
        for (auto& h: roundtrip.HardwareList) {
            std::cout << "Item: " << h.HardwareID << ", " << h.HardwareHostID << ", " << h.HardwareFriendlyName << "\n";
        }
    }

}

Prints

Read back: 1
Read back: 1
Read back: kiosk
Item: 2, 1, friendly

And writes the XML as:

<HardwareHost class_id="0" tracking_level="0" version="0">
    <HardwareHostID>1</HardwareHostID>
    <BranchID>1</BranchID>
    <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
    <HardwareList class_id="1" tracking_level="0" version="0">
        <count>1</count>
        <Hardware class_id="2" tracking_level="0" version="0">
            <HardwareID>2</HardwareID>
            <HardwareHostID>1</HardwareHostID>
            <HardwareFriendlyName>friendly</HardwareFriendlyName>
        </Hardware>
    </HardwareList>
</HardwareHost>

What To Do

Using an XML library. This calls for (a lot) generalization, but here's a start using PugiXML:

Live On Coliru

#include <pugixml.hpp>
#include <iostream>
#include <vector>

struct HardwareDto {
    int HardwareHostID;
    int HardwareID;
    std::string HardwareFriendlyName;
};

struct HardwareHostDto {
    int HardwareHostID;
    int BranchID;
    std::string HardwareHostFriendlyName;
    std::vector<HardwareDto> HardwareList;
};

struct Xml {
    struct Saver {
        template <typename T>
        void operator()(pugi::xml_node parent, std::string const& name, T const& value) const {
            auto node = named_child(parent, name);
            node.text().set(to_xml(value));
        }

        void operator()(pugi::xml_node parent, std::string const& name, HardwareDto const& o) const {
            auto dto = named_child(parent, name);
            operator()(dto, "HardwareHostID", o.HardwareHostID);
            operator()(dto, "HardwareID", o.HardwareID);
            operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
        }

        template <typename C>
        void operator()(pugi::xml_node parent, std::string const& name, std::string const& item_name, C const& container) const {
            auto list = named_child(parent, name);

            for (auto& item : container)
                operator()(list, item_name, item);
        }

        void operator()(pugi::xml_node parent, std::string const& name, HardwareHostDto const& o) const {
            auto dto = named_child(parent, name);
            operator()(dto, "HardwareHostID", o.HardwareHostID);
            operator()(dto, "BranchID", o.BranchID);
            operator()(dto, "HardwareHostFriendlyName", o.HardwareHostFriendlyName);
            operator()(dto, "HardwareList", "Hardware", o.HardwareList);
        }
      private:
        // serialization
        template <typename T> static T const& to_xml(T const& v) { return v; }
        static char const* to_xml(std::string const& v) { return v.c_str(); }

        pugi::xml_node named_child(pugi::xml_node parent, std::string const& name) const {
            auto child = parent.append_child();
            child.set_name(name.c_str());
            return child;
        }
    };

    struct Loader {
        void operator()(pugi::xml_node parent, std::string const& name, std::string& value) const {
            auto node = parent.first_element_by_path(name.c_str());
            value = node.text().as_string();
        }
        void operator()(pugi::xml_node parent, std::string const& name, int& value) const {
            auto node = parent.first_element_by_path(name.c_str());
            value = node.text().as_int();
        }

        void operator()(pugi::xml_node dto, HardwareDto& o) const {
            operator()(dto, "HardwareHostID", o.HardwareHostID);
            operator()(dto, "HardwareID", o.HardwareID);
            operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
        }

        void operator()(pugi::xml_node parent, std::string const& name, HardwareDto& o) const {
            auto dto = parent.first_element_by_path(name.c_str());
            operator()(dto, "HardwareHostID", o.HardwareHostID);
            operator()(dto, "HardwareID", o.HardwareID);
            operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
        }

        template <typename C>
        void operator()(pugi::xml_node parent, std::string const& name, std::string const& item_name, C& container) const {
            auto list = parent.first_element_by_path(name.c_str());

            for (auto& node : list) {
                if (node.type() != pugi::xml_node_type::node_element) {
                    std::cerr << "Warning: unexpected child node type ignored\n";
                    continue;
                }
                if (node.name() != item_name) {
                    std::cerr << "Warning: unexpected child node ignored (" << node.name() << ")\n";
                    continue;
                }

                container.emplace_back();
                operator()(node, container.back());
            }
        }

        void operator()(pugi::xml_node dto, HardwareHostDto& o) const {
            operator()(dto, "HardwareHostID", o.HardwareHostID);
            operator()(dto, "BranchID", o.BranchID);
            operator()(dto, "HardwareHostFriendlyName", o.HardwareHostFriendlyName);
            operator()(dto, "HardwareList", "Hardware", o.HardwareList);
        }

        void operator()(pugi::xml_node parent, std::string const& name, HardwareHostDto& o) const {
            operator()(parent.first_element_by_path(name.c_str()), o);
        }
    };
};

int main() {
    {

        pugi::xml_document _doc;
        Xml::Saver saver;

        HardwareHostDto host = { 1, 1, "kiosk", { { 1, 2, "friendly" } } };
        saver(_doc.root(), "HardwareHost", host);

        _doc.save_file("test.xml");
    }

    {
        HardwareHostDto roundtrip;
        {
            pugi::xml_document _doc;
            _doc.load_file("test.xml");
            Xml::Loader loader;

            loader(_doc.root(), "HardwareHost", roundtrip);
        }

        std::cout << "Read back: " << roundtrip.HardwareHostID << "\n";
        std::cout << "Read back: " << roundtrip.BranchID << "\n";
        std::cout << "Read back: " << roundtrip.HardwareHostFriendlyName << "\n";
        for (auto& h: roundtrip.HardwareList) {
            std::cout << "Item: " << h.HardwareID << ", " << h.HardwareHostID << ", " << h.HardwareFriendlyName << "\n";
        }
    }

}

Which also prints

Read back: 1
Read back: 1
Read back: kiosk
Item: 2, 1, friendly

And writes a test.xml:

<?xml version="1.0"?>
<HardwareHost>
    <HardwareHostID>1</HardwareHostID>
    <BranchID>1</BranchID>
    <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
    <HardwareList>
        <Hardware>
            <HardwareHostID>1</HardwareHostID>
            <HardwareID>2</HardwareID>
            <HardwareFriendlyName>friendly</HardwareFriendlyName>
        </Hardware>
    </HardwareList>
</HardwareHost>
查看更多
beautiful°
3楼-- · 2019-04-02 02:15

In order to fully customize XML output from boost serialization you can write your own XML archive. It's a three step process:

  1. Define a class that writes primitive types, let's say TextOPrimitiveOnXML.
  2. Define a class that writes composite types: BasicXMLOArchive. This is the class that will write XML tags.
  3. Glue everything together into XMLOArchive.

The advantage is that not only you can reuse your serialize code, but also you can use different backends. For example, you can have an archive that creates a DOM right away instead of merely text output.

Here are skeletons for the classes I mentioned.

TextOPrimitiveOnXML, its save methods define writing of primitive types.

class TextOPrimitiveOnXML
{
protected:
  TextOPrimitiveOnXML()
  {}
  template <class T>
  void save(const T & t) {
  }
  void save(const bool t) {
  }
  void save(const signed char t) {
  }
  void save(const unsigned char t) {
  }
  void save(const char t) {
  }
  void save(const wchar_t t) {
  }
  void save(const char *t) {
  }
  void save(const wchar_t *t) {
  }
  void save(const std::string &t) {
  }
  void save(const std::wstring &t) {
  }
  void save_binary(const void *address, std::size_t count);
};

BasicXMLOArchive, this is where you manipulate tags and attributes:

template <class Archive>
class BasicXMLOArchive :
  public boost::archive::detail::common_oarchive<Archive>
{
  friend class boost::archive::detail::interface_oarchive<Archive>;
  friend class boost::archive::save_access;

  typedef boost::archive::detail::common_oarchive<Archive> base;

protected:
  void init() {
  }

  BasicXMLOArchive(unsigned int flags)
    : base(flags)
  {}

  template <class T>
  void save_override(T & t, int)
  {
    // If your program fails to compile here, its most likely due to
    // not specifying an nvp wrapper around the variable to
    // be serialized.
    BOOST_MPL_ASSERT((boost::serialization::is_wrapper<T>));
    this->base::save_override(t, 0);
  }

  // special treatment for name-value pairs.
  template <class T>
  void save_override(const boost::serialization::nvp<T> &t, int)
  {
  }

  void save_override(const boost::archive::object_id_type & t);
  void save_override(const boost::archive::object_reference_type & t);
  void save_override(const boost::archive::version_type & t);
  void save_override(const boost::archive::class_id_type & t);
  void save_override(const boost::archive::class_id_optional_type & t);
  void save_override(const boost::archive::class_id_reference_type & t);
  void save_override(const boost::archive::class_name_type & t);
  void save_override(const boost::archive::tracking_type & t);

public:
  boost::archive::library_version_type get_library_version() const {
    return boost::archive::library_version_type(0);
  }
};

The final glue:

template <class Archive>
class XMLOArchiveImpl :
    public TextOPrimitiveOnXML
  , public BasicXMLOArchive<Archive>
{
  friend class boost::archive::save_access;
public:
  XMLOArchiveImpl(unsigned int flags)
    : TextOPrimitiveOnXML()
    , BasicXMLOArchive<Archive>(flags)
  {
    init();
  }

  void save_binary(const void *address, std::size_t count){
    this->TextOPrimitiveOnXML::save_binary(address, count);
  }
};

class XMLOArchive :
  public XMLOArchiveImpl<XMLOArchive>
{
  friend class BasicXMLOArchive<XMLOArchive>;
public:
  XMLOArchive(unsigned int flags)
    : XMLOArchiveImpl(flags)
  {}
};
查看更多
登录 后发表回答