tdf#139991: move 0-byte file handling to SfxFrameLoader_Impl::load

This centralizes the code that handles templates, and allows the empty
files to use default templates.

This partially reverts commits:

ada07f303e
  Author Miklos Vajna <vmiklos@collabora.com>
  Date   Wed Oct 28 14:54:52 2020 +0100
    tdf#123476 filter: try to detect 0-byte files based on extension

2854362f42
  Author Mike Kaganski <mike.kaganski@collabora.com>
  Date   Wed Jan 27 16:05:54 2021 +0100
   tdf#123476 filter: Also handle empty ODF

dff586735b
  Author Mike Kaganski <mike.kaganski@collabora.com>
  Date   Mon May 03 17:04:04 2021 +0200
    tdf#123476: also use filter by extension when its service is the same

The unit tests from these commits are retained and extended for templates.

Change-Id: I755738d2d5a6d6955d84d6e12f3accc017e0391f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132938
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
This commit is contained in:
Mike Kaganski 2022-04-13 08:40:23 +03:00
parent 15f70da655
commit 064f4fe82c
8 changed files with 170 additions and 115 deletions

View file

@ -21,6 +21,7 @@ $(eval $(call gb_CppunitTest_use_libraries,filter_textfilterdetect, \
cppu \
cppuhelper \
sal \
sfx \
test \
textfd \
tl \

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -13,8 +13,13 @@
#include <com/sun/star/document/XExtendedFilterDetection.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/sheet/XCellRangesAccess.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <comphelper/propertyvalue.hxx>
#include <sfx2/docfac.hxx>
#include <unotools/mediadescriptor.hxx>
#include <unotools/streamwrap.hxx>
#include <tools/stream.hxx>
@ -31,12 +36,8 @@ namespace
/// Test class for PlainTextFilterDetect.
class TextFilterDetectTest : public test::BootstrapFixture, public unotest::MacrosTest
{
uno::Reference<lang::XComponent> mxComponent;
public:
void setUp() override;
void tearDown() override;
uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
};
void TextFilterDetectTest::setUp()
@ -46,14 +47,6 @@ void TextFilterDetectTest::setUp()
mxDesktop.set(frame::Desktop::create(mxComponentContext));
}
void TextFilterDetectTest::tearDown()
{
if (mxComponent.is())
mxComponent->dispose();
test::BootstrapFixture::tearDown();
}
constexpr OUStringLiteral DATA_DIRECTORY = u"/filter/qa/unit/data/";
CPPUNIT_TEST_FIXTURE(TextFilterDetectTest, testTdf114428)
@ -78,68 +71,126 @@ CPPUNIT_TEST_FIXTURE(TextFilterDetectTest, testTdf114428)
CPPUNIT_TEST_FIXTURE(TextFilterDetectTest, testEmptyFile)
{
// Given an empty file, with a pptx extension
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.pptx";
const OUString sDataDirectory = m_directories.getURLFromSrc(DATA_DIRECTORY);
auto supportsService = [](const uno::Reference<lang::XComponent>& x, const OUString& s) {
return uno::Reference<lang::XServiceInfo>(x, uno::UNO_QUERY_THROW)->supportsService(s);
};
// Given an empty file, with a pptx extension
// When loading the file
getComponent() = loadFromDesktop(aURL);
auto xComponent = loadFromDesktop(sDataDirectory + "empty.pptx");
// Then make sure it is opened in Impress.
uno::Reference<lang::XServiceInfo> xServiceInfo(getComponent(), uno::UNO_QUERY);
CPPUNIT_ASSERT(xServiceInfo.is());
// Without the accompanying fix in place, this test would have failed, as it was opened in
// Writer instead.
CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.presentation.PresentationDocument"));
getComponent()->dispose();
CPPUNIT_ASSERT(supportsService(xComponent, "com.sun.star.presentation.PresentationDocument"));
xComponent->dispose();
// Now also test ODT
aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.odt";
getComponent() = loadFromDesktop(aURL);
xServiceInfo.set(getComponent(), uno::UNO_QUERY);
CPPUNIT_ASSERT(xServiceInfo.is());
xComponent = loadFromDesktop(sDataDirectory + "empty.odt");
// Make sure it opens in Writer.
CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextDocument"));
getComponent()->dispose();
CPPUNIT_ASSERT(supportsService(xComponent, "com.sun.star.text.TextDocument"));
xComponent->dispose();
// ... and ODS
aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.ods";
getComponent() = loadFromDesktop(aURL);
xServiceInfo.set(getComponent(), uno::UNO_QUERY);
CPPUNIT_ASSERT(xServiceInfo.is());
xComponent = loadFromDesktop(sDataDirectory + "empty.ods");
// Make sure it opens in Calc.
CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument"));
getComponent()->dispose();
CPPUNIT_ASSERT(supportsService(xComponent, "com.sun.star.sheet.SpreadsheetDocument"));
xComponent->dispose();
// ... and ODP
aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.odp";
getComponent() = loadFromDesktop(aURL);
xServiceInfo.set(getComponent(), uno::UNO_QUERY);
CPPUNIT_ASSERT(xServiceInfo.is());
xComponent = loadFromDesktop(sDataDirectory + "empty.odp");
// Without the accompanying fix in place, this test would have failed, as it was opened in
// Writer instead.
CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.presentation.PresentationDocument"));
getComponent()->dispose();
CPPUNIT_ASSERT(supportsService(xComponent, "com.sun.star.presentation.PresentationDocument"));
xComponent->dispose();
// ... and DOC
aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "empty.doc";
// Without the accompanying fix in place, this test would have failed, the import filter aborted
// loading.
getComponent() = loadFromDesktop(aURL);
xServiceInfo.set(getComponent(), uno::UNO_QUERY);
CPPUNIT_ASSERT(xServiceInfo.is());
CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextDocument"));
uno::Reference<frame::XModel> xModel(getComponent(), uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aArgs = xModel->getArgs();
comphelper::SequenceAsHashMap aMap(aArgs);
OUString aFilterName;
aMap["FilterName"] >>= aFilterName;
// Without the accompanying fix in place, this test would have failed with:
// - Expected: MS Word 97
// - Actual : MS WinWord 6.0
// i.e. opening worked, but saving back failed instead of producing a WW8 binary file.
CPPUNIT_ASSERT_EQUAL(OUString("MS Word 97"), aFilterName);
xComponent = loadFromDesktop(sDataDirectory + "empty.doc");
CPPUNIT_ASSERT(supportsService(xComponent, "com.sun.star.text.TextDocument"));
{
uno::Reference<frame::XModel> xModel(xComponent, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aArgs = xModel->getArgs();
comphelper::SequenceAsHashMap aMap(aArgs);
OUString aFilterName;
aMap["FilterName"] >>= aFilterName;
// Without the accompanying fix in place, this test would have failed with:
// - Expected: MS Word 97
// - Actual : MS WinWord 6.0
// i.e. opening worked, but saving back failed instead of producing a WW8 binary file.
CPPUNIT_ASSERT_EQUAL(OUString("MS Word 97"), aFilterName);
}
xComponent->dispose();
// Now test with default templates set
SfxObjectFactory::SetStandardTemplate("com.sun.star.presentation.PresentationDocument",
sDataDirectory + "impress.otp");
SfxObjectFactory::SetStandardTemplate("com.sun.star.text.TextDocument",
sDataDirectory + "writer.ott");
SfxObjectFactory::SetStandardTemplate("com.sun.star.sheet.SpreadsheetDocument",
sDataDirectory + "calc.ots");
xComponent = loadFromDesktop(sDataDirectory + "empty.pptx");
{
uno::Reference<drawing::XDrawPagesSupplier> xDoc(xComponent, uno::UNO_QUERY_THROW);
uno::Reference<drawing::XDrawPages> xPages(xDoc->getDrawPages(), uno::UNO_SET_THROW);
uno::Reference<drawing::XDrawPage> xPage(xPages->getByIndex(0), uno::UNO_QUERY_THROW);
uno::Reference<text::XTextRange> xBox(xPage->getByIndex(0), uno::UNO_QUERY_THROW);
// Make sure the template's text was loaded
CPPUNIT_ASSERT_EQUAL(OUString("Title of Impress template"), xBox->getString());
}
xComponent->dispose();
xComponent = loadFromDesktop(sDataDirectory + "empty.odt");
{
uno::Reference<text::XTextDocument> xDoc(xComponent, uno::UNO_QUERY_THROW);
uno::Reference<container::XEnumerationAccess> xEA(xDoc->getText(), uno::UNO_QUERY_THROW);
uno::Reference<container::XEnumeration> xEnum(xEA->createEnumeration(), uno::UNO_SET_THROW);
uno::Reference<text::XTextRange> xParagraph(xEnum->nextElement(), uno::UNO_QUERY_THROW);
// Make sure the template's text was loaded
CPPUNIT_ASSERT_EQUAL(OUString(u"Writer templates first line"), xParagraph->getString());
}
xComponent->dispose();
xComponent = loadFromDesktop(sDataDirectory + "empty.ods");
{
uno::Reference<sheet::XSpreadsheetDocument> xDoc(xComponent, uno::UNO_QUERY_THROW);
uno::Reference<sheet::XCellRangesAccess> xRA(xDoc->getSheets(), uno::UNO_QUERY_THROW);
uno::Reference<text::XTextRange> xC(xRA->getCellByPosition(0, 0, 0), uno::UNO_QUERY_THROW);
// Make sure the template's text was loaded
CPPUNIT_ASSERT_EQUAL(OUString(u"Calc templates first cell"), xC->getString());
}
xComponent->dispose();
xComponent = loadFromDesktop(sDataDirectory + "empty.odp");
{
uno::Reference<drawing::XDrawPagesSupplier> xDoc(xComponent, uno::UNO_QUERY_THROW);
uno::Reference<drawing::XDrawPages> xPages(xDoc->getDrawPages(), uno::UNO_SET_THROW);
uno::Reference<drawing::XDrawPage> xPage(xPages->getByIndex(0), uno::UNO_QUERY_THROW);
uno::Reference<text::XTextRange> xBox(xPage->getByIndex(0), uno::UNO_QUERY_THROW);
// Make sure the template's text was loaded
CPPUNIT_ASSERT_EQUAL(OUString("Title of Impress template"), xBox->getString());
}
xComponent->dispose();
xComponent = loadFromDesktop(sDataDirectory + "empty.doc");
{
uno::Reference<text::XTextDocument> xDoc(xComponent, uno::UNO_QUERY_THROW);
uno::Reference<container::XEnumerationAccess> xEA(xDoc->getText(), uno::UNO_QUERY_THROW);
uno::Reference<container::XEnumeration> xEnum(xEA->createEnumeration(), uno::UNO_SET_THROW);
uno::Reference<text::XTextRange> xParagraph(xEnum->nextElement(), uno::UNO_QUERY_THROW);
// Make sure the template's text was loaded
CPPUNIT_ASSERT_EQUAL(OUString(u"Writer templates first line"), xParagraph->getString());
}
xComponent->dispose();
}
}

View file

@ -20,8 +20,6 @@
#include <com/sun/star/io/XInputStream.hpp>
#include <cppuhelper/supportsservice.hxx>
#include <memory>
#include <sfx2/fcontnr.hxx>
#include <sfx2/docfilt.hxx>
constexpr OUStringLiteral WRITER_TEXT_FILTER = u"Text";
constexpr OUStringLiteral CALC_TEXT_FILTER = u"Text - txt - csv (StarCalc)";
@ -129,45 +127,6 @@ bool IsHTMLStream( const uno::Reference<io::XInputStream>& xInStream )
OString aToken = sHeader.copy( nStartOfTagIndex, i - nStartOfTagIndex );
return GetHTMLToken( OStringToOUString( aToken.toAsciiLowerCase(), RTL_TEXTENCODING_ASCII_US ) ) != HtmlTokenId::NONE;
}
/**
* Given an (empty) file URL in rMediaDesc and rExt, looks up the best filter type for it and
* writes the type name to rType, the filter name to rMediaDesc.
*/
bool HandleEmptyFileUrlByExtension(MediaDescriptor& rMediaDesc, const OUString& rExt,
OUString& rType, OUString& rService)
{
OUString aURL = rMediaDesc.getUnpackedValueOrDefault(MediaDescriptor::PROP_URL, OUString());
if (!tools::isEmptyFileUrl(aURL))
{
return false;
}
if (rExt.isEmpty())
{
return false;
}
// Requiring the export+preferred flags helps to find the relevant filter, e.g. .doc -> WW8 (and
// not WW6 or Mac_Word).
SfxFilterFlags nMust
= SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::PREFERED;
std::shared_ptr<const SfxFilter> pFilter(SfxFilterMatcher().GetFilter4Extension(rExt, nMust));
if (!pFilter)
{
// retry without PREFERRED so we can find at least something for 0-byte *.ods
nMust = SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT;
pFilter = SfxFilterMatcher().GetFilter4Extension(rExt, nMust);
if (!pFilter)
return false;
}
rMediaDesc[MediaDescriptor::PROP_FILTERNAME] <<= pFilter->GetFilterName();
rType = pFilter->GetTypeName();
rService = pFilter->GetServiceName();
return true;
}
}
PlainTextFilterDetect::PlainTextFilterDetect() {}
@ -226,15 +185,7 @@ OUString SAL_CALL PlainTextFilterDetect::detect(uno::Sequence<beans::PropertyVal
OUString aName = aParser.getName().toAsciiLowerCase();
// Decide which filter to use based on the document service first,
// then on extension if that's not available. Make exception for 0-byte files
// whose extensions are handled by the same service as passed document service.
OUString aEmptyType, aEmptyService;
bool bEmpty = HandleEmptyFileUrlByExtension(aMediaDesc, aExt, aEmptyType, aEmptyService);
if (bEmpty && aDocService == aEmptyService)
{
aDocService.clear(); // don't fallback to text filter, use extension-based match
// TODO: maybe reset aExt when it's "xls"
}
// then on extension if that's not available.
if (aDocService == CALC_DOCSERVICE)
aMediaDesc[MediaDescriptor::PROP_FILTERNAME] <<= OUString(CALC_TEXT_FILTER);
@ -242,8 +193,6 @@ OUString SAL_CALL PlainTextFilterDetect::detect(uno::Sequence<beans::PropertyVal
aMediaDesc[MediaDescriptor::PROP_FILTERNAME] <<= OUString(WRITER_TEXT_FILTER);
else if (aExt == "csv" || aExt == "tsv" || aExt == "tab" || aExt == "xls" || aName.endsWith(".csv.gz"))
aMediaDesc[MediaDescriptor::PROP_FILTERNAME] <<= OUString(CALC_TEXT_FILTER);
else if (bEmpty)
aType = aEmptyType; // aMediaDesc is already updated in HandleEmptyFileUrlByExtension
else
aMediaDesc[MediaDescriptor::PROP_FILTERNAME] <<= OUString(WRITER_TEXT_FILTER);
}

View file

@ -429,7 +429,7 @@ bool SfxObjectShell::InitNew( const uno::Reference< embed::XStorage >& xStorage
bool SfxObjectShell::Load( SfxMedium& rMedium )
{
return GeneralInit_Impl(rMedium.GetStorage(), !tools::isEmptyFileUrl(rMedium.GetName()));
return GeneralInit_Impl(rMedium.GetStorage(), true);
}
void SfxObjectShell::DoInitUnitTest()
@ -662,9 +662,7 @@ bool SfxObjectShell::DoLoad( SfxMedium *pMed )
bWarnMediaTypeFallback = false;
}
if (bWarnMediaTypeFallback
|| (!tools::isEmptyFileUrl(pMedium->GetName())
&& !xStorage->getElementNames().hasElements()))
if (bWarnMediaTypeFallback || !xStorage->getElementNames().hasElements())
SetError(ERRCODE_IO_BROKENPACKAGE);
}
catch( uno::Exception& )
@ -2260,11 +2258,7 @@ bool SfxObjectShell::ImportFrom(SfxMedium& rMedium,
// #i119492# During loading, some OLE objects like chart will be set
// modified flag, so needs to reset the flag to false after loading
bool bRtn = true;
if (!tools::isEmptyFileUrl(rMedium.GetName()))
{
bRtn = xLoader->filter(aArgs);
}
bool bRtn = xLoader->filter(aArgs);
const uno::Sequence < OUString > aNames = GetEmbeddedObjectContainer().GetObjectNames();
for ( const auto& rName : aNames )
{

View file

@ -21,6 +21,7 @@
#include <sfx2/app.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/docfac.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/doctempl.hxx>
#include <sfx2/fcontnr.hxx>
@ -55,8 +56,11 @@
#include <rtl/ref.hxx>
#include <sal/log.hxx>
#include <svl/eitem.hxx>
#include <svl/stritem.hxx>
#include <unotools/moduleoptions.hxx>
#include <tools/diagnose_ex.h>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
#include <vcl/svapp.hxx>
using namespace com::sun::star;
@ -585,6 +589,25 @@ Reference< XController2 > SfxFrameLoader_Impl::impl_createDocumentView( const Re
return xController;
}
std::shared_ptr<const SfxFilter> getEmptyURLFilter(const OUString& sURL)
{
INetURLObject aParser(sURL);
const OUString aExt = aParser.getExtension(INetURLObject::LAST_SEGMENT, true,
INetURLObject::DecodeMechanism::WithCharset);
const SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher();
// Requiring the export+preferred flags helps to find the relevant filter, e.g. .doc -> WW8 (and
// not WW6 or Mac_Word).
std::shared_ptr<const SfxFilter> pFilter = rMatcher.GetFilter4Extension(
aExt, SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::PREFERED);
if (!pFilter)
{
// retry without PREFERED so we can find at least something for 0-byte *.ods
pFilter
= rMatcher.GetFilter4Extension(aExt, SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT);
}
return pFilter;
}
sal_Bool SAL_CALL SfxFrameLoader_Impl::load( const Sequence< PropertyValue >& rArgs,
const Reference< XFrame >& _rTargetFrame )
@ -608,6 +631,7 @@ sal_Bool SAL_CALL SfxFrameLoader_Impl::load( const Sequence< PropertyValue >& rA
// check for factory URLs to create a new doc, instead of loading one
const OUString sURL = aDescriptor.getOrDefault( "URL", OUString() );
const bool bIsFactoryURL = sURL.startsWith( "private:factory/" );
std::shared_ptr<const SfxFilter> pEmptyURLFilter;
bool bInitNewModel = bIsFactoryURL;
const bool bIsDefault = bIsFactoryURL && !bExternalModel;
if (!aDescriptor.has("Replaceable"))
@ -640,6 +664,28 @@ sal_Bool SAL_CALL SfxFrameLoader_Impl::load( const Sequence< PropertyValue >& rA
{
// compatibility
aDescriptor.put( "FileName", aDescriptor.get( "URL" ) );
if (!bIsFactoryURL && !bExternalModel && tools::isEmptyFileUrl(sURL))
{
pEmptyURLFilter = getEmptyURLFilter(sURL);
if (pEmptyURLFilter)
{
aDescriptor.put("DocumentService", pEmptyURLFilter->GetServiceName());
if (impl_determineTemplateDocument(aDescriptor))
{
// if the media descriptor allowed us to determine a template document
// to create the new document from, then do not init a new document model
// from scratch (below), but instead load the template document
bInitNewModel = false;
// Do not try to load from empty UCB content
aDescriptor.remove("UCBContent");
}
else
{
bInitNewModel = true;
}
}
}
}
bool bLoadSuccess = false;
@ -692,6 +738,20 @@ sal_Bool SAL_CALL SfxFrameLoader_Impl::load( const Sequence< PropertyValue >& rA
const SfxObjectShellRef xDoc = impl_findObjectShell( xModel );
ENSURE_OR_THROW( xDoc.is(), "no SfxObjectShell for the given model" );
if (pEmptyURLFilter)
{
// Detach the medium from the template, and set proper document name and filter
auto pMedium = xDoc->GetMedium();
auto pItemSet = pMedium->GetItemSet();
pItemSet->ClearItem(SID_TEMPLATE);
pItemSet->Put(SfxStringItem(SID_FILTER_NAME, pEmptyURLFilter->GetFilterName()));
pMedium->SetName(sURL, true);
pMedium->SetFilter(pEmptyURLFilter);
pMedium->GetInitFileDate(true);
xDoc->SetLoading(SfxLoadedFlags::NONE);
xDoc->FinishedLoading();
}
// ensure the ID of the to-be-created view is in the descriptor, if possible
const SfxInterfaceId nViewId = impl_determineEffectiveViewId_nothrow( *xDoc, aDescriptor );
const sal_Int16 nViewNo = xDoc->GetFactory().GetViewNo_Impl( nViewId, 0 );