Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / writerperfect / qa / unit / EPUBExportTest.cxx
blob5827da5673f5a8192efe2de23ebeaefb9b8d58ee
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 <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;
34 namespace
36 /// Tests the EPUB export filter.
37 class EPUBExportTest : public UnoApiXmlTest
39 protected:
40 uno::Reference<packages::zip::XZipFileAccess2> mxZipFile;
41 OUString maFilterOptions;
43 public:
44 EPUBExportTest()
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.
71 loadFromURL(rFile);
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;
77 else
78 aMediaDescriptor["FilterOptions"] <<= maFilterOptions;
79 xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
80 mxZipFile
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.
92 OString aLine;
93 OUString aRuleName;
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));
104 return aRet;
107 OUString EPUBExportTest::getCss(std::map<OUString, std::vector<OUString>>& rCss,
108 const OUString& rClass, std::u16string_view rKey)
110 OUString aRet;
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);
126 break;
130 return aRet;
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");
160 // Default is EPUB3.
161 assertXPath(mpXmlDoc, "/opf:package", "version", "3.0");
163 // This was just "libepubgen/x.y.z", i.e. the LO version was missing.
164 OUString aGenerator
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(),
169 uno::UNO_QUERY);
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.
173 xFilter->cancel();
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']",
203 "pre-paginated");
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']",
215 "pre-paginated");
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
232 // document.
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).
243 { "EPUBSplitMethod",
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
261 // did not differ.
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");
426 // This was lost.
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.
431 assertXPathContent(
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");
527 OUString aClass
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");
540 OUString aClass1
541 = getXPath(mpXmlDoc, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[1]", "class");
542 OUString aClass2
543 = getXPath(mpXmlDoc, "//xhtml:table/xhtml:tbody/xhtml:tr[1]/xhtml:td[2]", "class");
544 OUString aClass3
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");
630 // Expected spans:
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)
644 #if !defined(MACOSX)
645 createDoc(u"font-embedding.fodt", {});
647 // Make sure that the params of defineEmbeddedFont() are all handled.
648 // librevenge:name
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"));
665 #endif
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
780 // define its URL.
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: */