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 
20 
21 #include "filterfactory.hxx"
22 #include "constant.hxx"
23 
24 #include <com/sun/star/lang/XInitialization.hpp>
25 #include <comphelper/enumhelper.hxx>
26 #include <comphelper/sequence.hxx>
27 #include <officecfg/Setup.hxx>
28 #include <officecfg/TypeDetection/UISort.hxx>
29 
30 
31 namespace filter::config{
32 
GetTheFilterCache()33 FilterCache& GetTheFilterCache()
34 {
35     static FilterCache CACHE;
36     return CACHE;
37 }
38 
39 /** @short  define all possible parts of a filter query.
40 
41     @descr  syntax: "<query>[:<param>[=<value>]]"
42             e.g.:   "_query_writer:default_first:use_order:sort_prop=uiname"
43 
44             argument                        description                                     default
45             -----------------------------------------------------------------------------------------------
46             iflags=<mask>                   include filters by given mask                   0
47             eflags=<mask>                   exclude filters by given mask                   0
48             sort_prop=<[name,uiname]>       sort by internal name or uiname                 name
49             descending                      sort descending                                 false
50             use_order                       use order flag of filters for sorting           false
51             default_first                   set default filter on top of return list        false
52             case_sensitive                  compare "sort_prop" case sensitive              false
53  */
54 
FilterFactory(const css::uno::Reference<css::uno::XComponentContext> & rxContext)55 FilterFactory::FilterFactory(const css::uno::Reference< css::uno::XComponentContext >& rxContext)
56     : m_xContext(rxContext)
57 {
58     static const css::uno::Sequence<OUString> sServiceNames { u"com.sun.star.document.FilterFactory"_ustr };
59     BaseContainer::init(u"com.sun.star.comp.filter.config.FilterFactory"_ustr   ,
60                          sServiceNames,
61                         FilterCache::E_FILTER                         );
62 }
63 
64 
~FilterFactory()65 FilterFactory::~FilterFactory()
66 {
67 }
68 
69 
createInstance(const OUString & sFilter)70 css::uno::Reference< css::uno::XInterface > SAL_CALL FilterFactory::createInstance(const OUString& sFilter)
71 {
72     return createInstanceWithArguments(sFilter, css::uno::Sequence< css::uno::Any >());
73 }
74 
75 
createInstanceWithArguments(const OUString & sFilter,const css::uno::Sequence<css::uno::Any> & lArguments)76 css::uno::Reference< css::uno::XInterface > SAL_CALL FilterFactory::createInstanceWithArguments(const OUString&                     sFilter   ,
77                                                                                                 const css::uno::Sequence< css::uno::Any >& lArguments)
78 {
79     // SAFE ->
80     std::unique_lock aLock(m_aMutex);
81 
82     auto & cache = GetTheFilterCache();
83 
84     // search filter on cache
85     CacheItem aFilter = cache.getItem(FilterCache::E_FILTER, sFilter);
86     OUString sFilterService;
87     aFilter[PROPNAME_FILTERSERVICE] >>= sFilterService;
88 
89     // create service instance
90     css::uno::Reference< css::uno::XInterface > xFilter;
91     if (!sFilterService.isEmpty())
92         xFilter = m_xContext->getServiceManager()->createInstanceWithContext(sFilterService, m_xContext);
93 
94     // initialize filter
95     css::uno::Reference< css::lang::XInitialization > xInit(xFilter, css::uno::UNO_QUERY);
96     if (xInit.is())
97     {
98         // format: lInitData[0] = seq<PropertyValue>, which contains all configuration properties of this filter
99         //         lInitData[1] = lArguments[0]
100         //         ...
101         //         lInitData[n] = lArguments[n-1]
102         css::uno::Sequence< css::beans::PropertyValue > lConfig;
103         aFilter >> lConfig;
104 
105         ::std::vector< css::uno::Any > stlArguments(comphelper::sequenceToContainer< ::std::vector< css::uno::Any > >(lArguments));
106         stlArguments.insert(stlArguments.begin(), css::uno::Any(lConfig));
107 
108         xInit->initialize(comphelper::containerToSequence(stlArguments));
109     }
110 
111     return xFilter;
112     // <- SAFE
113 }
114 
115 
getAvailableServiceNames()116 css::uno::Sequence< OUString > SAL_CALL FilterFactory::getAvailableServiceNames()
117 {
118     /* Attention: Instead of getElementNames() this method have to return only filter names,
119                   which can be created as UNO Services really. That's why we search for filters,
120                   which don't have a valid value for the property "FilterService".
121                   Of course we can't check for corrupted service names here. We can check
122                   for empty strings only...
123     */
124     css::beans::NamedValue lEProps[] {
125         { PROPNAME_FILTERSERVICE, css::uno::Any(OUString()) } };
126 
127     std::vector<OUString> lUNOFilters;
128     try
129     {
130         lUNOFilters = GetTheFilterCache().getMatchingItemsByProps(FilterCache::E_FILTER, {}, lEProps);
131     }
132     catch(const css::uno::RuntimeException&)
133         { throw; }
134     catch(const css::uno::Exception&)
135         { lUNOFilters.clear(); }
136 
137     return comphelper::containerToSequence(lUNOFilters);
138 }
139 
140 
createSubSetEnumerationByQuery(const OUString & sQuery)141 css::uno::Reference< css::container::XEnumeration > SAL_CALL FilterFactory::createSubSetEnumerationByQuery(const OUString& sQuery)
142 {
143     // reject old deprecated queries ...
144     if (sQuery.startsWith("_filterquery_"))
145         throw css::uno::RuntimeException(
146                     u"Use of deprecated and now unsupported query!"_ustr,
147                     static_cast< css::container::XContainerQuery* >(this));
148 
149     // convert "_query_xxx:..." to "getByDocService=xxx:..."
150     OUString sNewQuery(sQuery);
151     sal_Int32 pos = sNewQuery.indexOf("_query_");
152     if (pos != -1)
153     {
154         OSL_FAIL("DEPRECATED!\nPlease use new query format: 'matchByDocumentService=...'");
155         sNewQuery = OUString::Concat("matchByDocumentService=") + sNewQuery.subView(7);
156     }
157 
158     // analyze query and split it into its tokens
159     QueryTokenizer                  lTokens(sNewQuery);
160     std::vector<OUString>           lEnumSet;
161 
162     // start query
163     // (see attention comment below!)
164     if (lTokens.valid())
165     {
166         // SAFE -> ----------------------
167         {
168             std::unique_lock aLock(m_aMutex);
169             // May be not all filters was loaded ...
170             // But we need it now!
171             impl_loadOnDemand(aLock);
172         }
173         // <- SAFE ----------------------
174 
175         if (lTokens.find(QUERY_IDENTIFIER_GETPREFERREDFILTERFORTYPE) != lTokens.end())
176             OSL_FAIL("DEPRECATED!\nPlease use prop search at the TypeDetection container!");
177         else
178         if (lTokens.find(QUERY_IDENTIFIER_MATCHBYDOCUMENTSERVICE) != lTokens.end())
179             lEnumSet = impl_queryMatchByDocumentService(lTokens);
180         else
181         if (lTokens.find(QUERY_IDENTIFIER_GET_SORTED_FILTERLIST) != lTokens.end())
182             lEnumSet = impl_getSortedFilterList(lTokens);
183     }
184 
185     // pack list of item names as an enum list
186     // Attention: Do not return empty reference for empty list!
187     // The outside check "hasMoreElements()" should be enough, to detect this state :-)
188     return new ::comphelper::OEnumerationByName(this, std::move(lEnumSet));
189 }
190 
191 
impl_queryMatchByDocumentService(const QueryTokenizer & lTokens) const192 std::vector<OUString> FilterFactory::impl_queryMatchByDocumentService(const QueryTokenizer& lTokens) const
193 {
194     // analyze query
195     QueryTokenizer::const_iterator pIt;
196 
197     OUString sDocumentService;
198     sal_Int32       nIFlags = 0;
199     sal_Int32       nEFlags = 0;
200 
201     pIt = lTokens.find(QUERY_IDENTIFIER_MATCHBYDOCUMENTSERVICE);
202     if (pIt != lTokens.end())
203         sDocumentService = pIt->second;
204 
205 #define COMP_HACK
206 #ifdef COMP_HACK
207     if ( sDocumentService == "writer" )
208     {
209         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
210         sDocumentService = "com.sun.star.text.TextDocument";
211     }
212     else if ( sDocumentService == "web" )
213     {
214         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
215         sDocumentService = "com.sun.star.text.WebDocument";
216     }
217     else if ( sDocumentService == "global" )
218     {
219         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
220         sDocumentService = "com.sun.star.text.GlobalDocument";
221     }
222     else if ( sDocumentService == "calc" )
223     {
224         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
225         sDocumentService = "com.sun.star.sheet.SpreadsheetDocument";
226     }
227     else if ( sDocumentService == "draw" )
228     {
229         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
230         sDocumentService = "com.sun.star.drawing.DrawingDocument";
231     }
232     else if ( sDocumentService == "impress" )
233     {
234         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
235         sDocumentService = "com.sun.star.presentation.PresentationDocument";
236     }
237     else if ( sDocumentService == "math" )
238     {
239         OSL_FAIL("DEPRECATED!\nPlease use right document service for filter query!");
240         sDocumentService = "com.sun.star.formula.FormulaProperties";
241     }
242 #endif
243 
244     pIt = lTokens.find(QUERY_PARAM_IFLAGS);
245     if (pIt != lTokens.end())
246         nIFlags = pIt->second.toInt32();
247 
248     pIt = lTokens.find(QUERY_PARAM_EFLAGS);
249     if (pIt != lTokens.end())
250         nEFlags = pIt->second.toInt32();
251 
252     // SAFE -> ----------------------
253     std::unique_lock aLock(m_aMutex);
254 
255     // search suitable filters
256     FilterCache* pCache       = impl_getWorkingCache(aLock);
257     std::vector<OUString> lFilterNames = pCache->getItemNames(FilterCache::E_FILTER);
258     std::vector<OUString> lResult      ;
259 
260     for (auto const& filterName : lFilterNames)
261     {
262         try
263         {
264             const CacheItem aFilter = pCache->getItem(FilterCache::E_FILTER, filterName);
265 
266             // "matchByDocumentService="                    => any filter will be addressed here
267             // "matchByDocumentService=all"                 => any filter will be addressed here
268             // "matchByDocumentService=com.sun.star..."     => only filter matching this document service will be addressed
269             OUString sCheckValue = aFilter.getUnpackedValueOrDefault(PROPNAME_DOCUMENTSERVICE, OUString());
270             if (
271                 (!sDocumentService.isEmpty()                   ) &&
272                 (sDocumentService != QUERY_CONSTVALUE_ALL      ) &&
273                 (sCheckValue != sDocumentService         )
274             )
275             {
276                 continue; // ignore filter -> try next one!
277             }
278 
279             // "iflags="        => not allowed
280             // "iflags=-1"      => not allowed
281             // "iflags=0"       => not useful
282             // "iflags=283648"  => only filter, which has set these flag field will be addressed
283             sal_Int32 nCheckValue = aFilter.getUnpackedValueOrDefault(PROPNAME_FLAGS, sal_Int32(0));
284             if (
285                 (nIFlags > 0                       ) &&
286                 ((nCheckValue & nIFlags) != nIFlags)
287             )
288             {
289                 continue; // ignore filter -> try next one!
290             }
291 
292             // "eflags="        => not allowed
293             // "eflags=-1"      => not allowed
294             // "eflags=0"       => not useful
295             // "eflags=283648"  => only filter, which has not set these flag field will be addressed
296             if (
297                 (nEFlags > 0                       ) &&
298                 ((nCheckValue & nEFlags) == nEFlags)
299             )
300             {
301                 continue; // ignore filter -> try next one!
302             }
303 
304             // OK - this filter passed all checks.
305             // It match the query ...
306             lResult.push_back(filterName);
307         }
308         catch(const css::uno::RuntimeException&)
309             { throw; }
310         catch(const css::uno::Exception&)
311             { continue; }
312     }
313 
314     aLock.unlock();
315     // <- SAFE ----------------------
316 
317     return lResult;
318 }
319 
320 namespace {
321 
322 class stlcomp_removeIfMatchFlags
323 {
324     private:
325         FilterCache* m_pCache ;
326         sal_Int32    m_nFlags ;
327         bool     m_bIFlags;
328 
329     public:
stlcomp_removeIfMatchFlags(FilterCache * pCache,sal_Int32 nFlags,bool bIFlags)330         stlcomp_removeIfMatchFlags(FilterCache* pCache ,
331                                    sal_Int32    nFlags ,
332                                    bool     bIFlags)
333             : m_pCache (pCache )
334             , m_nFlags (nFlags )
335             , m_bIFlags(bIFlags)
336         {}
337 
operator ()(const OUString & sFilter) const338         bool operator() (const OUString& sFilter) const
339         {
340             try
341             {
342                 const CacheItem aFilter = m_pCache->getItem(FilterCache::E_FILTER, sFilter);
343                 sal_Int32 nFlags  = aFilter.getUnpackedValueOrDefault(PROPNAME_FLAGS, (sal_Int32(0)));
344 
345                 bool bMatch = false;
346                 if (m_bIFlags)
347                     // IFlags are interpreted as ALL_FLAGS_MUST_MATCH !
348                     bMatch = ((nFlags & m_nFlags) == m_nFlags);
349                 else
350                     // EFlags are interpreted as AT_LEAST_ONE_FLAG_MUST_MATCH !
351                     bMatch = !(nFlags & m_nFlags);
352                 // We are asked for bRemove ! And bMatch = !bRemove => so bRemove = !bMatch .-)
353                 return !bMatch;
354             }
355             catch(const css::container::NoSuchElementException &)
356             {
357                 return true;
358             }
359         }
360 };
361 
362 }
363 
impl_getSortedFilterList(const QueryTokenizer & lTokens) const364 std::vector<OUString> FilterFactory::impl_getSortedFilterList(const QueryTokenizer& lTokens) const
365 {
366     // analyze the given query parameter
367     QueryTokenizer::const_iterator pIt1;
368 
369     OUString sModule;
370     sal_Int32       nIFlags = -1;
371     sal_Int32       nEFlags = -1;
372 
373     pIt1 = lTokens.find(QUERY_PARAM_MODULE);
374     if (pIt1 != lTokens.end())
375         sModule = pIt1->second;
376     pIt1 = lTokens.find(QUERY_PARAM_IFLAGS);
377     if (pIt1 != lTokens.end())
378         nIFlags = pIt1->second.toInt32();
379     pIt1 = lTokens.find(QUERY_PARAM_EFLAGS);
380     if (pIt1 != lTokens.end())
381         nEFlags = pIt1->second.toInt32();
382 
383     // simple search for filters of one specific module.
384     std::vector<OUString> lFilterList;
385     if (!sModule.isEmpty())
386         lFilterList = impl_getSortedFilterListForModule(sModule, nIFlags, nEFlags);
387     else
388     {
389         // more complex search for all filters
390         // We check first, which office modules are installed...
391         const css::uno::Sequence<OUString> lModules = impl_getListOfInstalledModules();
392         for (auto const& module : lModules)
393         {
394             std::vector<OUString> lFilters4Module = impl_getSortedFilterListForModule(module, nIFlags, nEFlags);
395             for (auto const& filter4Module : lFilters4Module)
396             {
397                 lFilterList.push_back(filter4Module);
398             }
399         }
400     }
401 
402     return lFilterList;
403 }
404 
405 
impl_getListOfInstalledModules()406 css::uno::Sequence<OUString> FilterFactory::impl_getListOfInstalledModules()
407 {
408     css::uno::Reference< css::container::XNameAccess > xModuleConfig = officecfg::Setup::Office::Factories::get();
409     return xModuleConfig->getElementNames();
410 }
411 
412 
impl_getSortedFilterListForModule(const OUString & sModule,sal_Int32 nIFlags,sal_Int32 nEFlags) const413 std::vector<OUString> FilterFactory::impl_getSortedFilterListForModule(const OUString& sModule,
414                                                                     sal_Int32        nIFlags,
415                                                                     sal_Int32        nEFlags) const
416 {
417     std::vector<OUString> lSortedFilters = impl_readSortedFilterListFromConfig(sModule);
418 
419     // get all filters for the requested module
420     css::beans::NamedValue lIProps[] { { PROPNAME_DOCUMENTSERVICE, css::uno::Any(sModule) } };
421 
422     // SAFE -> ----------------------
423     std::unique_lock aLock(m_aMutex);
424     FilterCache* pCache        = impl_getWorkingCache(aLock);
425     std::vector<OUString> lOtherFilters = pCache->getMatchingItemsByProps(FilterCache::E_FILTER, lIProps);
426     aLock.unlock();
427     // <- SAFE ----------------------
428 
429     // bring "other" filters in an alphabetical order
430     // It's needed below.
431     ::std::sort(lOtherFilters.begin(), lOtherFilters.end());
432 
433     // merge both lists together
434     std::vector<OUString> lMergedFilters = lSortedFilters;
435     const auto itlSortedFiltersEnd = lSortedFilters.cend();
436     for (auto const& otherFilter : lOtherFilters)
437     {
438         if (::std::find(lSortedFilters.cbegin(), lSortedFilters.cend(), otherFilter) == itlSortedFiltersEnd)
439             lMergedFilters.push_back(otherFilter);
440     }
441 
442     // remove all filters from this merged list, which does not fit the flag specification
443     if (nIFlags != -1)
444     {
445         std::erase_if(lMergedFilters, stlcomp_removeIfMatchFlags(pCache, nIFlags, true));
446     }
447     if (nEFlags != -1)
448     {
449         std::erase_if(lMergedFilters, stlcomp_removeIfMatchFlags(pCache, nEFlags, false));
450     }
451 
452     // sort the default filter to the front of this list
453     // TODO
454 
455     return lMergedFilters;
456 }
457 
458 
impl_readSortedFilterListFromConfig(const OUString & sModule)459 std::vector<OUString> FilterFactory::impl_readSortedFilterListFromConfig(const OUString& sModule)
460 {
461     try
462     {
463         css::uno::Reference< css::container::XNameAccess > xUISortConfig = officecfg::TypeDetection::UISort::ModuleDependendFilterOrder::get();
464         // don't check the module name here. If it does not exists, an exception is thrown and caught below.
465         // We return an empty list as result then.
466         css::uno::Reference< css::container::XNameAccess > xModule;
467         xUISortConfig->getByName(sModule) >>= xModule;
468         if (xModule.is()) // only to be on the safe side of life if the exception was not thrown .-)
469         {
470             // Note: conversion of the returned Any to std::vector<OUString> throws
471             // an IllegalArgumentException if the type does not match ...
472             // but it resets the std::vector<OUString> to a length of 0 if the Any is empty!
473             std::vector<OUString> lSortedFilters(
474                     comphelper::sequenceToContainer< std::vector<OUString> >(xModule->getByName(PROPNAME_SORTEDFILTERLIST).get<css::uno::Sequence<OUString> >()));
475             return lSortedFilters;
476         }
477     }
478     catch(const css::uno::RuntimeException&)
479         { throw; }
480     catch(const css::uno::Exception&)
481         {}
482 
483     return std::vector<OUString>();
484 }
485 
486 } // namespace filter
487 
488 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
filter_FilterFactory_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)489 filter_FilterFactory_get_implementation(
490     css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
491 {
492     return cppu::acquire(new filter::config::FilterFactory(context));
493 }
494 
495 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
496