LibreOffice Module framework (master) 1
dispatchrecorder.cxx
Go to the documentation of this file.
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
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>
31
32using namespace ::com::sun::star::uno;
33
34namespace framework{
35
36// used to mark a dispatch as comment (mostly it indicates an error) Changing of this define will impact all using of such comments...
37constexpr OUStringLiteral REM_AS_COMMENT = u"rem ";
38
39// XInterface, XTypeProvider, XServiceInfo
40
42{
43 return "com.sun.star.comp.framework.DispatchRecorder";
44}
45
46sal_Bool SAL_CALL DispatchRecorder::supportsService( const OUString& sServiceName )
47{
49}
50
51css::uno::Sequence< OUString > SAL_CALL DispatchRecorder::getSupportedServiceNames()
52{
53 return { "com.sun.star.frame.DispatchRecorder" };
54}
55
56
57
59 ::std::vector< Any > * vec, void const * data,
60 typelib_CompoundTypeDescription * pTD )
61{
62 if (pTD->pBaseTypeDescription)
63 {
64 flatten_struct_members( vec, data, pTD->pBaseTypeDescription );
65 }
66 for ( sal_Int32 nPos = 0; nPos < pTD->nMembers; ++nPos )
67 {
68 vec->push_back(
69 Any( static_cast<char const *>(data) + pTD->pMemberOffsets[ nPos ], pTD->ppTypeRefs[ nPos ] ) );
70 }
71}
72
73static Sequence< Any > make_seq_out_of_struct(
74 Any const & val )
75{
76 Type const & type = val.getValueType();
77 TypeClass eTypeClass = type.getTypeClass();
78 if (TypeClass_STRUCT != eTypeClass && TypeClass_EXCEPTION != eTypeClass)
79 {
80 throw RuntimeException(
81 type.getTypeName() + "is no struct or exception!" );
82 }
83 typelib_TypeDescription * pTD = nullptr;
84 TYPELIB_DANGER_GET( &pTD, type.getTypeLibType() );
85 OSL_ASSERT( pTD );
86 if (! pTD)
87 {
88 throw RuntimeException(
89 "cannot get type descr of type " + type.getTypeName() );
90 }
91
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() );
97}
98
99DispatchRecorder::DispatchRecorder( const css::uno::Reference< css::uno::XComponentContext >& xContext )
100 : m_nRecordingID(0)
101 , m_xConverter(css::script::Converter::create(xContext))
102{
103}
104
106{
107}
108
109// generate header
110void SAL_CALL DispatchRecorder::startRecording( const css::uno::Reference< css::frame::XFrame >& )
111{
112 /* SAFE{ */
113 /* } */
114}
115
116void SAL_CALL DispatchRecorder::recordDispatch( const css::util::URL& aURL,
117 const css::uno::Sequence< css::beans::PropertyValue >& lArguments )
118{
119 css::frame::DispatchStatement aStatement( aURL.Complete, OUString(), lArguments, 0, false );
120 m_aStatements.push_back( aStatement );
121}
122
123void SAL_CALL DispatchRecorder::recordDispatchAsComment( const css::util::URL& aURL,
124 const css::uno::Sequence< css::beans::PropertyValue >& lArguments )
125{
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 );
129}
130
132{
134 m_aStatements.clear();
135}
136
138{
140
141 if ( m_aStatements.empty() )
142 return OUString();
143
144 OUStringBuffer aScriptBuffer;
145 aScriptBuffer.ensureCapacity(10000);
146 m_nRecordingID = 1;
147
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");
157
158 for (auto const& statement : m_aStatements)
159 implts_recordMacro( statement.aCommand, statement.aArgs, statement.bIsComment, aScriptBuffer );
160 OUString sScript = aScriptBuffer.makeStringAndClear();
161 return sScript;
162}
163
164void DispatchRecorder::AppendToBuffer( const css::uno::Any& aValue, OUStringBuffer& aArgumentBuffer )
165{
166 // if value == bool
167 if (aValue.getValueTypeClass() == css::uno::TypeClass_STRUCT )
168 {
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++ )
173 {
174 AppendToBuffer( aSeq[nAny], aArgumentBuffer );
175 if ( nAny+1 < aSeq.getLength() )
176 // not last argument
177 aArgumentBuffer.append(",");
178 }
179
180 aArgumentBuffer.append(")");
181 }
182 else if (aValue.getValueTypeClass() == css::uno::TypeClass_SEQUENCE )
183 {
184 // convert to "Sequence of any"
185 css::uno::Sequence < css::uno::Any > aSeq;
186 css::uno::Any aNew;
187 try { aNew = m_xConverter->convertTo( aValue, cppu::UnoType<css::uno::Sequence < css::uno::Any >>::get() ); }
188 catch (const css::uno::Exception&) {}
189
190 aNew >>= aSeq;
191 aArgumentBuffer.append("Array(");
192 for ( sal_Int32 nAny=0; nAny<aSeq.getLength(); nAny++ )
193 {
194 AppendToBuffer( aSeq[nAny], aArgumentBuffer );
195 if ( nAny+1 < aSeq.getLength() )
196 // not last argument
197 aArgumentBuffer.append(",");
198 }
199
200 aArgumentBuffer.append(")");
201 }
202 else if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING )
203 {
204 // strings need \"
205 OUString sVal;
206 aValue >>= sVal;
207
208 // encode non printable characters or '"' by using the CHR$ function
209 if ( !sVal.isEmpty() )
210 {
211 const sal_Unicode* pChars = sVal.getStr();
212 bool bInString = false;
213 for ( sal_Int32 nChar=0; nChar<sVal.getLength(); nChar ++ )
214 {
215 if ( pChars[nChar] < 32 || pChars[nChar] == '"' )
216 {
217 // problematic character detected
218 if ( bInString )
219 {
220 // close current string
221 aArgumentBuffer.append("\"");
222 bInString = false;
223 }
224
225 if ( nChar>0 )
226 // if this is not the first character, parts of the string have already been added
227 aArgumentBuffer.append("+");
228
229 // add the character constant
230 aArgumentBuffer.append("CHR$(");
231 aArgumentBuffer.append( static_cast<sal_Int32>(pChars[nChar]) );
232 aArgumentBuffer.append(")");
233 }
234 else
235 {
236 if ( !bInString )
237 {
238 if ( nChar>0 )
239 // if this is not the first character, parts of the string have already been added
240 aArgumentBuffer.append("+");
241
242 // start a new string
243 aArgumentBuffer.append("\"");
244 bInString = true;
245 }
246
247 aArgumentBuffer.append( pChars[nChar] );
248 }
249 }
250
251 // close string
252 if ( bInString )
253 aArgumentBuffer.append("\"");
254 }
255 else
256 aArgumentBuffer.append("\"\"");
257 }
258 else if (auto nVal = o3tl::tryAccess<sal_Unicode>(aValue))
259 {
260 // character variables are recorded as strings, back conversion must be handled in client code
261 aArgumentBuffer.append("\"");
262 if ( *nVal == '\"' )
263 // encode \" to \"\"
264 aArgumentBuffer.append(*nVal);
265 aArgumentBuffer.append(*nVal);
266 aArgumentBuffer.append("\"");
267 }
268 else
269 {
270 css::uno::Any aNew;
271 try
272 {
273 aNew = m_xConverter->convertToSimpleType( aValue, css::uno::TypeClass_STRING );
274 }
275 catch (const css::script::CannotConvertException&) {}
276 catch (const css::uno::Exception&) {}
277 OUString sVal;
278 aNew >>= sVal;
279
280 if (aValue.getValueTypeClass() == css::uno::TypeClass_ENUM )
281 {
282 OUString aName = aValue.getValueType().getTypeName();
283 aArgumentBuffer.append( aName );
284 aArgumentBuffer.append(".");
285 }
286
287 aArgumentBuffer.append(sVal);
288 }
289}
290
291void DispatchRecorder::implts_recordMacro( std::u16string_view aURL,
292 const css::uno::Sequence< css::beans::PropertyValue >& lArguments,
293 bool bAsComment, OUStringBuffer& aScriptBuffer )
294{
295 OUStringBuffer aArgumentBuffer(1000);
296 // this value is used to name the arrays of aArgumentBuffer
297 OUString sArrayName = "args" + OUString::number(m_nRecordingID);
298
299 aScriptBuffer.append("rem ----------------------------------------------------------------------\n");
300
301 sal_Int32 nLength = lArguments.getLength();
302 sal_Int32 nValidArgs = 0;
303 for( sal_Int32 i=0; i<nLength; ++i )
304 {
305 if(!lArguments[i].Value.hasValue())
306 continue;
307
308 OUStringBuffer sValBuffer(100);
309 try
310 {
311 AppendToBuffer(lArguments[i].Value, sValBuffer);
312 }
313 catch(const css::uno::Exception&)
314 {
315 sValBuffer.setLength(0);
316 }
317 if (sValBuffer.isEmpty())
318 continue;
319
320 {
321 // add arg().Name
322 if(bAsComment)
323 aArgumentBuffer.append(REM_AS_COMMENT);
324 aArgumentBuffer.append(sArrayName
325 + "(" + OUString::number(nValidArgs)
326 + ").Name = \"" + lArguments[i].Name
327 + "\"\n");
328
329 // add arg().Value
330 if(bAsComment)
331 aArgumentBuffer.append(REM_AS_COMMENT);
332 aArgumentBuffer.append(sArrayName
333 + "(" + OUString::number(nValidArgs)
334 + ").Value = " + sValBuffer + "\n");
335
336 ++nValidArgs;
337 }
338 }
339
340 // if aArgumentBuffer exist - pack it into the aScriptBuffer
341 if(nValidArgs>0)
342 {
343 if(bAsComment)
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");
352 }
353
354 // add code for dispatches
355 if(bAsComment)
356 aScriptBuffer.append(REM_AS_COMMENT);
357 aScriptBuffer.append("dispatcher.executeDispatch(document, \"");
358 aScriptBuffer.append(aURL);
359 aScriptBuffer.append("\", \"\", 0, ");
360 if(nValidArgs<1)
361 aScriptBuffer.append("Array()");
362 else
363 {
364 aScriptBuffer.append( sArrayName );
365 aScriptBuffer.append("()");
366 }
367 aScriptBuffer.append(")\n\n");
368
369 /* SAFE { */
371 /* } */
372}
373
374css::uno::Type SAL_CALL DispatchRecorder::getElementType()
375{
377}
378
380{
381 return (! m_aStatements.empty());
382}
383
384sal_Int32 SAL_CALL DispatchRecorder::getCount()
385{
386 return m_aStatements.size();
387}
388
389css::uno::Any SAL_CALL DispatchRecorder::getByIndex(sal_Int32 idx)
390{
391 if (idx >= static_cast<sal_Int32>(m_aStatements.size()))
392 throw css::lang::IndexOutOfBoundsException( "Dispatch recorder out of bounds" );
393
394 Any element(&m_aStatements[idx],
396
397 return element;
398}
399
400void SAL_CALL DispatchRecorder::replaceByIndex(sal_Int32 idx, const css::uno::Any& element)
401{
402 if (element.getValueType() !=
404 throw css::lang::IllegalArgumentException(
405 "Illegal argument in dispatch recorder",
406 Reference< XInterface >(), 2 );
407 }
408
409 if (idx >= static_cast<sal_Int32>(m_aStatements.size()))
410 throw css::lang::IndexOutOfBoundsException(
411 "Dispatch recorder out of bounds" );
412
413 auto pStatement = o3tl::doAccess<css::frame::DispatchStatement>(element);
414
415 css::frame::DispatchStatement aStatement(
416 pStatement->aCommand,
417 pStatement->aTarget,
418 pStatement->aArgs,
419 pStatement->nFlags,
420 pStatement->bIsComment);
421
422 m_aStatements[idx] = aStatement;
423}
424
425} // namespace framework
426
427
428extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
430 css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
431{
432 return cppu::acquire(new framework::DispatchRecorder(context));
433}
434
435/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
constexpr OUStringLiteral sServiceName
sal_Int16 script
css::uno::Type const & get()
virtual void SAL_CALL replaceByIndex(sal_Int32, const css::uno::Any &) override
virtual OUString SAL_CALL getRecordedMacro() override
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
virtual css::uno::Type SAL_CALL getElementType() override
virtual OUString SAL_CALL getImplementationName() override
virtual void SAL_CALL startRecording(const css::uno::Reference< css::frame::XFrame > &xFrame) override
virtual void SAL_CALL recordDispatch(const css::util::URL &aURL, const css::uno::Sequence< css::beans::PropertyValue > &lArguments) override
virtual sal_Bool SAL_CALL supportsService(const OUString &sServiceName) override
virtual css::uno::Any SAL_CALL getByIndex(sal_Int32) override
DispatchRecorder(const css::uno::Reference< css::uno::XComponentContext > &xSMGR)
virtual sal_Int32 SAL_CALL getCount() override
::std::vector< css::frame::DispatchStatement > m_aStatements
css::uno::Reference< css::script::XTypeConverter > m_xConverter
virtual sal_Bool SAL_CALL hasElements() override
void implts_recordMacro(std::u16string_view aURL, const css::uno::Sequence< css::beans::PropertyValue > &lArguments, bool bAsComment, OUStringBuffer &)
virtual ~DispatchRecorder() override
virtual void SAL_CALL recordDispatchAsComment(const css::util::URL &aURL, const css::uno::Sequence< css::beans::PropertyValue > &lArguments) override
virtual void SAL_CALL endRecording() override
void AppendToBuffer(const css::uno::Any &aValue, OUStringBuffer &aArgumentBuffer)
SAL_DLLPUBLIC_EXPORT css::uno::XInterface * framework_DispatchRecorder_get_implementation(css::uno::XComponentContext *context, css::uno::Sequence< css::uno::Any > const &)
URL aURL
float u
Reference< XTypeConverter > m_xConverter
const sal_uInt16 idx[]
OUString aName
sal_uInt16 nPos
Sequence< sal_Int8 > aSeq
struct _typelib_TypeDescription typelib_TypeDescription
Type
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
css::uno::Reference< css::deployment::XPackageRegistry > create(css::uno::Reference< css::deployment::XPackageRegistry > const &xRootRegistry, OUString const &context, OUString const &cachePath, css::uno::Reference< css::uno::XComponentContext > const &xComponentContext)
Value
constexpr OUStringLiteral REM_AS_COMMENT
static Sequence< Any > make_seq_out_of_struct(Any const &val)
static void flatten_struct_members(::std::vector< Any > *vec, void const *data, typelib_CompoundTypeDescription *pTD)
int i
OUString Name
unsigned char sal_Bool
sal_uInt16 sal_Unicode
ResultType type
constexpr OUStringLiteral sScript
sal_Int32 nLength