/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "LibXSLTTransformer.hxx" #define TRANSFORMATION_TIMEOUT_SEC 60 using namespace ::cppu; using namespace ::osl; using namespace ::sax; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::io; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::registry; using namespace ::com::sun::star::xml; using namespace ::com::sun::star::xml::sax; using namespace ::com::sun::star::util; using namespace ::com::sun::star::task; namespace XSLT { namespace { /* * XSLTFilter reads flat XML streams from the XML filter framework and passes * them to an XSLT transformation service. XSLT transformation errors are * reported to XSLTFilter. * * Currently, our transformation service is libxslt based, so it * only supports XSLT 1.0. There is a possibility to use XSLT 2.0 * supporting service from an extension for a specific filter; the * service must support com.sun.star.xml.xslt.XSLT2Transformer. */ class XSLTFilter : public WeakImplHelper { private: // the UNO ServiceFactory css::uno::Reference m_xContext; // DocumentHandler interface of the css::xml::sax::Writer service css::uno::Reference m_rOutputStream; css::uno::Reference m_tcontrol; osl::Condition m_cTransformed; bool m_bTerminated; bool m_bError; OUString m_aExportBaseUrl; OUString rel2abs(const OUString&); OUString expandUrl(const OUString&); css::uno::Reference impl_createTransformer(const OUString& rTransformer, const Sequence& rArgs); public: // ctor... explicit XSLTFilter(const css::uno::Reference &r); // XServiceInfo virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; virtual OUString SAL_CALL getImplementationName() override; virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; // XStreamListener virtual void SAL_CALL error(const Any& a) override; virtual void SAL_CALL closed() override; virtual void SAL_CALL terminated() override; virtual void SAL_CALL started() override; virtual void SAL_CALL disposing(const EventObject& e) override; // XImportFilter virtual sal_Bool SAL_CALL importer(const Sequence& aSourceData, const css::uno::Reference< XDocumentHandler>& xHandler, const Sequence& msUserData) override; // XImportFilter2 virtual sal_Bool SAL_CALL importer(const Sequence& aSourceData, const css::uno::Reference< XFastParser>& xFastParser, const Sequence& msUserData) override; // XExportFilter virtual sal_Bool SAL_CALL exporter(const Sequence& aSourceData, const Sequence< OUString>& msUserData) override; // XDocumentHandler virtual void SAL_CALL startDocument() override; virtual void SAL_CALL endDocument() override; }; } XSLTFilter::XSLTFilter(const css::uno::Reference &r): m_xContext(r), m_bTerminated(false), m_bError(false) {} void XSLTFilter::disposing(const EventObject&) { } // XServiceInfo sal_Bool XSLTFilter::supportsService(const OUString& sServiceName) { return cppu::supportsService(this, sServiceName); } OUString XSLTFilter::getImplementationName() { return "com.sun.star.comp.documentconversion.XSLTFilter"; } css::uno::Sequence< OUString > XSLTFilter::getSupportedServiceNames() { return { "com.sun.star.documentconversion.XSLTFilter" }; } OUString XSLTFilter::expandUrl(const OUString& sUrl) { OUString sExpandedUrl; try { css::uno::Reference xMacroExpander = theMacroExpander::get(m_xContext); sExpandedUrl = xMacroExpander->expandMacros(sUrl); sal_Int32 nPos = sExpandedUrl.indexOf( "vnd.sun.star.expand:" ); if (nPos != -1) sExpandedUrl = sExpandedUrl.copy(nPos + 20); } catch (const Exception&) { } return sExpandedUrl; } css::uno::Reference XSLTFilter::impl_createTransformer(const OUString& rTransformer, const Sequence& rArgs) { css::uno::Reference xTransformer; // check if the filter needs XSLT-2.0-capable transformer // COMPATIBILITY: libreoffice 3.5/3.6 used to save the impl. // name of the XSLT 2.0 transformation service there, so check // for that too (it is sufficient to check that there is _a_ // service name there) if (rTransformer.toBoolean() || rTransformer.startsWith("com.sun.")) { try { xTransformer = xslt::XSLT2Transformer::create(m_xContext, rArgs); } catch (const Exception&) { // TODO: put a dialog telling about the need to install // xslt2-transformer extension here SAL_WARN("filter.xslt", "could not create XSLT 2.0 transformer"); throw; } } // instantiation of XSLT 2.0 transformer service failed, or the // filter does not need it if (!xTransformer.is()) { xTransformer = xslt::XSLTTransformer::create(m_xContext, rArgs); } return xTransformer; } void XSLTFilter::started() { m_cTransformed.reset(); } void XSLTFilter::error(const Any& a) { SAL_WARN("filter.xslt", "XSLTFilter::error was called: " << exceptionToString(a)); m_bError = true; m_cTransformed.set(); } void XSLTFilter::closed() { m_cTransformed.set(); } void XSLTFilter::terminated() { m_bTerminated = true; m_cTransformed.set(); } OUString XSLTFilter::rel2abs(const OUString& s) { css::uno::Reference subs(css::util::PathSubstitution::create(m_xContext)); OUString aWorkingDir(subs->getSubstituteVariableValue( "$(progurl)" )); INetURLObject aObj(aWorkingDir); aObj.setFinalSlash(); bool bWasAbsolute; INetURLObject aURL = aObj.smartRel2Abs(s, bWasAbsolute, false, INetURLObject::EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, true); return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); } sal_Bool XSLTFilter::importer(const Sequence& aSourceData, const css::uno::Reference& xHandler, const Sequence< OUString>& msUserData) { if (msUserData.getLength() < 5) return false; OUString udStyleSheet = rel2abs(msUserData[4]); // get information from media descriptor // the input stream that represents the imported file // is most important here since we need to supply it to // the sax parser that drives the supplied document handler sal_Int32 nLength = aSourceData.getLength(); OUString aName, aURL; css::uno::Reference xInputStream; css::uno::Reference xInterActionHandler; for (sal_Int32 i = 0; i < nLength; i++) { aName = aSourceData[i].Name; Any value = aSourceData[i].Value; if ( aName == "InputStream" ) value >>= xInputStream; else if ( aName == "URL" ) value >>= aURL; else if ( aName == "InteractionHandler" ) value >>= xInterActionHandler; } OSL_ASSERT(xInputStream.is()); if (!xInputStream.is()) return false; // create transformer Sequence args(3); NamedValue nv; nv.Name = "StylesheetURL"; nv.Value <<= expandUrl(udStyleSheet); args[0] <<= nv; nv.Name = "SourceURL"; nv.Value <<= aURL; args[1] <<= nv; nv.Name = "SourceBaseURL"; nv.Value <<= INetURLObject(aURL).getBase(); args[2] <<= nv; m_tcontrol = impl_createTransformer(msUserData[1], args); OSL_ASSERT(xHandler.is()); OSL_ASSERT(xInputStream.is()); OSL_ASSERT(m_tcontrol.is()); if (xHandler.is() && xInputStream.is() && m_tcontrol.is()) { try { css::uno::Reference xSeek(xInputStream, UNO_QUERY); if (xSeek.is()) xSeek->seek(0); // we want to be notified when the processing is done... m_tcontrol->addListener(css::uno::Reference ( this)); // connect input to transformer m_tcontrol->setInputStream(xInputStream); // create pipe css::uno::Reference pipeout = Pipe::create(m_xContext); css::uno::Reference pipein(pipeout, UNO_QUERY); //connect transformer to pipe m_tcontrol->setOutputStream(pipeout); // connect pipe to sax parser InputSource aInput; aInput.sSystemId = aURL; aInput.sPublicId = aURL; aInput.aInputStream = pipein; css::uno::Reference< css::xml::sax::XFastParser > xFastParser = dynamic_cast< css::xml::sax::XFastParser* >( xHandler.get() ); // transform m_tcontrol->start(); TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0}; osl::Condition::Result result(m_cTransformed.wait(&timeout)); while (osl::Condition::result_timeout == result) { if (xInterActionHandler.is()) { Sequence excArgs(0); css::ucb::InteractiveAugmentedIOException exc( "Timeout!", static_cast< OWeakObject * >( this ), InteractionClassification_ERROR, css::ucb::IOErrorCode_GENERAL, excArgs); Any r; r <<= exc; ::comphelper::OInteractionRequest* pRequest = new ::comphelper::OInteractionRequest(r); css::uno::Reference< XInteractionRequest > xRequest(pRequest); ::comphelper::OInteractionRetry* pRetry = new ::comphelper::OInteractionRetry; ::comphelper::OInteractionAbort* pAbort = new ::comphelper::OInteractionAbort; pRequest->addContinuation(pRetry); pRequest->addContinuation(pAbort); xInterActionHandler->handle(xRequest); if (pAbort->wasSelected()) { m_bError = true; m_cTransformed.set(); } } result = m_cTransformed.wait(&timeout); }; if (!m_bError) { if( xFastParser.is() ) xFastParser->parseStream( aInput ); else { // create SAX parser that will read the document file // and provide events to xHandler passed to this call css::uno::Reference xSaxParser = Parser::create(m_xContext); // set doc handler xSaxParser->setDocumentHandler(xHandler); xSaxParser->parseStream( aInput ); } } m_tcontrol->terminate(); return !m_bError; } catch( const Exception& ) { // something went wrong TOOLS_WARN_EXCEPTION("filter.xslt", ""); return false; } } else { return false; } } sal_Bool XSLTFilter::importer(const Sequence& aSourceData, const css::uno::Reference& xFastParser, const Sequence< OUString>& msUserData) { if (msUserData.getLength() < 5) return false; OUString udStyleSheet = rel2abs(msUserData[4]); // get information from media descriptor // the input stream that represents the imported file // is most important here since we need to supply it to // the sax parser that drives the supplied document handler sal_Int32 nLength = aSourceData.getLength(); OUString aName, aURL; css::uno::Reference xInputStream; css::uno::Reference xInterActionHandler; for (sal_Int32 i = 0; i < nLength; i++) { aName = aSourceData[i].Name; Any value = aSourceData[i].Value; if ( aName == "InputStream" ) value >>= xInputStream; else if ( aName == "URL" ) value >>= aURL; else if ( aName == "InteractionHandler" ) value >>= xInterActionHandler; } OSL_ASSERT(xInputStream.is()); if (!xInputStream.is()) return false; // create transformer Sequence args(3); NamedValue nv; nv.Name = "StylesheetURL"; nv.Value <<= expandUrl(udStyleSheet); args[0] <<= nv; nv.Name = "SourceURL"; nv.Value <<= aURL; args[1] <<= nv; nv.Name = "SourceBaseURL"; nv.Value <<= INetURLObject(aURL).getBase(); args[2] <<= nv; m_tcontrol = impl_createTransformer(msUserData[1], args); assert(xFastParser.is()); OSL_ASSERT(xInputStream.is()); OSL_ASSERT(m_tcontrol.is()); if (xFastParser.is() && xInputStream.is() && m_tcontrol.is()) { try { css::uno::Reference xSeek(xInputStream, UNO_QUERY); if (xSeek.is()) xSeek->seek(0); // we want to be notified when the processing is done... m_tcontrol->addListener(css::uno::Reference ( this)); // connect input to transformer m_tcontrol->setInputStream(xInputStream); // create pipe css::uno::Reference pipeout = Pipe::create(m_xContext); css::uno::Reference pipein(pipeout, UNO_QUERY); //connect transformer to pipe m_tcontrol->setOutputStream(pipeout); // connect pipe to sax parser InputSource aInput; aInput.sSystemId = aURL; aInput.sPublicId = aURL; aInput.aInputStream = pipein; // transform m_tcontrol->start(); TimeValue timeout = { TRANSFORMATION_TIMEOUT_SEC, 0}; osl::Condition::Result result(m_cTransformed.wait(&timeout)); while (osl::Condition::result_timeout == result) { if (xInterActionHandler.is()) { Sequence excArgs(0); css::ucb::InteractiveAugmentedIOException exc( "Timeout!", static_cast< OWeakObject * >( this ), InteractionClassification_ERROR, css::ucb::IOErrorCode_GENERAL, excArgs); Any r; r <<= exc; ::comphelper::OInteractionRequest* pRequest = new ::comphelper::OInteractionRequest(r); css::uno::Reference< XInteractionRequest > xRequest(pRequest); ::comphelper::OInteractionRetry* pRetry = new ::comphelper::OInteractionRetry; ::comphelper::OInteractionAbort* pAbort = new ::comphelper::OInteractionAbort; pRequest->addContinuation(pRetry); pRequest->addContinuation(pAbort); xInterActionHandler->handle(xRequest); if (pAbort->wasSelected()) { m_bError = true; m_cTransformed.set(); } } result = m_cTransformed.wait(&timeout); }; if (!m_bError) xFastParser->parseStream( aInput ); m_tcontrol->terminate(); return !m_bError; } catch( const Exception& ) { // something went wrong TOOLS_WARN_EXCEPTION("filter.xslt", ""); return false; } } else { return false; } } sal_Bool XSLTFilter::exporter(const Sequence& aSourceData, const Sequence& msUserData) { if (msUserData.getLength() < 6) return false; // get interesting values from user data OUString udStyleSheet = rel2abs(msUserData[5]); // read source data // we are especially interested in the output stream // since that is where our xml-writer will push the data // from its data-source interface OUString aName, sURL; OUString aDoctypePublic; // css::uno::Reference rOutputStream; sal_Int32 nLength = aSourceData.getLength(); for (sal_Int32 i = 0; i < nLength; i++) { aName = aSourceData[i].Name; if ( aName == "DocType_Public" ) aSourceData[i].Value >>= aDoctypePublic; else if ( aName == "OutputStream" ) aSourceData[i].Value >>= m_rOutputStream; else if ( aName == "URL" ) aSourceData[i].Value >>= sURL; } if (!getDelegate().is()) { // get the document writer setDelegate(css::uno::Reference( Writer::create(m_xContext), UNO_QUERY_THROW)); } // create transformer Sequence args(4); NamedValue nv; nv.Name = "StylesheetURL"; nv.Value <<= expandUrl(udStyleSheet); args[0] <<= nv; nv.Name = "TargetURL"; nv.Value <<= sURL; args[1] <<= nv; nv.Name = "DoctypePublic"; nv.Value <<= aDoctypePublic; args[2] <<= nv; nv.Name = "TargetBaseURL"; INetURLObject ineturl(sURL); ineturl.removeSegment(); m_aExportBaseUrl = ineturl.GetMainURL(INetURLObject::DecodeMechanism::NONE); nv.Value <<= m_aExportBaseUrl; args[3] <<= nv; m_tcontrol = impl_createTransformer(msUserData[1], args); OSL_ASSERT(m_rOutputStream.is()); OSL_ASSERT(m_tcontrol.is()); if (m_tcontrol.is() && m_rOutputStream.is()) { // we want to be notified when the processing is done... m_tcontrol->addListener(css::uno::Reference (this)); // create pipe css::uno::Reference pipeout = Pipe::create(m_xContext); css::uno::Reference pipein(pipeout, UNO_QUERY); // connect sax writer to pipe css::uno::Reference xmlsource(getDelegate(), UNO_QUERY); xmlsource->setOutputStream(pipeout); // connect pipe to transformer m_tcontrol->setInputStream(pipein); // connect transformer to output m_tcontrol->setOutputStream(m_rOutputStream); // we will start receiving events after returning 'true'. // we will start the transformation as soon as we receive the startDocument // event. return true; } else { return false; } } // for the DocumentHandler implementation, we just proxy the // events to the XML writer that we created upon the output stream // that was provided by the XMLFilterAdapter void XSLTFilter::startDocument() { ExtendedDocumentHandlerAdapter::startDocument(); m_tcontrol->start(); } void XSLTFilter::endDocument() { ExtendedDocumentHandlerAdapter::endDocument(); // wait for the transformer to finish m_cTransformed.wait(); m_tcontrol->terminate(); if (m_bError || m_bTerminated) throw RuntimeException(); } } // Component management extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* filter_XSLTFilter_get_implementation( css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new XSLT::XSLTFilter(context)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */