tdf#152203 DOCX import: fix mixed footnotes/endnotes

Footnotes (like endnotes) were imported in the order
of their w:footnote elements in footnotes.xml, resulting
mixed footnote text content during loading documents
exported from Google Docs. Import them in the order
of their w:id attributes.

Regression from commit 9b39ce0e66
"tdf#76260 DOCX import: fix slow footnote import".

Change-Id: I7d9ed36fe96b2b90c4d62fb1ca7201318581775d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143824
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
This commit is contained in:
László Németh
2022-12-10 18:28:25 +00:00
parent 080d2d21cb
commit 09ae3c0194
10 changed files with 119 additions and 8 deletions
Binary file not shown.
@@ -21,6 +21,7 @@
#include <com/sun/star/text/XDocumentIndex.hpp>
#include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
#include <com/sun/star/text/XFootnotesSupplier.hpp>
#include <com/sun/star/text/XFootnote.hpp>
#include <com/sun/star/text/XTextContentAppend.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextFieldsSupplier.hpp>
@@ -1272,6 +1273,23 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf143583)
assertXPath(pXml, "/w:footnotes/w:footnote[6]/w:p", 3);
}
CPPUNIT_TEST_FIXTURE(Test, testTdf152203)
{
loadAndSave("tdf152203.docx");
xmlDocUniquePtr pXml = parseExport("word/footnotes.xml");
CPPUNIT_ASSERT(pXml);
uno::Reference<text::XFootnotesSupplier> xFootnotesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XIndexAccess> xFootnotes = xFootnotesSupplier->getFootnotes();
uno::Reference<text::XTextRange> xLastFootnote(xFootnotes->getByIndex(5), uno::UNO_QUERY);
// This was "Footnote for pg5" (replaced footnotes)
CPPUNIT_ASSERT_EQUAL( OUString("Footnote for pg 6"), xLastFootnote->getString().trim() );
uno::Reference<text::XTextRange> xLastButOne(xFootnotes->getByIndex(4), uno::UNO_QUERY);
// This was "Footnote for pg 6" (replaced footnotes)
CPPUNIT_ASSERT_EQUAL( OUString("Footnote for pg5"), xLastButOne->getString().trim() );
}
// skip test for macOS (missing fonts?)
#if !defined(MACOSX)
DECLARE_OOXMLEXPORT_TEST(testTdf146346, "tdf146346.docx")
@@ -313,6 +313,9 @@ public:
/// Receives end mark for glossary document entry.
virtual void endGlossaryEntry() = 0;
/// Receives identifier for node entry.
virtual void checkId(const sal_Int32 nId) = 0;
virtual void commentProps(const OUString& /*sId*/, const CommentProperties& /*rProps*/) {}
protected:
@@ -197,6 +197,9 @@ DomainMapper::~DomainMapper()
{
try
{
// Remove temporary footnotes and endnotes
m_pImpl->RemoveTemporaryFootOrEndnotes();
uno::Reference< text::XDocumentIndexesSupplier> xIndexesSupplier( m_pImpl->GetTextDocument(), uno::UNO_QUERY );
sal_Int32 nIndexes = 0;
if( xIndexesSupplier.is() )
@@ -3739,6 +3742,17 @@ void DomainMapper::lcl_positivePercentage(const OUString& rText)
m_pImpl->m_aPositivePercentages.push(rText);
}
void DomainMapper::lcl_checkId(const sal_Int32 nId)
{
if (m_pImpl->IsInFootnote())
{
if (m_pImpl->GetFootnoteCount() > -1)
m_pImpl->m_aFootnoteIds.push_back(nId);
}
else if (m_pImpl->GetEndnoteCount() > -1)
m_pImpl->m_aEndnoteIds.push_back(nId);
}
void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len)
{
// All these fixed values are defined as static const sal_Unicode codepoints in the fast parser,
@@ -162,6 +162,7 @@ private:
::writerfilter::Reference<Stream>::Pointer_t ref) override;
virtual void lcl_startGlossaryEntry() override;
virtual void lcl_endGlossaryEntry() override;
virtual void lcl_checkId(const sal_Int32 nId) override;
// Properties
virtual void lcl_attribute(Id Name, Value & val) override;
@@ -3617,12 +3617,49 @@ static void lcl_PasteRedlines(
}
}
void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes()
{
uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
uno::Reference< text::XFootnote > xNote;
if (GetFootnoteCount() > 0)
{
auto xFootnotes = xFootnotesSupplier->getFootnotes();
for (sal_Int32 i = GetFootnoteCount(); i > 0; --i)
{
xFootnotes->getByIndex(i) >>= xNote;
xNote->getAnchor()->setString("");
}
}
if (GetEndnoteCount() > 0)
{
auto xEndnotes = xEndnotesSupplier->getEndnotes();
for (sal_Int32 i = GetEndnoteCount(); i > 0; --i)
{
xEndnotes->getByIndex(i) >>= xNote;
xNote->getAnchor()->setString("");
}
}
}
static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds)
{
// convert arbitrary footnote identifiers to 0, 1, 2...
// indices, keeping their possible random order
std::deque<sal_Int32> aSortedIds = rNoteIds;
std::sort(aSortedIds.begin(), aSortedIds.end());
std::map<sal_Int32, size_t> aMapIds;
for (size_t i = 0; i < aSortedIds.size(); ++i)
aMapIds[aSortedIds[i]] = i;
for (size_t i = 0; i < rNoteIds.size(); ++i)
rNoteIds[i] = aMapIds[rNoteIds[i]];
}
void DomainMapper_Impl::PopFootOrEndnote()
{
// content of the footnotes were inserted after the first footnote in temporary footnotes,
// restore the content of the actual footnote by copying its content from the first
// (remaining) temporary footnote and remove the temporary footnote.
// FIXME: add footnote IDs to handle possible differences in footnote serialization
uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
bool bCopied = false;
@@ -3638,11 +3675,31 @@ void DomainMapper_Impl::PopFootOrEndnote()
( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xFootnoteLast ) )
) && xFootnoteLast->getLabel().isEmpty() )
{
// copy content of the first remaining temporary footnote
if ( IsInFootnote() )
xFootnotes->getByIndex(1) >>= xFootnoteFirst;
else
xEndnotes->getByIndex(1) >>= xFootnoteFirst;
// copy content of the next temporary footnote
try
{
if ( IsInFootnote() && !m_aFootnoteIds.empty() )
{
if ( m_aFootnoteIds.size() == sal::static_int_cast<size_t>(GetFootnoteCount()) )
lcl_convertToNoteIndices(m_aFootnoteIds);
xFootnotes->getByIndex(m_aFootnoteIds.front() + 1) >>= xFootnoteFirst;
m_aFootnoteIds.pop_front();
}
else if ( !IsInFootnote() && !m_aEndnoteIds.empty() )
{
if ( m_aEndnoteIds.size() == sal::static_int_cast<size_t>(GetEndnoteCount()) )
lcl_convertToNoteIndices(m_aEndnoteIds);
xEndnotes->getByIndex(m_aEndnoteIds.front() + 1) >>= xFootnoteFirst;
m_aEndnoteIds.pop_front();
}
else
m_bSaxError = true;
}
catch (uno::Exception const&)
{
TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote");
m_bSaxError = true;
}
if (!m_bSaxError && xFootnoteFirst != xFootnoteLast)
{
uno::Reference< text::XText > xSrc( xFootnoteFirst, uno::UNO_QUERY_THROW );
@@ -3663,8 +3720,6 @@ void DomainMapper_Impl::PopFootOrEndnote()
for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++)
m_aStoredRedlines[eType].pop_front();
// remove temporary footnote
xFootnoteFirst->getAnchor()->setString("");
bCopied = true;
}
}
@@ -633,6 +633,8 @@ private:
public:
css::uno::Reference<css::text::XTextRange> m_xInsertTextRange;
css::uno::Reference<css::text::XTextRange> m_xAltChunkStartingRange;
std::deque<sal_Int32> m_aFootnoteIds;
std::deque<sal_Int32> m_aEndnoteIds;
bool m_bIsInTextBox;
private:
@@ -884,6 +886,7 @@ public:
void IncrementFootnoteCount() { ++m_nFootnotes; }
sal_Int32 GetEndnoteCount() const { return m_nEndnotes; }
void IncrementEndnoteCount() { ++m_nEndnotes; }
void RemoveTemporaryFootOrEndnotes();
void PushAnnotation();
void PopAnnotation();
@@ -316,6 +316,20 @@ void LoggedStream::endGlossaryEntry()
#endif
}
void LoggedStream::checkId(const sal_Int32 nId)
{
#ifdef DBG_UTIL
mHelper.startElement("checkId");
LoggedResourcesHelper::chars(OUString::number(nId));
#endif
lcl_checkId(nId);
#ifdef DBG_UTIL
LoggedResourcesHelper::endElement();
#endif
}
LoggedProperties::LoggedProperties(
#ifdef DBG_UTIL
const std::string& sPrefix)
@@ -73,6 +73,7 @@ public:
void info(const std::string& info) override;
void startGlossaryEntry() override;
void endGlossaryEntry() override;
void checkId(const sal_Int32 nId) override;
virtual void setDocumentReference(writerfilter::ooxml::OOXMLDocument* /*pDocument*/) override{};
@@ -98,6 +99,7 @@ protected:
virtual void lcl_substream(Id name, writerfilter::Reference<Stream>::Pointer_t ref) = 0;
virtual void lcl_startGlossaryEntry() {}
virtual void lcl_endGlossaryEntry() {}
virtual void lcl_checkId(const sal_Int32) {}
#ifdef DBG_UTIL
LoggedResourcesHelper mHelper;
@@ -1410,6 +1410,7 @@ void OOXMLFastContextHandlerXNote::lcl_endFastElement
void OOXMLFastContextHandlerXNote::checkId(const OOXMLValue::Pointer_t& pValue)
{
mnMyXNoteId = sal_Int32(pValue->getInt());
mpStream->checkId(mnMyXNoteId);
}
void OOXMLFastContextHandlerXNote::checkType(const OOXMLValue::Pointer_t& pValue)