include/xmloff/txtparae.hxx                           |    6 
 sw/inc/numrule.hxx                                    |    3 
 sw/qa/core/unocore/unocore.cxx                        |   41 -----
 sw/source/core/doc/number.cxx                         |   23 --
 sw/source/core/unocore/unoparagraph.cxx               |   18 --
 sw/source/uibase/uno/unotxdoc.cxx                     |   28 +++
 xmloff/qa/unit/data/differentListStylesInOneList.fodt |   47 +++++
 xmloff/qa/unit/text.cxx                               |  104 ++++++++++++
 xmloff/source/text/XMLTextNumRuleInfo.cxx             |   12 -
 xmloff/source/text/XMLTextNumRuleInfo.hxx             |    5 
 xmloff/source/text/txtparae.cxx                       |  146 +++++++++++++++---
 11 files changed, 328 insertions(+), 105 deletions(-)

New commits:
commit 82bbf63582bdf28e7918e58ebf6657a9144bc9f3
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Tue Jun 13 23:15:08 2023 +0300
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Wed Jun 14 15:46:53 2023 +0200

    tdf#155823: Improve the check if the list id is not required
    
    The implementation introduced in commit 
8f48f91009caa86d896f247059874242ed18bf39
    (SwNumRule::HasContinueList) was a bit naive: it assumed that 
maTextNodeList is
    sorted (it is not, and so, valid cases to avoid the id were missed); it 
assumed
    that a given list can only consist of items of a single numbering style, 
and so
    only tested the list of nodes referenced from maTextNodeList of given 
SwNumRule.
    I.e., this implementation targeted a special case of a list style fully 
covering
    a single continuous list.
    
    This skipped ids for list items with list styles, in which maTextNodeList 
passed
    the check in HasContinueList, but which were followed by items with a 
different
    list style, continuing the same list. This constellation outputs 
continue-list
    attribute in the following items (see 
XMLTextParagraphExport::exportListChange),
    which references the skipped id. The resulting ODF is an invalid XML (an 
xml:id
    is missing that is referenced), and also does not allow to continue such a 
list.
    
    The change tries to fix this, using a list of nodes in 
XMLTextParagraphExport,
    and analyzing if the list of the current paragraph has a continuation that 
needs
    to reference this list id. Two new hidden properties introduced in 
SwXParagraph
    and SwXTextDocument: "ODFExport_NodeIndex" and "ODFExport_ListNodes", resp. 
They
    allow to pipe the data to the export. The previous special casing of 
property
    state for "ListId", used in SwNumRule::HasContinueList, is removed together 
with
    the mentioned function.
    
    The intention is to have a logic allowing to detect 100% cases where the 
list id
    is required, and where it's not required.
    
    A related unit test for tdf#149668 was fixed to not rely on the mentioned 
ListId
    property state workaround, and moved from sw/qa/core/unocore to 
xmloff/qa/unit.
    
    Change-Id: If6a6ac7a3dfe0b2ea143229678a603875153eedb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153044
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/include/xmloff/txtparae.hxx b/include/xmloff/txtparae.hxx
index 1ba8b0b1a0c8..af23376d1160 100644
--- a/include/xmloff/txtparae.hxx
+++ b/include/xmloff/txtparae.hxx
@@ -112,6 +112,9 @@ class XMLOFF_DLLPUBLIC XMLTextParagraphExport : public 
XMLStyleExport
     XMLTextListsHelper* mpTextListsHelper;
     ::std::vector< std::unique_ptr<XMLTextListsHelper> > 
maTextListsHelperStack;
 
+    struct DocumentListNodes;
+    std::unique_ptr<DocumentListNodes> mpDocumentListNodes;
+
     o3tl::sorted_vector<css::uno::Reference<css::text::XTextFrame>> 
maFrameRecurseGuard;
     o3tl::sorted_vector<css::uno::Reference<css::drawing::XShape>> 
maShapeRecurseGuard;
 
@@ -537,6 +540,9 @@ public:
     void PopTextListsHelper();
 
 private:
+    bool ShouldSkipListId(const css::uno::Reference<css::text::XTextContent>& 
xTextContent);
+    bool ExportListId() const;
+
         XMLTextParagraphExport(XMLTextParagraphExport const &) = delete;
 
 };
diff --git a/sw/inc/numrule.hxx b/sw/inc/numrule.hxx
index fde2c8de0fb8..f642e21e746c 100644
--- a/sw/inc/numrule.hxx
+++ b/sw/inc/numrule.hxx
@@ -272,9 +272,6 @@ public:
     void dumpAsXml(xmlTextWriterPtr w) const;
     void GetGrabBagItem(css::uno::Any& rVal) const;
     void SetGrabBagItem(const css::uno::Any& rVal);
-
-    /// Is it possible that this numbering has multiple lists?
-    bool HasContinueList() const;
 };
 
 /// namespace for static functions and methods for numbering and bullets
diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx
index 605813a719b3..a1e931e75fb4 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -668,47 +668,6 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, 
testContentControlDate)
     CPPUNIT_ASSERT_EQUAL(OUString("sdtContentLocked"), 
pContentControl->GetLock());
 }
 
-CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testListIdState)
-{
-    // Given a document with 3 paragraphs: an outer numbering on para 1 & 3, 
an inner numbering on
-    // para 2:
-    createSwDoc();
-    SwDoc* pDoc = getSwDoc();
-    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
-    {
-        SfxItemSetFixed<RES_PARATR_NUMRULE, RES_PARATR_NUMRULE> 
aSet(pWrtShell->GetAttrPool());
-        SwNumRuleItem aItem("Numbering ABC");
-        aSet.Put(aItem);
-        pWrtShell->SetAttrSet(aSet);
-    }
-    pWrtShell->SplitNode();
-    {
-        SfxItemSetFixed<RES_PARATR_NUMRULE, RES_PARATR_NUMRULE> 
aSet(pWrtShell->GetAttrPool());
-        SwNumRuleItem aItem("Numbering 123");
-        aSet.Put(aItem);
-        pWrtShell->SetAttrSet(aSet);
-    }
-    pWrtShell->SplitNode();
-    {
-        SfxItemSetFixed<RES_PARATR_NUMRULE, RES_PARATR_NUMRULE> 
aSet(pWrtShell->GetAttrPool());
-        SwNumRuleItem aItem("Numbering ABC");
-        aSet.Put(aItem);
-        pWrtShell->SetAttrSet(aSet);
-    }
-
-    // When checking if xml:id="..." needs writing for the first paragraph 
during ODT export:
-    uno::Reference<beans::XPropertyState> xPara(getParagraph(1), 
uno::UNO_QUERY);
-    beans::PropertyState eState = xPara->getPropertyState("ListId");
-
-    // Then make sure that xml:id="..." gets written for para 1, as it'll be 
continued in para 3.
-    // Without the accompanying fix in place, this test would have failed with:
-    // - Expected: 0 (DIRECT_VALUE)
-    // - Actual  : 1 (DEFAULT_VALUE)
-    // i.e. para 1 didn't write an xml:id="..." but para 3 referred to it 
using continue-list="...",
-    // which is inconsistent.
-    CPPUNIT_ASSERT_EQUAL(beans::PropertyState_DIRECT_VALUE, eState);
-}
-
 CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testContentControlPlainText)
 {
     // Given an empty document:
diff --git a/sw/source/core/doc/number.cxx b/sw/source/core/doc/number.cxx
index 7dc2953608cf..f953a93dbde4 100644
--- a/sw/source/core/doc/number.cxx
+++ b/sw/source/core/doc/number.cxx
@@ -1153,29 +1153,6 @@ void SwNumRule::SetGrabBagItem(const uno::Any& rVal)
     mpGrabBagItem->PutValue(rVal, 0);
 }
 
-bool SwNumRule::HasContinueList() const
-{
-    // In case all text nodes are after each other, then we won't have a later 
list that wants to
-    // continue us.
-    SwNodeOffset nIndex(0);
-    for (size_t i = 0; i < maTextNodeList.size(); ++i)
-    {
-        SwTextNode* pNode = maTextNodeList[i];
-        if (i > 0)
-        {
-            if (pNode->GetIndex() != nIndex + 1)
-            {
-                // May have a continue list.
-                return true;
-            }
-        }
-        nIndex = pNode->GetIndex();
-    }
-
-    // Definitely won't have a continue list.
-    return false;
-}
-
 namespace numfunc
 {
     namespace {
diff --git a/sw/source/core/unocore/unoparagraph.cxx 
b/sw/source/core/unocore/unoparagraph.cxx
index 1d0fae809f41..15f9b56c490f 100644
--- a/sw/source/core/unocore/unoparagraph.cxx
+++ b/sw/source/core/unocore/unoparagraph.cxx
@@ -542,6 +542,14 @@ uno::Sequence< uno::Any > 
SwXParagraph::Impl::GetPropertyValues_Impl(
             continue;
         }
 
+        if (pPropertyNames[nProp] == "ODFExport_NodeIndex")
+        {
+            // A hack to avoid writing random list ids to ODF when they are 
not referred later
+            // see XMLTextParagraphExport::DocumentListNodes::ShouldSkipListId
+            pValues[nProp] <<= rTextNode.GetIndex().get();
+            continue;
+        }
+
         SfxItemPropertyMapEntry const*const pEntry =
             rMap.getByName( pPropertyNames[nProp] );
         if (!pEntry)
@@ -931,16 +939,6 @@ static beans::PropertyState 
lcl_SwXParagraph_getPropertyState(
             bDone = true;
             break;
         }
-        case FN_UNO_LIST_ID:
-        {
-            SwNumRule* pNumRule = rTextNode.GetNumRule();
-            if (pNumRule && pNumRule->HasContinueList())
-            {
-                eRet = beans::PropertyState_DIRECT_VALUE;
-            }
-            bDone = true;
-            break;
-        }
         case FN_UNO_ANCHOR_TYPES:
         {
             bDone = true;
diff --git a/sw/source/uibase/uno/unotxdoc.cxx 
b/sw/source/uibase/uno/unotxdoc.cxx
index fadfe90a82b6..51c777ee47da 100644
--- a/sw/source/uibase/uno/unotxdoc.cxx
+++ b/sw/source/uibase/uno/unotxdoc.cxx
@@ -1969,6 +1969,34 @@ Any SwXTextDocument::getPropertyValue(const OUString& 
rPropertyName)
     if(!IsValid())
         throw DisposedException("", static_cast< XTextDocument* >(this));
 
+    if (rPropertyName == "ODFExport_ListNodes")
+    {
+        // A hack to avoid writing random list ids to ODF when they are not 
referred later
+        // see XMLTextParagraphExport::DocumentListNodes ctor
+
+        // Sequence of nodes, each of them represented by four-element 
sequence:
+        // [ index, styleIntPtr, list_id, isRestart ]
+        std::vector<css::uno::Sequence<css::uno::Any>> nodes;
+
+        const SwDoc& rDoc = *m_pDocShell->GetDoc();
+        for (const SwNumRule* pNumRule : rDoc.GetNumRuleTable())
+        {
+            SwNumRule::tTextNodeList textNodes;
+            pNumRule->GetTextNodeList(textNodes);
+            css::uno::Any styleIntPtr(reinterpret_cast<sal_uInt64>(pNumRule));
+
+            for (const SwTextNode* pTextNode : textNodes)
+            {
+                css::uno::Any index(pTextNode->GetIndex().get());
+                css::uno::Any list_id(pTextNode->GetListId());
+                css::uno::Any isRestart(pTextNode->IsListRestart());
+
+                nodes.push_back({ index, styleIntPtr, list_id, isRestart });
+            }
+        }
+        return css::uno::Any(comphelper::containerToSequence(nodes));
+    }
+
     const SfxItemPropertyMapEntry*  pEntry = 
m_pPropSet->getPropertyMap().getByName( rPropertyName);
 
     if(!pEntry)
diff --git a/xmloff/qa/unit/data/differentListStylesInOneList.fodt 
b/xmloff/qa/unit/data/differentListStylesInOneList.fodt
new file mode 100644
index 000000000000..5f90135fbb23
--- /dev/null
+++ b/xmloff/qa/unit/data/differentListStylesInOneList.fodt
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style: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:styles>
+  <text:list-style style:name="ListStyleOne">
+   <text:list-level-style-number text:level="1" style:num-suffix="." 
style:num-format="1">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" 
fo:text-indent="-1cm" fo:margin-left="1cm"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+  <text:list-style style:name="ListStyleAnother">
+   <text:list-level-style-number text:level="1" style:num-suffix="." 
style:num-format="1">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab" 
fo:text-indent="-1cm" fo:margin-left="1cm"/>
+    </style:list-level-properties>
+   </text:list-level-style-number>
+  </text:list-style>
+ </office:styles>
+ <office:body>
+  <office:text>
+   <text:list xml:id="list1" text:style-name="ListStyleOne">
+    <text:list-item>
+     <text:p>Item1 (ListStyleOne)</text:p>
+    </text:list-item>
+   </text:list>
+   <text:p/>
+   <text:list xml:id="list2" text:continue-numbering="true" 
text:style-name="ListStyleOne">
+    <text:list-item>
+     <text:p>Item2 (ListStyleOne)</text:p>
+    </text:list-item>
+   </text:list>
+   <text:p/>
+   <text:list xml:id="list3" text:continue-list="list2" 
text:style-name="ListStyleAnother">
+    <text:list-item>
+     <text:p>Item3 (ListStyleAnother)</text:p>
+    </text:list-item>
+   </text:list>
+   <text:p/>
+   <text:list xml:id="list4" text:continue-list="list3" 
text:style-name="ListStyleOne">
+    <text:list-item>
+     <text:p>Item4 (ListStyleOne)</text:p>
+    </text:list-item>
+   </text:list>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/xmloff/qa/unit/text.cxx b/xmloff/qa/unit/text.cxx
index 20147161d52f..bff3f8b14821 100644
--- a/xmloff/qa/unit/text.cxx
+++ b/xmloff/qa/unit/text.cxx
@@ -13,6 +13,7 @@
 #include <com/sun/star/beans/PropertyValues.hpp>
 #include <com/sun/star/frame/XStorable.hpp>
 #include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/text/ControlCharacter.hpp>
 #include <com/sun/star/text/BibliographyDataType.hpp>
 #include <com/sun/star/text/TextContentAnchorType.hpp>
 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
@@ -243,6 +244,109 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testListId)
     assertXPathNoAttribute(pXmlDoc, "//text:list", "id");
 }
 
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testListId2)
+{
+    // tdf#155823 Given a document with a list consisting of items having 
different list styles:
+    loadFromURL(u"differentListStylesInOneList.fodt");
+
+    auto xTextDocument(mxComponent.queryThrow<css::text::XTextDocument>());
+    auto 
xParaEnumAccess(xTextDocument->getText().queryThrow<css::container::XEnumerationAccess>());
+    auto xParaEnum(xParaEnumAccess->createEnumeration());
+
+    auto xPara(xParaEnum->nextElement().queryThrow<beans::XPropertySet>());
+    auto aActual(xPara->getPropertyValue("ListLabelString").get<OUString>());
+    CPPUNIT_ASSERT_EQUAL(OUString("1."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("2."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("3."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("4."), aActual);
+
+    // When storing that document as ODF:
+    // Without the fix in place, automatic validation would fail with:
+    // Error: "list123456789012345" is referenced by an IDREF, but not defined.
+    saveAndReload("writer8");
+
+    xTextDocument.set(mxComponent.queryThrow<css::text::XTextDocument>());
+    
xParaEnumAccess.set(xTextDocument->getText().queryThrow<css::container::XEnumerationAccess>());
+    xParaEnum.set(xParaEnumAccess->createEnumeration());
+
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("1."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("2."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    CPPUNIT_ASSERT_EQUAL(OUString("3."), aActual);
+    xParaEnum->nextElement(); // Skip empty intermediate paragraph
+
+    // Check that the last item number is correct
+
+    xPara.set(xParaEnum->nextElement(), uno::UNO_QUERY);
+    aActual = xPara->getPropertyValue("ListLabelString").get<OUString>();
+    // Without the fix in place, this would fail with:
+    // - Expected: 4.
+    // - Actual  : 1.
+    // i.e. the numbering was not continued.
+    CPPUNIT_ASSERT_EQUAL(OUString("4."), aActual);
+
+    // Then make sure that required xml:id="..." attributes is written when 
the style changes:
+    xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+    CPPUNIT_ASSERT(pXmlDoc);
+    // Without the fix in place, this would fail,
+    // i.e. xml:id="..." was omitted, even though it was needed for the next 
item.
+    OUString id
+        = getXPath(pXmlDoc, 
"/office:document-content/office:body/office:text/text:list[3]", "id");
+    CPPUNIT_ASSERT(!id.isEmpty());
+    assertXPath(pXmlDoc, 
"/office:document-content/office:body/office:text/text:list[4]",
+                "continue-list", id);
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testListIdState)
+{
+    // tdf#149668: given a document with 3 paragraphs: an outer numbering on 
para 1 & 3, an inner
+    // numbering on para 2:
+    mxComponent = loadFromDesktop("private:factory/swriter");
+    auto xTextDocument(mxComponent.queryThrow<text::XTextDocument>());
+    auto xText(xTextDocument->getText());
+    xText->insertControlCharacter(xText->getEnd(), 
css::text::ControlCharacter::PARAGRAPH_BREAK,
+                                  false);
+    xText->insertControlCharacter(xText->getEnd(), 
css::text::ControlCharacter::PARAGRAPH_BREAK,
+                                  false);
+
+    auto paraEnumAccess(xText.queryThrow<container::XEnumerationAccess>());
+    auto paraEnum(paraEnumAccess->createEnumeration());
+    auto xParaProps(paraEnum->nextElement().queryThrow<beans::XPropertySet>());
+    xParaProps->setPropertyValue("NumberingStyleName", 
css::uno::Any(OUString("Numbering ABC")));
+    xParaProps.set(paraEnum->nextElement().queryThrow<beans::XPropertySet>());
+    xParaProps->setPropertyValue("NumberingStyleName", 
css::uno::Any(OUString("Numbering 123")));
+    xParaProps.set(paraEnum->nextElement().queryThrow<beans::XPropertySet>());
+    xParaProps->setPropertyValue("NumberingStyleName", 
css::uno::Any(OUString("Numbering ABC")));
+
+    // When storing that document as ODF:
+    save("writer8");
+    xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+
+    // Make sure that xml:id="..." gets written for para 1, as it'll be 
continued in para 3.
+    // Without the accompanying fix in place, this test would have failed,
+    // i.e. para 1 didn't write an xml:id="..." but para 3 referred to it 
using continue-list="...",
+    // which is inconsistent.
+    OUString id
+        = getXPath(pXmlDoc, 
"/office:document-content/office:body/office:text/text:list[1]", "id");
+    CPPUNIT_ASSERT(!id.isEmpty());
+}
+
 CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testClearingBreakExport)
 {
     // Given a document with a clearing break:
diff --git a/xmloff/source/text/XMLTextNumRuleInfo.cxx 
b/xmloff/source/text/XMLTextNumRuleInfo.cxx
index 062b92879ee2..5f9a5e2b7a54 100644
--- a/xmloff/source/text/XMLTextNumRuleInfo.cxx
+++ b/xmloff/source/text/XMLTextNumRuleInfo.cxx
@@ -54,14 +54,14 @@ void XMLTextNumRuleInfo::Set(
         const css::uno::Reference < css::text::XTextContent > & xTextContent,
         const bool bOutlineStyleAsNormalListStyle,
         const XMLTextListAutoStylePool& rListAutoPool,
-        const bool bExportTextNumberElement )
+        const bool bExportTextNumberElement,
+        const bool bListIdIsDefault )
 {
     Reset();
     // Written OpenDocument file format doesn't fit to the created text 
document (#i69627#)
     mbOutlineStyleAsNormalListStyle = bOutlineStyleAsNormalListStyle;
 
     Reference< XPropertySet > xPropSet( xTextContent, UNO_QUERY );
-    Reference<XPropertyState> xPropState(xTextContent, UNO_QUERY);
     Reference< XPropertySetInfo > xPropSetInfo = 
xPropSet->getPropertySetInfo();
 
     // check if this paragraph supports a numbering
@@ -138,14 +138,10 @@ void XMLTextNumRuleInfo::Set(
         if( xPropSetInfo->hasPropertyByName( "ListId" ) )
         {
             xPropSet->getPropertyValue( "ListId" ) >>= msListId;
-
-            if (xPropState.is())
-            {
-                mbListIdIsDefault
-                    = xPropState->getPropertyState("ListId") == 
PropertyState_DEFAULT_VALUE;
-            }
         }
 
+        mbListIdIsDefault = bListIdIsDefault;
+
         mbContinueingPreviousSubTree = false;
         if( xPropSetInfo->hasPropertyByName( "ContinueingPreviousSubTree" ) )
         {
diff --git a/xmloff/source/text/XMLTextNumRuleInfo.hxx 
b/xmloff/source/text/XMLTextNumRuleInfo.hxx
index adb405411164..7cbc3cf8d4fb 100644
--- a/xmloff/source/text/XMLTextNumRuleInfo.hxx
+++ b/xmloff/source/text/XMLTextNumRuleInfo.hxx
@@ -66,9 +66,10 @@ public:
     inline XMLTextNumRuleInfo& operator=( const XMLTextNumRuleInfo& rInfo );
 
     void Set( const css::uno::Reference < css::text::XTextContent > & 
rTextContent,
-              const bool bOutlineStyleAsNormalListStyle,
+              bool bOutlineStyleAsNormalListStyle,
               const XMLTextListAutoStylePool& rListAutoPool,
-              const bool bExportTextNumberElement );
+              bool bExportTextNumberElement,
+              bool bListIdIsDefault );
     inline void Reset();
 
     const OUString& GetNumRulesName() const
diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx
index 2b3af03b93dd..c78c504bfa50 100644
--- a/xmloff/source/text/txtparae.cxx
+++ b/xmloff/source/text/txtparae.cxx
@@ -997,7 +997,7 @@ void XMLTextParagraphExport::exportListChange(
     // end a list
     if ( rPrevInfo.GetLevel() > 0 )
     {
-        sal_Int16 nListLevelsToBeClosed = 0;
+        sal_uInt32 nListLevelsToBeClosed = 0; // unsigned larger type to 
safely multiply and compare
         if ( !rNextInfo.BelongsToSameList( rPrevInfo ) ||
              rNextInfo.GetLevel() <= 0 )
         {
@@ -1007,13 +1007,11 @@ void XMLTextParagraphExport::exportListChange(
         else if ( rPrevInfo.GetLevel() > rNextInfo.GetLevel() )
         {
             // close corresponding sub lists
-            SAL_WARN_IF( rNextInfo.GetLevel() <= 0, "xmloff",
-                        "<rPrevInfo.GetLevel() > 0> not hold. Serious defect." 
);
             nListLevelsToBeClosed = rPrevInfo.GetLevel() - 
rNextInfo.GetLevel();
         }
 
         if ( nListLevelsToBeClosed > 0 &&
-             maListElements.size() >= sal::static_int_cast< sal_uInt32 >( 2 * 
nListLevelsToBeClosed ) )
+             maListElements.size() >= 2 * nListLevelsToBeClosed )
         {
             do {
                 for(size_t j = 0; j < 2; ++j)
@@ -1031,11 +1029,6 @@ void XMLTextParagraphExport::exportListChange(
         }
     }
 
-    const bool bExportODF =
-                bool( GetExport().getExportFlags() & SvXMLExportFlags::OASIS );
-    const SvtSaveOptions::ODFSaneDefaultVersion eODFDefaultVersion =
-                                    GetExport().getSaneDefaultVersion();
-
     // start a new list
     if ( rNextInfo.GetLevel() > 0 )
     {
@@ -1051,8 +1044,6 @@ void XMLTextParagraphExport::exportListChange(
         else if ( rNextInfo.GetLevel() > rPrevInfo.GetLevel() )
         {
             // open corresponding sub lists
-            SAL_WARN_IF( rPrevInfo.GetLevel() <= 0, "xmloff",
-                        "<rPrevInfo.GetLevel() > 0> not hold. Serious defect." 
);
             nListLevelsToBeOpened = rNextInfo.GetLevel() - 
rPrevInfo.GetLevel();
         }
 
@@ -1074,8 +1065,7 @@ void XMLTextParagraphExport::exportListChange(
                 {
                     if ( !mpTextListsHelper->IsListProcessed( sListId ) )
                     {
-                        if ( bExportODF &&
-                            eODFDefaultVersion >= SvtSaveOptions::ODFSVER_012 
&&
+                        if ( ExportListId() &&
                              !sListId.isEmpty() && 
!rNextInfo.IsListIdDefault() )
                         {
                             /* Property text:id at element <text:list> has to 
be
@@ -1093,8 +1083,7 @@ void XMLTextParagraphExport::exportListChange(
                     {
                         const OUString sNewListId(
                                         mpTextListsHelper->GenerateNewListId() 
);
-                        if ( bExportODF &&
-                            eODFDefaultVersion >= SvtSaveOptions::ODFSVER_012 
&&
+                        if ( ExportListId() &&
                              !sListId.isEmpty() && 
!rNextInfo.IsListIdDefault() )
                         {
                             /* Property text:id at element <text:list> has to 
be
@@ -1124,8 +1113,7 @@ void XMLTextParagraphExport::exportListChange(
                         }
                         else
                         {
-                            if ( bExportODF &&
-                                eODFDefaultVersion >= 
SvtSaveOptions::ODFSVER_012 &&
+                            if ( ExportListId() &&
                                  !sListId.isEmpty() )
                             {
                                 GetExport().AddAttribute( XML_NAMESPACE_TEXT,
@@ -1803,6 +1791,127 @@ void XMLTextParagraphExport::exportText(
         m_pRedlineExport->ExportStartOrEndRedline( xPropertySet, false );
 }
 
+bool XMLTextParagraphExport::ExportListId() const
+{
+    return (GetExport().getExportFlags() & SvXMLExportFlags::OASIS)
+           && GetExport().getSaneDefaultVersion() >= 
SvtSaveOptions::ODFSVER_012;
+}
+
+struct XMLTextParagraphExport::DocumentListNodes
+{
+    struct NodeData
+    {
+        sal_Int32 index; // see SwNode::GetIndex and SwNodeOffset
+        sal_uInt64 style_id; // actually a pointer to NumRule
+        OUString list_id;
+        bool isRestart;
+    };
+    std::vector<NodeData> docListNodes;
+    DocumentListNodes(const css::uno::Reference<css::frame::XModel>& xModel)
+    {
+        // Sequence of nodes, each of them represented by four-element 
sequence,
+        // corresponding to NodeData members
+        css::uno::Sequence<css::uno::Sequence<css::uno::Any>> nodes;
+        if (auto xPropSet = xModel.query<css::beans::XPropertySet>())
+        {
+            try
+            {
+                // See SwXTextDocument::getPropertyValue
+                xPropSet->getPropertyValue("ODFExport_ListNodes") >>= nodes;
+            }
+            catch (css::beans::UnknownPropertyException&)
+            {
+                // That's absolutely fine!
+            }
+        }
+
+        docListNodes.reserve(nodes.getLength());
+        for (const auto& node : nodes)
+        {
+            assert(node.getLength() == 4);
+            docListNodes.push_back({ node[0].get<sal_Int32>(), 
node[1].get<sal_uInt64>(),
+                                     node[2].get<OUString>(), 
node[3].get<bool>() });
+        }
+
+        std::sort(docListNodes.begin(), docListNodes.end(),
+                  [](const NodeData& lhs, const NodeData& rhs) { return 
lhs.index < rhs.index; });
+    }
+    bool ShouldSkipListId(const Reference<XTextContent>& xTextContent) const
+    {
+        if (docListNodes.empty())
+            return false;
+
+        if (auto xPropSet = xTextContent.query<css::beans::XPropertySet>())
+        {
+            sal_Int32 index = 0;
+            try
+            {
+                // See SwXParagraph::Impl::GetPropertyValues_Impl
+                xPropSet->getPropertyValue("ODFExport_NodeIndex") >>= index;
+            }
+            catch (css::beans::UnknownPropertyException&)
+            {
+                // That's absolutely fine!
+                return false;
+            }
+
+            auto it = std::lower_bound(docListNodes.begin(), 
docListNodes.end(), index,
+                                       [](const NodeData& lhs, sal_Int32 rhs)
+                                       { return lhs.index < rhs; });
+            if (it == docListNodes.end() || it->index != index)
+                return false;
+
+            // We need to write the id, when there will be continuation of the 
list either with
+            // a different list style, or after another list.
+
+            for (auto next = it + 1; next != docListNodes.end(); ++next)
+            {
+                if (it->list_id != next->list_id)
+                {
+                    // List changed. We will have to refer to this id, only if 
there will
+                    // appear a continuation of this list
+                    return std::find_if(next + 1, docListNodes.end(),
+                                        [list_id = it->list_id](const 
NodeData& data)
+                                        { return data.list_id == list_id; })
+                           == docListNodes.end();
+                }
+
+                if (it->style_id != next->style_id)
+                {
+                    // Same list, new style -> this "next" will refer to the 
id, no skipping
+                    return false;
+                }
+                if (it->index + 1 != next->index)
+                {
+                    // we have a gap before the next node with the same list 
and style,
+                    // with no other lists in between. There will be a 
continuation;
+                    // in case of restart, there will be a reference to the id;
+                    // otherwise, there will be simple 
'text:continue-numbering="true"'.
+                    return !next->isRestart;
+                }
+                it = next; // walk through adjacent nodes of the same list
+            }
+            // all nodes were adjacent and of the same list and style -> no 
continuation, skip id
+            return true;
+        }
+
+        return false;
+    }
+};
+
+bool XMLTextParagraphExport::ShouldSkipListId(const Reference<XTextContent>& 
xTextContent)
+{
+    if (!mpDocumentListNodes)
+    {
+        if (ExportListId())
+            mpDocumentListNodes.reset(new 
DocumentListNodes(GetExport().GetModel()));
+        else
+            mpDocumentListNodes.reset(new DocumentListNodes({}));
+    }
+
+    return mpDocumentListNodes->ShouldSkipListId(xTextContent);
+}
+
 void XMLTextParagraphExport::exportTextContentEnumeration(
         const Reference < XEnumeration > & rContEnum,
         bool bAutoStyles,
@@ -1861,7 +1970,8 @@ void XMLTextParagraphExport::exportTextContentEnumeration(
                 aNextNumInfo.Set( xTxtCntnt,
                                   
GetExport().writeOutlineStyleAsNormalListStyle(),
                                   GetListAutoStylePool(),
-                                  GetExport().exportTextNumberElement() );
+                                  GetExport().exportTextNumberElement(),
+                                  ShouldSkipListId(xTxtCntnt) );
 
                 exportListAndSectionChange( xCurrentTextSection, 
aPropSetHelper,
                                             TEXT_SECTION, xTxtCntnt,

Reply via email to