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 );