oox/source/export/vmlexport.cxx                                   |    5 -
 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx                        |   25 
++++++--
 sw/qa/extras/ooxmlexport/ooxmlexport5.cxx                         |    2 
 sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx                     |    6 +-
 sw/source/filter/ww8/attributeoutputbase.hxx                      |    2 
 sw/source/filter/ww8/docxattributeoutput.cxx                      |   16 ++++-
 sw/source/filter/ww8/docxattributeoutput.hxx                      |    2 
 sw/source/filter/ww8/wrtw8nds.cxx                                 |    2 
 writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx                |   30 
++++++++++
 writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx |binary
 writerfilter/source/dmapper/DomainMapper.cxx                      |   20 ++++++
 writerfilter/source/dmapper/DomainMapper_Impl.cxx                 |    5 +
 writerfilter/source/dmapper/SdtHelper.cxx                         |    1 
 writerfilter/source/dmapper/SdtHelper.hxx                         |    4 +
 14 files changed, 100 insertions(+), 20 deletions(-)

New commits:
commit 9700c1b2170ad04453a361ed5647937833ac3c18
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Jul 25 09:13:03 2022 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Jul 25 09:51:22 2022 +0200

    sw content controls, plain text: add DOCX import
    
    - the core of this is the writerfilter/ change to call PopSdt() for
      SdtControlType::plainText, which maps inline plain text SDTs to Writer 
content
      controls, not to input fields
    
    - disable the grab-bag in this case, otherwise we would run duplicated 
<w:sdt>
      elements on export
    
    - fix CppunitTest_sw_ooxmlexport7's testSdtAndShapeOverlapping by postponing
      the SDT start in DocxAttributeOutput::WriteContentControlStart() in case a
      shape is anchored at the same position as the SDT start: if the shape 
should
      start inside the content control, then it should be anchored after the 
dummy
      character
    
    - reduce the debug output in VMLExport::Commit(), which could write control
      characters to the terminal on test failure, potentially breaking it 
(requiring
      a 'reset' to recover)
    
    - fix CppunitTest_sw_ooxmlexport5's testSdt2Run: now we merge two runs 
inside a
      plain text content control into a single one, and there is no problem with
      that, so adapt the test instead
    
    - fix CppunitTest_sw_ooxmlexport17's testTdf148361: plain text inline SDT is
      now a content control, not a field
    
    - fix CppunitTest_sw_ooxmlfieldexport's testfdo82492: explicitly assert that
      there is 1 text run inside the SDT and there is a shape after it 
(outside).
      Also extend DocxAttributeOutput::EndContentControl(), so it ends the 
content
      control at the correct, earlier position in case it's followed by an 
as-char
      shape
    
    - fix CppunitTest_sw_ooxmlfieldexport's testfdo82123: again assert that the 
SDT
      has 1 run with text, and there is a drawing after the SDT
    
    - fix CppunitTest_sw_ooxmlfieldexport's testTdf104823: this revealed that 
some
      more complex logic is needed to support data bindings, so exclude
      text-with-databinding from the scope of this commit and continue to map 
those
      to input fields for now
    
    - fix CppunitTest_sw_ooxmlfieldexport's testFdo81945: this had a similar
      problem as as-char shapes, but this time a new SDT is starting right 
after a
      previous SDT. Adapt DocxAttributeOutput::EndContentControl() accordingly,
      though perhaps this should be generalized later, so we always close SDTs 
in the
      previous run, unless this is the last run, or something similar
    
    Change-Id: Ifaf581be884a683de6c8b932008a03ba43734b75
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137399
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/oox/source/export/vmlexport.cxx b/oox/source/export/vmlexport.cxx
index 47a196908a4d..1bfc59ee13db 100644
--- a/oox/source/export/vmlexport.cxx
+++ b/oox/source/export/vmlexport.cxx
@@ -1008,12 +1008,9 @@ void VMLExport::Commit( EscherPropertyContainer& rProps, 
const tools::Rectangle&
             default:
 #if OSL_DEBUG_LEVEL > 0
                 const size_t opt_nProp_size(opt.nProp.size());
-                const sal_uInt8 opt_nProp_empty(0);
                 SAL_WARN( "oox.vml", "TODO VMLExport::Commit(), unimplemented 
id: " << nId
                         << ", value: " << opt.nPropValue
-                        << ", data: [" << opt_nProp_size << ", "
-                        << (0 == opt_nProp_size ? &opt_nProp_empty : 
opt.nProp.data())
-                        << "]");
+                        << ", data: [" << opt_nProp_size << "]");
                 if ( opt.nProp.size() )
                 {
                     const sal_uInt8 *pIt = opt.nProp.data();
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
index f139837b822a..ee5b0dd19b14 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx
@@ -676,15 +676,26 @@ DECLARE_OOXMLEXPORT_TEST(testTdf123642_BookmarkAtDocEnd, 
"tdf123642.docx")
 
 DECLARE_OOXMLEXPORT_TEST(testTdf148361, "tdf148361.docx")
 {
-    // Refresh fields and ensure cross-reference to numbered para is okay
-    uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, 
uno::UNO_QUERY);
-    uno::Reference<container::XEnumerationAccess> 
xFieldsAccess(xTextFieldsSupplier->getTextFields());
+    if (mbExported)
+    {
+        // Block SDT is turned into run SDT on export, so the next import will 
have this as content
+        // control, not as a field.
+        OUString aActual = getParagraph(1)->getString();
+        // This was "itadmin".
+        CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), aActual);
+    }
+    else
+    {
+        // Refresh fields and ensure cross-reference to numbered para is okay
+        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());
+        uno::Reference<container::XEnumeration> 
xFields(xFieldsAccess->createEnumeration());
+        CPPUNIT_ASSERT(xFields->hasMoreElements());
 
-    uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
-    CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
+        uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), 
uno::UNO_QUERY);
+        CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), 
xTextField1->getPresentation(false));
+    }
 
     OUString aActual = getParagraph(2)->getString();
     // This was "itadmin".
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
index cd405b196cba..d34e5ce957f0 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx
@@ -1095,7 +1095,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdt2Run)
     xmlDocUniquePtr pXmlDoc = parseExport();
 
     // The problem was that <w:sdt> was closed after "first", not after 
"second", so the second assert failed.
-    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r", 1);
+    assertXPathContent(pXmlDoc, 
"/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r/w:t", "firstsecond");
     // Make sure the third portion is still outside <w:sdt>.
     assertXPathContent(pXmlDoc, "/w:document/w:body/w:p[1]/w:r/w:t", "third");
 }
diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
index d5fd1691c2a3..43a7c2a67ce4 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx
@@ -587,7 +587,8 @@ CPPUNIT_TEST_FIXTURE(Test, testfdo82123)
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
 
     // make sure there is only one run inside first SDT after RT as in the 
Original file.
-    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:sdt[1]/w:sdtContent/w:r",1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:sdt[1]/w:sdtContent/w:r/w:t", 1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:r/w:drawing", 1);
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testSdtBeforeField)
@@ -612,7 +613,8 @@ CPPUNIT_TEST_FIXTURE(Test, testfdo82492)
     xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
 
     // make sure there is only one run inside first SDT after RT as in the 
Original file.
-    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt[1]/w:sdtContent/w:r",1);
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:p/w:sdt[1]/w:sdtContent/w:r/w:t", 1);
+    assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:r/mc:AlternateContent", 1);
 }
 
 CPPUNIT_TEST_FIXTURE(Test, testSdtHeader)
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx 
b/sw/source/filter/ww8/attributeoutputbase.hxx
index a9331c9b628b..35ac50057786 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -373,7 +373,7 @@ public:
     virtual void StartContentControl(const SwFormatContentControl& 
/*rFormatContentControl*/) {}
 
     /// Output content control end.
-    virtual void EndContentControl() {}
+    virtual void EndContentControl( const SwTextNode& /*rNode*/, sal_Int32 
/*nPos*/ ) {}
 
 protected:
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index f525aadf460f..9bf73a95ff9a 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -368,9 +368,16 @@ void DocxAttributeOutput::StartContentControl(const 
SwFormatContentControl& rFor
     m_pContentControl = rFormatContentControl.GetContentControl();
 }
 
-void DocxAttributeOutput::EndContentControl()
+void DocxAttributeOutput::EndContentControl(const SwTextNode& rNode, sal_Int32 
nPos)
 {
-    ++m_nCloseContentControlInThisRun;
+    if (rNode.GetTextAttrForCharAt(nPos, RES_TXTATR_FLYCNT) || 
rNode.GetTextAttrForCharAt(nPos, RES_TXTATR_CONTENTCONTROL))
+    {
+        ++m_nCloseContentControlInPreviousRun;
+    }
+    else
+    {
+        ++m_nCloseContentControlInThisRun;
+    }
 }
 
 static void checkAndWriteFloatingTables(DocxAttributeOutput& 
rDocxAttributeOutput)
@@ -2331,6 +2338,11 @@ void DocxAttributeOutput::WriteContentControlStart()
         return;
     }
 
+    if (m_bAnchorLinkedToNode)
+    {
+        return;
+    }
+
     m_pSerializer->startElementNS(XML_w, XML_sdt);
     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
     if (!m_pContentControl->GetPlaceholderDocPart().isEmpty())
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index 877e8ded562a..c25c4ad1225a 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -408,7 +408,7 @@ public:
     void StartContentControl(const SwFormatContentControl& 
rFormatContentControl) override;
 
     /// See AttributeOutputBase::EndContentControl().
-    void EndContentControl() override;
+    void EndContentControl( const SwTextNode& rNode, sal_Int32 nPos ) override;
 
 private:
     /// Initialize the structures where we are going to collect some of the 
paragraph properties.
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index 7bd841d727b5..4b42fc63e2e3 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -1407,7 +1407,7 @@ int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& 
rNode, sal_Int32 nPos)
                     pEnd = pHt->End();
                     if (nPos == *pEnd && nPos != pHt->GetStart())
                     {
-                        m_rExport.AttrOutput().EndContentControl();
+                        m_rExport.AttrOutput().EndContentControl(rNode, nPos);
                         --nRet;
                     }
                     break;
diff --git a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx 
b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
index b2e7f1058f88..6b568619785e 100644
--- a/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
+++ b/writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
@@ -88,6 +88,36 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
     CPPUNIT_ASSERT_EQUAL(24.f, fCharheight);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testSdtRunPlainText)
+{
+    // Given a document with a plain text inline/run SDT:
+    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + 
"sdt-run-plain-text.docx";
+
+    // When loading the document:
+    getComponent() = loadFromDesktop(aURL);
+
+    // Then make sure that the text inside the SDT is not rich:
+    uno::Reference<text::XTextDocument> xTextDocument(getComponent(), 
uno::UNO_QUERY);
+    uno::Reference<container::XEnumerationAccess> 
xParaEnumAccess(xTextDocument->getText(),
+                                                                  
uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xParaEnum = 
xParaEnumAccess->createEnumeration();
+    uno::Reference<container::XEnumerationAccess> 
xPara(xParaEnum->nextElement(), uno::UNO_QUERY);
+    uno::Reference<container::XEnumeration> xPortionEnum = 
xPara->createEnumeration();
+    uno::Reference<beans::XPropertySet> xPortion(xPortionEnum->nextElement(), 
uno::UNO_QUERY);
+    OUString aTextPortionType;
+    xPortion->getPropertyValue("TextPortionType") >>= aTextPortionType;
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: ContentControl
+    // - Actual  : TextField
+    // i.e. the SDT was imported as a text field, not as a content control.
+    CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
+    uno::Reference<beans::XPropertySet> xContentControl;
+    xPortion->getPropertyValue("ContentControl") >>= xContentControl;
+    bool bPlainText{};
+    xContentControl->getPropertyValue("PlainText") >>= bPlainText;
+    CPPUNIT_ASSERT(bPlainText);
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testSdtRunCheckbox)
 {
     // Given a document with a checkbox inline/run SDT:
diff --git a/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx
new file mode 100644
index 000000000000..127d81fd966b
Binary files /dev/null and 
b/writerfilter/qa/cppunittests/dmapper/data/sdt-run-plain-text.docx differ
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx 
b/writerfilter/source/dmapper/DomainMapper.cxx
index c3742cdbaa47..826404d640be 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -1067,6 +1067,7 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
         break;
         case NS_ooxml::LN_CT_SdtBlock_sdtContent:
         case NS_ooxml::LN_CT_SdtRun_sdtContent:
+            m_pImpl->m_pSdtHelper->SetSdtType(nName);
             if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::unknown)
             {
                 // Still not determined content type? and it is even not 
unsupported? Then it is plain text field
@@ -1107,6 +1108,15 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
                     default:
                         break;
                 }
+
+                if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText)
+                {
+                    // The plain text && data binding case needs more work 
before it can be enabled.
+                    if 
(m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
+                    {
+                        m_pImpl->PopSdt();
+                    }
+                }
             }
 
             m_pImpl->SetSdt(false);
@@ -2754,6 +2764,14 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const 
PropertyMapPtr& rContext )
     case NS_ooxml::LN_CT_SdtPr_text:
     {
         m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText);
+        if (m_pImpl->m_pSdtHelper->GetSdtType() == 
NS_ooxml::LN_CT_SdtRun_sdtContent)
+        {
+            if (m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
+            {
+                m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear();
+                break;
+            }
+        }
         enableInteropGrabBag("ooxml:CT_SdtPr_text");
         writerfilter::Reference<Properties>::Pointer_t pProperties = 
rSprm.getProps();
         if (pProperties)
@@ -3753,7 +3771,7 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, 
size_t len)
             return;
         }
     }
-    else if (m_pImpl->m_pSdtHelper->getControlType() == 
SdtControlType::plainText)
+    else if ((m_pImpl->m_pSdtHelper->GetSdtType() != 
NS_ooxml::LN_CT_SdtRun_sdtContent || 
!m_pImpl->m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty()) && 
m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText)
     {
         m_pImpl->m_pSdtHelper->getSdtTexts().append(sText);
         if (bNewLine)
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx 
b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index f37e7c2d9032..ba66d5c340f7 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -978,6 +978,11 @@ void DomainMapper_Impl::PopSdt()
                                                
uno::Any(m_pSdtHelper->getDate().makeStringAndClear()));
     }
 
+    if (m_pSdtHelper->getControlType() == SdtControlType::plainText)
+    {
+        xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
+    }
+
     xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
     m_pSdtHelper->clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.cxx 
b/writerfilter/source/dmapper/SdtHelper.cxx
index 8f5e809efdec..0301264bbaef 100644
--- a/writerfilter/source/dmapper/SdtHelper.cxx
+++ b/writerfilter/source/dmapper/SdtHelper.cxx
@@ -443,6 +443,7 @@ void SdtHelper::clear()
     m_aDropDownItems.clear();
     m_aDropDownDisplayTexts.clear();
     setControlType(SdtControlType::unknown);
+    m_nSdtType = 0;
     m_sDataBindingPrefixMapping.clear();
     m_sDataBindingXPath.clear();
     m_sDataBindingStoreItemID.clear();
diff --git a/writerfilter/source/dmapper/SdtHelper.hxx 
b/writerfilter/source/dmapper/SdtHelper.hxx
index c817285095e7..441fa927b045 100644
--- a/writerfilter/source/dmapper/SdtHelper.hxx
+++ b/writerfilter/source/dmapper/SdtHelper.hxx
@@ -66,6 +66,7 @@ class SdtHelper final : public virtual SvRefBase
     std::vector<OUString> m_aDropDownDisplayTexts;
     /// Type of sdt control
     SdtControlType m_aControlType;
+    sal_uInt32 m_nSdtType = 0;
     /// Pieces of the default text -- currently used only by the dropdown 
control.
     OUStringBuffer m_aSdtTexts;
     /// Date ISO string contained in the w:date element, used by the date 
control.
@@ -169,6 +170,9 @@ public:
     SdtControlType getControlType() { return m_aControlType; }
     void setControlType(SdtControlType aType) { m_aControlType = aType; }
 
+    void SetSdtType(sal_uInt32 nSdtType) { m_nSdtType = nSdtType; }
+    sal_uInt32 GetSdtType() const { return m_nSdtType; }
+
     /// Create drop-down control from w:sdt's w:dropDownList.
     void createDropDownControl();
     /// Create date control from w:sdt's w:date.

Reply via email to