summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stahl <Michael.Stahl@cib.de>2020-10-01 17:31:21 +0200
committerMichael Stahl <Michael.Stahl@cib.de>2020-10-16 11:09:32 +0200
commit42db7c8086abb205dbdcf6b44dcd5377866425e0 (patch)
tree2ce070e25178e138ee1b96b5748f0078e1a5008a
parent1bb8842f8c1f8bd12a3b9e6a99c037579654d2cd (diff)
sw: ODF import: convert the simplest sub-tables to rowspan tables
Before OOo 2.3, CWS swnewtable, Writer represented complex table structures as sub-tables, i.e. <table:table table:is-sub-table="true">. Try to convert these to the modern rowspan tables, which export to non-ODF formats much easier. There are some cases where the result is going to look different, or where further work is required to adapt other things in the document; leave these alone for now. Change-Id: I6a6c497089ef886826484d2d723bf57c72f95b14
-rw-r--r--sw/inc/swtable.hxx4
-rw-r--r--sw/source/core/table/swnewtable.cxx287
-rw-r--r--sw/source/filter/xml/xmlimp.cxx16
3 files changed, 307 insertions, 0 deletions
diff --git a/sw/inc/swtable.hxx b/sw/inc/swtable.hxx
index a373f561704f..b62c2567e2de 100644
--- a/sw/inc/swtable.hxx
+++ b/sw/inc/swtable.hxx
@@ -168,6 +168,7 @@ private:
void AdjustWidths( const long nOld, const long nNew );
void NewSetTabCols( Parm &rP, const SwTabCols &rNew, const SwTabCols &rOld,
const SwTableBox *pStart, bool bCurRowOnly );
+ void ConvertSubtableBox(sal_uInt16 const nRow, sal_uInt16 const nBox);
public:
@@ -340,6 +341,9 @@ public:
#endif
bool HasLayout() const;
+
+ bool CanConvertSubtables() const;
+ void ConvertSubtables();
};
/// SwTableLine is one table row in the document model.
diff --git a/sw/source/core/table/swnewtable.cxx b/sw/source/core/table/swnewtable.cxx
index 04a2509b1e37..892b31e807cc 100644
--- a/sw/source/core/table/swnewtable.cxx
+++ b/sw/source/core/table/swnewtable.cxx
@@ -21,6 +21,7 @@
#include <tblsel.hxx>
#include <tblrwcl.hxx>
#include <ndtxt.hxx>
+#include <ndole.hxx>
#include <node.hxx>
#include <UndoTable.hxx>
#include <pam.hxx>
@@ -30,8 +31,14 @@
#include <fmtfsize.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
+#include <IDocumentChartDataProviderAccess.hxx>
#include <IDocumentContentOperations.hxx>
+#include <IDocumentFieldsAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <fmtfld.hxx>
+#include <ftnidx.hxx>
#include <cstdlib>
#include <vector>
#include <set>
@@ -2089,6 +2096,286 @@ void SwTable::CleanUpBottomRowSpan( sal_uInt16 nDelLines )
}
}
+/**
+ This is kind of similar to InsertSpannedRow()/InsertRow() but that one would
+ recursively copy subtables, which would kind of defeat the purpose;
+ this function directly moves the subtable rows's cells into the newly
+ created rows. For the non-subtable boxes, covered-cells are created.
+
+ Outer row heights are adjusted to match the inner row heights, and the
+ last row's height is tweaked to ensure the sum of the heights is at least
+ the original outer row's minimal height.
+
+ Inner row backgrounds are copied to its cells, if they lack a background.
+
+ This currently can't handle more than 1 subtable in a row;
+ the inner rows of all subtables would need to be sorted by their height
+ to create the correct outer row structure, which is tricky and probably
+ requires a layout for the typical variable-height case.
+
+ */
+void SwTable::ConvertSubtableBox(sal_uInt16 const nRow, sal_uInt16 const nBox)
+{
+ SwDoc *const pDoc(GetFrameFormat()->GetDoc());
+ SwTableLine *const pSourceLine(GetTabLines()[nRow]);
+ SwTableBox *const pSubTableBox(pSourceLine->GetTabBoxes()[nBox]);
+ assert(!pSubTableBox->GetTabLines().empty());
+ // are relative (%) heights possible? apparently not
+ SwFormatFrameSize const outerSize(pSourceLine->GetFrameFormat()->GetFrameSize());
+ long minHeights(0);
+ {
+ SwFormatFrameSize const* pSize(nullptr);
+ SwFrameFormat const& rSubLineFormat(*pSubTableBox->GetTabLines()[0]->GetFrameFormat());
+ if (rSubLineFormat.GetItemState(RES_FRM_SIZE, true,
+ reinterpret_cast<SfxPoolItem const**>(&pSize)) == SfxItemState::SET)
+ { // for first row, apply height from inner row to outer row.
+ // in case the existing outer row height was larger than the entire
+ // subtable, the last inserted row needs to be tweaked (below)
+ pSourceLine->GetFrameFormat()->SetFormatAttr(*pSize);
+ if (pSize->GetHeightSizeType() != ATT_VAR_SIZE)
+ {
+ minHeights += pSize->GetHeight();
+ }
+ }
+ }
+ for (size_t i = 1; i < pSubTableBox->GetTabLines().size(); ++i)
+ {
+ SwTableLine *const pSubLine(pSubTableBox->GetTabLines()[i]);
+ SwTableLine *const pNewLine = new SwTableLine(
+ static_cast<SwTableLineFormat*>(pSourceLine->GetFrameFormat()),
+ pSourceLine->GetTabBoxes().size() - 1 + pSubLine->GetTabBoxes().size(),
+ nullptr);
+ SwFrameFormat const& rSubLineFormat(*pSubLine->GetFrameFormat());
+ SwFormatFrameSize const* pSize(nullptr);
+ if (rSubLineFormat.GetItemState(RES_FRM_SIZE, true,
+ reinterpret_cast<SfxPoolItem const**>(&pSize)) == SfxItemState::SET)
+ { // for rows 2..N, copy inner row height to outer row
+ pNewLine->ClaimFrameFormat();
+ pNewLine->GetFrameFormat()->SetFormatAttr(*pSize);
+ if (pSize->GetHeightSizeType() != ATT_VAR_SIZE)
+ {
+ minHeights += pSize->GetHeight();
+ }
+ }
+ // ensure the sum of the lines is at least as high as the outer line was
+ if (i == pSubTableBox->GetTabLines().size() - 1
+ && outerSize.GetHeightSizeType() != ATT_VAR_SIZE
+ && minHeights < outerSize.GetHeight())
+ {
+ SwFormatFrameSize lastSize(pNewLine->GetFrameFormat()->GetFrameSize());
+ lastSize.SetHeight(lastSize.GetHeight() + outerSize.GetHeight() - minHeights);
+ if (lastSize.GetHeightSizeType() == ATT_VAR_SIZE)
+ {
+ lastSize.SetHeightSizeType(ATT_MIN_SIZE);
+ }
+ pNewLine->GetFrameFormat()->SetFormatAttr(lastSize);
+ }
+ SfxPoolItem const* pRowBrush(nullptr);
+ rSubLineFormat.GetItemState(RES_BACKGROUND, true, &pRowBrush);
+ GetTabLines().insert(GetTabLines().begin() + nRow + i, pNewLine);
+ for (size_t j = 0; j < pSourceLine->GetTabBoxes().size(); ++j)
+ {
+ if (j == nBox)
+ {
+ for (size_t k = 0; k < pSubLine->GetTabBoxes().size(); ++k)
+ {
+ // move box k to new outer row
+ SwTableBox *const pSourceBox(pSubLine->GetTabBoxes()[k]);
+ assert(pSourceBox->getRowSpan() == 1);
+ // import filter (xmltbli.cxx) converts all box widths to absolute
+ assert(pSourceBox->GetFrameFormat()->GetFrameSize().GetWidthPercent() == 0);
+ ::InsTableBox(pDoc, GetTableNode(), pNewLine,
+ static_cast<SwTableBoxFormat*>(pSourceBox->GetFrameFormat()),
+ pSourceBox, j+k, 1);
+ // insert dummy text node...
+ pDoc->GetNodes().MakeTextNode(
+ SwNodeIndex(*pSourceBox->GetSttNd(), +1),
+ pDoc->GetDfltTextFormatColl());
+ SwNodeRange content(*pSourceBox->GetSttNd(), +2,
+ *pSourceBox->GetSttNd()->EndOfSectionNode());
+ SwTableBox *const pNewBox(pNewLine->GetTabBoxes()[j+k]);
+ SwNodeIndex insPos(*pNewBox->GetSttNd(), 1);
+ // MoveNodes would delete the box SwStartNode/SwEndNode
+ // without the dummy node
+#if 0
+ pDoc->GetNodes().MoveNodes(content, pDoc->GetNodes(), insPos, false);
+#else
+ pDoc->getIDocumentContentOperations().MoveNodeRange(content, insPos, SwMoveFlags::NO_DELFRMS);
+#endif
+ // delete the empty node that was bundled in the new box
+ pDoc->GetNodes().Delete(insPos);
+ if (pRowBrush)
+ {
+ SfxPoolItem const* pCellBrush(nullptr);
+ if (pNewBox->GetFrameFormat()->GetItemState(RES_BACKGROUND, true, &pCellBrush) != SfxItemState::SET)
+ { // set inner row background on inner cell
+ pNewBox->ClaimFrameFormat();
+ pNewBox->GetFrameFormat()->SetFormatAttr(*pRowBrush);
+ }
+ }
+ // assume that the borders can be left as they are, because
+ // lines don't have borders, only boxes do
+ }
+ }
+ else
+ {
+ // insert covered cell for box j
+ SwTableBox *const pSourceBox(pSourceLine->GetTabBoxes()[j]);
+ assert(pSourceBox->GetTabLines().empty()); // checked for that
+ sal_uInt16 const nInsPos(j < nBox ? j : j + pSubLine->GetTabBoxes().size() - 1);
+ ::InsTableBox(pDoc, GetTableNode(), pNewLine,
+ static_cast<SwTableBoxFormat*>(pSourceBox->GetFrameFormat()),
+ pSourceBox, nInsPos, 1);
+ // adjust row span:
+ // N rows in subtable, N-1 rows inserted:
+ // -1 -> -N ; -(N-1) ... -1
+ // -2 -> -(N+1) ; -N .. -2
+ // 1 -> N ; -(N-1) .. -1
+ // 2 -> N+1 ; -N .. -2
+ long newSourceRowSpan(pSourceBox->getRowSpan());
+ long newBoxRowSpan;
+ if (newSourceRowSpan < 0)
+ {
+ newSourceRowSpan -= pSubTableBox->GetTabLines().size() - 1;
+ newBoxRowSpan = newSourceRowSpan + i;
+ }
+ else
+ {
+ newSourceRowSpan += pSubTableBox->GetTabLines().size() - 1;
+ newBoxRowSpan = -(newSourceRowSpan - sal::static_int_cast<long>(i));
+ }
+ pNewLine->GetTabBoxes()[nInsPos]->setRowSpan(newBoxRowSpan);
+ if (i == pSubTableBox->GetTabLines().size() - 1)
+ { // only last iteration
+ pSourceBox->setRowSpan(newSourceRowSpan);
+ }
+ }
+ }
+ }
+ // delete inner rows 2..N
+ while (1 < pSubTableBox->GetTabLines().size())
+ {
+ // careful: the last box deletes pSubLine!
+ SwTableLine *const pSubLine(pSubTableBox->GetTabLines()[1]);
+ for (size_t j = pSubLine->GetTabBoxes().size(); 0 < j; --j)
+ {
+ SwTableBox *const pBox(pSubLine->GetTabBoxes()[0]);
+ DeleteBox_(*this, pBox, nullptr, false, false, nullptr);
+ }
+ }
+ // fix row spans in lines preceding nRow
+ lcl_ChangeRowSpan(*this, pSubTableBox->GetTabLines().size() - 1, nRow - 1, false);
+ // note: the first line of the inner table remains; caller will call
+ // GCLines() to remove it
+}
+
+bool SwTable::CanConvertSubtables() const
+{
+ for (SwTableLine const*const pLine : GetTabLines())
+ {
+ bool haveSubtable(false);
+ for (SwTableBox const*const pBox : pLine->GetTabBoxes())
+ {
+ if (pBox->IsFormulaOrValueBox() == RES_BOXATR_FORMULA)
+ {
+ return false; // no table box formulas yet
+ }
+ if (!pBox->GetTabLines().empty())
+ {
+ if (haveSubtable)
+ { // can't handle 2 subtable in a row yet
+ return false;
+ }
+ haveSubtable = true;
+ for (SwTableLine const*const pInnerLine : pBox->GetTabLines())
+ {
+ // bitmap row background will look different
+ SwFrameFormat const& rRowFormat(*pInnerLine->GetFrameFormat());
+ std::shared_ptr<SvxBrushItem> pBrush(rRowFormat.makeBackgroundBrushItem());
+ assert(pBrush);
+ if (pBrush->GetGraphicObject() != nullptr)
+ {
+ /* TODO: all cells could override this?
+ for (SwTableBox & rInnerBox : rInnerLine.GetTabBoxes())
+ */
+ if (1 < pInnerLine->GetTabBoxes().size()) // except if only 1 cell?
+ {
+ return false;
+ }
+ }
+ for (SwTableBox const*const pInnerBox : pInnerLine->GetTabBoxes())
+ {
+ if (!pInnerBox->GetTabLines().empty())
+ {
+ return false; // nested subtable :(
+ }
+ }
+ }
+ }
+ }
+ }
+ // note: fields that refer to table cells may be *outside* the table,
+ // so the entire document needs to be imported before checking here
+ // (same for table box formulas and charts)
+ SwDoc *const pDoc(GetFrameFormat()->GetDoc());
+ SwFieldType const*const pTableFields(
+ pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Table, "", false));
+ SwIterator<SwFormatField, SwFieldType> aIter(*pTableFields);
+ if (aIter.First() != nullptr)
+ {
+ return false; // no formulas in fields yet
+ }
+ if (pDoc->GetAttrPool().GetItemCount2(RES_BOXATR_FORMULA) != 0)
+ {
+ return false; // no table box formulas yet
+ }
+ OUString const tableName(GetFrameFormat()->GetName());
+ SwNodeIndex temp(*pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), +1);
+ while (SwStartNode const*const pStartNode = temp.GetNode().GetStartNode())
+ {
+ ++temp;
+ SwOLENode const*const pOLENode(temp.GetNode().GetOLENode());
+ if (pOLENode && tableName == pOLENode->GetChartTableName())
+ { // there are charts that refer to this table
+ // presumably such charts would need to be adapted somehow?
+ return false;
+ }
+ temp.Assign(*pStartNode->EndOfSectionNode(), +1);
+ }
+ return true;
+}
+
+void SwTable::ConvertSubtables()
+{
+ for (size_t i = 0; i < GetTabLines().size(); ++i)
+ {
+ SwTableLine *const pLine(GetTabLines()[i]);
+ for (size_t j = 0; j < pLine->GetTabBoxes().size(); ++j)
+ {
+ SwTableBox *const pBox(pLine->GetTabBoxes()[j]);
+ SwTableLines & rInnerLines(pBox->GetTabLines());
+ if (!rInnerLines.empty())
+ {
+ ConvertSubtableBox(i, j);
+ }
+ }
+ }
+ GCLines();
+ m_bNewModel = true;
+#if 0
+ // note: outline nodes (and ordinary lists) are sorted by MoveNodes() itself
+ // (this could change order inside table of contents, but that's a
+ // really esoteric use-case)
+ // nodes were moved - sort marks, redlines, footnotes
+ SwDoc *const pDoc(GetFrameFormat()->GetDoc());
+ pDoc->getIDocumentMarkAccess()->assureSortedMarkContainers();
+ pDoc->getIDocumentRedlineAccess().GetRedlineTable().Resort();
+ pDoc->GetFootnoteIdxs().UpdateAllFootnote();
+#endif
+ // assume that there aren't any node indexes to the deleted box start/end nodes
+ CHECK_TABLE( *this )
+}
+
#ifdef DBG_UTIL
struct RowSpanCheck
diff --git a/sw/source/filter/xml/xmlimp.cxx b/sw/source/filter/xml/xmlimp.cxx
index 75766a7f8a64..68a72af1cb2f 100644
--- a/sw/source/filter/xml/xmlimp.cxx
+++ b/sw/source/filter/xml/xmlimp.cxx
@@ -967,6 +967,22 @@ void SwXMLImport::endDocument()
}
}
+#if 1
+ if (!pDoc) { pDoc = SwImport::GetDocFromXMLImport(*this); }
+ for (sal_uLong i = 0; i < pDoc->GetNodes().Count(); ++i)
+ {
+ if (SwTableNode *const pTableNode = pDoc->GetNodes()[i]->GetTableNode())
+ {
+ if (!pTableNode->GetTable().IsNewModel()
+ && pTableNode->GetTable().CanConvertSubtables())
+ {
+ pTableNode->GetTable().ConvertSubtables();
+ }
+ }
+ // don't skip to the end; nested tables could have subtables too...
+ }
+#endif
+
// delegate to parent: takes care of error handling
SvXMLImport::endDocument();
ClearTextImport();