OFFICE-2102: ODF export: write <text:s> after non-mark elements too

... and fix the wrong export for <text:meta-field>, which erroneously
did not use the same bIsPrevCharSpace flag as everything else (which was
obviously a bug, as there should be one flag per paragraph).

Interop testing demonstrates that at least Word and Calligra Words
differ in their interpretation of spaces following <draw:frame> and
other shape elements, and <text:note>: if there is a U+0020 space
before the element, LO will not collapse a U+0020 space behind the
element but Word and Calligra Words do collapse it.

The distinction between "mark elements" and "non-mark elements" in the
whitespace collapsing implementation in OOo since the beginning was
never explicitly spelled out in ODF, although listing the
<text:bookmark> etc. elements in ODF 1.1, 5.1.1 was a strong hint.

Fortunately all 3 applications agree that a <text:s> following a
<draw:frame> is consumed as a space, and it is valid to write a <text:s>
in this situation, so just do that to improve interoperability.

Change-Id: I42260c0528db9fe7e87e8dbae5105aeadb83780d
diff --git a/include/xmloff/txtparae.hxx b/include/xmloff/txtparae.hxx
index a93615a..172131a 100644
--- a/include/xmloff/txtparae.hxx
+++ b/include/xmloff/txtparae.hxx
@@ -246,8 +246,7 @@ public:

    void exportTextRangeEnumeration(
        const css::uno::Reference< css::container::XEnumeration > & rRangeEnum,
        bool bAutoStyles, bool bProgress,
        bool bPrvChrIsSpc = true );
        bool bAutoStyles, bool bProgress, bool & rPrevCharIsSpace);

protected:

@@ -276,7 +275,7 @@ protected:
        const css::uno::Reference< css::text::XTextSection > & rBaseSection,
        bool bAutoStyles, bool bProgress, bool bExportParagraph, TextPNS eExtensionNS = TextPNS::ODF );

    bool exportTextContentEnumeration(
    void exportTextContentEnumeration(
        const css::uno::Reference< css::container::XEnumeration > & rContentEnum,
        bool bAutoStyles,
        const css::uno::Reference< css::text::XTextSection > & rBaseSection,
@@ -298,12 +297,12 @@ protected:

    void exportTextField(
        const css::uno::Reference< css::text::XTextRange > & rTextRange,
        bool bAutoStyles, bool bProgress );
        bool bAutoStyles, bool bProgress, bool * pPrevCharIsSpace);

    void exportTextField(
        const css::uno::Reference< css::text::XTextField> & xTextField,
        const bool bAutoStyles, const bool bProgress,
        const bool bRecursive );
        const bool bRecursive, bool * pPrevCharIsSpace);

    void exportAnyTextFrame(
        const css::uno::Reference< css::text::XTextContent > & rTextContent,
@@ -418,7 +417,7 @@ protected:
    /// export a text:meta
    void exportMeta(
        const css::uno::Reference< css::beans::XPropertySet> & i_xPortion,
        bool i_bAutoStyles, bool i_isProgress );
        bool i_bAutoStyles, bool i_isProgress, bool & rPrevCharIsSpace);

public:

diff --git a/sc/source/filter/xml/XMLStylesExportHelper.cxx b/sc/source/filter/xml/XMLStylesExportHelper.cxx
index ab5baa5..e5cd4e9 100644
--- a/sc/source/filter/xml/XMLStylesExportHelper.cxx
+++ b/sc/source/filter/xml/XMLStylesExportHelper.cxx
@@ -319,6 +319,7 @@ void ScMyValidationsContainer::WriteMessage(ScXMLExport& rExport,
            {
                SvXMLElementExport aElemP(rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
                rExport.GetTextParagraphExport()->exportCharacterData(sTemp.makeStringAndClear(), bPrevCharWasSpace);
                bPrevCharWasSpace = true; // reset for start of next paragraph
            }
            else
                sTemp.append(sText[i]);
diff --git a/sw/qa/extras/odfexport/data/whitespace.odt b/sw/qa/extras/odfexport/data/whitespace.odt
new file mode 100644
index 0000000..fe2b3dd
--- /dev/null
+++ b/sw/qa/extras/odfexport/data/whitespace.odt
Binary files differ
diff --git a/sw/qa/extras/odfexport/odfexport.cxx b/sw/qa/extras/odfexport/odfexport.cxx
index 41d7abb..0ed193c 100644
--- a/sw/qa/extras/odfexport/odfexport.cxx
+++ b/sw/qa/extras/odfexport/odfexport.cxx
@@ -862,6 +862,329 @@ DECLARE_ODFEXPORT_TEST(testTextboxRoundedCorners, "textbox-rounded-corners.odt")
        assertXPath(pXmlDoc, "//draw:custom-shape/loext:table", "name", "Table1");
}

// test that import whitespace collapsing is compatible with old docs
DECLARE_ODFEXPORT_TEST(testWhitespace, "whitespace.odt")
{
    uno::Reference<container::XEnumerationAccess> xPara;
    uno::Reference<container::XEnumeration> xPortions;
    uno::Reference<text::XTextRange> xPortion;
    xPara.set(getParagraphOrTable(1), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(2), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    CPPUNIT_ASSERT_EQUAL(OUString("http://example.com/"), getProperty<OUString>(xPortion, "HyperLinkURL"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(3), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Ruby"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(""), xPortion->getString());
    CPPUNIT_ASSERT_EQUAL(OUString("foo"), getProperty<OUString>(xPortion, "RubyText"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Ruby"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(""), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(4), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("InContentMetadata"), getProperty<OUString>(xPortion, "TextPortionType"));
    {
        // what a stupid idea to require recursively enumerating this
        uno::Reference<container::XEnumerationAccess> xMeta(
            getProperty<uno::Reference<text::XTextContent>>(xPortion, "InContentMetadata"), uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xMetaPortions(
            xMeta->createEnumeration(), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xMP(xMetaPortions->nextElement(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xMP, "TextPortionType"));
        CPPUNIT_ASSERT_EQUAL(OUString(" "), xMP->getString());
        CPPUNIT_ASSERT(!xMetaPortions->hasMoreElements());
    }
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(5), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("TextField"), getProperty<OUString>(xPortion, "TextPortionType"));
    {
        // what a stupid idea to require recursively enumerating this
        uno::Reference<container::XEnumerationAccess> xMeta(
            getProperty<uno::Reference<text::XTextContent>>(xPortion, "TextField"), uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xMetaPortions(
            xMeta->createEnumeration(), uno::UNO_QUERY);
        uno::Reference<text::XTextRange> xMP(xMetaPortions->nextElement(), uno::UNO_QUERY);
        CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xMP, "TextPortionType"));
        CPPUNIT_ASSERT_EQUAL(OUString(" "), xMP->getString());
        CPPUNIT_ASSERT(!xMetaPortions->hasMoreElements());
    }
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(7), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Frame"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(8), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Frame"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(9), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Frame"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(10), uno::UNO_QUERY);
    uno::Reference<container::XContentEnumerationAccess> xCEA(xPara, uno::UNO_QUERY);
    uno::Reference<container::XEnumeration> xFrames(
            xCEA->createContentEnumeration("com.sun.star.text.TextContent"));
    xFrames->nextElement(); // one at-paragraph frame
    CPPUNIT_ASSERT(!xFrames->hasMoreElements());
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(11), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Footnote"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(12), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("TextField"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(13), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Annotation"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("AnnotationEnd"), getProperty<OUString>(xPortion, "TextPortionType"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(15), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Bookmark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(16), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Bookmark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Bookmark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(17), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Redline"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Redline"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(18), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Redline"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Redline"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(19), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("ReferenceMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(20), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("ReferenceMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("ReferenceMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(21), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("DocumentIndexMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());

    xPara.set(getParagraphOrTable(22), uno::UNO_QUERY);
    xPortions.set(xPara->createEnumeration());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString("X "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("DocumentIndexMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" "), xPortion->getString());
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("DocumentIndexMark"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT(!getProperty<bool>(xPortion, "IsCollapsed"));
    xPortion.set(xPortions->nextElement(), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(OUString("Text"), getProperty<OUString>(xPortion, "TextPortionType"));
    CPPUNIT_ASSERT_EQUAL(OUString(" X"), xPortion->getString());
    CPPUNIT_ASSERT(!xPortions->hasMoreElements());
}

DECLARE_ODFEXPORT_TEST(testFdo86963, "fdo86963.odt")
{
    // Export of this document failed with beans::UnknownPropertyException.
diff --git a/xmloff/inc/txtflde.hxx b/xmloff/inc/txtflde.hxx
index 732d10b..5f7fd45 100644
--- a/xmloff/inc/txtflde.hxx
+++ b/xmloff/inc/txtflde.hxx
@@ -166,7 +166,7 @@ public:
    /// Export this field and the surrounding span element with the formatting.
    /// To be called for every field in the document body.
    void ExportField(const css::uno::Reference < css::text::XTextField > & rTextField,
                     bool bProgress );
                     bool bProgress, bool & rPrevCharIsSpace);

    /// collect styles (character styles, data styles, ...) for this field
    /// (if appropriate).
@@ -217,7 +217,8 @@ private:
        const css::uno::Reference< css::beans::XPropertySet> & rPropSet,
        const css::uno::Reference< css::beans::XPropertySet> & rRangePropSet,
        enum FieldIdEnum nToken,
        bool bProgress );
        bool bProgress,
        bool & rPrevCharIsSpace);

    /// export an empty element
    void ExportElement(enum ::xmloff::token::XMLTokenEnum eElement, /// element token
@@ -234,7 +235,8 @@ private:

    /// export text:meta-field (RDF metadata)
    void ExportMetaField( const css::uno::Reference< css::beans::XPropertySet> & i_xMeta,
                          bool i_bAutoStyles, bool i_bProgress );
                          bool i_bAutoStyles, bool i_bProgress,
                          bool & rPrevCharIsSpace);

    /// export a boolean attribute
    void ProcessBoolean(
diff --git a/xmloff/source/text/txtflde.cxx b/xmloff/source/text/txtflde.cxx
index 7474292..11bdee3 100644
--- a/xmloff/source/text/txtflde.cxx
+++ b/xmloff/source/text/txtflde.cxx
@@ -833,7 +833,8 @@ void XMLTextFieldExport::ExportFieldAutoStyle(
        // recurse into content (does not export element, so can be done first)
        if (bRecursive)
        {
            ExportMetaField(xPropSet, true, bProgress);
            bool dummy_for_autostyles(true);
            ExportMetaField(xPropSet, true, bProgress, dummy_for_autostyles);
        }
        SAL_FALLTHROUGH;
    case FIELD_ID_DOCINFO_PRINT_TIME:
@@ -955,7 +956,8 @@ void XMLTextFieldExport::ExportFieldAutoStyle(

/// export the given field to XML. Called on second pass through document
void XMLTextFieldExport::ExportField(
    const Reference<XTextField> & rTextField, bool bProgress )
    const Reference<XTextField> & rTextField, bool bProgress,
    bool & rPrevCharIsSpace)
{
    // get property set
    Reference<XPropertySet> xPropSet(rTextField, UNO_QUERY);
@@ -1033,7 +1035,7 @@ void XMLTextFieldExport::ExportField(

        // finally, export the field itself
        ExportFieldHelper( rTextField, xPropSet, xRangePropSet, nToken,
            bProgress );
            bProgress, rPrevCharIsSpace);
    }
}

@@ -1043,7 +1045,8 @@ void XMLTextFieldExport::ExportFieldHelper(
    const Reference<XPropertySet> & rPropSet,
    const Reference<XPropertySet> &,
    enum FieldIdEnum nToken,
    bool bProgress )
    bool bProgress,
    bool & rPrevCharIsSpace)
{
    // get property set info (because some attributes are not support
    // in all implementations)
@@ -1783,7 +1786,7 @@ void XMLTextFieldExport::ExportFieldHelper(

    case FIELD_ID_META:
    {
        ExportMetaField(rPropSet, false, bProgress);
        ExportMetaField(rPropSet, false, bProgress, rPrevCharIsSpace);
        break;
    }

@@ -2305,7 +2308,8 @@ void XMLTextFieldExport::ExportMacro(

void XMLTextFieldExport::ExportMetaField(
    const Reference<XPropertySet> & i_xMeta,
    bool i_bAutoStyles, bool i_bProgress )
    bool i_bAutoStyles, bool i_bProgress,
    bool & rPrevCharIsSpace)
{
    bool doExport(!i_bAutoStyles); // do not export element if autostyles
    // check version >= 1.2
@@ -2340,7 +2344,7 @@ void XMLTextFieldExport::ExportMetaField(

    // recurse to export content
    GetExport().GetTextParagraphExport()->
        exportTextRangeEnumeration( xTextEnum, i_bAutoStyles, i_bProgress );
        exportTextRangeEnumeration(xTextEnum, i_bAutoStyles, i_bProgress, rPrevCharIsSpace);
}

/// export all data-style related attributes
diff --git a/xmloff/source/text/txtparae.cxx b/xmloff/source/text/txtparae.cxx
index c9b6456..a0ae49d 100644
--- a/xmloff/source/text/txtparae.cxx
+++ b/xmloff/source/text/txtparae.cxx
@@ -1484,7 +1484,7 @@ bool XMLTextParagraphExport::collectTextAutoStylesOptimized( bool bIsProgress )
            Any aAny = xTextFieldsEnum->nextElement();
            Reference< XTextField > xTextField = *o3tl::doAccess<Reference<XTextField>>(aAny);
            exportTextField( xTextField, bAutoStyles, bIsProgress,
                !xAutoStylesSupp.is() );
                !xAutoStylesSupp.is(), nullptr );
            try
            {
                Reference < XPropertySet > xSet( xTextField, UNO_QUERY );
@@ -1727,7 +1727,7 @@ void XMLTextParagraphExport::exportText(
        pRedlineExport->ExportStartOrEndRedline( xPropertySet, false );
}

bool XMLTextParagraphExport::exportTextContentEnumeration(
void XMLTextParagraphExport::exportTextContentEnumeration(
        const Reference < XEnumeration > & rContEnum,
        bool bAutoStyles,
        const Reference < XTextSection > & rBaseSection,
@@ -1739,7 +1739,7 @@ bool XMLTextParagraphExport::exportTextContentEnumeration(
    SAL_WARN_IF( !rContEnum.is(), "xmloff", "No enumeration to export!" );
    bool bHasMoreElements = rContEnum->hasMoreElements();
    if( !bHasMoreElements )
        return false;
        return;

    XMLTextNumRuleInfo aPrevNumInfo;
    XMLTextNumRuleInfo aNextNumInfo;
@@ -1889,8 +1889,6 @@ bool XMLTextParagraphExport::exportTextContentEnumeration(
                                    aPrevNumInfo, aNextNumInfo,
                                    bAutoStyles );
    }

    return true;
}

void XMLTextParagraphExport::exportParagraph(
@@ -2128,6 +2126,8 @@ void XMLTextParagraphExport::exportParagraph(
        }
    }

    bool bPrevCharIsSpace(true); // true because whitespace at start is ignored

    if( bAutoStyles )
    {
        if( bHasContentEnum )
@@ -2135,33 +2135,33 @@ void XMLTextParagraphExport::exportParagraph(
                                    xContentEnum, bAutoStyles, xSection,
                                    bIsProgress );
        if ( bHasPortions )
            exportTextRangeEnumeration( xTextEnum, bAutoStyles, bIsProgress );
        {
            exportTextRangeEnumeration(xTextEnum, bAutoStyles, bIsProgress, bPrevCharIsSpace);
        }
    }
    else
    {
        bool bPrevCharIsSpace = true;
        enum XMLTokenEnum eElem =
            0 < nOutlineLevel ? XML_H : XML_P;
        SvXMLElementExport aElem( GetExport(), eExtensionNS == TextPNS::EXTENSION ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_TEXT, eElem,
                                  true, false );
        if( bHasContentEnum )
            bPrevCharIsSpace = !exportTextContentEnumeration(
        {
            exportTextContentEnumeration(
                                    xContentEnum, bAutoStyles, xSection,
                                    bIsProgress );
        exportTextRangeEnumeration( xTextEnum, bAutoStyles, bIsProgress,
                                     bPrevCharIsSpace );
        }
        exportTextRangeEnumeration(xTextEnum, bAutoStyles, bIsProgress, bPrevCharIsSpace);
    }
}

void XMLTextParagraphExport::exportTextRangeEnumeration(
        const Reference < XEnumeration > & rTextEnum,
        bool bAutoStyles, bool bIsProgress,
        bool bPrvChrIsSpc )
        bool & rPrevCharIsSpace)
{
    static const char sFieldMarkName[] = "__FieldMark_";

    bool bPrevCharIsSpace = bPrvChrIsSpc;

    /* This is  used for exporting to strict OpenDocument 1.2, in which case traditional
     * bookmarks are used instead of fieldmarks. */
    FieldmarkType openFieldMark = NONE;
@@ -2180,17 +2180,15 @@ void XMLTextParagraphExport::exportTextRangeEnumeration(
            if( sType.equals(sText))
            {
                exportTextRange( xTxtRange, bAutoStyles,
                                 bPrevCharIsSpace, openFieldMark);
                                 rPrevCharIsSpace, openFieldMark);
            }
            else if( sType.equals(sTextField))
            {
                exportTextField( xTxtRange, bAutoStyles, bIsProgress );
                bPrevCharIsSpace = false;
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else if ( sType == "Annotation" )
            {
                exportTextField( xTxtRange, bAutoStyles, bIsProgress );
                bPrevCharIsSpace = false;
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else if ( sType == "AnnotationEnd" )
            {
@@ -2221,14 +2219,12 @@ void XMLTextParagraphExport::exportTextRangeEnumeration(
                                                    xSection, bIsProgress, true,
                                                     &xPropSet  );

                bPrevCharIsSpace = false;
            }
            else if (sType.equals(sFootnote))
            {
                exportTextFootnote(xPropSet,
                                   xTxtRange->getString(),
                                   bAutoStyles, bIsProgress );
                bPrevCharIsSpace = false;
            }
            else if (sType.equals(sBookmark))
            {
@@ -2259,7 +2255,7 @@ void XMLTextParagraphExport::exportTextRangeEnumeration(
            }
            else if (sType == "InContentMetadata")
            {
                exportMeta(xPropSet, bAutoStyles, bIsProgress);
                exportMeta(xPropSet, bAutoStyles, bIsProgress, rPrevCharIsSpace);
            }
            else if (sType.equals(sTextFieldStart))
            {
@@ -2415,13 +2411,12 @@ void XMLTextParagraphExport::exportTextRangeEnumeration(
            Reference<XServiceInfo> xServiceInfo( xTxtRange, UNO_QUERY );
            if( xServiceInfo->supportsService( sTextFieldService ) )
            {
                exportTextField( xTxtRange, bAutoStyles, bIsProgress );
                bPrevCharIsSpace = false;
                exportTextField(xTxtRange, bAutoStyles, bIsProgress, &rPrevCharIsSpace);
            }
            else
            {
                // no TextPortionType property -> non-Writer app -> text
                exportTextRange( xTxtRange, bAutoStyles, bPrevCharIsSpace, openFieldMark );
                exportTextRange(xTxtRange, bAutoStyles, rPrevCharIsSpace, openFieldMark);
            }
        }
    }
@@ -2438,7 +2433,7 @@ void XMLTextParagraphExport::exportTable(

void XMLTextParagraphExport::exportTextField(
        const Reference < XTextRange > & rTextRange,
        bool bAutoStyles, bool bIsProgress )
        bool bAutoStyles, bool bIsProgress, bool *const pPrevCharIsSpace)
{
    Reference < XPropertySet > xPropSet( rTextRange, UNO_QUERY );
    // non-Writer apps need not support Property TextField, so test first
@@ -2448,7 +2443,7 @@ void XMLTextParagraphExport::exportTextField(
        SAL_WARN_IF( !xTxtFld.is(), "xmloff", "text field missing" );
        if( xTxtFld.is() )
        {
            exportTextField(xTxtFld, bAutoStyles, bIsProgress, true);
            exportTextField(xTxtFld, bAutoStyles, bIsProgress, true, pPrevCharIsSpace);
        }
        else
        {
@@ -2461,7 +2456,7 @@ void XMLTextParagraphExport::exportTextField(
void XMLTextParagraphExport::exportTextField(
        const Reference < XTextField > & xTextField,
        const bool bAutoStyles, const bool bIsProgress,
        const bool bRecursive )
        const bool bRecursive, bool *const pPrevCharIsSpace)
{
    if ( bAutoStyles )
    {
@@ -2470,7 +2465,8 @@ void XMLTextParagraphExport::exportTextField(
    }
    else
    {
        pFieldExport->ExportField( xTextField, bIsProgress );
        assert(pPrevCharIsSpace);
        pFieldExport->ExportField(xTextField, bIsProgress, *pPrevCharIsSpace);
    }
}

@@ -3701,7 +3697,7 @@ void XMLTextParagraphExport::exportRuby(

void XMLTextParagraphExport::exportMeta(
    const Reference<XPropertySet> & i_xPortion,
    bool i_bAutoStyles, bool i_isProgress)
    bool i_bAutoStyles, bool i_isProgress, bool & rPrevCharIsSpace)
{
    bool doExport(!i_bAutoStyles); // do not export element if autostyles
    // check version >= 1.2
@@ -3732,7 +3728,7 @@ void XMLTextParagraphExport::exportMeta(
        XML_NAMESPACE_TEXT, XML_META, false, false );

    // recurse to export content
    exportTextRangeEnumeration( xTextEnum, i_bAutoStyles, i_isProgress );
    exportTextRangeEnumeration(xTextEnum, i_bAutoStyles, i_isProgress, rPrevCharIsSpace);
}

void XMLTextParagraphExport::PreventExportOfControlsInMuteSections(