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 "scdetect.hxx"
22 #include <sal/macros.h>
24 #include <framework/interaction.hxx>
25 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
26 #include <com/sun/star/beans/PropertyValue.hpp>
27 #include <com/sun/star/frame/XFrame.hpp>
28 #include <com/sun/star/frame/XModel.hpp>
29 #include <com/sun/star/awt/XWindow.hpp>
30 #include <com/sun/star/lang/XUnoTunnel.hpp>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/string.hxx>
33 #include <com/sun/star/container/XNameAccess.hpp>
34 #include <com/sun/star/io/XInputStream.hpp>
35 #include <com/sun/star/task/XInteractionHandler.hpp>
36 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
37 #include <com/sun/star/ucb/CommandAbortedException.hpp>
38 #include <com/sun/star/ucb/InteractiveAppException.hpp>
39 #include <com/sun/star/ucb/XContent.hpp>
40 #include <com/sun/star/packages/zip/ZipIOException.hpp>
42 #include <toolkit/helper/vclunohelper.hxx>
43 #include <ucbhelper/simpleinteractionrequest.hxx>
45 #include <svtools/parhtml.hxx>
46 #include <rtl/ustring.h>
47 #include <svl/itemset.hxx>
48 #include <vcl/window.hxx>
49 #include <svl/eitem.hxx>
50 #include <svl/stritem.hxx>
51 #include <tools/urlobj.hxx>
52 #include <osl/mutex.hxx>
53 #include <svtools/sfxecode.hxx>
54 #include <svtools/ehdl.hxx>
55 #include <sot/storinfo.hxx>
56 #include <vcl/svapp.hxx>
57 #include <sfx2/sfxsids.hrc>
58 #include <sfx2/request.hxx>
59 #include <sfx2/docfile.hxx>
60 #include <sfx2/docfilt.hxx>
61 #include <sfx2/fcontnr.hxx>
62 #include <sfx2/app.hxx>
63 #include <sfx2/brokenpackageint.hxx>
65 using namespace ::com::sun::star
;
66 using namespace ::com::sun::star::uno
;
67 using namespace ::com::sun::star::io
;
68 using namespace ::com::sun::star::frame
;
69 using namespace ::com::sun::star::task
;
70 using namespace ::com::sun::star::beans
;
71 using namespace ::com::sun::star::lang
;
72 using namespace ::com::sun::star::ucb
;
76 const sal_Char pFilterLotus
[] = "Lotus";
77 const sal_Char pFilterQPro6
[] = "Quattro Pro 6.0";
78 const sal_Char pFilterDBase
[] = "dBase";
79 const sal_Char pFilterDif
[] = "DIF";
80 const sal_Char pFilterSylk
[] = "SYLK";
82 // Tabelle mit Suchmustern
83 // Bedeutung der Sequenzen
84 // 0x00??: genau Byte 0x?? muss an dieser Stelle stehen
85 // 0x0100: ein Byte ueberlesen (don't care)
86 // 0x02nn: ein Byte aus 0xnn Alternativen folgt
87 // 0x8000: Erkennung abgeschlossen
91 #define M_ALT(ANZ) (0x0200+(ANZ))
94 const sal_uInt16 pLotus
[] = // Lotus 1/1A/2
95 { 0x0000, 0x0000, 0x0002, 0x0000,
96 M_ALT(2), 0x0004, 0x0006,
99 const sal_uInt16 pLotusNew
[] = // Lotus >= 9.7
100 { 0x0000, 0x0000, M_DC
, 0x0000, // Rec# + Len (0x1a)
101 M_ALT(3), 0x0003, 0x0004, 0x0005, // File Revision Code 97->ME
102 0x0010, 0x0004, 0x0000, 0x0000,
105 const sal_uInt16 pLotus2
[] = // Lotus >3
106 { 0x0000, 0x0000, 0x001A, 0x0000, // Rec# + Len (26)
107 M_ALT(2), 0x0000, 0x0002, // File Revision Code
109 0x0004, 0x0000, // File Revision Subcode
112 const sal_uInt16 pQPro
[] =
113 { 0x0000, 0x0000, 0x0002, 0x0000,
114 M_ALT(4), 0x0001, 0x0002, // WB1, WB2
115 0x0006, 0x0007, // QPro 6/7 (?)
119 const sal_uInt16 pDIF1
[] = // DIF mit CR-LF
121 'T', 'A', 'B', 'L', 'E',
128 const sal_uInt16 pDIF2
[] = // DIF mit CR oder LF
130 'T', 'A', 'B', 'L', 'E',
137 const sal_uInt16 pSylk
[] = // Sylk
140 M_ALT(3), 'P', 'N', 'E', // 'P' plus undocumented Excel extensions 'N' and 'E'
143 bool detectThisFormat(SvStream
& rStr
, const sal_uInt16
* pSearch
)
146 rStr
.Seek( 0 ); // am Anfang war alles Uebel...
149 while( !rStr
.IsEof() && bSync
)
151 sal_uInt16 nMuster
= *pSearch
;
153 if( nMuster
< 0x0100 )
154 { // direkter Byte-Vergleich
155 if( ( sal_uInt8
) nMuster
!= nByte
)
158 else if( nMuster
& M_DC
)
161 else if( nMuster
& M_ALT(0) )
162 { // alternative Bytes
163 sal_uInt8 nAnzAlt
= ( sal_uInt8
) nMuster
;
164 bSync
= false; // zunaechst unsynchron
168 if( ( sal_uInt8
) *pSearch
== nByte
)
169 bSync
= true; // jetzt erst Synchronisierung
173 else if( nMuster
& M_ENDE
)
187 ScFilterDetect::ScFilterDetect( const uno::Reference
<uno::XComponentContext
>& /*xContext*/ )
191 ScFilterDetect::~ScFilterDetect()
196 // This method is no longer used, but I do want to keep this for now to see
197 // if we could transfer this check to the now centralized ascii detection
198 // code in the filter module.
199 static sal_Bool
lcl_MayBeAscii( SvStream
& rStream
)
201 // ASCII/CSV is considered possible if there are no null bytes, or a Byte
202 // Order Mark is present, or if, for Unicode UCS2/UTF-16, all null bytes
203 // are on either even or uneven byte positions.
205 rStream
.Seek(STREAM_SEEK_TO_BEGIN
);
207 const size_t nBufSize
= 2048;
208 sal_uInt16 aBuffer
[ nBufSize
];
209 sal_uInt8
* pByte
= reinterpret_cast<sal_uInt8
*>(aBuffer
);
210 sal_uLong nBytesRead
= rStream
.Read( pByte
, nBufSize
*2);
212 if ( nBytesRead
>= 2 && (aBuffer
[0] == 0xfffe || aBuffer
[0] == 0xfeff) )
214 // Unicode BOM file may contain null bytes.
218 const sal_uInt16
* p
= aBuffer
;
219 sal_uInt16 nMask
= 0xffff;
221 while( nBytesRead
-- && nMask
)
223 sal_uInt16 nVal
= *p
++ & nMask
;
224 if (!(nVal
& 0x00ff))
226 if (!(nVal
& 0xff00))
234 static sal_Bool
lcl_MayBeDBase( SvStream
& rStream
)
236 // Look for dbf marker, see connectivity/source/inc/dbase/DTable.hxx
237 // DBFType for values.
238 const sal_uInt8 nValidMarks
[] = {
239 0x03, 0x04, 0x05, 0x30, 0x43, 0xB3, 0x83, 0x8b, 0x8e, 0xf5 };
241 rStream
.Seek(STREAM_SEEK_TO_BEGIN
);
243 bool bValidMark
= false;
244 for (size_t i
=0; i
< sizeof(nValidMarks
)/sizeof(nValidMarks
[0]) && !bValidMark
; ++i
)
246 if (nValidMarks
[i
] == nMark
)
252 const size_t nHeaderBlockSize
= 32;
253 // Empty dbf is >= 32*2+1 bytes in size.
254 const size_t nEmptyDbf
= nHeaderBlockSize
* 2 + 1;
256 rStream
.Seek(STREAM_SEEK_TO_END
);
257 sal_uLong nSize
= rStream
.Tell();
258 if ( nSize
< nEmptyDbf
)
261 // length of header starts at 8
263 sal_uInt16 nHeaderLen
;
264 rStream
>> nHeaderLen
;
266 if ( nHeaderLen
< nEmptyDbf
|| nSize
< nHeaderLen
)
269 // Last byte of header must be 0x0d, this is how it's specified.
270 // #i9581#,#i26407# but some applications don't follow the specification
271 // and pad the header with one byte 0x00 to reach an
272 // even boundary. Some (#i88577# ) even pad more or pad using a 0x1a ^Z
273 // control character (#i8857#). This results in:
274 // Last byte of header must be 0x0d on 32 bytes boundary.
275 sal_uInt16 nBlocks
= (nHeaderLen
- 1) / nHeaderBlockSize
;
276 sal_uInt8 nEndFlag
= 0;
277 while ( nBlocks
> 1 && nEndFlag
!= 0x0d ) {
278 rStream
.Seek( nBlocks
-- * nHeaderBlockSize
);
282 return ( 0x0d == nEndFlag
);
285 OUString SAL_CALL
ScFilterDetect::detect( uno::Sequence
<beans::PropertyValue
>& lDescriptor
)
286 throw( uno::RuntimeException
)
288 uno::Reference
< XInputStream
> xStream
;
289 uno::Reference
< XContent
> xContent
;
290 uno::Reference
< XInteractionHandler
> xInteraction
;
293 OUString aTypeName
; // a name describing the type (from MediaDescriptor, usually from flat detection)
294 OUString aPreselectedFilterName
; // a name describing the filter to use (from MediaDescriptor, usually from UI action)
296 OUString aDocumentTitle
; // interesting only if set in this method
298 // opening as template is done when a parameter tells to do so and a template filter can be detected
299 // (otherwise no valid filter would be found) or if the detected filter is a template filter and
300 // there is no parameter that forbids to open as template
301 sal_Bool bOpenAsTemplate
= false;
302 sal_Bool bWasReadOnly
= false, bReadOnly
= false;
304 sal_Bool bRepairPackage
= false;
305 sal_Bool bRepairAllowed
= false;
306 bool bDeepDetection
= false;
308 // now some parameters that can already be in the array, but may be overwritten or new inserted here
309 // remember their indices in the case new values must be added to the array
310 sal_Int32 nPropertyCount
= lDescriptor
.getLength();
311 sal_Int32 nIndexOfFilterName
= -1;
312 sal_Int32 nIndexOfInputStream
= -1;
313 sal_Int32 nIndexOfContent
= -1;
314 sal_Int32 nIndexOfReadOnlyFlag
= -1;
315 sal_Int32 nIndexOfTemplateFlag
= -1;
316 sal_Int32 nIndexOfDocumentTitle
= -1;
318 for( sal_Int32 nProperty
=0; nProperty
<nPropertyCount
; ++nProperty
)
320 // extract properties
321 if ( lDescriptor
[nProperty
].Name
== "URL" )
323 lDescriptor
[nProperty
].Value
>>= sTemp
;
326 else if( aURL
.isEmpty() && lDescriptor
[nProperty
].Name
== "FileName" )
328 lDescriptor
[nProperty
].Value
>>= sTemp
;
331 else if ( lDescriptor
[nProperty
].Name
== "TypeName" )
333 lDescriptor
[nProperty
].Value
>>= sTemp
;
336 else if ( lDescriptor
[nProperty
].Name
== "FilterName" )
338 lDescriptor
[nProperty
].Value
>>= sTemp
;
339 aPreselectedFilterName
= sTemp
;
341 // if the preselected filter name is not correct, it must be erased after detection
342 // remember index of property to get access to it later
343 nIndexOfFilterName
= nProperty
;
345 else if ( lDescriptor
[nProperty
].Name
== "InputStream" )
346 nIndexOfInputStream
= nProperty
;
347 else if ( lDescriptor
[nProperty
].Name
== "ReadOnly" )
348 nIndexOfReadOnlyFlag
= nProperty
;
349 else if ( lDescriptor
[nProperty
].Name
== "UCBContent" )
350 nIndexOfContent
= nProperty
;
351 else if ( lDescriptor
[nProperty
].Name
== "AsTemplate" )
353 lDescriptor
[nProperty
].Value
>>= bOpenAsTemplate
;
354 nIndexOfTemplateFlag
= nProperty
;
356 else if ( lDescriptor
[nProperty
].Name
== "InteractionHandler" )
357 lDescriptor
[nProperty
].Value
>>= xInteraction
;
358 else if ( lDescriptor
[nProperty
].Name
== "RepairPackage" )
359 lDescriptor
[nProperty
].Value
>>= bRepairPackage
;
360 else if ( lDescriptor
[nProperty
].Name
== "DocumentTitle" )
361 nIndexOfDocumentTitle
= nProperty
;
362 else if (lDescriptor
[nProperty
].Name
== "DeepDetection")
363 bDeepDetection
= lDescriptor
[nProperty
].Value
.get
<sal_Bool
>();
366 // can't check the type for external filters, so set the "dont" flag accordingly
367 SolarMutexGuard aGuard
;
368 //SfxFilterFlags nMust = SFX_FILTER_IMPORT, nDont = SFX_FILTER_NOTINSTALLED;
370 SfxAllItemSet
*pSet
= new SfxAllItemSet( SFX_APP()->GetPool() );
371 TransformParameters( SID_OPENDOC
, lDescriptor
, *pSet
);
372 SFX_ITEMSET_ARG( pSet
, pItem
, SfxBoolItem
, SID_DOC_READONLY
, false );
374 bWasReadOnly
= pItem
&& pItem
->GetValue();
376 const SfxFilter
* pFilter
= 0;
377 OUString aPrefix
= "private:factory/";
378 if( aURL
.startsWith( aPrefix
) )
380 OUString aPattern
= aPrefix
+ "scalc";
381 if ( aURL
.startsWith( aPattern
) )
382 pFilter
= SfxFilter::GetDefaultFilterFromFactory( aURL
);
386 // container for Calc filters
387 SfxFilterMatcher
aMatcher("scalc");
388 if ( !aPreselectedFilterName
.isEmpty() )
389 pFilter
= SfxFilter::GetFilterByName( aPreselectedFilterName
);
390 else if( !aTypeName
.isEmpty() )
391 pFilter
= aMatcher
.GetFilter4EA( aTypeName
);
393 // ctor of SfxMedium uses owner transition of ItemSet
394 SfxMedium
aMedium( aURL
, bWasReadOnly
? STREAM_STD_READ
: STREAM_STD_READWRITE
, NULL
, pSet
);
395 aMedium
.UseInteractionHandler( sal_True
);
397 sal_Bool bIsStorage
= aMedium
.IsStorage();
398 if ( aMedium
.GetErrorCode() == ERRCODE_NONE
)
400 // remember input stream and content and put them into the descriptor later
401 // should be done here since later the medium can switch to a version
402 xStream
.set(aMedium
.GetInputStream());
403 xContent
.set(aMedium
.GetContent());
404 bReadOnly
= aMedium
.IsReadOnly();
406 // maybe that IsStorage() already created an error!
409 uno::Reference
< embed::XStorage
> xStorage(aMedium
.GetStorage( false ));
410 if ( aMedium
.GetLastStorageCreationState() != ERRCODE_NONE
)
412 // error during storage creation means _here_ that the medium
413 // is broken, but we can not handle it in medium since unpossibility
414 // to create a storage does not _always_ means that the medium is broken
415 aMedium
.SetError(aMedium
.GetLastStorageCreationState(), OUString(OSL_LOG_PREFIX
));
416 if ( xInteraction
.is() )
421 InteractiveAppException
xException( empty
,
422 uno::Reference
< XInterface
>(),
423 InteractionClassification_ERROR
,
424 aMedium
.GetError() );
426 uno::Reference
< XInteractionRequest
> xRequest(
427 new ucbhelper::SimpleInteractionRequest( makeAny( xException
),
428 ucbhelper::CONTINUATION_APPROVE
) );
429 xInteraction
->handle( xRequest
);
431 catch ( Exception
& ) {};
434 else if ( xStorage
.is() )
438 OUString aFilterName
;
440 aFilterName
= pFilter
->GetName();
441 aTypeName
= SfxFilter::GetTypeFromStorage( xStorage
, pFilter
? pFilter
->IsOwnTemplateFormat() : false, &aFilterName
);
443 catch( const lang::WrappedTargetException
& aWrap
)
446 // Bail out early unless it's a deep detection.
449 packages::zip::ZipIOException aZipException
;
451 // repairing is done only if this type is requested from outside
452 if ( ( aWrap
.TargetException
>>= aZipException
) && !aTypeName
.isEmpty() )
454 if ( xInteraction
.is() )
456 // the package is broken one
457 aDocumentTitle
= aMedium
.GetURLObject().getName(
458 INetURLObject::LAST_SEGMENT
,
460 INetURLObject::DECODE_WITH_CHARSET
);
462 if ( !bRepairPackage
)
464 // ask the user whether he wants to try to repair
465 RequestPackageReparation
aRequest( aDocumentTitle
);
466 xInteraction
->handle( aRequest
.GetRequest() );
467 bRepairAllowed
= aRequest
.isApproved();
470 if ( !bRepairAllowed
)
472 // repair either not allowed or not successful
473 NotifyBrokenPackage
aNotifyRequest( aDocumentTitle
);
474 xInteraction
->handle( aNotifyRequest
.GetRequest() );
478 if ( !bRepairAllowed
)
482 catch( uno::RuntimeException
& )
486 catch( uno::Exception
& )
491 if ( !aTypeName
.isEmpty() )
492 pFilter
= SfxFilterMatcher("scalc").GetFilter4EA( aTypeName
);
497 // Non-storage format.
499 if (aTypeName
== "calc8_template" ||
500 aTypeName
== "calc8" ||
501 aTypeName
== "calc_StarOffice_XML_Calc" ||
502 aTypeName
== "calc_StarOffice_XML_Calc_Template")
503 // These types require storage. Bail out.
506 SvStream
* pStream
= aMedium
.GetInStream();
508 // No stream, no detection.
513 pStream
->Seek( STREAM_SEEK_TO_END
);
514 sal_Size nSize
= pStream
->Tell();
516 // Do not attempt to create an SotStorage on a
517 // 0-length stream as that would create the compound
518 // document header on the stream and effectively write to
522 const char* pSearchFilterName
= NULL
;
523 if (aTypeName
== "calc_Lotus")
525 if (!detectThisFormat(*pStream
, pLotus
) && !detectThisFormat(*pStream
, pLotusNew
) && !detectThisFormat(*pStream
, pLotus2
))
528 pSearchFilterName
= pFilterLotus
;
530 else if (aTypeName
== "calc_QPro")
532 if (!detectThisFormat(*pStream
, pQPro
))
535 pSearchFilterName
= pFilterQPro6
;
537 else if (aTypeName
== "calc_SYLK")
539 if (!detectThisFormat(*pStream
, pSylk
))
542 pSearchFilterName
= pFilterSylk
;
544 else if (aTypeName
== "calc_DIF")
546 if (!detectThisFormat(*pStream
, pDIF1
) && !detectThisFormat(*pStream
, pDIF2
))
549 pSearchFilterName
= pFilterDif
;
551 else if (aTypeName
== "calc_dBase")
553 if (!lcl_MayBeDBase(*pStream
))
556 pSearchFilterName
= pFilterDBase
;
559 if (!pSearchFilterName
)
562 pFilter
= aMatcher
.GetFilter4FilterName(OUString::createFromAscii(pSearchFilterName
));
568 if ( nIndexOfInputStream
== -1 && xStream
.is() )
570 // if input stream wasn't part of the descriptor, now it should be, otherwise the content would be opend twice
571 lDescriptor
.realloc( nPropertyCount
+ 1 );
572 lDescriptor
[nPropertyCount
].Name
= "InputStream";
573 lDescriptor
[nPropertyCount
].Value
<<= xStream
;
577 if ( nIndexOfContent
== -1 && xContent
.is() )
579 // if input stream wasn't part of the descriptor, now it should be, otherwise the content would be opend twice
580 lDescriptor
.realloc( nPropertyCount
+ 1 );
581 lDescriptor
[nPropertyCount
].Name
= "UCBContent";
582 lDescriptor
[nPropertyCount
].Value
<<= xContent
;
586 if ( bReadOnly
!= bWasReadOnly
)
588 if ( nIndexOfReadOnlyFlag
== -1 )
590 lDescriptor
.realloc( nPropertyCount
+ 1 );
591 lDescriptor
[nPropertyCount
].Name
= "ReadOnly";
592 lDescriptor
[nPropertyCount
].Value
<<= bReadOnly
;
596 lDescriptor
[nIndexOfReadOnlyFlag
].Value
<<= bReadOnly
;
599 if ( !bRepairPackage
&& bRepairAllowed
)
601 lDescriptor
.realloc( nPropertyCount
+ 1 );
602 lDescriptor
[nPropertyCount
].Name
= "RepairPackage";
603 lDescriptor
[nPropertyCount
].Value
<<= bRepairAllowed
;
606 bOpenAsTemplate
= sal_True
;
608 // TODO/LATER: set progress bar that should be used
611 if ( bOpenAsTemplate
)
613 if ( nIndexOfTemplateFlag
== -1 )
615 lDescriptor
.realloc( nPropertyCount
+ 1 );
616 lDescriptor
[nPropertyCount
].Name
= "AsTemplate";
617 lDescriptor
[nPropertyCount
].Value
<<= bOpenAsTemplate
;
621 lDescriptor
[nIndexOfTemplateFlag
].Value
<<= bOpenAsTemplate
;
624 if ( !aDocumentTitle
.isEmpty() )
626 // the title was set here
627 if ( nIndexOfDocumentTitle
== -1 )
629 lDescriptor
.realloc( nPropertyCount
+ 1 );
630 lDescriptor
[nPropertyCount
].Name
= "DocumentTitle";
631 lDescriptor
[nPropertyCount
].Value
<<= aDocumentTitle
;
635 lDescriptor
[nIndexOfDocumentTitle
].Value
<<= aDocumentTitle
;
641 if (nIndexOfFilterName
== -1)
643 lDescriptor
.realloc(nPropertyCount
+ 1);
644 lDescriptor
[nPropertyCount
].Name
= "FilterName";
645 lDescriptor
[nPropertyCount
].Value
<<= pFilter
->GetName();
649 lDescriptor
[nIndexOfFilterName
].Value
<<= pFilter
->GetName();
654 OUString SAL_CALL
ScFilterDetect::getImplementationName() throw (uno::RuntimeException
)
656 return impl_getStaticImplementationName();
659 sal_Bool
ScFilterDetect::supportsService( const OUString
& sServiceName
)
660 throw (uno::RuntimeException
)
662 uno::Sequence
<OUString
> seqServiceNames(getSupportedServiceNames());
663 const OUString
* pArray
= seqServiceNames
.getConstArray();
664 for ( sal_Int32 nCounter
=0; nCounter
<seqServiceNames
.getLength(); nCounter
++ )
666 if ( pArray
[nCounter
] == sServiceName
)
674 com::sun::star::uno::Sequence
<OUString
> ScFilterDetect::getSupportedServiceNames()
675 throw (uno::RuntimeException
)
677 return impl_getStaticSupportedServiceNames();
680 uno::Sequence
<OUString
> ScFilterDetect::impl_getStaticSupportedServiceNames()
682 uno::Sequence
<OUString
> seqServiceNames(1);
683 seqServiceNames
.getArray()[0] = "com.sun.star.frame.ExtendedTypeDetection";
684 return seqServiceNames
;
687 OUString
ScFilterDetect::impl_getStaticImplementationName()
689 return OUString("com.sun.star.comp.calc.FormatDetector");
692 uno::Reference
<uno::XInterface
> ScFilterDetect::impl_createInstance(
693 const uno::Reference
<uno::XComponentContext
>& xContext
) throw (uno::Exception
)
695 return static_cast<cppu::OWeakObject
*>(new ScFilterDetect(xContext
));
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */