1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <swmodeltestbase.hxx>
12 #include <com/sun/star/awt/CharSet.hpp>
13 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
14 #include <com/sun/star/text/XTextDocument.hpp>
15 #include <com/sun/star/text/WrapTextMode.hpp>
18 #include <formatcontentcontrol.hxx>
20 #include <itabenum.hxx>
22 #include <frameformats.hxx>
23 #include <formatflysplit.hxx>
24 #include <IDocumentLayoutAccess.hxx>
25 #include <rootfrm.hxx>
26 #include <pagefrm.hxx>
28 #include <IDocumentSettingAccess.hxx>
29 #include <sortedobjs.hxx>
30 #include <fmtwrapinfluenceonobjpos.hxx>
35 * Covers sw/source/filter/ww8/ fixes.
37 * Note that these tests are meant to be simple: either load a file and assert some result or build
38 * a document model with code, export and assert that result.
40 * Keep using the various sw_<format>import/export suites for multiple filter calls inside a single
43 class Test
: public SwModelTestBase
47 : SwModelTestBase("/sw/qa/filter/ww8/data/")
52 CPPUNIT_TEST_FIXTURE(Test
, testNegativePageBorderDocImport
)
54 // Given a document with a border distance that is larger than the margin, when loading that
56 createSwDoc("negative-page-border.doc");
58 // Then make sure we map that to a negative border distance (move border from the edge of body
59 // frame towards the center of the page, not towards the edge of the page):
60 uno::Reference
<container::XNameAccess
> xStyleFamily
= getStyles("PageStyles");
61 uno::Reference
<beans::XPropertySet
> xStyle(xStyleFamily
->getByName("Standard"), uno::UNO_QUERY
);
62 auto nTopMargin
= xStyle
->getPropertyValue("TopMargin").get
<sal_Int32
>();
63 // Without the accompanying fix in place, this test would have failed with:
66 // i.e. the border properties influenced the margin, which was 284 twips in the sprmSDyaTop
68 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32
>(501), nTopMargin
);
69 auto aTopBorder
= xStyle
->getPropertyValue("TopBorder").get
<table::BorderLine2
>();
70 CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32
>(159), aTopBorder
.LineWidth
);
71 auto nTopBorderDistance
= xStyle
->getPropertyValue("TopBorderDistance").get
<sal_Int32
>();
72 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32
>(-646), nTopBorderDistance
);
75 CPPUNIT_TEST_FIXTURE(Test
, testPlainTextContentControlExport
)
77 // Given a document with a plain text content control around a text portion:
79 uno::Reference
<lang::XMultiServiceFactory
> xMSF(mxComponent
, uno::UNO_QUERY
);
80 uno::Reference
<text::XTextDocument
> xTextDocument(mxComponent
, uno::UNO_QUERY
);
81 uno::Reference
<text::XText
> xText
= xTextDocument
->getText();
82 uno::Reference
<text::XTextCursor
> xCursor
= xText
->createTextCursor();
83 xText
->insertString(xCursor
, "test", /*bAbsorb=*/false);
84 xCursor
->gotoStart(/*bExpand=*/false);
85 xCursor
->gotoEnd(/*bExpand=*/true);
86 uno::Reference
<text::XTextContent
> xContentControl(
87 xMSF
->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY
);
88 uno::Reference
<beans::XPropertySet
> xContentControlProps(xContentControl
, uno::UNO_QUERY
);
89 xContentControlProps
->setPropertyValue("PlainText", uno::Any(true));
90 xText
->insertTextContent(xCursor
, xContentControl
, /*bAbsorb=*/true);
92 // When exporting to DOCX:
93 save("Office Open XML Text");
95 // Then make sure the expected markup is used:
96 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
97 // Without the accompanying fix in place, this test would have failed with:
100 // - XPath '//w:sdt/w:sdtPr/w:text' number of nodes is incorrect
101 // i.e. the plain text content control was turned into a rich text one on export.
102 assertXPath(pXmlDoc
, "//w:sdt/w:sdtPr/w:text", 1);
105 CPPUNIT_TEST_FIXTURE(Test
, testDocxComboBoxContentControlExport
)
107 // Given a document with a combo box content control around a text portion:
109 SwDoc
* pDoc
= getSwDoc();
110 SwWrtShell
* pWrtShell
= pDoc
->GetDocShell()->GetWrtShell();
111 pWrtShell
->InsertContentControl(SwContentControlType::COMBO_BOX
);
113 // When exporting to DOCX:
114 save("Office Open XML Text");
116 // Then make sure the expected markup is used:
117 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
118 // Without the accompanying fix in place, this test would have failed with:
121 // - XPath '//w:sdt/w:sdtPr/w:comboBox' number of nodes is incorrect
122 // i.e. the combo box content control was turned into a drop-down one on export.
123 assertXPath(pXmlDoc
, "//w:sdt/w:sdtPr/w:comboBox", 1);
126 CPPUNIT_TEST_FIXTURE(Test
, testDocxHyperlinkShape
)
128 // Given a document with a hyperlink at char positions 0 -> 6 and a shape with text anchored at
131 uno::Reference
<text::XTextDocument
> xTextDocument(mxComponent
, uno::UNO_QUERY
);
132 uno::Reference
<text::XText
> xText
= xTextDocument
->getText();
133 uno::Reference
<text::XTextCursor
> xCursor
= xText
->createTextCursor();
134 xText
->insertString(xCursor
, "beforeafter", /*bAbsorb=*/false);
135 xCursor
->gotoStart(/*bExpand=*/false);
136 xCursor
->goRight(/*nCount=*/6, /*bExpand=*/true);
137 uno::Reference
<beans::XPropertySet
> xCursorProps(xCursor
, uno::UNO_QUERY
);
138 xCursorProps
->setPropertyValue("HyperLinkURL", uno::Any(OUString("http://www.example.com/")));
139 xCursor
->gotoStart(/*bExpand=*/false);
140 xCursor
->goRight(/*nCount=*/6, /*bExpand=*/false);
141 uno::Reference
<lang::XMultiServiceFactory
> xFactory(mxComponent
, uno::UNO_QUERY
);
142 uno::Reference
<drawing::XShape
> xShape(
143 xFactory
->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY
);
144 xShape
->setSize(awt::Size(5000, 5000));
145 uno::Reference
<beans::XPropertySet
> xShapeProps(xShape
, uno::UNO_QUERY
);
146 xShapeProps
->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER
));
147 uno::Reference
<text::XTextContent
> xShapeContent(xShape
, uno::UNO_QUERY
);
148 xText
->insertTextContent(xCursor
, xShapeContent
, /*bAbsorb=*/false);
149 xShapeProps
->setPropertyValue("TextBox", uno::Any(true));
151 // When saving this document to DOCX, then make sure we don't crash on export (due to an
152 // assertion failure for not-well-formed XML output):
153 save("Office Open XML Text");
156 CPPUNIT_TEST_FIXTURE(Test
, testDocxContentControlDropdownEmptyDisplayText
)
158 // Given a document with a dropdown content control, the only list item has no display text
161 SwDoc
* pDoc
= getSwDoc();
162 SwWrtShell
* pWrtShell
= pDoc
->GetDocShell()->GetWrtShell();
163 pWrtShell
->InsertContentControl(SwContentControlType::DROP_DOWN_LIST
);
165 // When saving to DOCX:
166 save("Office Open XML Text");
168 // Then make sure that no display text attribute is written:
169 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
170 // Without the accompanying fix in place, this test would have failed with:
171 // - XPath '//w:sdt/w:sdtPr/w:dropDownList/w:listItem' unexpected 'displayText' attribute
172 // i.e. we wrote an empty attribute instead of omitting it.
173 assertXPathNoAttribute(pXmlDoc
, "//w:sdt/w:sdtPr/w:dropDownList/w:listItem", "displayText");
176 CPPUNIT_TEST_FIXTURE(Test
, testDocxSymbolFontExport
)
178 // Create document with symbol character and font Wingdings
179 mxComponent
= loadFromDesktop("private:factory/swriter");
180 uno::Reference
<text::XTextDocument
> xTextDocument(mxComponent
, uno::UNO_QUERY
);
181 uno::Reference
<text::XText
> xText
= xTextDocument
->getText();
182 uno::Reference
<text::XTextCursor
> xCursor
= xText
->createTextCursor();
184 xText
->insertString(xCursor
, u
"", true);
186 uno::Reference
<text::XTextRange
> xRange
= xCursor
;
187 uno::Reference
<beans::XPropertySet
> xTextProps(xRange
, uno::UNO_QUERY
);
188 xTextProps
->setPropertyValue("CharFontName", uno::Any(OUString("Wingdings")));
189 xTextProps
->setPropertyValue("CharFontNameAsian", uno::Any(OUString("Wingdings")));
190 xTextProps
->setPropertyValue("CharFontNameComplex", uno::Any(OUString("Wingdings")));
191 xTextProps
->setPropertyValue("CharFontCharSet", uno::Any(awt::CharSet::SYMBOL
));
193 // When exporting to DOCX:
194 save("Office Open XML Text");
196 // Then make sure the expected markup is used:
197 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
199 assertXPath(pXmlDoc
, "//w:p/w:r/w:sym", 1);
200 assertXPath(pXmlDoc
, "//w:p/w:r/w:sym[1]", "font", "Wingdings");
201 assertXPath(pXmlDoc
, "//w:p/w:r/w:sym[1]", "char", "f0e0");
204 CPPUNIT_TEST_FIXTURE(Test
, testDocxFloatingTableExport
)
206 // Given a document with a floating table:
208 SwDoc
* pDoc
= getSwDoc();
209 SwWrtShell
* pWrtShell
= pDoc
->GetDocShell()->GetWrtShell();
211 SwInsertTableOptions
aTableOptions(SwInsertTableFlags::DefaultBorder
, 0);
212 pWrtShell
->InsertTable(aTableOptions
, 1, 1);
213 pWrtShell
->MoveTable(GotoPrevTable
, fnTableStart
);
217 SwFlyFrameAttrMgr
aMgr(true, pWrtShell
, Frmmgr_Type::TEXT
, nullptr);
218 pWrtShell
->StartAllAction();
219 aMgr
.InsertFlyFrame(RndStdIds::FLY_AT_PARA
, aMgr
.GetPos(), aMgr
.GetSize());
220 // Mark it as a floating table:
221 auto& rFlys
= *pDoc
->GetSpzFrameFormats();
222 auto pFly
= rFlys
[0];
223 SwAttrSet
aSet(pFly
->GetAttrSet());
224 aSet
.Put(SwFormatFlySplit(true));
225 pDoc
->SetAttr(aSet
, *pFly
);
226 pWrtShell
->EndAllAction();
228 // When saving to docx:
229 save("Office Open XML Text");
231 // Then make sure we write a floating table, not a textframe containing a table:
232 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
233 // Without the accompanying fix in place, this test would have failed with:
234 // - XPath '//w:tbl/w:tblPr/w:tblpPr' number of nodes is incorrect
235 // i.e. no floating table was exported.
236 assertXPath(pXmlDoc
, "//w:tbl/w:tblPr/w:tblpPr", 1);
239 CPPUNIT_TEST_FIXTURE(Test
, testDocFloatingTableImport
)
241 // Given a document with 2 pages:
242 createSwDoc("floattable-compat14.doc");
244 // When laying out that document:
247 // Make sure that the table is split between page 1 and page 2:
248 SwDoc
* pDoc
= getSwDoc();
249 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
250 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
251 CPPUNIT_ASSERT(pPage1
);
252 // Without the accompanying fix in place, this test would have failed, the fly frame was not
253 // split between page 1 and page 2.
254 CPPUNIT_ASSERT(pPage1
->GetNext());
257 CPPUNIT_TEST_FIXTURE(Test
, testWrapThroughLayoutInCell
)
259 // Given a document with a shape, "keep inside text boundaries" is off, wrap type is set to
262 uno::Reference
<css::lang::XMultiServiceFactory
> xFactory(mxComponent
, uno::UNO_QUERY
);
263 uno::Reference
<drawing::XShape
> xShape(
264 xFactory
->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY
);
265 xShape
->setSize(awt::Size(10000, 10000));
266 uno::Reference
<beans::XPropertySet
> xShapeProps(xShape
, uno::UNO_QUERY
);
267 xShapeProps
->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER
));
268 xShapeProps
->setPropertyValue("Surround", uno::Any(text::WrapTextMode_THROUGH
));
269 xShapeProps
->setPropertyValue("HoriOrientRelation", uno::Any(text::RelOrientation::FRAME
));
270 uno::Reference
<drawing::XDrawPageSupplier
> xDrawPageSupplier(mxComponent
, uno::UNO_QUERY
);
271 xDrawPageSupplier
->getDrawPage()->add(xShape
);
273 // When saving to docx:
274 save("Office Open XML Text");
276 // Then make sure that layoutInCell is undoing the effect of the import-time tweak:
277 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
278 // Without the accompanying fix in place, this test would have failed with:
281 // - attribute 'layoutInCell' of '//wp:anchor' incorrect value.
282 // i.e. layoutInCell was disabled, leading to bad layout in Word.
283 assertXPath(pXmlDoc
, "//wp:anchor", "layoutInCell", "1");
286 CPPUNIT_TEST_FIXTURE(Test
, test3Endnotes
)
288 // Given a DOC file with 3 endnotes:
289 createSwDoc("3endnotes.doc");
291 // When laying out that document:
294 // Then make sure that all 3 endnotes are on the last page, like in Word:
295 SwDoc
* pDoc
= getSwDoc();
296 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
297 SwPageFrame
* pPage
= pLayout
->GetLastPage();
298 SwFootnoteContFrame
* pFootnoteCont
= pPage
->FindFootnoteCont();
300 for (SwFrame
* pLower
= pFootnoteCont
->GetLower(); pLower
; pLower
= pLower
->GetNext())
304 // Without the accompanying fix in place, this test would have failed with:
307 // i.e. only 1 endnote was on the last page, the other 2 was not moved to the end of the
308 // document, which is incorrect.
309 CPPUNIT_ASSERT_EQUAL(3, nEndnotes
);
312 CPPUNIT_TEST_FIXTURE(Test
, testDoNotBreakWrappedTables
)
314 // Given a document with the DO_NOT_BREAK_WRAPPED_TABLES compat mode enabled:
316 SwDoc
* pDoc
= getSwDoc();
317 IDocumentSettingAccess
& rIDSA
= pDoc
->getIDocumentSettingAccess();
318 rIDSA
.set(DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES
, true);
320 // When saving to docx:
321 save("Office Open XML Text");
323 // Then make sure the compat flag is serialized:
324 xmlDocUniquePtr pXmlDoc
= parseExport("word/settings.xml");
325 // Without the accompanying fix in place, this test would have failed with:
328 // - XPath '/w:settings/w:compat/w:doNotBreakWrappedTables' number of nodes is incorrect
329 // i.e. <w:doNotBreakWrappedTables> was not written.
330 assertXPath(pXmlDoc
, "/w:settings/w:compat/w:doNotBreakWrappedTables", 1);
333 CPPUNIT_TEST_FIXTURE(Test
, testDOCfDontBreakWrappedTables
)
335 // Given a document with fDontBreakWrappedTables:
336 // When importing that document:
337 createSwDoc("dont-break-wrapped-tables.doc");
339 // Then make sure that the matching compat flag is set:
340 SwDoc
* pDoc
= getSwDoc();
341 IDocumentSettingAccess
& rIDSA
= pDoc
->getIDocumentSettingAccess();
342 bool bDontBreakWrappedTables
= rIDSA
.get(DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES
);
343 // Without the accompanying fix in place, this test would have failed, the compat flag was not
345 CPPUNIT_ASSERT(bDontBreakWrappedTables
);
348 CPPUNIT_TEST_FIXTURE(Test
, testDOCFloatingTableHiddenAnchor
)
350 // Given a document with a normal table and a floating table with a hidden anchor:
351 createSwDoc("floattable-hidden-anchor.doc");
353 // When laying out that document:
354 xmlDocUniquePtr pLayout
= parseLayoutDump();
356 // Then make sure that both tables are visible:
357 // Without the accompanying fix in place, this test would have failed with:
360 // i.e. the floating table was lost.
361 assertXPath(pLayout
, "//tab", 2);
364 CPPUNIT_TEST_FIXTURE(Test
, testDOCVerticalFlyOffset
)
366 // Given a document with 2 pages, a floating table on the first page and an inline table on the
368 createSwDoc("floattable-vertical-fly-offset.doc");
370 // When laying out that document:
373 // Then make sure that the tables don't overlap:
374 SwDoc
* pDoc
= getSwDoc();
375 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
376 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
377 CPPUNIT_ASSERT(pPage1
);
378 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
379 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
380 // Page 1 has a floating table:
381 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
382 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
383 // Without the accompanying fix in place, this test would have failed, there was no second page.
384 CPPUNIT_ASSERT(pPage2
);
385 SwFrame
* pBody2
= pPage2
->GetLower();
386 SwFrame
* pTable2
= pBody2
->GetLower();
387 CPPUNIT_ASSERT(pTable2
);
388 // Page 2 starts with an inline table:
389 CPPUNIT_ASSERT(pTable2
->IsTabFrame());
392 CPPUNIT_TEST_FIXTURE(Test
, testFloattableThenFloattable
)
394 // Given a document that contains a floating table, immediately followed by an other floating
396 // When importing the document & laying it out:
397 createSwDoc("floattable-then-floattable.doc");
400 // Then make sure that the two floating table has different anchors:
401 SwDoc
* pDoc
= getSwDoc();
402 auto& rFlys
= *pDoc
->GetSpzFrameFormats();
403 auto pFly1
= rFlys
[0];
404 SwNodeOffset nFly1Anchor
= pFly1
->GetAttrSet().GetAnchor().GetAnchorContentNode()->GetIndex();
405 auto pFly2
= rFlys
[1];
406 SwNodeOffset nFly2Anchor
= pFly2
->GetAttrSet().GetAnchor().GetAnchorContentNode()->GetIndex();
407 // Without the accompanying fix in place, this test would have failed with:
410 // i.e. the two anchor positions were the same instead of first anchor followed by the second
412 CPPUNIT_ASSERT_EQUAL(nFly1Anchor
+ 1, nFly2Anchor
);
415 CPPUNIT_TEST_FIXTURE(Test
, testFloattableOverlapNeverDOCXExport
)
417 // Given a document with a floating table, overlap is not allowed:
419 SwDoc
* pDoc
= getSwDoc();
420 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
421 pWrtShell
->Insert2("before table");
423 SwInsertTableOptions
aTableOptions(SwInsertTableFlags::DefaultBorder
, 0);
424 pWrtShell
->InsertTable(aTableOptions
, /*nRows=*/1, /*nCols=*/1);
425 pWrtShell
->MoveTable(GotoPrevTable
, fnTableStart
);
428 // Wrap the table in a text frame:
429 SwFlyFrameAttrMgr
aMgr(true, pWrtShell
, Frmmgr_Type::TEXT
, nullptr);
430 pWrtShell
->StartAllAction();
431 aMgr
.InsertFlyFrame(RndStdIds::FLY_AT_PARA
, aMgr
.GetPos(), aMgr
.GetSize());
432 pWrtShell
->EndAllAction();
433 // Allow the text frame to split:
434 pWrtShell
->StartAllAction();
435 auto& rFlys
= *pDoc
->GetSpzFrameFormats();
436 auto pFly
= rFlys
[0];
437 SwAttrSet
aSet(pFly
->GetAttrSet());
438 aSet
.Put(SwFormatFlySplit(true));
439 // Don't allow overlap:
440 SwFormatWrapInfluenceOnObjPos aInfluence
;
441 aInfluence
.SetAllowOverlap(false);
442 aSet
.Put(aInfluence
);
443 pDoc
->SetAttr(aSet
, *pFly
);
444 pWrtShell
->EndAllAction();
446 // When saving to DOCX:
447 save("Office Open XML Text");
449 // Then make sure that the overlap=never markup is written:
450 xmlDocUniquePtr pXmlDoc
= parseExport("word/document.xml");
451 // Without the accompanying fix in place, this test would have failed with:
454 // - XPath '//w:tblPr/w:tblOverlap' number of nodes is incorrect
455 // i.e. <w:tblOverlap> was not written.
456 assertXPath(pXmlDoc
, "//w:tblPr/w:tblOverlap", "val", "never");
459 CPPUNIT_TEST_FIXTURE(Test
, testFloattableOverlapNeverDOCImport
)
461 // Given a document with two floating tables, the second has sprmTFNoAllowOverlap=1 set:
462 // When importing that document:
463 createSwDoc("floattable-tbl-overlap.doc");
465 // Then make sure the second table is marked as "can't overlap":
466 SwDoc
* pDoc
= getSwDoc();
467 sw::FrameFormats
<sw::SpzFrameFormat
*>& rFlys
= *pDoc
->GetSpzFrameFormats();
468 sw::SpzFrameFormat
* pFly
= rFlys
[1];
469 // Without the accompanying fix in place, this test would have failed, the fly had the default
471 CPPUNIT_ASSERT(!pFly
->GetAttrSet().GetWrapInfluenceOnObjPos().GetAllowOverlap());
475 CPPUNIT_PLUGIN_IMPLEMENT();
477 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */