Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / qa / filter / ww8 / ww8.cxx
blobdb1dbcd1bf36ea35b154404915b056e43a4dc612
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
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>
17 #include <docsh.hxx>
18 #include <formatcontentcontrol.hxx>
19 #include <wrtsh.hxx>
20 #include <itabenum.hxx>
21 #include <frmmgr.hxx>
22 #include <frameformats.hxx>
23 #include <formatflysplit.hxx>
24 #include <IDocumentLayoutAccess.hxx>
25 #include <rootfrm.hxx>
26 #include <pagefrm.hxx>
27 #include <ftnfrm.hxx>
28 #include <IDocumentSettingAccess.hxx>
29 #include <sortedobjs.hxx>
30 #include <fmtwrapinfluenceonobjpos.hxx>
32 namespace
34 /**
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
41 * test.
43 class Test : public SwModelTestBase
45 public:
46 Test()
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
55 // document:
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:
64 // - Expected: 501
65 // - Actual : 342
66 // i.e. the border properties influenced the margin, which was 284 twips in the sprmSDyaTop
67 // SPRM.
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:
78 createSwDoc();
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:
98 // - Expected: 1
99 // - Actual : 0
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:
108 createSwDoc();
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:
119 // - Expected: 1
120 // - Actual : 0
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
129 // char position 6:
130 createSwDoc();
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
159 // (only a value):
160 createSwDoc();
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:
207 createSwDoc();
208 SwDoc* pDoc = getSwDoc();
209 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
210 // Insert a table:
211 SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
212 pWrtShell->InsertTable(aTableOptions, 1, 1);
213 pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
214 // Select it:
215 pWrtShell->SelAll();
216 // Wrap in a fly:
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:
245 calcLayout();
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
260 // "through":
261 createSwDoc();
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:
279 // - Expected: 1
280 // - Actual : 0
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:
292 calcLayout();
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();
299 int nEndnotes = 0;
300 for (SwFrame* pLower = pFootnoteCont->GetLower(); pLower; pLower = pLower->GetNext())
302 ++nEndnotes;
304 // Without the accompanying fix in place, this test would have failed with:
305 // - Expected: 3
306 // - Actual : 1
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:
315 createSwDoc();
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:
326 // - Expected: 1
327 // - Actual : 0
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
344 // set.
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:
358 // - Expected: 2
359 // - Actual : 1
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
367 // second page:
368 createSwDoc("floattable-vertical-fly-offset.doc");
370 // When laying out that document:
371 calcLayout();
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
395 // table:
396 // When importing the document & laying it out:
397 createSwDoc("floattable-then-floattable.doc");
398 calcLayout();
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:
408 // - Expected: 42
409 // - Actual : 41
410 // i.e. the two anchor positions were the same instead of first anchor followed by the second
411 // anchor.
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:
418 createSwDoc();
419 SwDoc* pDoc = getSwDoc();
420 SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
421 pWrtShell->Insert2("before table");
422 // Insert a table:
423 SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
424 pWrtShell->InsertTable(aTableOptions, /*nRows=*/1, /*nCols=*/1);
425 pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
426 // Select table:
427 pWrtShell->SelAll();
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:
452 // - Expected: 1
453 // - Actual : 0
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
470 // "can overlap".
471 CPPUNIT_ASSERT(!pFly->GetAttrSet().GetWrapInfluenceOnObjPos().GetAllowOverlap());
475 CPPUNIT_PLUGIN_IMPLEMENT();
477 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */