1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <libepubgen/libepubgen.h>
12 #include <com/sun/star/document/XFilter.hpp>
13 #include <com/sun/star/frame/Desktop.hpp>
14 #include <com/sun/star/frame/XStorable.hpp>
15 #include <com/sun/star/lang/XServiceInfo.hpp>
16 #include <com/sun/star/packages/zip/ZipFileAccess.hpp>
18 #include <comphelper/processfactory.hxx>
19 #include <comphelper/propertysequence.hxx>
20 #include <comphelper/string.hxx>
21 #include <test/bootstrapfixture.hxx>
22 #include <test/xmltesttools.hxx>
23 #include <unotest/macros_test.hxx>
24 #include <unotools/docinfohelper.hxx>
25 #include <unotools/mediadescriptor.hxx>
26 #include <unotools/tempfile.hxx>
27 #include <unotools/ucbstreamhelper.hxx>
29 using namespace ::com::sun::star
;
33 char const DATA_DIRECTORY
[] = "/writerperfect/qa/unit/data/writer/epubexport/";
35 /// Tests the EPUB export filter.
36 class EPUBExportTest
: public test::BootstrapFixture
,
37 public unotest::MacrosTest
,
41 uno::Reference
<uno::XComponentContext
> mxComponentContext
;
42 uno::Reference
<lang::XComponent
> mxComponent
;
43 utl::TempFile maTempFile
;
44 xmlDocPtr mpXmlDoc
= nullptr;
45 uno::Reference
<packages::zip::XZipFileAccess2
> mxZipFile
;
46 OUString maFilterOptions
;
49 void setUp() override
;
50 void tearDown() override
;
51 void registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
) override
;
52 void createDoc(const OUString
& rFile
, const uno::Sequence
<beans::PropertyValue
>& rFilterData
);
53 /// Returns an XML representation of the stream named rName in the exported package.
54 xmlDocPtr
parseExport(const OUString
& rName
);
55 /// Parses a CSS representation of the stream named rName and returns it.
56 std::map
<OUString
, std::vector
<OUString
>> parseCss(const OUString
& rName
);
57 /// Looks up a key of a class in rCss.
58 static OUString
getCss(std::map
<OUString
, std::vector
<OUString
>>& rCss
, const OUString
& rClass
,
59 const OUString
& rKey
);
62 void EPUBExportTest::setUp()
64 test::BootstrapFixture::setUp();
66 mxComponentContext
.set(comphelper::getComponentContext(getMultiServiceFactory()));
67 mxDesktop
.set(frame::Desktop::create(mxComponentContext
));
70 void EPUBExportTest::tearDown()
73 mxComponent
->dispose();
81 test::BootstrapFixture::tearDown();
84 void EPUBExportTest::registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
)
86 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("dc"), BAD_CAST("http://purl.org/dc/elements/1.1/"));
87 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("opf"), BAD_CAST("http://www.idpf.org/2007/opf"));
88 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml"));
89 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("svg"), BAD_CAST("http://www.w3.org/2000/svg"));
92 void EPUBExportTest::createDoc(const OUString
& rFile
,
93 const uno::Sequence
<beans::PropertyValue
>& rFilterData
)
95 // Import the bugdoc and export as EPUB.
96 OUString aURL
= m_directories
.getURLFromSrc(DATA_DIRECTORY
) + rFile
;
97 mxComponent
= loadFromDesktop(aURL
);
98 CPPUNIT_ASSERT(mxComponent
.is());
100 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
101 maTempFile
.EnableKillingFile();
102 utl::MediaDescriptor aMediaDescriptor
;
103 aMediaDescriptor
["FilterName"] <<= OUString("EPUB");
104 if (maFilterOptions
.isEmpty())
105 aMediaDescriptor
["FilterData"] <<= rFilterData
;
107 aMediaDescriptor
["FilterOptions"] <<= maFilterOptions
;
108 xStorable
->storeToURL(maTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
110 = packages::zip::ZipFileAccess::createWithURL(mxComponentContext
, maTempFile
.GetURL());
113 xmlDocPtr
EPUBExportTest::parseExport(const OUString
& rName
)
115 uno::Reference
<io::XInputStream
> xInputStream(mxZipFile
->getByName(rName
), uno::UNO_QUERY
);
116 std::shared_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
117 return parseXmlStream(pStream
.get());
120 std::map
<OUString
, std::vector
<OUString
>> EPUBExportTest::parseCss(const OUString
& rName
)
122 std::map
<OUString
, std::vector
<OUString
>> aRet
;
124 uno::Reference
<io::XInputStream
> xInputStream(mxZipFile
->getByName(rName
), uno::UNO_QUERY
);
125 std::shared_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
127 // Minimal CSS handler till orcus is up to our needs.
130 while (!pStream
->eof())
132 pStream
->ReadLine(aLine
);
133 if (aLine
.endsWith("{"))
134 // '.name {' -> 'name'
135 aRuleName
= OUString::fromUtf8(aLine
.copy(1, aLine
.getLength() - 3));
136 else if (aLine
.endsWith(";"))
137 aRet
[aRuleName
].push_back(OUString::fromUtf8(aLine
));
143 OUString
EPUBExportTest::getCss(std::map
<OUString
, std::vector
<OUString
>>& rCss
,
144 const OUString
& rClass
, const OUString
& rKey
)
148 auto it
= rCss
.find(rClass
);
149 CPPUNIT_ASSERT(it
!= rCss
.end());
151 for (const auto& rKeyValue
: it
->second
)
153 OUString aKeyValue
= rKeyValue
.trim();
154 std::vector
<OUString
> aTokens
= comphelper::string::split(aKeyValue
, ':');
155 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTokens
.size());
156 if (aTokens
[0].trim() == rKey
)
158 aRet
= aTokens
[1].trim();
159 if (aRet
.endsWith(";"))
160 // Ignore trailing semicolon.
161 aRet
= aRet
.copy(0, aRet
.getLength() - 1);
169 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testOutlineLevel
)
171 createDoc("outline-level.fodt", {});
173 // Make sure that the output is split into two.
174 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
175 // This failed, output was a single section.
176 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
177 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
180 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMimetype
)
182 createDoc("hello.fodt", {});
184 // Check that the mime type is written uncompressed at the expected location.
185 SvFileStream
aFileStream(maTempFile
.GetURL(), StreamMode::READ
);
186 SvMemoryStream aMemoryStream
;
187 aMemoryStream
.WriteStream(aFileStream
);
188 OString
aExpected("application/epub+zip");
189 CPPUNIT_ASSERT(aMemoryStream
.GetSize() > static_cast<sal_uInt64
>(aExpected
.getLength()) + 38);
191 OString
aActual(static_cast<const char*>(aMemoryStream
.GetData()) + 38, aExpected
.getLength());
192 // This failed: actual data was some garbage, not the uncompressed mime type.
193 CPPUNIT_ASSERT_EQUAL(aExpected
, aActual
);
195 mpXmlDoc
= parseExport("OEBPS/content.opf");
197 assertXPath(mpXmlDoc
, "/opf:package", "version", "3.0");
199 // This was just "libepubgen/x.y.z", i.e. the LO version was missing.
201 = getXPath(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@name='generator']", "content");
202 CPPUNIT_ASSERT(aGenerator
.startsWith(utl::DocInfoHelper::GetGeneratorString()));
204 uno::Reference
<lang::XMultiServiceFactory
> xMSF(mxComponentContext
->getServiceManager(),
206 const OUString
aServiceName("com.sun.star.comp.Writer.EPUBExportFilter");
207 uno::Reference
<document::XFilter
> xFilter(xMSF
->createInstance(aServiceName
), uno::UNO_QUERY
);
208 // Should result in no errors.
210 // We got back what we expected.
211 uno::Reference
<lang::XServiceInfo
> xServiceInfo(xFilter
, uno::UNO_QUERY
);
212 CPPUNIT_ASSERT_EQUAL(aServiceName
, xServiceInfo
->getImplementationName());
213 CPPUNIT_ASSERT(xServiceInfo
->supportsService("com.sun.star.document.ExportFilter"));
216 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUB2
)
218 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
219 { // Explicitly request EPUB2.
220 { "EPUBVersion", uno::makeAny(static_cast<sal_Int32
>(20)) } }));
221 createDoc("hello.fodt", aFilterData
);
223 mpXmlDoc
= parseExport("OEBPS/content.opf");
224 // This was 3.0, EPUBVersion filter option was ignored and we always emitted EPUB3.
225 assertXPath(mpXmlDoc
, "/opf:package", "version", "2.0");
228 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayout
)
230 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
231 { // Explicitly request fixed layout.
232 { "EPUBLayoutMethod",
233 uno::makeAny(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
234 createDoc("hello.fodt", aFilterData
);
236 mpXmlDoc
= parseExport("OEBPS/content.opf");
237 // This was missing, EPUBLayoutMethod filter option was ignored and we always emitted reflowable layout.
238 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
242 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayoutOption
)
244 // Explicitly request fixed layout, this time via FilterOptions.
245 maFilterOptions
= "layout=fixed";
246 createDoc("hello.fodt", {});
248 // This failed, fixed layout was only working via the FilterData map.
249 mpXmlDoc
= parseExport("OEBPS/content.opf");
250 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
254 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayoutImplicitBreak
)
256 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
257 { // Explicitly request fixed layout.
258 { "EPUBLayoutMethod",
259 uno::makeAny(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
260 createDoc("fxl-2page.fodt", aFilterData
);
262 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
263 // This was missing, implicit page break (as calculated by the layout) was lost on export.
264 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
265 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
267 // Make sure that fixed layout has chapter names in the navigation
269 mpXmlDoc
= parseExport("OEBPS/toc.xhtml");
270 // This was 'Page 1' instead.
271 assertXPathContent(mpXmlDoc
, "//xhtml:li[1]/xhtml:a", "First chapter");
272 assertXPathContent(mpXmlDoc
, "//xhtml:li[2]/xhtml:a", "Second chapter");
275 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPageBreakSplit
)
277 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
278 { // Explicitly request split on page break (instead of on heading).
280 uno::makeAny(static_cast<sal_Int32
>(libepubgen::EPUB_SPLIT_METHOD_PAGE_BREAK
)) } }));
281 createDoc("2pages.fodt", aFilterData
);
283 // Make sure that the output is split into two.
284 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
285 // This failed, output was a single section.
286 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
287 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
290 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSpanAutostyle
)
292 createDoc("span-autostyle.fodt", {});
294 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
295 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[1]", "class", "span0");
296 // This failed, it was still span1, i.e. the bold and the italic formatting
298 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class", "span1");
299 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[3]", "class", "span2");
302 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaAutostyleCharProps
)
304 createDoc("para-autostyle-char-props.fodt", {});
306 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
307 // This failed, para-level char props were not exported.
308 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span", "class", "span0");
309 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "class", "span1");
312 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMeta
)
314 createDoc("meta.fodt", {});
316 mpXmlDoc
= parseExport("OEBPS/content.opf");
317 // This was "Unknown Author", <meta:initial-creator> was not handled.
318 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "A U Thor");
319 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "Title");
320 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "hu");
321 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
322 "2017-09-27T09:51:19Z");
324 // Make sure that cover image next to the source document is picked up.
325 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
326 "properties", "cover-image");
327 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
328 "media-type", "image/png");
329 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.png"));
332 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMetaXMP
)
334 createDoc("meta-xmp.fodt", {});
335 mpXmlDoc
= parseExport("OEBPS/content.opf");
337 // These were the libepubgen default values, metadata from a matching .xmp file was not picked up.
338 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:identifier",
339 "deadbeef-e394-4cd6-9b83-7172794612e5");
340 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "unknown title from xmp");
341 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "unknown author from xmp");
342 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "nl");
343 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
344 "2016-11-20T17:16:07Z");
347 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMetaAPI
)
349 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
350 { { "RVNGIdentifier", uno::makeAny(OUString("deadc0de-e394-4cd6-9b83-7172794612e5")) },
351 { "RVNGTitle", uno::makeAny(OUString("unknown title from api")) },
352 { "RVNGInitialCreator", uno::makeAny(OUString("unknown author from api")) },
353 { "RVNGLanguage", uno::makeAny(OUString("hu")) },
354 { "RVNGDate", uno::makeAny(OUString("2015-11-20T17:16:07Z")) } }));
355 createDoc("meta-xmp.fodt", aFilterData
);
356 mpXmlDoc
= parseExport("OEBPS/content.opf");
358 // These were values from XMP (deadbeef, etc.), not from API.
359 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:identifier",
360 "deadc0de-e394-4cd6-9b83-7172794612e5");
361 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "unknown title from api");
362 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "unknown author from api");
363 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "hu");
364 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
365 "2015-11-20T17:16:07Z");
368 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testCoverImage
)
370 OUString aCoverURL
= m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "meta.cover-image.png";
371 uno::Sequence
<beans::PropertyValue
> aFilterData(
372 comphelper::InitPropertySequence({ { "RVNGCoverImage", uno::makeAny(aCoverURL
) } }));
373 createDoc("hello.fodt", aFilterData
);
374 mpXmlDoc
= parseExport("OEBPS/content.opf");
376 // Make sure that the explicitly set cover image is used.
377 // This failed, as the image was not part of the package.
378 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
379 "properties", "cover-image");
380 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
381 "media-type", "image/png");
382 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.png"));
385 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaNamedstyle
)
387 createDoc("para-namedstyle.fodt", {});
389 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
390 assertXPath(mpXmlDoc
, "//xhtml:p[1]", "class", "para0");
391 // This failed, paragraph properties from style were not exported.
392 assertXPath(mpXmlDoc
, "//xhtml:p[2]", "class", "para1");
394 // Test character properties from named paragraph style.
395 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span", "class", "span0");
396 // This failed, character properties from paragraph style were not exported.
397 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "class", "span1");
400 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testCharNamedstyle
)
402 createDoc("char-namedstyle.fodt", {});
404 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
406 // Test character properties from named text style.
407 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[1]", "class", "span0");
408 // This failed, character properties from text style were not exported.
409 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class", "span1");
412 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testNamedStyleInheritance
)
414 createDoc("named-style-inheritance.fodt", {});
416 // Find the CSS rule for the blue text.
417 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
418 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
419 OUString aBlue
= getXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span[2]", "class");
421 CPPUNIT_ASSERT_EQUAL(OUString("#0000ff"), EPUBExportTest::getCss(aCssDoc
, aBlue
, "color"));
422 // This failed, the span only had the properties from its style, but not
423 // from the style's parent(s).
424 CPPUNIT_ASSERT_EQUAL(OUString("'Liberation Mono'"),
425 EPUBExportTest::getCss(aCssDoc
, aBlue
, "font-family"));
428 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testNestedSpan
)
430 createDoc("nested-span.fodt", {});
432 // Check textural content of nested span.
433 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
434 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
435 // This crashed, span had no content.
436 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "red");
438 // Check formatting of nested span.
439 OUString aRed
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class");
440 // This failed, direct formatting on top of named style was lost.
441 CPPUNIT_ASSERT_EQUAL(OUString("#ff0000"), EPUBExportTest::getCss(aCssDoc
, aRed
, "color"));
442 CPPUNIT_ASSERT_EQUAL(OUString("'Liberation Mono'"),
443 EPUBExportTest::getCss(aCssDoc
, aRed
, "font-family"));
446 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLineBreak
)
448 createDoc("line-break.fodt", {});
450 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
451 // This was 0, line break was not handled.
452 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span/xhtml:br", 1);
453 // This was 0, line break inside span was not handled.
454 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span/xhtml:br", 1);
457 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEscape
)
459 createDoc("escape.fodt", {});
461 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
463 assertXPathContent(mpXmlDoc
, "//xhtml:p[1]/xhtml:span[1]", OUString::fromUtf8("\xc2\xa0"));
464 // Make sure escaping happens only once.
465 assertXPathContent(mpXmlDoc
, "//xhtml:p[1]/xhtml:span[2]", "a&b");
466 // This was also lost.
468 mpXmlDoc
, "//xhtml:p[1]/xhtml:span[3]",
469 OUString::fromUtf8("\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2"
470 "\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 "));
473 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaCharProps
)
475 createDoc("para-char-props.fodt", {});
477 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
478 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
479 // Check formatting of the middle span.
480 OUString aMiddle
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class");
481 CPPUNIT_ASSERT_EQUAL(OUString("italic"),
482 EPUBExportTest::getCss(aCssDoc
, aMiddle
, "font-style"));
483 // Direct para formatting was lost, only direct char formatting was
484 // written, so this failed.
485 CPPUNIT_ASSERT_EQUAL(OUString("bold"), EPUBExportTest::getCss(aCssDoc
, aMiddle
, "font-weight"));
488 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSection
)
490 createDoc("section.fodt", {});
492 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
493 // This was "After.", i.e. in-section content was ignored.
494 assertXPathContent(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "In section.");
497 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testList
)
499 createDoc("list.fodt", {});
501 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
502 // This was "C", i.e. in-list content was ignored.
503 assertXPathContent(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "B");
504 // Test nested list content.
505 assertXPathContent(mpXmlDoc
, "//xhtml:p[6]/xhtml:span", "F");
508 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImage
)
510 createDoc("image.fodt", {});
512 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
513 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:img", 1);
516 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageBorder
)
518 createDoc("image-border.fodt", {});
520 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
521 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
523 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:img", "class");
524 // This failed, image had no border.
525 CPPUNIT_ASSERT_EQUAL(OUString("0.99pt dashed #ed1c24"),
526 EPUBExportTest::getCss(aCssDoc
, aClass
, "border"));
529 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageNospan
)
531 createDoc("image-nospan.fodt", {});
533 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
534 // Image outside a span was lost.
535 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:img", 1);
538 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTable
)
540 createDoc("table.fodt", {});
542 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
543 assertXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr/xhtml:td", 4);
546 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableRowSpan
)
548 createDoc("table-row-span.fodt", {});
550 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
551 // This failed, row span wasn't exported.
552 assertXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "rowspan", "2");
555 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableCellBorder
)
557 createDoc("table-cell-border.fodt", {});
559 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
560 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
563 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "class");
564 // This failed, cell border wasn't exported.
565 CPPUNIT_ASSERT_EQUAL(OUString("0.05pt solid #000000"),
566 EPUBExportTest::getCss(aCssDoc
, aClass
, "border-left"));
569 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableCellWidth
)
571 createDoc("table-cell-width.fodt", {});
573 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
574 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
576 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "class");
578 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[2]", "class");
580 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[3]", "class");
581 // These failed, all widths were 0.
582 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass2
, "width").toDouble(),
583 EPUBExportTest::getCss(aCssDoc
, aClass1
, "width").toDouble());
584 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass3
, "width").toDouble(),
585 EPUBExportTest::getCss(aCssDoc
, aClass1
, "width").toDouble());
588 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableRowHeight
)
590 createDoc("table-row-height.fodt", {});
592 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
593 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
594 OUString aClass1
= getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]", "class");
595 OUString aClass2
= getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[2]", "class");
596 // These failed, both heights were 0.
597 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass2
, "height").toDouble(),
598 EPUBExportTest::getCss(aCssDoc
, aClass1
, "height").toDouble());
601 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLink
)
603 createDoc("link.fodt", {});
605 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
606 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "https://libreoffice.org/");
607 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "https://libreoffice.org/");
610 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkInvalid
)
612 createDoc("link-invalid.odt", {});
614 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
615 // This was 1, invalid relative link was not filtered out.
616 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", 0);
619 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkCharFormat
)
621 createDoc("link-charformat.fodt", {});
623 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
624 // <span> was lost, link text having a char format was missing.
625 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "https://libreoffice.org/");
626 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "https://libreoffice.org/");
629 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkNamedCharFormat
)
631 // Character properties from named character style on hyperlink was lost.
632 createDoc("link-namedcharformat.fodt", {});
634 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
635 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
636 // This failed, there was no span inside the hyperlink.
637 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "http://libreoffice.org");
638 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "http://libreoffice.org/");
640 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "class");
641 CPPUNIT_ASSERT_EQUAL(OUString("#ff0000"), EPUBExportTest::getCss(aCssDoc
, aClass
, "color"));
644 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableWidth
)
646 createDoc("table-width.fodt", {});
648 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
649 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
651 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:table", "class");
652 // This failed, relative total width of table was lost.
653 CPPUNIT_ASSERT_EQUAL(OUString("50%"), EPUBExportTest::getCss(aCssDoc
, aClass
, "width"));
656 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTextBox
)
658 createDoc("text-box.fodt", {});
660 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
661 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
663 // This failed, image with caption was lost.
664 assertXPath(mpXmlDoc
, "//xhtml:img", "class", "frame1");
666 // 1) break after the image
667 // 2) "Illustration "
668 // 3) The sequence field, this was missing (was ": foo" instead).
669 assertXPathContent(mpXmlDoc
, "//xhtml:div/xhtml:p/xhtml:span[3]", "1");
671 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:div/xhtml:p/xhtml:span[3]", "class");
672 // This failed, the 3rd span was not italic.
673 CPPUNIT_ASSERT_EQUAL(OUString("italic"), EPUBExportTest::getCss(aCssDoc
, aClass
, "font-style"));
676 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testFontEmbedding
)
679 createDoc("font-embedding.fodt", {});
681 // Make sure that the params of defineEmbeddedFont() are all handled.
683 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
684 // 'SketchFlow Print' or ''SketchFlow Print1'
685 CPPUNIT_ASSERT(EPUBExportTest::getCss(aCssDoc
, "font-face", "font-family")
686 .startsWith("'SketchFlow Print"));
687 // librevenge:mime-type
688 mpXmlDoc
= parseExport("OEBPS/content.opf");
689 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='fonts/font0001.otf']",
690 "media-type", "application/vnd.ms-opentype");
691 // office:binary-data
692 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/fonts/font0001.otf"));
693 // librevenge:font-style
694 CPPUNIT_ASSERT_EQUAL(OUString("normal"),
695 EPUBExportTest::getCss(aCssDoc
, "font-face", "font-style"));
696 // librevenge:font-weight
697 CPPUNIT_ASSERT_EQUAL(OUString("normal"),
698 EPUBExportTest::getCss(aCssDoc
, "font-face", "font-weight"));
702 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageLink
)
704 createDoc("image-link.fodt", {});
706 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
707 // This failed, image was missing.
708 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:img", 1);
711 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testFootnote
)
713 createDoc("footnote.fodt", {});
715 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
716 // These were missing, footnote was lost.
717 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:sup/xhtml:a", "type", "noteref");
718 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside", "type", "footnote");
721 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopup
)
723 createDoc("popup.odt", {});
725 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
726 // Test image popup anchor.
727 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", "noteref");
728 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
729 // Test image popup content.
730 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]", "type", "footnote");
731 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]/xhtml:img", 1);
733 // Test text popup anchor.
734 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "type", "noteref");
735 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "link");
736 // Test text popup content.
737 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[2]", "type", "footnote");
738 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[2]/xhtml:img", 1);
741 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopupMedia
)
743 // This is the same as testPopup(), but the links point to images in the
744 // default media directory, not in the document directory.
745 createDoc("popup-media.odt", {});
747 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
748 // Test image popup anchor. This failed, number of XPath nodes was 0.
749 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", "noteref");
750 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
753 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopupAPI
)
755 // Make sure that the popup works with data from a media directory.
756 OUString aMediaDir
= m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "popup";
757 uno::Sequence
<beans::PropertyValue
> aFilterData(
758 comphelper::InitPropertySequence({ { "RVNGMediaDir", uno::makeAny(aMediaDir
) } }));
759 createDoc("popup-api.odt", aFilterData
);
761 // We have a non-empty anchor image.
762 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
763 OUString aAnchor
= getXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", "src");
764 CPPUNIT_ASSERT(!aAnchor
.isEmpty());
765 // We have a non-empty popup image.
766 OUString aData
= getXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]/xhtml:img", "src");
767 CPPUNIT_ASSERT(!aData
.isEmpty());
768 // The anchor is different from the popup image.
769 CPPUNIT_ASSERT(aAnchor
!= aData
);
772 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPageSize
)
774 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
775 { { "EPUBLayoutMethod",
776 uno::makeAny(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
777 createDoc("hello.fodt", aFilterData
);
779 // This failed, viewport was empty, so page size was lost.
780 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
781 // 21,59cm x 27.94cm (letter).
782 assertXPath(mpXmlDoc
, "/xhtml:html/xhtml:head/xhtml:meta[@name='viewport']", "content",
783 "width=816, height=1056");
785 xmlFreeDoc(mpXmlDoc
);
786 mpXmlDoc
= parseExport("OEBPS/images/image0001.svg");
787 // This was 288mm, logic->logic conversion input was a pixel value.
788 assertXPath(mpXmlDoc
, "/svg:svg", "width", "216mm");
789 assertXPath(mpXmlDoc
, "/svg:svg", "height", "279mm");
792 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSVG
)
794 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
795 { { "EPUBLayoutMethod",
796 uno::makeAny(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
797 createDoc("hello.fodt", aFilterData
);
799 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.svg"));
800 uno::Reference
<io::XInputStream
> xInputStream(
801 mxZipFile
->getByName("OEBPS/images/image0001.svg"), uno::UNO_QUERY
);
802 std::shared_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
804 SvMemoryStream aMemoryStream
;
805 aMemoryStream
.WriteStream(*pStream
);
806 OString
aExpected("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<svg");
807 CPPUNIT_ASSERT(aMemoryStream
.GetSize() > static_cast<sal_uInt64
>(aExpected
.getLength()));
809 // This failed, there was a '<!DOCTYPE' line between the xml and the svg
810 // one, causing a validation error.
811 OString
aActual(static_cast<const char*>(aMemoryStream
.GetData()), aExpected
.getLength());
812 CPPUNIT_ASSERT_EQUAL(aExpected
, aActual
);
814 // This failed, we used the xlink attribute namespace, but we did not
816 mpXmlDoc
= parseExport("OEBPS/images/image0001.svg");
817 assertXPathNSDef(mpXmlDoc
, "/svg:svg", "xlink", "http://www.w3.org/1999/xlink");
820 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623SingleWritingMode
)
822 // Simple page that has single writing mode should work.
823 createDoc("tdf115623-single-writing-mode.odt", {});
824 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
825 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
826 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
827 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
828 EPUBExportTest::getCss(aCssDoc
, aClass
, "writing-mode"));
831 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623SplitByChapter
)
833 createDoc("tdf115623-split-by-chapter.odt", {});
834 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
836 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
837 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
838 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
839 EPUBExportTest::getCss(aCssDoc
, aClass
, "writing-mode"));
840 xmlFreeDoc(mpXmlDoc
);
843 // Split HTML should keep the same writing-mode.
845 mpXmlDoc
= parseExport("OEBPS/sections/section0002.xhtml");
846 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
847 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
848 EPUBExportTest::getCss(aCssDoc
, aClass
, "writing-mode"));
852 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623ManyPageSpans
)
854 createDoc("tdf115623-many-pagespans.odt", {});
855 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
856 // Two pages should have different writing modes.
858 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
859 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
860 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
861 EPUBExportTest::getCss(aCssDoc
, aClass
, "writing-mode"));
862 xmlFreeDoc(mpXmlDoc
);
866 mpXmlDoc
= parseExport("OEBPS/sections/section0002.xhtml");
867 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
868 CPPUNIT_ASSERT_EQUAL(OUString("horizontal-tb"),
869 EPUBExportTest::getCss(aCssDoc
, aClass
, "writing-mode"));
873 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSimpleRuby
)
875 createDoc("simple-ruby.odt", {});
876 mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
877 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:span", "base text");
878 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:rt", "ruby text");
881 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testAbi11105
)
883 // Disable the test on this branch, .abw is flagged as EXOTIC, so loading would fail.
885 // This crashed because the paragraph style "P5" which had a master-page-name
886 // appeared in a table cell messed up page spans.
887 createDoc("abi11105.abw", {});
892 CPPUNIT_PLUGIN_IMPLEMENT();
894 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */