sc: sparkline import/export for ODF

Change-Id: I0d8293cdd35cc8c7afab98efac0a28a3613d122b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132505
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 22e2459..86cc93f 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -449,8 +449,16 @@ namespace xmloff::token {
        XML_CODEBASE,
        XML_COLLAPSE,
        XML_COLOR,
        XML_COLOR_AXIS,
        XML_COLOR_FIRST,
        XML_COLOR_HIGH,
        XML_COLOR_INVERSION,
        XML_COLOR_LAST,
        XML_COLOR_LOW,
        XML_COLOR_MARKERS,
        XML_COLOR_MODE,
        XML_COLOR_NEGATIVE,
        XML_COLOR_SERIES,
        XML_COLOR_SCALE,
        XML_COLOR_SCALE_ENTRY,
        XML_COLOR_TABLE,
@@ -593,6 +601,7 @@ namespace xmloff::token {
        XML_DATA_PILOT_TABLE,
        XML_DATA_PILOT_TABLES,
        XML_DATA_POINT,
        XML_DATA_RANGE,
        XML_DATA_STREAM_SOURCE,
        XML_DATA_STYLE,
        XML_DATA_STYLE_NAME,
@@ -609,6 +618,7 @@ namespace xmloff::token {
        XML_DATABASE_SOURCE_TABLE,
        XML_DATABASE_TABLE_NAME,
        XML_DATE,
        XML_DATE_AXIS,
        XML_DATE_IS,
        XML_DATE_ADJUST,
        XML_DATE_STYLE,
@@ -668,12 +678,15 @@ namespace xmloff::token {
        XML_DISPLAY_DETAILS,
        XML_DISPLAY_DUPLICATES,
        XML_DISPLAY_EMPTY,
        XML_DISPLAY_EMPTY_CELLS_AS,
        XML_DISPLAY_FILTER_BUTTONS,
        XML_DISPLAY_FORMULA,
        XML_DISPLAY_HIDDEN,
        XML_DISPLAY_LABEL,
        XML_DISPLAY_LEVELS,
        XML_DISPLAY_NAME,
        XML_DISPLAY_OUTLINE_LEVEL,
        XML_DISPLAY_X_AXIS,
        XML_DISSOLVE,
        XML_DISTANCE,
        XML_DISTANCE_AFTER_SEP,
@@ -1181,6 +1194,7 @@ namespace xmloff::token {
        XML_LINE_SKEW,
        XML_LINE_SPACING,
        XML_LINE_STYLE,
        XML_LINE_WIDTH,
        XML_LINEAR,
        XML_LINEARGRADIENT,
        XML_LINENUMBERING_CONFIGURATION,
@@ -1208,6 +1222,7 @@ namespace xmloff::token {
        XML_LOGARITHMIC,
        XML_LOGBASE,
        XML_LONG,
        XML_LOW,
        XML_LOWLIMIT,
        XML_LR_TB,
        XML_LT,
@@ -1221,6 +1236,8 @@ namespace xmloff::token {
        XML_MALIGNGROUP,
        XML_MALIGNMARK,
        XML_MANUAL,
        XML_MANUAL_MIN,
        XML_MANUAL_MAX,
        XML_MAP,
        XML_MARGIN_BOTTOM,
        XML_MARGIN_LEFT,
@@ -1228,6 +1245,7 @@ namespace xmloff::token {
        XML_MARGIN_TOP,
        XML_MARGINS,
        XML_MARKER,
        XML_MARKERS,
        XML_MARKER_END,
        XML_MARKER_END_CENTER,
        XML_MARKER_END_WIDTH,
@@ -1246,6 +1264,7 @@ namespace xmloff::token {
        XML_MATRIX_COVERED,
        XML_MATRIXROW,
        XML_MAX,
        XML_MAX_AXIS_TYPE,
        XML_MAX_EDGE,
        XML_MAX_HEIGHT,
        XML_MAX_LENGTH,
@@ -1271,6 +1290,7 @@ namespace xmloff::token {
        XML_MIDDLE,
        XML_MIME_TYPE,
        XML_MIN,
        XML_MIN_AXIS_TYPE,
        XML_MIN_DENOMINATOR_DIGITS,
        XML_MIN_EDGE,
        XML_MIN_EXPONENT_DIGITS,
@@ -1345,6 +1365,7 @@ namespace xmloff::token {
        XML_NAMED_RANGE,
        XML_NAVIGATION_MODE,
        XML_NAVY,
        XML_NEGATIVE,
        XML_NEGATIVE_COLOR,
        XML_NEQ,
        XML_NEW,
@@ -1606,6 +1627,7 @@ namespace xmloff::token {
        XML_RIGHT,
        XML_RIGHT_OUTSIDE,
        XML_RIGHT_TEXT,
        XML_RIGHT_TO_LEFT,
        XML_RIGHTARC,
        XML_RIGHTCIRCLE,
        XML_RING,
@@ -1760,6 +1782,10 @@ namespace xmloff::token {
        XML_SOURCE_RANGE_ADDRESS,
        XML_SOURCE_SERVICE,
        XML_SPACE_BEFORE,
        XML_SPARKLINE_GROUPS,
        XML_SPARKLINE_GROUP,
        XML_SPARKLINES,
        XML_SPARKLINE,
        XML_SPAN,
        XML_SPECULAR,
        XML_SPECULAR_COLOR,
diff --git a/sc/CppunitTest_sc_sparkline_test.mk b/sc/CppunitTest_sc_sparkline_test.mk
index 7a46d5d..b756de2 100644
--- a/sc/CppunitTest_sc_sparkline_test.mk
+++ b/sc/CppunitTest_sc_sparkline_test.mk
@@ -18,6 +18,12 @@ $(eval $(call gb_CppunitTest_add_exception_objects,sc_sparkline_test, \
    sc/qa/unit/SparklineTest \
))

$(eval $(call gb_CppunitTest_use_externals,sc_sparkline_test, \
    boost_headers \
    mdds_headers \
    libxml2 \
))

$(eval $(call gb_CppunitTest_use_libraries,sc_sparkline_test, \
    basegfx \
    comphelper \
diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 2c78147..e97f6e0 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -293,6 +293,8 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
    sc/source/core/tool/webservicelink \
    sc/source/core/tool/zforauto \
    sc/source/filter/xml/datastreamimport \
    sc/source/filter/xml/SparklineGroupsExport \
    sc/source/filter/xml/SparklineGroupsImportContext \
    sc/source/filter/xml/XMLCalculationSettingsContext \
    sc/source/filter/xml/XMLCellRangeSourceContext \
    sc/source/filter/xml/XMLChangeTrackingExportHelper \
diff --git a/sc/inc/SparklineGroup.hxx b/sc/inc/SparklineGroup.hxx
index 051e45d..5a3bda6 100644
--- a/sc/inc/SparklineGroup.hxx
+++ b/sc/inc/SparklineGroup.hxx
@@ -26,6 +26,7 @@ private:

public:
    SparklineAttributes& getAttributes() { return m_aAttributes; }
    SparklineAttributes const& getAttributes() const { return m_aAttributes; }

    OUString getID() { return m_sUID; }

diff --git a/sc/qa/unit/SparklineImportExportTest.cxx b/sc/qa/unit/SparklineImportExportTest.cxx
index 2c32412..6da76fb 100644
--- a/sc/qa/unit/SparklineImportExportTest.cxx
+++ b/sc/qa/unit/SparklineImportExportTest.cxx
@@ -8,6 +8,7 @@
 */

#include "helper/qahelper.hxx"
#include "helper/xpath.hxx"

#include <com/sun/star/lang/XComponent.hpp>
#include <docsh.hxx>
@@ -16,7 +17,7 @@

using namespace css;

class SparklineImportExportTest : public ScBootstrapFixture
class SparklineImportExportTest : public ScBootstrapFixture, public XmlTestTools
{
private:
    uno::Reference<uno::XInterface> m_xCalcComponent;
@@ -44,10 +45,19 @@ public:
        test::BootstrapFixture::tearDown();
    }

    void testSparklines();
    virtual void registerNamespaces(xmlXPathContextPtr& pXmlXPathContextPtr) override
    {
        XmlTestTools::registerODFNamespaces(pXmlXPathContextPtr);
    }

    void testSparklinesRoundtripXLSX();
    void testSparklinesExportODS();
    void testSparklinesRoundtripODS();

    CPPUNIT_TEST_SUITE(SparklineImportExportTest);
    CPPUNIT_TEST(testSparklines);
    CPPUNIT_TEST(testSparklinesRoundtripXLSX);
    CPPUNIT_TEST(testSparklinesExportODS);
    CPPUNIT_TEST(testSparklinesRoundtripODS);
    CPPUNIT_TEST_SUITE_END();
};

@@ -74,7 +84,7 @@ void checkSparklines(ScDocument& rDocument)
        CPPUNIT_ASSERT_EQUAL(Color(0x92d050), rAttributes.getColorHigh());
        CPPUNIT_ASSERT_EQUAL(Color(0x00b0f0), rAttributes.getColorLow());

        CPPUNIT_ASSERT_EQUAL(1.0, rAttributes.getLineWeight());
        CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, rAttributes.getLineWeight(), 1E-2);
        CPPUNIT_ASSERT_EQUAL(false, rAttributes.isDateAxis());
        CPPUNIT_ASSERT_EQUAL(sc::DisplayEmptyCellsAs::Gap, rAttributes.getDisplayEmptyCellsAs());

@@ -153,7 +163,7 @@ void checkSparklines(ScDocument& rDocument)
}
} // end anonymous namespace

void SparklineImportExportTest::testSparklines()
void SparklineImportExportTest::testSparklinesRoundtripXLSX()
{
    ScDocShellRef xDocSh = loadDoc(u"Sparklines.", FORMAT_XLSX);
    CPPUNIT_ASSERT(xDocSh);
@@ -167,6 +177,70 @@ void SparklineImportExportTest::testSparklines()
    xDocSh->DoClose();
}

void SparklineImportExportTest::testSparklinesExportODS()
{
    // Load the document containing sparklines
    ScDocShellRef xDocSh = loadDoc(u"Sparklines.", FORMAT_XLSX);
    CPPUNIT_ASSERT(xDocSh);

    // Save as ODS and check content.xml with XPath
    std::shared_ptr<utl::TempFile> pXPathFile = ScBootstrapFixture::exportTo(*xDocSh, FORMAT_ODS);
    xmlDocUniquePtr pXmlDoc = XPathHelper::parseExport(pXPathFile, m_xSFactory, "content.xml");

    // We have 3 sparkline groups = 3 tables that contain spakrlines
    assertXPath(pXmlDoc, "//table:table/calcext:sparkline-groups", 3);

    // Check the number of sparkline groups in table[1]
    assertXPath(pXmlDoc, "//table:table[1]/calcext:sparkline-groups/calcext:sparkline-group", 2);
    // Check the number of sparkline groups in table[2]
    assertXPath(pXmlDoc, "//table:table[2]/calcext:sparkline-groups/calcext:sparkline-group", 2);
    // Check the number of sparkline groups in table[3]
    assertXPath(pXmlDoc, "//table:table[3]/calcext:sparkline-groups/calcext:sparkline-group", 3);

    // Check table[1] - sparkline-group[1]
    OString aSparklineGroupPath
        = "//table:table[1]/calcext:sparkline-groups/calcext:sparkline-group[1]";
    assertXPath(pXmlDoc, aSparklineGroupPath, "type", "line");
    assertXPath(pXmlDoc, aSparklineGroupPath, "line-width", "1pt");
    assertXPath(pXmlDoc, aSparklineGroupPath, "display-empty-cells-as", "gap");
    assertXPath(pXmlDoc, aSparklineGroupPath, "markers", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "high", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "low", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "first", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "last", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "negative", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "display-x-axis", "true");
    assertXPath(pXmlDoc, aSparklineGroupPath, "min-axis-type", "individual");
    assertXPath(pXmlDoc, aSparklineGroupPath, "max-axis-type", "individual");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-series", "#376092");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-negative", "#00b050");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-axis", "#000000");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-markers", "#000000");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-first", "#7030a0");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-last", "#ff0000");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-high", "#92d050");
    assertXPath(pXmlDoc, aSparklineGroupPath, "color-low", "#00b0f0");

    assertXPath(pXmlDoc, aSparklineGroupPath + "/calcext:sparklines/calcext:sparkline", 1);
    assertXPath(pXmlDoc, aSparklineGroupPath + "/calcext:sparklines/calcext:sparkline[1]",
                "cell-address", "Sheet1.A2");
}

void SparklineImportExportTest::testSparklinesRoundtripODS()
{
    ScDocShellRef xDocSh = loadDoc(u"Sparklines.", FORMAT_XLSX);
    CPPUNIT_ASSERT(xDocSh);

    checkSparklines(xDocSh->GetDocument());

    // Trigger export and import of sparklines
    xDocSh = saveAndReload(*xDocSh, FORMAT_ODS);

    checkSparklines(xDocSh->GetDocument());

    xDocSh->DoClose();
}

CPPUNIT_TEST_SUITE_REGISTRATION(SparklineImportExportTest);

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/filter/xml/SparklineGroupsExport.cxx b/sc/source/filter/xml/SparklineGroupsExport.cxx
new file mode 100644
index 0000000..6be8a61
--- /dev/null
+++ b/sc/source/filter/xml/SparklineGroupsExport.cxx
@@ -0,0 +1,222 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */

#include "SparklineGroupsExport.hxx"
#include "xmlexprt.hxx"
#include <rangeutl.hxx>

#include <xmloff/xmluconv.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/namespacemap.hxx>
#include <rtl/ustrbuf.hxx>
#include <sax/tools/converter.hxx>
#include <o3tl/unit_conversion.hxx>

using namespace css;
using namespace xmloff::token;

namespace sc
{
SparklineGroupsExport::SparklineGroupsExport(
    ScXMLExport& rExport, SCTAB nTable, std::vector<std::shared_ptr<Sparkline>> const& rSparklines)
    : m_rExport(rExport)
    , m_nTable(nTable)
{
    for (auto const& pSparkline : rSparklines)
    {
        auto* pGroupPointer = pSparkline->getSparklineGroup().get();
        auto aIterator = m_aSparklineGroupMap.find(pGroupPointer);
        if (aIterator == m_aSparklineGroupMap.end())
        {
            m_aSparklineGroups.push_back(pGroupPointer);
            std::vector<std::shared_ptr<sc::Sparkline>> aSparklineVector;
            aSparklineVector.push_back(pSparkline);
            m_aSparklineGroupMap.emplace(pGroupPointer, aSparklineVector);
        }
        else
        {
            aIterator->second.push_back(pSparkline);
        }
    }
}

void SparklineGroupsExport::insertColor(Color aColor, XMLTokenEnum eToken)
{
    OUStringBuffer aStringBuffer;
    if (aColor != COL_TRANSPARENT)
    {
        sax::Converter::convertColor(aStringBuffer, aColor);
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, eToken, aStringBuffer.makeStringAndClear());
    }
}

void SparklineGroupsExport::insertBool(bool bValue, XMLTokenEnum eToken)
{
    if (bValue)
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, eToken, "true");
}

void SparklineGroupsExport::addSparklineAttributes(Sparkline const& rSparkline)
{
    auto const* pDocument = m_rExport.GetDocument();

    {
        OUString sAddressString;
        ScAddress aAddress(rSparkline.getColumn(), rSparkline.getRow(), m_nTable);
        ScRangeStringConverter::GetStringFromAddress(sAddressString, aAddress, pDocument,
                                                     formula::FormulaGrammar::CONV_OOO);
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_CELL_ADDRESS, sAddressString);
    }

    {
        OUString sDataRangeString;
        ScRangeList const& rRangeList = rSparkline.getInputRange();
        ScRangeStringConverter::GetStringFromRangeList(sDataRangeString, &rRangeList, pDocument,
                                                       formula::FormulaGrammar::CONV_OOO);
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_DATA_RANGE, sDataRangeString);
    }
}

namespace
{
OUString convertSparklineType(sc::SparklineType eType)
{
    switch (eType)
    {
        case sc::SparklineType::Line:
            return u"line";
        case sc::SparklineType::Column:
            return u"column";
        case sc::SparklineType::Stacked:
            return u"stacked";
    }
    return u"";
}

OUString convertDisplayEmptyCellsAs(sc::DisplayEmptyCellsAs eType)
{
    switch (eType)
    {
        case sc::DisplayEmptyCellsAs::Zero:
            return u"zero";
        case sc::DisplayEmptyCellsAs::Gap:
            return u"gap";
        case sc::DisplayEmptyCellsAs::Span:
            return u"span";
    }
    return u"";
}

OUString convertAxisType(sc::AxisType eType)
{
    switch (eType)
    {
        case sc::AxisType::Individual:
            return u"individual";
        case sc::AxisType::Group:
            return u"group";
        case sc::AxisType::Custom:
            return u"custom";
    }
    return u"";
}

} // end anonymous ns

void SparklineGroupsExport::addSparklineGroupAttributes(SparklineAttributes const& rAttributes)
{
    OUString sType = convertSparklineType(rAttributes.getType());
    m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_TYPE, sType);

    // Line Weight = Line Width in ODF

    m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_LINE_WIDTH,
                           OUString::number(rAttributes.getLineWeight()) + "pt");

    insertBool(rAttributes.isDateAxis(), XML_DATE_AXIS);

    OUString sDisplayEmptyCellsAs
        = convertDisplayEmptyCellsAs(rAttributes.getDisplayEmptyCellsAs());
    m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_DISPLAY_EMPTY_CELLS_AS,
                           sDisplayEmptyCellsAs);

    insertBool(rAttributes.isMarkers(), XML_MARKERS);
    insertBool(rAttributes.isHigh(), XML_HIGH);
    insertBool(rAttributes.isLow(), XML_LOW);
    insertBool(rAttributes.isFirst(), XML_FIRST);
    insertBool(rAttributes.isLast(), XML_LAST);
    insertBool(rAttributes.isNegative(), XML_NEGATIVE);
    insertBool(rAttributes.shouldDisplayXAxis(), XML_DISPLAY_X_AXIS);
    insertBool(rAttributes.shouldDisplayHidden(), XML_DISPLAY_HIDDEN);

    OUString sMinAxisType = convertAxisType(rAttributes.getMinAxisType());
    m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_MIN_AXIS_TYPE, sMinAxisType);

    OUString sMaxAxisType = convertAxisType(rAttributes.getMaxAxisType());
    m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_MAX_AXIS_TYPE, sMaxAxisType);

    insertBool(rAttributes.isRightToLeft(), XML_RIGHT_TO_LEFT);

    if (rAttributes.getManualMax() && rAttributes.getMaxAxisType() == sc::AxisType::Custom)
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_MANUAL_MAX,
                               OUString::number(*rAttributes.getManualMax()));

    if (rAttributes.getManualMin() && rAttributes.getMinAxisType() == sc::AxisType::Custom)
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_MANUAL_MIN,
                               OUString::number(*rAttributes.getManualMin()));

    insertColor(rAttributes.getColorSeries(), XML_COLOR_SERIES);
    insertColor(rAttributes.getColorNegative(), XML_COLOR_NEGATIVE);
    insertColor(rAttributes.getColorAxis(), XML_COLOR_AXIS);
    insertColor(rAttributes.getColorMarkers(), XML_COLOR_MARKERS);
    insertColor(rAttributes.getColorFirst(), XML_COLOR_FIRST);
    insertColor(rAttributes.getColorLast(), XML_COLOR_LAST);
    insertColor(rAttributes.getColorHigh(), XML_COLOR_HIGH);
    insertColor(rAttributes.getColorLow(), XML_COLOR_LOW);
}

void SparklineGroupsExport::addSparklineGroup(SparklineGroup* pSparklineGroup)
{
    auto const& rAttributes = pSparklineGroup->getAttributes();

    OUString sID = pSparklineGroup->getID();
    if (!sID.isEmpty())
    {
        m_rExport.AddAttribute(XML_NAMESPACE_CALC_EXT, XML_ID, sID);
    }

    addSparklineGroupAttributes(rAttributes);

    SvXMLElementExport aElementSparklineGroup(m_rExport, XML_NAMESPACE_CALC_EXT,
                                              XML_SPARKLINE_GROUP, true, true);

    SvXMLElementExport aElementSparklines(m_rExport, XML_NAMESPACE_CALC_EXT, XML_SPARKLINES, true,
                                          true);
    for (auto const& rSparkline : m_aSparklineGroupMap[pSparklineGroup])
    {
        addSparklineAttributes(*rSparkline);
        SvXMLElementExport aElementSparkline(m_rExport, XML_NAMESPACE_CALC_EXT, XML_SPARKLINE, true,
                                             true);
    }
}

void SparklineGroupsExport::write()
{
    SvXMLElementExport aElement(m_rExport, XML_NAMESPACE_CALC_EXT, XML_SPARKLINE_GROUPS, true,
                                true);
    for (auto* pSparklineGroup : m_aSparklineGroups)
    {
        addSparklineGroup(pSparklineGroup);
    }
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/xml/SparklineGroupsExport.hxx b/sc/source/filter/xml/SparklineGroupsExport.hxx
new file mode 100644
index 0000000..4e49f58
--- /dev/null
+++ b/sc/source/filter/xml/SparklineGroupsExport.hxx
@@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */

#pragma once

#include <memory>
#include <unordered_map>
#include <tools/color.hxx>
#include <xmloff/xmltoken.hxx>

#include <Sparkline.hxx>
#include <SparklineGroup.hxx>

class ScXMLExport;

namespace sc
{
class SparklineGroupsExport
{
    ScXMLExport& m_rExport;
    std::vector<SparklineGroup*> m_aSparklineGroups;
    std::unordered_map<SparklineGroup*, std::vector<std::shared_ptr<Sparkline>>>
        m_aSparklineGroupMap;
    SCTAB m_nTable;

    void addSparklineGroupAttributes(sc::SparklineAttributes const& rAttributes);
    void addSparklineGroup(SparklineGroup* pSparklineGroup);
    void addSparklineAttributes(Sparkline const& rSparkline);

    void insertColor(Color aColor, xmloff::token::XMLTokenEnum eToken);
    void insertBool(bool bValue, xmloff::token::XMLTokenEnum eToken);

public:
    SparklineGroupsExport(ScXMLExport& rExport, SCTAB nTable,
                          std::vector<std::shared_ptr<Sparkline>> const& rSparklines);

    void write();
};
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/xml/SparklineGroupsImportContext.cxx b/sc/source/filter/xml/SparklineGroupsImportContext.cxx
new file mode 100644
index 0000000..87acbd0
--- /dev/null
+++ b/sc/source/filter/xml/SparklineGroupsImportContext.cxx
@@ -0,0 +1,332 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "SparklineGroupsImportContext.hxx"

#include <sax/tools/converter.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmluconv.hxx>

#include <document.hxx>
#include <rangeutl.hxx>
#include <Sparkline.hxx>
#include <SparklineGroup.hxx>
#include <SparklineAttributes.hxx>

using namespace xmloff::token;
using namespace css;

namespace sc
{
SparklineGroupsImportContext::SparklineGroupsImportContext(ScXMLImport& rImport)
    : ScXMLImportContext(rImport)
{
}

namespace
{
sc::SparklineType parseSparklineType(std::u16string_view aString)
{
    if (aString == u"column")
        return sc::SparklineType::Column;
    else if (aString == u"stacked")
        return sc::SparklineType::Stacked;
    return sc::SparklineType::Line;
}

sc::DisplayEmptyCellsAs parseDisplayEmptyCellsAs(std::u16string_view aString)
{
    if (aString == u"span")
        return sc::DisplayEmptyCellsAs::Span;
    else if (aString == u"gap")
        return sc::DisplayEmptyCellsAs::Gap;
    return sc::DisplayEmptyCellsAs::Zero;
}

sc::AxisType parseAxisType(std::u16string_view aString)
{
    if (aString == u"group")
        return sc::AxisType::Group;
    else if (aString == u"custom")
        return sc::AxisType::Custom;
    return sc::AxisType::Individual;
}

} // end anonymous namespace

void SparklineGroupsImportContext::fillSparklineGroupID(
    uno::Reference<xml::sax::XFastAttributeList> const& xAttrList)
{
    for (auto& rIter : sax_fastparser::castToFastAttributeList(xAttrList))
    {
        switch (rIter.getToken())
        {
            case XML_ELEMENT(CALC_EXT, XML_ID):
            {
                m_pCurrentSparklineGroup->setID(rIter.toString());
                break;
            }
        }
    }
}

void SparklineGroupsImportContext::fillSparklineGroupAttributes(
    uno::Reference<xml::sax::XFastAttributeList> const& xAttrList)
{
    sc::SparklineAttributes& rAttributes = m_pCurrentSparklineGroup->getAttributes();

    for (auto& rIter : sax_fastparser::castToFastAttributeList(xAttrList))
    {
        switch (rIter.getToken())
        {
            case XML_ELEMENT(CALC_EXT, XML_TYPE):
            {
                rAttributes.setType(parseSparklineType(rIter.toString()));
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_LINE_WIDTH):
            {
                OUString sLineWidth = rIter.toString();
                double fLineWidth;
                sal_Int16 const eSrcUnit
                    = ::sax::Converter::GetUnitFromString(sLineWidth, util::MeasureUnit::POINT);
                ::sax::Converter::convertDouble(fLineWidth, sLineWidth, eSrcUnit,
                                                util::MeasureUnit::POINT);
                rAttributes.setLineWeight(fLineWidth);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_DATE_AXIS):
            {
                rAttributes.setDateAxis(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_DISPLAY_EMPTY_CELLS_AS):
            {
                auto eDisplayEmptyCellsAs = parseDisplayEmptyCellsAs(rIter.toString());
                rAttributes.setDisplayEmptyCellsAs(eDisplayEmptyCellsAs);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_MARKERS):
            {
                rAttributes.setMarkers(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_HIGH):
            {
                rAttributes.setHigh(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_LOW):
            {
                rAttributes.setLow(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_FIRST):
            {
                rAttributes.setFirst(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_LAST):
            {
                rAttributes.setLast(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_NEGATIVE):
            {
                rAttributes.setNegative(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_DISPLAY_X_AXIS):
            {
                rAttributes.setDisplayXAxis(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_DISPLAY_HIDDEN):
            {
                rAttributes.setDisplayHidden(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_MIN_AXIS_TYPE):
            {
                rAttributes.setMinAxisType(parseAxisType(rIter.toString()));
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_MAX_AXIS_TYPE):
            {
                rAttributes.setMaxAxisType(parseAxisType(rIter.toString()));
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_RIGHT_TO_LEFT):
            {
                rAttributes.setRightToLeft(rIter.toBoolean());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_MANUAL_MAX):
            {
                rAttributes.setManualMax(rIter.toDouble());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_MANUAL_MIN):
            {
                rAttributes.setManualMin(rIter.toDouble());
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_SERIES):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorSeries(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_NEGATIVE):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorNegative(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_AXIS):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorAxis(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_MARKERS):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorMarkers(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_FIRST):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorFirst(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_LAST):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorLast(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_HIGH):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorHigh(aColor);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_COLOR_LOW):
            {
                Color aColor;
                sax::Converter::convertColor(aColor, rIter.toString());
                rAttributes.setColorLow(aColor);
                break;
            }
            default:
                break;
        }
    }
}

void SparklineGroupsImportContext::fillSparklineAttributes(
    SparklineImportData& rImportData, uno::Reference<xml::sax::XFastAttributeList> const& xAttrList)
{
    ScDocument* pDocument = GetScImport().GetDocument();

    for (auto& rIter : sax_fastparser::castToFastAttributeList(xAttrList))
    {
        switch (rIter.getToken())
        {
            case XML_ELEMENT(CALC_EXT, XML_CELL_ADDRESS):
            {
                sal_Int32 nOffset = 0;
                ScRangeStringConverter::GetAddressFromString(
                    rImportData.m_aAddress, rIter.toString(), *pDocument,
                    formula::FormulaGrammar::CONV_OOO, nOffset);
                break;
            }
            case XML_ELEMENT(CALC_EXT, XML_DATA_RANGE):
            {
                ScRangeStringConverter::GetRangeListFromString(rImportData.m_aDataRangeList,
                                                               rIter.toString(), *pDocument,
                                                               formula::FormulaGrammar::CONV_OOO);
                break;
            }
            default:
                break;
        }
    }
}

uno::Reference<xml::sax::XFastContextHandler>
    SAL_CALL SparklineGroupsImportContext::createFastChildContext(
        sal_Int32 nElement, uno::Reference<xml::sax::XFastAttributeList> const& xAttrList)
{
    SvXMLImportContext* pContext = nullptr;
    switch (nElement)
    {
        case XML_ELEMENT(CALC_EXT, XML_SPARKLINE_GROUP):
        {
            m_pCurrentSparklineGroup = std::make_shared<sc::SparklineGroup>();
            fillSparklineGroupID(xAttrList);
            fillSparklineGroupAttributes(xAttrList);
            pContext = this;
            break;
        }
        case XML_ELEMENT(CALC_EXT, XML_SPARKLINES):
        {
            pContext = this;
            break;
        }
        case XML_ELEMENT(CALC_EXT, XML_SPARKLINE):
        {
            SparklineImportData& rImportData = m_aCurrentSparklineDataList.emplace_back();
            fillSparklineAttributes(rImportData, xAttrList);
            pContext = this;
            break;
        }
    }

    return pContext;
}

void SparklineGroupsImportContext::insertSparklines()
{
    ScDocument* pDocument = GetScImport().GetDocument();
    for (auto const& rSparklineImportData : m_aCurrentSparklineDataList)
    {
        auto* pSparkline
            = pDocument->CreateSparkline(rSparklineImportData.m_aAddress, m_pCurrentSparklineGroup);
        pSparkline->setInputRange(rSparklineImportData.m_aDataRangeList);
    }
}

void SAL_CALL SparklineGroupsImportContext::endFastElement(sal_Int32 nElement)
{
    switch (nElement)
    {
        case XML_ELEMENT(CALC_EXT, XML_SPARKLINE_GROUP):
        {
            insertSparklines();
            m_pCurrentSparklineGroup.reset();
            m_aCurrentSparklineDataList.clear();
            break;
        }
    }
}

} // end sc

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/xml/SparklineGroupsImportContext.hxx b/sc/source/filter/xml/SparklineGroupsImportContext.hxx
new file mode 100644
index 0000000..f643c1c
--- /dev/null
+++ b/sc/source/filter/xml/SparklineGroupsImportContext.hxx
@@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <memory>
#include "importcontext.hxx"
#include "xmlimprt.hxx"
#include <address.hxx>
#include <rangelst.hxx>

namespace sax_fastparser
{
class FastAttributeList;
}

namespace sc
{
class SparklineGroup;

struct SparklineImportData
{
    ScAddress m_aAddress;
    ScRangeList m_aDataRangeList;
};

class SparklineGroupsImportContext : public ScXMLImportContext
{
private:
    std::shared_ptr<sc::SparklineGroup> m_pCurrentSparklineGroup;
    std::vector<SparklineImportData> m_aCurrentSparklineDataList;

    void
    fillSparklineGroupID(css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList);
    void fillSparklineGroupAttributes(
        css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList);
    void fillSparklineAttributes(
        SparklineImportData& rImportData,
        css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList);

    void insertSparklines();

public:
    SparklineGroupsImportContext(ScXMLImport& rImport);

    css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext(
        sal_Int32 nElement,
        css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList) override;

    void SAL_CALL endFastElement(sal_Int32 nElement) override;
};

} // end sc

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/xml/xmlexprt.cxx b/sc/source/filter/xml/xmlexprt.cxx
index daee5e7..20da166 100644
--- a/sc/source/filter/xml/xmlexprt.cxx
+++ b/sc/source/filter/xml/xmlexprt.cxx
@@ -67,6 +67,7 @@
#include <cellform.hxx>
#include <datamapper.hxx>
#include <datatransformation.hxx>
#include "SparklineGroupsExport.hxx"

#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlnamespace.hxx>
@@ -2984,6 +2985,7 @@ void ScXMLExport::WriteTable(sal_Int32 nTable, const uno::Reference<sheet::XSpre
    {
        //export new conditional format information
        ExportConditionalFormat(nTable);
        exportSparklineGroups(nTable);
    }
}

@@ -4512,6 +4514,19 @@ void ScXMLExport::WriteNamedRange(ScRangeName* pRangeName)
    }
}

void ScXMLExport::exportSparklineGroups(SCTAB nTable)
{
    if (sc::SparklineList* pSparklineList = pDoc->GetSparklineList(nTable))
    {
        auto pSparklines = pSparklineList->getSparklines();
        if (!pSparklines.empty())
        {
            sc::SparklineGroupsExport aSparklineGroupExport(*this, nTable, pSparklines);
            aSparklineGroupExport.write();
        }
    }
}

namespace {

OUString getCondFormatEntryType(const ScColorScaleEntry& rEntry, bool bFirst = true)
diff --git a/sc/source/filter/xml/xmlexprt.hxx b/sc/source/filter/xml/xmlexprt.hxx
index e4a165a..8ab8901 100644
--- a/sc/source/filter/xml/xmlexprt.hxx
+++ b/sc/source/filter/xml/xmlexprt.hxx
@@ -198,6 +198,7 @@ class ScXMLExport : public SvXMLExport
    void WriteExternalDataTransformations(const std::vector<std::shared_ptr<sc::DataTransformation>>& aDataTransformations);
    void WriteDataStream();
    void WriteNamedRange(ScRangeName* pRangeName);
    void exportSparklineGroups(SCTAB nTab);
    void ExportConditionalFormat(SCTAB nTab);
    void WriteExternalRefCaches();
    void WriteConsolidation();  // core implementation
diff --git a/sc/source/filter/xml/xmltabi.cxx b/sc/source/filter/xml/xmltabi.cxx
index dd1baf29..8354745 100644
--- a/sc/source/filter/xml/xmltabi.cxx
+++ b/sc/source/filter/xml/xmltabi.cxx
@@ -34,6 +34,7 @@
#include <externalrefmgr.hxx>
#include <sheetdata.hxx>
#include "xmlcondformat.hxx"
#include "SparklineGroupsImportContext.hxx"

#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlnamespace.hxx>
@@ -298,6 +299,9 @@ uno::Reference< xml::sax::XFastContextHandler > SAL_CALL
    case XML_ELEMENT( CALC_EXT, XML_CONDITIONAL_FORMATS ):
        pContext = new ScXMLConditionalFormatsContext( GetScImport() );
        break;
     case XML_ELEMENT(CALC_EXT, XML_SPARKLINE_GROUPS):
        pContext = new sc::SparklineGroupsImportContext(GetScImport());
        break;
    case XML_ELEMENT(OFFICE, XML_EVENT_LISTENERS):
    case XML_ELEMENT(OFFICE_EXT, XML_EVENT_LISTENERS):
        {
@@ -315,6 +319,7 @@ uno::Reference< xml::sax::XFastContextHandler > SAL_CALL
        break;
    default:
        XMLOFF_WARN_UNKNOWN_ELEMENT("sc", nElement);
        break;
    }

    return pContext;
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index 1dff482..ccbc6e4 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -1535,6 +1535,171 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
            </rng:oneOrMore>
          </rng:element>
        </rng:optional>
        <!-- Sparklines-->
        <rng:optional>
          <rng:element name="calcext:sparkline-groups">
            <rng:oneOrMore>
              <rng:element name="calcext:sparkline-group">
                <rng:attribute name="calcext:id">
                  <rng:ref name="string"/>
                </rng:attribute>
                <rng:optional>
                  <rng:attribute name="calcext:type">
                    <rng:choice>
                      <rng:value>line</rng:value>
                      <rng:value>column</rng:value>
                      <rng:value>stacked</rng:value>
                    </rng:choice>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:line-width">
                    <rng:ref name="length"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:date-axis">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:display-empty-cells-as">
                    <rng:choice>
                      <rng:value>zero</rng:value>
                      <rng:value>gap</rng:value>
                      <rng:value>span</rng:value>
                    </rng:choice>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:markers">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:high">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:low">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:first">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:last">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:negative">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:display-x-axis">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:display-hidden">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:min-axis-type">
                    <rng:choice>
                      <rng:value>individual</rng:value>
                      <rng:value>group</rng:value>
                      <rng:value>custom</rng:value>
                    </rng:choice>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:max-axis-type">
                    <rng:choice>
                      <rng:value>individual</rng:value>
                      <rng:value>group</rng:value>
                      <rng:value>custom</rng:value>
                    </rng:choice>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:right-to-left">
                    <rng:ref name="boolean"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:manual-max">
                    <rng:ref name="double"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:manual-min">
                    <rng:ref name="double"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-series">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-negative">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-axis">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-markers">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-first">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-last">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-high">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:optional>
                  <rng:attribute name="calcext:color-low">
                    <rng:ref name="color"/>
                  </rng:attribute>
                </rng:optional>
                <rng:element name="calcext:sparklines">
                  <rng:oneOrMore>
                    <rng:element name="calcext:sparkline">
                      <rng:attribute name="calcext:cell-address">
                        <rng:ref name="cellAddress"/>
                      </rng:attribute>
                      <rng:attribute name="calcext:data-range">
                        <rng:ref name="cellRangeAddressList"/>
                      </rng:attribute>
                    </rng:element>
                  </rng:oneOrMore>
                </rng:element>
              </rng:element>
            </rng:oneOrMore>
          </rng:element>
        </rng:optional>
      </rng:element>
    </rng:define>

diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 0b8f36b..34ae9d9 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -461,8 +461,16 @@ namespace xmloff::token {
        TOKEN( "codebase",                        XML_CODEBASE ),
        TOKEN( "collapse",                        XML_COLLAPSE ),
        TOKEN( "color",                           XML_COLOR ),
        TOKEN( "color-axis",                      XML_COLOR_AXIS ),
        TOKEN( "color-first",                     XML_COLOR_FIRST ),
        TOKEN( "color-high",                      XML_COLOR_HIGH ),
        TOKEN( "color-inversion",                 XML_COLOR_INVERSION ),
        TOKEN( "color-last",                      XML_COLOR_LAST ),
        TOKEN( "color-low",                       XML_COLOR_LOW ),
        TOKEN( "color-markers",                   XML_COLOR_MARKERS ),
        TOKEN( "color-mode",                      XML_COLOR_MODE ),
        TOKEN( "color-negative",                  XML_COLOR_NEGATIVE ),
        TOKEN( "color-series",                    XML_COLOR_SERIES ),
        TOKEN( "color-scale",                     XML_COLOR_SCALE ),
        TOKEN( "color-scale-entry",               XML_COLOR_SCALE_ENTRY ),
        TOKEN( "color-table",                     XML_COLOR_TABLE ),
@@ -605,6 +613,7 @@ namespace xmloff::token {
        TOKEN( "data-pilot-table",                XML_DATA_PILOT_TABLE ),
        TOKEN( "data-pilot-tables",               XML_DATA_PILOT_TABLES ),
        TOKEN( "data-point",                      XML_DATA_POINT ),
        TOKEN( "data-range",                      XML_DATA_RANGE ),
        TOKEN( "data-stream-source",              XML_DATA_STREAM_SOURCE ),
        TOKEN( "data-style",                      XML_DATA_STYLE ),
        TOKEN( "data-style-name",                 XML_DATA_STYLE_NAME ),
@@ -621,6 +630,7 @@ namespace xmloff::token {
        TOKEN( "database-source-table",           XML_DATABASE_SOURCE_TABLE ),
        TOKEN( "database-table-name",             XML_DATABASE_TABLE_NAME ),
        TOKEN( "date",                            XML_DATE ),
        TOKEN( "date-axis",                       XML_DATE_AXIS ),
        TOKEN( "date-is",                         XML_DATE_IS ),
        TOKEN( "date-adjust",                     XML_DATE_ADJUST ),
        TOKEN( "date-style",                      XML_DATE_STYLE ),
@@ -680,12 +690,15 @@ namespace xmloff::token {
        TOKEN( "display-details",                 XML_DISPLAY_DETAILS ),
        TOKEN( "display-duplicates",              XML_DISPLAY_DUPLICATES ),
        TOKEN( "display-empty",                   XML_DISPLAY_EMPTY ),
        TOKEN( "display-empty-cells-as",          XML_DISPLAY_EMPTY_CELLS_AS ),
        TOKEN( "display-filter-buttons",          XML_DISPLAY_FILTER_BUTTONS ),
        TOKEN( "display-formula",                 XML_DISPLAY_FORMULA ),
        TOKEN( "display-hidden",                  XML_DISPLAY_HIDDEN ),
        TOKEN( "display-label",                   XML_DISPLAY_LABEL ),
        TOKEN( "display-levels",                  XML_DISPLAY_LEVELS ),
        TOKEN( "display-name",                    XML_DISPLAY_NAME ),
        TOKEN( "display-outline-level",           XML_DISPLAY_OUTLINE_LEVEL ),
        TOKEN( "display-x-axis",                  XML_DISPLAY_X_AXIS ),
        TOKEN( "dissolve",                        XML_DISSOLVE ),
        TOKEN( "distance",                        XML_DISTANCE ),
        TOKEN( "distance-after-sep",              XML_DISTANCE_AFTER_SEP ),
@@ -1194,6 +1207,7 @@ namespace xmloff::token {
        TOKEN( "line-skew",                       XML_LINE_SKEW ),
        TOKEN( "line-spacing",                    XML_LINE_SPACING ),
        TOKEN( "line-style",                      XML_LINE_STYLE ),
        TOKEN( "line-width",                      XML_LINE_WIDTH ),
        TOKEN( "linear",                          XML_LINEAR ),
        TOKEN( "linearGradient",                  XML_LINEARGRADIENT ),
        TOKEN( "linenumbering-configuration",     XML_LINENUMBERING_CONFIGURATION ),
@@ -1221,6 +1235,7 @@ namespace xmloff::token {
        TOKEN( "logarithmic",                     XML_LOGARITHMIC ),
        TOKEN( "logbase",                         XML_LOGBASE ),
        TOKEN( "long",                            XML_LONG ),
        TOKEN( "low",                             XML_LOW ),
        TOKEN( "lowlimit",                        XML_LOWLIMIT ),
        TOKEN( "lr-tb",                           XML_LR_TB ),
        TOKEN( "lt",                              XML_LT ),
@@ -1234,6 +1249,8 @@ namespace xmloff::token {
        TOKEN( "maligngroup",                     XML_MALIGNGROUP ),
        TOKEN( "malignmark",                      XML_MALIGNMARK ),
        TOKEN( "manual",                          XML_MANUAL ),
        TOKEN( "manual-min",                      XML_MANUAL_MIN ),
        TOKEN( "manual-max",                      XML_MANUAL_MAX ),
        TOKEN( "map",                             XML_MAP ),
        TOKEN( "margin-bottom",                   XML_MARGIN_BOTTOM ),
        TOKEN( "margin-left",                     XML_MARGIN_LEFT ),
@@ -1241,6 +1258,7 @@ namespace xmloff::token {
        TOKEN( "margin-top",                      XML_MARGIN_TOP ),
        TOKEN( "margins",                         XML_MARGINS ),
        TOKEN( "marker",                          XML_MARKER ),
        TOKEN( "markers",                         XML_MARKERS ),
        TOKEN( "marker-end",                      XML_MARKER_END ),
        TOKEN( "marker-end-center",               XML_MARKER_END_CENTER ),
        TOKEN( "marker-end-width",                XML_MARKER_END_WIDTH ),
@@ -1259,6 +1277,7 @@ namespace xmloff::token {
        TOKEN( "matrix-covered",                  XML_MATRIX_COVERED ),
        TOKEN( "matrixrow",                       XML_MATRIXROW ),
        TOKEN( "max",                             XML_MAX ),
        TOKEN( "max-axis-type",                   XML_MAX_AXIS_TYPE ),
        TOKEN( "max-edge",                        XML_MAX_EDGE ),
        TOKEN( "max-height",                      XML_MAX_HEIGHT ),
        TOKEN( "max-length",                      XML_MAX_LENGTH ),
@@ -1284,6 +1303,7 @@ namespace xmloff::token {
        TOKEN( "middle",                          XML_MIDDLE ),
        TOKEN( "mime-type",                       XML_MIME_TYPE ),
        TOKEN( "min",                             XML_MIN ),
        TOKEN( "min-axis-type",                   XML_MIN_AXIS_TYPE ),
        TOKEN( "min-denominator-digits",          XML_MIN_DENOMINATOR_DIGITS ),
        TOKEN( "min-edge",                        XML_MIN_EDGE ),
        TOKEN( "min-exponent-digits",             XML_MIN_EXPONENT_DIGITS ),
@@ -1358,7 +1378,8 @@ namespace xmloff::token {
        TOKEN( "named-range",                     XML_NAMED_RANGE ),
        TOKEN( "navigation-mode",                 XML_NAVIGATION_MODE ),
        TOKEN( "navy",                            XML_NAVY ),
        TOKEN( "negative-color",                   XML_NEGATIVE_COLOR ),
        TOKEN( "negative",                        XML_NEGATIVE ),
        TOKEN( "negative-color",                  XML_NEGATIVE_COLOR ),
        TOKEN( "neq",                             XML_NEQ ),
        TOKEN( "new",                             XML_NEW ),
        TOKEN( "next",                            XML_NEXT ),
@@ -1619,6 +1640,7 @@ namespace xmloff::token {
        TOKEN( "right",                           XML_RIGHT ),
        TOKEN( "right-outside",                   XML_RIGHT_OUTSIDE ),
        TOKEN( "right-text",                      XML_RIGHT_TEXT ),
        TOKEN( "right-to-left",                   XML_RIGHT_TO_LEFT ),
        TOKEN( "right-arc",                       XML_RIGHTARC ),
        TOKEN( "right-circle",                    XML_RIGHTCIRCLE ),
        TOKEN( "ring",                            XML_RING ),
@@ -1773,6 +1795,10 @@ namespace xmloff::token {
        TOKEN( "source-range-address",            XML_SOURCE_RANGE_ADDRESS ),
        TOKEN( "source-service",                  XML_SOURCE_SERVICE ),
        TOKEN( "space-before",                    XML_SPACE_BEFORE ),
        TOKEN( "sparkline-groups",                XML_SPARKLINE_GROUPS ),
        TOKEN( "sparkline-group",                 XML_SPARKLINE_GROUP ),
        TOKEN( "sparklines",                      XML_SPARKLINES ),
        TOKEN( "sparkline",                       XML_SPARKLINE ),
        TOKEN( "span",                            XML_SPAN ),
        TOKEN( "specular",                        XML_SPECULAR ),
        TOKEN( "specular-color",                  XML_SPECULAR_COLOR ),
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 8639006..da3080a 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -366,8 +366,16 @@ code
codebase
collapse
color
color-axis
color-first
color-high
color-inversion
color-last
color-low
color-markers
color-mode
color-negative
color-series
color-scale
color-scale-entry
color-table
@@ -510,6 +518,7 @@ data-pilot-subtotals
data-pilot-table
data-pilot-tables
data-point
data-range
data-stream-source
data-style
data-style-name
@@ -526,6 +535,7 @@ database-source-sql
database-source-table
database-table-name
date
date-axis
date-is
date-adjust
date-style
@@ -585,12 +595,15 @@ display-border
display-details
display-duplicates
display-empty
display-empty-cells-as
display-filter-buttons
display-formula
display-hidden
display-label
display-levels
display-name
display-outline-level
display-x-axis
dissolve
distance
distance-after-sep
@@ -1094,6 +1107,7 @@ line-number
line-skew
line-spacing
line-style
line-width
linear
linearGradient
linenumbering-configuration
@@ -1121,6 +1135,7 @@ log
logarithmic
logbase
long
low
lowlimit
lr-tb
lt
@@ -1134,6 +1149,8 @@ major-origin
maligngroup
malignmark
manual
manual-min
manual-max
map
margin-bottom
margin-left
@@ -1141,6 +1158,7 @@ margin-right
margin-top
margins
marker
markers
marker-end
marker-end-center
marker-end-width
@@ -1159,6 +1177,7 @@ matrix
matrix-covered
matrixrow
max
max-axis-type
max-edge
max-height
max-length
@@ -1184,6 +1203,7 @@ mi
middle
mime-type
min
min-axis-type
min-denominator-digits
min-edge
min-exponent-digits
@@ -1258,6 +1278,7 @@ named-expressions
named-range
navigation-mode
navy
negative
negative-color
neq
new
@@ -1519,6 +1540,7 @@ ridge
right
right-outside
right-text
right-to-left
right-arc
right-circle
ring
@@ -1673,6 +1695,10 @@ source-name
source-range-address
source-service
space-before
sparkline-groups
sparkline-group
sparklines
sparkline
span
specular
specular-color