/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override; void createDoc(std::u16string_view rFile, const uno::Sequence<beans::PropertyValue>& rFilterData); /// Parses a CSS representation of the stream named rName and returns it.
std::map<OUString, std::vector<OUString>> parseCss(const OUString& rName); /// Looks up a key of a class in rCss. static OUString getCss(std::map<OUString, std::vector<OUString>>& rCss, const OUString& rClass,
std::u16string_view rKey);
};
// Make sure that the output is split into two.
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0001.xhtml"_ustr)); // This failed, output was a single section.
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0002.xhtml"_ustr));
CPPUNIT_ASSERT(!mxZipFile->hasByName(u"OEBPS/sections/section0003.xhtml"_ustr));
}
// Check that the mime type is written uncompressed at the expected location.
SvFileStream aFileStream(maTempFile.GetURL(), StreamMode::READ);
SvMemoryStream aMemoryStream;
aMemoryStream.WriteStream(aFileStream);
OString aExpected("application/epub+zip"_ostr);
CPPUNIT_ASSERT(aMemoryStream.GetSize() > static_cast<sal_uInt64>(aExpected.getLength()) + 38);
OString aActual(static_cast<constchar*>(aMemoryStream.GetData()) + 38, aExpected.getLength()); // This failed: actual data was some garbage, not the uncompressed mime type.
CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
// This was just "libepubgen/x.y.z", i.e. the LO version was missing.
OUString aGenerator
= getXPath(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@name='generator']", "content");
CPPUNIT_ASSERT(aGenerator.startsWith(utl::DocInfoHelper::GetGeneratorString()));
uno::Reference<lang::XMultiServiceFactory> xMSF(m_xContext->getServiceManager(),
uno::UNO_QUERY); static constexpr OUString aServiceName(u"com.sun.star.comp.Writer.EPUBExportFilter"_ustr);
uno::Reference<document::XFilter> xFilter(xMSF->createInstance(aServiceName), uno::UNO_QUERY); // Should result in no errors.
xFilter->cancel(); // We got back what we expected.
uno::Reference<lang::XServiceInfo> xServiceInfo(xFilter, uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(aServiceName, xServiceInfo->getImplementationName());
CPPUNIT_ASSERT(xServiceInfo->supportsService(u"com.sun.star.document.ExportFilter"_ustr));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/content.opf"_ustr); // This was 3.0, EPUBVersion filter option was ignored and we always emitted EPUB3.
assertXPath(mpXmlDoc, "/opf:package", "version", u"2.0");
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/content.opf"_ustr); // This was missing, EPUBLayoutMethod filter option was ignored and we always emitted reflowable layout.
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
u"pre-paginated");
}
CPPUNIT_TEST_FIXTURE(EPUBExportTest, testEPUBFixedLayoutOption)
{ // Explicitly request fixed layout, this time via FilterOptions.
maFilterOptions = "layout=fixed";
createDoc(u"hello.fodt", {});
// This failed, fixed layout was only working via the FilterData map.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/content.opf"_ustr);
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='rendition:layout']",
u"pre-paginated");
}
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0001.xhtml"_ustr)); // This was missing, implicit page break (as calculated by the layout) was lost on export.
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0002.xhtml"_ustr));
CPPUNIT_ASSERT(!mxZipFile->hasByName(u"OEBPS/sections/section0003.xhtml"_ustr));
// Make sure that fixed layout has chapter names in the navigation // document.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/toc.xhtml"_ustr); // This was 'Page 1' instead.
assertXPathContent(mpXmlDoc, "//xhtml:li[1]/xhtml:a", u"First chapter");
assertXPathContent(mpXmlDoc, "//xhtml:li[2]/xhtml:a", u"Second chapter");
}
CPPUNIT_TEST_FIXTURE(EPUBExportTest, testPageBreakSplit)
{
uno::Sequence<beans::PropertyValue> aFilterData(comphelper::InitPropertySequence(
{ // Explicitly request split on page break (instead of on heading).
{ "EPUBSplitMethod",
uno::Any(static_cast<sal_Int32>(libepubgen::EPUB_SPLIT_METHOD_PAGE_BREAK)) } }));
createDoc(u"2pages.fodt", aFilterData);
// Make sure that the output is split into two.
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0001.xhtml"_ustr)); // This failed, output was a single section.
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/sections/section0002.xhtml"_ustr));
CPPUNIT_ASSERT(!mxZipFile->hasByName(u"OEBPS/sections/section0003.xhtml"_ustr));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:span[1]", "class", u"span0"); // This failed, it was still span1, i.e. the bold and the italic formatting // did not differ.
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "class", u"span1");
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:span[3]", "class", u"span2");
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/content.opf"_ustr); // This was "Unknown Author", <meta:initial-creator> was not handled.
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:creator", u"A U Thor");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:title", u"Title");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:language", u"hu");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
u"2017-09-27T09:51:19Z");
// Make sure that cover image next to the source document is picked up.
assertXPath(mpXmlDoc, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']", "properties", u"cover-image");
assertXPath(mpXmlDoc, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']", "media-type", u"image/png");
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/images/image0001.png"_ustr));
}
// These were the libepubgen default values, metadata from a matching .xmp file was not picked up.
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:identifier",
u"deadbeef-e394-4cd6-9b83-7172794612e5");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:title", u"unknown title from xmp");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:creator",
u"unknown author from xmp");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:language", u"nl");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
u"2016-11-20T17:16:07Z");
}
// These were values from XMP (deadbeef, etc.), not from API.
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:identifier",
u"deadc0de-e394-4cd6-9b83-7172794612e5");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:title", u"unknown title from api");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:creator",
u"unknown author from api");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:language", u"hu");
assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']",
u"2015-11-20T17:16:07Z");
}
// Make sure that the explicitly set cover image is used. // This failed, as the image was not part of the package.
assertXPath(mpXmlDoc, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']", "properties", u"cover-image");
assertXPath(mpXmlDoc, "/opf:package/opf:manifest/opf:item[@href='images/image0001.png']", "media-type", u"image/png");
CPPUNIT_ASSERT(mxZipFile->hasByName(u"OEBPS/images/image0001.png"_ustr));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
assertXPath(mpXmlDoc, "//xhtml:p[1]", "class", u"para0"); // This failed, paragraph properties from style were not exported.
assertXPath(mpXmlDoc, "//xhtml:p[2]", "class", u"para1");
// Test character properties from named paragraph style.
assertXPath(mpXmlDoc, "//xhtml:p[1]/xhtml:span", "class", u"span0"); // This failed, character properties from paragraph style were not exported.
assertXPath(mpXmlDoc, "//xhtml:p[2]/xhtml:span", "class", u"span1");
}
// Test character properties from named text style.
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:span[1]", "class", u"span0"); // This failed, character properties from text style were not exported.
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "class", u"span1");
}
// Find the CSS rule for the blue text.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
std::map<OUString, std::vector<OUString>> aCssDoc
= parseCss(u"OEBPS/styles/stylesheet.css"_ustr);
OUString aBlue = getXPath(mpXmlDoc, "//xhtml:p[2]/xhtml:span[2]", "class");
CPPUNIT_ASSERT_EQUAL(u"#0000ff"_ustr, EPUBExportTest::getCss(aCssDoc, aBlue, u"color")); // This failed, the span only had the properties from its style, but not // from the style's parent(s).
CPPUNIT_ASSERT_EQUAL(u"'Liberation Mono'"_ustr,
EPUBExportTest::getCss(aCssDoc, aBlue, u"font-family"));
}
// Check textural content of nested span.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
std::map<OUString, std::vector<OUString>> aCssDoc
= parseCss(u"OEBPS/styles/stylesheet.css"_ustr); // This crashed, span had no content.
assertXPathContent(mpXmlDoc, "//xhtml:p/xhtml:span[2]", u"red");
// Check formatting of nested span.
OUString aRed = getXPath(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "class"); // This failed, direct formatting on top of named style was lost.
CPPUNIT_ASSERT_EQUAL(u"#ff0000"_ustr, EPUBExportTest::getCss(aCssDoc, aRed, u"color"));
CPPUNIT_ASSERT_EQUAL(u"'Liberation Mono'"_ustr,
EPUBExportTest::getCss(aCssDoc, aRed, u"font-family"));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // This was 0, line break was not handled.
assertXPath(mpXmlDoc, "//xhtml:p[1]/xhtml:span/xhtml:br", 1); // This was 0, line break inside span was not handled.
assertXPath(mpXmlDoc, "//xhtml:p[2]/xhtml:span/xhtml:br", 1);
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // This was lost.
assertXPathContent(mpXmlDoc, "//xhtml:p[1]/xhtml:span[1]", OUString::fromUtf8("\xc2\xa0")); // Make sure escaping happens only once.
assertXPathContent(mpXmlDoc, "//xhtml:p[1]/xhtml:span[2]", u"a&b"); // This was also lost.
assertXPathContent(
mpXmlDoc, "//xhtml:p[1]/xhtml:span[3]",
OUString::fromUtf8("\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2" "\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 "));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
std::map<OUString, std::vector<OUString>> aCssDoc
= parseCss(u"OEBPS/styles/stylesheet.css"_ustr); // Check formatting of the middle span.
OUString aMiddle = getXPath(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "class");
CPPUNIT_ASSERT_EQUAL(u"italic"_ustr, EPUBExportTest::getCss(aCssDoc, aMiddle, u"font-style")); // Direct para formatting was lost, only direct char formatting was // written, so this failed.
CPPUNIT_ASSERT_EQUAL(u"bold"_ustr, EPUBExportTest::getCss(aCssDoc, aMiddle, u"font-weight"));
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // This was "After.", i.e. in-section content was ignored.
assertXPathContent(mpXmlDoc, "//xhtml:p[2]/xhtml:span", u"In section.");
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // This was "C", i.e. in-list content was ignored.
assertXPathContent(mpXmlDoc, "//xhtml:p[2]/xhtml:span", u"B"); // Test nested list content.
assertXPathContent(mpXmlDoc, "//xhtml:p[6]/xhtml:span", u"F");
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // This was 1, invalid relative link was not filtered out.
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:a", 0);
}
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // <span> was lost, link text having a char format was missing.
assertXPathContent(mpXmlDoc, "//xhtml:p/xhtml:a/xhtml:span", u"https://libreoffice.org/");
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:a", "href", u"https://libreoffice.org/");
}
CPPUNIT_TEST_FIXTURE(EPUBExportTest, testLinkNamedCharFormat)
{ // Character properties from named character style on hyperlink was lost.
createDoc(u"link-namedcharformat.fodt", {});
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
std::map<OUString, std::vector<OUString>> aCssDoc
= parseCss(u"OEBPS/styles/stylesheet.css"_ustr); // This failed, there was no span inside the hyperlink.
assertXPathContent(mpXmlDoc, "//xhtml:p/xhtml:a/xhtml:span", u"http://libreoffice.org");
assertXPath(mpXmlDoc, "//xhtml:p/xhtml:a", "href", u"http://libreoffice.org/");
// This failed, image with caption was lost.
assertXPath(mpXmlDoc, "//xhtml:img", "class", u"frame1"); // Expected spans: // 1) break after the image // 2) "Illustration " // 3) The sequence field, this was missing (was ": foo" instead).
assertXPathContent(mpXmlDoc, "//xhtml:div/xhtml:p/xhtml:span[3]", u"1");
OUString aClass = getXPath(mpXmlDoc, "//xhtml:div/xhtml:p/xhtml:span[3]", "class"); // This failed, the 3rd span was not italic.
CPPUNIT_ASSERT_EQUAL(u"italic"_ustr, EPUBExportTest::getCss(aCssDoc, aClass, u"font-style"));
}
// Test text popup anchor.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", "type", u"noteref");
assertXPathContent(mpXmlDoc, "//xhtml:body/xhtml:p[2]/xhtml:span/xhtml:a", u"link"); // Test text popup content.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[2]", "type", u"footnote");
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[2]/xhtml:img", 1);
}
CPPUNIT_TEST_FIXTURE(EPUBExportTest, testPopupMedia)
{ // This is the same as testPopup(), but the links point to images in the // default media directory, not in the document directory.
createDoc(u"popup-media.odt", {});
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // Test image popup anchor. This failed, number of XPath nodes was 0.
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[1]/xhtml:a", "type", u"noteref");
assertXPath(mpXmlDoc, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", 1);
}
CPPUNIT_TEST_FIXTURE(EPUBExportTest, testPopupAPI)
{ // Make sure that the popup works with data from a media directory.
OUString aMediaDir = createFileURL(u"popup");
uno::Sequence<beans::PropertyValue> aFilterData(
comphelper::InitPropertySequence({ { "RVNGMediaDir", uno::Any(aMediaDir) } }));
createDoc(u"popup-api.odt", aFilterData);
// We have a non-empty anchor image.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr);
OUString aAnchor = getXPath(mpXmlDoc, "//xhtml:body/xhtml:p[1]/xhtml:a/xhtml:img", "src");
CPPUNIT_ASSERT(!aAnchor.isEmpty()); // We have a non-empty popup image.
OUString aData = getXPath(mpXmlDoc, "//xhtml:body/xhtml:aside[1]/xhtml:img", "src");
CPPUNIT_ASSERT(!aData.isEmpty()); // The anchor is different from the popup image.
CPPUNIT_ASSERT(aAnchor != aData);
}
// This failed, viewport was empty, so page size was lost.
xmlDocUniquePtr mpXmlDoc = parseExport(u"OEBPS/sections/section0001.xhtml"_ustr); // 21,59cm x 27.94cm (letter).
assertXPath(mpXmlDoc, "/xhtml:html/xhtml:head/xhtml:meta[@name='viewport']", "content",
u"width=816, height=1056");
mpXmlDoc = parseExport(u"OEBPS/images/image0001.svg"_ustr); // This was 288mm, logic->logic conversion input was a pixel value.
assertXPath(mpXmlDoc, "/svg:svg", "width", u"216mm");
assertXPath(mpXmlDoc, "/svg:svg", "height", u"279mm");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.