/* -*- 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/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <limits>
#include <memory>
#include "xmlcelli.hxx"
#include "xmlimprt.hxx"
#include "xmlannoi.hxx"
#include <global.hxx>
#include <cellvalue.hxx>
#include <document.hxx>
#include <docuno.hxx>
#include <docsh.hxx>
#include <postit.hxx>
#include <sheetdata.hxx>
#include <cellform.hxx>
#include <validat.hxx>
#include <patattr.hxx>
#include <scitems.hxx>
#include <docpool.hxx>
#include "XMLTableShapeImportHelper.hxx"
#include "XMLStylesImportHelper.hxx"
#include "celltextparacontext.hxx"
#include "XMLCellRangeSourceContext.hxx"
#include <arealink.hxx>
#include <sfx2/linkmgr.hxx>
#include <scerrors.hxx>
#include <editutil.hxx>
#include <formulacell.hxx>
#include "editattributemap.hxx"
#include <tokenarray.hxx>
#include <scmatrix.hxx>
#include <stringutil.hxx>
#include <documentimport.hxx>
#include <externalrefmgr.hxx>
#include <xmloff/maptype.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlprmap.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/families.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/prstylei.hxx>
#include <xmloff/xmlimppr.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svx/svdocapt.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/flditem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wrlmitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/charreliefitem.hxx>
#include <editeng/charscaleitem.hxx>
#include <editeng/contouritem.hxx>
#include <editeng/shdditem.hxx>
#include <editeng/kernitem.hxx>
#include <editeng/autokernitem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/emphasismarkitem.hxx>
#include <editeng/langitem.hxx>
#include <svl/sharedstringpool.hxx>
#include <sax/tools/converter.hxx>
#include <sax/fastattribs.hxx>
#include <com/sun/star/util/NumberFormat.hpp>
#include <com/sun/star/sheet/ValidationType.hpp>
#include <com/sun/star/sheet/ValidationAlertStyle.hpp>
#include <rtl/ustrbuf.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <i18nlangtag/lang.h>
#include <comphelper/servicehelper.hxx>
#include <comphelper/lok.hxx>
using namespace com::sun::star;
using namespace xmloff::token;
ScXMLTableRowCellContext::ParaFormat::ParaFormat(const ScEditEngineDefaulter& rEditEngine) :
maItemSet(rEditEngine.GetEmptyItemSet()) {}
ScXMLTableRowCellContext::Field::Field(std::unique_ptr<SvxFieldData> pData) : mpData(std::move(pData)) {}
ScXMLTableRowCellContext::Field::~Field()
{
}
ScXMLTableRowCellContext::ScXMLTableRowCellContext( ScXMLImport& rImport,
const rtl::Reference<sax_fastparser::FastAttributeList>& rAttrList,
const bool bTempIsCovered,
const sal_Int32 nTempRepeatedRows ) :
ScXMLImportContext( rImport ),
mpEditEngine(GetScImport().GetEditEngine()),
mnCurParagraph(0),
fValue(std::numeric_limits<double>::quiet_NaN()),
nMergedRows(1),
nMatrixRows(0),
nRepeatedRows(nTempRepeatedRows),
nMergedCols(1),
nMatrixCols(0),
nColsRepeated(1),
rXMLImport(rImport),
eGrammar( formula::FormulaGrammar::GRAM_STORAGE_DEFAULT),
nCellType(util::NumberFormat::TEXT),
bIsMerged(false),
bIsMatrix(false),
bIsCovered(bTempIsCovered),
bIsEmpty(true),
mbNewValueType(false),
mbErrorValue(false),
bSolarMutexLocked(false),
bFormulaTextResult(false),
mbPossibleErrorCell(false),
mbCheckWithCompilerForError(false),
mbEditEngineHasText(false),
mbHasFormatRuns(false),
mbHasStyle(false),
mbPossibleEmptyDisplay(false)
{
rXMLImport.GetTables().AddColumn(bTempIsCovered);
std::optional<OUString> xStyleName;
std::optional<OUString> xCurrencySymbol;
if ( rAttrList.is() )
{
for (auto &it : *rAttrList)
{
switch ( it.getToken() )
{
case XML_ELEMENT( TABLE, XML_STYLE_NAME ):
xStyleName = it.toString();
mbHasStyle = true;
break;
case XML_ELEMENT( TABLE, XML_CONTENT_VALIDATION_NAME ):
OSL_ENSURE(!maContentValidationName, "here should be only one Validation Name");
if (!it.isEmpty())
maContentValidationName = it.toString();
break;
case XML_ELEMENT( TABLE, XML_NUMBER_ROWS_SPANNED ):
bIsMerged = true;
nMergedRows = static_cast<SCROW>(it.toInt32());
break;
case XML_ELEMENT( TABLE, XML_NUMBER_COLUMNS_SPANNED ):
bIsMerged = true;
nMergedCols = static_cast<SCCOL>(it.toInt32());
break;
case XML_ELEMENT( TABLE, XML_NUMBER_MATRIX_COLUMNS_SPANNED ):
bIsMatrix = true;
nMatrixCols = static_cast<SCCOL>(it.toInt32());
break;
case XML_ELEMENT( TABLE, XML_NUMBER_MATRIX_ROWS_SPANNED ):
bIsMatrix = true;
nMatrixRows = static_cast<SCROW>(it.toInt32());
break;
case XML_ELEMENT( TABLE, XML_NUMBER_COLUMNS_REPEATED ):
nColsRepeated = static_cast<SCCOL>(
std::min<sal_Int32>( rImport.GetDocument()->GetSheetLimits().GetMaxColCount(),
std::max( it.toInt32(), static_cast<sal_Int32>(1) ) ));
break;
case XML_ELEMENT( OFFICE, XML_VALUE_TYPE ):
nCellType = ScXMLImport::GetCellType(it.toCString(), it.getLength());
bIsEmpty = false;
break;
case XML_ELEMENT( CALC_EXT, XML_VALUE_TYPE ):
if(it.isString( "error" ) )
mbErrorValue = true;
else
nCellType = ScXMLImport::GetCellType(it.toCString(), it.getLength());
bIsEmpty = false;
mbNewValueType = true;
break;
case XML_ELEMENT( OFFICE, XML_VALUE ):
{
if (!it.isEmpty())
{
fValue = it.toDouble();
bIsEmpty = false;
//if office:value="0", let's get the text:p in case this is
//a special case in HasSpecialCaseFormulaText(). If it
//turns out not to be a special case, we'll use the 0 value.
if(fValue == 0.0)
bFormulaTextResult = true;
}
}
break;
case XML_ELEMENT( OFFICE, XML_DATE_VALUE ):
{
if (!it.isEmpty() && rXMLImport.SetNullDateOnUnitConverter())
{
rXMLImport.GetMM100UnitConverter().convertDateTime(fValue, it.toView());
bIsEmpty = false;
}
}
break;
case XML_ELEMENT( OFFICE, XML_TIME_VALUE ):
{
if (!it.isEmpty())
{
::sax::Converter::convertDuration(fValue, it.toView());
bIsEmpty = false;
}
}
break;
case XML_ELEMENT( OFFICE, XML_STRING_VALUE ):
{
if (!it.isEmpty())
{
OSL_ENSURE(!maStringValue, "here should be only one string value");
maStringValue = it.toString();
bIsEmpty = false;
}
}
break;
case XML_ELEMENT( OFFICE , XML_BOOLEAN_VALUE ):
{
if (!it.isEmpty())
{
if ( IsXMLToken( it, XML_TRUE ) )
fValue = 1.0;
else if ( IsXMLToken( it, XML_FALSE ) )
fValue = 0.0;
else
fValue = it.toDouble();
bIsEmpty = false;
}
}
break;
case XML_ELEMENT( TABLE, XML_FORMULA ):
{
if (!it.isEmpty())
{
OSL_ENSURE(!maFormula, "here should be only one formula");
OUString aFormula, aFormulaNmsp;
rXMLImport.ExtractFormulaNamespaceGrammar( aFormula, aFormulaNmsp, eGrammar, it.toString() );
maFormula = FormulaWithNamespace(aFormula, aFormulaNmsp);
}
}
break;
case XML_ELEMENT( OFFICE, XML_CURRENCY ):
xCurrencySymbol = it.toString();
break;
default:
;
}
}
}
if (maFormula)
{
if (nCellType == util::NumberFormat::TEXT)
bFormulaTextResult = true;
if(nCellType == util::NumberFormat::DATETIME)
nCellType = util::NumberFormat::UNDEFINED;
//if bIsEmpty is true at this point, then there is no office value.
//we must get the text:p (even if it is empty) in case this a special
//case in HasSpecialCaseFormulaText().
if(bIsEmpty)
bFormulaTextResult = true;
}
rXMLImport.GetStylesImportHelper()->SetAttributes(std::move(xStyleName), std::move(xCurrencySymbol), nCellType);
}
ScXMLTableRowCellContext::~ScXMLTableRowCellContext()
{
}
void ScXMLTableRowCellContext::LockSolarMutex()
{
if (!bSolarMutexLocked)
{
GetScImport().LockSolarMutex();
bSolarMutexLocked = true;
}
}
namespace {
bool cellExists( const ScDocument& rDoc, const ScAddress& rCellPos )
{
return( rCellPos.Col() >= 0 && rCellPos.Row() >= 0 &&
rCellPos.Col() <= rDoc.MaxCol() && rCellPos.Row() <= rDoc.MaxRow() );
}
}
void ScXMLTableRowCellContext::PushParagraphSpan(std::u16string_view rSpan, const OUString& rStyleName)
{
sal_Int32 nBegin = maParagraph.getLength();
sal_Int32 nEnd = nBegin + rSpan.size();
maParagraph.append(rSpan);
PushFormat(nBegin, nEnd, rStyleName);
}
void ScXMLTableRowCellContext::PushParagraphField(std::unique_ptr<SvxFieldData> pData, const OUString& rStyleName)
{
mbHasFormatRuns = true;
maFields.push_back(std::make_unique<Field>(std::move(pData)));
Field& rField = *maFields.back();
sal_Int32 nPos = maParagraph.getLength();
maParagraph.append('\1'); // Placeholder text for inserted field item.
rField.maSelection.nStartPara = mnCurParagraph;
rField.maSelection.nEndPara = mnCurParagraph;
rField.maSelection.nStartPos = nPos;
rField.maSelection.nEndPos = nPos+1;
PushFormat(nPos, nPos+1, rStyleName);
}
void ScXMLTableRowCellContext::PushFormat(sal_Int32 nBegin, sal_Int32 nEnd, const OUString& rStyleName)
{
if (rStyleName.isEmpty())
return;
// Get the style information from xmloff.
rtl::Reference<XMLPropertySetMapper> xMapper = GetImport().GetTextImport()->GetTextImportPropertySetMapper()->getPropertySetMapper();
if (!xMapper.is())
// We can't do anything without the mapper.
return;
sal_Int32 nEntryCount = xMapper->GetEntryCount();
SvXMLStylesContext* pAutoStyles = GetImport().GetAutoStyles();
if (!pAutoStyles)
return;
// Style name for text span corresponds with the name of an automatic style.
const XMLPropStyleContext* pStyle = dynamic_cast<const XMLPropStyleContext*>(
pAutoStyles->FindStyleChildContext(XmlStyleFamily::TEXT_TEXT, rStyleName));
if (!pStyle)
// No style by that name found.
return;
const std::vector<XMLPropertyState>& rProps = pStyle->GetProperties();
if (rProps.empty())
return;
const ScXMLEditAttributeMap& rEditAttrMap = GetScImport().GetEditAttributeMap();
mbHasFormatRuns = true;
maFormats.push_back(std::make_unique<ParaFormat>(*mpEditEngine));
ParaFormat& rFmt = *maFormats.back();
rFmt.maSelection.nStartPara = rFmt.maSelection.nEndPara = mnCurParagraph;
rFmt.maSelection.nStartPos = nBegin;
rFmt.maSelection.nEndPos = nEnd;
// Store the used text styles for export.
ScSheetSaveData* pSheetData = rXMLImport.GetScModel()->GetSheetSaveData();
ScAddress aCellPos = rXMLImport.GetTables().GetCurrentCellPos();
pSheetData->AddTextStyle(rStyleName, aCellPos, rFmt.maSelection);
std::unique_ptr<SfxPoolItem> pPoolItem;
sal_uInt16 nLastItemID = EE_CHAR_END + 1;
for (const auto& rProp : rProps)
{
if (rProp.mnIndex == -1 || rProp.mnIndex >= nEntryCount)
continue;
const OUString& rName = xMapper->GetEntryAPIName(rProp.mnIndex);
const ScXMLEditAttributeMap::Entry* pEntry = rEditAttrMap.getEntryByAPIName(rName);
if (!pEntry)
continue;
if (nLastItemID != pEntry->mnItemID && pPoolItem)
{
// Flush the last item when the item ID changes.
rFmt.maItemSet.Put(std::move(pPoolItem));
}
switch (pEntry->mnItemID)
{
case EE_CHAR_FONTINFO:
case EE_CHAR_FONTINFO_CJK:
case EE_CHAR_FONTINFO_CTL:
{
// Font properties need to be consolidated into a single item.
if (!pPoolItem)
pPoolItem.reset(new SvxFontItem(pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_WEIGHT:
case EE_CHAR_WEIGHT_CJK:
case EE_CHAR_WEIGHT_CTL:
{
if (!pPoolItem)
pPoolItem.reset(new SvxWeightItem(WEIGHT_NORMAL, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_FONTHEIGHT:
case EE_CHAR_FONTHEIGHT_CJK:
case EE_CHAR_FONTHEIGHT_CTL:
{
if (!pPoolItem)
pPoolItem.reset(new SvxFontHeightItem(240, 100, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_ITALIC:
case EE_CHAR_ITALIC_CJK:
case EE_CHAR_ITALIC_CTL:
{
if (!pPoolItem)
pPoolItem.reset(new SvxPostureItem(ITALIC_NONE, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_UNDERLINE:
{
if (!pPoolItem)
pPoolItem.reset(new SvxUnderlineItem(LINESTYLE_NONE, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_OVERLINE:
{
if (!pPoolItem)
pPoolItem.reset(new SvxOverlineItem(LINESTYLE_NONE, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_COLOR:
{
if (!pPoolItem)
pPoolItem.reset(new SvxColorItem(pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_WLM:
{
if (!pPoolItem)
pPoolItem.reset(new SvxWordLineModeItem(false, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_STRIKEOUT:
{
if (!pPoolItem)
pPoolItem.reset(new SvxCrossedOutItem(STRIKEOUT_NONE, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_RELIEF:
{
if (!pPoolItem)
pPoolItem.reset(new SvxCharReliefItem(FontRelief::NONE, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_OUTLINE:
{
if (!pPoolItem)
pPoolItem.reset(new SvxContourItem(false, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_SHADOW:
{
if (!pPoolItem)
pPoolItem.reset(new SvxShadowedItem(false, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_KERNING:
{
if (!pPoolItem)
pPoolItem.reset(new SvxKerningItem(0, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_PAIRKERNING:
{
if (!pPoolItem)
pPoolItem.reset(new SvxAutoKernItem(false, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_FONTWIDTH:
{
if (!pPoolItem)
pPoolItem.reset(new SvxCharScaleWidthItem(100, TypedWhichId<SvxCharScaleWidthItem>(pEntry->mnItemID)));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_ESCAPEMENT:
{
if (!pPoolItem)
pPoolItem.reset(new SvxEscapementItem(pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_EMPHASISMARK:
{
if (!pPoolItem)
pPoolItem.reset(new SvxEmphasisMarkItem(FontEmphasisMark::NONE, TypedWhichId<SvxEmphasisMarkItem>(pEntry->mnItemID)));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
case EE_CHAR_LANGUAGE:
case EE_CHAR_LANGUAGE_CJK:
case EE_CHAR_LANGUAGE_CTL:
{
if (!pPoolItem)
pPoolItem.reset(new SvxLanguageItem(LANGUAGE_DONTKNOW, pEntry->mnItemID));
pPoolItem->PutValue(rProp.maValue, pEntry->mnFlag);
}
break;
default:
;
}
nLastItemID = pEntry->mnItemID;
}
if (pPoolItem)
rFmt.maItemSet.Put(std::move(pPoolItem));
}
OUString ScXMLTableRowCellContext::GetFirstParagraph() const
{
if (!maFirstParagraph)
return mpEditEngine->GetText(0);
return *maFirstParagraph;
}
void ScXMLTableRowCellContext::PushParagraphFieldDate(const OUString& rStyleName)
{
PushParagraphField(std::make_unique<SvxDateField>(), rStyleName);
}
void ScXMLTableRowCellContext::PushParagraphFieldSheetName(const OUString& rStyleName)
{
SCTAB nTab = GetScImport().GetTables().GetCurrentCellPos().Tab();
PushParagraphField(std::make_unique<SvxTableField>(nTab), rStyleName);
}
void ScXMLTableRowCellContext::PushParagraphFieldDocTitle(const OUString& rStyleName)
{
PushParagraphField(std::make_unique<SvxFileField>(), rStyleName);
}
void ScXMLTableRowCellContext::PushParagraphFieldURL(
const OUString& rURL, const OUString& rRep, const OUString& rStyleName, const OUString& rTargetFrame)
{
OUString aAbsURL = GetScImport().GetAbsoluteReference(rURL);
std::unique_ptr<SvxURLField> pURLField(new SvxURLField(aAbsURL, rRep, SvxURLFormat::Repr));
pURLField->SetTargetFrame(rTargetFrame);
PushParagraphField(std::move(pURLField), rStyleName);
}
void ScXMLTableRowCellContext::PushParagraphEnd()
{
// EditEngine always has at least one paragraph even when its content is empty.
if (mbEditEngineHasText)
{
if (maFirstParagraph)
{
// Flush the cached first paragraph first.
mpEditEngine->Clear();
mpEditEngine->SetTextCurrentDefaults(*maFirstParagraph);
maFirstParagraph.reset();
}
mpEditEngine->InsertParagraph(mpEditEngine->GetParagraphCount(), maParagraph.makeStringAndClear());
}
else if (mbHasFormatRuns || ScStringUtil::isMultiline(maParagraph))
{
mpEditEngine->Clear();
mpEditEngine->SetTextCurrentDefaults(maParagraph.makeStringAndClear());
mbEditEngineHasText = true;
}
else if (mnCurParagraph == 0)
{
maFirstParagraph = maParagraph.makeStringAndClear();
mbEditEngineHasText = true;
}
++mnCurParagraph;
}
uno::Reference< xml::sax::XFastContextHandler > SAL_CALL ScXMLTableRowCellContext::createFastChildContext(
sal_Int32 nElement, const uno::Reference< xml::sax::XFastAttributeList >& xAttrList )
{
SvXMLImportContext *pContext = nullptr;
sax_fastparser::FastAttributeList *pAttribList =
&sax_fastparser::castToFastAttributeList( xAttrList );
bool bTextP(false);
switch (nElement)
{
case XML_ELEMENT( TEXT, XML_P ):
{
bIsEmpty = false;
bTextP = true;
pContext = new ScXMLCellTextParaContext(rXMLImport, *this);
}
break;
case XML_ELEMENT( TABLE, XML_SUB_TABLE ):
{
SAL_WARN("sc", "ScXMLTableRowCellContext::createFastChildContext: subtables are not supported");
}
break;
case XML_ELEMENT( TABLE, XML_DETECTIVE ):
{
bIsEmpty = false;
if (!pDetectiveObjVec)
pDetectiveObjVec.reset( new ScMyImpDetectiveObjVec );
pContext = new ScXMLDetectiveContext(
rXMLImport, pDetectiveObjVec.get() );
}
break;
case XML_ELEMENT( TABLE, XML_CELL_RANGE_SOURCE ):
{
bIsEmpty = false;
if (!pCellRangeSource)
pCellRangeSource.reset(new ScMyImpCellRangeSource());
pContext = new ScXMLCellRangeSourceContext(
rXMLImport, pAttribList, pCellRangeSource.get() );
}
break;
case XML_ELEMENT(OFFICE, XML_ANNOTATION):
{
bIsEmpty = false;
OSL_ENSURE(
!mxAnnotationData,
"ScXMLTableRowCellContext::CreateChildContext - multiple annotations in one cell");
mxAnnotationData.reset( new ScXMLAnnotationData );
pContext = new ScXMLAnnotationContext( rXMLImport, nElement,
xAttrList, *mxAnnotationData);
}
break;
}
if (!pContext && !bTextP)
{
ScAddress aCellPos = rXMLImport.GetTables().GetCurrentCellPos();
uno::Reference<drawing::XShapes> xShapes (rXMLImport.GetTables().GetCurrentXShapes());
if (xShapes.is())
{
ScDocument* pDoc = rXMLImport.GetDocument();
if (aCellPos.Col() > pDoc->MaxCol())
aCellPos.SetCol(pDoc->MaxCol());
if (aCellPos.Row() > pDoc->MaxRow())
aCellPos.SetRow(pDoc->MaxRow());
XMLTableShapeImportHelper* pTableShapeImport =
static_cast< XMLTableShapeImportHelper* >( rXMLImport.GetShapeImport().get() );
pTableShapeImport->SetOnTable(false);
pTableShapeImport->SetCell(aCellPos);
pContext = XMLShapeImportHelper::CreateGroupChildContext(
rXMLImport, nElement, xAttrList, xShapes);
if (pContext)
{
bIsEmpty = false;
rXMLImport.ProgressBarIncrement();
}
}
}
return pContext;
}
void ScXMLTableRowCellContext::DoMerge( const ScAddress& rScAddress, const SCCOL nCols, const SCROW nRows )
{
SCCOL mergeToCol = rScAddress.Col() + nCols;
SCROW mergeToRow = rScAddress.Row() + nRows;
ScDocument* pDoc = rXMLImport.GetDocument();
bool bInBounds = rScAddress.Col() <= pDoc->MaxCol() && rScAddress.Row() <= pDoc->MaxRow() &&
mergeToCol <= pDoc->MaxCol() && mergeToRow <= pDoc->MaxRow();
if( bInBounds )
{
pDoc->DoMerge( rScAddress.Col(), rScAddress.Row(),
mergeToCol, mergeToRow, rScAddress.Tab() );
}
}
namespace {
ScValidationMode validationTypeToMode( const sheet::ValidationType eVType )
{
ScValidationMode eMode;
switch( eVType )
{
case sheet::ValidationType_WHOLE: eMode = SC_VALID_WHOLE; break;
case sheet::ValidationType_DECIMAL: eMode = SC_VALID_DECIMAL; break;
case sheet::ValidationType_DATE: eMode = SC_VALID_DATE; break;
case sheet::ValidationType_TIME: eMode = SC_VALID_TIME; break;
case sheet::ValidationType_TEXT_LEN: eMode = SC_VALID_TEXTLEN; break;
case sheet::ValidationType_LIST: eMode = SC_VALID_LIST; break;
case sheet::ValidationType_CUSTOM: eMode = SC_VALID_CUSTOM; break;
default: eMode = SC_VALID_ANY; break;
}
return eMode;
}
ScValidErrorStyle validAlertToValidError( const sheet::ValidationAlertStyle eVAlertStyle )
{
ScValidErrorStyle eVErrStyle;
switch( eVAlertStyle )
{
case sheet::ValidationAlertStyle_STOP: eVErrStyle = SC_VALERR_STOP; break;
case sheet::ValidationAlertStyle_WARNING: eVErrStyle = SC_VALERR_WARNING; break;
case sheet::ValidationAlertStyle_MACRO: eVErrStyle = SC_VALERR_MACRO; break;
default: eVErrStyle = SC_VALERR_INFO; break;
//should INFO be the default? seems to be the most unobtrusive choice.
}
return eVErrStyle;
}
}
void ScXMLTableRowCellContext::SetContentValidation( const ScRange& rScRange )
{
if (!maContentValidationName)
return;
ScDocument* pDoc = rXMLImport.GetDocument();
ScMyImportValidation aValidation;
aValidation.eGrammar1 = aValidation.eGrammar2 = pDoc->GetStorageGrammar();
if( !rXMLImport.GetValidation(*maContentValidationName, aValidation) )
return;
ScValidationData aScValidationData(
validationTypeToMode(aValidation.aValidationType),
ScConditionEntry::GetModeFromApi(aValidation.aOperator),
aValidation.sFormula1, aValidation.sFormula2, *pDoc, ScAddress(),
aValidation.sFormulaNmsp1, aValidation.sFormulaNmsp2,
aValidation.eGrammar1, aValidation.eGrammar2
);
aScValidationData.SetIgnoreBlank( aValidation.bIgnoreBlanks );
aScValidationData.SetCaseSensitive( aValidation.bCaseSensitive );
aScValidationData.SetListType( aValidation.nShowList );
// set strings for error / input even if disabled (and disable afterwards)
aScValidationData.SetInput( aValidation.sInputTitle, aValidation.sInputMessage );
if( !aValidation.bShowInputMessage )
aScValidationData.ResetInput();
aScValidationData.SetError( aValidation.sErrorTitle, aValidation.sErrorMessage, validAlertToValidError(aValidation.aAlertStyle) );
if( !aValidation.bShowErrorMessage )
aScValidationData.ResetError();
if( !aValidation.sBaseCellAddress.isEmpty() )
aScValidationData.SetSrcString( aValidation.sBaseCellAddress );
sal_uInt32 nIndex = pDoc->AddValidationEntry( aScValidationData );
ScPatternAttr aPattern(pDoc->getCellAttributeHelper());
aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALIDDATA, nIndex ) );
if( rScRange.aStart == rScRange.aEnd ) //for a single cell
{
pDoc->ApplyPattern( rScRange.aStart.Col(), rScRange.aStart.Row(),
rScRange.aStart.Tab(), aPattern );
}
else //for repeating cells
{
pDoc->ApplyPatternAreaTab( rScRange.aStart.Col(), rScRange.aStart.Row(),
rScRange.aEnd.Col(), rScRange.aEnd.Row(),
rScRange.aStart.Tab(), aPattern );
}
// is the below still needed?
// For now, any sheet with validity is blocked from stream-copying.
// Later, the validation names could be stored along with the style names.
ScSheetSaveData* pSheetData = GetScImport().GetScModel()->GetSheetSaveData();
pSheetData->BlockSheet( GetScImport().GetTables().GetCurrentSheet() );
}
void ScXMLTableRowCellContext::SetContentValidation( const ScAddress& rCellPos )
{
SetContentValidation( ScRange(rCellPos, rCellPos) );
}
void ScXMLTableRowCellContext::SetAnnotation(const ScAddress& rPos)
{
ScDocument* pDoc = rXMLImport.GetDocument();
if (!pDoc || !mxAnnotationData)
return;
LockSolarMutex();
ScPostIt* pNote = nullptr;
uno::Reference< drawing::XShapes > xShapes = rXMLImport.GetTables().GetCurrentXShapes();
sal_Int32 nOldShapeCount = xShapes.is() ? xShapes->getCount() : 0;
OSL_ENSURE( !mxAnnotationData->mxShape.is() || mxAnnotationData->mxShapes.is(),
"ScXMLTableRowCellContext::SetAnnotation - shape without drawing page" );
if( mxAnnotationData->mxShape.is() && mxAnnotationData->mxShapes.is() )
{
OSL_ENSURE( mxAnnotationData->mxShapes.get() == xShapes.get(), "ScXMLTableRowCellContext::SetAnnotation - different drawing pages" );
/* Don't attempt to get the style from the SdrObject,
as it might be a default assigned one. */
auto pStyle = rXMLImport.GetShapeImport()->GetAutoStylesContext()->FindStyleChildContext(
XmlStyleFamily::SD_GRAPHICS_ID, mxAnnotationData->maStyleName);
OUString aStyleName = pStyle ? pStyle->GetParentName() : mxAnnotationData->maStyleName;
assert(!rXMLImport.GetShapeImport()->GetAutoStylesContext()->FindStyleChildContext(
XmlStyleFamily::SD_GRAPHICS_ID, aStyleName));
aStyleName = rXMLImport.GetStyleDisplayName(XmlStyleFamily::SD_GRAPHICS_ID, aStyleName);
SdrObject* pObject = SdrObject::getSdrObjectFromXShape(mxAnnotationData->mxShape);
OSL_ENSURE( pObject, "ScXMLTableRowCellContext::SetAnnotation - cannot get SdrObject from shape" );
/* Try to reuse the drawing object already created (but only if the
note is visible, and the object is a caption object). */
if( mxAnnotationData->mbShown && mxAnnotationData->mbUseShapePos && !comphelper::LibreOfficeKit::isActive())
{
if( SdrCaptionObj* pCaption = dynamic_cast< SdrCaptionObj* >( pObject ) )
{
OSL_ENSURE( !pCaption->GetLogicRect().IsEmpty(), "ScXMLTableRowCellContext::SetAnnotation - invalid caption rectangle" );
// create the cell note with the caption object
pNote = ScNoteUtil::CreateNoteFromCaption( *pDoc, rPos, pCaption, !aStyleName.isEmpty() );
// forget pointer to object (do not create note again below)
pObject = nullptr;
}
}
// drawing object has not been used to create a note -> use shape data
if( pObject )
{
// rescue settings from drawing object before the shape is removed
SfxItemSet aItemSet( pObject->GetMergedItemSet() );
std::optional<OutlinerParaObject> pOutlinerObj;
if (auto p = pObject->GetOutlinerParaObject())
pOutlinerObj = *p;
tools::Rectangle aCaptionRect;
if( mxAnnotationData->mbUseShapePos )
aCaptionRect = pObject->GetLogicRect();
// remove the shape from the drawing page, this invalidates pObject
mxAnnotationData->mxShapes->remove( mxAnnotationData->mxShape );
pObject = nullptr;
// update current number of existing objects
if( xShapes.is() )
nOldShapeCount = xShapes->getCount();
// an outliner object is required (empty note captions not allowed)
if (pOutlinerObj)
{
// create cell note with all data from drawing object
if(!comphelper::LibreOfficeKit::isActive())
{
pNote = ScNoteUtil::CreateNoteFromObjectData( *pDoc, rPos,
std::move(aItemSet), aStyleName, *pOutlinerObj,
aCaptionRect, mxAnnotationData->mbShown );
}
else
{
pNote = ScNoteUtil::CreateNoteFromObjectData( *pDoc, rPos,
std::move(aItemSet), aStyleName, *pOutlinerObj,
aCaptionRect, false );
}
}
}
}
else if( !mxAnnotationData->maSimpleText.isEmpty() )
{
// create note from simple text
pNote = ScNoteUtil::CreateNoteFromString( *pDoc, rPos,
mxAnnotationData->maSimpleText, mxAnnotationData->mbShown, false );
}
// set author and date
if( pNote )
{
double fDate;
if (rXMLImport.GetMM100UnitConverter().convertDateTime(fDate, mxAnnotationData->maCreateDate))
{
SvNumberFormatter* pNumForm = pDoc->GetFormatTable();
// Date string is in format ISO 8601 inside <dc:date>
// i.e: 2024-08-14 or 2024-08-14T23:55:06 or 20240814T235506
// Time always has prefix 'T'
sal_uInt32 nfIndex = pNumForm->GetFormatIndex(
mxAnnotationData->maCreateDate.indexOf('T') > -1 ? NF_DATETIME_SYS_DDMMYYYY_HHMMSS
: NF_DATE_SYS_DDMMYYYY,
LANGUAGE_SYSTEM);
OUString aDate;
const Color* pColor = nullptr;
pNumForm->GetOutputString( fDate, nfIndex, aDate, &pColor );
pNote->SetDate( aDate );
}
pNote->SetAuthor( mxAnnotationData->maAuthor );
}
// register a shape that has been newly created in the ScNoteUtil functions
if( xShapes.is() && (nOldShapeCount < xShapes->getCount()) )
{
uno::Reference< drawing::XShape > xShape;
rXMLImport.GetShapeImport()->shapeWithZIndexAdded( xShape, xShapes->getCount() );
}
// store the style names for stream copying
ScSheetSaveData* pSheetData = rXMLImport.GetScModel()->GetSheetSaveData();
pSheetData->HandleNoteStyles( mxAnnotationData->maStyleName, mxAnnotationData->maTextStyle, rPos );
for (const auto& rContentStyle : mxAnnotationData->maContentStyles)
{
pSheetData->AddNoteContentStyle( rContentStyle.mnFamily, rContentStyle.maName, rPos, rContentStyle.maSelection );
}
}
// core implementation
void ScXMLTableRowCellContext::SetDetectiveObj( const ScAddress& rPosition )
{
ScDocument* pDoc = rXMLImport.GetDocument();
if( !pDoc || !cellExists(*pDoc, rPosition) || !pDetectiveObjVec || pDetectiveObjVec->empty() )
return;
LockSolarMutex();
ScDetectiveFunc aDetFunc( *pDoc, rPosition.Tab() );
uno::Reference<container::XIndexAccess> xShapesIndex = rXMLImport.GetTables().GetCurrentXShapes(); // make draw page
for(const auto& rDetectiveObj : *pDetectiveObjVec)
{
aDetFunc.InsertObject( rDetectiveObj.eObjType, rPosition, rDetectiveObj.aSourceRange, rDetectiveObj.bHasError );
if (xShapesIndex.is())
{
sal_Int32 nShapes = xShapesIndex->getCount();
uno::Reference < drawing::XShape > xShape;
rXMLImport.GetShapeImport()->shapeWithZIndexAdded(xShape, nShapes);
}
}
}
// core implementation
void ScXMLTableRowCellContext::SetCellRangeSource( const ScAddress& rPosition )
{
ScDocument* pDoc = rXMLImport.GetDocument();
if( !pDoc || !cellExists(*pDoc, rPosition) || !pCellRangeSource || pCellRangeSource->sSourceStr.isEmpty() ||
pCellRangeSource->sFilterName.isEmpty() || pCellRangeSource->sURL.isEmpty() )
return;
LockSolarMutex();
ScRange aDestRange( rPosition.Col(), rPosition.Row(), rPosition.Tab(),
rPosition.Col() + static_cast<SCCOL>(pCellRangeSource->nColumns - 1),
rPosition.Row() + static_cast<SCROW>(pCellRangeSource->nRows - 1), rPosition.Tab() );
OUString sFilterName( pCellRangeSource->sFilterName );
OUString sSourceStr( pCellRangeSource->sSourceStr );
ScAreaLink* pLink = new ScAreaLink( pDoc->GetDocumentShell(), pCellRangeSource->sURL,
sFilterName, pCellRangeSource->sFilterOptions, sSourceStr, aDestRange, pCellRangeSource->nRefresh );
sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager();
pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, pCellRangeSource->sURL, &sFilterName, &sSourceStr );
}
void ScXMLTableRowCellContext::SetFormulaCell(ScFormulaCell* pFCell) const
{
if(!pFCell)
return;
bool bMayForceNumberformat = true;
if(mbErrorValue)
{
// don't do anything here
// we need to recalc anyway
}
else if( bFormulaTextResult && maStringValue )
{
if( !IsPossibleErrorString() )
{
ScDocument* pDoc = rXMLImport.GetDocument();
pFCell->SetHybridString(pDoc->GetSharedStringPool().intern(*maStringValue));
pFCell->ResetDirty();
// A General format doesn't force any other format for a string
// result, don't attempt to recalculate this later.
bMayForceNumberformat = false;
}
}
else if (std::isfinite(fValue))
{
pFCell->SetHybridDouble(fValue);
if (mbPossibleEmptyDisplay && fValue == 0.0)
{
// Needs to be recalculated to propagate, otherwise would be
// propagated as empty string. So don't ResetDirty().
pFCell->SetHybridEmptyDisplayedAsString();
}
else
pFCell->ResetDirty();
}
if (bMayForceNumberformat)
// Re-calculate to get number format only when style is not set.
pFCell->SetNeedNumberFormat(!mbHasStyle);
}
void ScXMLTableRowCellContext::PutTextCell( const ScAddress& rCurrentPos,
const SCCOL nCurrentCol, const ::std::optional< OUString >& pOUText )
{
ScDocument* pDoc = rXMLImport.GetDocument();
bool bDoIncrement = true;
//matrix reference cells that contain text formula results;
//cell was already put in document, just need to set text here.
if( pDoc && rXMLImport.GetTables().IsPartOfMatrix(rCurrentPos) )
{
ScRefCellValue aCell(*pDoc, rCurrentPos);
bDoIncrement = aCell.getType() == CELLTYPE_FORMULA;
if ( bDoIncrement )
{
ScFormulaCell* pFCell = aCell.getFormula();
OUString aCellString;
if (maStringValue)
aCellString = *maStringValue;
else if (mbEditEngineHasText)
aCellString = GetFirstParagraph();
else if ( nCurrentCol > 0 && pOUText && !pOUText->isEmpty() )
aCellString = *pOUText;
else
bDoIncrement = false;
if(mbErrorValue)
bDoIncrement = false;
if(!aCellString.isEmpty())
{
if (bDoIncrement && !IsPossibleErrorString() && pFCell)
{
pFCell->SetHybridString(pDoc->GetSharedStringPool().intern(aCellString));
pFCell->ResetDirty();
}
else
{
ScAddress aTopLeftMatrixCell;
if (pFCell && pFCell->GetMatrixOrigin(*pDoc, aTopLeftMatrixCell))
{
ScFormulaCell* pMatrixCell = pDoc->GetFormulaCell(aTopLeftMatrixCell);
if (pMatrixCell)
pMatrixCell->SetDirty();
}
else
SAL_WARN("sc", "matrix cell without matrix");
}
}
}
}
else //regular text cells
{
ScDocumentImport& rDoc = rXMLImport.GetDoc();
if (maStringValue)
{
rDoc.setStringCell(rCurrentPos, *maStringValue);
}
else if (mbEditEngineHasText)
{
if (maFirstParagraph)
{
// This is a normal text without format runs.
rDoc.setStringCell(rCurrentPos, *maFirstParagraph);
}
else
{
// This text either has format runs, has field(s), or consists of multiple lines.
for (const auto& rxFormat : maFormats)
mpEditEngine->QuickSetAttribs(rxFormat->maItemSet, rxFormat->maSelection);
for (const auto& rxField : maFields)
mpEditEngine->QuickInsertField(SvxFieldItem(*rxField->mpData, EE_FEATURE_FIELD), rxField->maSelection);
// This edit engine uses the SfxItemPool instance returned
// from pDoc->GetEditPool() to create the text object, which
// is a prerequisite for using this constructor of ScEditCell.
rDoc.setEditCell(rCurrentPos, mpEditEngine->CreateTextObject());
}
}
else if ( nCurrentCol > 0 && pOUText && !pOUText->isEmpty() )
{
rDoc.setStringCell(rCurrentPos, *pOUText);
}
else
bDoIncrement = false;
}
// #i56027# This is about setting simple text, not edit cells,
// so ProgressBarIncrement must be called with bEditCell = FALSE.
// Formatted text that is put into the cell by the child context
// is handled in AddCellsToTable() (bIsEmpty is true then).
if (bDoIncrement)
rXMLImport.ProgressBarIncrement();
}
void ScXMLTableRowCellContext::PutValueCell( const ScAddress& rCurrentPos )
{
//matrix reference cells that contain value formula results;
//cell was already put in document, just need to set value here.
if( rXMLImport.GetTables().IsPartOfMatrix(rCurrentPos) )
{
ScRefCellValue aCell(*rXMLImport.GetDocument(), rCurrentPos);
if (aCell.getType() == CELLTYPE_FORMULA)
{
ScFormulaCell* pFCell = aCell.getFormula();
SetFormulaCell(pFCell);
if (pFCell)
pFCell->SetNeedNumberFormat( true );
}
}
else //regular value cell
{
// fdo#62250 absent values are not NaN, set to 0.0
// PutValueCell() is called only for a known cell value type,
// bIsEmpty==false in all these cases, no sense to check it here.
if (!std::isfinite( fValue))
fValue = 0.0;
// #i62435# Initialize the value cell's script type if the default
// style's number format is latin-only. If the cell uses a different
// format, the script type will be reset when the style is applied.
rXMLImport.GetDoc().setNumericCell(rCurrentPos, fValue);
}
rXMLImport.ProgressBarIncrement();
}
namespace {
bool isEmptyOrNote( const ScDocument* pDoc, const ScAddress& rCurrentPos )
{
CellType eType = pDoc->GetCellType(rCurrentPos);
return (eType == CELLTYPE_NONE);
}
}
void ScXMLTableRowCellContext::AddTextAndValueCell( const ScAddress& rCellPos,
const ::std::optional< OUString >& pOUText, ScAddress& rCurrentPos )
{
ScDocument* pDoc = rXMLImport.GetDocument();
ScMyTables& rTables = rXMLImport.GetTables();
bool bWasEmpty = bIsEmpty;
for (SCCOL i = 0; i < nColsRepeated; ++i)
{
rCurrentPos.SetCol( rCellPos.Col() + i );
// it makes no sense to import data after the last supported column
// fdo#58539 & gnome#627150
if(rCurrentPos.Col() > pDoc->MaxCol())
{
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_COLUMN_OVERFLOW);
break;
}
if (i > 0)
rTables.AddColumn(false);
if (!bIsEmpty)
{
for (SCROW j = 0; j < nRepeatedRows; ++j)
{
rCurrentPos.SetRow( rCellPos.Row() + j );
// it makes no sense to import data after last supported row
// fdo#58539 & gnome#627150
if(rCurrentPos.Row() > pDoc->MaxRow())
{
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_ROW_OVERFLOW);
break;
}
if( (rCurrentPos.Col() == 0) && (j > 0) )
{
rTables.AddRow();
rTables.AddColumn(false);
}
if( cellExists(*pDoc, rCurrentPos) )
{
if( !bIsCovered || isEmptyOrNote(pDoc, rCurrentPos) )
{
switch (nCellType)
{
case util::NumberFormat::TEXT:
{
PutTextCell( rCurrentPos, i, pOUText );
}
break;
case util::NumberFormat::NUMBER:
case util::NumberFormat::PERCENT:
case util::NumberFormat::CURRENCY:
case util::NumberFormat::TIME:
case util::NumberFormat::DATETIME:
case util::NumberFormat::LOGICAL:
{
PutValueCell( rCurrentPos );
}
break;
default:
{
OSL_FAIL("no cell type given");
}
break;
}
}
SetAnnotation( rCurrentPos );
SetDetectiveObj( rCurrentPos );
SetCellRangeSource( rCurrentPos );
}
else
{
if (!bWasEmpty || mxAnnotationData)
{
if (rCurrentPos.Row() > pDoc->MaxRow())
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_ROW_OVERFLOW);
else
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_COLUMN_OVERFLOW);
}
}
}
}
else
{
if ((i == 0) && (rCellPos.Col() == 0))
{
for (sal_Int32 j = 1; j < nRepeatedRows; ++j)
{
rTables.AddRow();
rTables.AddColumn(false);
}
}
// if nothing else useful can happen in the loop, just exit early
if (i != 0 && bIsEmpty && rCurrentPos.Row() != 0)
{
rCurrentPos.SetCol( rCellPos.Col() + nColsRepeated - 1 );
rTables.AddColumns(nColsRepeated - i - 1);
break;
}
}
}
}
bool ScXMLTableRowCellContext::CellsAreRepeated() const
{
return ( (nColsRepeated > 1) || (nRepeatedRows > 1) );
}
namespace {
// from ScCellObj::GetOutputString_Imp(). all of it may not be necessary.
OUString getOutputString( ScDocument* pDoc, const ScAddress& aCellPos )
{
if (!pDoc)
return OUString();
ScRefCellValue aCell(*pDoc, aCellPos);
switch (aCell.getType())
{
case CELLTYPE_NONE:
return OUString();
case CELLTYPE_EDIT:
{
// GetString on EditCell replaces linebreaks with spaces;
// however here we need line breaks
const EditTextObject* pData = aCell.getEditText();
EditEngine& rEngine = pDoc->GetEditEngine();
rEngine.SetText(*pData);
return rEngine.GetText();
// also don't format EditCells per NumberFormatter
}
break;
default:
{
// like in GetString for document (column)
const Color* pColor;
sal_uInt32 nNumFmt = pDoc->GetNumberFormat(ScRange(aCellPos));
return ScCellFormat::GetString(aCell, nNumFmt, &pColor, nullptr, *pDoc);
}
}
}
}
void ScXMLTableRowCellContext::AddNonFormulaCell( const ScAddress& rCellPos )
{
::std::optional< OUString > pOUText;
ScDocument* pDoc = rXMLImport.GetDocument();
if( nCellType == util::NumberFormat::TEXT )
{
if( !bIsEmpty && !maStringValue && !mbEditEngineHasText && cellExists(*pDoc, rCellPos) && CellsAreRepeated() )
pOUText = getOutputString(pDoc, rCellPos);
if (!mbEditEngineHasText && !pOUText && !maStringValue)
bIsEmpty = true;
}
ScAddress aCurrentPos( rCellPos );
if( mxAnnotationData || pDetectiveObjVec || pCellRangeSource ) // has special content
bIsEmpty = false;
AddTextAndValueCell( rCellPos, pOUText, aCurrentPos );
if( CellsAreRepeated() )
{
SCCOL nStartCol( std::min(rCellPos.Col(), pDoc->MaxCol()) );
SCROW nStartRow( std::min(rCellPos.Row(), pDoc->MaxRow()) );
SCCOL nEndCol( std::min<SCCOL>(rCellPos.Col() + nColsRepeated - 1, pDoc->MaxCol()) );
SCROW nEndRow( std::min(rCellPos.Row() + nRepeatedRows - 1, pDoc->MaxRow()) );
ScRange aScRange( nStartCol, nStartRow, rCellPos.Tab(), nEndCol, nEndRow, rCellPos.Tab() );
SetContentValidation( aScRange );
rXMLImport.GetStylesImportHelper()->AddRange( aScRange );
}
else if( cellExists(*pDoc, rCellPos) )
{
rXMLImport.GetStylesImportHelper()->AddCell(rCellPos);
SetContentValidation( rCellPos );
}
}
void ScXMLTableRowCellContext::PutFormulaCell( const ScAddress& rCellPos )
{
ScDocument* pDoc = rXMLImport.GetDocument();
ScDocumentImport& rDocImport = rXMLImport.GetDoc();
const OUString & aText = maFormula->first;
ScExternalRefManager::ApiGuard aExtRefGuard(*pDoc);
if ( aText.isEmpty() )
return;
// temporary formula string as string tokens
std::unique_ptr<ScTokenArray> pCode(new ScTokenArray(*pDoc));
// Check the special case of a single error constant without leading
// '=' and create an error formula cell without tokens.
FormulaError nError = GetScImport().GetFormulaErrorConstant(aText);
if (nError != FormulaError::NONE)
{
pCode->SetCodeError(nError);
}
else
{
// 5.2 and earlier wrote broken "Err:xxx" as formula to designate
// an error formula cell.
if (aText.startsWithIgnoreAsciiCase("Err:") && aText.getLength() <= 9 &&
((nError =
GetScImport().GetFormulaErrorConstant( OUString::Concat("#ERR") + aText.subView(4) + "!")) != FormulaError::NONE))
{
pCode->SetCodeError(nError);
}
else
{
OUString aFormulaNmsp = maFormula->second;
if( eGrammar != formula::FormulaGrammar::GRAM_EXTERNAL )
aFormulaNmsp.clear();
pCode->AssignXMLString( aText, aFormulaNmsp );
rDocImport.getDoc().IncXMLImportedFormulaCount( aText.getLength() );
}
}
ScFormulaCell* pNewCell = new ScFormulaCell(*pDoc, rCellPos, std::move(pCode), eGrammar, ScMatrixMode::NONE);
SetFormulaCell(pNewCell);
rDocImport.setFormulaCell(rCellPos, pNewCell);
}
void ScXMLTableRowCellContext::AddFormulaCell( const ScAddress& rCellPos )
{
ScDocument* pDoc = rXMLImport.GetDocument();
if( cellExists(*pDoc, rCellPos) )
{
SetContentValidation( rCellPos );
SAL_WARN_IF((nColsRepeated != 1) || (nRepeatedRows != 1), "sc", "repeated cells with formula not possible now");
rXMLImport.GetStylesImportHelper()->AddCell(rCellPos);
//add matrix
if(bIsMatrix)
{
if (nMatrixCols > 0 && nMatrixRows > 0)
{
//matrix cells are put in the document, but we must set the
//value/text of each matrix cell later
rXMLImport.GetTables().AddMatrixRange(
rCellPos.Col(), rCellPos.Row(),
std::min<SCCOL>(rCellPos.Col() + nMatrixCols - 1, pDoc->MaxCol()),
std::min<SCROW>(rCellPos.Row() + nMatrixRows - 1, pDoc->MaxRow()),
maFormula->first, maFormula->second, eGrammar);
// Set the value/text of the top-left matrix position in its
// cached result. For import, we only need to set the correct
// matrix geometry and the value type of the top-left element.
ScFormulaCell* pFCell = pDoc->GetFormulaCell(rCellPos);
if (pFCell)
{
ScMatrixRef pMat(new ScMatrix(nMatrixCols, nMatrixRows));
if (bFormulaTextResult && maStringValue)
{
if (!IsPossibleErrorString())
{
pFCell->SetResultMatrix(
nMatrixCols, nMatrixRows, pMat, new formula::FormulaStringToken(
pDoc->GetSharedStringPool().intern( *maStringValue)));
pFCell->ResetDirty();
}
}
else if (std::isfinite(fValue))
{
pFCell->SetResultMatrix(
nMatrixCols, nMatrixRows, pMat, new formula::FormulaDoubleToken(fValue));
pFCell->ResetDirty();
}
}
}
}
else
PutFormulaCell( rCellPos );
SetAnnotation( rCellPos );
SetDetectiveObj( rCellPos );
SetCellRangeSource( rCellPos );
rXMLImport.ProgressBarIncrement();
}
else
{
if (rCellPos.Row() > pDoc->MaxRow())
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_ROW_OVERFLOW);
else
rXMLImport.SetRangeOverflowType(SCWARN_IMPORT_COLUMN_OVERFLOW);
}
}
//There are cases where a formula cell is exported with an office:value of 0 or
//no office:value at all, but the formula cell will have a text:p value which
//contains the intended formula result.
//These cases include when a formula result:
// - is blank
// - has a constant error value beginning with "#" (such as "#VALUE!" or "#N/A")
// - has an "Err:[###]" (where "[###]" is an error number)
// Libreoffice 4.1+ with ODF1.2 extended write however calcext:value-type="error" in that case
void ScXMLTableRowCellContext::HasSpecialCaseFormulaText()
{
if (!mbEditEngineHasText)
return;
const OUString aStr = GetFirstParagraph();
if (mbNewValueType)
{
if (aStr.isEmpty())
mbPossibleEmptyDisplay = true;
return;
}
if (aStr.isEmpty())
{
mbPossibleErrorCell = true;
mbPossibleEmptyDisplay = true;
}
else if (aStr.startsWith("Err:"))
mbPossibleErrorCell = true;
else if (aStr.startsWith("#"))
mbCheckWithCompilerForError = true;
}
bool ScXMLTableRowCellContext::IsPossibleErrorString() const
{
if(mbNewValueType)
return mbErrorValue;
return mbPossibleErrorCell || (mbCheckWithCompilerForError && maStringValue &&
GetScImport().GetFormulaErrorConstant(*maStringValue) != FormulaError::NONE);
}
void SAL_CALL ScXMLTableRowCellContext::endFastElement(sal_Int32 /*nElement*/)
{
HasSpecialCaseFormulaText();
if( bFormulaTextResult && (mbPossibleErrorCell || mbCheckWithCompilerForError) )
{
maStringValue = GetFirstParagraph();
}
ScAddress aCellPos = rXMLImport.GetTables().GetCurrentCellPos();
if( aCellPos.Col() > 0 && nRepeatedRows > 1 )
aCellPos.SetRow( aCellPos.Row() - (nRepeatedRows - 1) );
if( bIsMerged )
DoMerge( aCellPos, nMergedCols - 1, nMergedRows - 1 );
if (maFormula)
AddFormulaCell(aCellPos);
else
AddNonFormulaCell(aCellPos);
//if LockSolarMutex got used, we presumably need to ensure an UnlockSolarMutex
if (bSolarMutexLocked)
{
GetScImport().UnlockSolarMutex();
bSolarMutexLocked = false;
}
bIsMerged = false;
nMergedCols = 1;
nMergedRows = 1;
nColsRepeated = 1;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V560 A part of conditional expression is always true: bIsEmpty.