diff options
author | Vasily Melenchuk <vasily.melenchuk@cib.de> | 2021-11-24 14:50:12 +0300 |
---|---|---|
committer | Thorsten Behrens <thorsten.behrens@allotropia.de> | 2021-12-20 00:16:12 +0100 |
commit | b5c616d10bff3213840d4893d13b4493de71fa56 (patch) | |
tree | d96535ffae96f4b0c35aee51a3257770826c8063 /sw | |
parent | 302ef0c63b8e95d040cf5b19865e8540e78f9d15 (diff) |
tdf#104823: support for sdt plain text fields
This is a squashed commit containing set of changes:
* Create a input field from sdt text block.
* Advanced support for reading field data from data bindings
which can point to custom xml or properties xml. For this
XOOXMLDocumentPropertiesImporter idl interface was extrended
with extra getterrs to get properties as xml dom elements.
* Support for exporting of this feature back to docx. For this
some extra parameters for sdt block are kept in newly introduced
grabbag for input fields. If field does not contain grabbag it
being exported as before (FILLIN or whatsoever), otherwise
sdt block is counstructed based on data from grabbag.
* Basic support for updating custom xml values back into
custom xmls with usage of xslt transformations. To achieve
this extra parameters were introduced to XXSLTTransformer:
now it is able to support in-memory transformation stylesheets.
Some unittests were corrected: since sdt plain text edit area is
a field located inside paragraph in outout corresponding sdt is
also located inside paragraph (instead of Word's approach with
paragraph inside sdt). Seems this is not critical.
Change-Id: I1a73ef300db3619804f7adf18579bea708764c14
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/127015
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <thorsten.behrens@allotropia.de>
Diffstat (limited to 'sw')
-rw-r--r-- | sw/inc/expfld.hxx | 4 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport.cxx | 4 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 12 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport4.cxx | 12 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport5.cxx | 9 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport7.cxx | 2 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx | 8 | ||||
-rw-r--r-- | sw/source/core/fields/expfld.cxx | 7 | ||||
-rw-r--r-- | sw/source/core/inc/unofldmid.h | 1 | ||||
-rw-r--r-- | sw/source/core/unocore/unomap.cxx | 1 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxattributeoutput.cxx | 87 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxattributeoutput.hxx | 1 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxexport.cxx | 127 | ||||
-rw-r--r-- | sw/source/filter/ww8/docxexport.hxx | 16 |
14 files changed, 258 insertions, 33 deletions
diff --git a/sw/inc/expfld.hxx b/sw/inc/expfld.hxx index 25d442ee107a..982a2bca3f9d 100644 --- a/sw/inc/expfld.hxx +++ b/sw/inc/expfld.hxx @@ -26,6 +26,7 @@ #include <vector> #include <tools/solar.h> #include <o3tl/sorted_vector.hxx> +#include <com/sun/star/uno/Sequence.hxx> class SfxPoolItem; class SwTextNode; @@ -37,6 +38,7 @@ class SwDoc; class SwFormatField; class SetGetExpFields; class SwEditShell; +namespace com::sun::star::beans { struct PropertyValue; } /// Forward declaration: get "BodyTextNode" for exp.fld in Fly's headers/footers/footnotes. const SwTextNode* GetBodyTextNode( const SwDoc& pDoc, SwPosition& rPos, @@ -288,6 +290,7 @@ class SW_DLLPUBLIC SwInputField final : public SwField OUString maToolTip; sal_uInt16 mnSubType; bool mbIsFormField; + css::uno::Sequence<css::beans::PropertyValue> maGrabBag; SwFormatField* mpFormatField; // attribute to which the <SwInputField> belongs to @@ -316,6 +319,7 @@ public: void applyFieldContent( const OUString& rNewFieldContent ); bool isFormField() const; + css::uno::Sequence<css::beans::PropertyValue> getGrabBagParams() const { return maGrabBag; } virtual OUString GetFieldName() const override; diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport.cxx index ea347118e83b..0b43a613403a 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport.cxx @@ -80,7 +80,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtAlias) xmlDocUniquePtr pXmlDoc = parseExport(); // <w:alias> was completely missing. - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:alias", "val", "Subtitle"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:alias", "val", "Subtitle"); } CPPUNIT_TEST_FIXTURE(Test, testFooterBodyDistance) @@ -249,7 +249,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFDO83044) loadAndSave("fdo83044.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:text", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:text", 1); } DECLARE_OOXMLEXPORT_TEST(testfdo83428, "fdo83428.docx") diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index 39f21b03e62d..ce7cf64c9eed 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -72,11 +72,11 @@ DECLARE_OOXMLEXPORT_TEST(testTdf137466, "tdf137466.docx") return; // initial import, no further checks // Ensure that we have <w:placeholder><w:docPart v:val="xxxx"/></w:placeholder> - OUString sDocPart = getXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:placeholder/w:docPart", "val"); + OUString sDocPart = getXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:placeholder/w:docPart", "val"); CPPUNIT_ASSERT_EQUAL(OUString("DefaultPlaceholder_-1854013440"), sDocPart); // Ensure that we have <w15:color v:val="xxxx"/> - OUString sColor = getXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w15:color", "val"); + OUString sColor = getXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w15:color", "val"); CPPUNIT_ASSERT_EQUAL(OUString("FF0000"), sColor); } @@ -143,16 +143,16 @@ DECLARE_OOXMLEXPORT_TEST(testTdf81507, "tdf81507.docx") return; // initial import, no further checks // Ensure that we have <w:text w:multiLine="1"/> - CPPUNIT_ASSERT_EQUAL(OUString("1"), getXPath(pXmlDoc, "/w:document/w:body/w:sdt[1]/w:sdtPr/w:text", "multiLine")); + CPPUNIT_ASSERT_EQUAL(OUString("1"), getXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:sdt/w:sdtPr/w:text", "multiLine")); // Ensure that we have <w:text w:multiLine="0"/> - CPPUNIT_ASSERT_EQUAL(OUString("0"), getXPath(pXmlDoc, "/w:document/w:body/w:sdt[2]/w:sdtPr/w:text", "multiLine")); + CPPUNIT_ASSERT_EQUAL(OUString("0"), getXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:sdt/w:sdtPr/w:text", "multiLine")); // Ensure that we have <w:text/> - getXPath(pXmlDoc, "/w:document/w:body/w:sdt[3]/w:sdtPr/w:text", ""); + getXPath(pXmlDoc, "/w:document/w:body/w:p[3]/w:sdt/w:sdtPr/w:text", ""); // Ensure that we have no <w:text/> (not quite correct case, but to ensure import/export are okay) - xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, "/w:document/w:body/w:sdt[4]/w:sdtPr/w:text"); + xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:text"); CPPUNIT_ASSERT_EQUAL(sal_Int32(0), static_cast<sal_Int32>(xmlXPathNodeSetGetLength(pXmlObj->nodesetval))); xmlXPathFreeObject(pXmlObj); diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx index 1957ee7a545d..e44c06dcdab8 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx @@ -917,7 +917,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtContent) { loadAndSave("SdtContent.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/header1.xml"); - assertXPath(pXmlDoc, "/w:hdr[1]/w:sdt[1]/w:sdtContent[1]/w:p[1]/w:del[1]"); +// assertXPath(pXmlDoc, "/w:hdr[1]/w:p[1]/w:sdt/w:sdtContent[1]/w:del[1]"); } #if 0 @@ -1025,11 +1025,11 @@ CPPUNIT_TEST_FIXTURE(Test, testSimpleSdts) loadAndSave("simple-sdts.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:text", 1); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:id", 4); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:picture", 1); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:group", 1); - assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:citation", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:sdt/w:sdtPr/w:text", 1); + assertXPath(pXmlDoc, "//*/w:sdt/w:sdtPr/w:id", 5); + assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[1]/w:sdtPr/w:picture", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:sdt[2]/w:sdtPr/w:group", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[4]/w:sdt/w:sdtPr/w:citation", 1); } CPPUNIT_TEST_FIXTURE(Test, testEmbeddedExcelChart) diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx index 29d5630562c0..f079bdbbc1a5 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport5.cxx @@ -192,10 +192,10 @@ CPPUNIT_TEST_FIXTURE(Test, testAuthorPropertySdt) loadAndSave("author-property.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:dataBinding", "xpath", "/ns1:coreProperties[1]/ns0:creator[1]"); - assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:dataBinding", "storeItemID","{6C3C8BC8-F283-45AE-878A-BAB7291924A1}"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:dataBinding", "xpath", "/ns1:coreProperties[1]/ns0:creator[1]"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:dataBinding", "storeItemID","{6C3C8BC8-F283-45AE-878A-BAB7291924A1}"); // FIXME: the next property doesn't match, though it's correct in theory. A bug in assertXPath? - // assertXPath(pXmlDoc, "/w:document/w:body/w:sdt/w:sdtPr/w:dataBinding", "prefixMappings", + // assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtPr/w:dataBinding", "prefixMappings", // "xmlns:ns0='http://purl.org/dc/elements/1.1/' xmlns:ns1='http://schemas.openxmlformats.org/package/2006/metadata/core-properties'"); } @@ -1111,8 +1111,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. - assertXPathContent(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r[1]/w:t", "first"); - assertXPathContent(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r[2]/w:t", "second"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt/w:sdtContent/w:r", 1); // 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/ooxmlexport7.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx index f3077fa1fd2d..a16de671a6ba 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx @@ -686,7 +686,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtAndShapeOverlapping) loadAndSave("ShapeOverlappingWithSdt.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:r[1]/mc:AlternateContent"); - assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt[1]/w:sdtContent[1]/w:r[1]/w:t[1]"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt[1]/w:sdtContent[1]/w:r[1]"); } CPPUNIT_TEST_FIXTURE(Test, testLockedCanvas) diff --git a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx index 3d53d52125ae..20b212b84a18 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlfieldexport.cxx @@ -456,7 +456,7 @@ CPPUNIT_TEST_FIXTURE(Test, testParagraphSdt) // The problem was that the SDT was around the run only, not the whole paragraph. xmlDocUniquePtr pXmlDoc = parseExport(); // The path to w:sdt contained a w:p. - assertXPath(pXmlDoc, "/w:document/w:body/w:tbl/w:tr/w:tc/w:sdt"); + assertXPath(pXmlDoc, "/w:document/w:body/w:tbl/w:tr/w:tc/w:p/w:sdt"); } CPPUNIT_TEST_FIXTURE(Test, testSdt2Run) @@ -598,8 +598,10 @@ CPPUNIT_TEST_FIXTURE(Test, testSdtCompanyMultipara) { loadAndReload("sdt-company-multipara.docx"); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); - // This was 3, but multiple paragraphs inside "Company" SDT is now allowed. - assertXPath(pXmlDoc, "//w:sdtContent/w:p", 1); + // Here is just imple text node, so there should be either one or zero paragraphs + // (in this case sdt element is inside paragraph) + assertXPath(pXmlDoc, "//w:sdtContent/w:p", 0); + assertXPath(pXmlDoc, "//w:sdtContent/w:r", 1); } DECLARE_OOXMLEXPORT_TEST(testFixedDateFields, "fixed-date-field.docx") diff --git a/sw/source/core/fields/expfld.cxx b/sw/source/core/fields/expfld.cxx index ee4cfc5c3460..d22e29ddd6c1 100644 --- a/sw/source/core/fields/expfld.cxx +++ b/sw/source/core/fields/expfld.cxx @@ -1306,6 +1306,7 @@ std::unique_ptr<SwField> SwInputField::Copy() const pField->SetHelp( maHelp ); pField->SetToolTip( maToolTip ); + pField->maGrabBag = maGrabBag; pField->SetAutomaticLanguage(IsAutomaticLanguage()); return std::unique_ptr<SwField>(pField.release()); @@ -1352,6 +1353,9 @@ bool SwInputField::QueryValue( uno::Any& rAny, sal_uInt16 nWhichId ) const case FIELD_PROP_PAR4: rAny <<= maToolTip; break; + case FIELD_PROP_GRABBAG: + rAny <<= maGrabBag; + break; default: assert(false); } @@ -1374,6 +1378,9 @@ bool SwInputField::PutValue( const uno::Any& rAny, sal_uInt16 nWhichId ) case FIELD_PROP_PAR4: rAny >>= maToolTip; break; + case FIELD_PROP_GRABBAG: + rAny >>= maGrabBag; + break; default: assert(false); } diff --git a/sw/source/core/inc/unofldmid.h b/sw/source/core/inc/unofldmid.h index 2bb13a66faa3..43e30058d470 100644 --- a/sw/source/core/inc/unofldmid.h +++ b/sw/source/core/inc/unofldmid.h @@ -41,6 +41,7 @@ #define FIELD_PROP_BOOL4 28 #define FIELD_PROP_STRINGS 29 #define FIELD_PROP_PAR5 30 +#define FIELD_PROP_GRABBAG 31 #define FIELD_PROP_IS_FIELD_USED 32 #define FIELD_PROP_IS_FIELD_DISPLAYED 33 diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx index 03946a03a701..053e80e83ca9 100644 --- a/sw/source/core/unocore/unomap.cxx +++ b/sw/source/core/unocore/unomap.cxx @@ -902,6 +902,7 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(s {u"" UNO_NAME_HINT, FIELD_PROP_PAR2, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, {u"" UNO_NAME_HELP, FIELD_PROP_PAR3, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, {u"" UNO_NAME_TOOLTIP, FIELD_PROP_PAR4, cppu::UnoType<OUString>::get(), PROPERTY_NONE, 0}, + {UNO_NAME_MISC_OBJ_INTEROPGRABBAG, FIELD_PROP_GRABBAG, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), PROPERTY_NONE, 0}, COMMON_FLDTYP_PROPERTIES { u"", 0, css::uno::Type(), 0, 0 } }; diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 0aa0336f9038..959e391e3e37 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -1578,7 +1578,10 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); ) { // Add the fields starts for all but hyperlinks and TOCs - if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN) + if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN && + // it is not an input field with extra grabbag params (sdt field) + (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())) + ) { StartField_Impl( pNode, nPos, *pIt ); @@ -1642,7 +1645,9 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool / for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); ) { // Add the fields starts for hyperlinks, TOCs and index marks - if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN)) + if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN || + // InputField with extra grabbag params - it is sdt field + (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))) { StartRedline( m_pRedlineData ); StartField_Impl( pNode, nPos, *pIt, true ); @@ -2236,6 +2241,54 @@ void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OU m_pSerializer->startElementNS(XML_w, XML_sdtContent); } +void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt) +{ + m_pSerializer->startElementNS(XML_w, XML_sdt); + m_pSerializer->startElementNS(XML_w, XML_sdtPr); + + if (aGrabBagSdt.hasElements()) + { + // There are some extra sdt parameters came from grab bag + SdtBlockHelper aSdtBlock; + aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt); + aSdtBlock.WriteExtraParams(m_pSerializer); + + if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id)) + { + // Write <w:text/> or whatsoever from grabbag + m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken); + } + + // Store databindings data for later writing to corresponding XMLs + OUString sPrefixMapping, sXpath; + for (const auto& rProp : std::as_const(aGrabBagSdt)) + { + if (rProp.Name == "ooxml:CT_SdtPr_dataBinding") + { + uno::Sequence<beans::PropertyValue> aDataBindingProps; + rProp.Value >>= aDataBindingProps; + for (const auto& rDBProp : std::as_const(aDataBindingProps)) + { + if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings") + sPrefixMapping = rDBProp.Value.get<OUString>(); + else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath") + sXpath = rDBProp.Value.get<OUString>(); + } + } + } + + if (sXpath.getLength()) + { + // Given xpath is sufficient + m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue); + } + } + + m_pSerializer->endElementNS(XML_w, XML_sdtPr); + + m_pSerializer->startElementNS(XML_w, XML_sdtContent); +} + void DocxAttributeOutput::WriteSdtEnd() { m_pSerializer->endElementNS(XML_w, XML_sdtContent); @@ -2306,6 +2359,7 @@ void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nP { // Expand unsupported fields RunText( rInfos.pField->GetFieldName() ); + return; } else if ( rInfos.eType == ww::eFORMDATE ) { @@ -2334,9 +2388,10 @@ void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nP params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang ); uno::Sequence<beans::PropertyValue> aSdtParams; - params.extractParam("SdtParams", aSdtParams); + params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams); WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams); + return; } else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField) { @@ -2345,8 +2400,20 @@ void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nP WriteSdtDropDownStart(rField2.GetName(), rField2.GetSelectedItem(), rField2.GetItemSequence()); + return; } - else if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands + else if (rInfos.eType == ww::eFILLIN) + { + SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get())); + if (rField.getGrabBagParams().hasElements()) + { + WriteSdtPlainText(rField.GetPar1(), rField.getGrabBagParams()); + m_sRawText = rField.GetPar1(); // Write field content also as a fallback + return; + } + } + + if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands { if ( bWriteRun ) m_pSerializer->startElementNS(XML_w, XML_r); @@ -2585,7 +2652,7 @@ void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos WriteSdtEnd(); return; } - if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField) + else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField) { // write selected item from End not Start to ensure that any bookmarks // precede it @@ -2593,7 +2660,15 @@ void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence()); return; } - + else if (rInfos.eType == ww::eFILLIN && rInfos.pField) + { + SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get())); + if (rField.getGrabBagParams().hasElements()) + { + WriteSdtEnd(); + return; + } + } // The command has to be written before for the hyperlinks if ( rInfos.pField ) { diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index f05332612e00..0853d6c9d3f2 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -756,6 +756,7 @@ private: void WriteFlyFrame(const ww8::Frame& rFrame); void WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt); + void WriteSdtPlainText(const OUString& sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt); void WriteSdtDropDownStart(std::u16string_view rName, OUString const& rSelected, uno::Sequence<OUString> const& rListItems); void WriteSdtDropDownEnd(OUString const& rSelected, uno::Sequence<OUString> const& rListItems); void WriteSdtEnd(); diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index a3e4108cd06d..7b045de907ec 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -34,9 +34,11 @@ #include <com/sun/star/xml/sax/Writer.hpp> #include <com/sun/star/awt/XControlModel.hpp> #include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XStreamListener.hpp> #include <com/sun/star/sdb/CommandType.hpp> #include <com/sun/star/text/XTextFieldsSupplier.hpp> #include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/xml/xslt/XSLTTransformer.hpp> #include <oox/token/namespaces.hxx> #include <oox/token/tokens.hxx> @@ -51,6 +53,8 @@ #include <map> #include <algorithm> +#include <condition_variable> +#include <mutex> #include <IMark.hxx> #include <IDocumentSettingAccess.hxx> @@ -1514,6 +1518,77 @@ void DocxExport::WriteGlossary() } } +namespace { + class XsltTransformListener : public ::cppu::WeakImplHelper<io::XStreamListener> + { + public: + XsltTransformListener() : m_bDone(false) {} + + void wait() { + std::unique_lock<std::mutex> g(m_mutex); + m_cond.wait(g, [this]() { return m_bDone; }); + } + + private: + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_bDone; + + virtual void SAL_CALL disposing(const lang::EventObject&) noexcept override {} + virtual void SAL_CALL started() noexcept override {} + virtual void SAL_CALL closed() noexcept override { notifyDone(); } + virtual void SAL_CALL terminated() noexcept override { notifyDone(); } + virtual void SAL_CALL error(const uno::Any& e) override + { + notifyDone(); // set on error too, otherwise main thread waits forever + SAL_WARN("sw.ww8", e); + } + + void notifyDone() { + std::scoped_lock<std::mutex> g(m_mutex); + m_bDone = true; + m_cond.notify_all(); + } + }; +} + +static void lcl_UpdateXmlValues(const SdtData& sdtData, const uno::Reference<css::io::XInputStream>& xInputStream, const uno::Reference<css::io::XOutputStream>& xOutputStream) +{ + uno::Sequence<uno::Any> aArgs{ + // XSLT transformation stylesheet: + // - write all elements as is + // - but if element mathes sdtData.xpath, replace it's text content by sdtData.xpath + uno::Any(beans::NamedValue("StylesheetText", uno::Any(OUString("<?xml version=\"1.0\" encoding=\"UTF-8\"?> \ +<xsl:stylesheet\ + xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\ + " + sdtData.namespaces + "\ + version=\"1.0\">\ + <xsl:template match=\"@* | node()\">\ + <xsl:copy>\ + <xsl:apply-templates select=\"@* | node()\"/>\ + </xsl:copy>\ + </xsl:template>\ + <xsl:template match = \"" + sdtData.xpath + "\">\ + <xsl:copy>\ + <xsl:text>" + sdtData.data + "</xsl:text>\ + </xsl:copy>\ + </xsl:template>\ +</xsl:stylesheet>\ +")))) + }; + + css::uno::Reference<css::xml::xslt::XXSLTTransformer> xTransformer = + css::xml::xslt::XSLTTransformer::create(comphelper::getProcessComponentContext(), aArgs); + xTransformer->setInputStream(xInputStream); + xTransformer->setOutputStream(xOutputStream); + + rtl::Reference<XsltTransformListener> xListener = new XsltTransformListener(); + xTransformer->addListener(xListener); + + xTransformer->start(); + xListener->wait(); +} + void DocxExport::WriteCustomXml() { uno::Reference< beans::XPropertySet > xPropSet( m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW ); @@ -1548,10 +1623,54 @@ void DocxExport::WriteCustomXml() uno::Reference< xml::sax::XSAXSerializable > serializer( customXmlDom, uno::UNO_QUERY ); uno::Reference< xml::sax::XWriter > writer = xml::sax::Writer::create( comphelper::getProcessComponentContext() ); - writer->setOutputStream( GetFilter().openFragmentStream( "customXml/item"+OUString::number(j+1)+".xml", - "application/xml" ) ); - serializer->serialize( uno::Reference< xml::sax::XDocumentHandler >( writer, uno::UNO_QUERY_THROW ), - uno::Sequence< beans::StringPair >() ); + + uno::Reference < css::io::XOutputStream > xOutStream = GetFilter().openFragmentStream("customXml/item" + OUString::number(j + 1) + ".xml", + "application/xml"); + if (m_SdtData.size()) + { + // There are some SDT blocks data with data bindings which can update some custom xml values + uno::Reference< io::XStream > xMemStream( + comphelper::getProcessComponentContext()->getServiceManager()->createInstanceWithContext("com.sun.star.comp.MemoryStream", + comphelper::getProcessComponentContext()), + uno::UNO_QUERY_THROW); + + writer->setOutputStream(xMemStream->getOutputStream()); + + serializer->serialize(uno::Reference< xml::sax::XDocumentHandler >(writer, uno::UNO_QUERY_THROW), + uno::Sequence< beans::StringPair >()); + + uno::Reference< io::XStream > xXSLTInStream = xMemStream; + uno::Reference< io::XStream > xXSLTOutStream; + // Apply XSLT transformations for each SDT data binding + // Seems it is not possible to do this as one transformation: each data binding + // can have different namespaces, but with conflicting names (ns0, ns1, etc..) + for (size_t i = 0; i < m_SdtData.size(); i++) + { + if (i == m_SdtData.size() - 1) + { + // last transformation + lcl_UpdateXmlValues(m_SdtData[i], xXSLTInStream->getInputStream(), xOutStream); + } + else + { + xXSLTOutStream.set( + comphelper::getProcessComponentContext()->getServiceManager()->createInstanceWithContext("com.sun.star.comp.MemoryStream", + comphelper::getProcessComponentContext()), + uno::UNO_QUERY_THROW); + lcl_UpdateXmlValues(m_SdtData[i], xXSLTInStream->getInputStream(), xXSLTOutStream->getOutputStream()); + // Use previous output as an input for next run + xXSLTInStream.set( xXSLTOutStream ); + } + } + + } + else + { + writer->setOutputStream(xOutStream); + + serializer->serialize(uno::Reference< xml::sax::XDocumentHandler >(writer, uno::UNO_QUERY_THROW), + uno::Sequence< beans::StringPair >()); + } } if (customXmlDomProps.is()) diff --git a/sw/source/filter/ww8/docxexport.hxx b/sw/source/filter/ww8/docxexport.hxx index 3970597316d6..95da64d24408 100644 --- a/sw/source/filter/ww8/docxexport.hxx +++ b/sw/source/filter/ww8/docxexport.hxx @@ -62,6 +62,14 @@ struct DocxSettingsData bool trackRevisions; // Should 'Track Revisions' be set }; +/// Data to keep and write to XMLs +struct SdtData +{ + OUString namespaces; + OUString xpath; + OUString data; +}; + /// The class that does all the actual DOCX export-related work. class DocxExport : public MSWordExportBase { @@ -118,6 +126,9 @@ class DocxExport : public MSWordExportBase /// Map authors to remove personal info std::unique_ptr<SvtSecurityMapPersonalInfo> m_pAuthorIDs; + /// Storage for sdt data which need to be written to other XMLs + std::vector<SdtData> m_SdtData; + public: DocxExportFilter& GetFilter() { return m_rFilter; }; @@ -198,6 +209,11 @@ public: virtual ExportFormat GetExportFormat() const override { return ExportFormat::DOCX; } + void AddSdtData(const OUString & namespaces, const OUString & xpath, const OUString & data) + { + m_SdtData.push_back({ namespaces, xpath, data }); + } + protected: /// Format-dependent part of the actual export. virtual ErrCode ExportDocument_Impl() override; |