sw/qa: warning C6011: Dereferencing NULL pointer
[LibreOffice.git] / sw / qa / extras / globalfilter / globalfilter.cxx
blobd4ef7c16fd20b3739e0f240bd02cf29ee68b0f8f
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/XBitmap.hpp>
13 #include <com/sun/star/graphic/XGraphic.hpp>
14 #include <com/sun/star/graphic/GraphicType.hpp>
15 #include <com/sun/star/text/ControlCharacter.hpp>
16 #include <com/sun/star/text/XText.hpp>
17 #include <com/sun/star/text/XDocumentIndex.hpp>
18 #include <o3tl/safeint.hxx>
19 #include <o3tl/string_view.hxx>
20 #include <officecfg/Office/Common.hxx>
21 #include <tools/zcodec.hxx>
22 #include <vcl/filter/pdfdocument.hxx>
23 #include <sfx2/linkmgr.hxx>
24 #include <comphelper/propertysequence.hxx>
25 #include <unotxdoc.hxx>
26 #include <docsh.hxx>
27 #include <editsh.hxx>
28 #include <IDocumentRedlineAccess.hxx>
29 #include <IDocumentContentOperations.hxx>
30 #include <doc.hxx>
31 #include <ndgrf.hxx>
32 #include <ndtxt.hxx>
33 #include <ndindex.hxx>
34 #include <pam.hxx>
35 #include <xmloff/odffields.hxx>
36 #include <IDocumentMarkAccess.hxx>
37 #include <IMark.hxx>
38 #include <com/sun/star/awt/FontWeight.hpp>
39 #include <unotools/mediadescriptor.hxx>
41 namespace
43 class Test : public SwModelTestBase
45 public:
46 Test() : SwModelTestBase("/sw/qa/extras/globalfilter/data/") {}
48 void testEmbeddedGraphicRoundtrip();
49 void testLinkedGraphicRT();
50 void testImageWithSpecialID();
51 void testGraphicShape();
52 void testMultipleIdenticalGraphics();
53 void testCharHighlight();
54 void testCharHighlightODF();
55 void testCharHighlightBody();
56 void testCharStyleHighlight();
57 void testMSCharBackgroundEditing();
58 void testCharBackgroundToHighlighting();
59 #if !defined(_WIN32)
60 void testSkipImages();
61 #endif
62 void testNestedFieldmark();
63 void verifyText13(char const*);
64 void testODF13();
65 void testRedlineFlags();
66 void testBulletAsImage();
67 void testTextFormField();
68 void testCheckBoxFormField();
69 void testDropDownFormField();
70 void testDateFormField();
71 void testDateFormFieldCharacterFormatting();
72 void testSvgImageSupport();
74 CPPUNIT_TEST_SUITE(Test);
75 CPPUNIT_TEST(testEmbeddedGraphicRoundtrip);
76 CPPUNIT_TEST(testLinkedGraphicRT);
77 CPPUNIT_TEST(testImageWithSpecialID);
78 CPPUNIT_TEST(testGraphicShape);
79 CPPUNIT_TEST(testMultipleIdenticalGraphics);
80 CPPUNIT_TEST(testCharHighlight);
81 CPPUNIT_TEST(testCharHighlightODF);
82 CPPUNIT_TEST(testMSCharBackgroundEditing);
83 CPPUNIT_TEST(testCharBackgroundToHighlighting);
84 #if !defined(_WIN32)
85 CPPUNIT_TEST(testSkipImages);
86 #endif
87 CPPUNIT_TEST(testNestedFieldmark);
88 CPPUNIT_TEST(testODF13);
89 CPPUNIT_TEST(testRedlineFlags);
90 CPPUNIT_TEST(testBulletAsImage);
91 CPPUNIT_TEST(testTextFormField);
92 CPPUNIT_TEST(testCheckBoxFormField);
93 CPPUNIT_TEST(testDropDownFormField);
94 CPPUNIT_TEST(testDateFormField);
95 CPPUNIT_TEST(testDateFormFieldCharacterFormatting);
96 CPPUNIT_TEST(testSvgImageSupport);
97 CPPUNIT_TEST_SUITE_END();
100 void Test::testEmbeddedGraphicRoundtrip()
102 OUString aFilterNames[] = {
103 "writer8",
104 "Rich Text Format",
105 "MS Word 97",
106 "Office Open XML Text",
109 for (OUString const & rFilterName : aFilterNames)
111 // Check whether the export code swaps in the image which was swapped out before by auto mechanism
113 createSwDoc("document_with_two_images.odt");
115 // Export the document and import again for a check
116 saveAndReload(rFilterName);
118 // Check whether graphic exported well after it was swapped out
119 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
120 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
122 // First image
123 uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
124 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
126 // Check graphic, size
128 uno::Reference<graphic::XGraphic> xGraphic;
129 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
130 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
131 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
132 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
133 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
134 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
135 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
138 // Second Image
139 xImage.set(getShape(2), uno::UNO_QUERY);
140 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
142 // Check graphic, size
144 uno::Reference<graphic::XGraphic> xGraphic;
145 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
146 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
147 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
148 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
149 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
150 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
151 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
156 void Test::testLinkedGraphicRT()
158 const OUString aFilterNames[] = {
159 "writer8",
160 // "Rich Text Format", Note: picture is there, but SwGrfNode is not found?
161 "MS Word 97",
162 "Office Open XML Text",
165 for (OUString const & rFilterName : aFilterNames)
167 createSwDoc("document_with_linked_graphic.odt");
169 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
171 // Export the document and import again for a check
172 saveAndReload(rFilterName);
174 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
175 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
176 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
177 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pDoc);
178 SwNodes& aNodes = pDoc->GetNodes();
180 // Find the image
181 bool bImageFound = false;
182 Graphic aGraphic;
183 for (SwNodeOffset nIndex(0); nIndex < aNodes.Count(); ++nIndex)
185 if (aNodes[nIndex]->IsGrfNode())
187 SwGrfNode* pGrfNode = aNodes[nIndex]->GetGrfNode();
188 CPPUNIT_ASSERT(pGrfNode);
190 const GraphicObject& rGraphicObj = pGrfNode->GetGrfObj(true);
191 aGraphic = rGraphicObj.GetGraphic();
192 bImageFound = true;
196 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bImageFound);
198 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
199 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_uLong(864900), aGraphic.GetSizeBytes());
201 // Check if linked graphic is registered in LinkManager
202 SwEditShell* const pEditShell(pTextDoc->GetDocShell()->GetDoc()->GetEditShell());
203 CPPUNIT_ASSERT(pEditShell);
204 sfx2::LinkManager& rLinkManager = pEditShell->GetLinkManager();
205 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(1), rLinkManager.GetLinks().size());
206 const tools::SvRef<sfx2::SvBaseLink> & rLink = rLinkManager.GetLinks()[0];
207 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), rLink->GetLinkSourceName().indexOf("linked_graphic.jpg") >= 0);
211 void Test::testImageWithSpecialID()
213 // Check how LO handles when the imported graphic's ID is different from that one
214 // which is generated by LO.
216 const OUString aFilterNames[] = {
217 "writer8",
218 "Rich Text Format",
219 "MS Word 97",
220 "Office Open XML Text",
223 for (OUString const & rFilterName : aFilterNames)
225 createSwDoc("images_with_special_IDs.odt");
227 // Export the document and import again for a check
228 saveAndReload(rFilterName);
230 // Check whether graphic exported well
231 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
232 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
234 uno::Reference<drawing::XShape> xImage = getShape(1);
235 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
237 // Check graphic, size
239 uno::Reference<graphic::XGraphic> xGraphic;
240 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
241 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
242 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
243 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
244 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
245 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
246 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
249 // Second Image
250 xImage.set(getShape(2), uno::UNO_QUERY);
251 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
253 // Check graphic, size
255 uno::Reference<graphic::XGraphic> xGraphic;
256 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
257 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
258 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
259 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
260 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
261 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
262 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
267 /// Gives the first embedded or linked image in a document.
268 uno::Reference<drawing::XShape> lcl_getShape(const uno::Reference<lang::XComponent>& xComponent, bool bEmbedded)
270 uno::Reference<drawing::XShape> xShape;
272 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
273 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
274 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
276 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
277 uno::Reference<graphic::XGraphic> xGraphic;
278 xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
279 if (xGraphic.is())
281 Graphic aGraphic(xGraphic);
283 if (bEmbedded == aGraphic.getOriginURL().isEmpty())
285 xShape.set(xShapeProperties, uno::UNO_QUERY);
286 return xShape;
291 return xShape;
294 void Test::testGraphicShape()
296 // There are two kind of images in Writer: 1) Writer specific handled by SwGrfNode and
297 // 2) graphic shape handled by SdrGrafObj (e.g. after copy&paste from Impress).
299 const OUString aFilterNames[] = {
300 "writer8",
301 "Rich Text Format",
302 "MS Word 97",
303 "Office Open XML Text",
306 for (OUString const & rFilterName : aFilterNames)
308 createSwDoc("graphic_shape.odt");
310 // Export the document and import again for a check
311 saveAndReload(rFilterName);
313 // Check whether graphic exported well
314 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
315 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
317 uno::Reference<drawing::XShape> xImage = lcl_getShape(mxComponent, true);
318 CPPUNIT_ASSERT_MESSAGE("Couldn't load the shape/image", xImage.is());
319 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY );
320 // First image is embedded
321 // Check size
323 uno::Reference<graphic::XGraphic> xGraphic;
324 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
325 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
326 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
327 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
328 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width );
329 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height );
332 // MS filters make this kind of linked images broken !?
333 if (rFilterName != "writer8")
334 return;
336 // Second image is a linked one
337 xImage = lcl_getShape(mxComponent, false);
338 XPropSet.set(xImage, uno::UNO_QUERY);
339 const OString sFailedImageLoad = OString::Concat("Couldn't load the shape/image for ") + rFilterName.toUtf8();
340 CPPUNIT_ASSERT_MESSAGE(sFailedImageLoad.getStr(), xImage.is());
342 // Check size
344 uno::Reference<graphic::XGraphic> xGraphic;
345 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
346 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
348 Graphic aGraphic(xGraphic);
349 OUString sURL = aGraphic.getOriginURL();
350 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), sURL.endsWith("linked_graphic.jpg"));
352 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
353 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
354 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(620), xBitmap->getSize().Width);
355 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(465), xBitmap->getSize().Height);
360 std::vector<uno::Reference<graphic::XGraphic>>
361 lcl_getGraphics(const uno::Reference<lang::XComponent>& xComponent)
363 std::vector<uno::Reference<graphic::XGraphic>> aGraphics;
365 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
366 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
367 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
369 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
370 uno::Reference<graphic::XGraphic> xGraphic;
371 xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
372 if (xGraphic.is())
374 aGraphics.push_back(xGraphic);
378 return aGraphics;
381 void Test::testMultipleIdenticalGraphics()
383 // We have multiple identical graphics. When we save them we want
384 // them to be saved de-duplicated and the same should still be true
385 // after loading them again. This test check that the de-duplication
386 // works as expected.
388 const OUString aFilterNames[] {
389 "writer8",
390 //"Rich Text Format", // doesn't work correctly for now
391 "MS Word 97",
392 "Office Open XML Text",
395 for (OUString const & rFilterName : aFilterNames)
397 createSwDoc("multiple_identical_graphics.odt");
399 // Export the document and import again for a check
400 saveAndReload(rFilterName);
402 // Check whether graphic exported well
403 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
404 auto aGraphics = lcl_getGraphics(mxComponent);
406 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(5), aGraphics.size());
408 // Get all GfxLink addresses, we expect all of them to be the same
409 // indicating we use the same graphic instance for all shapes
410 std::vector<sal_Int64> aGfxLinkAddresses;
411 for (auto const & rxGraphic : aGraphics)
413 GfxLink* pLink = Graphic(rxGraphic).GetSharedGfxLink().get();
414 aGfxLinkAddresses.emplace_back(reinterpret_cast<sal_Int64>(pLink));
417 // Check all addresses are the same
418 bool bResult = std::equal(aGfxLinkAddresses.begin() + 1, aGfxLinkAddresses.end(), aGfxLinkAddresses.begin());
419 const OString sGraphicNotTheSameFailedMessage = OString::Concat("Graphics not the same for filter: '") +
420 rFilterName.toUtf8() + OString::Concat("'");
421 CPPUNIT_ASSERT_EQUAL_MESSAGE(sGraphicNotTheSameFailedMessage.getStr(), true, bResult);
425 void Test::testCharHighlightBody()
427 // MS Word has two kind of character backgrounds called character shading and highlighting
428 // MS filters handle these attributes separately, but ODF export merges them into one background attribute
430 const OUString aFilterNames[] = {
431 "writer8",
432 "Rich Text Format",
433 "MS Word 97",
434 "Office Open XML Text",
437 for (OUString const & rFilterName : aFilterNames)
439 createSwDoc("char_highlight.docx");
441 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
443 // Export the document and import again for a check
444 saveAndReload(rFilterName);
446 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
447 // Both highlight and background
448 const Color nBackColor(0x4F81BD);
449 for( int nRun = 1; nRun <= 16; ++nRun )
451 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
452 Color nHighlightColor;
453 switch( nRun )
455 case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
456 case 2: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
457 case 3: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
458 case 4: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
459 case 5: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
460 case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
461 case 7: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
462 case 8: nHighlightColor = COL_WHITE; break; //white 0xffffff
463 case 9: nHighlightColor = COL_BLUE; break;//blue 0x000080
464 case 10: nHighlightColor = COL_CYAN; break; //cyan 0x008080
465 case 11: nHighlightColor = COL_GREEN; break; //green 0x008000
466 case 12: nHighlightColor = COL_MAGENTA; break; //magenta 0x800080
467 case 13: nHighlightColor = COL_RED; break; //red 0x800000
468 case 14: nHighlightColor = COL_BROWN; break; //brown 0x808000
469 case 15: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
470 case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
473 if (rFilterName == "writer8")
475 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
476 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharBackColor"));
478 else // MS filters
480 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharHighlight"));
481 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharBackColor"));
485 // Only highlight
487 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,18), uno::UNO_QUERY);
488 if (rFilterName == "writer8")
490 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
491 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
493 else
495 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharHighlight"));
496 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
500 // Only background
502 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,19), uno::UNO_QUERY);
503 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
504 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharBackColor"));
509 void Test::testCharStyleHighlight()
511 // MS Word has two kind of character backgrounds called character shading and highlighting.
512 // However, their character style can only accept shading. It ignores the highlighting value.
514 const OUString aFilterNames[] = {
515 "Rich Text Format",
516 "MS Word 97",
517 "Office Open XML Text",
520 for (OUString const & rFilterName : aFilterNames)
522 createSwDoc("tdf138345_charstyle_highlight.odt");
524 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
526 // Export the document and import again for a check
527 saveAndReload(rFilterName);
529 uno::Reference<beans::XPropertySet> xCharStyle;
530 getStyles("CharacterStyles")->getByName("charBackground") >>= xCharStyle;
531 const Color nBackColor(0xFFDBB6); //orange-y
533 // Always export character style's background colour as shading, never as highlighting.
534 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xCharStyle,"CharHighlight"));
535 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xCharStyle,"CharBackColor"));
539 void Test::testCharHighlight()
541 auto batch = comphelper::ConfigurationChanges::create();
542 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(false, batch);
543 batch->commit();
545 testCharHighlightBody();
546 testCharStyleHighlight();
548 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
549 batch->commit();
551 testCharHighlightBody();
552 testCharStyleHighlight();
555 void Test::testCharHighlightODF()
557 createSwDoc("char_background_editing.docx");
559 // don't check import, testMSCharBackgroundEditing already does that
561 uno::Reference<text::XTextRange> xPara = getParagraph(1);
562 for (int i = 1; i <= 4; ++i)
564 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
565 switch (i)
567 case 1: // non-transparent highlight
568 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
569 xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
570 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
571 break;
573 case 2: // transparent backcolor
574 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
575 xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
576 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
577 break;
579 case 3: // non-transparent backcolor
580 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
581 xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
582 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
583 break;
585 case 4: // non-transparent highlight again
586 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
587 xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
588 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
589 break;
593 saveAndReload("writer8");
595 xPara.set(getParagraph(1));
596 for (int i = 1; i <= 4; ++i)
598 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
599 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(COL_TRANSPARENT), getProperty<sal_Int32>(xRun, "CharHighlight"));
600 switch (i)
602 case 1: // non-transparent highlight
603 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
604 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
605 break;
606 case 2: // transparent backcolor
607 CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, getProperty<Color>(xRun, "CharBackColor"));
608 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRun, "CharBackTransparent"));
609 break;
610 case 3: // non-transparent backcolor
611 CPPUNIT_ASSERT_EQUAL(COL_BLUE, getProperty<Color>(xRun, "CharBackColor"));
612 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
613 break;
614 case 4: // non-transparent highlight again
615 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
616 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
617 break;
622 void Test::testMSCharBackgroundEditing()
624 // Simulate the editing process of imported MSO character background attributes
625 // and check how export behaves.
627 const OUString aFilterNames[] = {
628 "writer8",
629 "Rich Text Format",
630 "MS Word 97",
631 "Office Open XML Text",
634 auto batch = comphelper::ConfigurationChanges::create();
635 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
636 batch->commit();
638 for (OUString const & rFilterName : aFilterNames)
640 createSwDoc("char_background_editing.docx");
642 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
644 // Check whether import was done on the right way
645 uno::Reference< text::XTextRange > xPara = getParagraph(1);
647 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,1), uno::UNO_QUERY);
648 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
649 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
651 xRun.set(getRun(xPara,2), uno::UNO_QUERY);
652 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
653 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
655 xRun.set(getRun(xPara,3), uno::UNO_QUERY);
656 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
657 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
659 xRun.set(getRun(xPara,4), uno::UNO_QUERY);
660 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
661 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
664 // Simulate editing
665 for( int i = 1; i <= 4; ++i )
667 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
668 // Change background
669 Color nBackColor;
670 switch( i )
672 case 1: nBackColor = COL_BLACK; break; //black 0x000000
673 case 2: nBackColor = COL_LIGHTCYAN; break; //cyan 0x00ffff
674 case 3: nBackColor = COL_LIGHTGREEN; break; //green 0x00ff00
675 case 4: nBackColor = COL_LIGHTMAGENTA; break; //magenta 0xff00ff
677 xRun->setPropertyValue("CharBackColor", uno::Any(nBackColor));
678 // Remove highlighting
679 xRun->setPropertyValue("CharHighlight", uno::Any(COL_TRANSPARENT));
680 // Remove shading marker
681 uno::Sequence<beans::PropertyValue> aGrabBag = getProperty<uno::Sequence<beans::PropertyValue> >(xRun,"CharInteropGrabBag");
682 for (beans::PropertyValue& rProp : asNonConstRange(aGrabBag))
684 if (rProp.Name == "CharShadingMarker")
686 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), true, rProp.Value.get<bool>());
687 rProp.Value <<= false;
690 xRun->setPropertyValue("CharInteropGrabBag", uno::Any(aGrabBag));
693 // Export the document and import again for a check
694 saveAndReload(rFilterName);
696 // Check whether background was exported as highlighting
697 xPara.set(getParagraph(1));
698 for( int i = 1; i <= 4; ++i )
700 Color nBackColor;
701 switch( i )
703 case 1: nBackColor = COL_BLACK; break; //black 0x000000
704 case 2: nBackColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
705 case 3: nBackColor = COL_LIGHTGREEN; break; //light green 0x00ff00
706 case 4: nBackColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
708 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
709 if (rFilterName == "writer8")
711 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
712 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharBackColor"));
714 else
716 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharHighlight"));
717 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
723 void Test::testCharBackgroundToHighlighting()
725 // MSO highlighting has less kind of values so let's see how LO character background is converted
726 // to these values
728 const OUString aFilterNames[] = {
729 "Rich Text Format",
730 "MS Word 97",
731 "Office Open XML Text",
734 for (OUString const & rFilterName : aFilterNames)
736 createSwDoc("char_background.odt");
738 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
740 auto batch = comphelper::ConfigurationChanges::create();
741 officecfg::Office::Common::Filter::Microsoft::Export::CharBackgroundToHighlighting::set(true, batch);
742 batch->commit();
744 // Export the document and import again for a check
745 saveAndReload(rFilterName);
747 // Check highlight color
748 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
749 for( int nRun = 1; nRun <= 19; ++nRun )
751 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
752 Color nHighlightColor;
753 switch( nRun )
755 case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
756 case 2: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
757 case 3: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
758 case 4: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
759 case 5: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
760 case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
761 case 7: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
762 case 8: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
763 case 9: nHighlightColor = COL_GREEN; break; //dark green 0x008000
764 case 10: nHighlightColor = COL_MAGENTA; break; //dark magenta 0x800080
765 case 11: nHighlightColor = COL_BLUE; break; //dark blue 0x000080
766 case 12: nHighlightColor = COL_BROWN; break; //brown 0x808000
767 case 13: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
768 case 14: nHighlightColor = COL_BLACK; break; //black 0x000000
769 case 15: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
770 case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
771 case 17: nHighlightColor = COL_RED; break; //dark red 0x800000
772 case 18: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
773 case 19: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
775 const OString sMessage = sFailedMessage +". Index of run with unmatched color: " + OString::number(nRun);
776 CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharHighlight"));
781 #if !defined(_WIN32)
782 void Test::testSkipImages()
784 // Check how LO skips image loading (but not texts of textboxes and custom shapes)
785 // during DOC and DOCX import, using the "SkipImages" FilterOptions.
787 std::pair<OUString, OUString> aFilterNames[] = {
788 { "skipimages.doc", "" },
789 { "skipimages.doc", "SkipImages" },
790 { "skipimages.docx", "" },
791 { "skipimages.docx", "SkipImages" }
794 for (auto const & rFilterNamePair : aFilterNames)
796 bool bSkipImages = !rFilterNamePair.second.isEmpty();
797 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterNamePair.first.toUtf8();
799 setImportFilterOptions(rFilterNamePair.second);
800 createSwDoc(rFilterNamePair.first.toUtf8().getStr());
801 sFailedMessage += " - " + rFilterNamePair.second.toUtf8();
803 // Check shapes (images, textboxes, custom shapes)
804 uno::Reference<drawing::XShape> xShape;
805 uno::Reference<graphic::XGraphic> xGraphic;
806 uno::Reference< beans::XPropertySet > XPropSet;
807 uno::Reference<awt::XBitmap> xBitmap;
809 bool bHasTextboxText = false;
810 bool bHasCustomShapeText = false;
811 sal_Int32 nImageCount = 0;
813 for (int i = 1; i<= getShapes(); i++)
815 xShape = getShape(i);
816 XPropSet.set( xShape, uno::UNO_QUERY_THROW );
819 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
820 xBitmap.set(xGraphic, uno::UNO_QUERY);
821 if (xBitmap.is())
822 nImageCount++;
824 catch (beans::UnknownPropertyException &)
825 { /* ignore */ }
827 uno::Reference<text::XTextRange> xText(xShape, uno::UNO_QUERY);
828 if (xText.is())
830 OUString shapeText = xText->getString();
831 if (shapeText.startsWith("Lorem ipsum"))
832 bHasTextboxText = true;
833 else if (shapeText.startsWith("Nam pretium"))
834 bHasCustomShapeText = true;
838 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasTextboxText);
839 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasCustomShapeText);
840 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(bSkipImages ? 0 : 3), nImageCount );
843 #endif
845 auto verifyNestedFieldmark(OUString const& rTestName,
846 uno::Reference<lang::XComponent> const& xComponent) -> void
848 SwDoc const*const pDoc(dynamic_cast<SwXTextDocument&>(*xComponent).GetDocShell()->GetDoc());
849 IDocumentMarkAccess const& rIDMA(*pDoc->getIDocumentMarkAccess());
851 // no spurious bookmarks have been created
852 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
853 sal_Int32(0), rIDMA.getBookmarksCount());
855 // check inner fieldmark
856 SwNodeIndex const node1(*pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), +2);
857 SwPosition const innerPos(*node1.GetNode().GetTextNode(),
858 node1.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
859 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
860 sal_Int32(1), innerPos.GetContentIndex());
861 ::sw::mark::IFieldmark *const pInner(rIDMA.getFieldmarkAt(innerPos));
862 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pInner);
863 OUString const innerString(SwPaM(pInner->GetMarkPos(), pInner->GetOtherMarkPos()).GetText());
864 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
865 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
866 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
867 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
868 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
869 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), innerString);
871 // check outer fieldmark
872 SwNodeIndex const node2(node1, -1);
873 SwPosition const outerPos(*node2.GetNode().GetTextNode(),
874 node2.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
875 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
876 sal_Int32(0), outerPos.GetContentIndex());
877 ::sw::mark::IFieldmark const*const pOuter(rIDMA.getFieldmarkAt(outerPos));
878 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pOuter);
879 OUString const outerString(SwPaM(pOuter->GetMarkPos(), pOuter->GetOtherMarkPos()).GetText());
880 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
881 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
882 + u" " + OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
883 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
884 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
885 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
886 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND) + OUStringChar(CH_TXTATR_NEWLINE)
887 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
888 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
889 + u" foo " + OUStringChar(CH_TXTATR_NEWLINE)
890 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
891 + u"baz" + OUStringChar(CH_TXTATR_NEWLINE)
892 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
893 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), outerString);
895 // must return innermost mark
896 CPPUNIT_ASSERT_EQUAL(pInner, rIDMA.getInnerFieldmarkFor(innerPos));
899 void Test::testNestedFieldmark()
901 // experimental config setting
902 Resetter resetter(
903 [] () {
904 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
905 comphelper::ConfigurationChanges::create());
906 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(false, pBatch);
907 return pBatch->commit();
909 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
910 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(true, pBatch);
911 pBatch->commit();
913 std::pair<OUString, OUString> const aFilterNames[] = {
914 {"writer8", "fieldmark_QUOTE_nest.fodt"},
915 {"Office Open XML Text", "fieldmark_QUOTE_nest.docx"},
916 {"Rich Text Format", "fieldmark_QUOTE_nest.rtf"},
919 for (auto const & rFilterName : aFilterNames)
921 createSwDoc(rFilterName.second.toUtf8().getStr());
923 verifyNestedFieldmark(rFilterName.first + ", load", mxComponent);
925 // Export the document and import again
926 saveAndReload(rFilterName.first);
928 verifyNestedFieldmark(rFilterName.first + " exported-reload", mxComponent);
932 auto Test::verifyText13(char const*const pTestName) -> void
934 // OFFICE-3789 style:header-first/style:footer-first
935 uno::Reference<beans::XPropertySet> xPageStyle;
936 getStyles("PageStyles")->getByName("Standard") >>= xPageStyle;
937 uno::Reference<text::XText> xHF(getProperty<uno::Reference<text::XText>>(xPageStyle, "HeaderTextFirst"));
938 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Header first"), xHF->getString());
939 uno::Reference<text::XText> xFF(getProperty<uno::Reference<text::XText>>(xPageStyle, "FooterTextFirst"));
940 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Footer first"), xFF->getString());
941 // OFFICE-3767 text:contextual-spacing
942 uno::Reference<text::XTextRange> xPara(getParagraph(1));
943 CPPUNIT_ASSERT_MESSAGE(pTestName, getProperty<bool>(xPara, "ParaContextMargin"));
944 // OFFICE-3776 meta:creator-initials
945 uno::Reference<text::XTextRange> xRun(getRun(xPara, 1));
946 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Annotation"), getProperty<OUString>(xRun, "TextPortionType"));
947 uno::Reference<beans::XPropertySet> xComment(getProperty<uno::Reference<beans::XPropertySet>>(xRun, "TextField"));
948 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("dj"), getProperty<OUString>(xComment, "Initials"));
949 // OFFICE-3941 text:index-entry-link-start/text:index-entry-link-end
950 uno::Reference<text::XDocumentIndexesSupplier> xDIS(mxComponent, uno::UNO_QUERY);
951 uno::Reference<container::XIndexAccess> xIndexes(xDIS->getDocumentIndexes());
952 uno::Reference<text::XDocumentIndex> xIndex(xIndexes->getByIndex(0), uno::UNO_QUERY);
953 uno::Reference<container::XIndexReplace> xLevels(getProperty<uno::Reference<container::XIndexReplace>>(xIndex, "LevelFormat"));
954 uno::Sequence<beans::PropertyValues> format;
955 xLevels->getByIndex(1) >>= format; // 1-based?
956 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[0][0].Name);
957 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkStart"), format[0][0].Value.get<OUString>());
958 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[4][0].Name);
959 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkEnd"), format[4][0].Value.get<OUString>());
962 // test ODF 1.3 new text document features
963 void Test::testODF13()
965 // import
966 createSwDoc("text13e.odt");
968 // check model
969 verifyText13("import");
971 Resetter _([]() {
972 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
973 comphelper::ConfigurationChanges::create());
974 officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
975 return pBatch->commit();
979 // export ODF 1.3
980 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
981 comphelper::ConfigurationChanges::create());
982 officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
983 pBatch->commit();
985 saveAndReload("writer8");
987 // check XML
988 xmlDocUniquePtr pContentXml = parseExport("content.xml");
989 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@style:contextual-spacing='true']"_ostr);
990 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr);
991 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr, 0);
992 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr);
993 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr, 0);
994 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr);
995 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr, 0);
996 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
997 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr);
998 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr, 0);
999 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr);
1000 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr, 0);
1002 // check model
1003 verifyText13("1.3 reload");
1006 // export ODF 1.2 extended
1007 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1008 comphelper::ConfigurationChanges::create());
1009 officecfg::Office::Common::Save::ODF::DefaultVersion::set(9, pBatch);
1010 pBatch->commit();
1012 // FIXME: it's not possible to use 'reload' here because the validation fails with
1013 // Error: unexpected attribute "loext:contextual-spacing"
1014 utl::MediaDescriptor aMediaDescriptor;
1015 aMediaDescriptor["FilterName"] <<= OUString("writer8");
1017 uno::Reference<frame::XStorable> const xStorable(mxComponent, uno::UNO_QUERY);
1018 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1020 // check XML
1021 xmlDocUniquePtr pContentXml = parseExport("content.xml");
1022 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@loext:contextual-spacing='true']"_ostr);
1023 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr);
1024 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr, 0);
1025 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr);
1026 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr, 0);
1027 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr);
1028 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr, 0);
1029 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
1030 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr);
1031 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr, 0);
1032 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr);
1033 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr, 0);
1035 // reload
1036 mxComponent->dispose();
1037 mxComponent = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument");
1039 // check model
1040 verifyText13("1.2 Extended reload");
1043 // export ODF 1.2
1044 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1045 comphelper::ConfigurationChanges::create());
1046 officecfg::Office::Common::Save::ODF::DefaultVersion::set(4, pBatch);
1047 pBatch->commit();
1049 // don't reload - no point
1050 save("writer8");
1052 // check XML
1053 xmlDocUniquePtr pContentXml = parseExport("content.xml");
1054 assertXPathNoAttribute(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties"_ostr, "contextual-spacing"_ostr);
1055 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials"_ostr, 0);
1056 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials"_ostr, 0);
1057 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-start"_ostr, 0);
1058 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-start"_ostr, 0);
1059 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/text:index-entry-link-end"_ostr, 0);
1060 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:illustration-index/text:illustration-index-source/text:illustration-index-entry-template/loext:index-entry-link-end"_ostr, 0);
1061 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
1062 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first"_ostr, 0);
1063 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first"_ostr, 0);
1064 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first"_ostr, 0);
1065 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first"_ostr, 0);
1069 void Test::testRedlineFlags()
1071 const OUString aFilterNames[] = {
1072 "writer8",
1073 "Rich Text Format",
1074 "MS Word 97",
1075 "Office Open XML Text",
1078 createSwDoc();
1079 SwDoc* pDoc = getSwDoc();
1081 SwPaM pam(SwPosition(pDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1)));
1082 pDoc->getIDocumentContentOperations().InsertString(pam, "foo bar baz");
1084 IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess());
1085 // enable change tracking
1086 rIDRA.SetRedlineFlags(rIDRA.GetRedlineFlags()
1087 | RedlineFlags::On | RedlineFlags::ShowDelete);
1089 // need a delete redline to trigger mode switching
1090 pam.Move(fnMoveForward, GoInDoc);
1091 pam.SetMark();
1092 pam.Move(fnMoveBackward, GoInDoc);
1093 pDoc->getIDocumentContentOperations().DeleteAndJoin(pam);
1095 // hide delete redlines
1096 RedlineFlags const nRedlineFlags =
1097 rIDRA.GetRedlineFlags() & ~RedlineFlags::ShowDelete;
1098 rIDRA.SetRedlineFlags(nRedlineFlags);
1100 for (OUString const & rFilterName : aFilterNames)
1102 // export the document
1103 save(rFilterName);
1105 // tdf#97103 check that redline mode is properly restored
1106 CPPUNIT_ASSERT_EQUAL_MESSAGE(
1107 OString(OString::Concat("redline mode not restored in ") + rFilterName.toUtf8()).getStr(),
1108 static_cast<int>(nRedlineFlags), static_cast<int>(rIDRA.GetRedlineFlags()));
1112 void Test::testBulletAsImage()
1114 OUString aFilterNames[] = {
1115 "writer8",
1116 "MS Word 97",
1117 "Office Open XML Text",
1118 "Rich Text Format",
1121 for (OUString const & rFilterName : aFilterNames)
1123 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1125 createSwDoc("BulletAsImage.odt");
1127 // Check if import was successful
1129 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1130 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1131 uno::Reference<container::XIndexAccess> xLevels;
1132 xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
1133 uno::Sequence<beans::PropertyValue> aProperties;
1134 xLevels->getByIndex(0) >>= aProperties;
1135 uno::Reference<awt::XBitmap> xBitmap;
1136 awt::Size aSize;
1137 sal_Int16 nNumberingType = -1;
1139 for (beans::PropertyValue const& rProperty : aProperties)
1141 if (rProperty.Name == "NumberingType")
1143 nNumberingType = rProperty.Value.get<sal_Int16>();
1145 else if (rProperty.Name == "GraphicBitmap")
1147 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1149 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1152 else if (rProperty.Name == "GraphicSize")
1154 aSize = rProperty.Value.get<awt::Size>();
1158 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1160 // Graphic Bitmap
1161 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1162 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1163 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1164 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1165 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1166 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1168 // Graphic Size
1169 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1170 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1173 // Export the document and import again for a check
1174 saveAndReload(rFilterName);
1177 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1178 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1179 uno::Reference<container::XIndexAccess> xLevels;
1180 xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
1181 uno::Sequence<beans::PropertyValue> aProperties;
1182 xLevels->getByIndex(0) >>= aProperties;
1183 uno::Reference<awt::XBitmap> xBitmap;
1184 awt::Size aSize;
1185 sal_Int16 nNumberingType = -1;
1187 for (beans::PropertyValue const& rProperty : aProperties)
1189 if (rProperty.Name == "NumberingType")
1191 nNumberingType = rProperty.Value.get<sal_Int16>();
1193 else if (rProperty.Name == "GraphicBitmap")
1195 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1197 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1200 else if (rProperty.Name == "GraphicSize")
1202 aSize = rProperty.Value.get<awt::Size>();
1206 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1208 // Graphic Bitmap
1209 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1210 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1211 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1212 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1213 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1214 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1216 // Graphic Size
1217 if (rFilterName == "write8") // ODT is correct
1219 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1220 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1222 // FIXME: MS Filters don't work correctly for graphic bullet size
1223 else if (rFilterName == "Office Open XML Text" || rFilterName == "Rich Text Format")
1225 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Width);
1226 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Height);
1228 else if (rFilterName == "MS Word 97")
1230 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Width);
1231 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Height);
1237 CPPUNIT_TEST_FIXTURE(Test, testListLabelPDFExport)
1239 createSwDoc();
1241 uno::Reference<text::XTextDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
1242 uno::Reference<text::XText> xText(xDoc->getText());
1243 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY_THROW);
1244 uno::Reference<container::XIndexReplace> xNumRule(
1245 xFactory->createInstance("com.sun.star.text.NumberingRules"),
1246 uno::UNO_QUERY_THROW);
1247 OUString listFormat;
1248 for (sal_Int32 i = 0; i < xNumRule->getCount(); ++i)
1250 uno::Sequence<beans::PropertyValue> format;
1251 format.getArray();
1252 xNumRule->getByIndex(i) >>= format;
1254 auto it(::std::find_if(format.begin(), format.end(),
1255 [](auto const& r) { return r.Name == "NumberingType"; }));
1256 // need something RTL
1257 const_cast<uno::Any&>(it->Value) <<= style::NumberingType::CHARS_ARABIC;
1260 #if 0
1261 // this doesn't work any more
1262 auto it(::std::find_if(format.begin(), format.end(),
1263 [](auto const& r) { return r.Name == "ParentNumbering"; }));
1264 const_cast<uno::Any&>(it->Value) <<= sal_Int16(i + 1);
1265 #endif
1266 listFormat += "%" + OUString::number(i+1) + "%.";
1267 auto it(::std::find_if(format.begin(), format.end(),
1268 [](auto const& r) { return r.Name == "ListFormat"; }));
1269 const_cast<uno::Any&>(it->Value) <<= listFormat;
1271 xNumRule->replaceByIndex(i, uno::Any(format));
1273 uno::Reference<beans::XPropertySet>(getParagraph(1), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingRules", uno::Any(xNumRule));
1274 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1275 uno::Reference<beans::XPropertySet>(getParagraph(2), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(1)));
1276 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1277 uno::Reference<beans::XPropertySet>(getParagraph(3), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(2)));
1279 // check PDF export of the list items (label in particular)
1280 utl::MediaDescriptor aMediaDescriptor;
1281 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1282 // Enable PDF/UA
1283 uno::Sequence<beans::PropertyValue> aFilterData(
1284 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1285 aMediaDescriptor["FilterData"] <<= aFilterData;
1286 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1287 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1289 // Parse the export result with pdfium.
1290 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1292 // Non-NULL pPdfDocument means pdfium is available.
1293 if (pPdfDocument != nullptr)
1295 // The document has one page.
1296 CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
1297 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
1298 CPPUNIT_ASSERT(pPdfPage);
1300 std::unique_ptr<vcl::pdf::PDFiumTextPage> pPdfTextPage = pPdfPage->getTextPage();
1301 CPPUNIT_ASSERT(pPdfTextPage);
1303 int nChars = pPdfTextPage->countChars();
1304 CPPUNIT_ASSERT_EQUAL(22, nChars);
1306 // Check that the label strings were exported correctly
1307 std::vector<sal_uInt32> aChars(nChars);
1308 for (int i = 0; i < nChars; i++)
1309 aChars[i] = pPdfTextPage->getUnicode(i);
1310 OUString aText(aChars.data(), aChars.size());
1311 CPPUNIT_ASSERT_EQUAL(u"\u0623\r\n.\r\n\u0623.\u0623\r\n.\r\n\u0623.\u0623.\u0623\r\n."_ustr, aText);
1314 // Parse the document again to get its raw content
1315 // TODO: get the content from PDFiumPage somehow
1316 vcl::filter::PDFDocument aDocument;
1317 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1318 CPPUNIT_ASSERT(aDocument.Read(aStream));
1320 // The document has one page.
1321 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1322 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1324 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
1325 CPPUNIT_ASSERT(pContents);
1326 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1327 CPPUNIT_ASSERT(pStream);
1328 SvMemoryStream& rObjectStream = pStream->GetMemory();
1329 // Uncompress it.
1330 SvMemoryStream aUncompressed;
1331 ZCodec aZCodec;
1332 aZCodec.BeginCompression();
1333 rObjectStream.Seek(0);
1334 aZCodec.Decompress(rObjectStream, aUncompressed);
1335 CPPUNIT_ASSERT(aZCodec.EndCompression());
1337 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1338 const char* const pEnd = pStart + aUncompressed.GetSize();
1340 enum
1342 Default,
1343 Lbl,
1344 LblFoundText
1345 } state
1346 = Default;
1348 auto nLine(0);
1349 auto nLbl(0);
1350 auto nLblTj(0);
1351 auto nLblTJ(0);
1352 std::vector<int> mcids;
1353 while (true)
1355 ++nLine;
1356 auto const pLine = ::std::find(pStart, pEnd, '\n');
1357 if (pLine == pEnd)
1359 break;
1361 std::string_view const line(pStart, pLine - pStart);
1362 pStart = pLine + 1;
1363 if (!line.empty() && line[0] != '%')
1365 ::std::cerr << nLine << ": " << line << "\n";
1366 if (o3tl::starts_with(line, "/Lbl<</MCID") && o3tl::ends_with(line, ">>BDC"))
1368 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1369 mcids.push_back(o3tl::toInt32(line.substr(12)));
1370 state = Lbl;
1371 ++nLbl;
1373 else if (state == Lbl)
1375 auto const endj(line.find(">Tj"));
1376 if (endj != ::std::string_view::npos)
1378 state = LblFoundText;
1379 ++nLblTj;
1381 else
1383 auto const endJ(line.find("]TJ"));
1384 if (endJ != ::std::string_view::npos)
1386 state = LblFoundText;
1387 ++nLblTJ;
1391 else if (state != Default && line == "EMC")
1393 CPPUNIT_ASSERT_EQUAL_MESSAGE("missing text", LblFoundText, state);
1394 state = Default;
1398 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1399 // ideally there should be 3 but apparently every text portion gets its own
1400 // tag - this should not be a problem if these are grouped in the structure
1401 // tree into 3 Lbl.
1402 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLbl);
1403 // these are quite arbitrary?
1404 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLblTJ + nLblTj);
1406 auto nL(0);
1407 for (const auto& rDocElement : aDocument.GetElements())
1409 auto pObject0 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
1410 if (!pObject0)
1411 continue;
1412 auto pType0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("Type"_ostr));
1413 if (!pType0 || pType0->GetValue() != "StructElem")
1415 continue;
1417 auto pS0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("S"_ostr));
1418 if (!pS0 || pS0->GetValue() != "Document")
1420 continue;
1422 auto pKids0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject0->Lookup("K"_ostr));
1423 CPPUNIT_ASSERT(pKids0);
1425 for (const auto& pKid0 : pKids0->GetElements())
1427 auto pRefKid0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid0);
1428 CPPUNIT_ASSERT(pRefKid0);
1429 auto pObject1 = pRefKid0->LookupObject();
1430 CPPUNIT_ASSERT(pObject1);
1431 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"_ostr));
1432 CPPUNIT_ASSERT(pType1);
1434 if (pType1 && pType1->GetValue() == "StructElem")
1436 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"_ostr));
1437 if (pS1 && pS1->GetValue() == "L")
1439 ++nL;
1440 auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"_ostr));
1441 CPPUNIT_ASSERT(pKids1);
1442 // this is purely structural so there should be 1 child
1443 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1->GetElements().size());
1445 auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1->GetElements()[0]);
1446 CPPUNIT_ASSERT(pRefKid11);
1447 auto pObject11 = pRefKid11->LookupObject();
1448 CPPUNIT_ASSERT(pObject11);
1449 auto pType11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"_ostr));
1450 CPPUNIT_ASSERT(pType11);
1451 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11->GetValue());
1452 auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"_ostr));
1453 CPPUNIT_ASSERT(pS11);
1454 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11->GetValue());
1455 // LI has 2 children: Lbl and LBody
1456 auto pKids11 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11->Lookup("K"_ostr));
1457 CPPUNIT_ASSERT(pKids11);
1458 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11->GetElements().size());
1460 auto pRefKid111 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[0]);
1461 CPPUNIT_ASSERT(pRefKid111);
1462 auto pObject111 = pRefKid111->LookupObject();
1463 CPPUNIT_ASSERT(pObject111);
1464 auto pType111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("Type"_ostr));
1465 CPPUNIT_ASSERT(pType111);
1466 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType111->GetValue());
1467 auto pS111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("S"_ostr));
1468 CPPUNIT_ASSERT(pS111);
1469 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS111->GetValue());
1470 // Lbl has 2 children: the first 2 mcids (in order)
1471 auto pKids111 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject111->Lookup("K"_ostr));
1472 CPPUNIT_ASSERT(pKids111);
1473 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids111->GetElements().size());
1475 auto pRefKid1111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[0]);
1476 CPPUNIT_ASSERT(pRefKid1111);
1477 CPPUNIT_ASSERT_EQUAL(mcids[0], int(pRefKid1111->GetValue()));
1478 auto pRefKid1112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[1]);
1479 CPPUNIT_ASSERT(pRefKid1112);
1480 CPPUNIT_ASSERT_EQUAL(mcids[1], int(pRefKid1112->GetValue()));
1482 auto pRefKid112 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[1]);
1483 CPPUNIT_ASSERT(pRefKid112);
1484 auto pObject112 = pRefKid112->LookupObject();
1485 CPPUNIT_ASSERT(pObject112);
1486 auto pType112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("Type"_ostr));
1487 CPPUNIT_ASSERT(pType112);
1488 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112->GetValue());
1489 auto pS112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("S"_ostr));
1490 CPPUNIT_ASSERT(pS112);
1491 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112->GetValue());
1492 // LBody has 2 children: paragraph and nested L (in order)
1493 auto pKids112 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112->Lookup("K"_ostr));
1494 CPPUNIT_ASSERT(pKids112);
1495 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112->GetElements().size());
1497 auto pRefKid1121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[0]);
1498 CPPUNIT_ASSERT(pRefKid1121);
1499 auto pObject1121 = pRefKid1121->LookupObject();
1500 CPPUNIT_ASSERT(pObject1121);
1501 auto pType1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("Type"_ostr));
1502 CPPUNIT_ASSERT(pType1121);
1503 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1121->GetValue());
1504 auto pS1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("S"_ostr));
1505 CPPUNIT_ASSERT(pS1121);
1506 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1121->GetValue());
1508 auto pRefKid1122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[1]);
1509 CPPUNIT_ASSERT(pRefKid1122);
1510 auto pObject1122 = pRefKid1122->LookupObject();
1511 CPPUNIT_ASSERT(pObject1122);
1512 auto pType1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("Type"_ostr));
1513 CPPUNIT_ASSERT(pType1122);
1514 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122->GetValue());
1515 auto pS1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("S"_ostr));
1516 CPPUNIT_ASSERT(pS1122);
1517 CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122->GetValue());
1518 auto pKids1122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122->Lookup("K"_ostr));
1519 CPPUNIT_ASSERT(pKids1122);
1520 // this is purely structural so there should be 1 child
1521 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122->GetElements().size());
1523 auto pRefKid11221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122->GetElements()[0]);
1524 CPPUNIT_ASSERT(pRefKid11221);
1525 auto pObject11221 = pRefKid11221->LookupObject();
1526 CPPUNIT_ASSERT(pObject11221);
1527 auto pType11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("Type"_ostr));
1528 CPPUNIT_ASSERT(pType11221);
1529 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221->GetValue());
1530 auto pS11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("S"_ostr));
1531 CPPUNIT_ASSERT(pS11221);
1532 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221->GetValue());
1533 // LI has 2 children: Lbl and LBody
1534 auto pKids11221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221->Lookup("K"_ostr));
1535 CPPUNIT_ASSERT(pKids11221);
1536 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221->GetElements().size());
1538 auto pRefKid112211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[0]);
1539 CPPUNIT_ASSERT(pRefKid112211);
1540 auto pObject112211 = pRefKid112211->LookupObject();
1541 CPPUNIT_ASSERT(pObject112211);
1542 auto pType112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("Type"_ostr));
1543 CPPUNIT_ASSERT(pType112211);
1544 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112211->GetValue());
1545 auto pS112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("S"_ostr));
1546 CPPUNIT_ASSERT(pS112211);
1547 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112211->GetValue());
1548 // Lbl has 2 children: the first 2 mcids (in order)
1549 auto pKids112211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112211->Lookup("K"_ostr));
1550 CPPUNIT_ASSERT(pKids112211);
1551 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112211->GetElements().size());
1553 auto pRefKid1122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[0]);
1554 CPPUNIT_ASSERT(pRefKid1122111);
1555 CPPUNIT_ASSERT_EQUAL(mcids[2], int(pRefKid1122111->GetValue()));
1556 auto pRefKid1122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[1]);
1557 CPPUNIT_ASSERT(pRefKid1122112);
1558 CPPUNIT_ASSERT_EQUAL(mcids[3], int(pRefKid1122112->GetValue()));
1560 auto pRefKid112212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[1]);
1561 CPPUNIT_ASSERT(pRefKid112212);
1562 auto pObject112212 = pRefKid112212->LookupObject();
1563 CPPUNIT_ASSERT(pObject112212);
1564 auto pType112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("Type"_ostr));
1565 CPPUNIT_ASSERT(pType112212);
1566 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212->GetValue());
1567 auto pS112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("S"_ostr));
1568 CPPUNIT_ASSERT(pS112212);
1569 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212->GetValue());
1570 // LBody has 2 children: paragraph and nested L (in order)
1571 auto pKids112212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212->Lookup("K"_ostr));
1572 CPPUNIT_ASSERT(pKids112212);
1573 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212->GetElements().size());
1575 auto pRefKid1122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[0]);
1576 CPPUNIT_ASSERT(pRefKid1122121);
1577 auto pObject1122121 = pRefKid1122121->LookupObject();
1578 CPPUNIT_ASSERT(pObject1122121);
1579 auto pType1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("Type"_ostr));
1580 CPPUNIT_ASSERT(pType1122121);
1581 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122121->GetValue());
1582 auto pS1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("S"_ostr));
1583 CPPUNIT_ASSERT(pS1122121);
1584 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122121->GetValue());
1586 auto pRefKid1122122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[1]);
1587 CPPUNIT_ASSERT(pRefKid1122122);
1588 auto pObject1122122 = pRefKid1122122->LookupObject();
1589 CPPUNIT_ASSERT(pObject1122122);
1590 auto pType1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("Type"_ostr));
1591 CPPUNIT_ASSERT(pType1122122);
1592 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122->GetValue());
1593 auto pS1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("S"_ostr));
1594 CPPUNIT_ASSERT(pS1122122);
1595 CPPUNIT_ASSERT_EQUAL("L"_ostr, pS1122122->GetValue());
1596 auto pKids1122122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122122->Lookup("K"_ostr));
1597 CPPUNIT_ASSERT(pKids1122122);
1598 // this is purely structural so there should be 1 child
1599 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122122->GetElements().size());
1601 auto pRefKid11221221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122122->GetElements()[0]);
1602 CPPUNIT_ASSERT(pRefKid11221221);
1603 auto pObject11221221 = pRefKid11221221->LookupObject();
1604 CPPUNIT_ASSERT(pObject11221221);
1605 auto pType11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("Type"_ostr));
1606 CPPUNIT_ASSERT(pType11221221);
1607 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType11221221->GetValue());
1608 auto pS11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("S"_ostr));
1609 CPPUNIT_ASSERT(pS11221221);
1610 CPPUNIT_ASSERT_EQUAL("LI"_ostr, pS11221221->GetValue());
1611 // LI has 2 children: Lbl and LBody
1612 auto pKids11221221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221221->Lookup("K"_ostr));
1613 CPPUNIT_ASSERT(pKids11221221);
1614 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221221->GetElements().size());
1616 auto pRefKid112212211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[0]);
1617 CPPUNIT_ASSERT(pRefKid112212211);
1618 auto pObject112212211 = pRefKid112212211->LookupObject();
1619 CPPUNIT_ASSERT(pObject112212211);
1620 auto pType112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("Type"_ostr));
1621 CPPUNIT_ASSERT(pType112212211);
1622 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212211->GetValue());
1623 auto pS112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("S"_ostr));
1624 CPPUNIT_ASSERT(pS112212211);
1625 CPPUNIT_ASSERT_EQUAL("Lbl"_ostr, pS112212211->GetValue());
1626 // Lbl has 2 children: the first 2 mcids (in order)
1627 auto pKids112212211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212211->Lookup("K"_ostr));
1628 CPPUNIT_ASSERT(pKids112212211);
1629 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212211->GetElements().size());
1631 auto pRefKid1122122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[0]);
1632 CPPUNIT_ASSERT(pRefKid1122122111);
1633 CPPUNIT_ASSERT_EQUAL(mcids[4], int(pRefKid1122122111->GetValue()));
1634 auto pRefKid1122122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[1]);
1635 CPPUNIT_ASSERT(pRefKid1122122112);
1636 CPPUNIT_ASSERT_EQUAL(mcids[5], int(pRefKid1122122112->GetValue()));
1638 auto pRefKid112212212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[1]);
1639 CPPUNIT_ASSERT(pRefKid112212212);
1640 auto pObject112212212 = pRefKid112212212->LookupObject();
1641 CPPUNIT_ASSERT(pObject112212212);
1642 auto pType112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("Type"_ostr));
1643 CPPUNIT_ASSERT(pType112212212);
1644 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType112212212->GetValue());
1645 auto pS112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("S"_ostr));
1646 CPPUNIT_ASSERT(pS112212212);
1647 CPPUNIT_ASSERT_EQUAL("LBody"_ostr, pS112212212->GetValue());
1648 // inner LBody has 1 children: paragraph
1649 auto pKids112212212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212212->Lookup("K"_ostr));
1650 CPPUNIT_ASSERT(pKids112212212);
1651 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids112212212->GetElements().size());
1653 auto pRefKid1122122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212212->GetElements()[0]);
1654 CPPUNIT_ASSERT(pRefKid1122122121);
1655 auto pObject1122122121 = pRefKid1122122121->LookupObject();
1656 CPPUNIT_ASSERT(pObject1122122121);
1657 auto pType1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("Type"_ostr));
1658 CPPUNIT_ASSERT(pType1122122121);
1659 CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType1122122121->GetValue());
1660 auto pS1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("S"_ostr));
1661 CPPUNIT_ASSERT(pS1122122121);
1662 CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS1122122121->GetValue());
1667 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nL)>(1), nL);
1670 CPPUNIT_TEST_FIXTURE(Test, testTdf143311)
1672 createSwDoc("tdf143311-1.docx");
1673 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1675 // add another one that's a SdrObject
1676 uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
1677 uno::Reference<drawing::XShape> xShape(
1678 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
1679 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
1680 xShapeProps->setPropertyValue("Decorative", uno::Any(true));
1681 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
1682 uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage());
1683 xDrawPage->add(xShape);
1685 // check DOCX filters
1686 saveAndReload("Office Open XML Text");
1687 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1688 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
1690 // tdf#153925 not imported - check default and set it to test ODF filters
1691 uno::Reference<beans::XPropertySet> const xStyle(getStyles("FrameStyles")->getByName("Formula"), uno::UNO_QUERY_THROW);
1692 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xStyle, "Decorative"));
1693 xStyle->setPropertyValue("Decorative", uno::Any(true));
1695 // check ODF filters
1696 saveAndReload("writer8");
1697 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1698 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
1699 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getStyles("FrameStyles")->getByName("Formula"), "Decorative"));
1701 // check PDF export
1702 utl::MediaDescriptor aMediaDescriptor;
1703 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1704 // Enable PDF/UA
1705 uno::Sequence<beans::PropertyValue> aFilterData(
1706 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1707 aMediaDescriptor["FilterData"] <<= aFilterData;
1708 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1709 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1711 vcl::filter::PDFDocument aDocument;
1712 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1713 CPPUNIT_ASSERT(aDocument.Read(aStream));
1715 // The document has one page.
1716 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1717 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1719 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
1720 CPPUNIT_ASSERT(pContents);
1721 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1722 CPPUNIT_ASSERT(pStream);
1723 SvMemoryStream& rObjectStream = pStream->GetMemory();
1724 // Uncompress it.
1725 SvMemoryStream aUncompressed;
1726 ZCodec aZCodec;
1727 aZCodec.BeginCompression();
1728 rObjectStream.Seek(0);
1729 aZCodec.Decompress(rObjectStream, aUncompressed);
1730 CPPUNIT_ASSERT(aZCodec.EndCompression());
1732 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1733 const char* const pEnd = pStart + aUncompressed.GetSize();
1735 enum
1737 Default,
1738 Artifact,
1739 Tagged
1740 } state
1741 = Default;
1743 auto nLine(0);
1744 auto nTagged(0);
1745 auto nArtifacts(0);
1746 while (true)
1748 ++nLine;
1749 auto const pLine = ::std::find(pStart, pEnd, '\n');
1750 if (pLine == pEnd)
1752 break;
1754 std::string_view const line(pStart, pLine - pStart);
1755 pStart = pLine + 1;
1756 if (!line.empty() && line[0] != '%')
1758 ::std::cerr << nLine << ": " << line << "\n";
1759 if (line == "/Artifact BMC")
1761 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1762 state = Artifact;
1763 ++nArtifacts;
1765 else if (o3tl::starts_with(line, "/Standard<</MCID") && o3tl::ends_with(line, ">>BDC"))
1767 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1768 state = Tagged;
1769 ++nTagged;
1771 else if (line == "EMC")
1773 CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
1774 state = Default;
1776 else if (nLine > 1) // first line is expected "0.1 w"
1778 CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
1782 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1783 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(25), nTagged); // text in body
1784 // 1 decorative image + 1 decorative shape + 1 pre-existing rectangle border or something
1785 CPPUNIT_ASSERT(nArtifacts >= 3);
1788 void Test::testTextFormField()
1790 const OUString aFilterNames[] = {
1791 "writer8",
1792 "MS Word 97",
1793 "Office Open XML Text",
1796 for (const OUString& rFilterName : aFilterNames)
1798 createSwDoc("text_form_field.odt");
1800 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1802 // Export the document and import again for a check
1803 saveAndReload(rFilterName);
1805 // Check the document after round trip
1806 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1807 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1808 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1809 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1811 // We have two text form fields
1812 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1814 // Check whether all fieldmarks are text form fields
1815 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1817 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1818 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1819 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMTEXT, pFieldmark->GetFieldname());
1822 // In the first paragraph we have an empty text form field with the placeholder spaces
1823 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
1824 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara, 1), "TextPortionType"));
1825 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara, 2), "TextPortionType"));
1826 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara, 3), "TextPortionType"));
1827 sal_Unicode vEnSpaces[5] = {8194, 8194, 8194, 8194, 8194};
1828 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), getRun(xPara, 3)->getString());
1829 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara, 4), "TextPortionType"));
1831 // In the second paragraph we have a set text
1832 const uno::Reference< text::XTextRange > xPara2 = getParagraph(2);
1833 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara2, 1), "TextPortionType"));
1834 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara2, 2), "TextPortionType"));
1835 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara2, 3), "TextPortionType"));
1836 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("xxxxx"), getRun(xPara2, 3)->getString());
1837 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara2, 4), "TextPortionType"));
1841 void Test::testCheckBoxFormField()
1843 const OUString aFilterNames[] = {
1844 "writer8",
1845 "MS Word 97",
1846 "Office Open XML Text",
1849 for (const OUString& rFilterName : aFilterNames)
1851 createSwDoc("checkbox_form_field.odt");
1853 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1855 // Export the document and import again for a check
1856 saveAndReload(rFilterName);
1858 // Check the document after round trip
1859 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1860 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1861 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1862 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1864 // We have two check box form fields
1865 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1867 int nIndex = 0;
1868 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1870 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1872 if(rFilterName == "Office Open XML Text") // OOXML import also generates bookmarks
1874 if(!pFieldmark)
1875 continue;
1878 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1879 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMCHECKBOX, pFieldmark->GetFieldname());
1880 ::sw::mark::ICheckboxFieldmark* pCheckBox = dynamic_cast< ::sw::mark::ICheckboxFieldmark* >(pFieldmark);
1881 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox);
1883 // The first one is unchecked, the other one is checked
1884 if(nIndex == 0)
1885 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !pCheckBox->IsChecked());
1886 else
1887 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox->IsChecked());
1888 ++nIndex;
1890 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1894 void Test::testDropDownFormField()
1896 const OUString aFilterNames[] = {
1897 "writer8",
1898 "MS Word 97",
1899 "Office Open XML Text",
1902 for (const OUString& rFilterName : aFilterNames)
1904 createSwDoc("dropdown_form_field.odt");
1906 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1908 // Export the document and import again for a check
1909 saveAndReload(rFilterName);
1911 // Check the document after round trip
1912 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1913 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1914 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1915 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1917 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1919 int nIndex = 0;
1920 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1922 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1924 if(!pFieldmark)
1925 continue;
1927 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1928 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDROPDOWN, pFieldmark->GetFieldname());
1930 // Check drop down field's parameters.
1931 const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
1932 css::uno::Sequence<OUString> vListEntries;
1933 sal_Int32 nSelection = -1;
1934 auto pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
1935 if (pListEntries != pParameters->end())
1937 pListEntries->second >>= vListEntries;
1939 if(vListEntries.hasElements())
1941 auto pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
1942 if (pResult != pParameters->end())
1944 pResult->second >>= nSelection;
1949 // The first one is empty
1950 if(nIndex == 0)
1952 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !vListEntries.hasElements());
1953 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(-1), nSelection);
1955 else // The second one has list and also a selected item
1957 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(4), vListEntries.getLength());
1958 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), nSelection);
1959 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("1000"), vListEntries[0]);
1960 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2000"), vListEntries[1]);
1961 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("3000"), vListEntries[2]);
1962 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("4000"), vListEntries[3]);
1964 ++nIndex;
1966 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1970 void Test::testDateFormField()
1972 const OUString aFilterNames[] = {
1973 "writer8",
1974 "Office Open XML Text",
1977 for (const OUString& rFilterName : aFilterNames)
1979 createSwDoc("date_form_field.odt");
1981 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1983 // Export the document and import again for a check
1984 saveAndReload(rFilterName);
1986 // Check the document after round trip
1987 if (rFilterName == "writer8")
1989 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1990 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1991 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1992 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1994 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pMarkAccess->getAllMarksCount());
1996 int nIndex = 0;
1997 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1999 ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*aIter);
2000 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
2001 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
2003 // Check date form field's parameters.
2004 const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
2005 OUString sDateFormat;
2006 auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
2007 if (pResult != pParameters->end())
2009 pResult->second >>= sDateFormat;
2012 OUString sLang;
2013 pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
2014 if (pResult != pParameters->end())
2016 pResult->second >>= sLang;
2019 OUString sCurrentDate = pFieldmark->GetContent();
2021 // The first one has the default field content
2022 if(nIndex == 0)
2025 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2026 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2027 sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
2028 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
2030 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2031 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pFieldmark->GetMarkStart().GetContentIndex());
2033 else if (nIndex == 1) // The second has the default format
2035 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2036 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2037 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
2039 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2040 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(20), pFieldmark->GetMarkStart().GetContentIndex());
2042 else if (nIndex == 2) // The third one has special format
2044 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
2045 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
2046 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
2048 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2049 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(40), pFieldmark->GetMarkStart().GetContentIndex());
2052 else if (nIndex == 3) // The fourth one has placeholder text
2054 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
2055 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
2056 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
2058 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2059 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(62), pFieldmark->GetMarkStart().GetContentIndex());
2062 else // The last one is really empty
2064 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2065 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2066 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
2068 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2069 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(82), pFieldmark->GetMarkStart().GetContentIndex());
2072 ++nIndex;
2074 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2076 else
2078 // Import from DOCX, so the fieldmark is now a content control.
2079 uno::Reference<container::XEnumerationAccess> xEnumAccess(getParagraph(1), uno::UNO_QUERY);
2080 uno::Reference<container::XEnumeration> xTextPortions = xEnumAccess->createEnumeration();
2082 int nIndex = 0;
2083 while (xTextPortions->hasMoreElements())
2085 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
2086 OUString aPortionType;
2087 xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
2088 if (aPortionType != "ContentControl")
2090 continue;
2093 uno::Reference<text::XTextContent> xContentControl;
2094 xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
2095 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2097 bool bDate{};
2098 xContentControlProps->getPropertyValue("Date") >>= bDate;
2099 CPPUNIT_ASSERT(bDate);
2101 // Check date form field's parameters.
2102 OUString sDateFormat;
2103 xContentControlProps->getPropertyValue("DateFormat") >>= sDateFormat;
2105 OUString sLang;
2106 xContentControlProps->getPropertyValue("DateLanguage") >>= sLang;
2108 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2109 uno::UNO_QUERY);
2110 uno::Reference<container::XEnumeration> xContentControlEnum
2111 = xContentControlEnumAccess->createEnumeration();
2112 uno::Reference<text::XTextRange> xContentControlTextPortion(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2113 OUString sCurrentDate = xContentControlTextPortion->getString();
2115 // The first one has the default field content
2116 if(nIndex == 0)
2118 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2119 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2120 sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
2121 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
2123 else if (nIndex == 1) // The second has the default format
2125 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2126 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2127 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
2129 else if (nIndex == 2) // The third one has special format
2131 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
2132 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
2133 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
2135 else if (nIndex == 3) // The fourth one has placeholder text
2137 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
2138 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
2139 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
2141 else // The last one is really empty
2143 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2144 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2145 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
2147 ++nIndex;
2149 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2154 void Test::testDateFormFieldCharacterFormatting()
2156 const OUString aFilterNames[] = {
2157 "writer8",
2158 "Office Open XML Text",
2161 for (const OUString& rFilterName : aFilterNames)
2163 createSwDoc("date_form_field_char_formatting.odt");
2165 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
2167 // Export the document and import again for a check
2168 saveAndReload(rFilterName);
2170 // Check the document after round trip
2171 if (rFilterName == "writer8")
2173 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
2174 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
2175 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
2176 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
2178 // Check that we have the field at the right place
2179 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), pMarkAccess->getAllMarksCount());
2180 ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin());
2181 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
2182 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), ODF_FORMDATE, pFieldmark->GetFieldname());
2183 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(0), pFieldmark->GetMarkStart().GetContentIndex());
2184 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(11), pFieldmark->GetMarkEnd().GetContentIndex());
2186 // We have one date field, first half of the field has bold character weight and second part has red character color
2187 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(getRun(getParagraph(1), 3), "CharWeight"));
2188 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(getRun(getParagraph(1), 3), "CharColor"));
2189 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(getRun(getParagraph(1), 4), "CharWeight"));
2190 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(getRun(getParagraph(1), 4), "CharColor"));
2192 else
2194 uno::Reference<beans::XPropertySet> xTextPortion(getRun(getParagraph(1), 1), uno::UNO_QUERY);
2195 OUString aPortionType;
2196 xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
2197 CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aPortionType);
2199 uno::Reference<text::XTextContent> xContentControl;
2200 xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
2201 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2202 bool bDate{};
2203 xContentControlProps->getPropertyValue("Date") >>= bDate;
2204 CPPUNIT_ASSERT(bDate);
2206 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2207 uno::UNO_QUERY);
2208 uno::Reference<container::XEnumeration> xContentControlEnum
2209 = xContentControlEnumAccess->createEnumeration();
2210 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2211 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(xTextPortion, "CharWeight"));
2212 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(xTextPortion, "CharColor"));
2213 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2214 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(xTextPortion, "CharWeight"));
2215 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xTextPortion, "CharColor"));
2220 void Test::testSvgImageSupport()
2222 OUString aFilterNames[] = {
2223 "writer8",
2224 "Office Open XML Text",
2227 for (OUString const & rFilterName : aFilterNames)
2229 // Use case to import a document containing a SVG image, export in target format, import and check if the
2230 // SVG image is present and as expected in the document
2232 // Import ODT file
2233 createSwDoc("SvgImageTest.odt");
2235 // Export the document in target format and import again
2236 saveAndReload(rFilterName);
2238 // Prepare fail message (writing which import/export filter was used)
2239 const OString sFailedMessage = "Failed on filter: "_ostr + rFilterName.toUtf8();
2241 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 1, getShapes());
2243 // Get the image
2244 uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
2245 uno::Reference<beans::XPropertySet> xPropertySet(xImage, uno::UNO_QUERY_THROW);
2247 // Convert to a XGraphic
2248 uno::Reference<graphic::XGraphic> xGraphic;
2249 xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
2250 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
2252 // Access the Graphic
2253 Graphic aGraphic(xGraphic);
2255 // Check if it contains a VectorGraphicData struct
2256 auto pVectorGraphic = aGraphic.getVectorGraphicData();
2257 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pVectorGraphic);
2259 // Which should be of type SVG, which means we have a SVG file
2260 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), VectorGraphicDataType::Svg, pVectorGraphic->getType());
2264 } // end of anonymous namespace
2265 CPPUNIT_TEST_SUITE_REGISTRATION(Test);
2267 CPPUNIT_PLUGIN_IMPLEMENT();
2269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */