sw/qa/extras/htmlexport/htmlexport.cxx |   65 ++++++++++++++++
 sw/source/filter/html/htmlatr.cxx      |   30 +++++++
 sw/source/filter/html/wrthtml.cxx      |  133 +++++++++++++++++++++++++--------
 sw/source/filter/html/wrthtml.hxx      |    5 +
 4 files changed, 203 insertions(+), 30 deletions(-)

New commits:
commit 02516860272521f1bb83f4744d9ad5e2f6435f24
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Feb 22 17:06:39 2022 +0100
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Thu Jul 7 17:04:50 2022 +0200

    sw HTML export: handle LeadingTabWidth without FilterOptions
    
    LeadingTabWidth is meant to be useful for plain HTML as well, but the
    old code only considered it when FilterOptions was set (typically to
    enable th XHTML mode).
    
    Thanks Stephan Bergmann for noticing that the u"" SAL_NEWLINE_STRING
    "\xa0\xa0 test" form would not work with MSVC.
    
    (cherry picked from commit 89667371e2c773760e7f7496590f55e7da062cb7)
    
    Change-Id: I89a8b266d9a0f543f6022d82cf043bda4e6e639f
    (cherry picked from commit 2bd52c78a707d4229543e3b00e706176f7ca2367)

diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx 
b/sw/qa/extras/htmlexport/htmlexport.cxx
index 25583638aa44..af92c6e1859c 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -1707,6 +1707,33 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testLeadingTab)
     assertXPathContent(pXmlDoc, "//reqif-xhtml:p[3]", u"thi \t rd");
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testLeadingTabHTML)
+{
+    // Given a document with leading tabs:
+    loadURL("private:factory/swriter", nullptr);
+    SwXTextDocument* pTextDoc = 
dynamic_cast<SwXTextDocument*>(mxComponent.get());
+    SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->Insert("\t test");
+
+    // When exporting to plain HTML, using LeadingTabWidth=2:
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML 
(StarWriter)")),
+        comphelper::makePropertyValue("LeadingTabWidth", 
static_cast<sal_Int32>(2)),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Then make sure that leading tabs are replaced with 2 nbsps:
+    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
+    CPPUNIT_ASSERT(pHtmlDoc);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: <newline><nbsp><nbsp><space>test
+    // - Actual  : <newline><tab><space>test
+    // i.e. the leading tab was not replaced by 2 nbsps.
+    assertXPathContent(pHtmlDoc, "/html/body/p", SAL_NEWLINE_STRING u"\xa0\xa0 
test");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/wrthtml.cxx 
b/sw/source/filter/html/wrthtml.cxx
index 62e43946a49c..becaf51cbe83 100644
--- a/sw/source/filter/html/wrthtml.cxx
+++ b/sw/source/filter/html/wrthtml.cxx
@@ -187,12 +187,11 @@ void SwHTMLWriter::SetupFilterOptions(SfxMedium& rMedium)
         return;
 
     const SfxPoolItem* pItem;
-    if (pSet->GetItemState( SID_FILE_FILTEROPTIONS, true, &pItem ) != 
SfxItemState::SET)
-        return;
-
-
-    const OUString sFilterOptions = static_cast<const 
SfxStringItem*>(pItem)->GetValue();
-    SetupFilterOptions(sFilterOptions);
+    if (pSet->GetItemState(SID_FILE_FILTEROPTIONS, true, &pItem) == 
SfxItemState::SET)
+    {
+        const OUString sFilterOptions = static_cast<const 
SfxStringItem*>(pItem)->GetValue();
+        SetupFilterOptions(sFilterOptions);
+    }
 
     SetupFilterFromPropertyValues(rMedium.GetArgs());
 }
commit bafe6d1c933a6256a348e9a9d9c5b68cfd46887b
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Feb 15 17:07:45 2022 +0100
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Thu Jul 7 17:04:49 2022 +0200

    sw HTML export: add a new LeadingTabWidth option
    
    This is a simple way to not loose indentation done with tabs (e.g.
    source code) during the HTML export.
    
    A more complex way would be ask the layout for the tab portion width,
    ask VCL what's the size of an nbsp glyph and then act accordingly, which
    is is not done here.
    
    (cherry picked from commit 505f5db522f8406715f455d8007d014073a99097)
    
    Change-Id: I2a5c0512e9e5541e55e10f29952679bf05d63f1b
    (cherry picked from commit 5fa9481a203edf21bd7fe2373fdc51312bb357a4)

diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx 
b/sw/qa/extras/htmlexport/htmlexport.cxx
index cb2d697d7664..25583638aa44 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -1669,6 +1669,44 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, 
testTableBackground)
     assertXPathNoAttribute(pXmlDoc, 
"//reqif-xhtml:table[2]/reqif-xhtml:tr[2]", "style");
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testLeadingTab)
+{
+    // Given a document with leading tabs:
+    loadURL("private:factory/swriter", nullptr);
+    SwXTextDocument* pTextDoc = 
dynamic_cast<SwXTextDocument*>(mxComponent.get());
+    SwDoc* pDoc = pTextDoc->GetDocShell()->GetDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    pWrtShell->Insert("\t first");
+    pWrtShell->SplitNode();
+    pWrtShell->Insert("\t\t second");
+    pWrtShell->SplitNode();
+    pWrtShell->Insert("thi \t rd");
+
+    // When exporting to HTML, using LeadingTabWidth=2:
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML 
(StarWriter)")),
+        comphelper::makePropertyValue("FilterOptions", 
OUString("xhtmlns=reqif-xhtml")),
+        comphelper::makePropertyValue("LeadingTabWidth", 
static_cast<sal_Int32>(2)),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Then make sure that leading tabs are replaced with 2 nbsps:
+    SvMemoryStream aStream;
+    HtmlExportTest::wrapFragment(maTempFile, aStream);
+    xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream);
+    CPPUNIT_ASSERT(pDoc);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: <nbsp><nbsp><space>first
+    // - Actual  : <tab><space>first
+    // i.e. the leading tab was not replaced by 2 nbsps.
+    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[1]", u"\xa0\xa0 first");
+    // Test a leading tab that is not at the start of the paragraph:
+    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[2]", u"\xa0\xa0\xa0\xa0 
second");
+    // Test a tab which is not leading:
+    assertXPathContent(pXmlDoc, "//reqif-xhtml:p[3]", u"thi \t rd");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index 46abffd664a9..31a28db4f9b3 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -2358,6 +2358,8 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
     {
         HTMLOutContext aContext( rHTMLWrt.m_eDestEnc );
 
+        // Tabs are leading till there is a non-tab since the start of the 
paragraph.
+        bool bLeadingTab = true;
         for( ; nStrPos < nEnd; nStrPos++ )
         {
             // output the frames that are anchored to the current position
@@ -2491,7 +2493,33 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
                         rHTMLWrt.OutPointFieldmarks(aMarkPos);
                     }
                     else
-                        HTMLOutFuncs::Out_Char( rWrt.Strm(), c, aContext, 
&rHTMLWrt.m_aNonConvertableCharacters );
+                    {
+                        bool bConsumed = false;
+                        if (c == '\t')
+                        {
+                            if (bLeadingTab && 
rHTMLWrt.m_nLeadingTabWidth.has_value())
+                            {
+                                // Consume a tab if it's leading and we know 
the number of NBSPs to
+                                // be used as a replacement.
+                                for (sal_Int32 i = 0; i < 
*rHTMLWrt.m_nLeadingTabWidth; ++i)
+                                {
+                                    rWrt.Strm().WriteCharPtr("&#160;");
+                                }
+                                bConsumed = true;
+                            }
+                        }
+                        else
+                        {
+                            // Not a tab -> later tabs are no longer leading.
+                            bLeadingTab = false;
+                        }
+
+                        if (!bConsumed)
+                        {
+                            HTMLOutFuncs::Out_Char(rWrt.Strm(), c, aContext,
+                                                   
&rHTMLWrt.m_aNonConvertableCharacters);
+                        }
+                    }
 
                     // if a paragraph's last character is a hard line break
                     // then we need to add an extra <br>
diff --git a/sw/source/filter/html/wrthtml.cxx 
b/sw/source/filter/html/wrthtml.cxx
index e5ac25005de0..62e43946a49c 100644
--- a/sw/source/filter/html/wrthtml.cxx
+++ b/sw/source/filter/html/wrthtml.cxx
@@ -306,6 +306,14 @@ void SwHTMLWriter::SetupFilterFromPropertyValues(
         // XHTML namespace implies XHTML.
         mbXHTML = true;
     }
+
+    it = aStoreMap.find("LeadingTabWidth");
+    if (it != aStoreMap.end())
+    {
+        sal_Int32 nVal{};
+        it->second >>= nVal;
+        m_nLeadingTabWidth.emplace(nVal);
+    }
 }
 
 ErrCode SwHTMLWriter::WriteStream()
diff --git a/sw/source/filter/html/wrthtml.hxx 
b/sw/source/filter/html/wrthtml.hxx
index 0774efc41da1..592a403c57de 100644
--- a/sw/source/filter/html/wrthtml.hxx
+++ b/sw/source/filter/html/wrthtml.hxx
@@ -423,6 +423,9 @@ public:
 
     OUString m_aRTFOLEMimeType;
 
+    /// If set, replace leading tabs with this many non-breaking spaces.
+    std::optional<sal_Int32> m_nLeadingTabWidth;
+
     /// Construct an instance of SwHTMLWriter and optionally give it
     /// the filter options directly, which can also be set via 
SetupFilterOptions().
     explicit SwHTMLWriter( const OUString& rBaseURL, const OUString& 
rFilterOptions = "" );
commit 533b2385022843274a619a788e2a1594ae9090fd
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Feb 15 10:00:33 2022 +0100
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Thu Jul 7 17:04:47 2022 +0200

    sw HTML export: extract a SetupFilterFromPropertyValues()
    
    From the two SetupFilterOptions() overloads, which meant that some
    options were only possible to set from FilterOptions, and others were
    only possible to set via UNO property values.
    
    (cherry picked from commit 22d09d65c0e61cac1fa27af6a04a23e16f97c907)
    
    Conflicts:
            sw/source/filter/html/wrthtml.cxx
    
    Change-Id: Ib7cdbb082e93b9ff105afe72f295994733b4525a
    (cherry picked from commit 97fee18693461e5b0272f2826860269483ca547e)

