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::registry
;
81 using namespace ::com::sun::star::xml
;
82 using namespace ::com::sun::star::xml::sax
;
83 using namespace ::com::sun::star::util
;
84 using namespace ::com::sun::star::task
;
91 class XSLTFilterStreamListener
: public WeakImplHelper
<XStreamListener
>
94 XSLTFilterStreamListener(XSLTFilter
& rParent
) : m_rParent(rParent
) {}
98 error(const Any
& a
) override
;
101 virtual void SAL_CALL
102 terminated() override
;
103 virtual void SAL_CALL
105 virtual void SAL_CALL
106 disposing(const EventObject
& e
) override
;
108 XSLTFilter
& m_rParent
;
112 * XSLTFilter reads flat XML streams from the XML filter framework and passes
113 * them to an XSLT transformation service. XSLT transformation errors are
114 * reported to XSLTFilter.
116 * Currently, our transformation service is libxslt based, so it
117 * only supports XSLT 1.0. There is a possibility to use XSLT 2.0
118 * supporting service from an extension for a specific filter; the
119 * service must support com.sun.star.xml.xslt.XSLT2Transformer.
121 class XSLTFilter
: public WeakImplHelper
<XImportFilter
, XImportFilter2
, XExportFilter
,
122 ExtendedDocumentHandlerAdapter
, XServiceInfo
>
124 friend class XSLTFilterStreamListener
;
127 // the UNO ServiceFactory
128 css::uno::Reference
<XComponentContext
> m_xContext
;
130 // DocumentHandler interface of the css::xml::sax::Writer service
131 css::uno::Reference
<XOutputStream
> m_rOutputStream
;
133 css::uno::Reference
<xslt::XXSLTTransformer
> m_tcontrol
;
135 osl::Condition m_cTransformed
;
139 OUString m_aExportBaseUrl
;
142 rel2abs(const OUString
&);
144 expandUrl(const OUString
&);
146 css::uno::Reference
<xslt::XXSLTTransformer
> impl_createTransformer(const OUString
& rTransformer
, const Sequence
<Any
>& rArgs
);
151 explicit XSLTFilter(css::uno::Reference
<XComponentContext
> x
);
154 virtual sal_Bool SAL_CALL
supportsService(const OUString
& sServiceName
) override
;
155 virtual OUString SAL_CALL
getImplementationName() override
;
156 virtual css::uno::Sequence
< OUString
> SAL_CALL
getSupportedServiceNames() override
;
159 virtual sal_Bool SAL_CALL
160 importer(const Sequence
<PropertyValue
>& aSourceData
, const css::uno::Reference
<
161 XDocumentHandler
>& xHandler
,
162 const Sequence
<OUString
>& msUserData
) override
;
165 virtual sal_Bool SAL_CALL
166 importer(const Sequence
<PropertyValue
>& aSourceData
, const css::uno::Reference
<
167 XFastParser
>& xFastParser
,
168 const Sequence
<OUString
>& msUserData
) override
;
171 virtual sal_Bool SAL_CALL
172 exporter(const Sequence
<PropertyValue
>& aSourceData
, const Sequence
<
173 OUString
>& msUserData
) override
;
176 virtual void SAL_CALL
177 startDocument() override
;
178 virtual void SAL_CALL
179 endDocument() override
;
184 XSLTFilter::XSLTFilter(css::uno::Reference
<XComponentContext
> x
):
185 m_xContext(std::move(x
)), m_bTerminated(false), m_bError(false)
189 XSLTFilterStreamListener::disposing(const EventObject
&)
194 sal_Bool
XSLTFilter::supportsService(const OUString
& sServiceName
)
196 return cppu::supportsService(this, sServiceName
);
198 OUString
XSLTFilter::getImplementationName()
200 return "com.sun.star.comp.documentconversion.XSLTFilter";
202 css::uno::Sequence
< OUString
> XSLTFilter::getSupportedServiceNames()
204 return { "com.sun.star.documentconversion.XSLTFilter" };
208 XSLTFilter::expandUrl(const OUString
& sUrl
)
210 OUString
sPreparedURL(sUrl
);
211 if (sPreparedURL
.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &sPreparedURL
))
213 sPreparedURL
= rtl::Uri::decode(sPreparedURL
, rtl_UriDecodeWithCharset
,
214 RTL_TEXTENCODING_UTF8
);
215 css::uno::Reference
<XMacroExpander
>
216 xMacroExpander
= theMacroExpander::get(m_xContext
);
217 sPreparedURL
= xMacroExpander
->expandMacros(sPreparedURL
);
222 css::uno::Reference
<xslt::XXSLTTransformer
>
223 XSLTFilter::impl_createTransformer(const OUString
& rTransformer
, const Sequence
<Any
>& rArgs
)
225 css::uno::Reference
<xslt::XXSLTTransformer
> xTransformer
;
227 // check if the filter needs XSLT-2.0-capable transformer
228 // COMPATIBILITY: libreoffice 3.5/3.6 used to save the impl.
229 // name of the XSLT 2.0 transformation service there, so check
230 // for that too (it is sufficient to check that there is _a_
231 // service name there)
232 if (rTransformer
.toBoolean() || rTransformer
.startsWith("com.sun."))
237 m_xContext
->getServiceManager()->createInstanceWithArgumentsAndContext(
238 "com.sun.star.xml.xslt.XSLT2Transformer", rArgs
, m_xContext
),
239 css::uno::UNO_QUERY_THROW
);
241 catch (const Exception
&)
243 // TODO: put a dialog telling about the need to install
244 // xslt2-transformer extension here
245 SAL_WARN("filter.xslt", "could not create XSLT 2.0 transformer");
251 xTransformer
= xslt::XSLTTransformer::create(m_xContext
, rArgs
);
258 XSLTFilterStreamListener::started()
260 m_rParent
.m_cTransformed
.reset();
263 XSLTFilterStreamListener::error(const Any
& a
)
265 SAL_WARN("filter.xslt", "XSLTFilter::error was called: " << exceptionToString(a
));
266 m_rParent
.m_bError
= true;
267 m_rParent
.m_cTransformed
.set();
270 XSLTFilterStreamListener::closed()
272 m_rParent
.m_cTransformed
.set();
275 XSLTFilterStreamListener::terminated()
277 m_rParent
.m_bTerminated
= true;
278 m_rParent
.m_cTransformed
.set();
282 XSLTFilter::rel2abs(const OUString
& s
)
285 css::uno::Reference
<XStringSubstitution
>
286 subs(css::util::PathSubstitution::create(m_xContext
));
287 OUString
aWorkingDir(subs
->getSubstituteVariableValue( "$(progurl)" ));
288 INetURLObject
aObj(aWorkingDir
);
289 aObj
.setFinalSlash();
291 INetURLObject aURL
= aObj
.smartRel2Abs(s
, bWasAbsolute
, false,
292 INetURLObject::EncodeMechanism::WasEncoded
, RTL_TEXTENCODING_UTF8
, true);
293 return aURL
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
297 XSLTFilter::importer(const Sequence
<PropertyValue
>& aSourceData
,
298 const css::uno::Reference
<XDocumentHandler
>& xHandler
, const Sequence
<
299 OUString
>& msUserData
)
301 if (msUserData
.getLength() < 5)
304 OUString udStyleSheet
= rel2abs(msUserData
[4]);
306 // get information from media descriptor
307 // the input stream that represents the imported file
308 // is most important here since we need to supply it to
309 // the sax parser that drives the supplied document handler
310 OUString aName
, aURL
;
311 css::uno::Reference
<XInputStream
> xInputStream
;
312 css::uno::Reference
<XInteractionHandler
> xInterActionHandler
;
313 for (const auto& sourceDataItem
: aSourceData
)
315 aName
= sourceDataItem
.Name
;
316 Any value
= sourceDataItem
.Value
;
317 if ( aName
== "InputStream" )
318 value
>>= xInputStream
;
319 else if ( aName
== "URL" )
321 else if ( aName
== "InteractionHandler" )
322 value
>>= xInterActionHandler
;
324 OSL_ASSERT(xInputStream
.is());
325 if (!xInputStream
.is())
328 // create transformer
329 Sequence
<Any
> args
{ Any(NamedValue("StylesheetURL", Any(expandUrl(udStyleSheet
)))),
330 Any(NamedValue("SourceURL", Any(aURL
))),
331 Any(NamedValue("SourceBaseURL", Any(INetURLObject(aURL
).getBase()))) };
332 m_tcontrol
= impl_createTransformer(msUserData
[1], args
);
334 OSL_ASSERT(xHandler
.is());
335 OSL_ASSERT(xInputStream
.is());
336 OSL_ASSERT(m_tcontrol
.is());
337 if (xHandler
.is() && xInputStream
.is() && m_tcontrol
.is())
341 css::uno::Reference
<css::io::XSeekable
> xSeek(xInputStream
, UNO_QUERY
);
345 // we want to be notified when the processing is done...
346 m_tcontrol
->addListener(new XSLTFilterStreamListener(*this));
348 // connect input to transformer
349 m_tcontrol
->setInputStream(xInputStream
);
352 css::uno::Reference
<XOutputStream
> pipeout
=
353 Pipe::create(m_xContext
);
354 css::uno::Reference
<XInputStream
> pipein(pipeout
, UNO_QUERY
);
356 //connect transformer to pipe
357 m_tcontrol
->setOutputStream(pipeout
);
359 // connect pipe to sax parser
361 aInput
.sSystemId
= aURL
;
362 aInput
.sPublicId
= aURL
;
363 aInput
.aInputStream
= pipein
;
365 css::uno::Reference
< css::xml::sax::XFastParser
> xFastParser(
366 xHandler
, css::uno::UNO_QUERY
);
370 TimeValue timeout
= { TRANSFORMATION_TIMEOUT_SEC
, 0};
371 osl::Condition::Result
result(m_cTransformed
.wait(&timeout
));
372 while (osl::Condition::result_timeout
== result
) {
373 if (xInterActionHandler
.is()) {
374 Sequence
<Any
> excArgs(0);
375 css::ucb::InteractiveAugmentedIOException
exc(
378 InteractionClassification_ERROR
,
379 css::ucb::IOErrorCode_GENERAL
,
383 rtl::Reference
<::comphelper::OInteractionRequest
> pRequest
= new ::comphelper::OInteractionRequest(r
);
384 rtl::Reference
<::comphelper::OInteractionRetry
> pRetry
= new ::comphelper::OInteractionRetry
;
385 rtl::Reference
<::comphelper::OInteractionAbort
> pAbort
= new ::comphelper::OInteractionAbort
;
386 pRequest
->addContinuation(pRetry
);
387 pRequest
->addContinuation(pAbort
);
388 xInterActionHandler
->handle(pRequest
);
389 if (pAbort
->wasSelected()) {
391 m_cTransformed
.set();
394 result
= m_cTransformed
.wait(&timeout
);
397 if( xFastParser
.is() )
398 xFastParser
->parseStream( aInput
);
401 // create SAX parser that will read the document file
402 // and provide events to xHandler passed to this call
403 css::uno::Reference
<XParser
> xSaxParser
= Parser::create(m_xContext
);
405 xSaxParser
->setDocumentHandler(xHandler
);
406 xSaxParser
->parseStream( aInput
);
409 m_tcontrol
->terminate();
412 catch( const Exception
& )
414 // something went wrong
415 TOOLS_WARN_EXCEPTION("filter.xslt", "");
426 XSLTFilter::importer(const Sequence
<PropertyValue
>& aSourceData
,
427 const css::uno::Reference
<XFastParser
>& xFastParser
, const Sequence
<
428 OUString
>& msUserData
)
430 if (msUserData
.getLength() < 5)
433 OUString udStyleSheet
= rel2abs(msUserData
[4]);
435 // get information from media descriptor
436 // the input stream that represents the imported file
437 // is most important here since we need to supply it to
438 // the sax parser that drives the supplied document handler
439 sal_Int32 nLength
= aSourceData
.getLength();
440 OUString aName
, aURL
;
441 css::uno::Reference
<XInputStream
> xInputStream
;
442 css::uno::Reference
<XInteractionHandler
> xInterActionHandler
;
443 for (sal_Int32 i
= 0; i
< nLength
; i
++)
445 aName
= aSourceData
[i
].Name
;
446 Any value
= aSourceData
[i
].Value
;
447 if ( aName
== "InputStream" )
448 value
>>= xInputStream
;
449 else if ( aName
== "URL" )
451 else if ( aName
== "InteractionHandler" )
452 value
>>= xInterActionHandler
;
454 OSL_ASSERT(xInputStream
.is());
455 if (!xInputStream
.is())
458 // create transformer
459 Sequence
<Any
> args
{ Any(NamedValue("StylesheetURL", Any(expandUrl(udStyleSheet
)))),
460 Any(NamedValue("SourceURL", Any(aURL
))),
461 Any(NamedValue("SourceBaseURL", Any(INetURLObject(aURL
).getBase()))) };
462 m_tcontrol
= impl_createTransformer(msUserData
[1], args
);
464 assert(xFastParser
.is());
465 OSL_ASSERT(xInputStream
.is());
466 OSL_ASSERT(m_tcontrol
.is());
467 if (xFastParser
.is() && xInputStream
.is() && m_tcontrol
.is())
471 css::uno::Reference
<css::io::XSeekable
> xSeek(xInputStream
, UNO_QUERY
);
475 // we want to be notified when the processing is done...
476 m_tcontrol
->addListener(new XSLTFilterStreamListener(*this));
478 // connect input to transformer
479 m_tcontrol
->setInputStream(xInputStream
);
482 css::uno::Reference
<XOutputStream
> pipeout
=
483 Pipe::create(m_xContext
);
484 css::uno::Reference
<XInputStream
> pipein(pipeout
, UNO_QUERY
);
486 //connect transformer to pipe
487 m_tcontrol
->setOutputStream(pipeout
);
489 // connect pipe to sax parser
491 aInput
.sSystemId
= aURL
;
492 aInput
.sPublicId
= aURL
;
493 aInput
.aInputStream
= pipein
;
497 TimeValue timeout
= { TRANSFORMATION_TIMEOUT_SEC
, 0};
498 osl::Condition::Result
result(m_cTransformed
.wait(&timeout
));
499 while (osl::Condition::result_timeout
== result
) {
500 if (xInterActionHandler
.is()) {
501 Sequence
<Any
> excArgs(0);
502 css::ucb::InteractiveAugmentedIOException
exc(
505 InteractionClassification_ERROR
,
506 css::ucb::IOErrorCode_GENERAL
,
510 rtl::Reference
<::comphelper::OInteractionRequest
> pRequest
= new ::comphelper::OInteractionRequest(r
);
511 rtl::Reference
<::comphelper::OInteractionRetry
> pRetry
= new ::comphelper::OInteractionRetry
;
512 rtl::Reference
<::comphelper::OInteractionAbort
> pAbort
= new ::comphelper::OInteractionAbort
;
513 pRequest
->addContinuation(pRetry
);
514 pRequest
->addContinuation(pAbort
);
515 xInterActionHandler
->handle(pRequest
);
516 if (pAbort
->wasSelected()) {
518 m_cTransformed
.set();
521 result
= m_cTransformed
.wait(&timeout
);
524 xFastParser
->parseStream( aInput
);
525 m_tcontrol
->terminate();
528 catch( const Exception
& )
530 // something went wrong
531 TOOLS_WARN_EXCEPTION("filter.xslt", "");
542 XSLTFilter::exporter(const Sequence
<PropertyValue
>& aSourceData
,
543 const Sequence
<OUString
>& msUserData
)
545 if (msUserData
.getLength() < 6)
548 // get interesting values from user data
549 OUString udStyleSheet
= rel2abs(msUserData
[5]);
552 // we are especially interested in the output stream
553 // since that is where our xml-writer will push the data
554 // from its data-source interface
555 OUString aName
, sURL
;
556 OUString aDoctypePublic
;
557 // css::uno::Reference<XOutputStream> rOutputStream;
558 sal_Int32 nLength
= aSourceData
.getLength();
559 for (sal_Int32 i
= 0; i
< nLength
; i
++)
561 aName
= aSourceData
[i
].Name
;
562 if ( aName
== "DocType_Public" )
563 aSourceData
[i
].Value
>>= aDoctypePublic
;
564 else if ( aName
== "OutputStream" )
565 aSourceData
[i
].Value
>>= m_rOutputStream
;
566 else if ( aName
== "URL" )
567 aSourceData
[i
].Value
>>= sURL
;
570 if (!getDelegate().is())
572 // get the document writer
573 setDelegate(css::uno::Reference
<XExtendedDocumentHandler
>(
574 Writer::create(m_xContext
),
578 // create transformer
579 INetURLObject
ineturl(sURL
);
580 ineturl
.removeSegment();
581 m_aExportBaseUrl
= ineturl
.GetMainURL(INetURLObject::DecodeMechanism::NONE
);
582 Sequence
<Any
> args
{ Any(NamedValue("StylesheetURL", Any(expandUrl(udStyleSheet
)))),
583 Any(NamedValue("TargetURL", Any(sURL
))),
584 Any(NamedValue("DoctypePublic", Any(aDoctypePublic
))),
585 Any(NamedValue("TargetBaseURL", Any(m_aExportBaseUrl
))) };
586 m_tcontrol
= impl_createTransformer(msUserData
[1], args
);
588 OSL_ASSERT(m_rOutputStream
.is());
589 OSL_ASSERT(m_tcontrol
.is());
590 if (m_tcontrol
.is() && m_rOutputStream
.is())
592 // we want to be notified when the processing is done...
593 m_tcontrol
->addListener(new XSLTFilterStreamListener(*this));
596 css::uno::Reference
<XOutputStream
> pipeout
=
597 Pipe::create(m_xContext
);
598 css::uno::Reference
<XInputStream
> pipein(pipeout
, UNO_QUERY
);
600 // connect sax writer to pipe
601 css::uno::Reference
<XActiveDataSource
> xmlsource(getDelegate(),
603 xmlsource
->setOutputStream(pipeout
);
605 // connect pipe to transformer
606 m_tcontrol
->setInputStream(pipein
);
608 // connect transformer to output
609 m_tcontrol
->setOutputStream(m_rOutputStream
);
611 // we will start receiving events after returning 'true'.
612 // we will start the transformation as soon as we receive the startDocument
622 // for the DocumentHandler implementation, we just proxy the
623 // events to the XML writer that we created upon the output stream
624 // that was provided by the XMLFilterAdapter
626 XSLTFilter::startDocument()
628 ExtendedDocumentHandlerAdapter::startDocument();
633 XSLTFilter::endDocument()
635 ExtendedDocumentHandlerAdapter::endDocument();
636 // wait for the transformer to finish
637 m_cTransformed
.wait();
638 m_tcontrol
->terminate();
639 if (m_bError
|| m_bTerminated
)
640 throw RuntimeException();
646 // Component management
648 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
649 filter_XSLTFilter_get_implementation(
650 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const&)
652 return cppu::acquire(new XSLT::XSLTFilter(context
));
655 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */