/* -*- 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 #include #include "xmlfmt.hxx" #include "xmlictxt.hxx" #include "xmlmetai.hxx" #include "xmltext.hxx" using namespace com::sun::star; namespace writerperfect::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. auto pProp = std::find_if( rFilterData.begin(), rFilterData.end(), [](const beans::PropertyValue& rProp) { return rProp.Name == "RVNGMediaDir"; }); if (pProp != rFilterData.end()) pProp->Value >>= aMediaDir; 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. auto pProp = std::find_if( rFilterData.begin(), rFilterData.end(), [](const beans::PropertyValue& rProp) { return rProp.Name == "RVNGCoverImage"; }); if (pProp != rFilterData.end()) pProp->Value >>= aRet; if (!aRet.isEmpty()) { INetURLObject aRetURL(aRet); rMimeType = GetMimeType(aRetURL.GetFileExtension()); 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 = { u"gif", u"jpg", u"png", u"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 (const auto& rProp : rFilterData) { if (rProp.Name == "RVNGIdentifier") { rProp.Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:identifier", aValue.toUtf8().getStr()); } else if (rProp.Name == "RVNGTitle") { rProp.Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:title", aValue.toUtf8().getStr()); } else if (rProp.Name == "RVNGInitialCreator") { rProp.Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("meta:initial-creator", aValue.toUtf8().getStr()); } else if (rProp.Name == "RVNGLanguage") { rProp.Value >>= aValue; if (!aValue.isEmpty()) rMetaData.insert("dc:language", aValue.toUtf8().getStr()); } else if (rProp.Name == "RVNGDate") { rProp.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(GetImport()); return nullptr; } namespace { /// 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(GetImport()); if (rName == "office:automatic-styles") return new XMLStylesContext(GetImport(), XMLStylesContext::StyleType_AUTOMATIC); if (rName == "office:styles") return new XMLStylesContext(GetImport(), XMLStylesContext::StyleType_NONE); if (rName == "office:master-styles") return new XMLStylesContext(GetImport(), XMLStylesContext::StyleType_NONE); if (rName == "office:font-face-decls") return new XMLFontFaceDeclsContext(GetImport()); if (rName == "office:body") { if (GetImport().GetPageMetafiles().empty()) return new XMLBodyContext(GetImport()); // Ignore text from doc model in the fixed layout case, instead // insert the page metafiles. bool bFirst = true; for (const auto& rPage : GetImport().GetPageMetafiles()) { HandleFixedLayoutPage(rPage, bFirst); if (bFirst) bFirst = false; } } return nullptr; } void XMLOfficeDocContext::HandleFixedLayoutPage(const FixedLayoutPage& rPage, bool bFirst) { uno::Reference xCtx = GetImport().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); } GetImport().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"); GetImport().GetGenerator().openParagraph(aParagraphProperties); librevenge::RVNGPropertyList aImageProperties; aImageProperties.insert("librevenge:mime-type", "image/svg+xml"); librevenge::RVNGBinaryData aBinaryData; aBinaryData.append(static_cast(aMemoryStream.GetData()), aMemoryStream.GetSize()); aImageProperties.insert("office:binary-data", aBinaryData); GetImport().GetGenerator().insertBinaryObject(aImageProperties); GetImport().GetGenerator().closeParagraph(); GetImport().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) , mbIsInPageSpan(false) , mrPageMetafiles(rPageMetafiles) { uno::Sequence aFilterData; auto pDescriptor = std::find_if( rDescriptor.begin(), rDescriptor.end(), [](const beans::PropertyValue& rProp) { return rProp.Name == "FilterData"; }); if (pDescriptor != rDescriptor.end()) pDescriptor->Value >>= aFilterData; 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.GetData()), 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() const { return maCoverImages; } const librevenge::RVNGPropertyList& XMLImport::GetMetaData() const { 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.GetData()), aMemoryStream.GetSize()); rPropList.insert("office:binary-data", aBinaryData); INetURLObject aAbsURL(aAbs); OUString aMimeType = GetMimeType(aAbsURL.GetFileExtension()); 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*/) {} void XMLImport::HandlePageSpan(const librevenge::RVNGPropertyList& rPropertyList) { OUString sMasterPageName; OUString sLayoutName; if (rPropertyList["style:master-page-name"]) sMasterPageName = OStringToOUString( rPropertyList["style:master-page-name"]->getStr().cstr(), RTL_TEXTENCODING_UTF8); else if (!GetIsInPageSpan()) sMasterPageName = "Standard"; if (sMasterPageName.getLength()) { librevenge::RVNGPropertyList& rMasterPage = GetMasterStyles()[sMasterPageName]; if (rMasterPage["style:page-layout-name"]) { sLayoutName = OStringToOUString(rMasterPage["style:page-layout-name"]->getStr().cstr(), RTL_TEXTENCODING_UTF8); } } if (sLayoutName.getLength()) { librevenge::RVNGPropertyList& rPageLayout = GetPageLayouts()[sLayoutName]; if (GetIsInPageSpan()) GetGenerator().closePageSpan(); GetGenerator().openPageSpan(rPageLayout); mbIsInPageSpan = true; } } } // namespace writerperfect /* vim:set shiftwidth=4 softtabstop=4 expandtab: */