diff --git a/ce_product_name.hpp b/ce_product_name.hpp
index ef8d66f..e816ac2 100644
--- a/ce_product_name.hpp
+++ b/ce_product_name.hpp
@@ -43,7 +43,7 @@
 /// Because there is no enum to map to, there is no 'm' for "Mapped"
 /// at the beginning of the class's or file's name.
 ///
-/// Valid values are the base names of policy ('.pol') files found
+/// Valid values are the base names of policy ('.xpol') files found
 /// in the (configurable) data directory. They are read only once and
 /// cached, not because of any measured improvement in run time, but
 /// in order to keep the type from mutating with unknown consequences.
diff --git a/configure.ac b/configure.ac
index 9a63b62..4f7cfd5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -139,6 +139,20 @@ AC_ARG_ENABLE(pch,
              esac], lmi_use_pch=no)
 AM_CONDITIONAL(LMI_USE_PCH, [ test "x$lmi_use_pch" = "xyes" ])
 
+AC_ARG_ENABLE(legacy-formats,
+             [AC_HELP_STRING([--disable-legacy-formats],
+                             [don't build support for legacy file formats])],
+             [case "x${enableval}" in
+                   x) legacy_option=no ;;
+                xyes) legacy_option=yes ;;
+                 xno) legacy_option=no ;;
+                   *) AC_MSG_ERROR(bad value ${enableval} for --enable-legacy-formats) ;;
+             esac], [legacy_option=yes])
+
+if test "x$legacy_option" = "xno"; then
+    AC_DEFINE(LMI_NO_LEGACY_FORMATS, [1], [Define if you don't need support for legacy file formats])
+fi
+
 dnl Pass standard options to wx.
 WX_STANDARD_OPTIONS([debug,shared,toolkit,wxshared])
 
diff --git a/dbnames.cpp b/dbnames.cpp
index 01879bd..6c4d7eb 100644
--- a/dbnames.cpp
+++ b/dbnames.cpp
@@ -33,6 +33,8 @@
 #include "assert_lmi.hpp"
 #include "miscellany.hpp"
 
+#include <map>
+
 namespace
 {
 bool check_order(std::vector<db_names> const& v)
@@ -93,3 +95,31 @@ std::vector<db_names> const& GetDBNames()
     return static_get_db_names();
 }
 
+int db_name_to_index(std::string const& short_name)
+{
+    typedef std::map<std::string, int> name_to_key_map;
+
+    static name_to_key_map static_mapping;
+
+    if(static_mapping.empty())
+        {
+        std::vector<db_names> const& names = GetDBNames();
+        for(int i = 0; i < DB_LAST; ++i)
+            {
+            static_mapping[names[i].ShortName] = i;
+            }
+        }
+
+    name_to_key_map::const_iterator i = static_mapping.find(short_name);
+    if(static_mapping.end() == i)
+        {
+        fatal_error()
+            << "Value '"
+            << short_name
+            << "' is invalid database name."
+            << LMI_FLUSH
+            ;
+        }
+
+    return i->second;
+}
diff --git a/dbnames.hpp b/dbnames.hpp
index ef82963..b3dd1b9 100644
--- a/dbnames.hpp
+++ b/dbnames.hpp
@@ -42,8 +42,8 @@ enum DatabaseNames
         ,DB_AllowFullUW
         ,DB_AllowSimpUW
         ,DB_AllowGuarUW
-        ,DB_SmokeOrTobacco       // DATABASE !! Move to '.pol'
-        ,DB_PrefOrSelect         // DATABASE !! Move to '.pol'
+        ,DB_SmokeOrTobacco       // DATABASE !! Move to '.xpol'
+        ,DB_PrefOrSelect         // DATABASE !! Move to '.xpol'
         ,DB_AllowPreferredClass
         ,DB_AllowUltraPrefClass
 
@@ -432,7 +432,9 @@ enum DatabaseNames
         ,DB_PrimaryHurdle
         ,DB_SecondaryHurdle
 
-    ,DB_LAST    // Adding a new item? Insert directly above *only*.
+    ,DB_LAST
+#ifndef LMI_NO_LEGACY_FORMATS
+    // Adding a new item? Insert directly above *only*.
     // That way, old databases remain compatible with new code, and any
     // error in using a new item will occur when the new item is used
     // instead of where a renumbered old item is used--that should make
@@ -440,6 +442,10 @@ enum DatabaseNames
     // additions into categories when preparing a major release.
     //
     // See 'dbnames.xpp' for the definition of each entity.
+    //
+    // TODO !! Once we get rid of read-only support for legacy .db4 files too,
+    //         we can remove this restriction on where to add new values.
+#endif // !LMI_NO_LEGACY_FORMATS
     };
 
 struct db_names
@@ -452,5 +458,11 @@ struct db_names
 
 std::vector<db_names> const& LMI_SO GetDBNames();
 
-#endif // dbnames_hpp
+inline char const* db_name_from_index(int key)
+{
+    return GetDBNames()[key].ShortName;
+}
 
+int db_name_to_index(std::string const& short_name);
+
+#endif // dbnames_hpp
diff --git a/ihs_basicval.cpp b/ihs_basicval.cpp
index c0923eb..99a2df4 100644
--- a/ihs_basicval.cpp
+++ b/ihs_basicval.cpp
@@ -397,7 +397,7 @@ double BasicValues::InvestmentManagementFee() const
             {
             // Allow hardcoded number of funds < Funds.GetNumberOfFunds
             // so an accurate fund average can be calculated (based
-            // on the funds in the *.fnd file), even though the inputs
+            // on the funds in the *.xfnd file), even though the inputs
             // class may not accommodate that many funds. If j falls
             // outside the range of yare_input_.FundAllocations, use a
             // weight of zero.
@@ -409,11 +409,11 @@ double BasicValues::InvestmentManagementFee() const
         else if(j < static_cast<int>(yare_input_.FundAllocations.size()))
             {
             // TODO ?? Can this be correct? Shouldn't we be looking to
-            // the .fnd file rather than the input class?
+            // the .xfnd file rather than the input class?
             //
             // Allow hardcoded number of funds < Funds.GetNumberOfFunds
             // so an accurate fund average can be calculated (based
-            // on the funds in the *.fnd file), even though the inputs
+            // on the funds in the *.xfnd file), even though the inputs
             // class may not accommodate that many funds. If j falls
             // outside the range of yare_input_.FundAllocations, use a
             // weight of zero.
diff --git a/ihs_dbdict.cpp b/ihs_dbdict.cpp
index 0d79861..5c7e55b 100644
--- a/ihs_dbdict.cpp
+++ b/ihs_dbdict.cpp
@@ -35,6 +35,7 @@
 #include "mc_enum_type_enums.hpp"
 #include "miscellany.hpp"
 #include "oecumenic_enumerations.hpp"
+#include "xml_serialize.hpp"
 
 #include <boost/filesystem/convenience.hpp>
 #include <boost/filesystem/fstream.hpp>
@@ -66,6 +67,39 @@ DBDictionary::~DBDictionary()
 }
 
 //============================================================================
+namespace xml_serialize
+{
+
+// Specialize type_io<> for dict_map instead of using some generic std::map
+// serialization, because the key would be stored redundantly: it's already
+// part of TDBValue.
+template<>
+struct type_io<dict_map>
+{
+    static void to_xml(xml::node& out, dict_map const& in)
+    {
+        for(dict_map::const_iterator i = in.begin(); i != in.end(); ++i)
+            {
+            add_property(out, "value", i->second);
+            }
+    }
+
+    static void from_xml(dict_map& out, xml::node const& in)
+    {
+        out.clear();
+        xml::const_nodes_view items = in.elements("value");
+        for(xml::const_nodes_view::iterator i = items.begin(); i != items.end(); ++i)
+            {
+            TDBValue v;
+            xml_serialize::from_xml(v, *i);
+            out[v.GetKey()] = v;
+            }
+    }
+};
+
+} // namespace xml_serialize
+
+//============================================================================
 void DBDictionary::Init(std::string const& NewFilename)
 {
     // Perform the expensive operation of reading the dictionary from
@@ -90,6 +124,36 @@ void DBDictionary::Init(std::string const& NewFilename)
     CachedFilename = NewFilename;
     dictionary.erase(dictionary.begin(), dictionary.end());
 
+#ifndef LMI_NO_LEGACY_FORMATS
+    // We temporarily support reading both XML and the old file formats.
+    if(".db4" == fs::extension(NewFilename))
+        {
+            InitLegacy(NewFilename);
+            return;
+        }
+#endif
+
+    xml_lmi::dom_parser doc(NewFilename);
+    xml::element const& root = doc.root_node("database");
+
+    xml_serialize::get_property(root, "dictionary", dictionary);
+
+    if(NumberOfEntries != static_cast<int>(dictionary.size()))
+        {
+        std::ostringstream oss;
+        oss
+            << "is not up to date or is corrupted."
+            << " It should contain " << NumberOfEntries
+            << " elements, but it actually contains " << dictionary.size()
+            << " elements."
+            ;
+        BadFile(NewFilename, oss.str());
+        }
+}
+
+#ifndef LMI_NO_LEGACY_FORMATS
+void DBDictionary::InitLegacy(std::string const& NewFilename)
+{
     JRPS::JrPs_ifpstream ips
         (NewFilename.c_str()
         );
@@ -130,6 +194,7 @@ void DBDictionary::Init(std::string const& NewFilename)
         delete temp;
         }
 }
+#endif // !LMI_NO_LEGACY_FORMATS
 
 //============================================================================
 void DBDictionary::InvalidateCache()
@@ -161,17 +226,6 @@ void DBDictionary::BadFile(std::string const& Filename, std::string const& why)
 //============================================================================
 void DBDictionary::WriteDB(std::string const& filename)
 {
-    JRPS::JrPs_ofpstream ops
-        (filename.c_str()
-        ,JRPS::JrPs_pstream::xxtrunc | JRPS::JrPs_pstream::xxcreat
-        );
-    if(!ops)
-        {
-        fatal_error()
-            << "Cannot open database file '" << filename << "'."
-            << LMI_FLUSH
-            ;
-        }
     if(NumberOfEntries != static_cast<int>(dictionary.size()))
         {
         fatal_error()
@@ -189,10 +243,17 @@ void DBDictionary::WriteDB(std::string const& filename)
             }
         fatal_error() << LMI_FLUSH;
         }
-    ops << dictionary.size();
-    for(unsigned int j = 0; j < dictionary.size(); j++)
+
+    xml::document doc("database");
+    xml::node& root = doc.get_root_node();
+    xml_serialize::add_property(root, "dictionary", dictionary);
+
+    if(!doc.save_to_file(filename.c_str()))
         {
-        ops << &dictionary[j];
+        fatal_error()
+            << "Cannot open database file '" << filename << "'."
+            << LMI_FLUSH
+            ;
         }
 }
 
