summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2023-06-13 23:15:08 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2023-06-14 15:46:53 +0200
commit82bbf63582bdf28e7918e58ebf6657a9144bc9f3 (patch)
tree65ce64ae4961e1de726ec8cdf1aadfed82526a29
parented2ca47393d06786bc055c3c6fa7f84c0f952f14 (diff)
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.kaganski@collabora.com>
-rw-r--r--include/xmloff/txtparae.hxx6
-rw-r--r--sw/inc/numrule.hxx3
-rw-r--r--sw/qa/core/unocore/unocore.cxx41
-rw-r--r--sw/source/core/doc/number.cxx23
-rw-r--r--sw/source/core/unocore/unoparagraph.cxx18
-rw-r--r--sw/source/uibase/uno/unotxdoc.cxx28
-rw-r--r--xmloff/qa/unit/data/differentListStylesInOneList.fodt47
-rw-r--r--xmloff/qa/unit/text.cxx104
-rw-r--r--xmloff/source/text/XMLTextNumRuleInfo.cxx12
-rw-r--r--xmloff/source/text/XMLTextNumRuleInfo.hxx5
-rw-r--r--xmloff/source/text/txtparae.cxx146
11 files changed, 328 insertions, 105 deletions
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,