sw content controls: add initial DOCX import

- map inline/run SDTs with unknown type (i.e. rich text) to
  SwContentControl

- decouple block and run SDTs and leave block ones unchanged

- track start position of run SDTs similar to bookmarks, which needs
  different code to SDT at text start vs later

- fix DocxAttributeOutput::RunText() to please
  CppunitTest_sw_ooxmlexport2's testFdo67013, which had an inline SDT in
  footer, not at para end

Change-Id: I59b8b7f3170cf37f1547db07ae0992850e0e3aa8
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133195
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Miklos Vajna 2022-04-20 08:29:35 +02:00
parent d5fb8d502e
commit f2ab1375b2
13 changed files with 208 additions and 4 deletions

View file

@ -321,8 +321,9 @@ DECLARE_OOXMLEXPORT_TEST(testTdf148361, "tdf148361.docx")
uno::Reference<text::XTextField> xTextField1(xFields->nextElement(), uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(OUString("itadmin"), xTextField1->getPresentation(false));
uno::Reference<text::XTextField> xTextField2(xFields->nextElement(), uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(OUString("[Type text]"), xTextField2->getPresentation(false));
OUString aActual = getParagraph(2)->getString();
// This was "itadmin".
CPPUNIT_ASSERT_EQUAL(OUString("[Type text]"), aActual);
}
DECLARE_OOXMLEXPORT_TEST(testTdf142407, "tdf142407.docx")

View file

@ -3273,6 +3273,7 @@ void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCh
if (m_nCloseContentControlInThisRun > 0)
{
++m_nCloseContentControlInPreviousRun;
--m_nCloseContentControlInThisRun;
}
m_bRunTextIsOn = true;
// one text can be split into more <w:t>blah</w:t>'s by line breaks etc.

View file

@ -23,6 +23,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,writerfilter_dmapper, \
writerfilter/qa/cppunittests/dmapper/GraphicImport \
writerfilter/qa/cppunittests/dmapper/TextEffectsHandler \
writerfilter/qa/cppunittests/dmapper/PropertyMap \
writerfilter/qa/cppunittests/dmapper/SdtHelper \
))
$(eval $(call gb_CppunitTest_use_libraries,writerfilter_dmapper, \

View file

@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <test/bootstrapfixture.hxx>
#include <unotest/macros_test.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
using namespace com::sun::star;
namespace
{
/// Tests for writerfilter/source/dmapper/SdtHelper.cxx.
class Test : public test::BootstrapFixture, public unotest::MacrosTest
{
private:
uno::Reference<lang::XComponent> mxComponent;
public:
void setUp() override;
void tearDown() override;
uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
};
void Test::setUp()
{
test::BootstrapFixture::setUp();
mxDesktop.set(frame::Desktop::create(mxComponentContext));
}
void Test::tearDown()
{
if (mxComponent.is())
mxComponent->dispose();
test::BootstrapFixture::tearDown();
}
constexpr OUStringLiteral DATA_DIRECTORY = u"/writerfilter/qa/cppunittests/dmapper/data/";
CPPUNIT_TEST_FIXTURE(Test, testSdtRunRichText)
{
// Given a document with a rich text inline/run SDT:
OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "sdt-run-rich-text.docx";
// When loading the document:
getComponent() = loadFromDesktop(aURL);
// Then make sure that formatting of the text inside the SDT is not lost:
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, and the whole SDT had 12pt font size.
CPPUNIT_ASSERT_EQUAL(OUString("ContentControl"), aTextPortionType);
uno::Reference<text::XTextContent> xContentControl;
xPortion->getPropertyValue("ContentControl") >>= xContentControl;
uno::Reference<text::XTextRange> xContentControlRange(xContentControl, uno::UNO_QUERY);
uno::Reference<text::XText> xText = xContentControlRange->getText();
uno::Reference<container::XEnumerationAccess> xContentEnumAccess(xText, uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xContentEnum = xContentEnumAccess->createEnumeration();
uno::Reference<beans::XPropertySet> xContent(xContentEnum->nextElement(), uno::UNO_QUERY);
float fCharheight{};
xContent->getPropertyValue("CharHeight") >>= fCharheight;
CPPUNIT_ASSERT_EQUAL(12.f, fCharheight);
xContent.set(xContentEnum->nextElement(), uno::UNO_QUERY);
xContent->getPropertyValue("CharHeight") >>= fCharheight;
CPPUNIT_ASSERT_EQUAL(24.f, fCharheight);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -1074,14 +1074,33 @@ void DomainMapper::lcl_attribute(Id nName, Value & val)
}
break;
case NS_ooxml::LN_CT_SdtBlock_sdtContent:
case NS_ooxml::LN_CT_SdtRun_sdtContent:
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
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText);
if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent)
{
m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText);
m_pImpl->PushSdt();
}
}
m_pImpl->SetSdt(true);
break;
case NS_ooxml::LN_CT_SdtBlock_sdtEndContent:
case NS_ooxml::LN_CT_SdtRun_sdtEndContent:
if (nName == NS_ooxml::LN_CT_SdtRun_sdtEndContent)
{
switch (m_pImpl->m_pSdtHelper->getControlType())
{
case SdtControlType::richText:
m_pImpl->PopSdt();
break;
default:
break;
}
}
m_pImpl->SetSdt(false);
// It's not possible to insert the relevant property to the character context here:
@ -2734,6 +2753,11 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
m_pImpl->disableInteropGrabBag();
}
break;
case NS_ooxml::LN_CT_SdtPr_showingPlcHdr:
{
m_pImpl->m_pSdtHelper->SetShowingPlcHdr();
}
break;
case NS_ooxml::LN_CT_SdtPr_dataBinding:
case NS_ooxml::LN_CT_SdtPr_equation:
case NS_ooxml::LN_CT_SdtPr_checkbox:

View file

@ -832,6 +832,60 @@ void DomainMapper_Impl::SetSdt(bool bSdt)
}
}
void DomainMapper_Impl::PushSdt()
{
if (m_aTextAppendStack.empty())
{
return;
}
uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
uno::Reference<text::XTextCursor> xCursor
= xTextAppend->getText()->createTextCursorByRange(xTextAppend->getEnd());
// Offset so the cursor is not adjusted as we import the SDT's content.
bool bStart = !xCursor->goLeft(1, /*bExpand=*/false);
m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()});
}
void DomainMapper_Impl::PopSdt()
{
if (m_xSdtStarts.empty())
{
return;
}
BookmarkInsertPosition aPosition = m_xSdtStarts.top();
m_xSdtStarts.pop();
uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange;
uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd();
uno::Reference<text::XText> xText = xEnd->getText();
uno::Reference<text::XTextCursor> xCursor = xText->createTextCursorByRange(xStart);
if (!xCursor)
{
SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position");
return;
}
if (aPosition.m_bIsStartOfText)
{
xCursor->gotoStart(/*bExpand=*/false);
}
else
{
// Undo the goLeft() in DomainMapper_Impl::PushSdt();
xCursor->goRight(1, /*bExpand=*/false);
}
xCursor->gotoRange(xEnd, /*bExpand=*/true);
uno::Reference<text::XTextContent> xContentControl(
m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
if (m_pSdtHelper->GetShowingPlcHdr())
{
xContentControlProps->setPropertyValue("ShowingPlaceHolder",
uno::makeAny(m_pSdtHelper->GetShowingPlcHdr()));
}
xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
}
void DomainMapper_Impl::PushProperties(ContextType eId)
{

View file

@ -625,6 +625,7 @@ private:
css::uno::Reference<css::text::XTextRange> m_xGlossaryEntryStart;
css::uno::Reference<css::text::XTextRange> m_xSdtEntryStart;
std::stack<BookmarkInsertPosition> m_xSdtStarts;
std::queue< css::uno::Reference< css::text::XTextFrame > > m_xPendingTextBoxFrames;
@ -725,6 +726,8 @@ public:
/// Setter method for m_bSdt.
void SetSdt(bool bSdt);
void PushSdt();
void PopSdt();
/// Getter method for m_bSdt.
bool GetSdt() const { return m_bSdt;}
bool GetParaChanged() const { return m_bParaChanged;}

View file

@ -419,6 +419,10 @@ bool SdtHelper::containedInInteropGrabBag(const OUString& rValueName)
[&rValueName](const beans::PropertyValue& i) { return i.Name == rValueName; });
}
void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = true; }
bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; }
void SdtHelper::clear()
{
m_aDropDownItems.clear();
@ -427,6 +431,7 @@ void SdtHelper::clear()
m_sDataBindingXPath.clear();
m_sDataBindingStoreItemID.clear();
m_aGrabBag.clear();
m_bShowingPlcHdr = false;
}
} // namespace writerfilter::dmapper

View file

@ -42,6 +42,7 @@ enum class SdtControlType
datePicker,
dropDown,
plainText,
richText,
unsupported, // Sdt block is defined, but we still do not support such type of field
unknown
};
@ -93,6 +94,9 @@ class SdtHelper final : public virtual SvRefBase
/// empty sequence from not yet initialized)
bool m_bPropertiesXMLsLoaded;
/// Current contents are placeholder text.
bool m_bShowingPlcHdr = false;
/// Create and append the drawing::XControlShape, containing the various models.
void createControlShape(css::awt::Size aSize,
css::uno::Reference<css::awt::XControlModel> const& xControlModel,
@ -155,6 +159,9 @@ public:
bool isInteropGrabBagEmpty() const;
bool containedInInteropGrabBag(const OUString& rValueName);
sal_Int32 getInteropGrabBagSize() const;
void SetShowingPlcHdr();
bool GetShowingPlcHdr() const;
};
} // namespace writerfilter::dmapper

View file

@ -468,6 +468,22 @@ void OOXMLFastContextHandler::endSdt()
mpStream->props(pProps.get());
}
void OOXMLFastContextHandler::startSdtRun()
{
OOXMLPropertySet::Pointer_t pProps(new OOXMLPropertySet);
OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(1);
pProps->add(NS_ooxml::LN_CT_SdtRun_sdtContent, pVal, OOXMLProperty::ATTRIBUTE);
mpStream->props(pProps.get());
}
void OOXMLFastContextHandler::endSdtRun()
{
OOXMLPropertySet::Pointer_t pProps(new OOXMLPropertySet);
OOXMLValue::Pointer_t pVal = OOXMLIntegerValue::Create(1);
pProps->add(NS_ooxml::LN_CT_SdtRun_sdtEndContent, pVal, OOXMLProperty::ATTRIBUTE);
mpStream->props(pProps.get());
}
void OOXMLFastContextHandler::startSectionGroup()
{
if (isForwardEvents())

View file

@ -139,6 +139,8 @@ public:
virtual void popBiDiEmbedLevel();
void startSdt();
void endSdt();
void startSdtRun();
void endSdtRun();
void startField();
void fieldSeparator();

View file

@ -18315,8 +18315,9 @@
<element name="sdtPr" tokenid="ooxml:CT_SdtRun_sdtPr"/>
<element name="sdtEndPr" tokenid="ooxml:CT_SdtRun_sdtEndPr"/>
<element name="sdtContent" tokenid="ooxml:CT_SdtRun_sdtContent"/>
<action name="start" action="startSdt"/>
<action name="end" action="endSdt"/>
<element name="sdtEndContent" tokenid="ooxml:CT_SdtRun_sdtEndContent"/>
<action name="start" action="startSdtRun"/>
<action name="end" action="endSdtRun"/>
</resource>
<resource name="CT_SdtCell" resource="Stream">
<element name="sdtPr" tokenid="ooxml:CT_SdtCell_sdtPr"/>