tdf#114166 DOCX chart import: fix missing complex categories

Now complex category labels are visible, and the inner data
table contains the correct texts of the category columns.

Note: repeating call of createDataSequenceByValueArray() API function
can create all columns of the complex categories. See also
commit 6c4e21a234f12e1310ba06f9859e08b424acf8bf
"bnc#812796: Correctly handle static value array for OOXML charts."

Change-Id: I333b79be35a24a912bb9e662116d0c85809a8fb2
Reviewed-on: https://gerrit.libreoffice.org/75776
Reviewed-by: László Németh <nemeth@numbertext.org>
Tested-by: László Németh <nemeth@numbertext.org>
diff --git a/chart2/qa/extras/chart2import.cxx b/chart2/qa/extras/chart2import.cxx
index 107daed..710d15e 100644
--- a/chart2/qa/extras/chart2import.cxx
+++ b/chart2/qa/extras/chart2import.cxx
@@ -131,6 +131,7 @@ public:
    void testDataPointInheritedColorDOCX();
    void testExternalStrRefsXLSX();
    void testSourceNumberFormatComplexCategoriesXLS();
    void testMultilevelCategoryAxis();
    void testTdf123504();
    void testTdf122765();

@@ -216,6 +217,7 @@ public:
    CPPUNIT_TEST(testDataPointInheritedColorDOCX);
    CPPUNIT_TEST(testExternalStrRefsXLSX);
    CPPUNIT_TEST(testSourceNumberFormatComplexCategoriesXLS);
    CPPUNIT_TEST(testMultilevelCategoryAxis);
    CPPUNIT_TEST(testTdf123504);
    CPPUNIT_TEST(testTdf122765);

@@ -1534,10 +1536,10 @@ void Chart2ImportTest::testInternalDataProvider() {
    // Parse mixed types, mixed role
    xDataSeq = rxDataProvider->createDataSequenceByValueArray("categories", "{42;\"hello\";0;\"world\"}");
    xSequence = xDataSeq->getData();
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("42")),    xSequence[0]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("hello")), xSequence[1]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("0")),     xSequence[2]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("world")), xSequence[3]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("Row 1 42")), xSequence[0]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("Row 2 hello")), xSequence[1]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("Row 3 0")), xSequence[2]);
    CPPUNIT_ASSERT_EQUAL(uno::Any(OUString("Row 4 world")), xSequence[3]);
}

void Chart2ImportTest::testTdf90510()
@@ -1946,6 +1948,34 @@ void Chart2ImportTest::testSourceNumberFormatComplexCategoriesXLS()
    CPPUNIT_ASSERT(nNumberFormat != 0);
}

void Chart2ImportTest::testMultilevelCategoryAxis()
{
    load("/chart2/qa/extras/data/docx/", "testMultilevelCategoryAxis.docx");
    uno::Reference< chart2::XChartDocument > xChartDoc(getChartDocFromWriter(0), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xChartDoc.is());

    // Test the internal data.
    CPPUNIT_ASSERT(xChartDoc->hasInternalDataProvider());

    Reference<chart2::XInternalDataProvider> xInternalProvider(xChartDoc->getDataProvider(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xInternalProvider.is());

    Reference<chart::XComplexDescriptionAccess> xDescAccess(xInternalProvider, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xDescAccess.is());

    // Get the complex category labels.
    Sequence<Sequence<OUString> > aCategories = xDescAccess->getComplexRowDescriptions();
    CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aCategories.getLength());
    CPPUNIT_ASSERT_EQUAL(OUString("2011"), aCategories[0][0]);
    CPPUNIT_ASSERT_EQUAL(OUString(""), aCategories[1][0]);
    CPPUNIT_ASSERT_EQUAL(OUString("2012"), aCategories[2][0]);
    CPPUNIT_ASSERT_EQUAL(OUString(""), aCategories[3][0]);
    CPPUNIT_ASSERT_EQUAL(OUString("Categoria 1"), aCategories[0][1]);
    CPPUNIT_ASSERT_EQUAL(OUString("Categoria 2"), aCategories[1][1]);
    CPPUNIT_ASSERT_EQUAL(OUString("Categoria 3"), aCategories[2][1]);
    CPPUNIT_ASSERT_EQUAL(OUString("Categoria 4"), aCategories[3][1]);
}

