sd: use XTheme to transport the theme to xmloff import/export

Refactor the existing places and tests in Impress code (sd) to
use it instead.

Also keep the old property of construction and view of the theme
with a sequence of property values, but  under the new property
named "ThemeUnoRepresentation". This is needed by the UI tests
currently.

Change-Id: I484567f4a603f1a5e2e03955fdd2b63132dcc66e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146225
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/cui/qa/uitest/dialogs/chardlg.py b/cui/qa/uitest/dialogs/chardlg.py
index c52664f..4a756cf 100644
--- a/cui/qa/uitest/dialogs/chardlg.py
+++ b/cui/qa/uitest/dialogs/chardlg.py
@@ -74,7 +74,7 @@ class Test(UITestCase):
                    0x000000,  # folHlink
                ])
            })
            master.Theme = theme
            master.ThemeUnoRepresentation = theme

            # Select the title shape.
            editWin.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"}))
diff --git a/cui/qa/uitest/tabpages/themepage.py b/cui/qa/uitest/tabpages/themepage.py
index f85af96..0145f03 100644
--- a/cui/qa/uitest/tabpages/themepage.py
+++ b/cui/qa/uitest/tabpages/themepage.py
@@ -41,7 +41,7 @@ class Test(UITestCase):
                    0x000000,  # folHlink
                ])
            })
            master.Theme = theme
            master.ThemeUnoRepresentation = theme

            # When changing the name of the theme:
            self.xUITest.executeCommand(".uno:SlideMasterPage")
@@ -71,7 +71,7 @@ class Test(UITestCase):
            # Without the accompanying fix in place, this test would have failed with:
            # AssertionError: 'nameA' != 'nameB'
            # i.e. the UI didn't update the theme name.
            theme = convert_property_values_to_dict(master.Theme)
            theme = convert_property_values_to_dict(master.ThemeUnoRepresentation)
            self.assertEqual(theme["Name"], "nameB")
            # Without the accompanying fix in place, this test would have failed with:
            # AssertionError: 'colorSetA' != 'colorSetB'
diff --git a/cui/qa/uitest/tabpages/tpcolor.py b/cui/qa/uitest/tabpages/tpcolor.py
index e6ae91ad..ad52711 100644
--- a/cui/qa/uitest/tabpages/tpcolor.py
+++ b/cui/qa/uitest/tabpages/tpcolor.py
@@ -41,7 +41,7 @@ class Test(UITestCase):
                    0x000000,  # folHlink
                ])
            })
            master.Theme = theme
            master.ThemeUnoRepresentation = theme
            # Select the title shape.
            editWin.executeAction("TYPE", mkPropertyValues({"KEYCODE": "TAB"}))

diff --git a/docmodel/source/theme/Theme.cxx b/docmodel/source/theme/Theme.cxx
index 5f755b0..b8fad07 100644
--- a/docmodel/source/theme/Theme.cxx
+++ b/docmodel/source/theme/Theme.cxx
@@ -24,6 +24,8 @@ using namespace com::sun::star;

namespace model
{
Theme::Theme() = default;

Theme::Theme(OUString const& rName)
    : maName(rName)
{
diff --git a/include/docmodel/theme/Theme.hxx b/include/docmodel/theme/Theme.hxx
index 6ef239f..9c9bc02 100644
--- a/include/docmodel/theme/Theme.hxx
+++ b/include/docmodel/theme/Theme.hxx
@@ -134,6 +134,7 @@ private:
    FontScheme maFontScheme;

public:
    Theme();
    Theme(OUString const& rName);

    Theme(Theme const& rTheme);
diff --git a/oox/qa/unit/drawingml.cxx b/oox/qa/unit/drawingml.cxx
index 5c09ca3..3355b66 100644
--- a/oox/qa/unit/drawingml.cxx
+++ b/oox/qa/unit/drawingml.cxx
@@ -28,8 +28,10 @@
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/table/XCellRange.hpp>
#include <com/sun/star/util/XTheme.hpp>

#include <docmodel/uno/UnoThemeColor.hxx>
#include <docmodel/uno/UnoTheme.hxx>

#include <comphelper/sequenceashashmap.hxx>

@@ -373,19 +375,21 @@ CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, testPptxTheme)
    uno::Reference<drawing::XMasterPageTarget> xDrawPage(
        xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xMasterpage(xDrawPage->getMasterPage(), uno::UNO_QUERY);
    comphelper::SequenceAsHashMap aMap(xMasterpage->getPropertyValue("Theme"));
    CPPUNIT_ASSERT_EQUAL(OUString("Office Theme"), aMap["Name"].get<OUString>());
    // Without the accompanying fix in place, this test would have failed with:
    // - Cannot extract an Any(void) to string!
    // i.e. the name of the color scheme was lost on import.
    CPPUNIT_ASSERT_EQUAL(OUString("Office"), aMap["ColorSchemeName"].get<OUString>());

    // Check the last color in the color set, value is from ppt/theme/theme1.xml.
    // Without the accompanying fix in place, this test would have failed with:
    // - Cannot extract an Any(void) to []long!
    auto aColorScheme = aMap["ColorScheme"].get<uno::Sequence<util::Color>>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(12), aColorScheme.getLength());
    CPPUNIT_ASSERT_EQUAL(static_cast<util::Color>(0x954F72), aColorScheme[11]);
    uno::Reference<util::XTheme> xTheme;
    xMasterpage->getPropertyValue("Theme") >>= xTheme;

    // We expect the theme to be set on the master page
    CPPUNIT_ASSERT(xTheme.is());
    auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
    CPPUNIT_ASSERT(pUnoTheme);
    auto const& rTheme = pUnoTheme->getTheme();

    CPPUNIT_ASSERT_EQUAL(OUString("Office Theme"), rTheme.GetName());
    CPPUNIT_ASSERT_EQUAL(OUString("Office"), rTheme.GetColorSet()->getName());

    CPPUNIT_ASSERT_EQUAL(Color(0x954F72),
                         rTheme.GetColorSet()->getColor(model::ThemeColorType::FollowedHyperlink));

    // Check the reference to that theme:
    uno::Reference<drawing::XShapes> xDrawPageShapes(xDrawPage, uno::UNO_QUERY);
diff --git a/sd/CppunitTest_sd_filter_eppt.mk b/sd/CppunitTest_sd_filter_eppt.mk
index 2a570b5..261fa30 100644
--- a/sd/CppunitTest_sd_filter_eppt.mk
+++ b/sd/CppunitTest_sd_filter_eppt.mk
@@ -24,6 +24,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sd_filter_eppt, \
    comphelper \
    cppu \
    cppuhelper \
    docmodel \
    sd \
    sal \
    subsequenttest \
diff --git a/sd/qa/filter/eppt/eppt.cxx b/sd/qa/filter/eppt/eppt.cxx
index 57c7345..fe8151a 100644
--- a/sd/qa/filter/eppt/eppt.cxx
+++ b/sd/qa/filter/eppt/eppt.cxx
@@ -15,7 +15,7 @@
#include <com/sun/star/util/Color.hpp>

#include <test/xmldocptr.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <docmodel/uno/UnoTheme.hxx>

using namespace ::com::sun::star;

@@ -66,14 +66,24 @@ CPPUNIT_TEST_FIXTURE(Test, testThemeExport)
    uno::Reference<drawing::XMasterPageTarget> xDrawPage(
        xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xMasterPage(xDrawPage->getMasterPage(), uno::UNO_QUERY);
    comphelper::SequenceAsHashMap aMap;
    aMap["Name"] <<= OUString("mytheme");
    aMap["ColorSchemeName"] <<= OUString("mycolorscheme");
    uno::Sequence<util::Color> aColorScheme
        = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc };
    aMap["ColorScheme"] <<= aColorScheme;
    uno::Any aTheme(aMap.getAsConstPropertyValueList());
    xMasterPage->setPropertyValue("Theme", aTheme);

    model::Theme aTheme("mytheme");
    std::unique_ptr<model::ColorSet> pColorSet(new model::ColorSet("mycolorscheme"));
    pColorSet->add(model::ThemeColorType::Dark1, 0x1);
    pColorSet->add(model::ThemeColorType::Light1, 0x2);
    pColorSet->add(model::ThemeColorType::Dark2, 0x3);
    pColorSet->add(model::ThemeColorType::Light2, 0x4);
    pColorSet->add(model::ThemeColorType::Accent1, 0x5);
    pColorSet->add(model::ThemeColorType::Accent2, 0x6);
    pColorSet->add(model::ThemeColorType::Accent3, 0x7);
    pColorSet->add(model::ThemeColorType::Accent4, 0x8);
    pColorSet->add(model::ThemeColorType::Accent5, 0x9);
    pColorSet->add(model::ThemeColorType::Accent6, 0xa);
    pColorSet->add(model::ThemeColorType::Hyperlink, 0xb);
    pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0xc);
    aTheme.SetColorSet(std::move(pColorSet));

    xMasterPage->setPropertyValue("Theme", uno::Any(model::theme::createXTheme(aTheme)));

    // When exporting to PPTX:
    save("Impress Office Open XML");
diff --git a/sd/qa/unit/uiimpress.cxx b/sd/qa/unit/uiimpress.cxx
index e311087..4eb16f5 100644
--- a/sd/qa/unit/uiimpress.cxx
+++ b/sd/qa/unit/uiimpress.cxx
@@ -46,6 +46,7 @@
#include <docmodel/uno/UnoThemeColor.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <docmodel/uno/UnoTheme.hxx>

#include <drawdoc.hxx>
#include <DrawDocShell.hxx>
@@ -1195,14 +1196,24 @@ CPPUNIT_TEST_FIXTURE(SdUiImpressTest, testThemeShapeInsert)
    uno::Reference<drawing::XMasterPageTarget> xMasterPageTarget(xDrawPage, uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xMasterPage(xMasterPageTarget->getMasterPage(),
                                                    uno::UNO_QUERY);
    comphelper::SequenceAsHashMap aMap;
    aMap["Name"] <<= OUString("mytheme");
    aMap["ColorSchemeName"] <<= OUString("mycolorscheme");
    uno::Sequence<util::Color> aColorScheme
        = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb };
    aMap["ColorScheme"] <<= aColorScheme;
    uno::Any aTheme(aMap.getAsConstPropertyValueList());
    xMasterPage->setPropertyValue("Theme", aTheme);

    model::Theme aTheme("mytheme");
    std::unique_ptr<model::ColorSet> pColorSet(new model::ColorSet("mycolorscheme"));
    pColorSet->add(model::ThemeColorType::Dark1, 0x0);
    pColorSet->add(model::ThemeColorType::Light1, 0x1);
    pColorSet->add(model::ThemeColorType::Dark2, 0x2);
    pColorSet->add(model::ThemeColorType::Light2, 0x3);
    pColorSet->add(model::ThemeColorType::Accent1, 0x4);
    pColorSet->add(model::ThemeColorType::Accent2, 0x5);
    pColorSet->add(model::ThemeColorType::Accent3, 0x6);
    pColorSet->add(model::ThemeColorType::Accent4, 0x7);
    pColorSet->add(model::ThemeColorType::Accent5, 0x8);
    pColorSet->add(model::ThemeColorType::Accent6, 0x9);
    pColorSet->add(model::ThemeColorType::Hyperlink, 0xa);
    pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0xb);
    aTheme.SetColorSet(std::move(pColorSet));

    xMasterPage->setPropertyValue("Theme", uno::Any(model::theme::createXTheme(aTheme)));

    // When inserting a shape:
    uno::Sequence<beans::PropertyValue> aArgs = {
diff --git a/sd/source/ui/unoidl/unomodel.cxx b/sd/source/ui/unoidl/unomodel.cxx
index 9b9ed18..b5c9e64 100644
--- a/sd/source/ui/unoidl/unomodel.cxx
+++ b/sd/source/ui/unoidl/unomodel.cxx
@@ -28,6 +28,7 @@
#include <com/sun/star/awt/XDevice.hpp>
#include <com/sun/star/document/IndexedPropertyValues.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/util/XTheme.hpp>

#include <com/sun/star/embed/Aspects.hpp>

@@ -245,7 +246,7 @@ static const SvxItemPropertySet* ImplGetDrawModelPropertySet()
        { sUNO_Prop_HasValidSignatures,   WID_MODEL_HASVALIDSIGNATURES, ::cppu::UnoType<sal_Bool>::get(),                      beans::PropertyAttribute::READONLY, 0},
        { u"Fonts",                        WID_MODEL_FONTS,              cppu::UnoType<uno::Sequence<uno::Any>>::get(),                     beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_InteropGrabBag,       WID_MODEL_INTEROPGRABBAG,     cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(),       0, 0},
        { sUNO_Prop_Theme,                WID_MODEL_THEME,              cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(),       0, 0},
        { sUNO_Prop_Theme,                WID_MODEL_THEME,              cppu::UnoType<util::XTheme>::get(),       0, 0},
    };
    static SvxItemPropertySet aDrawModelPropertySet_Impl( aDrawModelPropertyMap_Impl, SdrObject::GetGlobalDrawObjectItemPool() );
    return &aDrawModelPropertySet_Impl;
diff --git a/sd/source/ui/unoidl/unopage.cxx b/sd/source/ui/unoidl/unopage.cxx
index 2dfff46..5292349 100644
--- a/sd/source/ui/unoidl/unopage.cxx
+++ b/sd/source/ui/unoidl/unopage.cxx
@@ -29,6 +29,7 @@
#include <com/sun/star/presentation/AnimationSpeed.hpp>
#include <com/sun/star/view/PaperOrientation.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/util/XTheme.hpp>
#include <cppuhelper/implbase.hxx>
#include <comphelper/profilezone.hxx>
#include <comphelper/servicehelper.hxx>
@@ -74,6 +75,7 @@
#include <vcl/dibtools.hxx>
#include <tools/debug.hxx>
#include <tools/stream.hxx>
#include <docmodel/uno/UnoTheme.hxx>
#include <o3tl/string_view.hxx>

using ::com::sun::star::animations::XAnimationNode;
@@ -100,7 +102,7 @@ enum WID_PAGE
    WID_PAGE_PAGENUMBERVISIBLE, WID_PAGE_DATETIMEVISIBLE, WID_PAGE_DATETIMEFIXED,
    WID_PAGE_DATETIMETEXT, WID_PAGE_DATETIMEFORMAT, WID_TRANSITION_TYPE, WID_TRANSITION_SUBTYPE,
    WID_TRANSITION_DIRECTION, WID_TRANSITION_FADE_COLOR, WID_TRANSITION_DURATION, WID_LOOP_SOUND,
    WID_NAVORDER, WID_PAGE_PREVIEWMETAFILE, WID_PAGE_THEME
    WID_NAVORDER, WID_PAGE_PREVIEWMETAFILE, WID_PAGE_THEME, WID_PAGE_THEME_UNO_REPRESENTATION
};

}
@@ -279,7 +281,9 @@ static const SvxItemPropertySet* ImplGetMasterPagePropertySet( PageKind ePageKin
        { u"BackgroundFullSize",           WID_PAGE_BACKFULL,  cppu::UnoType<bool>::get(),                        0, 0},
        { sUNO_Prop_UserDefinedAttributes,WID_PAGE_USERATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(),         0,     0},
        { u"IsBackgroundDark",             WID_PAGE_ISDARK,    cppu::UnoType<bool>::get(),                        beans::PropertyAttribute::READONLY, 0},
        { u"Theme", WID_PAGE_THEME, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(), 0,  0}
        { u"Theme", WID_PAGE_THEME, cppu::UnoType<util::XTheme>::get(), 0,  0},
        // backwards compatible view of the theme for use in tests
        { u"ThemeUnoRepresentation", WID_PAGE_THEME_UNO_REPRESENTATION, cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get(), 0,  0}
    };

    static const SfxItemPropertyMapEntry aHandoutMasterPagePropertyMap_Impl[] =
@@ -976,6 +980,19 @@ void SAL_CALL SdGenericDrawPage::setPropertyValue( const OUString& aPropertyName
        case WID_PAGE_THEME:
        {
            SdrPage* pPage = GetPage();
            uno::Reference<util::XTheme> xTheme;
            if (aValue >>= xTheme)
            {
                auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
                std::unique_ptr<model::Theme> pTheme(new model::Theme(pUnoTheme->getTheme()));
                pPage->getSdrPageProperties().SetTheme(std::move(pTheme));
            }
            break;
        }

        case WID_PAGE_THEME_UNO_REPRESENTATION:
        {
            SdrPage* pPage = GetPage();
            std::unique_ptr<model::Theme> pTheme = model::Theme::FromAny(aValue);
            pPage->getSdrPageProperties().SetTheme(std::move(pTheme));
            break;
@@ -1298,6 +1315,17 @@ Any SAL_CALL SdGenericDrawPage::getPropertyValue( const OUString& PropertyName )
    case WID_PAGE_THEME:
    {
        SdrPage* pPage = GetPage();
        css::uno::Reference<css::util::XTheme> xTheme;
        auto* pTheme = pPage->getSdrPageProperties().GetTheme();
        if (pTheme)
            xTheme = new UnoTheme(*pTheme);
        aAny <<= xTheme;
        break;
    }

    case WID_PAGE_THEME_UNO_REPRESENTATION:
    {
        SdrPage* pPage = GetPage();
        model::Theme* pTheme = pPage->getSdrPageProperties().GetTheme();
        if (pTheme)
        {
diff --git a/xmloff/inc/XMLThemeContext.hxx b/xmloff/inc/XMLThemeContext.hxx
index 706c125..50889a0 100644
--- a/xmloff/inc/XMLThemeContext.hxx
+++ b/xmloff/inc/XMLThemeContext.hxx
@@ -14,14 +14,14 @@
#include <com/sun/star/util/Color.hpp>
#include <com/sun/star/container/XNameContainer.hpp>

#include <comphelper/sequenceashashmap.hxx>
#include <comphelper/namecontainer.hxx>
#include <docmodel/theme/Theme.hxx>
#include <docmodel/theme/ColorSet.hxx>

/// Imports the theme
class XMLThemeContext : public SvXMLImportContext
{
    css::uno::Reference<css::drawing::XDrawPage> m_xPage;
    comphelper::SequenceAsHashMap m_aTheme;
    model::Theme maTheme;

public:
    XMLThemeContext(SvXMLImport& rImport,
@@ -37,13 +37,14 @@ public:
/// Imports the color table of a theme
class XMLColorTableContext : public SvXMLImportContext
{
    comphelper::SequenceAsHashMap& m_rTheme;
    model::Theme& mrTheme;
    std::unique_ptr<model::ColorSet> mpColorSet;
    std::vector<css::util::Color> m_aColorScheme;

public:
    XMLColorTableContext(SvXMLImport& rImport,
                         css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList,
                         comphelper::SequenceAsHashMap& rTheme);
                         model::Theme& mrTheme);
    ~XMLColorTableContext();

    css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext(
@@ -57,7 +58,7 @@ class XMLColorContext : public SvXMLImportContext
public:
    XMLColorContext(SvXMLImport& rImport,
                    css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList,
                    std::vector<css::util::Color>& rColorScheme);
                    std::unique_ptr<model::ColorSet>& rpColorSet);
};

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/qa/unit/draw.cxx b/xmloff/qa/unit/draw.cxx
index d66a96e..c4671d9 100644
--- a/xmloff/qa/unit/draw.cxx
+++ b/xmloff/qa/unit/draw.cxx
@@ -30,6 +30,7 @@
#include <svx/svdpage.hxx>
#include <svx/svdomedia.hxx>
#include <docmodel/uno/UnoThemeColor.hxx>
#include <docmodel/uno/UnoTheme.hxx>

using namespace ::com::sun::star;

@@ -130,14 +131,25 @@ CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testThemeExport)
    uno::Reference<drawing::XMasterPageTarget> xDrawPage(
        xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xMasterPage(xDrawPage->getMasterPage(), uno::UNO_QUERY);
    comphelper::SequenceAsHashMap aMap;
    aMap["Name"] <<= OUString("mytheme");
    aMap["ColorSchemeName"] <<= OUString("mycolorscheme");
    uno::Sequence<util::Color> aColorScheme
        = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb };
    aMap["ColorScheme"] <<= aColorScheme;
    uno::Any aTheme(aMap.getAsConstPropertyValueList());
    xMasterPage->setPropertyValue("Theme", aTheme);

    model::Theme aTheme("mytheme");
    std::unique_ptr<model::ColorSet> pColorSet(new model::ColorSet("mycolorscheme"));
    pColorSet->add(model::ThemeColorType::Dark1, 0x0);
    pColorSet->add(model::ThemeColorType::Light1, 0x1);
    pColorSet->add(model::ThemeColorType::Dark2, 0x2);
    pColorSet->add(model::ThemeColorType::Light2, 0x3);
    pColorSet->add(model::ThemeColorType::Accent1, 0x4);
    pColorSet->add(model::ThemeColorType::Accent2, 0x5);
    pColorSet->add(model::ThemeColorType::Accent3, 0x6);
    pColorSet->add(model::ThemeColorType::Accent4, 0x7);
    pColorSet->add(model::ThemeColorType::Accent5, 0x8);
    pColorSet->add(model::ThemeColorType::Accent6, 0x9);
    pColorSet->add(model::ThemeColorType::Hyperlink, 0xa);
    pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0xb);
    aTheme.SetColorSet(std::move(pColorSet));

    uno::Reference<util::XTheme> xTheme = model::theme::createXTheme(aTheme);
    xMasterPage->setPropertyValue("Theme", uno::Any(xTheme));

    // Export to ODP:
    save("impress8");
@@ -205,15 +217,21 @@ CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testThemeImport)
    uno::Reference<drawing::XMasterPageTarget> xDrawPage(
        xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xMasterpage(xDrawPage->getMasterPage(), uno::UNO_QUERY);
    comphelper::SequenceAsHashMap aMap(xMasterpage->getPropertyValue("Theme"));
    // Without the accompanying fix in place, this test would have failed with:
    // Cannot extract an Any(void) to string!
    // i.e. the master page had no theme.
    CPPUNIT_ASSERT_EQUAL(OUString("Office Theme"), aMap["Name"].get<OUString>());
    CPPUNIT_ASSERT_EQUAL(OUString("Office"), aMap["ColorSchemeName"].get<OUString>());
    auto aColorScheme = aMap["ColorScheme"].get<uno::Sequence<util::Color>>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(12), aColorScheme.getLength());
    CPPUNIT_ASSERT_EQUAL(static_cast<util::Color>(0x954F72), aColorScheme[11]);

    uno::Reference<util::XTheme> xTheme;
    xMasterpage->getPropertyValue("Theme") >>= xTheme;

    // We expect the theme to be set on the master page
    CPPUNIT_ASSERT(xTheme.is());
    auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
    CPPUNIT_ASSERT(pUnoTheme);
    auto const& rTheme = pUnoTheme->getTheme();

    CPPUNIT_ASSERT_EQUAL(OUString("Office Theme"), rTheme.GetName());
    CPPUNIT_ASSERT_EQUAL(OUString("Office"), rTheme.GetColorSet()->getName());

    CPPUNIT_ASSERT_EQUAL(Color(0x954F72),
                         rTheme.GetColorSet()->getColor(model::ThemeColorType::FollowedHyperlink));
}

CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testThemeColorExportImport)
diff --git a/xmloff/source/draw/sdxmlexp.cxx b/xmloff/source/draw/sdxmlexp.cxx
index df0c7e4..0e7fc61 100644
--- a/xmloff/source/draw/sdxmlexp.cxx
+++ b/xmloff/source/draw/sdxmlexp.cxx
@@ -73,8 +73,8 @@
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/util/Color.hpp>

#include <comphelper/sequenceashashmap.hxx>
#include <docmodel/uno/UnoTheme.hxx>
#include <o3tl/enumrange.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
@@ -2367,56 +2367,42 @@ void SdXMLExport::exportFormsElement( const Reference< XDrawPage >& xDrawPage )

void SdXMLExport::ExportThemeElement(const uno::Reference<drawing::XDrawPage>& xDrawPage)
{
    uno::Reference<beans::XPropertySet> xPropertySet(xDrawPage, uno::UNO_QUERY);
    if (!xPropertySet.is())
        return;

    comphelper::SequenceAsHashMap aMap(xPropertySet->getPropertyValue("Theme"));
    if (aMap.empty())
    {
        return;
    }

    if ((getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED) == 0)
    {
        // Do not export in standard ODF 1.3 or older.
        return;
    }

    auto it = aMap.find("Name");
    if (it != aMap.end())
    {
        OUString aName;
        it->second >>= aName;
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_NAME, aName);
    }
    uno::Reference<beans::XPropertySet> xPropertySet(xDrawPage, uno::UNO_QUERY);
    if (!xPropertySet.is())
        return;

    uno::Reference<util::XTheme> xTheme;
    xPropertySet->getPropertyValue("Theme") >>= xTheme;
    if (!xTheme.is())
        return;

    auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
    if (!pUnoTheme)
        return;

    auto const& rTheme = pUnoTheme->getTheme();

    if (!rTheme.GetName().isEmpty())
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_NAME, rTheme.GetName());
    SvXMLElementExport aTheme(*this, XML_NAMESPACE_LO_EXT, XML_THEME, true, true);

    uno::Sequence<util::Color> aColors;
    it = aMap.find("ColorScheme");
    if (it != aMap.end())
    {
        it->second >>= aColors;
    }
    if (!aColors.hasElements())
    {
        return;
    }

    it = aMap.find("ColorSchemeName");
    if (it != aMap.end())
    {
        OUString aName;
        it->second >>= aName;
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_NAME, aName);
    }
    auto* pColorSet = rTheme.GetColorSet();
    if (!pColorSet->getName().isEmpty())
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_NAME, pColorSet->getName());
    SvXMLElementExport aColorTable(*this, XML_NAMESPACE_LO_EXT, XML_COLOR_TABLE, true, true);

    static const XMLTokenEnum aColorTokens[] = {
        XML_DK1, // Background 1
        XML_LT1, // Text 1
        XML_DK2, // Background 2
        XML_LT2, // Text 2
    static const XMLTokenEnum aColorTokens[] =
    {
        XML_DK1, // Text 1
        XML_LT1, // Background 1
        XML_DK2, // Text 2
        XML_LT2, // Background 2
        XML_ACCENT1,
        XML_ACCENT2,
        XML_ACCENT3,
@@ -2426,17 +2412,17 @@ void SdXMLExport::ExportThemeElement(const uno::Reference<drawing::XDrawPage>& x
        XML_HLINK, // Hyperlink
        XML_FOLHLINK, // Followed hyperlink
    };
    for (size_t nColor = 0; nColor < aColors.size(); ++nColor)

    for (auto eThemeColorType : o3tl::enumrange<model::ThemeColorType>())
    {
        // Import goes via model::Theme::FromAny(), which sanitizes user input.
        assert(nColor < SAL_N_ELEMENTS(aColorTokens));
        if (eThemeColorType == model::ThemeColorType::Unknown)
            continue;

        auto nColor = size_t(eThemeColorType);
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_NAME, GetXMLToken(aColorTokens[nColor]));

        OUStringBuffer sValue;
        sax::Converter::convertColor(sValue, aColors[nColor]);
        sax::Converter::convertColor(sValue, pColorSet->getColor(eThemeColorType));
        AddAttribute(XML_NAMESPACE_LO_EXT, XML_COLOR, sValue.makeStringAndClear());

        SvXMLElementExport aColor(*this, XML_NAMESPACE_LO_EXT, XML_COLOR, true, true);
    }
}
diff --git a/xmloff/source/style/XMLThemeContext.cxx b/xmloff/source/style/XMLThemeContext.cxx
index 4160266..8a9a5cd 100644
--- a/xmloff/source/style/XMLThemeContext.cxx
+++ b/xmloff/source/style/XMLThemeContext.cxx
@@ -25,6 +25,8 @@
#include <sax/tools/converter.hxx>
#include <comphelper/sequence.hxx>

#include <docmodel/uno/UnoTheme.hxx>

using namespace css;
using namespace xmloff::token;

@@ -40,7 +42,8 @@ XMLThemeContext::XMLThemeContext(SvXMLImport& rImport,
        {
            case XML_ELEMENT(LO_EXT, XML_NAME):
            {
                m_aTheme["Name"] <<= rAttribute.toString();
                OUString aName = rAttribute.toString();
                maTheme.SetName(aName);
                break;
            }
        }
@@ -49,9 +52,9 @@ XMLThemeContext::XMLThemeContext(SvXMLImport& rImport,

XMLThemeContext::~XMLThemeContext()
{
    uno::Any aTheme(m_aTheme.getAsConstPropertyValueList());
    uno::Reference<beans::XPropertySet> xPropertySet(m_xPage, uno::UNO_QUERY);
    xPropertySet->setPropertyValue("Theme", aTheme);
    uno::Reference<util::XTheme> xTheme(new UnoTheme(maTheme));
    xPropertySet->setPropertyValue("Theme", uno::Any(xTheme));
}

uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLThemeContext::createFastChildContext(
@@ -59,7 +62,7 @@ uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLThemeContext::createFa
{
    if (nElement == XML_ELEMENT(LO_EXT, XML_COLOR_TABLE))
    {
        return new XMLColorTableContext(GetImport(), xAttribs, m_aTheme);
        return new XMLColorTableContext(GetImport(), xAttribs, maTheme);
    }

    return nullptr;
@@ -67,9 +70,9 @@ uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLThemeContext::createFa

XMLColorTableContext::XMLColorTableContext(
    SvXMLImport& rImport, const uno::Reference<xml::sax::XFastAttributeList>& xAttrList,
    comphelper::SequenceAsHashMap& rTheme)
    model::Theme& rTheme)
    : SvXMLImportContext(rImport)
    , m_rTheme(rTheme)
    , mrTheme(rTheme)
{
    for (const auto& rAttribute : sax_fastparser::castToFastAttributeList(xAttrList))
    {
@@ -77,7 +80,8 @@ XMLColorTableContext::XMLColorTableContext(
        {
            case XML_ELEMENT(LO_EXT, XML_NAME):
            {
                m_rTheme["ColorSchemeName"] <<= rAttribute.toString();
                OUString aName = rAttribute.toString();
                mpColorSet.reset(new model::ColorSet(aName));
                break;
            }
        }
@@ -86,7 +90,8 @@ XMLColorTableContext::XMLColorTableContext(

XMLColorTableContext::~XMLColorTableContext()
{
    m_rTheme["ColorScheme"] <<= comphelper::containerToSequence(m_aColorScheme);
    if (mpColorSet)
        mrTheme.SetColorSet(std::move(mpColorSet));
}

uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLColorTableContext::createFastChildContext(
@@ -94,7 +99,8 @@ uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLColorTableContext::cre
{
    if (nElement == XML_ELEMENT(LO_EXT, XML_COLOR))
    {
        return new XMLColorContext(GetImport(), xAttribs, m_aColorScheme);
        if (mpColorSet)
            return new XMLColorContext(GetImport(), xAttribs, mpColorSet);
    }

    return nullptr;
@@ -102,20 +108,60 @@ uno::Reference<xml::sax::XFastContextHandler> SAL_CALL XMLColorTableContext::cre

XMLColorContext::XMLColorContext(SvXMLImport& rImport,
                                 const uno::Reference<xml::sax::XFastAttributeList>& xAttrList,
                                 std::vector<util::Color>& rColorScheme)
                                 std::unique_ptr<model::ColorSet>& rpColorSet)
    : SvXMLImportContext(rImport)
{
    OUString aName;
    ::Color aColor;

    for (const auto& rAttribute : sax_fastparser::castToFastAttributeList(xAttrList))
    {
        switch (rAttribute.getToken())
        {
            case XML_ELEMENT(LO_EXT, XML_COLOR):
            case XML_ELEMENT(LO_EXT, XML_NAME):
            {
                util::Color nColor;
                sax::Converter::convertColor(nColor, rAttribute.toView());
                rColorScheme.push_back(nColor);
                aName = rAttribute.toString();
                break;
            }
            case XML_ELEMENT(LO_EXT, XML_COLOR):
            {
                sax::Converter::convertColor(aColor, rAttribute.toView());
                break;
            }
        }
    }

    if (!aName.isEmpty())
    {
        auto eType = model::ThemeColorType::Unknown;
        if (aName == u"dk1")
            eType = model::ThemeColorType::Dark1;
        else if (aName == u"lt1")
            eType = model::ThemeColorType::Light1;
        else if (aName == u"dk2")
            eType = model::ThemeColorType::Dark2;
        else if (aName == u"lt2")
            eType = model::ThemeColorType::Light2;
        else if (aName == u"accent1")
            eType = model::ThemeColorType::Accent1;
        else if (aName == u"accent2")
            eType = model::ThemeColorType::Accent2;
        else if (aName == u"accent3")
            eType = model::ThemeColorType::Accent3;
        else if (aName == u"accent4")
            eType = model::ThemeColorType::Accent4;
        else if (aName == u"accent5")
            eType = model::ThemeColorType::Accent5;
        else if (aName == u"accent6")
            eType = model::ThemeColorType::Accent6;
        else if (aName == u"hlink")
            eType = model::ThemeColorType::Hyperlink;
        else if (aName == u"folHlink")
            eType = model::ThemeColorType::FollowedHyperlink;

        if (eType != model::ThemeColorType::Unknown)
        {
            rpColorSet->add(eType, aColor);
        }
    }
}