include/svtools/htmlkywd.hxx            |    1 
 include/svtools/htmltokn.h              |    1 
 include/svtools/parhtml.hxx             |    4 +
 svtools/source/svhtml/htmlkywd.cxx      |    1 
 svtools/source/svhtml/parhtml.cxx       |   59 ++++++++++---------
 sw/qa/extras/htmlexport/htmlexport.cxx  |   66 +++++++++++++++++++++
 sw/source/filter/html/css1atr.cxx       |   20 +++++-
 sw/source/filter/html/css1kywd.cxx      |    2 
 sw/source/filter/html/css1kywd.hxx      |    2 
 sw/source/filter/html/htmlatr.cxx       |   97 ++++++++++++++++++++++----------
 sw/source/filter/html/htmlfldw.cxx      |    4 -
 sw/source/filter/html/htmlflywriter.cxx |   28 ++++-----
 sw/source/filter/html/htmlforw.cxx      |   16 ++---
 sw/source/filter/html/htmlftn.cxx       |    8 +-
 sw/source/filter/html/htmlnumwriter.cxx |    4 -
 sw/source/filter/html/htmlplug.cxx      |    6 -
 sw/source/filter/html/htmltabw.cxx      |   18 ++---
 sw/source/filter/html/svxcss1.cxx       |   14 ++++
 sw/source/filter/html/svxcss1.hxx       |    1 
 sw/source/filter/html/swhtml.cxx        |    9 ++
 sw/source/filter/html/wrthtml.cxx       |   24 +++++--
 sw/source/filter/html/wrthtml.hxx       |   15 +++-
 22 files changed, 289 insertions(+), 111 deletions(-)

New commits:
commit 2e3f3bcf3f243918d79aa97545bcd30627b458a4
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Mon Oct 23 19:52:14 2023 +0300
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Tue Oct 24 08:42:16 2023 +0300

    Implement PreserveSpaces boolean HTML/ReqIF export filter option
    
    This option changes how HTML/ReqIF export handles paragraphs with
    leading/trailing spaces, or multiple sequential spaces. Normally
    export may insert newlines every ~256 characters, in place of
    normal space characters; this relies on default processing of
    spaces, where leading/trailing spaces are trimmed, and runs of
    spaces are reduced to a single space.
    
    When PreserveSpaces is true, HTML/ReqIF export takes care to not
    alter spaces inside paragraphs. For that, it checks if paragraphs
    contain sequences of spaces that normally would be reduced; and
    for those paragraphs, it adds "white-space: pre-wrap" to style
    (in HTML), or 'xml::space="preserve"' attribute (in ReqIF).
    
    Import of 'xml::space' attribute and "white-space: pre-wrap" style
    is implemented; when paragraph has these, it keeps the spaces read
    from HTML/ReqIF intact.
    
    Import does not currently support this attribute/style in elements
    other than 'p'.
    
    Change-Id: I62dba5eaf313b965bf37d8fa5e3f5bbb8f5e8357
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158362
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/include/svtools/htmlkywd.hxx b/include/svtools/htmlkywd.hxx
index 9a84cddd37bf..6f06c74f3ee0 100644
--- a/include/svtools/htmlkywd.hxx
+++ b/include/svtools/htmlkywd.hxx
@@ -522,6 +522,7 @@
 #define OOO_STRING_SVTOOLS_HTML_O_valign "valign"
 #define OOO_STRING_SVTOOLS_HTML_O_valuetype "valuetype"
 #define OOO_STRING_SVTOOLS_HTML_O_wrap "wrap"
+#define OOO_STRING_SVTOOLS_XHTML_O_xml_space "xml:space"
 
 // attributes with script code as value
 #define OOO_STRING_SVTOOLS_HTML_O_onblur "onblur"
diff --git a/include/svtools/htmltokn.h b/include/svtools/htmltokn.h
index 9dca8a8f3ea7..4a333ee2f6d9 100644
--- a/include/svtools/htmltokn.h
+++ b/include/svtools/htmltokn.h
@@ -429,6 +429,7 @@ ENUM_START          = NUMBER_END,
     VALIGN,
     VALUETYPE,
     WRAP,
+    XML_SPACE,
 ENUM_END,
 
 // attributes with script code as value
diff --git a/include/svtools/parhtml.hxx b/include/svtools/parhtml.hxx
index b4fee63e311a..a6a15b93e3c1 100644
--- a/include/svtools/parhtml.hxx
+++ b/include/svtools/parhtml.hxx
@@ -162,6 +162,8 @@ private:
     bool bReadNextChar : 1;         // true: read NextChar again(JavaScript!)
     bool bReadComment : 1;          // true: read NextChar again (JavaScript!)
 
+    bool m_bPreserveSpaces = false;
+
     sal_uInt32 nPre_LinePos;            // Pos in the line in the PRE-Tag
 
     HtmlTokenId mnPendingOffToken;          ///< OFF token pending for a 
<XX.../> ON/OFF ON token
@@ -187,6 +189,8 @@ protected:
 
     void SetNamespace(std::u16string_view rNamespace);
 
+    void SetPreserveSpaces(bool val) { m_bPreserveSpaces = val; }
+
 public:
     HTMLParser( SvStream& rIn, bool bReadNewDoc = true );
 
diff --git a/svtools/source/svhtml/htmlkywd.cxx 
b/svtools/source/svhtml/htmlkywd.cxx
index 584322fac8bc..b4c0492fbec3 100644
--- a/svtools/source/svhtml/htmlkywd.cxx
+++ b/svtools/source/svhtml/htmlkywd.cxx
@@ -598,6 +598,7 @@ static HTML_OptionEntry aHTMLOptionTab[] = {
     {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_valign),        
HtmlOptionId::VALIGN},
     {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_valuetype), 
HtmlOptionId::VALUETYPE},
     {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_wrap),      
HtmlOptionId::WRAP},
+    {std::u16string_view(u"" OOO_STRING_SVTOOLS_XHTML_O_xml_space), 
HtmlOptionId::XML_SPACE},
 
 // Attributes with script code value
     {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onblur),        
HtmlOptionId::ONBLUR}, // JavaScript
diff --git a/svtools/source/svhtml/parhtml.cxx 
b/svtools/source/svhtml/parhtml.cxx
index e705c98013e4..16313f226a54 100644
--- a/svtools/source/svhtml/parhtml.cxx
+++ b/svtools/source/svhtml/parhtml.cxx
@@ -377,9 +377,14 @@ namespace {
 
 constexpr bool HTML_ISPRINTABLE(sal_Unicode c) { return c >= 32 && c != 127; }
 
+constexpr bool HTML_ISSPACE(sal_uInt32 c)
+{
+    return ' ' == c || '\t' == c || '\r' == c || '\n' == c || '\x0b' == c;
+}
+
 }
 
-HtmlTokenId HTMLParser::ScanText( const sal_Unicode cBreak )
+HtmlTokenId HTMLParser::ScanText(const sal_Unicode cBreak)
 {
     OUStringBuffer sTmpBuffer( MAX_LEN );
     bool bContinue = true;
@@ -705,37 +710,39 @@ HtmlTokenId HTMLParser::ScanText( const sal_Unicode 
cBreak )
             {
                 break;
             }
-            nNextCh = ' ';
+            if (!m_bPreserveSpaces)
+                nNextCh = ' ';
             [[fallthrough]];
         case ' ':
-            sTmpBuffer.appendUtf32( nNextCh );
-            if( '>'!=cBreak && (!bReadListing && !bReadXMP &&
-                                !bReadPRE && !bReadTextArea) )
+            if (!m_bPreserveSpaces)
             {
-                // Reduce sequences of Blanks/Tabs/CR/LF to a single blank
-                do {
-                    nNextCh = GetNextChar();
-                    if( sal_Unicode(EOF) == nNextCh && rInput.eof() )
+                sTmpBuffer.appendUtf32(nNextCh);
+                if ('>' != cBreak && (!bReadListing && !bReadXMP && !bReadPRE 
&& !bReadTextArea))
+                {
+                    // Reduce sequences of Blanks/Tabs/CR/LF to a single blank
+                    do
                     {
-                        if( !aToken.isEmpty() || sTmpBuffer.getLength() > 1 )
+                        nNextCh = GetNextChar();
+                        if (sal_Unicode(EOF) == nNextCh && rInput.eof())
                         {
-                            // Have seen s.th. aside from blanks?
-                            aToken.append( sTmpBuffer );
-                            sTmpBuffer.setLength(0);
-                            return HtmlTokenId::TEXTTOKEN;
+                            if (!aToken.isEmpty() || sTmpBuffer.getLength() > 
1)
+                            {
+                                // Have seen s.th. aside from blanks?
+                                aToken.append(sTmpBuffer);
+                                sTmpBuffer.setLength(0);
+                                return HtmlTokenId::TEXTTOKEN;
+                            }
+                            else
+                                // Only read blanks: no text must be returned
+                                // and GetNextToken_ has to read until EOF
+                                return HtmlTokenId::NONE;
                         }
-                        else
-                            // Only read blanks: no text must be returned
-                            // and GetNextToken_ has to read until EOF
-                            return HtmlTokenId::NONE;
-                    }
-                } while ( ' ' == nNextCh || '\t' == nNextCh ||
-                          '\r' == nNextCh || '\n' == nNextCh ||
-                          '\x0b' == nNextCh );
-                bNextCh = false;
+                    } while (HTML_ISSPACE(nNextCh));
+                    bNextCh = false;
+                }
+                break;
             }
-            break;
-
+            [[fallthrough]];
         default:
             bEqSignFound = false;
             if (nNextCh == cBreak && !cQuote)
@@ -743,7 +750,7 @@ HtmlTokenId HTMLParser::ScanText( const sal_Unicode cBreak )
             else
             {
                 do {
-                    if (!linguistic::IsControlChar(nNextCh))
+                    if (!linguistic::IsControlChar(nNextCh) || 
HTML_ISSPACE(nNextCh))
                     {
                     // All remaining characters make their way into the text.
                         sTmpBuffer.appendUtf32( nNextCh );
diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx 
b/sw/qa/extras/htmlexport/htmlexport.cxx
index 59997f03ae7e..7c74a27161ee 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -2764,6 +2764,72 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, 
testTdf157643_WideHBorder)
     assertXPath(pXmlDoc, 
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:table/reqif-xhtml:tr", 2);
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_PreserveSpaces)
+{
+    // Given a document with leading, trailing, and repeating intermediate 
spaces:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    const OUString paraText = u"\t test  \t more  text \t";
+    pWrtShell->Insert(paraText);
+
+    // When exporting to plain HTML, using PreserveSpaces:
+    uno::Reference<css::frame::XStorable> xStorable(mxComponent, 
uno::UNO_QUERY_THROW);
+    css::uno::Sequence<css::beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML 
(StarWriter)")),
+        comphelper::makePropertyValue("PreserveSpaces", true),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Then make sure that "white-space: pre-wrap" is written into the 
paragraph's style:
+    htmlDocUniquePtr pHtmlDoc = parseHtml(maTempFile);
+    CPPUNIT_ASSERT(pHtmlDoc);
+    const OUString style = getXPath(pHtmlDoc, "/html/body/p", "style");
+    CPPUNIT_ASSERT(style.indexOf("white-space: pre-wrap") >= 0);
+    // Also check that the paragraph text is correct, without modifications in 
whitespace
+    assertXPathContent(pHtmlDoc, "/html/body/p", paraText);
+
+    // Test import
+
+    setImportFilterName("HTML (StarWriter)");
+    UnoApiTest::load(maTempFile.GetURL());
+    CPPUNIT_ASSERT_EQUAL(paraText, getParagraph(1)->getString());
+}
+
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_PreserveSpaces)
+{
+    // Given a document with leading, trailing, and repeating intermediate 
spaces:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    const OUString paraText = u"\t test  \t more  text \t";
+    pWrtShell->Insert(paraText);
+
+    // When exporting to ReqIF, using PreserveSpaces:
+    uno::Reference<css::frame::XStorable> xStorable(mxComponent, 
uno::UNO_QUERY_THROW);
+    css::uno::Sequence<css::beans::PropertyValue> aStoreProperties = {
+        comphelper::makePropertyValue("FilterName", OUString("HTML 
(StarWriter)")),
+        comphelper::makePropertyValue("FilterOptions", 
OUString("xhtmlns=reqif-xhtml")),
+        comphelper::makePropertyValue("PreserveSpaces", true),
+    };
+    xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+    // Then make sure that xml:space="preserve" attribute exists in the 
paragraph element:
+    SvMemoryStream aStream;
+    WrapReqifFromTempFile(aStream);
+    xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream);
+    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", 
"space", u"preserve");
+    // Also check that the paragraph text is correct, without modifications in 
whitespace
+    assertXPathContent(pXmlDoc, 
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", paraText);
+
+    // Test import
+
+    setImportFilterOptions("xhtmlns=reqif-xhtml");
+    setImportFilterName("HTML (StarWriter)");
+    UnoApiTest::load(maTempFile.GetURL());
+    CPPUNIT_ASSERT_EQUAL(paraText, getParagraph(1)->getString());
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/css1atr.cxx 
b/sw/source/filter/html/css1atr.cxx
index c435cc66b050..27d0c645ad56 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -1791,13 +1791,13 @@ Writer& OutCSS1_BodyTagStyleOpt( Writer& rWrt, const 
SfxItemSet& rItemSet )
     return rWrt;
 }
 
-Writer& OutCSS1_ParaTagStyleOpt( Writer& rWrt, const SfxItemSet& rItemSet )
+Writer& OutCSS1_ParaTagStyleOpt( Writer& rWrt, const SfxItemSet& rItemSet, 
std::string_view rAdd )
 {
     SwHTMLWriter& rHTMLWrt = static_cast<SwHTMLWriter&>(rWrt);
 
     SwCSS1OutMode aMode( rHTMLWrt, 
rHTMLWrt.m_nCSS1Script|CSS1_OUTMODE_STYLE_OPT |
                                    CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_PARA, 
nullptr );
-    rHTMLWrt.OutCSS1_SfxItemSet( rItemSet, false );
+    rHTMLWrt.OutCSS1_SfxItemSet( rItemSet, false, rAdd );
 
     return rWrt;
 }
@@ -3599,7 +3599,7 @@ SwAttrFnTab const aCSS1AttrFnTab = {
 static_assert(SAL_N_ELEMENTS(aCSS1AttrFnTab) == RES_BOXATR_END);
 
 void SwHTMLWriter::OutCSS1_SfxItemSet( const SfxItemSet& rItemSet,
-                                       bool bDeep )
+                                       bool bDeep, std::string_view rAdd )
 {
     // print ItemSet, including all attributes
     Out_SfxItemSet( aCSS1AttrFnTab, *this, rItemSet, bDeep );
@@ -3631,6 +3631,20 @@ void SwHTMLWriter::OutCSS1_SfxItemSet( const SfxItemSet& 
rItemSet,
         OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( *this, rItemSet, 
bDeep );
     }
 
+    if (!rAdd.empty())
+    {
+        for (std::size_t index = 0; index != std::string_view::npos;)
+        {
+            OString attr(o3tl::trim(o3tl::getToken(rAdd, ':', index)));
+            assert(!attr.isEmpty());
+            assert(index != std::string_view::npos);
+
+            std::string_view val = o3tl::trim(o3tl::getToken(rAdd, ':', 
index));
+            assert(!val.empty());
+            OutCSS1_PropertyAscii(attr.getStr(), val);
+        }
+    }
+
     if( m_bFirstCSS1Property )
         return;
 
diff --git a/sw/source/filter/html/css1kywd.cxx 
b/sw/source/filter/html/css1kywd.cxx
index 12d70903ad39..56285cfe890e 100644
--- a/sw/source/filter/html/css1kywd.cxx
+++ b/sw/source/filter/html/css1kywd.cxx
@@ -212,4 +212,6 @@ const char* const sCSS1_PV_inherit = "inherit";
 
 const char* const sCSS1_P_display = "display";
 
+const char* const sCSS1_P_white_space = "white-space";
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/css1kywd.hxx 
b/sw/source/filter/html/css1kywd.hxx
index 67d2c9e2802c..7e70961069bd 100644
--- a/sw/source/filter/html/css1kywd.hxx
+++ b/sw/source/filter/html/css1kywd.hxx
@@ -212,6 +212,8 @@ extern const char* const sCSS1_PV_inherit;
 
 extern const char* const sCSS1_P_display;
 
+extern const char* const sCSS1_P_white_space;
+
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index fdf53cb9f84a..114d6de16b3a 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -133,12 +133,12 @@ void SwHTMLWriter::OutAndSetDefList( sal_uInt16 nNewLvl )
         // write according to the level difference
         for( sal_uInt16 i=m_nDefListLvl; i<nNewLvl; i++ )
         {
-            if( m_bLFPossible )
+            if (IsLFPossible())
                 OutNewLine();
             HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_deflist) );
             HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_dd) );
             IncIndentLevel();
-            m_bLFPossible = true;
+            SetLFPossible(true);
         }
     }
     else if( m_nDefListLvl > nNewLvl )
@@ -146,11 +146,11 @@ void SwHTMLWriter::OutAndSetDefList( sal_uInt16 nNewLvl )
         for( sal_uInt16 i=nNewLvl ; i < m_nDefListLvl; i++ )
         {
             DecIndentLevel();
-            if( m_bLFPossible )
+            if (IsLFPossible())
                 OutNewLine();
             HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_dd), false );
             HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_deflist), false );
-            m_bLFPossible = true;
+            SetLFPossible(true);
         }
     }
 
@@ -162,7 +162,7 @@ void SwHTMLWriter::ChangeParaToken( HtmlTokenId nNew )
     if( nNew != m_nLastParaToken && HtmlTokenId::PREFORMTXT_ON == 
m_nLastParaToken )
     {
         HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_preformtxt), false );
-        m_bLFPossible = true;
+        SetLFPossible(true);
     }
     m_nLastParaToken = nNew;
 }
@@ -744,7 +744,7 @@ static void OutHTML_SwFormat( Writer& rWrt, const SwFormat& 
rFormat,
             (rHWrt.m_nDefListLvl-1) * rHWrt.m_nDefListMargin;
     }
 
-    if( rHWrt.m_bLFPossible && !rHWrt.m_bFirstLine )
+    if (rHWrt.IsLFPossible() && !rHWrt.m_bFirstLine)
         rHWrt.OutNewLine(); // paragraph tag on a new line
     rInfo.bOutPara = false;
 
@@ -805,7 +805,7 @@ static void OutHTML_SwFormat( Writer& rWrt, const SwFormat& 
rFormat,
         rHWrt.m_bNoAlign = false;
         rInfo.bOutDiv = true;
         rHWrt.IncIndentLevel();
-        rHWrt.m_bLFPossible = true;
+        rHWrt.SetLFPossible(true);
         rHWrt.OutNewLine();
     }
 
@@ -919,6 +919,15 @@ static void OutHTML_SwFormat( Writer& rWrt, const 
SwFormat& rFormat,
         rWrt.Strm().WriteOString( sOut );
         sOut = "";
 
+        std::string_view sStyle;
+        if (rHWrt.IsSpacePreserve())
+        {
+            if (rHWrt.mbXHTML)
+                rWrt.Strm().WriteOString(" xml:space=\"preserve\"");
+            else
+                sStyle = "white-space: pre-wrap";
+        }
+
         // if necessary, output alignment
         if( !rHWrt.m_bNoAlign && pAdjItem )
             OutHTML_SvxAdjust( rWrt, *pAdjItem );
@@ -929,7 +938,7 @@ static void OutHTML_SwFormat( Writer& rWrt, const SwFormat& 
rFormat,
         // and now, if necessary, the STYLE options
         if (rHWrt.m_bCfgOutStyles && rInfo.moItemSet)
         {
-            OutCSS1_ParaTagStyleOpt( rWrt, *rInfo.moItemSet );
+            OutCSS1_ParaTagStyleOpt(rWrt, *rInfo.moItemSet, sStyle);
         }
 
         if (rHWrt.m_bParaDotLeaders) {
@@ -1001,7 +1010,7 @@ static void OutHTML_SwFormatOff( Writer& rWrt, const 
SwHTMLTextCollOutputInfo& r
 
     if( rInfo.ShouldOutputToken() )
     {
-        if( rHWrt.m_bLFPossible )
+        if (rHWrt.IsLFPossible())
             rHWrt.OutNewLine( true );
 
         // if necessary, for BLOCKQUOTE, ADDRESS and DD another paragraph token
@@ -1012,18 +1021,18 @@ static void OutHTML_SwFormatOff( Writer& rWrt, const 
SwHTMLTextCollOutputInfo& r
             HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rHWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_parabreak), false );
 
         HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rHWrt.GetNamespace() + rInfo.aToken), false );
-        rHWrt.m_bLFPossible =
+        rHWrt.SetLFPossible(
             rInfo.aToken != OOO_STRING_SVTOOLS_HTML_dt &&
             rInfo.aToken != OOO_STRING_SVTOOLS_HTML_dd &&
-            rInfo.aToken != OOO_STRING_SVTOOLS_HTML_li;
+            rInfo.aToken != OOO_STRING_SVTOOLS_HTML_li);
     }
     if( rInfo.bOutDiv )
     {
         rHWrt.DecIndentLevel();
-        if( rHWrt.m_bLFPossible )
+        if (rHWrt.IsLFPossible())
             rHWrt.OutNewLine();
         HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rHWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false );
-        rHWrt.m_bLFPossible = true;
+        rHWrt.SetLFPossible(true);
     }
 
     // if necessary, close the list item, then close a bulleted or numbered 
list
@@ -1996,6 +2005,30 @@ void HTMLEndPosLst::OutEndAttrs( SwHTMLWriter& rHWrt, 
sal_Int32 nPos )
     }
 }
 
+static bool NeedPreserveWhitespace(std::u16string_view str)
+{
+    if (str.empty())
+        return false;
+    // leading / trailing spaces
+    if (o3tl::internal::implIsWhitespace(str.front())
+        || o3tl::internal::implIsWhitespace(str.back()))
+        return true;
+    bool bWasSpace = false;
+    for (auto ch : str)
+    {
+        if (o3tl::internal::implIsWhitespace(ch))
+        {
+            if (bWasSpace)
+                return true; // Second whitespace in a row
+            else
+                bWasSpace = true;
+        }
+        else
+            bWasSpace = false;
+    }
+    return false;
+}
+
 /* Output of the nodes*/
 Writer& OutHTML_SwTextNode( Writer& rWrt, const SwContentNode& rNode )
 {
@@ -2022,10 +2055,10 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
         // Output all the nodes that are anchored to a frame
         rHTMLWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any );
 
-        if( rHTMLWrt.m_bLFPossible )
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine(); // paragraph tag on a new line
 
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
 
         HtmlWriter aHtml(rWrt.Strm(), rHTMLWrt.maNamespace);
         aHtml.start(OOO_STRING_SVTOOLS_HTML_horzrule);
@@ -2127,11 +2160,11 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
             {
                 // ... and it is located before a table or a section
                 rHTMLWrt.OutBookmarks();
-                rHTMLWrt.m_bLFPossible = rHTMLWrt.m_nLastParaToken == 
HtmlTokenId::NONE;
+                rHTMLWrt.SetLFPossible(rHTMLWrt.m_nLastParaToken == 
HtmlTokenId::NONE);
 
                 // Output all frames that are anchored to this node
                 rHTMLWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any );
-                rHTMLWrt.m_bLFPossible = false;
+                rHTMLWrt.SetLFPossible(false);
 
                 return rWrt;
             }
@@ -2202,12 +2235,15 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
     // now, output the tag of the paragraph
     const SwFormat& rFormat = pNd->GetAnyFormatColl();
     SwHTMLTextCollOutputInfo aFormatInfo;
-    bool bOldLFPossible = rHTMLWrt.m_bLFPossible;
+    bool bOldLFPossible = rHTMLWrt.IsLFPossible();
+    bool bOldSpacePreserve = rHTMLWrt.IsSpacePreserve();
+    if (rHTMLWrt.IsPreserveSpacesOnWritePrefSet())
+        rHTMLWrt.SetSpacePreserve(NeedPreserveWhitespace(rStr));
     OutHTML_SwFormat( rWrt, rFormat, pNd->GetpSwAttrSet(), aFormatInfo );
 
     // If we didn't open a new line before the paragraph tag, we do that now
-    rHTMLWrt.m_bLFPossible = rHTMLWrt.m_nLastParaToken == HtmlTokenId::NONE;
-    if( !bOldLFPossible && rHTMLWrt.m_bLFPossible )
+    rHTMLWrt.SetLFPossible(rHTMLWrt.m_nLastParaToken == HtmlTokenId::NONE);
+    if (!bOldLFPossible && rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
 
     // then, the bookmarks (including end tag)
@@ -2217,12 +2253,12 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
     // now it's a good opportunity again for an LF - if it is still allowed
     // FIXME: for LOK case we set rHTMLWrt.m_nWishLineLen as -1, for now keep 
old flow
     // when LOK side will be fixed - don't insert new line at the beginning
-    if( rHTMLWrt.m_bLFPossible &&
+    if( rHTMLWrt.IsLFPossible() &&
         rHTMLWrt.GetLineLen() >= (rHTMLWrt.m_nWishLineLen >= 0 ? 
rHTMLWrt.m_nWishLineLen : 70 ) )
     {
         rHTMLWrt.OutNewLine();
     }
-    rHTMLWrt.m_bLFPossible = false;
+    rHTMLWrt.SetLFPossible(false);
 
     // find text that originates from an outline numbering
     sal_Int32 nOffset = 0;
@@ -2419,16 +2455,16 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
 
             if( pTextHt )
             {
-                rHTMLWrt.m_bLFPossible = rHTMLWrt.m_nLastParaToken == 
HtmlTokenId::NONE &&
+                rHTMLWrt.SetLFPossible(rHTMLWrt.m_nLastParaToken == 
HtmlTokenId::NONE &&
                                        nStrPos > 0 &&
-                                       rStr[nStrPos-1] == ' ';
+                                       rStr[nStrPos-1] == ' ');
                 sal_uInt16 nCSS1Script = rHTMLWrt.m_nCSS1Script;
                 rHTMLWrt.m_nCSS1Script = aEndPosLst.GetScriptAtPos(
                                                 nStrPos + nOffset, nCSS1Script 
);
                 HTMLOutFuncs::FlushToAscii( rWrt.Strm() );
                 Out( aHTMLAttrFnTab, pTextHt->GetAttr(), rHTMLWrt );
                 rHTMLWrt.m_nCSS1Script = nCSS1Script;
-                rHTMLWrt.m_bLFPossible = false;
+                rHTMLWrt.SetLFPossible(false);
             }
 
             if( bOutChar )
@@ -2446,7 +2482,7 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
 
                 // try to split a line after about 255 characters
                 // at a space character unless in a PRE-context
-                if( ' ' == c && rHTMLWrt.m_nLastParaToken == HtmlTokenId::NONE 
 )
+                if( ' ' == c && rHTMLWrt.m_nLastParaToken == HtmlTokenId::NONE 
&& !rHTMLWrt.IsSpacePreserve() )
                 {
                     sal_Int32 nLineLen;
                     nLineLen = rHTMLWrt.GetLineLen();
@@ -2556,7 +2592,7 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
             {
                 aHtml.single(OOO_STRING_SVTOOLS_HTML_linebreak);
             }
-            rHTMLWrt.m_bLFPossible = true;
+            rHTMLWrt.SetLFPossible(true);
         }
     }
 
@@ -2583,15 +2619,15 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
         rHTMLWrt.m_bClearLeft = false;
         rHTMLWrt.m_bClearRight = false;
 
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
     }
 
     // if an LF is not allowed already, it is allowed once the paragraphs
     // ends with a ' '
-    if( !rHTMLWrt.m_bLFPossible &&
+    if (!rHTMLWrt.IsLFPossible() &&
         rHTMLWrt.m_nLastParaToken == HtmlTokenId::NONE &&
         nEnd > 0 && ' ' == rStr[nEnd-1] )
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
 
     // dot leaders: print the skipped page number in a different span element
     if (nIndexTab > -1) {
@@ -2601,6 +2637,7 @@ Writer& OutHTML_SwTextNode( Writer& rWrt, const 
SwContentNode& rNode )
 
     rHTMLWrt.m_bTagOn = false;
     OutHTML_SwFormatOff( rWrt, aFormatInfo );
+    rHTMLWrt.SetSpacePreserve(bOldSpacePreserve);
 
     // if necessary, close a form
     rHTMLWrt.OutForm( false );
diff --git a/sw/source/filter/html/htmlfldw.cxx 
b/sw/source/filter/html/htmlfldw.cxx
index ba3f48d8d96e..c858358e3d5d 100644
--- a/sw/source/filter/html/htmlfldw.cxx
+++ b/sw/source/filter/html/htmlfldw.cxx
@@ -518,7 +518,7 @@ Writer& OutHTML_SwFormatField( Writer& rWrt, const 
SfxPoolItem& rHt )
     }
     else if( SwFieldIds::Script == pFieldTyp->Which() )
     {
-        if( rHTMLWrt.m_bLFPossible )
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine( true );
 
         bool bURL = static_cast<const SwScriptField *>(pField)->IsCodeURL();
@@ -534,7 +534,7 @@ Writer& OutHTML_SwFormatField( Writer& rWrt, const 
SfxPoolItem& rHt )
         HTMLOutFuncs::OutScript( rWrt.Strm(), rWrt.GetBaseURL(), aContents, 
rType, JAVASCRIPT,
                                  aURL, nullptr, nullptr );
 
-        if( rHTMLWrt.m_bLFPossible )
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine( true );
     }
     else
diff --git a/sw/source/filter/html/htmlflywriter.cxx 
b/sw/source/filter/html/htmlflywriter.cxx
index 14f04ff35742..b58d3d9f6c44 100644
--- a/sw/source/filter/html/htmlflywriter.cxx
+++ b/sw/source/filter/html/htmlflywriter.cxx
@@ -416,7 +416,7 @@ void SwHTMLWriter::OutFrameFormat( AllHtmlFlags nMode, 
const SwFrameFormat& rFra
     if( HtmlContainerFlags::NONE != nCntnrMode )
     {
 
-        if( m_bLFPossible && HtmlContainerFlags::Div == nCntnrMode )
+        if (IsLFPossible() && HtmlContainerFlags::Div == nCntnrMode)
             OutNewLine();
 
         OStringBuffer sOut;
@@ -444,7 +444,7 @@ void SwHTMLWriter::OutFrameFormat( AllHtmlFlags nMode, 
const SwFrameFormat& rFra
         if( HtmlContainerFlags::Div == nCntnrMode )
         {
             IncIndentLevel();
-            m_bLFPossible = true;
+            SetLFPossible(true);
         }
     }
 
@@ -496,10 +496,10 @@ void SwHTMLWriter::OutFrameFormat( AllHtmlFlags nMode, 
const SwFrameFormat& rFra
     if( HtmlContainerFlags::Div == nCntnrMode )
     {
         DecIndentLevel();
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_division), false );
-        m_bLFPossible = true;
+        SetLFPossible(true);
     }
     else if( HtmlContainerFlags::Span == nCntnrMode )
         HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_span), false );
@@ -1200,7 +1200,7 @@ OUString lclWriteOutImap(SwHTMLWriter& rHTMLWrt, const 
SfxItemSet& rItemSet, con
         OString aIndMap, aIndArea;
         const char *pIndArea = nullptr, *pIndMap = nullptr;
 
-        if (rHTMLWrt.m_bLFPossible)
+        if (rHTMLWrt.IsLFPossible())
         {
             rHTMLWrt.OutNewLine( true );
             aIndMap = rHTMLWrt.GetIndentString();
@@ -1291,7 +1291,7 @@ Writer& OutHTML_ImageStart( HtmlWriter& rHtml, Writer& 
rWrt, const SwFrameFormat
     OUString aIMapName = lclWriteOutImap(rHTMLWrt, rItemSet, rFrameFormat, 
rRealSize, pAltImgMap, pURLItem);
 
     // put img into new line
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine( true );
 
     // <a name=...></a>...<img ...>
@@ -1647,7 +1647,7 @@ static Writer & OutHTML_FrameFormatAsMulticol( Writer& 
rWrt,
     rHTMLWrt.OutAndSetDefList( 0 );
 
     // output as Multicol
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
 
     OStringBuffer sOut;
@@ -1690,7 +1690,7 @@ static Writer & OutHTML_FrameFormatAsMulticol( Writer& 
rWrt,
 
     rWrt.Strm().WriteChar( '>' );
 
-    rHTMLWrt.m_bLFPossible = true;
+    rHTMLWrt.SetLFPossible(true);
     rHTMLWrt.IncIndentLevel();  // indent the content of Multicol
 
     const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
@@ -1709,10 +1709,10 @@ static Writer & OutHTML_FrameFormatAsMulticol( Writer& 
rWrt,
     }
 
     rHTMLWrt.DecIndentLevel();  // indent the content of Multicol;
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
     HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rHTMLWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_multicol), false 
);
-    rHTMLWrt.m_bLFPossible = true;
+    rHTMLWrt.SetLFPossible(true);
 
     return rWrt;
 }
@@ -1722,7 +1722,7 @@ static Writer& OutHTML_FrameFormatAsSpacer( Writer& rWrt, 
const SwFrameFormat& r
     SwHTMLWriter & rHTMLWrt = static_cast<SwHTMLWriter&>(rWrt);
 
     // if possible, output a line break before the graphic
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine( true );
 
     OString sOut =
@@ -1759,7 +1759,7 @@ static Writer& OutHTML_FrameFormatAsDivOrSpan( Writer& 
rWrt,
         aTag = OOO_STRING_SVTOOLS_HTML_span;
 
     // output as DIV
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
 
     OStringBuffer sOut;
@@ -1775,7 +1775,7 @@ static Writer& OutHTML_FrameFormatAsDivOrSpan( Writer& 
rWrt,
     rWrt.Strm().WriteChar( '>' );
 
     rHTMLWrt.IncIndentLevel();  // indent the content
-    rHTMLWrt.m_bLFPossible = true;
+    rHTMLWrt.SetLFPossible(true);
 
     const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
     SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex();
@@ -1797,7 +1797,7 @@ static Writer& OutHTML_FrameFormatAsDivOrSpan( Writer& 
rWrt,
     }
 
     rHTMLWrt.DecIndentLevel();  // indent the content of Multicol;
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
     HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rHTMLWrt.GetNamespace() + aTag), false );
 
diff --git a/sw/source/filter/html/htmlforw.cxx 
b/sw/source/filter/html/htmlforw.cxx
index 9bad5a4cbc8f..669bcc5686a7 100644
--- a/sw/source/filter/html/htmlforw.cxx
+++ b/sw/source/filter/html/htmlforw.cxx
@@ -433,16 +433,16 @@ void SwHTMLWriter::OutForm( bool bOn,
     if( !bOn )
     {
         DecIndentLevel(); // indent content of form
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_form), false );
-        m_bLFPossible = true;
+        SetLFPossible(true);
 
         return;
     }
 
     // the new form is opened
-    if( m_bLFPossible )
+    if (IsLFPossible())
         OutNewLine();
     OString sOut = "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_form;
 
@@ -524,7 +524,7 @@ void SwHTMLWriter::OutForm( bool bOn,
     Strm().WriteChar( '>' );
 
     IncIndentLevel(); // indent content of form
-    m_bLFPossible = true;
+    SetLFPossible(true);
 }
 
 void SwHTMLWriter::OutHiddenControls(
@@ -569,7 +569,7 @@ void SwHTMLWriter::OutHiddenControls(
 
         if( form::FormComponentType::HIDDENCONTROL == *n )
         {
-            if( m_bLFPossible )
+            if (IsLFPossible())
                 OutNewLine( true );
             OString sOut = "<" + GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_input " "
                 OOO_STRING_SVTOOLS_HTML_O_type "=\""
@@ -770,7 +770,7 @@ Writer& OutHTML_DrawFrameFormatAsControl( Writer& rWrt,
         break;
 
     case form::FormComponentType::LISTBOX:
-        if( rHTMLWrt.m_bLFPossible )
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine( true );
         eTag = TAG_SELECT;
         aTmp = xPropSet->getPropertyValue( "Dropdown" );
@@ -816,7 +816,7 @@ Writer& OutHTML_DrawFrameFormatAsControl( Writer& rWrt,
 
             if( bMultiLine )
             {
-                if( rHTMLWrt.m_bLFPossible )
+                if (rHTMLWrt.IsLFPossible())
                     rHTMLWrt.OutNewLine( true );
                 eTag = TAG_TEXTAREA;
 
@@ -1257,7 +1257,7 @@ Writer& OutHTML_DrawFrameFormatAsControl( Writer& rWrt,
         rWrt.Strm().WriteOString( aEndTags );
 
     // Controls aren't bound to a paragraph, therefore don't output LF anymore!
-    rHTMLWrt.m_bLFPossible = false;
+    rHTMLWrt.SetLFPossible(false);
 
     if( rHTMLWrt.mxFormComps.is() )
         rHTMLWrt.OutHiddenControls( rHTMLWrt.mxFormComps, xPropSet );
diff --git a/sw/source/filter/html/htmlftn.cxx 
b/sw/source/filter/html/htmlftn.cxx
index 773fe0b5e06a..74f20368e6d4 100644
--- a/sw/source/filter/html/htmlftn.cxx
+++ b/sw/source/filter/html/htmlftn.cxx
@@ -358,7 +358,7 @@ void SwHTMLWriter::OutFootEndNotes()
             sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdfootnote + 
OUString::number(static_cast<sal_Int32>(++m_nFootNote));
         }
 
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         OString sOut =
             "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_division
@@ -367,7 +367,7 @@ void SwHTMLWriter::OutFootEndNotes()
         HTMLOutFuncs::Out_String( Strm(), sFootnoteName );
         Strm().WriteCharPtr( "\">" );
 
-        m_bLFPossible = true;
+        SetLFPossible(true);
         IncIndentLevel();   // indent content of <DIV>
 
         OSL_ENSURE( pTextFootnote, "SwHTMLWriter::OutFootEndNotes: 
SwTextFootnote is missing" );
@@ -382,10 +382,10 @@ void SwHTMLWriter::OutFootEndNotes()
         }
 
         DecIndentLevel();   // indent content of <DIV>
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_division), false );
-        m_bLFPossible = true;
+        SetLFPossible(true);
 
         OSL_ENSURE( !m_pFormatFootnote,
                 "SwHTMLWriter::OutFootEndNotes: Footnote was not output" );
diff --git a/sw/source/filter/html/htmlnumwriter.cxx 
b/sw/source/filter/html/htmlnumwriter.cxx
index 381b146c36e7..05fc0f4e992a 100644
--- a/sw/source/filter/html/htmlnumwriter.cxx
+++ b/sw/source/filter/html/htmlnumwriter.cxx
@@ -315,7 +315,7 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
     for( sal_uInt16 i=rInfo.GetDepth(); i>nNextDepth; i-- )
     {
         rWrt.DecIndentLevel(); // indent content of <OL>
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine(); // </OL>/</UL> in a new line
 
         // a list is started or ended:
@@ -333,7 +333,7 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
                 rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_li),
                 /*bOn=*/false);
         }
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     return rWrt;
diff --git a/sw/source/filter/html/htmlplug.cxx 
b/sw/source/filter/html/htmlplug.cxx
index 819218527bf2..c4131e1af4f9 100644
--- a/sw/source/filter/html/htmlplug.cxx
+++ b/sw/source/filter/html/htmlplug.cxx
@@ -1246,7 +1246,7 @@ Writer& OutHTML_FrameFormatOLENode( Writer& rWrt, const 
SwFrameFormat& rFrameFor
     HtmlFrmOpts nFrameOpts;
 
     // if possible output a line break before the "object"
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine( true );
 
     if( !rFrameFormat.GetName().isEmpty() )
@@ -1643,7 +1643,7 @@ Writer& OutHTML_FrameFormatOLENodeGrf( Writer& rWrt, 
const SwFrameFormat& rFrame
         aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), 
aFileName);
 
         // Refer to this data.
-        if (rHTMLWrt.m_bLFPossible)
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine();
         rWrt.Strm().WriteOString(Concat2View("<" + rHTMLWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_object));
         rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + 
"\""));
@@ -1651,7 +1651,7 @@ Writer& OutHTML_FrameFormatOLENodeGrf( Writer& rWrt, 
const SwFrameFormat& rFrame
             rWrt.Strm().WriteOString(Concat2View(" type=\"" + 
aFileType.toUtf8() + "\""));
         rWrt.Strm().WriteOString(">");
         bObjectOpened = true;
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
     }
 
     if (!bObjectOpened || bWriteReplacementGraphic)
diff --git a/sw/source/filter/html/htmltabw.cxx 
b/sw/source/filter/html/htmltabw.cxx
index 80d1011539d6..ddf7fc6ca7c8 100644
--- a/sw/source/filter/html/htmltabw.cxx
+++ b/sw/source/filter/html/htmltabw.cxx
@@ -467,7 +467,7 @@ void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt,
     sOut.append('>');
     rWrt.Strm().WriteOString( sOut );
     sOut.setLength(0);
-    rWrt.m_bLFPossible = true;
+    rWrt.SetLFPossible(true);
 
     rWrt.IncIndentLevel();  // indent the content of <TD>...</TD>
 
@@ -504,11 +504,11 @@ void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt,
 
     rWrt.DecIndentLevel();  // indent the content of <TD>...</TD>
 
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();
     aTag = bHead ? OOO_STRING_SVTOOLS_HTML_tableheader : 
OOO_STRING_SVTOOLS_HTML_tabledata;
     HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
aTag), false);
-    rWrt.m_bLFPossible = true;
+    rWrt.SetLFPossible(true);
 }
 
 // output a line as lines
@@ -640,7 +640,7 @@ void SwHTMLWrtTable::Write( SwHTMLWriter& rWrt, sal_Int16 
eAlign,
     // close previous numbering, etc
     rWrt.ChangeParaToken( HtmlTokenId::NONE );
 
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();  // <TABLE> in new line
     OStringBuffer sOut;
     sOut.append('<').append(rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_table);
@@ -1092,7 +1092,7 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode & 
rNode,
 
     if( text::HoriOrientation::NONE!=eDivHoriOri )
     {
-        if( rHTMLWrt.m_bLFPossible )
+        if (rHTMLWrt.IsLFPossible())
             rHTMLWrt.OutNewLine();  // <CENTER> in new line
         if( text::HoriOrientation::CENTER==eDivHoriOri )
         {
@@ -1120,12 +1120,12 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode 
& rNode,
             }
         }
         rHTMLWrt.IncIndentLevel();  // indent content of <CENTER>
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
     }
 
     // If the table isn't in a frame, then you always can output a LF.
     if( text::HoriOrientation::NONE==eTabHoriOri )
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
 
     const SwHTMLTableLayout *pLayout = rTable.GetHTMLTableLayout();
 
@@ -1155,7 +1155,7 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode & 
rNode,
 
     // If the table wasn't in a frame, then you always can output a LF.
     if( text::HoriOrientation::NONE==eTabHoriOri )
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
 
     if( text::HoriOrientation::NONE!=eDivHoriOri )
     {
@@ -1169,7 +1169,7 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode & 
rNode,
             // Not XHTML's css center: end <center>.
             HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), 
Concat2View(rHTMLWrt.GetNamespace() + aTag), false);
         }
-        rHTMLWrt.m_bLFPossible = true;
+        rHTMLWrt.SetLFPossible(true);
     }
 
     // move Pam behind the table
diff --git a/sw/source/filter/html/svxcss1.cxx 
b/sw/source/filter/html/svxcss1.cxx
index 9b312e0b3239..e4d06650e9a0 100644
--- a/sw/source/filter/html/svxcss1.cxx
+++ b/sw/source/filter/html/svxcss1.cxx
@@ -3083,6 +3083,19 @@ static void ParseCSS1_visibility(const CSS1Expression* 
pExpr, SfxItemSet& /*rIte
     rPropInfo.m_bVisible = pExpr->GetString() != "hidden";
 }
 
+static void ParseCSS1_white_space(const CSS1Expression* pExpr, SfxItemSet& 
/*rItemSet*/,
+                                  SvxCSS1PropertyInfo& rPropInfo, const 
SvxCSS1Parser& /*rParser*/)
+{
+    if (pExpr->GetType() == CSS1_IDENT)
+    {
+        if (pExpr->GetString().equalsIgnoreAsciiCase("pre")
+            || pExpr->GetString().equalsIgnoreAsciiCase("pre-wrap"))
+        {
+            rPropInfo.m_bPreserveSpace = true;
+        }
+    }
+}
+
 namespace {
 
 // the assignment of property to parsing function
@@ -3152,6 +3165,7 @@ CSS1PropEntry const aCSS1PropFnTab[] =
     CSS1_PROP_ENTRY(text_transform),
     CSS1_PROP_ENTRY(top),
     CSS1_PROP_ENTRY(visibility),
+    CSS1_PROP_ENTRY(white_space),
     CSS1_PROP_ENTRY(widows),
     CSS1_PROP_ENTRY(width),
 };
diff --git a/sw/source/filter/html/svxcss1.hxx 
b/sw/source/filter/html/svxcss1.hxx
index 30c5a7b4accb..5e992b62b08a 100644
--- a/sw/source/filter/html/svxcss1.hxx
+++ b/sw/source/filter/html/svxcss1.hxx
@@ -115,6 +115,7 @@ public:
     bool m_bTextIndent : 1;
     bool m_bNumbering : 1;
     bool m_bBullet : 1;
+    bool m_bPreserveSpace = false;
 
     SvxAdjust m_eFloat;
 
diff --git a/sw/source/filter/html/swhtml.cxx b/sw/source/filter/html/swhtml.cxx
index 6f9c71494461..2d8f99c44d95 100644
--- a/sw/source/filter/html/swhtml.cxx
+++ b/sw/source/filter/html/swhtml.cxx
@@ -3976,6 +3976,11 @@ void SwHTMLParser::NewPara()
             case HtmlOptionId::DIR:
                 aDir = rOption.GetString();
                 break;
+            case HtmlOptionId::XML_SPACE:
+                if (rOption.GetString() == "preserve")
+                    SetPreserveSpaces(true);
+                break;
+
             default: break;
         }
     }
@@ -3999,6 +4004,9 @@ void SwHTMLParser::NewPara()
                     "Class is not considered" );
             DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
             InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
+
+            if (aPropInfo.m_bPreserveSpace)
+                SetPreserveSpaces(true);
         }
     }
 
@@ -4064,6 +4072,7 @@ void SwHTMLParser::EndPara( bool bReal )
         SetTextCollAttrs();
 
     m_nOpenParaToken = HtmlTokenId::NONE;
+    SetPreserveSpaces(false);
 }
 
 void SwHTMLParser::NewHeading( HtmlTokenId nToken )
