Avoid potential negative array index access to cached text.
[LibreOffice.git] / xmloff / source / chart / SchXMLTableContext.cxx
blobac0e9aeed5245a0381a36fd3d0b9454135bbb1b7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sax/tools/converter.hxx>
22 #include "SchXMLTableContext.hxx"
23 #include "SchXMLParagraphContext.hxx"
24 #include "SchXMLTextListContext.hxx"
25 #include <SchXMLImport.hxx>
26 #include "SchXMLTools.hxx"
27 #include "transporttypes.hxx"
28 #include <XMLStringBufferImportContext.hxx>
29 #include <o3tl/safeint.hxx>
30 #include <o3tl/string_view.hxx>
31 #include <sal/log.hxx>
32 #include <xmloff/xmlnamespace.hxx>
33 #include <xmloff/xmltoken.hxx>
34 #include <xmloff/namespacemap.hxx>
35 #include <comphelper/sequence.hxx>
36 #include <comphelper/string.hxx>
37 #include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
38 #include <com/sun/star/chart2/XDataSeriesContainer.hpp>
39 #include <com/sun/star/chart2/XChartDocument.hpp>
40 #include <com/sun/star/chart2/XChartTypeContainer.hpp>
41 #include <com/sun/star/chart2/XInternalDataProvider.hpp>
42 #include <com/sun/star/beans/XPropertySet.hpp>
44 #include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
46 #include <limits>
47 #include <vector>
48 #include <algorithm>
49 #include <iterator>
50 #include <string_view>
52 using namespace com::sun::star;
53 using namespace ::xmloff::token;
54 using ::com::sun::star::uno::Sequence;
55 using ::com::sun::star::uno::Reference;
57 namespace
60 constexpr OUString aCategoriesRange = u"categories"_ustr;
62 typedef ::std::multimap< OUString, OUString >
63 lcl_tOriginalRangeToInternalRangeMap;
65 struct lcl_ApplyCellToData
67 explicit lcl_ApplyCellToData( Sequence< double > & rOutData ) :
68 m_rData( rOutData ),
69 m_nIndex( 0 ),
70 m_nSize( rOutData.getLength())
74 void operator() ( const SchXMLCell & rCell )
76 if( m_nIndex < m_nSize )
78 auto pData = m_rData.getArray();
79 if( rCell.eType == SCH_CELL_TYPE_FLOAT )
80 pData[m_nIndex] = rCell.fValue;
81 else
82 pData[m_nIndex] = std::numeric_limits<double>::quiet_NaN();
84 ++m_nIndex;
87 sal_Int32 getCurrentIndex() const
89 return m_nIndex;
92 private:
93 Sequence< double > & m_rData;
94 sal_Int32 m_nIndex;
95 sal_Int32 m_nSize;
98 void lcl_fillRangeMapping(
99 const SchXMLTable & rTable,
100 lcl_tOriginalRangeToInternalRangeMap & rOutRangeMap,
101 chart::ChartDataRowSource eDataRowSource )
103 sal_Int32 nRowOffset = ( rTable.bHasHeaderRow ? 1 : 0 );
104 sal_Int32 nColOffset = ( rTable.bHasHeaderColumn ? 1 : 0 );
106 const OUString lcl_aCategoriesRange(aCategoriesRange);
107 static constexpr OUString lcl_aLabelPrefix(u"label "_ustr);
109 // Fill range mapping
110 const size_t nTableRowCount( rTable.aData.size());
111 for( size_t nRow = 0; nRow < nTableRowCount; ++nRow )
113 const ::std::vector< SchXMLCell > & rRow( rTable.aData[nRow] );
114 const size_t nTableColCount( rRow.size());
115 for( size_t nCol = 0; nCol < nTableColCount; ++nCol )
117 const OUString aRangeId( rRow[nCol].aRangeId );
118 if( !aRangeId.isEmpty())
120 if( eDataRowSource == chart::ChartDataRowSource_COLUMNS )
122 if( nCol == 0 && rTable.bHasHeaderColumn )
124 SAL_WARN_IF( static_cast< sal_Int32 >( nRow ) != nRowOffset, "xmloff.chart", "nRow != nRowOffset" );
125 rOutRangeMap.emplace(aRangeId, lcl_aCategoriesRange);
127 else
129 OUString aColNumStr = OUString::number( nCol - nColOffset);
130 if( nRow == 0 && rTable.bHasHeaderRow )
131 rOutRangeMap.emplace( aRangeId, lcl_aLabelPrefix + aColNumStr );
132 else
133 rOutRangeMap.emplace( aRangeId, aColNumStr );
136 else // eDataRowSource == chart::ChartDataRowSource_ROWS
138 if( nRow == 0 && rTable.bHasHeaderRow )
140 SAL_WARN_IF( static_cast< sal_Int32 >( nCol ) != nColOffset, "xmloff.chart", "nCol != nColOffset" );
141 rOutRangeMap.emplace( aRangeId, lcl_aCategoriesRange );
143 else
145 OUString aRowNumStr = OUString::number( nRow - nRowOffset);
146 if( nCol == 0 && rTable.bHasHeaderColumn )
147 rOutRangeMap.emplace( aRangeId, lcl_aLabelPrefix + aRowNumStr );
148 else
149 rOutRangeMap.emplace( aRangeId, aRowNumStr );
157 Reference< chart2::data::XDataSequence >
158 lcl_reassignDataSequence(
159 const Reference< chart2::data::XDataSequence > & xSequence,
160 const Reference< chart2::data::XDataProvider > & xDataProvider,
161 lcl_tOriginalRangeToInternalRangeMap & rRangeMap,
162 const OUString & rRange )
164 Reference< chart2::data::XDataSequence > xResult( xSequence );
165 lcl_tOriginalRangeToInternalRangeMap::iterator aIt( rRangeMap.find( rRange ));
166 if( aIt != rRangeMap.end())
168 // set sequence with correct data
169 xResult.set( xDataProvider->createDataSequenceByRangeRepresentation( aIt->second ));
170 // remove translation, because it was used
171 rRangeMap.erase( aIt );
174 return xResult;
177 bool lcl_mapContainsRange(
178 lcl_tOriginalRangeToInternalRangeMap & rRangeMap,
179 const OUString & rRange )
181 lcl_tOriginalRangeToInternalRangeMap::iterator aIt( rRangeMap.find( rRange ));
182 return ( aIt != rRangeMap.end());
185 bool lcl_tableOfRangeMatches(
186 std::u16string_view rRange,
187 std::u16string_view rTableName )
189 // both strings are non-empty and the table name is part of the range
190 return ( !rRange.empty() &&
191 !rTableName.empty() &&
192 (rRange.find( rTableName ) != std::u16string_view::npos ));
195 } // anonymous namespace
197 // class SchXMLTableContext
198 SchXMLTableContext::SchXMLTableContext( SvXMLImport& rImport,
199 SchXMLTable& aTable ) :
200 SvXMLImportContext( rImport ),
201 mrTable( aTable ),
202 mbHasRowPermutation( false ),
203 mbHasColumnPermutation( false )
205 mrTable.nColumnIndex = -1;
206 mrTable.nMaxColumnIndex = -1;
207 mrTable.nRowIndex = -1;
208 mrTable.aData.clear();
211 SchXMLTableContext::~SchXMLTableContext()
215 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTableContext::createFastChildContext(
216 sal_Int32 nElement,
217 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
219 SvXMLImportContext* pContext = nullptr;
221 switch(nElement)
223 case XML_ELEMENT(TABLE, XML_TABLE_HEADER_COLUMNS):
224 mrTable.bHasHeaderColumn = true;
225 [[fallthrough]];
226 case XML_ELEMENT(TABLE, XML_TABLE_COLUMNS):
227 pContext = new SchXMLTableColumnsContext( GetImport(), mrTable );
228 break;
230 case XML_ELEMENT(TABLE, XML_TABLE_COLUMN):
231 pContext = new SchXMLTableColumnContext( GetImport(), mrTable );
232 break;
234 case XML_ELEMENT(TABLE, XML_TABLE_HEADER_ROWS):
235 mrTable.bHasHeaderRow = true;
236 [[fallthrough]];
237 case XML_ELEMENT(TABLE, XML_TABLE_ROWS):
238 pContext = new SchXMLTableRowsContext( GetImport(), mrTable );
239 break;
241 case XML_ELEMENT(TABLE, XML_TABLE_ROW):
242 pContext = new SchXMLTableRowContext( GetImport(), mrTable );
243 break;
244 default:
245 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
248 return pContext;
251 void SchXMLTableContext::startFastElement (sal_Int32 /*nElement*/,
252 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList)
254 // get table-name
256 for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
258 switch(aIter.getToken())
260 case XML_ELEMENT(TABLE, XML_NAME):
261 mrTable.aTableNameOfFile = aIter.toString();
262 break;
263 case XML_ELEMENT(TABLE, XML_PROTECTED):
264 if ( IsXMLToken( aIter, XML_TRUE ) )
266 mrTable.bProtected = true;
268 break;
269 default:
270 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
275 void SchXMLTableContext::endFastElement(sal_Int32 )
277 if( mbHasColumnPermutation )
279 SAL_WARN_IF( mbHasRowPermutation, "xmloff.chart", "mbHasColumnPermutation is true" );
280 const auto & aPermutation( maColumnPermutation );
281 SAL_WARN_IF( !aPermutation.hasElements(), "xmloff.chart", "aPermutation is NULL");
282 if( !aPermutation.hasElements())
283 return;
285 // permute the values of all rows according to aPermutation
286 for( auto& rRow : mrTable.aData )
288 bool bModified = false;
289 ::std::vector< SchXMLCell > aModifiedRow;
290 const size_t nPermSize = aPermutation.size();
291 SAL_WARN_IF( static_cast< sal_Int32 >( nPermSize ) - 1 != *(::std::max_element( aPermutation.begin(), aPermutation.end())), "xmloff.chart", "nPermSize - 1 != *(::std::max_element( aPermutation.begin(), aPermutation.end())");
292 const size_t nRowSize = rRow.size();
293 const size_t nDestSize = ::std::min( nPermSize, nRowSize );
294 for( size_t nDestinationIndex = 0; nDestinationIndex < nDestSize; ++nDestinationIndex )
296 const size_t nSourceIndex = static_cast< size_t >( aPermutation[ nDestinationIndex ] );
297 if( nSourceIndex != nDestinationIndex &&
298 nSourceIndex < nRowSize )
300 // copy original on first real permutation
301 if( !bModified )
303 SAL_WARN_IF( !aModifiedRow.empty(), "xmloff.chart", "aModifiedRow is NOT NULL");
304 aModifiedRow.insert( aModifiedRow.end(), rRow.begin(), rRow.end() );
305 SAL_WARN_IF( aModifiedRow.empty(), "xmloff.chart", "aModifiedRow is NULL");
307 SAL_WARN_IF( nDestinationIndex >= aModifiedRow.size(), "xmloff.chart", "nDestinationIndex >= aModifiedRow.size()");
308 aModifiedRow[ nDestinationIndex ] = rRow[ nSourceIndex ];
309 bModified = true;
312 // copy back
313 if( bModified )
314 ::std::copy( aModifiedRow.begin(), aModifiedRow.end(), rRow.begin());
317 else if( mbHasRowPermutation )
319 const auto & aPermutation( maRowPermutation );
320 SAL_WARN_IF( !aPermutation.hasElements(), "xmloff.chart", "aPermutation is NULL");
321 if( !aPermutation.hasElements())
322 return;
324 bool bModified = false;
325 const size_t nPermSize = aPermutation.size();
326 SAL_WARN_IF( static_cast< sal_Int32 >( nPermSize ) - 1 != *(::std::max_element( aPermutation.begin(), aPermutation.end())), "xmloff.chart", "nPermSize - 1 != *(::std::max_element( aPermutation.begin(), aPermutation.end())");
327 const size_t nTableRowCount = mrTable.aData.size();
328 const size_t nDestSize = ::std::min( nPermSize, nTableRowCount );
329 ::std::vector< ::std::vector< SchXMLCell > > aDestination;
330 for( size_t nDestinationIndex = 0; nDestinationIndex < nDestSize; ++nDestinationIndex )
332 const size_t nSourceIndex = static_cast< size_t >( aPermutation[ nDestinationIndex ] );
333 if( nSourceIndex != nDestinationIndex &&
334 nSourceIndex < nTableRowCount )
336 // copy original on first real permutation
337 if( !bModified )
339 SAL_WARN_IF( !aDestination.empty(), "xmloff.chart", "aDestination is NOT NULL");
340 aDestination.insert( aDestination.end(), mrTable.aData.begin(), mrTable.aData.end());
341 SAL_WARN_IF( aDestination.empty(), "xmloff.chart", "aDestination is NULL");
343 SAL_WARN_IF( nDestinationIndex >= aDestination.size(), "xmloff.chart", "nDestinationIndex >= aDestination.size()");
344 aDestination[ nDestinationIndex ] = mrTable.aData[ nSourceIndex ];
345 bModified = true;
348 if( bModified )
350 // copy back
351 ::std::copy( aDestination.begin(), aDestination.end(), mrTable.aData.begin());
356 void SchXMLTableContext::setRowPermutation( const uno::Sequence< sal_Int32 > & rPermutation )
358 maRowPermutation = rPermutation;
359 mbHasRowPermutation = rPermutation.hasElements();
361 if( mbHasRowPermutation && mbHasColumnPermutation )
363 mbHasColumnPermutation = false;
364 maColumnPermutation.realloc( 0 );
368 void SchXMLTableContext::setColumnPermutation( const uno::Sequence< sal_Int32 > & rPermutation )
370 maColumnPermutation = rPermutation;
371 mbHasColumnPermutation = rPermutation.hasElements();
373 if( mbHasColumnPermutation && mbHasRowPermutation )
375 mbHasRowPermutation = false;
376 maRowPermutation.realloc( 0 );
380 // classes for columns
381 // class SchXMLTableColumnsContext
382 SchXMLTableColumnsContext::SchXMLTableColumnsContext(
383 SvXMLImport& rImport,
384 SchXMLTable& aTable ) :
385 SvXMLImportContext( rImport ),
386 mrTable( aTable )
390 SchXMLTableColumnsContext::~SchXMLTableColumnsContext()
394 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTableColumnsContext::createFastChildContext(
395 sal_Int32 nElement,
396 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
398 SvXMLImportContext* pContext = nullptr;
400 if( nElement == XML_ELEMENT(TABLE, XML_TABLE_COLUMN) )
401 pContext = new SchXMLTableColumnContext( GetImport(), mrTable );
402 else
403 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
405 return pContext;
408 // class SchXMLTableColumnContext
409 SchXMLTableColumnContext::SchXMLTableColumnContext(
410 SvXMLImport& rImport,
411 SchXMLTable& aTable ) :
412 SvXMLImportContext( rImport ),
413 mrTable( aTable )
417 void SchXMLTableColumnContext::startFastElement (sal_Int32 /*nElement*/,
418 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList)
420 // get number-columns-repeated attribute
421 sal_Int32 nRepeated = 1;
422 bool bHidden = false;
424 for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
426 switch(aIter.getToken())
428 case XML_ELEMENT(TABLE, XML_NUMBER_COLUMNS_REPEATED):
430 if( !aIter.isEmpty())
431 nRepeated = aIter.toInt32();
432 break;
434 case XML_ELEMENT(TABLE, XML_VISIBILITY):
436 OUString aVisibility = aIter.toString();
437 bHidden = aVisibility == GetXMLToken( XML_COLLAPSE );
438 break;
440 default:
441 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
445 sal_Int32 nOldCount = mrTable.nNumberOfColsEstimate;
446 sal_Int32 nNewCount = nOldCount + nRepeated;
447 mrTable.nNumberOfColsEstimate = nNewCount;
449 if( bHidden )
451 //i91578 display of hidden values (copy paste scenario; use hidden flag during migration to locale table upon paste )
452 sal_Int32 nColOffset = ( mrTable.bHasHeaderColumn ? 1 : 0 );
453 for( sal_Int32 nN = nOldCount; nN<nNewCount; nN++ )
455 sal_Int32 nHiddenColumnIndex = nN-nColOffset;
456 if( nHiddenColumnIndex>=0 )
457 mrTable.aHiddenColumns.push_back(nHiddenColumnIndex);
462 SchXMLTableColumnContext::~SchXMLTableColumnContext()
466 // classes for rows
467 // class SchXMLTableRowsContext
468 SchXMLTableRowsContext::SchXMLTableRowsContext(
469 SvXMLImport& rImport,
470 SchXMLTable& aTable ) :
471 SvXMLImportContext( rImport ),
472 mrTable( aTable )
476 SchXMLTableRowsContext::~SchXMLTableRowsContext()
480 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTableRowsContext::createFastChildContext(
481 sal_Int32 nElement,
482 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
484 SvXMLImportContext* pContext = nullptr;
486 if( nElement == XML_ELEMENT(TABLE, XML_TABLE_ROW) )
487 pContext = new SchXMLTableRowContext( GetImport(), mrTable );
488 else
489 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
491 return pContext;
494 // class SchXMLTableRowContext
495 SchXMLTableRowContext::SchXMLTableRowContext(
496 SvXMLImport& rImport,
497 SchXMLTable& aTable ) :
498 SvXMLImportContext( rImport ),
499 mrTable( aTable )
501 mrTable.nColumnIndex = -1;
502 mrTable.nRowIndex++;
504 std::vector< SchXMLCell > aNewRow;
505 aNewRow.reserve( mrTable.nNumberOfColsEstimate );
506 while( mrTable.aData.size() <= o3tl::make_unsigned(mrTable.nRowIndex) )
507 mrTable.aData.push_back( aNewRow );
510 SchXMLTableRowContext::~SchXMLTableRowContext()
514 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTableRowContext::createFastChildContext(
515 sal_Int32 nElement,
516 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
518 SvXMLImportContext* pContext = nullptr;
520 // <table:table-cell> element
521 if( nElement == XML_ELEMENT(TABLE, XML_TABLE_CELL) )
523 pContext = new SchXMLTableCellContext( GetImport(), mrTable );
525 else
527 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
528 assert(false);
531 return pContext;
534 namespace {
536 class SchXMLRangeSomewhereContext : public SvXMLImportContext
538 //#i113950# previously the range was exported to attribute text:id,
539 //but that attribute does not allow arbitrary strings anymore
540 //so we need to find an alternative to save that range info for copy/paste scenario ...
541 //-> use description at an empty group element for now
543 private:
544 OUString& mrRangeString;
545 OUStringBuffer maRangeStringBuffer;
547 public:
548 SchXMLRangeSomewhereContext( SvXMLImport& rImport,
549 OUString& rRangeString );
551 virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
552 sal_Int32 nElement,
553 const css::uno::Reference< css::xml::sax::XFastAttributeList >& AttrList ) override;
554 virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
559 // classes for cells and their content
560 // class SchXMLTableCellContext
561 SchXMLTableCellContext::SchXMLTableCellContext(
562 SvXMLImport& rImport,
563 SchXMLTable& aTable)
564 : SvXMLImportContext(rImport)
565 , mrTable(aTable)
566 , mbReadText(false)
570 SchXMLTableCellContext::~SchXMLTableCellContext()
574 void SchXMLTableCellContext::startFastElement (sal_Int32 /*nElement*/,
575 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList)
577 OUString aCellContent;
578 SchXMLCellType eValueType = SCH_CELL_TYPE_UNKNOWN;
580 for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
582 switch(aIter.getToken())
584 case XML_ELEMENT(OFFICE, XML_VALUE_TYPE):
585 if( IsXMLToken( aIter, XML_FLOAT ) )
586 eValueType = SCH_CELL_TYPE_FLOAT;
587 else if( IsXMLToken( aIter, XML_STRING ) )
588 eValueType = SCH_CELL_TYPE_STRING;
589 break;
591 case XML_ELEMENT(OFFICE, XML_VALUE):
592 aCellContent = aIter.toString();
593 break;
595 default:
596 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
600 mbReadText = true;
601 SchXMLCell aCell;
602 aCell.eType = eValueType;
604 if( eValueType == SCH_CELL_TYPE_FLOAT )
606 double fData;
607 // the result may be false if a NaN is read, but that's ok
608 ::sax::Converter::convertDouble( fData, aCellContent );
610 aCell.fValue = fData;
611 // don't read text from following <text:p> or <text:list> element
612 mbReadText = false;
615 mrTable.aData[ mrTable.nRowIndex ].push_back( aCell );
616 mrTable.nColumnIndex++;
617 if( mrTable.nMaxColumnIndex < mrTable.nColumnIndex )
618 mrTable.nMaxColumnIndex = mrTable.nColumnIndex;
621 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLTableCellContext::createFastChildContext(
622 sal_Int32 nElement,
623 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
625 SvXMLImportContext* pContext = nullptr;
627 // <text:list> element
628 if( nElement == XML_ELEMENT(TEXT, XML_LIST ) && mbReadText )
630 SchXMLCell& rCell = mrTable.aData[ mrTable.nRowIndex ][ mrTable.nColumnIndex ];
631 rCell.aComplexString = Sequence< OUString >();
632 rCell.eType = SCH_CELL_TYPE_COMPLEX_STRING;
633 pContext = new SchXMLTextListContext( GetImport(), rCell.aComplexString );
634 mbReadText = false;//don't apply text from <text:p>
636 // <text:p> element - read text (and range from text:id old version)
637 else if( nElement == XML_ELEMENT(TEXT, XML_P) ||
638 nElement == XML_ELEMENT(LO_EXT, XML_P) )
640 pContext = new SchXMLParagraphContext( GetImport(), maCellContent, &maRangeId );
642 // <draw:g> element - read range
643 else if( nElement == XML_ELEMENT(DRAW, XML_G) )
645 //#i113950# previously the range was exported to attribute text:id, but that attribute does not allow arbitrary strings anymore
646 //so we need to find an alternative to save that range info for copy/paste scenario ... -> use description at an empty group element for now
647 pContext = new SchXMLRangeSomewhereContext( GetImport(), maRangeId );
649 else
650 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
652 return pContext;
655 void SchXMLTableCellContext::endFastElement(sal_Int32 )
657 if( mbReadText && !maCellContent.isEmpty() ) //apply text from <text:p> element
658 mrTable.aData[ mrTable.nRowIndex ][ mrTable.nColumnIndex ].aString = maCellContent;
659 if( !maRangeId.isEmpty())
660 mrTable.aData[ mrTable.nRowIndex ][ mrTable.nColumnIndex ].aRangeId = maRangeId;
663 static void lcl_ApplyCellToComplexLabel( const SchXMLCell& rCell, Sequence< uno::Any >& rComplexLabel )
665 if( rCell.eType == SCH_CELL_TYPE_STRING )
667 rComplexLabel = { uno::Any(rCell.aString) };
669 else if( rCell.aComplexString.hasElements() && rCell.eType == SCH_CELL_TYPE_COMPLEX_STRING )
671 sal_Int32 nCount = rCell.aComplexString.getLength();
672 rComplexLabel.realloc( nCount );
673 auto pComplexLabel = rComplexLabel.getArray();
674 for( sal_Int32 nN=0; nN<nCount; nN++)
675 pComplexLabel[nN] <<= (rCell.aComplexString)[nN];
677 else if( rCell.eType == SCH_CELL_TYPE_FLOAT )
679 rComplexLabel = { uno::Any(rCell.fValue) };
683 void SchXMLTableHelper::applyTableToInternalDataProvider(
684 const SchXMLTable& rTable,
685 const uno::Reference< chart2::XChartDocument >& xChartDoc )
687 // apply all data read from the local table to the internal data provider
688 if( !xChartDoc.is() || !xChartDoc->hasInternalDataProvider() )
689 return;
690 Reference< chart2::data::XDataProvider > xDataProv( xChartDoc->getDataProvider() );
691 if( !xDataProv.is() )
692 return;
694 //prepare the read local table data
695 sal_Int32 nNumRows( static_cast< sal_Int32 >( rTable.aData.size()));
696 sal_Int32 nRowOffset = 0;
697 if( rTable.bHasHeaderRow )
699 --nNumRows;
700 nRowOffset = 1;
702 sal_Int32 nNumColumns( rTable.nMaxColumnIndex + 1 );
703 sal_Int32 nColOffset = 0;
704 if( rTable.bHasHeaderColumn )
706 --nNumColumns;
707 nColOffset = 1;
710 Sequence< Sequence< double > > aDataInRows( nNumRows );
711 auto aDataInRowsRange = asNonConstRange(aDataInRows);
712 Sequence< Sequence< uno::Any > > aComplexRowDescriptions( nNumRows );
713 auto aComplexRowDescriptionsRange = asNonConstRange(aComplexRowDescriptions);
714 Sequence< Sequence< uno::Any > > aComplexColumnDescriptions( nNumColumns );
715 auto aComplexColumnDescriptionsRange = asNonConstRange(aComplexColumnDescriptions);
716 for( sal_Int32 i=0; i<nNumRows; ++i )
717 aDataInRowsRange[i].realloc( nNumColumns );
719 if( !rTable.aData.empty() )
721 //apply column labels
722 if( rTable.bHasHeaderRow )
724 const ::std::vector< SchXMLCell >& rFirstRow = rTable.aData.front();
725 const sal_Int32 nColumnLabelsSize = aComplexColumnDescriptions.getLength();
726 const sal_Int32 nMax = ::std::min< sal_Int32 >( nColumnLabelsSize, static_cast< sal_Int32 >( rFirstRow.size()) - nColOffset );
727 SAL_WARN_IF( nMax != nColumnLabelsSize, "xmloff.chart", "nMax != nColumnLabelsSize");
728 for( sal_Int32 i=0; i<nMax; ++i )
729 lcl_ApplyCellToComplexLabel( rFirstRow[i+nColOffset], aComplexColumnDescriptionsRange[i] );
732 std::vector< ::std::vector< SchXMLCell > >::const_iterator aRowIter( rTable.aData.begin() + nRowOffset );
733 std::vector< ::std::vector< SchXMLCell > >::const_iterator aEnd( rTable.aData.end() );
734 for( sal_Int32 nRow = 0; aRowIter != aEnd && nRow < nNumRows; ++aRowIter, ++nRow )
736 const ::std::vector< SchXMLCell >& rRow = *aRowIter;
737 if( !rRow.empty() )
739 // row label
740 if( rTable.bHasHeaderColumn )
741 lcl_ApplyCellToComplexLabel( rRow.front(), aComplexRowDescriptionsRange[nRow] );
743 // values
744 Sequence< double >& rTargetRow = aDataInRowsRange[nRow];
745 auto pTargetRow = rTargetRow.getArray();
746 lcl_ApplyCellToData aApplyCellToData = ::std::for_each( rRow.begin() + nColOffset, rRow.end(), lcl_ApplyCellToData( rTargetRow ) );
747 for( sal_Int32 nCurrentIndex = aApplyCellToData.getCurrentIndex(); nCurrentIndex<nNumColumns; nCurrentIndex++ )
748 pTargetRow[nCurrentIndex] = std::numeric_limits<double>::quiet_NaN();//#i110615#
753 //apply the collected data to the chart
754 Reference< chart2::XAnyDescriptionAccess > xDataAccess( xDataProv, uno::UNO_QUERY );
755 if( !xDataAccess.is() )
756 return;
758 xDataAccess->setData( aDataInRows );
759 if( rTable.bHasHeaderColumn )
760 xDataAccess->setAnyRowDescriptions( aComplexRowDescriptions );
761 if( rTable.bHasHeaderRow )
762 xDataAccess->setAnyColumnDescriptions( aComplexColumnDescriptions );
764 if ( rTable.bProtected )
768 Reference< beans::XPropertySet > xProps( xChartDoc, uno::UNO_QUERY_THROW );
769 xProps->setPropertyValue( "DisableDataTableDialog", uno::Any( true ) );
770 xProps->setPropertyValue( "DisableComplexChartTypes", uno::Any( true ) );
772 catch ( uno::Exception& )
778 void SchXMLTableHelper::switchRangesFromOuterToInternalIfNecessary(
779 const SchXMLTable& rTable,
780 const tSchXMLLSequencesPerIndex & rLSequencesPerIndex,
781 const uno::Reference< chart2::XChartDocument >& xChartDoc,
782 chart::ChartDataRowSource eDataRowSource )
784 if( ! (xChartDoc.is() && xChartDoc->hasInternalDataProvider()))
785 return;
787 // If the range-strings are valid (starting with "local-table") they should
788 // be interpreted like given, otherwise (when the ranges refer to Calc- or
789 // Writer-ranges, but the container is not available like when pasting a
790 // chart from Calc to Impress) the range is ignored, and every object gets
791 // one table column in the order of appearance, which is: 1. categories,
792 // 2. data series: 2.a) domains, 2.b) values (main-role, usually y-values)
794 Reference< chart2::data::XDataProvider > xDataProv( xChartDoc->getDataProvider());
796 // create a mapping from original ranges to new ranges
797 lcl_tOriginalRangeToInternalRangeMap aRangeMap;
799 lcl_fillRangeMapping( rTable, aRangeMap, eDataRowSource );
801 const OUString lcl_aCategoriesRange(aCategoriesRange);
803 bool bCategoriesApplied = false;
804 // translate ranges (using the map created before)
805 for( const auto& rLSeq : rLSequencesPerIndex )
807 if( rLSeq.second.is())
809 // values/error bars/categories
810 if( rLSeq.first.second == SCH_XML_PART_VALUES ||
811 rLSeq.first.second == SCH_XML_PART_ERROR_BARS )
813 Reference< chart2::data::XDataSequence > xSeq( rLSeq.second->getValues());
815 OUString aRange;
816 if( xSeq.is() &&
817 SchXMLTools::getXMLRangePropertyFromDataSequence( xSeq, aRange, /* bClearProp = */ true ) &&
818 lcl_mapContainsRange( aRangeMap, aRange ))
820 Reference< chart2::data::XDataSequence > xNewSeq(
821 lcl_reassignDataSequence( xSeq, xDataProv, aRangeMap, aRange ));
822 if( xNewSeq != xSeq )
824 SchXMLTools::copyProperties( Reference< beans::XPropertySet >( xSeq, uno::UNO_QUERY ),
825 Reference< beans::XPropertySet >( xNewSeq, uno::UNO_QUERY ));
826 rLSeq.second->setValues( xNewSeq );
829 else
831 if( lcl_tableOfRangeMatches( aRange, rTable.aTableNameOfFile ))
833 if( rLSeq.first.first == SCH_XML_CATEGORIES_INDEX )
834 bCategoriesApplied = true;
836 else
838 if( rLSeq.first.first == SCH_XML_CATEGORIES_INDEX )
840 Reference< beans::XPropertySet > xOldSequenceProp( rLSeq.second->getValues(), uno::UNO_QUERY );
841 Reference< chart2::data::XDataSequence > xNewSequence(
842 xDataProv->createDataSequenceByRangeRepresentation("categories"));
843 SchXMLTools::copyProperties(
844 xOldSequenceProp, Reference< beans::XPropertySet >( xNewSequence, uno::UNO_QUERY ));
845 rLSeq.second->setValues( xNewSequence );
846 bCategoriesApplied = true;
848 else
850 Reference< beans::XPropertySet > xOldSequenceProp( rLSeq.second->getValues(), uno::UNO_QUERY );
851 OUString aRep( OUString::number( rLSeq.first.first ));
852 Reference< chart2::data::XDataSequence > xNewSequence(
853 xDataProv->createDataSequenceByRangeRepresentation( aRep ));
854 SchXMLTools::copyProperties(
855 xOldSequenceProp, Reference< beans::XPropertySet >( xNewSequence, uno::UNO_QUERY ));
856 rLSeq.second->setValues( xNewSequence );
861 else // labels
863 SAL_WARN_IF( rLSeq.first.second != SCH_XML_PART_LABEL, "xmloff.chart", "rLSeq.first.second != SCH_XML_PART_LABEL" );
864 // labels
865 Reference< chart2::data::XDataSequence > xSeq( rLSeq.second->getLabel());
866 OUString aRange;
867 if( xSeq.is() &&
868 SchXMLTools::getXMLRangePropertyFromDataSequence( xSeq, aRange, /* bClearProp = */ true ) &&
869 lcl_mapContainsRange( aRangeMap, aRange ))
871 Reference< chart2::data::XDataSequence > xNewSeq(
872 lcl_reassignDataSequence( xSeq, xDataProv, aRangeMap, aRange ));
873 if( xNewSeq != xSeq )
875 SchXMLTools::copyProperties( Reference< beans::XPropertySet >( xSeq, uno::UNO_QUERY ),
876 Reference< beans::XPropertySet >( xNewSeq, uno::UNO_QUERY ));
877 rLSeq.second->setLabel( xNewSeq );
880 else if( ! lcl_tableOfRangeMatches( aRange, rTable.aTableNameOfFile ))
882 OUString aRep = "label " + OUString::number( rLSeq.first.first );
884 Reference< chart2::data::XDataSequence > xNewSeq(
885 xDataProv->createDataSequenceByRangeRepresentation( aRep ));
886 SchXMLTools::copyProperties( Reference< beans::XPropertySet >( xSeq, uno::UNO_QUERY ),
887 Reference< beans::XPropertySet >( xNewSeq, uno::UNO_QUERY ));
888 rLSeq.second->setLabel( xNewSeq );
894 // there exist files with own data without a categories element but with row
895 // descriptions. The row descriptions were used as categories even without
896 // the categories element
897 if( ! bCategoriesApplied )
899 SchXMLTools::CreateCategories(
900 xDataProv, xChartDoc, "categories",
901 0 /* nCooSysIndex */, 0 /* nDimension */ );
904 //i91578 display of hidden values (copy paste scenario; use hidden flag during migration to locale table upon paste )
905 //remove series that consist only of hidden columns
906 Reference< chart2::XInternalDataProvider > xInternalDataProvider( xDataProv, uno::UNO_QUERY );
907 if( !xInternalDataProvider.is() || rTable.aHiddenColumns.empty() )
908 return;
912 Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xChartDoc->getFirstDiagram(), uno::UNO_QUERY_THROW );
913 const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems() );
914 for( const auto& rCooSys : aCooSysSeq )
916 Reference< chart2::XChartTypeContainer > xCooSysContainer( rCooSys, uno::UNO_QUERY_THROW );
917 const Sequence< Reference< chart2::XChartType > > aChartTypeSeq( xCooSysContainer->getChartTypes());
918 for( const auto& rChartType : aChartTypeSeq )
920 Reference< chart2::XDataSeriesContainer > xSeriesContainer( rChartType, uno::UNO_QUERY );
921 if(!xSeriesContainer.is())
922 continue;
923 const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xSeriesContainer->getDataSeries() );
924 std::vector< Reference< chart2::XDataSeries > > aRemainingSeries;
926 for( const auto& rSeries : aSeriesSeq )
928 Reference< chart2::data::XDataSource > xDataSource( rSeries, uno::UNO_QUERY );
929 if( xDataSource.is() )
931 bool bHasUnhiddenColumns = false;
932 OUString aRange;
933 const uno::Sequence< Reference< chart2::data::XLabeledDataSequence > > aSequences( xDataSource->getDataSequences() );
934 for( const auto& xLabeledSequence : aSequences )
936 if(!xLabeledSequence.is())
937 continue;
938 Reference< chart2::data::XDataSequence > xValues( xLabeledSequence->getValues() );
939 if( xValues.is() )
941 aRange = xValues->getSourceRangeRepresentation();
942 if( ::std::find( rTable.aHiddenColumns.begin(), rTable.aHiddenColumns.end(), aRange.toInt32() ) == rTable.aHiddenColumns.end() )
943 bHasUnhiddenColumns = true;
945 if( !bHasUnhiddenColumns )
947 Reference< chart2::data::XDataSequence > xLabel( xLabeledSequence->getLabel() );
948 if( xLabel.is() )
950 aRange = xLabel->getSourceRangeRepresentation();
951 const sal_Int32 nId = o3tl::toInt32(o3tl::getToken(aRange, 1, ' '));
952 if( ::std::find( rTable.aHiddenColumns.begin(), rTable.aHiddenColumns.end(), nId ) == rTable.aHiddenColumns.end() )
953 bHasUnhiddenColumns = true;
957 if( bHasUnhiddenColumns )
958 aRemainingSeries.push_back( rSeries );
962 if( aRemainingSeries.size() != o3tl::make_unsigned(aSeriesSeq.getLength()) )
964 //remove the series that have only hidden data
965 xSeriesContainer->setDataSeries( comphelper::containerToSequence(aRemainingSeries) );
967 //remove unused sequences
968 Reference< chart2::data::XDataSource > xDataSource( xChartDoc, uno::UNO_QUERY );
969 if( xDataSource.is() )
971 //first detect which columns are really used
972 std::map< sal_Int32, bool > aUsageMap;
973 OUString aRange;
974 const Sequence< Reference< chart2::data::XLabeledDataSequence > > aUsedSequences( xDataSource->getDataSequences() );
975 for( const auto& xLabeledSequence : aUsedSequences )
977 if(!xLabeledSequence.is())
978 continue;
979 Reference< chart2::data::XDataSequence > xValues( xLabeledSequence->getValues() );
980 if( xValues.is() )
982 aRange = xValues->getSourceRangeRepresentation();
983 sal_Int32 nIndex = aRange.toInt32();
984 if( nIndex!=0 || aRange != lcl_aCategoriesRange )
985 aUsageMap[nIndex] = true;
987 Reference< chart2::data::XDataSequence > xLabel( xLabeledSequence->getLabel() );
988 if( xLabel.is() )
990 aRange = xLabel->getSourceRangeRepresentation();
991 std::u16string_view aSecondToken = o3tl::getToken(aRange, 1, ' ');
992 if( !aSecondToken.empty() )
993 aUsageMap[o3tl::toInt32(aSecondToken)] = true;
997 ::std::vector< sal_Int32 > aSequenceIndexesToDelete;
998 std::copy_if(rTable.aHiddenColumns.begin(), rTable.aHiddenColumns.end(),
999 std::back_inserter(aSequenceIndexesToDelete),
1000 [&aUsageMap](sal_Int32 nSequenceIndex) { return aUsageMap.find(nSequenceIndex) == aUsageMap.end(); });
1002 // delete unnecessary sequences of the internal data
1003 // iterate using greatest index first, so that deletion does not
1004 // shift other sequences that will be deleted later
1005 ::std::sort( aSequenceIndexesToDelete.begin(), aSequenceIndexesToDelete.end());
1006 for( ::std::vector< sal_Int32 >::reverse_iterator aIt(
1007 aSequenceIndexesToDelete.rbegin()); aIt != aSequenceIndexesToDelete.rend(); ++aIt )
1009 if( *aIt != -1 )
1010 xInternalDataProvider->deleteSequence( *aIt );
1017 catch( const uno::Exception & )
1022 SchXMLRangeSomewhereContext::SchXMLRangeSomewhereContext( SvXMLImport& rImport,
1023 OUString& rRangeString ) :
1024 SvXMLImportContext( rImport ),
1025 mrRangeString( rRangeString )
1029 css::uno::Reference< css::xml::sax::XFastContextHandler > SchXMLRangeSomewhereContext::createFastChildContext(
1030 sal_Int32 nElement,
1031 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
1033 if( nElement == XML_ELEMENT(SVG, XML_DESC)
1034 || nElement == XML_ELEMENT(SVG_COMPAT, XML_DESC) )
1036 return new XMLStringBufferImportContext( GetImport(), maRangeStringBuffer );
1038 else
1039 XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
1040 return nullptr;
1043 void SchXMLRangeSomewhereContext::endFastElement(sal_Int32 )
1045 mrRangeString = maRangeStringBuffer.makeStringAndClear();
1048 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */