tdf#42346: DOC export of cross-references to objects

Same solution which was used for DOCX export:
98bc7215935f1eb2e0dc6f1db826d8e729430c13

Change-Id: I8af46db003a6192c6adaae1a35dff58744919eee
Reviewed-on: https://gerrit.libreoffice.org/44502
Reviewed-by: Tamás Zolnai <tamas.zolnai@collabora.com>
Tested-by: Tamás Zolnai <tamas.zolnai@collabora.com>
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
index 913b09b..7c8d3be 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport10.cxx
@@ -1295,6 +1295,8 @@ DECLARE_OOXMLEXPORT_TEST( testTableCrossReference, "table_cross_reference.odt" )
        }
        ++nIndex;
    }

    CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), nIndex);
}

DECLARE_OOXMLEXPORT_TEST( testTableCrossReferenceCustomFormat, "table_cross_reference_custom_format.odt" )
@@ -1708,6 +1710,8 @@ DECLARE_OOXMLEXPORT_TEST( testObjectCrossReference, "object_cross_reference.odt"
        }
        ++nIndex;
    }

    CPPUNIT_ASSERT_EQUAL(sal_uInt16(21), nIndex);
}

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/qa/extras/ww8export/data/object_cross_reference.odt b/sw/qa/extras/ww8export/data/object_cross_reference.odt
new file mode 100755
index 0000000..9eaca35
--- /dev/null
+++ b/sw/qa/extras/ww8export/data/object_cross_reference.odt
Binary files differ
diff --git a/sw/qa/extras/ww8export/data/table_cross_reference.odt b/sw/qa/extras/ww8export/data/table_cross_reference.odt
new file mode 100755
index 0000000..95f3313
--- /dev/null
+++ b/sw/qa/extras/ww8export/data/table_cross_reference.odt
Binary files differ
diff --git a/sw/qa/extras/ww8export/data/table_cross_reference_custom_format.odt b/sw/qa/extras/ww8export/data/table_cross_reference_custom_format.odt
new file mode 100755
index 0000000..1c41e36
--- /dev/null
+++ b/sw/qa/extras/ww8export/data/table_cross_reference_custom_format.odt
Binary files differ
diff --git a/sw/qa/extras/ww8export/ww8export2.cxx b/sw/qa/extras/ww8export/ww8export2.cxx
index 1c18017..0a3179d 100644
--- a/sw/qa/extras/ww8export/ww8export2.cxx
+++ b/sw/qa/extras/ww8export/ww8export2.cxx
@@ -323,6 +323,393 @@ DECLARE_WW8EXPORT_TEST( testActiveXCheckbox, "checkbox_control.odt" )
    CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER,getProperty<text::TextContentAnchorType>(xPropertySet2,"AnchorType"));
}

DECLARE_OOXMLEXPORT_TEST( testTableCrossReference, "table_cross_reference.odt" )
{
    // tdf#42346: Cross references to tables were not saved
    // MSO uses simple bookmarks for referencing table caption, so we do the same by export
    if (!mbExported)
        return;

    // Check whether we have all the neccessary bookmarks exported and imported back
    uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), xBookmarksByIdx->getCount());
    uno::Reference<container::XNameAccess> xBookmarksByName(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_number_only"));

    // Check bookmark text ranges
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table 1: Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table 1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1"), xRange->getString());
    }

    // Check reference fields
    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XEnumerationAccess> xFieldsAccess(xTextFieldsSupplier->getTextFields());
    uno::Reference<container::XEnumeration> xFields(xFieldsAccess->createEnumeration());
    CPPUNIT_ASSERT(xFields->hasMoreElements());

    sal_uInt16 nIndex = 0;
    while (xFields->hasMoreElements())
    {
        uno::Reference<lang::XServiceInfo> xServiceInfo(xFields->nextElement(), uno::UNO_QUERY);
        uno::Reference<beans::XPropertySet> xPropertySet(xServiceInfo, uno::UNO_QUERY);
        switch (nIndex)
        {
            // Full reference to table caption
            case 0:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Table 1: Table caption"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_full"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Reference to table number
            case 1:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("1"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_number_only"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Reference to caption only
            case 2:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_caption_only"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Reference to category and number
            case 3:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Table 1"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_label_and_number"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Reference to page of the table
            case 4:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("1"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_full"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Page style reference / exported as simple page reference
            case 5:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("1"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_full"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            // Above / bellow reference
            case 6:
            {
                CPPUNIT_ASSERT(xServiceInfo->supportsService("com.sun.star.text.TextField.GetReference"));
                OUString sValue;
                sal_Int16 nValue;
                xPropertySet->getPropertyValue("CurrentPresentation") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("above"), sValue);
                xPropertySet->getPropertyValue("SourceName") >>= sValue;
                CPPUNIT_ASSERT_EQUAL(OUString("Ref_Table0_full"), sValue);
                xPropertySet->getPropertyValue("SequenceNumber") >>= nValue;
                CPPUNIT_ASSERT_EQUAL(sal_Int16(0), nValue);
                break;
            }
            default:
                break;
        }
        ++nIndex;
    }
    CPPUNIT_ASSERT_EQUAL(sal_uInt16(8), nIndex);
}

DECLARE_OOXMLEXPORT_TEST( testTableCrossReferenceCustomFormat, "table_cross_reference_custom_format.odt" )
{
    // tdf#42346: Cross references to tables were not saved
    // Check also captions with custom formatting
    if (!mbExported)
        return;

    // Check whether we have all the neccessary bookmarks exported and imported back
    uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(16), xBookmarksByIdx->getCount());
    uno::Reference<container::XNameAccess> xBookmarksByName(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table0_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table1_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table1_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table1_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table1_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table2_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table2_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table2_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table2_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table3_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table3_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table3_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Table3_number_only"));

    // Check bookmark text ranges
    // First table's caption
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1. Table: Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1. Table"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table0_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1"), xRange->getString());
    }
    // Second table's caption
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table1_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("2. TableTable caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table1_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("2. Table"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table1_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table1_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("2"), xRange->getString());
    }
    // Third table's caption
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table2_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("3) Table Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table2_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("3) Table"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table2_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table2_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("3"), xRange->getString());
    }
    // Fourth table's caption
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table3_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table 4- Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table3_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table 4"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table3_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Table caption"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Table3_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("4"), xRange->getString());
    }
}

DECLARE_OOXMLEXPORT_TEST( testObjectCrossReference, "object_cross_reference.odt" )
{
    // tdf#42346: Cross references to objects were not saved
    // MSO uses simple bookmarks for referencing table caption, so we do the same by export
    if (!mbExported)
        return;

    // Check whether we have all the neccessary bookmarks exported and imported back
    uno::Reference<text::XBookmarksSupplier> xBookmarksSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xBookmarksByIdx(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(15), xBookmarksByIdx->getCount());
    uno::Reference<container::XNameAccess> xBookmarksByName(xBookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Drawing0_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Drawing0_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Drawing0_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Drawing0_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Drawing1_full"));

    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Illustration0_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Illustration0_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Illustration0_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Illustration0_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Illustration1_caption_only"));

    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Text0_full"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Text0_label_and_number"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Text0_caption_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Text0_number_only"));
    CPPUNIT_ASSERT(xBookmarksByName->hasByName("Ref_Text1_label_and_number"));

    // Check bookmark text ranges
    // Cross references to shapes
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Drawing0_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Drawing 1: A rectangle"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Drawing0_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Drawing 1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Drawing0_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("A rectangle"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Drawing0_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Drawing1_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Drawing 2: a circle"), xRange->getString());
    }

    // Cross references to pictures
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Illustration0_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Illustration 1: A picture"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Illustration0_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Illustration 1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Illustration0_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("A picture"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Illustration0_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Illustration1_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("an other image"), xRange->getString());
    }

    // Cross references to text frames
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Text0_full"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Text 1: A frame"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Text0_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Text 1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Text0_caption_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("A frame"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Text0_number_only"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("1"), xRange->getString());
    }
    {
        uno::Reference<text::XTextContent> xContent(xBookmarksByName->getByName("Ref_Text1_label_and_number"), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xRange(xContent->getAnchor(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Text 2"), xRange->getString());
    }
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx b/sw/source/filter/ww8/attributeoutputbase.hxx
index b2a7605..347153d 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -169,10 +169,10 @@ public:
    virtual void EmptyParagraph() = 0;

    /// Start of the text run.
    virtual void StartRun( const SwRedlineData* pRedlineData, bool bSingleEmptyRun = false ) = 0;
    virtual void StartRun( const SwRedlineData* pRedlineData, sal_Int32 nPos, bool bSingleEmptyRun = false ) = 0;

    /// End of the text run.
    virtual void EndRun( const SwTextNode* pNode, sal_Int32 nPos ) = 0;
    virtual void EndRun( const SwTextNode* pNode, sal_Int32 nPos, bool bLastRun = false ) = 0;

    /// Called before we start outputting the attributes.
    virtual void StartRunProperties() = 0;
@@ -209,7 +209,8 @@ public:

    virtual void FieldVanish( const OUString& rText, ww::eField eType ) = 0;

    virtual void GenerateBookmarksForSequenceField(const SwTextNode& rNode, SwWW8AttrIter& rAttrIter) = 0;
    /// MSO uses bookmarks to reference sequence fields, so we need to generate these additional bookmarks during export
    void GenerateBookmarksForSequenceField(const SwTextNode& rNode, SwWW8AttrIter& rAttrIter);

    void StartTOX( const SwSection& rSect );

@@ -625,6 +626,9 @@ protected:

    virtual bool AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark );

    /// Insert a bookmark inside the currently processed parargaph.
    virtual void WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos ) = 0;

    ww8::GridColsPtr GetGridCols( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner );
    ww8::WidthsPtr   GetColumnWidths( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner );

diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 1ab6cde..9ac659eb 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -125,8 +125,6 @@
#include <IDocumentSettingAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <reffld.hxx>

#include <osl/file.hxx>
#include <vcl/embeddedfontshelper.hxx>
@@ -596,10 +594,9 @@ void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pT
    if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
        m_bParagraphOpened = false;

    // Clear generated bookmarks
    m_aBookmarksWithPosStart.clear();
    m_aBookmarksWithPosEnd.clear();

    // Clear bookmarks of the current paragraph
    m_aBookmarksOfParagraphStart.clear();
    m_aBookmarksOfParagraphEnd.clear();
}

void DocxAttributeOutput::WriteSdtBlock( sal_Int32& nSdtPrToken,
@@ -1091,7 +1088,7 @@ bool DocxAttributeOutput::IsFlyProcessingPostponed()
    return m_bPostponedProcessingFly;
}

void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, bool /*bSingleEmptyRun*/ )
void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
{
    // Don't start redline data here, possibly there is a hyperlink later, and
    // that has to be started first.
@@ -1110,7 +1107,7 @@ void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, bool /*bS
    m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
}

void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos)
void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool /*bLastRun*/)
{
    int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
    // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
@@ -1414,9 +1411,9 @@ void DocxAttributeOutput::DoWriteBookmarkTagEnd(const OUString & bookmarkName)
    }
}

