sc/CppunitTest_sc_sparkline_test.mk            |    1 
 sc/Library_scfilt.mk                           |    1 
 sc/qa/unit/SparklineImportExportTest.cxx       |   21 +-
 sc/source/filter/excel/excdoc.cxx              |    3 
 sc/source/filter/excel/export/SparklineExt.cxx |  246 +++++++++++++++++++++++++
 sc/source/filter/inc/export/SparklineExt.hxx   |   54 +++++
 sc/source/filter/inc/xeextlst.hxx              |    3 
 7 files changed, 323 insertions(+), 6 deletions(-)

New commits:
commit 44d599ca1c4bfdf1b803b707cadc7eb5158af979
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Fri Mar 4 20:40:14 2022 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Mon Apr 11 01:59:20 2022 +0200

    sc: Sparkline export for OOXML documents + roundtrip test
    
    Change-Id: I4ab93d7ad33867ae817aa98d13ea9bc724b7d710
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132248
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    (cherry picked from commit 0d9aa7388541d10a463c2d0a8d752d234aecfddd)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132776
    Tested-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/sc/CppunitTest_sc_sparkline_test.mk 
b/sc/CppunitTest_sc_sparkline_test.mk
index 5dcd9a9921b5..30513b273791 100644
--- a/sc/CppunitTest_sc_sparkline_test.mk
+++ b/sc/CppunitTest_sc_sparkline_test.mk
@@ -32,6 +32,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sc_sparkline_test, 
\
     test \
     tl \
     unotest \
+    utl \
     vcl \
 ))
 
diff --git a/sc/Library_scfilt.mk b/sc/Library_scfilt.mk
index da73e3c43f15..eff7cca485e4 100644
--- a/sc/Library_scfilt.mk
+++ b/sc/Library_scfilt.mk
@@ -130,6 +130,7 @@ $(eval $(call gb_Library_add_exception_objects,scfilt,\
        sc/source/filter/excel/xltools \
        sc/source/filter/excel/xltracer \
        sc/source/filter/excel/xlview \
+       sc/source/filter/excel/export/SparklineExt \
        sc/source/filter/ftools/fapihelper \
        sc/source/filter/ftools/fprogressbar \
        sc/source/filter/ftools/ftools \
diff --git a/sc/qa/unit/SparklineImportExportTest.cxx 
b/sc/qa/unit/SparklineImportExportTest.cxx
index cf970ae8995c..f1af8f9d5eb1 100644
--- a/sc/qa/unit/SparklineImportExportTest.cxx
+++ b/sc/qa/unit/SparklineImportExportTest.cxx
@@ -50,12 +50,10 @@ public:
     CPPUNIT_TEST_SUITE_END();
 };
 
-void SparklineImportExportTest::testSparklines()
+namespace
+{
+void checkSparklines(ScDocument& rDocument)
 {
-    ScDocShellRef xDocSh = loadDoc(u"Sparklines.", FORMAT_XLSX);
-    CPPUNIT_ASSERT(xDocSh);
-
-    ScDocument& rDocument = xDocSh->GetDocument();
     // Sparkline at Sheet1:A2
     {
         sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 1, 
0)); // A2
@@ -148,6 +146,19 @@ void SparklineImportExportTest::testSparklines()
         sc::Sparkline* pSparkline = rDocument.GetSparkline(ScAddress(0, 3, 
0)); //A4
         CPPUNIT_ASSERT(!pSparkline);
     }
+}
+} // end anonymous namespace
+
+void SparklineImportExportTest::testSparklines()
+{
+    ScDocShellRef xDocSh = loadDoc(u"Sparklines.", FORMAT_XLSX);
+    CPPUNIT_ASSERT(xDocSh);
+
+    checkSparklines(xDocSh->GetDocument());
+
+    xDocSh = saveAndReload(*xDocSh, FORMAT_XLSX);
+
+    checkSparklines(xDocSh->GetDocument());
 
     xDocSh->DoClose();
 }
diff --git a/sc/source/filter/excel/excdoc.cxx 
b/sc/source/filter/excel/excdoc.cxx
index 6b02457f70f6..529e76971cbc 100644
--- a/sc/source/filter/excel/excdoc.cxx
+++ b/sc/source/filter/excel/excdoc.cxx
@@ -39,6 +39,7 @@
 #include <xecontent.hxx>
 #include <xeescher.hxx>
 #include <xepivot.hxx>
