/* -*- 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/. */ #include "xmlimp.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xmlfmt.hxx" #include "xmlictxt.hxx" #include "xmlmetai.hxx" #include "xmltext.hxx" using namespace com::sun::star; namespace writerperfect { namespace exp { namespace { /// Looks up mime type for a given image extension. OUString GetMimeType(const OUString &rExtension) { static const std::unordered_map vMimeTypes = { {"gif", "image/gif"}, {"jpg", "image/jpeg"}, {"png", "image/png"}, {"svg", "image/svg+xml"}, }; auto it = vMimeTypes.find(rExtension); return it == vMimeTypes.end() ? OUString() : it->second; } /// Determines the base directory for cover images, XMP metadata, popup images. OUString FindMediaDir(const OUString &rDocumentBaseURL, const uno::Sequence &rFilterData) { OUString aMediaDir; // See if filter data contains a media directory explicitly. for (sal_Int32 i = 0; i < rFilterData.getLength(); ++i) { if (rFilterData[i].Name == "RVNGMediaDir") { rFilterData[i].Value >>= aMediaDir; break; } } if (!aMediaDir.isEmpty()) return aMediaDir + "/"; // Not set explicitly, try to pick it up from the base directory. INetURLObject aURL(rDocumentBaseURL); try { aMediaDir = rtl::Uri::convertRelToAbs(rDocumentBaseURL, aURL.GetBase()) + "/"; } catch (const rtl::MalformedUriException &) { DBG_UNHANDLED_EXCEPTION("writerperfect"); } return aMediaDir; } /// Picks up a cover image from the base directory. OUString FindCoverImage(const OUString &rDocumentBaseURL, OUString &rMimeType, const uno::Sequence &rFilterData) { OUString aRet; // See if filter data contains a cover image explicitly. for (sal_Int32 i = 0; i < rFilterData.getLength(); ++i) { if (rFilterData[i].Name == "RVNGCoverImage") { rFilterData[i].Value >>= aRet; break; } } if (!aRet.isEmpty()) { INetURLObject aRetURL(aRet); rMimeType = GetMimeType(aRetURL.GetExtension()); return aRet; } // Not set explicitly, try to pick it up from the base directory. if (rDocumentBaseURL.isEmpty()) return aRet; static const std::initializer_list vExtensions = { "gif", "jpg", "png", "svg" }; OUString aMediaDir = FindMediaDir(rDocumentBaseURL, rFilterData); for (const auto &rExtension : vExtensions) { aRet = aMediaDir + "cover." + rExtension; if (!aRet.isEmpty()) { SvFileStream aStream(aRet, StreamMode::READ); if (aStream.IsOpen()) { rMimeType = GetMimeType(rExtension); // File exists. return aRet; } aRet.clear(); } } return aRet; } /// Picks up XMP metadata from the base directory. void FindXMPMetadata(const uno::Reference &xContext, const OUString &rDocumentBaseURL, const uno::Sequence &rFilterData, librevenge::RVNGPropertyList &rMetaData) { // See if filter data contains metadata explicitly. OUString aValue; for (sal_Int32 i = 0; i < rFilterData.getLength(); ++i) { if (rFilterData[i].Name == "RVNGIdentifier") { rFilterData[i].Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:identifier", aValue.toUtf8().getStr()); } else if (rFilterData[i].Name == "RVNGTitle") { rFilterData[i].Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:title", aValue.toUtf8().getStr()); } else if (rFilterData[i].Name == "RVNGInitialCreator") { rFilterData[i].Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("meta:initial-creator", aValue.toUtf8().getStr()); } else if (rFilterData[i].Name == "RVNGLanguage") { rFilterData[i].Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:language", aValue.toUtf8().getStr()); } else if (rFilterData[i].Name == "RVNGDate") { rFilterData[i].Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:date", aValue.toUtf8().getStr()); } } // If not set explicitly, try to pick it up from the base directory. if (rDocumentBaseURL.isEmpty()) return; OUString aMediaDir = FindMediaDir(rDocumentBaseURL, rFilterData); OUString aURL = aMediaDir + "metadata.xmp"; SvFileStream aStream(aURL, StreamMode::READ); if (!aStream.IsOpen()) return; xml::sax::InputSource aInputSource; uno::Reference xStream(new utl::OStreamWrapper(aStream)); aInputSource.aInputStream = xStream; uno::Reference xParser = xml::sax::Parser::create(xContext); rtl::Reference xXMP(new XMPParser(rMetaData)); uno::Reference xDocumentHandler(xXMP.get()); xParser->setDocumentHandler(xDocumentHandler); try { xParser->parseStream(aInputSource); } catch (const uno::Exception &) { DBG_UNHANDLED_EXCEPTION("writerperfect", "parseStream() failed"); return; } } } /// Handler for . class XMLBodyContext : public XMLImportContext { public: XMLBodyContext(XMLImport &rImport); rtl::Reference CreateChildContext(const OUString &rName, const uno::Reference &/*xAttribs*/) override; }; XMLBodyContext::XMLBodyContext(XMLImport &rImport) : XMLImportContext(rImport) { } rtl::Reference XMLBodyContext::CreateChildContext(const OUString &rName, const uno::Reference &/*xAttribs*/) { if (rName == "office:text") return new XMLBodyContentContext(mrImport); return nullptr; } /// Handler for . class XMLOfficeDocContext : public XMLImportContext { public: XMLOfficeDocContext(XMLImport &rImport); rtl::Reference CreateChildContext(const OUString &rName, const uno::Reference &/*xAttribs*/) override; // Handles metafile for a single page. void HandleFixedLayoutPage(const FixedLayoutPage &rPage, bool bFirst); }; XMLOfficeDocContext::XMLOfficeDocContext(XMLImport &rImport) : XMLImportContext(rImport) { } rtl::Reference XMLOfficeDocContext::CreateChildContext(const OUString &rName, const uno::Reference &/*xAttribs*/) { if (rName == "office:meta") return new XMLMetaDocumentContext(mrImport); if (rName == "office:automatic-styles") return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_AUTOMATIC); if (rName == "office:styles") return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_NONE); if (rName == "office:master-styles") return new XMLStylesContext(mrImport, XMLStylesContext::StyleType_NONE); if (rName == "office:font-face-decls") return new XMLFontFaceDeclsContext(mrImport); if (rName == "office:body") { if (mrImport.GetPageMetafiles().empty()) return new XMLBodyContext(mrImport); // Ignore text from doc model in the fixed layout case, instead // insert the page metafiles. bool bFirst = true; for (const auto &rPage : mrImport.GetPageMetafiles()) { HandleFixedLayoutPage(rPage, bFirst); if (bFirst) bFirst = false; } } return nullptr; } void XMLOfficeDocContext::HandleFixedLayoutPage(const FixedLayoutPage &rPage, bool bFirst) { uno::Reference xCtx = mrImport.GetComponentContext(); uno::Reference xSaxWriter = xml::sax::Writer::create(xCtx); if (!xSaxWriter.is()) return; uno::Sequence aArguments = { uno::makeAny>({comphelper::makePropertyValue("DTDString", false)}) }; uno::Reference xSVGWriter(xCtx->getServiceManager()->createInstanceWithArgumentsAndContext("com.sun.star.svg.SVGWriter", aArguments, xCtx), uno::UNO_QUERY); if (!xSVGWriter.is()) return; SvMemoryStream aMemoryStream; xSaxWriter->setOutputStream(new utl::OStreamWrapper(aMemoryStream)); xSVGWriter->write(xSaxWriter, rPage.aMetafile); // Have all the info, invoke the generator. librevenge::RVNGPropertyList aPageProperties; // Pixel -> inch. double fWidth = rPage.aCssPixels.getWidth(); fWidth /= 96; aPageProperties.insert("fo:page-width", fWidth); double fHeight = rPage.aCssPixels.getHeight(); fHeight /= 96; aPageProperties.insert("fo:page-height", fHeight); if (!rPage.aChapterNames.empty()) { // Name of chapters starting on this page. librevenge::RVNGPropertyListVector aChapterNames; for (const auto &rName : rPage.aChapterNames) { librevenge::RVNGPropertyList aChapter; aChapter.insert("librevenge:name", rName.toUtf8().getStr()); aChapterNames.append(aChapter); } aPageProperties.insert("librevenge:chapter-names", aChapterNames); } mrImport.GetGenerator().openPageSpan(aPageProperties); librevenge::RVNGPropertyList aParagraphProperties; if (!bFirst) // All pages except the first one needs a page break before the page // metafile. aParagraphProperties.insert("fo:break-before", "page"); mrImport.GetGenerator().openParagraph(aParagraphProperties); librevenge::RVNGPropertyList aImageProperties; aImageProperties.insert("librevenge:mime-type", "image/svg+xml"); librevenge::RVNGBinaryData aBinaryData; aBinaryData.append(static_cast(aMemoryStream.GetBuffer()), aMemoryStream.GetSize()); aImageProperties.insert("office:binary-data", aBinaryData); mrImport.GetGenerator().insertBinaryObject(aImageProperties); mrImport.GetGenerator().closeParagraph(); mrImport.GetGenerator().closePageSpan(); } XMLImport::XMLImport(const uno::Reference &xContext, librevenge::RVNGTextInterface &rGenerator, const OUString &rURL, const uno::Sequence &rDescriptor, const std::vector &rPageMetafiles) : mrGenerator(rGenerator), mxContext(xContext), mrPageMetafiles(rPageMetafiles) { uno::Sequence aFilterData; for (sal_Int32 i = 0; i < rDescriptor.getLength(); ++i) { if (rDescriptor[i].Name == "FilterData") { rDescriptor[i].Value >>= aFilterData; break; } } maMediaDir = FindMediaDir(rURL, aFilterData); OUString aMimeType; OUString aCoverImage = FindCoverImage(rURL, aMimeType, aFilterData); if (!aCoverImage.isEmpty()) { librevenge::RVNGBinaryData aBinaryData; SvFileStream aStream(aCoverImage, StreamMode::READ); SvMemoryStream aMemoryStream; aMemoryStream.WriteStream(aStream); aBinaryData.append(static_cast(aMemoryStream.GetBuffer()), aMemoryStream.GetSize()); librevenge::RVNGPropertyList aCoverImageProperties; aCoverImageProperties.insert("office:binary-data", aBinaryData); aCoverImageProperties.insert("librevenge:mime-type", aMimeType.toUtf8().getStr()); maCoverImages.append(aCoverImageProperties); } FindXMPMetadata(mxContext, rURL, aFilterData, maMetaData); mxUriReferenceFactory = uri::UriReferenceFactory::create(mxContext); } const librevenge::RVNGPropertyListVector &XMLImport::GetCoverImages() { return maCoverImages; } const librevenge::RVNGPropertyList &XMLImport::GetMetaData() { return maMetaData; } namespace { /// Finds out if a file URL exists. bool FileURLExists(const OUString &rURL) { SvFileStream aStream(rURL, StreamMode::READ); return aStream.IsOpen(); } } PopupState XMLImport::FillPopupData(const OUString &rURL, librevenge::RVNGPropertyList &rPropList) { uno::Reference xUriRef; try { xUriRef = mxUriReferenceFactory->parse(rURL); } catch (const uno::Exception &) { DBG_UNHANDLED_EXCEPTION("writerperfect", "XUriReference::parse() failed"); } bool bAbsolute = true; if (xUriRef.is()) bAbsolute = xUriRef->isAbsolute(); if (bAbsolute) return PopupState::NotConsumed; // Default case: relative URL, popup data was in the same directory as the // document at insertion time. OUString aAbs = maMediaDir + rURL; if (!FileURLExists(aAbs)) // Fallback case: relative URL, popup data was in the default media // directory at insertion time. aAbs = maMediaDir + "../" + rURL; if (!FileURLExists(aAbs)) // Relative link, but points to non-existing file: don't emit that to // librevenge, since it will be invalid anyway. return PopupState::Ignore; SvFileStream aStream(aAbs, StreamMode::READ); librevenge::RVNGBinaryData aBinaryData; SvMemoryStream aMemoryStream; aMemoryStream.WriteStream(aStream); aBinaryData.append(static_cast(aMemoryStream.GetBuffer()), aMemoryStream.GetSize()); rPropList.insert("office:binary-data", aBinaryData); INetURLObject aAbsURL(aAbs); OUString aMimeType = GetMimeType(aAbsURL.GetExtension()); rPropList.insert("librevenge:mime-type", aMimeType.toUtf8().getStr()); return PopupState::Consumed; } const std::vector &XMLImport::GetPageMetafiles() const { return mrPageMetafiles; } const uno::Reference &XMLImport::GetComponentContext() const { return mxContext; } rtl::Reference XMLImport::CreateContext(const OUString &rName, const uno::Reference &/*xAttribs*/) { if (rName == "office:document") return new XMLOfficeDocContext(*this); return nullptr; } librevenge::RVNGTextInterface &XMLImport::GetGenerator() const { return mrGenerator; } std::map &XMLImport::GetAutomaticTextStyles() { return maAutomaticTextStyles; } std::map &XMLImport::GetAutomaticParagraphStyles() { return maAutomaticParagraphStyles; } std::map &XMLImport::GetAutomaticCellStyles() { return maAutomaticCellStyles; } std::map &XMLImport::GetAutomaticColumnStyles() { return maAutomaticColumnStyles; } std::map &XMLImport::GetAutomaticRowStyles() { return maAutomaticRowStyles; } std::map &XMLImport::GetAutomaticTableStyles() { return maAutomaticTableStyles; } std::map &XMLImport::GetAutomaticGraphicStyles() { return maAutomaticGraphicStyles; } std::map &XMLImport::GetTextStyles() { return maTextStyles; } std::map &XMLImport::GetParagraphStyles() { return maParagraphStyles; } std::map &XMLImport::GetCellStyles() { return maCellStyles; } std::map &XMLImport::GetColumnStyles() { return maColumnStyles; } std::map &XMLImport::GetRowStyles() { return maRowStyles; } std::map &XMLImport::GetTableStyles() { return maTableStyles; } std::map &XMLImport::GetGraphicStyles() { return maGraphicStyles; } std::map &XMLImport::GetPageLayouts() { return maPageLayouts; } std::map &XMLImport::GetMasterStyles() { return maMasterStyles; } void XMLImport::startDocument() { mrGenerator.startDocument(librevenge::RVNGPropertyList()); } void XMLImport::endDocument() { mrGenerator.endDocument(); } void XMLImport::startElement(const OUString &rName, const uno::Reference &xAttribs) { rtl::Reference xContext; if (!maContexts.empty()) { if (maContexts.top().is()) xContext = maContexts.top()->CreateChildContext(rName, xAttribs); } else xContext = CreateContext(rName, xAttribs); if (xContext.is()) xContext->startElement(rName, xAttribs); maContexts.push(xContext); } void XMLImport::endElement(const OUString &rName) { if (maContexts.empty()) return; if (maContexts.top().is()) maContexts.top()->endElement(rName); maContexts.pop(); } void XMLImport::characters(const OUString &rChars) { if (maContexts.top().is()) maContexts.top()->characters(rChars); } void XMLImport::ignorableWhitespace(const OUString &/*rWhitespaces*/) { } void XMLImport::processingInstruction(const OUString &/*rTarget*/, const OUString &/*rData*/) { } void XMLImport::setDocumentLocator(const uno::Reference &/*xLocator*/) { } } // namespace exp } // namespace writerperfect /* vim:set shiftwidth=4 softtabstop=4 expandtab: */