diff --git a/sw/source/filter/html/wrthtml.cxx 
b/sw/source/filter/html/wrthtml.cxx
index 2f83734aee37..e5ac25005de0 100644
--- a/sw/source/filter/html/wrthtml.cxx
+++ b/sw/source/filter/html/wrthtml.cxx
@@ -194,49 +194,117 @@ void SwHTMLWriter::SetupFilterOptions(SfxMedium& rMedium)
     const OUString sFilterOptions = static_cast<const 
SfxStringItem*>(pItem)->GetValue();
     SetupFilterOptions(sFilterOptions);
 
-    comphelper::SequenceAsHashMap aStoreMap(rMedium.GetArgs());
-    auto it = aStoreMap.find("RTFOLEMimeType");
-    if (it == aStoreMap.end())
-    {
-        return;
-    }
-
-    it->second >>= m_aRTFOLEMimeType;
+    SetupFilterFromPropertyValues(rMedium.GetArgs());
 }
 
 void SwHTMLWriter::SetupFilterOptions(const OUString& rFilterOptions)
 {
-    if (rFilterOptions == "SkipImages")
+    comphelper::SequenceAsHashMap aStoreMap;
+    if (rFilterOptions.indexOf("SkipImages") >= 0)
     {
-        mbSkipImages = true;
+        aStoreMap["SkipImages"] <<= true;
     }
-    else if (rFilterOptions == "SkipHeaderFooter")
+    else if (rFilterOptions.indexOf("SkipHeaderFooter") >= 0)
     {
-        mbSkipHeaderFooter = true;
+        aStoreMap["SkipHeaderFooter"] <<= true;
     }
-    else if (rFilterOptions == "EmbedImages")
+    else if (rFilterOptions.indexOf("EmbedImages") >= 0)
     {
-        mbEmbedImages = true;
+        aStoreMap["EmbedImages"] <<= true;
     }
 
-    const uno::Sequence<OUString> aOptionSeq = 
comphelper::string::convertCommaSeparated(rFilterOptions);
+    // this option can be "on" together with any of above
+    if (rFilterOptions.indexOf("NoLineLimit") >= 0)
+    {
+        aStoreMap["NoLineLimit"] <<= true;
+    }
+
+    const uno::Sequence<OUString> aOptionSeq
+        = comphelper::string::convertCommaSeparated(rFilterOptions);
     const OUString aXhtmlNsKey("xhtmlns=");
     for (const auto& rOption : aOptionSeq)
     {
         if (rOption == "XHTML")
-            mbXHTML = true;
+        {
+            aStoreMap["XHTML"] <<= true;
+        }
         else if (rOption.startsWith(aXhtmlNsKey))
         {
-            maNamespace = rOption.copy(aXhtmlNsKey.getLength()).toUtf8();
-            if (maNamespace == "reqif-xhtml")
-            {
-                mbReqIF = true;
-                // XHTML is always just a fragment inside ReqIF.
-                mbSkipHeaderFooter = true;
-            }
-            // XHTML namespace implies XHTML.
-            mbXHTML = true;
+            aStoreMap["XhtmlNs"] <<= rOption.copy(aXhtmlNsKey.getLength());
+        }
+    }
+
+    SetupFilterFromPropertyValues(aStoreMap.getAsConstPropertyValueList());
+}
+
+void SwHTMLWriter::SetupFilterFromPropertyValues(
+    const css::uno::Sequence<css::beans::PropertyValue>& rPropertyValues)
+{
+    comphelper::SequenceAsHashMap aStoreMap(rPropertyValues);
+    auto it = aStoreMap.find("RTFOLEMimeType");
+    if (it != aStoreMap.end())
+    {
+        it->second >>= m_aRTFOLEMimeType;
+    }
+
+    it = aStoreMap.find("SkipImages");
+    if (it != aStoreMap.end())
+    {
+        bool bVal{};
+        it->second >>= bVal;
+        mbSkipImages = bVal;
+    }
+
+    it = aStoreMap.find("SkipHeaderFooter");
+    if (it != aStoreMap.end())
+    {
+        bool bVal{};
+        it->second >>= bVal;
+        mbSkipHeaderFooter = bVal;
+    }
+
+    it = aStoreMap.find("EmbedImages");
+    if (it != aStoreMap.end())
+    {
+        bool bVal{};
+        it->second >>= bVal;
+        mbEmbedImages = bVal;
+    }
+
+    it = aStoreMap.find("NoLineLimit");
+    if (it != aStoreMap.end())
+    {
+        bool bVal{};
+        it->second >>= bVal;
+        if (bVal)
+        {
+            m_nWhishLineLen = -1;
+        }
+    }
+
+    it = aStoreMap.find("XHTML");
+    if (it != aStoreMap.end())
+    {
+        bool bVal{};
+        it->second >>= bVal;
+        mbXHTML = bVal;
+    }
+
+    it = aStoreMap.find("XhtmlNs");
+    if (it != aStoreMap.end())
+    {
+        OUString aVal;
+        it->second >>= aVal;
+
+        maNamespace = aVal.toUtf8();
+        if (maNamespace == "reqif-xhtml")
+        {
+            mbReqIF = true;
+            // XHTML is always just a fragment inside ReqIF.
+            mbSkipHeaderFooter = true;
         }
+        // XHTML namespace implies XHTML.
+        mbXHTML = true;
     }
 }
 
diff --git a/sw/source/filter/html/wrthtml.hxx 
b/sw/source/filter/html/wrthtml.hxx
index ef5226f95674..0774efc41da1 100644
--- a/sw/source/filter/html/wrthtml.hxx
+++ b/sw/source/filter/html/wrthtml.hxx
@@ -288,6 +288,8 @@ class SW_DLLPUBLIC SwHTMLWriter : public Writer
 protected:
     ErrCode WriteStream() override;
     void SetupFilterOptions(SfxMedium& rMedium) override;
+    void SetupFilterFromPropertyValues(
+        const css::uno::Sequence<css::beans::PropertyValue>& rPropertyValues);
 
 public:
     std::vector<OUString> m_aImgMapNames;   // written image maps

Reply via email to