+#include <export/SparklineExt.hxx>
 #include <XclExpChangeTrack.hxx>
 #include <xepivotxml.hxx>
 #include <xedbdata.hxx>
@@ -611,6 +612,8 @@ void ExcTable::FillAsTableXml()
     // conditional formats
     Add( new XclExpCondFormatBuffer( GetRoot(), xExtLst ) );
 
+    Add(new xcl::exp::SparklineBuffer(GetRoot(), xExtLst));
+
     // data validation (DVAL and list of DV records), generated by the cell 
table
     aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) );
 
diff --git a/sc/source/filter/excel/export/SparklineExt.cxx 
b/sc/source/filter/excel/export/SparklineExt.cxx
new file mode 100644
index 000000000000..f0f3cd9e1d98
--- /dev/null
+++ b/sc/source/filter/excel/export/SparklineExt.cxx
@@ -0,0 +1,246 @@
+/* -*- 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 <export/SparklineExt.hxx>
+
+#include <oox/export/utils.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+
+using namespace oox;
+
+namespace xcl::exp
+{
+SparklineExt::SparklineExt(const XclExpRoot& rRoot,
+                           std::vector<std::shared_ptr<sc::Sparkline>> const& 
pSparklines)
+    : XclExpExt(rRoot)
+{
+    maURI = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}";
+
+    for (auto const& pSparkline : pSparklines)
+    {
+        auto* pGroupPointer = pSparkline->getSparklineGroup().get();
+
+        auto aIterator = m_aSparklineGroupMap.find(pGroupPointer);
+        if (aIterator == m_aSparklineGroupMap.end())
+        {
+            std::vector<std::shared_ptr<sc::Sparkline>> aSparklineVector;
+            aSparklineVector.push_back(pSparkline);
+            m_aSparklineGroupMap.emplace(pGroupPointer, aSparklineVector);
+        }
+        else
+        {
+            aIterator->second.push_back(pSparkline);
+        }
+    }
+}
+
+void SparklineExt::SaveXml(XclExpXmlStream& rStream)
+{
+    sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream();
+    rWorksheet->startElement(XML_ext, FSNS(XML_xmlns, XML_x14),
+                             rStream.getNamespaceURL(OOX_NS(xls14Lst)), 
XML_uri, maURI);
+
+    rWorksheet->startElementNS(XML_x14, XML_sparklineGroups, FSNS(XML_xmlns, 
XML_xm),
+                               rStream.getNamespaceURL(OOX_NS(xm)));
+
+    for (auto const & [ pSparklineGroup, rSparklineVector ] : 
m_aSparklineGroupMap)
+    {
+        addSparklineGroup(rStream, *pSparklineGroup, rSparklineVector);
+    }
+
+    rWorksheet->endElementNS(XML_x14, XML_sparklineGroups);
+    rWorksheet->endElement(XML_ext);
+}
+
+void SparklineExt::addSparklineGroupAttributes(
+    rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList,
+    sc::SparklineGroup& rSparklineGroup)
+{
+    if (rSparklineGroup.m_fLineWeight != 0.75)
+        pAttrList->add(XML_lineWeight, 
OString::number(rSparklineGroup.m_fLineWeight));
+
+    if (rSparklineGroup.m_eType != sc::SparklineType::Line)
+    {
+        if (rSparklineGroup.m_eType == sc::SparklineType::Column)
+            pAttrList->add(XML_type, "column");
+        else if (rSparklineGroup.m_eType == sc::SparklineType::Stacked)
+            pAttrList->add(XML_type, "stacked");
+    }
+
+    if (rSparklineGroup.m_bDateAxis)
+        pAttrList->add(XML_dateAxis, "1");
+
+    if (rSparklineGroup.m_eDisplayEmptyCellsAs != sc::DisplayEmptyCellAs::Zero)
+    {
+        if (rSparklineGroup.m_eDisplayEmptyCellsAs == 
sc::DisplayEmptyCellAs::Gap)
+            pAttrList->add(XML_displayEmptyCellsAs, "gap");
+        else if (rSparklineGroup.m_eDisplayEmptyCellsAs == 
sc::DisplayEmptyCellAs::Span)
+            pAttrList->add(XML_displayEmptyCellsAs, "span");
+    }
+
+    if (rSparklineGroup.m_bMarkers)
+        pAttrList->add(XML_markers, "1");
+    if (rSparklineGroup.m_bHigh)
+        pAttrList->add(XML_high, "1");
+    if (rSparklineGroup.m_bLow)
+        pAttrList->add(XML_low, "1");
+    if (rSparklineGroup.m_bFirst)
+        pAttrList->add(XML_first, "1");
+    if (rSparklineGroup.m_bLast)
+        pAttrList->add(XML_last, "1");
+    if (rSparklineGroup.m_bNegative)
+        pAttrList->add(XML_negative, "1");
+    if (rSparklineGroup.m_bDisplayXAxis)
+        pAttrList->add(XML_displayXAxis, "1");
+    if (rSparklineGroup.m_bDisplayHidden)
+        pAttrList->add(XML_displayHidden, "1");
+
+    if (rSparklineGroup.m_eMinAxisType != sc::AxisType::Individual)
+    {
+        if (rSparklineGroup.m_eMinAxisType == sc::AxisType::Group)
+            pAttrList->add(XML_minAxisType, "group");
+        else if (rSparklineGroup.m_eMinAxisType == sc::AxisType::Custom)
+            pAttrList->add(XML_minAxisType, "custom");
+    }
+
+    if (rSparklineGroup.m_eMaxAxisType != sc::AxisType::Individual)
+    {
+        if (rSparklineGroup.m_eMaxAxisType == sc::AxisType::Group)
+            pAttrList->add(XML_maxAxisType, "group");
+        else if (rSparklineGroup.m_eMaxAxisType == sc::AxisType::Custom)
+            pAttrList->add(XML_maxAxisType, "custom");
+    }
+
+    if (rSparklineGroup.m_bRightToLeft)
+        pAttrList->add(XML_rightToLeft, "1");
+
+    if (rSparklineGroup.m_aManualMax && rSparklineGroup.m_eMaxAxisType == 
sc::AxisType::Custom)
+        pAttrList->add(XML_manualMax, 
OString::number(*rSparklineGroup.m_aManualMax));
+
+    if (rSparklineGroup.m_aManualMin && rSparklineGroup.m_eMinAxisType == 
sc::AxisType::Custom)
+        pAttrList->add(XML_manualMin, 
OString::number(*rSparklineGroup.m_aManualMin));
+}
+
+void SparklineExt::addSparklineGroupColors(XclExpXmlStream& rStream,
+                                           sc::SparklineGroup& rSparklineGroup)
+{
+    sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream();
+
+    rWorksheet->singleElementNS(XML_x14, XML_colorSeries, XML_rgb,
+                                
XclXmlUtils::ToOString(rSparklineGroup.m_aColorSeries));
+
+    if (rSparklineGroup.m_aColorSeries != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorNegative, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorNegative));
+    }
+
+    if (rSparklineGroup.m_aColorAxis != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorAxis, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorAxis));
+    }
+
+    if (rSparklineGroup.m_aColorMarkers != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorMarkers, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorMarkers));
+    }
+
+    if (rSparklineGroup.m_aColorFirst != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorFirst, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorFirst));
+    }
+
+    if (rSparklineGroup.m_aColorLast != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorLast, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorLast));
+    }
+
+    if (rSparklineGroup.m_aColorHigh != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorHigh, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorHigh));
+    }
+
+    if (rSparklineGroup.m_aColorLow != COL_TRANSPARENT)
+    {
+        rWorksheet->singleElementNS(XML_x14, XML_colorLow, XML_rgb,
+                                    
XclXmlUtils::ToOString(rSparklineGroup.m_aColorLow));
+    }
+}
+
+void SparklineExt::addSparklineGroup(XclExpXmlStream& rStream, 
sc::SparklineGroup& rSparklineGroup,
+                                     
std::vector<std::shared_ptr<sc::Sparkline>> const& rSparklines)
+{
+    sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream();
+
+    // Sparkline Group Attributes
+    auto pAttrList = sax_fastparser::FastSerializerHelper::createAttrList();
+    addSparklineGroupAttributes(pAttrList, rSparklineGroup);
+
+    rWorksheet->startElementNS(XML_x14, XML_sparklineGroup, pAttrList);
+
+    addSparklineGroupColors(rStream, rSparklineGroup);
+
+    // Sparklines
+
+    rWorksheet->startElementNS(XML_x14, XML_sparklines);
+    for (auto const& rSparkline : rSparklines)
+    {
+        rWorksheet->startElementNS(XML_x14, XML_sparkline);
+
+        {
+            rWorksheet->startElementNS(XML_xm, XML_f);
+
+            OUString sRangeFormula;
+            ScRefFlags eFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D;
+            rSparkline->getInputRange().Format(sRangeFormula, eFlags, GetDoc(),
+                                               
formula::FormulaGrammar::CONV_XL_OOX, ' ', true);
+
+            rWorksheet->writeEscaped(sRangeFormula);
+            rWorksheet->endElementNS(XML_xm, XML_f);
+        }
+
+        {
+            rWorksheet->startElementNS(XML_xm, XML_sqref);
+
+            ScAddress::Details detailsXL(formula::FormulaGrammar::CONV_XL_OOX);
+            ScAddress aAddress(rSparkline->getColumn(), rSparkline->getRow(), 
GetCurrScTab());
+            OUString sLocation = aAddress.Format(ScRefFlags::VALID, &GetDoc(), 
detailsXL);
+
+            rWorksheet->writeEscaped(sLocation);
+            rWorksheet->endElementNS(XML_xm, XML_sqref);
+        }
+
+        rWorksheet->endElementNS(XML_x14, XML_sparkline);
+    }
+    rWorksheet->endElementNS(XML_x14, XML_sparklines);
+    rWorksheet->endElementNS(XML_x14, XML_sparklineGroup);
+}
+
+SparklineBuffer::SparklineBuffer(const XclExpRoot& rRoot, XclExtLstRef const& 
xExtLst)
+    : XclExpRoot(rRoot)
+{
+    if (sc::SparklineList* pSparklineList = 
GetDoc().GetSparklineList(GetCurrScTab()))
+    {
+        auto pSparklines = pSparklineList->getSparklines();
+        if (!pSparklines.empty())
+        {
+            xExtLst->AddRecord(new xcl::exp::SparklineExt(GetRoot(), 
pSparklines));
+        }
+    }
+}
+
+} // end namespace xcl::exp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/export/SparklineExt.hxx 
b/sc/source/filter/inc/export/SparklineExt.hxx
new file mode 100644
index 000000000000..aa649d89e772
--- /dev/null
+++ b/sc/source/filter/inc/export/SparklineExt.hxx
@@ -0,0 +1,54 @@
+/* -*- 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 <vector>
+#include <map>
+#include <rangelst.hxx>
+#include <Sparkline.hxx>
+
+#include <sax/fastattribs.hxx>
+
+#include <xerecord.hxx>
+#include <xeroot.hxx>
+#include <xeextlst.hxx>
+
+namespace xcl::exp
+{
+class SparklineExt : public XclExpExt
+{
+    std::map<sc::SparklineGroup*, std::vector<std::shared_ptr<sc::Sparkline>>> 
m_aSparklineGroupMap;
+
+public:
+    SparklineExt(const XclExpRoot& rRoot,
+                 std::vector<std::shared_ptr<sc::Sparkline>> const& 
pSparklines);
+
+    void SaveXml(XclExpXmlStream& rStream) override;
+    void addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& 
rSparklineGroup,
+                           std::vector<std::shared_ptr<sc::Sparkline>> const& 
rSparklines);
+    static void
+    
addSparklineGroupAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& 
pAttrList,
+                                sc::SparklineGroup& rSparklineGroup);
+    static void addSparklineGroupColors(XclExpXmlStream& rStream,
+                                        sc::SparklineGroup& rSparklineGroup);
+
+    XclExpExtType GetType() override { return XclExpExtSparklineType; }
+};
+
+class SparklineBuffer : public XclExpRecordBase, protected XclExpRoot
+{
+public:
+    explicit SparklineBuffer(const XclExpRoot& rRoot, const XclExtLstRef& 
xExtLst);
+};
+
+} // end namespace xcl::exp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/xeextlst.hxx 
b/sc/source/filter/inc/xeextlst.hxx
index c5db5fcaff99..1770f9af191d 100644
--- a/sc/source/filter/inc/xeextlst.hxx
+++ b/sc/source/filter/inc/xeextlst.hxx
@@ -19,7 +19,8 @@
 enum XclExpExtType
 {
     XclExpExtDataBarType,
-    XclExpExtDataFooType
+    XclExpExtDataFooType,
+    XclExpExtSparklineType,
 };
 
 struct XclExpExtCondFormatData

Reply via email to