tdf#160253: fix list identifier export decision code

Commits 8f48f91009 (ODT export: omit
unreferenced <text:list xml:id="...">, 2022-03-10) and
82bbf63582  (tdf#155823: Improve the
check if the list id is not required, 2023-06-14) tried to improve
deterministic ODF output, by omitting the list identifiers in case
when those identifiers were unreferenced. The latter of these used
document model node numbers to check if other lists appeared after
the last occurrence of the list  that is continuing in the current
node. But it turned out, that this isn't robust. Consider this ODF:

  <text:list xml:id="list1" text:style-name="L1">
   <text:list-item>
    <text:p>a</text:p>
   </text:list-item>
  </text:list>
  <text:p>b<text:note text:id="ftn1" text:note-class="endnote"><text:note-citation>i</text:note-citation><text:note-body>
     <text:list text:style-name="L2">
      <text:list-item>
       <text:p>x</text:p>
      </text:list-item>
     </text:list></text:note-body></text:note></text:p>
  <text:list text:continue-list="list1" text:style-name="L1">
   <text:list-item>
    <text:p>c</text:p>
   </text:list-item>
  </text:list>

The paragraphs a, b, and c are all in the main document body,  and
have sequential document model node numbers  (say, 15, 16, 17). If
these numbers are checked,  there is no node between node 15 ("a")
and node 17 ("c") with a different list  (both 15 and 17 belong to
a list with style "L1" and identifier "list1", and node 16 doesn't
belong to any lists). That suggests that the list identifier isn't
needed in this case. Bug when the actual output of node 16 is done,
it includes a node from an endnote  ("x"),  which is located  in a
different place in the document model,  and has a node number like
7 (so not between 15 and 17). The paragraph "x" belongs to another
list with style  "L2", and is output to ODF between paragraphs "a"
and "c". Here, we must refer from paragraph "c" to the list of the
paragraph "a" using the list id, but this is not obvious when only
considering node numbers,  and requires the prior knowledge of the
actual order of appearance of lists in the ODF.

Unless we build a DOM,  this is only possible, if we do a two-pass
output, and collect the nodes order in the first pass.  The output
already does that in a "collect autostyles" pass. The problem here
is that the  "collect autostyles"  pass used an optimized function,
XMLTextParagraphExport::collectTextAutoStylesOptimized, introduced
in commit  8195d7061e  (INTEGRATION:
CWS swautomatic01 (1.126.4); FILE MERGED, 2006-12-01) for #i65476#
and which used style::XAutoStylesSupplier for optimization  to get
the autostyles.

This drops  XMLTextParagraphExport::collectTextAutoStylesOptimized,
and reverts to use of  collectTextAutoStyles,  which handles nodes
in the same order as when writing to ODF. There, we build a vector
of the node numbers sequence, used later to sort DocumentListNodes.

This uncovered an omission from the work on paragraph mark (commit
1a88efa8e0  tdf#155238:  Reimplement
how ListAutoFormat is stored to ODF, 2023-05-11).  Turns out, that
the code in SwTextFormatter::NewNumberPortion introduced in commit
cb0e1b52d6  (sw,  numbering  portion
format: consider full-para char formats as well,  2022-10-20)  was
left behind when re-implementing paragraph marks  to use dedicated
property;  empty trailing spans still affected  how the lists were
rendered,  and that allowed to overlook import defects,  where the
paragraph mark properties weren't properly set.

In ODF import (XMLParaContext::endFastElement),  for compatibility,
this treats empty trailing spans as defining paragraph mark  (when
the paragraph mark wasn't set explicitly).  This way, the trailing
spans get converted to the paragraph mark.

In WW8 import,  last cell paragraphs didn't call the code handling
the paragraph marks. This is also fixed now.

The changes result  in slightly different numbering  of autostyles
in the ODF.  It seems, that the new numbering more closely follows
the order of appearance of the autostyles in the output;  and some
cases of autostyles that were written,  but unreferenced,  are now
eliminated. The unit tests were updated accordingly.

I hope that the performance impact on the export time would not be
too large.

It is unclear why outline numbering  exports a list element at all.
Fixing that to not emit the list element is a separate task / TODO.

Change-Id: I5c99f8d48be77c4454ffac6ffa9f5babfe0d4909
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166572
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
This commit is contained in:
Mike Kaganski
2024-04-24 11:37:27 +02:00
parent dfb412699b
commit 69ed893087
14 changed files with 244 additions and 182 deletions
+4 -3
View File
@@ -114,6 +114,8 @@ class XMLOFF_DLLPUBLIC XMLTextParagraphExport : public XMLStyleExport
struct DocumentListNodes;
std::unique_ptr<DocumentListNodes> mpDocumentListNodes;
std::vector<sal_Int32> maDocumentNodeOrder;
bool bInDocumentNodeOrderCollection = false;
o3tl::sorted_vector<css::uno::Reference<css::text::XTextFrame>> maFrameRecurseGuard;
o3tl::sorted_vector<css::uno::Reference<css::drawing::XShape>> maShapeRecurseGuard;
@@ -472,9 +474,7 @@ public:
exportText( rText, rBaseSection, true, bIsProgress, true/*bExportParagraph*/ );
}
// It the model implements the xAutoStylesSupplier interface, the automatic
// styles can exported without iterating over the text portions
void collectTextAutoStylesOptimized( bool bIsProgress );
void collectTextAutoStylesAndNodeExportOrder(bool bIsProgress);
// This method exports all automatic styles that have been collected.
void exportTextAutoStyles();
@@ -540,6 +540,7 @@ public:
void PopTextListsHelper();
private:
void RecordNodeIndex(const css::uno::Reference<css::text::XTextContent>& xTextContent);
bool ShouldSkipListId(const css::uno::Reference<css::text::XTextContent>& xTextContent);
bool ExportListId() const;
+2 -2
View File
@@ -178,9 +178,9 @@ CPPUNIT_TEST_FIXTURE(XHtmlExportTest, testTdf66305)
sal_uInt64 nLength = pStream->TellEnd();
OString aStream(read_uInt8s_ToOString(*pStream, nLength));
CPPUNIT_ASSERT(
aStream.indexOf("<p class=\"paragraph-P6\"><a href=\"#__RefHeading__82004_486970805\" "
aStream.indexOf("<p class=\"paragraph-P5\"><a href=\"#__RefHeading__82004_486970805\" "
"class=\"text-Internet_20_link\">Introduction</a></p><p "
"class=\"paragraph-P7\"> </p>")
"class=\"paragraph-P6\"> </p>")
!= -1);
}
@@ -0,0 +1,43 @@
<?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:automatic-styles>
<text:list-style style:name="L1">
<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="space"/>
</style:list-level-properties>
</text:list-level-style-number>
</text:list-style>
<text:list-style style:name="L2">
<text:list-level-style-bullet text:level="1" text:bullet-char="•">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="space" fo:text-indent="-0.5cm" fo:margin-left="1cm"/>
</style:list-level-properties>
</text:list-level-style-bullet>
</text:list-style>
</office:automatic-styles>
<office:body>
<office:text>
<text:list xml:id="list1" text:style-name="L1">
<text:list-item>
<text:p>a</text:p>
</text:list-item>
<text:list-item>
<text:p>b</text:p>
</text:list-item>
</text:list>
<text:p>c<text:note text:id="ftn1" text:note-class="endnote"><text:note-citation>i</text:note-citation><text:note-body>
<text:list text:style-name="L2">
<text:list-item>
<text:p>xyz</text:p>
</text:list-item>
</text:list></text:note-body></text:note></text:p>
<text:list text:continue-list="list1" text:style-name="L1">
<text:list-item>
<text:p>d</text:p>
</text:list-item>
</text:list>
</office:text>
</office:body>
</office:document>
@@ -0,0 +1,37 @@
<?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>
<style:style style:name="Heading" style:family="paragraph" style:default-outline-level="1" style:list-style-name="Numbering_20_1" style:class="text"/>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:list-style-name="Outline" style:class="text">
<style:text-properties fo:font-weight="bold"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" 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="space"/>
</style:list-level-properties>
</text:outline-level-style>
</text:outline-style>
<text:list-style style:name="Numbering_20_1" style:display-name="Numbering 1">
<text:list-level-style-number text:level="1" style:num-format="1"/>
</text:list-style>
</office:styles>
<office:master-styles>
<style:master-page style:name="Standard"/>
<style:master-page style:name="Endnote"/>
</office:master-styles>
<office:automatic-styles>
<text:list-style style:name="L1">
<text:list-level-style-bullet text:level="1" text:bullet-char="•"/>
</text:list-style>
</office:automatic-styles>
<office:body>
<office:text>
<text:h text:style-name="Heading_20_1" text:outline-level="1">foo</text:h>
<text:p>xyz<text:note text:id="ftn1" text:note-class="endnote"><text:note-citation>i</text:note-citation><text:note-body>
<text:list text:style-name="L1"><text:list-item><text:p>abc</text:p></text:list-item></text:list></text:note-body></text:note></text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">bar</text:h>
</office:text>
</office:body>
</office:document>
+52 -4
View File
@@ -115,10 +115,14 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf106733)
CPPUNIT_ASSERT_EQUAL(1, getPages());
xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
OUString autostyle = getXPath(pXmlDoc, "//office:body/office:text/text:p[2]/text:span"_ostr,
"style-name"_ostr);
OString autostyle_span_xpath = "//style:style[@style:name='" + autostyle.toUtf8() + "']";
// keep fo:hyphenate="false" in direct formatting
assertXPath(
pXmlDoc,
"//style:style[@style:name='T3']/style:text-properties"_ostr,
autostyle_span_xpath + "/style:text-properties",
"hyphenate"_ostr, "false");
// keep fo:hyphenate="false" in character style
@@ -1250,9 +1254,13 @@ CPPUNIT_TEST_FIXTURE(Test, testParagraphMarkerMarkupRoundtrip)
loadAndReload("ParagraphMarkerMarkup.fodt");
// Test that the markup stays at save-and-reload
xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
assertXPath(pXmlDoc, "/office:document-content/office:body/office:text/text:p"_ostr, "marker-style-name"_ostr, "T2");
assertXPath(pXmlDoc, "/office:document-content/office:automatic-styles/style:style[@style:name='T2']/style:text-properties"_ostr, "font-size"_ostr, "9pt");
assertXPath(pXmlDoc, "/office:document-content/office:automatic-styles/style:style[@style:name='T2']/style:text-properties"_ostr, "color"_ostr, "#ff0000");
OUString autostyle
= getXPath(pXmlDoc, "//office:body/office:text/text:p"_ostr, "marker-style-name"_ostr);
OString style_text_properties
= "/office:document-content/office:automatic-styles/style:style[@style:name='"
+ autostyle.toUtf8() + "']/style:text-properties";
assertXPath(pXmlDoc, style_text_properties, "font-size"_ostr, "9pt");
assertXPath(pXmlDoc, style_text_properties, "color"_ostr, "#ff0000");
}
CPPUNIT_TEST_FIXTURE(Test, testCommentStyles)
@@ -1529,6 +1537,46 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf160700)
assertXPath(pXmlDoc, "//office:text/text:list/text:list-item/text:p/text:bookmark"_ostr);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf160253_ordinary_numbering)
{
// Given a document with a list, and an out-of-the-list paragraph in the middle, having an
// endnote, which has a paragraph in another list.
// Before the fix, this already failed with
// Error: "list2916587379" is referenced by an IDREF, but not defined.
loadAndReload("tdf160253_ordinary_numbering.fodt");
// Make sure that the fourth paragraph has correct number - it was "1." before the fix
CPPUNIT_ASSERT_EQUAL(u"3."_ustr,
getProperty<OUString>(getParagraph(4), u"ListLabelString"_ustr));
// Make sure that we emit an identifier for the first list, and refer to it in the continuation
xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
// This failed before the fix, because 'xml:id' attribute wasn't emitted
OUString firstListId
= getXPath(pXmlDoc, "//office:body/office:text/text:list[1]"_ostr, "id"_ostr);
CPPUNIT_ASSERT(!firstListId.isEmpty());
assertXPath(pXmlDoc, "//office:body/office:text/text:list[2]"_ostr, "continue-list"_ostr,
firstListId);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf160253_outline_numbering)
{
// Given a document with an outline (chapter) numbering, and a paragraph in the middle, having
// an endnote, which has a paragraph in a list.
// Before the fix, this already failed with
// Error: "list2916587379" is referenced by an IDREF, but not defined.
loadAndReload("tdf160253_outline_numbering.fodt");
// Make sure that the third paragraph has correct number - it was "1" before the fix
CPPUNIT_ASSERT_EQUAL(u"2"_ustr,
getProperty<OUString>(getParagraph(3), u"ListLabelString"_ustr));
// The difference with the ordinary numbering is that for outline numbering, the list element
// isn't really necessary. It is a TODO to fix the output, and not export the list.
// xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
// assertXPath(pXmlDoc, "//office:body/office:text/text:list"_ostr, 0);
}
} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
-22
View File
@@ -657,28 +657,6 @@ SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) con
// Build a new numbering font basing on the current paragraph font:
std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));
const SwTextNode& rTextNode = *rInf.GetTextFrame()->GetTextNodeForParaProps();
if (const SwpHints* pHints = rTextNode.GetpSwpHints())
{
// Also look for an empty character hint that sits at the paragraph end:
for (size_t i = 0; i < pHints->Count(); ++i)
{
const SwTextAttr* pHint = pHints->GetSortedByEnd(i);
if (pHint->Which() == RES_TXTATR_AUTOFMT && pHint->GetEnd()
&& pHint->GetStart() == *pHint->GetEnd()
&& pHint->GetStart() == rTextNode.GetText().getLength())
{
std::shared_ptr<SfxItemSet> pSet
= pHint->GetAutoFormat().GetStyleHandle();
if (pSet)
{
pNumFnt->SetDiffFnt(pSet.get(), pIDSA);
break;
}
}
}
}
// #i53199#
if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
{
+10 -9
View File
@@ -2477,7 +2477,7 @@ void wwSectionManager::SetHdFt(wwSection const &rSection, int nSect,
}
void SwWW8ImplReader::AppendTextNode(SwPosition& rPos)
void SwWW8ImplReader::FinalizeTextNode(SwPosition& rPos, bool bAddNew)
{
SwTextNode* pText = m_pPaM->GetPointNode().GetTextNode();
@@ -2564,7 +2564,8 @@ void SwWW8ImplReader::AppendTextNode(SwPosition& rPos)
m_bFirstPara = false;
m_rDoc.getIDocumentContentOperations().AppendTextNode(rPos);
if (bAddNew)
m_rDoc.getIDocumentContentOperations().AppendTextNode(rPos);
// We can flush all anchored graphics at the end of a paragraph.
m_xAnchorStck->Flush();
@@ -3480,13 +3481,13 @@ void SwWW8ImplReader::simpleAddTextToParagraph(std::u16string_view aAddString)
else
{
m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, addString.copy(0, nCharsLeft));
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, addString.copy(nCharsLeft));
}
}
else
{
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, addString);
}
@@ -3557,7 +3558,7 @@ bool SwWW8ImplReader::HandlePageBreakChar()
&& (m_bFirstPara || m_bFirstParaOfPage))
{
IsTemp = false;
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
pTemp->SetAttr(*GetDfltAttr(RES_PARATR_NUMRULE));
}
@@ -3636,7 +3637,7 @@ bool SwWW8ImplReader::ReadChar(tools::Long nPosCp, tools::Long nCpOfs)
// Always insert a txtnode for a column break, e.g. ##
SwContentNode *pCntNd=m_pPaM->GetPointContentNode();
if (pCntNd!=nullptr && pCntNd->Len()>0) // if par is empty not break is needed
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SvxFormatBreakItem(SvxBreak::ColumnBefore, RES_BREAK));
}
break;
@@ -4105,7 +4106,7 @@ bool SwWW8ImplReader::ReadText(WW8_CP nStartCp, WW8_CP nTextLen, ManTypes nType)
}
if (bSplit)
{
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
}
}
@@ -4220,7 +4221,7 @@ bool SwWW8ImplReader::ReadText(WW8_CP nStartCp, WW8_CP nTextLen, ManTypes nType)
// to insert a text node.
if (!bStartLine && !m_xAnchorStck->empty())
{
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
}
m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM,
SvxFormatBreakItem(SvxBreak::PageBefore, RES_BREAK));
@@ -4233,7 +4234,7 @@ bool SwWW8ImplReader::ReadText(WW8_CP nStartCp, WW8_CP nTextLen, ManTypes nType)
m_xPreviousNode.reset();
if (m_pPaM->GetPoint()->GetContentIndex())
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
if (!m_bInHyperlink)
bJoined = JoinNode(*m_pPaM);
+1 -1
View File
@@ -1421,7 +1421,7 @@ private:
bool StyleExists(unsigned int nColl) const { return (nColl < m_vColl.size()); }
SwWW8StyInf *GetStyle(sal_uInt16 nColl) const;
void AppendTextNode(SwPosition& rPos);
void FinalizeTextNode(SwPosition& rPos, bool bAddNew = true);
void Read_HdFt(int nSect, const SwPageDesc *pPrev,
const wwSection &rSection);
+3 -1
View File
@@ -2402,7 +2402,7 @@ void WW8TabDesc::CreateSwTable()
}
if (bInsNode)
m_pIo->AppendTextNode(*pPoint);
m_pIo->FinalizeTextNode(*pPoint);
m_xTmpPos = m_pIo->m_rDoc.CreateUnoCursor(*m_pIo->m_pPaM->GetPoint());
@@ -3497,6 +3497,8 @@ bool SwWW8ImplReader::StartTable(WW8_CP nStartCp)
void SwWW8ImplReader::TabCellEnd()
{
FinalizeTextNode(*m_pPaM->GetPoint(), false);
if (m_nInTable && m_xTableDesc)
m_xTableDesc->TableCellEnd();
+1 -1
View File
@@ -3477,7 +3477,7 @@ eF_ResT SwWW8ImplReader::Read_F_Tox( WW8FieldDesc* pF, OUString& rStr )
}
if (m_pPaM->GetPoint()->GetContentIndex())
AppendTextNode(*m_pPaM->GetPoint());
FinalizeTextNode(*m_pPaM->GetPoint());
const SwPosition* pPos = m_pPaM->GetPoint();
+2 -2
View File
@@ -881,7 +881,7 @@ void wwSectionManager::CreateSep(const tools::Long nTextPos)
if( txtNode->Len() == 0 )
insert = false;
if( insert )
mrReader.AppendTextNode(*mrReader.m_pPaM->GetPoint());
mrReader.FinalizeTextNode(*mrReader.m_pPaM->GetPoint());
}
ww::WordVersion eVer = mrReader.GetFib().GetFIBVersion();
@@ -2528,7 +2528,7 @@ bool SwWW8ImplReader::StartApo(const ApoTestResults &rApo, const WW8_TablePos *p
{
// The two fly frames would have the same anchor position, leading to
// potentially overlapping text, prevent that.
AppendTextNode(*pPoint);
FinalizeTextNode(*pPoint);
}
}
}
+1 -1
View File
@@ -247,7 +247,7 @@ void SwXMLExport::collectAutoStyles()
GetFormExport()->examineForms(xPage);
}
GetTextParagraphExport()->collectTextAutoStylesOptimized( m_bShowProgress );
GetTextParagraphExport()->collectTextAutoStylesAndNodeExportOrder(m_bShowProgress);
}
mbAutoStylesCollected = true;
+46 -116
View File
@@ -39,6 +39,7 @@
#include <com/sun/star/text/XTextTablesSupplier.hpp>
#include <com/sun/star/text/XNumberingRulesSupplier.hpp>
#include <com/sun/star/text/XChapterNumberingSupplier.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextContent.hpp>
@@ -1331,12 +1332,14 @@ struct XMLTextParagraphExport::DocumentListNodes
{
struct NodeData
{
std::ptrdiff_t order;
sal_Int32 index; // see SwNode::GetIndex and SwNodeOffset
sal_uInt64 style_id; // actually a pointer to NumRule
OUString list_id;
};
std::vector<NodeData> docListNodes;
DocumentListNodes(const css::uno::Reference<css::frame::XModel>& xModel)
DocumentListNodes(const css::uno::Reference<css::frame::XModel>& xModel,
const std::vector<sal_Int32>& aDocumentNodeOrder)
{
// Sequence of nodes, each of them represented by three-element sequence,
// corresponding to NodeData members
@@ -1358,13 +1361,18 @@ struct XMLTextParagraphExport::DocumentListNodes
for (const auto& node : nodes)
{
assert(node.getLength() == 3);
docListNodes.push_back({ .index = node[0].get<sal_Int32>(),
sal_Int32 nodeIndex = node[0].get<sal_Int32>();
auto nodeOrder = std::distance(
aDocumentNodeOrder.begin(),
std::find(aDocumentNodeOrder.begin(), aDocumentNodeOrder.end(), nodeIndex));
docListNodes.push_back({ .order = nodeOrder,
.index = nodeIndex,
.style_id = node[1].get<sal_uInt64>(),
.list_id = node[2].get<OUString>() });
}
std::sort(docListNodes.begin(), docListNodes.end(),
[](const NodeData& lhs, const NodeData& rhs) { return lhs.index < rhs.index; });
[](const NodeData& lhs, const NodeData& rhs) { return lhs.order < rhs.order; });
}
bool ShouldSkipListId(const Reference<XTextContent>& xTextContent) const
{
@@ -1385,10 +1393,9 @@ struct XMLTextParagraphExport::DocumentListNodes
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)
auto it = std::find_if(docListNodes.begin(), docListNodes.end(),
[index](const NodeData& el) { return el.index == index; });
if (it == docListNodes.end())
return false;
// We need to write the id, when there will be continuation of the list either with
@@ -1616,9 +1623,7 @@ const enum XMLTokenEnum lcl_XmlReferenceElements[] = {
const enum XMLTokenEnum lcl_XmlBookmarkElements[] = {
XML_BOOKMARK, XML_BOOKMARK_START, XML_BOOKMARK_END };
// This function replaces the text portion iteration during auto style
// collection.
void XMLTextParagraphExport::collectTextAutoStylesOptimized( bool bIsProgress )
void XMLTextParagraphExport::collectTextAutoStylesAndNodeExportOrder(bool bIsProgress)
{
GetExport().GetShapeExport(); // make sure the graphics styles family is added
@@ -1628,60 +1633,11 @@ void XMLTextParagraphExport::collectTextAutoStylesOptimized( bool bIsProgress )
const bool bAutoStyles = true;
const bool bExportContent = false;
// Export AutoStyles:
Reference< XAutoStylesSupplier > xAutoStylesSupp( GetExport().GetModel(), UNO_QUERY );
if ( xAutoStylesSupp.is() )
if (auto xTextDocument = GetExport().GetModel().query<XTextDocument>())
{
Reference< XAutoStyles > xAutoStyleFamilies = xAutoStylesSupp->getAutoStyles();
const auto collectFamily = [this, &xAutoStyleFamilies](const OUString& sName,
XmlStyleFamily nFamily) {
Any aAny = xAutoStyleFamilies->getByName( sName );
Reference< XAutoStyleFamily > xAutoStyles = *o3tl::doAccess<Reference<XAutoStyleFamily>>(aAny);
Reference < XEnumeration > xAutoStylesEnum( xAutoStyles->createEnumeration() );
while ( xAutoStylesEnum->hasMoreElements() )
{
aAny = xAutoStylesEnum->nextElement();
Reference< XAutoStyle > xAutoStyle = *o3tl::doAccess<Reference<XAutoStyle>>(aAny);
Reference < XPropertySet > xPSet( xAutoStyle, uno::UNO_QUERY );
Add( nFamily, xPSet, {}, true );
}
};
collectFamily("CharacterStyles", XmlStyleFamily::TEXT_TEXT);
collectFamily("RubyStyles", XmlStyleFamily::TEXT_RUBY);
collectFamily("ParagraphStyles", XmlStyleFamily::TEXT_PARAGRAPH);
}
// Export Field AutoStyles:
Reference< XTextFieldsSupplier > xTextFieldsSupp( GetExport().GetModel(), UNO_QUERY );
if ( xTextFieldsSupp.is() )
{
Reference< XEnumerationAccess > xTextFields = xTextFieldsSupp->getTextFields();
Reference < XEnumeration > xTextFieldsEnum( xTextFields->createEnumeration() );
while ( xTextFieldsEnum->hasMoreElements() )
{
Any aAny = xTextFieldsEnum->nextElement();
Reference< XTextField > xTextField = *o3tl::doAccess<Reference<XTextField>>(aAny);
exportTextField( xTextField, bAutoStyles, bIsProgress,
!xAutoStylesSupp.is(), nullptr );
try
{
Reference < XPropertySet > xSet( xTextField, UNO_QUERY );
Reference < XText > xText;
Any a = xSet->getPropertyValue("TextRange");
a >>= xText;
if ( xText.is() )
{
exportText( xText, true, bIsProgress, bExportContent );
GetExport().GetTextParagraphExport()
->collectTextAutoStyles( xText );
}
}
catch (Exception&)
{
}
}
bInDocumentNodeOrderCollection = true;
collectTextAutoStyles(xTextDocument->getText(), bIsProgress);
bInDocumentNodeOrderCollection = false;
}
// Export text frames:
@@ -1728,47 +1684,11 @@ void XMLTextParagraphExport::collectTextAutoStylesOptimized( bool bIsProgress )
}
}
sal_Int32 nCount;
// AutoStyles for sections
Reference< XTextSectionsSupplier > xSectionsSupp( GetExport().GetModel(), UNO_QUERY );
if ( xSectionsSupp.is() )
{
Reference< XIndexAccess > xSections( xSectionsSupp->getTextSections(), UNO_QUERY );
if ( xSections.is() )
{
nCount = xSections->getCount();
for( sal_Int32 i = 0; i < nCount; ++i )
{
Any aAny = xSections->getByIndex( i );
Reference< XTextSection > xSection = *o3tl::doAccess<Reference<XTextSection>>(aAny);
Reference < XPropertySet > xPSet( xSection, uno::UNO_QUERY );
Add( XmlStyleFamily::TEXT_SECTION, xPSet );
}
}
}
// AutoStyles for tables (Note: suppress autostyle collection for paragraphs in exportTable)
Reference< XTextTablesSupplier > xTablesSupp( GetExport().GetModel(), UNO_QUERY );
if ( xTablesSupp.is() )
{
Reference< XIndexAccess > xTables( xTablesSupp->getTextTables(), UNO_QUERY );
if ( xTables.is() )
{
nCount = xTables->getCount();
for( sal_Int32 i = 0; i < nCount; ++i )
{
Any aAny = xTables->getByIndex( i );
Reference< XTextTable > xTable = *o3tl::doAccess<Reference<XTextTable>>(aAny);
exportTable( xTable, true, true );
}
}
}
Reference< XNumberingRulesSupplier > xNumberingRulesSupp( GetExport().GetModel(), UNO_QUERY );
if ( xNumberingRulesSupp.is() )
{
Reference< XIndexAccess > xNumberingRules = xNumberingRulesSupp->getNumberingRules();
nCount = xNumberingRules->getCount();
sal_Int32 nCount = xNumberingRules->getCount();
// Custom outline assignment lost after re-importing sxw (#i73361#)
for( sal_Int32 i = 0; i < nCount; ++i )
{
@@ -1894,14 +1814,36 @@ bool XMLTextParagraphExport::ExportListId() const
&& GetExport().getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_012;
}
void XMLTextParagraphExport::RecordNodeIndex(const css::uno::Reference<css::text::XTextContent>& xTextContent)
{
if (!bInDocumentNodeOrderCollection)
return;
if (auto xPropSet = xTextContent.query<css::beans::XPropertySet>())
{
try
{
sal_Int32 index = 0;
// See SwXParagraph::Impl::GetPropertyValues_Impl
xPropSet->getPropertyValue("ODFExport_NodeIndex") >>= index;
assert(std::find(maDocumentNodeOrder.begin(), maDocumentNodeOrder.end(), index)
== maDocumentNodeOrder.end());
maDocumentNodeOrder.push_back(index);
}
catch (css::beans::UnknownPropertyException&)
{
// That's absolutely fine!
}
}
}
bool XMLTextParagraphExport::ShouldSkipListId(const Reference<XTextContent>& xTextContent)
{
if (!mpDocumentListNodes)
{
if (ExportListId())
mpDocumentListNodes.reset(new DocumentListNodes(GetExport().GetModel()));
mpDocumentListNodes.reset(new DocumentListNodes(GetExport().GetModel(), maDocumentNodeOrder));
else
mpDocumentListNodes.reset(new DocumentListNodes({}));
mpDocumentListNodes.reset(new DocumentListNodes({}, {}));
}
return mpDocumentListNodes->ShouldSkipListId(xTextContent);
@@ -1952,6 +1894,7 @@ void XMLTextParagraphExport::exportTextContentEnumeration(
{
if( bAutoStyles )
{
RecordNodeIndex(xTxtCntnt);
exportListAndSectionChange( xCurrentTextSection, xTxtCntnt,
aPrevNumInfo, aNextNumInfo,
bAutoStyles );
@@ -2323,7 +2266,6 @@ void XMLTextParagraphExport::exportParagraph(
Reference < XEnumerationAccess > xEA( rTextContent, UNO_QUERY );
Reference < XEnumeration > xTextEnum = xEA->createEnumeration();
const bool bHasPortions = xTextEnum.is();
Reference < XEnumeration> xContentEnum;
Reference < XContentEnumerationAccess > xCEA( rTextContent, UNO_QUERY );
@@ -2357,22 +2299,10 @@ void XMLTextParagraphExport::exportParagraph(
bool bPrevCharIsSpace(true); // true because whitespace at start is ignored
if( bAutoStyles )
{
if( bHasContentEnum )
exportTextContentEnumeration(
xContentEnum, bAutoStyles, xSection,
bIsProgress );
if ( bHasPortions )
{
exportTextRangeEnumeration(xTextEnum, bAutoStyles, bIsProgress, bPrevCharIsSpace);
}
}
else
{
enum XMLTokenEnum eElem =
0 < nOutlineLevel ? XML_H : XML_P;
SvXMLElementExport aElem( GetExport(), eExtensionNS == TextPNS::EXTENSION ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_TEXT, eElem,
SvXMLElementExport aElem( GetExport(), !bAutoStyles, eExtensionNS == TextPNS::EXTENSION ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_TEXT, eElem,
true, false );
if( bHasContentEnum )
{
+42 -20
View File
@@ -1789,6 +1789,46 @@ void XMLParaContext::endFastElement(sal_Int32 )
true,
mbOutlineContentVisible);
bool bEmptyHints = false;
XMLHint_Impl* pMarkerStyleHint = nullptr;
if (m_xHints)
{
uno::Reference<text::XTextRangeCompare> xCompare(xTxtImport->GetText(), uno::UNO_QUERY);
if (xCompare.is())
{
try
{
for (const auto& pHint : m_xHints->GetHints())
{
if (xCompare->compareRegionStarts(pHint->GetStart(), pHint->GetEnd()) == 0)
{
bEmptyHints = true;
// Is this the trailing empty span, defining the paragraph mark properties?
// Convert it to the marker style, for backward compatibility with documents
// created between commits 6249858a8972aef077e0249bd93cfe8f01bce4d6 and
// 1a88efa8e02a6d765dab13c7110443bb9e6acecf, where the trailing empty spans
// were used to store the marker formatting
if (!m_aMarkerStyleName.hasValue()
&& xCompare->compareRegionStarts(pHint->GetStart(), xEnd) == 0)
{
if (auto pStyle = GetImport().GetTextImport()->FindAutoCharStyle(
static_cast<XMLStyleHint_Impl*>(pHint.get())->GetStyleName()))
{
m_aMarkerStyleName = pStyle->GetAutoName();
pMarkerStyleHint = pHint.get();
}
}
}
}
}
catch (const uno::Exception&)
{
TOOLS_WARN_EXCEPTION("xmloff.text", "");
}
}
}
if (m_aMarkerStyleName.hasValue())
{
if (auto xPropSet = xStart.query<css::beans::XPropertySet>())
@@ -1850,26 +1890,7 @@ void XMLParaContext::endFastElement(sal_Int32 )
{
bool bSetNoFormatAttr = false;
uno::Reference<beans::XPropertySet> xCursorProps(xAttrCursor, uno::UNO_QUERY);
int nEmptyHints = 0;
uno::Reference<text::XTextRangeCompare> xCompare(xTxtImport->GetText(), uno::UNO_QUERY);
if (xCompare.is())
{
try
{
for (const auto& pHint : m_xHints->GetHints())
{
if (xCompare->compareRegionStarts(pHint->GetStart(), pHint->GetEnd()) == 0)
{
++nEmptyHints;
}
}
}
catch (const uno::Exception&)
{
TOOLS_WARN_EXCEPTION("xmloff.text", "");
}
}
if (nEmptyHints > 0 || m_aMarkerStyleName.hasValue())
if (bEmptyHints || m_aMarkerStyleName.hasValue())
{
// We have at least one empty hint, then make try to ask the cursor to not upgrade our character
// attributes to paragraph-level formatting, which would lead to incorrect rendering.
@@ -1888,6 +1909,7 @@ void XMLParaContext::endFastElement(sal_Int32 )
switch( pHint->GetType() )
{
case XMLHintType::XML_HINT_STYLE:
if (pHint != pMarkerStyleHint) // already processed above
{
const OUString& rStyleName =
static_cast<XMLStyleHint_Impl *>(pHint)->GetStyleName();