1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
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
;
90 class XSLTFilterStreamListener
: public WeakImplHelper
<XStreamListener
>
93 XSLTFilterStreamListener(XSLTFilter
& rParent
) : m_rParent(rParent
) {}
97 error(const Any
& a
) override
;
100 virtual void SAL_CALL
101 terminated() override
;
102 virtual void SAL_CALL
104 virtual void SAL_CALL
105 disposing(const EventObject
& e
) override
;
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
;
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
;
138 OUString m_aExportBaseUrl
;
141 rel2abs(const OUString
&);
143 expandUrl(const OUString
&);
145 css::uno::Reference
<xslt::XXSLTTransformer
> impl_createTransformer(const OUString
& rTransformer
, const Sequence
<Any
>& rArgs
);
150 explicit XSLTFilter(css::uno::Reference
<XComponentContext
> x
);
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
;
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
;
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
;
170 virtual sal_Bool SAL_CALL
171 exporter(const Sequence
<PropertyValue
>& aSourceData
, const Sequence
<
172 OUString
>& msUserData
) override
;
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)
188 XSLTFilterStreamListener::disposing(const EventObject
&)
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
};
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
);
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."))
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");
250 xTransformer
= xslt::XSLTTransformer::create(m_xContext
, rArgs
);
257 XSLTFilterStreamListener::started()
259 m_rParent
.m_cTransformed
.reset();
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();
269 XSLTFilterStreamListener::closed()
271 m_rParent
.m_cTransformed
.set();
274 XSLTFilterStreamListener::terminated()
276 m_rParent
.m_bTerminated
= true;
277 m_rParent
.m_cTransformed
.set();
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();
290 INetURLObject aURL
= aObj
.smartRel2Abs(s
, bWasAbsolute
, false,
291 INetURLObject::EncodeMechanism::WasEncoded
, RTL_TEXTENCODING_UTF8
, true);
292 return aURL
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
296 XSLTFilter::importer(const Sequence
<PropertyValue
>& aSourceData
,
297 const css::uno::Reference
<XDocumentHandler
>& xHandler
, const Sequence
<
298 OUString
>& msUserData
)
300 if (msUserData
.getLength() < 5)
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" )
320 else if ( aName
== "InteractionHandler" )
321 value
>>= xInterActionHandler
;
323 OSL_ASSERT(xInputStream
.is());
324 if (!xInputStream
.is())
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
);
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
);
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
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
);
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(
376 InteractionClassification_ERROR
,
377 css::ucb::IOErrorCode_GENERAL
,
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()) {
389 m_cTransformed
.set();
392 result
= m_cTransformed
.wait(&timeout
);
395 if( xFastParser
.is() )
396 xFastParser
->parseStream( aInput
);
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
);
403 xSaxParser
->setDocumentHandler(xHandler
);
404 xSaxParser
->parseStream( aInput
);
407 m_tcontrol
->terminate();
410 catch( const Exception
& )
412 // something went wrong
413 TOOLS_WARN_EXCEPTION("filter.xslt", "");
424 XSLTFilter::importer(const Sequence
<PropertyValue
>& aSourceData
,
425 const css::uno::Reference
<XFastParser
>& xFastParser
, const Sequence
<
426 OUString
>& msUserData
)
428 if (msUserData
.getLength() < 5)
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" )
449 else if ( aName
== "InteractionHandler" )
450 value
>>= xInterActionHandler
;
452 OSL_ASSERT(xInputStream
.is());
453 if (!xInputStream
.is())
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
);
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
);
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
488 aInput
.sSystemId
= aURL
;
489 aInput
.sPublicId
= aURL
;
490 aInput
.aInputStream
.set(pipeout
, UNO_QUERY
);
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(
502 InteractionClassification_ERROR
,
503 css::ucb::IOErrorCode_GENERAL
,
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()) {
515 m_cTransformed
.set();
518 result
= m_cTransformed
.wait(&timeout
);
521 xFastParser
->parseStream( aInput
);
522 m_tcontrol
->terminate();
525 catch( const Exception
& )
527 // something went wrong
528 TOOLS_WARN_EXCEPTION("filter.xslt", "");
539 XSLTFilter::exporter(const Sequence
<PropertyValue
>& aSourceData
,
540 const Sequence
<OUString
>& msUserData
)
542 if (msUserData
.getLength() < 6)
545 // get interesting values from user data
546 OUString udStyleSheet
= rel2abs(msUserData
[5]);
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
),
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));
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(),
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
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
623 XSLTFilter::startDocument()
625 ExtendedDocumentHandlerAdapter::startDocument();
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: */