diff options
author | Miklos Vajna <vmiklos@collabora.co.uk> | 2017-11-30 08:48:06 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.co.uk> | 2017-11-30 15:35:44 +0100 |
commit | 1a48cdaf91633b354fb1110c566c766a4398fba0 (patch) | |
tree | 18973dc147cfd3f2d8276341593dca00c60e2723 | |
parent | 56d79a2d046f08e703ed6498b7c8d15abe057d3a (diff) |
EPUB export: allow overwriting of document metadata
Pick up overrides from <base directory>/<base name>.xmp as a start.
Change-Id: Ib64a6bbdadc53633fb1f0d4a7efdde2e3c96b5ef
Reviewed-on: https://gerrit.libreoffice.org/45551
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
-rw-r--r-- | external/libepubgen/libepubgen-epub3.patch.1 | 30 | ||||
-rw-r--r-- | writerperfect/qa/unit/EPUBExportTest.cxx | 15 | ||||
-rw-r--r-- | writerperfect/qa/unit/data/writer/epubexport/meta-xmp.fodt | 8 | ||||
-rw-r--r-- | writerperfect/qa/unit/data/writer/epubexport/meta-xmp.xmp | 28 | ||||
-rw-r--r-- | writerperfect/source/writer/EPUBExportFilter.cxx | 2 | ||||
-rw-r--r-- | writerperfect/source/writer/exp/xmlimp.cxx | 98 | ||||
-rw-r--r-- | writerperfect/source/writer/exp/xmlimp.hxx | 7 | ||||
-rw-r--r-- | writerperfect/source/writer/exp/xmlmetai.cxx | 91 | ||||
-rw-r--r-- | writerperfect/source/writer/exp/xmlmetai.hxx | 45 |
9 files changed, 304 insertions, 20 deletions
diff --git a/external/libepubgen/libepubgen-epub3.patch.1 b/external/libepubgen/libepubgen-epub3.patch.1 index af87b2644e8d..39bac59c51ff 100644 --- a/external/libepubgen/libepubgen-epub3.patch.1 +++ b/external/libepubgen/libepubgen-epub3.patch.1 @@ -4467,3 +4467,33 @@ index 3f4bf3c..cbb83b7 100644 -- 2.13.6 +From 631b21834883aa8f2ee83a20717dd37900331696 Mon Sep 17 00:00:00 2001 +From: Miklos Vajna <vmiklos@collabora.co.uk> +Date: Tue, 21 Nov 2017 11:52:03 +0100 +Subject: [PATCH] EPUBGenerator: allow overwriting dc:identifier default + +All other types had a way to be overwritten. +--- + src/lib/EPUBGenerator.cpp | 5 ++++- + src/test/EPUBTextGeneratorTest.cpp | 3 +++ + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/lib/EPUBGenerator.cpp b/src/lib/EPUBGenerator.cpp +index 62dac6e..1cb1112 100644 +--- a/src/lib/EPUBGenerator.cpp ++++ b/src/lib/EPUBGenerator.cpp +@@ -285,7 +285,10 @@ void EPUBGenerator::writeRoot() + boost::uuids::uuid uuid = boost::uuids::random_generator()(); + identifierStream << uuid; + std::string identifierCharactrs = identifierStream.str(); +- sink.insertCharacters(identifierCharactrs.c_str()); ++ RVNGString identifier = identifierCharactrs.c_str(); ++ if (m_metadata["dc:identifier"] && !m_metadata["dc:identifier"]->getStr().empty()) ++ identifier = m_metadata["dc:identifier"]->getStr(); ++ sink.insertCharacters(identifier); + sink.closeElement("dc:identifier"); + + RVNGString title("Unknown Title"); +-- +2.13.6 + diff --git a/writerperfect/qa/unit/EPUBExportTest.cxx b/writerperfect/qa/unit/EPUBExportTest.cxx index 72c9a27dc495..82452d958152 100644 --- a/writerperfect/qa/unit/EPUBExportTest.cxx +++ b/writerperfect/qa/unit/EPUBExportTest.cxx @@ -61,6 +61,7 @@ public: void testSpanAutostyle(); void testParaAutostyleCharProps(); void testMeta(); + void testMetaXMP(); void testCoverImage(); void testParaNamedstyle(); void testCharNamedstyle(); @@ -94,6 +95,7 @@ public: CPPUNIT_TEST(testSpanAutostyle); CPPUNIT_TEST(testParaAutostyleCharProps); CPPUNIT_TEST(testMeta); + CPPUNIT_TEST(testMetaXMP); CPPUNIT_TEST(testCoverImage); CPPUNIT_TEST(testParaNamedstyle); CPPUNIT_TEST(testCharNamedstyle); @@ -335,6 +337,19 @@ void EPUBExportTest::testMeta() CPPUNIT_ASSERT(mxZipFile->hasByName("OEBPS/images/image0001.png")); } +void EPUBExportTest::testMetaXMP() +{ + createDoc("meta-xmp.fodt", {}); + mpXmlDoc = parseExport("OEBPS/content.opf"); + + // These were the libepubgen default values, metadata from a matching .xmp file was not picked up. + assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:identifier", "deadbeef-e394-4cd6-9b83-7172794612e5"); + assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:title", "unknown title from xmp"); + assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:creator", "unknown author from xmp"); + assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/dc:language", "nl"); + assertXPathContent(mpXmlDoc, "/opf:package/opf:metadata/opf:meta[@property='dcterms:modified']", "2016-11-20T17:16:07Z"); +} + void EPUBExportTest::testCoverImage() { OUString aCoverURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "meta.cover-image.png"; diff --git a/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.fodt b/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.fodt new file mode 100644 index 000000000000..b245e9d7abce --- /dev/null +++ b/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.fodt @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document office:mimetype="application/vnd.oasis.opendocument.text" office:version="1.2" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"> + <office:body> + <office:text> + <text:p><text:span>Hello world!</text:span></text:p> + </office:text> + </office:body> +</office:document> diff --git a/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.xmp b/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.xmp new file mode 100644 index 000000000000..aa578159cb89 --- /dev/null +++ b/writerperfect/qa/unit/data/writer/epubexport/meta-xmp.xmp @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<x:xmpmeta xmlns:x="adobe:ns:meta/"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"> + <dc:identifier>deadbeef-e394-4cd6-9b83-7172794612e5</dc:identifier> + <dc:title> + <rdf:Alt> + <rdf:li>unknown title from xmp</rdf:li> + </rdf:Alt> + </dc:title> + <dc:creator> + <rdf:Seq> + <rdf:li>unknown author from xmp</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:language> + <rdf:Bag> + <rdf:li>nl</rdf:li> + </rdf:Bag> + </dc:language> + <dc:date> + <rdf:Seq> + <rdf:li>2016-11-20T17:16:07Z</rdf:li> + </rdf:Seq> + </dc:date> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> diff --git a/writerperfect/source/writer/EPUBExportFilter.cxx b/writerperfect/source/writer/EPUBExportFilter.cxx index 2454adddc781..8463c7c88165 100644 --- a/writerperfect/source/writer/EPUBExportFilter.cxx +++ b/writerperfect/source/writer/EPUBExportFilter.cxx @@ -79,7 +79,7 @@ sal_Bool EPUBExportFilter::filter(const uno::Sequence<beans::PropertyValue> &rDe uno::Reference<frame::XModel> xSourceModel(mxSourceDocument, uno::UNO_QUERY); if (xSourceModel.is()) aSourceURL = xSourceModel->getURL(); - uno::Reference<xml::sax::XDocumentHandler> xExportHandler(new exp::XMLImport(aGenerator, aSourceURL, rDescriptor)); + uno::Reference<xml::sax::XDocumentHandler> xExportHandler(new exp::XMLImport(mxContext, aGenerator, aSourceURL, rDescriptor)); uno::Reference<lang::XInitialization> xInitialization(mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.comp.Writer.XMLOasisExporter", mxContext), uno::UNO_QUERY); xInitialization->initialize({uno::makeAny(xExportHandler)}); diff --git a/writerperfect/source/writer/exp/xmlimp.cxx b/writerperfect/source/writer/exp/xmlimp.cxx index bc3b9c7693e4..57f89c870b9a 100644 --- a/writerperfect/source/writer/exp/xmlimp.cxx +++ b/writerperfect/source/writer/exp/xmlimp.cxx @@ -12,9 +12,12 @@ #include <initializer_list> #include <unordered_map> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> #include <rtl/uri.hxx> #include <tools/stream.hxx> #include <tools/urlobj.hxx> +#include <unotools/streamwrap.hxx> #include "xmlfmt.hxx" #include "xmlictxt.hxx" @@ -46,26 +49,16 @@ OUString GetMimeType(const OUString &rExtension) } /// Picks up a cover image from the base directory. -OUString FindCoverImage(const OUString &rDocumentBaseURL, OUString &rMimeType, const uno::Sequence<beans::PropertyValue> &rDescriptor) +OUString FindCoverImage(const OUString &rDocumentBaseURL, OUString &rMimeType, const uno::Sequence<beans::PropertyValue> &rFilterData) { OUString aRet; // See if filter data contains a cover image explicitly. - uno::Sequence<beans::PropertyValue> aFilterData; - for (sal_Int32 i = 0; i < rDescriptor.getLength(); ++i) - { - if (rDescriptor[i].Name == "FilterData") - { - rDescriptor[i].Value >>= aFilterData; - break; - } - } - - for (sal_Int32 i = 0; i < aFilterData.getLength(); ++i) + for (sal_Int32 i = 0; i < rFilterData.getLength(); ++i) { - if (aFilterData[i].Name == "EPUBCoverImage") + if (rFilterData[i].Name == "EPUBCoverImage") { - aFilterData[i].Value >>= aRet; + rFilterData[i].Value >>= aRet; break; } } @@ -99,7 +92,7 @@ OUString FindCoverImage(const OUString &rDocumentBaseURL, OUString &rMimeType, c } catch (const rtl::MalformedUriException &rException) { - SAL_WARN("writerfilter", "FindCoverImage: convertRelToAbs() failed:" << rException.getMessage()); + SAL_WARN("writerperfect", "FindCoverImage: convertRelToAbs() failed:" << rException.getMessage()); } if (!aRet.isEmpty()) @@ -118,6 +111,57 @@ OUString FindCoverImage(const OUString &rDocumentBaseURL, OUString &rMimeType, c return aRet; } + +/// Picks up XMP metadata from the base directory. +void FindXMPMetadata(const uno::Reference<uno::XComponentContext> &xContext, const OUString &rDocumentBaseURL, const uno::Sequence<beans::PropertyValue> &/*rFilterData*/, librevenge::RVNGPropertyList &rMetaData) +{ + if (rDocumentBaseURL.isEmpty()) + return; + + INetURLObject aDocumentBaseURL(rDocumentBaseURL); + OUString aURL; + try + { + aURL = rtl::Uri::convertRelToAbs(rDocumentBaseURL, aDocumentBaseURL.GetBase() + ".xmp"); + } + catch (const rtl::MalformedUriException &rException) + { + SAL_WARN("writerperfect", "FindXMPMetadata: convertRelToAbs() failed:" << rException.getMessage()); + return; + } + + SvFileStream aStream(aURL, StreamMode::READ); + if (!aStream.IsOpen()) + return; + + xml::sax::InputSource aInputSource; + uno::Reference<io::XInputStream> xStream(new utl::OStreamWrapper(aStream)); + aInputSource.aInputStream = xStream; + uno::Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(xContext); + rtl::Reference<XMPParser> xXMP(new XMPParser()); + uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xXMP.get()); + xParser->setDocumentHandler(xDocumentHandler); + try + { + xParser->parseStream(aInputSource); + } + catch (const uno::Exception &rException) + { + SAL_WARN("writerperfect", "FindXMPMetadata: parseStream() failed:" << rException.Message); + return; + } + + if (!xXMP->m_aIdentifier.isEmpty()) + rMetaData.insert("dc:identifier", xXMP->m_aIdentifier.toUtf8().getStr()); + if (!xXMP->m_aTitle.isEmpty()) + rMetaData.insert("dc:title", xXMP->m_aTitle.toUtf8().getStr()); + if (!xXMP->m_aCreator.isEmpty()) + rMetaData.insert("meta:initial-creator", xXMP->m_aCreator.toUtf8().getStr()); + if (!xXMP->m_aLanguage.isEmpty()) + rMetaData.insert("dc:language", xXMP->m_aLanguage.toUtf8().getStr()); + if (!xXMP->m_aDate.isEmpty()) + rMetaData.insert("dc:date", xXMP->m_aDate.toUtf8().getStr()); +} } /// Handler for <office:body>. @@ -170,11 +214,22 @@ rtl::Reference<XMLImportContext> XMLOfficeDocContext::CreateChildContext(const O return nullptr; } -XMLImport::XMLImport(librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence<beans::PropertyValue> &rDescriptor) - : mrGenerator(rGenerator) +XMLImport::XMLImport(const uno::Reference<uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence<beans::PropertyValue> &rDescriptor) + : mrGenerator(rGenerator), + mxContext(xContext) { + uno::Sequence<beans::PropertyValue> aFilterData; + for (sal_Int32 i = 0; i < rDescriptor.getLength(); ++i) + { + if (rDescriptor[i].Name == "FilterData") + { + rDescriptor[i].Value >>= aFilterData; + break; + } + } + OUString aMimeType; - OUString aCoverImage = FindCoverImage(rURL, aMimeType, rDescriptor); + OUString aCoverImage = FindCoverImage(rURL, aMimeType, aFilterData); if (!aCoverImage.isEmpty()) { librevenge::RVNGBinaryData aBinaryData; @@ -187,6 +242,8 @@ XMLImport::XMLImport(librevenge::RVNGTextInterface &rGenerator, const OUString & aCoverImageProperties.insert("librevenge:mime-type", aMimeType.toUtf8().getStr()); maCoverImages.append(aCoverImageProperties); } + + FindXMPMetadata(mxContext, rURL, aFilterData, maMetaData); } const librevenge::RVNGPropertyListVector &XMLImport::GetCoverImages() @@ -194,6 +251,11 @@ const librevenge::RVNGPropertyListVector &XMLImport::GetCoverImages() return maCoverImages; } +const librevenge::RVNGPropertyList &XMLImport::GetMetaData() +{ + return maMetaData; +} + rtl::Reference<XMLImportContext> XMLImport::CreateContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/) { if (rName == "office:document") diff --git a/writerperfect/source/writer/exp/xmlimp.hxx b/writerperfect/source/writer/exp/xmlimp.hxx index b1008f00dbf0..90a9762bb9b6 100644 --- a/writerperfect/source/writer/exp/xmlimp.hxx +++ b/writerperfect/source/writer/exp/xmlimp.hxx @@ -16,6 +16,7 @@ #include <librevenge/librevenge.h> #include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> #include <com/sun/star/xml/sax/XDocumentHandler.hpp> #include <cppuhelper/implbase.hxx> @@ -51,9 +52,12 @@ class XMLImport : public cppu::WeakImplHelper std::map<OUString, librevenge::RVNGPropertyList> maAutomaticGraphicStyles; std::map<OUString, librevenge::RVNGPropertyList> maGraphicStyles; librevenge::RVNGPropertyListVector maCoverImages; + /// Author, date, etc -- overwrites what would be from the document out of the box. + librevenge::RVNGPropertyList maMetaData; + const css::uno::Reference<css::uno::XComponentContext> &mxContext; public: - XMLImport(librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const css::uno::Sequence<css::beans::PropertyValue> &rDescriptor); + XMLImport(const css::uno::Reference<css::uno::XComponentContext> &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const css::uno::Sequence<css::beans::PropertyValue> &rDescriptor); rtl::Reference<XMLImportContext> CreateContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs); @@ -73,6 +77,7 @@ public: std::map<OUString, librevenge::RVNGPropertyList> &GetTableStyles(); std::map<OUString, librevenge::RVNGPropertyList> &GetGraphicStyles(); const librevenge::RVNGPropertyListVector &GetCoverImages(); + const librevenge::RVNGPropertyList &GetMetaData(); // XDocumentHandler void SAL_CALL startDocument() override; diff --git a/writerperfect/source/writer/exp/xmlmetai.cxx b/writerperfect/source/writer/exp/xmlmetai.cxx index ad39aa8c39f1..09e56c8c2388 100644 --- a/writerperfect/source/writer/exp/xmlmetai.cxx +++ b/writerperfect/source/writer/exp/xmlmetai.cxx @@ -131,6 +131,9 @@ void XMLMetaInitialCreatorContext::characters(const OUString &rChars) XMLMetaDocumentContext::XMLMetaDocumentContext(XMLImport &rImport) : XMLImportContext(rImport) { + librevenge::RVNGPropertyList::Iter it(mrImport.GetMetaData()); + for (it.rewind(); it.next();) + m_aPropertyList.insert(it.key(), it()->clone()); m_aPropertyList.insert("librevenge:cover-images", mrImport.GetCoverImages()); } @@ -154,6 +157,94 @@ void XMLMetaDocumentContext::endElement(const OUString &/*rName*/) mrImport.GetGenerator().setDocumentMetaData(m_aPropertyList); } +XMPParser::XMPParser() = default; + +XMPParser::~XMPParser() = default; + +void XMPParser::startDocument() +{ +} + +void XMPParser::endDocument() +{ +} + +void XMPParser::startElement(const OUString &rName, const uno::Reference<xml::sax::XAttributeList> &/*xAttribs*/) +{ + if (rName == "dc:identifier") + m_bInIdentifier = true; + else if (rName == "dc:title") + m_bInTitle = true; + else if (rName == "dc:creator") + m_bInCreator = true; + else if (rName == "dc:language") + m_bInLanguage = true; + else if (rName == "dc:date") + m_bInDate = true; + else if (rName == "rdf:li") + { + if (m_bInTitle) + m_bInTitleItem = true; + else if (m_bInCreator) + m_bInCreatorItem = true; + else if (m_bInLanguage) + m_bInLanguageItem = true; + else if (m_bInDate) + m_bInDateItem = true; + } +} + +void XMPParser::endElement(const OUString &rName) +{ + if (rName == "dc:identifier") + m_bInIdentifier = false; + else if (rName == "dc:title") + m_bInTitle = false; + else if (rName == "dc:creator") + m_bInCreator = false; + else if (rName == "dc:language") + m_bInLanguage = false; + else if (rName == "dc:date") + m_bInDate = false; + else if (rName == "rdf:li") + { + if (m_bInTitle) + m_bInTitleItem = false; + else if (m_bInCreator) + m_bInCreatorItem = false; + else if (m_bInLanguage) + m_bInLanguageItem = false; + else if (m_bInDate) + m_bInDateItem = false; + } +} + +void XMPParser::characters(const OUString &rChars) +{ + if (m_bInIdentifier) + m_aIdentifier += rChars; + else if (m_bInTitleItem) + m_aTitle += rChars; + else if (m_bInCreatorItem) + m_aCreator += rChars; + else if (m_bInLanguageItem) + m_aLanguage += rChars; + else if (m_bInDateItem) + m_aDate += rChars; +} + +void XMPParser::ignorableWhitespace(const OUString &/*rWhitespace*/) +{ +} + +void XMPParser::processingInstruction(const OUString &/*rTarget*/, const OUString &/*rData*/) +{ +} + +void XMPParser::setDocumentLocator(const uno::Reference<xml::sax::XLocator> &/*xLocator*/) +{ +} + } // namespace exp } // namespace writerperfect diff --git a/writerperfect/source/writer/exp/xmlmetai.hxx b/writerperfect/source/writer/exp/xmlmetai.hxx index 12285937a705..a011e6b83184 100644 --- a/writerperfect/source/writer/exp/xmlmetai.hxx +++ b/writerperfect/source/writer/exp/xmlmetai.hxx @@ -32,6 +32,51 @@ public: librevenge::RVNGPropertyList m_aPropertyList; }; +/// Parses an XMP file. +class XMPParser: public cppu::WeakImplHelper + < + css::xml::sax::XDocumentHandler + > +{ +public: + explicit XMPParser(); + virtual ~XMPParser() override; + + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + + virtual void SAL_CALL endDocument() override; + + virtual void SAL_CALL startElement(const OUString &aName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) override; + + virtual void SAL_CALL endElement(const OUString &aName) override; + + virtual void SAL_CALL characters(const OUString &aChars) override; + + virtual void SAL_CALL ignorableWhitespace(const OUString &aWhitespaces) override; + + virtual void SAL_CALL processingInstruction(const OUString &aTarget, const OUString &aData) override; + + virtual void SAL_CALL setDocumentLocator(const css::uno::Reference<css::xml::sax::XLocator> &xLocator) override; + + OUString m_aIdentifier; + OUString m_aTitle; + OUString m_aCreator; + OUString m_aLanguage; + OUString m_aDate; + +private: + bool m_bInIdentifier = false; + bool m_bInTitle = false; + bool m_bInTitleItem = false; + bool m_bInCreator = false; + bool m_bInCreatorItem = false; + bool m_bInLanguage = false; + bool m_bInLanguageItem = false; + bool m_bInDate = false; + bool m_bInDateItem = false; +}; + } // namespace exp } // namespace writerperfect |