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::sdbcx
;
53 using namespace ::com::sun::star::sdbc
;
54 using namespace ::com::sun::star::container
;
55 using namespace ::com::sun::star::lang
;
56 using namespace ::com::sun::star::sheet
;
57 using namespace ::com::sun::star::table
;
58 using namespace ::com::sun::star::text
;
59 using namespace ::com::sun::star::util
;
62 static void lcl_UpdateArea( const Reference
<XCellRange
>& xUsedRange
, sal_Int32
& rEndCol
, sal_Int32
& rEndRow
)
64 // update rEndCol, rEndRow if any non-empty cell in xUsedRange is right/below
66 const Reference
<XCellRangesQuery
> xUsedQuery( xUsedRange
, UNO_QUERY
);
67 if ( !xUsedQuery
.is() )
70 const sal_Int16 nContentFlags
=
71 CellFlags::STRING
| CellFlags::VALUE
| CellFlags::DATETIME
| CellFlags::FORMULA
| CellFlags::ANNOTATION
;
73 const Reference
<XSheetCellRanges
> xUsedRanges
= xUsedQuery
->queryContentCells( nContentFlags
);
74 const Sequence
<CellRangeAddress
> aAddresses
= xUsedRanges
->getRangeAddresses();
76 const sal_Int32 nCount
= aAddresses
.getLength();
77 const CellRangeAddress
* pData
= aAddresses
.getConstArray();
78 for ( sal_Int32 i
=0; i
<nCount
; i
++ )
80 rEndCol
= std::max(pData
[i
].EndColumn
, rEndCol
);
81 rEndRow
= std::max(pData
[i
].EndRow
, rEndRow
);
85 static void lcl_GetDataArea( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32
& rColumnCount
, sal_Int32
& rRowCount
)
87 Reference
<XSheetCellCursor
> xCursor
= xSheet
->createCursor();
88 Reference
<XCellRangeAddressable
> xRange( xCursor
, UNO_QUERY
);
91 rColumnCount
= rRowCount
= 0;
95 // first find the contiguous cell area starting at A1
97 xCursor
->collapseToSize( 1, 1 ); // single (first) cell
98 xCursor
->collapseToCurrentRegion(); // contiguous data area
100 CellRangeAddress aRegionAddr
= xRange
->getRangeAddress();
101 sal_Int32 nEndCol
= aRegionAddr
.EndColumn
;
102 sal_Int32 nEndRow
= aRegionAddr
.EndRow
;
104 Reference
<XUsedAreaCursor
> xUsed( xCursor
, UNO_QUERY
);
107 // The used area from XUsedAreaCursor includes visible attributes.
108 // If the used area is larger than the contiguous cell area, find non-empty
109 // cells in that area.
111 xUsed
->gotoEndOfUsedArea( false );
112 CellRangeAddress aUsedAddr
= xRange
->getRangeAddress();
114 if ( aUsedAddr
.EndColumn
> aRegionAddr
.EndColumn
)
116 Reference
<XCellRange
> xUsedRange
= xSheet
->getCellRangeByPosition(
117 aRegionAddr
.EndColumn
+ 1, 0, aUsedAddr
.EndColumn
, aUsedAddr
.EndRow
);
118 lcl_UpdateArea( xUsedRange
, nEndCol
, nEndRow
);
121 if ( aUsedAddr
.EndRow
> aRegionAddr
.EndRow
)
123 // only up to the last column of aRegionAddr, the other columns are handled above
124 Reference
<XCellRange
> xUsedRange
= xSheet
->getCellRangeByPosition(
125 0, aRegionAddr
.EndRow
+ 1, aRegionAddr
.EndColumn
, aUsedAddr
.EndRow
);
126 lcl_UpdateArea( xUsedRange
, nEndCol
, nEndRow
);
130 rColumnCount
= nEndCol
+ 1; // number of columns
131 rRowCount
= nEndRow
; // first row (headers) is not counted
134 static CellContentType
lcl_GetContentOrResultType( const Reference
<XCell
>& xCell
)
136 CellContentType eCellType
= xCell
->getType();
137 if ( eCellType
== CellContentType_FORMULA
)
139 Reference
<XPropertySet
> xProp( xCell
, UNO_QUERY
);
142 xProp
->getPropertyValue( "CellContentType" ) >>= eCellType
; // type of cell content
144 catch (UnknownPropertyException
&)
146 eCellType
= CellContentType_VALUE
; // if CellContentType property not available
152 static Reference
<XCell
> lcl_GetUsedCell( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32 nDocColumn
, sal_Int32 nDocRow
)
154 Reference
<XCell
> xCell
= xSheet
->getCellByPosition( nDocColumn
, nDocRow
);
155 if ( xCell
.is() && xCell
->getType() == CellContentType_EMPTY
)
157 // get first non-empty cell
159 Reference
<XCellRangeAddressable
> xAddr( xSheet
, UNO_QUERY
);
162 CellRangeAddress aTotalRange
= xAddr
->getRangeAddress();
163 sal_Int32 nLastRow
= aTotalRange
.EndRow
;
164 Reference
<XCellRangesQuery
> xQuery( xSheet
->getCellRangeByPosition( nDocColumn
, nDocRow
, nDocColumn
, nLastRow
), UNO_QUERY
);
167 // queryIntersection to get a ranges object
168 Reference
<XSheetCellRanges
> xRanges
= xQuery
->queryIntersection( aTotalRange
);
171 Reference
<XEnumerationAccess
> xCells
= xRanges
->getCells();
174 Reference
<XEnumeration
> xEnum
= xCells
->createEnumeration();
175 if ( xEnum
.is() && xEnum
->hasMoreElements() )
177 // get first non-empty cell from enumeration
178 xCell
.set(xEnum
->nextElement(),UNO_QUERY
);
180 // otherwise, keep empty cell
189 static bool lcl_HasTextInColumn( const Reference
<XSpreadsheet
>& xSheet
, sal_Int32 nDocColumn
, sal_Int32 nDocRow
)
191 // look for any text cell or text result in the column
193 Reference
<XCellRangeAddressable
> xAddr( xSheet
, UNO_QUERY
);
196 CellRangeAddress aTotalRange
= xAddr
->getRangeAddress();
197 sal_Int32 nLastRow
= aTotalRange
.EndRow
;
198 Reference
<XCellRangesQuery
> xQuery( xSheet
->getCellRangeByPosition( nDocColumn
, nDocRow
, nDocColumn
, nLastRow
), UNO_QUERY
);
202 // are there text cells in the column?
203 Reference
<XSheetCellRanges
> xTextContent
= xQuery
->queryContentCells( CellFlags::STRING
);
204 if ( xTextContent
.is() && xTextContent
->hasElements() )
207 // are there formulas with text results in the column?
208 Reference
<XSheetCellRanges
> xTextFormula
= xQuery
->queryFormulaCells( FormulaResult::STRING
);
209 if ( xTextFormula
.is() && xTextFormula
->hasElements() )
215 static void lcl_GetColumnInfo( const Reference
<XSpreadsheet
>& xSheet
, const Reference
<XNumberFormats
>& xFormats
,
216 sal_Int32 nDocColumn
, sal_Int32 nStartRow
, bool bHasHeaders
,
217 OUString
& rName
, sal_Int32
& rDataType
, bool& rCurrency
)
219 //! avoid duplicate field names
221 // get column name from first row, if range contains headers
225 Reference
<XText
> xHeaderText( xSheet
->getCellByPosition( nDocColumn
, nStartRow
), UNO_QUERY
);
226 if ( xHeaderText
.is() )
227 rName
= xHeaderText
->getString();
230 // get column type from first data row
232 sal_Int32 nDataRow
= nStartRow
;
235 Reference
<XCell
> xDataCell
= lcl_GetUsedCell( xSheet
, nDocColumn
, nDataRow
);
237 Reference
<XPropertySet
> xProp( xDataCell
, UNO_QUERY
);
241 rCurrency
= false; // set to true for currency below
243 const CellContentType eCellType
= lcl_GetContentOrResultType( xDataCell
);
244 // #i35178# use "text" type if there is any text cell in the column
245 if ( eCellType
== CellContentType_TEXT
|| lcl_HasTextInColumn( xSheet
, nDocColumn
, nDataRow
) )
246 rDataType
= DataType::VARCHAR
;
247 else if ( eCellType
== CellContentType_VALUE
)
249 // get number format to distinguish between different types
251 sal_Int16 nNumType
= NumberFormat::NUMBER
;
256 if ( xProp
->getPropertyValue( "NumberFormat" ) >>= nKey
)
258 const Reference
<XPropertySet
> xFormat
= xFormats
->getByKey( nKey
);
261 xFormat
->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE
) ) >>= nNumType
;
269 if ( nNumType
& NumberFormat::TEXT
)
270 rDataType
= DataType::VARCHAR
;
271 else if ( nNumType
& NumberFormat::NUMBER
)
272 rDataType
= DataType::DECIMAL
;
273 else if ( nNumType
& NumberFormat::CURRENCY
)
276 rDataType
= DataType::DECIMAL
;
278 else if ( ( nNumType
& NumberFormat::DATETIME
) == NumberFormat::DATETIME
)
280 // NumberFormat::DATETIME is DATE | TIME
281 rDataType
= DataType::TIMESTAMP
;
283 else if ( nNumType
& NumberFormat::DATE
)
284 rDataType
= DataType::DATE
;
285 else if ( nNumType
& NumberFormat::TIME
)
286 rDataType
= DataType::TIME
;
287 else if ( nNumType
& NumberFormat::LOGICAL
)
288 rDataType
= DataType::BIT
;
290 rDataType
= DataType::DECIMAL
;
294 // whole column empty
295 rDataType
= DataType::VARCHAR
;
300 static void lcl_SetValue( ORowSetValue
& rValue
, const Reference
<XSpreadsheet
>& xSheet
,
301 sal_Int32 nStartCol
, sal_Int32 nStartRow
, bool bHasHeaders
,
302 const ::Date
& rNullDate
,
303 sal_Int32 nDBRow
, sal_Int32 nDBColumn
, sal_Int32 nType
)
305 sal_Int32 nDocColumn
= nStartCol
+ nDBColumn
- 1; // database counts from 1
306 sal_Int32 nDocRow
= nStartRow
+ nDBRow
- 1;
310 const Reference
<XCell
> xCell
= xSheet
->getCellByPosition( nDocColumn
, nDocRow
);
314 CellContentType eCellType
= lcl_GetContentOrResultType( xCell
);
317 case DataType::VARCHAR
:
318 if ( eCellType
== CellContentType_EMPTY
)
322 // #i25840# still let Calc convert numbers to text
323 const Reference
<XText
> xText( xCell
, UNO_QUERY
);
325 rValue
= xText
->getString();
328 case DataType::DECIMAL
:
329 if ( eCellType
== CellContentType_VALUE
)
330 rValue
= xCell
->getValue(); // double
335 if ( eCellType
== CellContentType_VALUE
)
336 rValue
= xCell
->getValue() != 0.0;
341 if ( eCellType
== CellContentType_VALUE
)
343 ::Date
aDate( rNullDate
);
344 aDate
.AddDays(::rtl::math::approxFloor( xCell
->getValue() ));
345 rValue
= aDate
.GetUNODate();
351 if ( eCellType
== CellContentType_VALUE
)
353 double fCellVal
= xCell
->getValue();
354 double fTime
= fCellVal
- rtl::math::approxFloor( fCellVal
);
355 sal_Int64 nIntTime
= static_cast<sal_Int64
>(rtl::math::round( fTime
* static_cast<double>(::tools::Time::nanoSecPerDay
) ));
356 if ( nIntTime
== ::tools::Time::nanoSecPerDay
)
357 nIntTime
= 0; // 23:59:59.9999999995 and above is 00:00:00.00
358 css::util::Time aTime
;
359 aTime
.NanoSeconds
= static_cast<sal_uInt32
>( nIntTime
% ::tools::Time::nanoSecPerSec
);
360 nIntTime
/= ::tools::Time::nanoSecPerSec
;
361 aTime
.Seconds
= static_cast<sal_uInt16
>( nIntTime
% 60 );
363 aTime
.Minutes
= static_cast<sal_uInt16
>( nIntTime
% 60 );
365 OSL_ENSURE( nIntTime
< 24, "error in time calculation" );
366 aTime
.Hours
= static_cast<sal_uInt16
>(nIntTime
);
372 case DataType::TIMESTAMP
:
373 if ( eCellType
== CellContentType_VALUE
)
375 double fCellVal
= xCell
->getValue();
376 double fDays
= ::rtl::math::approxFloor( fCellVal
);
377 double fTime
= fCellVal
- fDays
;
378 tools::Long nIntDays
= static_cast<tools::Long
>(fDays
);
379 sal_Int64 nIntTime
= ::rtl::math::round( fTime
* static_cast<double>(::tools::Time::nanoSecPerDay
) );
380 if ( nIntTime
== ::tools::Time::nanoSecPerDay
)
382 nIntTime
= 0; // 23:59:59.9999999995 and above is 00:00:00.00
383 ++nIntDays
; // (next day)
386 css::util::DateTime aDateTime
;
388 aDateTime
.NanoSeconds
= static_cast<sal_uInt16
>( nIntTime
% ::tools::Time::nanoSecPerSec
);
389 nIntTime
/= ::tools::Time::nanoSecPerSec
;
390 aDateTime
.Seconds
= static_cast<sal_uInt16
>( nIntTime
% 60 );
392 aDateTime
.Minutes
= static_cast<sal_uInt16
>( nIntTime
% 60 );
394 OSL_ENSURE( nIntTime
< 24, "error in time calculation" );
395 aDateTime
.Hours
= static_cast<sal_uInt16
>(nIntTime
);
397 ::Date
aDate( rNullDate
);
398 aDate
.AddDays( nIntDays
);
399 aDateTime
.Day
= aDate
.GetDay();
400 aDateTime
.Month
= aDate
.GetMonth();
401 aDateTime
.Year
= aDate
.GetYear();
410 // rValue.setTypeKind(nType);
414 static OUString
lcl_GetColumnStr( sal_Int32 nColumn
)
417 return OUString( static_cast<sal_Unicode
>( 'A' + nColumn
) );
420 OUStringBuffer
aBuffer(2);
421 aBuffer
.setLength( 2 );
422 aBuffer
[0] = static_cast<sal_Unicode
>( 'A' + ( nColumn
/ 26 ) - 1 );
423 aBuffer
[1] = static_cast<sal_Unicode
>( 'A' + ( nColumn
% 26 ) );
424 return aBuffer
.makeStringAndClear();
428 void OCalcTable::fillColumns()
430 if ( !m_xSheet
.is() )
431 throw SQLException();
434 ::comphelper::UStringMixEqual
aCase(m_pConnection
->getMetaData()->supportsMixedCaseQuotedIdentifiers());
435 const bool bStoresMixedCaseQuotedIdentifiers
= getConnection()->getMetaData()->supportsMixedCaseQuotedIdentifiers();
437 for (sal_Int32 i
= 0; i
< m_nDataCols
; i
++)
439 OUString aColumnName
;
440 sal_Int32 eType
= DataType::OTHER
;
441 bool bCurrency
= false;
443 lcl_GetColumnInfo( m_xSheet
, m_xFormats
, m_nStartCol
+ i
, m_nStartRow
, m_bHasHeaders
,
444 aColumnName
, eType
, bCurrency
);
446 if ( aColumnName
.isEmpty() )
447 aColumnName
= lcl_GetColumnStr( i
);
449 sal_Int32 nPrecision
= 0; //! ...
450 sal_Int32 nDecimals
= 0; //! ...
454 case DataType::VARCHAR
:
455 aTypeName
= "VARCHAR";
457 case DataType::DECIMAL
:
458 aTypeName
= "DECIMAL";
469 case DataType::TIMESTAMP
:
470 aTypeName
= "TIMESTAMP";
473 SAL_WARN( "connectivity.drivers","missing type name");
477 // check if the column name already exists
478 OUString aAlias
= aColumnName
;
479 OSQLColumns::const_iterator aFind
= connectivity::find(m_aColumns
->begin(),m_aColumns
->end(),aAlias
,aCase
);
480 sal_Int32 nExprCnt
= 0;
481 while(aFind
!= m_aColumns
->end())
483 aAlias
= aColumnName
+ OUString::number(++nExprCnt
);
484 aFind
= connectivity::find(m_aColumns
->begin(),m_aColumns
->end(),aAlias
,aCase
);
487 rtl::Reference
<sdbcx::OColumn
> pColumn
= new sdbcx::OColumn( aAlias
, aTypeName
, OUString(),OUString(),
488 ColumnValue::NULLABLE
, nPrecision
, nDecimals
,
489 eType
, false, false, bCurrency
,
490 bStoresMixedCaseQuotedIdentifiers
,
491 m_CatalogName
, getSchema(), getName());
492 m_aColumns
->push_back(pColumn
);
493 m_aTypes
.push_back(eType
);
498 OCalcTable::OCalcTable(sdbcx::OCollection
* _pTables
,OCalcConnection
* _pConnection
,
499 const OUString
& Name
,
500 const OUString
& Type
,
501 const OUString
& Description
,
502 const OUString
& SchemaName
,
503 const OUString
& CatalogName
504 ) : OCalcTable_BASE(_pTables
,_pConnection
,Name
,
509 ,m_pCalcConnection(_pConnection
)
513 ,m_bHasHeaders(false)
514 ,m_aNullDate(::Date::EMPTY
)
518 void OCalcTable::construct()
521 Reference
< XSpreadsheetDocument
> xDoc
= m_pCalcConnection
->acquireDoc();
524 Reference
<XSpreadsheets
> xSheets
= xDoc
->getSheets();
525 if ( xSheets
.is() && xSheets
->hasByName( m_Name
) )
527 m_xSheet
.set(xSheets
->getByName( m_Name
),UNO_QUERY
);
530 lcl_GetDataArea( m_xSheet
, m_nDataCols
, m_nDataRows
);
531 m_bHasHeaders
= true;
532 // whole sheet is always assumed to include a header row
535 else // no sheet -> try database range
537 Reference
<XPropertySet
> xDocProp( xDoc
, UNO_QUERY
);
540 Reference
<XDatabaseRanges
> xRanges(xDocProp
->getPropertyValue("DatabaseRanges"),UNO_QUERY
);
542 if ( xRanges
.is() && xRanges
->hasByName( m_Name
) )
544 Reference
<XDatabaseRange
> xDBRange(xRanges
->getByName( m_Name
),UNO_QUERY
);
545 Reference
<XCellRangeReferrer
> xRefer( xDBRange
, UNO_QUERY
);
548 // Header flag is always stored with database range
549 // Get flag from FilterDescriptor
551 bool bRangeHeader
= true;
552 Reference
<XPropertySet
> xFiltProp( xDBRange
->getFilterDescriptor(), UNO_QUERY
);
553 if ( xFiltProp
.is() )
554 xFiltProp
->getPropertyValue("ContainsHeader") >>= bRangeHeader
;
556 Reference
<XSheetCellRange
> xSheetRange( xRefer
->getReferredCells(), UNO_QUERY
);
557 Reference
<XCellRangeAddressable
> xAddr( xSheetRange
, UNO_QUERY
);
558 if ( xSheetRange
.is() && xAddr
.is() )
560 m_xSheet
= xSheetRange
->getSpreadsheet();
561 CellRangeAddress aRangeAddr
= xAddr
->getRangeAddress();
562 m_nStartCol
= aRangeAddr
.StartColumn
;
563 m_nStartRow
= aRangeAddr
.StartRow
;
564 m_nDataCols
= aRangeAddr
.EndColumn
- m_nStartCol
+ 1;
565 // m_nDataRows is excluding header row
566 m_nDataRows
= aRangeAddr
.EndRow
- m_nStartRow
;
569 // m_nDataRows counts the whole range
573 m_bHasHeaders
= bRangeHeader
;
580 Reference
<XNumberFormatsSupplier
> xSupp( xDoc
, UNO_QUERY
);
582 m_xFormats
= xSupp
->getNumberFormats();
584 Reference
<XPropertySet
> xProp( xDoc
, UNO_QUERY
);
587 css::util::Date aDateStruct
;
588 if ( xProp
->getPropertyValue("NullDate") >>= aDateStruct
)
589 m_aNullDate
= ::Date( aDateStruct
.Day
, aDateStruct
.Month
, aDateStruct
.Year
);
593 //! default if no null date available?
600 void SAL_CALL
OCalcTable::disposing()
602 OFileTable::disposing();
603 ::osl::MutexGuard
aGuard(m_aMutex
);
604 m_aColumns
= nullptr;
605 if ( m_pCalcConnection
)
606 m_pCalcConnection
->releaseDoc();
607 m_pCalcConnection
= nullptr;
611 bool OCalcTable::fetchRow( OValueRefRow
& _rRow
, const OSQLColumns
& _rCols
,
616 _rRow
->setDeleted(false);
617 *(*_rRow
)[0] = m_nFilePos
;
624 const OValueRefVector::size_type nCount
= std::min(_rRow
->size(), _rCols
.size() + 1);
625 for (OValueRefVector::size_type i
= 1; i
< nCount
; i
++)
627 if ( (*_rRow
)[i
]->isBound() )
629 sal_Int32 nType
= m_aTypes
[i
-1];
631 lcl_SetValue( (*_rRow
)[i
]->get(), m_xSheet
, m_nStartCol
, m_nStartRow
, m_bHasHeaders
,
632 m_aNullDate
, m_nFilePos
, i
, nType
);
638 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */