cool#10610 Ensure the parent-child relations of comments.
[LibreOffice.git] / filter / source / xsltfilter / XSLTFilter.cxx
blob00f1bd3629f32242045ae20c13ba623c78946863
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <cppuhelper/factory.hxx>
22 #include <cppuhelper/implbase.hxx>
23 #include <cppuhelper/supportsservice.hxx>
25 #include <sax/tools/documenthandleradapter.hxx>
27 #include <osl/diagnose.h>
28 #include <osl/time.h>
29 #include <osl/conditn.hxx>
30 #include <tools/urlobj.hxx>
31 #include <comphelper/diagnose_ex.hxx>
32 #include <sal/log.hxx>
33 #include <rtl/ref.hxx>
34 #include <rtl/uri.hxx>
36 #include <comphelper/interaction.hxx>
38 #include <com/sun/star/lang/EventObject.hpp>
39 #include <com/sun/star/lang/XServiceInfo.hpp>
41 #include <com/sun/star/uno/Any.hxx>
43 #include <com/sun/star/beans/PropertyValue.hpp>
45 #include <com/sun/star/xml/sax/Parser.hpp>
46 #include <com/sun/star/xml/sax/InputSource.hpp>
47 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
48 #include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp>
49 #include <com/sun/star/xml/sax/XFastParser.hpp>
50 #include <com/sun/star/xml/sax/Writer.hpp>
51 #include <com/sun/star/xml/XImportFilter.hpp>
52 #include <com/sun/star/xml/XImportFilter2.hpp>
53 #include <com/sun/star/xml/XExportFilter.hpp>
55 #include <com/sun/star/util/theMacroExpander.hpp>
57 #include <com/sun/star/io/Pipe.hpp>
58 #include <com/sun/star/io/XInputStream.hpp>
59 #include <com/sun/star/io/XOutputStream.hpp>
60 #include <com/sun/star/io/XActiveDataSource.hpp>
61 #include <com/sun/star/io/XSeekable.hpp>
62 #include <com/sun/star/io/XStreamListener.hpp>
63 #include <com/sun/star/util/PathSubstitution.hpp>
64 #include <com/sun/star/util/XStringSubstitution.hpp>
65 #include <com/sun/star/beans/NamedValue.hpp>
66 #include <com/sun/star/task/XInteractionHandler.hpp>
67 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
68 #include <com/sun/star/xml/xslt/XSLTTransformer.hpp>
69 #include <utility>
71 #define TRANSFORMATION_TIMEOUT_SEC 60
73 using namespace ::cppu;
74 using namespace ::osl;
75 using namespace ::sax;
76 using namespace ::com::sun::star::beans;
77 using namespace ::com::sun::star::io;
78 using namespace ::com::sun::star::uno;
79 using namespace ::com::sun::star::lang;
80 using namespace ::com::sun::star::xml;
81 using namespace ::com::sun::star::xml::sax;
82 using namespace ::com::sun::star::util;
83 using namespace ::com::sun::star::task;
85 namespace XSLT
87 namespace {
89 class XSLTFilter;
90 class XSLTFilterStreamListener : public WeakImplHelper<XStreamListener>
92 public:
93 XSLTFilterStreamListener(XSLTFilter& rParent) : m_rParent(rParent) {}
95 // XStreamListener
96 virtual void SAL_CALL
97 error(const Any& a) override;
98 virtual void SAL_CALL
99 closed() override;
100 virtual void SAL_CALL
101 terminated() override;
102 virtual void SAL_CALL
103 started() override;
104 virtual void SAL_CALL
105 disposing(const EventObject& e) override;
106 private:
107 XSLTFilter& m_rParent;
111 * XSLTFilter reads flat XML streams from the XML filter framework and passes
112 * them to an XSLT transformation service. XSLT transformation errors are
113 * reported to XSLTFilter.
115 * Currently, our transformation service is libxslt based, so it
116 * only supports XSLT 1.0. There is a possibility to use XSLT 2.0
117 * supporting service from an extension for a specific filter; the
118 * service must support com.sun.star.xml.xslt.XSLT2Transformer.
120 class XSLTFilter : public WeakImplHelper<XImportFilter, XImportFilter2, XExportFilter,
121 ExtendedDocumentHandlerAdapter, XServiceInfo>
123 friend class XSLTFilterStreamListener;
124 private:
126 // the UNO ServiceFactory
127 css::uno::Reference<XComponentContext> m_xContext;
129 // DocumentHandler interface of the css::xml::sax::Writer service
130 css::uno::Reference<XOutputStream> m_rOutputStream;
132 css::uno::Reference<xslt::XXSLTTransformer> m_tcontrol;
134 osl::Condition m_cTransformed;
135 bool m_bTerminated;
136 bool m_bError;
138 OUString m_aExportBaseUrl;
140 OUString
141 rel2abs(const OUString&);
142 OUString
143 expandUrl(const OUString&);
145 css::uno::Reference<xslt::XXSLTTransformer> impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs);
147 public:
149 // ctor...
150 explicit XSLTFilter(css::uno::Reference<XComponentContext> x);
152 // XServiceInfo
153 virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override;
154 virtual OUString SAL_CALL getImplementationName() override;
155 virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
157 // XImportFilter
158 virtual sal_Bool SAL_CALL
159 importer(const Sequence<PropertyValue>& aSourceData, const css::uno::Reference<
160 XDocumentHandler>& xHandler,
161 const Sequence<OUString>& msUserData) override;
163 // XImportFilter2
164 virtual sal_Bool SAL_CALL
165 importer(const Sequence<PropertyValue>& aSourceData, const css::uno::Reference<
166 XFastParser>& xFastParser,
167 const Sequence<OUString>& msUserData) override;
169 // XExportFilter
170 virtual sal_Bool SAL_CALL
171 exporter(const Sequence<PropertyValue>& aSourceData, const Sequence<
172 OUString>& msUserData) override;
174 // XDocumentHandler
175 virtual void SAL_CALL
176 startDocument() override;
177 virtual void SAL_CALL
178 endDocument() override;
183 XSLTFilter::XSLTFilter(css::uno::Reference<XComponentContext> x):
184 m_xContext(std::move(x)), m_bTerminated(false), m_bError(false)
187 void
188 XSLTFilterStreamListener::disposing(const EventObject&)
192 // XServiceInfo
193 sal_Bool XSLTFilter::supportsService(const OUString& sServiceName)
195 return cppu::supportsService(this, sServiceName);
197 OUString XSLTFilter::getImplementationName()
199 return u"com.sun.star.comp.documentconversion.XSLTFilter"_ustr;
201 css::uno::Sequence< OUString > XSLTFilter::getSupportedServiceNames()
203 return { u"com.sun.star.documentconversion.XSLTFilter"_ustr };
206 OUString
207 XSLTFilter::expandUrl(const OUString& sUrl)
209 OUString sPreparedURL(sUrl);
210 if (sPreparedURL.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &sPreparedURL))
212 sPreparedURL = rtl::Uri::decode(sPreparedURL, rtl_UriDecodeWithCharset,
213 RTL_TEXTENCODING_UTF8);
214 css::uno::Reference<XMacroExpander>
215 xMacroExpander = theMacroExpander::get(m_xContext);
216 sPreparedURL = xMacroExpander->expandMacros(sPreparedURL);
218 return sPreparedURL;
221 css::uno::Reference<xslt::XXSLTTransformer>
222 XSLTFilter::impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs)
224 css::uno::Reference<xslt::XXSLTTransformer> xTransformer;
226 // check if the filter needs XSLT-2.0-capable transformer
227 // COMPATIBILITY: libreoffice 3.5/3.6 used to save the impl.
228 // name of the XSLT 2.0 transformation service there, so check
229 // for that too (it is sufficient to check that there is _a_
230 // service name there)
231 if (rTransformer.toBoolean() || rTransformer.startsWith("com.sun."))
235 xTransformer.set(
236 m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
237 u"com.sun.star.xml.xslt.XSLT2Transformer"_ustr, rArgs, m_xContext),
238 css::uno::UNO_QUERY_THROW);
240 catch (const Exception&)
242 // TODO: put a dialog telling about the need to install
243 // xslt2-transformer extension here
244 SAL_WARN("filter.xslt", "could not create XSLT 2.0 transformer");
245 throw;
248 else
250 xTransformer = xslt::XSLTTransformer::create(m_xContext, rArgs);
253 return xTransformer;
256 void
257 XSLTFilterStreamListener::started()
259 m_rParent.m_cTransformed.reset();
261 void
262 XSLTFilterStreamListener::error(const Any& a)
264 SAL_WARN("filter.xslt", "XSLTFilter::error was called: " << exceptionToString(a));
265 m_rParent.m_bError = true;
266 m_rParent.m_cTransformed.set();
268 void
269 XSLTFilterStreamListener::closed()
271 m_rParent.m_cTransformed.set();
273 void
274 XSLTFilterStreamListener::terminated()
276 m_rParent.m_bTerminated = true;
277 m_rParent.m_cTransformed.set();
280 OUString
281 XSLTFilter::rel2abs(const OUString& s)
284 css::uno::Reference<XStringSubstitution>
285 subs(css::util::PathSubstitution::create(m_xContext));
286 OUString aWorkingDir(subs->getSubstituteVariableValue( u"$(progurl)"_ustr ));
287 INetURLObject aObj(aWorkingDir);
288 aObj.setFinalSlash();
289 bool bWasAbsolute;
290 INetURLObject aURL = aObj.smartRel2Abs(s, bWasAbsolute, false,
291 INetURLObject::EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, true);
292 return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
295 sal_Bool
296 XSLTFilter::importer(const Sequence<PropertyValue>& aSourceData,
297 const css::uno::Reference<XDocumentHandler>& xHandler, const Sequence<
298 OUString>& msUserData)
300 if (msUserData.getLength() < 5)
301 return false;
303 OUString udStyleSheet = rel2abs(msUserData[4]);
305 // get information from media descriptor
306 // the input stream that represents the imported file
307 // is most important here since we need to supply it to
308 // the sax parser that drives the supplied document handler
309 OUString aName, aURL;
310 css::uno::Reference<XInputStream> xInputStream;
311 css::uno::Reference<XInteractionHandler> xInterActionHandler;
312 for (const auto& sourceDataItem : aSourceData)
314 aName = sourceDataItem.Name;
315 Any value = sourceDataItem.Value;
316 if ( aName == "InputStream" )
317 value >>= xInputStream;
318 else if ( aName == "URL" )
319 value >>= aURL;
320 else if ( aName == "InteractionHandler" )
321 value >>= xInterActionHandler;
323 OSL_ASSERT(xInputStream.is());
324 if (!xInputStream.is())
325 return false;
327 // create transformer
328 Sequence<Any> args{ Any(NamedValue(u"StylesheetURL"_ustr, Any(expandUrl(udStyleSheet)))),
329 Any(NamedValue(u"SourceURL"_ustr, Any(aURL))),
330 Any(NamedValue(u"SourceBaseURL"_ustr, Any(INetURLObject(aURL).getBase()))) };
331 m_tcontrol = impl_createTransformer(msUserData[1], args);
333 OSL_ASSERT(xHandler.is());
334 OSL_ASSERT(xInputStream.is());
335 OSL_ASSERT(m_tcontrol.is());
336 if (xHandler.is() && xInputStream.is() && m_tcontrol.is())
340 css::uno::Reference<css::io::XSeekable> xSeek(xInputStream, UNO_QUERY);
341 if (xSeek.is())
342 xSeek->seek(0);
344 // we want to be notified when the processing is done...
345 m_tcontrol->addListener(new XSLTFilterStreamListener(*this));
347 // connect input to transformer
348 m_tcontrol->setInputStream(xInputStream);
350 // create pipe
351 css::uno::Reference<XOutputStream> pipeout =
352 Pipe::create(m_xContext);
354 //connect transformer to pipe
355 m_tcontrol->setOutputStream(pipeout);
357 // connect pipe to sax parser
358 InputSource aInput;
359 aInput.sSystemId = aURL;
360 aInput.sPublicId = aURL;
361 aInput.aInputStream.set(pipeout, UNO_QUERY);
363 css::uno::Reference< css::xml::sax::XFastParser > xFastParser(
364 xHandler, css::uno::UNO_QUERY );
366 // transform
367 m_tcontrol->start();
368 TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0};
369 osl::Condition::Result result(m_cTransformed.wait(&timeout));
370 while (osl::Condition::result_timeout == result) {
371 if (xInterActionHandler.is()) {
372 Sequence<Any> excArgs(0);
373 css::ucb::InteractiveAugmentedIOException exc(
374 u"Timeout!"_ustr,
375 getXWeak(),
376 InteractionClassification_ERROR,
377 css::ucb::IOErrorCode_GENERAL,
378 excArgs);
379 Any r;
380 r <<= exc;
381 rtl::Reference<::comphelper::OInteractionRequest> pRequest = new ::comphelper::OInteractionRequest(r);
382 rtl::Reference<::comphelper::OInteractionRetry> pRetry = new ::comphelper::OInteractionRetry;
383 rtl::Reference<::comphelper::OInteractionAbort> pAbort = new ::comphelper::OInteractionAbort;
384 pRequest->addContinuation(pRetry);
385 pRequest->addContinuation(pAbort);
386 xInterActionHandler->handle(pRequest);
387 if (pAbort->wasSelected()) {
388 m_bError = true;
389 m_cTransformed.set();
392 result = m_cTransformed.wait(&timeout);
394 if (!m_bError) {
395 if( xFastParser.is() )
396 xFastParser->parseStream( aInput );
397 else
399 // create SAX parser that will read the document file
400 // and provide events to xHandler passed to this call
401 css::uno::Reference<XParser> xSaxParser = Parser::create(m_xContext);
402 // set doc handler
403 xSaxParser->setDocumentHandler(xHandler);
404 xSaxParser->parseStream( aInput );
407 m_tcontrol->terminate();
408 return !m_bError;
410 catch( const Exception& )
412 // something went wrong
413 TOOLS_WARN_EXCEPTION("filter.xslt", "");
414 return false;
417 else
419 return false;
423 sal_Bool
424 XSLTFilter::importer(const Sequence<PropertyValue>& aSourceData,
425 const css::uno::Reference<XFastParser>& xFastParser, const Sequence<
426 OUString>& msUserData)
428 if (msUserData.getLength() < 5)
429 return false;
431 OUString udStyleSheet = rel2abs(msUserData[4]);
433 // get information from media descriptor
434 // the input stream that represents the imported file
435 // is most important here since we need to supply it to
436 // the sax parser that drives the supplied document handler
437 sal_Int32 nLength = aSourceData.getLength();
438 OUString aName, aURL;
439 css::uno::Reference<XInputStream> xInputStream;
440 css::uno::Reference<XInteractionHandler> xInterActionHandler;
441 for (sal_Int32 i = 0; i < nLength; i++)
443 aName = aSourceData[i].Name;
444 Any value = aSourceData[i].Value;
445 if ( aName == "InputStream" )
446 value >>= xInputStream;
447 else if ( aName == "URL" )
448 value >>= aURL;
449 else if ( aName == "InteractionHandler" )
450 value >>= xInterActionHandler;
452 OSL_ASSERT(xInputStream.is());
453 if (!xInputStream.is())
454 return false;
456 // create transformer
457 Sequence<Any> args{ Any(NamedValue(u"StylesheetURL"_ustr, Any(expandUrl(udStyleSheet)))),
458 Any(NamedValue(u"SourceURL"_ustr, Any(aURL))),
459 Any(NamedValue(u"SourceBaseURL"_ustr, Any(INetURLObject(aURL).getBase()))) };
460 m_tcontrol = impl_createTransformer(msUserData[1], args);
462 assert(xFastParser.is());
463 OSL_ASSERT(xInputStream.is());
464 OSL_ASSERT(m_tcontrol.is());
465 if (xFastParser.is() && xInputStream.is() && m_tcontrol.is())
469 css::uno::Reference<css::io::XSeekable> xSeek(xInputStream, UNO_QUERY);
470 if (xSeek.is())
471 xSeek->seek(0);
473 // we want to be notified when the processing is done...
474 m_tcontrol->addListener(new XSLTFilterStreamListener(*this));
476 // connect input to transformer
477 m_tcontrol->setInputStream(xInputStream);
479 // create pipe
480 css::uno::Reference<XOutputStream> pipeout =
481 Pipe::create(m_xContext);
483 //connect transformer to pipe
484 m_tcontrol->setOutputStream(pipeout);
486 // connect pipe to sax parser
487 InputSource aInput;
488 aInput.sSystemId = aURL;
489 aInput.sPublicId = aURL;
490 aInput.aInputStream.set(pipeout, UNO_QUERY);
492 // transform
493 m_tcontrol->start();
494 TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0};
495 osl::Condition::Result result(m_cTransformed.wait(&timeout));
496 while (osl::Condition::result_timeout == result) {
497 if (xInterActionHandler.is()) {
498 Sequence<Any> excArgs(0);
499 css::ucb::InteractiveAugmentedIOException exc(
500 u"Timeout!"_ustr,
501 getXWeak(),
502 InteractionClassification_ERROR,
503 css::ucb::IOErrorCode_GENERAL,
504 excArgs);
505 Any r;
506 r <<= exc;
507 rtl::Reference<::comphelper::OInteractionRequest> pRequest = new ::comphelper::OInteractionRequest(r);
508 rtl::Reference<::comphelper::OInteractionRetry> pRetry = new ::comphelper::OInteractionRetry;
509 rtl::Reference<::comphelper::OInteractionAbort> pAbort = new ::comphelper::OInteractionAbort;
510 pRequest->addContinuation(pRetry);
511 pRequest->addContinuation(pAbort);
512 xInterActionHandler->handle(pRequest);
513 if (pAbort->wasSelected()) {
514 m_bError = true;
515 m_cTransformed.set();
518 result = m_cTransformed.wait(&timeout);
520 if (!m_bError)
521 xFastParser->parseStream( aInput );
522 m_tcontrol->terminate();
523 return !m_bError;
525 catch( const Exception& )
527 // something went wrong
528 TOOLS_WARN_EXCEPTION("filter.xslt", "");
529 return false;
532 else
534 return false;
538 sal_Bool
539 XSLTFilter::exporter(const Sequence<PropertyValue>& aSourceData,
540 const Sequence<OUString>& msUserData)
542 if (msUserData.getLength() < 6)
543 return false;
545 // get interesting values from user data
546 OUString udStyleSheet = rel2abs(msUserData[5]);
548 // read source data
549 // we are especially interested in the output stream
550 // since that is where our xml-writer will push the data
551 // from its data-source interface
552 OUString aName, sURL;
553 OUString aDoctypePublic;
554 // css::uno::Reference<XOutputStream> rOutputStream;
555 sal_Int32 nLength = aSourceData.getLength();
556 for (sal_Int32 i = 0; i < nLength; i++)
558 aName = aSourceData[i].Name;
559 if ( aName == "DocType_Public" )
560 aSourceData[i].Value >>= aDoctypePublic;
561 else if ( aName == "OutputStream" )
562 aSourceData[i].Value >>= m_rOutputStream;
563 else if ( aName == "URL" )
564 aSourceData[i].Value >>= sURL;
567 if (!getDelegate().is())
569 // get the document writer
570 setDelegate(css::uno::Reference<XExtendedDocumentHandler>(
571 Writer::create(m_xContext),
572 UNO_QUERY_THROW));
575 // create transformer
576 INetURLObject ineturl(sURL);
577 ineturl.removeSegment();
578 m_aExportBaseUrl = ineturl.GetMainURL(INetURLObject::DecodeMechanism::NONE);
579 Sequence<Any> args{ Any(NamedValue(u"StylesheetURL"_ustr, Any(expandUrl(udStyleSheet)))),
580 Any(NamedValue(u"TargetURL"_ustr, Any(sURL))),
581 Any(NamedValue(u"DoctypePublic"_ustr, Any(aDoctypePublic))),
582 Any(NamedValue(u"TargetBaseURL"_ustr, Any(m_aExportBaseUrl))) };
583 m_tcontrol = impl_createTransformer(msUserData[1], args);
585 OSL_ASSERT(m_rOutputStream.is());
586 OSL_ASSERT(m_tcontrol.is());
587 if (m_tcontrol.is() && m_rOutputStream.is())
589 // we want to be notified when the processing is done...
590 m_tcontrol->addListener(new XSLTFilterStreamListener(*this));
592 // create pipe
593 css::uno::Reference<XOutputStream> pipeout =
594 Pipe::create(m_xContext);
595 css::uno::Reference<XInputStream> pipein(pipeout, UNO_QUERY);
597 // connect sax writer to pipe
598 css::uno::Reference<XActiveDataSource> xmlsource(getDelegate(),
599 UNO_QUERY);
600 xmlsource->setOutputStream(pipeout);
602 // connect pipe to transformer
603 m_tcontrol->setInputStream(pipein);
605 // connect transformer to output
606 m_tcontrol->setOutputStream(m_rOutputStream);
608 // we will start receiving events after returning 'true'.
609 // we will start the transformation as soon as we receive the startDocument
610 // event.
611 return true;
613 else
615 return false;
619 // for the DocumentHandler implementation, we just proxy the
620 // events to the XML writer that we created upon the output stream
621 // that was provided by the XMLFilterAdapter
622 void
623 XSLTFilter::startDocument()
625 ExtendedDocumentHandlerAdapter::startDocument();
626 m_tcontrol->start();
629 void
630 XSLTFilter::endDocument()
632 ExtendedDocumentHandlerAdapter::endDocument();
633 // wait for the transformer to finish
634 m_cTransformed.wait();
635 m_tcontrol->terminate();
636 if (m_bError || m_bTerminated)
637 throw RuntimeException();
643 // Component management
645 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
646 filter_XSLTFilter_get_implementation(
647 css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
649 return cppu::acquire(new XSLT::XSLTFilter(context));
652 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */