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  |   61 ++++++++++++++++++++
 sw/source/filter/html/css1atr.cxx       |   20 +++++-
 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       |   27 ++++++--
 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 +++-
 21 files changed, 287 insertions(+), 119 deletions(-)

New commits:
commit 926826e40955175a8c115472e0d2f6c7f2f1a453
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Mon Oct 23 19:52:14 2023 +0300
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Mon Oct 23 22:10:06 2023 +0200

    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 cdcee168acba..00c8260749bd 100644
--- a/include/svtools/htmlkywd.hxx
+++ b/include/svtools/htmlkywd.hxx
@@ -523,6 +523,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..5ce3d27cb61d 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 : 1 = 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 5f81b3e3ca30..d1b0ea2ee03e 100644
--- a/svtools/source/svhtml/htmlkywd.cxx
+++ b/svtools/source/svhtml/htmlkywd.cxx
@@ -599,6 +599,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 7e8ac63fc61e..d94a24632779 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 be3c67fb0bba..a106dd53bf14 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -2762,6 +2762,67 @@ 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();
+    constexpr OUString paraText = u"\t test  \t more  text \t"_ustr;
+    pWrtShell->Insert(paraText);
+
+    // When exporting to plain HTML, using PreserveSpaces:
+    saveWithParams({
+        comphelper::makePropertyValue("FilterName", u"HTML (StarWriter)"_ustr),
+        comphelper::makePropertyValue("PreserveSpaces", true),
+    });
+
+    // 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"_ostr);
+    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)");
+    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();
+    constexpr OUString paraText = u"\t test  \t more  text \t"_ustr;
+    pWrtShell->Insert(paraText);
+
+    // When exporting to ReqIF, using PreserveSpaces:
+    saveWithParams({
+        comphelper::makePropertyValue("FilterName", u"HTML (StarWriter)"_ustr),
+        comphelper::makePropertyValue("FilterOptions", 
u"xhtmlns=reqif-xhtml"_ustr),
+        comphelper::makePropertyValue("PreserveSpaces", true),
+    });
+
+    // Then make sure that xml:space="preserve" attribute exists in the 
paragraph element:
+    xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile();
+    assertXPath(pXmlDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p", 
"space"_ostr,
+                u"preserve"_ustr);
+    // 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)");
+    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 a922be479841..b98ba5cf73d1 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -1782,11 +1782,11 @@ SwHTMLWriter& OutCSS1_BodyTagStyleOpt( SwHTMLWriter& 
rWrt, const SfxItemSet& rIt
     return rWrt;
 }
 
-SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& 
rItemSet )
+SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& 
rItemSet, std::string_view rAdd )
 {
     SwCSS1OutMode aMode( rWrt, rWrt.m_nCSS1Script|CSS1_OUTMODE_STYLE_OPT |
                                    CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_PARA, 
nullptr );
-    rWrt.OutCSS1_SfxItemSet( rItemSet, false );
+    rWrt.OutCSS1_SfxItemSet( rItemSet, false, rAdd );
 
     return rWrt;
 }
@@ -3571,7 +3571,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 );
@@ -3603,6 +3603,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;)
+        {
+            std::string_view attr = o3tl::trim(o3tl::getToken(rAdd, ':', 
index));
+            assert(!attr.empty());
+            assert(index != std::string_view::npos);
+
+            std::string_view val = o3tl::trim(o3tl::getToken(rAdd, ':', 
index));
+            assert(!val.empty());
+            OutCSS1_PropertyAscii(attr, val);
+        }
+    }
+
     if( m_bFirstCSS1Property )
         return;
 
diff --git a/sw/source/filter/html/css1kywd.hxx 
b/sw/source/filter/html/css1kywd.hxx
index 5d84e3825191..42ad28da9259 100644
--- a/sw/source/filter/html/css1kywd.hxx
+++ b/sw/source/filter/html/css1kywd.hxx
@@ -217,6 +217,8 @@ constexpr inline std::string_view sCSS1_PV_inherit = 
"inherit";
 
 constexpr inline std::string_view sCSS1_P_display = "display";
 
+constexpr inline std::string_view sCSS1_white_space = "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 d38f759acb84..da610d7ceccb 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;
 }
@@ -749,7 +749,7 @@ static void OutHTML_SwFormat( SwHTMLWriter& rWrt, const 
SwFormat& rFormat,
             (rWrt.m_nDefListLvl-1) * rWrt.m_nDefListMargin;
     }
 
-    if( rWrt.m_bLFPossible && !rWrt.m_bFirstLine )
+    if (rWrt.IsLFPossible() && !rWrt.m_bFirstLine)
         rWrt.OutNewLine(); // paragraph tag on a new line
     rInfo.bOutPara = false;
 
@@ -811,7 +811,7 @@ static void OutHTML_SwFormat( SwHTMLWriter& rWrt, const 
SwFormat& rFormat,
         rWrt.m_bNoAlign = false;
         rInfo.bOutDiv = true;
         rWrt.IncIndentLevel();
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
         rWrt.OutNewLine();
     }
 
@@ -925,6 +925,15 @@ static void OutHTML_SwFormat( SwHTMLWriter& rWrt, const 
SwFormat& rFormat,
         rWrt.Strm().WriteOString( sOut );
         sOut = "";
 
+        std::string_view sStyle;
+        if (rWrt.IsSpacePreserve())
+        {
+            if (rWrt.mbXHTML)
+                rWrt.Strm().WriteOString(" xml:space=\"preserve\"");
+            else
+                sStyle = "white-space: pre-wrap";
+        }
+
         // if necessary, output alignment
         if( !rWrt.m_bNoAlign && pAdjItem )
             OutHTML_SvxAdjust( rWrt, *pAdjItem );
@@ -935,7 +944,7 @@ static void OutHTML_SwFormat( SwHTMLWriter& rWrt, const 
SwFormat& rFormat,
         // and now, if necessary, the STYLE options
         if (rWrt.m_bCfgOutStyles && rInfo.moItemSet)
         {
-            OutCSS1_ParaTagStyleOpt( rWrt, *rInfo.moItemSet );
+            OutCSS1_ParaTagStyleOpt(rWrt, *rInfo.moItemSet, sStyle);
         }
 
         if (rWrt.m_bParaDotLeaders) {
@@ -1005,7 +1014,7 @@ static void OutHTML_SwFormatOff( SwHTMLWriter& rWrt, 
const SwHTMLTextCollOutputI
 
     if( rInfo.ShouldOutputToken() )
     {
-        if( rWrt.m_bPrettyPrint && rWrt.m_bLFPossible )
+        if (rWrt.m_bPrettyPrint && rWrt.IsLFPossible())
             rWrt.OutNewLine( true );
 
         // if necessary, for BLOCKQUOTE, ADDRESS and DD another paragraph token
@@ -1016,18 +1025,18 @@ static void OutHTML_SwFormatOff( SwHTMLWriter& rWrt, 
const SwHTMLTextCollOutputI
             HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_parabreak), false );
 
         HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rWrt.GetNamespace() + rInfo.aToken), false );
-        rWrt.m_bLFPossible =
+        rWrt.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 )
     {
         rWrt.DecIndentLevel();
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine();
         HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false );
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     // if necessary, close the list item, then close a bulleted or numbered 
list
@@ -2002,6 +2011,30 @@ void HTMLEndPosLst::OutEndAttrs( SwHTMLWriter& rWrt, 
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*/
 SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, const SwContentNode& 
rNode )
 {
@@ -2027,10 +2060,10 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
         // Output all the nodes that are anchored to a frame
         rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any );
 
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine(); // paragraph tag on a new line
 
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
 
         HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace);
         aHtml.prettyPrint(rWrt.m_bPrettyPrint);
@@ -2138,11 +2171,11 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
             {
                 // ... and it is located before a table or a section
                 rWrt.OutBookmarks();
-                rWrt.m_bLFPossible = rWrt.m_nLastParaToken == 
HtmlTokenId::NONE;
+                rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE);
 
                 // Output all frames that are anchored to this node
                 rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any );
-                rWrt.m_bLFPossible = false;
+                rWrt.SetLFPossible(false);
 
                 return rWrt;
             }
@@ -2213,12 +2246,15 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
     // now, output the tag of the paragraph
     const SwFormat& rFormat = pNd->GetAnyFormatColl();
     SwHTMLTextCollOutputInfo aFormatInfo;
-    bool bOldLFPossible = rWrt.m_bLFPossible;
+    bool bOldLFPossible = rWrt.IsLFPossible();
+    bool bOldSpacePreserve = rWrt.IsSpacePreserve();
+    if (rWrt.IsPreserveSpacesOnWritePrefSet())
+        rWrt.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
-    rWrt.m_bLFPossible = rWrt.m_nLastParaToken == HtmlTokenId::NONE;
-    if( !bOldLFPossible && rWrt.m_bLFPossible )
+    rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE);
+    if (!bOldLFPossible && rWrt.IsLFPossible())
         rWrt.OutNewLine();
 
     // then, the bookmarks (including end tag)
@@ -2228,12 +2264,12 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& 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 rWrt.m_nWishLineLen as -1, for now keep old 
flow
     // when LOK side will be fixed - don't insert new line at the beginning
-    if( rWrt.m_bLFPossible && rWrt.m_bPrettyPrint && rWrt.m_nWishLineLen >= 0 
&&
+    if( rWrt.IsLFPossible() && rWrt.m_bPrettyPrint && rWrt.m_nWishLineLen >= 0 
&&
         rWrt.GetLineLen() >= rWrt.m_nWishLineLen )
     {
         rWrt.OutNewLine();
     }
-    rWrt.m_bLFPossible = false;
+    rWrt.SetLFPossible(false);
 
     // find text that originates from an outline numbering
     sal_Int32 nOffset = 0;
@@ -2430,16 +2466,16 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
 
             if( pTextHt )
             {
-                rWrt.m_bLFPossible = rWrt.m_nLastParaToken == 
HtmlTokenId::NONE &&
+                rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE 
&&
                                        nStrPos > 0 &&
-                                       rStr[nStrPos-1] == ' ';
+                                       rStr[nStrPos-1] == ' ');
                 sal_uInt16 nCSS1Script = rWrt.m_nCSS1Script;
                 rWrt.m_nCSS1Script = aEndPosLst.GetScriptAtPos(
                                                 nStrPos + nOffset, nCSS1Script 
);
                 HTMLOutFuncs::FlushToAscii( rWrt.Strm() );
                 Out( aHTMLAttrFnTab, pTextHt->GetAttr(), rWrt );
                 rWrt.m_nCSS1Script = nCSS1Script;
-                rWrt.m_bLFPossible = false;
+                rWrt.SetLFPossible(false);
             }
 
             if( bOutChar )
@@ -2457,7 +2493,7 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
 
                 // try to split a line after about 255 characters
                 // at a space character unless in a PRE-context
-                if( ' ' == c && rWrt.m_nLastParaToken == HtmlTokenId::NONE  )
+                if( ' ' == c && rWrt.m_nLastParaToken == HtmlTokenId::NONE && 
!rWrt.IsSpacePreserve() )
                 {
                     sal_Int32 nLineLen;
                     nLineLen = rWrt.GetLineLen();
@@ -2569,7 +2605,7 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
             {
                 aHtml.single(OOO_STRING_SVTOOLS_HTML_linebreak);
             }
-            rWrt.m_bLFPossible = true;
+            rWrt.SetLFPossible(true);
         }
     }
 
@@ -2597,15 +2633,15 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
         rWrt.m_bClearLeft = false;
         rWrt.m_bClearRight = false;
 
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     // if an LF is not allowed already, it is allowed once the paragraphs
     // ends with a ' '
-    if( !rWrt.m_bLFPossible &&
+    if (!rWrt.IsLFPossible() &&
         rWrt.m_nLastParaToken == HtmlTokenId::NONE &&
         nEnd > 0 && ' ' == rStr[nEnd-1] )
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
 
     // dot leaders: print the skipped page number in a different span element
     if (nIndexTab > -1) {
@@ -2615,6 +2651,7 @@ SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, 
const SwContentNode& rNode
 
     rWrt.m_bTagOn = false;
     OutHTML_SwFormatOff( rWrt, aFormatInfo );
+    rWrt.SetSpacePreserve(bOldSpacePreserve);
 
     // if necessary, close a form
     rWrt.OutForm( false );
diff --git a/sw/source/filter/html/htmlfldw.cxx 
b/sw/source/filter/html/htmlfldw.cxx
index 04f5dad8dbdc..e8d38608b42c 100644
--- a/sw/source/filter/html/htmlfldw.cxx
+++ b/sw/source/filter/html/htmlfldw.cxx
@@ -519,7 +519,7 @@ SwHTMLWriter& OutHTML_SwFormatField( SwHTMLWriter& rWrt, 
const SfxPoolItem& rHt
     }
     else if( SwFieldIds::Script == pFieldTyp->Which() )
     {
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine( true );
 
         bool bURL = static_cast<const SwScriptField *>(pField)->IsCodeURL();
@@ -535,7 +535,7 @@ SwHTMLWriter& OutHTML_SwFormatField( SwHTMLWriter& rWrt, 
const SfxPoolItem& rHt
         HTMLOutFuncs::OutScript( rWrt.Strm(), rWrt.GetBaseURL(), aContents, 
rType, JAVASCRIPT,
                                  aURL, nullptr, nullptr );
 
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine( true );
     }
     else
diff --git a/sw/source/filter/html/htmlflywriter.cxx 
b/sw/source/filter/html/htmlflywriter.cxx
index ed25c65d162e..1f19eb77a6c7 100644
--- a/sw/source/filter/html/htmlflywriter.cxx
+++ b/sw/source/filter/html/htmlflywriter.cxx
@@ -415,7 +415,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;
@@ -443,7 +443,7 @@ void SwHTMLWriter::OutFrameFormat( AllHtmlFlags nMode, 
const SwFrameFormat& rFra
         if( HtmlContainerFlags::Div == nCntnrMode )
         {
             IncIndentLevel();
-            m_bLFPossible = true;
+            SetLFPossible(true);
         }
     }
 
@@ -495,10 +495,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 );
@@ -1163,7 +1163,7 @@ OUString lclWriteOutImap(SwHTMLWriter& rWrt, const 
SfxItemSet& rItemSet, const S
         OString aIndMap, aIndArea;
         const char *pIndArea = nullptr, *pIndMap = nullptr;
 
-        if (rWrt.m_bLFPossible)
+        if (rWrt.IsLFPossible())
         {
             rWrt.OutNewLine( true );
             aIndMap = rWrt.GetIndentString();
@@ -1253,7 +1253,7 @@ SwHTMLWriter& OutHTML_ImageStart( HtmlWriter& rHtml, 
SwHTMLWriter& rWrt, const S
     OUString aIMapName = lclWriteOutImap(rWrt, rItemSet, rFrameFormat, 
rRealSize, pAltImgMap, pURLItem);
 
     // put img into new line
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine( true );
 
     // <a name=...></a>...<img ...>
@@ -1594,7 +1594,7 @@ static SwHTMLWriter & OutHTML_FrameFormatAsMulticol( 
SwHTMLWriter& rWrt,
     rWrt.OutAndSetDefList( 0 );
 
     // output as Multicol
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();
 
     OStringBuffer sOut("<" + rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_multicol);
@@ -1631,7 +1631,7 @@ static SwHTMLWriter & OutHTML_FrameFormatAsMulticol( 
SwHTMLWriter& rWrt,
 
     rWrt.Strm().WriteChar( '>' );
 
-    rWrt.m_bLFPossible = true;
+    rWrt.SetLFPossible(true);
     rWrt.IncIndentLevel();  // indent the content of Multicol
 
     const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
@@ -1650,10 +1650,10 @@ static SwHTMLWriter & OutHTML_FrameFormatAsMulticol( 
SwHTMLWriter& rWrt,
     }
 
     rWrt.DecIndentLevel();  // indent the content of Multicol;
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();
     HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_multicol), false );
-    rWrt.m_bLFPossible = true;
+    rWrt.SetLFPossible(true);
 
     return rWrt;
 }
@@ -1661,7 +1661,7 @@ static SwHTMLWriter & OutHTML_FrameFormatAsMulticol( 
SwHTMLWriter& rWrt,
 static SwHTMLWriter& OutHTML_FrameFormatAsSpacer( SwHTMLWriter& rWrt, const 
SwFrameFormat& rFrameFormat )
 {
     // if possible, output a line break before the graphic
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine( true );
 
     OString sOut =
@@ -1696,7 +1696,7 @@ static SwHTMLWriter& OutHTML_FrameFormatAsDivOrSpan( 
SwHTMLWriter& rWrt,
         aTag = OOO_STRING_SVTOOLS_HTML_span;
 
     // output as DIV
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();
 
     OStringBuffer sOut("<" + rWrt.GetNamespace() + aTag);
@@ -1711,7 +1711,7 @@ static SwHTMLWriter& OutHTML_FrameFormatAsDivOrSpan( 
SwHTMLWriter& rWrt,
     rWrt.Strm().WriteChar( '>' );
 
     rWrt.IncIndentLevel();  // indent the content
-    rWrt.m_bLFPossible = true;
+    rWrt.SetLFPossible(true);
 
     const SwFormatContent& rFlyContent = rFrameFormat.GetContent();
     SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex();
@@ -1733,7 +1733,7 @@ static SwHTMLWriter& OutHTML_FrameFormatAsDivOrSpan( 
SwHTMLWriter& rWrt,
     }
 
     rWrt.DecIndentLevel();  // indent the content of Multicol;
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine();
     HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
aTag), false );
 
diff --git a/sw/source/filter/html/htmlforw.cxx 
b/sw/source/filter/html/htmlforw.cxx
index b35cd76a24a8..e5a1efa8af1e 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 "=\""
@@ -769,7 +769,7 @@ SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( 
SwHTMLWriter& rWrt,
         break;
 
     case form::FormComponentType::LISTBOX:
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine( true );
         eTag = TAG_SELECT;
         aTmp = xPropSet->getPropertyValue( "Dropdown" );
@@ -815,7 +815,7 @@ SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( 
SwHTMLWriter& rWrt,
 
             if( bMultiLine )
             {
-                if( rWrt.m_bLFPossible )
+                if (rWrt.IsLFPossible())
                     rWrt.OutNewLine( true );
                 eTag = TAG_TEXTAREA;
 
@@ -1244,7 +1244,7 @@ SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( 
SwHTMLWriter& rWrt,
         rWrt.Strm().WriteOString( aEndTags );
 
     // Controls aren't bound to a paragraph, therefore don't output LF anymore!
-    rWrt.m_bLFPossible = false;
+    rWrt.SetLFPossible(false);
 
     if( rWrt.mxFormComps.is() )
         rWrt.OutHiddenControls( rWrt.mxFormComps, xPropSet );
diff --git a/sw/source/filter/html/htmlftn.cxx 
b/sw/source/filter/html/htmlftn.cxx
index 10b909d1df41..ed222e4e457d 100644
--- a/sw/source/filter/html/htmlftn.cxx
+++ b/sw/source/filter/html/htmlftn.cxx
@@ -355,7 +355,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
@@ -364,7 +364,7 @@ void SwHTMLWriter::OutFootEndNotes()
         HTMLOutFuncs::Out_String( Strm(), sFootnoteName );
         Strm().WriteOString( "\">" );
 
-        m_bLFPossible = true;
+        SetLFPossible(true);
         IncIndentLevel();   // indent content of <DIV>
 
         OSL_ENSURE( pTextFootnote, "SwHTMLWriter::OutFootEndNotes: 
SwTextFootnote is missing" );
@@ -379,10 +379,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 6640f8f9376b..a4115ae7b4b8 100644
--- a/sw/source/filter/html/htmlnumwriter.cxx
+++ b/sw/source/filter/html/htmlnumwriter.cxx
@@ -309,7 +309,7 @@ SwHTMLWriter& 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:
@@ -327,7 +327,7 @@ SwHTMLWriter& 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 e078b00e2d3b..0216a6929dbf 100644
--- a/sw/source/filter/html/htmlplug.cxx
+++ b/sw/source/filter/html/htmlplug.cxx
@@ -1240,7 +1240,7 @@ SwHTMLWriter& OutHTML_FrameFormatOLENode( SwHTMLWriter& 
rWrt, const SwFrameForma
     HtmlFrmOpts nFrameOpts;
 
     // if possible output a line break before the "object"
-    if( rWrt.m_bLFPossible )
+    if (rWrt.IsLFPossible())
         rWrt.OutNewLine( true );
 
     if( !rFrameFormat.GetName().isEmpty() )
@@ -1635,7 +1635,7 @@ SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( 
SwHTMLWriter& rWrt, const SwFrameFo
         aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), 
aFileName);
 
         // Refer to this data.
-        if (rWrt.m_bLFPossible)
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine();
         rWrt.Strm().WriteOString(Concat2View("<" + rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_object));
         rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + 
"\""));
@@ -1643,7 +1643,7 @@ SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( 
SwHTMLWriter& rWrt, const SwFrameFo
             rWrt.Strm().WriteOString(Concat2View(" type=\"" + 
aFileType.toUtf8() + "\""));
         rWrt.Strm().WriteOString(">");
         bObjectOpened = true;
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     if (!bObjectOpened || bWriteReplacementGraphic)
diff --git a/sw/source/filter/html/htmltabw.cxx 
b/sw/source/filter/html/htmltabw.cxx
index d55512e1a44a..89fc247b8b7c 100644
--- a/sw/source/filter/html/htmltabw.cxx
+++ b/sw/source/filter/html/htmltabw.cxx
@@ -449,7 +449,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>
 
@@ -486,11 +486,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
@@ -614,7 +614,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("<" + rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_table);
 
@@ -1047,7 +1047,7 @@ SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& rWrt, 
SwTableNode & rNode,
 
     if( text::HoriOrientation::NONE!=eDivHoriOri )
     {
-        if( rWrt.m_bLFPossible )
+        if (rWrt.IsLFPossible())
             rWrt.OutNewLine();  // <CENTER> in new line
         if( text::HoriOrientation::CENTER==eDivHoriOri )
         {
@@ -1075,12 +1075,12 @@ SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& rWrt, 
SwTableNode & rNode,
             }
         }
         rWrt.IncIndentLevel();  // indent content of <CENTER>
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     // If the table isn't in a frame, then you always can output a LF.
     if( text::HoriOrientation::NONE==eTabHoriOri )
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
 
     const SwHTMLTableLayout *pLayout = rTable.GetHTMLTableLayout();
 
@@ -1110,7 +1110,7 @@ SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& rWrt, 
SwTableNode & rNode,
 
     // If the table wasn't in a frame, then you always can output a LF.
     if( text::HoriOrientation::NONE==eTabHoriOri )
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
 
     if( text::HoriOrientation::NONE!=eDivHoriOri )
     {
@@ -1124,7 +1124,7 @@ SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& rWrt, 
SwTableNode & rNode,
             // Not XHTML's css center: end <center>.
             HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), 
Concat2View(rWrt.GetNamespace() + aTag), false);
         }
-        rWrt.m_bLFPossible = true;
+        rWrt.SetLFPossible(true);
     }
 
     // move Pam behind the table
diff --git a/sw/source/filter/html/svxcss1.cxx 
b/sw/source/filter/html/svxcss1.cxx
index 0150971144fe..d75cc487dab1 100644
--- a/sw/source/filter/html/svxcss1.cxx
+++ b/sw/source/filter/html/svxcss1.cxx
@@ -3044,6 +3044,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
@@ -3056,7 +3069,7 @@ struct CSS1PropEntry
 }
 
 // the table with assignments
-CSS1PropEntry const aCSS1PropFnTab[] =
+CSS1PropEntry constexpr aCSS1PropFnTab[] =
 {
     { sCSS1_P_background, ParseCSS1_background },
     { sCSS1_P_background_color, ParseCSS1_background_color },
@@ -3110,10 +3123,15 @@ CSS1PropEntry const aCSS1PropFnTab[] =
     { sCSS1_P_text_transform, ParseCSS1_text_transform },
     { sCSS1_P_top, ParseCSS1_top },
     { sCSS1_P_visibility, ParseCSS1_visibility },
+    { sCSS1_white_space, ParseCSS1_white_space },
     { sCSS1_P_widows, ParseCSS1_widows },
     { sCSS1_P_width, ParseCSS1_width },
 };
 
+static_assert(std::is_sorted(std::begin(aCSS1PropFnTab), 
std::end(aCSS1PropFnTab),
+                             [](const auto& lhs, const auto& rhs) constexpr
+                             { return lhs.pName < rhs.pName; }));
+
 static bool CSS1PropEntryFindCompare(CSS1PropEntry const & lhs, OUString const 
& s)
 {
     return s.compareToIgnoreAsciiCaseAscii(lhs.pName) > 0;
@@ -3124,13 +3142,6 @@ void SvxCSS1Parser::DeclarationParsed( const OUString& 
rProperty,
 {
     OSL_ENSURE( m_pItemSet, "DeclarationParsed() without ItemSet" );
 
-    // TODO: convert to static_assert, when C++20 constexpr std::is_sorted is 
available
-    [[maybe_unused]] static const bool bSortedPropFns = []() {
-        assert( std::is_sorted( std::begin(aCSS1PropFnTab), 
std::end(aCSS1PropFnTab),
-            [](const auto& lhs, const auto& rhs) constexpr { return lhs.pName 
< rhs.pName; } ) );
-        return true;
-    }();
-
     auto it = std::lower_bound( std::begin(aCSS1PropFnTab), 
std::end(aCSS1PropFnTab), rProperty,
                                 CSS1PropEntryFindCompare );
     if( it != std::end(aCSS1PropFnTab) && 
!CSS1PropEntryFindCompare(*it,rProperty)  )
diff --git a/sw/source/filter/html/svxcss1.hxx 
b/sw/source/filter/html/svxcss1.hxx
index 985f98a079b7..669ed92a5b95 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 : 1 = false;
 
     SvxAdjust m_eFloat;
 
diff --git a/sw/source/filter/html/swhtml.cxx b/sw/source/filter/html/swhtml.cxx
index 2ef2105995db..17c0da4e25c2 100644
--- a/sw/source/filter/html/swhtml.cxx
+++ b/sw/source/filter/html/swhtml.cxx
@@ -3977,6 +3977,11 @@ void SwHTMLParser::NewPara()
             case HtmlOptionId::DIR:
                 aDir = rOption.GetString();
                 break;
+            case HtmlOptionId::XML_SPACE:
+                if (rOption.GetString() == "preserve")
+                    SetPreserveSpaces(true);
+                break;
+
             default: break;
         }
     }
@@ -4000,6 +4005,9 @@ void SwHTMLParser::NewPara()
                     "Class is not considered" );
             DoPositioning( aItemSet, aPropInfo, xCntxt.get() );
             InsertAttrs( aItemSet, aPropInfo, xCntxt.get() );
+
+            if (aPropInfo.m_bPreserveSpace)
+                SetPreserveSpaces(true);
         }
     }
 
@@ -4065,6 +4073,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 32cdb1ba5271..0578bbbf6682 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)
@@ -348,6 +347,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()
@@ -455,6 +464,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;
@@ -561,7 +571,7 @@ ErrCode SwHTMLWriter::WriteStream()
         sal_uInt16 nHeaderAttrs = 0;
         m_pCurrPageDesc = MakeHeader( nHeaderAttrs );
 
-        m_bLFPossible = true;
+        SetLFPossible(true);
 
         // output forms which contain only HiddenControls
         OutHiddenForms();
@@ -601,7 +611,7 @@ ErrCode SwHTMLWriter::WriteStream()
                 OutHTML_HeaderFooter( *this, *pFooterFormat, false );
         }
 
-        if( m_bLFPossible )
+        if (IsLFPossible())
             OutNewLine();
         if (!mbSkipHeaderFooter)
         {
@@ -721,7 +731,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("<" + rHTMLWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_division);
@@ -793,7 +803,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" );
 
@@ -803,10 +813,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 SwHTMLWriter& OutHTML_Section( SwHTMLWriter& rWrt, const SwSectionNode& 
rSectNd )
diff --git a/sw/source/filter/html/wrthtml.hxx 
b/sw/source/filter/html/wrthtml.hxx
index 5dc00bbb982a..6b49fe984119 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
 
@@ -485,7 +488,7 @@ public:
                            const OUString *pSVal, 
std::optional<sw::Css1Background> oBackground = std::nullopt );
     void OutCSS1_UnitProperty( std::string_view pProp, tools::Long nVal );
     void OutCSS1_PixelProperty( std::string_view pProp, tools::Long nTwips );
-    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
@@ -706,7 +715,7 @@ SwHTMLWriter& OutHTML_SwFormatLineBreak(SwHTMLWriter& rWrt, 
const SfxPoolItem& r
 SwHTMLWriter& OutHTML_INetFormat( SwHTMLWriter&, const SwFormatINetFormat& 
rINetFormat, bool bOn );
 
 SwHTMLWriter& OutCSS1_BodyTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& 
rItemSet );
-SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& 
rItemSet );
+SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& 
rItemSet, std::string_view rAdd = {} );
 
 SwHTMLWriter& OutCSS1_HintSpanTag( SwHTMLWriter& rWrt, const SfxPoolItem& rHt 
);
 SwHTMLWriter& OutCSS1_HintStyleOpt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt 
);

Reply via email to