void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nPos)
void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
{
    auto aRange = m_aBookmarksWithPosStart.equal_range(nPos);
    auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
    for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
    {
        DoWriteBookmarkTagStart(aIter->second);
@@ -1426,9 +1423,9 @@ void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nPos)
    }
}

void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nPos)
void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
{
    auto aRange = m_aBookmarksWithPosEnd.equal_range(nPos);
    auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
    for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
    {
        // Get the id of the bookmark
@@ -2536,7 +2533,7 @@ void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, co
    m_pSerializer->endElementNS( XML_w, XML_rubyPr );

    m_pSerializer->startElementNS( XML_w, XML_rt, FSEND );
    StartRun( nullptr );
    StartRun( nullptr, nPos );
    StartRunProperties( );
    SwWW8AttrIter aAttrIt( m_rExport, rNode );
    aAttrIt.OutAttr( nPos, true );
@@ -2552,7 +2549,7 @@ void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, co
    m_pSerializer->endElementNS( XML_w, XML_rt );

    m_pSerializer->startElementNS( XML_w, XML_rubyBase, FSEND );
    StartRun( nullptr );
    StartRun( nullptr, nPos );
}

void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
@@ -2561,7 +2558,7 @@ void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
    EndRun( &rNode, nPos );
    m_pSerializer->endElementNS( XML_w, XML_rubyBase );
    m_pSerializer->endElementNS( XML_w, XML_ruby );
    StartRun(nullptr); // open Run again so OutputTextNode loop can close it
    StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
}

bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
@@ -2588,6 +2585,12 @@ bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTar
    return bBookMarkOnly;
}

void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
{
    m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
    m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
}

bool DocxAttributeOutput::StartURL( const OUString& rUrl, const OUString& rTarget )
{
    OUString sMark;
@@ -7205,176 +7208,6 @@ bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
    return false; // do not expand
}

void DocxAttributeOutput::GenerateBookmarksForSequenceField(const SwTextNode& rNode, SwWW8AttrIter& rAttrIter)
{
    if (const SwpHints* pTextAttrs = rNode.GetpSwpHints())
    {
        for( size_t i = 0; i < pTextAttrs->Count(); ++i )
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            if (pHt->GetAttr().Which() == RES_TXTATR_FIELD)
            {
                const SwFormatField& rField = static_cast<const SwFormatField&>(pHt->GetAttr());
                const SwField* pField = rField.GetField();
                // Need to have bookmarks only for sequence fields
                if (pField && pField->GetTyp()->Which() == SwFieldIds::SetExp && pField->GetSubType() == nsSwGetSetExpType::GSE_SEQ)
                {
                    const sal_uInt16 nSeqFieldNumber = static_cast<const SwSetExpField*>(pField)->GetSeqNumber();
                    const OUString sObjectName = static_cast<const SwSetExpFieldType*>(pField->GetTyp())->GetName();
                    const SwFieldTypes* pFieldTypes = m_rExport.m_pDoc->getIDocumentFieldsAccess().GetFieldTypes();
                    bool bHaveFullBkm = false;
                    bool bHaveLabelAndNumberBkm = false;
                    bool bHaveCaptionOnlyBkm = false;
                    bool bHaveNumberOnlyBkm = false;
                    bool bRunSplittedAtSep = false;
                    for( auto pFieldType : *pFieldTypes )
                    {
                        if( SwFieldIds::GetRef == pFieldType->Which() )
                        {
                            SwIterator<SwFormatField,SwFieldType> aIter( *pFieldType );
                            for( SwFormatField* pFormatField = aIter.First(); pFormatField; pFormatField = aIter.Next() )
                            {
                                SwGetRefField* pRefField = static_cast<SwGetRefField*>(pFormatField->GetField());
                                // If we have a reference to the current sequence field
                                if(pRefField->GetSeqNo() == nSeqFieldNumber && pRefField->GetSetRefName() == sObjectName)
                                {
                                    // Need to create a separate run for separator character
                                    SwWW8AttrIter aLocalAttrIter( m_rExport, rNode );
                                    const OUString aText = rNode.GetText();
                                    const sal_Int32 nCategoryStart = aText.indexOf(pRefField->GetSetRefName());
                                    const sal_Int32 nPosBeforeSeparator = std::max(nCategoryStart, pHt->GetStart());
                                    bool bCategoryFirst = nCategoryStart < pHt->GetStart();
                                    sal_Int32 nSeparatorPos = 0;
                                    if (bCategoryFirst)
                                    {
                                        nSeparatorPos = aLocalAttrIter.WhereNext();
                                        while (nSeparatorPos <= nPosBeforeSeparator)
                                        {
                                            aLocalAttrIter.NextPos();
                                            nSeparatorPos = aLocalAttrIter.WhereNext();
                                        }
                                    }
                                    else
                                    {
                                        nSeparatorPos = nCategoryStart + pRefField->GetSetRefName().getLength();
                                    }
                                    sal_Int32 nRefTextPos = 0;
                                    if(nSeparatorPos < aText.getLength())
                                    {
                                        nRefTextPos = SwGetExpField::GetReferenceTextPos(pHt->GetFormatField(), *m_rExport.m_pDoc, nSeparatorPos);
                                        if(nRefTextPos != nSeparatorPos)
                                        {
                                            if(!bRunSplittedAtSep)
                                            {
                                                if(!bCategoryFirst)
                                                    rAttrIter.SplitRun(nSeparatorPos);
                                                rAttrIter.SplitRun(nRefTextPos);
                                                bRunSplittedAtSep = true;
                                            }
                                            if(!bCategoryFirst)
                                                aLocalAttrIter.SplitRun(nSeparatorPos);
                                            aLocalAttrIter.SplitRun(nRefTextPos);
                                        }
                                        else if (bCategoryFirst)
                                        {
                                            if(!bRunSplittedAtSep)
                                            {
                                                rAttrIter.SplitRun(nSeparatorPos);
                                                bRunSplittedAtSep = true;
                                            }
                                            aLocalAttrIter.SplitRun(nSeparatorPos);
                                        }
                                    }
                                    // Generate bookmarks on the right position
                                    OUString sName("Ref_" + pRefField->GetSetRefName());
                                    sName += OUString::number(pRefField->GetSeqNo());
                                    switch (pRefField->GetFormat())
                                    {
                                        case REF_PAGE:
                                        case REF_PAGE_PGDESC:
                                        case REF_CONTENT:
                                        case REF_UPDOWN:
                                            sName += "_full";
                                            if(!bHaveFullBkm)
                                            {
                                                sal_Int32 nLastAttrStart = 0;
                                                sal_Int32 nActAttr = aLocalAttrIter.WhereNext();
                                                while (nActAttr < rNode.GetText().getLength())
                                                {
                                                    nLastAttrStart = nActAttr;
                                                    aLocalAttrIter.NextPos();
                                                    nActAttr = aLocalAttrIter.WhereNext();
                                                }
                                                WriteBookmarks_Impl( sName, std::min(nCategoryStart, pHt->GetStart()), nLastAttrStart );
                                                bHaveFullBkm = true;
                                            }
                                            break;
                                        case REF_ONLYNUMBER:
                                        {
                                            sName += "_label_and_number";
                                            if(!bHaveLabelAndNumberBkm)
                                            {
                                                if(bCategoryFirst)
                                                    WriteBookmarks_Impl( sName, std::min(nCategoryStart, pHt->GetStart()), std::max(nCategoryStart, pHt->GetStart()) );
                                                else
                                                {
                                                    // Find the last run which contains category text
                                                    SwWW8AttrIter aLocalAttrIter2( m_rExport, rNode );
                                                    sal_Int32 nCatLastRun = 0;
                                                    sal_Int32 nNextAttr = aLocalAttrIter2.WhereNext();
                                                    while (nNextAttr < nSeparatorPos)
                                                    {
                                                        nCatLastRun = nNextAttr;
                                                        aLocalAttrIter2.NextPos();
                                                        nNextAttr = aLocalAttrIter2.WhereNext();
                                                    }
                                                    WriteBookmarks_Impl( sName, pHt->GetStart(), nCatLastRun );
                                                }
                                                bHaveLabelAndNumberBkm = true;
                                            }
                                            break;
                                        }
                                        case REF_ONLYCAPTION:
                                        {
                                            sName += "_caption_only";
                                            if(!bHaveCaptionOnlyBkm)
                                            {
                                                // Find last run
                                                sal_Int32 nLastAttrStart = 0;
                                                sal_Int32 nActAttr = aLocalAttrIter.WhereNext();
                                                while (nActAttr < rNode.GetText().getLength())
                                                {
                                                    nLastAttrStart = nActAttr;
                                                    aLocalAttrIter.NextPos();
                                                    nActAttr = aLocalAttrIter.WhereNext();
                                                }
                                                WriteBookmarks_Impl( sName, nRefTextPos, nLastAttrStart );
                                                bHaveCaptionOnlyBkm = true;
                                            }
                                            break;
                                        }
                                        case REF_ONLYSEQNO:
                                        {
                                            sName += "_number_only";
                                            if(!bHaveNumberOnlyBkm)
                                            {
                                                WriteBookmarks_Impl( sName, pHt->GetStart(), pHt->GetStart() );
                                                bHaveNumberOnlyBkm = true;
                                            }
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return;
                }
            }
        }
    }
}

void DocxAttributeOutput::WritePendingPlaceholder()
{
    if( pendingPlaceholder == nullptr )
@@ -7480,12 +7313,6 @@ void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts,
    rEnds.clear();
}

void DocxAttributeOutput::WriteBookmarks_Impl( const OUString& rName, sal_Int32 nWithStartPos, sal_Int32 nWithEndPos )
{
    m_aBookmarksWithPosStart.insert(std::pair<sal_Int32, OUString>(nWithStartPos, rName));
    m_aBookmarksWithPosEnd.insert(std::pair<sal_Int32, OUString>(nWithEndPos, rName));
}

void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
        std::vector< OUString >& rEnds )
{
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx
index 76bfc1a..8a4a565 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -163,10 +163,10 @@ public:
    virtual void EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted) override;

    /// Start of the text run.
    virtual void StartRun( const SwRedlineData* pRedlineData, bool bSingleEmptyRun = false ) override;
    virtual void StartRun( const SwRedlineData* pRedlineData, sal_Int32 nPos, bool bSingleEmptyRun = false ) override;

    /// End of the text run.
    virtual void EndRun(const SwTextNode* pNode, sal_Int32 nPos) override;
    virtual void EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool bLastRun = false) override;

    /// Called before we start outputting the attributes.
    virtual void StartRunProperties() override;
@@ -370,7 +370,6 @@ public:
    void WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark );

    void WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds );
    void WriteBookmarks_Impl( const OUString& rName, sal_Int32 nWithStartPos, sal_Int32 nWithEndPos );
    void WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds );
    void PushRelIdCache();
    void PopRelIdCache();
@@ -685,6 +684,8 @@ protected:

    virtual bool AnalyzeURL( const OUString& rURL, const OUString& rTarget, OUString* pLinkURL, OUString* pMark ) override;

    virtual void WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos ) override;

    /// Reference to the export, where to get the data from
    DocxExport &m_rExport;

@@ -700,8 +701,8 @@ private:
    void DoWriteBookmarkTagEnd(const OUString & bookmarkName);
    void DoWriteBookmarksStart();
    void DoWriteBookmarksEnd();
    void DoWriteBookmarkStartIfExist(sal_Int32 nPos);
    void DoWriteBookmarkEndIfExist(sal_Int32 nPos);
    void DoWriteBookmarkStartIfExist(sal_Int32 nRunPos);
    void DoWriteBookmarkEndIfExist(sal_Int32 nRunPos);

    void DoWritePermissionTagStart(const OUString & permission);
    void DoWritePermissionTagEnd(const OUString & permission);
@@ -733,7 +734,6 @@ private:
    void CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun );
    void EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos );
    void DoWriteFieldRunProperties( const SwTextNode* pNode, sal_Int32 nPos, bool bWriteCombChars = false );
    virtual void GenerateBookmarksForSequenceField(const SwTextNode& rNode, SwWW8AttrIter& rAttrIter) override;

    static void AddToAttrList( rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nAttrName, const sal_Char* sAttrValue );
    static void AddToAttrList( rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nArgs, ... );
@@ -791,9 +791,9 @@ private:
    std::vector<OUString> m_rBookmarksStart;
    std::vector<OUString> m_rBookmarksEnd;

    /// Bookmarks with position to output
    std::multimap<sal_Int32, OUString> m_aBookmarksWithPosStart;
    std::multimap<sal_Int32, OUString> m_aBookmarksWithPosEnd;
    /// Bookmarks of the current paragraph
    std::multimap<sal_Int32, OUString> m_aBookmarksOfParagraphStart;
    std::multimap<sal_Int32, OUString> m_aBookmarksOfParagraphEnd;

    /// Permissions to output
    std::vector<OUString> m_rPermissionsStart;
diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx
index fddce24..cc9b518 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -1490,7 +1490,7 @@ void DocxExport::WriteOutliner(const OutlinerParaObject& rParaObj, sal_uInt8 nTy
        sal_Int32 nAktPos = 0;
        const sal_Int32 nEnd = aStr.getLength();
        do {
            AttrOutput().StartRun( nullptr );
            AttrOutput().StartRun( nullptr, 0 );
            const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);
            rtl_TextEncoding eNextChrSet = aAttrIter.GetNextCharSet();

diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx
index ad19d27..f0af3cb 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.cxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.cxx
@@ -380,7 +380,7 @@ void RtfAttributeOutput::EndParagraphProperties(const SfxItemSet& /*rParagraphMa
    m_rExport.Strm().WriteCharPtr(m_aStyles.makeStringAndClear().getStr());
}

void RtfAttributeOutput::StartRun(const SwRedlineData* pRedlineData, bool bSingleEmptyRun)
void RtfAttributeOutput::StartRun(const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool bSingleEmptyRun)
{
    SAL_INFO("sw.rtf", OSL_THIS_FUNC << ", bSingleEmptyRun: " << bSingleEmptyRun);

@@ -395,7 +395,7 @@ void RtfAttributeOutput::StartRun(const SwRedlineData* pRedlineData, bool bSingl
    OSL_ENSURE(m_aRunText.getLength() == 0, "m_aRunText is not empty");
}

void RtfAttributeOutput::EndRun(const SwTextNode* /*pNode*/, sal_Int32 /*nPos*/)
void RtfAttributeOutput::EndRun(const SwTextNode* /*pNode*/, sal_Int32 /*nPos*/, bool /*bLastRun*/)
{
    m_aRun->append(SAL_NEWLINE_STRING);
    m_aRun.appendAndClear(m_aRunText);
diff --git a/sw/source/filter/ww8/rtfattributeoutput.hxx b/sw/source/filter/ww8/rtfattributeoutput.hxx
index 0f25754..9b73bf4 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.hxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.hxx
@@ -63,10 +63,10 @@ public:
    void EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted) override;

    /// Start of the text run.
    void StartRun(const SwRedlineData* pRedlineData, bool bSingleEmptyRun = false) override;
    void StartRun(const SwRedlineData* pRedlineData, sal_Int32 nPos, bool bSingleEmptyRun = false) override;

    /// End of the text run.
    void EndRun(const SwTextNode* pNode, sal_Int32 nPos) override;
    void EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool bLastRun = false) override;

    /// Called before we start outputting the attributes.
    void StartRunProperties() override;
@@ -218,7 +218,7 @@ public:
    void WriteBookmarks_Impl(std::vector< OUString >& rStarts, std::vector< OUString >& rEnds);
    void WriteAnnotationMarks_Impl(std::vector< OUString >& rStarts, std::vector< OUString >& rEnds);
    void WriteHeaderFooter_Impl(const SwFrameFormat& rFormat, bool bHeader, const sal_Char* pStr, bool bTitlepg);
    void GenerateBookmarksForSequenceField(const SwTextNode& /*rNode*/, SwWW8AttrIter& /*rAttrIter*/) override {};
    void WriteBookmarkInActParagraph( const OUString& /*rName*/, sal_Int32 /*nFirstRunPos*/, sal_Int32 /*nLastRunPos*/ ) override {};

protected:
    /// Output frames - the implementation.
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx
index 9925dfb..570057a 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -990,6 +990,12 @@ bool WW8AttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarg
    return bBookMarkOnly;
}

void WW8AttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
{
    m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
    m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
}

bool WW8AttributeOutput::StartURL( const OUString &rUrl, const OUString &rTarget )
{
    INetURLObject aURL( rUrl );
@@ -2181,8 +2187,7 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
    }

    // Call this before write out fields and runs
    if(GetExportFormat() == ExportFormat::DOCX)
        AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);
    AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);

    const OUString& aStr( rNode.GetText() );

@@ -2217,20 +2222,20 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode )

        // Is this the only run in this paragraph and it's empty?
        bool bSingleEmptyRun = nAktPos == 0 && nNextAttr == 0;
        AttrOutput().StartRun( pRedlineData, bSingleEmptyRun );
        AttrOutput().StartRun( pRedlineData, nAktPos, bSingleEmptyRun );

        if( nNextAttr > nEnd )
            nNextAttr = nEnd;

        if( m_nTextTyp == TXT_FTN || m_nTextTyp == TXT_EDN )
        {
            if( AttrOutput().FootnoteEndnoteRefTag() )
            {
                AttrOutput().EndRun( &rNode, nAktPos );
                AttrOutput().StartRun( pRedlineData, bSingleEmptyRun );
                AttrOutput().EndRun( &rNode, nAktPos, nNextAttr == nEnd );
                AttrOutput().StartRun( pRedlineData, nAktPos, bSingleEmptyRun );
            }
        }

        if( nNextAttr > nEnd )
            nNextAttr = nEnd;

        /*
            1) If there is a text node and an overlapping anchor, then write them in two different
            runs and not as part of the same run.
@@ -2497,9 +2502,9 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode )

        if( bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame )
        {
            AttrOutput().EndRun(&rNode, nAktPos);
            AttrOutput().EndRun(&rNode, nAktPos, nNextAttr == nEnd);
            //write the postponed text run
            AttrOutput().StartRun( pRedlineData, bSingleEmptyRun );
            AttrOutput().StartRun( pRedlineData, nAktPos, bSingleEmptyRun );
            AttrOutput().SetAnchorIsLinkedToNode( false );
            AttrOutput().ResetFlyProcessingFlag();
            if (0 != nEnd)
@@ -2509,16 +2514,16 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
                AttrOutput().EndRunProperties( pRedlineData );
            }
            AttrOutput().RunText( aSavedSnippet, eChrSet );
            AttrOutput().EndRun(&rNode, nAktPos);
            AttrOutput().EndRun(&rNode, nAktPos, nNextAttr == nEnd);
        }
        else if( bPostponeWritingText && !aSavedSnippet.isEmpty() )
        {
            //write the postponed text run
            AttrOutput().RunText( aSavedSnippet, eChrSet );
            AttrOutput().EndRun(&rNode, nAktPos);
            AttrOutput().EndRun(&rNode, nAktPos, nNextAttr == nEnd);
        }
        else
            AttrOutput().EndRun(&rNode, nAktPos);
            AttrOutput().EndRun(&rNode, nAktPos, nNextAttr == nEnd);

        nAktPos = nNextAttr;
        UpdatePosition( &aAttrIter, nAktPos );
diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx
index dfa54a7..97f4154 100644
--- a/sw/source/filter/ww8/wrtww8.cxx
+++ b/sw/source/filter/ww8/wrtww8.cxx
@@ -1513,6 +1513,12 @@ void WW8Export::AppendBookmark( const OUString& rName )
    m_pBkmks->Append( nSttCP, rName );
}

void WW8Export::AppendBookmarkEndWithCorrection( const OUString& rName )
{
    sal_uLong nEndCP = Fc2Cp( Strm().Tell() );
    m_pBkmks->Append( nEndCP - 1, rName );
}

boost::optional<SvxBrushItem> MSWordExportBase::getBackground()
{
    boost::optional<SvxBrushItem> oRet;
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index 31ccbeb..7195677 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -1040,6 +1040,7 @@ public:

    virtual void AppendBookmarks( const SwTextNode& rNd, sal_Int32 nAktPos, sal_Int32 nLen ) override;
    virtual void AppendBookmark( const OUString& rName ) override;
    void AppendBookmarkEndWithCorrection( const OUString& rName );

    virtual void AppendAnnotationMarks( const SwTextNode& rNd, sal_Int32 nAktPos, sal_Int32 nLen ) override;

diff --git a/sw/source/filter/ww8/ww8atr.cxx b/sw/source/filter/ww8/ww8atr.cxx
index 9bc93b6..cb7f1e7 100644
--- a/sw/source/filter/ww8/ww8atr.cxx
+++ b/sw/source/filter/ww8/ww8atr.cxx
@@ -978,6 +978,10 @@ void WW8AttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTe
            m_rWW8Export.pO->clear();
        }
    }

    // Clear bookmarks of the current paragraph
    m_aBookmarksOfParagraphStart.clear();
    m_aBookmarksOfParagraphEnd.clear();
}

void WW8AttributeOutput::StartRunProperties()
@@ -986,7 +990,7 @@ void WW8AttributeOutput::StartRunProperties()
    m_nFieldResults = pCurrentFields ? pCurrentFields->ResultCount() : 0;
}

void WW8AttributeOutput::StartRun( const SwRedlineData* pRedlineData, bool /*bSingleEmptyRun*/ )
void WW8AttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 nPos, bool /*bSingleEmptyRun*/ )
{
    if (pRedlineData)
    {
@@ -1001,6 +1005,13 @@ void WW8AttributeOutput::StartRun( const SwRedlineData* pRedlineData, bool /*bSi
            }
        }
    }

    /// Insert bookmarks started at this run
    auto aRange = m_aBookmarksOfParagraphStart.equal_range(nPos);
    for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
    {
        GetExport().AppendBookmark(BookmarkToWord(aIter->second));
    }
}

void WW8AttributeOutput::OnTOXEnding()
@@ -1008,6 +1019,19 @@ void WW8AttributeOutput::OnTOXEnding()
    mbOnTOXEnding = true;
}

void WW8AttributeOutput::EndRun( const SwTextNode* /*pNode*/, sal_Int32 nPos, bool bLastRun )
{
    /// Insert bookmarks ended after this run
    auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nPos);
    for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
    {
        if(bLastRun)
            GetExport().AppendBookmarkEndWithCorrection(BookmarkToWord(aIter->second));
        else
            GetExport().AppendBookmark(BookmarkToWord(aIter->second));
    }
}

void WW8AttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
{
    Redline( pRedlineData );
@@ -1947,6 +1971,179 @@ static bool lcl_IsHyperlinked(const SwForm& rForm, sal_uInt16 nTOXLvl)
    return bRes;
}

void AttributeOutputBase::GenerateBookmarksForSequenceField(const SwTextNode& rNode, SwWW8AttrIter& rAttrIter)
{
    if(GetExport().GetExportFormat() == MSWordExportBase::ExportFormat::RTF) // Not implemented for RTF
        return;

    if (const SwpHints* pTextAttrs = rNode.GetpSwpHints())
    {
        for( size_t i = 0; i < pTextAttrs->Count(); ++i )
        {
            const SwTextAttr* pHt = pTextAttrs->Get(i);
            if (pHt->GetAttr().Which() == RES_TXTATR_FIELD)
            {
                const SwFormatField& rField = static_cast<const SwFormatField&>(pHt->GetAttr());
                const SwField* pField = rField.GetField();
                // Need to have bookmarks only for sequence fields
                if (pField && pField->GetTyp()->Which() == SwFieldIds::SetExp && pField->GetSubType() == nsSwGetSetExpType::GSE_SEQ)
                {
                    const sal_uInt16 nSeqFieldNumber = static_cast<const SwSetExpField*>(pField)->GetSeqNumber();
                    const OUString sObjectName = static_cast<const SwSetExpFieldType*>(pField->GetTyp())->GetName();
                    const SwFieldTypes* pFieldTypes = GetExport().m_pDoc->getIDocumentFieldsAccess().GetFieldTypes();
                    bool bHaveFullBkm = false;
                    bool bHaveLabelAndNumberBkm = false;
                    bool bHaveCaptionOnlyBkm = false;
                    bool bHaveNumberOnlyBkm = false;
                    bool bRunSplittedAtSep = false;
                    for( auto pFieldType : *pFieldTypes )
                    {
                        if( SwFieldIds::GetRef == pFieldType->Which() )
                        {
                            SwIterator<SwFormatField,SwFieldType> aIter( *pFieldType );
                            for( SwFormatField* pFormatField = aIter.First(); pFormatField; pFormatField = aIter.Next() )
                            {
                                SwGetRefField* pRefField = static_cast<SwGetRefField*>(pFormatField->GetField());
                                // If we have a reference to the current sequence field
                                if(pRefField->GetSeqNo() == nSeqFieldNumber && pRefField->GetSetRefName() == sObjectName)
                                {
                                    // Need to create a seperate run for separator character
                                    SwWW8AttrIter aLocalAttrIter( GetExport(), rNode ); // We need a local iterator having the right number of runs
                                    const OUString aText = rNode.GetText();
                                    const sal_Int32 nCategoryStart = aText.indexOf(pRefField->GetSetRefName());
                                    const sal_Int32 nPosBeforeSeparator = std::max(nCategoryStart, pHt->GetStart());
                                    bool bCategoryFirst = nCategoryStart < pHt->GetStart();
                                    sal_Int32 nSeparatorPos = 0;
                                    if (bCategoryFirst)
                                    {
                                        nSeparatorPos = aLocalAttrIter.WhereNext();
                                        while (nSeparatorPos <= nPosBeforeSeparator)
                                        {
                                            aLocalAttrIter.NextPos();
                                            nSeparatorPos = aLocalAttrIter.WhereNext();
                                        }
                                    }
                                    else
                                    {
                                        nSeparatorPos = nCategoryStart + pRefField->GetSetRefName().getLength();
                                    }
                                    sal_Int32 nRefTextPos = 0;
                                    if(nSeparatorPos < aText.getLength())
                                    {
                                        nRefTextPos = SwGetExpField::GetReferenceTextPos(pHt->GetFormatField(), *GetExport().m_pDoc, nSeparatorPos);
                                        if(nRefTextPos != nSeparatorPos)
                                        {
                                            if(!bRunSplittedAtSep)
                                            {
                                                if(!bCategoryFirst)
                                                    rAttrIter.SplitRun(nSeparatorPos);
                                                rAttrIter.SplitRun(nRefTextPos);
                                                bRunSplittedAtSep = true;
                                            }
                                            if(!bCategoryFirst)
                                                aLocalAttrIter.SplitRun(nSeparatorPos);
                                            aLocalAttrIter.SplitRun(nRefTextPos);
                                        }
                                        else if (bCategoryFirst)
                                        {
                                            if(!bRunSplittedAtSep)
                                            {
                                                rAttrIter.SplitRun(nSeparatorPos);
                                                bRunSplittedAtSep = true;
                                            }
                                            aLocalAttrIter.SplitRun(nSeparatorPos);
                                        }
                                    }
                                    // Generate bookmarks on the right position
                                    OUString sName("Ref_" + pRefField->GetSetRefName());
                                    sName += OUString::number(pRefField->GetSeqNo());
                                    switch (pRefField->GetFormat())
                                    {
                                        case REF_PAGE:
                                        case REF_PAGE_PGDESC:
                                        case REF_CONTENT:
                                        case REF_UPDOWN:
                                            sName += "_full";
                                            if(!bHaveFullBkm)
                                            {
                                                sal_Int32 nLastAttrStart = 0;
                                                sal_Int32 nActAttr = aLocalAttrIter.WhereNext();
                                                while (nActAttr < rNode.GetText().getLength())
                                                {
                                                    nLastAttrStart = nActAttr;
                                                    aLocalAttrIter.NextPos();
                                                    nActAttr = aLocalAttrIter.WhereNext();
                                                }
                                                WriteBookmarkInActParagraph( sName, std::min(nCategoryStart, pHt->GetStart()), nLastAttrStart );
                                                bHaveFullBkm = true;
                                            }
                                            break;
                                        case REF_ONLYNUMBER:
                                        {
                                            sName += "_label_and_number";
                                            if(!bHaveLabelAndNumberBkm)
                                            {
                                                if(bCategoryFirst)
                                                    WriteBookmarkInActParagraph( sName, std::min(nCategoryStart, pHt->GetStart()), std::max(nCategoryStart, pHt->GetStart()) );
                                                else
                                                {
                                                    // Find the last run which contains category text
                                                    SwWW8AttrIter aLocalAttrIter2( GetExport(), rNode );
                                                    sal_Int32 nCatLastRun = 0;
                                                    sal_Int32 nNextAttr = aLocalAttrIter2.WhereNext();
                                                    while (nNextAttr < nSeparatorPos)
                                                    {
                                                        nCatLastRun = nNextAttr;
                                                        aLocalAttrIter2.NextPos();
                                                        nNextAttr = aLocalAttrIter2.WhereNext();
                                                    }
                                                    WriteBookmarkInActParagraph( sName, pHt->GetStart(), nCatLastRun );
                                                }
                                                bHaveLabelAndNumberBkm = true;
                                            }
                                            break;
                                        }
                                        case REF_ONLYCAPTION:
                                        {
                                            sName += "_caption_only";
                                            if(!bHaveCaptionOnlyBkm)
                                            {
                                                // Find last run
                                                sal_Int32 nLastAttrStart = 0;
                                                sal_Int32 nActAttr = aLocalAttrIter.WhereNext();
                                                while (nActAttr < rNode.GetText().getLength())
                                                {
                                                    nLastAttrStart = nActAttr;
                                                    aLocalAttrIter.NextPos();
                                                    nActAttr = aLocalAttrIter.WhereNext();
                                                }
                                                WriteBookmarkInActParagraph( sName, nRefTextPos, nLastAttrStart );
                                                bHaveCaptionOnlyBkm = true;
                                            }
                                            break;
                                        }
                                        case REF_ONLYSEQNO:
                                        {
                                            sName += "_number_only";
                                            if(!bHaveNumberOnlyBkm)
                                            {
                                                WriteBookmarkInActParagraph( sName, pHt->GetStart(), pHt->GetStart() );
                                                bHaveNumberOnlyBkm = true;
                                            }
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return;
                }
            }
        }
    }
}

void AttributeOutputBase::StartTOX( const SwSection& rSect )
{
    if ( const SwTOXBase* pTOX = rSect.GetTOXBase() )
@@ -2800,8 +2997,8 @@ void AttributeOutputBase::TextField( const SwFormatField& rField )
                    break;
                case REF_SEQUENCEFLD:
                {
                    // Have this only for DOCX format by now
                    if(!(GetExport().GetExportFormat() == MSWordExportBase::ExportFormat::DOCX))
                    // Not implemented for RTF
                    if(!(GetExport().GetExportFormat() != MSWordExportBase::ExportFormat::RTF))
                        break;

                    switch (pField->GetFormat())
diff --git a/sw/source/filter/ww8/ww8attributeoutput.hxx b/sw/source/filter/ww8/ww8attributeoutput.hxx
index d814fff..cf47f53 100644
--- a/sw/source/filter/ww8/ww8attributeoutput.hxx
+++ b/sw/source/filter/ww8/ww8attributeoutput.hxx
@@ -49,14 +49,14 @@ public:

    /// Start of the text run.
    ///
    virtual void StartRun( const SwRedlineData* pRedlineData, bool bSingleEmptyRun = false ) override;
    virtual void StartRun( const SwRedlineData* pRedlineData, sal_Int32 nPos, bool bSingleEmptyRun = false ) override;

    virtual void OnTOXEnding() override;

    /// End of the text run.
    ///
    /// No-op for binary filters.
    virtual void EndRun(const SwTextNode* , sal_Int32 ) override {}
    virtual void EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool bLastRun = false) override;

    /// Before we start outputting the attributes.
    virtual void StartRunProperties() override;
@@ -84,8 +84,6 @@ public:

    virtual void FieldVanish( const OUString& rText, ww::eField eType ) override;

    virtual void GenerateBookmarksForSequenceField(const SwTextNode& /*rNode*/, SwWW8AttrIter& /*rAttrIter*/) override {};

    /// Output redlining.
    virtual void Redline( const SwRedlineData* pRedline ) override;

@@ -432,6 +430,8 @@ protected:

    virtual bool AnalyzeURL( const OUString& rURL, const OUString& rTarget, OUString* pLinkURL, OUString* pMark ) override;

    virtual void WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos ) override;

    /// Reference to the export, where to get the data from
    WW8Export &m_rWW8Export;

@@ -460,6 +460,10 @@ protected:

    bool mbOnTOXEnding;

    /// Bookmarks of the current paragraph
    std::multimap<sal_Int32, OUString> m_aBookmarksOfParagraphStart;
    std::multimap<sal_Int32, OUString> m_aBookmarksOfParagraphEnd;

public:
    explicit WW8AttributeOutput( WW8Export &rWW8Export )
        : AttributeOutputBase()