void Chart2ImportTest::testTdf123504()
{
    load("/chart2/qa/extras/data/ods/", "pie_chart_100_and_0.ods");
diff --git a/chart2/qa/extras/data/docx/testMultilevelCategoryAxis.docx b/chart2/qa/extras/data/docx/testMultilevelCategoryAxis.docx
new file mode 100644
index 0000000..75605de
--- /dev/null
+++ b/chart2/qa/extras/data/docx/testMultilevelCategoryAxis.docx
Binary files differ
diff --git a/chart2/source/tools/InternalData.cxx b/chart2/source/tools/InternalData.cxx
index b99578f..b7a5118 100644
--- a/chart2/source/tools/InternalData.cxx
+++ b/chart2/source/tools/InternalData.cxx
@@ -220,7 +220,16 @@ void InternalData::setComplexRowLabel( sal_Int32 nRowIndex, const vector< uno::A
        m_aRowLabels.resize(nRowIndex+1);
        enlargeData( 0, nRowIndex+1 );
    }
    m_aRowLabels[nRowIndex] = rComplexLabel;
    sal_Int32 nSize = static_cast<sal_Int32>( m_aRowLabels[nRowIndex].size() );
    if( nSize >= 1 )
    {
        m_aRowLabels[nRowIndex].resize(nSize+1);
        m_aRowLabels[nRowIndex][nSize] = rComplexLabel[0];
    }
    else
    {
        m_aRowLabels[nRowIndex] = rComplexLabel;
    }
}

vector< uno::Any > InternalData::getComplexColumnLabel( sal_Int32 nColumnIndex ) const
diff --git a/include/oox/drawingml/chart/datasourcemodel.hxx b/include/oox/drawingml/chart/datasourcemodel.hxx
index 097bfd1..e2d9336 100644
--- a/include/oox/drawingml/chart/datasourcemodel.hxx
+++ b/include/oox/drawingml/chart/datasourcemodel.hxx
@@ -40,6 +40,7 @@ struct DataSequenceModel
    OUString     maFormula;          /// Formula reference, e.g. into a spreadsheet.
    OUString     maFormatCode;       /// Number format for double values.
    sal_Int32           mnPointCount;       /// Number of points in this series source.
    sal_Int32           mnLevelCount;       /// Number of category levels.

    explicit            DataSequenceModel();
                        ~DataSequenceModel();
diff --git a/oox/source/drawingml/chart/chartconverter.cxx b/oox/source/drawingml/chart/chartconverter.cxx
index f86ea8a..538f995 100644
--- a/oox/source/drawingml/chart/chartconverter.cxx
+++ b/oox/source/drawingml/chart/chartconverter.cxx
@@ -56,12 +56,12 @@ static OUString lclGenerateApiString( const OUString& rString )
    return "\"" + aRetString + "\"";
}

