sw/qa/extras/htmlexport/data/sub_li_and_ctd.fodt |   33 ++++++++++++++++
 sw/qa/extras/htmlexport/htmlexport.cxx           |   34 +++++++++++++++++
 sw/source/filter/html/htmlatr.cxx                |    2 -
 sw/source/filter/html/htmlnum.cxx                |   46 +++++++++++++++++++++++
 sw/source/filter/html/htmlnum.hxx                |    6 +--
 sw/source/filter/html/htmlnumwriter.cxx          |   19 +++++----
 sw/source/filter/html/htmltabw.cxx               |   10 ++---
 7 files changed, 132 insertions(+), 18 deletions(-)

New commits:
commit ebf8b0b0699fe34c86badb3087869d902a4e40a8
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Thu May 18 15:56:46 2023 +0300
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Mon May 22 11:28:06 2023 +0200

    tdf#155387: fix list restart and opening/closing conditions
    
    1. List restart flag in the node does not yet mean that the restart
    would actually happen: it will be ignored, if the next node is more
    nested than the previous one.
    
    2. When writing nested list items, we should not write additional
    <li><ul> pairs for current level (previously, only level 0 was
    skipped); same for closing.
    
    Change-Id: I5f67e8fa9236e7e2e6dba2e0ec5dffbf0d7b7f8b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151958
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152010

diff --git a/sw/qa/extras/htmlexport/data/sub_li_and_ctd.fodt 
b/sw/qa/extras/htmlexport/data/sub_li_and_ctd.fodt
new file mode 100644
index 000000000000..b4a3977926c6
--- /dev/null
+++ b/sw/qa/extras/htmlexport/data/sub_li_and_ctd.fodt
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:body>
+  <office:text>
+   <text:list>
+    <text:list-item>
+     <text:p>l1</text:p>
+     <text:p>l1_ctd1</text:p>
+     <text:list>
+      <text:list-item>
+       <text:p>l2</text:p>
+      </text:list-item>
+      <text:list-item>
+       <text:p>l2</text:p>
+      </text:list-item>
+     </text:list>
+     <text:p>l1_ctd2</text:p>
+     <text:list>
+      <text:list-item>
+       <text:list>
+        <text:list-item>
+         <text:p>l3</text:p>
+        </text:list-item>
+       </text:list>
+      </text:list-item>
+     </text:list>
+     <text:p>l1_ctd3</text:p>
+    </text:list-item>
+   </text:list>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx 
b/sw/qa/extras/htmlexport/htmlexport.cxx
index 710a45c10f3e..b42eef6d83f5 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -2459,6 +2459,40 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, 
testReqIfTransparentTifImg)
     CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::TIF, aDescriptor.GetFileFormat());
 }
 
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testTdf155387)
+{
+    createSwDoc("sub_li_and_ctd.fodt");
+    ExportToReqif();
+
+    SvMemoryStream aStream;
+    WrapReqifFromTempFile(aStream);
+    xmlDocUniquePtr pDoc = parseXmlStream(&aStream);
+    // Without the fix in place, this would fail
+    CPPUNIT_ASSERT(pDoc);
+
+    // Single top-level list
+    assertXPath(pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul");
+    // Single top-level item
+    assertXPath(pDoc, 
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li");
+    // 4 top-level paragraphs in the item
+    assertXPath(pDoc,
+                
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:p",
 4);
+    // 2 sublists in the item
+    assertXPath(
+        pDoc, 
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul",
 2);
+    // 2 items in the first sublist
+    assertXPath(pDoc,
+                
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul[1]/"
+                "reqif-xhtml:li",
+                2);
+    // Check the last (most nested) subitem's text
+    assertXPathContent(
+        pDoc,
+        
"/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:ul[2]/"
+        "reqif-xhtml:li/reqif-xhtml:ul/reqif-xhtml:li/reqif-xhtml:p",
+        "l3");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index 822718763e4d..95db5a76525a 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -982,7 +982,7 @@ static void OutHTML_SwFormatOff( Writer& rWrt, const 
SwHTMLTextCollOutputInfo& r
             const SwHTMLNumRuleInfo& rNRInfo = rHWrt.GetNumInfo();
             if( rNextInfo.GetNumRule() != rNRInfo.GetNumRule() ||
                 rNextInfo.GetDepth() != rNRInfo.GetDepth() ||
-                rNextInfo.IsNumbered() || rNextInfo.IsRestart() )
+                rNextInfo.IsNumbered() || rNextInfo.IsRestart(rNRInfo) )
                 rHWrt.ChangeParaToken( HtmlTokenId::NONE );
             OutHTML_NumberBulletListEnd( rHWrt, rNextInfo );
         }
diff --git a/sw/source/filter/html/htmlnum.cxx 
b/sw/source/filter/html/htmlnum.cxx
index bd8317bb5676..8fa120a630cf 100644
--- a/sw/source/filter/html/htmlnum.cxx
+++ b/sw/source/filter/html/htmlnum.cxx
@@ -44,4 +44,50 @@ void SwHTMLNumRuleInfo::Set(const SwTextNode& rTextNd)
     }
 }
 
+// Restart flag is only effective when this level is not below the previous
+bool SwHTMLNumRuleInfo::IsRestart(const SwHTMLNumRuleInfo& rPrev) const
+{
+    // calling this, when the rules are different, makes no sense
+    assert(rPrev.GetNumRule() == GetNumRule());
+
+    // An example ODF when the restart flag is set, but has no effect:
+    //   <text:list text:style-name="L1">
+    //    <text:list-item>
+    //     <text:p>l1</text:p>
+    //     <text:list>
+    //      <text:list-item>
+    //       <text:p>l2</text:p>
+    //      </text:list-item>
+    //      <text:list-item>
+    //       <text:p>l2</text:p>
+    //      </text:list-item>
+    //     </text:list>
+    //     <text:list>
+    //      <text:list-item>
+    //       <text:list>
+    //        <text:list-item>
+    //         <text:p>l3</text:p>
+    //        </text:list-item>
+    //       </text:list>
+    //      </text:list-item>
+    //     </text:list>
+    //    </text:list-item>
+    //   </text:list>
+    // In this case, "l3" is in a separate sublist than "l2", and so the "l3" 
node gets the
+    // "list restart" property. But the document rendering would be
+    //   1. l1
+    //        1.1. l2
+    //        1.2. l2
+    //             1.2.1. l3
+    // and the second-level numbering will not actually restart at the "l3" 
node.
+    //
+    // TODO/LATER: note that restarting may happen at different levels. In the 
code using this
+    // function, the level is reset to 0 whenever a restart is detected. And 
also, there is no
+    // code to actually descend to that new level (close corresponding 
li/ul/ol elements).
+
+    if (rPrev.GetDepth() < GetDepth())
+        return false; // No matter if the restart flag is set, it is not 
effective for subitems
+    return m_bRestart;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlnum.hxx 
b/sw/source/filter/html/htmlnum.hxx
index b73fd386e3f3..670f08229df2 100644
--- a/sw/source/filter/html/htmlnum.hxx
+++ b/sw/source/filter/html/htmlnum.hxx
@@ -38,8 +38,8 @@ class SwHTMLNumRuleInfo
     sal_uInt16      m_aNumStarts[MAXLEVEL];
     SwNumRule   *   m_pNumRule;       // current numbering
     sal_uInt16      m_nDeep;          // current numbering depth (1, 2, 3, ...)
-    bool        m_bRestart : 1;   // Export: restart numbering
-    bool        m_bNumbered : 1;  // Export: paragraph is numbered
+    bool        m_bRestart;   // Export: restart numbering
+    bool        m_bNumbered;  // Export: paragraph is numbered
 
 public:
 
@@ -75,7 +75,7 @@ public:
     void DecDepth() { if (m_nDeep!=0) --m_nDeep; }
     inline sal_uInt8 GetLevel() const;
 
-    bool IsRestart() const { return m_bRestart; }
+    bool IsRestart(const SwHTMLNumRuleInfo& rPrev) const;
 
     bool IsNumbered() const { return m_bNumbered; }
 
diff --git a/sw/source/filter/html/htmlnumwriter.cxx 
b/sw/source/filter/html/htmlnumwriter.cxx
index 19483e518b9e..49ea346d17e2 100644
--- a/sw/source/filter/html/htmlnumwriter.cxx
+++ b/sw/source/filter/html/htmlnumwriter.cxx
@@ -53,7 +53,7 @@ void SwHTMLWriter::FillNextNumInfo()
             // numbering level during import.
             if( bTable &&
                 m_pNextNumRuleInfo->GetNumRule()==GetNumInfo().GetNumRule() &&
-                !m_pNextNumRuleInfo->IsRestart() )
+                !m_pNextNumRuleInfo->IsRestart(GetNumInfo()) )
             {
                 m_pNextNumRuleInfo->SetDepth( GetNumInfo().GetDepth() );
             }
@@ -90,7 +90,7 @@ Writer& OutHTML_NumberBulletListStart( SwHTMLWriter& rWrt,
     SwHTMLNumRuleInfo& rPrevInfo = rWrt.GetNumInfo();
     bool bSameRule = rPrevInfo.GetNumRule() == rInfo.GetNumRule();
     if( bSameRule && rPrevInfo.GetDepth() >= rInfo.GetDepth() &&
-        !rInfo.IsRestart() )
+        !rInfo.IsRestart(rPrevInfo) )
     {
         return rWrt;
     }
@@ -205,7 +205,7 @@ Writer& OutHTML_NumberBulletListStart( SwHTMLWriter& rWrt,
     OSL_ENSURE( rWrt.m_nLastParaToken == HtmlTokenId::NONE,
                 "<PRE> was not closed before <OL>." );
     sal_uInt16 nPrevDepth =
-        (bSameRule && !rInfo.IsRestart()) ? rPrevInfo.GetDepth() : 0;
+        (bSameRule && !rInfo.IsRestart(rPrevInfo)) ? rPrevInfo.GetDepth() : 0;
 
     for( sal_uInt16 i=nPrevDepth; i<rInfo.GetDepth(); i++ )
     {
@@ -213,8 +213,9 @@ Writer& OutHTML_NumberBulletListStart( SwHTMLWriter& rWrt,
 
         rWrt.m_aBulletGrfs[i].clear();
         OString sOut = "<" + rWrt.GetNamespace();
-        if (rWrt.mbXHTML && (nPrevDepth != 0 || i != 0))
+        if (rWrt.mbXHTML && i != nPrevDepth)
         {
+            // for all skipped sublevels, add a li
             sOut += OOO_STRING_SVTOOLS_HTML_li "><" + rWrt.GetNamespace();
         }
         const SwNumFormat& rNumFormat = rInfo.GetNumRule()->Get( i );
@@ -321,7 +322,8 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
 {
     SwHTMLNumRuleInfo& rInfo = rWrt.GetNumInfo();
     bool bSameRule = rNextInfo.GetNumRule() == rInfo.GetNumRule();
-    bool bListEnd = !bSameRule || rNextInfo.GetDepth() < rInfo.GetDepth() || 
rNextInfo.IsRestart();
+    bool bListEnd = !bSameRule || rNextInfo.GetDepth() < rInfo.GetDepth() || 
rNextInfo.IsRestart(rInfo);
+    bool bNextIsSubitem = !bListEnd && rNextInfo.GetDepth() > rInfo.GetDepth();
 
     std::optional<bool> oAtLeastOneNumbered;
     if (rWrt.mbXHTML && !rInfo.IsNumbered())
@@ -360,7 +362,7 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
         // node is numbered.
         bool bPrevIsNumbered = rInfo.IsNumbered() || *oAtLeastOneNumbered;
         // XHTML </li> for the list item content, if there is an open <li>.
-        if ((bListEnd && bPrevIsNumbered) || (!bListEnd && 
rNextInfo.IsNumbered()))
+        if ((bListEnd && bPrevIsNumbered) || (!bListEnd && !bNextIsSubitem && 
rNextInfo.IsNumbered()))
         {
             HTMLOutFuncs::Out_AsciiTag(
                 rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_li),
@@ -385,7 +387,7 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
     OSL_ENSURE( rWrt.m_nLastParaToken == HtmlTokenId::NONE,
                 "<PRE> was not closed before </OL>." );
     sal_uInt16 nNextDepth =
-        (bSameRule && !rNextInfo.IsRestart()) ? rNextInfo.GetDepth() : 0;
+        (bSameRule && !rNextInfo.IsRestart(rInfo)) ? rNextInfo.GetDepth() : 0;
 
     // MIB 23.7.97: We must loop backwards, to get the right order of 
</OL>/</UL>
     for( sal_uInt16 i=rInfo.GetDepth(); i>nNextDepth; i-- )
@@ -402,8 +404,9 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt,
         else
             aTag = OOO_STRING_SVTOOLS_HTML_orderlist;
         HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), 
Concat2View(rWrt.GetNamespace() + aTag), false );
-        if (rWrt.mbXHTML && (nNextDepth != 0 || i != 1))
+        if (rWrt.mbXHTML && i != nNextDepth + 1)
         {
+            // for all skipped sublevels, close a li
             HTMLOutFuncs::Out_AsciiTag(
                 rWrt.Strm(), Concat2View(rWrt.GetNamespace() + 
OOO_STRING_SVTOOLS_HTML_li),
                 /*bOn=*/false);
diff --git a/sw/source/filter/html/htmltabw.cxx 
b/sw/source/filter/html/htmltabw.cxx
index ab611724474d..538a509db53c 100644
--- a/sw/source/filter/html/htmltabw.cxx
+++ b/sw/source/filter/html/htmltabw.cxx
@@ -1036,9 +1036,8 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode & 
rNode,
         if( aLRItem.GetLeft() > 0 && rHTMLWrt.m_nDefListMargin > 0 &&
             ( !rHTMLWrt.GetNumInfo().GetNumRule() ||
               ( rHTMLWrt.GetNextNumInfo() &&
-                (rHTMLWrt.GetNextNumInfo()->IsRestart() ||
-                 rHTMLWrt.GetNumInfo().GetNumRule() !=
-                    rHTMLWrt.GetNextNumInfo()->GetNumRule()) ) ) )
+                (rHTMLWrt.GetNumInfo().GetNumRule() != 
rHTMLWrt.GetNextNumInfo()->GetNumRule() ||
+                 rHTMLWrt.GetNextNumInfo()->IsRestart(rHTMLWrt.GetNumInfo())) 
) ) )
         {
             // If the paragraph before the table is not numbered or the
             // paragraph after the table starts with a new numbering or with
@@ -1186,9 +1185,8 @@ Writer& OutHTML_SwTableNode( Writer& rWrt, SwTableNode & 
rNode,
     rHTMLWrt.m_bOutTable = false;
 
     if( rHTMLWrt.GetNextNumInfo() &&
-        !rHTMLWrt.GetNextNumInfo()->IsRestart() &&
-        rHTMLWrt.GetNextNumInfo()->GetNumRule() ==
-            rHTMLWrt.GetNumInfo().GetNumRule() )
+        rHTMLWrt.GetNextNumInfo()->GetNumRule() == 
rHTMLWrt.GetNumInfo().GetNumRule() &&
+        !rHTMLWrt.GetNextNumInfo()->IsRestart(rHTMLWrt.GetNumInfo()) )
     {
         // If the paragraph after the table is numbered with the same rule as 
the
         // one before, then the NumInfo of the next paragraph holds the level 
of

Reply via email to