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 <sal/config.h>
12 #include <test/unoapixml_test.hxx>
14 #include <string_view>
16 #include <libepubgen/libepubgen.h>
18 #include <com/sun/star/document/XFilter.hpp>
19 #include <com/sun/star/frame/XStorable.hpp>
20 #include <com/sun/star/lang/XServiceInfo.hpp>
21 #include <com/sun/star/packages/zip/ZipFileAccess.hpp>
23 #include <comphelper/propertysequence.hxx>
24 #include <comphelper/string.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <unotools/docinfohelper.hxx>
27 #include <unotools/mediadescriptor.hxx>
28 #include <unotools/tempfile.hxx>
29 #include <unotools/ucbstreamhelper.hxx>
30 #include <o3tl/string_view.hxx>
32 using namespace ::com::sun::star
;
36 /// Tests the EPUB export filter.
37 class EPUBExportTest
: public UnoApiXmlTest
40 uno::Reference
<packages::zip::XZipFileAccess2
> mxZipFile
;
41 OUString maFilterOptions
;
45 : UnoApiXmlTest("/writerperfect/qa/unit/data/writer/epubexport/")
49 void registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
) override
;
50 void createDoc(std::u16string_view rFile
,
51 const uno::Sequence
<beans::PropertyValue
>& rFilterData
);
52 /// Parses a CSS representation of the stream named rName and returns it.
53 std::map
<OUString
, std::vector
<OUString
>> parseCss(const OUString
& rName
);
54 /// Looks up a key of a class in rCss.
55 static OUString
getCss(std::map
<OUString
, std::vector
<OUString
>>& rCss
, const OUString
& rClass
,
56 std::u16string_view rKey
);
59 void EPUBExportTest::registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
)
61 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("dc"), BAD_CAST("http://purl.org/dc/elements/1.1/"));
62 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("opf"), BAD_CAST("http://www.idpf.org/2007/opf"));
63 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml"));
64 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("svg"), BAD_CAST("http://www.w3.org/2000/svg"));
67 void EPUBExportTest::createDoc(std::u16string_view rFile
,
68 const uno::Sequence
<beans::PropertyValue
>& rFilterData
)
70 // Import the bugdoc and export as EPUB.
72 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
73 utl::MediaDescriptor aMediaDescriptor
;
74 aMediaDescriptor
["FilterName"] <<= OUString("EPUB");
75 if (maFilterOptions
.isEmpty())
76 aMediaDescriptor
["FilterData"] <<= rFilterData
;
78 aMediaDescriptor
["FilterOptions"] <<= maFilterOptions
;
79 xStorable
->storeToURL(maTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
81 = packages::zip::ZipFileAccess::createWithURL(mxComponentContext
, maTempFile
.GetURL());
84 std::map
<OUString
, std::vector
<OUString
>> EPUBExportTest::parseCss(const OUString
& rName
)
86 std::map
<OUString
, std::vector
<OUString
>> aRet
;
88 uno::Reference
<io::XInputStream
> xInputStream(mxZipFile
->getByName(rName
), uno::UNO_QUERY
);
89 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
91 // Minimal CSS handler till orcus is up to our needs.
94 while (!pStream
->eof())
96 pStream
->ReadLine(aLine
);
97 if (aLine
.endsWith("{"))
98 // '.name {' -> 'name'
99 aRuleName
= OUString::fromUtf8(aLine
.subView(1, aLine
.getLength() - 3));
100 else if (aLine
.endsWith(";"))
101 aRet
[aRuleName
].push_back(OUString::fromUtf8(aLine
));
107 OUString
EPUBExportTest::getCss(std::map
<OUString
, std::vector
<OUString
>>& rCss
,
108 const OUString
& rClass
, std::u16string_view rKey
)
112 auto it
= rCss
.find(rClass
);
113 CPPUNIT_ASSERT(it
!= rCss
.end());
115 for (const auto& rKeyValue
: it
->second
)
117 OUString aKeyValue
= rKeyValue
.trim();
118 std::vector
<OUString
> aTokens
= comphelper::string::split(aKeyValue
, ':');
119 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), aTokens
.size());
120 if (o3tl::trim(aTokens
[0]) == rKey
)
122 aRet
= aTokens
[1].trim();
123 if (aRet
.endsWith(";"))
124 // Ignore trailing semicolon.
125 aRet
= aRet
.copy(0, aRet
.getLength() - 1);
133 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testOutlineLevel
)
135 createDoc(u
"outline-level.fodt", {});
137 // Make sure that the output is split into two.
138 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
139 // This failed, output was a single section.
140 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
141 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
144 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMimetype
)
146 createDoc(u
"hello.fodt", {});
148 // Check that the mime type is written uncompressed at the expected location.
149 SvFileStream
aFileStream(maTempFile
.GetURL(), StreamMode::READ
);
150 SvMemoryStream aMemoryStream
;
151 aMemoryStream
.WriteStream(aFileStream
);
152 OString
aExpected("application/epub+zip");
153 CPPUNIT_ASSERT(aMemoryStream
.GetSize() > static_cast<sal_uInt64
>(aExpected
.getLength()) + 38);
155 OString
aActual(static_cast<const char*>(aMemoryStream
.GetData()) + 38, aExpected
.getLength());
156 // This failed: actual data was some garbage, not the uncompressed mime type.
157 CPPUNIT_ASSERT_EQUAL(aExpected
, aActual
);
159 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
161 assertXPath(mpXmlDoc
, "/opf:package", "version", "3.0");
163 // This was just "libepubgen/x.y.z", i.e. the LO version was missing.
165 = getXPath(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@name='generator']", "content");
166 CPPUNIT_ASSERT(aGenerator
.startsWith(utl::DocInfoHelper::GetGeneratorString()));
168 uno::Reference
<lang::XMultiServiceFactory
> xMSF(mxComponentContext
->getServiceManager(),
170 const OUString
aServiceName("com.sun.star.comp.Writer.EPUBExportFilter");
171 uno::Reference
<document::XFilter
> xFilter(xMSF
->createInstance(aServiceName
), uno::UNO_QUERY
);
172 // Should result in no errors.
174 // We got back what we expected.
175 uno::Reference
<lang::XServiceInfo
> xServiceInfo(xFilter
, uno::UNO_QUERY
);
176 CPPUNIT_ASSERT_EQUAL(aServiceName
, xServiceInfo
->getImplementationName());
177 CPPUNIT_ASSERT(xServiceInfo
->supportsService("com.sun.star.document.ExportFilter"));
180 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUB2
)
182 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
183 { // Explicitly request EPUB2.
184 { "EPUBVersion", uno::Any(static_cast<sal_Int32
>(20)) } }));
185 createDoc(u
"hello.fodt", aFilterData
);
187 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
188 // This was 3.0, EPUBVersion filter option was ignored and we always emitted EPUB3.
189 assertXPath(mpXmlDoc
, "/opf:package", "version", "2.0");
192 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayout
)
194 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
195 { // Explicitly request fixed layout.
196 { "EPUBLayoutMethod",
197 uno::Any(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
198 createDoc(u
"hello.fodt", aFilterData
);
200 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
201 // This was missing, EPUBLayoutMethod filter option was ignored and we always emitted reflowable layout.
202 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
206 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayoutOption
)
208 // Explicitly request fixed layout, this time via FilterOptions.
209 maFilterOptions
= "layout=fixed";
210 createDoc(u
"hello.fodt", {});
212 // This failed, fixed layout was only working via the FilterData map.
213 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
214 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
218 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEPUBFixedLayoutImplicitBreak
)
220 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
221 { // Explicitly request fixed layout.
222 { "EPUBLayoutMethod",
223 uno::Any(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
224 createDoc(u
"fxl-2page.fodt", aFilterData
);
226 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
227 // This was missing, implicit page break (as calculated by the layout) was lost on export.
228 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
229 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
231 // Make sure that fixed layout has chapter names in the navigation
233 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/toc.xhtml");
234 // This was 'Page 1' instead.
235 assertXPathContent(mpXmlDoc
, "//xhtml:li[1]/xhtml:a", "First chapter");
236 assertXPathContent(mpXmlDoc
, "//xhtml:li[2]/xhtml:a", "Second chapter");
239 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPageBreakSplit
)
241 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
242 { // Explicitly request split on page break (instead of on heading).
244 uno::Any(static_cast<sal_Int32
>(libepubgen::EPUB_SPLIT_METHOD_PAGE_BREAK
)) } }));
245 createDoc(u
"2pages.fodt", aFilterData
);
247 // Make sure that the output is split into two.
248 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0001.xhtml"));
249 // This failed, output was a single section.
250 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/sections/section0002.xhtml"));
251 CPPUNIT_ASSERT(!mxZipFile
->hasByName("OEBPS/sections/section0003.xhtml"));
254 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSpanAutostyle
)
256 createDoc(u
"span-autostyle.fodt", {});
258 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
259 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[1]", "class", "span0");
260 // This failed, it was still span1, i.e. the bold and the italic formatting
262 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class", "span1");
263 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[3]", "class", "span2");
266 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaAutostyleCharProps
)
268 createDoc(u
"para-autostyle-char-props.fodt", {});
270 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
271 // This failed, para-level char props were not exported.
272 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span", "class", "span0");
273 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "class", "span1");
276 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMeta
)
278 createDoc(u
"meta.fodt", {});
280 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
281 // This was "Unknown Author", <meta:initial-creator> was not handled.
282 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "A U Thor");
283 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "Title");
284 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "hu");
285 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
286 "2017-09-27T09:51:19Z");
288 // Make sure that cover image next to the source document is picked up.
289 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
290 "properties", "cover-image");
291 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
292 "media-type", "image/png");
293 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.png"));
296 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMetaXMP
)
298 createDoc(u
"meta-xmp.fodt", {});
299 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
301 // These were the libepubgen default values, metadata from a matching .xmp file was not picked up.
302 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:identifier",
303 "deadbeef-e394-4cd6-9b83-7172794612e5");
304 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "unknown title from xmp");
305 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "unknown author from xmp");
306 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "nl");
307 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
308 "2016-11-20T17:16:07Z");
311 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testMetaAPI
)
313 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
314 { { "RVNGIdentifier", uno::Any(OUString("deadc0de-e394-4cd6-9b83-7172794612e5")) },
315 { "RVNGTitle", uno::Any(OUString("unknown title from api")) },
316 { "RVNGInitialCreator", uno::Any(OUString("unknown author from api")) },
317 { "RVNGLanguage", uno::Any(OUString("hu")) },
318 { "RVNGDate", uno::Any(OUString("2015-11-20T17:16:07Z")) } }));
319 createDoc(u
"meta-xmp.fodt", aFilterData
);
320 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
322 // These were values from XMP (deadbeef, etc.), not from API.
323 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:identifier",
324 "deadc0de-e394-4cd6-9b83-7172794612e5");
325 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:title", "unknown title from api");
326 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:creator", "unknown author from api");
327 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/dc:language", "hu");
328 assertXPathContent(mpXmlDoc
, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
329 "2015-11-20T17:16:07Z");
332 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testCoverImage
)
334 OUString aCoverURL
= createFileURL(u
"meta.cover-image.png");
335 uno::Sequence
<beans::PropertyValue
> aFilterData(
336 comphelper::InitPropertySequence({ { "RVNGCoverImage", uno::Any(aCoverURL
) } }));
337 createDoc(u
"hello.fodt", aFilterData
);
338 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
340 // Make sure that the explicitly set cover image is used.
341 // This failed, as the image was not part of the package.
342 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
343 "properties", "cover-image");
344 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']",
345 "media-type", "image/png");
346 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.png"));
349 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaNamedstyle
)
351 createDoc(u
"para-namedstyle.fodt", {});
353 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
354 assertXPath(mpXmlDoc
, "//xhtml:p[1]", "class", "para0");
355 // This failed, paragraph properties from style were not exported.
356 assertXPath(mpXmlDoc
, "//xhtml:p[2]", "class", "para1");
358 // Test character properties from named paragraph style.
359 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span", "class", "span0");
360 // This failed, character properties from paragraph style were not exported.
361 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "class", "span1");
364 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testCharNamedstyle
)
366 createDoc(u
"char-namedstyle.fodt", {});
368 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
370 // Test character properties from named text style.
371 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[1]", "class", "span0");
372 // This failed, character properties from text style were not exported.
373 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class", "span1");
376 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testNamedStyleInheritance
)
378 createDoc(u
"named-style-inheritance.fodt", {});
380 // Find the CSS rule for the blue text.
381 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
382 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
383 OUString aBlue
= getXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span[2]", "class");
385 CPPUNIT_ASSERT_EQUAL(OUString("#0000ff"), EPUBExportTest::getCss(aCssDoc
, aBlue
, u
"color"));
386 // This failed, the span only had the properties from its style, but not
387 // from the style's parent(s).
388 CPPUNIT_ASSERT_EQUAL(OUString("'Liberation Mono'"),
389 EPUBExportTest::getCss(aCssDoc
, aBlue
, u
"font-family"));
392 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testNestedSpan
)
394 createDoc(u
"nested-span.fodt", {});
396 // Check textural content of nested span.
397 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
398 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
399 // This crashed, span had no content.
400 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "red");
402 // Check formatting of nested span.
403 OUString aRed
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class");
404 // This failed, direct formatting on top of named style was lost.
405 CPPUNIT_ASSERT_EQUAL(OUString("#ff0000"), EPUBExportTest::getCss(aCssDoc
, aRed
, u
"color"));
406 CPPUNIT_ASSERT_EQUAL(OUString("'Liberation Mono'"),
407 EPUBExportTest::getCss(aCssDoc
, aRed
, u
"font-family"));
410 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLineBreak
)
412 createDoc(u
"line-break.fodt", {});
414 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
415 // This was 0, line break was not handled.
416 assertXPath(mpXmlDoc
, "//xhtml:p[1]/xhtml:span/xhtml:br", 1);
417 // This was 0, line break inside span was not handled.
418 assertXPath(mpXmlDoc
, "//xhtml:p[2]/xhtml:span/xhtml:br", 1);
421 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testEscape
)
423 createDoc(u
"escape.fodt", {});
425 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
427 assertXPathContent(mpXmlDoc
, "//xhtml:p[1]/xhtml:span[1]", OUString::fromUtf8("\xc2\xa0"));
428 // Make sure escaping happens only once.
429 assertXPathContent(mpXmlDoc
, "//xhtml:p[1]/xhtml:span[2]", "a&b");
430 // This was also lost.
432 mpXmlDoc
, "//xhtml:p[1]/xhtml:span[3]",
433 OUString::fromUtf8("\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2"
434 "\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 "));
437 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testParaCharProps
)
439 createDoc(u
"para-char-props.fodt", {});
441 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
442 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
443 // Check formatting of the middle span.
444 OUString aMiddle
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:span[2]", "class");
445 CPPUNIT_ASSERT_EQUAL(OUString("italic"),
446 EPUBExportTest::getCss(aCssDoc
, aMiddle
, u
"font-style"));
447 // Direct para formatting was lost, only direct char formatting was
448 // written, so this failed.
449 CPPUNIT_ASSERT_EQUAL(OUString("bold"),
450 EPUBExportTest::getCss(aCssDoc
, aMiddle
, u
"font-weight"));
453 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSection
)
455 createDoc(u
"section.fodt", {});
457 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
458 // This was "After.", i.e. in-section content was ignored.
459 assertXPathContent(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "In section.");
462 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testList
)
464 createDoc(u
"list.fodt", {});
466 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
467 // This was "C", i.e. in-list content was ignored.
468 assertXPathContent(mpXmlDoc
, "//xhtml:p[2]/xhtml:span", "B");
469 // Test nested list content.
470 assertXPathContent(mpXmlDoc
, "//xhtml:p[6]/xhtml:span", "F");
473 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImage
)
475 createDoc(u
"image.fodt", {});
477 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
478 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:img", 1);
481 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageBorder
)
483 createDoc(u
"image-border.fodt", {});
485 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
486 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
488 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:img", "class");
489 // This failed, image had no border.
490 CPPUNIT_ASSERT_EQUAL(OUString("0.99pt dashed #ed1c24"),
491 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"border"));
494 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageNospan
)
496 createDoc(u
"image-nospan.fodt", {});
498 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
499 // Image outside a span was lost.
500 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:img", 1);
503 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTable
)
505 createDoc(u
"table.fodt", {});
507 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
508 assertXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr/xhtml:td", 4);
511 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableRowSpan
)
513 createDoc(u
"table-row-span.fodt", {});
515 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
516 // This failed, row span wasn't exported.
517 assertXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "rowspan", "2");
520 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableCellBorder
)
522 createDoc(u
"table-cell-border.fodt", {});
524 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
525 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
528 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "class");
529 // This failed, cell border wasn't exported.
530 CPPUNIT_ASSERT_EQUAL(OUString("0.05pt solid #000000"),
531 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"border-left"));
534 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableCellWidth
)
536 createDoc(u
"table-cell-width.fodt", {});
538 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
539 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
541 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "class");
543 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[2]", "class");
545 = getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[3]", "class");
546 // These failed, all widths were 0.
547 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass2
, u
"width").toDouble(),
548 EPUBExportTest::getCss(aCssDoc
, aClass1
, u
"width").toDouble());
549 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass3
, u
"width").toDouble(),
550 EPUBExportTest::getCss(aCssDoc
, aClass1
, u
"width").toDouble());
553 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableRowHeight
)
555 createDoc(u
"table-row-height.fodt", {});
557 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
558 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
559 OUString aClass1
= getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[1]", "class");
560 OUString aClass2
= getXPath(mpXmlDoc
, "//xhtml:table/xhtml:tbody/xhtml:tr[2]", "class");
561 // These failed, both heights were 0.
562 CPPUNIT_ASSERT_GREATER(EPUBExportTest::getCss(aCssDoc
, aClass2
, u
"height").toDouble(),
563 EPUBExportTest::getCss(aCssDoc
, aClass1
, u
"height").toDouble());
566 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLink
)
568 createDoc(u
"link.fodt", {});
570 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
571 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "https://libreoffice.org/");
572 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "https://libreoffice.org/");
575 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkInvalid
)
577 createDoc(u
"link-invalid.odt", {});
579 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
580 // This was 1, invalid relative link was not filtered out.
581 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", 0);
584 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkCharFormat
)
586 createDoc(u
"link-charformat.fodt", {});
588 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
589 // <span> was lost, link text having a char format was missing.
590 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "https://libreoffice.org/");
591 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "https://libreoffice.org/");
594 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testLinkNamedCharFormat
)
596 // Character properties from named character style on hyperlink was lost.
597 createDoc(u
"link-namedcharformat.fodt", {});
599 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
600 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
601 // This failed, there was no span inside the hyperlink.
602 assertXPathContent(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "http://libreoffice.org");
603 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a", "href", "http://libreoffice.org/");
605 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:span", "class");
606 CPPUNIT_ASSERT_EQUAL(OUString("#ff0000"), EPUBExportTest::getCss(aCssDoc
, aClass
, u
"color"));
609 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTableWidth
)
611 createDoc(u
"table-width.fodt", {});
613 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
614 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
616 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:table", "class");
617 // This failed, relative total width of table was lost.
618 CPPUNIT_ASSERT_EQUAL(OUString("50%"), EPUBExportTest::getCss(aCssDoc
, aClass
, u
"width"));
621 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTextBox
)
623 createDoc(u
"text-box.fodt", {});
625 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
626 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
628 // This failed, image with caption was lost.
629 assertXPath(mpXmlDoc
, "//xhtml:img", "class", "frame1");
631 // 1) break after the image
632 // 2) "Illustration "
633 // 3) The sequence field, this was missing (was ": foo" instead).
634 assertXPathContent(mpXmlDoc
, "//xhtml:div/xhtml:p/xhtml:span[3]", "1");
636 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:div/xhtml:p/xhtml:span[3]", "class");
637 // This failed, the 3rd span was not italic.
638 CPPUNIT_ASSERT_EQUAL(OUString("italic"),
639 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"font-style"));
642 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testFontEmbedding
)
645 createDoc(u
"font-embedding.fodt", {});
647 // Make sure that the params of defineEmbeddedFont() are all handled.
649 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
650 // 'SketchFlow Print' or ''SketchFlow Print1'
651 CPPUNIT_ASSERT(EPUBExportTest::getCss(aCssDoc
, "font-face", u
"font-family")
652 .startsWith("'SketchFlow Print"));
653 // librevenge:mime-type
654 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/content.opf");
655 assertXPath(mpXmlDoc
, "/opf:package/opf:manifest/opf:item[@href='fonts/font0001.otf']",
656 "media-type", "application/vnd.ms-opentype");
657 // office:binary-data
658 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/fonts/font0001.otf"));
659 // librevenge:font-style
660 CPPUNIT_ASSERT_EQUAL(OUString("normal"),
661 EPUBExportTest::getCss(aCssDoc
, "font-face", u
"font-style"));
662 // librevenge:font-weight
663 CPPUNIT_ASSERT_EQUAL(OUString("normal"),
664 EPUBExportTest::getCss(aCssDoc
, "font-face", u
"font-weight"));
668 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testImageLink
)
670 createDoc(u
"image-link.fodt", {});
672 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
673 // This failed, image was missing.
674 assertXPath(mpXmlDoc
, "//xhtml:p/xhtml:a/xhtml:img", 1);
677 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testFootnote
)
679 createDoc(u
"footnote.fodt", {});
681 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
682 // These were missing, footnote was lost.
683 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:sup/xhtml:a", "type", "noteref");
684 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside", "type", "footnote");
687 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopup
)
689 createDoc(u
"popup.odt", {});
691 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
692 // Test image popup anchor.
693 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", "noteref");
694 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
695 // Test image popup content.
696 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]", "type", "footnote");
697 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]/xhtml:img", 1);
699 // Test text popup anchor.
700 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "type", "noteref");
701 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "link");
702 // Test text popup content.
703 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[2]", "type", "footnote");
704 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[2]/xhtml:img", 1);
707 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopupMedia
)
709 // This is the same as testPopup(), but the links point to images in the
710 // default media directory, not in the document directory.
711 createDoc(u
"popup-media.odt", {});
713 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
714 // Test image popup anchor. This failed, number of XPath nodes was 0.
715 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", "noteref");
716 assertXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
719 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPopupAPI
)
721 // Make sure that the popup works with data from a media directory.
722 OUString aMediaDir
= createFileURL(u
"popup");
723 uno::Sequence
<beans::PropertyValue
> aFilterData(
724 comphelper::InitPropertySequence({ { "RVNGMediaDir", uno::Any(aMediaDir
) } }));
725 createDoc(u
"popup-api.odt", aFilterData
);
727 // We have a non-empty anchor image.
728 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
729 OUString aAnchor
= getXPath(mpXmlDoc
, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", "src");
730 CPPUNIT_ASSERT(!aAnchor
.isEmpty());
731 // We have a non-empty popup image.
732 OUString aData
= getXPath(mpXmlDoc
, "//xhtml:body/xhtml:aside[1]/xhtml:img", "src");
733 CPPUNIT_ASSERT(!aData
.isEmpty());
734 // The anchor is different from the popup image.
735 CPPUNIT_ASSERT(aAnchor
!= aData
);
738 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testPageSize
)
740 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
741 { { "EPUBLayoutMethod",
742 uno::Any(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
743 createDoc(u
"hello.fodt", aFilterData
);
745 // This failed, viewport was empty, so page size was lost.
746 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
747 // 21,59cm x 27.94cm (letter).
748 assertXPath(mpXmlDoc
, "/xhtml:html/xhtml:head/xhtml:meta[@name='viewport']", "content",
749 "width=816, height=1056");
751 mpXmlDoc
= parseExport("OEBPS/images/image0001.svg");
752 // This was 288mm, logic->logic conversion input was a pixel value.
753 assertXPath(mpXmlDoc
, "/svg:svg", "width", "216mm");
754 assertXPath(mpXmlDoc
, "/svg:svg", "height", "279mm");
757 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSVG
)
759 uno::Sequence
<beans::PropertyValue
> aFilterData(comphelper::InitPropertySequence(
760 { { "EPUBLayoutMethod",
761 uno::Any(static_cast<sal_Int32
>(libepubgen::EPUB_LAYOUT_METHOD_FIXED
)) } }));
762 createDoc(u
"hello.fodt", aFilterData
);
764 CPPUNIT_ASSERT(mxZipFile
->hasByName("OEBPS/images/image0001.svg"));
765 uno::Reference
<io::XInputStream
> xInputStream(
766 mxZipFile
->getByName("OEBPS/images/image0001.svg"), uno::UNO_QUERY
);
767 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
769 SvMemoryStream aMemoryStream
;
770 aMemoryStream
.WriteStream(*pStream
);
771 OString
aExpected("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<svg");
772 CPPUNIT_ASSERT(aMemoryStream
.GetSize() > o3tl::make_unsigned(aExpected
.getLength()));
774 // This failed, there was a '<!DOCTYPE' line between the xml and the svg
775 // one, causing a validation error.
776 OString
aActual(static_cast<const char*>(aMemoryStream
.GetData()), aExpected
.getLength());
777 CPPUNIT_ASSERT_EQUAL(aExpected
, aActual
);
779 // This failed, we used the xlink attribute namespace, but we did not
781 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/images/image0001.svg");
782 assertXPathNSDef(mpXmlDoc
, "/svg:svg", u
"xlink", u
"http://www.w3.org/1999/xlink");
785 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623SingleWritingMode
)
787 // Simple page that has single writing mode should work.
788 createDoc(u
"tdf115623-single-writing-mode.odt", {});
789 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
790 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
791 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
792 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
793 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"writing-mode"));
796 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623SplitByChapter
)
798 createDoc(u
"tdf115623-split-by-chapter.odt", {});
799 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
801 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
802 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
803 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
804 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"writing-mode"));
806 // Split HTML should keep the same writing-mode.
808 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0002.xhtml");
809 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
810 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
811 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"writing-mode"));
815 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testTdf115623ManyPageSpans
)
817 createDoc(u
"tdf115623-many-pagespans.odt", {});
818 std::map
<OUString
, std::vector
<OUString
>> aCssDoc
= parseCss("OEBPS/styles/stylesheet.css");
819 // Two pages should have different writing modes.
821 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
822 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
823 CPPUNIT_ASSERT_EQUAL(OUString("vertical-rl"),
824 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"writing-mode"));
827 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0002.xhtml");
828 OUString aClass
= getXPath(mpXmlDoc
, "//xhtml:body", "class");
829 CPPUNIT_ASSERT_EQUAL(OUString("horizontal-tb"),
830 EPUBExportTest::getCss(aCssDoc
, aClass
, u
"writing-mode"));
834 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testSimpleRuby
)
836 createDoc(u
"simple-ruby.odt", {});
837 xmlDocUniquePtr mpXmlDoc
= parseExport("OEBPS/sections/section0001.xhtml");
838 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:span", "base text");
839 assertXPathContent(mpXmlDoc
, "//xhtml:body/xhtml:p/xhtml:ruby/xhtml:rt", "ruby text");
842 CPPUNIT_TEST_FIXTURE(EPUBExportTest
, testAbi11105
)
844 // This crashed because the paragraph style "P5" which had a master-page-name
845 // appeared in a table cell messed up page spans.
846 createDoc(u
"abi11105.abw", {});
850 CPPUNIT_PLUGIN_IMPLEMENT();
852 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */