Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / qa / extras / globalfilter / globalfilter.cxx
blob0c7415ddabe66b379ef0228d7eb76dc848a92981
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 <unotools/fltrcfg.hxx>
36 #include <xmloff/odffields.hxx>
37 #include <IDocumentMarkAccess.hxx>
38 #include <IMark.hxx>
39 #include <com/sun/star/awt/FontWeight.hpp>
40 #include <unotools/mediadescriptor.hxx>
42 class Test : public SwModelTestBase
44 public:
45 Test() : SwModelTestBase("/sw/qa/extras/globalfilter/data/") {}
47 void testEmbeddedGraphicRoundtrip();
48 void testLinkedGraphicRT();
49 void testImageWithSpecialID();
50 void testGraphicShape();
51 void testMultipleIdenticalGraphics();
52 void testCharHighlight();
53 void testCharHighlightODF();
54 void testCharHighlightBody();
55 void testCharStyleHighlight();
56 void testMSCharBackgroundEditing();
57 void testCharBackgroundToHighlighting();
58 #if !defined(_WIN32)
59 void testSkipImages();
60 #endif
61 void testNestedFieldmark();
62 void verifyText13(char const*);
63 void testODF13();
64 void testRedlineFlags();
65 void testBulletAsImage();
66 void testTextFormField();
67 void testCheckBoxFormField();
68 void testDropDownFormField();
69 void testDateFormField();
70 void testDateFormFieldCharacterFormatting();
72 CPPUNIT_TEST_SUITE(Test);
73 CPPUNIT_TEST(testEmbeddedGraphicRoundtrip);
74 CPPUNIT_TEST(testLinkedGraphicRT);
75 CPPUNIT_TEST(testImageWithSpecialID);
76 CPPUNIT_TEST(testGraphicShape);
77 CPPUNIT_TEST(testMultipleIdenticalGraphics);
78 CPPUNIT_TEST(testCharHighlight);
79 CPPUNIT_TEST(testCharHighlightODF);
80 CPPUNIT_TEST(testMSCharBackgroundEditing);
81 CPPUNIT_TEST(testCharBackgroundToHighlighting);
82 #if !defined(_WIN32)
83 CPPUNIT_TEST(testSkipImages);
84 #endif
85 CPPUNIT_TEST(testNestedFieldmark);
86 CPPUNIT_TEST(testODF13);
87 CPPUNIT_TEST(testRedlineFlags);
88 CPPUNIT_TEST(testBulletAsImage);
89 CPPUNIT_TEST(testTextFormField);
90 CPPUNIT_TEST(testCheckBoxFormField);
91 CPPUNIT_TEST(testDropDownFormField);
92 CPPUNIT_TEST(testDateFormField);
93 CPPUNIT_TEST(testDateFormFieldCharacterFormatting);
94 CPPUNIT_TEST_SUITE_END();
97 void Test::testEmbeddedGraphicRoundtrip()
99 OUString aFilterNames[] = {
100 "writer8",
101 "Rich Text Format",
102 "MS Word 97",
103 "Office Open XML Text",
106 for (OUString const & rFilterName : aFilterNames)
108 // Check whether the export code swaps in the image which was swapped out before by auto mechanism
110 createSwDoc("document_with_two_images.odt");
112 // Export the document and import again for a check
113 saveAndReload(rFilterName);
115 // Check whether graphic exported well after it was swapped out
116 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
117 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
119 // First image
120 uno::Reference<drawing::XShape> xImage(getShape(1), uno::UNO_QUERY);
121 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
123 // Check graphic, size
125 uno::Reference<graphic::XGraphic> xGraphic;
126 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
127 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
128 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
129 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
130 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
131 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
132 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
135 // Second Image
136 xImage.set(getShape(2), uno::UNO_QUERY);
137 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
139 // Check graphic, size
141 uno::Reference<graphic::XGraphic> xGraphic;
142 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
143 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
144 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
145 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
146 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
147 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
148 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
153 void Test::testLinkedGraphicRT()
155 const OUString aFilterNames[] = {
156 "writer8",
157 // "Rich Text Format", Note: picture is there, but SwGrfNode is not found?
158 "MS Word 97",
159 "Office Open XML Text",
162 for (OUString const & rFilterName : aFilterNames)
164 createSwDoc("document_with_linked_graphic.odt");
166 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
168 // Export the document and import again for a check
169 saveAndReload(rFilterName);
171 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
172 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
173 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
174 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pDoc);
175 SwNodes& aNodes = pDoc->GetNodes();
177 // Find the image
178 bool bImageFound = false;
179 Graphic aGraphic;
180 for (SwNodeOffset nIndex(0); nIndex < aNodes.Count(); ++nIndex)
182 if (aNodes[nIndex]->IsGrfNode())
184 SwGrfNode* pGrfNode = aNodes[nIndex]->GetGrfNode();
185 CPPUNIT_ASSERT(pGrfNode);
187 const GraphicObject& rGraphicObj = pGrfNode->GetGrfObj(true);
188 aGraphic = rGraphicObj.GetGraphic();
189 bImageFound = true;
193 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bImageFound);
195 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
196 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_uLong(864900), aGraphic.GetSizeBytes());
198 // Check if linked graphic is registered in LinkManager
199 sfx2::LinkManager& rLinkManager = pTextDoc->GetDocShell()->GetDoc()->GetEditShell()->GetLinkManager();
200 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), size_t(1), rLinkManager.GetLinks().size());
201 const tools::SvRef<sfx2::SvBaseLink> & rLink = rLinkManager.GetLinks()[0];
202 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), rLink->GetLinkSourceName().indexOf("linked_graphic.jpg") >= 0);
206 void Test::testImageWithSpecialID()
208 // Check how LO handles when the imported graphic's ID is different from that one
209 // which is generated by LO.
211 const OUString aFilterNames[] = {
212 "writer8",
213 "Rich Text Format",
214 "MS Word 97",
215 "Office Open XML Text",
218 for (OUString const & rFilterName : aFilterNames)
220 createSwDoc("images_with_special_IDs.odt");
222 // Export the document and import again for a check
223 saveAndReload(rFilterName);
225 // Check whether graphic exported well
226 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
227 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
229 uno::Reference<drawing::XShape> xImage = getShape(1);
230 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY_THROW );
232 // Check graphic, size
234 uno::Reference<graphic::XGraphic> xGraphic;
235 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
236 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
237 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
238 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
239 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
240 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width);
241 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height);
244 // Second Image
245 xImage.set(getShape(2), uno::UNO_QUERY);
246 XPropSet.set( xImage, uno::UNO_QUERY_THROW );
248 // Check graphic, size
250 uno::Reference<graphic::XGraphic> xGraphic;
251 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
252 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
253 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), graphic::GraphicType::PIXEL, xGraphic->getType());
254 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
255 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
256 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(900), xBitmap->getSize().Width);
257 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(600), xBitmap->getSize().Height);
262 /// Gives the first embedded or linked image in a document.
263 static uno::Reference<drawing::XShape> lcl_getShape(const uno::Reference<lang::XComponent>& xComponent, bool bEmbedded)
265 uno::Reference<drawing::XShape> xShape;
267 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
268 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
269 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
271 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
272 uno::Reference<graphic::XGraphic> xGraphic;
273 xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
274 if (xGraphic.is())
276 Graphic aGraphic(xGraphic);
278 if (bEmbedded == aGraphic.getOriginURL().isEmpty())
280 xShape.set(xShapeProperties, uno::UNO_QUERY);
281 return xShape;
286 return xShape;
289 void Test::testGraphicShape()
291 // There are two kind of images in Writer: 1) Writer specific handled by SwGrfNode and
292 // 2) graphic shape handled by SdrGrafObj (e.g. after copy&paste from Impress).
294 const OUString aFilterNames[] = {
295 "writer8",
296 "Rich Text Format",
297 "MS Word 97",
298 "Office Open XML Text",
301 for (OUString const & rFilterName : aFilterNames)
303 createSwDoc("graphic_shape.odt");
305 // Export the document and import again for a check
306 saveAndReload(rFilterName);
308 // Check whether graphic exported well
309 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
310 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), 2, getShapes());
312 uno::Reference<drawing::XShape> xImage = lcl_getShape(mxComponent, true);
313 CPPUNIT_ASSERT_MESSAGE("Couldn't load the shape/image", xImage.is());
314 uno::Reference< beans::XPropertySet > XPropSet( xImage, uno::UNO_QUERY );
315 // First image is embedded
316 // Check size
318 uno::Reference<graphic::XGraphic> xGraphic;
319 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
320 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
321 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
322 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
323 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(610), xBitmap->getSize().Width );
324 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(381), xBitmap->getSize().Height );
327 // MS filters make this kind of linked images broken !?
328 if (rFilterName != "writer8")
329 return;
331 // Second image is a linked one
332 xImage = lcl_getShape(mxComponent, false);
333 XPropSet.set(xImage, uno::UNO_QUERY);
334 const OString sFailedImageLoad = OString::Concat("Couldn't load the shape/image for ") + rFilterName.toUtf8();
335 CPPUNIT_ASSERT_MESSAGE(sFailedImageLoad.getStr(), xImage.is());
337 // Check size
339 uno::Reference<graphic::XGraphic> xGraphic;
340 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
341 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xGraphic.is());
343 Graphic aGraphic(xGraphic);
344 OUString sURL = aGraphic.getOriginURL();
345 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), sURL.endsWith("linked_graphic.jpg"));
347 uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY);
348 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
349 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(620), xBitmap->getSize().Width);
350 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(465), xBitmap->getSize().Height);
355 namespace
358 std::vector<uno::Reference<graphic::XGraphic>>
359 lcl_getGraphics(const uno::Reference<lang::XComponent>& xComponent)
361 std::vector<uno::Reference<graphic::XGraphic>> aGraphics;
363 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xComponent, uno::UNO_QUERY);
364 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
365 for (sal_Int32 i = 0; i < xDrawPage->getCount(); ++i)
367 uno::Reference<beans::XPropertySet> xShapeProperties(xDrawPage->getByIndex(i), uno::UNO_QUERY);
368 uno::Reference<graphic::XGraphic> xGraphic;
369 xShapeProperties->getPropertyValue("Graphic") >>= xGraphic;
370 if (xGraphic.is())
372 aGraphics.push_back(xGraphic);
376 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 SvtFilterOptions& rOpt = SvtFilterOptions::Get();
542 rOpt.SetCharBackground2Shading();
544 testCharHighlightBody();
545 testCharStyleHighlight();
547 rOpt.SetCharBackground2Highlighting();
549 testCharHighlightBody();
550 testCharStyleHighlight();
553 void Test::testCharHighlightODF()
555 createSwDoc("char_background_editing.docx");
557 // don't check import, testMSCharBackgroundEditing already does that
559 uno::Reference<text::XTextRange> xPara = getParagraph(1);
560 for (int i = 1; i <= 4; ++i)
562 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
563 switch (i)
565 case 1: // non-transparent highlight
566 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
567 xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
568 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
569 break;
571 case 2: // transparent backcolor
572 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
573 xRun->setPropertyValue("CharBackTransparent", uno::Any(true));
574 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
575 break;
577 case 3: // non-transparent backcolor
578 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
579 xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
580 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(COL_TRANSPARENT)));
581 break;
583 case 4: // non-transparent highlight again
584 xRun->setPropertyValue("CharBackColor", uno::Any(static_cast<sal_Int32>(128)));
585 xRun->setPropertyValue("CharBackTransparent", uno::Any(false));
586 xRun->setPropertyValue("CharHighlight", uno::Any(static_cast<sal_Int32>(64)));
587 break;
591 saveAndReload("writer8");
593 xPara.set(getParagraph(1));
594 for (int i = 1; i <= 4; ++i)
596 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
597 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(COL_TRANSPARENT), getProperty<sal_Int32>(xRun, "CharHighlight"));
598 switch (i)
600 case 1: // non-transparent highlight
601 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
602 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
603 break;
604 case 2: // transparent backcolor
605 CPPUNIT_ASSERT_EQUAL(COL_TRANSPARENT, getProperty<Color>(xRun, "CharBackColor"));
606 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(xRun, "CharBackTransparent"));
607 break;
608 case 3: // non-transparent backcolor
609 CPPUNIT_ASSERT_EQUAL(COL_BLUE, getProperty<Color>(xRun, "CharBackColor"));
610 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
611 break;
612 case 4: // non-transparent highlight again
613 CPPUNIT_ASSERT_EQUAL(Color(0x000040), getProperty<Color>(xRun, "CharBackColor"));
614 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xRun, "CharBackTransparent"));
615 break;
620 void Test::testMSCharBackgroundEditing()
622 // Simulate the editing process of imported MSO character background attributes
623 // and check how export behaves.
625 const OUString aFilterNames[] = {
626 "writer8",
627 "Rich Text Format",
628 "MS Word 97",
629 "Office Open XML Text",
632 for (OUString const & rFilterName : aFilterNames)
634 createSwDoc("char_background_editing.docx");
636 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
638 // Check whether import was done on the right way
639 uno::Reference< text::XTextRange > xPara = getParagraph(1);
641 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,1), uno::UNO_QUERY);
642 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
643 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
645 xRun.set(getRun(xPara,2), uno::UNO_QUERY);
646 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
647 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
649 xRun.set(getRun(xPara,3), uno::UNO_QUERY);
650 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTBLUE, getProperty<Color>(xRun,"CharHighlight"));
651 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_LIGHTRED, getProperty<Color>(xRun,"CharBackColor"));
653 xRun.set(getRun(xPara,4), uno::UNO_QUERY);
654 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
655 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
658 // Simulate editing
659 for( int i = 1; i <= 4; ++i )
661 uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
662 // Change background
663 Color nBackColor;
664 switch( i )
666 case 1: nBackColor = COL_BLACK; break; //black 0x000000
667 case 2: nBackColor = COL_LIGHTCYAN; break; //cyan 0x00ffff
668 case 3: nBackColor = COL_LIGHTGREEN; break; //green 0x00ff00
669 case 4: nBackColor = COL_LIGHTMAGENTA; break; //magenta 0xff00ff
671 xRun->setPropertyValue("CharBackColor", uno::Any(nBackColor));
672 // Remove highlighting
673 xRun->setPropertyValue("CharHighlight", uno::Any(COL_TRANSPARENT));
674 // Remove shading marker
675 uno::Sequence<beans::PropertyValue> aGrabBag = getProperty<uno::Sequence<beans::PropertyValue> >(xRun,"CharInteropGrabBag");
676 for (beans::PropertyValue& rProp : asNonConstRange(aGrabBag))
678 if (rProp.Name == "CharShadingMarker")
680 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), true, rProp.Value.get<bool>());
681 rProp.Value <<= false;
684 xRun->setPropertyValue("CharInteropGrabBag", uno::Any(aGrabBag));
687 SvtFilterOptions& rOpt = SvtFilterOptions::Get();
688 rOpt.SetCharBackground2Highlighting();
690 // Export the document and import again for a check
691 saveAndReload(rFilterName);
693 // Check whether background was exported as highlighting
694 xPara.set(getParagraph(1));
695 for( int i = 1; i <= 4; ++i )
697 Color nBackColor;
698 switch( i )
700 case 1: nBackColor = COL_BLACK; break; //black 0x000000
701 case 2: nBackColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
702 case 3: nBackColor = COL_LIGHTGREEN; break; //light green 0x00ff00
703 case 4: nBackColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
705 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,i), uno::UNO_QUERY);
706 if (rFilterName == "writer8")
708 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharHighlight"));
709 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharBackColor"));
711 else
713 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), nBackColor, getProperty<Color>(xRun,"CharHighlight"));
714 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_TRANSPARENT, getProperty<Color>(xRun,"CharBackColor"));
720 void Test::testCharBackgroundToHighlighting()
722 // MSO highlighting has less kind of values so let's see how LO character background is converted
723 // to these values
725 const OUString aFilterNames[] = {
726 "Rich Text Format",
727 "MS Word 97",
728 "Office Open XML Text",
731 for (OUString const & rFilterName : aFilterNames)
733 createSwDoc("char_background.odt");
735 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
738 SvtFilterOptions& rOpt = SvtFilterOptions::Get();
739 rOpt.SetCharBackground2Highlighting();
741 // Export the document and import again for a check
742 saveAndReload(rFilterName);
744 // Check highlight color
745 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
746 for( int nRun = 1; nRun <= 19; ++nRun )
748 const uno::Reference<beans::XPropertySet> xRun(getRun(xPara,nRun), uno::UNO_QUERY);
749 Color nHighlightColor;
750 switch( nRun )
752 case 1: nHighlightColor = COL_BLACK; break; //black 0x000000
753 case 2: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
754 case 3: nHighlightColor = COL_LIGHTMAGENTA; break; //light magenta 0xff00ff
755 case 4: nHighlightColor = COL_LIGHTCYAN; break; //light cyan 0x00ffff
756 case 5: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
757 case 6: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
758 case 7: nHighlightColor = COL_LIGHTBLUE; break; //light blue 0x0000ff
759 case 8: nHighlightColor = COL_LIGHTGREEN; break; //light green 0x00ff00
760 case 9: nHighlightColor = COL_GREEN; break; //dark green 0x008000
761 case 10: nHighlightColor = COL_MAGENTA; break; //dark magenta 0x800080
762 case 11: nHighlightColor = COL_BLUE; break; //dark blue 0x000080
763 case 12: nHighlightColor = COL_BROWN; break; //brown 0x808000
764 case 13: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
765 case 14: nHighlightColor = COL_BLACK; break; //black 0x000000
766 case 15: nHighlightColor = COL_LIGHTRED; break; //light red 0xff0000
767 case 16: nHighlightColor = COL_LIGHTGRAY; break; //light gray 0xC0C0C0
768 case 17: nHighlightColor = COL_RED; break; //dark red 0x800000
769 case 18: nHighlightColor = COL_GRAY; break; //dark gray 0x808080
770 case 19: nHighlightColor = COL_YELLOW; break; //yellow 0xffff00
772 const OString sMessage = sFailedMessage +". Index of run with unmatched color: " + OString::number(nRun);
773 CPPUNIT_ASSERT_EQUAL_MESSAGE(sMessage.getStr(), nHighlightColor, getProperty<Color>(xRun,"CharHighlight"));
778 #if !defined(_WIN32)
779 void Test::testSkipImages()
781 // Check how LO skips image loading (but not texts of textboxes and custom shapes)
782 // during DOC and DOCX import, using the "SkipImages" FilterOptions.
784 std::pair<OUString, OUString> aFilterNames[] = {
785 { "skipimages.doc", "" },
786 { "skipimages.doc", "SkipImages" },
787 { "skipimages.docx", "" },
788 { "skipimages.docx", "SkipImages" }
791 for (auto const & rFilterNamePair : aFilterNames)
793 bool bSkipImages = !rFilterNamePair.second.isEmpty();
794 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterNamePair.first.toUtf8();
796 setImportFilterOptions(rFilterNamePair.second);
797 createSwDoc(rFilterNamePair.first.toUtf8().getStr());
798 sFailedMessage += " - " + rFilterNamePair.second.toUtf8();
800 // Check shapes (images, textboxes, custom shapes)
801 uno::Reference<drawing::XShape> xShape;
802 uno::Reference<graphic::XGraphic> xGraphic;
803 uno::Reference< beans::XPropertySet > XPropSet;
804 uno::Reference<awt::XBitmap> xBitmap;
806 bool bHasTextboxText = false;
807 bool bHasCustomShapeText = false;
808 sal_Int32 nImageCount = 0;
810 for (int i = 1; i<= getShapes(); i++)
812 xShape = getShape(i);
813 XPropSet.set( xShape, uno::UNO_QUERY_THROW );
816 XPropSet->getPropertyValue("Graphic") >>= xGraphic;
817 xBitmap.set(xGraphic, uno::UNO_QUERY);
818 if (xBitmap.is())
819 nImageCount++;
821 catch (beans::UnknownPropertyException &)
822 { /* ignore */ }
824 uno::Reference<text::XTextRange> xText(xShape, uno::UNO_QUERY);
825 if (xText.is())
827 OUString shapeText = xText->getString();
828 if (shapeText.startsWith("Lorem ipsum"))
829 bHasTextboxText = true;
830 else if (shapeText.startsWith("Nam pretium"))
831 bHasCustomShapeText = true;
835 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasTextboxText);
836 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), bHasCustomShapeText);
837 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), static_cast<sal_Int32>(bSkipImages ? 0 : 3), nImageCount );
840 #endif
842 static auto verifyNestedFieldmark(OUString const& rTestName,
843 uno::Reference<lang::XComponent> const& xComponent) -> void
845 SwDoc const*const pDoc(dynamic_cast<SwXTextDocument&>(*xComponent).GetDocShell()->GetDoc());
846 IDocumentMarkAccess const& rIDMA(*pDoc->getIDocumentMarkAccess());
848 // no spurious bookmarks have been created
849 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
850 sal_Int32(0), rIDMA.getBookmarksCount());
852 // check inner fieldmark
853 SwNodeIndex const node1(*pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), +2);
854 SwPosition const innerPos(*node1.GetNode().GetTextNode(),
855 node1.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
856 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
857 sal_Int32(1), innerPos.GetContentIndex());
858 ::sw::mark::IFieldmark *const pInner(rIDMA.getFieldmarkAt(innerPos));
859 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pInner);
860 OUString const innerString(SwPaM(pInner->GetMarkPos(), pInner->GetOtherMarkPos()).GetText());
861 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
862 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
863 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
864 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
865 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
866 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), innerString);
868 // check outer fieldmark
869 SwNodeIndex const node2(node1, -1);
870 SwPosition const outerPos(*node2.GetNode().GetTextNode(),
871 node2.GetNode().GetTextNode()->GetText().indexOf(CH_TXT_ATR_FIELDSTART));
872 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(),
873 sal_Int32(0), outerPos.GetContentIndex());
874 ::sw::mark::IFieldmark const*const pOuter(rIDMA.getFieldmarkAt(outerPos));
875 CPPUNIT_ASSERT_MESSAGE(rTestName.toUtf8().getStr(), pOuter);
876 OUString const outerString(SwPaM(pOuter->GetMarkPos(), pOuter->GetOtherMarkPos()).GetText());
877 CPPUNIT_ASSERT_EQUAL_MESSAGE(rTestName.toUtf8().getStr(), OUString(
878 OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
879 + u" " + OUStringChar(CH_TXT_ATR_FIELDSTART) + u" QUOTE \"foo " + OUStringChar(CH_TXTATR_NEWLINE)
880 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
881 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
882 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
883 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND) + OUStringChar(CH_TXTATR_NEWLINE)
884 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
885 + u"baz\" " + OUStringChar(CH_TXT_ATR_FIELDSEP) + u"foo " + OUStringChar(CH_TXTATR_NEWLINE)
886 + u" foo " + OUStringChar(CH_TXTATR_NEWLINE)
887 + u" bar " + OUStringChar(CH_TXTATR_NEWLINE)
888 + u"baz" + OUStringChar(CH_TXTATR_NEWLINE)
889 + u"bar " + OUStringChar(CH_TXTATR_NEWLINE)
890 + u"baz" + OUStringChar(CH_TXT_ATR_FIELDEND)), outerString);
892 // must return innermost mark
893 CPPUNIT_ASSERT_EQUAL(pInner, rIDMA.getInnerFieldmarkFor(innerPos));
896 void Test::testNestedFieldmark()
898 // experimental config setting
899 Resetter resetter(
900 [] () {
901 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
902 comphelper::ConfigurationChanges::create());
903 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(false, pBatch);
904 return pBatch->commit();
906 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
907 officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::set(true, pBatch);
908 pBatch->commit();
910 std::pair<OUString, OUString> const aFilterNames[] = {
911 {"writer8", "fieldmark_QUOTE_nest.fodt"},
912 {"Office Open XML Text", "fieldmark_QUOTE_nest.docx"},
913 {"Rich Text Format", "fieldmark_QUOTE_nest.rtf"},
916 for (auto const & rFilterName : aFilterNames)
918 createSwDoc(rFilterName.second.toUtf8().getStr());
920 verifyNestedFieldmark(rFilterName.first + ", load", mxComponent);
922 // Export the document and import again
923 saveAndReload(rFilterName.first);
925 verifyNestedFieldmark(rFilterName.first + " exported-reload", mxComponent);
929 auto Test::verifyText13(char const*const pTestName) -> void
931 // OFFICE-3789 style:header-first/style:footer-first
932 uno::Reference<beans::XPropertySet> xPageStyle;
933 getStyles("PageStyles")->getByName("Standard") >>= xPageStyle;
934 uno::Reference<text::XText> xHF(getProperty<uno::Reference<text::XText>>(xPageStyle, "HeaderTextFirst"));
935 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Header first"), xHF->getString());
936 uno::Reference<text::XText> xFF(getProperty<uno::Reference<text::XText>>(xPageStyle, "FooterTextFirst"));
937 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Footer first"), xFF->getString());
938 // OFFICE-3767 text:contextual-spacing
939 uno::Reference<text::XTextRange> xPara(getParagraph(1));
940 CPPUNIT_ASSERT_MESSAGE(pTestName, getProperty<bool>(xPara, "ParaContextMargin"));
941 // OFFICE-3776 meta:creator-initials
942 uno::Reference<text::XTextRange> xRun(getRun(xPara, 1));
943 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("Annotation"), getProperty<OUString>(xRun, "TextPortionType"));
944 uno::Reference<beans::XPropertySet> xComment(getProperty<uno::Reference<beans::XPropertySet>>(xRun, "TextField"));
945 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("dj"), getProperty<OUString>(xComment, "Initials"));
946 // OFFICE-3941 text:index-entry-link-start/text:index-entry-link-end
947 uno::Reference<text::XDocumentIndexesSupplier> xDIS(mxComponent, uno::UNO_QUERY);
948 uno::Reference<container::XIndexAccess> xIndexes(xDIS->getDocumentIndexes());
949 uno::Reference<text::XDocumentIndex> xIndex(xIndexes->getByIndex(0), uno::UNO_QUERY);
950 uno::Reference<container::XIndexReplace> xLevels(getProperty<uno::Reference<container::XIndexReplace>>(xIndex, "LevelFormat"));
951 uno::Sequence<beans::PropertyValues> format;
952 xLevels->getByIndex(1) >>= format; // 1-based?
953 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[0][0].Name);
954 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkStart"), format[0][0].Value.get<OUString>());
955 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenType"), format[4][0].Name);
956 CPPUNIT_ASSERT_EQUAL_MESSAGE(pTestName, OUString("TokenHyperlinkEnd"), format[4][0].Value.get<OUString>());
959 // test ODF 1.3 new text document features
960 void Test::testODF13()
962 // import
963 createSwDoc("text13e.odt");
965 // check model
966 verifyText13("import");
968 Resetter _([]() {
969 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
970 comphelper::ConfigurationChanges::create());
971 officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
972 return pBatch->commit();
976 // export ODF 1.3
977 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
978 comphelper::ConfigurationChanges::create());
979 officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
980 pBatch->commit();
982 saveAndReload("writer8");
984 // check XML
985 xmlDocUniquePtr pContentXml = parseExport("content.xml");
986 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@style:contextual-spacing='true']");
987 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials");
988 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials", 0);
989 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");
990 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", 0);
991 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");
992 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", 0);
993 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
994 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first");
995 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first", 0);
996 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first");
997 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first", 0);
999 // check model
1000 verifyText13("1.3 reload");
1003 // export ODF 1.2 extended
1004 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1005 comphelper::ConfigurationChanges::create());
1006 officecfg::Office::Common::Save::ODF::DefaultVersion::set(9, pBatch);
1007 pBatch->commit();
1009 // FIXME: it's not possible to use 'reload' here because the validation fails with
1010 // Error: unexpected attribute "loext:contextual-spacing"
1011 utl::MediaDescriptor aMediaDescriptor;
1012 aMediaDescriptor["FilterName"] <<= OUString("writer8");
1014 uno::Reference<frame::XStorable> const xStorable(mxComponent, uno::UNO_QUERY);
1015 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1017 // check XML
1018 xmlDocUniquePtr pContentXml = parseExport("content.xml");
1019 assertXPath(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties[@loext:contextual-spacing='true']");
1020 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials");
1021 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials", 0);
1022 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");
1023 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", 0);
1024 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");
1025 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", 0);
1026 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
1027 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first");
1028 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first", 0);
1029 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first");
1030 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first", 0);
1032 // reload
1033 mxComponent->dispose();
1034 mxComponent = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument");
1036 // check model
1037 verifyText13("1.2 Extended reload");
1040 // export ODF 1.2
1041 std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1042 comphelper::ConfigurationChanges::create());
1043 officecfg::Office::Common::Save::ODF::DefaultVersion::set(4, pBatch);
1044 pBatch->commit();
1046 // don't reload - no point
1047 save("writer8");
1049 // check XML
1050 xmlDocUniquePtr pContentXml = parseExport("content.xml");
1051 assertXPathNoAttribute(pContentXml, "/office:document-content/office:automatic-styles/style:style/style:paragraph-properties", "contextual-spacing");
1052 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/meta:creator-initials", 0);
1053 assertXPath(pContentXml, "/office:document-content/office:body/office:text/text:p/office:annotation/loext:sender-initials", 0);
1054 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", 0);
1055 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", 0);
1056 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", 0);
1057 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", 0);
1058 xmlDocUniquePtr pStylesXml = parseExport("styles.xml");
1059 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:header-first", 0);
1060 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:header-first", 0);
1061 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/style:footer-first", 0);
1062 assertXPath(pStylesXml, "/office:document-styles/office:master-styles/style:master-page/loext:footer-first", 0);
1066 void Test::testRedlineFlags()
1068 const OUString aFilterNames[] = {
1069 "writer8",
1070 "Rich Text Format",
1071 "MS Word 97",
1072 "Office Open XML Text",
1075 createSwDoc();
1076 SwDoc* pDoc = getSwDoc();
1078 SwPaM pam(SwPosition(pDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1)));
1079 pDoc->getIDocumentContentOperations().InsertString(pam, "foo bar baz");
1081 IDocumentRedlineAccess & rIDRA(pDoc->getIDocumentRedlineAccess());
1082 // enable change tracking
1083 rIDRA.SetRedlineFlags(rIDRA.GetRedlineFlags()
1084 | RedlineFlags::On | RedlineFlags::ShowDelete);
1086 // need a delete redline to trigger mode switching
1087 pam.Move(fnMoveForward, GoInDoc);
1088 pam.SetMark();
1089 pam.Move(fnMoveBackward, GoInDoc);
1090 pDoc->getIDocumentContentOperations().DeleteAndJoin(pam);
1092 // hide delete redlines
1093 RedlineFlags const nRedlineFlags =
1094 rIDRA.GetRedlineFlags() & ~RedlineFlags::ShowDelete;
1095 rIDRA.SetRedlineFlags(nRedlineFlags);
1097 for (OUString const & rFilterName : aFilterNames)
1099 // export the document
1100 save(rFilterName);
1102 // tdf#97103 check that redline mode is properly restored
1103 CPPUNIT_ASSERT_EQUAL_MESSAGE(
1104 OString(OString::Concat("redline mode not restored in ") + rFilterName.toUtf8()).getStr(),
1105 static_cast<int>(nRedlineFlags), static_cast<int>(rIDRA.GetRedlineFlags()));
1109 void Test::testBulletAsImage()
1111 OUString aFilterNames[] = {
1112 "writer8",
1113 "MS Word 97",
1114 "Office Open XML Text",
1115 "Rich Text Format",
1118 for (OUString const & rFilterName : aFilterNames)
1120 OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1122 createSwDoc("BulletAsImage.odt");
1124 // Check if import was successful
1126 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1127 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1128 uno::Reference<container::XIndexAccess> xLevels;
1129 xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
1130 uno::Sequence<beans::PropertyValue> aProperties;
1131 xLevels->getByIndex(0) >>= aProperties;
1132 uno::Reference<awt::XBitmap> xBitmap;
1133 awt::Size aSize;
1134 sal_Int16 nNumberingType = -1;
1136 for (beans::PropertyValue const & rProperty : std::as_const(aProperties))
1138 if (rProperty.Name == "NumberingType")
1140 nNumberingType = rProperty.Value.get<sal_Int16>();
1142 else if (rProperty.Name == "GraphicBitmap")
1144 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1146 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1149 else if (rProperty.Name == "GraphicSize")
1151 aSize = rProperty.Value.get<awt::Size>();
1155 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1157 // Graphic Bitmap
1158 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1159 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1160 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1161 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1162 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1163 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1165 // Graphic Size
1166 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1167 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1170 // Export the document and import again for a check
1171 saveAndReload(rFilterName);
1174 uno::Reference<text::XTextRange> xPara(getParagraph(1));
1175 uno::Reference<beans::XPropertySet> xPropertySet(xPara, uno::UNO_QUERY);
1176 uno::Reference<container::XIndexAccess> xLevels;
1177 xLevels.set(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
1178 uno::Sequence<beans::PropertyValue> aProperties;
1179 xLevels->getByIndex(0) >>= aProperties;
1180 uno::Reference<awt::XBitmap> xBitmap;
1181 awt::Size aSize;
1182 sal_Int16 nNumberingType = -1;
1184 for (beans::PropertyValue const & rProperty : std::as_const(aProperties))
1186 if (rProperty.Name == "NumberingType")
1188 nNumberingType = rProperty.Value.get<sal_Int16>();
1190 else if (rProperty.Name == "GraphicBitmap")
1192 if (rProperty.Value.has<uno::Reference<awt::XBitmap>>())
1194 xBitmap = rProperty.Value.get<uno::Reference<awt::XBitmap>>();
1197 else if (rProperty.Name == "GraphicSize")
1199 aSize = rProperty.Value.get<awt::Size>();
1203 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), style::NumberingType::BITMAP, nNumberingType);
1205 // Graphic Bitmap
1206 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), xBitmap.is());
1207 Graphic aGraphic(uno::Reference<graphic::XGraphic>(xBitmap, uno::UNO_QUERY));
1208 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), GraphicType::Bitmap, aGraphic.GetType());
1209 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), aGraphic.GetSizeBytes() > o3tl::make_unsigned(0));
1210 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Width());
1211 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), tools::Long(16), aGraphic.GetSizePixel().Height());
1213 // Graphic Size
1214 if (rFilterName == "write8") // ODT is correct
1216 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Width);
1217 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(400), aSize.Height);
1219 // FIXME: MS Filters don't work correctly for graphic bullet size
1220 else if (rFilterName == "Office Open XML Text" || rFilterName == "Rich Text Format")
1222 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Width);
1223 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(279), aSize.Height);
1225 else if (rFilterName == "MS Word 97")
1227 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Width);
1228 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(296), aSize.Height);
1234 CPPUNIT_TEST_FIXTURE(Test, testListLabelPDFExport)
1236 createSwDoc();
1238 uno::Reference<text::XTextDocument> xDoc(mxComponent, uno::UNO_QUERY_THROW);
1239 uno::Reference<text::XText> xText(xDoc->getText());
1240 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY_THROW);
1241 uno::Reference<container::XIndexReplace> xNumRule(
1242 xFactory->createInstance("com.sun.star.text.NumberingRules"),
1243 uno::UNO_QUERY_THROW);
1244 OUString listFormat;
1245 for (sal_Int32 i = 0; i < xNumRule->getCount(); ++i)
1247 uno::Sequence<beans::PropertyValue> format;
1248 format.getArray();
1249 xNumRule->getByIndex(i) >>= format;
1251 auto it(::std::find_if(format.begin(), format.end(),
1252 [](auto const& r) { return r.Name == "NumberingType"; }));
1253 // need something RTL
1254 const_cast<uno::Any&>(it->Value) <<= style::NumberingType::CHARS_ARABIC;
1257 #if 0
1258 // this doesn't work any more
1259 auto it(::std::find_if(format.begin(), format.end(),
1260 [](auto const& r) { return r.Name == "ParentNumbering"; }));
1261 const_cast<uno::Any&>(it->Value) <<= sal_Int16(i + 1);
1262 #endif
1263 listFormat += "%" + OUString::number(i+1) + "%.";
1264 auto it(::std::find_if(format.begin(), format.end(),
1265 [](auto const& r) { return r.Name == "ListFormat"; }));
1266 const_cast<uno::Any&>(it->Value) <<= listFormat;
1268 xNumRule->replaceByIndex(i, uno::Any(format));
1270 uno::Reference<beans::XPropertySet>(getParagraph(1), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingRules", uno::Any(xNumRule));
1271 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1272 uno::Reference<beans::XPropertySet>(getParagraph(2), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(1)));
1273 xText->insertControlCharacter(xText->getEnd(), text::ControlCharacter::PARAGRAPH_BREAK, false);
1274 uno::Reference<beans::XPropertySet>(getParagraph(3), uno::UNO_QUERY_THROW)->setPropertyValue("NumberingLevel", uno::Any(sal_Int16(2)));
1276 // check PDF export of the list items (label in particular)
1277 utl::MediaDescriptor aMediaDescriptor;
1278 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1279 // Enable PDF/UA
1280 uno::Sequence<beans::PropertyValue> aFilterData(
1281 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1282 aMediaDescriptor["FilterData"] <<= aFilterData;
1283 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1284 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1286 vcl::filter::PDFDocument aDocument;
1287 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1288 CPPUNIT_ASSERT(aDocument.Read(aStream));
1290 // The document has one page.
1291 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1292 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1294 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
1295 CPPUNIT_ASSERT(pContents);
1296 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1297 CPPUNIT_ASSERT(pStream);
1298 SvMemoryStream& rObjectStream = pStream->GetMemory();
1299 // Uncompress it.
1300 SvMemoryStream aUncompressed;
1301 ZCodec aZCodec;
1302 aZCodec.BeginCompression();
1303 rObjectStream.Seek(0);
1304 aZCodec.Decompress(rObjectStream, aUncompressed);
1305 CPPUNIT_ASSERT(aZCodec.EndCompression());
1307 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1308 const char* const pEnd = pStart + aUncompressed.GetSize();
1310 enum
1312 Default,
1313 Lbl,
1314 LblFoundText
1315 } state
1316 = Default;
1318 auto nLine(0);
1319 auto nLbl(0);
1320 auto nLblTj(0);
1321 auto nLblTJ(0);
1322 std::vector<int> mcids;
1323 while (true)
1325 ++nLine;
1326 auto const pLine = ::std::find(pStart, pEnd, '\n');
1327 if (pLine == pEnd)
1329 break;
1331 std::string_view const line(pStart, pLine - pStart);
1332 pStart = pLine + 1;
1333 if (!line.empty() && line[0] != '%')
1335 ::std::cerr << nLine << ": " << line << "\n";
1336 if (o3tl::starts_with(line, "/Lbl<</MCID") && o3tl::ends_with(line, ">>BDC"))
1338 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1339 mcids.push_back(o3tl::toInt32(line.substr(12)));
1340 state = Lbl;
1341 ++nLbl;
1343 else if (state == Lbl)
1345 auto const endj(line.find(">Tj"));
1346 if (endj != ::std::string_view::npos)
1348 auto const start(line.rfind("<", endj) + 1);
1349 // for these, expected length is 1 glyphs, each 2 digits
1350 // would be better to check the content but it depends on CMap
1351 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(endj - start)>(1 * 2), endj - start);
1352 state = LblFoundText;
1353 ++nLblTj;
1355 else
1357 auto const endJ(line.find("]TJ"));
1358 if (endJ != ::std::string_view::npos)
1360 auto const start(line.rfind("[", endJ) + 1);
1361 auto i(line.find("<", start));
1362 auto digits(0);
1363 while (i != ::std::string_view::npos && i < endJ)
1365 auto const j(line.find(">", i));
1366 digits += j - (i+1);
1367 i = line.find("<", j);
1369 // these have list-level numbers + one less ".", each 2 digits
1370 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(digits)>((((nLbl/2 + 1) * 2) - 1) * 2), digits);
1371 state = LblFoundText;
1372 ++nLblTJ;
1376 else if (state != Default && line == "EMC")
1378 CPPUNIT_ASSERT_EQUAL_MESSAGE("missing text", LblFoundText, state);
1379 state = Default;
1383 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1384 // ideally there should be 3 but apparently every text portion gets its own
1385 // tag - this should not be a problem if these are grouped in the structure
1386 // tree into 3 Lbl.
1387 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(6), nLbl);
1388 // these are quite arbitrary?
1389 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(2), nLblTJ);
1390 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nLbl)>(4), nLblTj);
1392 auto nL(0);
1393 for (const auto& rDocElement : aDocument.GetElements())
1395 auto pObject0 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
1396 if (!pObject0)
1397 continue;
1398 auto pType0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("Type"));
1399 if (!pType0 || pType0->GetValue() != "StructElem")
1401 continue;
1403 auto pS0 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject0->Lookup("S"));
1404 if (!pS0 || pS0->GetValue() != "Document")
1406 continue;
1408 auto pKids0 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject0->Lookup("K"));
1409 CPPUNIT_ASSERT(pKids0);
1411 for (const auto& pKid0 : pKids0->GetElements())
1413 auto pRefKid0 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKid0);
1414 CPPUNIT_ASSERT(pRefKid0);
1415 auto pObject1 = pRefKid0->LookupObject();
1416 CPPUNIT_ASSERT(pObject1);
1417 auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type"));
1418 CPPUNIT_ASSERT(pType1);
1420 if (pType1 && pType1->GetValue() == "StructElem")
1422 auto pS1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("S"));
1423 if (pS1 && pS1->GetValue() == "L")
1425 ++nL;
1426 auto pKids1 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1->Lookup("K"));
1427 CPPUNIT_ASSERT(pKids1);
1428 // this is purely structural so there should be 1 child
1429 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1->GetElements().size());
1431 auto pRefKid11 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1->GetElements()[0]);
1432 CPPUNIT_ASSERT(pRefKid11);
1433 auto pObject11 = pRefKid11->LookupObject();
1434 CPPUNIT_ASSERT(pObject11);
1435 auto pType11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("Type"));
1436 CPPUNIT_ASSERT(pType11);
1437 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType11->GetValue());
1438 auto pS11 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11->Lookup("S"));
1439 CPPUNIT_ASSERT(pS11);
1440 CPPUNIT_ASSERT_EQUAL(OString("LI"), pS11->GetValue());
1441 // LI has 2 children: Lbl and LBody
1442 auto pKids11 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11->Lookup("K"));
1443 CPPUNIT_ASSERT(pKids11);
1444 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11->GetElements().size());
1446 auto pRefKid111 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[0]);
1447 CPPUNIT_ASSERT(pRefKid111);
1448 auto pObject111 = pRefKid111->LookupObject();
1449 CPPUNIT_ASSERT(pObject111);
1450 auto pType111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("Type"));
1451 CPPUNIT_ASSERT(pType111);
1452 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType111->GetValue());
1453 auto pS111 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject111->Lookup("S"));
1454 CPPUNIT_ASSERT(pS111);
1455 CPPUNIT_ASSERT_EQUAL(OString("Lbl"), pS111->GetValue());
1456 // Lbl has 2 children: the first 2 mcids (in order)
1457 auto pKids111 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject111->Lookup("K"));
1458 CPPUNIT_ASSERT(pKids111);
1459 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids111->GetElements().size());
1461 auto pRefKid1111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[0]);
1462 CPPUNIT_ASSERT(pRefKid1111);
1463 CPPUNIT_ASSERT_EQUAL(mcids[0], int(pRefKid1111->GetValue()));
1464 auto pRefKid1112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids111->GetElements()[1]);
1465 CPPUNIT_ASSERT(pRefKid1112);
1466 CPPUNIT_ASSERT_EQUAL(mcids[1], int(pRefKid1112->GetValue()));
1468 auto pRefKid112 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11->GetElements()[1]);
1469 CPPUNIT_ASSERT(pRefKid112);
1470 auto pObject112 = pRefKid112->LookupObject();
1471 CPPUNIT_ASSERT(pObject112);
1472 auto pType112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("Type"));
1473 CPPUNIT_ASSERT(pType112);
1474 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType112->GetValue());
1475 auto pS112 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112->Lookup("S"));
1476 CPPUNIT_ASSERT(pS112);
1477 CPPUNIT_ASSERT_EQUAL(OString("LBody"), pS112->GetValue());
1478 // LBody has 2 children: paragraph and nested L (in order)
1479 auto pKids112 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112->Lookup("K"));
1480 CPPUNIT_ASSERT(pKids112);
1481 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112->GetElements().size());
1483 auto pRefKid1121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[0]);
1484 CPPUNIT_ASSERT(pRefKid1121);
1485 auto pObject1121 = pRefKid1121->LookupObject();
1486 CPPUNIT_ASSERT(pObject1121);
1487 auto pType1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("Type"));
1488 CPPUNIT_ASSERT(pType1121);
1489 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType1121->GetValue());
1490 auto pS1121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1121->Lookup("S"));
1491 CPPUNIT_ASSERT(pS1121);
1492 CPPUNIT_ASSERT_EQUAL(OString("Standard"), pS1121->GetValue());
1494 auto pRefKid1122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112->GetElements()[1]);
1495 CPPUNIT_ASSERT(pRefKid1122);
1496 auto pObject1122 = pRefKid1122->LookupObject();
1497 CPPUNIT_ASSERT(pObject1122);
1498 auto pType1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("Type"));
1499 CPPUNIT_ASSERT(pType1122);
1500 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType1122->GetValue());
1501 auto pS1122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122->Lookup("S"));
1502 CPPUNIT_ASSERT(pS1122);
1503 CPPUNIT_ASSERT_EQUAL(OString("L"), pS1122->GetValue());
1504 auto pKids1122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122->Lookup("K"));
1505 CPPUNIT_ASSERT(pKids1122);
1506 // this is purely structural so there should be 1 child
1507 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122->GetElements().size());
1509 auto pRefKid11221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122->GetElements()[0]);
1510 CPPUNIT_ASSERT(pRefKid11221);
1511 auto pObject11221 = pRefKid11221->LookupObject();
1512 CPPUNIT_ASSERT(pObject11221);
1513 auto pType11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("Type"));
1514 CPPUNIT_ASSERT(pType11221);
1515 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType11221->GetValue());
1516 auto pS11221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221->Lookup("S"));
1517 CPPUNIT_ASSERT(pS11221);
1518 CPPUNIT_ASSERT_EQUAL(OString("LI"), pS11221->GetValue());
1519 // LI has 2 children: Lbl and LBody
1520 auto pKids11221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221->Lookup("K"));
1521 CPPUNIT_ASSERT(pKids11221);
1522 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221->GetElements().size());
1524 auto pRefKid112211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[0]);
1525 CPPUNIT_ASSERT(pRefKid112211);
1526 auto pObject112211 = pRefKid112211->LookupObject();
1527 CPPUNIT_ASSERT(pObject112211);
1528 auto pType112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("Type"));
1529 CPPUNIT_ASSERT(pType112211);
1530 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType112211->GetValue());
1531 auto pS112211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112211->Lookup("S"));
1532 CPPUNIT_ASSERT(pS112211);
1533 CPPUNIT_ASSERT_EQUAL(OString("Lbl"), pS112211->GetValue());
1534 // Lbl has 2 children: the first 2 mcids (in order)
1535 auto pKids112211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112211->Lookup("K"));
1536 CPPUNIT_ASSERT(pKids112211);
1537 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112211->GetElements().size());
1539 auto pRefKid1122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[0]);
1540 CPPUNIT_ASSERT(pRefKid1122111);
1541 CPPUNIT_ASSERT_EQUAL(mcids[2], int(pRefKid1122111->GetValue()));
1542 auto pRefKid1122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112211->GetElements()[1]);
1543 CPPUNIT_ASSERT(pRefKid1122112);
1544 CPPUNIT_ASSERT_EQUAL(mcids[3], int(pRefKid1122112->GetValue()));
1546 auto pRefKid112212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221->GetElements()[1]);
1547 CPPUNIT_ASSERT(pRefKid112212);
1548 auto pObject112212 = pRefKid112212->LookupObject();
1549 CPPUNIT_ASSERT(pObject112212);
1550 auto pType112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("Type"));
1551 CPPUNIT_ASSERT(pType112212);
1552 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType112212->GetValue());
1553 auto pS112212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212->Lookup("S"));
1554 CPPUNIT_ASSERT(pS112212);
1555 CPPUNIT_ASSERT_EQUAL(OString("LBody"), pS112212->GetValue());
1556 // LBody has 2 children: paragraph and nested L (in order)
1557 auto pKids112212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212->Lookup("K"));
1558 CPPUNIT_ASSERT(pKids112212);
1559 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212->GetElements().size());
1561 auto pRefKid1122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[0]);
1562 CPPUNIT_ASSERT(pRefKid1122121);
1563 auto pObject1122121 = pRefKid1122121->LookupObject();
1564 CPPUNIT_ASSERT(pObject1122121);
1565 auto pType1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("Type"));
1566 CPPUNIT_ASSERT(pType1122121);
1567 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType1122121->GetValue());
1568 auto pS1122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122121->Lookup("S"));
1569 CPPUNIT_ASSERT(pS1122121);
1570 CPPUNIT_ASSERT_EQUAL(OString("Standard"), pS1122121->GetValue());
1572 auto pRefKid1122122 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212->GetElements()[1]);
1573 CPPUNIT_ASSERT(pRefKid1122122);
1574 auto pObject1122122 = pRefKid1122122->LookupObject();
1575 CPPUNIT_ASSERT(pObject1122122);
1576 auto pType1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("Type"));
1577 CPPUNIT_ASSERT(pType1122122);
1578 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType1122122->GetValue());
1579 auto pS1122122 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122->Lookup("S"));
1580 CPPUNIT_ASSERT(pS1122122);
1581 CPPUNIT_ASSERT_EQUAL(OString("L"), pS1122122->GetValue());
1582 auto pKids1122122 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject1122122->Lookup("K"));
1583 CPPUNIT_ASSERT(pKids1122122);
1584 // this is purely structural so there should be 1 child
1585 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids1122122->GetElements().size());
1587 auto pRefKid11221221 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids1122122->GetElements()[0]);
1588 CPPUNIT_ASSERT(pRefKid11221221);
1589 auto pObject11221221 = pRefKid11221221->LookupObject();
1590 CPPUNIT_ASSERT(pObject11221221);
1591 auto pType11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("Type"));
1592 CPPUNIT_ASSERT(pType11221221);
1593 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType11221221->GetValue());
1594 auto pS11221221 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject11221221->Lookup("S"));
1595 CPPUNIT_ASSERT(pS11221221);
1596 CPPUNIT_ASSERT_EQUAL(OString("LI"), pS11221221->GetValue());
1597 // LI has 2 children: Lbl and LBody
1598 auto pKids11221221 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject11221221->Lookup("K"));
1599 CPPUNIT_ASSERT(pKids11221221);
1600 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids11221221->GetElements().size());
1602 auto pRefKid112212211 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[0]);
1603 CPPUNIT_ASSERT(pRefKid112212211);
1604 auto pObject112212211 = pRefKid112212211->LookupObject();
1605 CPPUNIT_ASSERT(pObject112212211);
1606 auto pType112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("Type"));
1607 CPPUNIT_ASSERT(pType112212211);
1608 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType112212211->GetValue());
1609 auto pS112212211 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212211->Lookup("S"));
1610 CPPUNIT_ASSERT(pS112212211);
1611 CPPUNIT_ASSERT_EQUAL(OString("Lbl"), pS112212211->GetValue());
1612 // Lbl has 2 children: the first 2 mcids (in order)
1613 auto pKids112212211 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212211->Lookup("K"));
1614 CPPUNIT_ASSERT(pKids112212211);
1615 CPPUNIT_ASSERT_EQUAL(size_t(2), pKids112212211->GetElements().size());
1617 auto pRefKid1122122111 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[0]);
1618 CPPUNIT_ASSERT(pRefKid1122122111);
1619 CPPUNIT_ASSERT_EQUAL(mcids[4], int(pRefKid1122122111->GetValue()));
1620 auto pRefKid1122122112 = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids112212211->GetElements()[1]);
1621 CPPUNIT_ASSERT(pRefKid1122122112);
1622 CPPUNIT_ASSERT_EQUAL(mcids[5], int(pRefKid1122122112->GetValue()));
1624 auto pRefKid112212212 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids11221221->GetElements()[1]);
1625 CPPUNIT_ASSERT(pRefKid112212212);
1626 auto pObject112212212 = pRefKid112212212->LookupObject();
1627 CPPUNIT_ASSERT(pObject112212212);
1628 auto pType112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("Type"));
1629 CPPUNIT_ASSERT(pType112212212);
1630 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType112212212->GetValue());
1631 auto pS112212212 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject112212212->Lookup("S"));
1632 CPPUNIT_ASSERT(pS112212212);
1633 CPPUNIT_ASSERT_EQUAL(OString("LBody"), pS112212212->GetValue());
1634 // inner LBody has 1 children: paragraph
1635 auto pKids112212212 = dynamic_cast<vcl::filter::PDFArrayElement*>(pObject112212212->Lookup("K"));
1636 CPPUNIT_ASSERT(pKids112212212);
1637 CPPUNIT_ASSERT_EQUAL(size_t(1), pKids112212212->GetElements().size());
1639 auto pRefKid1122122121 = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids112212212->GetElements()[0]);
1640 CPPUNIT_ASSERT(pRefKid1122122121);
1641 auto pObject1122122121 = pRefKid1122122121->LookupObject();
1642 CPPUNIT_ASSERT(pObject1122122121);
1643 auto pType1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("Type"));
1644 CPPUNIT_ASSERT(pType1122122121);
1645 CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType1122122121->GetValue());
1646 auto pS1122122121 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1122122121->Lookup("S"));
1647 CPPUNIT_ASSERT(pS1122122121);
1648 CPPUNIT_ASSERT_EQUAL(OString("Standard"), pS1122122121->GetValue());
1653 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nL)>(1), nL);
1656 CPPUNIT_TEST_FIXTURE(Test, testTdf143311)
1658 createSwDoc("tdf143311-1.docx");
1659 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1661 // add another one that's a SdrObject
1662 uno::Reference<css::lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
1663 uno::Reference<drawing::XShape> xShape(
1664 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
1665 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
1666 xShapeProps->setPropertyValue("Decorative", uno::Any(true));
1667 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
1668 uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPageSupplier->getDrawPage());
1669 xDrawPage->add(xShape);
1671 // check DOCX filters
1672 saveAndReload("Office Open XML Text");
1673 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1674 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
1676 // tdf#153925 not imported - check default and set it to test ODF filters
1677 uno::Reference<beans::XPropertySet> const xStyle(getStyles("FrameStyles")->getByName("Formula"), uno::UNO_QUERY_THROW);
1678 CPPUNIT_ASSERT_EQUAL(false, getProperty<bool>(xStyle, "Decorative"));
1679 xStyle->setPropertyValue("Decorative", uno::Any(true));
1681 // check ODF filters
1682 saveAndReload("writer8");
1683 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(1), "Decorative"));
1684 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getShape(2), "Decorative"));
1685 CPPUNIT_ASSERT_EQUAL(true, getProperty<bool>(getStyles("FrameStyles")->getByName("Formula"), "Decorative"));
1687 // check PDF export
1688 utl::MediaDescriptor aMediaDescriptor;
1689 aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
1690 // Enable PDF/UA
1691 uno::Sequence<beans::PropertyValue> aFilterData(
1692 comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } }));
1693 aMediaDescriptor["FilterData"] <<= aFilterData;
1694 css::uno::Reference<frame::XStorable> xStorable(mxComponent, css::uno::UNO_QUERY_THROW);
1695 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
1697 vcl::filter::PDFDocument aDocument;
1698 SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
1699 CPPUNIT_ASSERT(aDocument.Read(aStream));
1701 // The document has one page.
1702 std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
1703 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size());
1705 vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents");
1706 CPPUNIT_ASSERT(pContents);
1707 vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
1708 CPPUNIT_ASSERT(pStream);
1709 SvMemoryStream& rObjectStream = pStream->GetMemory();
1710 // Uncompress it.
1711 SvMemoryStream aUncompressed;
1712 ZCodec aZCodec;
1713 aZCodec.BeginCompression();
1714 rObjectStream.Seek(0);
1715 aZCodec.Decompress(rObjectStream, aUncompressed);
1716 CPPUNIT_ASSERT(aZCodec.EndCompression());
1718 auto pStart = static_cast<const char*>(aUncompressed.GetData());
1719 const char* const pEnd = pStart + aUncompressed.GetSize();
1721 enum
1723 Default,
1724 Artifact,
1725 Tagged
1726 } state
1727 = Default;
1729 auto nLine(0);
1730 auto nTagged(0);
1731 auto nArtifacts(0);
1732 while (true)
1734 ++nLine;
1735 auto const pLine = ::std::find(pStart, pEnd, '\n');
1736 if (pLine == pEnd)
1738 break;
1740 std::string_view const line(pStart, pLine - pStart);
1741 pStart = pLine + 1;
1742 if (!line.empty() && line[0] != '%')
1744 ::std::cerr << nLine << ": " << line << "\n";
1745 if (line == "/Artifact BMC")
1747 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1748 state = Artifact;
1749 ++nArtifacts;
1751 else if (o3tl::starts_with(line, "/Standard<</MCID") && o3tl::ends_with(line, ">>BDC"))
1753 CPPUNIT_ASSERT_EQUAL_MESSAGE("unexpected nesting", Default, state);
1754 state = Tagged;
1755 ++nTagged;
1757 else if (line == "EMC")
1759 CPPUNIT_ASSERT_MESSAGE("unexpected end", state != Default);
1760 state = Default;
1762 else if (nLine > 1) // first line is expected "0.1 w"
1764 CPPUNIT_ASSERT_MESSAGE("unexpected content outside MCS", state != Default);
1768 CPPUNIT_ASSERT_EQUAL_MESSAGE("unclosed MCS", Default, state);
1769 CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nTagged)>(25), nTagged); // text in body
1770 // 1 decorative image + 1 decorative shape + 1 pre-existing rectangle border or something
1771 CPPUNIT_ASSERT(nArtifacts >= 3);
1774 void Test::testTextFormField()
1776 const OUString aFilterNames[] = {
1777 "writer8",
1778 "MS Word 97",
1779 "Office Open XML Text",
1782 for (const OUString& rFilterName : aFilterNames)
1784 createSwDoc("text_form_field.odt");
1786 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1788 // Export the document and import again for a check
1789 saveAndReload(rFilterName);
1791 // Check the document after round trip
1792 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1793 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1794 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1795 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1797 // We have two text form fields
1798 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1800 // Check whether all fieldmarks are text form fields
1801 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1803 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1804 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1805 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(ODF_FORMTEXT), pFieldmark->GetFieldname());
1808 // In the first paragraph we have an empty text form field with the placeholder spaces
1809 const uno::Reference< text::XTextRange > xPara = getParagraph(1);
1810 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara, 1), "TextPortionType"));
1811 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara, 2), "TextPortionType"));
1812 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara, 3), "TextPortionType"));
1813 sal_Unicode vEnSpaces[5] = {8194, 8194, 8194, 8194, 8194};
1814 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), getRun(xPara, 3)->getString());
1815 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara, 4), "TextPortionType"));
1817 // In the second paragraph we have a set text
1818 const uno::Reference< text::XTextRange > xPara2 = getParagraph(2);
1819 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldStart"), getProperty<OUString>(getRun(xPara2, 1), "TextPortionType"));
1820 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldSeparator"), getProperty<OUString>(getRun(xPara2, 2), "TextPortionType"));
1821 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("Text"), getProperty<OUString>(getRun(xPara2, 3), "TextPortionType"));
1822 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("xxxxx"), getRun(xPara2, 3)->getString());
1823 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("TextFieldEnd"), getProperty<OUString>(getRun(xPara2, 4), "TextPortionType"));
1827 void Test::testCheckBoxFormField()
1829 const OUString aFilterNames[] = {
1830 "writer8",
1831 "MS Word 97",
1832 "Office Open XML Text",
1835 for (const OUString& rFilterName : aFilterNames)
1837 createSwDoc("checkbox_form_field.odt");
1839 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1841 // Export the document and import again for a check
1842 saveAndReload(rFilterName);
1844 // Check the document after round trip
1845 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1846 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1847 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1848 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1850 // We have two check box form fields
1851 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1853 int nIndex = 0;
1854 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1856 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1858 if(rFilterName == "Office Open XML Text") // OOXML import also generates bookmarks
1860 if(!pFieldmark)
1861 continue;
1864 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1865 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(ODF_FORMCHECKBOX), pFieldmark->GetFieldname());
1866 ::sw::mark::ICheckboxFieldmark* pCheckBox = dynamic_cast< ::sw::mark::ICheckboxFieldmark* >(pFieldmark);
1867 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox);
1869 // The first one is unchecked, the other one is checked
1870 if(nIndex == 0)
1871 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !pCheckBox->IsChecked());
1872 else
1873 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pCheckBox->IsChecked());
1874 ++nIndex;
1876 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1880 void Test::testDropDownFormField()
1882 const OUString aFilterNames[] = {
1883 "writer8",
1884 "MS Word 97",
1885 "Office Open XML Text",
1888 for (const OUString& rFilterName : aFilterNames)
1890 createSwDoc("dropdown_form_field.odt");
1892 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1894 // Export the document and import again for a check
1895 saveAndReload(rFilterName);
1897 // Check the document after round trip
1898 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1899 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1900 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1901 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1903 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(2), pMarkAccess->getAllMarksCount());
1905 int nIndex = 0;
1906 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1908 ::sw::mark::IFieldmark* pFieldmark = dynamic_cast<::sw::mark::IFieldmark*>(*aIter);
1910 if(!pFieldmark)
1911 continue;
1913 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1914 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(ODF_FORMDROPDOWN), pFieldmark->GetFieldname());
1916 // Check drop down field's parameters.
1917 const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
1918 css::uno::Sequence<OUString> vListEntries;
1919 sal_Int32 nSelection = -1;
1920 auto pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
1921 if (pListEntries != pParameters->end())
1923 pListEntries->second >>= vListEntries;
1925 if(vListEntries.hasElements())
1927 auto pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
1928 if (pResult != pParameters->end())
1930 pResult->second >>= nSelection;
1935 // The first one is empty
1936 if(nIndex == 0)
1938 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), !vListEntries.hasElements());
1939 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(-1), nSelection);
1941 else // The second one has list and also a selected item
1943 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(4), vListEntries.getLength());
1944 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), nSelection);
1945 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("1000"), vListEntries[0]);
1946 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2000"), vListEntries[1]);
1947 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("3000"), vListEntries[2]);
1948 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("4000"), vListEntries[3]);
1950 ++nIndex;
1952 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(2), nIndex);
1956 void Test::testDateFormField()
1958 const OUString aFilterNames[] = {
1959 "writer8",
1960 "Office Open XML Text",
1963 for (const OUString& rFilterName : aFilterNames)
1965 createSwDoc("date_form_field.odt");
1967 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
1969 // Export the document and import again for a check
1970 saveAndReload(rFilterName);
1972 // Check the document after round trip
1973 if (rFilterName == "writer8")
1975 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
1976 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
1977 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
1978 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
1980 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pMarkAccess->getAllMarksCount());
1982 int nIndex = 0;
1983 for(auto aIter = pMarkAccess->getAllMarksBegin(); aIter != pMarkAccess->getAllMarksEnd(); ++aIter)
1985 ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*aIter);
1986 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
1987 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(ODF_FORMDATE), pFieldmark->GetFieldname());
1989 // Check date form field's parameters.
1990 const sw::mark::IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters();
1991 OUString sDateFormat;
1992 auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
1993 if (pResult != pParameters->end())
1995 pResult->second >>= sDateFormat;
1998 OUString sLang;
1999 pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
2000 if (pResult != pParameters->end())
2002 pResult->second >>= sLang;
2005 OUString sCurrentDate = pFieldmark->GetContent();
2007 // The first one has the default field content
2008 if(nIndex == 0)
2011 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2012 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2013 sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
2014 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
2016 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2017 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(5), pFieldmark->GetMarkStart().GetContentIndex());
2019 else if (nIndex == 1) // The second has the default format
2021 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2022 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2023 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
2025 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2026 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(20), pFieldmark->GetMarkStart().GetContentIndex());
2028 else if (nIndex == 2) // The third one has special format
2030 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
2031 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
2032 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
2034 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2035 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(40), pFieldmark->GetMarkStart().GetContentIndex());
2038 else if (nIndex == 3) // The fourth one has placeholder text
2040 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
2041 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
2042 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
2044 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2045 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(62), pFieldmark->GetMarkStart().GetContentIndex());
2048 else // The last one is really empty
2050 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2051 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2052 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
2054 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), SwNodeOffset(9), pFieldmark->GetMarkStart().GetNodeIndex());
2055 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(82), pFieldmark->GetMarkStart().GetContentIndex());
2058 ++nIndex;
2060 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2062 else
2064 // Import from DOCX, so the fieldmark is now a content control.
2065 uno::Reference<container::XEnumerationAccess> xEnumAccess(getParagraph(1), uno::UNO_QUERY);
2066 uno::Reference<container::XEnumeration> xTextPortions = xEnumAccess->createEnumeration();
2068 int nIndex = 0;
2069 while (xTextPortions->hasMoreElements())
2071 uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
2072 OUString aPortionType;
2073 xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
2074 if (aPortionType != "ContentControl")
2076 continue;
2079 uno::Reference<text::XTextContent> xContentControl;
2080 xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
2081 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2083 bool bDate{};
2084 xContentControlProps->getPropertyValue("Date") >>= bDate;
2085 CPPUNIT_ASSERT(bDate);
2087 // Check date form field's parameters.
2088 OUString sDateFormat;
2089 xContentControlProps->getPropertyValue("DateFormat") >>= sDateFormat;
2091 OUString sLang;
2092 xContentControlProps->getPropertyValue("DateLanguage") >>= sLang;
2094 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2095 uno::UNO_QUERY);
2096 uno::Reference<container::XEnumeration> xContentControlEnum
2097 = xContentControlEnumAccess->createEnumeration();
2098 uno::Reference<text::XTextRange> xContentControlTextPortion(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2099 OUString sCurrentDate = xContentControlTextPortion->getString();
2101 // The first one has the default field content
2102 if(nIndex == 0)
2104 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2105 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2106 sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
2107 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(vEnSpaces, 5), sCurrentDate);
2109 else if (nIndex == 1) // The second has the default format
2111 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2112 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2113 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("06/12/19"), sCurrentDate);
2115 else if (nIndex == 2) // The third one has special format
2117 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[NatNum12 MMMM=abbreviation]YYYY\". \"MMMM D."), sDateFormat);
2118 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("hu-HU"), sLang);
2119 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("2019. febr. 12."), sCurrentDate);
2121 else if (nIndex == 3) // The fourth one has placeholder text
2123 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("D, MMM YY"), sDateFormat);
2124 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("bm-ML"), sLang);
2125 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("[select date]"), sCurrentDate);
2127 else // The last one is really empty
2129 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("MM/DD/YY"), sDateFormat);
2130 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString("en-US"), sLang);
2131 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(""), sCurrentDate);
2133 ++nIndex;
2135 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), int(5), nIndex);
2140 void Test::testDateFormFieldCharacterFormatting()
2142 const OUString aFilterNames[] = {
2143 "writer8",
2144 "Office Open XML Text",
2147 for (const OUString& rFilterName : aFilterNames)
2149 createSwDoc("date_form_field_char_formatting.odt");
2151 const OString sFailedMessage = OString::Concat("Failed on filter: ") + rFilterName.toUtf8();
2153 // Export the document and import again for a check
2154 saveAndReload(rFilterName);
2156 // Check the document after round trip
2157 if (rFilterName == "writer8")
2159 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get());
2160 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pTextDoc);
2161 SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
2162 IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
2164 // Check that we have the field at the right place
2165 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(1), pMarkAccess->getAllMarksCount());
2166 ::sw::mark::IDateFieldmark* pFieldmark = dynamic_cast<::sw::mark::IDateFieldmark*>(*pMarkAccess->getAllMarksBegin());
2167 CPPUNIT_ASSERT_MESSAGE(sFailedMessage.getStr(), pFieldmark);
2168 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), OUString(ODF_FORMDATE), pFieldmark->GetFieldname());
2169 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(0), pFieldmark->GetMarkStart().GetContentIndex());
2170 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), sal_Int32(11), pFieldmark->GetMarkEnd().GetContentIndex());
2172 // We have one date field, first half of the field has bold character weight and second part has red character color
2173 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(getRun(getParagraph(1), 3), "CharWeight"));
2174 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(getRun(getParagraph(1), 3), "CharColor"));
2175 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(getRun(getParagraph(1), 4), "CharWeight"));
2176 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), Color(0xff0000), getProperty<Color>(getRun(getParagraph(1), 4), "CharColor"));
2178 else
2180 uno::Reference<beans::XPropertySet> xTextPortion(getRun(getParagraph(1), 1), uno::UNO_QUERY);
2181 OUString aPortionType;
2182 xTextPortion->getPropertyValue("TextPortionType") >>= aPortionType;
2183 CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aPortionType);
2185 uno::Reference<text::XTextContent> xContentControl;
2186 xTextPortion->getPropertyValue("ContentControl") >>= xContentControl;
2187 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
2188 bool bDate{};
2189 xContentControlProps->getPropertyValue("Date") >>= bDate;
2190 CPPUNIT_ASSERT(bDate);
2192 uno::Reference<container::XEnumerationAccess> xContentControlEnumAccess(xContentControl,
2193 uno::UNO_QUERY);
2194 uno::Reference<container::XEnumeration> xContentControlEnum
2195 = xContentControlEnumAccess->createEnumeration();
2196 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2197 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::BOLD, getProperty<float>(xTextPortion, "CharWeight"));
2198 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), COL_AUTO, getProperty<Color>(xTextPortion, "CharColor"));
2199 xTextPortion.set(xContentControlEnum->nextElement(), uno::UNO_QUERY);
2200 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), awt::FontWeight::NORMAL, getProperty<float>(xTextPortion, "CharWeight"));
2201 CPPUNIT_ASSERT_EQUAL_MESSAGE(sFailedMessage.getStr(), Color(0xff0000), getProperty<Color>(xTextPortion, "CharColor"));
2206 CPPUNIT_TEST_SUITE_REGISTRATION(Test);
2208 CPPUNIT_PLUGIN_IMPLEMENT();
2210 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */