docx export: set custom xml values through XPathAPI instead of XSLT

each data binding built its own stylesheet and its own transformation
pass over the item, because different bindings can use conflicting
namespace prefixes

The import side already resolves the same binding with XPathAPI and
RegisterDataBindingNamespaces, so select the nodes that way here too
and set their text on a copy of the custom xml.

Signed-off-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Change-Id: I4b64bc56fd8a1c6c6641cf3ea9a44f7fed20206f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9941
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit fe2f99ef74ef02e803aaf01dcc2b7f6e1cea67f7)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210430
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Caolán McNamara
2026-09-14 12:56:31 +02:00
committed by Xisco Fauli
parent c3ec833e11
commit 5033a915c3
+41 -106
View File
@@ -41,7 +41,11 @@
#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 <com/sun/star/xml/xpath/XPathAPI.hpp>
#include <com/sun/star/xml/dom/XNode.hpp>
#include <com/sun/star/xml/dom/XNodeList.hpp>
#include <com/sun/star/xml/xpath/XXPathObject.hpp>
#include <databindingnamespaces.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/tokens.hxx>
@@ -1848,76 +1852,29 @@ 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)
static void lcl_UpdateXmlValues(const SdtData& rSdtData,
const uno::Reference<xml::dom::XDocument>& xDocument)
{
uno::Sequence<uno::Any> aArgs{
// XSLT transformation stylesheet:
// - write all elements as is
// - but if element matches sdtData.xpath, replace its text content by sdtData.xpath
uno::Any(beans::NamedValue(u"StylesheetText"_ustr, 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>\
"))))
};
uno::Reference<xml::xpath::XXPathAPI> xXPathAPI
= xml::xpath::XPathAPI::create(comphelper::getProcessComponentContext());
sw::RegisterDataBindingNamespaces(rSdtData.namespaces, xXPathAPI);
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();
xTransformer->terminate();
try
{
uno::Reference<xml::dom::XNodeList> xNodes
= xXPathAPI->eval(xDocument, rSdtData.xpath)->getNodeList();
for (sal_Int32 nNode = 0; nNode < xNodes->getLength(); ++nNode)
{
uno::Reference<xml::dom::XNode> xNode = xNodes->item(nNode);
while (uno::Reference<xml::dom::XNode> xChild = xNode->getFirstChild())
xNode->removeChild(xChild);
xNode->appendChild(xDocument->createTextNode(rSdtData.data));
}
}
catch (uno::Exception const&)
{
TOOLS_WARN_EXCEPTION("sw.ww8", "custom xml value at " << rSdtData.xpath);
}
}
void DocxExport::WriteCustomXml()
@@ -1951,49 +1908,27 @@ void DocxExport::WriteCustomXml()
oox::getRelationship(Relationship::CUSTOMXML),
Concat2View("../customXml/item"+OUString::number(j+1)+".xml" ));
uno::Reference< xml::sax::XSAXSerializable > serializer( customXmlDom, uno::UNO_QUERY );
uno::Reference< xml::sax::XWriter > writer = xml::sax::Writer::create( comphelper::getProcessComponentContext() );
xCustomXmlItemOutStream = GetFilter().openFragmentStream(
"customXml/item" + OUString::number(j + 1) + ".xml", u"application/xml"_ustr);
uno::Reference<xml::dom::XDocument> xItemDom = customXmlDom;
if (m_SdtData.size())
{
// There are some SDT blocks data with data bindings which can update some custom xml values
rtl::Reference< comphelper::UNOMemoryStream > xMemStream = new comphelper::UNOMemoryStream();
writer->setOutputStream(xMemStream->getOutputStream());
serializer->serialize(writer, uno::Sequence< beans::StringPair >());
uno::Reference< io::XStream > xXSLTInStream = xMemStream;
rtl::Reference< comphelper::UNOMemoryStream > 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(), xCustomXmlItemOutStream);
}
else
{
xXSLTOutStream = new comphelper::UNOMemoryStream();
lcl_UpdateXmlValues(m_SdtData[i], xXSLTInStream->getInputStream(), xXSLTOutStream->getOutputStream());
// Use previous output as an input for next run
xXSLTInStream.set( xXSLTOutStream );
}
// A data-bound content control can hold a value the user has edited since the
// document was loaded, so write the current values into the stored XML. The edit
// goes to a copy, which leaves the document model as it is.
uno::Reference<xml::dom::XDocument> xCopy(customXmlDom->cloneNode(true),
uno::UNO_QUERY_THROW);
for (const SdtData& rSdtData : m_SdtData)
lcl_UpdateXmlValues(rSdtData, xCopy);
xItemDom = xCopy;
}
}
else
{
writer->setOutputStream(xCustomXmlItemOutStream);
serializer->serialize(writer, uno::Sequence< beans::StringPair >());
}
uno::Reference<xml::sax::XSAXSerializable> xSerializer(xItemDom, uno::UNO_QUERY_THROW);
uno::Reference<xml::sax::XWriter> xWriter
= xml::sax::Writer::create(comphelper::getProcessComponentContext());
xWriter->setOutputStream(xCustomXmlItemOutStream);
xSerializer->serialize(xWriter, uno::Sequence<beans::StringPair>());
}
if (customXmlDomProps.is())