/* -*- 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 <hintids.hxx>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/table/XCellRange.hpp>
#include <comphelper/servicehelper.hxx>
#include <o3tl/numeric.hxx>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>
#include <svl/itemset.hxx>
#include <svl/numformat.hxx>
#include <svl/zformat.hxx>
#include <sax/tools/converter.hxx>
#include <comphelper/configuration.hxx>
#include <utility>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/namespacemap.hxx>
#include <xmloff/families.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/i18nmap.hxx>
#include <editeng/protitem.hxx>
#include <editeng/lrspitem.hxx>
#include <poolfmt.hxx>
#include <fmtfsize.hxx>
#include <fmtornt.hxx>
#include <fmtfordr.hxx>
#include <doc.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <swtable.hxx>
#include <swtblfmt.hxx>
#include <pam.hxx>
#include <unoprnms.hxx>
#include <unotbl.hxx>
#include <unotextrange.hxx>
#include <cellatr.hxx>
#include <swddetbl.hxx>
#include <ddefld.hxx>
#include <sfx2/linkmgr.hxx>
#include "xmlimp.hxx"
#include "xmltbli.hxx"
#include <vcl/svapp.hxx>
#include <ndtxt.hxx>
#include <unotextcursor.hxx>
#include <SwStyleNameMapper.hxx>
#include <IDocumentSettingAccess.hxx>
#include <algorithm>
#include <vector>
#include <memory>
#include <limits.h>
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::xml::sax;
using namespace ::xmloff::token;
class SwXMLTableCell_Impl
{
OUString m_aStyleName;
OUString m_StringValue;
OUString m_sFormula; // cell formula; valid if length > 0
double m_dValue; // formula value
rtl::Reference<SwXMLTableContext> m_xSubTable;
const SwStartNode *m_pStartNode;
sal_uInt32 m_nRowSpan;
sal_uInt32 m_nColSpan;
bool m_bProtected : 1;
bool m_bHasValue; // determines whether dValue attribute is valid
bool mbCovered;
bool m_bHasStringValue;
public:
SwXMLTableCell_Impl( sal_uInt32 nRSpan=1, sal_uInt32 nCSpan=1 ) :
m_dValue( 0.0 ),
m_pStartNode( nullptr ),
m_nRowSpan( nRSpan ),
m_nColSpan( nCSpan ),
m_bProtected( false ),
m_bHasValue( false ),
mbCovered( false )
, m_bHasStringValue(false)
{}
inline void Set( const OUString& rStyleName,
sal_uInt32 nRSpan, sal_uInt32 nCSpan,
const SwStartNode *pStNd, SwXMLTableContext *pTable,
bool bProtect,
const OUString* pFormula,
bool bHasValue,
bool bCovered,
double dVal,
OUString const*const pStringValue);
bool IsUsed() const { return m_pStartNode!=nullptr ||
m_xSubTable.is() || m_bProtected;}
sal_uInt32 GetRowSpan() const { return m_nRowSpan; }
void SetRowSpan( sal_uInt32 nSet ) { m_nRowSpan = nSet; }
sal_uInt32 GetColSpan() const { return m_nColSpan; }
void SetStyleName(const OUString& rStyleName) { m_aStyleName = rStyleName; }
const OUString& GetStyleName() const { return m_aStyleName; }
const OUString& GetFormula() const { return m_sFormula; }
double GetValue() const { return m_dValue; }
bool HasValue() const { return m_bHasValue; }
bool IsProtected() const { return m_bProtected; }
bool IsCovered() const { return mbCovered; }
bool HasStringValue() const { return m_bHasStringValue; }
OUString const* GetStringValue() const {
return m_bHasStringValue ? &m_StringValue : nullptr;
}
const SwStartNode *GetStartNode() const { return m_pStartNode; }
inline void SetStartNode( const SwStartNode *pSttNd );
inline SwXMLTableContext *GetSubTable() const;
inline void Dispose();
};
inline void SwXMLTableCell_Impl::Set( const OUString& rStyleName,
sal_uInt32 nRSpan, sal_uInt32 nCSpan,
const SwStartNode *pStNd,
SwXMLTableContext *pTable,
bool bProtect,
const OUString* pFormula,
bool bHasVal,
bool bCov,
double dVal,
OUString const*const pStringValue )
{
m_aStyleName = rStyleName;
m_nRowSpan = nRSpan;
m_nColSpan = nCSpan;
m_pStartNode = pStNd;
m_xSubTable = pTable;
m_dValue = dVal;
m_bHasValue = bHasVal;
mbCovered = bCov;
if (pStringValue)
{
m_StringValue = *pStringValue;
}
m_bHasStringValue = (pStringValue != nullptr);
m_bProtected = bProtect;
// set formula, if valid
if (pFormula != nullptr)
{
m_sFormula = *pFormula;
}
}
inline void SwXMLTableCell_Impl::SetStartNode( const SwStartNode *pSttNd )
{
m_pStartNode = pSttNd;
m_xSubTable = nullptr;
}
inline SwXMLTableContext *SwXMLTableCell_Impl::GetSubTable() const
{
return m_xSubTable.get();
}
inline void SwXMLTableCell_Impl::Dispose()
{
if( m_xSubTable.is() )
m_xSubTable = nullptr;
}
class SwXMLTableRow_Impl
{
OUString m_aStyleName;
OUString m_aDefaultCellStyleName;
std::vector<std::unique_ptr<SwXMLTableCell_Impl>> m_Cells;
bool m_bSplitable;
public:
SwXMLTableRow_Impl( OUString aStyleName, sal_uInt32 nCells,
const OUString *pDfltCellStyleName = nullptr );
inline SwXMLTableCell_Impl *GetCell( sal_uInt32 nCol );
inline void Set( const OUString& rStyleName,
const OUString& rDfltCellStyleName );
void Expand( sal_uInt32 nCells, bool bOneCell );
void SetSplitable( bool bSet ) { m_bSplitable = bSet; }
bool IsSplitable() const { return m_bSplitable; }
const OUString& GetStyleName() const { return m_aStyleName; }
const OUString& GetDefaultCellStyleName() const { return m_aDefaultCellStyleName; }
void Dispose();
};
SwXMLTableRow_Impl::SwXMLTableRow_Impl( OUString aStyleName,
sal_uInt32 nCells,
const OUString *pDfltCellStyleName ) :
m_aStyleName(std::move( aStyleName )),
m_bSplitable( false )
{
if( pDfltCellStyleName )
m_aDefaultCellStyleName = *pDfltCellStyleName;
OSL_ENSURE( nCells <= USHRT_MAX,
"SwXMLTableRow_Impl::SwXMLTableRow_Impl: too many cells" );
if( nCells > USHRT_MAX )
nCells = USHRT_MAX;
for( sal_uInt32 i=0U; i<nCells; ++i )
{
m_Cells.push_back(std::make_unique<SwXMLTableCell_Impl>());
}
}
inline SwXMLTableCell_Impl *SwXMLTableRow_Impl::GetCell( sal_uInt32 nCol )
{
OSL_ENSURE( nCol < USHRT_MAX,
"SwXMLTableRow_Impl::GetCell: column number is too big" );
// #i95726# - some fault tolerance
OSL_ENSURE( nCol < m_Cells.size(),
"SwXMLTableRow_Impl::GetCell: column number is out of bound" );
return nCol < m_Cells.size() ? m_Cells[nCol].get() : nullptr;
}
void SwXMLTableRow_Impl::Expand( sal_uInt32 nCells, bool bOneCell )
{
OSL_ENSURE( nCells <= USHRT_MAX,
"SwXMLTableRow_Impl::Expand: too many cells" );
if( nCells > USHRT_MAX )
nCells = USHRT_MAX;
sal_uInt32 nColSpan = nCells - m_Cells.size();
for (size_t i = m_Cells.size(); i < nCells; ++i)
{
m_Cells.push_back(std::make_unique<SwXMLTableCell_Impl>(
1UL, bOneCell ? nColSpan : 1UL));
nColSpan--;
}
OSL_ENSURE( nCells <= m_Cells.size(),
"SwXMLTableRow_Impl::Expand: wrong number of cells" );
}
inline void SwXMLTableRow_Impl::Set( const OUString& rStyleName,
const OUString& rDfltCellStyleName )
{
m_aStyleName = rStyleName;
m_aDefaultCellStyleName = rDfltCellStyleName;
}
void SwXMLTableRow_Impl::Dispose()
{
for (auto & pCell : m_Cells)
{
pCell->Dispose();
}
}
namespace {
class SwXMLTableCellContext_Impl : public SvXMLImportContext
{
OUString m_aStyleName;
OUString m_sFormula;
OUString m_sSaveParaDefault;
OUString m_StringValue;
rtl::Reference<SwXMLTableContext> m_xMyTable;
double m_fValue;
bool m_bHasValue;
bool m_bHasStringValue;
bool m_bValueTypeIsString;
bool m_bProtect;
sal_uInt32 m_nRowSpan;
sal_uInt32 m_nColSpan;
sal_uInt32 m_nColRepeat;
bool m_bHasTextContent : 1;
bool m_bHasTableContent : 1;
SwXMLTableContext *GetTable() { return m_xMyTable.get(); }
bool HasContent() const { return m_bHasTextContent || m_bHasTableContent; }
inline void InsertContent_();
inline void InsertContent();
inline void InsertContent( SwXMLTableContext *pTable );
public:
SwXMLTableCellContext_Impl(
SwXMLImport& rImport, sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable );
virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList ) override;
virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
SwXMLImport& GetSwImport() { return static_cast<SwXMLImport&>(GetImport()); }
};
/// Handles <table:covered-table-cell>.
class SwXMLCoveredTableCellContext : public SvXMLImportContext
{
public:
SwXMLCoveredTableCellContext(SwXMLImport& rImport,
const Reference<xml::sax::XFastAttributeList>& xAttrList,
SwXMLTableContext& rTable);
};
SwXMLCoveredTableCellContext::SwXMLCoveredTableCellContext(
SwXMLImport& rImport, const Reference<xml::sax::XFastAttributeList>& xAttrList,
SwXMLTableContext& rTable)
: SvXMLImportContext(rImport)
{
OUString aStyleName;
for (auto& rIter : sax_fastparser::castToFastAttributeList(xAttrList))
{
switch (rIter.getToken())
{
case XML_ELEMENT(TABLE, XML_STYLE_NAME):
aStyleName = rIter.toString();
break;
}
}
if (!aStyleName.isEmpty())
{
rTable.InsertCoveredCell(aStyleName);
}
}
}
SwXMLTableCellContext_Impl::SwXMLTableCellContext_Impl(
SwXMLImport& rImport, sal_Int32 /*nElement*/,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable ) :
SvXMLImportContext( rImport ),
m_xMyTable( pTable ),
m_fValue( 0.0 ),
m_bHasValue( false ),
m_bHasStringValue(false),
m_bValueTypeIsString(false),
m_bProtect( false ),
m_nRowSpan( 1 ),
m_nColSpan( 1 ),
m_nColRepeat( 1 ),
m_bHasTextContent( false ),
m_bHasTableContent( false )
{
m_sSaveParaDefault = GetImport().GetTextImport()->GetCellParaStyleDefault();
for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
{
switch( aIter.getToken() )
{
case XML_ELEMENT(TABLE, XML_STYLE_NAME):
m_aStyleName = aIter.toString();
GetImport().GetTextImport()->SetCellParaStyleDefault(m_aStyleName);
break;
case XML_ELEMENT(TABLE, XML_NUMBER_COLUMNS_SPANNED):
m_nColSpan = static_cast<sal_uInt32>(std::max<sal_Int32>(1, aIter.toInt32()));
if (m_nColSpan > 256)
{
SAL_INFO("sw.xml", "ignoring huge table:number-columns-spanned " << m_nColSpan);
m_nColSpan = 1;
}
break;
case XML_ELEMENT(TABLE, XML_NUMBER_ROWS_SPANNED):
m_nRowSpan = static_cast<sal_uInt32>(std::max<sal_Int32>(1, aIter.toInt32()));
if (m_nRowSpan > 8192 || (m_nRowSpan > 256 && comphelper::IsFuzzing()))
{
SAL_INFO("sw.xml", "ignoring huge table:number-rows-spanned " << m_nRowSpan);
m_nRowSpan = 1;
}
break;
case XML_ELEMENT(TABLE, XML_NUMBER_COLUMNS_REPEATED):
m_nColRepeat = static_cast<sal_uInt32>(std::max<sal_Int32>(1, aIter.toInt32()));
if (m_nColRepeat > 256)
{
SAL_INFO("sw.xml", "ignoring huge table:number-columns-repeated " << m_nColRepeat);
m_nColRepeat = 1;
}
break;
case XML_ELEMENT(TABLE, XML_FORMULA):
{
OUString sTmp;
const sal_uInt16 nPrefix2 = GetImport().GetNamespaceMap().
GetKeyByAttrValueQName(aIter.toString(), &sTmp);
m_sFormula = XML_NAMESPACE_OOOW == nPrefix2 ? sTmp : aIter.toString();
}
break;
case XML_ELEMENT(OFFICE, XML_VALUE):
{
// Writer wrongly uses DBL_MAX to flag error but fails to
// check for it after import, so check that here, tdf#139126.
double fTmp;
if (::sax::Converter::convertDouble(fTmp, aIter.toView()) && fTmp < DBL_MAX)
{
m_fValue = fTmp;
m_bHasValue = true;
}
}
break;
case XML_ELEMENT(OFFICE, XML_TIME_VALUE):
{
double fTmp;
if (::sax::Converter::convertDuration(fTmp, aIter.toView()))
{
m_fValue = fTmp;
m_bHasValue = true;
}
}
break;
case XML_ELEMENT(OFFICE, XML_DATE_VALUE):
{
double fTmp;
if (GetImport().GetMM100UnitConverter().convertDateTime(fTmp,
aIter.toView()))
{
m_fValue = fTmp;
m_bHasValue = true;
}
}
break;
case XML_ELEMENT(OFFICE, XML_BOOLEAN_VALUE):
{
bool bTmp(false);
if (::sax::Converter::convertBool(bTmp, aIter.toView()))
{
m_fValue = (bTmp ? 1.0 : 0.0);
m_bHasValue = true;
}
}
break;
case XML_ELEMENT(TABLE, XML_PROTECT): // for backwards compatibility with SRC629 (and before)
case XML_ELEMENT(TABLE, XML_PROTECTED):
{
bool bTmp(false);
if (::sax::Converter::convertBool(bTmp, aIter.toView()))
{
m_bProtect = bTmp;
}
}
break;
case XML_ELEMENT(OFFICE, XML_STRING_VALUE):
{
m_StringValue = aIter.toString();
m_bHasStringValue = true;
}
break;
case XML_ELEMENT(OFFICE, XML_VALUE_TYPE):
{
if ("string" == aIter.toView())
{
m_bValueTypeIsString = true;
}
// ignore other types - it would be correct to require
// matching value-type and $type-value attributes,
// but we've been reading those without checking forever.
}
break;
default:
SAL_WARN("sw", "unknown attribute " << SvXMLImport::getPrefixAndNameFromToken(aIter.getToken()) << "=" << aIter.toString());
}
}
}
inline void SwXMLTableCellContext_Impl::InsertContent_()
{
SwStartNode const*const pStartNode( GetTable()->InsertTableSection(nullptr,
(m_bHasStringValue && m_bValueTypeIsString &&
!m_aStyleName.isEmpty()) ? & m_aStyleName : nullptr) );
GetTable()->InsertCell( m_aStyleName, m_nRowSpan, m_nColSpan,
pStartNode,
nullptr, m_bProtect, &m_sFormula, m_bHasValue, m_fValue,
(m_bHasStringValue && m_bValueTypeIsString) ? &m_StringValue : nullptr);
}
inline void SwXMLTableCellContext_Impl::InsertContent()
{
OSL_ENSURE( !HasContent(), "content already there" );
m_bHasTextContent = true;
InsertContent_();
}
inline void SwXMLTableCellContext_Impl::InsertContent(
SwXMLTableContext *pTable )
{
GetTable()->InsertCell( m_aStyleName, m_nRowSpan, m_nColSpan, nullptr, pTable, m_bProtect );
m_bHasTableContent = true;
}
css::uno::Reference<css::xml::sax::XFastContextHandler> SwXMLTableCellContext_Impl::createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList )
{
SvXMLImportContext *pContext = nullptr;
bool bSubTable = false;
if( nElement == XML_ELEMENT(TABLE, XML_TABLE) )
{
for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
{
if( aIter.getToken() == XML_ELEMENT(TABLE, XML_IS_SUB_TABLE) )
{
if ( IsXMLToken( aIter, XML_TRUE ) )
bSubTable = true;
}
//FIXME: RDFa
}
}
if( bSubTable )
{
if( !HasContent() )
{
SwXMLTableContext *pTableContext =
new SwXMLTableContext( GetSwImport(), GetTable() );
pContext = pTableContext;
if( GetTable()->IsValid() )
InsertContent( pTableContext );
GetTable()->SetHasSubTables( true );
}
}
else
{
if( GetTable()->IsValid() && !HasContent() )
InsertContent();
// fdo#60842: "office:string-value" overrides text content -> no import
if (!(m_bValueTypeIsString && m_bHasStringValue))
{
pContext = GetImport().GetTextImport()->CreateTextChildContext(
GetImport(), nElement, xAttrList,
XMLTextType::Cell );
}
}
return pContext;
}
void SwXMLTableCellContext_Impl::endFastElement(sal_Int32 )
{
if( GetTable()->IsValid() )
{
if( m_bHasTextContent )
{
GetImport().GetTextImport()->DeleteParagraph();
if( m_nColRepeat > 1 && m_nColSpan == 1 )
{
// The original text is invalid after deleting the last
// paragraph
Reference < XTextCursor > xSrcTextCursor =
GetImport().GetTextImport()->GetText()->createTextCursor();
xSrcTextCursor->gotoEnd( true );
// Until we have an API for copying we have to use the core.
OTextCursorHelper *pSrcTextCursor = dynamic_cast<OTextCursorHelper*>(xSrcTextCursor.get());
assert(pSrcTextCursor && "SwXTextCursor missing");
SwDoc *pDoc = pSrcTextCursor->GetDoc();
const SwPaM *pSrcPaM = pSrcTextCursor->GetPaM();
while( m_nColRepeat > 1 && GetTable()->IsInsertCellPossible() )
{
InsertContent_();
OTextCursorHelper *pDstTextCursor = dynamic_cast<OTextCursorHelper*>(GetImport().GetTextImport()->GetCursor().get());
assert(pDstTextCursor && "SwXTextCursor missing");
SwPaM aSrcPaM(*pSrcPaM->GetMark(), *pSrcPaM->GetPoint());
SwPosition aDstPos( *pDstTextCursor->GetPaM()->GetPoint() );
pDoc->getIDocumentContentOperations().CopyRange(aSrcPaM, aDstPos, SwCopyFlags::CheckPosInFly);
m_nColRepeat--;
}
}
}
else if( !m_bHasTableContent )
{
InsertContent();
if( m_nColRepeat > 1 && m_nColSpan == 1 )
{
while( m_nColRepeat > 1 && GetTable()->IsInsertCellPossible() )
{
InsertContent_();
m_nColRepeat--;
}
}
}
}
GetImport().GetTextImport()->SetCellParaStyleDefault(m_sSaveParaDefault);
}
namespace {
class SwXMLTableColContext_Impl : public SvXMLImportContext
{
rtl::Reference<SwXMLTableContext> m_xMyTable;
SwXMLTableContext *GetTable() { return m_xMyTable.get(); }
public:
SwXMLTableColContext_Impl(
SwXMLImport& rImport,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable );
SwXMLImport& GetSwImport() { return static_cast<SwXMLImport&>(GetImport()); }
};
}
SwXMLTableColContext_Impl::SwXMLTableColContext_Impl(
SwXMLImport& rImport,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable ) :
SvXMLImportContext( rImport ),
m_xMyTable( pTable )
{
sal_uInt32 nColRep = 1;
OUString aStyleName, aDfltCellStyleName;
for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
{
switch (aIter.getToken())
{
case XML_ELEMENT(TABLE, XML_STYLE_NAME):
aStyleName = aIter.toString();
break;
case XML_ELEMENT(TABLE, XML_NUMBER_COLUMNS_REPEATED):
{
nColRep = static_cast<sal_uInt32>(std::max<sal_Int32>(1, aIter.toInt32()));
if (nColRep > 256)
{
SAL_INFO("sw.xml", "ignoring huge table:number-columns-repeated " << nColRep);
nColRep = 1;
}
break;
}
case XML_ELEMENT(TABLE, XML_DEFAULT_CELL_STYLE_NAME):
aDfltCellStyleName = aIter.toString();
break;
case XML_ELEMENT(XML, XML_ID):
{
//FIXME where to put this??? columns do not actually exist in writer...
break;
}
default:
XMLOFF_WARN_UNKNOWN("sw", aIter);
}
}
sal_Int32 nWidth = MINLAY;
bool bRelWidth = true;
if( !aStyleName.isEmpty() )
{
const SwFormatFrameSize *pSize;
const SfxItemSet *pAutoItemSet = nullptr;
if( GetSwImport().FindAutomaticStyle(
XmlStyleFamily::TABLE_COLUMN,
aStyleName, &pAutoItemSet ) &&
pAutoItemSet &&
(pSize = pAutoItemSet->GetItemIfSet( RES_FRM_SIZE, false )) )
{
nWidth = pSize->GetWidth();
bRelWidth = SwFrameSize::Variable == pSize->GetHeightSizeType();
}
}
if( nWidth )
{
while( nColRep-- && GetTable()->IsInsertColPossible() )
GetTable()->InsertColumn( nWidth, bRelWidth, &aDfltCellStyleName );
}
}
namespace {
class SwXMLTableColsContext_Impl : public SvXMLImportContext
{
rtl::Reference<SwXMLTableContext> m_xMyTable;
SwXMLTableContext *GetTable() { return m_xMyTable.get(); }
public:
SwXMLTableColsContext_Impl(
SwXMLImport& rImport,
SwXMLTableContext *pTable );
virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override;
SwXMLImport& GetSwImport() { return static_cast<SwXMLImport&>(GetImport()); }
};
}
SwXMLTableColsContext_Impl::SwXMLTableColsContext_Impl(
SwXMLImport& rImport,
SwXMLTableContext *pTable ) :
SvXMLImportContext( rImport ),
m_xMyTable( pTable )
{
}
css::uno::Reference< css::xml::sax::XFastContextHandler > SwXMLTableColsContext_Impl::createFastChildContext(
sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList )
{
SvXMLImportContext *pContext = nullptr;
if( nElement == XML_ELEMENT(TABLE, XML_TABLE_COLUMN) &&
GetTable()->IsInsertColPossible() )
pContext = new SwXMLTableColContext_Impl( GetSwImport(), xAttrList, GetTable() );
else
XMLOFF_WARN_UNKNOWN_ELEMENT("sw", nElement);
return pContext;
}
namespace {
class SwXMLTableRowContext_Impl : public SvXMLImportContext
{
rtl::Reference<SwXMLTableContext> m_xMyTable;
sal_uInt32 m_nRowRepeat;
SwXMLTableContext *GetTable() { return m_xMyTable.get(); }
public:
SwXMLTableRowContext_Impl(
SwXMLImport& rImport, sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable, bool bInHead=false );
virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList ) override;
virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
SwXMLImport& GetSwImport() { return static_cast<SwXMLImport&>(GetImport()); }
};
}
SwXMLTableRowContext_Impl::SwXMLTableRowContext_Impl( SwXMLImport& rImport,
sal_Int32 /*nElement*/,
const Reference< xml::sax::XFastAttributeList > & xAttrList,
SwXMLTableContext *pTable,
bool bInHead ) :
SvXMLImportContext( rImport ),
m_xMyTable( pTable ),
m_nRowRepeat( 1 )
{
OUString aStyleName, aDfltCellStyleName;
for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
{
switch(aIter.getToken())
{
case XML_ELEMENT(TABLE, XML_STYLE_NAME):
aStyleName = aIter.toString();
break;
case XML_ELEMENT(STYLE, XML_NUMBER_ROWS_REPEATED):
{
m_nRowRepeat = static_cast<sal_uInt32>(std::max<sal_Int32>(1, aIter.toInt32()));
if (m_nRowRepeat > 8192 || (m_nRowRepeat > 256 && comphelper::IsFuzzing()))
{
SAL_INFO("sw.xml", "ignoring huge table:number-rows-repeated " << m_nRowRepeat);
m_nRowRepeat = 1;
}
break;
}
case XML_ELEMENT(STYLE, XML_DEFAULT_CELL_STYLE_NAME):
aDfltCellStyleName = aIter.toString();
break;
case XML_ELEMENT(XML, XML_ID):
break;
default:
XMLOFF_WARN_UNKNOWN("sw", aIter);
}
}
if( GetTable()->IsValid() )
GetTable()->InsertRow( aStyleName, aDfltCellStyleName, bInHead );
}
void SwXMLTableRowContext_Impl::endFastElement(sal_Int32 )
{
if( GetTable()->IsValid() )
{
GetTable()->FinishRow();
if( m_nRowRepeat > 1 )
GetTable()->InsertRepRows( m_nRowRepeat );
}
}
css::uno::Reference<css::xml::sax::XFastContextHandler> SwXMLTableRowContext_Impl::createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList )
{
SvXMLImportContext *pContext = nullptr;
if( nElement == XML_ELEMENT(TABLE, XML_TABLE_CELL) ||
nElement == XML_ELEMENT(LO_EXT, XML_TABLE_CELL) )
{
if( !GetTable()->IsValid() || GetTable()->IsInsertCellPossible() )
pContext = new SwXMLTableCellContext_Impl( GetSwImport(), nElement,
xAttrList,
GetTable() );
}
else if( nElement == XML_ELEMENT(TABLE, XML_COVERED_TABLE_CELL) ||
nElement == XML_ELEMENT(LO_EXT, XML_COVERED_TABLE_CELL) )
{
if (GetTable()->IsValid() && GetTable()->IsInsertCoveredCellPossible())
{
pContext = new SwXMLCoveredTableCellContext(GetSwImport(), xAttrList, *GetTable());
}
else
{
pContext = new SvXMLImportContext(GetImport());
}
}
else
SAL_WARN("sw", "unknown element " << SvXMLImport::getPrefixAndNameFromToken(nElement));
return pContext;
}
namespace {
class SwXMLTableRowsContext_Impl : public SvXMLImportContext
{
rtl::Reference<SwXMLTableContext> m_xMyTable;
bool m_bHeader;
SwXMLTableContext *GetTable() { return m_xMyTable.get(); }
public:
SwXMLTableRowsContext_Impl( SwXMLImport& rImport,
SwXMLTableContext *pTable,
bool bHead );
virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList ) override;
SwXMLImport& GetSwImport() { return static_cast<SwXMLImport&>(GetImport()); }
};
}
SwXMLTableRowsContext_Impl::SwXMLTableRowsContext_Impl( SwXMLImport& rImport,
SwXMLTableContext *pTable,
bool bHead ) :
SvXMLImportContext( rImport ),
m_xMyTable( pTable ),
m_bHeader( bHead )
{
}
css::uno::Reference<css::xml::sax::XFastContextHandler> SwXMLTableRowsContext_Impl::createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList )
{
if( nElement== XML_ELEMENT(TABLE, XML_TABLE_ROW ) &&
GetTable()->IsInsertRowPossible() )
return new SwXMLTableRowContext_Impl( GetSwImport(), nElement,
xAttrList,
GetTable(),
m_bHeader );
SAL_WARN("sw", "unknown element " << SvXMLImport::getPrefixAndNameFromToken(nElement));
return nullptr;
}
class SwXMLDDETableContext_Impl : public SvXMLImportContext
{
OUString m_sConnectionName;
OUString m_sDDEApplication;
OUString m_sDDEItem;
OUString m_sDDETopic;
bool m_bIsAutomaticUpdate;
public:
SwXMLDDETableContext_Impl(SwXMLImport& rImport);
virtual void SAL_CALL startFastElement(
sal_Int32 nElement,
const Reference<xml::sax::XFastAttributeList> & xAttrList) override;
OUString& GetConnectionName() { return m_sConnectionName; }
OUString& GetDDEApplication() { return m_sDDEApplication; }
OUString& GetDDEItem() { return m_sDDEItem; }
OUString& GetDDETopic() { return m_sDDETopic; }
bool GetIsAutomaticUpdate() const { return m_bIsAutomaticUpdate; }
};
SwXMLDDETableContext_Impl::SwXMLDDETableContext_Impl(SwXMLImport& rImport) :
SvXMLImportContext(rImport),
m_bIsAutomaticUpdate(false)
{
}
void SwXMLDDETableContext_Impl::startFastElement(
sal_Int32 /*nElement*/,
const Reference<xml::sax::XFastAttributeList> & xAttrList)
{
for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
{
switch (aIter.getToken())
{
case XML_ELEMENT(OFFICE, XML_DDE_APPLICATION):
m_sDDEApplication = aIter.toString();
break;
case XML_ELEMENT(OFFICE, XML_DDE_TOPIC):
m_sDDETopic = aIter.toString();
break;
case XML_ELEMENT(OFFICE, XML_DDE_ITEM):
m_sDDEItem = aIter.toString();
break;
case XML_ELEMENT(OFFICE, XML_NAME):
m_sConnectionName = aIter.toString();
break;
case XML_ELEMENT(OFFICE, XML_AUTOMATIC_UPDATE):
{
bool bTmp(false);
if (::sax::Converter::convertBool(bTmp, aIter.toView()))
{
m_bIsAutomaticUpdate = bTmp;
}
break;
}
default:
XMLOFF_WARN_UNKNOWN("sw", aIter);
}
// else: unknown attribute namespace
}
}
// generate a new name for DDE field type (called by lcl_GetDDEFieldType below)
static OUString lcl_GenerateFieldTypeName(const OUString& sPrefix, SwTableNode* pTableNode)
{
const OUString sPrefixStr(sPrefix.isEmpty() ? u"_"_ustr : sPrefix);
// increase count until we find a name that is not yet taken
OUString sName;
sal_Int32 nCount = 0;
do
{
// this is crazy, but just in case all names are taken: exit gracefully
if (nCount == SAL_MAX_INT32)
return sName;
++nCount;
sName = sPrefixStr + OUString::number(nCount);
}
while (nullptr != pTableNode->GetDoc().getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Dde, sName, false));
return sName;
}
// set table properties
static SwDDEFieldType* lcl_GetDDEFieldType(SwXMLDDETableContext_Impl* pContext,
SwTableNode* pTableNode)
{
// make command string
const OUString sCommand(pContext->GetDDEApplication()
+ OUStringChar(sfx2::cTokenSeparator)
+ pContext->GetDDEItem()
+ OUStringChar(sfx2::cTokenSeparator)
+ pContext->GetDDETopic());
const SfxLinkUpdateMode nType = pContext->GetIsAutomaticUpdate()
? SfxLinkUpdateMode::ALWAYS
: SfxLinkUpdateMode::ONCALL;
OUString sName(pContext->GetConnectionName());
// field type to be returned
SwDDEFieldType* pType = nullptr;
// valid name?
if (sName.isEmpty())
{
sName = lcl_GenerateFieldTypeName(pContext->GetDDEApplication(),
pTableNode);
}
else
{
// check for existing DDE field type with the same name
SwDDEFieldType* pOldType = static_cast<SwDDEFieldType*>(pTableNode->GetDoc().getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Dde, sName, false));
if (nullptr != pOldType)
{
// same values -> return old type
if ( (pOldType->GetCmd() == sCommand) &&
(pOldType->GetType() == nType) )
{
// same name, same values -> return old type!
pType = pOldType;
}
else
{
// same name, different values -> think of new name
sName = lcl_GenerateFieldTypeName(pContext->GetDDEApplication(),
pTableNode);
}
}
// no old type -> create new one
}
// create new field type (unless we already have one)
if (nullptr == pType)
{
// create new field type and return
SwDDEFieldType aDDEFieldType(sName, sCommand, nType);
pType = static_cast<SwDDEFieldType*>(pTableNode->
GetDoc().getIDocumentFieldsAccess().InsertFieldType(aDDEFieldType));
}
OSL_ENSURE(nullptr != pType, "We really want a SwDDEFieldType here!");
return pType;
}
class TableBoxIndex
{
public:
OUString msName;
sal_Int32 mnWidth;
bool mbProtected;
TableBoxIndex( OUString aName, sal_Int32 nWidth,
bool bProtected ) :
msName(std::move( aName )),
mnWidth( nWidth ),
mbProtected( bProtected )
{ }
bool operator== ( const TableBoxIndex& rArg ) const
{
return (rArg.mnWidth == mnWidth) &&
(rArg.mbProtected == mbProtected) &&
(rArg.msName == msName);
}
};
class TableBoxIndexHasher
{
public:
size_t operator() (const TableBoxIndex& rArg) const
{
return rArg.msName.hashCode() + rArg.mnWidth + (rArg.mbProtected ? 1 : 0);
}
};
const SwXMLTableCell_Impl *SwXMLTableContext::GetCell( sal_uInt32 nRow,
sal_uInt32 nCol ) const
{
return (*m_pRows)[nRow]->GetCell( nCol );
}
SwXMLTableCell_Impl *SwXMLTableContext::GetCell( sal_uInt32 nRow,
sal_uInt32 nCol )
{
return (*m_pRows)[nRow]->GetCell( nCol );
}
SwXMLTableContext::SwXMLTableContext( SwXMLImport& rImport,
const Reference< xml::sax::XFastAttributeList > & xAttrList ) :
XMLTextTableContext( rImport ),
m_pRows( new SwXMLTableRows_Impl ),
m_pTableNode( nullptr ),
m_pBox1( nullptr ),
m_bOwnsBox1( false ),
m_pSttNd1( nullptr ),
m_pBoxFormat( nullptr ),
m_pLineFormat( nullptr ),
m_bFirstSection( true ),
m_bRelWidth( true ),
m_bHasSubTables( false ),
m_nHeaderRows( 0 ),
m_nCurRow( 0 ),
m_nCurCol( 0 ),
m_nNonMergedCurCol( 0 ),
m_nWidth( 0 )
{
OUString aName;
OUString sXmlId;
// this method will modify the document directly -> lock SolarMutex
SolarMutexGuard aGuard;
for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
{
const OUString sValue = aIter.toString();
switch(aIter.getToken())
{
case XML_ELEMENT(TABLE, XML_STYLE_NAME):
m_aStyleName = sValue;
break;
case XML_ELEMENT(TABLE, XML_NAME):
aName = sValue;
break;
case XML_ELEMENT(TABLE, XML_DEFAULT_CELL_STYLE_NAME):
m_aDfltCellStyleName = sValue;
break;
case XML_ELEMENT(TABLE, XML_TEMPLATE_NAME):
m_aTemplateName = sValue;
break;
case XML_ELEMENT(XML, XML_ID):
sXmlId = sValue;
break;
default:
XMLOFF_WARN_UNKNOWN("sw", aIter);
}
}
SwDoc *pDoc = GetSwImport().getDoc();
OUString sTableName;
if( !aName.isEmpty() )
{
const SwTableFormat *pTableFormat = pDoc->FindTableFormatByName( aName );
if( !pTableFormat )
sTableName = aName;
}
if( sTableName.isEmpty() )
{
// Optimization: use import's own map to create unique names, because
// SwDoc::GetUniqueTableName scans all the already present tables,
// builds a bitset using rather complex rules, and that has quadratic
// complexity. Try once, then fallback to SwDoc::GetUniqueTableName
auto& tableNameMap = rImport.GetTableNameMap();
sal_Int32 nextIx = ++tableNameMap[aName];
OUString test = aName.isEmpty()
? OUString(rImport.GetDefTableName() + OUString::number(nextIx))
: OUString(aName + "_" + OUString::number(nextIx));
if (const SwTableFormat* pExisting = pDoc->FindTableFormatByName(test); !pExisting)
sTableName = test;
else
sTableName = pDoc->GetUniqueTableName();
GetImport().GetTextImport()
->GetRenameMap().Add( XML_TEXT_RENAME_TYPE_TABLE, aName, sTableName );
}
Reference< XTextTable > xTable;
SwXTextTable *pXTable = nullptr;
Reference<XMultiServiceFactory> xFactory( GetImport().GetModel(),
UNO_QUERY );
OSL_ENSURE( xFactory.is(), "factory missing" );
if( xFactory.is() )
{
Reference<XInterface> xIfc = xFactory->createInstance( u"com.sun.star.text.TextTable"_ustr );
OSL_ENSURE( xIfc.is(), "Couldn't create a table" );
if( xIfc.is() )
xTable.set( xIfc, UNO_QUERY );
}
if( xTable.is() )
{
xTable->initialize( 1, 1 );
if (auto xPropSet = xTable.query<css::beans::XPropertySet>())
xPropSet->setPropertyValue(UNO_NAME_TABLE_NAME, css::uno::Any(sTableName));
try
{
m_xTextContent = xTable;
GetImport().GetTextImport()->InsertTextContent( m_xTextContent );
}
catch( IllegalArgumentException& )
{
xTable = nullptr;
}
}
if( xTable.is() )
{
//FIXME
// xml:id for RDF metadata
GetImport().SetXmlId(xTable, sXmlId);
pXTable = dynamic_cast<SwXTextTable*>(xTable.get());
Reference < XCellRange > xCellRange( xTable, UNO_QUERY );
Reference < XCell > xCell = xCellRange->getCellByPosition( 0, 0 );
Reference < XText> xText( xCell, UNO_QUERY );
m_xOldCursor = GetImport().GetTextImport()->GetCursor();
GetImport().GetTextImport()->SetCursor( xText->createTextCursor() );
// take care of open redlines for tables
GetImport().GetTextImport()->RedlineAdjustStartNodeCursor();
}
if( !pXTable )
return;
SwFrameFormat *const pTableFrameFormat = pXTable->GetFrameFormat();
OSL_ENSURE( pTableFrameFormat, "table format missing" );
SwTable *pTable = SwTable::FindTable( pTableFrameFormat );
assert(pTable && "table missing");
m_pTableNode = pTable->GetTableNode();
assert(m_pTableNode && "table node missing");
SwTableLine *pLine1 = m_pTableNode->GetTable().GetTabLines()[0U];
m_pBox1 = pLine1->GetTabBoxes()[0U];
m_pSttNd1 = m_pBox1->GetSttNd();
}
SwXMLTableContext::SwXMLTableContext( SwXMLImport& rImport,
SwXMLTableContext *pTable ) :
XMLTextTableContext( rImport ),
m_pRows( new SwXMLTableRows_Impl ),
m_pTableNode( pTable->m_pTableNode ),
m_pBox1( nullptr ),
m_bOwnsBox1( false ),
m_pSttNd1( nullptr ),
m_pBoxFormat( nullptr ),
m_pLineFormat( nullptr ),
m_xParentTable( pTable ),
m_bFirstSection( false ),
m_bRelWidth( true ),
m_bHasSubTables( false ),
m_nHeaderRows( 0 ),
m_nCurRow( 0 ),
m_nCurCol( 0 ),
m_nNonMergedCurCol( 0 ),
m_nWidth( 0 )
{
}
SwXMLTableContext::~SwXMLTableContext()
{
if (m_bOwnsBox1)
delete m_pBox1;
m_xColumnDefaultCellStyleNames.reset();
m_pSharedBoxFormats.reset();
m_pRows.reset();
// close redlines on table end nodes
GetImport().GetTextImport()->RedlineAdjustStartNodeCursor();
}
css::uno::Reference<css::xml::sax::XFastContextHandler> SwXMLTableContext::createFastChildContext(
sal_Int32 nElement,
const Reference< xml::sax::XFastAttributeList > & xAttrList )
{
bool bHeader = false;
switch (nElement)
{
case XML_ELEMENT(TABLE, XML_TABLE_ROW):
case XML_ELEMENT(LO_EXT, XML_TABLE_ROW):
if( IsInsertRowPossible() )
return new SwXMLTableRowContext_Impl( GetSwImport(), nElement, xAttrList, this );
break;
case XML_ELEMENT(TABLE, XML_TABLE_HEADER_ROWS):
bHeader = true;
[[fallthrough]];
case XML_ELEMENT(TABLE, XML_TABLE_ROWS):
return new SwXMLTableRowsContext_Impl( GetSwImport(), this, bHeader );
case XML_ELEMENT(TABLE, XML_TABLE_HEADER_COLUMNS):
case XML_ELEMENT(TABLE, XML_TABLE_COLUMNS):
// There are slight differences between <table:table-columns> and
// <table:table-columns-groups>. However, none of these are
// supported in Writer (they are Calc-only features), so we
// support column groups by simply using the <table:table-columns>
// token for column groups, too.
case XML_ELEMENT(TABLE, XML_TABLE_COLUMN_GROUP):
if( IsValid() )
return new SwXMLTableColsContext_Impl( GetSwImport(), this );
break;
case XML_ELEMENT(TABLE, XML_TABLE_COLUMN):
case XML_ELEMENT(LO_EXT, XML_TABLE_COLUMN):
if( IsValid() && IsInsertColPossible() )
return new SwXMLTableColContext_Impl( GetSwImport(), xAttrList,
this );
break;
case XML_ELEMENT(OFFICE, XML_DDE_SOURCE):
// save context for later processing (discard old context, if approp.)
if( IsValid() )
{
m_xDDESource.set(new SwXMLDDETableContext_Impl( GetSwImport() ));
return m_xDDESource;
}
break;
}
return nullptr;
}
void SwXMLTableContext::InsertColumn( sal_Int32 nWidth2, bool bRelWidth2,
const OUString *pDfltCellStyleName )
{
OSL_ENSURE( m_nCurCol < USHRT_MAX,
"SwXMLTableContext::InsertColumn: no space left" );
if( m_nCurCol >= USHRT_MAX )
return;
if( nWidth2 < MINLAY )
nWidth2 = MINLAY;
else if( nWidth2 > MAX_WIDTH )
nWidth2 = MAX_WIDTH;
m_aColumnWidths.emplace_back(nWidth2, bRelWidth2 );
if( !((pDfltCellStyleName && !pDfltCellStyleName->isEmpty()) ||
m_xColumnDefaultCellStyleNames) )
return;
if( !m_xColumnDefaultCellStyleNames )
{
m_xColumnDefaultCellStyleNames.emplace();
sal_uLong nCount = m_aColumnWidths.size() - 1;
while( nCount-- )
m_xColumnDefaultCellStyleNames->emplace_back();
}
if(pDfltCellStyleName)
m_xColumnDefaultCellStyleNames->push_back(*pDfltCellStyleName);
else
m_xColumnDefaultCellStyleNames->push_back(OUString());
}
sal_Int32 SwXMLTableContext::GetColumnWidth( sal_uInt32 nCol,
sal_uInt32 nColSpan ) const
{
sal_uInt32 nLast = nCol+nColSpan;
if( nLast > m_aColumnWidths.size() )
nLast = m_aColumnWidths.size();
sal_Int32 nWidth2 = 0;
for( sal_uInt32 i=nCol; i < nLast; ++i )
nWidth2 += m_aColumnWidths[i].width;
return nWidth2;
}
const OUString & SwXMLTableContext::GetColumnDefaultCellStyleName( sal_uInt32 nCol ) const
{
if( m_xColumnDefaultCellStyleNames && nCol < m_xColumnDefaultCellStyleNames->size())
return (*m_xColumnDefaultCellStyleNames)[static_cast<size_t>(nCol)];
return EMPTY_OUSTRING;
}
void SwXMLTableContext::InsertCell( const OUString& rStyleName,
sal_uInt32 nRowSpan, sal_uInt32 nColSpan,
const SwStartNode *pStartNode,
SwXMLTableContext *pTable,
bool bProtect,
const OUString* pFormula,
bool bHasValue,
double fValue,
OUString const*const pStringValue )
{
OSL_ENSURE( m_nCurCol < GetColumnCount(),
"SwXMLTableContext::InsertCell: row is full" );
OSL_ENSURE( m_nCurRow < USHRT_MAX,
"SwXMLTableContext::InsertCell: table is full" );
if( m_nCurCol >= USHRT_MAX || m_nCurRow > USHRT_MAX )
return;
OSL_ENSURE( nRowSpan >=1, "SwXMLTableContext::InsertCell: row span is 0" );
if( 0 == nRowSpan )
nRowSpan = 1;
OSL_ENSURE( nColSpan >=1, "SwXMLTableContext::InsertCell: col span is 0" );
if( 0 == nColSpan )
nColSpan = 1;
// Until it is possible to add columns here, fix the column span.
sal_uInt32 nColsReq = m_nCurCol + nColSpan;
if( nColsReq > GetColumnCount() )
{
nColSpan = GetColumnCount() - m_nCurCol;
nColsReq = GetColumnCount();
}
// Check whether there are cells from a previous line already that reach
// into the current row.
if( m_nCurRow > 0 && nColSpan > 1 )
{
SwXMLTableRow_Impl *pCurRow = (*m_pRows)[m_nCurRow].get();
sal_uInt32 nLastCol = GetColumnCount() < nColsReq ? GetColumnCount()
: nColsReq;
for( sal_uInt32 i=m_nCurCol+1; i<nLastCol; ++i )
{
if( pCurRow->GetCell(i)->IsUsed() )
{
// If this cell is used, the column span is truncated
nColSpan = i - m_nCurCol;
nColsReq = i;
break;
}
}
}
sal_uInt32 nRowsReq = m_nCurRow + nRowSpan;
if( nRowsReq > USHRT_MAX )
{
nRowSpan = USHRT_MAX - m_nCurRow;
nRowsReq = USHRT_MAX;
}
// Add columns (if # required columns greater than # columns):
// This should never happen, since we require column definitions!
if ( nColsReq > GetColumnCount() )
{
for( sal_uInt32 i=GetColumnCount(); i<nColsReq; ++i )
{
m_aColumnWidths.emplace_back(MINLAY, true );
}
// adjust columns in *all* rows, if columns must be inserted
for (size_t i = 0; i < m_pRows->size(); ++i)
(*m_pRows)[i]->Expand( nColsReq, i<m_nCurRow );
}
// Add rows
if (m_pRows->size() < nRowsReq)
{
for (size_t i = m_pRows->size(); i < nRowsReq; ++i)
m_pRows->push_back(std::make_unique<SwXMLTableRow_Impl>(
"", GetColumnCount()));
}
OUString sStyleName( rStyleName );
if( sStyleName.isEmpty() )
{
sStyleName = (*m_pRows)[m_nCurRow]->GetDefaultCellStyleName();
if( sStyleName.isEmpty() && m_xColumnDefaultCellStyleNames )
{
sStyleName = GetColumnDefaultCellStyleName( m_nCurCol );
if( sStyleName.isEmpty() )
sStyleName = m_aDfltCellStyleName;
}
}
// Fill the cells
for( sal_uInt32 i=nColSpan; i>0; --i )
{
for( sal_uInt32 j=nRowSpan; j>0; --j )
{
const bool bCovered = i != nColSpan || j != nRowSpan;
SwXMLTableCell_Impl *pCell = GetCell( nRowsReq-j, nColsReq-i );
if (!pCell)
throw css::lang::IndexOutOfBoundsException();
pCell->Set( sStyleName, j, i, pStartNode,
pTable, bProtect, pFormula, bHasValue, bCovered, fValue,
pStringValue );
}
}
// Set current col to the next (free) column
m_nCurCol = nColsReq;
m_nNonMergedCurCol = nColsReq;
while( m_nCurCol<GetColumnCount() && GetCell(m_nCurRow,m_nCurCol)->IsUsed() )
m_nCurCol++;
}
void SwXMLTableContext::InsertCoveredCell(const OUString& rStyleName)
{
const IDocumentSettingAccess& rIDSA = GetSwImport().getDoc()->getIDocumentSettingAccess();
bool bWordTableCell = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP);
if (!bWordTableCell)
{
// Compatibility flag not active, ignore formatting of covered cells.
return;
}
SwXMLTableCell_Impl* pCell = GetCell(m_nCurRow, m_nNonMergedCurCol);
++m_nNonMergedCurCol;
if (!pCell)
{
return;
}
pCell->SetStyleName(rStyleName);
}
void SwXMLTableContext::InsertRow( const OUString& rStyleName,
const OUString& rDfltCellStyleName,
bool bInHead )
{
OSL_ENSURE( m_nCurRow < USHRT_MAX,
"SwXMLTableContext::InsertRow: no space left" );
if( m_nCurRow >= USHRT_MAX )
return;
// Make sure there is at least one column.
if( 0==m_nCurRow && 0 == GetColumnCount() )
InsertColumn( USHRT_MAX, true );
if (m_nCurRow < m_pRows->size())
{
// The current row has already been inserted because of a row span
// of a previous row.
(*m_pRows)[m_nCurRow]->Set(
rStyleName, rDfltCellStyleName );
}
else
{
// add a new row
m_pRows->push_back(std::make_unique<SwXMLTableRow_Impl>(
rStyleName, GetColumnCount(),
&rDfltCellStyleName));
}
// We start at the first column ...
m_nCurCol=0;
m_nNonMergedCurCol = 0;
// ... but this cell may be occupied already.
while( m_nCurCol<GetColumnCount() && GetCell(m_nCurRow,m_nCurCol)->IsUsed() )
m_nCurCol++;
if( bInHead && m_nHeaderRows == m_nCurRow )
m_nHeaderRows++;
}
void SwXMLTableContext::InsertRepRows( sal_uInt32 nCount )
{
const SwXMLTableRow_Impl *pSrcRow = (*m_pRows)[m_nCurRow-1].get();
while( nCount > 1 && IsInsertRowPossible() )
{
InsertRow( pSrcRow->GetStyleName(), pSrcRow->GetDefaultCellStyleName(),
false );
while( m_nCurCol < GetColumnCount() )
{
if( !GetCell(m_nCurRow,m_nCurCol)->IsUsed() )
{
const SwXMLTableCell_Impl *pSrcCell =
GetCell( m_nCurRow-1, m_nCurCol );
InsertCell( pSrcCell->GetStyleName(), 1U,
pSrcCell->GetColSpan(),
InsertTableSection(),
nullptr, pSrcCell->IsProtected(),
&pSrcCell->GetFormula(),
pSrcCell->HasValue(), pSrcCell->GetValue(),
pSrcCell->GetStringValue() );
}
}
FinishRow();
nCount--;
}
}
void SwXMLTableContext::FinishRow()
{
// Insert an empty cell at the end of the line if the row is not complete
if( m_nCurCol < GetColumnCount() )
{
InsertCell( u""_ustr, 1U, GetColumnCount() - m_nCurCol,
InsertTableSection() );
}
// Move to the next row.
m_nCurRow++;
}
const SwStartNode *SwXMLTableContext::GetPrevStartNode( sal_uInt32 nRow,
sal_uInt32 nCol ) const
{
const SwXMLTableCell_Impl *pPrevCell = nullptr;
if( GetColumnCount() == nCol )
{
// The last cell is the right one here.
pPrevCell = GetCell( m_pRows->size() - 1U, GetColumnCount() - 1 );
}
else if( nCol > 0 )
{
// The previous cell in this row.
pPrevCell = GetCell( nRow, nCol-1 );
}
else if( nRow > 0 )
{
// The last cell from the previous row.
pPrevCell = GetCell( nRow-1, GetColumnCount()-1 );
}
const SwStartNode *pSttNd = nullptr;
if( pPrevCell )
{
if( pPrevCell->GetStartNode() )
pSttNd = pPrevCell->GetStartNode();
// #i95726# - Some fault tolerance
// else
else if ( pPrevCell->GetSubTable() )
pSttNd = pPrevCell->GetSubTable()->GetLastStartNode();
OSL_ENSURE( pSttNd != nullptr,
"table corrupt" );
}
return pSttNd;
}
void SwXMLTableContext::FixRowSpan( sal_uInt32 nRow, sal_uInt32 nCol,
sal_uInt32 nColSpan )
{
sal_uInt32 nLastCol = nCol + nColSpan;
for( sal_uInt32 i = nCol; i < nLastCol; i++ )
{
sal_uInt32 j = nRow;
sal_uInt32 nRowSpan = 1;
SwXMLTableCell_Impl *pCell = GetCell( j, i );
while( pCell && pCell->GetRowSpan() > 1 )
{
pCell->SetRowSpan( nRowSpan++ );
pCell = j > 0 ? GetCell( --j, i ) : nullptr;
}
}
}
void SwXMLTableContext::ReplaceWithEmptyCell( sal_uInt32 nRow, sal_uInt32 nCol, bool bRows )
{
const SwStartNode *pPrevSttNd = GetPrevStartNode( nRow, nCol );
const SwStartNode *pSttNd = InsertTableSection( pPrevSttNd );
const SwXMLTableCell_Impl *pCell = GetCell( nRow, nCol );
sal_uInt32 nLastRow = bRows ? nRow + pCell->GetRowSpan() : nRow + 1;
sal_uInt32 nLastCol = nCol + pCell->GetColSpan();
for( sal_uInt32 i=nRow; i<nLastRow; i++ )
{
SwXMLTableRow_Impl *pRow = (*m_pRows)[i].get();
for( sal_uInt32 j=nCol; j<nLastCol; j++ )
pRow->GetCell( j )->SetStartNode( pSttNd );
}
}
SwTableBox *SwXMLTableContext::NewTableBox( const SwStartNode *pStNd,
SwTableLine *pUpper )
{
// The topmost table is the only table that maintains the two members
// pBox1 and bFirstSection.
if( m_xParentTable.is() )
return m_xParentTable->NewTableBox( pStNd, pUpper );
SwTableBox *pBox;
if( m_pBox1 &&
m_pBox1->GetSttNd() == pStNd )
{
// if the StartNode is equal to the StartNode of the initially
// created box, we use this box
pBox = m_pBox1;
pBox->SetUpper( pUpper );
m_pBox1 = nullptr;
m_bOwnsBox1 = false;
}
else
pBox = new SwTableBox( m_pBoxFormat, *pStNd, pUpper );
return pBox;
}
SwTableBoxFormat* SwXMLTableContext::GetSharedBoxFormat(
SwTableBox* pBox,
const OUString& rStyleName,
sal_Int32 nColumnWidth,
bool bProtected,
bool bMayShare,
bool& bNew,
bool* pModifyLocked )
{
if ( m_pSharedBoxFormats == nullptr )
m_pSharedBoxFormats.reset(new map_BoxFormat);
SwTableBoxFormat* pBoxFormat2;
TableBoxIndex aKey( rStyleName, nColumnWidth, bProtected );
map_BoxFormat::iterator aIter = m_pSharedBoxFormats->find( aKey );
if ( aIter == m_pSharedBoxFormats->end() )
{
// unknown format so far -> construct a new one
// get the old format, and reset all attributes
// (but preserve FillOrder)
pBoxFormat2 = pBox->ClaimFrameFormat();
SwFormatFillOrder aFillOrder( pBoxFormat2->GetFillOrder() );
pBoxFormat2->ResetAllFormatAttr(); // #i73790# - method renamed
pBoxFormat2->SetFormatAttr( aFillOrder );
bNew = true; // it's a new format now
// share this format, if allowed
if ( bMayShare )
(*m_pSharedBoxFormats)[ aKey ] = pBoxFormat2;
}
else
{
// set the shared format
pBoxFormat2 = aIter->second;
pBox->ChgFrameFormat( pBoxFormat2, /*bNeedToReregister*/false );
bNew = false; // copied from an existing format
// claim it, if we are not allowed to share
if ( !bMayShare )
pBoxFormat2 = pBox->ClaimFrameFormat();
}
// lock format (if so desired)
if ( pModifyLocked != nullptr )
{
(*pModifyLocked) = pBoxFormat2->IsModifyLocked();
pBoxFormat2->LockModify();
}
return pBoxFormat2;
}
SwTableBox *SwXMLTableContext::MakeTableBox( SwTableLine *pUpper,
sal_uInt32 nTopRow,
sal_uInt32 nLeftCol,
sal_uInt32 nBottomRow,
sal_uInt32 nRightCol )
{
//FIXME: here would be a great place to handle XmlId for cell
SwTableBox *pBox = new SwTableBox( m_pBoxFormat, 0, pUpper );
sal_uInt32 nColSpan = nRightCol - nLeftCol;
sal_Int32 nColWidth = GetColumnWidth( nLeftCol, nColSpan );
// TODO: Share formats!
SwFrameFormat *pFrameFormat = pBox->ClaimFrameFormat();
SwFormatFillOrder aFillOrder( pFrameFormat->GetFillOrder() );
pFrameFormat->ResetAllFormatAttr(); // #i73790# - method renamed
pFrameFormat->SetFormatAttr( aFillOrder );
pFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nColWidth ) );
SwTableLines& rLines = pBox->GetTabLines();
bool bSplitted = false;
while( !bSplitted )
{
sal_uInt32 nStartRow = nTopRow;
sal_uInt32 i;
for( i = nTopRow; i < nBottomRow; i++ )
{
// Could the table be split behind the current row?
bool bSplit = true;
SwXMLTableRow_Impl *pRow = (*m_pRows)[i].get();
for( sal_uInt32 j=nLeftCol; j<nRightCol; j++ )
{
bSplit = ( 1 == pRow->GetCell(j)->GetRowSpan() );
if( !bSplit )
break;
}
if( bSplit && (nStartRow>nTopRow || i+1<nBottomRow) )
{
SwTableLine *pLine =
MakeTableLine( pBox, nStartRow, nLeftCol, i+1,
nRightCol );
rLines.push_back( pLine );
nStartRow = i+1;
bSplitted = true;
}
}
if( !bSplitted )
{
// No splitting was possible. That for, we have to force it.
// Ruthless!
nStartRow = nTopRow;
while( nStartRow < nBottomRow )
{
sal_uInt32 nMaxRowSpan = 0;
SwXMLTableRow_Impl *pStartRow = (*m_pRows)[nStartRow].get();
const SwXMLTableCell_Impl *pCell;
for( i=nLeftCol; i<nRightCol; i++ )
{
pCell = pStartRow->GetCell(i);
if( pCell->GetRowSpan() > nMaxRowSpan )
nMaxRowSpan = pCell->GetRowSpan();
}
nStartRow += nMaxRowSpan;
if( nStartRow<nBottomRow )
{
SwXMLTableRow_Impl *pPrevRow = (*m_pRows)[nStartRow - 1U].get();
i = nLeftCol;
while( i < nRightCol )
{
if( pPrevRow->GetCell(i)->GetRowSpan() > 1 )
{
const SwXMLTableCell_Impl *pCell2 =
GetCell( nStartRow, i );
const sal_uInt32 nColSpan2 = pCell2->GetColSpan();
FixRowSpan( nStartRow-1, i, nColSpan2 );
ReplaceWithEmptyCell( nStartRow, i, true );
i += nColSpan2;
}
else
{
i++;
}
}
}
}
// and now start over again...
}
}
return pBox;
}
SwTableBox *SwXMLTableContext::MakeTableBox(
SwTableLine *pUpper, const SwXMLTableCell_Impl *pCell,
sal_uInt32 nLeftCol, sal_uInt32 nRightCol )
{
//FIXME: here would be a great place to handle XmlId for cell
SwTableBox *pBox;
sal_uInt32 nColSpan = nRightCol - nLeftCol;
sal_Int32 nColWidth = GetColumnWidth( nLeftCol, nColSpan );
if( pCell->GetStartNode() )
{
pBox = NewTableBox( pCell->GetStartNode(), pUpper );
}
else
{
// and it is a table: therefore we build a new box and
// put the rows of the table into the rows of the box
pBox = new SwTableBox( m_pBoxFormat, 0, pUpper );
pCell->GetSubTable()->MakeTable( pBox, nColWidth );
}
// Share formats!
const OUString& sStyleName = pCell->GetStyleName();
bool bModifyLocked;
bool bNew;
SwTableBoxFormat *pBoxFormat2 = GetSharedBoxFormat(
pBox, sStyleName, nColWidth, pCell->IsProtected(),
pCell->GetStartNode() && pCell->GetFormula().isEmpty() &&
! pCell->HasValue(),
bNew, &bModifyLocked );
// if a new format was created, then we need to set the style
if ( bNew )
{
// set style
const SfxItemSet *pAutoItemSet = nullptr;
if( pCell->GetStartNode() && !sStyleName.isEmpty() &&
GetSwImport().FindAutomaticStyle(
XmlStyleFamily::TABLE_CELL, sStyleName, &pAutoItemSet ) )
{
if( pAutoItemSet )
pBoxFormat2->SetFormatAttr( *pAutoItemSet );
}
}
if( pCell->GetStartNode() )
{
if (pCell->HasStringValue())
{
SwNodeIndex const aNodeIndex(*(pCell->GetStartNode()), 1);
SwTextNode *const pTextNode(aNodeIndex.GetNode().GetTextNode());
SAL_WARN_IF(!pTextNode, "sw", "Should have a text node in cell?");
if (pTextNode)
{
SAL_WARN_IF(!pTextNode->GetText().isEmpty(), "sw",
"why text here?");
pTextNode->InsertText(*pCell->GetStringValue(),
SwContentIndex(pTextNode, 0));
}
}
// try to rescue broken documents with a certain pattern
// if: 1) the cell has a default number format (number 0)
// 2) the call has no formula
// 3) the value is 0.0
// 4) the text doesn't look anything like 0.0
// [read: length > 10, or length smaller 10 and no 0 in it]
// then make it a text cell!
bool bSuppressNumericContent = false;
if( pCell->HasValue() && (pCell->GetValue() == 0.0) &&
pCell->GetFormula().isEmpty() &&
!sStyleName.isEmpty() )
{
// default num format?
if( const SwTableBoxNumFormat* pNumFormat = pBoxFormat2->GetItemIfSet( RES_BOXATR_FORMAT, false ) )
{
if ((pNumFormat->GetValue() % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
{
// only one text node?
SwNodeIndex aNodeIndex( *(pCell->GetStartNode()), 1 );
if( ( aNodeIndex.GetNode().EndOfSectionIndex() -
aNodeIndex.GetNode().StartOfSectionIndex() ) == SwNodeOffset(2) )
{
SwTextNode* pTextNode= aNodeIndex.GetNode().GetTextNode();
if( pTextNode != nullptr )
{
// check text: does it look like some form of 0.0?
const OUString& rText = pTextNode->GetText();
if( ( rText.getLength() > 10 ) ||
( rText.indexOf( '0' ) == -1 ) )
{
bSuppressNumericContent = true;
}
}
}
else
bSuppressNumericContent = true; // several nodes
}
}
}
if( bSuppressNumericContent )
{
// suppress numeric content? Then reset number format!
pBoxFormat2->ResetFormatAttr( RES_BOXATR_FORMULA );
pBoxFormat2->ResetFormatAttr( RES_BOXATR_FORMAT );
pBoxFormat2->ResetFormatAttr( RES_BOXATR_VALUE );
}
else
{
// the normal case: set formula and value (if available)
const OUString& rFormula = pCell->GetFormula();
if (!rFormula.isEmpty())
{
// formula cell: insert formula if valid
SwTableBoxFormula aFormulaItem( rFormula );
pBoxFormat2->SetFormatAttr( aFormulaItem );
}
else if (!pCell->HasValue() && pCell->HasStringValue())
{
// Check for another inconsistency:
// No value but a non-textual format, i.e. a number format
// Solution: the number format will be removed,
// the cell gets the default text format.
if( const SwTableBoxNumFormat* pNumFormat = m_pBoxFormat->GetItemIfSet( RES_BOXATR_FORMAT, false ) )
{
const SwDoc* pDoc = m_pBoxFormat->GetDoc();
const SvNumberFormatter* pNumberFormatter = pDoc ?
pDoc->GetNumberFormatter() : nullptr;
if( pNumberFormatter && !pNumberFormatter->GetEntry( pNumFormat->GetValue() )->IsTextFormat() )
m_pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT );
}
}
// always insert value, even if default
if( pCell->HasValue() )
{
SwTableBoxValue aValueItem( pCell->GetValue() );
pBoxFormat2->SetFormatAttr( aValueItem );
}
}
// update cell content depend on the default language
pBox->ActualiseValueBox();
}
// table cell protection
if( pCell->IsProtected() )
{
SvxProtectItem aProtectItem( RES_PROTECT );
aProtectItem.SetContentProtect( true );
pBoxFormat2->SetFormatAttr( aProtectItem );
}
// restore old modify-lock state
if (! bModifyLocked)
pBoxFormat2->UnlockModify();
pBoxFormat2->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nColWidth ) );
return pBox;
}
SwTableLine *SwXMLTableContext::MakeTableLine( SwTableBox *pUpper,
sal_uInt32 nTopRow,
sal_uInt32 nLeftCol,
sal_uInt32 nBottomRow,
sal_uInt32 nRightCol )
{
//FIXME: here would be a great place to handle XmlId for row
SwTableLine *pLine;
if( !pUpper && 0UL==nTopRow )
{
pLine = m_pTableNode->GetTable().GetTabLines()[0U];
}
else
{
pLine = new SwTableLine( m_pLineFormat, 0, pUpper );
}
// TODO: Share formats!
SwFrameFormat *pFrameFormat = pLine->ClaimFrameFormat();
SwFormatFillOrder aFillOrder( pFrameFormat->GetFillOrder() );
pFrameFormat->ResetAllFormatAttr(); // #i73790# - method renamed
pFrameFormat->SetFormatAttr( aFillOrder );
const SfxItemSet *pAutoItemSet = nullptr;
const OUString& rStyleName = (*m_pRows)[nTopRow]->GetStyleName();
if( 1 == (nBottomRow - nTopRow) &&
!rStyleName.isEmpty() &&
GetSwImport().FindAutomaticStyle(
XmlStyleFamily::TABLE_ROW, rStyleName, &pAutoItemSet ) )
{
if( pAutoItemSet )
pFrameFormat->SetFormatAttr( *pAutoItemSet );
}
SwTableBoxes& rBoxes = pLine->GetTabBoxes();
sal_uInt32 nStartCol = nLeftCol;
while( nStartCol < nRightCol )
{
for( sal_uInt32 nRow=nTopRow; nRow<nBottomRow; nRow++ )
(*m_pRows)[nRow]->SetSplitable( true );
sal_uInt32 nCol = nStartCol;
sal_uInt32 nSplitCol = nRightCol;
bool bSplitted = false;
while( !bSplitted )
{
OSL_ENSURE( nCol < nRightCol, "Ran too far" );
// Can be split after current HTML table column?
// If yes, can the created region still be split to
// rows if the next column is added to it?
bool bSplit = true;
bool bHoriSplitMayContinue = false;
bool bHoriSplitPossible = false;
if ( m_bHasSubTables )
{
// Convert row spans if the table has subtables:
for( sal_uInt32 nRow=nTopRow; nRow<nBottomRow; nRow++ )
{
SwXMLTableCell_Impl *pCell = GetCell(nRow,nCol);
// Could the table fragment be split horizontally behind
// the current line?
bool bHoriSplit = (*m_pRows)[nRow]->IsSplitable() &&
nRow+1 < nBottomRow &&
1 == pCell->GetRowSpan();
(*m_pRows)[nRow]->SetSplitable( bHoriSplit );
// Could the table fragment be split vertically behind the
// current column (uptp the current line?
bSplit &= ( 1 == pCell->GetColSpan() );
if( bSplit )
{
bHoriSplitPossible |= bHoriSplit;
// Could the current table fragment be split
// horizontally behind the next column, too?
bHoriSplit &= (nCol+1 < nRightCol &&
1 == GetCell(nRow,nCol+1)->GetRowSpan());
bHoriSplitMayContinue |= bHoriSplit;
}
}
}
else
{
// No subtables: we use the new table model.
SwXMLTableCell_Impl *pCell = GetCell(nTopRow,nCol);
// #i95726# - some fault tolerance
if ( pCell == nullptr )
{
OSL_FAIL( "table seems to be corrupt." );
break;
}
// Could the table fragment be split vertically behind the
// current column (uptp the current line?
bSplit = 1 == pCell->GetColSpan();
}
#if OSL_DEBUG_LEVEL > 0
if( nCol == nRightCol-1 )
{
OSL_ENSURE( bSplit, "Split-Flag wrong" );
if ( m_bHasSubTables )
{
OSL_ENSURE( !bHoriSplitMayContinue,
"HoriSplitMayContinue-Flag wrong" );
SwXMLTableCell_Impl *pTmpCell = GetCell( nTopRow, nStartCol );
OSL_ENSURE( pTmpCell->GetRowSpan() != (nBottomRow-nTopRow) ||
!bHoriSplitPossible, "HoriSplitPossible-Flag wrong" );
}
}
#endif
OSL_ENSURE( !m_bHasSubTables || !bHoriSplitMayContinue || bHoriSplitPossible,
"bHoriSplitMayContinue, but not bHoriSplitPossible" );
if( bSplit )
{
SwTableBox* pBox = nullptr;
SwXMLTableCell_Impl *pCell = GetCell( nTopRow, nStartCol );
// #i95726# - some fault tolerance
if( ( !m_bHasSubTables || ( pCell->GetRowSpan() == (nBottomRow-nTopRow) ) ) &&
pCell->GetColSpan() == (nCol+1-nStartCol) &&
( pCell->GetStartNode() || pCell->GetSubTable() ) )
{
// insert new empty cell for covered cells:
sal_Int32 nBoxRowSpan = 1;
if ( !m_bHasSubTables )
{
nBoxRowSpan = pCell->GetRowSpan();
if ( pCell->IsCovered() )
{
nBoxRowSpan = -1 * nBoxRowSpan;
ReplaceWithEmptyCell( nTopRow, nStartCol, false );
}
}
// The remaining box neither contains lines nor rows (i.e.
// is a content box
nSplitCol = nCol + 1;
pBox = MakeTableBox( pLine, pCell, nStartCol, nSplitCol );
if ( 1 != nBoxRowSpan )
pBox->setRowSpan( nBoxRowSpan );
bSplitted = true;
}
else if( m_bHasSubTables && bHoriSplitPossible && bHoriSplitMayContinue )
{
// The table fragment could be split behind the current
// column, and the remaining fragment could be divided
// into lines. Anyway, it could be that this applies to
// the next column, too. That for, we check the next
// column but remember the current one as a good place to
// split.
nSplitCol = nCol + 1;
}
else if ( m_bHasSubTables )
{
// If the table resulting table fragment could be divided
// into lines if splitting behind the current column, but
// this doesn't apply for thr next column, we split begind
// the current column. This applies for the last column,
// too.
// If the resulting box cannot be split into rows,
// the split at the last split position we remembered.
if( bHoriSplitPossible || nSplitCol > nCol+1 )
{
OSL_ENSURE( !bHoriSplitMayContinue,
"bHoriSplitMayContinue==true" );
OSL_ENSURE( bHoriSplitPossible || nSplitCol == nRightCol,
"bHoriSplitPossible flag should be set" );
nSplitCol = nCol + 1;
}
pBox = MakeTableBox( pLine, nTopRow, nStartCol,
nBottomRow, nSplitCol );
bSplitted = true;
}
OSL_ENSURE( m_bHasSubTables || pBox, "Colspan trouble" );
if( pBox )
rBoxes.push_back( pBox );
}
nCol++;
}
nStartCol = nSplitCol;
}
return pLine;
}
void SwXMLTableContext::MakeTable_( SwTableBox *pBox )
{
// fix column widths
sal_uInt32 nCols = GetColumnCount();
// If there are empty rows (because of some row span of previous rows)
// the have to be deleted. The previous rows have to be truncated.
if (m_pRows->size() > m_nCurRow)
{
SwXMLTableRow_Impl *pPrevRow = (*m_pRows)[m_nCurRow - 1U].get();
const SwXMLTableCell_Impl *pCell;
for( size_t i = 0; i < m_aColumnWidths.size(); ++i )
{
pCell = pPrevRow->GetCell(i);
if( pCell->GetRowSpan() > 1 )
{
FixRowSpan( m_nCurRow-1, i, 1UL );
}
}
for (size_t i = m_pRows->size() - 1; i >= m_nCurRow; --i)
m_pRows->pop_back();
}
if (m_pRows->empty())
{
InsertCell( u""_ustr, 1U, nCols, InsertTableSection() );
}
// TODO: Do we have to keep both values, the relative and the absolute
// width?
sal_Int32 nAbsWidth = 0;
sal_Int32 nMinAbsColWidth = 0;
sal_Int32 nRelWidth = 0;
sal_Int32 nMinRelColWidth = 0;
sal_uInt32 nRelCols = 0;
for( const auto& rCol : m_aColumnWidths)
{
if( rCol.isRelative )
{
nRelWidth += rCol.width;
if( 0 == nMinRelColWidth || rCol.width < nMinRelColWidth )
nMinRelColWidth = rCol.width;
nRelCols++;
}
else
{
nAbsWidth += rCol.width;
if( 0 == nMinAbsColWidth || rCol.width < nMinAbsColWidth )
nMinAbsColWidth = rCol.width;
}
}
sal_uInt32 nAbsCols = nCols - nRelCols;
if( m_bRelWidth )
{
// If there a columns that have an absolute width, we have to
// calculate a relative one for them.
if( nAbsCols > 0 )
{
// All column that have absolute widths get relative widths;
// these widths relate to each over like the original absolute
// widths. The smallest column gets a width that has the same
// value as the smallest column that has a relative width
// already.
if( 0 == nMinRelColWidth )
nMinRelColWidth = nMinAbsColWidth;
for( auto& rCol : m_aColumnWidths)
{
if( !rCol.isRelative )
{
if (nMinAbsColWidth == 0)
throw o3tl::divide_by_zero();
sal_Int32 nVal;
if (o3tl::checked_multiply<sal_Int32>(rCol.width, nMinRelColWidth, nVal))
throw std::overflow_error("overflow in multiply");
sal_Int32 nRelCol = nVal / nMinAbsColWidth;
rCol.width = nRelCol;
rCol.isRelative = true;
nRelWidth += nRelCol;
nAbsCols--;
if (nAbsCols <= 0)
break;
}
}
}
if( !m_nWidth )
{
// This happens only for percentage values for the table itself.
// In this case, the columns get the correct width even if
// the sum of the relative widths is smaller than the available
// width in TWIP. Therefore, we can use the relative width.
m_nWidth = std::min(nRelWidth, MAX_WIDTH);
}
if( nRelWidth != m_nWidth && nRelWidth && nCols )
{
double n = static_cast<double>(m_nWidth) / static_cast<double>(nRelWidth);
nRelWidth = 0;
for( auto colIter = m_aColumnWidths.begin(); colIter != (m_aColumnWidths.end() - 1); ++colIter)
{
sal_Int32 nW = static_cast<sal_Int32>( colIter->width * n);
colIter->width = o3tl::narrowing<sal_uInt16>(nW);
nRelWidth += nW;
}
m_aColumnWidths.back().width = (m_nWidth-nRelWidth);
}
}
else
{
// If there are columns that have relative widths, we have to
// calculate an absolute widths for them.
if( nRelCols > 0 )
{
// The absolute space that is available for all columns with a
// relative width.
sal_Int32 nAbsForRelWidth =
m_nWidth > nAbsWidth ? m_nWidth - nAbsWidth : sal_Int32(0L);
// The relative width that has to be distributed in addition to
// equally widthed columns.
sal_Int32 nExtraRel = nRelWidth - (nRelCols * nMinRelColWidth);
// The absolute space that may be distributed in addition to
// minimum widthed columns.
sal_Int32 nMinAbs = nRelCols * MINLAY;
sal_Int32 nExtraAbs =
nAbsForRelWidth > nMinAbs ? nAbsForRelWidth - nMinAbs : sal_Int32(0L);
bool bMin = false; // Do all columns get the minimum width?
bool bMinExtra = false; // Do all columns get the minimum width plus
// some extra space?
if( nAbsForRelWidth <= nMinAbs )
{
// If there is not enough space left for all columns to
// get the minimum width, they get the minimum width, anyway.
nAbsForRelWidth = nMinAbs;
bMin = true;
}
else if( nAbsForRelWidth <= (nRelWidth * MINLAY) /
nMinRelColWidth )
{
// If there is enough space for all columns to get the
// minimum width, but not to get a width that takes the
// relative width into account, each column gets the minimum
// width plus some extra space that is based on the additional
// space that is available.
bMinExtra = true;
}
// Otherwise, if there is enough space for every column, every
// column gets this space.
for( auto& rCol : m_aColumnWidths )
{
if( rCol.isRelative )
{
sal_Int32 nAbsCol;
if( 1 == nRelCols )
{
// The last column that has a relative width gets
// all absolute space that is left.
nAbsCol = nAbsForRelWidth;
}
else
{
if( bMin )
{
nAbsCol = MINLAY;
}
else if( bMinExtra )
{
sal_Int32 nExtraRelCol = rCol.width - nMinRelColWidth;
nAbsCol = MINLAY + (nExtraRelCol * nExtraAbs) /
nExtraRel;
}
else
{
nAbsCol = ( rCol.width * nAbsForRelWidth) / nRelWidth;
}
}
rCol.width = nAbsCol;
rCol.isRelative = false;
nAbsForRelWidth -= nAbsCol;
nAbsWidth += nAbsCol;
nRelCols--;
if (nRelCols <= 0)
break;
}
}
}
if( nCols && nAbsWidth )
{
if( nAbsWidth < m_nWidth )
{
// If the table's width is larger than the sum of the absolute
// column widths, every column get some extra width.
sal_Int32 nExtraAbs = m_nWidth - nAbsWidth;
sal_Int32 nAbsLastCol = m_aColumnWidths.back().width + nExtraAbs;
for( auto colIter = m_aColumnWidths.begin(); colIter != (m_aColumnWidths.end() - 1); ++colIter )
{
sal_Int32 nAbsCol = colIter->width;
sal_Int32 nExtraAbsCol = (nAbsCol * nExtraAbs) /
nAbsWidth;
nAbsCol += nExtraAbsCol;
colIter->width = nAbsCol;
nAbsLastCol -= nExtraAbsCol;
}
m_aColumnWidths.back().width = nAbsLastCol;
}
else if( nAbsWidth > m_nWidth )
{
// If the table's width is smaller than the sum of the absolute
// column widths, every column needs to shrink.
// Every column gets the minimum width plus some extra width.
sal_Int32 nExtraAbs = m_nWidth - (nCols * MINLAY);
sal_Int32 nAbsLastCol = MINLAY + nExtraAbs;
for( auto colIter = m_aColumnWidths.begin(); colIter != (m_aColumnWidths.end() - 1); ++colIter )
{
sal_Int32 nAbsCol = colIter->width;
sal_Int32 nExtraAbsCol = (nAbsCol * nExtraAbs) /
nAbsWidth;
nAbsCol = MINLAY + nExtraAbsCol;
colIter->width = nAbsCol;
nAbsLastCol -= nExtraAbsCol;
}
m_aColumnWidths.back().width = nAbsLastCol;
}
}
}
SwTableLines& rLines =
pBox ? pBox->GetTabLines()
: m_pTableNode->GetTable().GetTabLines();
sal_uInt32 nStartRow = 0;
sal_uInt32 nRows = m_pRows->size();
for(sal_uInt32 i=0; i<nRows; ++i )
{
// Could we split the table behind the current line?
bool bSplit = true;
if ( m_bHasSubTables )
{
SwXMLTableRow_Impl *pRow = (*m_pRows)[i].get();
for( sal_uInt32 j=0; j<nCols; j++ )
{
bSplit = ( 1 == pRow->GetCell(j)->GetRowSpan() );
if( !bSplit )
break;
}
}
if( bSplit )
{
SwTableLine *pLine =
MakeTableLine( pBox, nStartRow, 0UL, i+1, nCols );
if( pBox || nStartRow>0 )
rLines.push_back( pLine );
nStartRow = i+1;
}
}
}
void SwXMLTableContext::MakeTable()
{
// this method will modify the document directly -> lock SolarMutex
// This will call all other MakeTable*(..) methods, so
// those don't need to be locked separately.
SolarMutexGuard aGuard;
// #i97274# handle invalid tables
if (!m_pRows || m_pRows->empty() || !GetColumnCount())
{
OSL_FAIL("invalid table: no cells; deleting...");
m_pTableNode->GetDoc().getIDocumentContentOperations().DeleteSection( m_pTableNode );
m_pTableNode = nullptr;
m_pBox1 = nullptr;
m_bOwnsBox1 = false;
m_pSttNd1 = nullptr;
return;
}
SwXMLImport& rSwImport = GetSwImport();
SwFrameFormat *pFrameFormat = m_pTableNode->GetTable().GetFrameFormat();
sal_Int16 eHoriOrient = text::HoriOrientation::FULL;
bool bSetHoriOrient = false;
sal_uInt8 nPercentWidth = 0U;
OUString sStyleName;
SwStyleNameMapper::FillUIName( m_aTemplateName, sStyleName, SwGetPoolIdFromName::TabStyle );
m_pTableNode->GetTable().SetTableStyleName( sStyleName );
m_pTableNode->GetTable().SetRowsToRepeat( m_nHeaderRows );
m_pTableNode->GetTable().SetTableModel( !m_bHasSubTables );
const SfxItemSet *pAutoItemSet = nullptr;
if( !m_aStyleName.isEmpty() &&
rSwImport.FindAutomaticStyle(
XmlStyleFamily::TABLE_TABLE, m_aStyleName, &pAutoItemSet ) &&
pAutoItemSet )
{
const SvxLRSpaceItem *pLRSpace =
pAutoItemSet->GetItemIfSet( RES_LR_SPACE, false );
if( const SwFormatHoriOrient* pItem = pAutoItemSet->GetItemIfSet( RES_HORI_ORIENT, false ) )
{
eHoriOrient = pItem->GetHoriOrient();
switch( eHoriOrient )
{
case text::HoriOrientation::FULL:
if( pLRSpace )
{
eHoriOrient = text::HoriOrientation::NONE;
bSetHoriOrient = true;
}
break;
case text::HoriOrientation::LEFT:
if( pLRSpace )
{
eHoriOrient = text::HoriOrientation::LEFT_AND_WIDTH;
bSetHoriOrient = true;
}
break;
default:
;
}
}
else
{
bSetHoriOrient = true;
}
const SwFormatFrameSize *pSize =
pAutoItemSet->GetItemIfSet( RES_FRM_SIZE, false );
switch( eHoriOrient )
{
case text::HoriOrientation::FULL:
case text::HoriOrientation::NONE:
// For text::HoriOrientation::NONE we would prefer to use the sum
// of the relative column widths as reference width.
// Unfortunately this works only if this sum interpreted as
// twip value is larger than the space that is available.
// We don't know that space, so we have to use MAX_WIDTH, too.
// Even if a size is specified, it will be ignored!
m_nWidth = MAX_WIDTH;
break;
default:
if( pSize )
{
if( pSize->GetWidthPercent() )
{
// The width will be set in MakeTable_
nPercentWidth = pSize->GetWidthPercent();
}
else
{
m_nWidth = pSize->GetWidth();
sal_Int32 const min = static_cast<sal_Int32>(
std::min<sal_uInt32>(GetColumnCount() * MINLAY, MAX_WIDTH));
if( m_nWidth < min )
{
m_nWidth = min;
}
else if( m_nWidth > MAX_WIDTH )
{
m_nWidth = MAX_WIDTH;
}
m_bRelWidth = false;
}
}
else
{
eHoriOrient = text::HoriOrientation::LEFT_AND_WIDTH == eHoriOrient
? text::HoriOrientation::NONE : text::HoriOrientation::FULL;
bSetHoriOrient = true;
m_nWidth = MAX_WIDTH;
}
break;
}
pFrameFormat->SetFormatAttr( *pAutoItemSet );
}
else
{
bSetHoriOrient = true;
m_nWidth = MAX_WIDTH;
}
SwTableLine *pLine1 = m_pTableNode->GetTable().GetTabLines()[0U];
assert(m_pBox1 == pLine1->GetTabBoxes()[0] && !m_bOwnsBox1 && "Why is box 1 change?");
m_pBox1->m_pStartNode = m_pSttNd1;
pLine1->GetTabBoxes().erase( pLine1->GetTabBoxes().begin() );
m_bOwnsBox1 = true;
m_pLineFormat = pLine1->GetFrameFormat();
m_pBoxFormat = m_pBox1->GetFrameFormat();
MakeTable_();
if( bSetHoriOrient )
pFrameFormat->SetFormatAttr( SwFormatHoriOrient( 0, eHoriOrient ) );
// This must be after the call to MakeTable_, because nWidth might be
// changed there.
pFrameFormat->LockModify();
SwFormatFrameSize aSize( SwFrameSize::Variable, m_nWidth );
aSize.SetWidthPercent( nPercentWidth );
pFrameFormat->SetFormatAttr( aSize );
pFrameFormat->UnlockModify();
for (std::unique_ptr<SwXMLTableRow_Impl> & rRow : *m_pRows)
rRow->Dispose();
// now that table is complete, change into DDE table (if appropriate)
if (m_xDDESource.is())
{
// change existing table into DDE table:
// 1) Get DDE field type (get data from dde-source context),
SwDDEFieldType* pFieldType = lcl_GetDDEFieldType( m_xDDESource.get(),
m_pTableNode );
// 2) release the DDE source context,
m_xDDESource.clear();
// 3) create new DDE table, and
std::unique_ptr<SwDDETable> pDDETable( new SwDDETable( m_pTableNode->GetTable(),
pFieldType, false ) );
// 4) set new (DDE)table at node.
m_pTableNode->SetNewTable(std::move(pDDETable), false);
}
// ??? this is always false: root frame is only created in SwViewShell::Init
if( m_pTableNode->GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell() )
{
m_pTableNode->DelFrames();
m_pTableNode->MakeOwnFrames();
}
}
void SwXMLTableContext::MakeTable( SwTableBox *pBox, sal_Int32 nW )
{
//FIXME: here would be a great place to handle XmlId for subtable
m_pLineFormat = GetParentTable()->m_pLineFormat;
m_pBoxFormat = GetParentTable()->m_pBoxFormat;
m_nWidth = nW;
m_bRelWidth = GetParentTable()->m_bRelWidth;
MakeTable_( pBox );
for (std::unique_ptr<SwXMLTableRow_Impl> & rpRow : *m_pRows)
{
// i#113600, to break the cyclic reference to SwXMLTableContext object
rpRow->Dispose();
}
}
const SwStartNode *SwXMLTableContext::InsertTableSection(
const SwStartNode *const pPrevSttNd,
OUString const*const pStringValueStyleName)
{
// The topmost table is the only table that maintains the two members
// pBox1 and bFirstSection.
if( m_xParentTable.is() )
return m_xParentTable->InsertTableSection(pPrevSttNd, pStringValueStyleName);
const SwStartNode *pStNd;
if( m_bFirstSection )
{
Reference<XInterface> xCursorTunnel( GetImport().GetTextImport()->GetCursor(),
UNO_QUERY);
OSL_ENSURE( xCursorTunnel.is(), "missing XUnoTunnel for Cursor" );
OTextCursorHelper *pTextCursor = dynamic_cast<OTextCursorHelper*>(xCursorTunnel.get());
assert(pTextCursor && "SwXTextCursor missing");
// The Cursor already is in the first section
pStNd = pTextCursor->GetPaM()->GetPointNode().FindTableBoxStartNode();
m_bFirstSection = false;
GetImport().GetTextImport()->SetStyleAndAttrs( GetImport(),
GetImport().GetTextImport()->GetCursor(), u"Standard"_ustr, true );
}
else
{
SwDoc* pDoc = GetSwImport().getDoc();
const SwEndNode *pEndNd = pPrevSttNd ? pPrevSttNd->EndOfSectionNode()
: m_pTableNode->EndOfSectionNode();
// #i78921# - make code robust
OSL_ENSURE( pDoc, "<SwXMLTableContext::InsertTableSection(..)> - no <pDoc> at <SwXTextCursor> instance - <SwXTextCurosr> doesn't seem to be registered at a <SwUnoCursor> instance." );
if ( !pDoc )
{
pDoc = &const_cast<SwDoc&>(pEndNd->GetDoc());
}
SwNodeOffset nOffset(pPrevSttNd ? 1 : 0);
SwNodeIndex aIdx( *pEndNd, nOffset );
SwTextFormatColl *pColl =
pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD, false );
pStNd = pDoc->GetNodes().MakeTextSection( aIdx.GetNode(), SwTableBoxStartNode,
pColl );
// Consider the case that a table is defined without a row.
if( !pPrevSttNd && m_pBox1 != nullptr )
{
m_pBox1->m_pStartNode = pStNd;
SwContentNode *pCNd = pDoc->GetNodes()[ pStNd->GetIndex() + 1 ]
->GetContentNode();
SwFrameFormat *const pTableFormat = m_pTableNode->GetTable().GetFrameFormat();
rtl::Reference<SwXCell> xParent = SwXCell::CreateXCell( pTableFormat, m_pBox1 );
DBG_TESTSOLARMUTEX();
SwPaM aPam(*pCNd, *pCNd);
rtl::Reference<SwXTextCursor> xTextCursor =
new SwXTextCursor(*pDoc, xParent, CursorType::TableText,
*aPam.GetPoint(), aPam.GetMark());
GetImport().GetTextImport()->SetCursor( static_cast<XWordCursor*>(xTextCursor.get()) );
}
}
if (pStringValueStyleName)
{ // fdo#62147: apply style to paragraph on string-value cell
GetImport().GetTextImport()->SetStyleAndAttrs( GetImport(),
GetImport().GetTextImport()->GetCursor(), *pStringValueStyleName,
true, false, -1, false); // parameters same as sCellParaStyleName
}
return pStNd;
}
void SwXMLTableContext::endFastElement(sal_Int32 )
{
if( IsValid() && !m_xParentTable.is() )
{
MakeTable();
GetImport().GetTextImport()->SetCursor( m_xOldCursor );
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'InsertText' is required to be utilized.
↑ V530 The return value of function 'SetStyleAndAttrs' is required to be utilized.
↑ V530 The return value of function 'SetStyleAndAttrs' is required to be utilized.
↑ V522 There might be dereferencing of a potential null pointer 'pSrcTextCursor'.
↑ V522 There might be dereferencing of a potential null pointer 'pDstTextCursor'.
↑ V522 There might be dereferencing of a potential null pointer 'pTextCursor'.
↑ V1051 Consider checking for misprints. It's possible that the 'pStNd' should be checked here.