diff --git a/sw/source/filter/html/wrthtml.cxx 
b/sw/source/filter/html/wrthtml.cxx
index 63ea138ecf08..03879eb42ff0 100644
--- a/sw/source/filter/html/wrthtml.cxx
+++ b/sw/source/filter/html/wrthtml.cxx
@@ -146,7 +146,6 @@ SwHTMLWriter::SwHTMLWriter( const OUString& rBaseURL, 
std::u16string_view rFilte
     , m_bNoAlign( false )
     , m_bClearLeft( false )
     , m_bClearRight( false )
-    , m_bLFPossible( false )
     , m_bPreserveForm( false )
     , m_bCfgNetscape4( false )
     , mbSkipImages(false)
@@ -337,6 +336,16 @@ void SwHTMLWriter::SetupFilterFromPropertyValues(
         it->second >>= nVal;
         m_nLeadingTabWidth.emplace(nVal);
     }
+
+    it = aStoreMap.find("PreserveSpaces");
+    if (it != aStoreMap.end())
+    {
+        // Paragraphs with leading/trailing/repeated whitespace will have 
"white-space: pre-wrap"
+        // style; whitespace in content will not be altered (except for 
"LeadingTabWidth" effects)
+        bool bVal = false;
+        it->second >>= bVal;
+        m_bPreserveSpacesOnWrite = bVal;
+    }
 }
 
 ErrCode SwHTMLWriter::WriteStream()
@@ -444,6 +453,7 @@ ErrCode SwHTMLWriter::WriteStream()
     m_bPreserveForm = false;
     m_bClearLeft = m_bClearRight = false;
     m_bLFPossible = false;
+    m_bSpacePreserve = false;
 
     m_nLeftMargin = m_nDfltLeftMargin = m_nDfltRightMargin = 0;
     m_nDfltTopMargin = m_nDfltBottomMargin = 0;
@@ -550,7 +560,7 @@ ErrCode SwHTMLWriter::WriteStream()
         sal_uInt16 nHeaderAttrs = 0;
         m_pCurrPageDesc = MakeHeader( nHeaderAttrs );
 
-        m_bLFPossible = true;
+        SetLFPossible(true);
 
         // output forms which contain only HiddenControls
         OutHiddenForms();
@@ -590,7 +600,7 @@ ErrCode SwHTMLWriter::WriteStream()
                 OutHTML_HeaderFooter( *this, *pFooterFormat, false );
         }
 
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         if (!mbSkipHeaderFooter)
         {
@@ -710,7 +720,7 @@ static void lcl_html_OutSectionStartTag( SwHTMLWriter& 
rHTMLWrt,
 {
     OSL_ENSURE( pCol || !bContinued, "Continuation of DIV" );
 
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
 
     OStringBuffer sOut;
@@ -787,7 +797,7 @@ static void lcl_html_OutSectionStartTag( SwHTMLWriter& 
rHTMLWrt,
 
     rHTMLWrt.Strm().WriteChar( '>' );
 
-    rHTMLWrt.m_bLFPossible = true;
+    rHTMLWrt.SetLFPossible(true);
     if( !rName.isEmpty() && !bContinued )
         rHTMLWrt.OutImplicitMark( rName, "region" );
 
@@ -797,10 +807,10 @@ static void lcl_html_OutSectionStartTag( SwHTMLWriter& 
rHTMLWrt,
 static void lcl_html_OutSectionEndTag( SwHTMLWriter& rHTMLWrt )
 {
     rHTMLWrt.DecIndentLevel();
-    if( rHTMLWrt.m_bLFPossible )
+    if (rHTMLWrt.IsLFPossible())
         rHTMLWrt.OutNewLine();
     HTMLOutFuncs::Out_AsciiTag( rHTMLWrt.Strm(), 
Concat2View(rHTMLWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false 
);
-    rHTMLWrt.m_bLFPossible = true;
+    rHTMLWrt.SetLFPossible(true);
 }
 
 static Writer& OutHTML_Section( Writer& rWrt, const SwSectionNode& rSectNd )
diff --git a/sw/source/filter/html/wrthtml.hxx 
b/sw/source/filter/html/wrthtml.hxx
index 3914a27f134c..f8f5a111e6eb 100644
--- a/sw/source/filter/html/wrthtml.hxx
+++ b/sw/source/filter/html/wrthtml.hxx
@@ -278,6 +278,10 @@ class SW_DLLPUBLIC SwHTMLWriter : public Writer
 
     FieldUnit m_eCSS1Unit;
 
+    bool m_bLFPossible = false; // a line break can be inserted
+    bool m_bSpacePreserve = false; // Using xml::space="preserve", or 
"white-space: pre-wrap" style
+    bool m_bPreserveSpacesOnWrite = false; // If export should use 
m_bSpacePreserve
+
     sal_uInt16 OutHeaderAttrs();
     const SwPageDesc *MakeHeader( sal_uInt16& rHeaderAtrs );
     void GetControls();
@@ -392,7 +396,6 @@ public:
     bool m_bNoAlign : 1;              // HTML tag doesn't allow ALIGN=...
     bool m_bClearLeft : 1;            // <BR CLEAR=LEFT> write at end of 
paragraph
     bool m_bClearRight : 1;           // <BR CLEAR=RIGHT> write at end of 
paragraph
-    bool m_bLFPossible : 1;           // a line break can be inserted
 
     // others
 
@@ -486,7 +489,7 @@ public:
                            const OUString *pSVal, 
std::optional<sw::Css1Background> oBackground = std::nullopt );
     void OutCSS1_UnitProperty( const char *pProp, tools::Long nVal );
     void OutCSS1_PixelProperty( const char *pProp, tools::Long nVal, bool 
bVert );
-    void OutCSS1_SfxItemSet( const SfxItemSet& rItemSet, bool bDeep=true );
+    void OutCSS1_SfxItemSet( const SfxItemSet& rItemSet, bool bDeep=true, 
std::string_view rAdd = {} );
 
     // events of BODY tag from SFX configuration
     void OutBasicBodyEvents();
@@ -616,6 +619,12 @@ public:
 
     /// Determines the prefix string needed to respect the requested namespace 
alias.
     OString GetNamespace() const;
+
+    bool IsLFPossible() const { return !m_bSpacePreserve && m_bLFPossible; }
+    void SetLFPossible(bool val) { m_bLFPossible = val; }
+    bool IsSpacePreserve() const { return m_bSpacePreserve; }
+    void SetSpacePreserve(bool val) { m_bSpacePreserve = val; }
+    bool IsPreserveSpacesOnWritePrefSet() const { return 
m_bPreserveSpacesOnWrite; }
 };
 
 inline bool SwHTMLWriter::IsCSS1Source( sal_uInt16 n ) const
@@ -711,7 +720,7 @@ Writer& OutHTML_SwFormatLineBreak(Writer& rWrt, const 
SfxPoolItem& rHt);
 Writer& OutHTML_INetFormat( Writer&, const SwFormatINetFormat& rINetFormat, 
bool bOn );
 
 Writer& OutCSS1_BodyTagStyleOpt( Writer& rWrt, const SfxItemSet& rItemSet );
-Writer& OutCSS1_ParaTagStyleOpt( Writer& rWrt, const SfxItemSet& rItemSet );
+Writer& OutCSS1_ParaTagStyleOpt( Writer& rWrt, const SfxItemSet& rItemSet, 
std::string_view rAdd = {} );
 
 Writer& OutCSS1_HintSpanTag( Writer& rWrt, const SfxPoolItem& rHt );
 Writer& OutCSS1_HintStyleOpt( Writer& rWrt, const SfxPoolItem& rHt );

Reply via email to