Hello World / plɹoM ollǝH

Programmers Live in Vain

C++構造体のXmlシリアライザを自動生成したい

C#だったら属性を書くだけで済むけれど、C++はそういうわけにはいかないのでXMLで定義したフォーマットからコードを生成するスクリプトを書いた。

まずはデータフォーマットをXmlで表記する。

<?xml version='1.0' encoding='UTF-8'?>
<Root>
    <Type name="Scene">
        <Type name="Material">
            <Type name="Property">
                <value name="Name" type="String"/>
                <value name="Value" type="Float[]"/>
            </Type>
            <value name="Name" type="String"/>
            <value name="Properties" type="Property{}"/>
        </Type>
        <Type name="Mesh">
            <Type name="Vertex">
                <value name="Position" type="Float[]"/>
            </Type>
            <value name="Name" type="String"/>
            <value name="Vertices" type="Vertex[]"/>
            <value name="Indices" type="Integer[]"/>
        </Type>
        <Type name="Model">
            <value name="Name" type="String"/>
            <value name="Material" type="String"/>
            <value name="Mesh" type="String"/>
        </Type>
        <value name="Name" type="String"/>
        <value name="Materials" type="Material{}"/>
        <value name="Meshes" type="Mesh{}"/>
        <value name="Models" type="Model{}"/>
    </Type>
</Root>

このXMLからは次のようなC++コードが生成される

struct Scene {
    struct Material {
        struct Property {
            std::string name;
            std::vector< float > value;
        };

        std::string name;
        std::map< std::string, Property > properties;
    };

    struct Mesh {
        struct Vertex {
            std::vector< float > position;
        };

        std::string name;
        std::vector< Vertex > vertices;
        std::vector< int > indices;
    };

    struct Model {
        std::string name;
        std::string material;
        std::string mesh;
    };

    std::string name;
    std::map< std::string, Material > materials;
    std::map< std::string, Mesh > meshes;
    std::map< std::string, Model > models;
};

void serialize(tinyxml2::XMLElement* out_xml, const Scene::Material::Property& in_value);
void deserialize(Scene::Material::Property& out_value, const tinyxml2::XMLElement* in_xml);
void serialize(tinyxml2::XMLElement* out_xml, const Scene::Material& in_value);
void deserialize(Scene::Material& out_value, const tinyxml2::XMLElement* in_xml);
void serialize(tinyxml2::XMLElement* out_xml, const Scene::Mesh::Vertex& in_value);
void deserialize(Scene::Mesh::Vertex& out_value, const tinyxml2::XMLElement* in_xml);
void serialize(tinyxml2::XMLElement* out_xml, const Scene::Mesh& in_value);
void deserialize(Scene::Mesh& out_value, const tinyxml2::XMLElement* in_xml);
void serialize(tinyxml2::XMLElement* out_xml, const Scene::Model& in_value);
void deserialize(Scene::Model& out_value, const tinyxml2::XMLElement* in_xml);
void serialize(tinyxml2::XMLElement* out_xml, const Scene& in_value);
void deserialize(Scene& out_value, const tinyxml2::XMLElement* in_xml);

で、次のようなデータを読み込むことが可能になり

<?xml version='1.0' encoding='UTF-8'?>
<Scene>
  <Name>PRIMITIVES</Name>
    <Materials>
        <Material>
            <Name>RED</Name>
            <Properties>
                <Property>
                    <Name>Diffuse</Name>
                    <value>
                        <Float>1.0</Float>
                        <Float>0.0</Float>
                        <Float>0.0</Float>
                        <Float>1.0</Float>
                    </value>
                </Property>
            </Properties>
        </Material>
    </Materials>
    <Meshes>
        <Mesh>
            <Name>TRIANGLE</Name>
            <Vertices>
                <Vertex>
                    <Position>
                        <Float>0.0</Float>
                        <Float>1.0</Float>
                        <Float>0.0</Float>
                    </Position>
                    <Position>
                        <Float>-0.866</Float>
                        <Float>-0.5</Float>
                        <Float>0.0</Float>
                    </Position>
                    <Position>
                        <Float>0.866</Float>
                        <Float>-0.5</Float>
                        <Float>0.0</Float>
                    </Position>
                </Vertex>
            </Vertices>
            <Indices>
                <Uint>0</Uint>
                <Uint>1</Uint>
                <Uint>2</Uint>
            </Indices>
        </Mesh>
    </Meshes>
    <Models>
        <Model>
            <Name>TRIANGLE_RED</Name>
            <Material>RED</Material>
            <Mesh>TRIANGLE</Mesh>
        </Model>
    </Models>
</Scene>

次のように使う。

int main()
{
    Scene scene;

    // 読み込み
    tinyxml2::XMLDocument doc;
    doc.LoadFile("deserialize_test.xml");
    deserialize(scene, doc.FirstChildElement("Scene"));

    // 書き込み
    tinyxml2::XMLDocument doc2;
    auto* elem = doc2.NewElement("Scene");
    doc2.InsertFirstChild(elem);
    serialize(elem, scene);
    doc2.SaveFile("serialize_test.xml");

    return 0;
}

おとなしくflatbuffersを使ってね