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 .
20 #include <recording/dispatchrecorder.hxx>
21 #include <com/sun/star/frame/DispatchStatement.hpp>
22 #include <com/sun/star/lang/IllegalArgumentException.hpp>
23 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
24 #include <com/sun/star/script/CannotConvertException.hpp>
25 #include <com/sun/star/script/Converter.hpp>
26 #include <o3tl/any.hxx>
27 #include <osl/diagnose.h>
28 #include <vcl/svapp.hxx>
29 #include <typelib/typedescription.h>
30 #include <cppuhelper/supportsservice.hxx>
32 using namespace ::com::sun::star::uno
;
36 // used to mark a dispatch as comment (mostly it indicates an error) Changing of this define will impact all using of such comments...
37 constexpr OUStringLiteral REM_AS_COMMENT
= u
"rem ";
39 // XInterface, XTypeProvider, XServiceInfo
41 OUString SAL_CALL
DispatchRecorder::getImplementationName()
43 return "com.sun.star.comp.framework.DispatchRecorder";
46 sal_Bool SAL_CALL
DispatchRecorder::supportsService( const OUString
& sServiceName
)
48 return cppu::supportsService(this, sServiceName
);
51 css::uno::Sequence
< OUString
> SAL_CALL
DispatchRecorder::getSupportedServiceNames()
53 return { "com.sun.star.frame.DispatchRecorder" };
58 static void flatten_struct_members(
59 ::std::vector
< Any
> * vec
, void const * data
,
60 typelib_CompoundTypeDescription
* pTD
)
62 if (pTD
->pBaseTypeDescription
)
64 flatten_struct_members( vec
, data
, pTD
->pBaseTypeDescription
);
66 for ( sal_Int32 nPos
= 0; nPos
< pTD
->nMembers
; ++nPos
)
69 Any( static_cast<char const *>(data
) + pTD
->pMemberOffsets
[ nPos
], pTD
->ppTypeRefs
[ nPos
] ) );
73 static Sequence
< Any
> make_seq_out_of_struct(
76 Type
const & type
= val
.getValueType();
77 TypeClass eTypeClass
= type
.getTypeClass();
78 if (TypeClass_STRUCT
!= eTypeClass
&& TypeClass_EXCEPTION
!= eTypeClass
)
80 throw RuntimeException(
81 type
.getTypeName() + "is no struct or exception!" );
83 typelib_TypeDescription
* pTD
= nullptr;
84 TYPELIB_DANGER_GET( &pTD
, type
.getTypeLibType() );
88 throw RuntimeException(
89 "cannot get type descr of type " + type
.getTypeName() );
92 ::std::vector
< Any
> vec
;
93 vec
.reserve( reinterpret_cast<typelib_CompoundTypeDescription
*>(pTD
)->nMembers
); // good guess
94 flatten_struct_members( &vec
, val
.getValue(), reinterpret_cast<typelib_CompoundTypeDescription
*>(pTD
) );
95 TYPELIB_DANGER_RELEASE( pTD
);
96 return Sequence
< Any
>( vec
.data(), vec
.size() );
99 DispatchRecorder::DispatchRecorder( const css::uno::Reference
< css::uno::XComponentContext
>& xContext
)
101 , m_xConverter(css::script::Converter::create(xContext
))
105 DispatchRecorder::~DispatchRecorder()
110 void SAL_CALL
DispatchRecorder::startRecording( const css::uno::Reference
< css::frame::XFrame
>& )
116 void SAL_CALL
DispatchRecorder::recordDispatch( const css::util::URL
& aURL
,
117 const css::uno::Sequence
< css::beans::PropertyValue
>& lArguments
)
119 css::frame::DispatchStatement
aStatement( aURL
.Complete
, OUString(), lArguments
, 0, false );
120 m_aStatements
.push_back( aStatement
);
123 void SAL_CALL
DispatchRecorder::recordDispatchAsComment( const css::util::URL
& aURL
,
124 const css::uno::Sequence
< css::beans::PropertyValue
>& lArguments
)
126 // last parameter must be set to true -> it's a comment
127 css::frame::DispatchStatement
aStatement( aURL
.Complete
, OUString(), lArguments
, 0, true );
128 m_aStatements
.push_back( aStatement
);
131 void SAL_CALL
DispatchRecorder::endRecording()
134 m_aStatements
.clear();
137 OUString SAL_CALL
DispatchRecorder::getRecordedMacro()
141 if ( m_aStatements
.empty() )
144 OUStringBuffer aScriptBuffer
;
145 aScriptBuffer
.ensureCapacity(10000);
148 aScriptBuffer
.append(
149 "rem ----------------------------------------------------------------------\n"
150 "rem define variables\n"
151 "dim document as object\n"
152 "dim dispatcher as object\n"
153 "rem ----------------------------------------------------------------------\n"
154 "rem get access to the document\n"
155 "document = ThisComponent.CurrentController.Frame\n"
156 "dispatcher = createUnoService(\"com.sun.star.frame.DispatchHelper\")\n\n");
158 for (auto const& statement
: m_aStatements
)
159 implts_recordMacro( statement
.aCommand
, statement
.aArgs
, statement
.bIsComment
, aScriptBuffer
);
160 OUString sScript
= aScriptBuffer
.makeStringAndClear();
164 void DispatchRecorder::AppendToBuffer( const css::uno::Any
& aValue
, OUStringBuffer
& aArgumentBuffer
)
167 if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRUCT
)
169 // structs are recorded as arrays, convert to "Sequence of any"
170 Sequence
< Any
> aSeq
= make_seq_out_of_struct( aValue
);
171 aArgumentBuffer
.append("Array(");
172 for ( sal_Int32 nAny
=0; nAny
<aSeq
.getLength(); nAny
++ )
174 AppendToBuffer( aSeq
[nAny
], aArgumentBuffer
);
175 if ( nAny
+1 < aSeq
.getLength() )
177 aArgumentBuffer
.append(",");
180 aArgumentBuffer
.append(")");
182 else if (aValue
.getValueTypeClass() == css::uno::TypeClass_SEQUENCE
)
184 // convert to "Sequence of any"
185 css::uno::Sequence
< css::uno::Any
> aSeq
;
187 try { aNew
= m_xConverter
->convertTo( aValue
, cppu::UnoType
<css::uno::Sequence
< css::uno::Any
>>::get() ); }
188 catch (const css::uno::Exception
&) {}
191 aArgumentBuffer
.append("Array(");
192 for ( sal_Int32 nAny
=0; nAny
<aSeq
.getLength(); nAny
++ )
194 AppendToBuffer( aSeq
[nAny
], aArgumentBuffer
);
195 if ( nAny
+1 < aSeq
.getLength() )
197 aArgumentBuffer
.append(",");
200 aArgumentBuffer
.append(")");
202 else if (aValue
.getValueTypeClass() == css::uno::TypeClass_STRING
)
208 // encode non printable characters or '"' by using the CHR$ function
209 if ( !sVal
.isEmpty() )
211 const sal_Unicode
* pChars
= sVal
.getStr();
212 bool bInString
= false;
213 for ( sal_Int32 nChar
=0; nChar
<sVal
.getLength(); nChar
++ )
215 if ( pChars
[nChar
] < 32 || pChars
[nChar
] == '"' )
217 // problematic character detected
220 // close current string
221 aArgumentBuffer
.append("\"");
226 // if this is not the first character, parts of the string have already been added
227 aArgumentBuffer
.append("+");
229 // add the character constant
230 aArgumentBuffer
.append("CHR$(");
231 aArgumentBuffer
.append( static_cast<sal_Int32
>(pChars
[nChar
]) );
232 aArgumentBuffer
.append(")");
239 // if this is not the first character, parts of the string have already been added
240 aArgumentBuffer
.append("+");
242 // start a new string
243 aArgumentBuffer
.append("\"");
247 aArgumentBuffer
.append( pChars
[nChar
] );
253 aArgumentBuffer
.append("\"");
256 aArgumentBuffer
.append("\"\"");
258 else if (auto nVal
= o3tl::tryAccess
<sal_Unicode
>(aValue
))
260 // character variables are recorded as strings, back conversion must be handled in client code
261 aArgumentBuffer
.append("\"");
264 aArgumentBuffer
.append(*nVal
);
265 aArgumentBuffer
.append(*nVal
);
266 aArgumentBuffer
.append("\"");
273 aNew
= m_xConverter
->convertToSimpleType( aValue
, css::uno::TypeClass_STRING
);
275 catch (const css::script::CannotConvertException
&) {}
276 catch (const css::uno::Exception
&) {}
280 if (aValue
.getValueTypeClass() == css::uno::TypeClass_ENUM
)
282 OUString aName
= aValue
.getValueType().getTypeName();
283 aArgumentBuffer
.append( aName
);
284 aArgumentBuffer
.append(".");
287 aArgumentBuffer
.append(sVal
);
291 void DispatchRecorder::implts_recordMacro( std::u16string_view aURL
,
292 const css::uno::Sequence
< css::beans::PropertyValue
>& lArguments
,
293 bool bAsComment
, OUStringBuffer
& aScriptBuffer
)
295 OUStringBuffer
aArgumentBuffer(1000);
296 // this value is used to name the arrays of aArgumentBuffer
297 OUString sArrayName
= "args" + OUString::number(m_nRecordingID
);
299 aScriptBuffer
.append("rem ----------------------------------------------------------------------\n");
301 sal_Int32 nLength
= lArguments
.getLength();
302 sal_Int32 nValidArgs
= 0;
303 for( sal_Int32 i
=0; i
<nLength
; ++i
)
305 if(!lArguments
[i
].Value
.hasValue())
308 OUStringBuffer
sValBuffer(100);
311 AppendToBuffer(lArguments
[i
].Value
, sValBuffer
);
313 catch(const css::uno::Exception
&)
315 sValBuffer
.setLength(0);
317 if (sValBuffer
.isEmpty())
323 aArgumentBuffer
.append(REM_AS_COMMENT
);
324 aArgumentBuffer
.append(sArrayName
325 + "(" + OUString::number(nValidArgs
)
326 + ").Name = \"" + lArguments
[i
].Name
331 aArgumentBuffer
.append(REM_AS_COMMENT
);
332 aArgumentBuffer
.append(sArrayName
333 + "(" + OUString::number(nValidArgs
)
334 + ").Value = " + sValBuffer
+ "\n");
340 // if aArgumentBuffer exist - pack it into the aScriptBuffer
344 aScriptBuffer
.append(REM_AS_COMMENT
);
345 aScriptBuffer
.append("dim ");
346 aScriptBuffer
.append (sArrayName
);
347 aScriptBuffer
.append("(");
348 aScriptBuffer
.append (static_cast<sal_Int32
>(nValidArgs
-1)); // 0 based!
349 aScriptBuffer
.append(") as new com.sun.star.beans.PropertyValue\n");
350 aScriptBuffer
.append (aArgumentBuffer
);
351 aScriptBuffer
.append("\n");
354 // add code for dispatches
356 aScriptBuffer
.append(REM_AS_COMMENT
);
357 aScriptBuffer
.append("dispatcher.executeDispatch(document, \"");
358 aScriptBuffer
.append(aURL
);
359 aScriptBuffer
.append("\", \"\", 0, ");
361 aScriptBuffer
.append("Array()");
364 aScriptBuffer
.append( sArrayName
);
365 aScriptBuffer
.append("()");
367 aScriptBuffer
.append(")\n\n");
374 css::uno::Type SAL_CALL
DispatchRecorder::getElementType()
376 return cppu::UnoType
<css::frame::DispatchStatement
>::get();
379 sal_Bool SAL_CALL
DispatchRecorder::hasElements()
381 return (! m_aStatements
.empty());
384 sal_Int32 SAL_CALL
DispatchRecorder::getCount()
386 return m_aStatements
.size();
389 css::uno::Any SAL_CALL
DispatchRecorder::getByIndex(sal_Int32 idx
)
391 if (idx
>= static_cast<sal_Int32
>(m_aStatements
.size()))
392 throw css::lang::IndexOutOfBoundsException( "Dispatch recorder out of bounds" );
394 Any
element(&m_aStatements
[idx
],
395 cppu::UnoType
<css::frame::DispatchStatement
>::get());
400 void SAL_CALL
DispatchRecorder::replaceByIndex(sal_Int32 idx
, const css::uno::Any
& element
)
402 if (element
.getValueType() !=
403 cppu::UnoType
<css::frame::DispatchStatement
>::get()) {
404 throw css::lang::IllegalArgumentException(
405 "Illegal argument in dispatch recorder",
406 Reference
< XInterface
>(), 2 );
409 if (idx
>= static_cast<sal_Int32
>(m_aStatements
.size()))
410 throw css::lang::IndexOutOfBoundsException(
411 "Dispatch recorder out of bounds" );
413 auto pStatement
= o3tl::doAccess
<css::frame::DispatchStatement
>(element
);
415 css::frame::DispatchStatement
aStatement(
416 pStatement
->aCommand
,
420 pStatement
->bIsComment
);
422 m_aStatements
[idx
] = aStatement
;
425 } // namespace framework
428 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
429 framework_DispatchRecorder_get_implementation(
430 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const& )
432 return cppu::acquire(new framework::DispatchRecorder(context
));
435 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */