diff options
22 files changed, 582 insertions, 70 deletions
diff --git a/filter/source/xsltfilter/LibXSLTTransformer.cxx b/filter/source/xsltfilter/LibXSLTTransformer.cxx index 7ebc7aeb457d..706148b9e126 100644 --- a/filter/source/xsltfilter/LibXSLTTransformer.cxx +++ b/filter/source/xsltfilter/LibXSLTTransformer.cxx @@ -267,7 +267,7 @@ namespace XSLT OSL_ASSERT(m_transformer != nullptr); OSL_ASSERT(m_transformer->getInputStream().is()); OSL_ASSERT(m_transformer->getOutputStream().is()); - OSL_ASSERT(!m_transformer->getStyleSheetURL().isEmpty()); + OSL_ASSERT(!m_transformer->getStyleSheetURL().isEmpty() || !m_transformer->getStyleSheetText().isEmpty()); ::std::map<const char*, OString> pmap = m_transformer->getParameters(); ::std::vector< const char* > params( pmap.size() * 2 + 1 ); // build parameters int paramIndex = 0; @@ -280,8 +280,25 @@ namespace XSLT xmlDocPtr doc = xmlReadIO(&ParserInputBufferCallback::on_read, &ParserInputBufferCallback::on_close, static_cast<void*> (this), nullptr, nullptr, 0); - xsltStylesheetPtr styleSheet = xsltParseStylesheetFile( + xsltStylesheetPtr styleSheet = nullptr; + if (m_transformer->getStyleSheetURL().getLength()) + styleSheet = xsltParseStylesheetFile( reinterpret_cast<const xmlChar *>(m_transformer->getStyleSheetURL().getStr())); + else if (m_transformer->getStyleSheetText().getLength()) + { + xmlDocPtr styleSheetDoc = xmlReadMemory( + m_transformer->getStyleSheetText().getStr(), + m_transformer->getStyleSheetText().getLength(), + "noname.xml", nullptr, 0); + + styleSheet = xsltParseStylesheetDoc(styleSheetDoc); + } + + if (!styleSheet) + { + m_transformer->error("No stylesheet was created"); + } + xmlDocPtr result = nullptr; exsltRegisterAll(); registerExtensionModule(); @@ -326,7 +343,6 @@ namespace XSLT m_transformer->error(msg); } - closeOutput(); oh.reset(); xsltFreeStylesheet(styleSheet); xsltTransformContextPtr tcontext = nullptr; @@ -512,6 +528,10 @@ namespace XSLT { m_styleSheetURL = valueUTF8; } + if (nameUTF8 == "StylesheetText") + { + m_styleSheetText = valueUTF8; + } else if (nameUTF8 == "SourceURL") { m_parameters.insert(pair<const char*, OString> ( diff --git a/filter/source/xsltfilter/LibXSLTTransformer.hxx b/filter/source/xsltfilter/LibXSLTTransformer.hxx index 6da4a11e1adb..266fa2c559e1 100644 --- a/filter/source/xsltfilter/LibXSLTTransformer.hxx +++ b/filter/source/xsltfilter/LibXSLTTransformer.hxx @@ -113,6 +113,7 @@ namespace XSLT ListenerList m_listeners; OString m_styleSheetURL; + OString m_styleSheetText; ::std::map<const char *, OString> m_parameters; @@ -168,6 +169,8 @@ namespace XSLT const OString& getStyleSheetURL() const { return m_styleSheetURL; } + const OString& getStyleSheetText() const { return m_styleSheetText; } + const ::std::map<const char*, OString>& getParameters() const { return m_parameters; } diff --git a/offapi/com/sun/star/document/XOOXMLDocumentPropertiesImporter.idl b/offapi/com/sun/star/document/XOOXMLDocumentPropertiesImporter.idl index 9a2ba7fe4192..0a2548db4dc0 100644 --- a/offapi/com/sun/star/document/XOOXMLDocumentPropertiesImporter.idl +++ b/offapi/com/sun/star/document/XOOXMLDocumentPropertiesImporter.idl @@ -24,6 +24,7 @@ #include <com/sun/star/xml/sax/SAXException.idl> #include <com/sun/star/lang/IllegalArgumentException.idl> #include <com/sun/star/uno/Exception.idl> +#include <com/sun/star/io/XInputStream.idl> module com { module sun { module star { module document { @@ -70,6 +71,30 @@ interface XOOXMLDocumentPropertiesImporter: com::sun::star::uno::XInterface raises( com::sun::star::lang::IllegalArgumentException, com::sun::star::xml::sax::SAXException, com::sun::star::uno::Exception ); + + /** find and get core properties stream + + (usually it is docProps\core.xml) + @since LibreOffice 7.4 + */ + + com::sun::star::io::XInputStream getCorePropertiesStream([in] com::sun::star::embed::XStorage xSource); + + /** find and get extended properties stream + + (usually it is docProps/app.xml) + @since LibreOffice 7.4 + */ + + com::sun::star::io::XInputStream getExtendedPropertiesStream([in] com::sun::star::embed::XStorage xSource); + + /** find and get custom properties streams + + (usually it is customXml\*.xml) + @since LibreOffice 7.4 + */ + + sequence< com::sun::star::io::XInputStream > getCustomPropertiesStreams([in] com::sun::star::embed::XStorage xSource); }; diff --git a/oox/source/docprop/ooxmldocpropimport.cxx b/oox/source/docprop/ooxmldocpropimport.cxx index 4de14e0fca9f..30c058e209a4 100644 --- a/oox/source/docprop/ooxmldocpropimport.cxx +++ b/oox/source/docprop/ooxmldocpropimport.cxx @@ -86,6 +86,40 @@ Sequence< InputSource > lclGetRelatedStreams( const Reference< XStorage >& rxSto return comphelper::containerToSequence( aResult ); } +Sequence< InputSource > lclGetCoreStreams(const Reference< XStorage >& rxSource) +{ + Sequence< InputSource > aCoreStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE("metadata/core-properties")); + // OOXML strict + if (!aCoreStreams.hasElements()) + aCoreStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT("metadata/core-properties")); + // MS Office seems to have a bug, so we have to do similar handling + if (!aCoreStreams.hasElements()) + aCoreStreams = lclGetRelatedStreams(rxSource, "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"); + + return aCoreStreams; +} + +Sequence< InputSource > lclGetExtStreams(const Reference< XStorage >& rxSource) +{ + Sequence< InputSource > aExtStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE("extended-properties")); + // OOXML strict + if (!aExtStreams.hasElements()) + aExtStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT("extended-properties")); + + return aExtStreams; +} + +Sequence< InputSource > lclGetCustomStreams(const Reference< XStorage >& rxSource) +{ + Sequence< InputSource > aCustomStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE("custom-properties")); + // OOXML strict + if (!aCustomStreams.hasElements()) + aCustomStreams = lclGetRelatedStreams(rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT("custom-properties")); + + return aCustomStreams; +} + + } // namespace DocumentPropertiesImport::DocumentPropertiesImport( const Reference< XComponentContext >& rxContext ) : @@ -120,22 +154,11 @@ void SAL_CALL DocumentPropertiesImport::importProperties( if( !rxSource.is() || !rxDocumentProperties.is() ) throw IllegalArgumentException(); - Sequence< InputSource > aCoreStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE( "metadata/core-properties" ) ); - // OOXML strict - if( !aCoreStreams.hasElements() ) - aCoreStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "metadata/core-properties" ) ); - // MS Office seems to have a bug, so we have to do similar handling - if( !aCoreStreams.hasElements() ) - aCoreStreams = lclGetRelatedStreams( rxSource, "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" ); + Sequence< InputSource > aCoreStreams = lclGetCoreStreams(rxSource); - Sequence< InputSource > aExtStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE( "extended-properties" ) ); - // OOXML strict - if( !aExtStreams.hasElements() ) - aExtStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "extended-properties" ) ); - Sequence< InputSource > aCustomStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE( "custom-properties" ) ); - // OOXML strict - if( !aCustomStreams.hasElements() ) - aCustomStreams = lclGetRelatedStreams( rxSource, CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "custom-properties" ) ); + Sequence< InputSource > aExtStreams = lclGetExtStreams(rxSource); + + Sequence< InputSource > aCustomStreams = lclGetCustomStreams(rxSource); if( !(aCoreStreams.hasElements() || aExtStreams.hasElements() || aCustomStreams.hasElements()) ) return; @@ -160,6 +183,41 @@ void SAL_CALL DocumentPropertiesImport::importProperties( aParser.parseStream( rCustomStream, true ); } +Reference < com::sun::star::io::XInputStream > SAL_CALL DocumentPropertiesImport::getCorePropertiesStream( + const Reference< XStorage >& rxSource) +{ + Sequence< InputSource > aCoreStreams = lclGetCoreStreams(rxSource); + if (!aCoreStreams.hasElements()) + return nullptr; + + return aCoreStreams[0].aInputStream; +} + +Reference < com::sun::star::io::XInputStream > SAL_CALL DocumentPropertiesImport::getExtendedPropertiesStream( + const Reference< XStorage >& rxSource) +{ + Sequence< InputSource > aExtStreams = lclGetExtStreams(rxSource); + if (!aExtStreams.hasElements()) + return nullptr; + + return aExtStreams[0].aInputStream; +} + +css::uno::Sequence< css::uno::Reference< com::sun::star::io::XInputStream > > SAL_CALL DocumentPropertiesImport::getCustomPropertiesStreams( + const Reference< XStorage >& rxSource) +{ + Sequence <InputSource> aExtStreams = lclGetCustomStreams(rxSource); + + // Repack the sequence + std::vector<Reference<XInputStream>> aResult(aExtStreams.getLength()); + for (const auto& aInputSource : aExtStreams) + { + aResult.push_back(aInputSource.aInputStream); + } + + return comphelper::containerToSequence(aResult); +} + } // namespace oox::docprop extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* diff --git a/oox/source/docprop/ooxmldocpropimport.hxx b/oox/source/docprop/ooxmldocpropimport.hxx index f76d4feb7b46..ba406bae9aed 100644 --- a/oox/source/docprop/ooxmldocpropimport.hxx +++ b/oox/source/docprop/ooxmldocpropimport.hxx @@ -45,6 +45,12 @@ public: virtual void SAL_CALL importProperties( const css::uno::Reference< css::embed::XStorage >& rxSource, const css::uno::Reference< css::document::XDocumentProperties >& rxDocumentProperties ) override; + virtual css::uno::Reference < com::sun::star::io::XInputStream > SAL_CALL getCorePropertiesStream( + const css::uno::Reference< css::embed::XStorage >& rxSource) override; + virtual css::uno::Reference < com::sun::star::io::XInputStream > SAL_CALL getExtendedPropertiesStream( + const css::uno::Reference< css::embed::XStorage >& rxSource) override; + virtual css::uno::Sequence< css::uno::Reference< com::sun::star::io::XInputStream > > SAL_CALL getCustomPropertiesStreams( + const css::uno::Reference< css::embed::XStorage >& rxSource) override; private: css::uno::Reference< css::uno::XComponentContext > mxContext; 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; diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx index a7b759ed0e2d..b5e08cfe5bc9 100644 --- a/writerfilter/source/dmapper/DomainMapper.cxx +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -1071,6 +1071,11 @@ void DomainMapper::lcl_attribute(Id nName, Value & val) } break; case NS_ooxml::LN_CT_SdtBlock_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); + } m_pImpl->SetSdt(true); break; case NS_ooxml::LN_CT_SdtBlock_sdtEndContent: @@ -1088,6 +1093,9 @@ void DomainMapper::lcl_attribute(Id nName, Value & val) case SdtControlType::dropDown: m_pImpl->m_pSdtHelper->createDropDownControl(); break; + case SdtControlType::plainText: + m_pImpl->m_pSdtHelper->createPlainTextControl(); + break; case SdtControlType::datePicker: m_pImpl->m_pSdtHelper->createDateContentControl(); break; @@ -2701,6 +2709,17 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) m_pImpl->m_pSdtHelper->getLocale().append(sStringValue); } break; + case NS_ooxml::LN_CT_SdtPr_text: + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); + enableInteropGrabBag("ooxml:CT_SdtPr_text"); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + m_pImpl->m_pSdtHelper->appendToInteropGrabBag(getInteropGrabBag()); + m_pImpl->disableInteropGrabBag(); + } + break; case NS_ooxml::LN_CT_SdtPr_dataBinding: case NS_ooxml::LN_CT_SdtPr_equation: case NS_ooxml::LN_CT_SdtPr_checkbox: @@ -2709,7 +2728,6 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtPr_picture: case NS_ooxml::LN_CT_SdtPr_citation: case NS_ooxml::LN_CT_SdtPr_group: - case NS_ooxml::LN_CT_SdtPr_text: case NS_ooxml::LN_CT_SdtPr_id: case NS_ooxml::LN_CT_SdtPr_alias: case NS_ooxml::LN_CT_SdtPlaceholder_docPart: @@ -2727,13 +2745,21 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtPr_picture: sName = "ooxml:CT_SdtPr_picture"; break; case NS_ooxml::LN_CT_SdtPr_citation: sName = "ooxml:CT_SdtPr_citation"; break; case NS_ooxml::LN_CT_SdtPr_group: sName = "ooxml:CT_SdtPr_group"; break; - case NS_ooxml::LN_CT_SdtPr_text: sName = "ooxml:CT_SdtPr_text"; break; case NS_ooxml::LN_CT_SdtPr_id: sName = "ooxml:CT_SdtPr_id"; break; case NS_ooxml::LN_CT_SdtPr_alias: sName = "ooxml:CT_SdtPr_alias"; break; case NS_ooxml::LN_CT_SdtPlaceholder_docPart: sName = "ooxml:CT_SdtPlaceholder_docPart"; break; case NS_ooxml::LN_CT_SdtPr_color: sName = "ooxml:CT_SdtPr_color"; break; default: assert(false); }; + if ( + nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox || + nSprmId == NS_ooxml::LN_CT_SdtPr_docPartObj || + nSprmId == NS_ooxml::LN_CT_SdtPr_docPartList || + nSprmId == NS_ooxml::LN_CT_SdtPr_picture || + nSprmId == NS_ooxml::LN_CT_SdtPr_citation) + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::unsupported); + } enableInteropGrabBag(sName); // process subitems @@ -3556,6 +3582,16 @@ void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len) return; } } + else if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText) + { + m_pImpl->m_pSdtHelper->getSdtTexts().append(sText); + if (bNewLine) + { + m_pImpl->m_pSdtHelper->createPlainTextControl(); + finishParagraph(); + } + return; + } else if (!m_pImpl->m_pSdtHelper->isInteropGrabBagEmpty()) { // there are unsupported SDT properties in the document diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx index c53a93f62de6..c5ec47f2be23 100644 --- a/writerfilter/source/dmapper/SdtHelper.cxx +++ b/writerfilter/source/dmapper/SdtHelper.cxx @@ -12,26 +12,30 @@ #include <com/sun/star/drawing/XControlShape.hpp> #include <com/sun/star/text/VertOrientation.hpp> #include <editeng/unoprnms.hxx> +#include <sal/log.hxx> #include <vcl/svapp.hxx> #include <vcl/outdev.hxx> +#include <comphelper/string.hxx> #include <comphelper/sequence.hxx> #include <xmloff/odffields.hxx> #include <com/sun/star/text/XTextField.hpp> #include "DomainMapper_Impl.hxx" #include "StyleSheetTable.hxx" #include <officecfg/Office/Writer.hxx> - #include <com/sun/star/util/XRefreshable.hpp> #include <com/sun/star/text/XTextFieldsSupplier.hpp> - +#include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp> +#include <com/sun/star/embed/ElementModes.hpp> #include <ooxml/OOXMLDocument.hxx> #include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> #include <com/sun/star/xml/dom/XNode.hpp> namespace writerfilter::dmapper { using namespace ::com::sun::star; using namespace ::css::xml::xpath; +using namespace ::comphelper; /// w:sdt's w:dropDownList doesn't have width, so guess the size based on the longest string. static awt::Size lcl_getOptimalWidth(const StyleSheetTablePtr& pStyleSheet, @@ -80,39 +84,126 @@ SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl, , m_aControlType(SdtControlType::unknown) , m_bHasElements(false) , m_bOutsideAParagraph(false) + , m_bPropertiesXMLsLoaded(false) { } SdtHelper::~SdtHelper() = default; +void SdtHelper::loadPropertiesXMLs() +{ + // Initialize properties xml storage (m_xPropertiesXMLs) + uno::Reference<uno::XInterface> xTemp + = m_xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.OOXMLDocumentPropertiesImporter", m_xComponentContext); + uno::Reference<document::XOOXMLDocumentPropertiesImporter> xImporter(xTemp, uno::UNO_QUERY); + if (!xImporter.is()) + return; + + uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder( + xml::dom::DocumentBuilder::create(m_xComponentContext)); + if (!xDomBuilder.is()) + return; + + std::vector<uno::Reference<xml::dom::XDocument>> aPropDocs; + + // Load core properties + try + { + auto xCorePropsStream = xImporter->getCorePropertiesStream(m_rDM_Impl.m_xDocumentStorage); + aPropDocs.push_back(xDomBuilder->parse(xCorePropsStream)); + } + catch (const uno::Exception&) + { + SAL_WARN("writerfilter", + "SdtHelper::loadPropertiesXMLs: failed loading core properties XML"); + } + + // Load extended properties + try + { + auto xExtPropsStream + = xImporter->getExtendedPropertiesStream(m_rDM_Impl.m_xDocumentStorage); + aPropDocs.push_back(xDomBuilder->parse(xExtPropsStream)); + } + catch (const uno::Exception&) + { + SAL_WARN("writerfilter", + "SdtHelper::loadPropertiesXMLs: failed loading extended properties XML"); + } + + // TODO: some other property items? + + // Add custom XMLs + uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmls + = m_rDM_Impl.getDocumentReference()->getCustomXmlDomList(); + for (const auto& xDoc : aCustomXmls) + { + aPropDocs.push_back(xDoc); + } + + m_xPropertiesXMLs = comphelper::containerToSequence(aPropDocs); + m_bPropertiesXMLsLoaded = true; +} + +static void lcl_registerNamespaces(const OUString& sNamespaceString, + const uno::Reference<XXPathAPI>& xXPathAPI) +{ + // Split namespaces and register it in XPathAPI + auto aNamespaces = string::split(sNamespaceString, ' '); + for (const auto& sNamespace : aNamespaces) + { + // Here we have just one namespace in format "xmlns:ns0='http://someurl'" + auto aNamespace = string::split(sNamespace, '='); + if (aNamespace.size() < 2) + { + SAL_WARN("writerfilter", + "SdtHelper::getValueFromDataBinding: invalid namespace: " << sNamespace); + continue; + } + + auto aNamespaceId = string::split(aNamespace[0], ':'); + if (aNamespaceId.size() < 2) + { + SAL_WARN("writerfilter", + "SdtHelper::getValueFromDataBinding: invalid namespace: " << aNamespace[0]); + continue; + } + + OUString sNamespaceURL = aNamespace[1]; + sNamespaceURL = string::strip(sNamespaceURL, ' '); + sNamespaceURL = string::strip(sNamespaceURL, '\''); + + xXPathAPI->registerNS(aNamespaceId[1], sNamespaceURL); + } +} + std::optional<OUString> SdtHelper::getValueFromDataBinding() { // No xpath - nothing to do if (m_sDataBindingXPath.isEmpty()) return {}; - writerfilter::ooxml::OOXMLDocument* pDocument = m_rDM_Impl.getDocumentReference(); - assert(pDocument); - if (!pDocument) - return {}; + // Load properties XMLs + if (!m_bPropertiesXMLsLoaded) + loadPropertiesXMLs(); - // Iterate all custom xmls documents and evaluate xpath to get value - uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmls - = pDocument->getCustomXmlDomList(); - for (const auto& xCustomXml : aCustomXmls) - { - uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext); + uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext); + + lcl_registerNamespaces(m_sDataBindingPrefixMapping, xXpathAPI); - //xXpathAPI->registerNS("ns0", m_sDataBindingPrefixMapping); - xXpathAPI->registerNS("ns0", "http://schemas.microsoft.com/vsto/samples"); - uno::Reference<XXPathObject> xResult = xXpathAPI->eval(xCustomXml, m_sDataBindingXPath); + // Iterate all properties xml documents and try to fetch data + for (const auto& xDocument : m_xPropertiesXMLs) + { + uno::Reference<XXPathObject> xResult = xXpathAPI->eval(xDocument, m_sDataBindingXPath); - if (xResult.is()) + if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength()) { return xResult->getString(); } } + // No data return {}; } @@ -176,6 +267,38 @@ void SdtHelper::createDropDownControl() setControlType(SdtControlType::unknown); } +void SdtHelper::createPlainTextControl() +{ + assert(getControlType() == SdtControlType::plainText); + + OUString aDefaultText = m_aSdtTexts.makeStringAndClear(); + + // create field + uno::Reference<css::text::XTextField> xControlModel( + m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"), + uno::UNO_QUERY); + + // set properties + uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); + + std::optional<OUString> oData = getValueFromDataBinding(); + if (oData.has_value()) + aDefaultText = *oData; + + xPropertySet->setPropertyValue("Content", uno::makeAny(aDefaultText)); + + // add it into document + m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>()); + + // Store all unused sdt parameters from grabbag + xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, + uno::makeAny(getInteropGrabBagAndClear())); + + // clean up + m_aDropDownItems.clear(); + setControlType(SdtControlType::unknown); +} + void SdtHelper::createDateContentControl() { if (!m_xDateFieldStartRange.is()) @@ -251,7 +374,8 @@ void SdtHelper::createDateContentControl() setControlType(SdtControlType::unknown); // Store all unused sdt parameters from grabbag - xNameCont->insertByName("SdtParams", uno::makeAny(getInteropGrabBagAndClear())); + xNameCont->insertByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, + uno::makeAny(getInteropGrabBagAndClear())); } void SdtHelper::createControlShape(awt::Size aSize, diff --git a/writerfilter/source/dmapper/SdtHelper.hxx b/writerfilter/source/dmapper/SdtHelper.hxx index 9d2c6b1da7cf..e58d73168d79 100644 --- a/writerfilter/source/dmapper/SdtHelper.hxx +++ b/writerfilter/source/dmapper/SdtHelper.hxx @@ -14,6 +14,8 @@ #include <com/sun/star/beans/PropertyValue.hpp> #include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xml/dom/XDocument.hpp> #include <rtl/ustrbuf.hxx> #include <tools/ref.hxx> @@ -39,6 +41,8 @@ enum class SdtControlType { datePicker, dropDown, + plainText, + unsupported, // Sdt block is defined, but we still do not support such type of field unknown }; @@ -82,6 +86,13 @@ class SdtHelper final : public virtual SvRefBase /// The last stored SDT element is outside paragraphs. bool m_bOutsideAParagraph; + /// Storage for all properties documents as xml::dom::XDocument for later quering xpath for data + css::uno::Sequence<css::uno::Reference<css::xml::dom::XDocument>> m_xPropertiesXMLs; + + /// Check if m_xPropertiesXMLs is initialized and loaded (need extra flag to distinguish + /// empty sequence from not yet initialized) + bool m_bPropertiesXMLsLoaded; + /// Create and append the drawing::XControlShape, containing the various models. void createControlShape(css::awt::Size aSize, css::uno::Reference<css::awt::XControlModel> const& xControlModel, @@ -89,6 +100,8 @@ class SdtHelper final : public virtual SvRefBase std::optional<OUString> getValueFromDataBinding(); + void loadPropertiesXMLs(); + public: explicit SdtHelper(DomainMapper_Impl& rDM_Impl, css::uno::Reference<css::uno::XComponentContext> const& xContext); @@ -132,6 +145,8 @@ public: /// Create date control from w:sdt's w:date. void createDateContentControl(); + void createPlainTextControl(); + void appendToInteropGrabBag(const css::beans::PropertyValue& rValue); css::uno::Sequence<css::beans::PropertyValue> getInteropGrabBagAndClear(); bool isInteropGrabBagEmpty() const; |