diff options
Diffstat (limited to 'framework/source/recording')
-rw-r--r-- | framework/source/recording/dispatchrecorder.cxx | 429 | ||||
-rw-r--r-- | framework/source/recording/dispatchrecordersupplier.cxx | 160 |
2 files changed, 589 insertions, 0 deletions
diff --git a/framework/source/recording/dispatchrecorder.cxx b/framework/source/recording/dispatchrecorder.cxx new file mode 100644 index 000000000..647b2601d --- /dev/null +++ b/framework/source/recording/dispatchrecorder.cxx @@ -0,0 +1,429 @@ +/* -*- 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 <recording/dispatchrecorder.hxx> +#include <com/sun/star/frame/DispatchStatement.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <typelib/typedescription.h> + +using namespace ::com::sun::star::uno; + +namespace framework{ + +// used to mark a dispatch as comment (mostly it indicates an error) Changing of this define will impact all using of such comments... +#define REM_AS_COMMENT "rem " + +// XInterface, XTypeProvider, XServiceInfo + +DEFINE_XSERVICEINFO_MULTISERVICE_2( + DispatchRecorder, + ::cppu::OWeakObject, + "com.sun.star.frame.DispatchRecorder", + "com.sun.star.comp.framework.DispatchRecorder") + +DEFINE_INIT_SERVICE( + DispatchRecorder, + { + } +) + + +static void flatten_struct_members( + ::std::vector< Any > * vec, void const * data, + typelib_CompoundTypeDescription * pTD ) +{ + if (pTD->pBaseTypeDescription) + { + flatten_struct_members( vec, data, pTD->pBaseTypeDescription ); + } + for ( sal_Int32 nPos = 0; nPos < pTD->nMembers; ++nPos ) + { + vec->push_back( + Any( static_cast<char const *>(data) + pTD->pMemberOffsets[ nPos ], pTD->ppTypeRefs[ nPos ] ) ); + } +} + +static Sequence< Any > make_seq_out_of_struct( + Any const & val ) +{ + Type const & type = val.getValueType(); + TypeClass eTypeClass = type.getTypeClass(); + if (TypeClass_STRUCT != eTypeClass && TypeClass_EXCEPTION != eTypeClass) + { + throw RuntimeException( + type.getTypeName() + "is no struct or exception!" ); + } + typelib_TypeDescription * pTD = nullptr; + TYPELIB_DANGER_GET( &pTD, type.getTypeLibType() ); + OSL_ASSERT( pTD ); + if (! pTD) + { + throw RuntimeException( + "cannot get type descr of type " + type.getTypeName() ); + } + + ::std::vector< Any > vec; + vec.reserve( reinterpret_cast<typelib_CompoundTypeDescription *>(pTD)->nMembers ); // good guess + flatten_struct_members( &vec, val.getValue(), reinterpret_cast<typelib_CompoundTypeDescription *>(pTD) ); + TYPELIB_DANGER_RELEASE( pTD ); + return Sequence< Any >( vec.data(), vec.size() ); +} + +DispatchRecorder::DispatchRecorder( const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : m_nRecordingID(0) + , m_xConverter(css::script::Converter::create(xContext)) +{ +} + +DispatchRecorder::~DispatchRecorder() +{ +} + +// generate header +void SAL_CALL DispatchRecorder::startRecording( const css::uno::Reference< css::frame::XFrame >& ) +{ + /* SAFE{ */ + /* } */ +} + +void SAL_CALL DispatchRecorder::recordDispatch( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + css::frame::DispatchStatement aStatement( aURL.Complete, OUString(), lArguments, 0, false ); + m_aStatements.push_back( aStatement ); +} + +void SAL_CALL DispatchRecorder::recordDispatchAsComment( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + // last parameter must be set to true -> it's a comment + css::frame::DispatchStatement aStatement( aURL.Complete, OUString(), lArguments, 0, true ); + m_aStatements.push_back( aStatement ); +} + +void SAL_CALL DispatchRecorder::endRecording() +{ + SolarMutexGuard g; + m_aStatements.clear(); +} + +OUString SAL_CALL DispatchRecorder::getRecordedMacro() +{ + SolarMutexGuard g; + + if ( m_aStatements.empty() ) + return OUString(); + + OUStringBuffer aScriptBuffer; + aScriptBuffer.ensureCapacity(10000); + m_nRecordingID = 1; + + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + aScriptBuffer.append("rem define variables\n"); + aScriptBuffer.append("dim document as object\n"); + aScriptBuffer.append("dim dispatcher as object\n"); + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + aScriptBuffer.append("rem get access to the document\n"); + aScriptBuffer.append("document = ThisComponent.CurrentController.Frame\n"); + aScriptBuffer.append("dispatcher = createUnoService(\"com.sun.star.frame.DispatchHelper\")\n\n"); + + for (auto const& statement : m_aStatements) + implts_recordMacro( statement.aCommand, statement.aArgs, statement.bIsComment, aScriptBuffer ); + OUString sScript = aScriptBuffer.makeStringAndClear(); + return sScript; +} + +void DispatchRecorder::AppendToBuffer( const css::uno::Any& aValue, OUStringBuffer& aArgumentBuffer ) +{ + // if value == bool + if (aValue.getValueTypeClass() == css::uno::TypeClass_STRUCT ) + { + // structs are recorded as arrays, convert to "Sequence of any" + Sequence< Any > aSeq = make_seq_out_of_struct( aValue ); + aArgumentBuffer.append("Array("); + for ( sal_Int32 nAny=0; nAny<aSeq.getLength(); nAny++ ) + { + AppendToBuffer( aSeq[nAny], aArgumentBuffer ); + if ( nAny+1 < aSeq.getLength() ) + // not last argument + aArgumentBuffer.append(","); + } + + aArgumentBuffer.append(")"); + } + else if (aValue.getValueTypeClass() == css::uno::TypeClass_SEQUENCE ) + { + // convert to "Sequence of any" + css::uno::Sequence < css::uno::Any > aSeq; + css::uno::Any aNew; + try { aNew = m_xConverter->convertTo( aValue, cppu::UnoType<css::uno::Sequence < css::uno::Any >>::get() ); } + catch (const css::uno::Exception&) {} + + aNew >>= aSeq; + aArgumentBuffer.append("Array("); + for ( sal_Int32 nAny=0; nAny<aSeq.getLength(); nAny++ ) + { + AppendToBuffer( aSeq[nAny], aArgumentBuffer ); + if ( nAny+1 < aSeq.getLength() ) + // not last argument + aArgumentBuffer.append(","); + } + + aArgumentBuffer.append(")"); + } + else if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING ) + { + // strings need \" + OUString sVal; + aValue >>= sVal; + + // encode non printable characters or '"' by using the CHR$ function + if ( !sVal.isEmpty() ) + { + const sal_Unicode* pChars = sVal.getStr(); + bool bInString = false; + for ( sal_Int32 nChar=0; nChar<sVal.getLength(); nChar ++ ) + { + if ( pChars[nChar] < 32 || pChars[nChar] == '"' ) + { + // problematic character detected + if ( bInString ) + { + // close current string + aArgumentBuffer.append("\""); + bInString = false; + } + + if ( nChar>0 ) + // if this is not the first character, parts of the string have already been added + aArgumentBuffer.append("+"); + + // add the character constant + aArgumentBuffer.append("CHR$("); + aArgumentBuffer.append( static_cast<sal_Int32>(pChars[nChar]) ); + aArgumentBuffer.append(")"); + } + else + { + if ( !bInString ) + { + if ( nChar>0 ) + // if this is not the first character, parts of the string have already been added + aArgumentBuffer.append("+"); + + // start a new string + aArgumentBuffer.append("\""); + bInString = true; + } + + aArgumentBuffer.append( pChars[nChar] ); + } + } + + // close string + if ( bInString ) + aArgumentBuffer.append("\""); + } + else + aArgumentBuffer.append("\"\""); + } + else if (auto nVal = o3tl::tryAccess<sal_Unicode>(aValue)) + { + // character variables are recorded as strings, back conversion must be handled in client code + aArgumentBuffer.append("\""); + if ( *nVal == '\"' ) + // encode \" to \"\" + aArgumentBuffer.append(*nVal); + aArgumentBuffer.append(*nVal); + aArgumentBuffer.append("\""); + } + else + { + css::uno::Any aNew; + try + { + aNew = m_xConverter->convertToSimpleType( aValue, css::uno::TypeClass_STRING ); + } + catch (const css::script::CannotConvertException&) {} + catch (const css::uno::Exception&) {} + OUString sVal; + aNew >>= sVal; + + if (aValue.getValueTypeClass() == css::uno::TypeClass_ENUM ) + { + OUString aName = aValue.getValueType().getTypeName(); + aArgumentBuffer.append( aName ); + aArgumentBuffer.append("."); + } + + aArgumentBuffer.append(sVal); + } +} + +void DispatchRecorder::implts_recordMacro( const OUString& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + bool bAsComment, OUStringBuffer& aScriptBuffer ) +{ + OUStringBuffer aArgumentBuffer(1000); + // this value is used to name the arrays of aArgumentBuffer + OUString sArrayName = "args" + OUString::number(m_nRecordingID); + + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + + sal_Int32 nLength = lArguments.getLength(); + sal_Int32 nValidArgs = 0; + for( sal_Int32 i=0; i<nLength; ++i ) + { + if(!lArguments[i].Value.hasValue()) + continue; + + OUStringBuffer sValBuffer(100); + try + { + AppendToBuffer(lArguments[i].Value, sValBuffer); + } + catch(const css::uno::Exception&) + { + sValBuffer.setLength(0); + } + if (sValBuffer.isEmpty()) + continue; + + { + // add arg().Name + if(bAsComment) + aArgumentBuffer.append(REM_AS_COMMENT); + aArgumentBuffer.append (sArrayName); + aArgumentBuffer.append("("); + aArgumentBuffer.append (nValidArgs); + aArgumentBuffer.append(").Name = \""); + aArgumentBuffer.append (lArguments[i].Name); + aArgumentBuffer.append("\"\n"); + + // add arg().Value + if(bAsComment) + aArgumentBuffer.append(REM_AS_COMMENT); + aArgumentBuffer.append (sArrayName); + aArgumentBuffer.append("("); + aArgumentBuffer.append (nValidArgs); + aArgumentBuffer.append(").Value = "); + aArgumentBuffer.append (sValBuffer.makeStringAndClear()); + aArgumentBuffer.append("\n"); + + ++nValidArgs; + } + } + + // if aArgumentBuffer exist - pack it into the aScriptBuffer + if(nValidArgs>0) + { + if(bAsComment) + aScriptBuffer.append(REM_AS_COMMENT); + aScriptBuffer.append("dim "); + aScriptBuffer.append (sArrayName); + aScriptBuffer.append("("); + aScriptBuffer.append (static_cast<sal_Int32>(nValidArgs-1)); // 0 based! + aScriptBuffer.append(") as new com.sun.star.beans.PropertyValue\n"); + aScriptBuffer.append (aArgumentBuffer.makeStringAndClear()); + aScriptBuffer.append("\n"); + } + + // add code for dispatches + if(bAsComment) + aScriptBuffer.append(REM_AS_COMMENT); + aScriptBuffer.append("dispatcher.executeDispatch(document, \""); + aScriptBuffer.append(aURL); + aScriptBuffer.append("\", \"\", 0, "); + if(nValidArgs<1) + aScriptBuffer.append("Array()"); + else + { + aScriptBuffer.append( sArrayName ); + aScriptBuffer.append("()"); + } + aScriptBuffer.append(")\n\n"); + + /* SAFE { */ + m_nRecordingID++; + /* } */ +} + +css::uno::Type SAL_CALL DispatchRecorder::getElementType() +{ + return cppu::UnoType<css::frame::DispatchStatement>::get(); +} + +sal_Bool SAL_CALL DispatchRecorder::hasElements() +{ + return (! m_aStatements.empty()); +} + +sal_Int32 SAL_CALL DispatchRecorder::getCount() +{ + return m_aStatements.size(); +} + +css::uno::Any SAL_CALL DispatchRecorder::getByIndex(sal_Int32 idx) +{ + if (idx >= static_cast<sal_Int32>(m_aStatements.size())) { + throw css::lang::IndexOutOfBoundsException( "Dispatch recorder out of bounds" ); + } + + Any element(&m_aStatements[idx], + cppu::UnoType<css::frame::DispatchStatement>::get()); + + return element; +} + +void SAL_CALL DispatchRecorder::replaceByIndex(sal_Int32 idx, const css::uno::Any& element) +{ + if (element.getValueType() != + cppu::UnoType<css::frame::DispatchStatement>::get()) { + throw css::lang::IllegalArgumentException( + "Illegal argument in dispatch recorder", + Reference< XInterface >(), 2 ); + } + + if (idx >= static_cast<sal_Int32>(m_aStatements.size())) { + throw css::lang::IndexOutOfBoundsException( + "Dispatch recorder out of bounds" ); + + } + + auto pStatement = o3tl::doAccess<css::frame::DispatchStatement>(element); + + css::frame::DispatchStatement aStatement( + pStatement->aCommand, + pStatement->aTarget, + pStatement->aArgs, + pStatement->nFlags, + pStatement->bIsComment); + + m_aStatements[idx] = aStatement; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/recording/dispatchrecordersupplier.cxx b/framework/source/recording/dispatchrecordersupplier.cxx new file mode 100644 index 000000000..1abbb0535 --- /dev/null +++ b/framework/source/recording/dispatchrecordersupplier.cxx @@ -0,0 +1,160 @@ +/* -*- 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 <recording/dispatchrecordersupplier.hxx> +#include <services.h> + +#include <com/sun/star/frame/XRecordableDispatch.hpp> + +#include <vcl/svapp.hxx> + +namespace framework{ + +// XInterface, XTypeProvider + +DEFINE_XSERVICEINFO_MULTISERVICE( + DispatchRecorderSupplier, + ::cppu::OWeakObject, + "com.sun.star.frame.DispatchRecorderSupplier", + IMPLEMENTATIONNAME_DISPATCHRECORDERSUPPLIER) + +DEFINE_INIT_SERVICE( + DispatchRecorderSupplier, + { + /*Attention + I think we don't need any mutex or lock here ... because we are called by our own static method impl_createInstance() + to create a new instance of this class by our own supported service factory. + see macro DEFINE_XSERVICEINFO_MULTISERVICE and "impl_initService()" for further information! + */ + } +) + +/** + @short standard constructor to create instance + @descr Because an instance will be initialized by her interface methods + it's not necessary to do anything here. + */ +DispatchRecorderSupplier::DispatchRecorderSupplier( const css::uno::Reference< css::lang::XMultiServiceFactory >& ) +{ +} + +/** + @short standard destructor + @descr We are a helper and not a real service. So we don't provide + dispose() functionality. This supplier dies by ref count mechanism + and should release all internal used ones too. + */ +DispatchRecorderSupplier::~DispatchRecorderSupplier() +{ + m_xDispatchRecorder = nullptr; +} + +/** + @short set a new dispatch recorder on this supplier + @descr Because there can exist more than one recorder implementations + (to generate java/basic/... scripts from recorded data) it must + be possible to set it on a supplier. + + @see getDispatchRecorder() + + @param xRecorder + the new recorder to set it + <br><NULL/> isn't recommended, because recording without a + valid recorder can't work. But it's not checked here. So user + of this supplier can decide that without changing this + implementation. + + @change 09.04.2002 by Andreas Schluens + */ +void SAL_CALL DispatchRecorderSupplier::setDispatchRecorder( const css::uno::Reference< css::frame::XDispatchRecorder >& xRecorder ) +{ + SolarMutexGuard g; + m_xDispatchRecorder=xRecorder; +} + +/** + @short provides access to the dispatch recorder of this supplier + @descr Such recorder can be used outside to record dispatches. + But normally he is used internally only. Of course he must used + from outside to get the recorded data e.g. for saving it as a + script. + + @see setDispatchRecorder() + + @return the internal used dispatch recorder + <br>May it can be <NULL/> if no one was set before. + + @change 09.04.2002 by Andreas Schluens + */ +css::uno::Reference< css::frame::XDispatchRecorder > SAL_CALL DispatchRecorderSupplier::getDispatchRecorder() +{ + SolarMutexGuard g; + return m_xDispatchRecorder; +} + +/** + @short execute a dispatch request and record it + @descr If given dispatch object provides right recording interface it + will be used. If it's not supported it record the pure dispatch + parameters only. There is no code neither the possibility to + check if recording is enabled or not. + + @param aURL the command URL + @param lArguments optional arguments (see com.sun.star.document.MediaDescriptor for further information) + @param xDispatcher the original dispatch object which should be recorded + + @change 09.04.2002 by Andreas Schluens + */ +void SAL_CALL DispatchRecorderSupplier::dispatchAndRecord( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments , + const css::uno::Reference< css::frame::XDispatch >& xDispatcher ) +{ + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder = m_xDispatchRecorder; + aReadLock.clear(); + + // clear unspecific situations + if (!xDispatcher.is()) + throw css::uno::RuntimeException("specification violation: dispatcher is NULL", static_cast< ::cppu::OWeakObject* >(this)); + + if (!xRecorder.is()) + throw css::uno::RuntimeException("specification violation: no valid dispatch recorder available", static_cast< ::cppu::OWeakObject* >(this)); + + // check, if given dispatch supports record functionality by itself ... + // or must be wrapped. + css::uno::Reference< css::frame::XRecordableDispatch > xRecordable( + xDispatcher, + css::uno::UNO_QUERY); + + if (xRecordable.is()) + xRecordable->dispatchAndRecord(aURL,lArguments,xRecorder); + else + { + // There is no reason to wait for information about success + // of this request. Because status information of a dispatch + // are not guaranteed. So we execute it and record used + // parameters only. + xDispatcher->dispatch(aURL,lArguments); + xRecorder->recordDispatch(aURL,lArguments); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |