sc/qa/unit/data/xls/tdf90299.xls      |binary
 sc/qa/unit/subsequent_export_test.cxx |   77 ++++++++++++++++++++++++++++++++++
 sc/source/filter/excel/xehelper.cxx   |   76 ++++++++++++++++++++++-----------
 3 files changed, 129 insertions(+), 24 deletions(-)

New commits:
commit 59ddcb0445da12f2b912f1a8a876117fec112c41
Author:     Łukasz Leszko <leszko.lu...@gmail.com>
AuthorDate: Thu Mar 17 17:13:24 2022 +0100
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Tue Aug 30 06:34:06 2022 +0200

    tdf#90299 Fix saving external links as relative
    
    In current build some links to external xls files are not saved as relative 
even if
    "Save URLs relative to file system" option is checked. This patch aims to
    solve this issue.
    
    Change-Id: I6d0984bdcdeef57b227c8ab1353e002fa4355fc9
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131711
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/sc/qa/unit/data/xls/tdf90299.xls b/sc/qa/unit/data/xls/tdf90299.xls
new file mode 100644
index 000000000000..0028e11cfd51
Binary files /dev/null and b/sc/qa/unit/data/xls/tdf90299.xls differ
diff --git a/sc/qa/unit/subsequent_export_test.cxx 
b/sc/qa/unit/subsequent_export_test.cxx
index 15155636d2a1..b460319bd0a3 100644
--- a/sc/qa/unit/subsequent_export_test.cxx
+++ b/sc/qa/unit/subsequent_export_test.cxx
@@ -44,6 +44,10 @@
 #include <editeng/fhgtitem.hxx>
 #include <editeng/udlnitem.hxx>
 #include <editeng/colritem.hxx>
+#include <osl/file.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <unotools/tempfile.hxx>
 #include <unotools/useroptions.hxx>
 #include <tools/datetime.hxx>
 
@@ -182,6 +186,7 @@ public:
     void testPreserveTextWhitespace2XLSX();
     void testTdf113646();
     void testDateStandardfilterXLSX();
+    void testTdf90299();
 
     CPPUNIT_TEST_SUITE(ScExportTest);
     CPPUNIT_TEST(test);
@@ -287,6 +292,7 @@ public:
     CPPUNIT_TEST(testMoveCellAnchoredShapesODS);
     CPPUNIT_TEST(testTdf113646);
     CPPUNIT_TEST(testDateStandardfilterXLSX);
+    CPPUNIT_TEST(testTdf90299);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -4519,6 +4525,77 @@ void ScExportTest::testDateStandardfilterXLSX()
     xDocSh->DoClose();
 }
 
+void ScExportTest::testTdf90299()
+{
+    utl::TempFile aTmpDirectory1(nullptr, true);
+    utl::TempFile aTmpDirectory2(nullptr, true);
+    utl::TempFile aSavedFile(&aTmpDirectory1.GetURL());
+
+    struct
+    {
+        void checkFormula(ScDocShellRef xShell, OUString aExpectedFormula)
+        {
+            CPPUNIT_ASSERT(xShell.is());
+            xShell->ReloadAllLinks();
+
+            ScDocument& rDoc = xShell->GetDocument();
+
+            ScAddress aPos(0, 0, 0);
+            ScTokenArray* pCode = getTokens(rDoc, aPos);
+            CPPUNIT_ASSERT_MESSAGE("empty token array", pCode);
+
+            OUString aFormula = toString(rDoc, aPos, *pCode, 
rDoc.GetGrammar());
+
+            CPPUNIT_ASSERT_EQUAL(aExpectedFormula, aFormula);
+        }
+
+    } aCheckShell;
+
+    OUString aReferencedFileURL;
+    OUString aReferencingFileURL;
+    createFileURL(u"tdf90299.", u"xls", aReferencingFileURL);
+
+    auto eError = osl::File::copy(aReferencingFileURL, aTmpDirectory1.GetURL() 
+ "/tdf90299.xls");
+    CPPUNIT_ASSERT_EQUAL(osl::File::E_None, eError);
+
+    aReferencingFileURL = aTmpDirectory1.GetURL() + "/tdf90299.xls";
+    aReferencedFileURL = aTmpDirectory1.GetURL() + "/dummy.xls";
+
+    ScDocShellRef xShell = load(aReferencingFileURL, FORMAT_XLS);
+    aCheckShell.checkFormula(xShell, "'" + aReferencedFileURL + 
"'#$Sheet1.A1");
+
+    aReferencingFileURL = aSavedFile.GetURL();
+
+    FileFormat afilterFormat = 
ScBootstrapFixture::getFileFormats()[FORMAT_XLS];
+    OUString aFilterName(afilterFormat.pFilterName, 
strlen(afilterFormat.pFilterName),
+                         RTL_TEXTENCODING_UTF8);
+    OUString aFilterType(afilterFormat.pTypeName, 
strlen(afilterFormat.pTypeName),
+                         RTL_TEXTENCODING_UTF8);
+
+    SfxMedium aStoreMedium(aReferencingFileURL, StreamMode::STD_WRITE);
+
+    auto pExportFilter = std::make_shared<SfxFilter>(
+        aFilterName, OUString(), afilterFormat.nFormatType, 
SotClipboardFormatId::NONE, aFilterType,
+        OUString(), OUString(), "private:factory/scalc*");
+    pExportFilter->SetVersion(SOFFICE_FILEFORMAT_CURRENT);
+
+    aStoreMedium.SetFilter(pExportFilter);
+
+    xShell->DoSaveAs(aStoreMedium);
+    xShell->DoClose();
+
+    eError = osl::File::copy(aReferencingFileURL, aTmpDirectory2.GetURL() + 
"/tdf90299.xls");
+    CPPUNIT_ASSERT_EQUAL(osl::File::E_None, eError);
+
+    aReferencingFileURL = aTmpDirectory2.GetURL() + "/tdf90299.xls";
+    aReferencedFileURL = aTmpDirectory2.GetURL() + "/dummy.xls";
+
+    xShell = load(aReferencingFileURL, FORMAT_XLS);
+    aCheckShell.checkFormula(xShell, "'" + aReferencedFileURL + 
"'#$Sheet1.A1");
+
+    xShell->DoClose();
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(ScExportTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/filter/excel/xehelper.cxx 
b/sc/source/filter/excel/xehelper.cxx
index 2e5ebba684c4..2025e65a7bd2 100644
--- a/sc/source/filter/excel/xehelper.cxx
+++ b/sc/source/filter/excel/xehelper.cxx
@@ -23,6 +23,10 @@
 
 #include <com/sun/star/i18n/XBreakIterator.hpp>
 #include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/uri/XUriReference.hpp>
+#include <com/sun/star/uri/XUriReferenceFactory.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+#include <comphelper/processfactory.hxx>
 #include <o3tl/string_view.hxx>
 #include <sfx2/objsh.hxx>
 #include <vcl/font.hxx>
@@ -40,6 +44,7 @@
 #include <editeng/escapementitem.hxx>
 #include <editeng/svxfont.hxx>
 #include <editeng/editids.hrc>
+#include <osl/file.hxx>
 
 #include <document.hxx>
 #include <docpool.hxx>
@@ -887,34 +892,26 @@ namespace {
 /** Encodes special parts of the path, i.e. directory separators and volume 
names.
     @param pTableName  Pointer to a table name to be encoded in this path, or 
0. */
 OUString lclEncodeDosPath(
-    XclBiff eBiff, std::u16string_view path, std::u16string_view rBase, const 
OUString* pTableName)
+    XclBiff eBiff, std::u16string_view path, bool bIsRel, const OUString* 
pTableName)
 {
     OUStringBuffer aBuf;
 
     if (!path.empty())
     {
-        std::u16string_view aOldPath = path;
         aBuf.append(EXC_URLSTART_ENCODED);
 
-        if ( aOldPath.length() > 2 && o3tl::starts_with(aOldPath, u"\\\\") )
+        if ( path.length() > 2 && o3tl::starts_with(path, u"\\\\") )
         {
             // UNC
             aBuf.append(EXC_URL_DOSDRIVE).append('@');
-            aOldPath = aOldPath.substr(2);
+            path = path.substr(2);
         }
-        else if ( aOldPath.length() > 2 && 
o3tl::starts_with(aOldPath.substr(1), u":\\") )
+        else if ( path.length() > 2 && o3tl::starts_with(path.substr(1), 
u":\\") )
         {
-            // drive letter
-            sal_Unicode cThisDrive = rBase.empty() ? ' ' : rBase[0];
-            sal_Unicode cDrive = aOldPath[0];
-            if (cThisDrive == cDrive)
-                // This document and the referenced document are under the 
same drive.
-                aBuf.append(EXC_URL_DRIVEROOT);
-            else
-                aBuf.append(EXC_URL_DOSDRIVE).append(cDrive);
-            aOldPath = aOldPath.substr(3);
+            aBuf.append(EXC_URL_DOSDRIVE).append(path[0]);
+            path = path.substr(3);
         }
-        else
+        else if ( !bIsRel )
         {
             // URL probably points to a document on a Unix-like file system
             aBuf.append(EXC_URL_DRIVEROOT);
@@ -922,23 +919,23 @@ OUString lclEncodeDosPath(
 
         // directories
         auto nPos = std::u16string_view::npos;
-        while((nPos = aOldPath.find('\\')) != std::u16string_view::npos)
+        while((nPos = path.find('\\')) != std::u16string_view::npos)
         {
-            if ( o3tl::starts_with(aOldPath, u"..") )
+            if ( o3tl::starts_with(path, u"..") )
                 // parent dir (NOTE: the MS-XLS spec doesn't mention this, and
                 // Excel seems confused by this token).
                 aBuf.append(EXC_URL_PARENTDIR);
             else
-                aBuf.append(aOldPath.substr(0,nPos)).append(EXC_URL_SUBDIR);
+                aBuf.append(path.substr(0,nPos)).append(EXC_URL_SUBDIR);
 
-            aOldPath = aOldPath.substr(nPos + 1);
+            path = path.substr(nPos + 1);
         }
 
         // file name
         if (pTableName)    // enclose file name in brackets if table name 
follows
-            aBuf.append('[').append(aOldPath).append(']');
+            aBuf.append('[').append(path).append(']');
         else
-            aBuf.append(aOldPath);
+            aBuf.append(path);
     }
     else    // empty URL -> self reference
     {
@@ -968,13 +965,44 @@ OUString lclEncodeDosPath(
     return aBuf.makeStringAndClear();
 }
 
+bool isUrlRelative(const OUString& aUrl)
+{
+    css::uno::Reference<css::uri::XUriReferenceFactory> xUriFactory(
+        css::uri::UriReferenceFactory::create(
+            comphelper::getProcessComponentContext()));
+    css::uno::Reference<css::uri::XUriReference> 
xUri(xUriFactory->parse(aUrl));
+
+    return !xUri->isAbsolute();
+}
+
 } // namespace
 
 OUString XclExpUrlHelper::EncodeUrl( const XclExpRoot& rRoot, 
std::u16string_view rAbsUrl, const OUString* pTableName )
 {
-    OUString aDosPath = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos);
-    OUString aDosBase = 
INetURLObject(rRoot.GetBasePath()).getFSysPath(FSysStyle::Dos);
-    return lclEncodeDosPath(rRoot.GetBiff(), aDosPath, aDosBase, pTableName);
+    OUString aDosPath;
+    bool bIsRel = false;
+
+    if (rRoot.IsRelUrl())
+    {
+        OUString aUrlPath = INetURLObject::GetRelURL(
+            rRoot.GetBasePath(), OUString(rAbsUrl),
+            INetURLObject::EncodeMechanism::All,
+            INetURLObject::DecodeMechanism::NONE,
+            RTL_TEXTENCODING_UTF8, FSysStyle::Detect
+        );
+
+        if (isUrlRelative(aUrlPath))
+        {
+            bIsRel = true;
+            osl::FileBase::getSystemPathFromFileURL(aUrlPath, aDosPath);
+            aDosPath = aDosPath.replaceAll(u"/", u"\\");
+        }
+    }
+
+    if (!bIsRel)
+        aDosPath = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos);
+
+    return lclEncodeDosPath(rRoot.GetBiff(), aDosPath, bIsRel, pTableName);
 }
 
 OUString XclExpUrlHelper::EncodeDde( std::u16string_view rApplic, 
std::u16string_view rTopic )

Reply via email to