1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <calc/CTable.hxx>
21 #include <com/sun/star/sdbc/ColumnValue.hpp>
22 #include <com/sun/star/sdbc/DataType.hpp>
23 #include <com/sun/star/sdbc/SQLException.hpp>
24 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
25 #include <com/sun/star/sheet/XSpreadsheet.hpp>
26 #include <com/sun/star/sheet/XCellRangeAddressable.hpp>
27 #include <com/sun/star/sheet/XCellRangesQuery.hpp>
28 #include <com/sun/star/sheet/XDatabaseRanges.hpp>
29 #include <com/sun/star/sheet/XDatabaseRange.hpp>
30 #include <com/sun/star/sheet/XCellRangeReferrer.hpp>
31 #include <com/sun/star/sheet/XUsedAreaCursor.hpp>
32 #include <com/sun/star/sheet/CellFlags.hpp>
33 #include <com/sun/star/sheet/FormulaResult.hpp>
34 #include <com/sun/star/util/NumberFormat.hpp>
35 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
36 #include <com/sun/star/text/XText.hpp>
37 #include <calc/CConnection.hxx>
38 #include <connectivity/sdbcx/VColumn.hxx>
39 #include <rtl/ustrbuf.hxx>
40 #include <sal/log.hxx>
41 #include <rtl/math.hxx>
42 #include <tools/time.hxx>
43 #include <comphelper/servicehelper.hxx>
45 using namespace connectivity
;
46 using namespace connectivity::calc
;
47 using namespace connectivity::file
;
48 using namespace ::cppu
;
49 using namespace ::dbtools
;
50 using namespace ::com::sun::star::uno
;
51 using namespace ::com::sun::star::beans
;
52 using namespace ::com::sun::star::sdbc
;
53 using namespace ::com::sun::star::container
;
54 using namespace ::com::sun::star::sheet
;
55 using namespace ::com::sun::star::table
;
56 using namespace ::com::sun::star::text
;
57 using namespace ::com::sun::star::util
;
60 static void lcl_UpdateArea( const Reference
<XCellRange
>& xUsedRange
, sal_Int32
& rEndCol
, sal_Int32
& rEndRow
)
62 // update rEndCol, rEndRow if any non-empty cell in xUsedRange is right/below
64 const Reference
<XCellRangesQuery
> xUsedQuery( xUsedRange
, UNO_QUERY
);
65 if ( !xUsedQuery
.is() )
68 const sal_Int16 nContentFlags
=
69 CellFlags::STRING
| CellFlags::VALUE
| CellFlags::DATETIME
| CellFlags::FORMULA
| CellFlags::ANNOTATION
;
71 const Reference
<XSheetCellRanges
> xUsedRanges
= xUsedQuery
->queryContentCells( nContentFlags
);
73 for (auto& address
: xUsedRanges
->getRangeAddresses())
75 rEndCol
= std::max(address
.EndColumn
, rEndCol
);
76 rEndRow
= std::max(address
.EndRow
, rEndRow
);
80 static void lcl_GetDataArea( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32
& rColumnCount
, sal_Int32
& rRowCount
)
82 Reference
<XSheetCellCursor
> xCursor
= xSheet
->createCursor();
83 Reference
<XCellRangeAddressable
> xRange( xCursor
, UNO_QUERY
);
86 rColumnCount
= rRowCount
= 0;
90 // first find the contiguous cell area starting at A1
92 xCursor
->collapseToSize( 1, 1 ); // single (first) cell
93 xCursor
->collapseToCurrentRegion(); // contiguous data area
95 CellRangeAddress aRegionAddr
= xRange
->getRangeAddress();
96 sal_Int32 nEndCol
= aRegionAddr
.EndColumn
;
97 sal_Int32 nEndRow
= aRegionAddr
.EndRow
;
99 Reference
<XUsedAreaCursor
> xUsed( xCursor
, UNO_QUERY
);
102 // The used area from XUsedAreaCursor includes visible attributes.
103 // If the used area is larger than the contiguous cell area, find non-empty
104 // cells in that area.
106 xUsed
->gotoEndOfUsedArea( false );
107 CellRangeAddress aUsedAddr
= xRange
->getRangeAddress();
109 if ( aUsedAddr
.EndColumn
> aRegionAddr
.EndColumn
)
111 Reference
<XCellRange
> xUsedRange
= xSheet
->getCellRangeByPosition(
112 aRegionAddr
.EndColumn
+ 1, 0, aUsedAddr
.EndColumn
, aUsedAddr
.EndRow
);
113 lcl_UpdateArea( xUsedRange
, nEndCol
, nEndRow
);
116 if ( aUsedAddr
.EndRow
> aRegionAddr
.EndRow
)
118 // only up to the last column of aRegionAddr, the other columns are handled above
119 Reference
<XCellRange
> xUsedRange
= xSheet
->getCellRangeByPosition(
120 0, aRegionAddr
.EndRow
+ 1, aRegionAddr
.EndColumn
, aUsedAddr
.EndRow
);
121 lcl_UpdateArea( xUsedRange
, nEndCol
, nEndRow
);
125 rColumnCount
= nEndCol
+ 1; // number of columns
126 rRowCount
= nEndRow
; // first row (headers) is not counted
129 static CellContentType
lcl_GetContentOrResultType( const Reference
<XCell
>& xCell
)
131 CellContentType eCellType
= xCell
->getType();
132 if ( eCellType
== CellContentType_FORMULA
)
134 Reference
<XPropertySet
> xProp( xCell
, UNO_QUERY
);
137 xProp
->getPropertyValue( u
"CellContentType"_ustr
) >>= eCellType
; // type of cell content
139 catch (UnknownPropertyException
&)
141 eCellType
= CellContentType_VALUE
; // if CellContentType property not available
147 static Reference
<XCell
> lcl_GetUsedCell( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32 nDocColumn
, sal_Int32 nDocRow
)
149 Reference
<XCell
> xCell
= xSheet
->getCellByPosition( nDocColumn
, nDocRow
);
150 if ( xCell
.is() && xCell
->getType() == CellContentType_EMPTY
)
152 // get first non-empty cell
154 Reference
<XCellRangeAddressable
> xAddr( xSheet
, UNO_QUERY
);
157 CellRangeAddress aTotalRange
= xAddr
->getRangeAddress();
158 sal_Int32 nLastRow
= aTotalRange
.EndRow
;
159 Reference
<XCellRangesQuery
> xQuery( xSheet
->getCellRangeByPosition( nDocColumn
, nDocRow
, nDocColumn
, nLastRow
), UNO_QUERY
);
162 // queryIntersection to get a ranges object
163 Reference
<XSheetCellRanges
> xRanges
= xQuery
->queryIntersection( aTotalRange
);
166 Reference
<XEnumerationAccess
> xCells
= xRanges
->getCells();
169 Reference
<XEnumeration
> xEnum
= xCells
->createEnumeration();
170 if ( xEnum
.is() && xEnum
->hasMoreElements() )
172 // get first non-empty cell from enumeration
173 xCell
.set(xEnum
->nextElement(),UNO_QUERY
);
175 // otherwise, keep empty cell
184 static bool lcl_HasTextInColumn( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32 nDocColumn
, sal_Int32 nDocRow
)
186 // look for any text cell or text result in the column
188 Reference
<XCellRangeAddressable
> xAddr( xSheet
, UNO_QUERY
);
191 CellRangeAddress aTotalRange
= xAddr
->getRangeAddress();
192 sal_Int32 nLastRow
= aTotalRange
.EndRow
;
193 Reference
<XCellRangesQuery
> xQuery( xSheet
->getCellRangeByPosition( nDocColumn
, nDocRow
, nDocColumn
, nLastRow
), UNO_QUERY
);
197 // are there text cells in the column?
198 Reference
<XSheetCellRanges
> xTextContent
= xQuery
->queryContentCells( CellFlags::STRING
);
199 if ( xTextContent
.is() && xTextContent
->hasElements() )
202 // are there formulas with text results in the column?
203 Reference
<XSheetCellRanges
> xTextFormula
= xQuery
->queryFormulaCells( FormulaResult::STRING
);
204 if ( xTextFormula
.is() && xTextFormula
->hasElements() )
210 static void lcl_GetColumnInfo( const Reference
<XSpreadsheet
>& xSheet
, const Reference
<XNumberFormats
>& xFormats
,
211 sal_Int32 nDocColumn
, sal_Int32 nStartRow
, bool bHasHeaders
,
212 OUString
& rName
, sal_Int32
& rDataType
, bool& rCurrency
)
214 //! avoid duplicate field names
216 // get column name from first row, if range contains headers
220 Reference
<XText
> xHeaderText( xSheet
->getCellByPosition( nDocColumn
, nStartRow
), UNO_QUERY
);
221 if ( xHeaderText
.is() )
222 rName
= xHeaderText
->getString();
225 // get column type from first data row
227 sal_Int32 nDataRow
= nStartRow
;
230 Reference
<XCell
> xDataCell
= lcl_GetUsedCell( xSheet
, nDocColumn
, nDataRow
);
232 Reference
<XPropertySet
> xProp( xDataCell
, UNO_QUERY
);
236 rCurrency
= false; // set to true for currency below
238 const CellContentType eCellType
= lcl_GetContentOrResultType( xDataCell
);
239 // #i35178# use "text" type if there is any text cell in the column
240 if ( eCellType
== CellContentType_TEXT
|| lcl_HasTextInColumn( xSheet
, nDocColumn
, nDataRow
) )
241 rDataType
= DataType::VARCHAR
;
242 else if ( eCellType
== CellContentType_VALUE
)
244 // get number format to distinguish between different types
246 sal_Int16 nNumType
= NumberFormat::NUMBER
;
251 if ( xProp
->getPropertyValue( u
"NumberFormat"_ustr
) >>= nKey
)
253 const Reference
<XPropertySet
> xFormat
= xFormats
->getByKey( nKey
);
256 xFormat
->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE
) ) >>= nNumType
;
264 if ( nNumType
& NumberFormat::TEXT
)
265 rDataType
= DataType::VARCHAR
;
266 else if ( nNumType
& NumberFormat::NUMBER
)
267 rDataType
= DataType::DECIMAL
;
268 else if ( nNumType
& NumberFormat::CURRENCY
)
271 rDataType
= DataType::DECIMAL
;
273 else if ( ( nNumType
& NumberFormat::DATETIME
) == NumberFormat::DATETIME
)
275 // NumberFormat::DATETIME is DATE | TIME
276 rDataType
= DataType::TIMESTAMP
;
278 else if ( nNumType
& NumberFormat::DATE
)
279 rDataType
= DataType::DATE
;
280 else if ( nNumType
& NumberFormat::TIME
)
281 rDataType
= DataType::TIME
;
282 else if ( nNumType
& NumberFormat::LOGICAL
)
283 rDataType
= DataType::BIT
;
285 rDataType
= DataType::DECIMAL
;
289 // whole column empty
290 rDataType
= DataType::VARCHAR
;
295 static void lcl_SetValue( ORowSetValue
& rValue
, const Reference
<XSpreadsheet
>& xSheet
,
296 sal_Int32 nStartCol
, sal_Int32 nStartRow
, bool bHasHeaders
,
297 const ::Date
& rNullDate
,
298 sal_Int32 nDBRow
, sal_Int32 nDBColumn
, sal_Int32 nType
)
300 sal_Int32 nDocColumn
= nStartCol
+ nDBColumn
- 1; // database counts from 1
301 sal_Int32 nDocRow
= nStartRow
+ nDBRow
- 1;
305 const Reference
<XCell
> xCell
= xSheet
->getCellByPosition( nDocColumn
, nDocRow
);
309 CellContentType eCellType
= lcl_GetContentOrResultType( xCell
);
312 case DataType::VARCHAR
:
313 if ( eCellType
== CellContentType_EMPTY
)
317 // #i25840# still let Calc convert numbers to text
318 const Reference
<XText
> xText( xCell
, UNO_QUERY
);
320 rValue
= xText
->getString();
323 case DataType::DECIMAL
:
324 if ( eCellType
== CellContentType_VALUE
)
325 rValue
= xCell
->getValue(); // double
330 if ( eCellType
== CellContentType_VALUE
)
331 rValue
= xCell
->getValue() != 0.0;
336 if ( eCellType
== CellContentType_VALUE
)
338 ::Date
aDate( rNullDate
);
339 aDate
.AddDays(::rtl::math::approxFloor( xCell
->getValue() ));
340 rValue
= aDate
.GetUNODate();
346 if ( eCellType
== CellContentType_VALUE
)
348 double fCellVal
= xCell
->getValue();
349 double fTime
= fCellVal
- rtl::math::approxFloor( fCellVal
);
350 sal_Int64 nIntTime
= static_cast<sal_Int64
>(rtl::math::round( fTime
* static_cast<double>(::tools::Time::nanoSecPerDay
) ));
351 if ( nIntTime
== ::tools::Time::nanoSecPerDay
)
352 nIntTime
= 0; // 23:59:59.9999999995 and above is 00:00:00.00
353 css::util::Time aTime
;
354 aTime
.NanoSeconds
= static_cast<sal_uInt32
>( nIntTime
% ::tools::Time::nanoSecPerSec
);
355 nIntTime
/= ::tools::Time::nanoSecPerSec
;
356 aTime
.Seconds
= static_cast<sal_uInt16
>( nIntTime
% 60 );
358 aTime
.Minutes
= static_cast<sal_uInt16
>( nIntTime
% 60 );
360 OSL_ENSURE( nIntTime
< 24, "error in time calculation" );
361 aTime
.Hours
= static_cast<sal_uInt16
>(nIntTime
);
367 case DataType::TIMESTAMP
:
368 if ( eCellType
== CellContentType_VALUE
)
370 double fCellVal
= xCell
->getValue();
371 double fDays
= ::rtl::math::approxFloor( fCellVal
);
372 double fTime
= fCellVal
- fDays
;
373 tools::Long nIntDays
= static_cast<tools::Long
>(fDays
);
374 sal_Int64 nIntTime
= ::rtl::math::round( fTime
* static_cast<double>(::tools::Time::nanoSecPerDay
) );
375 if ( nIntTime
== ::tools::Time::nanoSecPerDay
)
377 nIntTime
= 0; // 23:59:59.9999999995 and above is 00:00:00.00
378 ++nIntDays
; // (next day)
381 css::util::DateTime aDateTime
;
383 aDateTime
.NanoSeconds
= static_cast<sal_uInt16
>( nIntTime
% ::tools::Time::nanoSecPerSec
);
384 nIntTime
/= ::tools::Time::nanoSecPerSec
;
385 aDateTime
.Seconds
= static_cast<sal_uInt16
>( nIntTime
% 60 );
387 aDateTime
.Minutes
= static_cast<sal_uInt16
>( nIntTime
% 60 );
389 OSL_ENSURE( nIntTime
< 24, "error in time calculation" );
390 aDateTime
.Hours
= static_cast<sal_uInt16
>(nIntTime
);
392 ::Date
aDate( rNullDate
);
393 aDate
.AddDays( nIntDays
);
394 aDateTime
.Day
= aDate
.GetDay();
395 aDateTime
.Month
= aDate
.GetMonth();
396 aDateTime
.Year
= aDate
.GetYear();
405 // rValue.setTypeKind(nType);
409 static OUString
lcl_GetColumnStr( sal_Int32 nColumn
)
412 return OUString( static_cast<sal_Unicode
>( 'A' + nColumn
) );
415 OUStringBuffer
aBuffer(2);
416 aBuffer
.setLength( 2 );
417 aBuffer
[0] = static_cast<sal_Unicode
>( 'A' + ( nColumn
/ 26 ) - 1 );
418 aBuffer
[1] = static_cast<sal_Unicode
>( 'A' + ( nColumn
% 26 ) );
419 return aBuffer
.makeStringAndClear();
423 void OCalcTable::fillColumns()
425 if ( !m_xSheet
.is() )
426 throw SQLException();
429 ::comphelper::UStringMixEqual
aCase(m_pConnection
->getMetaData()->supportsMixedCaseQuotedIdentifiers());
430 const bool bStoresMixedCaseQuotedIdentifiers
= getConnection()->getMetaData()->supportsMixedCaseQuotedIdentifiers();
432 for (sal_Int32 i
= 0; i
< m_nDataCols
; i
++)
434 OUString aColumnName
;
435 sal_Int32 eType
= DataType::OTHER
;
436 bool bCurrency
= false;
438 lcl_GetColumnInfo( m_xSheet
, m_xFormats
, m_nStartCol
+ i
, m_nStartRow
, m_bHasHeaders
,
439 aColumnName
, eType
, bCurrency
);
441 if ( aColumnName
.isEmpty() )
442 aColumnName
= lcl_GetColumnStr( i
);
444 sal_Int32 nPrecision
= 0; //! ...
445 sal_Int32 nDecimals
= 0; //! ...
449 case DataType::VARCHAR
:
450 aTypeName
= "VARCHAR";
452 case DataType::DECIMAL
:
453 aTypeName
= "DECIMAL";
464 case DataType::TIMESTAMP
:
465 aTypeName
= "TIMESTAMP";
468 SAL_WARN( "connectivity.drivers","missing type name");
472 // check if the column name already exists
473 OUString aAlias
= aColumnName
;
474 OSQLColumns::const_iterator aFind
= connectivity::find(m_aColumns
->begin(),m_aColumns
->end(),aAlias
,aCase
);
475 sal_Int32 nExprCnt
= 0;
476 while(aFind
!= m_aColumns
->end())
478 aAlias
= aColumnName
+ OUString::number(++nExprCnt
);
479 aFind
= connectivity::find(m_aColumns
->begin(),m_aColumns
->end(),aAlias
,aCase
);
482 rtl::Reference
<sdbcx::OColumn
> pColumn
= new sdbcx::OColumn( aAlias
, aTypeName
, OUString(),OUString(),
483 ColumnValue::NULLABLE
, nPrecision
, nDecimals
,
484 eType
, false, false, bCurrency
,
485 bStoresMixedCaseQuotedIdentifiers
,
486 m_CatalogName
, getSchema(), getName());
487 m_aColumns
->push_back(pColumn
);
488 m_aTypes
.push_back(eType
);
493 OCalcTable::OCalcTable(sdbcx::OCollection
* _pTables
,OCalcConnection
* _pConnection
,
494 const OUString
& Name
,
495 const OUString
& Type
,
496 const OUString
& Description
,
497 const OUString
& SchemaName
,
498 const OUString
& CatalogName
499 ) : OCalcTable_BASE(_pTables
,_pConnection
,Name
,
504 ,m_pCalcConnection(_pConnection
)
508 ,m_bHasHeaders(false)
509 ,m_aNullDate(::Date::EMPTY
)
513 void OCalcTable::construct()
516 Reference
< XSpreadsheetDocument
> xDoc
= m_pCalcConnection
->acquireDoc();
519 Reference
<XSpreadsheets
> xSheets
= xDoc
->getSheets();
520 if ( xSheets
.is() && xSheets
->hasByName( m_Name
) )
522 m_xSheet
.set(xSheets
->getByName( m_Name
),UNO_QUERY
);
525 lcl_GetDataArea( m_xSheet
, m_nDataCols
, m_nDataRows
);
526 m_bHasHeaders
= true;
527 // whole sheet is always assumed to include a header row
530 else // no sheet -> try database range
532 Reference
<XPropertySet
> xDocProp( xDoc
, UNO_QUERY
);
535 Reference
<XDatabaseRanges
> xRanges(xDocProp
->getPropertyValue(u
"DatabaseRanges"_ustr
),UNO_QUERY
);
537 if ( xRanges
.is() && xRanges
->hasByName( m_Name
) )
539 Reference
<XDatabaseRange
> xDBRange(xRanges
->getByName( m_Name
),UNO_QUERY
);
540 Reference
<XCellRangeReferrer
> xRefer( xDBRange
, UNO_QUERY
);
543 // Header flag is always stored with database range
544 // Get flag from FilterDescriptor
546 bool bRangeHeader
= true;
547 Reference
<XPropertySet
> xFiltProp( xDBRange
->getFilterDescriptor(), UNO_QUERY
);
548 if ( xFiltProp
.is() )
549 xFiltProp
->getPropertyValue(u
"ContainsHeader"_ustr
) >>= bRangeHeader
;
551 Reference
<XSheetCellRange
> xSheetRange( xRefer
->getReferredCells(), UNO_QUERY
);
552 Reference
<XCellRangeAddressable
> xAddr( xSheetRange
, UNO_QUERY
);
553 if ( xSheetRange
.is() && xAddr
.is() )
555 m_xSheet
= xSheetRange
->getSpreadsheet();
556 CellRangeAddress aRangeAddr
= xAddr
->getRangeAddress();
557 m_nStartCol
= aRangeAddr
.StartColumn
;
558 m_nStartRow
= aRangeAddr
.StartRow
;
559 m_nDataCols
= aRangeAddr
.EndColumn
- m_nStartCol
+ 1;
560 // m_nDataRows is excluding header row
561 m_nDataRows
= aRangeAddr
.EndRow
- m_nStartRow
;
564 // m_nDataRows counts the whole range
568 m_bHasHeaders
= bRangeHeader
;
575 Reference
<XNumberFormatsSupplier
> xSupp( xDoc
, UNO_QUERY
);
577 m_xFormats
= xSupp
->getNumberFormats();
579 Reference
<XPropertySet
> xProp( xDoc
, UNO_QUERY
);
582 css::util::Date aDateStruct
;
583 if ( xProp
->getPropertyValue(u
"NullDate"_ustr
) >>= aDateStruct
)
584 m_aNullDate
= ::Date( aDateStruct
.Day
, aDateStruct
.Month
, aDateStruct
.Year
);
588 //! default if no null date available?
595 void SAL_CALL
OCalcTable::disposing()
597 OFileTable::disposing();
598 ::osl::MutexGuard
aGuard(m_aMutex
);
599 m_aColumns
= nullptr;
600 if ( m_pCalcConnection
)
601 m_pCalcConnection
->releaseDoc();
602 m_pCalcConnection
= nullptr;
606 bool OCalcTable::fetchRow( OValueRefRow
& _rRow
, const OSQLColumns
& _rCols
,
611 _rRow
->setDeleted(false);
612 *(*_rRow
)[0] = m_nFilePos
;
619 const OValueRefVector::size_type nCount
= std::min(_rRow
->size(), _rCols
.size() + 1);
620 for (OValueRefVector::size_type i
= 1; i
< nCount
; i
++)
622 if ( (*_rRow
)[i
]->isBound() )
624 sal_Int32 nType
= m_aTypes
[i
-1];
626 lcl_SetValue( (*_rRow
)[i
]->get(), m_xSheet
, m_nStartCol
, m_nStartRow
, m_bHasHeaders
,
627 m_aNullDate
, m_nFilePos
, i
, nType
);
633 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */