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:
parent
d5fb8d502e
commit
f2ab1375b2
13 changed files with 208 additions and 4 deletions
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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, \
|
||||
|
|
89
writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
Normal file
89
writerfilter/qa/cppunittests/dmapper/SdtHelper.cxx
Normal 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: */
|
BIN
writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx
Normal file
BIN
writerfilter/qa/cppunittests/dmapper/data/sdt-run-rich-text.docx
Normal file
Binary file not shown.
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -139,6 +139,8 @@ public:
|
|||
virtual void popBiDiEmbedLevel();
|
||||
void startSdt();
|
||||
void endSdt();
|
||||
void startSdtRun();
|
||||
void endSdtRun();
|
||||
|
||||
void startField();
|
||||
void fieldSeparator();
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Loading…
Reference in a new issue