@@ -631,7 +692,7 @@ void DBDictionary::WriteSampleDBFile()
     Add(TDBValue(DB_ExpRatIBNRMult      , 6.0));
     Add(TDBValue(DB_ExpRatAmortPeriod   , 4.0));
 
-    WriteDB(AddDataDir("sample.db4"));
+    WriteDB(AddDataDir("sample.xdb4"));
 }
 
 //============================================================================
@@ -642,10 +703,16 @@ void print_databases()
     fs::directory_iterator end_i;
     for(; i != end_i; ++i)
         {
-        if(is_directory(*i) || ".db4" != fs::extension(*i))
-            {
+        if(is_directory(*i))
             continue;
-            }
+
+#ifdef LMI_NO_LEGACY_FORMATS
+        if(".xdb4" != fs::extension(*i))
+            continue;
+#else
+        if(".xdb4" != fs::extension(*i) && ".db4" != fs::extension(*i))
+            continue;
+#endif
 
         DBDictionary::instance().Init(i->string());
         fs::path out_file = fs::change_extension(*i, ".dbt");
diff --git a/ihs_dbdict.hpp b/ihs_dbdict.hpp
index 00f2ffe..0d81d4c 100644
--- a/ihs_dbdict.hpp
+++ b/ihs_dbdict.hpp
@@ -59,6 +59,9 @@ class LMI_SO DBDictionary
   private:
     DBDictionary();
 
+#ifndef LMI_NO_LEGACY_FORMATS
+    void InitLegacy(std::string const& NewFilename);
+#endif
     void WriteDB(std::string const& filename);
     void Add(TDBValue const& e);
     void BadFile(std::string const& Filename, std::string const& why);
diff --git a/ihs_dbvalue.cpp b/ihs_dbvalue.cpp
index 7f55784..dd8d30b 100644
--- a/ihs_dbvalue.cpp
+++ b/ihs_dbvalue.cpp
@@ -42,6 +42,8 @@
 #include <numeric>
 #include <ostream>
 
+#ifndef LMI_NO_LEGACY_FORMATS
+
 #ifndef stlstrm_hpp
 #define stlstrm_hpp
 
@@ -88,6 +90,8 @@ JRPS::JrPs_opstream& operator<< (JRPS::JrPs_opstream& ops, std::vector<T> const&
 
 #endif // stlstrm_hpp
 
+#endif // !LMI_NO_LEGACY_FORMATS
+
 static int const ScalarDims[TDBValue::e_number_of_axes] = {1, 1, 1, 1, 1, 1, 1};
 static int const MaxPossibleElements = std::numeric_limits<int>::max();
 
@@ -187,8 +191,12 @@ TDBValue::TDBValue
 
 //============================================================================
 TDBValue::TDBValue(TDBValue const& obj)
-    :JRPS::JrPs_pstreamable()
-    ,key(obj.key)
+    :
+#ifndef LMI_NO_LEGACY_FORMATS
+     JRPS::JrPs_pstreamable()
+    ,
+#endif
+     key(obj.key)
     ,axis_lengths(obj.axis_lengths)
     ,data_values(obj.data_values)
 {
@@ -692,7 +700,43 @@ std::ostream& operator<<(std::ostream& os, TDBValue const& z)
     return z.write(os);
 }
 
-// Streaming implementation
+// Serialization implementation
+
+void TDBValue::read(xml::node const& n)
+{
+    using namespace xml_serialize;
+
+    // This is the only place DatabaseNames value is serialized. Adding
+    // "proper" support for it as type_io<> specialization would be
+    // complicated, because choose_type_io<> unconditionally chooses
+    // type_io<mc_enum>. It doesn't seem worth the extra complications in
+    // xml_serialize code, so we do the conversion manually instead, for other
+    // code's simplicity sake.
+    key = db_name_to_index(get_property<std::string>(n, "key"));
+    get_property(n, "axis_lengths", axis_lengths);
+    get_property(n, "extra_axes_values", extra_axes_values);
+    get_property(n, "extra_axes_names", extra_axes_names);
+    get_property(n, "data_values", data_values);
+
+    LMI_ASSERT(getndata() == static_cast<int>(data_values.size()));
+    LMI_ASSERT
+        (   0 < static_cast<int>(data_values.size())
+        &&      static_cast<int>(data_values.size()) < MaxPossibleElements
+        );
+}
+
+void TDBValue::write(xml::node& n) const
+{
+    using namespace xml_serialize;
+
+    add_property(n, "key", db_name_from_index(key));
+    add_property(n, "axis_lengths", axis_lengths);
+    add_property(n, "extra_axes_values", extra_axes_values);
+    add_property(n, "extra_axes_names", extra_axes_names);
+    add_property(n, "data_values", data_values);
+}
+
+#ifndef LMI_NO_LEGACY_FORMATS
 
 TDBValue::TDBValue(JRPS::JrPs_pstreamableInit)
     :key(0)
@@ -712,11 +756,6 @@ JRPS::JrPs_pstreamreg RegTDBValue
     );
 
 // TODO ?? Couldn't templates handle this?
-JRPS::JrPs_opstream& operator<< (JRPS::JrPs_opstream& os, TDBValue const* p)
-{
-    return os << (JRPS::JrPs_pstreamable const*)p;
-}
-
 JRPS::JrPs_ipstream& operator>> (JRPS::JrPs_ipstream& is, TDBValue*& p)
 {
     return is >> (void const*&)p;
@@ -753,22 +792,9 @@ void* TDBValue::read(JRPS::JrPs_ipstream& is)
     return this;
 }
 
-void TDBValue::write(JRPS::JrPs_opstream& os) const
+void TDBValue::write(JRPS::JrPs_opstream&) const
 {
-    os << StreamingVersion;
-
-    LMI_ASSERT(getndata() == static_cast<int>(data_values.size()));
-    LMI_ASSERT
-        (   0 < static_cast<int>(data_values.size())
-        &&      static_cast<int>(data_values.size()) < MaxPossibleElements
-        );
-    AreAllAxesOK();
-
-    os << key;
-    os << axis_lengths;
-    os << extra_axes_values;
-    os << extra_axes_names;
-    os << data_values;
+    fatal_error() << "TDBValue::write() not implemented." << LMI_FLUSH;
 }
 
 char const* TDBValue::streamableName() const
@@ -776,3 +802,4 @@ char const* TDBValue::streamableName() const
     return "TDBValue";
 }
 
+#endif // !LMI_NO_LEGACY_FORMATS
diff --git a/ihs_dbvalue.hpp b/ihs_dbvalue.hpp
index e6097af..ebee156 100644
--- a/ihs_dbvalue.hpp
+++ b/ihs_dbvalue.hpp
@@ -32,9 +32,12 @@
 
 #include "dbindex.hpp"
 #include "so_attributes.hpp"
+#include "xml_serialize.hpp"
 
+#ifndef LMI_NO_LEGACY_FORMATS
 #include "ihs_fpios.hpp"
 namespace JRPS = JOSHUA_ROWE_PERSISTENT_STREAMS;
+#endif
 
 #include <iosfwd>
 #include <string>
@@ -50,10 +53,13 @@ enum e_IdxType
     };
 
 class LMI_SO TDBValue
+#ifndef LMI_NO_LEGACY_FORMATS
     :private JRPS::JrPs_pstreamable
+#endif
 {
     friend std::istream& operator>>(std::istream&, TDBValue&);
     friend std::ostream& operator<<(std::ostream&, TDBValue const&);
+    friend class xml_serialize::type_io<TDBValue>;
 
   public:
     // Separate enumerators here facilitate compile-time assertions
@@ -122,6 +128,8 @@ class LMI_SO TDBValue
     void ParanoidCheck()                      const;
     bool AreAllAxesOK()                       const;
     void FixupIndex(std::vector<double>& idx) const;
+    void read(xml::node const&);
+    void write(xml::node&) const;
 
     int  key;        // Database dictionary key
 
@@ -152,6 +160,7 @@ class LMI_SO TDBValue
     std::vector<double>      extra_axes_values;
     std::vector<e_IdxType>   extra_axes_types;
 
+#ifndef LMI_NO_LEGACY_FORMATS
 // The following sections don't follow the normal order for access
 // specifiers. Grouping them together here facilitates their
 // expunction as soon as we get rid of 'ihs_[f]pios.?pp'.
@@ -159,7 +168,6 @@ class LMI_SO TDBValue
   public:
     static JRPS::JrPs_pstreamable* jrps_build();
     friend JRPS::JrPs_ipstream& operator>> (JRPS::JrPs_ipstream&, TDBValue*&);
-    friend JRPS::JrPs_opstream& operator<< (JRPS::JrPs_opstream&, TDBValue const*);
 
   protected:
     virtual void*   read(JRPS::JrPs_ipstream&);
@@ -169,8 +177,21 @@ class LMI_SO TDBValue
     TDBValue(JRPS::JrPs_pstreamableInit);
     virtual char const* streamableName() const;
     enum {StreamingVersion = 1};
+#endif // !LMI_NO_LEGACY_FORMATS
 };
 
+namespace xml_serialize
+{
+
+template<>
+struct type_io<TDBValue>
+{
+    static void to_xml(xml::node& out, TDBValue const& in)   { in.write(out); }
+    static void from_xml(TDBValue& out, xml::node const& in) { out.read(in); }
+};
+
+} // namespace xml_serialize
+
 /*
 Database items should be allowed to vary across numerous axes, such as
     gender
diff --git a/ihs_fpios.cpp b/ihs_fpios.cpp
index 4332e26..b2a8e95 100644
--- a/ihs_fpios.cpp
+++ b/ihs_fpios.cpp
@@ -52,6 +52,8 @@
 
 #include "ihs_fpios.hpp" // GWC arbitrarily changed .hh to .hpp
 
+#ifndef LMI_NO_LEGACY_FORMATS
+
 // Begin GWC mods
 // Neither of these is in the language standard
 //#include  <unistd.h>
@@ -341,3 +343,4 @@ JrPs_iofpstream::JrPs_iofpstream(const char* aname, int aflags, int amode)
 
 }   // namespace JOSHUA_ROWE_PERSISTENT_STREAMS
 
+#endif // !LMI_NO_LEGACY_FORMATS
diff --git a/ihs_fpios.hpp b/ihs_fpios.hpp
index fe77c1d..1c5a318 100644
--- a/ihs_fpios.hpp
+++ b/ihs_fpios.hpp
@@ -47,6 +47,8 @@
 
 #include "config.hpp"
 
+#ifndef LMI_NO_LEGACY_FORMATS
+
 #include "ihs_pios.hpp" // GWC arbitrarily changed .hh to .hpp
 #include "so_attributes.hpp"
 
@@ -185,5 +187,8 @@ public:
 };
 
 }   // namespace JOSHUA_ROWE_PERSISTENT_STREAMS
+
+#endif // !LMI_NO_LEGACY_FORMATS
+
 #endif // ihs_fpios_hpp
 
diff --git a/ihs_funddata.cpp b/ihs_funddata.cpp
index c65b0d3..fd07f3c 100644
--- a/ihs_funddata.cpp
+++ b/ihs_funddata.cpp
@@ -22,8 +22,6 @@
 // $Id$
 
 // This class describes funds: their names and investment mgmt fees.
-// TODO ?? An extension other than .fnd would be preferable: msw uses
-// .fnd for "find"
 
 #include LMI_PCH_HEADER
 #ifdef __BORLANDC__
@@ -36,6 +34,9 @@
 #include "assert_lmi.hpp"
 #include "data_directory.hpp"
 #include "platform_dependent.hpp" // access()
+#include "xml_serialize.hpp"
+
+#include <boost/filesystem/convenience.hpp>
 
 #include <fstream>
 
@@ -65,6 +66,33 @@ FundInfo::~FundInfo()
 }
 
 //============================================================================
+namespace xml_serialize
+{
+    template<>
+    struct type_io<FundInfo>
+    {
+        static void to_xml(xml::node& out, FundInfo const& in)
+        {
+            add_property(out, "scalar_imf", in.ScalarIMF());
+            add_property(out, "short_name", in.ShortName());
+            add_property(out, "long_name",  in.LongName());
+        }
+
+        static void from_xml(FundInfo& out, xml::node const& in)
+        {
+            double simf;
+            std::string sname, lname;
+
+            get_property(in, "scalar_imf", simf);
+            get_property(in, "short_name", sname);
+            get_property(in, "long_name",  lname);
+
+            out = FundInfo(simf, sname, lname);
+        }
+    };
+} // namespace xml_serialize
+
+//============================================================================
 FundData::FundData()
 {
 }
@@ -81,17 +109,9 @@ FundData::~FundData()
 }
 
 //============================================================================
-void FundData::Read(std::string const& a_Filename)
+#ifndef LMI_NO_LEGACY_FORMATS
+void FundData::ReadLegacy(std::string const& a_Filename)
 {
-    if(access(a_Filename.c_str(), R_OK))
-        {
-        fatal_error()
-            << "File '"
-            << a_Filename
-            << "' is required but could not be found. Try reinstalling."
-            << LMI_FLUSH
-            ;
-        }
     std::ifstream is(a_Filename.c_str());
 
     LMI_ASSERT(0 == FundInfo_.size());
@@ -121,26 +141,40 @@ void FundData::Read(std::string const& a_Filename)
         FundInfo_.push_back(f);
         }
 }
+#endif // !LMI_NO_LEGACY_FORMATS
 
-//============================================================================
-void FundData::Write(std::string const& a_Filename)
+void FundData::Read(std::string const& a_Filename)
 {
-    std::ofstream os(a_Filename.c_str());
-
-    std::vector<FundInfo>::const_iterator i = FundInfo_.begin();
-    for(;i != FundInfo_.end(); i++)
+    if(access(a_Filename.c_str(), R_OK))
         {
-        os
-            << i->ScalarIMF_
-            << '\t'
-            << i->ShortName_
-            << '\t'
-            << i->LongName_
-            << '\n'
+        fatal_error()
+            << "File '"
+            << a_Filename
+            << "' is required but could not be found. Try reinstalling."
+            << LMI_FLUSH
             ;
         }
 
-    if(!os.good())
+#ifndef LMI_NO_LEGACY_FORMATS
+    // We temporarily support reading both XML and the old file formats.
+    if(".fnd" == fs::extension(a_Filename))
+        {
+        ReadLegacy(a_Filename);
+        return;
+        }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+    xml_lmi::dom_parser doc(a_Filename);
+    xml_serialize::from_xml(FundInfo_, doc.root_node("fund_data"));
+}
+
+//============================================================================
+void FundData::Write(std::string const& a_Filename)
+{
+    xml::document doc("fund_data");
+    xml_serialize::to_xml(doc.get_root_node(), FundInfo_);
+
+    if(!doc.save_to_file(a_Filename.c_str()))
         {
         fatal_error()
             << "Unable to write fund file '"
@@ -162,6 +196,6 @@ void FundData::WriteFundFiles()
             ,"Money Market Fund"
             )
         );
-    foo.Write(AddDataDir("sample.fnd"));
+    foo.Write(AddDataDir("sample.xfnd"));
 }
 
diff --git a/ihs_funddata.hpp b/ihs_funddata.hpp
index 7c4d98b..e060049 100644
--- a/ihs_funddata.hpp
+++ b/ihs_funddata.hpp
@@ -80,6 +80,9 @@ class LMI_SO FundData
     FundData(); // Private, but implemented.
 
     void Read (std::string const& a_Filename);
+#ifndef LMI_NO_LEGACY_FORMATS
+    void ReadLegacy (std::string const& a_Filename);
+#endif
     void Write(std::string const& a_Filename);
 
     std::vector<FundInfo> FundInfo_;
diff --git a/ihs_pios.cpp b/ihs_pios.cpp
index db7b79d..d26480a 100644
--- a/ihs_pios.cpp
+++ b/ihs_pios.cpp
@@ -49,6 +49,8 @@
 
 #include "ihs_pios.hpp" // GWC arbitrarily changed .hh to .hpp
 
+#ifndef LMI_NO_LEGACY_FORMATS
+
 #include <cstring>
 
 namespace JOSHUA_ROWE_PERSISTENT_STREAMS
@@ -615,3 +617,4 @@ void    JrPs_pstreambuf::error(int aerror)
 
 }   // namespace JOSHUA_ROWE_PERSISTENT_STREAMS
 
+#endif // !LMI_NO_LEGACY_FORMATS
diff --git a/ihs_pios.hpp b/ihs_pios.hpp
index 64444b4..498db92 100644
--- a/ihs_pios.hpp
+++ b/ihs_pios.hpp
@@ -47,6 +47,8 @@
 
 #include "config.hpp"
 
+#ifndef LMI_NO_LEGACY_FORMATS
+
 #include "so_attributes.hpp"
 
 #include <cstddef> // std::size_t
@@ -291,5 +293,7 @@ public:
 
 }   // namespace JOSHUA_ROWE_PERSISTENT_STREAMS
 
+#endif // !LMI_NO_LEGACY_FORMATS
+
 #endif // ihs_pios_hpp
 
diff --git a/ihs_proddata.cpp b/ihs_proddata.cpp
index aea0df0..bbf79a3 100644
--- a/ihs_proddata.cpp
+++ b/ihs_proddata.cpp
@@ -42,6 +42,7 @@
 #include "assert_lmi.hpp"
 #include "data_directory.hpp"
 #include "platform_dependent.hpp" // access()
+#include "xml_serialize.hpp"
 
 #include <boost/filesystem/convenience.hpp>
 #include <boost/filesystem/path.hpp>
@@ -70,22 +71,14 @@ void TProductData::Init(std::string const& a_Filename)
 {
     fs::path path(a_Filename);
     LMI_ASSERT(a_Filename == path.leaf());
-    path = fs::change_extension(path, ".pol");
+    path = fs::change_extension(path, ".xpol");
     Read(AddDataDir(path.string()));
 }
 
 //============================================================================
-void TProductData::Read(std::string const& a_Filename)
+#ifndef LMI_NO_LEGACY_FORMATS
+void TProductData::ReadLegacy(std::string const& a_Filename)
 {
-    if(access(a_Filename.c_str(), R_OK))
-        {
-        fatal_error()
-            << "File '"
-            << a_Filename
-            << "' is required but could not be found. Try reinstalling."
-            << LMI_FLUSH
-            ;
-        }
     std::ifstream is(a_Filename.c_str());
 
     std::getline(is, DatabaseFilename,              '\n');
@@ -212,114 +205,251 @@ void TProductData::Read(std::string const& a_Filename)
             ;
         }
 }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+//============================================================================
+void TProductData::Read(std::string const& a_Filename)
+{
+    if(access(a_Filename.c_str(), R_OK))
+        {
+        fatal_error()
+            << "File '"
+            << a_Filename
+            << "' is required but could not be found. Try reinstalling."
+            << LMI_FLUSH
+            ;
+        }
+
+#ifndef LMI_NO_LEGACY_FORMATS
+    // We temporarily support reading both XML and the old file formats.
+    if(".pol" == fs::extension(a_Filename))
+        {
+        ReadLegacy(a_Filename);
+        return;
+        }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+    xml_lmi::dom_parser doc(a_Filename);
+    xml::element const& root = doc.root_node("product");
+
+    #define GET_PROP(name) xml_serialize::get_property(root, #name, name)
+
+    GET_PROP(DatabaseFilename);
+    GET_PROP(FundFilename);
+    GET_PROP(CorridorFilename);
+    GET_PROP(CurrCOIFilename);
+    GET_PROP(GuarCOIFilename);
+    GET_PROP(WPFilename);
+    GET_PROP(ADDFilename);
+    GET_PROP(ChildRiderFilename);
+    GET_PROP(CurrSpouseRiderFilename);
+    GET_PROP(GuarSpouseRiderFilename);
+    GET_PROP(CurrTermFilename);
+    GET_PROP(GuarTermFilename);
+    GET_PROP(TableYFilename);
+    GET_PROP(PremTaxFilename);
+    GET_PROP(TAMRA7PayFilename);
+    GET_PROP(TgtPremFilename);
+    GET_PROP(IRC7702Filename);
+    GET_PROP(Gam83Filename);
+    GET_PROP(SubstdTblMultFilename);
+    GET_PROP(CurrSpecAmtLoadFilename);
+    GET_PROP(GuarSpecAmtLoadFilename);
+    GET_PROP(RoundingFilename);
+    GET_PROP(TierFilename);
+    GET_PROP(PolicyForm);
+    GET_PROP(PolicyMktgName);
+    GET_PROP(PolicyLegalName);
+    GET_PROP(InsCoShortName);
+    GET_PROP(InsCoName);
+    GET_PROP(InsCoAddr);
+    GET_PROP(InsCoStreet);
+    GET_PROP(InsCoPhone);
+    GET_PROP(InsCoDomicile);
+    GET_PROP(MainUnderwriter);
+    GET_PROP(MainUnderwriterAddress);
+    GET_PROP(CoUnderwriter);
+    GET_PROP(CoUnderwriterAddress);
+    GET_PROP(AvName);
+    GET_PROP(CsvName);
+    GET_PROP(CsvHeaderName);
+    GET_PROP(NoLapseProvisionName);
+    GET_PROP(InterestDisclaimer);
+    GET_PROP(GuarMortalityFootnote);
+    GET_PROP(AccountValueFootnote);
+    GET_PROP(AttainedAgeFootnote);
+    GET_PROP(CashSurrValueFootnote);
+    GET_PROP(DeathBenefitFootnote);
+    GET_PROP(InitialPremiumFootnote);
+    GET_PROP(NetPremiumFootnote);
+    GET_PROP(OutlayFootnote);
+    GET_PROP(PolicyYearFootnote);
+    GET_PROP(ADDFootnote);
+    GET_PROP(ChildFootnote);
+    GET_PROP(SpouseFootnote);
+    GET_PROP(TermFootnote);
+    GET_PROP(WaiverFootnote);
+    GET_PROP(MinimumPremiumFootnote);
+    GET_PROP(PremAllocationFootnote);
+    GET_PROP(ProductDescription);
+    GET_PROP(StableValueFootnote);
+    GET_PROP(NoVanishPremiumFootnote);
+    GET_PROP(RejectPremiumFootnote);
+    GET_PROP(ExpRatingFootnote);
+    GET_PROP(MortalityBlendFootnote);
+    GET_PROP(HypotheticalRatesFootnote);
+    GET_PROP(SalesLoadRefundFootnote);
+    GET_PROP(NoLapseFootnote);
+    GET_PROP(MarketValueAdjFootnote);
+    GET_PROP(ExchangeChargeFootnote0);
+    GET_PROP(CurrentValuesFootnote);
+    GET_PROP(DBOption1Footnote);
+    GET_PROP(DBOption2Footnote);
+    GET_PROP(ExpRatRiskChargeFootnote);
+    GET_PROP(ExchangeChargeFootnote1);
+    GET_PROP(FlexiblePremiumFootnote);
+    GET_PROP(GuaranteedValuesFootnote);
+    GET_PROP(CreditingRateFootnote);
+    GET_PROP(MecFootnote);
+    GET_PROP(MidpointValuesFootnote);
+    GET_PROP(SinglePremiumFootnote);
+    GET_PROP(MonthlyChargesFootnote);
+    GET_PROP(UltCreditingRateFootnote);
+    GET_PROP(MaxNaarFootnote);
+    GET_PROP(PremTaxSurrChgFootnote);
+    GET_PROP(PolicyFeeFootnote);
+    GET_PROP(AssetChargeFootnote);
+    GET_PROP(InvestmentIncomeFootnote);
+    GET_PROP(IrrDbFootnote);
+    GET_PROP(IrrCsvFootnote);
+    GET_PROP(MortalityChargesFootnote);
+    GET_PROP(LoanAndWithdrawalFootnote);
+    GET_PROP(PresaleTrackingNumber);
+    GET_PROP(CompositeTrackingNumber);
+    GET_PROP(InforceTrackingNumber);
+    GET_PROP(InforceCompositeTrackingNumber);
+    GET_PROP(InforceNonGuaranteedFootnote0);
+    GET_PROP(InforceNonGuaranteedFootnote1);
+    GET_PROP(InforceNonGuaranteedFootnote2);
+    GET_PROP(InforceNonGuaranteedFootnote3);
+    GET_PROP(NonGuaranteedFootnote);
+    GET_PROP(MonthlyChargesPaymentFootnote);
+
+    #undef GET_PROP
+}
 
 //============================================================================
 void TProductData::Write(std::string const& a_Filename) const
 {
-    std::ofstream os(a_Filename.c_str());
-
-    os << DatabaseFilename              << '\n';
-    os << FundFilename                  << '\n';
-    os << CorridorFilename              << '\n';
-    os << CurrCOIFilename               << '\n';
-    os << GuarCOIFilename               << '\n';
-    os << WPFilename                    << '\n';
-    os << ADDFilename                   << '\n';
-    os << ChildRiderFilename            << '\n';
-    os << CurrSpouseRiderFilename       << '\n';
-    os << GuarSpouseRiderFilename       << '\n';
-    os << CurrTermFilename              << '\n';
-    os << GuarTermFilename              << '\n';
-    os << TableYFilename                << '\n';
-    os << PremTaxFilename               << '\n';
-    os << TAMRA7PayFilename             << '\n';
-    os << TgtPremFilename               << '\n';
-    os << IRC7702Filename               << '\n';
-    os << Gam83Filename                 << '\n';
-    os << SubstdTblMultFilename         << '\n';
-    os << CurrSpecAmtLoadFilename       << '\n';
-    os << GuarSpecAmtLoadFilename       << '\n';
-    os << RoundingFilename              << '\n';
-    os << TierFilename                  << '\n';
-    os << PolicyForm                    << '\n';
-    os << PolicyMktgName                << '\n';
-    os << PolicyLegalName               << '\n';
-    os << InsCoShortName                << '\n';
-    os << InsCoName                     << '\n';
-    os << InsCoAddr                     << '\n';
-    os << InsCoStreet                   << '\n';
-    os << InsCoPhone                    << '\n';
-    os << InsCoDomicile                 << '\n';
-    os << MainUnderwriter               << '\n';
-    os << MainUnderwriterAddress        << '\n';
-    os << CoUnderwriter                 << '\n';
-    os << CoUnderwriterAddress          << '\n';
-    os << AvName                        << '\n';
-    os << CsvName                       << '\n';
-    os << CsvHeaderName                 << '\n';
-    os << NoLapseProvisionName          << '\n';
-    os << InterestDisclaimer            << '\n';
-    os << GuarMortalityFootnote         << '\n';
-    os << AccountValueFootnote          << '\n';
-    os << AttainedAgeFootnote           << '\n';
-    os << CashSurrValueFootnote         << '\n';
-    os << DeathBenefitFootnote          << '\n';
-    os << InitialPremiumFootnote        << '\n';
-    os << NetPremiumFootnote            << '\n';
-    os << OutlayFootnote                << '\n';
-    os << PolicyYearFootnote            << '\n';
-    os << ADDFootnote                   << '\n';
-    os << ChildFootnote                 << '\n';
-    os << SpouseFootnote                << '\n';
-    os << TermFootnote                  << '\n';
-    os << WaiverFootnote                << '\n';
-    os << MinimumPremiumFootnote        << '\n';
-    os << PremAllocationFootnote        << '\n';
-    os << ProductDescription            << '\n';
-    os << StableValueFootnote           << '\n';
-    os << NoVanishPremiumFootnote       << '\n';
-    os << RejectPremiumFootnote         << '\n';
-    os << ExpRatingFootnote             << '\n';
-    os << MortalityBlendFootnote        << '\n';
-    os << HypotheticalRatesFootnote     << '\n';
-    os << SalesLoadRefundFootnote       << '\n';
-    os << NoLapseFootnote               << '\n';
-    os << MarketValueAdjFootnote        << '\n';
-    os << ExchangeChargeFootnote0       << '\n';
-    os << CurrentValuesFootnote         << '\n';
-    os << DBOption1Footnote             << '\n';
-    os << DBOption2Footnote             << '\n';
-    os << ExpRatRiskChargeFootnote      << '\n';
-    os << ExchangeChargeFootnote1       << '\n';
-    os << FlexiblePremiumFootnote       << '\n';
-    os << GuaranteedValuesFootnote      << '\n';
-    os << CreditingRateFootnote         << '\n';
-    os << MecFootnote                   << '\n';
-    os << MidpointValuesFootnote        << '\n';
-    os << SinglePremiumFootnote         << '\n';
-    os << MonthlyChargesFootnote        << '\n';
-    os << UltCreditingRateFootnote      << '\n';
-    os << MaxNaarFootnote               << '\n';
-    os << PremTaxSurrChgFootnote        << '\n';
-    os << PolicyFeeFootnote             << '\n';
-    os << AssetChargeFootnote           << '\n';
-    os << InvestmentIncomeFootnote      << '\n';
-    os << IrrDbFootnote                 << '\n';
-    os << IrrCsvFootnote                << '\n';
-    os << MortalityChargesFootnote      << '\n';
-    os << LoanAndWithdrawalFootnote     << '\n';
-    os << PresaleTrackingNumber         << '\n';
-    os << CompositeTrackingNumber       << '\n';
-    os << InforceTrackingNumber         << '\n';
-    os << InforceCompositeTrackingNumber<< '\n';
-    os << InforceNonGuaranteedFootnote0 << '\n';
-    os << InforceNonGuaranteedFootnote1 << '\n';
-    os << InforceNonGuaranteedFootnote2 << '\n';
-    os << InforceNonGuaranteedFootnote3 << '\n';
-    os << NonGuaranteedFootnote         << '\n';
-    os << MonthlyChargesPaymentFootnote << '\n';
-
-    if(!os.good())
+    xml::document doc("product");
+    xml::node& root = doc.get_root_node();
+
+    #define ADD_PROP(name) xml_serialize::add_property(root, #name, name)
+
+    ADD_PROP(DatabaseFilename);
+    ADD_PROP(FundFilename);
+    ADD_PROP(CorridorFilename);
+    ADD_PROP(CurrCOIFilename);
+    ADD_PROP(GuarCOIFilename);
+    ADD_PROP(WPFilename);
+    ADD_PROP(ADDFilename);
+    ADD_PROP(ChildRiderFilename);
+    ADD_PROP(CurrSpouseRiderFilename);
+    ADD_PROP(GuarSpouseRiderFilename);
+    ADD_PROP(CurrTermFilename);
+    ADD_PROP(GuarTermFilename);
+    ADD_PROP(TableYFilename);
+    ADD_PROP(PremTaxFilename);
+    ADD_PROP(TAMRA7PayFilename);
+    ADD_PROP(TgtPremFilename);
+    ADD_PROP(IRC7702Filename);
+    ADD_PROP(Gam83Filename);
+    ADD_PROP(SubstdTblMultFilename);
+    ADD_PROP(CurrSpecAmtLoadFilename);
+    ADD_PROP(GuarSpecAmtLoadFilename);
+    ADD_PROP(RoundingFilename);
+    ADD_PROP(TierFilename);
+    ADD_PROP(PolicyForm);
+    ADD_PROP(PolicyMktgName);
+    ADD_PROP(PolicyLegalName);
+    ADD_PROP(InsCoShortName);
+    ADD_PROP(InsCoName);
+    ADD_PROP(InsCoAddr);
+    ADD_PROP(InsCoStreet);
+    ADD_PROP(InsCoPhone);
+    ADD_PROP(InsCoDomicile);
+    ADD_PROP(MainUnderwriter);
+    ADD_PROP(MainUnderwriterAddress);
+    ADD_PROP(CoUnderwriter);
+    ADD_PROP(CoUnderwriterAddress);
+    ADD_PROP(AvName);
+    ADD_PROP(CsvName);
+    ADD_PROP(CsvHeaderName);
+    ADD_PROP(NoLapseProvisionName);
+    ADD_PROP(InterestDisclaimer);
+    ADD_PROP(GuarMortalityFootnote);
+    ADD_PROP(AccountValueFootnote);
+    ADD_PROP(AttainedAgeFootnote);
+    ADD_PROP(CashSurrValueFootnote);
+    ADD_PROP(DeathBenefitFootnote);
+    ADD_PROP(InitialPremiumFootnote);
+    ADD_PROP(NetPremiumFootnote);
+    ADD_PROP(OutlayFootnote);
+    ADD_PROP(PolicyYearFootnote);
+    ADD_PROP(ADDFootnote);
+    ADD_PROP(ChildFootnote);
+    ADD_PROP(SpouseFootnote);
+    ADD_PROP(TermFootnote);
+    ADD_PROP(WaiverFootnote);
+    ADD_PROP(MinimumPremiumFootnote);
+    ADD_PROP(PremAllocationFootnote);
+    ADD_PROP(ProductDescription);
+    ADD_PROP(StableValueFootnote);
+    ADD_PROP(NoVanishPremiumFootnote);
+    ADD_PROP(RejectPremiumFootnote);
+    ADD_PROP(ExpRatingFootnote);
+    ADD_PROP(MortalityBlendFootnote);
+    ADD_PROP(HypotheticalRatesFootnote);
+    ADD_PROP(SalesLoadRefundFootnote);
+    ADD_PROP(NoLapseFootnote);
+    ADD_PROP(MarketValueAdjFootnote);
+    ADD_PROP(ExchangeChargeFootnote0);
+    ADD_PROP(CurrentValuesFootnote);
+    ADD_PROP(DBOption1Footnote);
+    ADD_PROP(DBOption2Footnote);
+    ADD_PROP(ExpRatRiskChargeFootnote);
+    ADD_PROP(ExchangeChargeFootnote1);
+    ADD_PROP(FlexiblePremiumFootnote);
+    ADD_PROP(GuaranteedValuesFootnote);
+    ADD_PROP(CreditingRateFootnote);
+    ADD_PROP(MecFootnote);
+    ADD_PROP(MidpointValuesFootnote);
+    ADD_PROP(SinglePremiumFootnote);
+    ADD_PROP(MonthlyChargesFootnote);
+    ADD_PROP(UltCreditingRateFootnote);
+    ADD_PROP(MaxNaarFootnote);
+    ADD_PROP(PremTaxSurrChgFootnote);
+    ADD_PROP(PolicyFeeFootnote);
+    ADD_PROP(AssetChargeFootnote);
+    ADD_PROP(InvestmentIncomeFootnote);
+    ADD_PROP(IrrDbFootnote);
+    ADD_PROP(IrrCsvFootnote);
+    ADD_PROP(MortalityChargesFootnote);
+    ADD_PROP(LoanAndWithdrawalFootnote);
+    ADD_PROP(PresaleTrackingNumber);
+    ADD_PROP(CompositeTrackingNumber);
+    ADD_PROP(InforceTrackingNumber);
+    ADD_PROP(InforceCompositeTrackingNumber);
+    ADD_PROP(InforceNonGuaranteedFootnote0);
+    ADD_PROP(InforceNonGuaranteedFootnote1);
+    ADD_PROP(InforceNonGuaranteedFootnote2);
+    ADD_PROP(InforceNonGuaranteedFootnote3);
+    ADD_PROP(NonGuaranteedFootnote);
+    ADD_PROP(MonthlyChargesPaymentFootnote);
+
+    #undef ADD_PROP
+
+    if(!doc.save_to_file(a_Filename.c_str()))
         {
         fatal_error()
             << "Unable to write product data file '"
@@ -432,11 +562,11 @@ void TProductData::WritePolFiles()
     foo.NonGuaranteedFootnote          = "";
     foo.MonthlyChargesPaymentFootnote  = "";
 
-    foo.DatabaseFilename = "sample.db4";
-    foo.FundFilename     = "sample.fnd";
-    foo.RoundingFilename = "sample.rnd";
-    foo.TierFilename     = "sample.tir";
+    foo.DatabaseFilename = "sample.xdb4";
+    foo.FundFilename     = "sample.xfnd";
+    foo.RoundingFilename = "sample.xrnd";
+    foo.TierFilename     = "sample.xtir";
 
-    foo.Write(AddDataDir("sample.pol"));
+    foo.Write(AddDataDir("sample.xpol"));
 }
 
diff --git a/ihs_proddata.hpp b/ihs_proddata.hpp
index 36cf16a..5bd9e94 100644
--- a/ihs_proddata.hpp
+++ b/ihs_proddata.hpp
@@ -151,6 +151,9 @@ class LMI_SO TProductData
 
     void Init(std::string const& a_Filename);
     void Read(std::string const& a_Filename);
+#ifndef LMI_NO_LEGACY_FORMATS
+    void ReadLegacy(std::string const& a_Filename);
+#endif
     void Write(std::string const& a_Filename) const;
 
     std::string DatabaseFilename;
diff --git a/ihs_rnddata.cpp b/ihs_rnddata.cpp
index c843b85..4665b64 100644
--- a/ihs_rnddata.cpp
+++ b/ihs_rnddata.cpp
@@ -33,6 +33,10 @@
 #include "alert.hpp"
 #include "data_directory.hpp"
 #include "platform_dependent.hpp" // access()
+#include "xml_serialize.hpp"
+
+#include <boost/filesystem/convenience.hpp>
+#include <boost/static_assert.hpp>
 
 #include <fstream>
 
@@ -65,8 +69,32 @@ rounding_rules const& StreamableRoundingRules::get_rounding_rules()
     return dynamic_cast<rounding_rules&>(*this);
 }
 
+namespace xml_serialize
+{
+    template<>
+    struct type_io< round_to<double> >
+    {
+        static void to_xml(xml::node& out, round_to<double> const& in)
+        {
+            add_property(out, "decimals", in.decimals());
+            add_property(out, "style", in.style());
+        }
+
+        static void from_xml(round_to<double>& out, xml::node const& in)
+        {
+            int decimals;
+            get_property(in, "decimals", decimals);
+            rounding_style style;
+            get_property(in, "style", style);
+            out = round_to<double>(decimals, style);
+        }
+    };
+} // namespace xml_serialize
+
+#ifndef LMI_NO_LEGACY_FORMATS
 namespace
 {
+    // for legacy file format, see ReadLegacy() below
     inline std::istream& operator>>(std::istream& is, round_to<double>& r)
         {
         int decimals;
@@ -77,25 +105,11 @@ namespace
         r = round_to<double>(decimals, style);
         return is;
         }
-    inline std::ostream& operator<<(std::ostream& os, round_to<double> const& r)
-        {
-        os << r.decimals() << '\n';
-        os << r.style() << '\n';
-        return os;
-        }
 } // Unnamed namespace.
 
 //============================================================================
-void StreamableRoundingRules::Read(std::string const& a_Filename)
+void StreamableRoundingRules::ReadLegacy(std::string const& a_Filename)
 {
-    if(access(a_Filename.c_str(), R_OK))
-        {
-        fatal_error()
-            << "File '"
-            << a_Filename
-            << "' is required but could not be found. Try reinstalling."
-            ;
-        }
     std::ifstream is(a_Filename.c_str());
 
     is >> round_specamt_;
@@ -136,28 +150,73 @@ void StreamableRoundingRules::Read(std::string const& a_Filename)
             ;
         }
 }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+//============================================================================
+void StreamableRoundingRules::Read(std::string const& a_Filename)
+{
+    if(access(a_Filename.c_str(), R_OK))
+        {
+        fatal_error()
+            << "File '"
+            << a_Filename
+            << "' is required but could not be found. Try reinstalling."
+            ;
+        }
+
+#ifndef LMI_NO_LEGACY_FORMATS
+    // We temporarily support reading both XML and the old file formats.
+    if(".rnd" == fs::extension(a_Filename))
+        {
+        ReadLegacy(a_Filename);
+        return;
+        }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+    xml_lmi::dom_parser doc(a_Filename);
+    xml::element const& root = doc.root_node("rounding");
+
+    using namespace xml_serialize;
+    get_property(root, "specamt",          round_specamt_);
+    get_property(root, "death_benefit",    round_death_benefit_);
+    get_property(root, "naar",             round_naar_);
+    get_property(root, "coi_rate",         round_coi_rate_);
+    get_property(root, "coi_charge",       round_coi_charge_);
+    get_property(root, "gross_premium",    round_gross_premium_);
+    get_property(root, "net_premium",      round_net_premium_);
+    get_property(root, "interest_rate",    round_interest_rate_);
+    get_property(root, "interest_credit",  round_interest_credit_);
+    get_property(root, "withdrawal",       round_withdrawal_);
+    get_property(root, "loan",             round_loan_);
+    get_property(root, "corridor_factor",  round_corridor_factor_);
+    get_property(root, "surrender_charge", round_surrender_charge_);
+    get_property(root, "irr",              round_irr_);
+}
 
 //============================================================================
 void StreamableRoundingRules::Write(std::string const& a_Filename)
 {
-    std::ofstream os(a_Filename.c_str());
-
-    os << round_specamt_;
-    os << round_death_benefit_;
-    os << round_naar_;
-    os << round_coi_rate_;
-    os << round_coi_charge_;
-    os << round_gross_premium_;
-    os << round_net_premium_;
-    os << round_interest_rate_;
-    os << round_interest_credit_;
-    os << round_withdrawal_;
-    os << round_loan_;
-    os << round_corridor_factor_;
-    os << round_surrender_charge_;
-    os << round_irr_;
-
-    if(!os.good())
+    xml::document doc("rounding");
+    xml::node& root = doc.get_root_node();
+
+    using namespace xml_serialize;
+
+    add_property(root, "specamt",          round_specamt_);
+    add_property(root, "death_benefit",    round_death_benefit_);
+    add_property(root, "naar",             round_naar_);
+    add_property(root, "coi_rate",         round_coi_rate_);
+    add_property(root, "coi_charge",       round_coi_charge_);
+    add_property(root, "gross_premium",    round_gross_premium_);
+    add_property(root, "net_premium",      round_net_premium_);
+    add_property(root, "interest_rate",    round_interest_rate_);
+    add_property(root, "interest_credit",  round_interest_credit_);
+    add_property(root, "withdrawal",       round_withdrawal_);
+    add_property(root, "loan",             round_loan_);
+    add_property(root, "corridor_factor",  round_corridor_factor_);
+    add_property(root, "surrender_charge", round_surrender_charge_);
+    add_property(root, "irr",              round_irr_);
+
+    if(!doc.save_to_file(a_Filename.c_str()))
         {
         fatal_error()
             << "Unable to write rounding file '"
@@ -172,6 +231,6 @@ void StreamableRoundingRules::Write(std::string const& a_Filename)
 void StreamableRoundingRules::WriteRndFiles()
 {
     StreamableRoundingRules sample;
-    sample.Write(AddDataDir("sample.rnd"));
+    sample.Write(AddDataDir("sample.xrnd"));
 }
 
diff --git a/ihs_rnddata.hpp b/ihs_rnddata.hpp
index a0a9466..5a83251 100644
--- a/ihs_rnddata.hpp
+++ b/ihs_rnddata.hpp
@@ -45,13 +45,14 @@ class LMI_SO StreamableRoundingRules
 
     rounding_rules const& get_rounding_rules();
 
-  protected:
-    void Init(std::string const& a_Filename);
-    void Read(std::string const& a_Filename);
-
   private:
     StreamableRoundingRules();
 
+    void Init(std::string const& a_Filename);
+    void Read(std::string const& a_Filename);
+#ifndef LMI_NO_LEGACY_FORMATS
+    void ReadLegacy(std::string const& a_Filename);
+#endif
     void Write(std::string const& a_Filename);
 };
 
diff --git a/main_wx.cpp b/main_wx.cpp
index 58d2d88..d78eb5a 100644
--- a/main_wx.cpp
+++ b/main_wx.cpp
@@ -362,9 +362,13 @@ void Skeleton::InitDocManager()
     new(wx) wxDocTemplate
         (doc_manager_
         ,"Database"
-        ,"*.db4"
+#ifdef LMI_NO_LEGACY_FORMATS
+        ,"*.xdb4"
+#else
+        ,"*.xdb4;*.db4"
+#endif
         ,""
-        ,"db4"
+        ,"xdb4"
         ,"Database document"
         ,"Database view"
         ,CLASSINFO(DatabaseDocument)
@@ -374,9 +378,13 @@ void Skeleton::InitDocManager()
     new(wx) wxDocTemplate
         (doc_manager_
         ,"Policy"
-        ,"*.pol"
+#ifdef LMI_NO_LEGACY_FORMATS
+        ,"*.xpol"
+#else
+        ,"*.xpol;*.pol"
+#endif
         ,""
-        ,"pol"
+        ,"xpol"
         ,"Policy document"
         ,"Policy view"
         ,CLASSINFO(PolicyDocument)
@@ -386,9 +394,13 @@ void Skeleton::InitDocManager()
     new(wx) wxDocTemplate
         (doc_manager_
         ,"Rounding"
-        ,"*.rnd"
+#ifdef LMI_NO_LEGACY_FORMATS
+        ,"*.xrnd"
+#else
+        ,"*.xrnd;*.rnd"
+#endif
         ,""
-        ,"rnd"
+        ,"xrnd"
         ,"Rounding document"
         ,"Rounding view"
         ,CLASSINFO(RoundingDocument)
@@ -398,9 +410,13 @@ void Skeleton::InitDocManager()
     new(wx) wxDocTemplate
         (doc_manager_
         ,"Tier"
-        ,"*.tir"
+#ifdef LMI_NO_LEGACY_FORMATS
+        ,"*.xtir"
+#else
+        ,"*.xtir;*.tir"
+#endif
         ,""
-        ,"tir"
+        ,"xtir"
         ,"Tier document"
         ,"Tier view"
         ,CLASSINFO(TierDocument)
diff --git a/my_db.cpp b/my_db.cpp
index 2b60ef5..17310f4 100644
--- a/my_db.cpp
+++ b/my_db.cpp
@@ -53,7 +53,7 @@ void DBDictionary::WriteProprietaryDBFiles()
     Add(TDBValue(DB_GuarPolFee, 10.00));
     // etc.
 // Not necessary for 'sample' product only, because it's built in.
-//  WriteDB(AddDataDir("sample.db4"));
+//  WriteDB(AddDataDir("sample.xdb4"));
   }
   // Another policy form....
 }
diff --git a/my_fund.cpp b/my_fund.cpp
index 3b284e1..7aefd56 100644
--- a/my_fund.cpp
+++ b/my_fund.cpp
@@ -50,14 +50,14 @@ void FundData::WriteProprietaryFundFiles()
     // Sample product.
     FundData foo;
 // Not necessary for 'sample' product only, because it's built in.
-//    foo->Write(AddDataDir("sample.fnd"));
+//    foo->Write(AddDataDir("sample.xfnd"));
     }
 #if 0 // Template for other products.
     {
     // Another product.
     FundData foo;
     foo.FundInfo_.push_back(FundInfo(70, "Short Name", "Long Fund Name"));
-    foo.Write(AddDataDir("another_product.fnd"));
+    foo.Write(AddDataDir("another_product.xfnd"));
     }
 #endif // 0
 }
diff --git a/my_prod.cpp b/my_prod.cpp
index 69318b1..e292fc4 100644
--- a/my_prod.cpp
+++ b/my_prod.cpp
@@ -151,12 +151,12 @@ void TProductData::WriteProprietaryPolFiles()
     foo.MonthlyChargesPaymentFootnote  = "";
 
     // Sample policy form.
-    foo.DatabaseFilename  = "sample.db4";
-    foo.FundFilename      = "sample.fnd";
-    foo.RoundingFilename  = "sample.rnd";
-    foo.TierFilename      = "sample.tir";
+    foo.DatabaseFilename  = "sample.xdb4";
+    foo.FundFilename      = "sample.xfnd";
+    foo.RoundingFilename  = "sample.xrnd";
+    foo.TierFilename      = "sample.xtir";
 
-    foo.Write(AddDataDir("sample.pol"));
+    foo.Write(AddDataDir("sample.xpol"));
 
     // Another policy form....
 }
diff --git a/my_rnd.cpp b/my_rnd.cpp
index f910800..2a68d50 100644
--- a/my_rnd.cpp
+++ b/my_rnd.cpp
@@ -68,7 +68,7 @@ void StreamableRoundingRules::WriteProprietaryRndFiles()
     sample.round_surrender_charge_= round_to<double>(2, r_to_nearest);
     sample.round_irr_             = round_to<double>(5, r_downward  );
 
-    sample.Write(AddDataDir("sample.rnd"));
+    sample.Write(AddDataDir("sample.xrnd"));
 
     // Another policy form....
 }
diff --git a/my_tier.cpp b/my_tier.cpp
index 05e820a..bd15271 100644
--- a/my_tier.cpp
+++ b/my_tier.cpp
@@ -56,7 +56,7 @@ void stratified_charges::write_proprietary_stratified_files()
     foo.raw_entity(e_curr_sepacct_load_tiered_by_assets   ).limits_.push_back(DBL_MAX);
 
 // Not necessary for 'sample' product only, because it's built in.
-//    foo.write(AddDataDir("sample.tir"));
+//    foo.write(AddDataDir("sample.xtir"));
 
     // Another policy form....
 }
diff --git a/product_names.cpp b/product_names.cpp
index 55a403c..624829c 100644
--- a/product_names.cpp
+++ b/product_names.cpp
@@ -49,7 +49,7 @@ std::vector<std::string> fetch_product_names()
     fs::directory_iterator end_i;
     for(; i != end_i; ++i)
         {
-        if(is_directory(*i) || ".pol" != fs::extension(*i))
+        if(is_directory(*i) || (".xpol" != fs::extension(*i) && ".pol" != fs::extension(*i)))
             {
             continue;
             }
diff --git a/product_names.hpp b/product_names.hpp
index c94d028..e6a148a 100644
--- a/product_names.hpp
+++ b/product_names.hpp
@@ -29,7 +29,7 @@
 #include <string>
 #include <vector>
 
-/// Base names of policy ('.pol') files found in data directory.
+/// Base names of policy ('.xpol') files found in data directory.
 /// They are read only once and cached: see documentation for
 /// class ce_product_name.
 
diff --git a/stratified_charges.cpp b/stratified_charges.cpp
index ec135b4..c6caa42 100644
--- a/stratified_charges.cpp
+++ b/stratified_charges.cpp
@@ -34,6 +34,10 @@
 #include "miscellany.hpp"         // minmax<T>()
 #include "platform_dependent.hpp" // access()
 #include "stratified_algorithms.hpp"
+#include "xml_serialize.hpp"
+
+#include <boost/filesystem/convenience.hpp>
+#include <boost/static_assert.hpp>
 
 #include <algorithm>
 #include <cfloat>                 // DBL_MAX
@@ -114,7 +118,8 @@ std::vector<double> const& stratified_entity::values() const
 }
 
 //============================================================================
-void stratified_entity::read(std::istream& is)
+#ifndef LMI_NO_LEGACY_FORMATS
+void stratified_entity::read_legacy(std::istream& is)
 {
     std::vector<double>::size_type vector_size;
     std::vector<double>::value_type z;
@@ -143,39 +148,29 @@ void stratified_entity::read(std::istream& is)
 }
 
 //============================================================================
-void stratified_entity::write(std::ostream& os) const
+std::istream& operator>>(std::istream& is, stratified_entity& z)
 {
-    assert_validity();
-
-    typedef std::vector<double>::const_iterator svdci;
-
-    os << values_.size() << " ";
-    for(svdci i = values_.begin(); i < values_.end(); ++i)
-        {
-        os << (*i) << " ";
-        }
-    os << '\n';
-
-    os << limits_.size() << " ";
-    for(svdci i = limits_.begin(); i < limits_.end(); ++i)
-        {
-        os << (*i) << " ";
-        }
-    os << '\n';
+    z.read_legacy(is);
+    return is;
 }
+#endif // !LMI_NO_LEGACY_FORMATS
 
 //============================================================================
-std::istream& operator>>(std::istream& is, stratified_entity& z)
+void stratified_entity::read(xml::element const& node)
 {
-    z.read(is);
-    return is;
+    xml_serialize::get_property(node, "values", values_);
+    xml_serialize::get_property(node, "limits", limits_);
+
+    assert_validity();
 }
 
 //============================================================================
-std::ostream& operator<<(std::ostream& os, stratified_entity const& z)
+void stratified_entity::write(xml::node& node) const
 {
-    z.write(os);
-    return os;
+    assert_validity();
+
+    xml_serialize::add_property(node, "values", values_);
+    xml_serialize::add_property(node, "limits", limits_);
 }
 
 // Class stratified_charges implementation.
@@ -459,6 +454,58 @@ double stratified_charges::minimum_tiered_premium_tax_rate(mcenum_state state) c
 }
 
 //============================================================================
+namespace
+{
+    const char* e_stratified_nodes[] =
+        {"stratified_first"
+
+        ,"topic_premium_banded"
+        ,"curr_sepacct_load_banded_by_premium"
+        ,"guar_sepacct_load_banded_by_premium"
+
+        ,"topic_asset_tiered"
+        ,"curr_m_and_e_tiered_by_assets"
+        ,"guar_m_and_e_tiered_by_assets"
+        ,"asset_based_comp_tiered_by_assets"
+        ,"investment_mgmt_fee_tiered_by_assets"
+        ,"curr_sepacct_load_tiered_by_assets"
+        ,"guar_sepacct_load_tiered_by_assets"
+
+        ,"topic_tiered_premium_tax"
+        ,"tiered_ak_premium_tax"
+        ,"tiered_de_premium_tax"
+        ,"tiered_sd_premium_tax"
+
+        ,"stratified_last"
+        };
+
+    BOOST_STATIC_ASSERT(e_stratified_last+1 == sizeof e_stratified_nodes / sizeof(const char*));
+} // anonymous namespace
+
+namespace xml_serialize
+{
+    template<>
+    struct type_io<stratified_entity>
+    {
+        static void to_xml(xml::node& out, stratified_entity const& in)
+        {
+            in.write(out);
+        }
+
+        static void from_xml(stratified_entity& out, xml::node const& in)
+        {
+            out.read(in);
+        }
+    };
+} // namespace xml_serialize
+
+//============================================================================
+void stratified_charges::read_entity(xml::element const& node, e_stratified e)
+{
+    xml_serialize::get_property(node, e_stratified_nodes[e], raw_entity(e));
+}
+
+//============================================================================
 void stratified_charges::read(std::string const& filename)
 {
     if(access(filename.c_str(), R_OK))
@@ -471,6 +518,34 @@ void stratified_charges::read(std::string const& filename)
             ;
         }
 
+#ifndef LMI_NO_LEGACY_FORMATS
+    // We temporarily support reading both XML and the old file formats.
+    if(".tir" == fs::extension(filename))
+        {
+        read_legacy(filename);
+        return;
+        }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+    xml_lmi::dom_parser doc(filename);
+    xml::element const& root = doc.root_node("tier");
+
+    read_entity(root, e_curr_sepacct_load_banded_by_premium  );
+    read_entity(root, e_guar_sepacct_load_banded_by_premium  );
+    read_entity(root, e_curr_m_and_e_tiered_by_assets        );
+    read_entity(root, e_guar_m_and_e_tiered_by_assets        );
+    read_entity(root, e_asset_based_comp_tiered_by_assets    );
+    read_entity(root, e_investment_mgmt_fee_tiered_by_assets );
+    read_entity(root, e_curr_sepacct_load_tiered_by_assets   );
+    read_entity(root, e_guar_sepacct_load_tiered_by_assets   );
+    read_entity(root, e_tiered_ak_premium_tax                );
+    read_entity(root, e_tiered_de_premium_tax                );
+    read_entity(root, e_tiered_sd_premium_tax                );
+}
+
+#ifndef LMI_NO_LEGACY_FORMATS
+void stratified_charges::read_legacy(std::string const& filename)
+{
     std::ifstream is(filename.c_str());
 
     is >> raw_entity(e_curr_sepacct_load_banded_by_premium  );
@@ -507,25 +582,33 @@ void stratified_charges::read(std::string const& filename)
             ;
         }
 }
+#endif // !LMI_NO_LEGACY_FORMATS
+
+//============================================================================
+void stratified_charges::write_entity(xml::element& node, e_stratified e) const
+{
+    xml_serialize::add_property(node, e_stratified_nodes[e], raw_entity(e));
+}
 
 //============================================================================
 void stratified_charges::write(std::string const& filename) const
 {
-    std::ofstream os(filename.c_str());
-
-    os << raw_entity(e_curr_sepacct_load_banded_by_premium  );
-    os << raw_entity(e_guar_sepacct_load_banded_by_premium  );
-    os << raw_entity(e_curr_m_and_e_tiered_by_assets        );
-    os << raw_entity(e_guar_m_and_e_tiered_by_assets        );
-    os << raw_entity(e_asset_based_comp_tiered_by_assets    );
-    os << raw_entity(e_investment_mgmt_fee_tiered_by_assets );
-    os << raw_entity(e_curr_sepacct_load_tiered_by_assets   );
-    os << raw_entity(e_guar_sepacct_load_tiered_by_assets   );
-    os << raw_entity(e_tiered_ak_premium_tax                );
-    os << raw_entity(e_tiered_de_premium_tax                );
-    os << raw_entity(e_tiered_sd_premium_tax                );
-
-    if(!os.good())
+    xml::document doc("tier");
+    xml::node& root = doc.get_root_node();
+
+    write_entity(root, e_curr_sepacct_load_banded_by_premium  );
+    write_entity(root, e_guar_sepacct_load_banded_by_premium  );
+    write_entity(root, e_curr_m_and_e_tiered_by_assets        );
+    write_entity(root, e_guar_m_and_e_tiered_by_assets        );
+    write_entity(root, e_asset_based_comp_tiered_by_assets    );
+    write_entity(root, e_investment_mgmt_fee_tiered_by_assets );
+    write_entity(root, e_curr_sepacct_load_tiered_by_assets   );
+    write_entity(root, e_guar_sepacct_load_tiered_by_assets   );
+    write_entity(root, e_tiered_ak_premium_tax                );
+    write_entity(root, e_tiered_de_premium_tax                );
+    write_entity(root, e_tiered_sd_premium_tax                );
+
+    if(!doc.save_to_file(filename.c_str()))
         {
         fatal_error()
             << "Unable to write stratified-data file '"
@@ -581,7 +664,7 @@ void stratified_charges::write_stratified_files()
     foo.raw_entity(e_tiered_sd_premium_tax                ).limits_.push_back(100000.0);
     foo.raw_entity(e_tiered_sd_premium_tax                ).limits_.push_back(DBL_MAX);
 
-    foo.write(AddDataDir("sample.tir"));
+    foo.write(AddDataDir("sample.xtir"));
 }
 
 /// Determine whether a double is in effect the highest representable.
diff --git a/stratified_charges.hpp b/stratified_charges.hpp
index c4f0c16..55937c7 100644
--- a/stratified_charges.hpp
+++ b/stratified_charges.hpp
@@ -29,6 +29,7 @@
 #include "mc_enum_type_enums.hpp"
 #include "obstruct_slicing.hpp"
 #include "so_attributes.hpp"
+#include "xml_lmi_fwd.hpp"
 
 #include <iosfwd>
 #include <map>
@@ -74,8 +75,11 @@ class LMI_SO stratified_entity
         );
     ~stratified_entity();
 
-    void read (std::istream&);
-    void write(std::ostream&) const;
+    void read       (xml::element const& node);
+#ifndef LMI_NO_LEGACY_FORMATS
+    void read_legacy(std::istream&);
+#endif
+    void write      (xml::node&) const;
 
   private:
     void assert_validity() const;
@@ -149,8 +153,13 @@ class LMI_SO stratified_charges
 
     void initialize_dictionary();
 
-    void read (std::string const& filename);
-    void write(std::string const& filename) const;
+    void read        (std::string const& filename);
+#ifndef LMI_NO_LEGACY_FORMATS
+    void read_legacy (std::string const& filename);
+#endif
+    void write       (std::string const& filename) const;
+    void read_entity (xml::element const& node, e_stratified);
+    void write_entity(xml::element& node, e_stratified) const;
 
     double banded_curr_sepacct_load
         (double assets
diff --git a/workhorse.make b/workhorse.make
index ca6ae29..4cf4c0a 100644
--- a/workhorse.make
+++ b/workhorse.make
@@ -919,12 +919,12 @@ shared_data_files = \
   qx_ins.dat \
   qx_ins.ndx \
   sample.dat \
-  sample.db4 \
-  sample.fnd \
+  sample.xdb4 \
+  sample.xfnd \
   sample.ndx \
-  sample.pol \
-  sample.rnd \
-  sample.tir \
+  sample.xpol \
+  sample.xrnd \
+  sample.xtir \
 
 .PHONY: archive_shared_data_files
 archive_shared_data_files:
@@ -1008,7 +1008,7 @@ fardel_files := \
 # one file of each given type.
 
 fardel_checksummed_files = \
-  *.dat *.db4 *.fnd *.ndx *.pol *.rnd *.tir \
+  *.dat *.xdb4 *.xfnd *.ndx *.xpol *.xrnd *.xtir \
   configurable_settings.xml \
   expiry \
   md5sum$(EXEEXT) \
@@ -1047,12 +1047,12 @@ wrap_fardel:
 .PHONY: test
 test: $(test_targets)
 
-# Some test targets require 'sample.pol' to exist even though they
+# Some test targets require 'sample.xpol' to exist even though they
 # don't actually read its contents.
 
-$(test_targets): sample.pol
+$(test_targets): sample.xpol
 
-sample.pol:
+sample.xpol:
 	@$(TOUCH) $@
 
 ################################################################################
diff --git a/xml_serialize.hpp b/xml_serialize.hpp
new file mode 100644
index 0000000..82beccf
--- /dev/null
+++ b/xml_serialize.hpp
@@ -0,0 +1,272 @@
+// XML serialization helpers.
+//
+// Copyright (C) 2009 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <gchicares@sbcglobal.net>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifndef xml_serialize_hpp
+#define xml_serialize_hpp
+
+#include "config.hpp"
+
+#include "xml_lmi.hpp"
+#include "mc_enum.hpp"
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/type_traits/is_enum.hpp>
+#include <boost/type_traits/is_floating_point.hpp>
+#include <boost/utility.hpp>
+
+#include <xmlwrapp/xmlwrapp.h>
+
+#include <limits>
+#include <stdexcept>
+#include <string>
+#include <sstream>
+#include <vector>
+
+
+/// Helpers for serialization to XML
+
+namespace xml_serialize
+{
+    // forward declarations
+    template<typename T> void to_xml(xml::node& out, T const& in);
+    template<typename T> void from_xml(T& out, xml::node const& in);
+
+    /// Serialization and deserialization of a type to/from XML.
+    ///
+    /// This template must be specialized for all nontrivial types that
+    /// will be serialized to XML.
+
+    template<typename T>
+    struct type_io
+    {
+        // writes value 'in' under existing node 'out'
+        static void to_xml(xml::node& out, T const& in)
+        {
+            std::ostringstream s;
+            if ( boost::is_floating_point<T>::value )
+                {
+                // Set maximum precision for floating points serialization.
+                // See http://stackoverflow.com/questions/1701055/what-is-the-maximum-length-in-chars-needed-to-represent-any-double-value/1701272#1701272
+                // for explanation of how this expression was arrived to.
+                s.precision(
+                      3
+                    + std::numeric_limits<T>::digits
+                    - std::numeric_limits<T>::min_exponent
+                );
+                }
+
+            s << in;
+            if(!s.good())
+                throw std::runtime_error("Failure to serialize data into XML.");
+            out.set_content(s.str().c_str());
+        }
+
+        // reads value from nodes under node 'in' into 'out'
+        static void from_xml(T& out, xml::node const& in)
+        {
+            std::istringstream s(xml_lmi::get_content(in));
+            s >> out;
+            if(s.fail() || s.bad())
+                throw std::runtime_error("Failure to read data from XML.");
+            if(!s.eof())
+                throw std::runtime_error("Unexpected extra data in XML.");
+        }
+    };
+
+    // specialization for std::string is both more efficient and required for
+    // correct reading of strings with whitespace in them
+    template<>
+    struct type_io<std::string>
+    {
+        static void to_xml(xml::node& out, std::string const& in)
+        {
+            out.set_content(in.c_str());
+        }
+
+        static void from_xml(std::string& out, xml::node const& in)
+        {
+            out = xml_lmi::get_content(in);
+        }
+    };
+
+    template<typename T>
+    struct container_type_io
+    {
+        typedef typename T::value_type element_type;
+
+        static void to_xml(xml::node& out, T const& in)
+        {
+            for(typename T::const_iterator i = in.begin(); i != in.end(); ++i)
+                {
+                add_property(out, "item", *i);
+                }
+        }
+
+        static void from_xml(T& out, xml::node const& in)
+        {
+            out.clear();
+            xml::const_nodes_view items = in.elements("item");
+            for(xml::const_nodes_view::iterator i = items.begin(); i != items.end(); ++i)
+                {
+                element_type e;
+                ::xml_serialize::from_xml(e, *i);
+                out.push_back(e);
+                }
+        }
+    };
+
+    template<typename T>
+    struct type_io< std::vector<T> >
+        : public container_type_io< std::vector<T> >
+    {
+    };
+
+    template<typename T>
+    struct enum_type_io
+    {
+        typedef mc_enum<T> mce_type;
+
+        static void to_xml(xml::node& out, T const& in)
+        {
+            ::xml_serialize::to_xml(out, mc_enum<T>(in));
+        }
+
+        static void from_xml(T& out, xml::node const& in)
+        {
+            mce_type x;
+            ::xml_serialize::from_xml(x, in);
+            out = x.value();
+        }
+    };
+
+    /// At first sight, this only adds another level of indirection.
+    /// But it actually is useful: it allows us to specialize type_io<> not
+    /// only for particular types, but for *groups* of types. In particular,
+    /// it makes it easy to use enum_type_io for all enum types.
+    ///
+    /// See http://www.boost.org/libs/utility/enable_if.html for explanation
+    /// of how this works.
+
+    template<typename T,
+             typename Enable = void /* for enable_if below */>
+    struct choose_type_io
+    {
+        typedef type_io<T> type;
+    };
+
+    template<typename T>
+    struct choose_type_io<T, typename boost::enable_if< boost::is_enum<T> >::type>
+    {
+        typedef enum_type_io<T> type;
+    };
+
+    /// Serializes 'value' into XML node 'node'.
+    /// This is convenience wrapper around type_io<>::to_xml().
+    template<typename T>
+    void to_xml(xml::node& out, T const& in)
+    {
+        choose_type_io<T>::type::to_xml(out, in);
+    }
+
+    /// Deserializes 'value' from XML node 'node'.
+    /// This is convenience wrapper around type_io<>::from_xml().
+    template<typename T>
+    void from_xml(T& out, xml::node const& in)
+    {
+        choose_type_io<T>::type::from_xml(out, in);
+    }
+
+    /// Adds a property to given XML node (root).
+    /// A property is serialized as <property-name>value</property-name>.
+    /// Serialization format is defined by xml_serialize::type_io<T>;
+    /// by default, C++ streams' operator<< is used.
+
+    template<typename T>
+    void add_property
+        (xml::element& root
+        ,const char* prop
+        ,T const& value)
+    {
+        xml::element node(prop);
+        to_xml(node, value);
+        root.push_back(node);
+    }
+
+    /// Reads a property from given XML node (root) and stores it in
+    /// 'value'. Returns true if it was found, false otherwise (in which
+    /// case the default value is assigned to 'value').
+
+    template<typename T>
+    bool get_property
+        (xml::element const& root
+        ,const char* prop
+        ,T& value
+        ,T const& default_value)
+    {
+        xml::node::const_iterator n = root.find(prop);
+        if(root.end() != n)
+            {
+            from_xml(value, *n);
+            return true;
+            }
+        else
+            {
+            value = default_value;
+            return false;
+            }
+    }
+
+    /// Reads a property from given XML node (root) and stores it in
+    /// 'value'. Throws if the property wasn't found.
+
+    template<typename T>
+    void get_property
+        (xml::element const& root
+        ,const char* prop
+        ,T& value)
+    {
+        xml::node::const_iterator n = root.find(prop);
+        if(root.end() == n)
+            {
+                std::string msg = "Required property '";
+                msg += prop;
+                msg += "' is missing.";
+                throw std::runtime_error(msg);
+            }
+        from_xml(value, *n);
+    }
+
+    /// Reads a property from given XML node (root) and returns it.
+    /// Throws if the property wasn't found.
+
+    template<typename T>
+    T get_property(xml::element const& root, const char* prop)
+    {
+        T value;
+        get_property(root, prop, value);
+        return value;
+    }
+} // namespace xml_serialize
+
+#endif // xml_serialize_hpp
+
