1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 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/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 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 . 18 */ 19 20 21 #include <cppuhelper/factory.hxx> 22 #include <cppuhelper/implbase.hxx> 23 24 #include <sax/tools/documenthandleradapter.hxx> 25 26 #include <osl/diagnose.h> 27 #include <osl/time.h> 28 #include <osl/conditn.hxx> 29 #include <rtl/strbuf.hxx> 30 #include <tools/urlobj.hxx> 31 #include <tools/diagnose_ex.h> 32 #include <sal/log.hxx> 33 34 #include <comphelper/interaction.hxx> 35 #include <comphelper/processfactory.hxx> 36 37 #include <com/sun/star/lang/XComponent.hpp> 38 #include <com/sun/star/lang/EventObject.hpp> 39 #include <com/sun/star/lang/XSingleServiceFactory.hpp> 40 41 #include <com/sun/star/uno/Any.hxx> 42 43 #include <com/sun/star/beans/PropertyValue.hpp> 44 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/SAXException.hpp> 51 #include <com/sun/star/xml/sax/Writer.hpp> 52 #include <com/sun/star/xml/XImportFilter.hpp> 53 #include <com/sun/star/xml/XExportFilter.hpp> 54 55 #include <com/sun/star/util/theMacroExpander.hpp> 56 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/XActiveDataSink.hpp> 62 #include <com/sun/star/io/XActiveDataControl.hpp> 63 #include <com/sun/star/io/XSeekable.hpp> 64 #include <com/sun/star/io/XStreamListener.hpp> 65 #include <com/sun/star/util/PathSubstitution.hpp> 66 #include <com/sun/star/util/XStringSubstitution.hpp> 67 #include <com/sun/star/beans/NamedValue.hpp> 68 #include <com/sun/star/task/XInteractionHandler.hpp> 69 #include <com/sun/star/task/XInteractionRequest.hpp> 70 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> 71 #include <com/sun/star/xml/xslt/XSLT2Transformer.hpp> 72 #include <com/sun/star/xml/xslt/XSLTTransformer.hpp> 73 74 #include <xmloff/attrlist.hxx> 75 76 #include "LibXSLTTransformer.hxx" 77 78 #define TRANSFORMATION_TIMEOUT_SEC 60 79 80 using namespace ::cppu; 81 using namespace ::osl; 82 using namespace ::sax; 83 using namespace ::com::sun::star::beans; 84 using namespace ::com::sun::star::io; 85 using namespace ::com::sun::star::uno; 86 using namespace ::com::sun::star::lang; 87 using namespace ::com::sun::star::registry; 88 using namespace ::com::sun::star::xml; 89 using namespace ::com::sun::star::xml::sax; 90 using namespace ::com::sun::star::util; 91 using namespace ::com::sun::star::task; 92 93 namespace XSLT 94 { 95 /* 96 * XSLTFilter reads flat XML streams from the XML filter framework and passes 97 * them to an XSLT transformation service. XSLT transformation errors are 98 * reported to XSLTFilter. 99 * 100 * Currently, our transformation service is libxslt based, so it 101 * only supports XSLT 1.0. There is a possibility to use XSLT 2.0 102 * supporting service from an extension for a specific filter; the 103 * service must support com.sun.star.xml.xslt.XSLT2Transformer. 104 */ 105 class XSLTFilter : public WeakImplHelper<XImportFilter, XExportFilter, 106 XStreamListener, ExtendedDocumentHandlerAdapter> 107 { 108 private: 109 110 // the UNO ServiceFactory 111 css::uno::Reference<XComponentContext> m_xContext; 112 113 // DocumentHandler interface of the css::xml::sax::Writer service 114 css::uno::Reference<XOutputStream> m_rOutputStream; 115 116 css::uno::Reference<xslt::XXSLTTransformer> m_tcontrol; 117 118 osl::Condition m_cTransformed; 119 bool m_bTerminated; 120 bool m_bError; 121 122 OUString m_aExportBaseUrl; 123 124 OUString 125 rel2abs(const OUString&); 126 OUString 127 expandUrl(const OUString&); 128 129 css::uno::Reference<xslt::XXSLTTransformer> impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs); 130 131 public: 132 133 // ctor... 134 explicit XSLTFilter(const css::uno::Reference<XComponentContext> &r); 135 136 // XStreamListener 137 virtual void SAL_CALL 138 error(const Any& a) override; 139 virtual void SAL_CALL 140 closed() override; 141 virtual void SAL_CALL 142 terminated() override; 143 virtual void SAL_CALL 144 started() override; 145 virtual void SAL_CALL 146 disposing(const EventObject& e) override; 147 148 // XImportFilter 149 virtual sal_Bool SAL_CALL 150 importer(const Sequence<PropertyValue>& aSourceData, const css::uno::Reference< 151 XDocumentHandler>& xHandler, 152 const Sequence<OUString>& msUserData) override; 153 154 // XExportFilter 155 virtual sal_Bool SAL_CALL 156 exporter(const Sequence<PropertyValue>& aSourceData, const Sequence< 157 OUString>& msUserData) override; 158 159 // XDocumentHandler 160 virtual void SAL_CALL 161 startDocument() override; 162 virtual void SAL_CALL 163 endDocument() override; 164 }; 165 XSLTFilter(const css::uno::Reference<XComponentContext> & r)166 XSLTFilter::XSLTFilter(const css::uno::Reference<XComponentContext> &r): 167 m_xContext(r), m_bTerminated(false), m_bError(false) 168 {} 169 170 void disposing(const EventObject &)171 XSLTFilter::disposing(const EventObject&) 172 { 173 } 174 175 OUString expandUrl(const OUString & sUrl)176 XSLTFilter::expandUrl(const OUString& sUrl) 177 { 178 OUString sExpandedUrl; 179 try 180 { 181 css::uno::Reference<XMacroExpander> 182 xMacroExpander = theMacroExpander::get(m_xContext); 183 sExpandedUrl = xMacroExpander->expandMacros(sUrl); 184 sal_Int32 nPos = sExpandedUrl.indexOf( "vnd.sun.star.expand:" ); 185 if (nPos != -1) 186 sExpandedUrl = sExpandedUrl.copy(nPos + 20); 187 } 188 catch (const Exception&) 189 { 190 } 191 return sExpandedUrl; 192 } 193 194 css::uno::Reference<xslt::XXSLTTransformer> impl_createTransformer(const OUString & rTransformer,const Sequence<Any> & rArgs)195 XSLTFilter::impl_createTransformer(const OUString& rTransformer, const Sequence<Any>& rArgs) 196 { 197 css::uno::Reference<xslt::XXSLTTransformer> xTransformer; 198 199 // check if the filter needs XSLT-2.0-capable transformer 200 // COMPATIBILITY: libreoffice 3.5/3.6 used to save the impl. 201 // name of the XSLT 2.0 transformation service there, so check 202 // for that too (it is sufficient to check that there is _a_ 203 // service name there) 204 if (rTransformer.toBoolean() || rTransformer.startsWith("com.sun.")) 205 { 206 try 207 { 208 xTransformer = xslt::XSLT2Transformer::create(m_xContext, rArgs); 209 } 210 catch (const Exception&) 211 { 212 // TODO: put a dialog telling about the need to install 213 // xslt2-transformer extension here 214 SAL_WARN("filter.xslt", "could not create XSLT 2.0 transformer"); 215 throw; 216 } 217 } 218 219 // instantiation of XSLT 2.0 transformer service failed, or the 220 // filter does not need it 221 if (!xTransformer.is()) 222 { 223 xTransformer = xslt::XSLTTransformer::create(m_xContext, rArgs); 224 } 225 226 return xTransformer; 227 } 228 229 void started()230 XSLTFilter::started() 231 { 232 m_cTransformed.reset(); 233 } 234 void error(const Any & a)235 XSLTFilter::error(const Any& a) 236 { 237 SAL_WARN("filter.xslt", "XSLTFilter::error was called: " << exceptionToString(a)); 238 m_bError = true; 239 m_cTransformed.set(); 240 } 241 void closed()242 XSLTFilter::closed() 243 { 244 m_cTransformed.set(); 245 } 246 void terminated()247 XSLTFilter::terminated() 248 { 249 m_bTerminated = true; 250 m_cTransformed.set(); 251 } 252 253 OUString rel2abs(const OUString & s)254 XSLTFilter::rel2abs(const OUString& s) 255 { 256 257 css::uno::Reference<XStringSubstitution> 258 subs(css::util::PathSubstitution::create(m_xContext)); 259 OUString aWorkingDir(subs->getSubstituteVariableValue( "$(progurl)" )); 260 INetURLObject aObj(aWorkingDir); 261 aObj.setFinalSlash(); 262 bool bWasAbsolute; 263 INetURLObject aURL = aObj.smartRel2Abs(s, bWasAbsolute, false, 264 INetURLObject::EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, true); 265 return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); 266 } 267 268 sal_Bool importer(const Sequence<PropertyValue> & aSourceData,const css::uno::Reference<XDocumentHandler> & xHandler,const Sequence<OUString> & msUserData)269 XSLTFilter::importer(const Sequence<PropertyValue>& aSourceData, 270 const css::uno::Reference<XDocumentHandler>& xHandler, const Sequence< 271 OUString>& msUserData) 272 { 273 if (msUserData.getLength() < 5) 274 return false; 275 276 OUString udStyleSheet = rel2abs(msUserData[4]); 277 278 // get information from media descriptor 279 // the input stream that represents the imported file 280 // is most important here since we need to supply it to 281 // the sax parser that drives the supplied document handler 282 sal_Int32 nLength = aSourceData.getLength(); 283 OUString aName, aURL; 284 css::uno::Reference<XInputStream> xInputStream; 285 css::uno::Reference<XInteractionHandler> xInterActionHandler; 286 for (sal_Int32 i = 0; i < nLength; i++) 287 { 288 aName = aSourceData[i].Name; 289 Any value = aSourceData[i].Value; 290 if ( aName == "InputStream" ) 291 value >>= xInputStream; 292 else if ( aName == "URL" ) 293 value >>= aURL; 294 else if ( aName == "InteractionHandler" ) 295 value >>= xInterActionHandler; 296 } 297 OSL_ASSERT(xInputStream.is()); 298 if (!xInputStream.is()) 299 return false; 300 301 // create SAX parser that will read the document file 302 // and provide events to xHandler passed to this call 303 css::uno::Reference<XParser> xSaxParser = Parser::create(m_xContext); 304 305 // create transformer 306 Sequence<Any> args(3); 307 NamedValue nv; 308 309 nv.Name = "StylesheetURL"; 310 nv.Value <<= expandUrl(udStyleSheet); 311 args[0] <<= nv; 312 nv.Name = "SourceURL"; 313 nv.Value <<= aURL; 314 args[1] <<= nv; 315 nv.Name = "SourceBaseURL"; 316 nv.Value <<= INetURLObject(aURL).getBase(); 317 args[2] <<= nv; 318 319 m_tcontrol = impl_createTransformer(msUserData[1], args); 320 321 OSL_ASSERT(xHandler.is()); 322 OSL_ASSERT(xInputStream.is()); 323 OSL_ASSERT(m_tcontrol.is()); 324 if (xHandler.is() && xInputStream.is() && m_tcontrol.is()) 325 { 326 try 327 { 328 css::uno::Reference<css::io::XSeekable> xSeek(xInputStream, UNO_QUERY); 329 if (xSeek.is()) 330 xSeek->seek(0); 331 332 // we want to be notified when the processing is done... 333 m_tcontrol->addListener(css::uno::Reference<XStreamListener> ( 334 this)); 335 336 // connect input to transformer 337 m_tcontrol->setInputStream(xInputStream); 338 339 // create pipe 340 css::uno::Reference<XOutputStream> pipeout = 341 Pipe::create(m_xContext); 342 css::uno::Reference<XInputStream> pipein(pipeout, UNO_QUERY); 343 344 //connect transformer to pipe 345 m_tcontrol->setOutputStream(pipeout); 346 347 // connect pipe to sax parser 348 InputSource aInput; 349 aInput.sSystemId = aURL; 350 aInput.sPublicId = aURL; 351 aInput.aInputStream = pipein; 352 353 // set doc handler 354 xSaxParser->setDocumentHandler(xHandler); 355 css::uno::Reference< css::xml::sax::XFastParser > xFastParser = dynamic_cast< 356 css::xml::sax::XFastParser* >( xHandler.get() ); 357 358 // transform 359 m_tcontrol->start(); 360 TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0}; 361 osl::Condition::Result result(m_cTransformed.wait(&timeout)); 362 while (osl::Condition::result_timeout == result) { 363 if (xInterActionHandler.is()) { 364 Sequence<Any> excArgs(0); 365 css::ucb::InteractiveAugmentedIOException exc( 366 "Timeout!", 367 static_cast< OWeakObject * >( this ), 368 InteractionClassification_ERROR, 369 css::ucb::IOErrorCode_GENERAL, 370 excArgs); 371 Any r; 372 r <<= exc; 373 ::comphelper::OInteractionRequest* pRequest = new ::comphelper::OInteractionRequest(r); 374 css::uno::Reference< XInteractionRequest > xRequest(pRequest); 375 ::comphelper::OInteractionRetry* pRetry = new ::comphelper::OInteractionRetry; 376 ::comphelper::OInteractionAbort* pAbort = new ::comphelper::OInteractionAbort; 377 pRequest->addContinuation(pRetry); 378 pRequest->addContinuation(pAbort); 379 xInterActionHandler->handle(xRequest); 380 if (pAbort->wasSelected()) { 381 m_bError = true; 382 m_cTransformed.set(); 383 } 384 } 385 result = m_cTransformed.wait(&timeout); 386 }; 387 if (!m_bError) { 388 if( xFastParser.is() ) 389 xFastParser->parseStream( aInput ); 390 else 391 xSaxParser->parseStream( aInput ); 392 } 393 m_tcontrol->terminate(); 394 return !m_bError; 395 } 396 catch( const Exception& ) 397 { 398 // something went wrong 399 TOOLS_WARN_EXCEPTION("filter.xslt", ""); 400 return false; 401 } 402 } 403 else 404 { 405 return false; 406 } 407 } 408 409 sal_Bool exporter(const Sequence<PropertyValue> & aSourceData,const Sequence<OUString> & msUserData)410 XSLTFilter::exporter(const Sequence<PropertyValue>& aSourceData, 411 const Sequence<OUString>& msUserData) 412 { 413 if (msUserData.getLength() < 6) 414 return false; 415 416 // get interesting values from user data 417 OUString udStyleSheet = rel2abs(msUserData[5]); 418 419 // read source data 420 // we are especially interested in the output stream 421 // since that is where our xml-writer will push the data 422 // from its data-source interface 423 OUString aName, sURL; 424 OUString aDoctypePublic; 425 // css::uno::Reference<XOutputStream> rOutputStream; 426 sal_Int32 nLength = aSourceData.getLength(); 427 for (sal_Int32 i = 0; i < nLength; i++) 428 { 429 aName = aSourceData[i].Name; 430 if ( aName == "DocType_Public" ) 431 aSourceData[i].Value >>= aDoctypePublic; 432 else if ( aName == "OutputStream" ) 433 aSourceData[i].Value >>= m_rOutputStream; 434 else if ( aName == "URL" ) 435 aSourceData[i].Value >>= sURL; 436 } 437 438 if (!getDelegate().is()) 439 { 440 // get the document writer 441 setDelegate(css::uno::Reference<XExtendedDocumentHandler>( 442 Writer::create(m_xContext), 443 UNO_QUERY_THROW)); 444 } 445 446 // create transformer 447 Sequence<Any> args(4); 448 NamedValue nv; 449 nv.Name = "StylesheetURL"; 450 nv.Value <<= expandUrl(udStyleSheet); 451 args[0] <<= nv; 452 nv.Name = "TargetURL"; 453 nv.Value <<= sURL; 454 args[1] <<= nv; 455 nv.Name = "DoctypePublic"; 456 nv.Value <<= aDoctypePublic; 457 args[2] <<= nv; 458 nv.Name = "TargetBaseURL"; 459 INetURLObject ineturl(sURL); 460 ineturl.removeSegment(); 461 m_aExportBaseUrl = ineturl.GetMainURL(INetURLObject::DecodeMechanism::NONE); 462 nv.Value <<= m_aExportBaseUrl; 463 args[3] <<= nv; 464 465 m_tcontrol = impl_createTransformer(msUserData[1], args); 466 467 OSL_ASSERT(m_rOutputStream.is()); 468 OSL_ASSERT(m_tcontrol.is()); 469 if (m_tcontrol.is() && m_rOutputStream.is()) 470 { 471 // we want to be notified when the processing is done... 472 m_tcontrol->addListener(css::uno::Reference<XStreamListener> (this)); 473 474 // create pipe 475 css::uno::Reference<XOutputStream> pipeout = 476 Pipe::create(m_xContext); 477 css::uno::Reference<XInputStream> pipein(pipeout, UNO_QUERY); 478 479 // connect sax writer to pipe 480 css::uno::Reference<XActiveDataSource> xmlsource(getDelegate(), 481 UNO_QUERY); 482 xmlsource->setOutputStream(pipeout); 483 484 // connect pipe to transformer 485 m_tcontrol->setInputStream(pipein); 486 487 // connect transformer to output 488 m_tcontrol->setOutputStream(m_rOutputStream); 489 490 // we will start receiving events after returning 'true'. 491 // we will start the transformation as soon as we receive the startDocument 492 // event. 493 return true; 494 } 495 else 496 { 497 return false; 498 } 499 } 500 501 // for the DocumentHandler implementation, we just proxy the 502 // events to the XML writer that we created upon the output stream 503 // that was provided by the XMLFilterAdapter 504 void startDocument()505 XSLTFilter::startDocument() 506 { 507 ExtendedDocumentHandlerAdapter::startDocument(); 508 m_tcontrol->start(); 509 } 510 511 void endDocument()512 XSLTFilter::endDocument() 513 { 514 ExtendedDocumentHandlerAdapter::endDocument(); 515 // wait for the transformer to finish 516 m_cTransformed.wait(); 517 m_tcontrol->terminate(); 518 if (m_bError || m_bTerminated) 519 throw RuntimeException(); 520 } 521 522 523 // Component management 524 525 #define FILTER_SERVICE_NAME "com.sun.star.documentconversion.XSLTFilter" 526 #define FILTER_IMPL_NAME "com.sun.star.comp.documentconversion.XSLTFilter" 527 #define TRANSFORMER_SERVICE_NAME "com.sun.star.xml.xslt.XSLTTransformer" 528 #define TRANSFORMER_IMPL_NAME "com.sun.star.comp.documentconversion.LibXSLTTransformer" 529 530 static css::uno::Reference<XInterface> CreateTransformerInstance(const css::uno::Reference<XMultiServiceFactory> & r)531 CreateTransformerInstance(const css::uno::Reference<XMultiServiceFactory> &r) 532 { 533 return css::uno::Reference<XInterface> (static_cast<OWeakObject *>(new LibXSLTTransformer( comphelper::getComponentContext(r) ))); 534 } 535 536 static css::uno::Reference<XInterface> CreateFilterInstance(const css::uno::Reference<XMultiServiceFactory> & r)537 CreateFilterInstance(const css::uno::Reference<XMultiServiceFactory> &r) 538 { 539 return css::uno::Reference<XInterface> (static_cast<OWeakObject *>(new XSLTFilter( comphelper::getComponentContext(r) ))); 540 } 541 542 } 543 544 using namespace XSLT; 545 546 extern "C" 547 { xsltfilter_component_getFactory(const sal_Char * pImplName,void * pServiceManager,void *)548 SAL_DLLPUBLIC_EXPORT void * xsltfilter_component_getFactory(const sal_Char * pImplName, 549 void * pServiceManager, void * /* pRegistryKey */) 550 { 551 void * pRet = nullptr; 552 553 if (pServiceManager) 554 { 555 if (rtl_str_compare(pImplName, FILTER_IMPL_NAME) == 0) 556 { 557 Sequence<OUString> serviceNames { FILTER_SERVICE_NAME }; 558 559 css::uno::Reference<XSingleServiceFactory> 560 xFactory( 561 createSingleFactory( 562 static_cast<XMultiServiceFactory *> (pServiceManager), 563 OUString::createFromAscii( 564 pImplName), 565 CreateFilterInstance, 566 serviceNames)); 567 568 if (xFactory.is()) 569 { 570 xFactory->acquire(); 571 pRet = xFactory.get(); 572 } 573 } 574 else if (rtl_str_compare(pImplName, TRANSFORMER_IMPL_NAME) == 0) 575 { 576 Sequence<OUString> serviceNames { TRANSFORMER_SERVICE_NAME }; 577 css::uno::Reference<XSingleServiceFactory> 578 xFactory( 579 createSingleFactory( 580 static_cast<XMultiServiceFactory *> (pServiceManager), 581 OUString::createFromAscii( 582 pImplName), 583 CreateTransformerInstance, 584 serviceNames)); 585 586 if (xFactory.is()) 587 { 588 xFactory->acquire(); 589 pRet = xFactory.get(); 590 } 591 592 } 593 } 594 return pRet; 595 } 596 597 } // extern "C" 598 599 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 600