static OUString lclGenerateApiArray(const std::vector<Any>& rRow)
static OUString lclGenerateApiArray(const std::vector<Any>& rRow, sal_Int32 nStart, sal_Int32 nCount)
{
    OSL_ENSURE( !rRow.empty(), "ChartConverter::lclGenerateApiArray - missing matrix values" );
    OUStringBuffer aBuffer;
    aBuffer.append( API_TOKEN_ARRAY_OPEN );
    for (auto aBeg = rRow.begin(), aIt = aBeg, aEnd = rRow.end(); aIt != aEnd; ++aIt)
    for (auto aBeg = rRow.begin() + nStart, aIt = aBeg, aEnd = aBeg + nCount; aIt != aEnd; ++aIt)
    {
        double fValue = 0.0;
        OUString aString;
@@ -124,21 +124,26 @@ Reference< XDataSequence > ChartConverter::createDataSequence(
    if( rxDataProvider.is() )
    {
        OUString aRangeRep;
        if( !rDataSeq.maData.empty() )
        if( !rDataSeq.maData.empty() ) try
        {
            // create a single-row array from constant source data
            std::vector<Any> aRow(rDataSeq.mnPointCount);
            // (multiple levels in the case of complex categories)
            std::vector<Any> aRow(rDataSeq.mnLevelCount * rDataSeq.mnPointCount);
            for (auto const& elem : rDataSeq.maData)
                aRow.at(elem.first) = elem.second;

            aRangeRep = lclGenerateApiArray(aRow);
        }
            for (sal_Int32 i = rDataSeq.mnLevelCount-1; i >= 0; i--)
            {
                aRangeRep = lclGenerateApiArray( aRow, i * rDataSeq.mnPointCount, rDataSeq.mnPointCount);

        if( !aRangeRep.isEmpty() ) try
        {
            // create the data sequence
            xDataSeq = rxDataProvider->createDataSequenceByValueArray(rRole, aRangeRep);
            return xDataSeq;
                if (!aRangeRep.isEmpty())
                {
                    // create or add a new level to the data sequence
                    xDataSeq = rxDataProvider->createDataSequenceByValueArray(rRole, aRangeRep);
                    if (i == 0)
                        return xDataSeq;
                }
            }
        }
        catch( Exception& )
        {
diff --git a/oox/source/drawingml/chart/datasourcecontext.cxx b/oox/source/drawingml/chart/datasourcecontext.cxx
index e50723b..7fa7b12 100644
--- a/oox/source/drawingml/chart/datasourcecontext.cxx
+++ b/oox/source/drawingml/chart/datasourcecontext.cxx
@@ -180,6 +180,7 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co
            switch( nElement )
            {
                case C_TOKEN( f ):
                case C_TOKEN( multiLvlStrCache ):
                    return this;
            }
        break;
@@ -206,6 +207,28 @@ ContextHandlerRef StringSequenceContext::onCreateContext( sal_Int32 nElement, co
            }
        break;

        case C_TOKEN( multiLvlStrCache ):
            switch (nElement)
            {
                case C_TOKEN( ptCount ):
                    mrModel.mnPointCount = rAttribs.getInteger(XML_val, -1);
                    mrModel.mnLevelCount--; // normalize level count
                    return nullptr;
                case C_TOKEN( lvl ):
                    mrModel.mnLevelCount++;
                    return this;
            }
            break;

        case C_TOKEN( lvl ):
            switch (nElement)
            {
                case C_TOKEN(pt):
                    mnPtIndex = rAttribs.getInteger(XML_idx, -1);
                    return this;
            }
            break;

        case C_TOKEN( pt ):
            switch( nElement )
            {
@@ -226,7 +249,7 @@ void StringSequenceContext::onCharacters( const OUString& rChars )
        break;
        case C_TOKEN( v ):
            if( mnPtIndex >= 0 )
                mrModel.maData[ mnPtIndex ] <<= rChars;
                mrModel.maData[ (mrModel.mnLevelCount-1) * mrModel.mnPointCount + mnPtIndex ] <<= rChars;
        break;
    }
}
diff --git a/oox/source/drawingml/chart/datasourcemodel.cxx b/oox/source/drawingml/chart/datasourcemodel.cxx
index c5c054e..ecb9afe 100644
--- a/oox/source/drawingml/chart/datasourcemodel.cxx
+++ b/oox/source/drawingml/chart/datasourcemodel.cxx
@@ -24,7 +24,8 @@ namespace drawingml {
namespace chart {

DataSequenceModel::DataSequenceModel() :
    mnPointCount( -1 )
    mnPointCount( -1 ),
    mnLevelCount( 1 )
{
}

diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx
index dd9d196..ec19bc40 100644
--- a/oox/source/export/chartexport.cxx
+++ b/oox/source/export/chartexport.cxx
@@ -515,8 +515,12 @@ Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUSt
                            aFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
                            for (auto const& elemLabel : aAnyCategories)
                            {
                                aFinalSplitSource[nLevelCount - i - 1][nElemLabel] = elemLabel[i].get<OUString>();
                                nElemLabel++;
                                // make sure elemLabel[i] exists!
                                if (elemLabel.getLength() > i)
                                {
                                    aFinalSplitSource[nLevelCount - i - 1][nElemLabel] = elemLabel[i].get<OUString>();
                                    nElemLabel++;
                                }
                            }
                        }
                        return aFinalSplitSource;