summaryrefslogtreecommitdiffstats
path: root/linguistic/source
diff options
context:
space:
mode:
Diffstat (limited to 'linguistic/source')
-rw-r--r--linguistic/source/convdic.cxx583
-rw-r--r--linguistic/source/convdic.hxx126
-rw-r--r--linguistic/source/convdiclist.cxx539
-rw-r--r--linguistic/source/convdiclist.hxx90
-rw-r--r--linguistic/source/convdicxml.cxx342
-rw-r--r--linguistic/source/convdicxml.hxx104
-rw-r--r--linguistic/source/defs.hxx94
-rw-r--r--linguistic/source/dicimp.cxx1089
-rw-r--r--linguistic/source/dicimp.hxx174
-rw-r--r--linguistic/source/dlistimp.cxx790
-rw-r--r--linguistic/source/dlistimp.hxx121
-rw-r--r--linguistic/source/gciterator.cxx1179
-rw-r--r--linguistic/source/gciterator.hxx211
-rw-r--r--linguistic/source/hhconvdic.cxx120
-rw-r--r--linguistic/source/hhconvdic.hxx45
-rw-r--r--linguistic/source/hyphdsp.cxx705
-rw-r--r--linguistic/source/hyphdsp.hxx131
-rw-r--r--linguistic/source/hyphdta.cxx164
-rw-r--r--linguistic/source/iprcache.cxx227
-rw-r--r--linguistic/source/lng.component42
-rw-r--r--linguistic/source/lngopt.cxx424
-rw-r--r--linguistic/source/lngopt.hxx202
-rw-r--r--linguistic/source/lngprophelp.cxx750
-rw-r--r--linguistic/source/lngsvcmgr.cxx1830
-rw-r--r--linguistic/source/lngsvcmgr.hxx167
-rw-r--r--linguistic/source/misc.cxx761
-rw-r--r--linguistic/source/misc2.cxx168
-rw-r--r--linguistic/source/spelldsp.cxx829
-rw-r--r--linguistic/source/spelldsp.hxx141
-rw-r--r--linguistic/source/spelldta.cxx270
-rw-r--r--linguistic/source/thesdsp.cxx240
-rw-r--r--linguistic/source/thesdsp.hxx92
32 files changed, 12750 insertions, 0 deletions
diff --git a/linguistic/source/convdic.cxx b/linguistic/source/convdic.cxx
new file mode 100644
index 000000000..d2fb118b5
--- /dev/null
+++ b/linguistic/source/convdic.cxx
@@ -0,0 +1,583 @@
+/* -*- 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 <cppuhelper/factory.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <osl/mutex.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/diagnose_ex.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <unotools/streamwrap.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+
+#include <com/sun/star/linguistic2/ConversionPropertyType.hpp>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/lang/EventObject.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/util/XFlushListener.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/InputSource.hpp>
+#include <com/sun/star/xml/sax/SAXParseException.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/container/ElementExistException.hpp>
+
+
+#include "convdic.hxx"
+#include "convdicxml.hxx"
+#include <linguistic/misc.hxx>
+#include <utility>
+
+using namespace utl;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+static void ReadThroughDic( const OUString &rMainURL, ConvDicXMLImport &rImport )
+{
+ if (rMainURL.isEmpty())
+ return;
+ DBG_ASSERT(!INetURLObject( rMainURL ).HasError(), "invalid URL");
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ // get xInputStream stream
+ uno::Reference< io::XInputStream > xIn;
+ try
+ {
+ uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
+ xIn = xAccess->openFileRead( rMainURL );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "failed to get input stream" );
+ }
+ if (!xIn.is())
+ return;
+
+ // prepare ParserInputSource
+ xml::sax::InputSource aParserInput;
+ aParserInput.aInputStream = xIn;
+
+ // finally, parser the stream
+ try
+ {
+ rImport.parseStream( aParserInput ); // implicitly calls ConvDicXMLImport::CreateContext
+ }
+ catch( xml::sax::SAXParseException& )
+ {
+ TOOLS_WARN_EXCEPTION("linguistic", "");
+ }
+ catch( xml::sax::SAXException& )
+ {
+ TOOLS_WARN_EXCEPTION("linguistic", "");
+ }
+ catch( io::IOException& )
+ {
+ TOOLS_WARN_EXCEPTION("linguistic", "");
+ }
+}
+
+bool IsConvDic( const OUString &rFileURL, LanguageType &nLang, sal_Int16 &nConvType )
+{
+ bool bRes = false;
+
+ if (rFileURL.isEmpty())
+ return bRes;
+
+ // check if file extension matches CONV_DIC_EXT
+ OUString aExt;
+ sal_Int32 nPos = rFileURL.lastIndexOf( '.' );
+ if (-1 != nPos)
+ aExt = rFileURL.copy( nPos + 1 ).toAsciiLowerCase();
+ if (aExt != CONV_DIC_EXT)
+ return bRes;
+
+ // first argument being 0 should stop the file from being parsed
+ // up to the end (reading all entries) when the required
+ // data (language, conversion type) is found.
+ rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( nullptr );
+
+ ReadThroughDic( rFileURL, *pImport ); // will implicitly add the entries
+ bRes = !LinguIsUnspecified( pImport->GetLanguage()) &&
+ pImport->GetConversionType() != -1;
+ DBG_ASSERT( bRes, "conversion dictionary corrupted?" );
+
+ if (bRes)
+ {
+ nLang = pImport->GetLanguage();
+ nConvType = pImport->GetConversionType();
+ }
+
+ return bRes;
+}
+
+
+ConvDic::ConvDic(
+ OUString aName_,
+ LanguageType nLang,
+ sal_Int16 nConvType,
+ bool bBiDirectional,
+ const OUString &rMainURL) :
+ aFlushListeners( GetLinguMutex() ),
+ aMainURL(rMainURL), aName(std::move(aName_)), nLanguage(nLang),
+ nConversionType(nConvType),
+ nMaxLeftCharCount(0), nMaxRightCharCount(0),
+ bMaxCharCountIsValid(true),
+ bNeedEntries(true),
+ bIsModified(false), bIsActive(false)
+
+{
+ if (bBiDirectional)
+ pFromRight.reset( new ConvMap );
+ if (nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL)
+ pConvPropType.reset( new PropTypeMap );
+
+ if( !rMainURL.isEmpty() )
+ {
+ bool bExists = false;
+ IsReadOnly( rMainURL, &bExists );
+
+ if( !bExists ) // new empty dictionary
+ {
+ bNeedEntries = false;
+ //! create physical representation of an **empty** dictionary
+ //! that could be found by the dictionary-list implementation
+ // (Note: empty dictionaries are not just empty files!)
+ Save();
+ }
+ }
+ else
+ {
+ bNeedEntries = false;
+ }
+}
+
+
+ConvDic::~ConvDic()
+{
+}
+
+
+void ConvDic::Load()
+{
+ DBG_ASSERT( !bIsModified, "dictionary is modified. Really do 'Load'?" );
+
+ //!! prevent function from being called recursively via HasEntry, AddEntry
+ bNeedEntries = false;
+ rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( this );
+ ReadThroughDic( aMainURL, *pImport ); // will implicitly add the entries
+ bIsModified = false;
+}
+
+
+void ConvDic::Save()
+{
+ DBG_ASSERT( !bNeedEntries, "saving while entries missing" );
+ if (aMainURL.isEmpty() || bNeedEntries)
+ return;
+ DBG_ASSERT(!INetURLObject( aMainURL ).HasError(), "invalid URL");
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ // get XOutputStream stream
+ uno::Reference< io::XStream > xStream;
+ try
+ {
+ uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
+ xStream = xAccess->openFileReadWrite( aMainURL );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "failed to get input stream" );
+ }
+ if (!xStream.is())
+ return;
+
+ std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
+
+ // get XML writer
+ uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(xContext);
+
+ if (xStream.is())
+ {
+ // connect XML writer to output stream
+ xSaxWriter->setOutputStream( xStream->getOutputStream() );
+
+ // prepare arguments (prepend doc handler to given arguments)
+ rtl::Reference<ConvDicXMLExport> pExport = new ConvDicXMLExport( *this, aMainURL, xSaxWriter );
+ bool bRet = pExport->Export(); // write entries to file
+ DBG_ASSERT( !pStream->GetError(), "I/O error while writing to stream" );
+ if (bRet)
+ bIsModified = false;
+ }
+ DBG_ASSERT( !bIsModified, "dictionary still modified after save. Save failed?" );
+}
+
+
+ConvMap::iterator ConvDic::GetEntry( ConvMap &rMap, const OUString &rFirstText, std::u16string_view rSecondText )
+{
+ std::pair< ConvMap::iterator, ConvMap::iterator > aRange =
+ rMap.equal_range( rFirstText );
+ ConvMap::iterator aPos = rMap.end();
+ for (ConvMap::iterator aIt = aRange.first;
+ aIt != aRange.second && aPos == rMap.end();
+ ++aIt)
+ {
+ if ((*aIt).second == rSecondText)
+ aPos = aIt;
+ }
+ return aPos;
+}
+
+
+bool ConvDic::HasEntry( const OUString &rLeftText, std::u16string_view rRightText )
+{
+ if (bNeedEntries)
+ Load();
+ ConvMap::iterator aIt = GetEntry( aFromLeft, rLeftText, rRightText );
+ return aIt != aFromLeft.end();
+}
+
+
+void ConvDic::AddEntry( const OUString &rLeftText, const OUString &rRightText )
+{
+ if (bNeedEntries)
+ Load();
+
+ DBG_ASSERT(!HasEntry( rLeftText, rRightText), "entry already exists" );
+ aFromLeft .emplace( rLeftText, rRightText );
+ if (pFromRight)
+ pFromRight->emplace( rRightText, rLeftText );
+
+ if (bMaxCharCountIsValid)
+ {
+ if (rLeftText.getLength() > nMaxLeftCharCount)
+ nMaxLeftCharCount = static_cast<sal_Int16>(rLeftText.getLength());
+ if (pFromRight && rRightText.getLength() > nMaxRightCharCount)
+ nMaxRightCharCount = static_cast<sal_Int16>(rRightText.getLength());
+ }
+
+ bIsModified = true;
+}
+
+
+void ConvDic::RemoveEntry( const OUString &rLeftText, const OUString &rRightText )
+{
+ if (bNeedEntries)
+ Load();
+
+ ConvMap::iterator aLeftIt = GetEntry( aFromLeft, rLeftText, rRightText );
+ DBG_ASSERT( aLeftIt != aFromLeft.end(), "left map entry missing" );
+ aFromLeft .erase( aLeftIt );
+
+ if (pFromRight)
+ {
+ ConvMap::iterator aRightIt = GetEntry( *pFromRight, rRightText, rLeftText );
+ DBG_ASSERT( aRightIt != pFromRight->end(), "right map entry missing" );
+ pFromRight->erase( aRightIt );
+ }
+
+ bIsModified = true;
+ bMaxCharCountIsValid = false;
+}
+
+
+OUString SAL_CALL ConvDic::getName( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return aName;
+}
+
+
+Locale SAL_CALL ConvDic::getLocale( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return LanguageTag::convertToLocale( nLanguage );
+}
+
+
+sal_Int16 SAL_CALL ConvDic::getConversionType( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return nConversionType;
+}
+
+
+void SAL_CALL ConvDic::setActive( sal_Bool bActivate )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ bIsActive = bActivate;
+}
+
+
+sal_Bool SAL_CALL ConvDic::isActive( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return bIsActive;
+}
+
+
+void SAL_CALL ConvDic::clear( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ aFromLeft .clear();
+ if (pFromRight)
+ pFromRight->clear();
+ bNeedEntries = false;
+ bIsModified = true;
+ nMaxLeftCharCount = 0;
+ nMaxRightCharCount = 0;
+ bMaxCharCountIsValid = true;
+}
+
+
+uno::Sequence< OUString > SAL_CALL ConvDic::getConversions(
+ const OUString& aText,
+ sal_Int32 nStartPos,
+ sal_Int32 nLength,
+ ConversionDirection eDirection,
+ sal_Int32 /*nTextConversionOptions*/ )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
+ return uno::Sequence< OUString >();
+
+ if (bNeedEntries)
+ Load();
+
+ OUString aLookUpText( aText.copy(nStartPos, nLength) );
+ ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
+ aFromLeft : *pFromRight;
+ std::pair< ConvMap::iterator, ConvMap::iterator > aRange =
+ rConvMap.equal_range( aLookUpText );
+
+ std::vector<OUString> aRes;
+ auto nCount = static_cast<size_t>(std::distance(aRange.first, aRange.second));
+ aRes.reserve(nCount);
+
+ std::transform(aRange.first, aRange.second, std::back_inserter(aRes),
+ [](ConvMap::const_reference rEntry) { return rEntry.second; });
+
+ return comphelper::containerToSequence(aRes);
+}
+
+
+uno::Sequence< OUString > SAL_CALL ConvDic::getConversionEntries(
+ ConversionDirection eDirection )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
+ return uno::Sequence< OUString >();
+
+ if (bNeedEntries)
+ Load();
+
+ ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
+ aFromLeft : *pFromRight;
+ std::vector<OUString> aRes;
+ aRes.reserve(rConvMap.size());
+ for (auto const& elem : rConvMap)
+ {
+ OUString aCurEntry( elem.first );
+ // skip duplicate entries ( duplicate = duplicate entries
+ // respective to the evaluated side (FROM_LEFT or FROM_RIGHT).
+ // Thus if FROM_LEFT is evaluated for pairs (A,B) and (A,C)
+ // only one entry for A will be returned in the result)
+ if (std::find(aRes.begin(), aRes.end(), aCurEntry) == aRes.end())
+ aRes.push_back(aCurEntry);
+ }
+
+ return comphelper::containerToSequence(aRes);
+}
+
+
+void SAL_CALL ConvDic::addEntry(
+ const OUString& aLeftText,
+ const OUString& aRightText )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (bNeedEntries)
+ Load();
+ if (HasEntry( aLeftText, aRightText ))
+ throw container::ElementExistException();
+ AddEntry( aLeftText, aRightText );
+}
+
+
+void SAL_CALL ConvDic::removeEntry(
+ const OUString& aLeftText,
+ const OUString& aRightText )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (bNeedEntries)
+ Load();
+ if (!HasEntry( aLeftText, aRightText ))
+ throw container::NoSuchElementException();
+ RemoveEntry( aLeftText, aRightText );
+}
+
+
+sal_Int16 SAL_CALL ConvDic::getMaxCharCount( ConversionDirection eDirection )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
+ {
+ DBG_ASSERT( nMaxRightCharCount == 0, "max right char count should be 0" );
+ return 0;
+ }
+
+ if (bNeedEntries)
+ Load();
+
+ if (!bMaxCharCountIsValid)
+ {
+ nMaxLeftCharCount = 0;
+ for (auto const& elem : aFromLeft)
+ {
+ sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
+ if (nTmp > nMaxLeftCharCount)
+ nMaxLeftCharCount = nTmp;
+ }
+
+ nMaxRightCharCount = 0;
+ if (pFromRight)
+ {
+ for (auto const& elem : *pFromRight)
+ {
+ sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
+ if (nTmp > nMaxRightCharCount)
+ nMaxRightCharCount = nTmp;
+ }
+ }
+
+ bMaxCharCountIsValid = true;
+ }
+ sal_Int16 nRes = eDirection == ConversionDirection_FROM_LEFT ?
+ nMaxLeftCharCount : nMaxRightCharCount;
+ DBG_ASSERT( nRes >= 0, "invalid MaxCharCount" );
+ return nRes;
+}
+
+
+void SAL_CALL ConvDic::setPropertyType(
+ const OUString& rLeftText,
+ const OUString& rRightText,
+ sal_Int16 nPropertyType )
+{
+ bool bHasElement = HasEntry( rLeftText, rRightText);
+ if (!bHasElement)
+ throw container::NoSuchElementException();
+
+ // currently we assume that entries with the same left text have the
+ // same PropertyType even if the right text is different...
+ if (pConvPropType)
+ pConvPropType->emplace( rLeftText, nPropertyType );
+ bIsModified = true;
+}
+
+
+sal_Int16 SAL_CALL ConvDic::getPropertyType(
+ const OUString& rLeftText,
+ const OUString& rRightText )
+{
+ bool bHasElement = HasEntry( rLeftText, rRightText);
+ if (!bHasElement)
+ throw container::NoSuchElementException();
+
+ sal_Int16 nRes = ConversionPropertyType::NOT_DEFINED;
+ if (pConvPropType)
+ {
+ // still assuming that entries with same left text have same PropertyType
+ // even if they have different right text...
+ PropTypeMap::iterator aIt = pConvPropType->find( rLeftText );
+ if (aIt != pConvPropType->end())
+ nRes = (*aIt).second;
+ }
+ return nRes;
+}
+
+
+void SAL_CALL ConvDic::flush( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bIsModified)
+ return;
+
+ Save();
+
+ // notify listeners
+ EventObject aEvtObj;
+ aEvtObj.Source = uno::Reference< XFlushable >( this );
+ aFlushListeners.notifyEach( &util::XFlushListener::flushed, aEvtObj );
+}
+
+
+void SAL_CALL ConvDic::addFlushListener(
+ const uno::Reference< util::XFlushListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (rxListener.is())
+ aFlushListeners.addInterface( rxListener );
+}
+
+
+void SAL_CALL ConvDic::removeFlushListener(
+ const uno::Reference< util::XFlushListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (rxListener.is())
+ aFlushListeners.removeInterface( rxListener );
+}
+
+
+OUString SAL_CALL ConvDic::getImplementationName( )
+{
+ return "com.sun.star.lingu2.ConvDic";
+}
+
+sal_Bool SAL_CALL ConvDic::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL ConvDic::getSupportedServiceNames( )
+{
+ return { SN_CONV_DICTIONARY };
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/convdic.hxx b/linguistic/source/convdic.hxx
new file mode 100644
index 000000000..e5f1d4bea
--- /dev/null
+++ b/linguistic/source/convdic.hxx
@@ -0,0 +1,126 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_LINGUISTIC_SOURCE_CONVDIC_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_CONVDIC_HXX
+
+#include <com/sun/star/linguistic2/XConversionDictionary.hpp>
+#include <com/sun/star/linguistic2/XConversionPropertyType.hpp>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <i18nlangtag/lang.h>
+
+#include <memory>
+#include <unordered_map>
+
+// text conversion dictionary extension
+inline constexpr OUStringLiteral CONV_DIC_EXT = u"tcd";
+#define CONV_DIC_DOT_EXT ".tcd"
+
+inline constexpr OUStringLiteral SN_CONV_DICTIONARY = u"com.sun.star.linguistic2.ConversionDictionary";
+
+
+bool IsConvDic( const OUString &rFileURL, LanguageType &nLang, sal_Int16 &nConvType );
+
+typedef std::unordered_multimap<OUString, OUString> ConvMap;
+typedef std::unordered_multimap<OUString, sal_Int16> PropTypeMap;
+
+class ConvDic :
+ public ::cppu::WeakImplHelper
+ <
+ css::linguistic2::XConversionDictionary,
+ css::linguistic2::XConversionPropertyType,
+ css::util::XFlushable,
+ css::lang::XServiceInfo
+ >
+{
+ friend class ConvDicXMLExport;
+
+protected:
+
+ ::comphelper::OInterfaceContainerHelper3<css::util::XFlushListener> aFlushListeners;
+
+ ConvMap aFromLeft;
+ std::unique_ptr< ConvMap > pFromRight; // only available for bidirectional conversion dictionaries
+
+ std::unique_ptr< PropTypeMap > pConvPropType;
+
+ OUString aMainURL; // URL to file
+ OUString aName;
+ LanguageType nLanguage;
+ sal_Int16 nConversionType;
+ sal_Int16 nMaxLeftCharCount;
+ sal_Int16 nMaxRightCharCount;
+ bool bMaxCharCountIsValid;
+ bool bNeedEntries;
+ bool bIsModified;
+ bool bIsActive;
+
+ // disallow copy-constructor and assignment-operator for now
+ ConvDic(const ConvDic &);
+ ConvDic & operator = (const ConvDic &);
+
+ static ConvMap::iterator GetEntry( ConvMap &rMap, const OUString &rFirstText, std::u16string_view rSecondText );
+ void Load();
+ void Save();
+
+public:
+ ConvDic( OUString aName,
+ LanguageType nLanguage,
+ sal_Int16 nConversionType,
+ bool bBiDirectional,
+ const OUString &rMainURL);
+ virtual ~ConvDic() override;
+
+ // XConversionDictionary
+ virtual OUString SAL_CALL getName( ) override;
+ virtual css::lang::Locale SAL_CALL getLocale( ) override;
+ virtual sal_Int16 SAL_CALL getConversionType( ) override;
+ virtual void SAL_CALL setActive( sal_Bool bActivate ) override;
+ virtual sal_Bool SAL_CALL isActive( ) override;
+ virtual void SAL_CALL clear( ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getConversions( const OUString& aText, sal_Int32 nStartPos, sal_Int32 nLength, css::linguistic2::ConversionDirection eDirection, sal_Int32 nTextConversionOptions ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getConversionEntries( css::linguistic2::ConversionDirection eDirection ) override;
+ virtual void SAL_CALL addEntry( const OUString& aLeftText, const OUString& aRightText ) override;
+ virtual void SAL_CALL removeEntry( const OUString& aLeftText, const OUString& aRightText ) override;
+ virtual sal_Int16 SAL_CALL getMaxCharCount( css::linguistic2::ConversionDirection eDirection ) override;
+
+ // XConversionPropertyType
+ virtual void SAL_CALL setPropertyType( const OUString& aLeftText, const OUString& aRightText, ::sal_Int16 nPropertyType ) override;
+ virtual ::sal_Int16 SAL_CALL getPropertyType( const OUString& aLeftText, const OUString& aRightText ) override;
+
+ // XFlushable
+ virtual void SAL_CALL flush( ) override;
+ virtual void SAL_CALL addFlushListener( const css::uno::Reference< css::util::XFlushListener >& l ) override;
+ virtual void SAL_CALL removeFlushListener( const css::uno::Reference< css::util::XFlushListener >& l ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ bool HasEntry( const OUString &rLeftText, std::u16string_view rRightText );
+ void AddEntry( const OUString &rLeftText, const OUString &rRightText );
+ void RemoveEntry( const OUString &rLeftText, const OUString &rRightText );
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/convdiclist.cxx b/linguistic/source/convdiclist.cxx
new file mode 100644
index 000000000..6be588ea1
--- /dev/null
+++ b/linguistic/source/convdiclist.cxx
@@ -0,0 +1,539 @@
+/* -*- 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 <sal/config.h>
+
+#include <string_view>
+
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/linguistic2/ConversionDictionaryType.hpp>
+#include <com/sun/star/linguistic2/XConversionDictionaryList.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+#include <ucbhelper/content.hxx>
+#include <unotools/localfilehelper.hxx>
+#include <unotools/lingucfg.hxx>
+#include <tools/diagnose_ex.h>
+
+#include "convdic.hxx"
+#include "convdiclist.hxx"
+#include "hhconvdic.hxx"
+#include <linguistic/misc.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::container;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+static OUString GetConvDicMainURL( std::u16string_view rDicName, std::u16string_view rDirectoryURL )
+{
+ // build URL to use for new (persistent) dictionaries
+
+ OUString aFullDicName = OUString::Concat(rDicName) + CONV_DIC_DOT_EXT;
+
+ INetURLObject aURLObj;
+ aURLObj.SetSmartProtocol( INetProtocol::File );
+ aURLObj.SetSmartURL( rDirectoryURL );
+ aURLObj.Append( aFullDicName, INetURLObject::EncodeMechanism::All );
+ DBG_ASSERT(!aURLObj.HasError(), "invalid URL");
+ if (aURLObj.HasError())
+ return OUString();
+ else
+ return aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri );
+}
+
+class ConvDicNameContainer :
+ public cppu::WeakImplHelper< css::container::XNameContainer >
+{
+ std::vector< uno::Reference< XConversionDictionary > > aConvDics;
+
+ sal_Int32 GetIndexByName_Impl( std::u16string_view rName );
+
+public:
+ ConvDicNameContainer();
+ ConvDicNameContainer(const ConvDicNameContainer&) = delete;
+ ConvDicNameContainer& operator=(const ConvDicNameContainer&) = delete;
+
+ // XElementAccess
+ virtual css::uno::Type SAL_CALL getElementType( ) override;
+ virtual sal_Bool SAL_CALL hasElements( ) override;
+
+ // XNameAccess
+ virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override;
+ virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override;
+
+ // XNameReplace
+ virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override;
+
+ // XNameContainer
+ virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override;
+ virtual void SAL_CALL removeByName( const OUString& Name ) override;
+
+ // looks for conversion dictionaries with the specified extension
+ // in the directory and adds them to the container
+ void AddConvDics( const OUString &rSearchDirPathURL, const OUString &rExtension );
+
+ // calls Flush for the dictionaries that support XFlushable
+ void FlushDics() const;
+
+ sal_Int32 GetCount() const { return aConvDics.size(); }
+ uno::Reference< XConversionDictionary > GetByName( std::u16string_view rName );
+
+ const uno::Reference< XConversionDictionary >& GetByIndex( sal_Int32 nIdx )
+ {
+ return aConvDics[nIdx];
+ }
+};
+
+ConvDicNameContainer::ConvDicNameContainer()
+{
+}
+
+void ConvDicNameContainer::FlushDics() const
+{
+ sal_Int32 nLen = aConvDics.size();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ uno::Reference< util::XFlushable > xFlush( aConvDics[i] , UNO_QUERY );
+ if (xFlush.is())
+ {
+ try
+ {
+ xFlush->flush();
+ }
+ catch(Exception &)
+ {
+ OSL_FAIL( "flushing of conversion dictionary failed" );
+ }
+ }
+ }
+}
+
+sal_Int32 ConvDicNameContainer::GetIndexByName_Impl(
+ std::u16string_view rName )
+{
+ sal_Int32 nRes = -1;
+ sal_Int32 nLen = aConvDics.size();
+ for (sal_Int32 i = 0; i < nLen && nRes == -1; ++i)
+ {
+ if (rName == aConvDics[i]->getName())
+ nRes = i;
+ }
+ return nRes;
+}
+
+uno::Reference< XConversionDictionary > ConvDicNameContainer::GetByName(
+ std::u16string_view rName )
+{
+ uno::Reference< XConversionDictionary > xRes;
+ sal_Int32 nIdx = GetIndexByName_Impl( rName );
+ if ( nIdx != -1)
+ xRes = aConvDics[nIdx];
+ return xRes;
+}
+
+uno::Type SAL_CALL ConvDicNameContainer::getElementType( )
+{
+ return cppu::UnoType<XConversionDictionary>::get();
+}
+
+sal_Bool SAL_CALL ConvDicNameContainer::hasElements( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return !aConvDics.empty();
+}
+
+uno::Any SAL_CALL ConvDicNameContainer::getByName( const OUString& rName )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ uno::Reference< XConversionDictionary > xRes( GetByName( rName ) );
+ if (!xRes.is())
+ throw NoSuchElementException();
+ return Any( xRes );
+}
+
+uno::Sequence< OUString > SAL_CALL ConvDicNameContainer::getElementNames( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector<OUString> aRes;
+ aRes.reserve(aConvDics.size());
+
+ std::transform(aConvDics.begin(), aConvDics.end(), std::back_inserter(aRes),
+ [](const uno::Reference<XConversionDictionary>& rDic) { return rDic->getName(); });
+
+ return comphelper::containerToSequence(aRes);
+}
+
+sal_Bool SAL_CALL ConvDicNameContainer::hasByName( const OUString& rName )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return GetByName( rName ).is();
+}
+
+void SAL_CALL ConvDicNameContainer::replaceByName(
+ const OUString& rName,
+ const uno::Any& rElement )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ sal_Int32 nRplcIdx = GetIndexByName_Impl( rName );
+ if (nRplcIdx == -1)
+ throw NoSuchElementException();
+ uno::Reference< XConversionDictionary > xNew;
+ rElement >>= xNew;
+ if (!xNew.is() || xNew->getName() != rName)
+ throw IllegalArgumentException();
+ aConvDics[ nRplcIdx ] = xNew;
+}
+
+void SAL_CALL ConvDicNameContainer::insertByName(
+ const OUString& rName,
+ const Any& rElement )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (GetByName( rName ).is())
+ throw ElementExistException();
+ uno::Reference< XConversionDictionary > xNew;
+ rElement >>= xNew;
+ if (!xNew.is() || xNew->getName() != rName)
+ throw IllegalArgumentException();
+
+ aConvDics.push_back(xNew);
+}
+
+void SAL_CALL ConvDicNameContainer::removeByName( const OUString& rName )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ sal_Int32 nRplcIdx = GetIndexByName_Impl( rName );
+ if (nRplcIdx == -1)
+ throw NoSuchElementException();
+
+ // physically remove dictionary
+ uno::Reference< XConversionDictionary > xDel = aConvDics[nRplcIdx];
+ OUString aName( xDel->getName() );
+ OUString aDicMainURL( GetConvDicMainURL( aName, GetDictionaryWriteablePath() ) );
+ INetURLObject aObj( aDicMainURL );
+ DBG_ASSERT( aObj.GetProtocol() == INetProtocol::File, "+HangulHanjaOptionsDialog::OkHdl(): non-file URLs cannot be deleted" );
+ if( aObj.GetProtocol() == INetProtocol::File )
+ {
+ try
+ {
+ ::ucbhelper::Content aCnt( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ uno::Reference< css::ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext() );
+ aCnt.executeCommand( "delete", Any( true ) );
+ }
+ catch( ... )
+ {
+ TOOLS_WARN_EXCEPTION( "linguistic", "HangulHanjaOptionsDialog::OkHdl()" );
+ }
+ }
+
+ aConvDics.erase(aConvDics.begin() + nRplcIdx);
+}
+
+void ConvDicNameContainer::AddConvDics(
+ const OUString &rSearchDirPathURL,
+ const OUString &rExtension )
+{
+ const Sequence< OUString > aDirCnt(
+ utl::LocalFileHelper::GetFolderContents( rSearchDirPathURL, false ) );
+
+ for (const OUString& aURL : aDirCnt)
+ {
+ sal_Int32 nPos = aURL.lastIndexOf('.');
+ OUString aExt( aURL.copy(nPos + 1).toAsciiLowerCase() );
+ OUString aSearchExt( rExtension.toAsciiLowerCase() );
+ if(aExt != aSearchExt)
+ continue; // skip other files
+
+ LanguageType nLang;
+ sal_Int16 nConvType;
+ if (IsConvDic( aURL, nLang, nConvType ))
+ {
+ // get decoded dictionary file name
+ INetURLObject aURLObj( aURL );
+ OUString aDicName = aURLObj.getBase( INetURLObject::LAST_SEGMENT,
+ true, INetURLObject::DecodeMechanism::WithCharset );
+
+ uno::Reference < XConversionDictionary > xDic;
+ if (nLang == LANGUAGE_KOREAN &&
+ nConvType == ConversionDictionaryType::HANGUL_HANJA)
+ {
+ xDic = new HHConvDic( aDicName, aURL );
+ }
+ else if ((nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL) &&
+ nConvType == ConversionDictionaryType::SCHINESE_TCHINESE)
+ {
+ xDic = new ConvDic( aDicName, nLang, nConvType, false, aURL );
+ }
+
+ if (xDic.is())
+ {
+ insertByName( xDic->getName(), Any(xDic) );
+ }
+ }
+ }
+}
+
+namespace
+{
+ rtl::Reference<ConvDicList>& StaticConvDicList()
+ {
+ static rtl::Reference<ConvDicList> SINGLETON = new ConvDicList;
+ return SINGLETON;
+ };
+}
+
+void ConvDicList::MyAppExitListener::AtExit()
+{
+ rMyDicList.FlushDics();
+ StaticConvDicList().clear();
+}
+
+ConvDicList::ConvDicList() :
+ aEvtListeners( GetLinguMutex() )
+{
+ bDisposing = false;
+
+ mxExitListener = new MyAppExitListener( *this );
+ mxExitListener->Activate();
+}
+
+ConvDicList::~ConvDicList()
+{
+ if (!bDisposing && mxNameContainer.is())
+ mxNameContainer->FlushDics();
+
+ mxExitListener->Deactivate();
+}
+
+void ConvDicList::FlushDics()
+{
+ // check only pointer to avoid creating the container when
+ // the dictionaries were not accessed yet
+ if (mxNameContainer.is())
+ mxNameContainer->FlushDics();
+}
+
+ConvDicNameContainer & ConvDicList::GetNameContainer()
+{
+ if (!mxNameContainer.is())
+ {
+ mxNameContainer = new ConvDicNameContainer;
+ mxNameContainer->AddConvDics( GetDictionaryWriteablePath(), CONV_DIC_EXT );
+
+ // access list of text conversion dictionaries to activate
+ SvtLinguOptions aOpt;
+ SvtLinguConfig().GetOptions( aOpt );
+ for (const OUString& rActiveConvDic : std::as_const(aOpt.aActiveConvDics))
+ {
+ uno::Reference< XConversionDictionary > xDic =
+ mxNameContainer->GetByName( rActiveConvDic );
+ if (xDic.is())
+ xDic->setActive( true );
+ }
+
+ // since there is no UI to active/deactivate the dictionaries
+ // for chinese text conversion they should be activated by default
+ uno::Reference< XConversionDictionary > xS2TDic =
+ mxNameContainer->GetByName( u"ChineseS2T" );
+ uno::Reference< XConversionDictionary > xT2SDic =
+ mxNameContainer->GetByName( u"ChineseT2S" );
+ if (xS2TDic.is())
+ xS2TDic->setActive( true );
+ if (xT2SDic.is())
+ xT2SDic->setActive( true );
+
+ }
+ return *mxNameContainer;
+}
+
+uno::Reference< container::XNameContainer > SAL_CALL ConvDicList::getDictionaryContainer( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ GetNameContainer();
+ DBG_ASSERT( mxNameContainer.is(), "missing name container" );
+ return mxNameContainer;
+}
+
+uno::Reference< XConversionDictionary > SAL_CALL ConvDicList::addNewDictionary(
+ const OUString& rName,
+ const Locale& rLocale,
+ sal_Int16 nConvDicType )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ LanguageType nLang = LinguLocaleToLanguage( rLocale );
+
+ if (GetNameContainer().hasByName( rName ))
+ throw ElementExistException();
+
+ uno::Reference< XConversionDictionary > xRes;
+ OUString aDicMainURL( GetConvDicMainURL( rName, GetDictionaryWriteablePath() ) );
+ if (nLang == LANGUAGE_KOREAN &&
+ nConvDicType == ConversionDictionaryType::HANGUL_HANJA)
+ {
+ xRes = new HHConvDic( rName, aDicMainURL );
+ }
+ else if ((nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL) &&
+ nConvDicType == ConversionDictionaryType::SCHINESE_TCHINESE)
+ {
+ xRes = new ConvDic( rName, nLang, nConvDicType, false, aDicMainURL );
+ }
+
+ if (!xRes.is())
+ throw NoSupportException();
+
+ xRes->setActive( true );
+ GetNameContainer().insertByName( rName, Any(xRes) );
+ return xRes;
+}
+
+uno::Sequence< OUString > SAL_CALL ConvDicList::queryConversions(
+ const OUString& rText,
+ sal_Int32 nStartPos,
+ sal_Int32 nLength,
+ const Locale& rLocale,
+ sal_Int16 nConversionDictionaryType,
+ ConversionDirection eDirection,
+ sal_Int32 nTextConversionOptions )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector< OUString > aRes;
+
+ bool bSupported = false;
+ sal_Int32 nLen = GetNameContainer().GetCount();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ const uno::Reference< XConversionDictionary > xDic( GetNameContainer().GetByIndex(i) );
+ bool bMatch = xDic.is() &&
+ xDic->getLocale() == rLocale &&
+ xDic->getConversionType() == nConversionDictionaryType;
+ bSupported |= bMatch;
+ if (bMatch && xDic->isActive())
+ {
+ const Sequence< OUString > aNewConv( xDic->getConversions(
+ rText, nStartPos, nLength,
+ eDirection, nTextConversionOptions ) );
+ aRes.insert( aRes.end(), aNewConv.begin(), aNewConv.end() );
+ }
+ }
+
+ if (!bSupported)
+ throw NoSupportException();
+
+ return comphelper::containerToSequence(aRes);
+}
+
+sal_Int16 SAL_CALL ConvDicList::queryMaxCharCount(
+ const Locale& rLocale,
+ sal_Int16 nConversionDictionaryType,
+ ConversionDirection eDirection )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ sal_Int16 nRes = 0;
+ GetNameContainer();
+ sal_Int32 nLen = GetNameContainer().GetCount();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ const uno::Reference< XConversionDictionary > xDic( GetNameContainer().GetByIndex(i) );
+ if (xDic.is() &&
+ xDic->getLocale() == rLocale &&
+ xDic->getConversionType() == nConversionDictionaryType)
+ {
+ sal_Int16 nC = xDic->getMaxCharCount( eDirection );
+ if (nC > nRes)
+ nRes = nC;
+ }
+ }
+ return nRes;
+}
+
+void SAL_CALL ConvDicList::dispose( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (!bDisposing)
+ {
+ bDisposing = true;
+ EventObject aEvtObj( static_cast<XConversionDictionaryList *>(this) );
+ aEvtListeners.disposeAndClear( aEvtObj );
+
+ FlushDics();
+ }
+}
+
+void SAL_CALL ConvDicList::addEventListener(
+ const uno::Reference< XEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.addInterface( rxListener );
+}
+
+void SAL_CALL ConvDicList::removeEventListener(
+ const uno::Reference< XEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.removeInterface( rxListener );
+}
+
+OUString SAL_CALL ConvDicList::getImplementationName()
+{
+ return "com.sun.star.lingu2.ConvDicList";
+}
+
+sal_Bool SAL_CALL ConvDicList::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL ConvDicList::getSupportedServiceNames()
+{
+ return { "com.sun.star.linguistic2.ConversionDictionaryList" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+linguistic_ConvDicList_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(StaticConvDicList().get());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/convdiclist.hxx b/linguistic/source/convdiclist.hxx
new file mode 100644
index 000000000..11ab399aa
--- /dev/null
+++ b/linguistic/source/convdiclist.hxx
@@ -0,0 +1,90 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_CONVDICLIST_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_CONVDICLIST_HXX
+
+#include <com/sun/star/linguistic2/XConversionDictionaryList.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <rtl/ref.hxx>
+
+#include <linguistic/misc.hxx>
+
+
+class ConvDicNameContainer;
+
+
+class ConvDicList :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XConversionDictionaryList,
+ css::lang::XComponent,
+ css::lang::XServiceInfo
+ >
+{
+
+ class MyAppExitListener : public linguistic::AppExitListener
+ {
+ ConvDicList & rMyDicList;
+
+ public:
+ explicit MyAppExitListener( ConvDicList &rDicList ) : rMyDicList( rDicList ) {}
+ virtual void AtExit() override;
+ };
+
+ ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener> aEvtListeners;
+ rtl::Reference<ConvDicNameContainer> mxNameContainer;
+ rtl::Reference<MyAppExitListener> mxExitListener;
+ bool bDisposing;
+
+ ConvDicList( const ConvDicList & ) = delete;
+ ConvDicList & operator = (const ConvDicList &) = delete;
+
+ ConvDicNameContainer & GetNameContainer();
+
+public:
+ ConvDicList();
+ virtual ~ConvDicList() override;
+
+ // XConversionDictionaryList
+ virtual css::uno::Reference< css::container::XNameContainer > SAL_CALL getDictionaryContainer( ) override;
+ virtual css::uno::Reference< css::linguistic2::XConversionDictionary > SAL_CALL addNewDictionary( const OUString& aName, const css::lang::Locale& aLocale, sal_Int16 nConversionDictionaryType ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL queryConversions( const OUString& aText, sal_Int32 nStartPos, sal_Int32 nLength, const css::lang::Locale& aLocale, sal_Int16 nConversionDictionaryType, css::linguistic2::ConversionDirection eDirection, sal_Int32 nTextConversionOptions ) override;
+ virtual sal_Int16 SAL_CALL queryMaxCharCount( const css::lang::Locale& aLocale, sal_Int16 nConversionDictionaryType, css::linguistic2::ConversionDirection eDirection ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose( ) override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // non UNO-specific
+ void FlushDics();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/convdicxml.cxx b/linguistic/source/convdicxml.cxx
new file mode 100644
index 000000000..c8a09997f
--- /dev/null
+++ b/linguistic/source/convdicxml.cxx
@@ -0,0 +1,342 @@
+/* -*- 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 <tools/debug.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <com/sun/star/linguistic2/ConversionDictionaryType.hpp>
+#include <com/sun/star/linguistic2/ConversionPropertyType.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <xmloff/namespacemap.hxx>
+#include <xmloff/xmlnamespace.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include "convdic.hxx"
+#include "convdicxml.hxx"
+#include <linguistic/misc.hxx>
+
+using namespace utl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+constexpr OUStringLiteral XML_NAMESPACE_TCD_STRING = u"http://openoffice.org/2003/text-conversion-dictionary";
+constexpr OUStringLiteral CONV_TYPE_HANGUL_HANJA = u"Hangul / Hanja";
+constexpr OUStringLiteral CONV_TYPE_SCHINESE_TCHINESE = u"Chinese simplified / Chinese traditional";
+
+
+static OUString ConversionTypeToText( sal_Int16 nConversionType )
+{
+ OUString aRes;
+ if (nConversionType == ConversionDictionaryType::HANGUL_HANJA)
+ aRes = CONV_TYPE_HANGUL_HANJA;
+ else if (nConversionType == ConversionDictionaryType::SCHINESE_TCHINESE)
+ aRes = CONV_TYPE_SCHINESE_TCHINESE;
+ return aRes;
+}
+
+static sal_Int16 GetConversionTypeFromText( std::u16string_view rText )
+{
+ sal_Int16 nRes = -1;
+ if (rText == CONV_TYPE_HANGUL_HANJA)
+ nRes = ConversionDictionaryType::HANGUL_HANJA;
+ else if (rText == CONV_TYPE_SCHINESE_TCHINESE)
+ nRes = ConversionDictionaryType::SCHINESE_TCHINESE;
+ return nRes;
+}
+
+namespace {
+
+class ConvDicXMLImportContext :
+ public SvXMLImportContext
+{
+public:
+ ConvDicXMLImportContext( ConvDicXMLImport &rImport ) :
+ SvXMLImportContext( rImport )
+ {
+ }
+
+ ConvDicXMLImport & GetConvDicImport()
+ {
+ return static_cast<ConvDicXMLImport &>(GetImport());
+ }
+
+ // SvXMLImportContext
+ virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext(
+ sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override;
+};
+
+
+class ConvDicXMLDictionaryContext_Impl :
+ public ConvDicXMLImportContext
+{
+ LanguageType nLanguage;
+ sal_Int16 nConversionType;
+
+public:
+ ConvDicXMLDictionaryContext_Impl( ConvDicXMLImport &rImport ) :
+ ConvDicXMLImportContext( rImport ),
+ nLanguage(LANGUAGE_NONE), nConversionType(-1)
+ {
+ }
+
+ // SvXMLImportContext
+ virtual void SAL_CALL startFastElement( sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& Attribs ) override;
+ virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext(
+ sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override;
+};
+
+
+class ConvDicXMLEntryTextContext_Impl :
+ public ConvDicXMLImportContext
+{
+ OUString aLeftText;
+
+public:
+ ConvDicXMLEntryTextContext_Impl( ConvDicXMLImport &rImport ) :
+ ConvDicXMLImportContext( rImport )
+ {
+ }
+
+ // SvXMLImportContext
+ virtual void SAL_CALL startFastElement( sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& Attribs ) override;
+ virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext(
+ sal_Int32 Element, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override;
+
+ const OUString & GetLeftText() const { return aLeftText; }
+};
+
+
+class ConvDicXMLRightTextContext_Impl :
+ public ConvDicXMLImportContext
+{
+ OUString aRightText;
+ ConvDicXMLEntryTextContext_Impl &rEntryContext;
+
+public:
+ ConvDicXMLRightTextContext_Impl(
+ ConvDicXMLImport &rImport,
+ ConvDicXMLEntryTextContext_Impl &rParentContext ) :
+ ConvDicXMLImportContext( rImport ),
+ rEntryContext( rParentContext )
+ {
+ }
+
+ // SvXMLImportContext
+ virtual void SAL_CALL endFastElement( sal_Int32 nElement ) override;
+ virtual void SAL_CALL characters( const OUString &rChars ) override;
+};
+
+}
+
+//void ConvDicXMLImportContext::characters(const OUString & /*rChars*/)
+//{
+ /*
+ Whitespace occurring within the content of token elements is "trimmed"
+ from the ends (i.e. all whitespace at the beginning and end of the
+ content is removed), and "collapsed" internally (i.e. each sequence of
+ 1 or more whitespace characters is replaced with one blank character).
+ */
+ //collapsing not done yet!
+
+//}
+
+css::uno::Reference<XFastContextHandler> ConvDicXMLImportContext::createFastChildContext(
+ sal_Int32 Element,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ )
+{
+ if ( Element == ConvDicXMLToken::TEXT_CONVERSION_DICTIONARY )
+ return new ConvDicXMLDictionaryContext_Impl( GetConvDicImport() );
+ return nullptr;
+}
+
+
+void ConvDicXMLDictionaryContext_Impl::startFastElement( sal_Int32 /*nElement*/,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList >& rxAttrList )
+{
+ for (auto &aIter : sax_fastparser::castToFastAttributeList( rxAttrList ))
+ {
+ switch (aIter.getToken())
+ {
+ case XML_NAMESPACE_TCD | XML_LANG:
+ nLanguage = LanguageTag::convertToLanguageType( aIter.toString() );
+ break;
+ case XML_NAMESPACE_TCD | XML_CONVERSION_TYPE:
+ nConversionType = GetConversionTypeFromText( aIter.toString() );
+ break;
+ default:
+ ;
+ }
+ }
+ GetConvDicImport().SetLanguage( nLanguage );
+ GetConvDicImport().SetConversionType( nConversionType );
+
+}
+
+css::uno::Reference<XFastContextHandler> ConvDicXMLDictionaryContext_Impl::createFastChildContext(
+ sal_Int32 Element,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ )
+{
+ if ( Element == ConvDicXMLToken::ENTRY )
+ return new ConvDicXMLEntryTextContext_Impl( GetConvDicImport() );
+ return nullptr;
+}
+
+css::uno::Reference<XFastContextHandler> ConvDicXMLEntryTextContext_Impl::createFastChildContext(
+ sal_Int32 Element,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ )
+{
+ if ( Element == ConvDicXMLToken::RIGHT_TEXT )
+ return new ConvDicXMLRightTextContext_Impl( GetConvDicImport(), *this );
+ return nullptr;
+}
+
+void ConvDicXMLEntryTextContext_Impl::startFastElement(
+ sal_Int32 /*Element*/,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList >& rxAttrList )
+{
+ for (auto &aIter : sax_fastparser::castToFastAttributeList( rxAttrList ))
+ {
+ switch (aIter.getToken())
+ {
+ case XML_NAMESPACE_TCD | XML_LEFT_TEXT:
+ aLeftText = aIter.toString();
+ break;
+ default:
+ ;
+ }
+ }
+}
+
+
+void ConvDicXMLRightTextContext_Impl::characters( const OUString &rChars )
+{
+ aRightText += rChars;
+}
+
+void ConvDicXMLRightTextContext_Impl::endFastElement( sal_Int32 /*nElement*/ )
+{
+ ConvDic *pDic = GetConvDicImport().GetDic();
+ if (pDic)
+ pDic->AddEntry( rEntryContext.GetLeftText(), aRightText );
+}
+
+
+bool ConvDicXMLExport::Export()
+{
+ uno::Reference< document::XExporter > xExporter( this );
+ uno::Reference< document::XFilter > xFilter( xExporter, UNO_QUERY );
+ xFilter->filter( {} ); // calls exportDoc implicitly
+
+ return bSuccess;
+}
+
+
+ErrCode ConvDicXMLExport::exportDoc( enum ::xmloff::token::XMLTokenEnum /*eClass*/ )
+{
+ GetNamespaceMap_().Add( "tcd",
+ XML_NAMESPACE_TCD_STRING, XML_NAMESPACE_TCD );
+
+ GetDocHandler()->startDocument();
+
+ // Add xmlns line and some other arguments
+ AddAttribute( GetNamespaceMap_().GetAttrNameByKey( XML_NAMESPACE_TCD ),
+ GetNamespaceMap_().GetNameByKey( XML_NAMESPACE_TCD ) );
+ AddAttribute( XML_NAMESPACE_TCD, "package", "org.openoffice.Office" );
+
+ OUString aIsoLang( LanguageTag::convertToBcp47( rDic.nLanguage ) );
+ AddAttribute( XML_NAMESPACE_TCD, "lang", aIsoLang );
+ OUString aConvType( ConversionTypeToText( rDic.nConversionType ) );
+ AddAttribute( XML_NAMESPACE_TCD, "conversion-type", aConvType );
+
+ //!! block necessary in order to have SvXMLElementExport d-tor called
+ //!! before the call to endDocument
+ {
+ SvXMLElementExport aRoot( *this, XML_NAMESPACE_TCD, "text-conversion-dictionary", true, true );
+ ExportContent_();
+ }
+
+ GetDocHandler()->endDocument();
+
+ bSuccess = true;
+ return ERRCODE_NONE;
+}
+
+
+void ConvDicXMLExport::ExportContent_()
+{
+ // acquire sorted list of all keys
+ std::set<OUString> aKeySet;
+ for (auto const& elem : rDic.aFromLeft)
+ aKeySet.insert( elem.first );
+
+ for (const OUString& aLeftText : aKeySet)
+ {
+ AddAttribute( XML_NAMESPACE_TCD, "left-text", aLeftText );
+ if (rDic.pConvPropType) // property-type list available?
+ {
+ sal_Int16 nPropertyType = -1;
+ PropTypeMap::iterator aIt2 = rDic.pConvPropType->find( aLeftText );
+ if (aIt2 != rDic.pConvPropType->end())
+ nPropertyType = (*aIt2).second;
+ DBG_ASSERT( nPropertyType, "property-type not found" );
+ if (nPropertyType == -1)
+ nPropertyType = ConversionPropertyType::NOT_DEFINED;
+ AddAttribute( XML_NAMESPACE_TCD, "property-type", OUString::number( nPropertyType ) );
+ }
+ SvXMLElementExport aEntryMain( *this, XML_NAMESPACE_TCD,
+ "entry" , true, true );
+
+ std::pair< ConvMap::iterator, ConvMap::iterator > aRange =
+ rDic.aFromLeft.equal_range(aLeftText);
+ for (auto aIt = aRange.first; aIt != aRange.second; ++aIt)
+ {
+ DBG_ASSERT( aLeftText == (*aIt).first, "key <-> entry mismatch" );
+ OUString aRightText( (*aIt).second );
+ SvXMLElementExport aEntryRightText( *this, XML_NAMESPACE_TCD,
+ "right-text" , true, false );
+ Characters( aRightText );
+ }
+ }
+}
+
+ //!! see comment for pDic member
+ConvDicXMLImport::ConvDicXMLImport( ConvDic *pConvDic ) :
+ SvXMLImport ( comphelper::getProcessComponentContext(), "com.sun.star.lingu2.ConvDicXMLImport", SvXMLImportFlags::ALL ),
+ pDic ( pConvDic ), nLanguage(LANGUAGE_NONE), nConversionType(-1)
+{
+ GetNamespaceMap().Add( GetXMLToken(XML_NP_TCD), GetXMLToken(XML_N_TCD), XML_NAMESPACE_TCD);
+}
+
+SvXMLImportContext * ConvDicXMLImport::CreateFastContext(
+ sal_Int32 Element,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList > & /*xAttrList*/ )
+{
+ if( Element == ConvDicXMLToken::TEXT_CONVERSION_DICTIONARY )
+ return new ConvDicXMLDictionaryContext_Impl( *this );
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/convdicxml.hxx b/linguistic/source/convdicxml.hxx
new file mode 100644
index 000000000..1eca995c1
--- /dev/null
+++ b/linguistic/source/convdicxml.hxx
@@ -0,0 +1,104 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_CONVDICXML_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_CONVDICXML_HXX
+
+#include <com/sun/star/util/MeasureUnit.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/xml/sax/FastToken.hpp>
+#include <comphelper/processfactory.hxx>
+#include <xmloff/xmlexp.hxx>
+#include <xmloff/xmlimp.hxx>
+#include <xmloff/xmltoken.hxx>
+#include <xmloff/xmlnamespace.hxx>
+#include <rtl/ustring.hxx>
+
+
+class ConvDic;
+using namespace css::xml::sax;
+using namespace ::xmloff::token;
+
+
+class ConvDicXMLExport : public SvXMLExport
+{
+ ConvDic &rDic;
+ bool bSuccess;
+
+public:
+ ConvDicXMLExport( ConvDic &rConvDic,
+ const OUString &rFileName,
+ css::uno::Reference< css::xml::sax::XDocumentHandler > const &rHandler) :
+ SvXMLExport ( comphelper::getProcessComponentContext(), "com.sun.star.lingu2.ConvDicXMLExport", rFileName,
+ css::util::MeasureUnit::CM, rHandler ),
+ rDic ( rConvDic ),
+ bSuccess ( false )
+ {
+ }
+
+ // SvXMLExport
+ void ExportAutoStyles_() override {}
+ void ExportMasterStyles_() override {}
+ void ExportContent_() override;
+ ErrCode exportDoc( enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID ) override;
+
+ bool Export();
+};
+
+
+enum ConvDicXMLToken : sal_Int32
+{
+ TEXT_CONVERSION_DICTIONARY = FastToken::NAMESPACE | XML_NAMESPACE_TCD | XML_BLOCK_LIST,
+ RIGHT_TEXT = FastToken::NAMESPACE | XML_NAMESPACE_TCD | XML_RIGHT_TEXT,
+ ENTRY = FastToken::NAMESPACE | XML_NAMESPACE_TCD | XML_ENTRY,
+};
+
+class ConvDicXMLImport : public SvXMLImport
+{
+ ConvDic *pDic; // conversion dictionary to be used
+ // if != NULL: whole file will be read and
+ // all entries will be added to the dictionary
+ // if == NULL: no entries will be added
+ // but the language and conversion type will
+ // still be determined!
+
+ LanguageType nLanguage; // language of the dictionary
+ sal_Int16 nConversionType; // conversion type the dictionary is used for
+
+public:
+
+ //!! see comment for pDic member
+ explicit ConvDicXMLImport( ConvDic *pConvDic );
+
+ ConvDic * GetDic() { return pDic; }
+ LanguageType GetLanguage() const { return nLanguage; }
+ sal_Int16 GetConversionType() const { return nConversionType; }
+
+ void SetLanguage( LanguageType nLang ) { nLanguage = nLang; }
+ void SetConversionType( sal_Int16 nType ) { nConversionType = nType; }
+
+private:
+ virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element,
+ const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override;
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/defs.hxx b/linguistic/source/defs.hxx
new file mode 100644
index 000000000..7e3d65895
--- /dev/null
+++ b/linguistic/source/defs.hxx
@@ -0,0 +1,94 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_DEFS_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_DEFS_HXX
+
+#include <com/sun/star/linguistic2/XSpellChecker.hpp>
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+#include <com/sun/star/linguistic2/XThesaurus.hpp>
+
+#include <memory>
+
+class SvStream;
+
+typedef std::shared_ptr< SvStream > SvStreamPtr;
+
+struct LangSvcEntries
+{
+ css::uno::Sequence< OUString > aSvcImplNames;
+
+ sal_Int16 nLastTriedSvcIndex;
+
+ explicit LangSvcEntries( const css::uno::Sequence< OUString > &rSvcImplNames ) :
+ aSvcImplNames(rSvcImplNames),
+ nLastTriedSvcIndex(-1)
+ {
+ }
+
+ explicit LangSvcEntries( const OUString &rSvcImplName ) :
+ nLastTriedSvcIndex(-1)
+ {
+ aSvcImplNames = { rSvcImplName };
+ }
+
+ void Clear()
+ {
+ aSvcImplNames.realloc(0);
+ nLastTriedSvcIndex = -1;
+ }
+};
+
+struct LangSvcEntries_Spell : public LangSvcEntries
+{
+ css::uno::Sequence< css::uno::Reference< css::linguistic2::XSpellChecker > > aSvcRefs;
+
+ explicit LangSvcEntries_Spell( const css::uno::Sequence< OUString > &rSvcImplNames ) : LangSvcEntries( rSvcImplNames ) {}
+};
+
+struct LangSvcEntries_Hyph : public LangSvcEntries
+{
+ css::uno::Sequence< css::uno::Reference< css::linguistic2::XHyphenator > > aSvcRefs;
+
+ explicit LangSvcEntries_Hyph( const OUString &rSvcImplName ) : LangSvcEntries( rSvcImplName ) {}
+};
+
+struct LangSvcEntries_Thes : public LangSvcEntries
+{
+ css::uno::Sequence< css::uno::Reference< css::linguistic2::XThesaurus > > aSvcRefs;
+
+ explicit LangSvcEntries_Thes( const css::uno::Sequence< OUString > &rSvcImplNames ) : LangSvcEntries( rSvcImplNames ) {}
+};
+
+
+// virtual base class for the different dispatchers
+class LinguDispatcher
+{
+public:
+ virtual void SetServiceList( const css::lang::Locale &rLocale, const css::uno::Sequence< OUString > &rSvcImplNames ) = 0;
+ virtual css::uno::Sequence< OUString > GetServiceList( const css::lang::Locale &rLocale ) const = 0;
+
+protected:
+ ~LinguDispatcher() {}
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/dicimp.cxx b/linguistic/source/dicimp.cxx
new file mode 100644
index 000000000..af83c96ff
--- /dev/null
+++ b/linguistic/source/dicimp.cxx
@@ -0,0 +1,1089 @@
+/* -*- 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 <cppuhelper/factory.hxx>
+#include "dicimp.hxx"
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <linguistic/misc.hxx>
+#include <osl/mutex.hxx>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/sequence.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
+
+#include <algorithm>
+#include <utility>
+
+
+using namespace utl;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+#define BUFSIZE 4096
+#define VERS2_NOLANGUAGE 1024
+
+#define MAX_HEADER_LENGTH 16
+
+// XML-header to query SPELLML support
+// to handle user words with "Grammar By" model words
+constexpr OUStringLiteral SPELLML_SUPPORT = u"<?xml?>";
+
+// User dictionaries can contain optional "title:" tags
+// to support custom titles with space and other characters.
+// (old mechanism stores the title of the user dictionary
+// only in its file name, but special characters are
+// problem for user dictionaries shipped with LibreOffice).
+//
+// The following fake file name extension will be
+// added to the text of the title: field for correct
+// text stripping and dictionary saving.
+constexpr OUStringLiteral EXTENSION_FOR_TITLE_TEXT = u".";
+
+const char* const pVerStr2 = "WBSWG2";
+const char* const pVerStr5 = "WBSWG5";
+const char* const pVerStr6 = "WBSWG6";
+const char* const pVerOOo7 = "OOoUserDict1";
+
+const sal_Int16 DIC_VERSION_DONTKNOW = -1;
+const sal_Int16 DIC_VERSION_2 = 2;
+const sal_Int16 DIC_VERSION_5 = 5;
+const sal_Int16 DIC_VERSION_6 = 6;
+const sal_Int16 DIC_VERSION_7 = 7;
+
+static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl()
+{
+ uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create( xContext ) ;
+ return xRes;
+}
+
+static bool getTag(std::string_view rLine, std::string_view rTagName,
+ OString &rTagValue)
+{
+ size_t nPos = rLine.find(rTagName);
+ if (nPos == std::string_view::npos)
+ return false;
+
+ rTagValue = OString(comphelper::string::strip(rLine.substr(nPos + rTagName.size()),
+ ' '));
+ return true;
+}
+
+
+sal_Int16 ReadDicVersion( SvStream& rStream, LanguageType &nLng, bool &bNeg, OUString &aDicName )
+{
+ // Sniff the header
+ sal_Int16 nDicVersion = DIC_VERSION_DONTKNOW;
+ char pMagicHeader[MAX_HEADER_LENGTH];
+
+ nLng = LANGUAGE_NONE;
+ bNeg = false;
+
+ if (rStream.GetError())
+ return -1;
+
+ sal_uInt64 const nSniffPos = rStream.Tell();
+ static std::size_t nVerOOo7Len = sal::static_int_cast< std::size_t >(strlen( pVerOOo7 ));
+ pMagicHeader[ nVerOOo7Len ] = '\0';
+ if ((rStream.ReadBytes(static_cast<void *>(pMagicHeader), nVerOOo7Len) == nVerOOo7Len) &&
+ !strcmp(pMagicHeader, pVerOOo7))
+ {
+ bool bSuccess;
+ OStringBuffer aLine;
+
+ nDicVersion = DIC_VERSION_7;
+
+ // 1st skip magic / header line
+ rStream.ReadLine(aLine);
+
+ // 2nd line: language all | en-US | pt-BR ...
+ while ((bSuccess = rStream.ReadLine(aLine)))
+ {
+ OString aTagValue;
+
+ if (aLine[0] == '#') // skip comments
+ continue;
+
+ // lang: field
+ if (getTag(aLine, "lang: ", aTagValue))
+ {
+ if (aTagValue == "<none>")
+ nLng = LANGUAGE_NONE;
+ else
+ nLng = LanguageTag::convertToLanguageType(
+ OStringToOUString( aTagValue, RTL_TEXTENCODING_ASCII_US));
+ }
+
+ // type: negative / positive
+ if (getTag(aLine, "type: ", aTagValue))
+ {
+ bNeg = aTagValue == "negative";
+ }
+
+ // lang: title
+ if (getTag(aLine, "title: ", aTagValue))
+ {
+ aDicName = OStringToOUString( aTagValue, RTL_TEXTENCODING_UTF8) +
+ // recent title text preparation in GetDicInfoStr() waits for an
+ // extension, so we add it to avoid bad stripping at final dot
+ // of the title text
+ EXTENSION_FOR_TITLE_TEXT;
+ }
+
+ if (std::string_view(aLine).find("---") != std::string_view::npos) // end of header
+ break;
+ }
+ if (!bSuccess)
+ return -2;
+ }
+ else
+ {
+ sal_uInt16 nLen;
+
+ rStream.Seek (nSniffPos );
+
+ rStream.ReadUInt16( nLen );
+ if (nLen >= MAX_HEADER_LENGTH)
+ return -1;
+
+ rStream.ReadBytes(pMagicHeader, nLen);
+ pMagicHeader[nLen] = '\0';
+
+ // Check version magic
+ if (0 == strcmp( pMagicHeader, pVerStr6 ))
+ nDicVersion = DIC_VERSION_6;
+ else if (0 == strcmp( pMagicHeader, pVerStr5 ))
+ nDicVersion = DIC_VERSION_5;
+ else if (0 == strcmp( pMagicHeader, pVerStr2 ))
+ nDicVersion = DIC_VERSION_2;
+ else
+ nDicVersion = DIC_VERSION_DONTKNOW;
+
+ if (DIC_VERSION_2 == nDicVersion ||
+ DIC_VERSION_5 == nDicVersion ||
+ DIC_VERSION_6 == nDicVersion)
+ {
+ // The language of the dictionary
+ sal_uInt16 nTmp = 0;
+ rStream.ReadUInt16( nTmp );
+ nLng = LanguageType(nTmp);
+ if (VERS2_NOLANGUAGE == static_cast<sal_uInt16>(nLng))
+ nLng = LANGUAGE_NONE;
+
+ // Negative Flag
+ rStream.ReadCharAsBool( bNeg );
+ }
+ }
+
+ return nDicVersion;
+}
+
+DictionaryNeo::DictionaryNeo(OUString aName,
+ LanguageType nLang, DictionaryType eType,
+ const OUString &rMainURL,
+ bool bWriteable) :
+ aDicEvtListeners( GetLinguMutex() ),
+ aDicName (std::move(aName)),
+ aMainURL (rMainURL),
+ eDicType (eType),
+ nLanguage (nLang)
+{
+ nDicVersion = DIC_VERSION_DONTKNOW;
+ bNeedEntries = true;
+ bIsModified = bIsActive = false;
+ bIsReadonly = !bWriteable;
+
+ if( !rMainURL.isEmpty())
+ {
+ bool bExists = FileExists( rMainURL );
+ if( !bExists )
+ {
+ // save new dictionaries with in Format 7 (UTF8 plain text)
+ nDicVersion = DIC_VERSION_7;
+
+ //! create physical representation of an **empty** dictionary
+ //! that could be found by the dictionary-list implementation
+ // (Note: empty dictionaries are not just empty files!)
+ DBG_ASSERT( !bIsReadonly,
+ "DictionaryNeo: dictionaries should be writeable if they are to be saved" );
+ if (!bIsReadonly)
+ saveEntries( rMainURL );
+ bNeedEntries = false;
+ }
+ }
+ else
+ {
+ // non persistent dictionaries (like IgnoreAllList) should always be writable
+ bIsReadonly = false;
+ bNeedEntries = false;
+ }
+}
+
+DictionaryNeo::~DictionaryNeo()
+{
+}
+
+ErrCode DictionaryNeo::loadEntries(const OUString &rMainURL)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ // counter check that it is safe to set bIsModified to sal_False at
+ // the end of the function
+ DBG_ASSERT(!bIsModified, "lng : dictionary already modified!");
+
+ // function should only be called once in order to load entries from file
+ bNeedEntries = false;
+
+ if (rMainURL.isEmpty())
+ return ERRCODE_NONE;
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ // get XInputStream stream
+ uno::Reference< io::XInputStream > xStream;
+ try
+ {
+ uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
+ xStream = xAccess->openFileRead( rMainURL );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "failed to get input stream" );
+ }
+ if (!xStream.is())
+ return ErrCode(sal_uInt32(-1));
+
+ std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
+
+ // read header
+ bool bNegativ;
+ LanguageType nLang;
+ nDicVersion = ReadDicVersion(*pStream, nLang, bNegativ, aDicName);
+ ErrCode nErr = pStream->GetError();
+ if (nErr != ERRCODE_NONE)
+ return nErr;
+
+ nLanguage = nLang;
+
+ eDicType = bNegativ ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
+
+ rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
+ if (nDicVersion >= DIC_VERSION_6)
+ eEnc = RTL_TEXTENCODING_UTF8;
+ aEntries.clear();
+
+ if (DIC_VERSION_6 == nDicVersion ||
+ DIC_VERSION_5 == nDicVersion ||
+ DIC_VERSION_2 == nDicVersion)
+ {
+ sal_uInt16 nLen = 0;
+ char aWordBuf[ BUFSIZE ];
+
+ // Read the first word
+ if (!pStream->eof())
+ {
+ pStream->ReadUInt16( nLen );
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ if ( nLen < BUFSIZE )
+ {
+ pStream->ReadBytes(aWordBuf, nLen);
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ *(aWordBuf + nLen) = 0;
+ }
+ else
+ return SVSTREAM_READ_ERROR;
+ }
+
+ while(!pStream->eof())
+ {
+ // Read from file
+ // Paste in dictionary without converting
+ if(*aWordBuf)
+ {
+ OUString aText(aWordBuf, rtl_str_getLength(aWordBuf), eEnc);
+ uno::Reference< XDictionaryEntry > xEntry =
+ new DicEntry( aText, bNegativ );
+ addEntry_Impl( xEntry, true ); //! don't launch events here
+ }
+
+ pStream->ReadUInt16( nLen );
+ if (pStream->eof())
+ break;
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+
+ if (nLen < BUFSIZE)
+ {
+ pStream->ReadBytes(aWordBuf, nLen);
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ }
+ else
+ return SVSTREAM_READ_ERROR;
+ *(aWordBuf + nLen) = 0;
+ }
+ }
+ else if (DIC_VERSION_7 == nDicVersion)
+ {
+ OStringBuffer aLine;
+
+ // remaining lines - stock strings (a [==] b)
+ while (pStream->ReadLine(aLine))
+ {
+ if (aLine.isEmpty() || aLine[0] == '#') // skip comments
+ continue;
+ OUString aText = OStringToOUString(aLine, RTL_TEXTENCODING_UTF8);
+ uno::Reference< XDictionaryEntry > xEntry =
+ new DicEntry( aText, eDicType == DictionaryType_NEGATIVE );
+ addEntry_Impl( xEntry, true ); //! don't launch events here
+ }
+ }
+
+ SAL_WARN_IF(!isSorted(), "linguistic", "dictionary is not sorted");
+
+ // since this routine should be called only initially (prior to any
+ // modification to be saved) we reset the bIsModified flag here that
+ // was implicitly set by addEntry_Impl
+ bIsModified = false;
+
+ return pStream->GetError();
+}
+
+static OString formatForSave(const uno::Reference< XDictionaryEntry > &xEntry,
+ rtl_TextEncoding eEnc )
+{
+ OUStringBuffer aStr(xEntry->getDictionaryWord());
+
+ if (xEntry->isNegative() || !xEntry->getReplacementText().isEmpty())
+ {
+ aStr.append("==" + xEntry->getReplacementText());
+ }
+ return OUStringToOString(aStr, eEnc);
+}
+
+ErrCode DictionaryNeo::saveEntries(const OUString &rURL)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (rURL.isEmpty())
+ return ERRCODE_NONE;
+ DBG_ASSERT(!INetURLObject( rURL ).HasError(), "lng : invalid URL");
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ // get XOutputStream stream
+ uno::Reference<io::XStream> xStream;
+ try
+ {
+ xStream = io::TempFile::create(xContext);
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_ASSERT( false, "failed to get input stream" );
+ }
+ if (!xStream.is())
+ return ErrCode(sal_uInt32(-1));
+
+ std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
+
+ // Always write as the latest version, i.e. DIC_VERSION_7
+
+ rtl_TextEncoding eEnc = RTL_TEXTENCODING_UTF8;
+ pStream->WriteLine(pVerOOo7);
+ ErrCode nErr = pStream->GetError();
+ if (nErr != ERRCODE_NONE)
+ return nErr;
+ /* XXX: the <none> case could be differentiated, is it absence or
+ * undetermined or multiple? Earlier versions did not know about 'und' and
+ * 'mul' and 'zxx' codes. Sync with ReadDicVersion() */
+ if (LinguIsUnspecified(nLanguage))
+ pStream->WriteLine("lang: <none>");
+ else
+ {
+ OString aLine = "lang: " + OUStringToOString(LanguageTag::convertToBcp47(nLanguage), eEnc);
+ pStream->WriteLine(aLine);
+ }
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ if (eDicType == DictionaryType_POSITIVE)
+ pStream->WriteLine("type: positive");
+ else
+ pStream->WriteLine("type: negative");
+ if (aDicName.endsWith(EXTENSION_FOR_TITLE_TEXT))
+ {
+ pStream->WriteLine(OStringConcatenation("title: " + OUStringToOString(
+ // strip EXTENSION_FOR_TITLE_TEXT
+ aDicName.subView(0, aDicName.lastIndexOf(EXTENSION_FOR_TITLE_TEXT)), eEnc)));
+ }
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ pStream->WriteLine("---");
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ for (const Reference<XDictionaryEntry> & aEntrie : aEntries)
+ {
+ OString aOutStr = formatForSave(aEntrie, eEnc);
+ pStream->WriteLine (aOutStr);
+ if (ERRCODE_NONE != (nErr = pStream->GetError()))
+ return nErr;
+ }
+
+ try
+ {
+ pStream.reset();
+ uno::Reference< ucb::XSimpleFileAccess3 > xAccess(ucb::SimpleFileAccess::create(xContext));
+ Reference<io::XInputStream> xInputStream(xStream, UNO_QUERY_THROW);
+ uno::Reference<io::XSeekable> xSeek(xInputStream, UNO_QUERY_THROW);
+ xSeek->seek(0);
+ xAccess->writeFile(rURL, xInputStream);
+ //If we are migrating from an older version, then on first successful
+ //write, we're now converted to the latest version, i.e. DIC_VERSION_7
+ nDicVersion = DIC_VERSION_7;
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_ASSERT( false, "failed to write stream" );
+ return ErrCode(sal_uInt32(-1));
+ }
+
+ return nErr;
+}
+
+void DictionaryNeo::launchEvent(sal_Int16 nEvent,
+ const uno::Reference< XDictionaryEntry >& xEntry)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ DictionaryEvent aEvt;
+ aEvt.Source = uno::Reference< XDictionary >( this );
+ aEvt.nEvent = nEvent;
+ aEvt.xDictionaryEntry = xEntry;
+
+ aDicEvtListeners.notifyEach( &XDictionaryEventListener::processDictionaryEvent, aEvt);
+}
+
+int DictionaryNeo::cmpDicEntry(std::u16string_view rWord1,
+ std::u16string_view rWord2,
+ bool bSimilarOnly)
+{
+ // returns 0 if rWord1 is equal to rWord2
+ // " a value < 0 if rWord1 is less than rWord2
+ // " a value > 0 if rWord1 is greater than rWord2
+
+ int nRes = 0;
+
+ sal_Int32 nLen1 = rWord1.size(),
+ nLen2 = rWord2.size();
+ if (bSimilarOnly)
+ {
+ const sal_Unicode cChar = '.';
+ if (nLen1 && cChar == rWord1[ nLen1 - 1 ])
+ nLen1--;
+ if (nLen2 && cChar == rWord2[ nLen2 - 1 ])
+ nLen2--;
+ }
+
+ const sal_Unicode cIgnChar = '=';
+ const sal_Unicode cIgnBeg = '['; // for alternative hyphenation, eg. Schif[f]fahrt, Zuc[1k]ker
+ const sal_Unicode cIgnEnd = ']'; // planned: gee"[1-/e]rfde or ge[-/1e]e"rfde (gee"rfde -> ge=erfde)
+ sal_Int32 nIdx1 = 0,
+ nIdx2 = 0,
+ nNumIgnChar1 = 0,
+ nNumIgnChar2 = 0;
+
+ bool IgnState;
+ sal_Int32 nDiff = 0;
+ sal_Unicode cChar1 = '\0';
+ sal_Unicode cChar2 = '\0';
+ do
+ {
+ // skip chars to be ignored
+ IgnState = false;
+ while (nIdx1 < nLen1)
+ {
+ cChar1 = rWord1[ nIdx1 ];
+ if (cChar1 != cIgnChar && cChar1 != cIgnBeg && !IgnState )
+ break;
+ if ( cChar1 == cIgnBeg )
+ IgnState = true;
+ else if (cChar1 == cIgnEnd)
+ IgnState = false;
+ nIdx1++;
+ nNumIgnChar1++;
+ }
+ IgnState = false;
+ while (nIdx2 < nLen2)
+ {
+ cChar2 = rWord2[ nIdx2 ];
+ if (cChar2 != cIgnChar && cChar2 != cIgnBeg && !IgnState )
+ break;
+ if ( cChar2 == cIgnBeg )
+ IgnState = true;
+ else if (cChar2 == cIgnEnd)
+ IgnState = false;
+ nIdx2++;
+ nNumIgnChar2++;
+ }
+
+ if (nIdx1 < nLen1 && nIdx2 < nLen2)
+ {
+ nDiff = cChar1 - cChar2;
+ if (nDiff)
+ break;
+ nIdx1++;
+ nIdx2++;
+ }
+ } while (nIdx1 < nLen1 && nIdx2 < nLen2);
+
+
+ if (nDiff)
+ nRes = nDiff;
+ else
+ { // the string with the smallest count of not ignored chars is the
+ // shorter one
+
+ // count remaining IgnChars
+ IgnState = false;
+ while (nIdx1 < nLen1 )
+ {
+ if (rWord1[ nIdx1 ] == cIgnBeg)
+ IgnState = true;
+ if (IgnState || rWord1[ nIdx1 ] == cIgnChar)
+ nNumIgnChar1++;
+ if (rWord1[ nIdx1] == cIgnEnd)
+ IgnState = false;
+ nIdx1++;
+ }
+ IgnState = false;
+ while (nIdx2 < nLen2 )
+ {
+ if (rWord2[ nIdx2 ] == cIgnBeg)
+ IgnState = true;
+ if (IgnState || rWord2[ nIdx2 ] == cIgnChar)
+ nNumIgnChar2++;
+ if (rWord2[ nIdx2 ] == cIgnEnd)
+ IgnState = false;
+ nIdx2++;
+ }
+
+ nRes = (nLen1 - nNumIgnChar1) - (nLen2 - nNumIgnChar2);
+ }
+
+ return nRes;
+}
+
+bool DictionaryNeo::seekEntry(std::u16string_view rWord,
+ sal_Int32 *pPos, bool bSimilarOnly)
+{
+ // look for entry with binary search.
+ // return sal_True if found sal_False else.
+ // if pPos != NULL it will become the position of the found entry, or
+ // if that was not found the position where it has to be inserted
+ // to keep the entries sorted
+
+ MutexGuard aGuard( GetLinguMutex() );
+
+ sal_Int32 nUpperIdx = getCount(),
+ nMidIdx,
+ nLowerIdx = 0;
+ if( nUpperIdx > 0 )
+ {
+ nUpperIdx--;
+ while( nLowerIdx <= nUpperIdx )
+ {
+ nMidIdx = (nLowerIdx + nUpperIdx) / 2;
+ DBG_ASSERT(aEntries[nMidIdx].is(), "lng : empty entry encountered");
+
+ int nCmp = - cmpDicEntry( aEntries[nMidIdx]->getDictionaryWord(),
+ rWord, bSimilarOnly );
+ if(nCmp == 0)
+ {
+ if( pPos ) *pPos = nMidIdx;
+ return true;
+ }
+ else if(nCmp > 0)
+ nLowerIdx = nMidIdx + 1;
+ else if( nMidIdx == 0 )
+ {
+ if( pPos ) *pPos = nLowerIdx;
+ return false;
+ }
+ else
+ nUpperIdx = nMidIdx - 1;
+ }
+ }
+ if( pPos ) *pPos = nLowerIdx;
+ return false;
+}
+
+bool DictionaryNeo::isSorted()
+{
+ bool bRes = true;
+
+ sal_Int32 nEntries = getCount();
+ sal_Int32 i;
+ for (i = 1; i < nEntries; i++)
+ {
+ if (cmpDicEntry( aEntries[i-1]->getDictionaryWord(),
+ aEntries[i]->getDictionaryWord() ) > 0)
+ {
+ bRes = false;
+ break;
+ }
+ }
+ return bRes;
+}
+
+bool DictionaryNeo::addEntry_Impl(const uno::Reference< XDictionaryEntry >& xDicEntry,
+ bool bIsLoadEntries)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+
+ if ( bIsLoadEntries || (!bIsReadonly && xDicEntry.is()) )
+ {
+ bool bIsNegEntry = xDicEntry->isNegative();
+ bool bAddEntry = !isFull() &&
+ ( ( eDicType == DictionaryType_POSITIVE && !bIsNegEntry )
+ || ( eDicType == DictionaryType_NEGATIVE && bIsNegEntry )
+ || ( eDicType == DictionaryType_MIXED ) );
+
+ // look for position to insert entry at
+ // if there is already an entry do not insert the new one
+ sal_Int32 nPos = 0;
+ if (bAddEntry)
+ {
+ const bool bFound = seekEntry( xDicEntry->getDictionaryWord(), &nPos );
+ if (bFound)
+ bAddEntry = false;
+ }
+
+ if (bAddEntry)
+ {
+ DBG_ASSERT(!bNeedEntries, "lng : entries still not loaded");
+
+ // insert new entry at specified position
+ aEntries.insert(aEntries.begin() + nPos, xDicEntry);
+ SAL_WARN_IF(!isSorted(), "linguistic", "dictionary entries unsorted");
+
+ bIsModified = true;
+ bRes = true;
+
+ if (!bIsLoadEntries)
+ launchEvent( DictionaryEventFlags::ADD_ENTRY, xDicEntry );
+ }
+ }
+
+ // add word to the Hunspell dictionary using a sample word for affixation/compounding
+ if (xDicEntry.is() && !xDicEntry->isNegative() && !xDicEntry->getReplacementText().isEmpty()) {
+ uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() );
+ uno::Reference< XSpellChecker1 > xSpell;
+ Reference< XSpellAlternatives > xTmpRes;
+ xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY );
+ Sequence< css::beans::PropertyValue > aEmptySeq;
+ if (xSpell.is() && (xSpell->isValid( SPELLML_SUPPORT, static_cast<sal_uInt16>(nLanguage), aEmptySeq )))
+ {
+ // "Grammar By" sample word is a Hunspell dictionary word?
+ if (xSpell->isValid( xDicEntry->getReplacementText(), static_cast<sal_uInt16>(nLanguage), aEmptySeq ))
+ {
+ xTmpRes = xSpell->spell( "<?xml?><query type='add'><word>" +
+ xDicEntry->getDictionaryWord() + "</word><word>" + xDicEntry->getReplacementText() +
+ "</word></query>", static_cast<sal_uInt16>(nLanguage), aEmptySeq );
+ bRes = true;
+ } else
+ bRes = false;
+ }
+ }
+
+ return bRes;
+}
+
+OUString SAL_CALL DictionaryNeo::getName( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return aDicName;
+}
+
+void SAL_CALL DictionaryNeo::setName( const OUString& aName )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (aDicName != aName)
+ {
+ aDicName = aName;
+ launchEvent(DictionaryEventFlags::CHG_NAME, nullptr);
+ }
+}
+
+DictionaryType SAL_CALL DictionaryNeo::getDictionaryType( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ return eDicType;
+}
+
+void SAL_CALL DictionaryNeo::setActive( sal_Bool bActivate )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bIsActive == bool(bActivate))
+ return;
+
+ bIsActive = bActivate;
+ sal_Int16 nEvent = bIsActive ?
+ DictionaryEventFlags::ACTIVATE_DIC : DictionaryEventFlags::DEACTIVATE_DIC;
+
+ // remove entries from memory if dictionary is deactivated
+ if (!bIsActive)
+ {
+ bool bIsEmpty = aEntries.empty();
+
+ // save entries first if necessary
+ if (bIsModified && hasLocation() && !isReadonly())
+ {
+ store();
+
+ aEntries.clear();
+ bNeedEntries = !bIsEmpty;
+ }
+ DBG_ASSERT( !bIsModified || !hasLocation() || isReadonly(),
+ "lng : dictionary is still modified" );
+ }
+
+ launchEvent(nEvent, nullptr);
+}
+
+sal_Bool SAL_CALL DictionaryNeo::isActive( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return bIsActive;
+}
+
+sal_Int32 SAL_CALL DictionaryNeo::getCount( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+ return static_cast<sal_Int32>(aEntries.size());
+}
+
+Locale SAL_CALL DictionaryNeo::getLocale( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return LanguageTag::convertToLocale( nLanguage );
+}
+
+void SAL_CALL DictionaryNeo::setLocale( const Locale& aLocale )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ LanguageType nLanguageP = LinguLocaleToLanguage( aLocale );
+ if (!bIsReadonly && nLanguage != nLanguageP)
+ {
+ nLanguage = nLanguageP;
+ bIsModified = true; // new language needs to be saved with dictionary
+
+ launchEvent( DictionaryEventFlags::CHG_LANGUAGE, nullptr );
+ }
+}
+
+uno::Reference< XDictionaryEntry > SAL_CALL DictionaryNeo::getEntry(
+ const OUString& aWord )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+
+ sal_Int32 nPos;
+ bool bFound = seekEntry( aWord, &nPos, true );
+ DBG_ASSERT(!bFound || nPos < static_cast<sal_Int32>(aEntries.size()), "lng : index out of range");
+
+ return bFound ? aEntries[ nPos ]
+ : uno::Reference< XDictionaryEntry >();
+}
+
+sal_Bool SAL_CALL DictionaryNeo::addEntry(
+ const uno::Reference< XDictionaryEntry >& xDicEntry )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+
+ if (!bIsReadonly)
+ {
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+ bRes = addEntry_Impl( xDicEntry );
+ }
+
+ return bRes;
+}
+
+sal_Bool SAL_CALL
+ DictionaryNeo::add( const OUString& rWord, sal_Bool bIsNegative,
+ const OUString& rRplcText )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+
+ if (!bIsReadonly)
+ {
+ uno::Reference< XDictionaryEntry > xEntry =
+ new DicEntry( rWord, bIsNegative, rRplcText );
+ bRes = addEntry_Impl( xEntry );
+ }
+
+ return bRes;
+}
+
+sal_Bool SAL_CALL DictionaryNeo::remove( const OUString& aWord )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRemoved = false;
+
+ if (!bIsReadonly)
+ {
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+
+ sal_Int32 nPos;
+ bool bFound = seekEntry( aWord, &nPos );
+ DBG_ASSERT(!bFound || nPos < static_cast<sal_Int32>(aEntries.size()), "lng : index out of range");
+
+ // remove element if found
+ if (bFound)
+ {
+ // entry to be removed
+ uno::Reference< XDictionaryEntry >
+ xDicEntry( aEntries[ nPos ] );
+ DBG_ASSERT(xDicEntry.is(), "lng : dictionary entry is NULL");
+
+ aEntries.erase(aEntries.begin() + nPos);
+
+ bRemoved = bIsModified = true;
+
+ launchEvent( DictionaryEventFlags::DEL_ENTRY, xDicEntry );
+ }
+ }
+
+ return bRemoved;
+}
+
+sal_Bool SAL_CALL DictionaryNeo::isFull( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+ return aEntries.size() >= DIC_MAX_ENTRIES;
+}
+
+uno::Sequence< uno::Reference< XDictionaryEntry > >
+ SAL_CALL DictionaryNeo::getEntries( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bNeedEntries)
+ loadEntries( aMainURL );
+ return comphelper::containerToSequence(aEntries);
+}
+
+
+void SAL_CALL DictionaryNeo::clear( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bIsReadonly && !aEntries.empty())
+ {
+ // release all references to old entries
+ aEntries.clear();
+
+ bNeedEntries = false;
+ bIsModified = true;
+
+ launchEvent( DictionaryEventFlags::ENTRIES_CLEARED , nullptr );
+ }
+}
+
+sal_Bool SAL_CALL DictionaryNeo::addDictionaryEventListener(
+ const uno::Reference< XDictionaryEventListener >& xListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+ if (xListener.is())
+ {
+ sal_Int32 nLen = aDicEvtListeners.getLength();
+ bRes = aDicEvtListeners.addInterface( xListener ) != nLen;
+ }
+ return bRes;
+}
+
+sal_Bool SAL_CALL DictionaryNeo::removeDictionaryEventListener(
+ const uno::Reference< XDictionaryEventListener >& xListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+ if (xListener.is())
+ {
+ sal_Int32 nLen = aDicEvtListeners.getLength();
+ bRes = aDicEvtListeners.removeInterface( xListener ) != nLen;
+ }
+ return bRes;
+}
+
+
+sal_Bool SAL_CALL DictionaryNeo::hasLocation()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return !aMainURL.isEmpty();
+}
+
+OUString SAL_CALL DictionaryNeo::getLocation()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return aMainURL;
+}
+
+sal_Bool SAL_CALL DictionaryNeo::isReadonly()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ return bIsReadonly;
+}
+
+void SAL_CALL DictionaryNeo::store()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (bIsModified && hasLocation() && !isReadonly())
+ {
+ if (!saveEntries( aMainURL ))
+ bIsModified = false;
+ }
+}
+
+void SAL_CALL DictionaryNeo::storeAsURL(
+ const OUString& aURL,
+ const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!saveEntries( aURL ))
+ {
+ aMainURL = aURL;
+ bIsModified = false;
+ bIsReadonly = IsReadOnly( getLocation() );
+ }
+}
+
+void SAL_CALL DictionaryNeo::storeToURL(
+ const OUString& aURL,
+ const uno::Sequence< beans::PropertyValue >& /*rArgs*/ )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ saveEntries(aURL);
+}
+
+
+DicEntry::DicEntry(const OUString &rDicFileWord,
+ bool bIsNegativWord)
+{
+ if (!rDicFileWord.isEmpty())
+ splitDicFileWord( rDicFileWord, aDicWord, aReplacement );
+ bIsNegativ = bIsNegativWord;
+}
+
+DicEntry::DicEntry(OUString aDicWord_, bool bNegativ,
+ OUString aRplcText_) :
+ aDicWord (std::move(aDicWord_)),
+ aReplacement (std::move(aRplcText_)),
+ bIsNegativ (bNegativ)
+{
+}
+
+DicEntry::~DicEntry()
+{
+}
+
+void DicEntry::splitDicFileWord(const OUString &rDicFileWord,
+ OUString &rDicWord,
+ OUString &rReplacement)
+{
+ sal_Int32 nDelimPos = rDicFileWord.indexOf( "==" );
+ if (-1 != nDelimPos)
+ {
+ sal_Int32 nTriplePos = nDelimPos + 2;
+ if ( nTriplePos < rDicFileWord.getLength()
+ && rDicFileWord[ nTriplePos ] == '=' )
+ ++nDelimPos;
+ rDicWord = rDicFileWord.copy( 0, nDelimPos );
+ rReplacement = rDicFileWord.copy( nDelimPos + 2 );
+ }
+ else
+ {
+ rDicWord = rDicFileWord;
+ rReplacement.clear();
+ }
+}
+
+OUString SAL_CALL DicEntry::getDictionaryWord( )
+{
+ return aDicWord;
+}
+
+sal_Bool SAL_CALL DicEntry::isNegative( )
+{
+ return bIsNegativ;
+}
+
+OUString SAL_CALL DicEntry::getReplacementText( )
+{
+ return aReplacement;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/dicimp.hxx b/linguistic/source/dicimp.hxx
new file mode 100644
index 000000000..c94094de6
--- /dev/null
+++ b/linguistic/source/dicimp.hxx
@@ -0,0 +1,174 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_DICIMP_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_DICIMP_HXX
+
+#include <com/sun/star/linguistic2/XDictionary.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <i18nlangtag/lang.h>
+#include <vcl/errcode.hxx>
+
+#include "defs.hxx"
+
+#define DIC_MAX_ENTRIES 30000
+
+sal_Int16 ReadDicVersion( SvStream& rStream, LanguageType &nLng, bool &bNeg, OUString &aDicName );
+
+class DictionaryNeo :
+ public ::cppu::WeakImplHelper
+ <
+ css::linguistic2::XDictionary,
+ css::frame::XStorable
+ >
+{
+
+ ::comphelper::OInterfaceContainerHelper3<css::linguistic2::XDictionaryEventListener> aDicEvtListeners;
+ std::vector< css::uno::Reference< css::linguistic2::XDictionaryEntry > >
+ aEntries;
+ OUString aDicName;
+ OUString aMainURL;
+ css::linguistic2::DictionaryType eDicType;
+ LanguageType nLanguage;
+ sal_Int16 nDicVersion;
+ bool bNeedEntries;
+ bool bIsModified;
+ bool bIsActive;
+ bool bIsReadonly;
+
+ DictionaryNeo(const DictionaryNeo &) = delete;
+ DictionaryNeo & operator = (const DictionaryNeo &) = delete;
+
+ void launchEvent(sal_Int16 nEvent,
+ const css::uno::Reference< css::linguistic2::XDictionaryEntry >& xEntry);
+
+ ErrCode loadEntries(const OUString &rMainURL);
+ ErrCode saveEntries(const OUString &rMainURL);
+ static int cmpDicEntry(std::u16string_view rWord1,
+ std::u16string_view rWord2,
+ bool bSimilarOnly = false);
+ bool seekEntry(std::u16string_view rWord, sal_Int32 *pPos,
+ bool bSimilarOnly = false);
+ bool isSorted();
+
+ bool addEntry_Impl(const css::uno::Reference< css::linguistic2::XDictionaryEntry >& rDicEntry,
+ bool bIsLoadEntries = false);
+
+public:
+ DictionaryNeo(OUString aName, LanguageType nLang,
+ css::linguistic2::DictionaryType eType,
+ const OUString &rMainURL,
+ bool bWriteable );
+ virtual ~DictionaryNeo() override;
+
+ // XNamed
+ virtual OUString SAL_CALL
+ getName() override;
+ virtual void SAL_CALL
+ setName( const OUString& aName ) override;
+
+ // XDictionary
+ virtual css::linguistic2::DictionaryType SAL_CALL
+ getDictionaryType() override;
+ virtual void SAL_CALL
+ setActive( sal_Bool bActivate ) override;
+ virtual sal_Bool SAL_CALL
+ isActive() override;
+ virtual sal_Int32 SAL_CALL
+ getCount() override;
+ virtual css::lang::Locale SAL_CALL
+ getLocale() override;
+ virtual void SAL_CALL
+ setLocale( const css::lang::Locale& aLocale ) override;
+ virtual css::uno::Reference<
+ css::linguistic2::XDictionaryEntry > SAL_CALL
+ getEntry( const OUString& aWord ) override;
+ virtual sal_Bool SAL_CALL
+ addEntry( const css::uno::Reference<
+ css::linguistic2::XDictionaryEntry >& xDicEntry ) override;
+ virtual sal_Bool SAL_CALL
+ add( const OUString& aWord, sal_Bool bIsNegative,
+ const OUString& aRplcText ) override;
+ virtual sal_Bool SAL_CALL
+ remove( const OUString& aWord ) override;
+ virtual sal_Bool SAL_CALL
+ isFull() override;
+ virtual css::uno::Sequence< css::uno::Reference< css::linguistic2::XDictionaryEntry > > SAL_CALL
+ getEntries() override;
+ virtual void SAL_CALL
+ clear() override;
+ virtual sal_Bool SAL_CALL
+ addDictionaryEventListener( const css::uno::Reference< css::linguistic2::XDictionaryEventListener >& xListener ) override;
+ virtual sal_Bool SAL_CALL
+ removeDictionaryEventListener( const css::uno::Reference< css::linguistic2::XDictionaryEventListener >& xListener ) override;
+
+ // XStorable
+ virtual sal_Bool SAL_CALL
+ hasLocation() override;
+ virtual OUString SAL_CALL
+ getLocation() override;
+ virtual sal_Bool SAL_CALL
+ isReadonly() override;
+ virtual void SAL_CALL
+ store() override;
+ virtual void SAL_CALL
+ storeAsURL( const OUString& aURL,
+ const css::uno::Sequence< css::beans::PropertyValue >& aArgs ) override;
+ virtual void SAL_CALL
+ storeToURL( const OUString& aURL,
+ const css::uno::Sequence< css::beans::PropertyValue >& aArgs ) override;
+};
+
+
+class DicEntry :
+ public cppu::WeakImplHelper< css::linguistic2::XDictionaryEntry >
+{
+ OUString aDicWord, // including hyphen positions represented by "="
+ aReplacement; // including hyphen positions represented by "="
+ bool bIsNegativ;
+
+ DicEntry(const DicEntry &) = delete;
+ DicEntry & operator = (const DicEntry &) = delete;
+
+ static void splitDicFileWord(const OUString &rDicFileWord,
+ OUString &rDicWord,
+ OUString &rReplacement);
+
+public:
+ DicEntry(const OUString &rDicFileWord, bool bIsNegativ);
+ DicEntry(OUString aDicWord, bool bIsNegativ,
+ OUString aRplcText);
+ virtual ~DicEntry() override;
+
+ // XDictionaryEntry
+ virtual OUString SAL_CALL
+ getDictionaryWord() override;
+ virtual sal_Bool SAL_CALL
+ isNegative() override;
+ virtual OUString SAL_CALL
+ getReplacementText() override;
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/dlistimp.cxx b/linguistic/source/dlistimp.cxx
new file mode 100644
index 000000000..281abce9e
--- /dev/null
+++ b/linguistic/source/dlistimp.cxx
@@ -0,0 +1,790 @@
+/* -*- 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 <cppuhelper/factory.hxx>
+#include <osl/file.hxx>
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/useroptions.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <unotools/localfilehelper.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
+#include <com/sun/star/linguistic2/DictionaryListEventFlags.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <svtools/strings.hrc>
+#include <unotools/resmgr.hxx>
+#include <unotools/charclass.hxx>
+#include <sal/log.hxx>
+#include <utility>
+
+#include "dlistimp.hxx"
+#include "dicimp.hxx"
+#include "lngopt.hxx"
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+static bool IsVers2OrNewer( const OUString& rFileURL, LanguageType& nLng, bool& bNeg, OUString& aDicName );
+
+static void AddInternal( const uno::Reference< XDictionary > &rDic,
+ const OUString& rNew );
+static void AddUserData( const uno::Reference< XDictionary > &rDic );
+
+
+class DicEvtListenerHelper :
+ public cppu::WeakImplHelper
+ <
+ XDictionaryEventListener
+ >
+{
+ comphelper::OInterfaceContainerHelper3<XDictionaryListEventListener> aDicListEvtListeners;
+ uno::Reference< XDictionaryList > xMyDicList;
+
+ sal_Int16 nCondensedEvt;
+ sal_Int16 nNumCollectEvtListeners;
+
+public:
+ explicit DicEvtListenerHelper( uno::Reference< XDictionaryList > xDicList );
+ virtual ~DicEvtListenerHelper() override;
+
+ // XEventListener
+ virtual void SAL_CALL
+ disposing( const EventObject& rSource ) override;
+
+ // XDictionaryEventListener
+ virtual void SAL_CALL
+ processDictionaryEvent( const DictionaryEvent& rDicEvent ) override;
+
+ // non-UNO functions
+ void DisposeAndClear( const EventObject &rEvtObj );
+
+ bool AddDicListEvtListener(
+ const uno::Reference< XDictionaryListEventListener >& rxListener );
+ bool RemoveDicListEvtListener(
+ const uno::Reference< XDictionaryListEventListener >& rxListener );
+ sal_Int16 BeginCollectEvents() { return ++nNumCollectEvtListeners;}
+ sal_Int16 EndCollectEvents();
+ sal_Int16 FlushEvents();
+ void ClearEvents() { nCondensedEvt = 0; }
+};
+
+
+DicEvtListenerHelper::DicEvtListenerHelper(
+ uno::Reference< XDictionaryList > xDicList ) :
+ aDicListEvtListeners ( GetLinguMutex() ),
+ xMyDicList (std::move( xDicList )),
+ nCondensedEvt(0), nNumCollectEvtListeners(0)
+{
+}
+
+
+DicEvtListenerHelper::~DicEvtListenerHelper()
+{
+ DBG_ASSERT(aDicListEvtListeners.getLength() == 0,
+ "lng : event listeners are still existing");
+}
+
+
+void DicEvtListenerHelper::DisposeAndClear( const EventObject &rEvtObj )
+{
+ aDicListEvtListeners.disposeAndClear( rEvtObj );
+}
+
+
+void SAL_CALL DicEvtListenerHelper::disposing( const EventObject& rSource )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Reference< XDictionaryListEventListener > xSrc( rSource.Source, UNO_QUERY );
+
+ // remove event object from EventListener list
+ if (xSrc.is())
+ aDicListEvtListeners.removeInterface( xSrc );
+
+ // if object is a dictionary then remove it from the dictionary list
+ // Note: this will probably happen only if someone makes a XDictionary
+ // implementation of his own that is also a XComponent.
+ uno::Reference< XDictionary > xDic( rSource.Source, UNO_QUERY );
+ if (xDic.is())
+ {
+ xMyDicList->removeDictionary( xDic );
+ }
+}
+
+
+void SAL_CALL DicEvtListenerHelper::processDictionaryEvent(
+ const DictionaryEvent& rDicEvent )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Reference< XDictionary > xDic( rDicEvent.Source, UNO_QUERY );
+ DBG_ASSERT(xDic.is(), "lng : missing event source");
+
+ // assert that there is a corresponding dictionary entry if one was
+ // added or deleted
+ DBG_ASSERT( !(rDicEvent.nEvent &
+ (DictionaryEventFlags::ADD_ENTRY | DictionaryEventFlags::DEL_ENTRY))
+ || rDicEvent.xDictionaryEntry.is(),
+ "lng : missing dictionary entry" );
+
+ // evaluate DictionaryEvents and update data for next DictionaryListEvent
+ DictionaryType eDicType = xDic->getDictionaryType();
+ DBG_ASSERT(eDicType != DictionaryType_MIXED,
+ "lng : unexpected dictionary type");
+ if ((rDicEvent.nEvent & DictionaryEventFlags::ADD_ENTRY) && xDic->isActive())
+ nCondensedEvt |= rDicEvent.xDictionaryEntry->isNegative() ?
+ DictionaryListEventFlags::ADD_NEG_ENTRY :
+ DictionaryListEventFlags::ADD_POS_ENTRY;
+ if ((rDicEvent.nEvent & DictionaryEventFlags::DEL_ENTRY) && xDic->isActive())
+ nCondensedEvt |= rDicEvent.xDictionaryEntry->isNegative() ?
+ DictionaryListEventFlags::DEL_NEG_ENTRY :
+ DictionaryListEventFlags::DEL_POS_ENTRY;
+ if ((rDicEvent.nEvent & DictionaryEventFlags::ENTRIES_CLEARED) && xDic->isActive())
+ nCondensedEvt |= eDicType == DictionaryType_NEGATIVE ?
+ DictionaryListEventFlags::DEL_NEG_ENTRY :
+ DictionaryListEventFlags::DEL_POS_ENTRY;
+ if ((rDicEvent.nEvent & DictionaryEventFlags::CHG_LANGUAGE) && xDic->isActive())
+ nCondensedEvt |= eDicType == DictionaryType_NEGATIVE ?
+ DictionaryListEventFlags::DEACTIVATE_NEG_DIC
+ | DictionaryListEventFlags::ACTIVATE_NEG_DIC :
+ DictionaryListEventFlags::DEACTIVATE_POS_DIC
+ | DictionaryListEventFlags::ACTIVATE_POS_DIC;
+ if (rDicEvent.nEvent & DictionaryEventFlags::ACTIVATE_DIC)
+ nCondensedEvt |= eDicType == DictionaryType_NEGATIVE ?
+ DictionaryListEventFlags::ACTIVATE_NEG_DIC :
+ DictionaryListEventFlags::ACTIVATE_POS_DIC;
+ if (rDicEvent.nEvent & DictionaryEventFlags::DEACTIVATE_DIC)
+ nCondensedEvt |= eDicType == DictionaryType_NEGATIVE ?
+ DictionaryListEventFlags::DEACTIVATE_NEG_DIC :
+ DictionaryListEventFlags::DEACTIVATE_POS_DIC;
+
+ if (nNumCollectEvtListeners == 0 && nCondensedEvt != 0)
+ FlushEvents();
+}
+
+
+bool DicEvtListenerHelper::AddDicListEvtListener(
+ const uno::Reference< XDictionaryListEventListener >& xListener )
+{
+ DBG_ASSERT( xListener.is(), "empty reference" );
+ sal_Int32 nCount = aDicListEvtListeners.getLength();
+ return aDicListEvtListeners.addInterface( xListener ) != nCount;
+}
+
+
+bool DicEvtListenerHelper::RemoveDicListEvtListener(
+ const uno::Reference< XDictionaryListEventListener >& xListener )
+{
+ DBG_ASSERT( xListener.is(), "empty reference" );
+ sal_Int32 nCount = aDicListEvtListeners.getLength();
+ return aDicListEvtListeners.removeInterface( xListener ) != nCount;
+}
+
+
+sal_Int16 DicEvtListenerHelper::EndCollectEvents()
+{
+ DBG_ASSERT(nNumCollectEvtListeners > 0, "lng: mismatched function call");
+ if (nNumCollectEvtListeners > 0)
+ {
+ FlushEvents();
+ nNumCollectEvtListeners--;
+ }
+
+ return nNumCollectEvtListeners;
+}
+
+
+sal_Int16 DicEvtListenerHelper::FlushEvents()
+{
+ if (0 != nCondensedEvt)
+ {
+ // build DictionaryListEvent to pass on to listeners
+ uno::Sequence< DictionaryEvent > aDicEvents;
+ DictionaryListEvent aEvent( xMyDicList, nCondensedEvt, aDicEvents );
+
+ // pass on event
+ aDicListEvtListeners.notifyEach( &XDictionaryListEventListener::processDictionaryListEvent, aEvent );
+
+ // clear "list" of events
+ nCondensedEvt = 0;
+ }
+
+ return nNumCollectEvtListeners;
+}
+
+
+void DicList::MyAppExitListener::AtExit()
+{
+ rMyDicList.SaveDics();
+}
+
+
+DicList::DicList() :
+ aEvtListeners ( GetLinguMutex() )
+{
+ mxDicEvtLstnrHelper = new DicEvtListenerHelper( this );
+ bDisposing = false;
+ bInCreation = false;
+
+ mxExitListener = new MyAppExitListener( *this );
+ mxExitListener->Activate();
+}
+
+DicList::~DicList()
+{
+ mxExitListener->Deactivate();
+}
+
+
+void DicList::SearchForDictionaries(
+ DictionaryVec_t&rDicList,
+ const OUString &rDicDirURL,
+ bool bIsWriteablePath )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ const uno::Sequence< OUString > aDirCnt( utl::LocalFileHelper::
+ GetFolderContents( rDicDirURL, false ) );
+ SvtSysLocale aSysLocale;
+
+ for (const OUString& aURL : aDirCnt)
+ {
+ LanguageType nLang = LANGUAGE_NONE;
+ bool bNeg = false;
+ OUString aDicTitle = "";
+
+ if(!::IsVers2OrNewer( aURL, nLang, bNeg, aDicTitle ))
+ {
+ // When not
+ sal_Int32 nPos = aURL.indexOf('.');
+ OUString aExt( aURL.copy(nPos + 1).toAsciiLowerCase() );
+
+ if ("dcn" == aExt) // negative
+ bNeg = true;
+ else if ("dcp" == aExt) // positive
+ bNeg = false;
+ else
+ continue; // other files
+ }
+
+ // Record in the list of Dictionaries
+ // When it already exists don't record
+ OUString aTmp1 = aSysLocale.GetCharClass().lowercase( aURL);
+ sal_Int32 nPos = aTmp1.lastIndexOf( '/' );
+ if (-1 != nPos)
+ aTmp1 = aTmp1.copy( nPos + 1 );
+ OUString aTmp2;
+ size_t j;
+ size_t nCount = rDicList.size();
+ for(j = 0; j < nCount; j++)
+ {
+ aTmp2 = rDicList[j]->getName();
+ aTmp2 = aSysLocale.GetCharClass().lowercase( aTmp2);
+ if(aTmp1 == aTmp2)
+ break;
+ }
+ if(j >= nCount) // dictionary not yet in DicList
+ {
+ // get decoded dictionary file name
+ INetURLObject aURLObj( aURL );
+ OUString aDicName = aURLObj.getName( INetURLObject::LAST_SEGMENT,
+ true, INetURLObject::DecodeMechanism::WithCharset );
+
+ DictionaryType eType = bNeg ? DictionaryType_NEGATIVE : DictionaryType_POSITIVE;
+ uno::Reference< XDictionary > xDic =
+ new DictionaryNeo( aDicTitle.isEmpty() ? aDicName : aDicTitle, nLang, eType, aURL, bIsWriteablePath );
+
+ addDictionary( xDic );
+ nCount++;
+ }
+ }
+}
+
+
+sal_Int32 DicList::GetDicPos(const uno::Reference< XDictionary > &xDic)
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ size_t n = rDicList.size();
+ for (size_t i = 0; i < n; i++)
+ {
+ if ( rDicList[i] == xDic )
+ return i;
+ }
+ return -1;
+}
+
+sal_Int16 SAL_CALL DicList::getCount()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ return static_cast< sal_Int16 >(GetOrCreateDicList().size());
+}
+
+uno::Sequence< uno::Reference< XDictionary > > SAL_CALL
+ DicList::getDictionaries()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+
+ return comphelper::containerToSequence(rDicList);
+}
+
+uno::Reference< XDictionary > SAL_CALL
+ DicList::getDictionaryByName( const OUString& aDictionaryName )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Reference< XDictionary > xDic;
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ size_t nCount = rDicList.size();
+ for (size_t i = 0; i < nCount; i++)
+ {
+ const uno::Reference< XDictionary > &rDic = rDicList[i];
+ if (rDic.is() && rDic->getName() == aDictionaryName)
+ {
+ xDic = rDic;
+ break;
+ }
+ }
+
+ return xDic;
+}
+
+sal_Bool SAL_CALL DicList::addDictionary(
+ const uno::Reference< XDictionary >& xDictionary )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing)
+ return false;
+
+ bool bRes = false;
+ if (xDictionary.is())
+ {
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ rDicList.push_back( xDictionary );
+ bRes = true;
+
+ // add listener helper to the dictionaries listener lists
+ xDictionary->addDictionaryEventListener( mxDicEvtLstnrHelper );
+ }
+ return bRes;
+}
+
+sal_Bool SAL_CALL
+ DicList::removeDictionary( const uno::Reference< XDictionary >& xDictionary )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing)
+ return false;
+
+ bool bRes = false;
+ sal_Int32 nPos = GetDicPos( xDictionary );
+ if (nPos >= 0)
+ {
+ // remove dictionary list from the dictionaries listener lists
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ uno::Reference< XDictionary > xDic( rDicList[ nPos ] );
+ DBG_ASSERT(xDic.is(), "lng : empty reference");
+ if (xDic.is())
+ {
+ // deactivate dictionary if not already done
+ xDic->setActive( false );
+
+ xDic->removeDictionaryEventListener( mxDicEvtLstnrHelper );
+ }
+
+ // remove element at nPos
+ rDicList.erase( rDicList.begin() + nPos );
+ bRes = true;
+ }
+ return bRes;
+}
+
+sal_Bool SAL_CALL DicList::addDictionaryListEventListener(
+ const uno::Reference< XDictionaryListEventListener >& xListener,
+ sal_Bool bReceiveVerbose )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing)
+ return false;
+
+ DBG_ASSERT(!bReceiveVerbose, "lng : not yet supported");
+
+ bool bRes = false;
+ if (xListener.is()) //! don't add empty references
+ {
+ bRes = mxDicEvtLstnrHelper->AddDicListEvtListener( xListener );
+ }
+ return bRes;
+}
+
+sal_Bool SAL_CALL DicList::removeDictionaryListEventListener(
+ const uno::Reference< XDictionaryListEventListener >& xListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing)
+ return false;
+
+ bool bRes = false;
+ if(xListener.is())
+ {
+ bRes = mxDicEvtLstnrHelper->RemoveDicListEvtListener( xListener );
+ }
+ return bRes;
+}
+
+sal_Int16 SAL_CALL DicList::beginCollectEvents()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ return mxDicEvtLstnrHelper->BeginCollectEvents();
+}
+
+sal_Int16 SAL_CALL DicList::endCollectEvents()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ return mxDicEvtLstnrHelper->EndCollectEvents();
+}
+
+sal_Int16 SAL_CALL DicList::flushEvents()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ return mxDicEvtLstnrHelper->FlushEvents();
+}
+
+uno::Reference< XDictionary > SAL_CALL
+ DicList::createDictionary( const OUString& rName, const Locale& rLocale,
+ DictionaryType eDicType, const OUString& rURL )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ bool bIsWriteablePath = rURL.match( GetDictionaryWriteablePath() );
+ return new DictionaryNeo( rName, nLanguage, eDicType, rURL, bIsWriteablePath );
+}
+
+
+uno::Reference< XDictionaryEntry > SAL_CALL
+ DicList::queryDictionaryEntry( const OUString& rWord, const Locale& rLocale,
+ sal_Bool bSearchPosDics, sal_Bool bSearchSpellEntry )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ return SearchDicList( this, rWord, LinguLocaleToLanguage( rLocale ),
+ bSearchPosDics, bSearchSpellEntry );
+}
+
+
+void SAL_CALL
+ DicList::dispose()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing)
+ return;
+
+ bDisposing = true;
+ EventObject aEvtObj( static_cast<XDictionaryList *>(this) );
+
+ aEvtListeners.disposeAndClear( aEvtObj );
+ if (mxDicEvtLstnrHelper.is())
+ mxDicEvtLstnrHelper->DisposeAndClear( aEvtObj );
+
+ //! avoid creation of dictionaries if not already done
+ if ( !aDicList.empty() )
+ {
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ size_t nCount = rDicList.size();
+ for (size_t i = 0; i < nCount; i++)
+ {
+ // save (modified) dictionaries
+ uno::Reference< frame::XStorable > xStor( rDicList[i] , UNO_QUERY );
+ if (xStor.is())
+ {
+ try
+ {
+ if (!xStor->isReadonly() && xStor->hasLocation())
+ xStor->store();
+ }
+ catch(Exception &)
+ {
+ }
+ }
+
+ // release references to (members of) this object hold by
+ // dictionaries
+ if (rDicList[i].is())
+ rDicList[i]->removeDictionaryEventListener( mxDicEvtLstnrHelper );
+ }
+ }
+ mxDicEvtLstnrHelper.clear();
+}
+
+void SAL_CALL
+ DicList::addEventListener( const uno::Reference< XEventListener >& rxListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.addInterface( rxListener );
+}
+
+void SAL_CALL
+ DicList::removeEventListener( const uno::Reference< XEventListener >& rxListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.removeInterface( rxListener );
+}
+
+void DicList::CreateDicList()
+{
+ bInCreation = true;
+
+ // look for dictionaries
+ const OUString aWriteablePath( GetDictionaryWriteablePath() );
+ std::vector< OUString > aPaths( GetDictionaryPaths() );
+ for (const OUString & aPath : aPaths)
+ {
+ const bool bIsWriteablePath = (aPath == aWriteablePath);
+ SearchForDictionaries( aDicList, aPath, bIsWriteablePath );
+ }
+
+ // create IgnoreAllList dictionary with empty URL (non persistent)
+ // and add it to list
+ std::locale loc(Translate::Create("svt"));
+ uno::Reference< XDictionary > xIgnAll(
+ createDictionary( Translate::get(STR_DESCRIPTION_IGNOREALLLIST, loc), LinguLanguageToLocale( LANGUAGE_NONE ),
+ DictionaryType_POSITIVE, OUString() ) );
+ if (xIgnAll.is())
+ {
+ AddUserData( xIgnAll );
+ xIgnAll->setActive( true );
+ addDictionary( xIgnAll );
+ }
+
+
+ // evaluate list of dictionaries to be activated from configuration
+ //! to suppress overwriting the list of active dictionaries in the
+ //! configuration with incorrect arguments during the following
+ //! activation of the dictionaries
+ mxDicEvtLstnrHelper->BeginCollectEvents();
+ const uno::Sequence< OUString > aActiveDics( aOpt.GetActiveDics() );
+ for (const OUString& rActiveDic : aActiveDics)
+ {
+ if (!rActiveDic.isEmpty())
+ {
+ uno::Reference< XDictionary > xDic( getDictionaryByName( rActiveDic ) );
+ if (xDic.is())
+ xDic->setActive( true );
+ }
+ }
+
+ // suppress collected events during creation of the dictionary list.
+ // there should be no events during creation.
+ mxDicEvtLstnrHelper->ClearEvents();
+
+ mxDicEvtLstnrHelper->EndCollectEvents();
+
+ bInCreation = false;
+}
+
+
+void DicList::SaveDics()
+{
+ // save dics only if they have already been used/created.
+ //! don't create them just for the purpose of saving them !
+ if ( aDicList.empty() )
+ return;
+
+ // save (modified) dictionaries
+ DictionaryVec_t& rDicList = GetOrCreateDicList();
+ size_t nCount = rDicList.size();
+ for (size_t i = 0; i < nCount; i++)
+ {
+ // save (modified) dictionaries
+ uno::Reference< frame::XStorable > xStor( rDicList[i], UNO_QUERY );
+ if (xStor.is())
+ {
+ try
+ {
+ if (!xStor->isReadonly() && xStor->hasLocation())
+ xStor->store();
+ }
+ catch(Exception &)
+ {
+ }
+ }
+ }
+}
+
+
+// Service specific part
+
+OUString SAL_CALL DicList::getImplementationName( )
+{
+ return "com.sun.star.lingu2.DicList";
+}
+
+
+sal_Bool SAL_CALL DicList::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL DicList::getSupportedServiceNames( )
+{
+ return { "com.sun.star.linguistic2.DictionaryList" };
+}
+
+
+
+static sal_Int32 lcl_GetToken( OUString &rToken,
+ const OUString &rText, sal_Int32 nPos, std::u16string_view rDelim )
+{
+ sal_Int32 nRes = -1;
+
+ if (rText.isEmpty() || nPos >= rText.getLength())
+ rToken.clear();
+ else if (rDelim.empty())
+ {
+ rToken = rText;
+ if (!rToken.isEmpty())
+ nRes = rText.getLength();
+ }
+ else
+ {
+ sal_Int32 i;
+ for (i = nPos; i < rText.getLength(); ++i)
+ {
+ if (std::string_view::npos != rDelim.find( rText[i] ))
+ break;
+ }
+
+ if (i >= rText.getLength()) // delimiter not found
+ rToken = rText.copy( nPos );
+ else
+ rToken = rText.copy( nPos, i - nPos );
+ nRes = i + 1; // continue after found delimiter
+ }
+
+ return nRes;
+}
+
+
+static void AddInternal(
+ const uno::Reference<XDictionary> &rDic,
+ const OUString& rNew )
+{
+ if (!rDic.is())
+ return;
+
+ //! TL TODO: word iterator should be used to break up the text
+ OUString aDelim("!\"#$%&'()*+,-/:;<=>?[]\\_^`{|}~\t \n");
+ OSL_ENSURE(aDelim.indexOf(u'.') == -1,
+ "ensure no '.'");
+
+ OUString aToken;
+ sal_Int32 nPos = 0;
+ while (-1 != (nPos = lcl_GetToken( aToken, rNew, nPos, aDelim )))
+ {
+ if( !aToken.isEmpty() && !IsNumeric( aToken ) )
+ {
+ rDic->add( aToken, false, OUString() );
+ }
+ }
+}
+
+static void AddUserData( const uno::Reference< XDictionary > &rDic )
+{
+ if (rDic.is())
+ {
+ SvtUserOptions aUserOpt;
+ AddInternal( rDic, aUserOpt.GetFullName() );
+ AddInternal( rDic, aUserOpt.GetCompany() );
+ AddInternal( rDic, aUserOpt.GetStreet() );
+ AddInternal( rDic, aUserOpt.GetCity() );
+ AddInternal( rDic, aUserOpt.GetTitle() );
+ AddInternal( rDic, aUserOpt.GetPosition() );
+ AddInternal( rDic, aUserOpt.GetEmail() );
+ }
+}
+
+static bool IsVers2OrNewer( const OUString& rFileURL, LanguageType& nLng, bool& bNeg, OUString& aDicName )
+{
+ if (rFileURL.isEmpty())
+ return false;
+ OUString aExt;
+ sal_Int32 nPos = rFileURL.lastIndexOf( '.' );
+ if (-1 != nPos)
+ aExt = rFileURL.copy( nPos + 1 ).toAsciiLowerCase();
+
+ if (aExt != "dic")
+ return false;
+
+ // get stream to be used
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ // get XInputStream stream
+ uno::Reference< io::XInputStream > xStream;
+ try
+ {
+ uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
+ xStream = xAccess->openFileRead( rFileURL );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "failed to get input stream" );
+ }
+ DBG_ASSERT( xStream.is(), "failed to get stream for read" );
+ if (!xStream.is())
+ return false;
+
+ std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
+
+ int nDicVersion = ReadDicVersion(*pStream, nLng, bNeg, aDicName);
+ return 2 == nDicVersion || nDicVersion >= 5;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+linguistic_DicList_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new DicList());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/dlistimp.hxx b/linguistic/source/dlistimp.hxx
new file mode 100644
index 000000000..97ae91f7c
--- /dev/null
+++ b/linguistic/source/dlistimp.hxx
@@ -0,0 +1,121 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_DLISTIMP_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_DLISTIMP_HXX
+
+#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <comphelper/interfacecontainer3.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+
+#include <vector>
+
+#include <linguistic/misc.hxx>
+#include "lngopt.hxx"
+
+class DicEvtListenerHelper;
+
+
+class DicList :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XSearchableDictionaryList,
+ css::lang::XComponent,
+ css::lang::XServiceInfo
+ >
+{
+ class MyAppExitListener : public linguistic::AppExitListener
+ {
+ DicList & rMyDicList;
+
+ public:
+ explicit MyAppExitListener( DicList &rDicList ) : rMyDicList( rDicList ) {}
+ virtual void AtExit() override;
+ };
+
+ LinguOptions aOpt;
+
+ ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener> aEvtListeners;
+
+ typedef std::vector< css::uno::Reference< css::linguistic2::XDictionary > > DictionaryVec_t;
+ DictionaryVec_t aDicList;
+
+ rtl::Reference<DicEvtListenerHelper> mxDicEvtLstnrHelper;
+ rtl::Reference<MyAppExitListener> mxExitListener;
+
+ bool bDisposing;
+ bool bInCreation;
+
+ DicList( const DicList & ) = delete;
+ DicList & operator = (const DicList &) = delete;
+
+ void CreateDicList();
+ DictionaryVec_t & GetOrCreateDicList()
+ {
+ if ( !bInCreation && aDicList.empty() )
+ CreateDicList();
+ return aDicList;
+ }
+
+ void SearchForDictionaries( DictionaryVec_t &rDicList,
+ const OUString &rDicDir, bool bIsWritePath );
+ sal_Int32 GetDicPos(const css::uno::Reference<
+ css::linguistic2::XDictionary > &xDic);
+
+public:
+ DicList();
+ virtual ~DicList() override;
+
+ // XDictionaryList
+ virtual ::sal_Int16 SAL_CALL getCount( ) override;
+ virtual css::uno::Sequence< css::uno::Reference< css::linguistic2::XDictionary > > SAL_CALL getDictionaries( ) override;
+ virtual css::uno::Reference< css::linguistic2::XDictionary > SAL_CALL getDictionaryByName( const OUString& aDictionaryName ) override;
+ virtual sal_Bool SAL_CALL addDictionary( const css::uno::Reference< css::linguistic2::XDictionary >& xDictionary ) override;
+ virtual sal_Bool SAL_CALL removeDictionary( const css::uno::Reference< css::linguistic2::XDictionary >& xDictionary ) override;
+ virtual sal_Bool SAL_CALL addDictionaryListEventListener( const css::uno::Reference< css::linguistic2::XDictionaryListEventListener >& xListener, sal_Bool bReceiveVerbose ) override;
+ virtual sal_Bool SAL_CALL removeDictionaryListEventListener( const css::uno::Reference< css::linguistic2::XDictionaryListEventListener >& xListener ) override;
+ virtual ::sal_Int16 SAL_CALL beginCollectEvents( ) override;
+ virtual ::sal_Int16 SAL_CALL endCollectEvents( ) override;
+ virtual ::sal_Int16 SAL_CALL flushEvents( ) override;
+ virtual css::uno::Reference< css::linguistic2::XDictionary > SAL_CALL createDictionary( const OUString& aName, const css::lang::Locale& aLocale, css::linguistic2::DictionaryType eDicType, const OUString& aURL ) override;
+
+ // XSearchableDictionaryList
+ virtual css::uno::Reference< css::linguistic2::XDictionaryEntry > SAL_CALL queryDictionaryEntry( const OUString& aWord, const css::lang::Locale& aLocale, sal_Bool bSearchPosDics, sal_Bool bSpellEntry ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // non UNO-specific
+ void SaveDics();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/gciterator.cxx b/linguistic/source/gciterator.cxx
new file mode 100644
index 000000000..28336c518
--- /dev/null
+++ b/linguistic/source/gciterator.cxx
@@ -0,0 +1,1179 @@
+/* -*- 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 <sal/macros.h>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/ElementExistException.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/linguistic2/XSupportedLocales.hpp>
+#include <com/sun/star/linguistic2/XProofreader.hpp>
+#include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
+#include <com/sun/star/linguistic2/SingleProofreadingError.hpp>
+#include <com/sun/star/linguistic2/ProofreadingResult.hpp>
+#include <com/sun/star/linguistic2/LinguServiceEvent.hpp>
+#include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+#include <com/sun/star/text/TextMarkupDescriptor.hpp>
+#include <com/sun/star/text/XMultiTextMarkup.hpp>
+#include <com/sun/star/text/XFlatParagraph.hpp>
+#include <com/sun/star/text/XFlatParagraphIterator.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/conditn.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <map>
+
+#include <linguistic/misc.hxx>
+
+#include "gciterator.hxx"
+
+using namespace linguistic;
+using namespace ::com::sun::star;
+
+// white space list: obtained from the fonts.config.txt of a Linux system.
+const sal_Unicode aWhiteSpaces[] =
+{
+ 0x0020, /* SPACE */
+ 0x00a0, /* NO-BREAK SPACE */
+ 0x00ad, /* SOFT HYPHEN */
+ 0x115f, /* HANGUL CHOSEONG FILLER */
+ 0x1160, /* HANGUL JUNGSEONG FILLER */
+ 0x1680, /* OGHAM SPACE MARK */
+ 0x2000, /* EN QUAD */
+ 0x2001, /* EM QUAD */
+ 0x2002, /* EN SPACE */
+ 0x2003, /* EM SPACE */
+ 0x2004, /* THREE-PER-EM SPACE */
+ 0x2005, /* FOUR-PER-EM SPACE */
+ 0x2006, /* SIX-PER-EM SPACE */
+ 0x2007, /* FIGURE SPACE */
+ 0x2008, /* PUNCTUATION SPACE */
+ 0x2009, /* THIN SPACE */
+ 0x200a, /* HAIR SPACE */
+ 0x200b, /* ZERO WIDTH SPACE */
+ 0x200c, /* ZERO WIDTH NON-JOINER */
+ 0x200d, /* ZERO WIDTH JOINER */
+ 0x200e, /* LEFT-TO-RIGHT MARK */
+ 0x200f, /* RIGHT-TO-LEFT MARK */
+ 0x2028, /* LINE SEPARATOR */
+ 0x2029, /* PARAGRAPH SEPARATOR */
+ 0x202a, /* LEFT-TO-RIGHT EMBEDDING */
+ 0x202b, /* RIGHT-TO-LEFT EMBEDDING */
+ 0x202c, /* POP DIRECTIONAL FORMATTING */
+ 0x202d, /* LEFT-TO-RIGHT OVERRIDE */
+ 0x202e, /* RIGHT-TO-LEFT OVERRIDE */
+ 0x202f, /* NARROW NO-BREAK SPACE */
+ 0x205f, /* MEDIUM MATHEMATICAL SPACE */
+ 0x2060, /* WORD JOINER */
+ 0x2061, /* FUNCTION APPLICATION */
+ 0x2062, /* INVISIBLE TIMES */
+ 0x2063, /* INVISIBLE SEPARATOR */
+ 0x206A, /* INHIBIT SYMMETRIC SWAPPING */
+ 0x206B, /* ACTIVATE SYMMETRIC SWAPPING */
+ 0x206C, /* INHIBIT ARABIC FORM SHAPING */
+ 0x206D, /* ACTIVATE ARABIC FORM SHAPING */
+ 0x206E, /* NATIONAL DIGIT SHAPES */
+ 0x206F, /* NOMINAL DIGIT SHAPES */
+ 0x3000, /* IDEOGRAPHIC SPACE */
+ 0x3164, /* HANGUL FILLER */
+ 0xfeff, /* ZERO WIDTH NO-BREAK SPACE */
+ 0xffa0, /* HALFWIDTH HANGUL FILLER */
+ 0xfff9, /* INTERLINEAR ANNOTATION ANCHOR */
+ 0xfffa, /* INTERLINEAR ANNOTATION SEPARATOR */
+ 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */
+};
+
+// Information about reason for proofreading (ProofInfo)
+ const sal_Int32 PROOFINFO_GET_PROOFRESULT = 1;
+ const sal_Int32 PROOFINFO_MARK_PARAGRAPH = 2;
+
+const int nWhiteSpaces = SAL_N_ELEMENTS( aWhiteSpaces );
+
+static bool lcl_IsWhiteSpace( sal_Unicode cChar )
+{
+ bool bFound = false;
+ for (int i = 0; i < nWhiteSpaces && !bFound; ++i)
+ {
+ if (cChar == aWhiteSpaces[i])
+ bFound = true;
+ }
+ return bFound;
+}
+
+static sal_Int32 lcl_SkipWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
+{
+ // note having nStartPos point right behind the string is OK since that one
+ // is a correct end-of-sentence position to be returned from a grammar checker...
+
+ const sal_Int32 nLen = rText.getLength();
+ bool bIllegalArgument = false;
+ if (nStartPos < 0)
+ {
+ bIllegalArgument = true;
+ nStartPos = 0;
+ }
+ if (nStartPos > nLen)
+ {
+ bIllegalArgument = true;
+ nStartPos = nLen;
+ }
+ if (bIllegalArgument)
+ {
+ SAL_WARN( "linguistic", "lcl_SkipWhiteSpaces: illegal arguments" );
+ }
+
+ sal_Int32 nRes = nStartPos;
+ if (0 <= nStartPos && nStartPos < nLen)
+ {
+ const sal_Unicode* const pEnd = rText.getStr() + nLen;
+ const sal_Unicode *pText = rText.getStr() + nStartPos;
+ while (pText != pEnd && lcl_IsWhiteSpace(*pText))
+ ++pText;
+ nRes = pText - rText.getStr();
+ }
+
+ DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_SkipWhiteSpaces return value out of range" );
+ return nRes;
+}
+
+static sal_Int32 lcl_BacktraceWhiteSpaces( const OUString &rText, sal_Int32 nStartPos )
+{
+ // note: having nStartPos point right behind the string is OK since that one
+ // is a correct end-of-sentence position to be returned from a grammar checker...
+
+ const sal_Int32 nLen = rText.getLength();
+ bool bIllegalArgument = false;
+ if (nStartPos < 0)
+ {
+ bIllegalArgument = true;
+ nStartPos = 0;
+ }
+ if (nStartPos > nLen)
+ {
+ bIllegalArgument = true;
+ nStartPos = nLen;
+ }
+ if (bIllegalArgument)
+ {
+ SAL_WARN( "linguistic", "lcl_BacktraceWhiteSpaces: illegal arguments" );
+ }
+
+ sal_Int32 nRes = nStartPos;
+ sal_Int32 nPosBefore = nStartPos - 1;
+ const sal_Unicode *pStart = rText.getStr();
+ if (0 <= nPosBefore && nPosBefore < nLen && lcl_IsWhiteSpace( pStart[ nPosBefore ] ))
+ {
+ nStartPos = nPosBefore;
+ const sal_Unicode *pText = rText.getStr() + nStartPos;
+ while (pText > pStart && lcl_IsWhiteSpace( *pText ))
+ --pText;
+ // now add 1 since we want to point to the first char after the last char in the sentence...
+ nRes = pText - pStart + 1;
+ }
+
+ DBG_ASSERT( 0 <= nRes && nRes <= nLen, "lcl_BacktraceWhiteSpaces return value out of range" );
+ return nRes;
+}
+
+
+extern "C" {
+
+static void lcl_workerfunc (void * gci)
+{
+ osl_setThreadName("GrammarCheckingIterator");
+
+ static_cast<GrammarCheckingIterator*>(gci)->DequeueAndCheck();
+}
+
+}
+
+static lang::Locale lcl_GetPrimaryLanguageOfSentence(
+ const uno::Reference< text::XFlatParagraph >& xFlatPara,
+ sal_Int32 nStartIndex )
+{
+ //get the language of the first word
+ return xFlatPara->getLanguageOfText( nStartIndex, 1 );
+}
+
+
+LngXStringKeyMap::LngXStringKeyMap() {}
+
+void SAL_CALL LngXStringKeyMap::insertValue(const OUString& aKey, const css::uno::Any& aValue)
+{
+ std::map<OUString, css::uno::Any>::const_iterator aIter = maMap.find(aKey);
+ if (aIter != maMap.end())
+ throw css::container::ElementExistException();
+
+ maMap[aKey] = aValue;
+}
+
+css::uno::Any SAL_CALL LngXStringKeyMap::getValue(const OUString& aKey)
+{
+ std::map<OUString, css::uno::Any>::const_iterator aIter = maMap.find(aKey);
+ if (aIter == maMap.end())
+ throw css::container::NoSuchElementException();
+
+ return (*aIter).second;
+}
+
+sal_Bool SAL_CALL LngXStringKeyMap::hasValue(const OUString& aKey)
+{
+ return maMap.find(aKey) != maMap.end();
+}
+
+::sal_Int32 SAL_CALL LngXStringKeyMap::getCount() { return maMap.size(); }
+
+OUString SAL_CALL LngXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex)
+{
+ if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maMap.size())
+ throw css::lang::IndexOutOfBoundsException();
+
+ return OUString();
+}
+
+css::uno::Any SAL_CALL LngXStringKeyMap::getValueByIndex(::sal_Int32 nIndex)
+{
+ if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maMap.size())
+ throw css::lang::IndexOutOfBoundsException();
+
+ return css::uno::Any();
+}
+
+
+osl::Mutex& GrammarCheckingIterator::MyMutex()
+{
+ static osl::Mutex SINGLETON;
+ return SINGLETON;
+}
+
+GrammarCheckingIterator::GrammarCheckingIterator() :
+ m_bEnd( false ),
+ m_bGCServicesChecked( false ),
+ m_nDocIdCounter( 0 ),
+ m_thread(nullptr),
+ m_aEventListeners( MyMutex() ),
+ m_aNotifyListeners( MyMutex() )
+{
+}
+
+
+GrammarCheckingIterator::~GrammarCheckingIterator()
+{
+ TerminateThread();
+}
+
+void GrammarCheckingIterator::TerminateThread()
+{
+ oslThread t;
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ t = m_thread;
+ m_thread = nullptr;
+ m_bEnd = true;
+ m_aWakeUpThread.set();
+ }
+ if (t != nullptr)
+ {
+ osl_joinWithThread(t);
+ osl_destroyThread(t);
+ }
+}
+
+sal_Int32 GrammarCheckingIterator::NextDocId()
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ m_nDocIdCounter += 1;
+ return m_nDocIdCounter;
+}
+
+
+OUString GrammarCheckingIterator::GetOrCreateDocId(
+ const uno::Reference< lang::XComponent > &xComponent )
+{
+ // internal method; will always be called with locked mutex
+
+ OUString aRes;
+ if (xComponent.is())
+ {
+ if (m_aDocIdMap.find( xComponent.get() ) != m_aDocIdMap.end())
+ {
+ // return already existing entry
+ aRes = m_aDocIdMap[ xComponent.get() ];
+ }
+ else // add new entry
+ {
+ sal_Int32 nRes = NextDocId();
+ aRes = OUString::number( nRes );
+ m_aDocIdMap[ xComponent.get() ] = aRes;
+ xComponent->addEventListener( this );
+ }
+ }
+ return aRes;
+}
+
+
+void GrammarCheckingIterator::AddEntry(
+ const uno::WeakReference< text::XFlatParagraphIterator >& xFlatParaIterator,
+ const uno::WeakReference< text::XFlatParagraph >& xFlatPara,
+ const OUString & rDocId,
+ sal_Int32 nStartIndex,
+ bool bAutomatic )
+{
+ // we may not need/have a xFlatParaIterator (e.g. if checkGrammarAtPos was called)
+ // but we always need a xFlatPara...
+ uno::Reference< text::XFlatParagraph > xPara( xFlatPara );
+ if (!xPara.is())
+ return;
+
+ FPEntry aNewFPEntry;
+ aNewFPEntry.m_xParaIterator = xFlatParaIterator;
+ aNewFPEntry.m_xPara = xFlatPara;
+ aNewFPEntry.m_aDocId = rDocId;
+ aNewFPEntry.m_nStartIndex = nStartIndex;
+ aNewFPEntry.m_bAutomatic = bAutomatic;
+
+ // add new entry to the end of this queue
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ if (!m_thread)
+ m_thread = osl_createThread( lcl_workerfunc, this );
+ m_aFPEntriesQueue.push_back( aNewFPEntry );
+
+ // wake up the thread in order to do grammar checking
+ m_aWakeUpThread.set();
+}
+
+
+void GrammarCheckingIterator::ProcessResult(
+ const linguistic2::ProofreadingResult &rRes,
+ const uno::Reference< text::XFlatParagraphIterator > &rxFlatParagraphIterator,
+ bool bIsAutomaticChecking )
+{
+ DBG_ASSERT( rRes.xFlatParagraph.is(), "xFlatParagraph is missing" );
+ //no guard necessary as no members are used
+ bool bContinueWithNextPara = false;
+ if (!rRes.xFlatParagraph.is() || rRes.xFlatParagraph->isModified())
+ {
+ // if paragraph was modified/deleted meanwhile continue with the next one...
+ bContinueWithNextPara = true;
+ }
+ else // paragraph is still unchanged...
+ {
+ // mark found errors...
+
+ sal_Int32 nTextLen = rRes.aText.getLength();
+ bool bBoundariesOk = 0 <= rRes.nStartOfSentencePosition && rRes.nStartOfSentencePosition <= nTextLen &&
+ 0 <= rRes.nBehindEndOfSentencePosition && rRes.nBehindEndOfSentencePosition <= nTextLen &&
+ 0 <= rRes.nStartOfNextSentencePosition && rRes.nStartOfNextSentencePosition <= nTextLen &&
+ rRes.nStartOfSentencePosition <= rRes.nBehindEndOfSentencePosition &&
+ rRes.nBehindEndOfSentencePosition <= rRes.nStartOfNextSentencePosition;
+ DBG_ASSERT( bBoundariesOk, "inconsistent sentence boundaries" );
+
+ uno::Reference< text::XMultiTextMarkup > xMulti( rRes.xFlatParagraph, uno::UNO_QUERY );
+ if (xMulti.is()) // use new API for markups
+ {
+ try
+ {
+ // length = number of found errors + 1 sentence markup
+ sal_Int32 nErrors = rRes.aErrors.getLength();
+ uno::Sequence< text::TextMarkupDescriptor > aDescriptors( nErrors + 1 );
+ text::TextMarkupDescriptor * pDescriptors = aDescriptors.getArray();
+
+ // at pos 0 .. nErrors-1 -> all grammar errors
+ for (const linguistic2::SingleProofreadingError &rError : rRes.aErrors)
+ {
+ text::TextMarkupDescriptor &rDesc = *pDescriptors++;
+
+ rDesc.nType = rError.nErrorType;
+ rDesc.nOffset = rError.nErrorStart;
+ rDesc.nLength = rError.nErrorLength;
+
+ // the proofreader may return SPELLING but right now our core
+ // does only handle PROOFREADING if the result is from the proofreader...
+ // (later on we may wish to color spelling errors found by the proofreader
+ // differently for example. But no special handling right now.
+ if (rDesc.nType == text::TextMarkupType::SPELLCHECK)
+ rDesc.nType = text::TextMarkupType::PROOFREADING;
+
+ uno::Reference< container::XStringKeyMap > xKeyMap(
+ new LngXStringKeyMap());
+ for( const beans::PropertyValue& rProperty : rError.aProperties )
+ {
+ if ( rProperty.Name == "LineColor" )
+ {
+ xKeyMap->insertValue(rProperty.Name,
+ rProperty.Value);
+ rDesc.xMarkupInfoContainer = xKeyMap;
+ }
+ else if ( rProperty.Name == "LineType" )
+ {
+ xKeyMap->insertValue(rProperty.Name,
+ rProperty.Value);
+ rDesc.xMarkupInfoContainer = xKeyMap;
+ }
+ }
+ }
+
+ // at pos nErrors -> sentence markup
+ // nSentenceLength: includes the white-spaces following the sentence end...
+ const sal_Int32 nSentenceLength = rRes.nStartOfNextSentencePosition - rRes.nStartOfSentencePosition;
+ pDescriptors->nType = text::TextMarkupType::SENTENCE;
+ pDescriptors->nOffset = rRes.nStartOfSentencePosition;
+ pDescriptors->nLength = nSentenceLength;
+
+ xMulti->commitMultiTextMarkup( aDescriptors ) ;
+ }
+ catch (lang::IllegalArgumentException &)
+ {
+ TOOLS_WARN_EXCEPTION( "linguistic", "commitMultiTextMarkup" );
+ }
+ }
+
+ // other sentences left to be checked in this paragraph?
+ if (rRes.nStartOfNextSentencePosition < rRes.aText.getLength())
+ {
+ AddEntry( rxFlatParagraphIterator, rRes.xFlatParagraph, rRes.aDocumentIdentifier, rRes.nStartOfNextSentencePosition, bIsAutomaticChecking );
+ }
+ else // current paragraph finished
+ {
+ // set "already checked" flag for the current flat paragraph
+ if (rRes.xFlatParagraph.is())
+ rRes.xFlatParagraph->setChecked( text::TextMarkupType::PROOFREADING, true );
+
+ bContinueWithNextPara = true;
+ }
+ }
+
+ if (bContinueWithNextPara)
+ {
+ // we need to continue with the next paragraph
+ uno::Reference< text::XFlatParagraph > xFlatParaNext;
+ if (rxFlatParagraphIterator.is())
+ xFlatParaNext = rxFlatParagraphIterator->getNextPara();
+ {
+ AddEntry( rxFlatParagraphIterator, xFlatParaNext, rRes.aDocumentIdentifier, 0, bIsAutomaticChecking );
+ }
+ }
+}
+
+
+uno::Reference< linguistic2::XProofreader > GrammarCheckingIterator::GetGrammarChecker(
+ const lang::Locale &rLocale )
+{
+ uno::Reference< linguistic2::XProofreader > xRes;
+
+ // ---- THREAD SAFE START ----
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+
+ // check supported locales for each grammarchecker if not already done
+ if (!m_bGCServicesChecked)
+ {
+ GetConfiguredGCSvcs_Impl();
+ m_bGCServicesChecked = true;
+ }
+
+ const LanguageType nLang = LanguageTag::convertToLanguageType( rLocale, false);
+ GCImplNames_t::const_iterator aLangIt( m_aGCImplNamesByLang.find( nLang ) );
+ if (aLangIt != m_aGCImplNamesByLang.end()) // matching configured language found?
+ {
+ OUString aSvcImplName( aLangIt->second );
+ GCReferences_t::const_iterator aImplNameIt( m_aGCReferencesByService.find( aSvcImplName ) );
+ if (aImplNameIt != m_aGCReferencesByService.end()) // matching impl name found?
+ {
+ xRes = aImplNameIt->second;
+ }
+ else // the service is to be instantiated here for the first time...
+ {
+ try
+ {
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ uno::Reference< linguistic2::XProofreader > xGC(
+ xContext->getServiceManager()->createInstanceWithContext(aSvcImplName, xContext),
+ uno::UNO_QUERY_THROW );
+ uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW );
+
+ if (xSuppLoc->hasLocale( rLocale ))
+ {
+ m_aGCReferencesByService[ aSvcImplName ] = xGC;
+ xRes = xGC;
+
+ uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xBC( xGC, uno::UNO_QUERY );
+ if (xBC.is())
+ xBC->addLinguServiceEventListener( this );
+ }
+ else
+ {
+ SAL_WARN( "linguistic", "grammar checker does not support required locale" );
+ }
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "instantiating grammar checker failed" );
+ }
+ }
+ }
+ // ---- THREAD SAFE END ----
+
+ return xRes;
+}
+
+static uno::Sequence<beans::PropertyValue>
+lcl_makeProperties(uno::Reference<text::XFlatParagraph> const& xFlatPara, sal_Int32 nProofInfo)
+{
+ uno::Reference<beans::XPropertySet> const xProps(
+ xFlatPara, uno::UNO_QUERY_THROW);
+ css::uno::Any a (nProofInfo);
+ return comphelper::InitPropertySequence({
+ { "FieldPositions", xProps->getPropertyValue("FieldPositions") },
+ { "FootnotePositions", xProps->getPropertyValue("FootnotePositions") },
+ { "ProofInfo", a }
+ });
+}
+
+void GrammarCheckingIterator::DequeueAndCheck()
+{
+ for (;;)
+ {
+ // ---- THREAD SAFE START ----
+ bool bQueueEmpty = false;
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ if (m_bEnd)
+ {
+ break;
+ }
+ bQueueEmpty = m_aFPEntriesQueue.empty();
+ }
+ // ---- THREAD SAFE END ----
+
+ if (!bQueueEmpty)
+ {
+ uno::Reference< text::XFlatParagraphIterator > xFPIterator;
+ uno::Reference< text::XFlatParagraph > xFlatPara;
+ FPEntry aFPEntryItem;
+ OUString aCurDocId;
+ // ---- THREAD SAFE START ----
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ aFPEntryItem = m_aFPEntriesQueue.front();
+ xFPIterator = aFPEntryItem.m_xParaIterator;
+ xFlatPara = aFPEntryItem.m_xPara;
+ m_aCurCheckedDocId = aFPEntryItem.m_aDocId;
+ aCurDocId = m_aCurCheckedDocId;
+
+ m_aFPEntriesQueue.pop_front();
+ }
+ // ---- THREAD SAFE END ----
+
+ if (xFlatPara.is() && xFPIterator.is())
+ {
+ try
+ {
+ OUString aCurTxt( xFlatPara->getText() );
+ lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, aFPEntryItem.m_nStartIndex );
+
+ const bool bModified = xFlatPara->isModified();
+ if (!bModified)
+ {
+ linguistic2::ProofreadingResult aRes;
+
+ // ---- THREAD SAFE START ----
+ {
+ osl::ClearableMutexGuard aGuard(MyMutex());
+
+ sal_Int32 nStartPos = aFPEntryItem.m_nStartIndex;
+ sal_Int32 nSuggestedEnd
+ = GetSuggestedEndOfSentence(aCurTxt, nStartPos, aCurLocale);
+ DBG_ASSERT((nSuggestedEnd == 0 && aCurTxt.isEmpty())
+ || nSuggestedEnd > nStartPos,
+ "nSuggestedEndOfSentencePos calculation failed?");
+
+ uno::Reference<linguistic2::XProofreader> xGC =
+ GetGrammarChecker(aCurLocale);
+ if (xGC.is())
+ {
+ aGuard.clear();
+ uno::Sequence<beans::PropertyValue> const aProps(
+ lcl_makeProperties(xFlatPara, PROOFINFO_MARK_PARAGRAPH));
+ aRes = xGC->doProofreading(aCurDocId, aCurTxt, aCurLocale,
+ nStartPos, nSuggestedEnd, aProps);
+
+ //!! work-around to prevent looping if the grammar checker
+ //!! failed to properly identify the sentence end
+ if (aRes.nBehindEndOfSentencePosition <= nStartPos
+ && aRes.nBehindEndOfSentencePosition != nSuggestedEnd)
+ {
+ SAL_WARN(
+ "linguistic",
+ "!! Grammarchecker failed to provide end of sentence !!");
+ aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
+ }
+
+ aRes.xFlatParagraph = xFlatPara;
+ aRes.nStartOfSentencePosition = nStartPos;
+ }
+ else
+ {
+ // no grammar checker -> no error
+ // but we need to provide the data below in order to continue with the next sentence
+ aRes.aDocumentIdentifier = aCurDocId;
+ aRes.xFlatParagraph = xFlatPara;
+ aRes.aText = aCurTxt;
+ aRes.aLocale = aCurLocale;
+ aRes.nStartOfSentencePosition = nStartPos;
+ aRes.nBehindEndOfSentencePosition = nSuggestedEnd;
+ }
+ aRes.nStartOfNextSentencePosition
+ = lcl_SkipWhiteSpaces(aCurTxt, aRes.nBehindEndOfSentencePosition);
+ aRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces(
+ aCurTxt, aRes.nStartOfNextSentencePosition);
+
+ //guard has to be cleared as ProcessResult calls out of this class
+ }
+ // ---- THREAD SAFE END ----
+ ProcessResult( aRes, xFPIterator, aFPEntryItem.m_bAutomatic );
+ }
+ else
+ {
+ // the paragraph changed meanwhile... (and maybe is still edited)
+ // thus we simply continue to ask for the next to be checked.
+ uno::Reference< text::XFlatParagraph > xFlatParaNext( xFPIterator->getNextPara() );
+ AddEntry( xFPIterator, xFlatParaNext, aCurDocId, 0, aFPEntryItem.m_bAutomatic );
+ }
+ }
+ catch (css::uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("linguistic", "GrammarCheckingIterator::DequeueAndCheck ignoring");
+ }
+ }
+
+ // ---- THREAD SAFE START ----
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ m_aCurCheckedDocId.clear();
+ }
+ // ---- THREAD SAFE END ----
+ }
+ else
+ {
+ // ---- THREAD SAFE START ----
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ if (m_bEnd)
+ {
+ break;
+ }
+ // Check queue state again
+ if (m_aFPEntriesQueue.empty())
+ m_aWakeUpThread.reset();
+ }
+ // ---- THREAD SAFE END ----
+
+ //if the queue is empty
+ // IMPORTANT: Don't call condition.wait() with locked
+ // mutex. Otherwise you would keep out other threads
+ // to add entries to the queue! A condition is thread-
+ // safe implemented.
+ m_aWakeUpThread.wait();
+ }
+ }
+}
+
+
+void SAL_CALL GrammarCheckingIterator::startProofreading(
+ const uno::Reference< ::uno::XInterface > & xDoc,
+ const uno::Reference< text::XFlatParagraphIteratorProvider > & xIteratorProvider )
+{
+ // get paragraph to start checking with
+ const bool bAutomatic = true;
+ uno::Reference<text::XFlatParagraphIterator> xFPIterator = xIteratorProvider->getFlatParagraphIterator(
+ text::TextMarkupType::PROOFREADING, bAutomatic );
+ uno::Reference< text::XFlatParagraph > xPara( xFPIterator.is()? xFPIterator->getFirstPara() : nullptr );
+ uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
+
+ // ---- THREAD SAFE START ----
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ if (xPara.is() && xComponent.is())
+ {
+ OUString aDocId = GetOrCreateDocId( xComponent );
+
+ // create new entry and add it to queue
+ AddEntry( xFPIterator, xPara, aDocId, 0, bAutomatic );
+ }
+ // ---- THREAD SAFE END ----
+}
+
+
+linguistic2::ProofreadingResult SAL_CALL GrammarCheckingIterator::checkSentenceAtPosition(
+ const uno::Reference< uno::XInterface >& xDoc,
+ const uno::Reference< text::XFlatParagraph >& xFlatPara,
+ const OUString& rText,
+ const lang::Locale&,
+ sal_Int32 nStartOfSentencePos,
+ sal_Int32 nSuggestedEndOfSentencePos,
+ sal_Int32 nErrorPosInPara )
+{
+ // for the context menu...
+
+ linguistic2::ProofreadingResult aRes;
+
+ uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
+ if (xFlatPara.is() && xComponent.is() &&
+ ( nErrorPosInPara < 0 || nErrorPosInPara < rText.getLength()))
+ {
+ // iterate through paragraph until we find the sentence we are interested in
+ linguistic2::ProofreadingResult aTmpRes;
+ sal_Int32 nStartPos = nStartOfSentencePos >= 0 ? nStartOfSentencePos : 0;
+
+ bool bFound = false;
+ do
+ {
+ lang::Locale aCurLocale = lcl_GetPrimaryLanguageOfSentence( xFlatPara, nStartPos );
+ sal_Int32 nOldStartOfSentencePos = nStartPos;
+ uno::Reference< linguistic2::XProofreader > xGC;
+ OUString aDocId;
+
+ // ---- THREAD SAFE START ----
+ {
+ ::osl::ClearableGuard< ::osl::Mutex > aGuard( MyMutex() );
+ aDocId = GetOrCreateDocId( xComponent );
+ nSuggestedEndOfSentencePos = GetSuggestedEndOfSentence( rText, nStartPos, aCurLocale );
+ DBG_ASSERT( nSuggestedEndOfSentencePos > nStartPos, "nSuggestedEndOfSentencePos calculation failed?" );
+
+ xGC = GetGrammarChecker( aCurLocale );
+ }
+ // ---- THREAD SAFE START ----
+ sal_Int32 nEndPos = -1;
+ if (xGC.is())
+ {
+ uno::Sequence<beans::PropertyValue> const aProps(
+ lcl_makeProperties(xFlatPara, PROOFINFO_GET_PROOFRESULT));
+ aTmpRes = xGC->doProofreading( aDocId, rText,
+ aCurLocale, nStartPos, nSuggestedEndOfSentencePos, aProps );
+
+ //!! work-around to prevent looping if the grammar checker
+ //!! failed to properly identify the sentence end
+ if (aTmpRes.nBehindEndOfSentencePosition <= nStartPos)
+ {
+ SAL_WARN( "linguistic", "!! Grammarchecker failed to provide end of sentence !!" );
+ aTmpRes.nBehindEndOfSentencePosition = nSuggestedEndOfSentencePos;
+ }
+
+ aTmpRes.xFlatParagraph = xFlatPara;
+ aTmpRes.nStartOfSentencePosition = nStartPos;
+ nEndPos = aTmpRes.nBehindEndOfSentencePosition;
+
+ if ((nErrorPosInPara< 0 || nStartPos <= nErrorPosInPara) && nErrorPosInPara < nEndPos)
+ bFound = true;
+ }
+ if (nEndPos == -1) // no result from grammar checker
+ nEndPos = nSuggestedEndOfSentencePos;
+ nStartPos = lcl_SkipWhiteSpaces( rText, nEndPos );
+ aTmpRes.nBehindEndOfSentencePosition = nEndPos;
+ aTmpRes.nStartOfNextSentencePosition = nStartPos;
+ aTmpRes.nBehindEndOfSentencePosition = lcl_BacktraceWhiteSpaces( rText, aTmpRes.nStartOfNextSentencePosition );
+
+ // prevent endless loop by forcefully advancing if needs be...
+ if (nStartPos <= nOldStartOfSentencePos)
+ {
+ SAL_WARN( "linguistic", "end-of-sentence detection failed?" );
+ nStartPos = nOldStartOfSentencePos + 1;
+ }
+ }
+ while (!bFound && nStartPos < rText.getLength());
+
+ if (bFound && !xFlatPara->isModified())
+ aRes = aTmpRes;
+ }
+
+ return aRes;
+}
+
+
+sal_Int32 GrammarCheckingIterator::GetSuggestedEndOfSentence(
+ const OUString &rText,
+ sal_Int32 nSentenceStartPos,
+ const lang::Locale &rLocale )
+{
+ // internal method; will always be called with locked mutex
+
+ if (!m_xBreakIterator.is())
+ {
+ uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ m_xBreakIterator = i18n::BreakIterator::create(xContext);
+ }
+ sal_Int32 nTextLen = rText.getLength();
+ sal_Int32 nEndPosition(0);
+ sal_Int32 nTmpStartPos = nSentenceStartPos;
+ do
+ {
+ sal_Int32 const nPrevEndPosition(nEndPosition);
+ nEndPosition = nTextLen;
+ if (nTmpStartPos < nTextLen)
+ {
+ nEndPosition = m_xBreakIterator->endOfSentence( rText, nTmpStartPos, rLocale );
+ if (nEndPosition <= nPrevEndPosition)
+ {
+ // fdo#68750 if there's no progress at all then presumably
+ // there's no end of sentence in this paragraph so just
+ // set the end position to end of paragraph
+ nEndPosition = nTextLen;
+ }
+ }
+ if (nEndPosition < 0)
+ nEndPosition = nTextLen;
+
+ ++nTmpStartPos;
+ }
+ while (nEndPosition <= nSentenceStartPos && nEndPosition < nTextLen);
+ if (nEndPosition > nTextLen)
+ nEndPosition = nTextLen;
+ return nEndPosition;
+}
+
+
+void SAL_CALL GrammarCheckingIterator::resetIgnoreRules( )
+{
+ for (auto const& elem : m_aGCReferencesByService)
+ {
+ uno::Reference< linguistic2::XProofreader > xGC(elem.second);
+ if (xGC.is())
+ xGC->resetIgnoreRules();
+ }
+}
+
+
+sal_Bool SAL_CALL GrammarCheckingIterator::isProofreading(
+ const uno::Reference< uno::XInterface >& xDoc )
+{
+ // ---- THREAD SAFE START ----
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+
+ bool bRes = false;
+
+ uno::Reference< lang::XComponent > xComponent( xDoc, uno::UNO_QUERY );
+ if (xComponent.is())
+ {
+ // if the component was already used in one of the two calls to check text
+ // i.e. in startGrammarChecking or checkGrammarAtPos it will be found in the
+ // m_aDocIdMap unless the document already disposed.
+ // If it is not found then it is not yet being checked (or requested to being checked)
+ const DocMap_t::const_iterator aIt( m_aDocIdMap.find( xComponent.get() ) );
+ if (aIt != m_aDocIdMap.end())
+ {
+ // check in document is checked automatically in the background...
+ OUString aDocId = aIt->second;
+ if (!m_aCurCheckedDocId.isEmpty() && m_aCurCheckedDocId == aDocId)
+ {
+ // an entry for that document was dequeued and is currently being checked.
+ bRes = true;
+ }
+ else
+ {
+ // we need to check if there is an entry for that document in the queue...
+ // That is the document is going to be checked sooner or later.
+
+ sal_Int32 nSize = m_aFPEntriesQueue.size();
+ for (sal_Int32 i = 0; i < nSize && !bRes; ++i)
+ {
+ if (aDocId == m_aFPEntriesQueue[i].m_aDocId)
+ bRes = true;
+ }
+ }
+ }
+ }
+ // ---- THREAD SAFE END ----
+
+ return bRes;
+}
+
+
+void SAL_CALL GrammarCheckingIterator::processLinguServiceEvent(
+ const linguistic2::LinguServiceEvent& rLngSvcEvent )
+{
+ if (rLngSvcEvent.nEvent != linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN)
+ return;
+
+ try
+ {
+ uno::Reference< uno::XInterface > xThis( static_cast< OWeakObject * >(this) );
+ linguistic2::LinguServiceEvent aEvent( xThis, linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN );
+ m_aNotifyListeners.notifyEach(
+ &linguistic2::XLinguServiceEventListener::processLinguServiceEvent,
+ aEvent);
+ }
+ catch (uno::RuntimeException &)
+ {
+ throw;
+ }
+ catch (const ::uno::Exception &)
+ {
+ // ignore
+ TOOLS_WARN_EXCEPTION("linguistic", "processLinguServiceEvent");
+ }
+}
+
+
+sal_Bool SAL_CALL GrammarCheckingIterator::addLinguServiceEventListener(
+ const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
+{
+ if (xListener.is())
+ {
+ m_aNotifyListeners.addInterface( xListener );
+ }
+ return true;
+}
+
+
+sal_Bool SAL_CALL GrammarCheckingIterator::removeLinguServiceEventListener(
+ const uno::Reference< linguistic2::XLinguServiceEventListener >& xListener )
+{
+ if (xListener.is())
+ {
+ m_aNotifyListeners.removeInterface( xListener );
+ }
+ return true;
+}
+
+
+void SAL_CALL GrammarCheckingIterator::dispose()
+{
+ lang::EventObject aEvt( static_cast<linguistic2::XProofreadingIterator *>(this) );
+ m_aEventListeners.disposeAndClear( aEvt );
+
+ TerminateThread();
+
+ // ---- THREAD SAFE START ----
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+
+ // release all UNO references
+
+ m_xBreakIterator.clear();
+
+ // clear containers with UNO references AND have those references released
+ GCReferences_t aTmpEmpty1;
+ DocMap_t aTmpEmpty2;
+ FPQueue_t aTmpEmpty3;
+ m_aGCReferencesByService.swap( aTmpEmpty1 );
+ m_aDocIdMap.swap( aTmpEmpty2 );
+ m_aFPEntriesQueue.swap( aTmpEmpty3 );
+ }
+ // ---- THREAD SAFE END ----
+}
+
+
+void SAL_CALL GrammarCheckingIterator::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ if (xListener.is())
+ {
+ m_aEventListeners.addInterface( xListener );
+ }
+}
+
+
+void SAL_CALL GrammarCheckingIterator::removeEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ if (xListener.is())
+ {
+ m_aEventListeners.removeInterface( xListener );
+ }
+}
+
+
+void SAL_CALL GrammarCheckingIterator::disposing( const lang::EventObject &rSource )
+{
+ // if the component (document) is disposing release all references
+ //!! There is no need to remove entries from the queue that are from this document
+ //!! since the respectives xFlatParagraphs should become invalid (isModified() == true)
+ //!! and the call to xFlatParagraphIterator->getNextPara() will result in an empty reference.
+ //!! And if an entry is currently checked by a grammar checker upon return the results
+ //!! should be ignored.
+ //!! Also GetOrCreateDocId will not use that very same Id again...
+ //!! All of the above resulting in that we only have to get rid of the implementation pointer here.
+ uno::Reference< lang::XComponent > xDoc( rSource.Source, uno::UNO_QUERY );
+ if (xDoc.is())
+ {
+ // ---- THREAD SAFE START ----
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ m_aDocIdMap.erase( xDoc.get() );
+ // ---- THREAD SAFE END ----
+ }
+}
+
+
+uno::Reference< util::XChangesBatch > const & GrammarCheckingIterator::GetUpdateAccess() const
+{
+ if (!m_xUpdateAccess.is())
+ {
+ try
+ {
+ // get configuration provider
+ uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider =
+ configuration::theDefaultProvider::get( xContext );
+
+ // get configuration update access
+ beans::PropertyValue aValue;
+ aValue.Name = "nodepath";
+ aValue.Value <<= OUString("org.openoffice.Office.Linguistic/ServiceManager");
+ uno::Sequence< uno::Any > aProps{ uno::Any(aValue) };
+ m_xUpdateAccess.set(
+ xConfigurationProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationUpdateAccess", aProps ),
+ uno::UNO_QUERY_THROW );
+ }
+ catch (uno::Exception &)
+ {
+ }
+ }
+
+ return m_xUpdateAccess;
+}
+
+
+void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
+{
+ GCImplNames_t aTmpGCImplNamesByLang;
+
+ try
+ {
+ // get node names (locale iso strings) for configured grammar checkers
+ uno::Reference< container::XNameAccess > xNA( GetUpdateAccess(), uno::UNO_QUERY_THROW );
+ xNA.set( xNA->getByName( "GrammarCheckerList" ), uno::UNO_QUERY_THROW );
+ const uno::Sequence< OUString > aElementNames( xNA->getElementNames() );
+
+ for (const OUString& rElementName : aElementNames)
+ {
+ uno::Sequence< OUString > aImplNames;
+ uno::Any aTmp( xNA->getByName( rElementName ) );
+ if (aTmp >>= aImplNames)
+ {
+ if (aImplNames.hasElements())
+ {
+ // only the first entry is used, there should be only one grammar checker per language
+ const OUString aImplName( aImplNames[0] );
+ const LanguageType nLang = LanguageTag::convertToLanguageType( rElementName );
+ aTmpGCImplNamesByLang[ nLang ] = aImplName;
+ }
+ }
+ else
+ {
+ SAL_WARN( "linguistic", "failed to get aImplNames. Wrong type?" );
+ }
+ }
+ }
+ catch (uno::Exception const &)
+ {
+ TOOLS_WARN_EXCEPTION( "linguistic", "exception caught. Failed to get configured services" );
+ }
+
+ {
+ // ---- THREAD SAFE START ----
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+ m_aGCImplNamesByLang = aTmpGCImplNamesByLang;
+ // ---- THREAD SAFE END ----
+ }
+}
+
+
+sal_Bool SAL_CALL GrammarCheckingIterator::supportsService(
+ const OUString & rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+
+OUString SAL_CALL GrammarCheckingIterator::getImplementationName( )
+{
+ return "com.sun.star.lingu2.ProofreadingIterator";
+}
+
+
+uno::Sequence< OUString > SAL_CALL GrammarCheckingIterator::getSupportedServiceNames( )
+{
+ return { "com.sun.star.linguistic2.ProofreadingIterator" };
+}
+
+
+void GrammarCheckingIterator::SetServiceList(
+ const lang::Locale &rLocale,
+ const uno::Sequence< OUString > &rSvcImplNames )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ OUString aImplName;
+ if (rSvcImplNames.hasElements())
+ aImplName = rSvcImplNames[0]; // there is only one grammar checker per language
+
+ if (!LinguIsUnspecified(nLanguage) && nLanguage != LANGUAGE_DONTKNOW)
+ {
+ if (!aImplName.isEmpty())
+ m_aGCImplNamesByLang[ nLanguage ] = aImplName;
+ else
+ m_aGCImplNamesByLang.erase( nLanguage );
+ }
+}
+
+
+uno::Sequence< OUString > GrammarCheckingIterator::GetServiceList(
+ const lang::Locale &rLocale ) const
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );
+
+ OUString aImplName; // there is only one grammar checker per language
+ LanguageType nLang = LinguLocaleToLanguage( rLocale );
+ GCImplNames_t::const_iterator aIt( m_aGCImplNamesByLang.find( nLang ) );
+ if (aIt != m_aGCImplNamesByLang.end())
+ aImplName = aIt->second;
+
+ if (!aImplName.isEmpty())
+ return { aImplName };
+ return {};
+}
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+linguistic_GrammarCheckingIterator_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new GrammarCheckingIterator());
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/gciterator.hxx b/linguistic/source/gciterator.hxx
new file mode 100644
index 000000000..c4cc076b3
--- /dev/null
+++ b/linguistic/source/gciterator.hxx
@@ -0,0 +1,211 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_GCITERATOR_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_GCITERATOR_HXX
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
+#include <com/sun/star/linguistic2/XLinguServiceEventListener.hpp>
+#include <com/sun/star/linguistic2/XLinguServiceEventBroadcaster.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <osl/mutex.hxx>
+#include <osl/conditn.hxx>
+#include <osl/thread.h>
+
+#include <com/sun/star/uno/Any.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <i18nlangtag/lang.h>
+
+#include <map>
+#include <deque>
+
+#include "defs.hxx"
+
+
+struct FPEntry
+{
+ // flat paragraph iterator
+ css::uno::Reference< css::text::XFlatParagraphIterator > m_xParaIterator;
+
+ // flat paragraph
+ css::uno::WeakReference< css::text::XFlatParagraph > m_xPara;
+
+ // document ID to identify different documents
+ OUString m_aDocId;
+
+ // the starting position to be checked
+ sal_Int32 m_nStartIndex;
+
+ // the flag to identify whether the document does automatic grammar checking
+ bool m_bAutomatic;
+
+ FPEntry()
+ : m_aDocId()
+ , m_nStartIndex( 0 )
+ , m_bAutomatic( false )
+ {
+ }
+};
+
+
+class GrammarCheckingIterator:
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XProofreadingIterator,
+ css::linguistic2::XLinguServiceEventListener,
+ css::linguistic2::XLinguServiceEventBroadcaster,
+ css::lang::XComponent,
+ css::lang::XServiceInfo
+ >,
+ public LinguDispatcher
+{
+ //the queue is keeping track of all sentences to be checked
+ //every element of this queue is a FlatParagraphEntry struct-object
+ typedef std::deque< FPEntry > FPQueue_t;
+
+ // queue for entries to be processed
+ FPQueue_t m_aFPEntriesQueue;
+
+ // the flag to end the endless loop
+ bool m_bEnd;
+
+ // Note that it must be the pointer and not the uno-reference to check if it is the same implementation object
+ typedef std::map< XComponent *, OUString > DocMap_t;
+ DocMap_t m_aDocIdMap;
+
+
+ // language -> implname mapping
+ typedef std::map< LanguageType, OUString > GCImplNames_t;
+ GCImplNames_t m_aGCImplNamesByLang;
+
+ // implname -> UNO reference mapping
+ typedef std::map< OUString, css::uno::Reference< css::linguistic2::XProofreader > > GCReferences_t;
+ GCReferences_t m_aGCReferencesByService;
+
+ OUString m_aCurCheckedDocId;
+ bool m_bGCServicesChecked;
+ sal_Int32 m_nDocIdCounter;
+ osl::Condition m_aWakeUpThread;
+ oslThread m_thread;
+
+ //! beware of initialization order!
+ static osl::Mutex& MyMutex();
+ comphelper::OInterfaceContainerHelper3<css::lang::XEventListener> m_aEventListeners;
+ comphelper::OInterfaceContainerHelper3<css::linguistic2::XLinguServiceEventListener> m_aNotifyListeners;
+
+ css::uno::Reference< css::i18n::XBreakIterator > m_xBreakIterator;
+ mutable css::uno::Reference< css::util::XChangesBatch > m_xUpdateAccess;
+
+ void TerminateThread();
+
+ sal_Int32 NextDocId();
+ OUString GetOrCreateDocId( const css::uno::Reference< css::lang::XComponent > &xComp );
+
+ void AddEntry(
+ const css::uno::WeakReference< css::text::XFlatParagraphIterator >& xFlatParaIterator,
+ const css::uno::WeakReference< css::text::XFlatParagraph >& xFlatPara,
+ const OUString &rDocId, sal_Int32 nStartIndex, bool bAutomatic );
+
+ void ProcessResult( const css::linguistic2::ProofreadingResult &rRes,
+ const css::uno::Reference< css::text::XFlatParagraphIterator > &rxFlatParagraphIterator,
+ bool bIsAutomaticChecking );
+
+ sal_Int32 GetSuggestedEndOfSentence( const OUString &rText, sal_Int32 nSentenceStartPos, const css::lang::Locale &rLocale );
+
+ void GetConfiguredGCSvcs_Impl();
+ css::uno::Reference< css::linguistic2::XProofreader > GetGrammarChecker( const css::lang::Locale & rLocale );
+
+ css::uno::Reference< css::util::XChangesBatch > const & GetUpdateAccess() const;
+
+ GrammarCheckingIterator( const GrammarCheckingIterator & ) = delete;
+ GrammarCheckingIterator & operator = ( const GrammarCheckingIterator & ) = delete;
+
+public:
+
+ void DequeueAndCheck();
+
+ explicit GrammarCheckingIterator();
+ virtual ~GrammarCheckingIterator() override;
+
+ // XProofreadingIterator
+ virtual void SAL_CALL startProofreading( const css::uno::Reference< css::uno::XInterface >& xDocument, const css::uno::Reference< css::text::XFlatParagraphIteratorProvider >& xIteratorProvider ) override;
+ virtual css::linguistic2::ProofreadingResult SAL_CALL checkSentenceAtPosition( const css::uno::Reference< css::uno::XInterface >& xDocument, const css::uno::Reference< css::text::XFlatParagraph >& xFlatParagraph, const OUString& aText, const css::lang::Locale& aLocale, ::sal_Int32 nStartOfSentencePosition, ::sal_Int32 nSuggestedBehindEndOfSentencePosition, ::sal_Int32 nErrorPositionInParagraph ) override;
+ virtual void SAL_CALL resetIgnoreRules( ) override;
+ virtual sal_Bool SAL_CALL isProofreading( const css::uno::Reference< css::uno::XInterface >& xDocument ) override;
+
+ // XLinguServiceEventListener
+ virtual void SAL_CALL processLinguServiceEvent( const css::linguistic2::LinguServiceEvent& aLngSvcEvent ) override;
+
+ // XLinguServiceEventBroadcaster
+ virtual sal_Bool SAL_CALL addLinguServiceEventListener( const css::uno::Reference< css::linguistic2::XLinguServiceEventListener >& xLstnr ) override;
+ virtual sal_Bool SAL_CALL removeLinguServiceEventListener( const css::uno::Reference< css::linguistic2::XLinguServiceEventListener >& xLstnr ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose( ) override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // LinguDispatcher
+ virtual void SetServiceList( const css::lang::Locale &rLocale, const css::uno::Sequence< OUString > &rSvcImplNames ) override;
+ virtual css::uno::Sequence< OUString > GetServiceList( const css::lang::Locale &rLocale ) const override;
+};
+
+
+/** Implementation of the css::container::XStringKeyMap interface
+ */
+class LngXStringKeyMap : public ::cppu::WeakImplHelper<css::container::XStringKeyMap>
+{
+public:
+ LngXStringKeyMap();
+
+ virtual css::uno::Any SAL_CALL getValue(const OUString& aKey) override;
+ virtual sal_Bool SAL_CALL hasValue(const OUString& aKey) override;
+ virtual void SAL_CALL insertValue(const OUString& aKey, const css::uno::Any& aValue) override;
+ virtual ::sal_Int32 SAL_CALL getCount() override;
+ virtual OUString SAL_CALL getKeyByIndex(::sal_Int32 nIndex) override;
+ virtual css::uno::Any SAL_CALL getValueByIndex(::sal_Int32 nIndex) override;
+
+private:
+ LngXStringKeyMap(LngXStringKeyMap const &) = delete;
+ void operator=(LngXStringKeyMap const &) = delete;
+
+ ~LngXStringKeyMap() override{};
+
+ std::map<OUString, css::uno::Any> maMap;
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/hhconvdic.cxx b/linguistic/source/hhconvdic.cxx
new file mode 100644
index 000000000..d20088a10
--- /dev/null
+++ b/linguistic/source/hhconvdic.cxx
@@ -0,0 +1,120 @@
+/* -*- 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 <unicode/uscript.h>
+#include <i18nlangtag/lang.h>
+#include <osl/mutex.hxx>
+
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/linguistic2/ConversionDictionaryType.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include "hhconvdic.hxx"
+#include <linguistic/misc.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+constexpr OUStringLiteral SN_HH_CONV_DICTIONARY = u"com.sun.star.linguistic2.HangulHanjaConversionDictionary";
+
+
+#include <com/sun/star/i18n/UnicodeScript.hpp>
+
+using namespace i18n;
+
+#define SCRIPT_OTHERS 0
+#define SCRIPT_HANJA 1
+#define SCRIPT_HANGUL 2
+
+// from i18npool/source/textconversion/textconversion_ko.cxx
+/// @throws RuntimeException
+static sal_Int16 checkScriptType(sal_Unicode c)
+{
+ UErrorCode status = U_ZERO_ERROR;
+
+ UScriptCode scriptCode = uscript_getScript(c, &status);
+
+ if ( !U_SUCCESS(status) ) throw RuntimeException();
+
+ return scriptCode == USCRIPT_HANGUL ? SCRIPT_HANGUL :
+ scriptCode == USCRIPT_HAN ? SCRIPT_HANJA : SCRIPT_OTHERS;
+}
+
+
+static bool TextIsAllScriptType( std::u16string_view rTxt, sal_Int16 nScriptType )
+{
+ for (size_t i = 0; i < rTxt.size(); ++i)
+ {
+ if (checkScriptType( rTxt[i]) != nScriptType)
+ return false;
+ }
+ return true;
+}
+
+
+HHConvDic::HHConvDic( const OUString &rName, const OUString &rMainURL ) :
+ ConvDic( rName, LANGUAGE_KOREAN, ConversionDictionaryType::HANGUL_HANJA, true, rMainURL )
+{
+}
+
+
+HHConvDic::~HHConvDic()
+{
+}
+
+
+void SAL_CALL HHConvDic::addEntry(
+ const OUString& aLeftText,
+ const OUString& aRightText )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if ((aLeftText.getLength() != aRightText.getLength()) ||
+ !TextIsAllScriptType( aLeftText, SCRIPT_HANGUL ) ||
+ !TextIsAllScriptType( aRightText, SCRIPT_HANJA ))
+ throw IllegalArgumentException();
+ ConvDic::addEntry( aLeftText, aRightText );
+}
+
+
+OUString SAL_CALL HHConvDic::getImplementationName( )
+{
+ return "com.sun.star.lingu2.HHConvDic";
+}
+
+
+sal_Bool SAL_CALL HHConvDic::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+
+uno::Sequence< OUString > SAL_CALL HHConvDic::getSupportedServiceNames( )
+{
+ return { SN_CONV_DICTIONARY, SN_HH_CONV_DICTIONARY };
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/hhconvdic.hxx b/linguistic/source/hhconvdic.hxx
new file mode 100644
index 000000000..107607be6
--- /dev/null
+++ b/linguistic/source/hhconvdic.hxx
@@ -0,0 +1,45 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_HHCONVDIC_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_HHCONVDIC_HXX
+
+#include "convdic.hxx"
+
+class HHConvDic : public ConvDic
+{
+ HHConvDic(const HHConvDic&) = delete;
+ HHConvDic& operator=(const HHConvDic&) = delete;
+
+public:
+ HHConvDic(const OUString& rName, const OUString& rMainURL);
+ virtual ~HHConvDic() override;
+
+ // XConversionDictionary
+ virtual void SAL_CALL addEntry(const OUString& aLeftText, const OUString& aRightText) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/hyphdsp.cxx b/linguistic/source/hyphdsp.cxx
new file mode 100644
index 000000000..6113960d4
--- /dev/null
+++ b/linguistic/source/hyphdsp.cxx
@@ -0,0 +1,705 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#if OSL_DEBUG_LEVEL > 0
+#include <utility>
+#endif
+
+#include <cppuhelper/factory.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/linguistic2/XLinguProperties.hpp>
+#include <com/sun/star/linguistic2/XLinguServiceEventBroadcaster.hpp>
+#include <rtl/ustrbuf.hxx>
+#include <i18nlangtag/lang.h>
+#include <unotools/localedatawrapper.hxx>
+#include <tools/debug.hxx>
+#include <svl/lngmisc.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <osl/mutex.hxx>
+
+#include "hyphdsp.hxx"
+#include <linguistic/hyphdta.hxx>
+#include <linguistic/misc.hxx>
+#include "lngsvcmgr.hxx"
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+HyphenatorDispatcher::HyphenatorDispatcher( LngSvcMgr &rLngSvcMgr ) :
+ rMgr (rLngSvcMgr)
+{
+}
+
+
+HyphenatorDispatcher::~HyphenatorDispatcher()
+{
+ ClearSvcList();
+}
+
+
+void HyphenatorDispatcher::ClearSvcList()
+{
+ // release memory for each table entry
+ HyphSvcByLangMap_t().swap(aSvcMap);
+}
+
+
+Reference<XHyphenatedWord> HyphenatorDispatcher::buildHyphWord(
+ const OUString& rOrigWord,
+ const Reference<XDictionaryEntry> &xEntry,
+ LanguageType nLang, sal_Int16 nMaxLeading )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference< XHyphenatedWord > xRes;
+
+ if (xEntry.is())
+ {
+ OUString aText( xEntry->getDictionaryWord() );
+ sal_Int32 nTextLen = aText.getLength();
+
+ // trailing '=' means "hyphenation should not be possible"
+ if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=' && aText[ nTextLen - 1 ] != '[')
+ {
+ sal_Int16 nHyphenationPos = -1;
+ sal_Int16 nOrigHyphPos = -1;
+
+ OUStringBuffer aTmp( nTextLen );
+ bool bSkip = false;
+ bool bSkip2 = false;
+ sal_Int32 nHyphIdx = -1;
+ sal_Int32 nLeading = 0;
+ for (sal_Int32 i = 0; i < nTextLen; i++)
+ {
+ sal_Unicode cTmp = aText[i];
+ if (cTmp == '[' || cTmp == ']')
+ bSkip2 = !bSkip2;
+ if (cTmp != '=' && !bSkip2 && cTmp != ']')
+ {
+ aTmp.append( cTmp );
+ nLeading++;
+ bSkip = false;
+ nHyphIdx++;
+ }
+ else
+ {
+ if (!bSkip && nHyphIdx >= 0)
+ {
+ if (nLeading <= nMaxLeading) {
+ nHyphenationPos = static_cast<sal_Int16>(nHyphIdx);
+ nOrigHyphPos = i;
+ }
+ }
+ bSkip = true; //! multiple '=' should count as one only
+ }
+ }
+
+ if (nHyphenationPos > 0)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ {
+ if (std::u16string_view(aTmp) != rOrigWord)
+ {
+ // both words should only differ by a having a trailing '.'
+ // character or not...
+ std::u16string_view aShorter(aTmp), aLonger(rOrigWord);
+ if (aTmp.getLength() > rOrigWord.getLength())
+ std::swap(aShorter, aLonger);
+ sal_Int32 nS = aShorter.size();
+ sal_Int32 nL = aLonger.size();
+ if (nS > 0 && nL > 0)
+ {
+ assert( ((nS + 1 == nL) && aLonger[nL-1] == '.') && "HyphenatorDispatcher::buildHyphWord: unexpected difference between words!" );
+ }
+ }
+ }
+#endif
+ sal_Int32 nHyphenPos = -1;
+ if (aText[ nOrigHyphPos ] == '[') // alternative hyphenation
+ {
+ sal_Int16 split = 0;
+ sal_Unicode c = aText [ nOrigHyphPos + 1 ];
+ sal_Int32 endhyphpat = aText.indexOf( ']', nOrigHyphPos );
+ if ('0' <= c && c <= '9')
+ {
+ split = c - '0';
+ nOrigHyphPos++;
+ }
+ if (endhyphpat > -1)
+ {
+ OUStringBuffer aTmp2 ( aTmp.copy(0, std::max (nHyphenationPos + 1 - split, 0) ) );
+ aTmp2.append( aText.subView( nOrigHyphPos + 1, endhyphpat - nOrigHyphPos - 1) );
+ nHyphenPos = aTmp2.getLength();
+ aTmp2.append( aTmp.subView( nHyphenationPos + 1 ) );
+ //! take care of #i22591#
+ if (rOrigWord[ rOrigWord.getLength() - 1 ] == '.')
+ aTmp2.append( '.' );
+ aText = aTmp2.makeStringAndClear();
+ }
+ }
+ if (nHyphenPos == -1)
+ aText = rOrigWord;
+
+ xRes = new HyphenatedWord( rOrigWord, nLang, nHyphenationPos,
+ aText, (nHyphenPos > -1) ? nHyphenPos - 1 : nHyphenationPos);
+ }
+ }
+ }
+
+ return xRes;
+}
+
+
+Reference< XPossibleHyphens > HyphenatorDispatcher::buildPossHyphens(
+ const Reference< XDictionaryEntry > &xEntry, LanguageType nLanguage )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference<XPossibleHyphens> xRes;
+
+ if (xEntry.is())
+ {
+ // text with hyphenation info
+ OUString aText( xEntry->getDictionaryWord() );
+ sal_Int32 nTextLen = aText.getLength();
+
+ // trailing '=' means "hyphenation should not be possible"
+ if (nTextLen > 0 && aText[ nTextLen - 1 ] != '=' && aText[ nTextLen - 1 ] != '[')
+ {
+ // sequence to hold hyphenation positions
+ Sequence< sal_Int16 > aHyphPos( nTextLen );
+ sal_Int16 *pPos = aHyphPos.getArray();
+ sal_Int32 nHyphCount = 0;
+
+ OUStringBuffer aTmp( nTextLen );
+ bool bSkip = false;
+ bool bSkip2 = false;
+ sal_Int32 nHyphIdx = -1;
+ for (sal_Int32 i = 0; i < nTextLen; i++)
+ {
+ sal_Unicode cTmp = aText[i];
+ if (cTmp == '[' || cTmp == ']')
+ bSkip2 = !bSkip2;
+ if (cTmp != '=' && !bSkip2 && cTmp != ']')
+ {
+ aTmp.append( cTmp );
+ bSkip = false;
+ nHyphIdx++;
+ }
+ else
+ {
+ if (!bSkip && nHyphIdx >= 0)
+ pPos[ nHyphCount++ ] = static_cast<sal_Int16>(nHyphIdx);
+ bSkip = true; //! multiple '=' should count as one only
+ }
+ }
+
+ // ignore (multiple) trailing '='
+ if (bSkip && nHyphIdx >= 0)
+ {
+ nHyphCount--;
+ }
+ DBG_ASSERT( nHyphCount >= 0, "lng : invalid hyphenation count");
+
+ if (nHyphCount > 0)
+ {
+ aHyphPos.realloc( nHyphCount );
+ xRes = new PossibleHyphens( aTmp.makeStringAndClear(), nLanguage,
+ aText, aHyphPos );
+ }
+ }
+ }
+
+ return xRes;
+}
+
+
+Sequence< Locale > SAL_CALL HyphenatorDispatcher::getLocales()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector<Locale> aLocales;
+ aLocales.reserve(aSvcMap.size());
+
+ std::transform(aSvcMap.begin(), aSvcMap.end(), std::back_inserter(aLocales),
+ [](HyphSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
+
+ return comphelper::containerToSequence(aLocales);
+}
+
+
+sal_Bool SAL_CALL HyphenatorDispatcher::hasLocale(const Locale& rLocale)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ HyphSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
+ return aIt != aSvcMap.end();
+}
+
+
+Reference< XHyphenatedWord > SAL_CALL
+ HyphenatorDispatcher::hyphenate(
+ const OUString& rWord, const Locale& rLocale, sal_Int16 nMaxLeading,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference< XHyphenatedWord > xRes;
+
+ sal_Int32 nWordLen = rWord.getLength();
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ if (LinguIsUnspecified(nLanguage) || !nWordLen ||
+ nMaxLeading == 0 || nMaxLeading == nWordLen)
+ return xRes;
+
+ // search for entry with that language
+ HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ bool bWordModified = false;
+ if (!pEntry || (nMaxLeading < 0 || nMaxLeading > nWordLen))
+ {
+ return nullptr;
+ }
+ else
+ {
+ OUString aChkWord( rWord );
+
+ // replace typographical apostroph by ascii apostroph
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
+
+ bWordModified |= RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ bWordModified |= RemoveControlChars( aChkWord );
+ sal_Int16 nChkMaxLeading = static_cast<sal_Int16>(GetPosInWordToCheck( rWord, nMaxLeading ));
+
+ // check for results from (positive) dictionaries which have precedence!
+ Reference< XDictionaryEntry > xEntry;
+
+ if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
+ {
+ xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
+ true, false );
+ }
+
+ if (xEntry.is())
+ {
+ //! because queryDictionaryEntry (in the end DictionaryNeo::getEntry)
+ //! does not distinguish between "XYZ" and "XYZ." in order to avoid
+ //! to require them as different entry we have to supply the
+ //! original word here as well so it can be used in th result
+ //! otherwise a strange effect may occur (see #i22591#)
+ xRes = buildHyphWord( rWord, xEntry, nLanguage, nChkMaxLeading );
+ }
+ else
+ {
+ sal_Int32 nLen = pEntry->aSvcImplNames.hasElements() ? 1 : 0;
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+ Reference< XHyphenator > xHyph;
+ if (pEntry->aSvcRefs.hasElements())
+ xHyph = pEntry->aSvcRefs[0];
+
+ // try already instantiated service
+ if (i <= pEntry->nLastTriedSvcIndex)
+ {
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
+ rProperties );
+ ++i;
+ }
+ else if (pEntry->nLastTriedSvcIndex < nLen - 1)
+ // instantiate services and try it
+ {
+ Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ // create specific service via it's implementation name
+ try
+ {
+ xHyph = Reference< XHyphenator >(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pEntry->aSvcImplNames[0], aArgs, xContext ),
+ UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+ pRef [i] = xHyph;
+
+ Reference< XLinguServiceEventBroadcaster >
+ xBroadcaster( xHyph, UNO_QUERY );
+ if (xBroadcaster.is())
+ rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
+
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->hyphenate( aChkWord, rLocale, nChkMaxLeading,
+ rProperties );
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+
+ // if language is not supported by the services
+ // remove it from the list.
+ if (xHyph.is() && !xHyph->hasLocale( rLocale ))
+ aSvcMap.erase( nLanguage );
+ }
+ } // if (xEntry.is())
+ }
+
+ if (bWordModified && xRes.is())
+ xRes = RebuildHyphensAndControlChars( rWord, xRes );
+
+ if (xRes.is() && xRes->getWord() != rWord)
+ {
+ xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
+ xRes->getHyphenatedWord(),
+ xRes->getHyphenPos() );
+ }
+
+ return xRes;
+}
+
+
+Reference< XHyphenatedWord > SAL_CALL
+ HyphenatorDispatcher::queryAlternativeSpelling(
+ const OUString& rWord, const Locale& rLocale, sal_Int16 nIndex,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference< XHyphenatedWord > xRes;
+
+ sal_Int32 nWordLen = rWord.getLength();
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ if (LinguIsUnspecified(nLanguage) || !nWordLen)
+ return xRes;
+
+ // search for entry with that language
+ HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ bool bWordModified = false;
+ if (!pEntry || 0 > nIndex || nIndex > nWordLen - 2)
+ {
+ return nullptr;
+ }
+ else
+ {
+ OUString aChkWord( rWord );
+
+ // replace typographical apostroph by ascii apostroph
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
+
+ bWordModified |= RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ bWordModified |= RemoveControlChars( aChkWord );
+ sal_Int16 nChkIndex = static_cast<sal_Int16>(GetPosInWordToCheck( rWord, nIndex ));
+
+ // check for results from (positive) dictionaries which have precedence!
+ Reference< XDictionaryEntry > xEntry;
+
+ if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
+ {
+ xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
+ true, false );
+ }
+
+ if (xEntry.is())
+ {
+ xRes = buildHyphWord(aChkWord, xEntry, nLanguage, nIndex + 1);
+ if (xRes.is() && xRes->isAlternativeSpelling() && xRes->getHyphenationPos() == nIndex)
+ return xRes;
+ }
+ else
+ {
+ sal_Int32 nLen = pEntry->aSvcImplNames.hasElements() ? 1 : 0;
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+ Reference< XHyphenator > xHyph;
+ if (pEntry->aSvcRefs.hasElements())
+ xHyph = pEntry->aSvcRefs[0];
+
+ // try already instantiated service
+ if (i <= pEntry->nLastTriedSvcIndex)
+ {
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
+ nChkIndex, rProperties );
+ ++i;
+ }
+ else if (pEntry->nLastTriedSvcIndex < nLen - 1)
+ // instantiate services and try it
+ {
+ Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ // create specific service via it's implementation name
+ try
+ {
+ xHyph = Reference< XHyphenator >(
+ xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pEntry->aSvcImplNames[0], aArgs, xContext ), UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+ pRef [i] = xHyph;
+
+ Reference< XLinguServiceEventBroadcaster >
+ xBroadcaster( xHyph, UNO_QUERY );
+ if (xBroadcaster.is())
+ rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
+
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->queryAlternativeSpelling( aChkWord, rLocale,
+ nChkIndex, rProperties );
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+
+ // if language is not supported by the services
+ // remove it from the list.
+ if (xHyph.is() && !xHyph->hasLocale( rLocale ))
+ aSvcMap.erase( nLanguage );
+ }
+ } // if (xEntry.is())
+ }
+
+ if (bWordModified && xRes.is())
+ xRes = RebuildHyphensAndControlChars( rWord, xRes );
+
+ if (xRes.is() && xRes->getWord() != rWord)
+ {
+ xRes = new HyphenatedWord( rWord, nLanguage, xRes->getHyphenationPos(),
+ xRes->getHyphenatedWord(),
+ xRes->getHyphenPos() );
+ }
+
+ return xRes;
+}
+
+
+Reference< XPossibleHyphens > SAL_CALL
+ HyphenatorDispatcher::createPossibleHyphens(
+ const OUString& rWord, const Locale& rLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference< XPossibleHyphens > xRes;
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ if (LinguIsUnspecified(nLanguage) || rWord.isEmpty())
+ return xRes;
+
+ // search for entry with that language
+ HyphSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ if (pEntry)
+ {
+ OUString aChkWord( rWord );
+
+ // replace typographical apostroph by ascii apostroph
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
+
+ RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ RemoveControlChars( aChkWord );
+
+ // check for results from (positive) dictionaries which have precedence!
+ Reference< XDictionaryEntry > xEntry;
+
+ if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
+ {
+ xEntry = GetDicList()->queryDictionaryEntry( aChkWord, rLocale,
+ true, false );
+ }
+
+ if (xEntry.is())
+ {
+ xRes = buildPossHyphens( xEntry, nLanguage );
+ }
+ else
+ {
+ sal_Int32 nLen = pEntry->aSvcImplNames.hasElements() ? 1 : 0;
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+ Reference< XHyphenator > xHyph;
+ if (pEntry->aSvcRefs.hasElements())
+ xHyph = pEntry->aSvcRefs[0];
+
+ // try already instantiated service
+ if (i <= pEntry->nLastTriedSvcIndex)
+ {
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->createPossibleHyphens( aChkWord, rLocale,
+ rProperties );
+ ++i;
+ }
+ else if (pEntry->nLastTriedSvcIndex < nLen - 1)
+ // instantiate services and try it
+ {
+ Reference< XHyphenator > *pRef = pEntry->aSvcRefs.getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ // create specific service via it's implementation name
+ try
+ {
+ xHyph.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pEntry->aSvcImplNames[0], aArgs, xContext ),
+ UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createWithArguments failed" );
+ }
+ pRef [i] = xHyph;
+
+ Reference< XLinguServiceEventBroadcaster >
+ xBroadcaster( xHyph, UNO_QUERY );
+ if (xBroadcaster.is())
+ rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
+
+ if (xHyph.is() && xHyph->hasLocale( rLocale ))
+ xRes = xHyph->createPossibleHyphens( aChkWord, rLocale, rProperties );
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+
+ // if language is not supported by the services
+ // remove it from the list.
+ if (xHyph.is() && !xHyph->hasLocale( rLocale ))
+ aSvcMap.erase( nLanguage );
+ }
+ } // if (xEntry.is())
+ }
+
+ if (xRes.is() && xRes->getWord() != rWord)
+ {
+ xRes = new PossibleHyphens( rWord, nLanguage,
+ xRes->getPossibleHyphens(),
+ xRes->getHyphenationPositions() );
+ }
+
+ return xRes;
+}
+
+
+void HyphenatorDispatcher::SetServiceList( const Locale &rLocale,
+ const Sequence< OUString > &rSvcImplNames )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+
+ if (!rSvcImplNames.hasElements())
+ // remove entry
+ aSvcMap.erase( nLanguage );
+ else
+ {
+ // modify/add entry
+ LangSvcEntries_Hyph *pEntry = aSvcMap[ nLanguage ].get();
+ if (pEntry)
+ {
+ pEntry->Clear();
+ pEntry->aSvcImplNames = rSvcImplNames;
+ pEntry->aSvcImplNames.realloc(1);
+ pEntry->aSvcRefs = Sequence< Reference < XHyphenator > > ( 1 );
+ }
+ else
+ {
+ auto pTmpEntry = std::make_shared<LangSvcEntries_Hyph>( rSvcImplNames[0] );
+ pTmpEntry->aSvcRefs = Sequence< Reference < XHyphenator > >( 1 );
+ aSvcMap[ nLanguage ] = pTmpEntry;
+ }
+ }
+}
+
+
+Sequence< OUString >
+ HyphenatorDispatcher::GetServiceList( const Locale &rLocale ) const
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Sequence< OUString > aRes;
+
+ // search for entry with that language and use data from that
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ const HyphSvcByLangMap_t::const_iterator aIt( aSvcMap.find( nLanguage ) );
+ const LangSvcEntries_Hyph *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+ if (pEntry)
+ {
+ aRes = pEntry->aSvcImplNames;
+ if (aRes.hasElements())
+ aRes.realloc(1);
+ }
+
+ return aRes;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/hyphdsp.hxx b/linguistic/source/hyphdsp.hxx
new file mode 100644
index 000000000..a119224ca
--- /dev/null
+++ b/linguistic/source/hyphdsp.hxx
@@ -0,0 +1,131 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_HYPHDSP_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_HYPHDSP_HXX
+
+
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+#include <com/sun/star/linguistic2/XPossibleHyphens.hpp>
+#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <map>
+#include <memory>
+
+#include <linguistic/misc.hxx>
+#include "defs.hxx"
+
+class LngSvcMgr;
+
+
+class HyphenatorDispatcher :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XHyphenator
+ >,
+ public LinguDispatcher
+{
+ typedef std::shared_ptr< LangSvcEntries_Hyph > LangSvcEntries_Hyph_Ptr_t;
+ typedef std::map< LanguageType, LangSvcEntries_Hyph_Ptr_t > HyphSvcByLangMap_t;
+ HyphSvcByLangMap_t aSvcMap;
+
+ css::uno::Reference< css::linguistic2::XLinguProperties > xPropSet;
+ css::uno::Reference< css::linguistic2::XSearchableDictionaryList > xDicList;
+
+ LngSvcMgr &rMgr;
+
+ HyphenatorDispatcher(const HyphenatorDispatcher &) = delete;
+ HyphenatorDispatcher & operator = (const HyphenatorDispatcher &) = delete;
+
+ inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ GetPropSet();
+ inline const css::uno::Reference< css::linguistic2::XSearchableDictionaryList > &
+ GetDicList();
+
+ void ClearSvcList();
+
+ static css::uno::Reference< css::linguistic2::XHyphenatedWord>
+ buildHyphWord( const OUString& rOrigWord,
+ const css::uno::Reference< css::linguistic2::XDictionaryEntry> &xEntry,
+ LanguageType nLang, sal_Int16 nMaxLeading );
+
+ static css::uno::Reference< css::linguistic2::XPossibleHyphens >
+ buildPossHyphens( const css::uno::Reference< css::linguistic2::XDictionaryEntry > &xEntry,
+ LanguageType nLanguage );
+
+public:
+ explicit HyphenatorDispatcher( LngSvcMgr &rLngSvcMgr );
+ virtual ~HyphenatorDispatcher() override;
+
+ // XSupportedLocales
+ virtual css::uno::Sequence< css::lang::Locale > SAL_CALL
+ getLocales() override;
+ virtual sal_Bool SAL_CALL
+ hasLocale( const css::lang::Locale& aLocale ) override;
+
+ // XHyphenator
+ virtual css::uno::Reference< css::linguistic2::XHyphenatedWord > SAL_CALL
+ hyphenate( const OUString& aWord,
+ const css::lang::Locale& aLocale,
+ sal_Int16 nMaxLeading,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+ virtual css::uno::Reference< css::linguistic2::XHyphenatedWord > SAL_CALL
+ queryAlternativeSpelling( const OUString& aWord,
+ const css::lang::Locale& aLocale,
+ sal_Int16 nIndex,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+ virtual css::uno::Reference<
+ css::linguistic2::XPossibleHyphens > SAL_CALL
+ createPossibleHyphens(
+ const OUString& aWord,
+ const css::lang::Locale& aLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+
+ // LinguDispatcher
+ virtual void
+ SetServiceList( const css::lang::Locale &rLocale,
+ const css::uno::Sequence< OUString > &rSvcImplNames ) override;
+ virtual css::uno::Sequence< OUString >
+ GetServiceList( const css::lang::Locale &rLocale ) const override;
+};
+
+
+inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ HyphenatorDispatcher::GetPropSet()
+{
+ if (!xPropSet.is())
+ xPropSet = ::linguistic::GetLinguProperties();
+ return xPropSet;
+}
+
+
+inline const css::uno::Reference< css::linguistic2::XSearchableDictionaryList > &
+ HyphenatorDispatcher::GetDicList()
+{
+ if (!xDicList.is())
+ xDicList = ::linguistic::GetDictionaryList();
+ return xDicList;
+}
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/hyphdta.cxx b/linguistic/source/hyphdta.cxx
new file mode 100644
index 000000000..d19d88340
--- /dev/null
+++ b/linguistic/source/hyphdta.cxx
@@ -0,0 +1,164 @@
+/* -*- 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 <linguistic/hyphdta.hxx>
+#include <linguistic/misc.hxx>
+#include <osl/mutex.hxx>
+
+
+#include <tools/debug.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <utility>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+
+
+namespace linguistic
+{
+
+
+HyphenatedWord::HyphenatedWord(const OUString &rWord, LanguageType nLang, sal_Int16 nHPos,
+ const OUString &rHyphWord, sal_Int16 nPos ) :
+ aWord (rWord),
+ aHyphenatedWord (rHyphWord),
+ nHyphPos (nPos),
+ nHyphenationPos (nHPos),
+ nLanguage (nLang)
+{
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ {
+ // ignore typographical apostrophes (which got replaced in original
+ // word when being checked for hyphenation) in results.
+ OUString aTmpWord( rWord );
+ OUString aTmpHyphWord( rHyphWord );
+ aTmpWord = aTmpWord .replace( aSingleQuote[0], '\'' );
+ aTmpHyphWord = aTmpHyphWord.replace( aSingleQuote[0], '\'' );
+ bIsAltSpelling = aTmpWord != aTmpHyphWord;
+ }
+ else
+ bIsAltSpelling = rWord != rHyphWord;
+}
+
+
+HyphenatedWord::~HyphenatedWord()
+{
+}
+
+
+OUString SAL_CALL HyphenatedWord::getWord()
+{
+ return aWord;
+}
+
+
+Locale SAL_CALL HyphenatedWord::getLocale()
+{
+ return LanguageTag::convertToLocale( nLanguage );
+}
+
+
+sal_Int16 SAL_CALL HyphenatedWord::getHyphenationPos()
+{
+ return nHyphenationPos;
+}
+
+
+OUString SAL_CALL HyphenatedWord::getHyphenatedWord()
+{
+ return aHyphenatedWord;
+}
+
+
+sal_Int16 SAL_CALL HyphenatedWord::getHyphenPos()
+{
+ return nHyphPos;
+}
+
+
+sal_Bool SAL_CALL HyphenatedWord::isAlternativeSpelling()
+{
+ return bIsAltSpelling;
+}
+
+
+PossibleHyphens::PossibleHyphens(OUString aWord_, LanguageType nLang,
+ OUString aHyphWord,
+ const Sequence< sal_Int16 > &rPositions) :
+ aWord (std::move(aWord_)),
+ aWordWithHyphens(std::move(aHyphWord)),
+ aOrigHyphenPos (rPositions),
+ nLanguage (nLang)
+{
+}
+
+
+PossibleHyphens::~PossibleHyphens()
+{
+}
+
+
+OUString SAL_CALL PossibleHyphens::getWord()
+{
+ return aWord;
+}
+
+
+Locale SAL_CALL PossibleHyphens::getLocale()
+{
+ return LanguageTag::convertToLocale( nLanguage );
+}
+
+
+OUString SAL_CALL PossibleHyphens::getPossibleHyphens()
+{
+ return aWordWithHyphens;
+}
+
+
+Sequence< sal_Int16 > SAL_CALL PossibleHyphens::getHyphenationPositions()
+{
+ return aOrigHyphenPos;
+}
+
+css::uno::Reference <css::linguistic2::XHyphenatedWord> HyphenatedWord::CreateHyphenatedWord(
+ const OUString &rWord, LanguageType nLang, sal_Int16 nHyphenationPos,
+ const OUString &rHyphenatedWord, sal_Int16 nHyphenPos )
+{
+ return new HyphenatedWord( rWord, nLang, nHyphenationPos, rHyphenatedWord, nHyphenPos );
+}
+
+css::uno::Reference < css::linguistic2::XPossibleHyphens > PossibleHyphens::CreatePossibleHyphens
+ (const OUString &rWord, LanguageType nLang,
+ const OUString &rHyphWord,
+ const css::uno::Sequence< sal_Int16 > &rPositions)
+{
+ return new PossibleHyphens( rWord, nLang, rHyphWord, rPositions );
+}
+
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/iprcache.cxx b/linguistic/source/iprcache.cxx
new file mode 100644
index 000000000..2b82e5684
--- /dev/null
+++ b/linguistic/source/iprcache.cxx
@@ -0,0 +1,227 @@
+/* -*- 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 <iprcache.hxx>
+#include <linguistic/misc.hxx>
+
+#include <com/sun/star/linguistic2/DictionaryListEventFlags.hpp>
+#include <osl/mutex.hxx>
+#include <unotools/linguprops.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+
+
+namespace linguistic
+{
+
+
+#define NUM_FLUSH_PROPS 6
+
+const struct
+{
+ OUString aPropName;
+ sal_Int32 nPropHdl;
+} aFlushProperties[ NUM_FLUSH_PROPS ] =
+{
+ { UPN_IS_USE_DICTIONARY_LIST, UPH_IS_USE_DICTIONARY_LIST },
+ { UPN_IS_IGNORE_CONTROL_CHARACTERS, UPH_IS_IGNORE_CONTROL_CHARACTERS },
+ { UPN_IS_SPELL_UPPER_CASE, UPH_IS_SPELL_UPPER_CASE },
+ { UPN_IS_SPELL_WITH_DIGITS, UPH_IS_SPELL_WITH_DIGITS },
+ { UPN_IS_SPELL_CAPITALIZATION, UPH_IS_SPELL_CAPITALIZATION }
+};
+
+
+static void lcl_AddAsPropertyChangeListener(
+ const Reference< XPropertyChangeListener >& xListener,
+ Reference< XLinguProperties > const &rPropSet )
+{
+ if (xListener.is() && rPropSet.is())
+ {
+ for (auto& aFlushProperty : aFlushProperties)
+ {
+ rPropSet->addPropertyChangeListener(
+ aFlushProperty.aPropName, xListener );
+ }
+ }
+}
+
+
+static void lcl_RemoveAsPropertyChangeListener(
+ const Reference< XPropertyChangeListener >& xListener,
+ Reference< XLinguProperties > const &rPropSet )
+{
+ if (xListener.is() && rPropSet.is())
+ {
+ for (auto& aFlushProperty : aFlushProperties)
+ {
+ rPropSet->removePropertyChangeListener(
+ aFlushProperty.aPropName, xListener );
+ }
+ }
+}
+
+
+static bool lcl_IsFlushProperty( sal_Int32 nHandle )
+{
+ int i;
+ for (i = 0; i < NUM_FLUSH_PROPS; ++i)
+ {
+ if (nHandle == aFlushProperties[i].nPropHdl)
+ break;
+ }
+ return i < NUM_FLUSH_PROPS;
+}
+
+
+void FlushListener::SetDicList( Reference<XSearchableDictionaryList> const &rDL )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (xDicList != rDL)
+ {
+ if (xDicList.is())
+ xDicList->removeDictionaryListEventListener( this );
+
+ xDicList = rDL;
+ if (xDicList.is())
+ xDicList->addDictionaryListEventListener( this, false );
+ }
+}
+
+
+void FlushListener::SetPropSet( Reference< XLinguProperties > const &rPS )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (xPropSet != rPS)
+ {
+ if (xPropSet.is())
+ lcl_RemoveAsPropertyChangeListener( this, xPropSet );
+
+ xPropSet = rPS;
+ if (xPropSet.is())
+ lcl_AddAsPropertyChangeListener( this, xPropSet );
+ }
+}
+
+
+void SAL_CALL FlushListener::disposing( const EventObject& rSource )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (xDicList.is() && rSource.Source == xDicList)
+ {
+ xDicList->removeDictionaryListEventListener( this );
+ xDicList = nullptr; //! release reference
+ }
+ if (xPropSet.is() && rSource.Source == xPropSet)
+ {
+ lcl_RemoveAsPropertyChangeListener( this, xPropSet );
+ xPropSet = nullptr; //! release reference
+ }
+}
+
+
+void SAL_CALL FlushListener::processDictionaryListEvent(
+ const DictionaryListEvent& rDicListEvent )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (rDicListEvent.Source != xDicList)
+ return;
+
+ sal_Int16 nEvt = rDicListEvent.nCondensedEvent;
+ sal_Int16 const nFlushFlags =
+ DictionaryListEventFlags::ADD_NEG_ENTRY |
+ DictionaryListEventFlags::DEL_POS_ENTRY |
+ DictionaryListEventFlags::ACTIVATE_NEG_DIC |
+ DictionaryListEventFlags::DEACTIVATE_POS_DIC;
+ bool bFlush = 0 != (nEvt & nFlushFlags);
+
+ if (bFlush)
+ mrSpellCache.Flush();
+}
+
+
+void SAL_CALL FlushListener::propertyChange(
+ const PropertyChangeEvent& rEvt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (rEvt.Source == xPropSet)
+ {
+ bool bFlush = lcl_IsFlushProperty( rEvt.PropertyHandle );
+
+ if (bFlush)
+ mrSpellCache.Flush();
+ }
+}
+
+
+SpellCache::SpellCache()
+{
+ mxFlushLstnr = new FlushListener( *this );
+ Reference<XSearchableDictionaryList> aDictionaryList(GetDictionaryList());
+ mxFlushLstnr->SetDicList( aDictionaryList ); //! after reference is established
+ Reference<XLinguProperties> aPropertySet(GetLinguProperties());
+ mxFlushLstnr->SetPropSet( aPropertySet ); //! after reference is established
+}
+
+SpellCache::~SpellCache()
+{
+ Reference<XSearchableDictionaryList> aEmptyList;
+ Reference<XLinguProperties> aEmptySet;
+ mxFlushLstnr->SetDicList( aEmptyList );
+ mxFlushLstnr->SetPropSet( aEmptySet );
+}
+
+void SpellCache::Flush()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ // clear word list
+ LangWordList_t().swap(aWordLists);
+}
+
+bool SpellCache::CheckWord( const OUString& rWord, LanguageType nLang )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ WordList_t &rList = aWordLists[ nLang ];
+ const WordList_t::const_iterator aIt = rList.find( rWord );
+ return aIt != rList.end();
+}
+
+void SpellCache::AddWord( const OUString& rWord, LanguageType nLang )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ WordList_t & rList = aWordLists[ nLang ];
+ // occasional clean-up...
+ if (rList.size() > 500)
+ rList.clear();
+ rList.insert( rWord );
+}
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/lng.component b/linguistic/source/lng.component
new file mode 100644
index 000000000..82622e2f0
--- /dev/null
+++ b/linguistic/source/lng.component
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.lingu2.ConvDicList"
+ constructor="linguistic_ConvDicList_get_implementation">
+ <service name="com.sun.star.linguistic2.ConversionDictionaryList"/>
+ </implementation>
+ <implementation name="com.sun.star.lingu2.DicList"
+ constructor="linguistic_DicList_get_implementation" single-instance="true">
+ <service name="com.sun.star.linguistic2.DictionaryList"/>
+ </implementation>
+ <implementation name="com.sun.star.lingu2.LinguProps"
+ constructor="linguistic_LinguProps_get_implementation" single-instance="true">
+ <service name="com.sun.star.linguistic2.LinguProperties"/>
+ </implementation>
+ <implementation name="com.sun.star.lingu2.LngSvcMgr"
+ constructor="linguistic_LngSvcMgr_get_implementation" single-instance="true">
+ <service name="com.sun.star.linguistic2.LinguServiceManager"/>
+ </implementation>
+ <implementation name="com.sun.star.lingu2.ProofreadingIterator"
+ constructor="linguistic_GrammarCheckingIterator_get_implementation" single-instance="true">
+ <service name="com.sun.star.linguistic2.ProofreadingIterator"/>
+ </implementation>
+</component>
diff --git a/linguistic/source/lngopt.cxx b/linguistic/source/lngopt.cxx
new file mode 100644
index 000000000..1a48789ae
--- /dev/null
+++ b/linguistic/source/lngopt.cxx
@@ -0,0 +1,424 @@
+/* -*- 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 <sal/macros.h>
+#include "lngopt.hxx"
+#include <linguistic/misc.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/debug.hxx>
+#include <unotools/lingucfg.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <com/sun/star/lang/Locale.hpp>
+
+using namespace utl;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+using namespace com::sun::star::registry;
+
+
+// static member initialization
+SvtLinguOptions * LinguOptions::pData = nullptr;
+oslInterlockedCount LinguOptions::nRefCount;
+
+
+LinguOptions::LinguOptions()
+{
+ if (!pData)
+ {
+ pData = new SvtLinguOptions;
+ SvtLinguConfig aLinguCfg;
+ aLinguCfg.GetOptions( *pData );
+ }
+
+ osl_atomic_increment( &nRefCount );
+}
+
+
+LinguOptions::LinguOptions(const LinguOptions & /*rOpt*/)
+{
+ DBG_ASSERT( pData, "lng : data missing" );
+ osl_atomic_increment( &nRefCount );
+}
+
+
+LinguOptions::~LinguOptions()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if ( osl_atomic_decrement( &nRefCount ) == 0 )
+ {
+ delete pData; pData = nullptr;
+ }
+}
+
+namespace {
+
+struct WID_Name
+{
+ sal_Int32 nWID;
+ OUString aPropertyName;
+};
+
+}
+
+//! order of entries is import (see LinguOptions::GetName)
+//! since the WID is used as index in this table!
+WID_Name const aWID_Name[] =
+{
+ { 0, "" },
+ { UPH_IS_USE_DICTIONARY_LIST, UPN_IS_USE_DICTIONARY_LIST },
+ { UPH_IS_IGNORE_CONTROL_CHARACTERS, UPN_IS_IGNORE_CONTROL_CHARACTERS },
+ { UPH_IS_SPELL_UPPER_CASE, UPN_IS_SPELL_UPPER_CASE },
+ { UPH_IS_SPELL_WITH_DIGITS, UPN_IS_SPELL_WITH_DIGITS },
+ { UPH_IS_SPELL_CAPITALIZATION, UPN_IS_SPELL_CAPITALIZATION },
+ { UPH_HYPH_MIN_LEADING, UPN_HYPH_MIN_LEADING },
+ { UPH_HYPH_MIN_TRAILING, UPN_HYPH_MIN_TRAILING },
+ { UPH_HYPH_MIN_WORD_LENGTH, UPN_HYPH_MIN_WORD_LENGTH },
+ { UPH_DEFAULT_LOCALE, UPN_DEFAULT_LOCALE },
+ { UPH_IS_SPELL_AUTO, UPN_IS_SPELL_AUTO },
+ { 0, "" },
+ { 0, "" },
+ { UPH_IS_SPELL_SPECIAL, UPN_IS_SPELL_SPECIAL },
+ { UPH_IS_HYPH_AUTO, UPN_IS_HYPH_AUTO },
+ { UPH_IS_HYPH_SPECIAL, UPN_IS_HYPH_SPECIAL },
+ { UPH_IS_WRAP_REVERSE, UPN_IS_WRAP_REVERSE },
+ { 0, "" },
+ { 0, "" },
+ { 0, "" },
+ { 0, "" },
+ { UPH_DEFAULT_LANGUAGE, UPN_DEFAULT_LANGUAGE },
+ { UPH_DEFAULT_LOCALE_CJK, UPN_DEFAULT_LOCALE_CJK },
+ { UPH_DEFAULT_LOCALE_CTL, UPN_DEFAULT_LOCALE_CTL }
+};
+
+
+OUString LinguOptions::GetName( sal_Int32 nWID )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ OUString aRes;
+
+ if (0 <= nWID && o3tl::make_unsigned(nWID) < SAL_N_ELEMENTS(aWID_Name)
+ && aWID_Name[ nWID ].nWID == nWID)
+ aRes = aWID_Name[nWID].aPropertyName;
+ else
+ OSL_FAIL("lng : unknown WID");
+
+ return aRes;
+}
+
+
+//! map must be sorted by first entry in alphabetical increasing order.
+static const SfxItemPropertyMapEntry* lcl_GetLinguProps()
+{
+ static const SfxItemPropertyMapEntry aLinguProps[] =
+ {
+ { UPN_DEFAULT_LANGUAGE, UPH_DEFAULT_LANGUAGE,
+ ::cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { UPN_DEFAULT_LOCALE, UPH_DEFAULT_LOCALE,
+ ::cppu::UnoType<Locale>::get(), 0, 0 },
+ { UPN_DEFAULT_LOCALE_CJK, UPH_DEFAULT_LOCALE_CJK,
+ ::cppu::UnoType<Locale>::get(), 0, 0 },
+ { UPN_DEFAULT_LOCALE_CTL, UPH_DEFAULT_LOCALE_CTL,
+ ::cppu::UnoType<Locale>::get(), 0, 0 },
+ { UPN_HYPH_MIN_LEADING, UPH_HYPH_MIN_LEADING,
+ ::cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { UPN_HYPH_MIN_TRAILING, UPH_HYPH_MIN_TRAILING,
+ ::cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { UPN_HYPH_MIN_WORD_LENGTH, UPH_HYPH_MIN_WORD_LENGTH,
+ ::cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { UPN_IS_GERMAN_PRE_REFORM, UPH_IS_GERMAN_PRE_REFORM, /*! deprecated !*/
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_HYPH_AUTO, UPH_IS_HYPH_AUTO,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_HYPH_SPECIAL, UPH_IS_HYPH_SPECIAL,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_IGNORE_CONTROL_CHARACTERS, UPH_IS_IGNORE_CONTROL_CHARACTERS,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_AUTO, UPH_IS_SPELL_AUTO,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_CAPITALIZATION, UPH_IS_SPELL_CAPITALIZATION,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_HIDE, UPH_IS_SPELL_HIDE, /*! deprecated !*/
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_IN_ALL_LANGUAGES, UPH_IS_SPELL_IN_ALL_LANGUAGES, /*! deprecated !*/
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_SPECIAL, UPH_IS_SPELL_SPECIAL,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_UPPER_CASE, UPH_IS_SPELL_UPPER_CASE,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_SPELL_WITH_DIGITS, UPH_IS_SPELL_WITH_DIGITS,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_USE_DICTIONARY_LIST, UPH_IS_USE_DICTIONARY_LIST,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { UPN_IS_WRAP_REVERSE, UPH_IS_WRAP_REVERSE,
+ cppu::UnoType<bool>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ return aLinguProps;
+}
+LinguProps::LinguProps() :
+ aEvtListeners (GetLinguMutex()),
+ aPropListeners (GetLinguMutex()),
+ aPropertyMap(lcl_GetLinguProps())
+{
+ bDisposing = false;
+}
+
+void LinguProps::launchEvent( const PropertyChangeEvent &rEvt ) const
+{
+ comphelper::OInterfaceContainerHelper3<XPropertyChangeListener> *pContainer =
+ aPropListeners.getContainer( rEvt.PropertyHandle );
+ if (pContainer)
+ pContainer->notifyEach( &XPropertyChangeListener::propertyChange, rEvt );
+}
+
+Reference< XPropertySetInfo > SAL_CALL LinguProps::getPropertySetInfo()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ static Reference< XPropertySetInfo > aRef =
+ new SfxItemPropertySetInfo( aPropertyMap );
+ return aRef;
+}
+
+void SAL_CALL LinguProps::setPropertyValue(
+ const OUString& rPropertyName, const Any& rValue )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ const SfxItemPropertyMapEntry* pCur = aPropertyMap.getByName( rPropertyName );
+ if (pCur)
+ {
+ Any aOld( aConfig.GetProperty( pCur->nWID ) );
+ if (aOld != rValue && aConfig.SetProperty( pCur->nWID, rValue ))
+ {
+ PropertyChangeEvent aChgEvt( static_cast<XPropertySet *>(this), rPropertyName,
+ false, pCur->nWID, aOld, rValue );
+ launchEvent( aChgEvt );
+ }
+ }
+}
+
+Any SAL_CALL LinguProps::getPropertyValue( const OUString& rPropertyName )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Any aRet;
+
+ const SfxItemPropertyMapEntry* pCur = aPropertyMap.getByName( rPropertyName );
+ if(pCur)
+ {
+ aRet = aConfig.GetProperty( pCur->nWID );
+ }
+
+ return aRet;
+}
+
+void SAL_CALL LinguProps::addPropertyChangeListener(
+ const OUString& rPropertyName,
+ const Reference< XPropertyChangeListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ {
+ const SfxItemPropertyMapEntry* pCur = aPropertyMap.getByName( rPropertyName );
+ if(pCur)
+ aPropListeners.addInterface( pCur->nWID, rxListener );
+ }
+}
+
+void SAL_CALL LinguProps::removePropertyChangeListener(
+ const OUString& rPropertyName,
+ const Reference< XPropertyChangeListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ {
+ const SfxItemPropertyMapEntry* pCur = aPropertyMap.getByName( rPropertyName );
+ if(pCur)
+ aPropListeners.removeInterface( pCur->nWID, rxListener );
+ }
+}
+
+void SAL_CALL LinguProps::addVetoableChangeListener(
+ const OUString& /*rPropertyName*/,
+ const Reference< XVetoableChangeListener >& /*xListener*/ )
+{
+}
+
+void SAL_CALL LinguProps::removeVetoableChangeListener(
+ const OUString& /*rPropertyName*/,
+ const Reference< XVetoableChangeListener >& /*xListener*/ )
+{
+}
+
+
+void SAL_CALL LinguProps::setFastPropertyValue( sal_Int32 nHandle, const Any& rValue )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Any aOld( aConfig.GetProperty( nHandle ) );
+ if (aOld != rValue && aConfig.SetProperty( nHandle, rValue ))
+ {
+ PropertyChangeEvent aChgEvt( static_cast<XPropertySet *>(this),
+ LinguOptions::GetName( nHandle ), false, nHandle, aOld, rValue );
+ launchEvent( aChgEvt );
+ }
+}
+
+
+Any SAL_CALL LinguProps::getFastPropertyValue( sal_Int32 nHandle )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Any aRes( aConfig.GetProperty( nHandle ) );
+ return aRes;
+}
+
+
+Sequence< PropertyValue > SAL_CALL
+ LinguProps::getPropertyValues()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector<PropertyValue> aProps;
+ aProps.reserve(aPropertyMap.getPropertyEntries().size());
+ for(auto pEntry : aPropertyMap.getPropertyEntries())
+ aProps.push_back(PropertyValue(pEntry->aName, pEntry->nWID,
+ aConfig.GetProperty(pEntry->nWID),
+ css::beans::PropertyState_DIRECT_VALUE));
+ return comphelper::containerToSequence(aProps);
+}
+
+void SAL_CALL
+ LinguProps::setPropertyValues( const Sequence< PropertyValue >& rProps )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ for (const PropertyValue &rVal : rProps)
+ {
+ setPropertyValue( rVal.Name, rVal.Value );
+ }
+}
+
+void SAL_CALL
+ LinguProps::dispose()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing)
+ {
+ bDisposing = true;
+
+ //! it's too late to save the options here!
+ // (see AppExitListener for saving)
+ //aOpt.Save(); // save (possible) changes before exiting
+
+ EventObject aEvtObj( static_cast<XPropertySet *>(this) );
+ aEvtListeners.disposeAndClear( aEvtObj );
+ aPropListeners.disposeAndClear( aEvtObj );
+ }
+}
+
+void SAL_CALL
+ LinguProps::addEventListener( const Reference< XEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.addInterface( rxListener );
+}
+
+void SAL_CALL
+ LinguProps::removeEventListener( const Reference< XEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && rxListener.is())
+ aEvtListeners.removeInterface( rxListener );
+}
+
+
+// Service specific part
+
+// XServiceInfo
+OUString SAL_CALL LinguProps::getImplementationName()
+{
+ return "com.sun.star.lingu2.LinguProps";
+}
+
+// XServiceInfo
+sal_Bool SAL_CALL LinguProps::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+// XServiceInfo
+uno::Sequence< OUString > SAL_CALL LinguProps::getSupportedServiceNames()
+{
+ return { "com.sun.star.linguistic2.LinguProperties" };
+}
+
+bool LinguProps::getPropertyBool(const OUString& aPropertyName)
+{
+ uno::Any any = getPropertyValue(aPropertyName);
+ bool b = false;
+ any >>= b;
+ return b;
+}
+
+sal_Int16 LinguProps::getPropertyInt16(const OUString& aPropertyName)
+{
+ uno::Any any = getPropertyValue(aPropertyName);
+ sal_Int16 b = 0;
+ any >>= b;
+ return b;
+}
+
+Locale LinguProps::getPropertyLocale(const OUString& aPropertyName)
+{
+ uno::Any any = getPropertyValue(aPropertyName);
+ css::lang::Locale b;
+ any >>= b;
+ return b;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+linguistic_LinguProps_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new LinguProps());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/lngopt.hxx b/linguistic/source/lngopt.hxx
new file mode 100644
index 000000000..896e345ed
--- /dev/null
+++ b/linguistic/source/lngopt.hxx
@@ -0,0 +1,202 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_LNGOPT_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_LNGOPT_HXX
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <comphelper/multiinterfacecontainer3.hxx>
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/linguistic2/XLinguProperties.hpp>
+#include <unotools/lingucfg.hxx>
+#include <svl/itemprop.hxx>
+#include <unotools/linguprops.hxx>
+#include <com/sun/star/uno/Any.h>
+
+namespace com::sun::star {
+ namespace beans {
+ struct PropertyChangeEvent;
+ }
+}
+
+
+// LinguOptions
+// This class represents all Linguistik relevant options.
+
+class LinguOptions
+{
+ static SvtLinguOptions *pData;
+ static oslInterlockedCount nRefCount; // number of objects of this class
+
+public:
+ LinguOptions();
+ LinguOptions(const LinguOptions &rOpt);
+ ~LinguOptions();
+
+ static OUString GetName( sal_Int32 nWID );
+
+ const css::uno::Sequence< OUString >&
+ GetActiveDics() const { return pData->aActiveDics; }
+};
+
+typedef comphelper::OMultiTypeInterfaceContainerHelperVar3<css::beans::XPropertyChangeListener, sal_Int32>
+ OPropertyListenerContainerHelper;
+
+class LinguProps :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XLinguProperties,
+ css::beans::XFastPropertySet,
+ css::beans::XPropertyAccess,
+ css::lang::XComponent,
+ css::lang::XServiceInfo
+ >
+{
+ ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener> aEvtListeners;
+ OPropertyListenerContainerHelper aPropListeners;
+
+ SfxItemPropertyMap aPropertyMap;
+ SvtLinguConfig aConfig;
+
+ bool bDisposing;
+
+ LinguProps(const LinguProps &) = delete;
+ LinguProps & operator = (const LinguProps &) = delete;
+
+ void launchEvent( const css::beans::PropertyChangeEvent &rEvt ) const;
+
+ /// @throws css::uno::RuntimeException
+ bool getPropertyBool(const OUString& aPropertyName);
+ /// @throws css::uno::RuntimeException
+ sal_Int16 getPropertyInt16(const OUString& aPropertyName);
+ /// @throws css::uno::RuntimeException
+ css::lang::Locale getPropertyLocale(const OUString& aPropertyName);
+ /// @throws css::uno::RuntimeException
+ void setProperty(const OUString& aPropertyName, bool p1)
+ { setPropertyValue( aPropertyName, css::uno::Any(p1) ); }
+ /// @throws css::uno::RuntimeException
+ void setProperty(const OUString& aPropertyName, sal_Int16 p1)
+ { setPropertyValue( aPropertyName, css::uno::Any(p1) ); }
+ /// @throws css::uno::RuntimeException
+ void setProperty(const OUString& aPropertyName, css::lang::Locale p1)
+ { setPropertyValue( aPropertyName, css::uno::Any(p1) ); }
+
+public:
+ LinguProps();
+
+ virtual sal_Bool SAL_CALL getIsUseDictionaryList() override
+ { return getPropertyBool(UPN_IS_USE_DICTIONARY_LIST); }
+ virtual void SAL_CALL setIsUseDictionaryList(sal_Bool p1) override
+ { setProperty(UPN_IS_USE_DICTIONARY_LIST, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsIgnoreControlCharacters() override
+ { return getPropertyBool(UPN_IS_IGNORE_CONTROL_CHARACTERS); }
+ virtual void SAL_CALL setIsIgnoreControlCharacters(sal_Bool p1) override
+ { setProperty(UPN_IS_IGNORE_CONTROL_CHARACTERS, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsSpellUpperCase() override
+ { return getPropertyBool(UPN_IS_SPELL_UPPER_CASE); }
+ virtual void SAL_CALL setIsSpellUpperCase(sal_Bool p1) override
+ { setProperty(UPN_IS_SPELL_UPPER_CASE, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsSpellWithDigits() override
+ { return getPropertyBool(UPN_IS_SPELL_WITH_DIGITS); }
+ virtual void SAL_CALL setIsSpellWithDigits(sal_Bool p1) override
+ { setProperty(UPN_IS_SPELL_WITH_DIGITS, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsSpellCapitalization() override
+ { return getPropertyBool(UPN_IS_SPELL_CAPITALIZATION); }
+ virtual void SAL_CALL setIsSpellCapitalization(sal_Bool p1) override
+ { setProperty(UPN_IS_SPELL_CAPITALIZATION, static_cast<bool>(p1)); }
+ virtual sal_Int16 SAL_CALL getHyphMinLeading() override
+ { return getPropertyInt16(UPN_HYPH_MIN_LEADING); }
+ virtual void SAL_CALL setHyphMinLeading(sal_Int16 p1) override
+ { setProperty(UPN_HYPH_MIN_LEADING, p1); }
+ virtual sal_Int16 SAL_CALL getHyphMinTrailing() override
+ { return getPropertyInt16(UPN_HYPH_MIN_TRAILING); }
+ virtual void SAL_CALL setHyphMinTrailing(sal_Int16 p1) override
+ { setProperty(UPN_HYPH_MIN_TRAILING, p1); }
+ virtual sal_Int16 SAL_CALL getHyphMinWordLength() override
+ { return getPropertyInt16(UPN_HYPH_MIN_WORD_LENGTH); }
+ virtual void SAL_CALL setHyphMinWordLength(sal_Int16 p1) override
+ { setProperty(UPN_HYPH_MIN_WORD_LENGTH, p1); }
+ virtual css::lang::Locale SAL_CALL getDefaultLocale() override
+ { return getPropertyLocale(UPN_DEFAULT_LOCALE); }
+ virtual void SAL_CALL setDefaultLocale(const css::lang::Locale& p1) override
+ { setProperty(UPN_DEFAULT_LOCALE, p1); }
+ virtual sal_Bool SAL_CALL getIsHyphAuto() override
+ { return getPropertyBool(UPN_IS_HYPH_AUTO); }
+ virtual void SAL_CALL setIsHyphAuto(sal_Bool p1) override
+ { setProperty(UPN_IS_HYPH_AUTO, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsHyphSpecial() override
+ { return getPropertyBool(UPN_IS_HYPH_SPECIAL); }
+ virtual void SAL_CALL setIsHyphSpecial(sal_Bool p1) override
+ { setProperty(UPN_IS_HYPH_SPECIAL, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsSpellAuto() override
+ { return getPropertyBool(UPN_IS_SPELL_AUTO); }
+ virtual void SAL_CALL setIsSpellAuto(sal_Bool p1) override
+ { setProperty(UPN_IS_SPELL_AUTO, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsSpellSpecial() override
+ { return getPropertyBool(UPN_IS_SPELL_SPECIAL); }
+ virtual void SAL_CALL setIsSpellSpecial(sal_Bool p1) override
+ { setProperty(UPN_IS_SPELL_SPECIAL, static_cast<bool>(p1)); }
+ virtual sal_Bool SAL_CALL getIsWrapReverse() override
+ { return getPropertyBool(UPN_IS_WRAP_REVERSE); }
+ virtual void SAL_CALL setIsWrapReverse(sal_Bool p1) override
+ { setProperty(UPN_IS_WRAP_REVERSE, static_cast<bool>(p1)); }
+ virtual css::lang::Locale SAL_CALL getDefaultLocale_CJK() override
+ { return getPropertyLocale(UPN_DEFAULT_LOCALE_CJK); }
+ virtual void SAL_CALL setDefaultLocale_CJK(const css::lang::Locale& p1) override
+ { setProperty(UPN_DEFAULT_LOCALE_CJK, p1); }
+ virtual css::lang::Locale SAL_CALL getDefaultLocale_CTL() override
+ { return getPropertyLocale(UPN_DEFAULT_LOCALE_CTL); }
+ virtual void SAL_CALL setDefaultLocale_CTL(const css::lang::Locale& p1) override
+ { setProperty(UPN_DEFAULT_LOCALE_CTL, p1); }
+
+ // XPropertySet
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+ virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override;
+ virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& rxListener ) override;
+ virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& rxListener ) override;
+ virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& rxListener ) override;
+ virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& rxListener ) override;
+
+ // XFastPropertySet
+ virtual void SAL_CALL setFastPropertyValue( sal_Int32 nHandle, const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getFastPropertyValue( sal_Int32 nHandle ) override;
+
+ // XPropertyAccess
+ virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getPropertyValues() override;
+ virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& aProps ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& rxListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& rxListener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/lngprophelp.cxx b/linguistic/source/lngprophelp.cxx
new file mode 100644
index 000000000..a14c96c95
--- /dev/null
+++ b/linguistic/source/lngprophelp.cxx
@@ -0,0 +1,750 @@
+/* -*- 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 <tools/debug.hxx>
+#include <sal/log.hxx>
+
+#include <com/sun/star/linguistic2/LinguServiceEvent.hpp>
+#include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
+#include <com/sun/star/linguistic2/XLinguServiceEventListener.hpp>
+#include <com/sun/star/linguistic2/XLinguProperties.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <osl/mutex.hxx>
+#include <unotools/linguprops.hxx>
+
+#include <linguistic/misc.hxx>
+#include <linguistic/lngprops.hxx>
+
+#include <linguistic/lngprophelp.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+namespace linguistic
+{
+
+
+PropertyChgHelper::PropertyChgHelper(
+ const Reference< XInterface > &rxSource,
+ Reference< XLinguProperties > const &rxPropSet,
+ int nAllowedEvents ) :
+ aPropNames ({UPN_IS_IGNORE_CONTROL_CHARACTERS, UPN_IS_USE_DICTIONARY_LIST}),
+ xMyEvtObj (rxSource),
+ aLngSvcEvtListeners (GetLinguMutex()),
+ xPropSet (rxPropSet),
+ nEvtFlags (nAllowedEvents)
+{
+ SetDefaultValues();
+}
+
+PropertyChgHelper::~PropertyChgHelper()
+{
+}
+
+void PropertyChgHelper::SetDefaultValues()
+{
+ bResIsIgnoreControlCharacters = bIsIgnoreControlCharacters = true;
+ bResIsUseDictionaryList = bIsUseDictionaryList = true;
+}
+
+
+void PropertyChgHelper::GetCurrentValues()
+{
+ const auto& rPropNames = GetPropNames();
+ if (!GetPropSet().is() || rPropNames.empty())
+ return;
+
+ for (const OUString& rPropName : rPropNames)
+ {
+ bool *pbVal = nullptr,
+ *pbResVal = nullptr;
+
+ if ( rPropName == UPN_IS_IGNORE_CONTROL_CHARACTERS )
+ {
+ pbVal = &bIsIgnoreControlCharacters;
+ pbResVal = &bResIsIgnoreControlCharacters;
+ }
+ else if ( rPropName == UPN_IS_USE_DICTIONARY_LIST )
+ {
+ pbVal = &bIsUseDictionaryList;
+ pbResVal = &bResIsUseDictionaryList;
+ }
+
+ if (pbVal && pbResVal)
+ {
+ GetPropSet()->getPropertyValue( rPropName ) >>= *pbVal;
+ *pbResVal = *pbVal;
+ }
+ }
+}
+
+
+void PropertyChgHelper::SetTmpPropVals( const PropertyValues &rPropVals )
+{
+ // return value is default value unless there is an explicitly supplied
+ // temporary value
+ bResIsIgnoreControlCharacters = bIsIgnoreControlCharacters;
+ bResIsUseDictionaryList = bIsUseDictionaryList;
+
+ for (const PropertyValue& rVal : rPropVals)
+ {
+ bool *pbResVal = nullptr;
+ switch (rVal.Handle)
+ {
+ case UPH_IS_IGNORE_CONTROL_CHARACTERS :
+ pbResVal = &bResIsIgnoreControlCharacters; break;
+ case UPH_IS_USE_DICTIONARY_LIST :
+ pbResVal = &bResIsUseDictionaryList; break;
+ default:
+ ;
+ }
+ if (pbResVal)
+ rVal.Value >>= *pbResVal;
+ }
+}
+
+
+bool PropertyChgHelper::propertyChange_Impl( const PropertyChangeEvent& rEvt )
+{
+ bool bRes = false;
+
+ if (GetPropSet().is() && rEvt.Source == GetPropSet())
+ {
+ sal_Int16 nLngSvcFlags = (nEvtFlags & AE_HYPHENATOR) ?
+ LinguServiceEventFlags::HYPHENATE_AGAIN : 0;
+ bool bSCWA = false, // SPELL_CORRECT_WORDS_AGAIN ?
+ bSWWA = false; // SPELL_WRONG_WORDS_AGAIN ?
+
+ bool *pbVal = nullptr;
+ switch (rEvt.PropertyHandle)
+ {
+ case UPH_IS_IGNORE_CONTROL_CHARACTERS :
+ {
+ pbVal = &bIsIgnoreControlCharacters;
+ nLngSvcFlags = 0;
+ break;
+ }
+ case UPH_IS_USE_DICTIONARY_LIST :
+ {
+ pbVal = &bIsUseDictionaryList;
+ bSCWA = bSWWA = true;
+ break;
+ }
+ }
+ if (pbVal)
+ rEvt.NewValue >>= *pbVal;
+
+ bRes = nullptr != pbVal; // sth changed?
+ if (bRes)
+ {
+ bool bSpellEvts = (nEvtFlags & AE_SPELLCHECKER);
+ if (bSCWA && bSpellEvts)
+ nLngSvcFlags |= LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN;
+ if (bSWWA && bSpellEvts)
+ nLngSvcFlags |= LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN;
+ if (nLngSvcFlags)
+ {
+ LinguServiceEvent aEvt( GetEvtObj(), nLngSvcFlags );
+ LaunchEvent( aEvt );
+ }
+ }
+ }
+
+ return bRes;
+}
+
+
+void SAL_CALL
+ PropertyChgHelper::propertyChange( const PropertyChangeEvent& rEvt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ propertyChange_Impl( rEvt );
+}
+
+
+void PropertyChgHelper::AddAsPropListener()
+{
+ if (xPropSet.is())
+ {
+ for (const OUString& rPropName : std::as_const(aPropNames))
+ {
+ if (!rPropName.isEmpty())
+ xPropSet->addPropertyChangeListener( rPropName, this );
+ }
+ }
+}
+
+void PropertyChgHelper::RemoveAsPropListener()
+{
+ if (xPropSet.is())
+ {
+ for (const OUString& rPropName : std::as_const(aPropNames))
+ {
+ if (!rPropName.isEmpty())
+ xPropSet->removePropertyChangeListener( rPropName, this );
+ }
+ }
+}
+
+
+void PropertyChgHelper::LaunchEvent( const LinguServiceEvent &rEvt )
+{
+ aLngSvcEvtListeners.notifyEach( &XLinguServiceEventListener::processLinguServiceEvent, rEvt );
+}
+
+
+void SAL_CALL PropertyChgHelper::disposing( const EventObject& rSource )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ if (rSource.Source == xPropSet)
+ {
+ RemoveAsPropListener();
+ xPropSet = nullptr;
+ aPropNames.clear();
+ }
+}
+
+
+sal_Bool SAL_CALL
+ PropertyChgHelper::addLinguServiceEventListener(
+ const Reference< XLinguServiceEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+ if (rxListener.is())
+ {
+ sal_Int32 nCount = aLngSvcEvtListeners.getLength();
+ bRes = aLngSvcEvtListeners.addInterface( rxListener ) != nCount;
+ }
+ return bRes;
+}
+
+
+sal_Bool SAL_CALL
+ PropertyChgHelper::removeLinguServiceEventListener(
+ const Reference< XLinguServiceEventListener >& rxListener )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = false;
+ if (rxListener.is())
+ {
+ sal_Int32 nCount = aLngSvcEvtListeners.getLength();
+ bRes = aLngSvcEvtListeners.removeInterface( rxListener ) != nCount;
+ }
+ return bRes;
+}
+
+
+PropertyHelper_Thes::PropertyHelper_Thes(
+ const Reference< XInterface > &rxSource,
+ Reference< XLinguProperties > const &rxPropSet ) :
+ PropertyChgHelper ( rxSource, rxPropSet, 0 )
+{
+ SetDefaultValues();
+ GetCurrentValues();
+}
+
+
+PropertyHelper_Thes::~PropertyHelper_Thes()
+{
+}
+
+
+void SAL_CALL
+ PropertyHelper_Thes::propertyChange( const PropertyChangeEvent& rEvt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ PropertyChgHelper::propertyChange_Impl( rEvt );
+}
+
+
+PropertyHelper_Spell::PropertyHelper_Spell(
+ const Reference< XInterface > & rxSource,
+ Reference< XLinguProperties > const &rxPropSet ) :
+ PropertyChgHelper ( rxSource, rxPropSet, AE_SPELLCHECKER )
+{
+ auto& rPropNames = GetPropNames();
+ rPropNames.push_back(UPN_IS_SPELL_UPPER_CASE);
+ rPropNames.push_back(UPN_IS_SPELL_WITH_DIGITS);
+ rPropNames.push_back(UPN_IS_SPELL_CAPITALIZATION);
+ SetDefaultValues();
+ GetCurrentValues();
+}
+
+
+PropertyHelper_Spell::~PropertyHelper_Spell()
+{
+}
+
+
+void PropertyHelper_Spell::SetDefaultValues()
+{
+ PropertyChgHelper::SetDefaultValues();
+
+ bResIsSpellUpperCase = bIsSpellUpperCase = false;
+ bResIsSpellWithDigits = bIsSpellWithDigits = false;
+ bResIsSpellCapitalization = bIsSpellCapitalization = true;
+}
+
+
+void PropertyHelper_Spell::GetCurrentValues()
+{
+ PropertyChgHelper::GetCurrentValues();
+
+ const auto& rPropNames = GetPropNames();
+ if (!GetPropSet().is() || rPropNames.empty())
+ return;
+
+ for (const OUString& rPropName : rPropNames)
+ {
+ bool *pbVal = nullptr,
+ *pbResVal = nullptr;
+
+ if ( rPropName == UPN_IS_SPELL_UPPER_CASE )
+ {
+ pbVal = &bIsSpellUpperCase;
+ pbResVal = &bResIsSpellUpperCase;
+ }
+ else if ( rPropName == UPN_IS_SPELL_WITH_DIGITS )
+ {
+ pbVal = &bIsSpellWithDigits;
+ pbResVal = &bResIsSpellWithDigits;
+ }
+ else if ( rPropName == UPN_IS_SPELL_CAPITALIZATION )
+ {
+ pbVal = &bIsSpellCapitalization;
+ pbResVal = &bResIsSpellCapitalization;
+ }
+
+ if (pbVal && pbResVal)
+ {
+ GetPropSet()->getPropertyValue( rPropName ) >>= *pbVal;
+ *pbResVal = *pbVal;
+ }
+ }
+}
+
+
+bool PropertyHelper_Spell::propertyChange_Impl( const PropertyChangeEvent& rEvt )
+{
+ bool bRes = PropertyChgHelper::propertyChange_Impl( rEvt );
+
+ if (!bRes && GetPropSet().is() && rEvt.Source == GetPropSet())
+ {
+ bool bSCWA = false, // SPELL_CORRECT_WORDS_AGAIN ?
+ bSWWA = false; // SPELL_WRONG_WORDS_AGAIN ?
+
+ bool *pbVal = nullptr;
+ switch (rEvt.PropertyHandle)
+ {
+ case UPH_IS_SPELL_UPPER_CASE :
+ {
+ pbVal = &bIsSpellUpperCase;
+ bSCWA = ! *pbVal; // sal_False->sal_True change?
+ bSWWA = !bSCWA; // sal_True->sal_False change?
+ break;
+ }
+ case UPH_IS_SPELL_WITH_DIGITS :
+ {
+ pbVal = &bIsSpellWithDigits;
+ bSCWA = ! *pbVal; // sal_False->sal_True change?
+ bSWWA = !bSCWA; // sal_True->sal_False change?
+ break;
+ }
+ case UPH_IS_SPELL_CAPITALIZATION :
+ {
+ pbVal = &bIsSpellCapitalization;
+ bSCWA = ! *pbVal; // sal_False->sal_True change?
+ bSWWA = !bSCWA; // sal_True->sal_False change?
+ break;
+ }
+ default:
+ SAL_WARN( "linguistic", "unknown property" );
+ }
+ if (pbVal)
+ rEvt.NewValue >>= *pbVal;
+
+ bRes = (pbVal != nullptr);
+ if (bRes)
+ {
+ sal_Int16 nLngSvcFlags = 0;
+ if (bSCWA)
+ nLngSvcFlags |= LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN;
+ if (bSWWA)
+ nLngSvcFlags |= LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN;
+ if (nLngSvcFlags)
+ {
+ LinguServiceEvent aEvt( GetEvtObj(), nLngSvcFlags );
+ LaunchEvent( aEvt );
+ }
+ }
+ }
+
+ return bRes;
+}
+
+
+void SAL_CALL
+ PropertyHelper_Spell::propertyChange( const PropertyChangeEvent& rEvt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ propertyChange_Impl( rEvt );
+}
+
+
+void PropertyHelper_Spell::SetTmpPropVals( const PropertyValues &rPropVals )
+{
+ PropertyChgHelper::SetTmpPropVals( rPropVals );
+
+ // return value is default value unless there is an explicitly supplied
+ // temporary value
+ bResIsSpellWithDigits = bIsSpellWithDigits;
+ bResIsSpellCapitalization = bIsSpellCapitalization;
+ bResIsSpellUpperCase = bIsSpellUpperCase;
+
+ for (const PropertyValue& rVal : rPropVals)
+ {
+ if ( rVal.Name == UPN_MAX_NUMBER_OF_SUGGESTIONS )
+ {
+ // special value that is not part of the property set and thus needs to be handled differently
+ }
+ else
+ {
+ bool *pbResVal = nullptr;
+ switch (rVal.Handle)
+ {
+ case UPH_IS_SPELL_UPPER_CASE : pbResVal = &bResIsSpellUpperCase; break;
+ case UPH_IS_SPELL_WITH_DIGITS : pbResVal = &bResIsSpellWithDigits; break;
+ case UPH_IS_SPELL_CAPITALIZATION : pbResVal = &bResIsSpellCapitalization; break;
+ default:
+ SAL_WARN( "linguistic", "unknown property" );
+ }
+ if (pbResVal)
+ rVal.Value >>= *pbResVal;
+ }
+ }
+}
+
+PropertyHelper_Hyphen::PropertyHelper_Hyphen(
+ const Reference< XInterface > & rxSource,
+ Reference< XLinguProperties > const &rxPropSet ) :
+ PropertyChgHelper ( rxSource, rxPropSet, AE_HYPHENATOR )
+{
+ auto& rPropNames = GetPropNames();
+ rPropNames.push_back(UPN_HYPH_MIN_LEADING);
+ rPropNames.push_back(UPN_HYPH_MIN_TRAILING);
+ rPropNames.push_back(UPN_HYPH_MIN_WORD_LENGTH);
+ SetDefaultValues();
+ GetCurrentValues();
+}
+
+
+PropertyHelper_Hyphen::~PropertyHelper_Hyphen()
+{
+}
+
+
+void PropertyHelper_Hyphen::SetDefaultValues()
+{
+ PropertyChgHelper::SetDefaultValues();
+
+ nResHyphMinLeading = nHyphMinLeading = 2;
+ nResHyphMinTrailing = nHyphMinTrailing = 2;
+ nResHyphMinWordLength = nHyphMinWordLength = 0;
+ bResNoHyphenateCaps = bNoHyphenateCaps = false;
+}
+
+
+void PropertyHelper_Hyphen::GetCurrentValues()
+{
+ PropertyChgHelper::GetCurrentValues();
+
+ const auto& rPropNames = GetPropNames();
+ if (!GetPropSet().is() || rPropNames.empty())
+ return;
+
+ for (const OUString& rPropName : rPropNames)
+ {
+ sal_Int16 *pnVal = nullptr,
+ *pnResVal = nullptr;
+ bool *pbVal = nullptr;
+ bool *pbResVal = nullptr;
+
+ if ( rPropName == UPN_HYPH_MIN_LEADING )
+ {
+ pnVal = &nHyphMinLeading;
+ pnResVal = &nResHyphMinLeading;
+ }
+ else if ( rPropName == UPN_HYPH_MIN_TRAILING )
+ {
+ pnVal = &nHyphMinTrailing;
+ pnResVal = &nResHyphMinTrailing;
+ }
+ else if ( rPropName == UPN_HYPH_MIN_WORD_LENGTH )
+ {
+ pnVal = &nHyphMinWordLength;
+ pnResVal = &nResHyphMinWordLength;
+ }
+ else if ( rPropName == UPN_HYPH_NO_CAPS )
+ {
+ pbVal = &bNoHyphenateCaps;
+ pbResVal = &bResNoHyphenateCaps;
+ }
+
+ if (pnVal && pnResVal)
+ {
+ GetPropSet()->getPropertyValue( rPropName ) >>= *pnVal;
+ *pnResVal = *pnVal;
+ }
+ else if (pbVal && pbResVal)
+ {
+ GetPropSet()->getPropertyValue( rPropName ) >>= *pbVal;
+ *pbResVal = *pbVal;
+ }
+ }
+}
+
+
+bool PropertyHelper_Hyphen::propertyChange_Impl( const PropertyChangeEvent& rEvt )
+{
+ bool bRes = PropertyChgHelper::propertyChange_Impl( rEvt );
+
+ if (!bRes && GetPropSet().is() && rEvt.Source == GetPropSet())
+ {
+ sal_Int16 *pnVal = nullptr;
+ bool *pbVal = nullptr;
+ switch (rEvt.PropertyHandle)
+ {
+ case UPH_HYPH_MIN_LEADING : pnVal = &nHyphMinLeading; break;
+ case UPH_HYPH_MIN_TRAILING : pnVal = &nHyphMinTrailing; break;
+ case UPH_HYPH_MIN_WORD_LENGTH : pnVal = &nHyphMinWordLength; break;
+ case UPH_HYPH_NO_CAPS : pbVal = &bNoHyphenateCaps; break;
+ default:
+ SAL_WARN( "linguistic", "unknown property" );
+ }
+ if (pnVal)
+ rEvt.NewValue >>= *pnVal;
+ else if (pbVal)
+ rEvt.NewValue >>= *pbVal;
+
+ bRes = (pnVal != nullptr || pbVal != nullptr);
+ if (bRes)
+ {
+ LinguServiceEvent aEvt(GetEvtObj(), LinguServiceEventFlags::HYPHENATE_AGAIN);
+ LaunchEvent(aEvt);
+ }
+ }
+
+ return bRes;
+}
+
+
+void SAL_CALL
+ PropertyHelper_Hyphen::propertyChange( const PropertyChangeEvent& rEvt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ propertyChange_Impl( rEvt );
+}
+
+
+void PropertyHelper_Hyphen::SetTmpPropVals( const PropertyValues &rPropVals )
+{
+ PropertyChgHelper::SetTmpPropVals( rPropVals );
+
+ // return value is default value unless there is an explicitly supplied
+ // temporary value
+ nResHyphMinLeading = nHyphMinLeading;
+ nResHyphMinTrailing = nHyphMinTrailing;
+ nResHyphMinWordLength = nHyphMinWordLength;
+ bResNoHyphenateCaps = bNoHyphenateCaps;
+
+ for (const PropertyValue& rVal : rPropVals)
+ {
+ sal_Int16 *pnResVal = nullptr;
+ bool *pbResVal = nullptr;
+
+ if ( rVal.Name == UPN_HYPH_MIN_LEADING )
+ pnResVal = &nResHyphMinLeading;
+ else if ( rVal.Name == UPN_HYPH_MIN_TRAILING )
+ pnResVal = &nResHyphMinTrailing;
+ else if ( rVal.Name == UPN_HYPH_MIN_WORD_LENGTH )
+ pnResVal = &nResHyphMinWordLength;
+ else if ( rVal.Name == UPN_HYPH_NO_CAPS )
+ pbResVal = &bResNoHyphenateCaps;
+
+ DBG_ASSERT( pnResVal || pbResVal, "unknown property" );
+
+ if (pnResVal)
+ rVal.Value >>= *pnResVal;
+ else if (pbResVal)
+ rVal.Value >>= *pbResVal;
+ }
+}
+
+PropertyHelper_Thesaurus::PropertyHelper_Thesaurus(
+ const css::uno::Reference< css::uno::XInterface > &rxSource,
+ css::uno::Reference< css::linguistic2::XLinguProperties > const &rxPropSet )
+{
+ mxPropHelper = new PropertyHelper_Thes( rxSource, rxPropSet );
+}
+
+PropertyHelper_Thesaurus::~PropertyHelper_Thesaurus()
+{
+}
+
+void PropertyHelper_Thesaurus::AddAsPropListener()
+{
+ mxPropHelper->AddAsPropListener();
+}
+
+void PropertyHelper_Thesaurus::RemoveAsPropListener()
+{
+ mxPropHelper->RemoveAsPropListener();
+}
+
+void PropertyHelper_Thesaurus::SetTmpPropVals( const css::beans::PropertyValues &rPropVals )
+{
+ mxPropHelper->SetTmpPropVals( rPropVals );
+}
+
+PropertyHelper_Hyphenation::PropertyHelper_Hyphenation(
+ const css::uno::Reference< css::uno::XInterface > &rxSource,
+ css::uno::Reference< css::linguistic2::XLinguProperties > const &rxPropSet)
+{
+ mxPropHelper = new PropertyHelper_Hyphen( rxSource, rxPropSet );
+}
+
+PropertyHelper_Hyphenation::~PropertyHelper_Hyphenation()
+{
+}
+
+void PropertyHelper_Hyphenation::AddAsPropListener()
+{
+ mxPropHelper->AddAsPropListener();
+}
+
+void PropertyHelper_Hyphenation::RemoveAsPropListener()
+{
+ mxPropHelper->RemoveAsPropListener();
+}
+
+void PropertyHelper_Hyphenation::SetTmpPropVals( const css::beans::PropertyValues &rPropVals )
+{
+ mxPropHelper->SetTmpPropVals( rPropVals );
+}
+
+sal_Int16 PropertyHelper_Hyphenation::GetMinLeading() const
+{
+ return mxPropHelper->GetMinLeading();
+}
+
+sal_Int16 PropertyHelper_Hyphenation::GetMinTrailing() const
+{
+ return mxPropHelper->GetMinTrailing();
+}
+
+sal_Int16 PropertyHelper_Hyphenation::GetMinWordLength() const
+{
+ return mxPropHelper->GetMinWordLength();
+}
+
+bool PropertyHelper_Hyphenation::IsNoHyphenateCaps() const
+{
+ return mxPropHelper->IsNoHyphenateCaps();
+}
+
+bool PropertyHelper_Hyphenation::addLinguServiceEventListener(
+ const css::uno::Reference< css::linguistic2::XLinguServiceEventListener >& rxListener )
+{
+ return mxPropHelper->addLinguServiceEventListener( rxListener );
+}
+
+bool PropertyHelper_Hyphenation::removeLinguServiceEventListener(
+ const css::uno::Reference< css::linguistic2::XLinguServiceEventListener >& rxListener )
+{
+ return mxPropHelper->removeLinguServiceEventListener( rxListener );
+}
+
+PropertyHelper_Spelling::PropertyHelper_Spelling(
+ const css::uno::Reference< css::uno::XInterface > &rxSource,
+ css::uno::Reference< css::linguistic2::XLinguProperties > const &rxPropSet )
+{
+ mxPropHelper = new PropertyHelper_Spell( rxSource, rxPropSet );
+}
+
+PropertyHelper_Spelling::~PropertyHelper_Spelling()
+{
+}
+
+void PropertyHelper_Spelling::AddAsPropListener()
+{
+ mxPropHelper->AddAsPropListener();
+}
+
+void PropertyHelper_Spelling::RemoveAsPropListener()
+{
+ mxPropHelper->RemoveAsPropListener();
+}
+
+void PropertyHelper_Spelling::SetTmpPropVals( const css::beans::PropertyValues &rPropVals )
+{
+ mxPropHelper->SetTmpPropVals( rPropVals );
+}
+
+bool PropertyHelper_Spelling::IsSpellUpperCase() const
+{
+ return mxPropHelper->IsSpellUpperCase();
+}
+
+bool PropertyHelper_Spelling::IsSpellWithDigits() const
+{
+ return mxPropHelper->IsSpellWithDigits();
+}
+
+bool PropertyHelper_Spelling::IsSpellCapitalization() const
+{
+ return mxPropHelper->IsSpellCapitalization();
+}
+
+bool PropertyHelper_Spelling::addLinguServiceEventListener(
+ const css::uno::Reference<
+ css::linguistic2::XLinguServiceEventListener >& rxListener )
+{
+ return mxPropHelper->addLinguServiceEventListener( rxListener );
+}
+
+bool PropertyHelper_Spelling::removeLinguServiceEventListener(
+ const css::uno::Reference<
+ css::linguistic2::XLinguServiceEventListener >& rxListener )
+{
+ return mxPropHelper->removeLinguServiceEventListener( rxListener );
+}
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/lngsvcmgr.cxx b/linguistic/source/lngsvcmgr.cxx
new file mode 100644
index 000000000..f38ee2620
--- /dev/null
+++ b/linguistic/source/lngsvcmgr.cxx
@@ -0,0 +1,1830 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <com/sun/star/deployment/DeploymentException.hpp>
+#include <com/sun/star/deployment/ExtensionManager.hpp>
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/lang/XSingleComponentFactory.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/linguistic2/XSupportedLocales.hpp>
+#include <com/sun/star/linguistic2/DictionaryListEventFlags.hpp>
+#include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
+#include <com/sun/star/linguistic2/ProofreadingIterator.hpp>
+
+#include <tools/debug.hxx>
+#include <unotools/lingucfg.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <comphelper/interfacecontainer2.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+
+#include "lngsvcmgr.hxx"
+#include <linguistic/misc.hxx>
+#include "spelldsp.hxx"
+#include "hyphdsp.hxx"
+#include "thesdsp.hxx"
+#include "gciterator.hxx"
+
+using namespace com::sun::star;
+using namespace linguistic;
+
+uno::Sequence< OUString > static GetLangSvcList( const uno::Any &rVal );
+uno::Sequence< OUString > static GetLangSvc( const uno::Any &rVal );
+
+static bool lcl_SeqHasString( const uno::Sequence< OUString > &rSeq, const OUString &rText )
+{
+ return !rText.isEmpty()
+ && comphelper::findValue(rSeq, rText) != -1;
+}
+
+
+static uno::Sequence< lang::Locale > GetAvailLocales(
+ const uno::Sequence< OUString > &rSvcImplNames )
+{
+ uno::Sequence< lang::Locale > aRes;
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ if( rSvcImplNames.hasElements() )
+ {
+ std::set< LanguageType > aLanguages;
+
+ // All of these services only use one arg, but need two args for compat reasons
+ uno::Sequence< uno::Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetLinguProperties();
+
+ // check all services for the supported languages and new
+ // languages to the result
+
+ for (const OUString& rImplName : rSvcImplNames)
+ {
+ uno::Reference< linguistic2::XSupportedLocales > xSuppLoc;
+ try
+ {
+ xSuppLoc.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ rImplName, aArgs, xContext ),
+ uno::UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+
+ if (xSuppLoc.is())
+ {
+ const uno::Sequence< lang::Locale > aLoc( xSuppLoc->getLocales() );
+ for (const lang::Locale& rLoc : aLoc)
+ {
+ LanguageType nLang = LinguLocaleToLanguage( rLoc );
+
+ // It's a set, so insertion fails if language was already added.
+ aLanguages.insert( nLang );
+ }
+ }
+ else
+ {
+ SAL_WARN( "linguistic", "interface not supported by service" );
+ }
+ }
+
+ // build return sequence
+ std::vector<lang::Locale> aVec;
+ aVec.reserve(aLanguages.size());
+
+ std::transform(aLanguages.begin(), aLanguages.end(), std::back_inserter(aVec),
+ [](const LanguageType& rLang) -> lang::Locale { return LanguageTag::convertToLocale(rLang); });
+
+ aRes = comphelper::containerToSequence(aVec);
+ }
+
+ return aRes;
+}
+
+
+struct SvcInfo
+{
+ const OUString aSvcImplName;
+ const std::vector< LanguageType > aSuppLanguages;
+
+ SvcInfo( OUString aSvcImplName_,
+ std::vector< LanguageType >&& rSuppLanguages ) :
+ aSvcImplName (std::move(aSvcImplName_)),
+ aSuppLanguages (std::move(rSuppLanguages))
+ {
+ }
+
+ bool HasLanguage( LanguageType nLanguage ) const;
+};
+
+
+bool SvcInfo::HasLanguage( LanguageType nLanguage ) const
+{
+ for ( auto const & i : aSuppLanguages)
+ {
+ if (nLanguage == i)
+ return true;
+ }
+ return false;
+}
+
+class LngSvcMgrListenerHelper :
+ public cppu::WeakImplHelper
+ <
+ linguistic2::XLinguServiceEventListener,
+ linguistic2::XDictionaryListEventListener
+ >
+{
+ LngSvcMgr &rMyManager;
+
+ ::comphelper::OInterfaceContainerHelper2 aLngSvcMgrListeners;
+ ::comphelper::OInterfaceContainerHelper2 aLngSvcEvtBroadcasters;
+ uno::Reference< linguistic2::XSearchableDictionaryList > xDicList;
+
+ sal_Int16 nCombinedLngSvcEvt;
+
+ void LaunchEvent( sal_Int16 nLngSvcEvtFlags );
+
+ void Timeout();
+
+public:
+ LngSvcMgrListenerHelper( LngSvcMgr &rLngSvcMgr,
+ uno::Reference< linguistic2::XSearchableDictionaryList > xDicList );
+
+ LngSvcMgrListenerHelper(const LngSvcMgrListenerHelper&) = delete;
+ LngSvcMgrListenerHelper& operator=(const LngSvcMgrListenerHelper&) = delete;
+
+ // lang::XEventListener
+ virtual void SAL_CALL
+ disposing( const lang::EventObject& rSource ) override;
+
+ // linguistic2::XLinguServiceEventListener
+ virtual void SAL_CALL
+ processLinguServiceEvent( const linguistic2::LinguServiceEvent& aLngSvcEvent ) override;
+
+ // linguistic2::XDictionaryListEventListener
+ virtual void SAL_CALL
+ processDictionaryListEvent(
+ const linguistic2::DictionaryListEvent& rDicListEvent ) override;
+
+ inline void AddLngSvcMgrListener(
+ const uno::Reference< lang::XEventListener >& rxListener );
+ inline void RemoveLngSvcMgrListener(
+ const uno::Reference< lang::XEventListener >& rxListener );
+ void DisposeAndClear( const lang::EventObject &rEvtObj );
+ void AddLngSvcEvtBroadcaster(
+ const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster );
+ void RemoveLngSvcEvtBroadcaster(
+ const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster );
+
+ void AddLngSvcEvt( sal_Int16 nLngSvcEvt );
+};
+
+
+LngSvcMgrListenerHelper::LngSvcMgrListenerHelper(
+ LngSvcMgr &rLngSvcMgr,
+ uno::Reference< linguistic2::XSearchableDictionaryList > xDicList_ ) :
+ rMyManager ( rLngSvcMgr ),
+ aLngSvcMgrListeners ( GetLinguMutex() ),
+ aLngSvcEvtBroadcasters ( GetLinguMutex() ),
+ xDicList (std::move( xDicList_ ))
+{
+ if (xDicList.is())
+ {
+ xDicList->addDictionaryListEventListener(
+ static_cast<linguistic2::XDictionaryListEventListener *>(this), false );
+ }
+
+ nCombinedLngSvcEvt = 0;
+}
+
+
+void SAL_CALL LngSvcMgrListenerHelper::disposing( const lang::EventObject& rSource )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Reference< uno::XInterface > xRef( rSource.Source );
+ if ( xRef.is() )
+ {
+ aLngSvcMgrListeners .removeInterface( xRef );
+ aLngSvcEvtBroadcasters.removeInterface( xRef );
+ if (xDicList == xRef)
+ xDicList = nullptr;
+ }
+}
+
+void LngSvcMgrListenerHelper::Timeout()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ {
+ // change event source to LinguServiceManager since the listeners
+ // probably do not know (and need not to know) about the specific
+ // SpellChecker's or Hyphenator's.
+ linguistic2::LinguServiceEvent aEvtObj(
+ static_cast<css::linguistic2::XLinguServiceManager*>(&rMyManager), nCombinedLngSvcEvt );
+ nCombinedLngSvcEvt = 0;
+
+ if (rMyManager.mxSpellDsp.is())
+ rMyManager.mxSpellDsp->FlushSpellCache();
+
+ // pass event on to linguistic2::XLinguServiceEventListener's
+ aLngSvcMgrListeners.notifyEach( &linguistic2::XLinguServiceEventListener::processLinguServiceEvent, aEvtObj );
+ }
+}
+
+
+void LngSvcMgrListenerHelper::AddLngSvcEvt( sal_Int16 nLngSvcEvt )
+{
+ nCombinedLngSvcEvt |= nLngSvcEvt;
+ Timeout();
+}
+
+
+void SAL_CALL
+ LngSvcMgrListenerHelper::processLinguServiceEvent(
+ const linguistic2::LinguServiceEvent& rLngSvcEvent )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+ AddLngSvcEvt( rLngSvcEvent.nEvent );
+}
+
+
+void SAL_CALL
+ LngSvcMgrListenerHelper::processDictionaryListEvent(
+ const linguistic2::DictionaryListEvent& rDicListEvent )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ sal_Int16 nDlEvt = rDicListEvent.nCondensedEvent;
+ if (0 == nDlEvt)
+ return;
+
+ // we do keep the original event source here though...
+
+ // pass event on to linguistic2::XDictionaryListEventListener's
+ aLngSvcMgrListeners.notifyEach( &linguistic2::XDictionaryListEventListener::processDictionaryListEvent, rDicListEvent );
+
+ // "translate" DictionaryList event into linguistic2::LinguServiceEvent
+ sal_Int16 nLngSvcEvt = 0;
+ sal_Int16 const nSpellCorrectFlags =
+ linguistic2::DictionaryListEventFlags::ADD_NEG_ENTRY |
+ linguistic2::DictionaryListEventFlags::DEL_POS_ENTRY |
+ linguistic2::DictionaryListEventFlags::ACTIVATE_NEG_DIC |
+ linguistic2::DictionaryListEventFlags::DEACTIVATE_POS_DIC;
+ if (0 != (nDlEvt & nSpellCorrectFlags))
+ nLngSvcEvt |= linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN;
+
+ sal_Int16 const nSpellWrongFlags =
+ linguistic2::DictionaryListEventFlags::ADD_POS_ENTRY |
+ linguistic2::DictionaryListEventFlags::DEL_NEG_ENTRY |
+ linguistic2::DictionaryListEventFlags::ACTIVATE_POS_DIC |
+ linguistic2::DictionaryListEventFlags::DEACTIVATE_NEG_DIC;
+ if (0 != (nDlEvt & nSpellWrongFlags))
+ nLngSvcEvt |= linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN;
+
+ sal_Int16 const nHyphenateFlags =
+ linguistic2::DictionaryListEventFlags::ADD_POS_ENTRY |
+ linguistic2::DictionaryListEventFlags::DEL_POS_ENTRY |
+ linguistic2::DictionaryListEventFlags::ACTIVATE_POS_DIC |
+ linguistic2::DictionaryListEventFlags::ACTIVATE_NEG_DIC;
+ if (0 != (nDlEvt & nHyphenateFlags))
+ nLngSvcEvt |= linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN;
+
+ if (rMyManager.mxSpellDsp.is())
+ rMyManager.mxSpellDsp->FlushSpellCache();
+ if (nLngSvcEvt)
+ LaunchEvent( nLngSvcEvt );
+}
+
+
+void LngSvcMgrListenerHelper::LaunchEvent( sal_Int16 nLngSvcEvtFlags )
+{
+ linguistic2::LinguServiceEvent aEvt(
+ static_cast<css::linguistic2::XLinguServiceManager*>(&rMyManager), nLngSvcEvtFlags );
+
+ // pass event on to linguistic2::XLinguServiceEventListener's
+ aLngSvcMgrListeners.notifyEach( &linguistic2::XLinguServiceEventListener::processLinguServiceEvent, aEvt );
+}
+
+
+inline void LngSvcMgrListenerHelper::AddLngSvcMgrListener(
+ const uno::Reference< lang::XEventListener >& rxListener )
+{
+ aLngSvcMgrListeners.addInterface( rxListener );
+}
+
+
+inline void LngSvcMgrListenerHelper::RemoveLngSvcMgrListener(
+ const uno::Reference< lang::XEventListener >& rxListener )
+{
+ aLngSvcMgrListeners.removeInterface( rxListener );
+}
+
+
+void LngSvcMgrListenerHelper::DisposeAndClear( const lang::EventObject &rEvtObj )
+{
+ // call "disposing" for all listeners and clear list
+ aLngSvcMgrListeners .disposeAndClear( rEvtObj );
+
+ // remove references to this object hold by the broadcasters
+ comphelper::OInterfaceIteratorHelper2 aIt( aLngSvcEvtBroadcasters );
+ while (aIt.hasMoreElements())
+ {
+ uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xRef( aIt.next(), uno::UNO_QUERY );
+ if (xRef.is())
+ RemoveLngSvcEvtBroadcaster( xRef );
+ }
+
+ // remove reference to this object hold by the dictionary-list
+ if (xDicList.is())
+ {
+ xDicList->removeDictionaryListEventListener(
+ static_cast<linguistic2::XDictionaryListEventListener *>(this) );
+ xDicList = nullptr;
+ }
+}
+
+
+void LngSvcMgrListenerHelper::AddLngSvcEvtBroadcaster(
+ const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
+{
+ if (rxBroadcaster.is())
+ {
+ aLngSvcEvtBroadcasters.addInterface( rxBroadcaster );
+ rxBroadcaster->addLinguServiceEventListener(
+ static_cast<linguistic2::XLinguServiceEventListener *>(this) );
+ }
+}
+
+
+void LngSvcMgrListenerHelper::RemoveLngSvcEvtBroadcaster(
+ const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
+{
+ if (rxBroadcaster.is())
+ {
+ aLngSvcEvtBroadcasters.removeInterface( rxBroadcaster );
+ rxBroadcaster->removeLinguServiceEventListener(
+ static_cast<linguistic2::XLinguServiceEventListener *>(this) );
+ }
+}
+
+
+LngSvcMgr::LngSvcMgr()
+ : utl::ConfigItem("Office.Linguistic")
+ , aEvtListeners(GetLinguMutex())
+ , aUpdateIdle("LngSvcMgr aUpdateIdle")
+{
+ bDisposing = false;
+
+ // request notify events when properties (i.e. something in the subtree) changes
+ uno::Sequence< OUString > aNames{
+ "ServiceManager/SpellCheckerList",
+ "ServiceManager/GrammarCheckerList",
+ "ServiceManager/HyphenatorList",
+ "ServiceManager/ThesaurusList"
+ };
+ EnableNotification( aNames );
+
+ UpdateAll();
+
+ aUpdateIdle.SetPriority(TaskPriority::LOWEST);
+ aUpdateIdle.SetInvokeHandler(LINK(this, LngSvcMgr, updateAndBroadcast));
+
+ // request to be notified if an extension has been added/removed
+ uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+
+ uno::Reference<deployment::XExtensionManager> xExtensionManager;
+ try {
+ xExtensionManager = deployment::ExtensionManager::get(xContext);
+ } catch ( const uno::DeploymentException & ) {
+ SAL_WARN( "linguistic", "no extension manager - should fire on mobile only" );
+ } catch ( const deployment::DeploymentException & ) {
+ SAL_WARN( "linguistic", "no extension manager - should fire on mobile only" );
+ }
+ if (xExtensionManager.is())
+ {
+ xMB.set(xExtensionManager, uno::UNO_QUERY_THROW);
+
+ uno::Reference<util::XModifyListener> xListener(this);
+ xMB->addModifyListener( xListener );
+ }
+}
+
+// css::util::XModifyListener
+void LngSvcMgr::modified(const lang::EventObject&)
+{
+ {
+ osl::MutexGuard aGuard(GetLinguMutex());
+ //assume that if an extension has been added/removed that
+ //it might be a dictionary extension, so drop our cache
+
+ pAvailSpellSvcs.reset();
+ pAvailGrammarSvcs.reset();
+ pAvailHyphSvcs.reset();
+ pAvailThesSvcs.reset();
+ }
+
+ {
+ SolarMutexGuard aGuard;
+ //schedule in an update to execute in the main thread
+ aUpdateIdle.Start();
+ }
+}
+
+//run update, and inform everyone that dictionaries (may) have changed, this
+//needs to be run in the main thread because
+//utl::ConfigChangeListener_Impl::changesOccurred grabs the SolarMutex and we
+//get notified that an extension was added from an extension manager thread
+IMPL_LINK_NOARG(LngSvcMgr, updateAndBroadcast, Timer *, void)
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ UpdateAll();
+
+ if (mxListenerHelper.is())
+ {
+ mxListenerHelper->AddLngSvcEvt(
+ linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN |
+ linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN |
+ linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN |
+ linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN );
+ }
+}
+
+void LngSvcMgr::stopListening()
+{
+ osl::MutexGuard aGuard(GetLinguMutex());
+
+ if (!xMB.is())
+ return;
+
+ try
+ {
+ uno::Reference<util::XModifyListener> xListener(this);
+ xMB->removeModifyListener(xListener);
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ xMB.clear();
+}
+
+void LngSvcMgr::disposing(const lang::EventObject&)
+{
+ stopListening();
+}
+
+LngSvcMgr::~LngSvcMgr()
+{
+ stopListening();
+
+ // memory for pSpellDsp, pHyphDsp, pThesDsp, pListenerHelper
+ // will be freed in the destructor of the respective Reference's
+ // xSpellDsp, xGrammarDsp, xHyphDsp, xThesDsp
+
+ pAvailSpellSvcs.reset();
+ pAvailGrammarSvcs.reset();
+ pAvailHyphSvcs.reset();
+ pAvailThesSvcs.reset();
+}
+
+namespace
+{
+ using lang::Locale;
+ using uno::Any;
+ using uno::Sequence;
+
+ bool lcl_FindEntry( const OUString &rEntry, const Sequence< OUString > &rCfgSvcs )
+ {
+ return comphelper::findValue(rCfgSvcs, rEntry) != -1;
+ }
+
+ bool lcl_FindEntry( const OUString &rEntry, const std::vector< OUString > &rCfgSvcs )
+ {
+ return std::find(rCfgSvcs.begin(), rCfgSvcs.end(), rEntry) != rCfgSvcs.end();
+ }
+
+ Sequence< OUString > lcl_GetLastFoundSvcs(
+ SvtLinguConfig const &rCfg,
+ const OUString &rLastFoundList ,
+ const OUString& rCfgLocaleStr )
+ {
+ Sequence< OUString > aRes;
+
+ Sequence< OUString > aNodeNames( rCfg.GetNodeNames(rLastFoundList) );
+ bool bFound = lcl_FindEntry( rCfgLocaleStr, aNodeNames);
+
+ if (bFound)
+ {
+ Sequence< OUString > aNames { rLastFoundList + "/" + rCfgLocaleStr };
+ Sequence< Any > aValues( rCfg.GetProperties( aNames ) );
+ if (aValues.hasElements())
+ {
+ SAL_WARN_IF( aValues.getLength() != 1, "linguistic", "unexpected length of sequence" );
+ Sequence< OUString > aSvcImplNames;
+ if (aValues.getConstArray()[0] >>= aSvcImplNames)
+ aRes = aSvcImplNames;
+ else
+ {
+ SAL_WARN( "linguistic", "type mismatch" );
+ }
+ }
+ }
+
+ return aRes;
+ }
+
+ Sequence< OUString > lcl_RemoveMissingEntries(
+ const Sequence< OUString > &rCfgSvcs,
+ const Sequence< OUString > &rAvailSvcs )
+ {
+ std::vector<OUString> aRes;
+ aRes.reserve(rCfgSvcs.getLength());
+
+ std::copy_if(rCfgSvcs.begin(), rCfgSvcs.end(), std::back_inserter(aRes),
+ [&rAvailSvcs](const OUString& entry) { return lcl_SeqHasString(rAvailSvcs, entry); });
+
+ return comphelper::containerToSequence(aRes);
+ }
+
+ Sequence< OUString > lcl_GetNewEntries(
+ const Sequence< OUString > &rLastFoundSvcs,
+ const Sequence< OUString > &rAvailSvcs )
+ {
+ std::vector<OUString> aRes;
+ aRes.reserve(rAvailSvcs.getLength());
+
+ std::copy_if(rAvailSvcs.begin(), rAvailSvcs.end(), std::back_inserter(aRes),
+ [&rLastFoundSvcs](const OUString& rEntry) {
+ return !rEntry.isEmpty() && !lcl_FindEntry( rEntry, rLastFoundSvcs ); });
+
+ return comphelper::containerToSequence(aRes);
+ }
+
+ Sequence< OUString > lcl_MergeSeq(
+ const Sequence< OUString > &rCfgSvcs,
+ const Sequence< OUString > &rNewSvcs )
+ {
+ std::vector<OUString> aRes;
+ aRes.reserve(rCfgSvcs.getLength() + rNewSvcs.getLength());
+
+ auto lVecNotHasString = [&aRes](const OUString& rEntry)
+ { return !rEntry.isEmpty() && !lcl_FindEntry(rEntry, aRes); };
+
+ // add previously configured service first and append
+ // new found services at the end
+ for (const Sequence< OUString > &rSeq : { rCfgSvcs, rNewSvcs })
+ {
+ std::copy_if(rSeq.begin(), rSeq.end(), std::back_inserter(aRes), lVecNotHasString);
+ }
+
+ return comphelper::containerToSequence(aRes);
+ }
+}
+
+void LngSvcMgr::UpdateAll()
+{
+ using beans::PropertyValue;
+ using lang::Locale;
+ using uno::Sequence;
+
+ typedef std::map< OUString, Sequence< OUString > > list_entry_map_t;
+
+ SvtLinguConfig aCfg;
+
+ const int nNumServices = 4;
+ static constexpr rtl::OUStringConstExpr apServices[nNumServices] = { SN_SPELLCHECKER, SN_GRAMMARCHECKER, SN_HYPHENATOR, SN_THESAURUS };
+ const char * const apCurLists[nNumServices] = { "ServiceManager/SpellCheckerList", "ServiceManager/GrammarCheckerList", "ServiceManager/HyphenatorList", "ServiceManager/ThesaurusList" };
+ const char * const apLastFoundLists[nNumServices] = { "ServiceManager/LastFoundSpellCheckers", "ServiceManager/LastFoundGrammarCheckers", "ServiceManager/LastFoundHyphenators", "ServiceManager/LastFoundThesauri" };
+
+ // usage of indices as above: 0 = spell checker, 1 = grammar checker, 2 = hyphenator, 3 = thesaurus
+ std::vector< list_entry_map_t > aLastFoundSvcs(nNumServices);
+ std::vector< list_entry_map_t > aCurSvcs(nNumServices);
+
+ for (int k = 0; k < nNumServices; ++k)
+ {
+ OUString aService( apServices[k] );
+ OUString aActiveList( OUString::createFromAscii( apCurLists[k] ) );
+ OUString aLastFoundList( OUString::createFromAscii( apLastFoundLists[k] ) );
+
+
+ // remove configured but not available language/services entries
+
+ const Sequence< OUString > aNodeNames( aCfg.GetNodeNames( aActiveList ) ); // list of configured locales
+ for (const OUString& rNodeName : aNodeNames)
+ {
+ Locale aLocale( LanguageTag::convertToLocale( rNodeName));
+ Sequence< OUString > aCfgSvcs( getConfiguredServices( aService, aLocale ));
+ Sequence< OUString > aAvailSvcs( getAvailableServices( aService, aLocale ));
+
+ aCfgSvcs = lcl_RemoveMissingEntries( aCfgSvcs, aAvailSvcs );
+
+ aCurSvcs[k][ rNodeName ] = aCfgSvcs;
+ }
+
+
+ // add new available language/service entries
+ // and
+ // set last found services to currently available ones
+
+ const Sequence< Locale > aAvailLocales( getAvailableLocales(aService) );
+ for (const Locale& rAvailLocale : aAvailLocales)
+ {
+ OUString aCfgLocaleStr( LanguageTag::convertToBcp47( rAvailLocale));
+
+ Sequence< OUString > aAvailSvcs( getAvailableServices( aService, rAvailLocale ));
+
+ aLastFoundSvcs[k][ aCfgLocaleStr ] = aAvailSvcs;
+
+ Sequence< OUString > aLastSvcs(
+ lcl_GetLastFoundSvcs( aCfg, aLastFoundList , aCfgLocaleStr ));
+ Sequence< OUString > aNewSvcs =
+ lcl_GetNewEntries( aLastSvcs, aAvailSvcs );
+
+ Sequence< OUString > aCfgSvcs( aCurSvcs[k][ aCfgLocaleStr ] );
+
+ // merge services list (previously configured to be listed first).
+ aCfgSvcs = lcl_MergeSeq( aCfgSvcs, aNewSvcs );
+
+ aCurSvcs[k][ aCfgLocaleStr ] = aCfgSvcs;
+ }
+ }
+
+
+ // write new data back to configuration
+
+ for (int k = 0; k < nNumServices; ++k)
+ {
+ for (int i = 0; i < 2; ++i)
+ {
+ const char *pSubNodeName = (i == 0) ? apCurLists[k] : apLastFoundLists[k];
+ OUString aSubNodeName( OUString::createFromAscii(pSubNodeName) );
+
+ list_entry_map_t &rCurMap = (i == 0) ? aCurSvcs[k] : aLastFoundSvcs[k];
+ sal_Int32 nVals = static_cast< sal_Int32 >( rCurMap.size() );
+ Sequence< PropertyValue > aNewValues( nVals );
+ PropertyValue *pNewValue = aNewValues.getArray();
+ for (auto const& elem : rCurMap)
+ {
+ pNewValue->Name = aSubNodeName + "/" + elem.first;
+ pNewValue->Value <<= elem.second;
+ ++pNewValue;
+ }
+ OSL_ENSURE( pNewValue - aNewValues.getConstArray() == nVals,
+ "possible mismatch of sequence size and property number" );
+
+ {
+ // add new or replace existing entries.
+ bool bRes = aCfg.ReplaceSetProperties( aSubNodeName, aNewValues );
+ SAL_WARN_IF(!bRes, "linguistic", "failed to set new configuration values");
+ }
+ }
+ }
+
+ //The new settings in the configuration get applied ! because we are
+ //listening to the configuration for changes of the relevant ! properties
+ //and Notify applies the new settings.
+}
+
+void LngSvcMgr::Notify( const uno::Sequence< OUString > &rPropertyNames )
+{
+ static const OUStringLiteral aSpellCheckerList( u"ServiceManager/SpellCheckerList" );
+ static const OUStringLiteral aGrammarCheckerList( u"ServiceManager/GrammarCheckerList" );
+ static const OUStringLiteral aHyphenatorList( u"ServiceManager/HyphenatorList" );
+ static const OUStringLiteral aThesaurusList( u"ServiceManager/ThesaurusList" );
+
+ const uno::Sequence< OUString > aSpellCheckerListEntries( GetNodeNames( aSpellCheckerList ) );
+ const uno::Sequence< OUString > aGrammarCheckerListEntries( GetNodeNames( aGrammarCheckerList ) );
+ const uno::Sequence< OUString > aHyphenatorListEntries( GetNodeNames( aHyphenatorList ) );
+ const uno::Sequence< OUString > aThesaurusListEntries( GetNodeNames( aThesaurusList ) );
+
+ uno::Sequence< uno::Any > aValues;
+ uno::Sequence< OUString > aNames( 1 );
+ OUString *pNames = aNames.getArray();
+
+ for (const OUString& rName : rPropertyNames)
+ {
+ // property names look like
+ // "ServiceManager/ThesaurusList/de-CH"
+
+ sal_Int32 nKeyStart;
+ nKeyStart = rName.lastIndexOf( '/' );
+ OUString aKeyText;
+ if (nKeyStart != -1)
+ aKeyText = rName.copy( nKeyStart + 1 );
+ SAL_WARN_IF( aKeyText.isEmpty(), "linguistic", "unexpected key (lang::Locale) string" );
+ if (rName.startsWith( aSpellCheckerList ))
+ {
+ osl::MutexGuard aGuard(GetLinguMutex());
+
+ // delete old cached data, needs to be acquired new on demand
+ pAvailSpellSvcs.reset();
+
+ if (lcl_SeqHasString( aSpellCheckerListEntries, aKeyText ))
+ {
+ pNames[0] = aSpellCheckerList + "/" + aKeyText;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ uno::Sequence< OUString > aSvcImplNames;
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
+
+ LanguageType nLang = LANGUAGE_NONE;
+ if (!aKeyText.isEmpty())
+ nLang = LanguageTag::convertToLanguageType( aKeyText );
+
+ GetSpellCheckerDsp_Impl( false ); // don't set service list, it will be done below
+ mxSpellDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
+ }
+ }
+ else if (rName.startsWith( aGrammarCheckerList ))
+ {
+ osl::MutexGuard aGuard(GetLinguMutex());
+
+ // delete old cached data, needs to be acquired new on demand
+ pAvailGrammarSvcs.reset();
+
+ if (lcl_SeqHasString( aGrammarCheckerListEntries, aKeyText ))
+ {
+ pNames[0] = aGrammarCheckerList + "/" + aKeyText;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ uno::Sequence< OUString > aSvcImplNames;
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
+
+ LanguageType nLang = LANGUAGE_NONE;
+ if (!aKeyText.isEmpty())
+ nLang = LanguageTag::convertToLanguageType( aKeyText );
+
+ if (SvtLinguConfig().HasGrammarChecker())
+ {
+ GetGrammarCheckerDsp_Impl( false ); // don't set service list, it will be done below
+ mxGrammarDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
+ }
+ }
+ }
+ else if (rName.startsWith( aHyphenatorList ))
+ {
+ osl::MutexGuard aGuard(GetLinguMutex());
+
+ // delete old cached data, needs to be acquired new on demand
+ pAvailHyphSvcs.reset();
+
+ if (lcl_SeqHasString( aHyphenatorListEntries, aKeyText ))
+ {
+ pNames[0] = aHyphenatorList + "/" + aKeyText;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ uno::Sequence< OUString > aSvcImplNames;
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
+
+ LanguageType nLang = LANGUAGE_NONE;
+ if (!aKeyText.isEmpty())
+ nLang = LanguageTag::convertToLanguageType( aKeyText );
+
+ GetHyphenatorDsp_Impl( false ); // don't set service list, it will be done below
+ mxHyphDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
+ }
+ }
+ else if (rName.startsWith( aThesaurusList ))
+ {
+ osl::MutexGuard aGuard(GetLinguMutex());
+
+ // delete old cached data, needs to be acquired new on demand
+ pAvailThesSvcs.reset();
+
+ if (lcl_SeqHasString( aThesaurusListEntries, aKeyText ))
+ {
+ pNames[0] = aThesaurusList + "/" + aKeyText;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ uno::Sequence< OUString > aSvcImplNames;
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
+
+ LanguageType nLang = LANGUAGE_NONE;
+ if (!aKeyText.isEmpty())
+ nLang = LanguageTag::convertToLanguageType( aKeyText );
+
+ GetThesaurusDsp_Impl( false ); // don't set service list, it will be done below
+ mxThesDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
+ }
+ }
+ else
+ {
+ SAL_WARN( "linguistic", "notified for unexpected property" );
+ }
+ }
+}
+
+
+void LngSvcMgr::ImplCommit()
+{
+ // everything necessary should have already been done by 'SaveCfgSvcs'
+ // called from within 'setConfiguredServices'.
+ // Also this class usually exits only when the Office is being shutdown.
+}
+
+
+void LngSvcMgr::GetListenerHelper_Impl()
+{
+ if (!mxListenerHelper.is())
+ {
+ mxListenerHelper = new LngSvcMgrListenerHelper( *this, linguistic::GetDictionaryList() );
+ }
+}
+
+
+void LngSvcMgr::GetSpellCheckerDsp_Impl( bool bSetSvcList )
+{
+ if (!mxSpellDsp.is())
+ {
+ mxSpellDsp = new SpellCheckerDispatcher( *this );
+ if (bSetSvcList)
+ SetCfgServiceLists( *mxSpellDsp );
+ }
+}
+
+
+void LngSvcMgr::GetGrammarCheckerDsp_Impl( bool bSetSvcList )
+{
+ if (mxGrammarDsp.is() || !SvtLinguConfig().HasGrammarChecker())
+ return;
+
+ //! since the grammar checking iterator needs to be a one instance service
+ //! we need to create it the correct way!
+ uno::Reference< linguistic2::XProofreadingIterator > xGCI;
+ try
+ {
+ xGCI = linguistic2::ProofreadingIterator::create( comphelper::getProcessComponentContext() );
+ }
+ catch (const uno::Exception &)
+ {
+ }
+ SAL_WARN_IF( !xGCI.is(), "linguistic", "instantiating grammar checking iterator failed" );
+
+ if (xGCI.is())
+ {
+ mxGrammarDsp = dynamic_cast< GrammarCheckingIterator * >(xGCI.get());
+ SAL_WARN_IF( mxGrammarDsp == nullptr, "linguistic", "failed to get implementation" );
+ if (bSetSvcList && mxGrammarDsp.is())
+ SetCfgServiceLists( *mxGrammarDsp );
+ }
+}
+
+
+void LngSvcMgr::GetHyphenatorDsp_Impl( bool bSetSvcList )
+{
+ if (!mxHyphDsp.is())
+ {
+ mxHyphDsp = new HyphenatorDispatcher( *this );
+ if (bSetSvcList)
+ SetCfgServiceLists( *mxHyphDsp );
+ }
+}
+
+
+void LngSvcMgr::GetThesaurusDsp_Impl( bool bSetSvcList )
+{
+ if (!mxThesDsp.is())
+ {
+ mxThesDsp = new ThesaurusDispatcher;
+ if (bSetSvcList)
+ SetCfgServiceLists( *mxThesDsp );
+ }
+}
+
+
+void LngSvcMgr::GetAvailableSpellSvcs_Impl()
+{
+ if (pAvailSpellSvcs)
+ return;
+
+ pAvailSpellSvcs.emplace();
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
+ uno::Reference< container::XEnumeration > xEnum;
+ if (xEnumAccess.is())
+ xEnum = xEnumAccess->createContentEnumeration( SN_SPELLCHECKER );
+
+ if (!xEnum.is())
+ return;
+
+ while (xEnum->hasMoreElements())
+ {
+ uno::Any aCurrent = xEnum->nextElement();
+ uno::Reference< lang::XSingleComponentFactory > xCompFactory;
+ uno::Reference< lang::XSingleServiceFactory > xFactory;
+
+ xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
+ if (!xCompFactory.is())
+ {
+ xFactory.set(aCurrent, css::uno::UNO_QUERY);
+ }
+ if ( xCompFactory.is() || xFactory.is() )
+ {
+ try
+ {
+ uno::Reference< linguistic2::XSpellChecker > xSvc( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY_THROW );
+
+ OUString aImplName;
+ std::vector< LanguageType > aLanguages;
+ uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
+ if (xInfo.is())
+ aImplName = xInfo->getImplementationName();
+ SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
+ uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
+ aLanguages = LocaleSeqToLangVec( aLocaleSequence );
+
+ pAvailSpellSvcs->push_back( SvcInfo( aImplName, std::move(aLanguages) ) );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+ }
+ }
+}
+
+
+void LngSvcMgr::GetAvailableGrammarSvcs_Impl()
+{
+ if (pAvailGrammarSvcs)
+ return;
+
+ pAvailGrammarSvcs.emplace();
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
+ uno::Reference< container::XEnumeration > xEnum;
+ if (xEnumAccess.is())
+ xEnum = xEnumAccess->createContentEnumeration( SN_GRAMMARCHECKER );
+
+ if (!xEnum.is())
+ return;
+
+ while (xEnum->hasMoreElements())
+ {
+ uno::Any aCurrent = xEnum->nextElement();
+ uno::Reference< lang::XSingleComponentFactory > xCompFactory;
+ uno::Reference< lang::XSingleServiceFactory > xFactory;
+
+ xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
+ if (!xCompFactory.is())
+ {
+ xFactory.set(aCurrent, css::uno::UNO_QUERY);
+ }
+ if ( xCompFactory.is() || xFactory.is() )
+ {
+ try
+ {
+ uno::Reference< linguistic2::XProofreader > xSvc(
+ xCompFactory.is()
+ ? xCompFactory->createInstanceWithContext(xContext)
+ : xFactory->createInstance(),
+ uno::UNO_QUERY_THROW);
+
+ OUString aImplName;
+ std::vector< LanguageType > aLanguages;
+ uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
+ if (xInfo.is())
+ aImplName = xInfo->getImplementationName();
+ SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
+ uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
+ aLanguages = LocaleSeqToLangVec( aLocaleSequence );
+
+ pAvailGrammarSvcs->push_back( SvcInfo( aImplName, std::move(aLanguages) ) );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+ }
+
+ }
+}
+
+
+void LngSvcMgr::GetAvailableHyphSvcs_Impl()
+{
+ if (pAvailHyphSvcs)
+ return;
+
+ pAvailHyphSvcs.emplace();
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
+ uno::Reference< container::XEnumeration > xEnum;
+ if (xEnumAccess.is())
+ xEnum = xEnumAccess->createContentEnumeration( SN_HYPHENATOR );
+
+ if (!xEnum.is())
+ return;
+
+ while (xEnum->hasMoreElements())
+ {
+ uno::Any aCurrent = xEnum->nextElement();
+ uno::Reference< lang::XSingleComponentFactory > xCompFactory;
+ uno::Reference< lang::XSingleServiceFactory > xFactory;
+
+ xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
+ if (!xCompFactory.is())
+ {
+ xFactory.set(aCurrent, css::uno::UNO_QUERY);
+ }
+ if ( xCompFactory.is() || xFactory.is() )
+ {
+ try
+ {
+ uno::Reference< linguistic2::XHyphenator > xSvc( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY_THROW );
+ OUString aImplName;
+ std::vector< LanguageType > aLanguages;
+ uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
+ if (xInfo.is())
+ aImplName = xInfo->getImplementationName();
+ SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
+ uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
+ aLanguages = LocaleSeqToLangVec( aLocaleSequence );
+ pAvailHyphSvcs->push_back( SvcInfo( aImplName, std::move(aLanguages) ) );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+ }
+ }
+}
+
+
+void LngSvcMgr::GetAvailableThesSvcs_Impl()
+{
+ if (pAvailThesSvcs)
+ return;
+
+ pAvailThesSvcs.emplace();
+
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
+ uno::Reference< container::XEnumeration > xEnum;
+ if (xEnumAccess.is())
+ xEnum = xEnumAccess->createContentEnumeration( SN_THESAURUS );
+
+ if (!xEnum.is())
+ return;
+
+ while (xEnum->hasMoreElements())
+ {
+ uno::Any aCurrent = xEnum->nextElement();
+ uno::Reference< lang::XSingleComponentFactory > xCompFactory;
+ uno::Reference< lang::XSingleServiceFactory > xFactory;
+
+ xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
+ if (!xCompFactory.is())
+ {
+ xFactory.set(aCurrent, css::uno::UNO_QUERY);
+ }
+ if ( xCompFactory.is() || xFactory.is() )
+ {
+ try
+ {
+ uno::Reference< linguistic2::XThesaurus > xSvc( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY_THROW );
+
+ OUString aImplName;
+ std::vector< LanguageType > aLanguages;
+ uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
+ if (xInfo.is())
+ aImplName = xInfo->getImplementationName();
+ SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
+ uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
+ aLanguages = LocaleSeqToLangVec( aLocaleSequence );
+
+ pAvailThesSvcs->push_back( SvcInfo( aImplName, std::move(aLanguages) ) );
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+ }
+ }
+}
+
+
+void LngSvcMgr::SetCfgServiceLists( SpellCheckerDispatcher &rSpellDsp )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Spell" );
+
+ OUString aNode("ServiceManager/SpellCheckerList");
+ uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
+
+ // append path prefix need for 'GetProperties' call below
+ OUString aPrefix = aNode + "/";
+ for (OUString & name : asNonConstRange(aNames))
+ {
+ name = aPrefix + name;
+ }
+
+ const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
+ if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
+ return;
+
+ const OUString *pNames = aNames.getConstArray();
+ for (const uno::Any& rValue : aValues)
+ {
+ uno::Sequence< OUString > aSvcImplNames;
+ if (rValue >>= aSvcImplNames)
+ {
+ OUString aLocaleStr( *pNames++ );
+ sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
+ aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
+ rSpellDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
+ }
+ }
+}
+
+
+void LngSvcMgr::SetCfgServiceLists( GrammarCheckingIterator &rGrammarDsp )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Grammar" );
+
+ OUString aNode("ServiceManager/GrammarCheckerList");
+ uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
+
+ // append path prefix need for 'GetProperties' call below
+ OUString aPrefix = aNode + "/";
+ for (OUString & name : asNonConstRange(aNames))
+ {
+ name = aPrefix + name;
+ }
+
+ const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
+ if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
+ return;
+
+ const OUString *pNames = aNames.getConstArray();
+ for (const uno::Any& rValue : aValues)
+ {
+ uno::Sequence< OUString > aSvcImplNames;
+ if (rValue >>= aSvcImplNames)
+ {
+ // there should only be one grammar checker in use per language...
+ if (aSvcImplNames.getLength() > 1)
+ aSvcImplNames.realloc(1);
+
+ OUString aLocaleStr( *pNames++ );
+ sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
+ aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
+ rGrammarDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
+ }
+ }
+}
+
+
+void LngSvcMgr::SetCfgServiceLists( HyphenatorDispatcher &rHyphDsp )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Hyph" );
+
+ OUString aNode("ServiceManager/HyphenatorList");
+ uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
+
+ // append path prefix need for 'GetProperties' call below
+ OUString aPrefix = aNode + "/";
+ for (OUString & name : asNonConstRange(aNames))
+ {
+ name = aPrefix + name;
+ }
+
+ const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
+ if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
+ return;
+
+ const OUString *pNames = aNames.getConstArray();
+ for (const uno::Any& rValue : aValues)
+ {
+ uno::Sequence< OUString > aSvcImplNames;
+ if (rValue >>= aSvcImplNames)
+ {
+ // there should only be one hyphenator in use per language...
+ if (aSvcImplNames.getLength() > 1)
+ aSvcImplNames.realloc(1);
+
+ OUString aLocaleStr( *pNames++ );
+ sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
+ aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
+ rHyphDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
+ }
+ }
+}
+
+
+void LngSvcMgr::SetCfgServiceLists( ThesaurusDispatcher &rThesDsp )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Thes" );
+
+ OUString aNode("ServiceManager/ThesaurusList");
+ uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
+
+ // append path prefix need for 'GetProperties' call below
+ OUString aPrefix = aNode + "/";
+ for (OUString & name : asNonConstRange(aNames))
+ {
+ name = aPrefix + name;
+ }
+
+ const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
+ if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
+ return;
+
+ const OUString *pNames = aNames.getConstArray();
+ for (const uno::Any& rValue : aValues)
+ {
+ uno::Sequence< OUString > aSvcImplNames;
+ if (rValue >>= aSvcImplNames)
+ {
+ OUString aLocaleStr( *pNames++ );
+ sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
+ aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
+ rThesDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
+ }
+ }
+}
+
+
+uno::Reference< linguistic2::XSpellChecker > SAL_CALL
+ LngSvcMgr::getSpellChecker()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+#if OSL_DEBUG_LEVEL > 0
+ getAvailableLocales(SN_SPELLCHECKER);
+#endif
+
+ uno::Reference< linguistic2::XSpellChecker > xRes;
+ if (!bDisposing)
+ {
+ if (!mxSpellDsp.is())
+ GetSpellCheckerDsp_Impl();
+ xRes = mxSpellDsp.get();
+ }
+ return xRes;
+}
+
+
+uno::Reference< linguistic2::XHyphenator > SAL_CALL
+ LngSvcMgr::getHyphenator()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+#if OSL_DEBUG_LEVEL > 0
+ getAvailableLocales(SN_HYPHENATOR);
+#endif
+ uno::Reference< linguistic2::XHyphenator > xRes;
+ if (!bDisposing)
+ {
+ if (!mxHyphDsp.is())
+ GetHyphenatorDsp_Impl();
+ xRes = mxHyphDsp.get();
+ }
+ return xRes;
+}
+
+
+uno::Reference< linguistic2::XThesaurus > SAL_CALL
+ LngSvcMgr::getThesaurus()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+#if OSL_DEBUG_LEVEL > 0
+ getAvailableLocales(SN_THESAURUS);
+#endif
+ uno::Reference< linguistic2::XThesaurus > xRes;
+ if (!bDisposing)
+ {
+ if (!mxThesDsp.is())
+ GetThesaurusDsp_Impl();
+ xRes = mxThesDsp.get();
+ }
+ return xRes;
+}
+
+
+sal_Bool SAL_CALL
+ LngSvcMgr::addLinguServiceManagerListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing || !xListener.is())
+ return false;
+
+ if (!mxListenerHelper.is())
+ GetListenerHelper_Impl();
+ mxListenerHelper->AddLngSvcMgrListener( xListener );
+ return true;
+}
+
+
+sal_Bool SAL_CALL
+ LngSvcMgr::removeLinguServiceManagerListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (bDisposing || !xListener.is())
+ return false;
+
+ DBG_ASSERT( mxListenerHelper.is(), "listener removed without being added" );
+ if (!mxListenerHelper.is())
+ GetListenerHelper_Impl();
+ mxListenerHelper->RemoveLngSvcMgrListener( xListener );
+ return true;
+}
+
+
+uno::Sequence< OUString > SAL_CALL
+ LngSvcMgr::getAvailableServices(
+ const OUString& rServiceName,
+ const lang::Locale& rLocale )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Sequence< OUString > aRes;
+ const SvcInfoArray *pInfoArray = nullptr;
+
+ if (rServiceName == SN_SPELLCHECKER)
+ {
+ GetAvailableSpellSvcs_Impl();
+ pInfoArray = &*pAvailSpellSvcs;
+ }
+ else if (rServiceName == SN_GRAMMARCHECKER)
+ {
+ GetAvailableGrammarSvcs_Impl();
+ pInfoArray = &*pAvailGrammarSvcs;
+ }
+ else if (rServiceName == SN_HYPHENATOR)
+ {
+ GetAvailableHyphSvcs_Impl();
+ pInfoArray = &*pAvailHyphSvcs;
+ }
+ else if (rServiceName == SN_THESAURUS)
+ {
+ GetAvailableThesSvcs_Impl();
+ pInfoArray = &*pAvailThesSvcs;
+ }
+
+ if (pInfoArray)
+ {
+ std::vector<OUString> aVec;
+ aVec.reserve(pInfoArray->size());
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ for (const auto& rInfo : *pInfoArray)
+ {
+ if (LinguIsUnspecified( nLanguage )
+ || rInfo.HasLanguage( nLanguage ))
+ {
+ aVec.push_back(rInfo.aSvcImplName);
+ }
+ }
+
+ aRes = comphelper::containerToSequence(aVec);
+ }
+
+ return aRes;
+}
+
+
+uno::Sequence< lang::Locale > SAL_CALL
+ LngSvcMgr::getAvailableLocales(
+ const OUString& rServiceName )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Sequence< lang::Locale > aRes;
+
+ uno::Sequence< lang::Locale > *pAvailLocales = nullptr;
+ if (rServiceName == SN_SPELLCHECKER)
+ pAvailLocales = &aAvailSpellLocales;
+ else if (rServiceName == SN_GRAMMARCHECKER)
+ pAvailLocales = &aAvailGrammarLocales;
+ else if (rServiceName == SN_HYPHENATOR)
+ pAvailLocales = &aAvailHyphLocales;
+ else if (rServiceName == SN_THESAURUS)
+ pAvailLocales = &aAvailThesLocales;
+
+ // Nowadays (with OOo lingu in SO) we want to know immediately about
+ // new downloaded dictionaries and have them ready right away if the Tools/Options...
+ // is used to activate them. Thus we can not rely anymore on buffered data.
+ if (pAvailLocales)
+ {
+ *pAvailLocales = GetAvailLocales(getAvailableServices(rServiceName, lang::Locale()));
+ aRes = *pAvailLocales;
+ }
+
+ return aRes;
+}
+
+static bool IsEqSvcList( const uno::Sequence< OUString > &rList1,
+ const uno::Sequence< OUString > &rList2 )
+{
+ // returns true if both sequences are equal
+ return rList1.getLength() == rList2.getLength()
+ && std::equal(rList1.begin(), rList1.end(), rList2.begin(), rList2.end());
+}
+
+
+void SAL_CALL
+ LngSvcMgr::setConfiguredServices(
+ const OUString& rServiceName,
+ const lang::Locale& rLocale,
+ const uno::Sequence< OUString >& rServiceImplNames )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::setConfiguredServices" );
+
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ if (LinguIsUnspecified( nLanguage))
+ return;
+
+ if (rServiceName == SN_SPELLCHECKER)
+ {
+ if (!mxSpellDsp.is())
+ GetSpellCheckerDsp_Impl();
+ bool bChanged = !IsEqSvcList( rServiceImplNames,
+ mxSpellDsp->GetServiceList( rLocale ) );
+ if (bChanged)
+ {
+ mxSpellDsp->SetServiceList( rLocale, rServiceImplNames );
+ SaveCfgSvcs( SN_SPELLCHECKER );
+
+ if (mxListenerHelper)
+ mxListenerHelper->AddLngSvcEvt(
+ linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN |
+ linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN );
+ }
+ }
+ else if (rServiceName == SN_GRAMMARCHECKER)
+ {
+ if (!mxGrammarDsp.is())
+ GetGrammarCheckerDsp_Impl();
+ bool bChanged = !IsEqSvcList( rServiceImplNames,
+ mxGrammarDsp->GetServiceList( rLocale ) );
+ if (bChanged)
+ {
+ mxGrammarDsp->SetServiceList( rLocale, rServiceImplNames );
+ SaveCfgSvcs( SN_GRAMMARCHECKER );
+
+ if (mxListenerHelper)
+ mxListenerHelper->AddLngSvcEvt(
+ linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN );
+ }
+ }
+ else if (rServiceName == SN_HYPHENATOR)
+ {
+ if (!mxHyphDsp.is())
+ GetHyphenatorDsp_Impl();
+ bool bChanged = !IsEqSvcList( rServiceImplNames,
+ mxHyphDsp->GetServiceList( rLocale ) );
+ if (bChanged)
+ {
+ mxHyphDsp->SetServiceList( rLocale, rServiceImplNames );
+ SaveCfgSvcs( SN_HYPHENATOR );
+
+ if (mxListenerHelper)
+ mxListenerHelper->AddLngSvcEvt(
+ linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN );
+ }
+ }
+ else if (rServiceName == SN_THESAURUS)
+ {
+ if (!mxThesDsp.is())
+ GetThesaurusDsp_Impl();
+ bool bChanged = !IsEqSvcList( rServiceImplNames,
+ mxThesDsp->GetServiceList( rLocale ) );
+ if (bChanged)
+ {
+ mxThesDsp->SetServiceList( rLocale, rServiceImplNames );
+ SaveCfgSvcs( SN_THESAURUS );
+ }
+ }
+}
+
+
+bool LngSvcMgr::SaveCfgSvcs( std::u16string_view rServiceName )
+{
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SaveCfgSvcs" );
+
+ bool bRes = false;
+
+ LinguDispatcher *pDsp = nullptr;
+ uno::Sequence< lang::Locale > aLocales;
+
+ if (rServiceName == SN_SPELLCHECKER)
+ {
+ if (!mxSpellDsp)
+ GetSpellCheckerDsp_Impl();
+ pDsp = mxSpellDsp.get();
+ aLocales = getAvailableLocales( SN_SPELLCHECKER );
+ }
+ else if (rServiceName == SN_GRAMMARCHECKER)
+ {
+ if (!mxGrammarDsp.is())
+ GetGrammarCheckerDsp_Impl();
+ pDsp = mxGrammarDsp.get();
+ aLocales = getAvailableLocales( SN_GRAMMARCHECKER );
+ }
+ else if (rServiceName == SN_HYPHENATOR)
+ {
+ if (!mxHyphDsp.is())
+ GetHyphenatorDsp_Impl();
+ pDsp = mxHyphDsp.get();
+ aLocales = getAvailableLocales( SN_HYPHENATOR );
+ }
+ else if (rServiceName == SN_THESAURUS)
+ {
+ if (!mxThesDsp.is())
+ GetThesaurusDsp_Impl();
+ pDsp = mxThesDsp.get();
+ aLocales = getAvailableLocales( SN_THESAURUS );
+ }
+
+ if (pDsp && aLocales.hasElements())
+ {
+ uno::Sequence< beans::PropertyValue > aValues( aLocales.getLength() );
+ beans::PropertyValue *pValue = aValues.getArray();
+
+ // get node name to be used
+ const char *pNodeName = nullptr;
+ if (pDsp == mxSpellDsp.get())
+ pNodeName = "ServiceManager/SpellCheckerList";
+ else if (pDsp == mxGrammarDsp.get())
+ pNodeName = "ServiceManager/GrammarCheckerList";
+ else if (pDsp == mxHyphDsp.get())
+ pNodeName = "ServiceManager/HyphenatorList";
+ else if (pDsp == mxThesDsp.get())
+ pNodeName = "ServiceManager/ThesaurusList";
+ else
+ {
+ SAL_WARN( "linguistic", "node name missing" );
+ }
+ OUString aNodeName( OUString::createFromAscii(pNodeName) );
+
+ for (const lang::Locale& rLocale : std::as_const(aLocales))
+ {
+ uno::Sequence< OUString > aSvcImplNames = pDsp->GetServiceList( rLocale );
+
+ // build value to be written back to configuration
+ uno::Any aCfgAny;
+ if ((pDsp == mxHyphDsp.get() || pDsp == mxGrammarDsp.get()) && aSvcImplNames.getLength() > 1)
+ aSvcImplNames.realloc(1); // there should be only one entry for hyphenators or grammar checkers (because they are not chained)
+ aCfgAny <<= aSvcImplNames;
+ DBG_ASSERT( aCfgAny.hasValue(), "missing value for 'Any' type" );
+
+ OUString aCfgLocaleStr( LanguageTag::convertToBcp47( rLocale));
+ pValue->Value = aCfgAny;
+ pValue->Name = aNodeName + "/" + aCfgLocaleStr;
+ pValue++;
+ }
+ {
+ SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SaveCfgSvcs - ReplaceSetProperties" );
+ // change, add new or replace existing entries.
+ bRes |= /*aCfg.*/ReplaceSetProperties( aNodeName, aValues );
+ }
+ }
+
+ return bRes;
+}
+
+
+static uno::Sequence< OUString > GetLangSvcList( const uno::Any &rVal )
+{
+ uno::Sequence< OUString > aRes;
+
+ if (rVal.hasValue())
+ {
+ rVal >>= aRes;
+#if OSL_DEBUG_LEVEL > 0
+ for (const OUString& rSvcName : std::as_const(aRes))
+ {
+ SAL_WARN_IF( rSvcName.isEmpty(), "linguistic", "service impl-name missing" );
+ }
+#endif
+ }
+
+ return aRes;
+}
+
+
+static uno::Sequence< OUString > GetLangSvc( const uno::Any &rVal )
+{
+ uno::Sequence< OUString > aRes;
+ if (!rVal.hasValue())
+ return aRes;
+
+ // allowing for a sequence here as well (even though it should only
+ // be a string) makes coding easier in other places since one needs
+ // not make a special case for writing a string only and not a
+ // sequence of strings.
+ if (rVal >>= aRes)
+ {
+ // but only the first string should be used.
+ if (aRes.getLength() > 1)
+ aRes.realloc(1);
+ }
+ else
+ {
+ OUString aImplName;
+ if ((rVal >>= aImplName) && !aImplName.isEmpty())
+ {
+ aRes.realloc(1);
+ aRes.getArray()[0] = aImplName;
+ }
+ else
+ {
+ SAL_WARN( "linguistic", "GetLangSvc: unexpected type encountered" );
+ }
+ }
+
+ return aRes;
+}
+
+
+uno::Sequence< OUString > SAL_CALL
+ LngSvcMgr::getConfiguredServices(
+ const OUString& rServiceName,
+ const lang::Locale& rLocale )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Sequence< OUString > aSvcImplNames;
+
+ OUString aCfgLocale( LanguageTag::convertToBcp47( rLocale) );
+
+ uno::Sequence< uno::Any > aValues;
+ uno::Sequence< OUString > aNames( 1 );
+ OUString *pNames = aNames.getArray();
+ if ( rServiceName == SN_SPELLCHECKER )
+ {
+ OUString aNode( "ServiceManager/SpellCheckerList");
+ const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
+ if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
+ {
+ pNames[0] = aNode + "/" + aCfgLocale;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
+ }
+ }
+ else if ( rServiceName == SN_GRAMMARCHECKER )
+ {
+ OUString aNode( "ServiceManager/GrammarCheckerList");
+ const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
+ if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
+ {
+ pNames[0] = aNode + "/" + aCfgLocale;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
+ }
+ }
+ else if ( rServiceName == SN_HYPHENATOR )
+ {
+ OUString aNode( "ServiceManager/HyphenatorList");
+ const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
+ if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
+ {
+ pNames[0] = aNode + "/" + aCfgLocale;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
+ }
+ }
+ else if ( rServiceName == SN_THESAURUS )
+ {
+ OUString aNode( "ServiceManager/ThesaurusList");
+ const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
+ if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
+ {
+ pNames[0] = aNode + "/" + aCfgLocale;
+ aValues = /*aCfg.*/GetProperties( aNames );
+ if (aValues.hasElements())
+ aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
+ }
+ }
+
+ return aSvcImplNames;
+}
+
+
+void SAL_CALL
+ LngSvcMgr::dispose()
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing)
+ {
+ bDisposing = true;
+
+ // require listeners to release this object
+ lang::EventObject aEvtObj( static_cast<XLinguServiceManager*>(this) );
+ aEvtListeners.disposeAndClear( aEvtObj );
+
+ if (mxListenerHelper.is())
+ mxListenerHelper->DisposeAndClear( aEvtObj );
+ }
+}
+
+
+void SAL_CALL
+ LngSvcMgr::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (!bDisposing && xListener.is())
+ {
+ aEvtListeners.addInterface( xListener );
+ }
+}
+
+
+void SAL_CALL
+ LngSvcMgr::removeEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ osl::MutexGuard aGuard( GetLinguMutex() );
+
+ if (xListener.is())
+ {
+ aEvtListeners.removeInterface( xListener );
+ }
+}
+
+
+bool LngSvcMgr::AddLngSvcEvtBroadcaster(
+ const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
+{
+ if (!rxBroadcaster.is())
+ return false;
+ if (!mxListenerHelper.is())
+ GetListenerHelper_Impl();
+ mxListenerHelper->AddLngSvcEvtBroadcaster( rxBroadcaster );
+ return true;
+}
+
+
+OUString SAL_CALL
+ LngSvcMgr::getImplementationName()
+{
+ return "com.sun.star.lingu2.LngSvcMgr";
+}
+
+
+sal_Bool SAL_CALL
+ LngSvcMgr::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+
+uno::Sequence< OUString > SAL_CALL
+ LngSvcMgr::getSupportedServiceNames()
+{
+ return { "com.sun.star.linguistic2.LinguServiceManager" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+linguistic_LngSvcMgr_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new LngSvcMgr());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/lngsvcmgr.hxx b/linguistic/source/lngsvcmgr.hxx
new file mode 100644
index 000000000..76cb7d182
--- /dev/null
+++ b/linguistic/source/lngsvcmgr.hxx
@@ -0,0 +1,167 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_LNGSVCMGR_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_LNGSVCMGR_HXX
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/linguistic2/XLinguServiceManager2.hpp>
+#include <com/sun/star/util/XModifyBroadcaster.hpp>
+#include <com/sun/star/util/XModifyListener.hpp>
+#include <unotools/configitem.hxx>
+#include <rtl/ref.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vector>
+#include <optional>
+
+class SpellCheckerDispatcher;
+class HyphenatorDispatcher;
+class ThesaurusDispatcher;
+class GrammarCheckingIterator;
+class LngSvcMgrListenerHelper;
+struct SvcInfo;
+
+namespace com::sun::star::linguistic2 {
+ class XLinguServiceEventBroadcaster;
+ class XSpellChecker;
+ class XProofreadingIterator;
+ class XHyphenator;
+ class XThesaurus;
+}
+
+
+class LngSvcMgr :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XLinguServiceManager2,
+ css::lang::XServiceInfo,
+ css::util::XModifyListener
+ >,
+ private utl::ConfigItem
+{
+ friend class LngSvcMgrListenerHelper;
+
+ ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener> aEvtListeners;
+
+ css::uno::Reference<
+ css::util::XModifyBroadcaster> xMB;
+
+ Idle aUpdateIdle;
+
+
+ css::uno::Sequence<
+ css::lang::Locale > aAvailSpellLocales;
+ css::uno::Sequence<
+ css::lang::Locale > aAvailGrammarLocales;
+ css::uno::Sequence<
+ css::lang::Locale > aAvailHyphLocales;
+ css::uno::Sequence<
+ css::lang::Locale > aAvailThesLocales;
+
+ rtl::Reference<SpellCheckerDispatcher> mxSpellDsp;
+ rtl::Reference<GrammarCheckingIterator> mxGrammarDsp;
+ rtl::Reference<HyphenatorDispatcher> mxHyphDsp;
+ rtl::Reference<ThesaurusDispatcher> mxThesDsp;
+
+ rtl::Reference<LngSvcMgrListenerHelper> mxListenerHelper;
+
+ typedef std::vector< SvcInfo > SvcInfoArray;
+ std::optional<SvcInfoArray> pAvailSpellSvcs;
+ std::optional<SvcInfoArray> pAvailGrammarSvcs;
+ std::optional<SvcInfoArray> pAvailHyphSvcs;
+ std::optional<SvcInfoArray> pAvailThesSvcs;
+
+ bool bDisposing;
+
+ LngSvcMgr(const LngSvcMgr &) = delete;
+ LngSvcMgr & operator = (const LngSvcMgr &) = delete;
+
+ void GetAvailableSpellSvcs_Impl();
+ void GetAvailableGrammarSvcs_Impl();
+ void GetAvailableHyphSvcs_Impl();
+ void GetAvailableThesSvcs_Impl();
+ void GetListenerHelper_Impl();
+
+ void GetSpellCheckerDsp_Impl( bool bSetSvcList = true );
+ void GetGrammarCheckerDsp_Impl( bool bSetSvcList = true );
+ void GetHyphenatorDsp_Impl( bool bSetSvcList = true );
+ void GetThesaurusDsp_Impl( bool bSetSvcList = true );
+
+ void SetCfgServiceLists( SpellCheckerDispatcher &rSpellDsp );
+ void SetCfgServiceLists( GrammarCheckingIterator &rGrammarDsp );
+ void SetCfgServiceLists( HyphenatorDispatcher &rHyphDsp );
+ void SetCfgServiceLists( ThesaurusDispatcher &rThesDsp );
+
+ bool SaveCfgSvcs( std::u16string_view rServiceName );
+
+ // utl::ConfigItem (to allow for listening of changes of relevant properties)
+ virtual void Notify( const css::uno::Sequence< OUString > &rPropertyNames ) override;
+ virtual void ImplCommit() override;
+
+ void UpdateAll();
+ void stopListening();
+ DECL_LINK( updateAndBroadcast, Timer*, void );
+
+public:
+ LngSvcMgr();
+ virtual ~LngSvcMgr() override;
+
+ // XLinguServiceManager
+ virtual css::uno::Reference< css::linguistic2::XSpellChecker > SAL_CALL getSpellChecker( ) override;
+ virtual css::uno::Reference< css::linguistic2::XHyphenator > SAL_CALL getHyphenator( ) override;
+ virtual css::uno::Reference< css::linguistic2::XThesaurus > SAL_CALL getThesaurus( ) override;
+ virtual sal_Bool SAL_CALL addLinguServiceManagerListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual sal_Bool SAL_CALL removeLinguServiceManagerListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getAvailableServices( const OUString& aServiceName, const css::lang::Locale& aLocale ) override;
+ virtual void SAL_CALL setConfiguredServices( const OUString& aServiceName, const css::lang::Locale& aLocale, const css::uno::Sequence< OUString >& aServiceImplNames ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getConfiguredServices( const OUString& aServiceName, const css::lang::Locale& aLocale ) override;
+
+ // XAvailableLocales
+ virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getAvailableLocales( const OUString& aServiceName ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose( ) override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& rSource ) override;
+
+ // XModifyListener
+ virtual void SAL_CALL modified( const css::lang::EventObject& rEvent ) override;
+
+ bool AddLngSvcEvtBroadcaster(
+ const css::uno::Reference< css::linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster );
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/misc.cxx b/linguistic/source/misc.cxx
new file mode 100644
index 000000000..de2bc9686
--- /dev/null
+++ b/linguistic/source/misc.cxx
@@ -0,0 +1,761 @@
+/* -*- 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 file754
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <sal/log.hxx>
+#include <svl/lngmisc.hxx>
+#include <ucbhelper/content.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValues.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/linguistic2/DictionaryType.hpp>
+#include <com/sun/star/linguistic2/DictionaryList.hpp>
+#include <com/sun/star/linguistic2/LinguProperties.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/linguprops.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <svtools/strings.hrc>
+#include <unotools/resmgr.hxx>
+
+#include <linguistic/misc.hxx>
+#include <linguistic/hyphdta.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::i18n;
+using namespace com::sun::star::linguistic2;
+
+namespace linguistic
+{
+
+//!! multi-thread safe mutex for all platforms !!
+osl::Mutex & GetLinguMutex()
+{
+ static osl::Mutex SINGLETON;
+ return SINGLETON;
+}
+
+const LocaleDataWrapper & GetLocaleDataWrapper( LanguageType nLang )
+{
+ static std::unique_ptr<LocaleDataWrapper> xLclDtaWrp;
+ if (!xLclDtaWrp || xLclDtaWrp->getLoadedLanguageTag().getLanguageType() != nLang)
+ xLclDtaWrp.reset(new LocaleDataWrapper(LanguageTag( nLang )));
+ return *xLclDtaWrp;
+}
+
+LanguageType LinguLocaleToLanguage( const css::lang::Locale& rLocale )
+{
+ if ( rLocale.Language.isEmpty() )
+ return LANGUAGE_NONE;
+ return LanguageTag::convertToLanguageType( rLocale );
+}
+
+css::lang::Locale LinguLanguageToLocale( LanguageType nLanguage )
+{
+ if (nLanguage == LANGUAGE_NONE)
+ return css::lang::Locale();
+ return LanguageTag::convertToLocale( nLanguage);
+}
+
+bool LinguIsUnspecified( LanguageType nLanguage )
+{
+ return nLanguage.anyOf(
+ LANGUAGE_NONE,
+ LANGUAGE_UNDETERMINED,
+ LANGUAGE_MULTIPLE);
+}
+
+// When adding anything keep both LinguIsUnspecified() methods in sync!
+// For mappings between language code string and LanguageType see
+// i18nlangtag/source/isolang/isolang.cxx
+
+bool LinguIsUnspecified( std::u16string_view rBcp47 )
+{
+ if (rBcp47.size() != 3)
+ return false;
+ return rBcp47 == u"zxx" || rBcp47 == u"und" || rBcp47 == u"mul";
+}
+
+static sal_Int32 Minimum( sal_Int32 n1, sal_Int32 n2, sal_Int32 n3 )
+{
+ return std::min(std::min(n1, n2), n3);
+}
+
+namespace {
+
+class IntArray2D
+{
+private:
+ std::unique_ptr<sal_Int32[]> pData;
+ int n1, n2;
+
+public:
+ IntArray2D( int nDim1, int nDim2 );
+
+ sal_Int32 & Value( int i, int k );
+};
+
+}
+
+IntArray2D::IntArray2D( int nDim1, int nDim2 )
+{
+ n1 = nDim1;
+ n2 = nDim2;
+ pData.reset( new sal_Int32[n1 * n2] );
+}
+
+sal_Int32 & IntArray2D::Value( int i, int k )
+{
+ assert( (0 <= i && i < n1) && "first index out of range" );
+ assert( (0 <= k && k < n2) && "second index out of range" );
+ assert( (i * n2 + k < n1 * n2) && "index out of range" );
+ return pData[ i * n2 + k ];
+}
+
+sal_Int32 LevDistance( std::u16string_view rTxt1, std::u16string_view rTxt2 )
+{
+ sal_Int32 nLen1 = rTxt1.size();
+ sal_Int32 nLen2 = rTxt2.size();
+
+ if (nLen1 == 0)
+ return nLen2;
+ if (nLen2 == 0)
+ return nLen1;
+
+ IntArray2D aData( nLen1 + 1, nLen2 + 1 );
+
+ sal_Int32 i, k;
+ for (i = 0; i <= nLen1; ++i)
+ aData.Value(i, 0) = i;
+ for (k = 0; k <= nLen2; ++k)
+ aData.Value(0, k) = k;
+ for (i = 1; i <= nLen1; ++i)
+ {
+ for (k = 1; k <= nLen2; ++k)
+ {
+ sal_Unicode c1i = rTxt1[i - 1];
+ sal_Unicode c2k = rTxt2[k - 1];
+ sal_Int32 nCost = c1i == c2k ? 0 : 1;
+ sal_Int32 nNew = Minimum( aData.Value(i-1, k ) + 1,
+ aData.Value(i , k-1) + 1,
+ aData.Value(i-1, k-1) + nCost );
+ // take transposition (exchange with left or right char) in account
+ if (2 < i && 2 < k)
+ {
+ int nT = aData.Value(i-2, k-2) + 1;
+ if (rTxt1[i - 2] != c1i)
+ ++nT;
+ if (rTxt2[k - 2] != c2k)
+ ++nT;
+ if (nT < nNew)
+ nNew = nT;
+ }
+
+ aData.Value(i, k) = nNew;
+ }
+ }
+ sal_Int32 nDist = aData.Value(nLen1, nLen2);
+ return nDist;
+}
+
+bool IsUseDicList( const PropertyValues &rProperties,
+ const uno::Reference< XPropertySet > &rxProp )
+{
+ bool bRes = true;
+
+ const PropertyValue *pVal = std::find_if(rProperties.begin(), rProperties.end(),
+ [](const PropertyValue& rVal) { return UPH_IS_USE_DICTIONARY_LIST == rVal.Handle; });
+
+ if (pVal != rProperties.end())
+ {
+ pVal->Value >>= bRes;
+ }
+ else // no temporary value found in 'rProperties'
+ {
+ uno::Reference< XFastPropertySet > xFast( rxProp, UNO_QUERY );
+ if (xFast.is())
+ xFast->getFastPropertyValue( UPH_IS_USE_DICTIONARY_LIST ) >>= bRes;
+ }
+
+ return bRes;
+}
+
+bool IsIgnoreControlChars( const PropertyValues &rProperties,
+ const uno::Reference< XPropertySet > &rxProp )
+{
+ bool bRes = true;
+
+ const PropertyValue *pVal = std::find_if(rProperties.begin(), rProperties.end(),
+ [](const PropertyValue& rVal) { return UPH_IS_IGNORE_CONTROL_CHARACTERS == rVal.Handle; });
+
+ if (pVal != rProperties.end())
+ {
+ pVal->Value >>= bRes;
+ }
+ else // no temporary value found in 'rProperties'
+ {
+ uno::Reference< XFastPropertySet > xFast( rxProp, UNO_QUERY );
+ if (xFast.is())
+ xFast->getFastPropertyValue( UPH_IS_IGNORE_CONTROL_CHARACTERS ) >>= bRes;
+ }
+
+ return bRes;
+}
+
+static bool lcl_HasHyphInfo( const uno::Reference<XDictionaryEntry> &xEntry )
+{
+ bool bRes = false;
+ if (xEntry.is())
+ {
+ // there has to be (at least one) '=' or '[' denoting a hyphenation position
+ // and it must not be before any character of the word
+ sal_Int32 nIdx = xEntry->getDictionaryWord().indexOf( '=' );
+ if (nIdx == -1)
+ nIdx = xEntry->getDictionaryWord().indexOf( '[' );
+ bRes = nIdx != -1 && nIdx != 0;
+ }
+ return bRes;
+}
+
+uno::Reference< XDictionaryEntry > SearchDicList(
+ const uno::Reference< XSearchableDictionaryList > &xDicList,
+ const OUString &rWord, LanguageType nLanguage,
+ bool bSearchPosDics, bool bSearchSpellEntry )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ uno::Reference< XDictionaryEntry > xEntry;
+
+ if (!xDicList.is())
+ return xEntry;
+
+ const uno::Sequence< uno::Reference< XDictionary > >
+ aDics( xDicList->getDictionaries() );
+ const uno::Reference< XDictionary >
+ *pDic = aDics.getConstArray();
+ sal_Int32 nDics = xDicList->getCount();
+
+ sal_Int32 i;
+ for (i = 0; i < nDics; i++)
+ {
+ uno::Reference< XDictionary > axDic = pDic[i];
+
+ DictionaryType eType = axDic->getDictionaryType();
+ LanguageType nLang = LinguLocaleToLanguage( axDic->getLocale() );
+
+ if ( axDic.is() && axDic->isActive()
+ && (nLang == nLanguage || LinguIsUnspecified( nLang)) )
+ {
+ // DictionaryType_MIXED is deprecated
+ SAL_WARN_IF(eType == DictionaryType_MIXED, "linguistic", "unexpected dictionary type");
+
+ if ( (!bSearchPosDics && eType == DictionaryType_NEGATIVE)
+ || ( bSearchPosDics && eType == DictionaryType_POSITIVE))
+ {
+ xEntry = axDic->getEntry( rWord );
+ if ( xEntry.is() && (bSearchSpellEntry || lcl_HasHyphInfo( xEntry )) )
+ break;
+ xEntry = nullptr;
+ }
+ }
+ }
+
+ return xEntry;
+}
+
+bool SaveDictionaries( const uno::Reference< XSearchableDictionaryList > &xDicList )
+{
+ if (!xDicList.is())
+ return true;
+
+ bool bRet = true;
+
+ const Sequence< uno::Reference< XDictionary > > aDics( xDicList->getDictionaries() );
+ for (const uno::Reference<XDictionary>& rDic : aDics)
+ {
+ try
+ {
+ uno::Reference< frame::XStorable > xStor( rDic, UNO_QUERY );
+ if (xStor.is())
+ {
+ if (!xStor->isReadonly() && xStor->hasLocation())
+ xStor->store();
+ }
+ }
+ catch(uno::Exception &)
+ {
+ bRet = false;
+ }
+ }
+
+ return bRet;
+}
+
+DictionaryError AddEntryToDic(
+ uno::Reference< XDictionary > const &rxDic,
+ const OUString &rWord, bool bIsNeg,
+ const OUString &rRplcTxt,
+ bool bStripDot )
+{
+ if (!rxDic.is())
+ return DictionaryError::NOT_EXISTS;
+
+ OUString aTmp( rWord );
+ if (bStripDot)
+ {
+ sal_Int32 nLen = rWord.getLength();
+ if (nLen > 0 && '.' == rWord[ nLen - 1])
+ {
+ // remove trailing '.'
+ // (this is the official way to do this :-( )
+ aTmp = aTmp.copy( 0, nLen - 1 );
+ }
+ }
+ bool bAddOk = rxDic->add( aTmp, bIsNeg, rRplcTxt );
+
+ DictionaryError nRes = DictionaryError::NONE;
+ if (!bAddOk)
+ {
+ if (rxDic->isFull())
+ nRes = DictionaryError::FULL;
+ else
+ {
+ uno::Reference< frame::XStorable > xStor( rxDic, UNO_QUERY );
+ if (xStor.is() && xStor->isReadonly())
+ nRes = DictionaryError::READONLY;
+ else
+ nRes = DictionaryError::UNKNOWN;
+ }
+ }
+
+ return nRes;
+}
+
+std::vector< LanguageType >
+ LocaleSeqToLangVec( uno::Sequence< Locale > const &rLocaleSeq )
+{
+ std::vector< LanguageType > aLangs;
+ aLangs.reserve(rLocaleSeq.getLength());
+
+ std::transform(rLocaleSeq.begin(), rLocaleSeq.end(), std::back_inserter(aLangs),
+ [](const Locale& rLocale) { return LinguLocaleToLanguage(rLocale); });
+
+ return aLangs;
+}
+
+uno::Sequence< sal_Int16 >
+ LocaleSeqToLangSeq( uno::Sequence< Locale > const &rLocaleSeq )
+{
+ std::vector<sal_Int16> aLangs;
+ aLangs.reserve(rLocaleSeq.getLength());
+
+ std::transform(rLocaleSeq.begin(), rLocaleSeq.end(), std::back_inserter(aLangs),
+ [](const Locale& rLocale) { return static_cast<sal_uInt16>(LinguLocaleToLanguage(rLocale)); });
+
+ return comphelper::containerToSequence(aLangs);
+}
+bool IsReadOnly( const OUString &rURL, bool *pbExist )
+{
+ bool bRes = false;
+ bool bExists = false;
+
+ if (!rURL.isEmpty())
+ {
+ try
+ {
+ uno::Reference< css::ucb::XCommandEnvironment > xCmdEnv;
+ ::ucbhelper::Content aContent( rURL, xCmdEnv, comphelper::getProcessComponentContext() );
+
+ bExists = aContent.isDocument();
+ if (bExists)
+ {
+ Any aAny( aContent.getPropertyValue( "IsReadOnly" ) );
+ aAny >>= bRes;
+ }
+ }
+ catch (Exception &)
+ {
+ bRes = true;
+ }
+ }
+
+ if (pbExist)
+ *pbExist = bExists;
+ return bRes;
+}
+
+static bool GetAltSpelling( sal_Int16 &rnChgPos, sal_Int16 &rnChgLen, OUString &rRplc,
+ uno::Reference< XHyphenatedWord > const &rxHyphWord )
+{
+ bool bRes = rxHyphWord->isAlternativeSpelling();
+ if (bRes)
+ {
+ OUString aWord( rxHyphWord->getWord() ),
+ aHyphenatedWord( rxHyphWord->getHyphenatedWord() );
+ sal_Int16 nHyphenationPos = rxHyphWord->getHyphenationPos();
+ /*sal_Int16 nHyphenPos = rxHyphWord->getHyphenPos()*/;
+ const sal_Unicode *pWord = aWord.getStr(),
+ *pAltWord = aHyphenatedWord.getStr();
+
+ // at least char changes directly left or right to the hyphen
+ // should(!) be handled properly...
+ //! nHyphenationPos and nHyphenPos differ at most by 1 (see above)
+ //! Beware: eg "Schiffahrt" in German (pre spelling reform)
+ //! proves to be a bit nasty (nChgPosLeft and nChgPosRight overlap
+ //! to an extend.)
+
+ // find first different char from left
+ sal_Int32 nPosL = 0,
+ nAltPosL = 0;
+ for (sal_Int16 i = 0 ; pWord[ nPosL ] == pAltWord[ nAltPosL ]; nPosL++, nAltPosL++, i++)
+ {
+ // restrict changes area beginning to the right to
+ // the char immediately following the hyphen.
+ //! serves to insert the additional "f" in "Schiffahrt" at
+ //! position 5 rather than position 6.
+ if (i >= nHyphenationPos + 1)
+ break;
+ }
+
+ // find first different char from right
+ sal_Int32 nPosR = aWord.getLength() - 1,
+ nAltPosR = aHyphenatedWord.getLength() - 1;
+ for ( ; nPosR >= nPosL && nAltPosR >= nAltPosL
+ && pWord[ nPosR ] == pAltWord[ nAltPosR ];
+ nPosR--, nAltPosR--)
+ ;
+
+ rnChgPos = sal::static_int_cast< sal_Int16 >(nPosL);
+ rnChgLen = sal::static_int_cast< sal_Int16 >(nAltPosR - nPosL);
+ assert( rnChgLen >= 0 && "nChgLen < 0");
+
+ sal_Int32 nTxtStart = nPosL;
+ sal_Int32 nTxtLen = nAltPosR - nPosL + 1;
+ rRplc = aHyphenatedWord.copy( nTxtStart, nTxtLen );
+ }
+ return bRes;
+}
+
+static sal_Int16 GetOrigWordPos( std::u16string_view rOrigWord, sal_Int16 nPos )
+{
+ sal_Int32 nLen = rOrigWord.size();
+ sal_Int32 i = -1;
+ while (nPos >= 0 && i++ < nLen)
+ {
+ sal_Unicode cChar = rOrigWord[i];
+ bool bSkip = IsHyphen( cChar ) || IsControlChar( cChar );
+ if (!bSkip)
+ --nPos;
+ }
+ return sal::static_int_cast< sal_Int16 >((0 <= i && i < nLen) ? i : -1);
+}
+
+sal_Int32 GetPosInWordToCheck( std::u16string_view rTxt, sal_Int32 nPos )
+{
+ sal_Int32 nRes = -1;
+ sal_Int32 nLen = rTxt.size();
+ if (0 <= nPos && nPos < nLen)
+ {
+ nRes = 0;
+ for (sal_Int32 i = 0; i < nPos; ++i)
+ {
+ sal_Unicode cChar = rTxt[i];
+ bool bSkip = IsHyphen( cChar ) || IsControlChar( cChar );
+ if (!bSkip)
+ ++nRes;
+ }
+ }
+ return nRes;
+}
+
+uno::Reference< XHyphenatedWord > RebuildHyphensAndControlChars(
+ const OUString &rOrigWord,
+ uno::Reference< XHyphenatedWord > const &rxHyphWord )
+{
+ uno::Reference< XHyphenatedWord > xRes;
+ if (!rOrigWord.isEmpty() && rxHyphWord.is())
+ {
+ sal_Int16 nChgPos = 0,
+ nChgLen = 0;
+ OUString aRplc;
+ bool bAltSpelling = GetAltSpelling( nChgPos, nChgLen, aRplc, rxHyphWord );
+
+ OUString aOrigHyphenatedWord;
+ sal_Int16 nOrigHyphenPos = -1;
+ sal_Int16 nOrigHyphenationPos = -1;
+ if (!bAltSpelling)
+ {
+ aOrigHyphenatedWord = rOrigWord;
+ nOrigHyphenPos = GetOrigWordPos( rOrigWord, rxHyphWord->getHyphenPos() );
+ nOrigHyphenationPos = GetOrigWordPos( rOrigWord, rxHyphWord->getHyphenationPos() );
+ }
+ else
+ {
+ //! should at least work with the German words
+ //! B-"u-c-k-er and Sc-hif-fah-rt
+
+ sal_Int16 nPos = GetOrigWordPos( rOrigWord, nChgPos );
+
+ // get words like Sc-hif-fah-rt to work correct
+ sal_Int16 nHyphenationPos = rxHyphWord->getHyphenationPos();
+ if (nChgPos > nHyphenationPos)
+ --nPos;
+
+ std::u16string_view aLeft = rOrigWord.subView( 0, nPos );
+ std::u16string_view aRight = rOrigWord.subView( nPos ); // FIXME: changes at the right side
+
+ aOrigHyphenatedWord = aLeft + aRplc + aRight;
+
+ nOrigHyphenPos = sal::static_int_cast< sal_Int16 >(aLeft.size() +
+ rxHyphWord->getHyphenPos() - nChgPos);
+ nOrigHyphenationPos = GetOrigWordPos( rOrigWord, nHyphenationPos );
+ }
+
+ if (nOrigHyphenPos == -1 || nOrigHyphenationPos == -1)
+ {
+ SAL_WARN( "linguistic", "failed to get nOrigHyphenPos or nOrigHyphenationPos" );
+ }
+ else
+ {
+ LanguageType nLang = LinguLocaleToLanguage( rxHyphWord->getLocale() );
+ xRes = new HyphenatedWord(
+ rOrigWord, nLang, nOrigHyphenationPos,
+ aOrigHyphenatedWord, nOrigHyphenPos );
+ }
+
+ }
+ return xRes;
+}
+
+bool IsUpper( const OUString &rText, sal_Int32 nPos, sal_Int32 nLen, LanguageType nLanguage )
+{
+ CharClass aCC(( LanguageTag( nLanguage ) ));
+ sal_Int32 nFlags = aCC.getStringType( rText, nPos, nLen );
+ return (nFlags & KCharacterType::UPPER)
+ && !(nFlags & KCharacterType::LOWER);
+}
+
+CapType capitalType(const OUString& aTerm, CharClass const * pCC)
+{
+ sal_Int32 tlen = aTerm.getLength();
+ if (!pCC || !tlen)
+ return CapType::UNKNOWN;
+
+ sal_Int32 nc = 0;
+ for (sal_Int32 tindex = 0; tindex < tlen; ++tindex)
+ {
+ if (pCC->getCharacterType(aTerm,tindex) &
+ css::i18n::KCharacterType::UPPER) nc++;
+ }
+
+ if (nc == 0)
+ return CapType::NOCAP;
+ if (nc == tlen)
+ return CapType::ALLCAP;
+ if ((nc == 1) && (pCC->getCharacterType(aTerm,0) &
+ css::i18n::KCharacterType::UPPER))
+ return CapType::INITCAP;
+
+ return CapType::MIXED;
+}
+
+// sorted(!) array of unicode ranges for code points that are exclusively(!) used as numbers
+// and thus may NOT not be part of names or words like the Chinese/Japanese number characters
+const sal_uInt32 the_aDigitZeroes [] =
+{
+ 0x00000030, //0039 ; Decimal # Nd [10] DIGIT ZERO..DIGIT NINE
+ 0x00000660, //0669 ; Decimal # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE
+ 0x000006F0, //06F9 ; Decimal # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE
+ 0x000007C0, //07C9 ; Decimal # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE
+ 0x00000966, //096F ; Decimal # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE
+ 0x000009E6, //09EF ; Decimal # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE
+ 0x00000A66, //0A6F ; Decimal # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE
+ 0x00000AE6, //0AEF ; Decimal # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE
+ 0x00000B66, //0B6F ; Decimal # Nd [10] ODIA DIGIT ZERO..ODIA DIGIT NINE
+ 0x00000BE6, //0BEF ; Decimal # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE
+ 0x00000C66, //0C6F ; Decimal # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE
+ 0x00000CE6, //0CEF ; Decimal # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE
+ 0x00000D66, //0D6F ; Decimal # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE
+ 0x00000E50, //0E59 ; Decimal # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE
+ 0x00000ED0, //0ED9 ; Decimal # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE
+ 0x00000F20, //0F29 ; Decimal # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE
+ 0x00001040, //1049 ; Decimal # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE
+ 0x00001090, //1099 ; Decimal # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE
+ 0x000017E0, //17E9 ; Decimal # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE
+ 0x00001810, //1819 ; Decimal # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE
+ 0x00001946, //194F ; Decimal # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE
+ 0x000019D0, //19D9 ; Decimal # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE
+ 0x00001B50, //1B59 ; Decimal # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE
+ 0x00001BB0, //1BB9 ; Decimal # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE
+ 0x00001C40, //1C49 ; Decimal # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE
+ 0x00001C50, //1C59 ; Decimal # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE
+ 0x0000A620, //A629 ; Decimal # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE
+ 0x0000A8D0, //A8D9 ; Decimal # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE
+ 0x0000A900, //A909 ; Decimal # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE
+ 0x0000AA50, //AA59 ; Decimal # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE
+ 0x0000FF10, //FF19 ; Decimal # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE
+ 0x000104A0, //104A9 ; Decimal # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE
+ 0x0001D7CE //1D7FF ; Decimal # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE
+};
+
+bool HasDigits( const OUString &rText )
+{
+ const sal_Int32 nLen = rText.getLength();
+
+ sal_Int32 i = 0;
+ while (i < nLen) // for all characters ...
+ {
+ const sal_uInt32 nCodePoint = rText.iterateCodePoints( &i ); // handle unicode surrogates correctly...
+ for (unsigned int nDigitZero : the_aDigitZeroes) // ... check in all 0..9 ranges
+ {
+ if (nDigitZero > nCodePoint)
+ break;
+ if (/*nDigitZero <= nCodePoint &&*/ nCodePoint <= nDigitZero + 9)
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IsNumeric( std::u16string_view rText )
+{
+ bool bRes = false;
+ if (!rText.empty())
+ {
+ sal_Int32 nLen = rText.size();
+ bRes = true;
+ for(sal_Int32 i = 0; i < nLen; ++i)
+ {
+ sal_Unicode cChar = rText[ i ];
+ if ( '0' > cChar || cChar > '9' )
+ {
+ bRes = false;
+ break;
+ }
+ }
+ }
+ return bRes;
+}
+
+uno::Reference< XLinguProperties > GetLinguProperties()
+{
+ return LinguProperties::create( comphelper::getProcessComponentContext() );
+}
+
+uno::Reference< XSearchableDictionaryList > GetDictionaryList()
+{
+ uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ uno::Reference< XSearchableDictionaryList > xRef;
+ try
+ {
+ xRef = DictionaryList::create(xContext);
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+
+ return xRef;
+}
+
+uno::Reference< XDictionary > GetIgnoreAllList()
+{
+ uno::Reference< XDictionary > xRes;
+ uno::Reference< XSearchableDictionaryList > xDL( GetDictionaryList() );
+ if (xDL.is())
+ {
+ std::locale loc(Translate::Create("svt"));
+ xRes = xDL->getDictionaryByName( Translate::get(STR_DESCRIPTION_IGNOREALLLIST, loc) );
+ }
+ return xRes;
+}
+
+AppExitListener::AppExitListener()
+{
+ // add object to Desktop EventListeners in order to properly call
+ // the AtExit function at application exit.
+ uno::Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
+
+ try
+ {
+ xDesktop = frame::Desktop::create(xContext);
+ }
+ catch (const uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstance failed" );
+ }
+}
+
+AppExitListener::~AppExitListener()
+{
+}
+
+void AppExitListener::Activate()
+{
+ if (xDesktop.is())
+ xDesktop->addTerminateListener( this );
+}
+
+void AppExitListener::Deactivate()
+{
+ if (xDesktop.is())
+ xDesktop->removeTerminateListener( this );
+}
+
+void SAL_CALL
+ AppExitListener::disposing( const EventObject& rEvtSource )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (xDesktop.is() && rEvtSource.Source == xDesktop)
+ {
+ xDesktop = nullptr; //! release reference to desktop
+ }
+}
+
+void SAL_CALL
+ AppExitListener::queryTermination( const EventObject& /*rEvtSource*/ )
+{
+}
+
+void SAL_CALL
+ AppExitListener::notifyTermination( const EventObject& rEvtSource )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (xDesktop.is() && rEvtSource.Source == xDesktop)
+ {
+ AtExit();
+ }
+}
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/misc2.cxx b/linguistic/source/misc2.cxx
new file mode 100644
index 000000000..86a81e5a1
--- /dev/null
+++ b/linguistic/source/misc2.cxx
@@ -0,0 +1,168 @@
+/* -*- 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 <sal/config.h>
+
+#include <string_view>
+
+#include <tools/urlobj.hxx>
+#include <ucbhelper/content.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/util/thePathSettings.hpp>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <linguistic/misc.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+/// Flags to be used with the multi-path related functions
+/// @see GetDictionaryPaths
+enum class DictionaryPathFlags
+{
+ NONE = 0x00,
+ INTERNAL = 0x01,
+ USER = 0x02,
+};
+
+}
+
+namespace o3tl
+{
+ template<> struct typed_flags<DictionaryPathFlags> : is_typed_flags<DictionaryPathFlags, 0x03> {};
+}
+#define PATH_FLAG_ALL (DictionaryPathFlags::INTERNAL | DictionaryPathFlags::USER)
+
+namespace linguistic
+{
+
+
+bool FileExists( const OUString &rMainURL )
+{
+ bool bExists = false;
+ if (!rMainURL.isEmpty())
+ {
+ try
+ {
+ ::ucbhelper::Content aContent( rMainURL,
+ uno::Reference< css::ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext());
+ bExists = aContent.isDocument();
+ }
+ catch (uno::Exception &)
+ {
+ }
+ }
+ return bExists;
+}
+
+static std::vector< OUString > GetMultiPaths_Impl(
+ std::u16string_view rPathPrefix,
+ DictionaryPathFlags nPathFlags )
+{
+ std::vector< OUString > aRes;
+ uno::Sequence< OUString > aInternalPaths;
+ uno::Sequence< OUString > aUserPaths;
+ OUString aWritablePath;
+
+ bool bSuccess = true;
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
+ try
+ {
+ OUString aInternal( OUString::Concat(rPathPrefix) + "_internal" );
+ OUString aUser( OUString::Concat(rPathPrefix) + "_user" );
+ OUString aWriteable( OUString::Concat(rPathPrefix) + "_writable" );
+
+ uno::Reference< util::XPathSettings > xPathSettings =
+ util::thePathSettings::get( xContext );
+ xPathSettings->getPropertyValue( aInternal ) >>= aInternalPaths;
+ xPathSettings->getPropertyValue( aUser ) >>= aUserPaths;
+ xPathSettings->getPropertyValue( aWriteable ) >>= aWritablePath;
+ }
+ catch (uno::Exception &)
+ {
+ bSuccess = false;
+ }
+ if (bSuccess)
+ {
+ // build resulting sequence by adding the paths in the following order:
+ // 1. writable path
+ // 2. all user paths
+ // 3. all internal paths
+ sal_Int32 nMaxEntries = aInternalPaths.getLength() + aUserPaths.getLength();
+ if (!aWritablePath.isEmpty())
+ ++nMaxEntries;
+ aRes.reserve( nMaxEntries );
+ if (!aWritablePath.isEmpty())
+ aRes.push_back(aWritablePath);
+
+ auto lPathIsNotEmpty = [](const OUString& rPath) { return !rPath.isEmpty(); };
+
+ if (nPathFlags & DictionaryPathFlags::USER)
+ std::copy_if(std::cbegin(aUserPaths), std::cend(aUserPaths), std::back_inserter(aRes), lPathIsNotEmpty);
+
+ if (nPathFlags & DictionaryPathFlags::INTERNAL)
+ std::copy_if(std::cbegin(aInternalPaths), std::cend(aInternalPaths), std::back_inserter(aRes), lPathIsNotEmpty);
+ }
+
+ return aRes;
+}
+
+OUString GetDictionaryWriteablePath()
+{
+ std::vector< OUString > aPaths(
+ GetMultiPaths_Impl( u"Dictionary", DictionaryPathFlags::NONE ) );
+ DBG_ASSERT( aPaths.size() == 1, "Dictionary_writable path corrupted?" );
+ OUString aRes;
+ if (!aPaths.empty())
+ aRes = aPaths[0];
+ return aRes;
+}
+
+std::vector< OUString > GetDictionaryPaths()
+{
+ return GetMultiPaths_Impl( u"Dictionary", PATH_FLAG_ALL );
+}
+
+OUString GetWritableDictionaryURL( std::u16string_view rDicName )
+{
+ // new user writable dictionaries should be created in the 'writable' path
+ OUString aDirName( GetDictionaryWriteablePath() );
+
+ // build URL to use for a new (persistent) dictionary
+ INetURLObject aURLObj;
+ aURLObj.SetSmartProtocol( INetProtocol::File );
+ aURLObj.SetSmartURL( aDirName );
+ DBG_ASSERT(!aURLObj.HasError(), "lng : invalid URL");
+ aURLObj.Append( rDicName, INetURLObject::EncodeMechanism::All );
+ DBG_ASSERT(!aURLObj.HasError(), "lng : invalid URL");
+
+ // DecodeMechanism::NONE preserves the escape sequences that might be included in aDirName
+ // depending on the characters used in the path string. (Needed when comparing
+ // the dictionary URL with GetDictionaryWriteablePath in DicList::createDictionary.)
+ return aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+}
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/spelldsp.cxx b/linguistic/source/spelldsp.cxx
new file mode 100644
index 000000000..c1b309b00
--- /dev/null
+++ b/linguistic/source/spelldsp.cxx
@@ -0,0 +1,829 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/linguistic2/XLinguServiceEventBroadcaster.hpp>
+#include <com/sun/star/linguistic2/SpellFailure.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <unotools/localedatawrapper.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <tools/debug.hxx>
+#include <svl/lngmisc.hxx>
+#include <osl/mutex.hxx>
+#include <sal/log.hxx>
+
+#include <vector>
+
+#include "spelldsp.hxx"
+#include <linguistic/spelldta.hxx>
+#include "lngsvcmgr.hxx"
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+namespace {
+
+// ProposalList: list of proposals for misspelled words
+// The order of strings in the array should be left unchanged because the
+// spellchecker should have put the more likely suggestions at the top.
+// New entries will be added to the end but duplicates are to be avoided.
+// Removing entries is done by assigning the empty string.
+// The sequence is constructed from all non empty strings in the original
+// while maintaining the order.
+class ProposalList
+{
+ std::vector< OUString > aVec;
+
+ bool HasEntry( std::u16string_view rText ) const;
+
+public:
+ ProposalList() {}
+ ProposalList(const ProposalList&) = delete;
+ ProposalList& operator=(const ProposalList&) = delete;
+
+ size_t Count() const;
+ void Prepend( const OUString &rText );
+ void Append( const OUString &rNew );
+ void Append( const std::vector< OUString > &rNew );
+ void Append( const Sequence< OUString > &rNew );
+ std::vector< OUString > GetVector() const;
+};
+
+}
+
+bool ProposalList::HasEntry( std::u16string_view rText ) const
+{
+ bool bFound = false;
+ size_t nCnt = aVec.size();
+ for (size_t i = 0; !bFound && i < nCnt; ++i)
+ {
+ if (aVec[i] == rText)
+ bFound = true;
+ }
+ return bFound;
+}
+
+void ProposalList::Prepend( const OUString &rText )
+{
+ if (!HasEntry( rText ))
+ aVec.insert( aVec.begin(), rText );
+}
+
+void ProposalList::Append( const OUString &rText )
+{
+ if (!HasEntry( rText ))
+ aVec.push_back( rText );
+}
+
+void ProposalList::Append( const std::vector< OUString > &rNew )
+{
+ size_t nLen = rNew.size();
+ for ( size_t i = 0; i < nLen; ++i)
+ {
+ const OUString &rText = rNew[i];
+ if (!HasEntry( rText ))
+ Append( rText );
+ }
+}
+
+void ProposalList::Append( const Sequence< OUString > &rNew )
+{
+ for (const OUString& rText : rNew)
+ {
+ if (!HasEntry( rText ))
+ Append( rText );
+ }
+}
+
+size_t ProposalList::Count() const
+{
+ // returns the number of non-empty strings in the vector
+
+ size_t nRes = 0;
+ size_t nLen = aVec.size();
+ for (size_t i = 0; i < nLen; ++i)
+ {
+ if (!aVec[i].isEmpty())
+ ++nRes;
+ }
+ return nRes;
+}
+
+std::vector< OUString > ProposalList::GetVector() const
+{
+ sal_Int32 nCount = Count();
+ sal_Int32 nIdx = 0;
+ std::vector< OUString > aRes( nCount );
+ sal_Int32 nLen = aVec.size();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ const OUString &rText = aVec[i];
+ DBG_ASSERT( nIdx < nCount, "index out of range" );
+ if (nIdx < nCount && !rText.isEmpty())
+ aRes[ nIdx++ ] = rText;
+ }
+ return aRes;
+}
+
+static bool SvcListHasLanguage(
+ const LangSvcEntries_Spell &rEntry,
+ LanguageType nLanguage )
+{
+ Locale aTmpLocale = LanguageTag::convertToLocale( nLanguage );
+
+ return std::any_of(rEntry.aSvcRefs.begin(), rEntry.aSvcRefs.end(),
+ [&aTmpLocale](const Reference<XSpellChecker>& rRef) {
+ return rRef.is() && rRef->hasLocale( aTmpLocale ); });
+}
+
+SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
+ m_rMgr (rLngSvcMgr)
+{
+}
+
+
+SpellCheckerDispatcher::~SpellCheckerDispatcher()
+{
+}
+
+
+Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector<Locale> aLocales;
+ aLocales.reserve(m_aSvcMap.size());
+
+ std::transform(m_aSvcMap.begin(), m_aSvcMap.end(), std::back_inserter(aLocales),
+ [](SpellSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
+
+ return comphelper::containerToSequence(aLocales);
+}
+
+
+sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
+ return aIt != m_aSvcMap.end();
+}
+
+
+sal_Bool SAL_CALL
+ SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return isValid_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
+}
+
+
+Reference< XSpellAlternatives > SAL_CALL
+ SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return spell_Impl( rWord, LinguLocaleToLanguage( rLocale ), rProperties );
+}
+
+
+// returns the overall result of cross-checking with all user-dictionaries
+// including the IgnoreAll list
+static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
+ const OUString &rWord,
+ LanguageType nLanguage )
+{
+ Reference< XDictionaryEntry > xRes;
+
+ // the order of winning from top to bottom is:
+ // 1) IgnoreAll list will always win
+ // 2) Negative dictionaries will win over positive dictionaries
+ Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
+ if (xIgnoreAll.is())
+ xRes = xIgnoreAll->getEntry( rWord );
+ if (!xRes.is())
+ {
+ Reference< XSearchableDictionaryList > xDList( GetDictionaryList() );
+ Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
+ rWord, nLanguage, false, true ) );
+ if (xNegEntry.is())
+ xRes = xNegEntry;
+ else
+ {
+ Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
+ rWord, nLanguage, true, true ) );
+ if (xPosEntry.is())
+ xRes = xPosEntry;
+ }
+ }
+
+ return xRes;
+}
+
+
+bool SpellCheckerDispatcher::isValid_Impl(
+ const OUString& rWord,
+ LanguageType nLanguage,
+ const PropertyValues& rProperties)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ bool bRes = true;
+
+ if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
+ return bRes;
+
+ // search for entry with that language
+ SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ if (pEntry)
+ {
+ OUString aChkWord( rWord );
+ Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
+
+ // replace typographical apostroph by ascii apostroph
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
+
+ RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ RemoveControlChars( aChkWord );
+
+ sal_Int32 nLen = pEntry->aSvcRefs.getLength();
+ DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
+ "lng : sequence length mismatch");
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+ bool bTmpRes = true;
+ bool bTmpResValid = false;
+
+ // try already instantiated services first
+ {
+ const Reference< XSpellChecker > *pRef =
+ pEntry->aSvcRefs.getConstArray();
+ while (i <= pEntry->nLastTriedSvcIndex
+ && (!bTmpResValid || !bTmpRes))
+ {
+ bTmpResValid = true;
+ if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
+ {
+ bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
+ if (!bTmpRes)
+ {
+ bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
+
+ // Add correct words to the cache.
+ // But not those that are correct only because of
+ // the temporary supplied settings.
+ if (bTmpRes && !rProperties.hasElements())
+ GetCache().AddWord( aChkWord, nLanguage );
+ }
+ }
+ else
+ bTmpResValid = false;
+
+ if (bTmpResValid)
+ bRes = bTmpRes;
+
+ ++i;
+ }
+ }
+
+ // if still no result instantiate new services and try those
+ if ((!bTmpResValid || !bTmpRes)
+ && pEntry->nLastTriedSvcIndex < nLen - 1)
+ {
+ const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
+ Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ while (i < nLen && (!bTmpResValid || !bTmpRes))
+ {
+ // create specific service via it's implementation name
+ Reference< XSpellChecker > xSpell;
+ try
+ {
+ xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pImplNames[i], aArgs, xContext ),
+ UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+ pRef [i] = xSpell;
+
+ Reference< XLinguServiceEventBroadcaster >
+ xBroadcaster( xSpell, UNO_QUERY );
+ if (xBroadcaster.is())
+ m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
+
+ bTmpResValid = true;
+ if (xSpell.is() && xSpell->hasLocale( aLocale ))
+ {
+ bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
+ if (!bTmpRes)
+ {
+ bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
+ // Add correct words to the cache.
+ // But not those that are correct only because of
+ // the temporary supplied settings.
+ if (bTmpRes && !rProperties.hasElements())
+ GetCache().AddWord( aChkWord, nLanguage );
+ }
+ }
+ else
+ bTmpResValid = false;
+ if (bTmpResValid)
+ bRes = bTmpRes;
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+ }
+
+ // if language is not supported by any of the services
+ // remove it from the list.
+ if (i == nLen)
+ {
+ if (!SvcListHasLanguage( *pEntry, nLanguage ))
+ m_aSvcMap.erase( nLanguage );
+ }
+ }
+
+ // cross-check against results from dictionaries which have precedence!
+ if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
+ {
+ Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
+ if (xTmp.is()) {
+ bRes = !xTmp->isNegative();
+ } else {
+ setCharClass(LanguageTag(nLanguage));
+ CapType ct = capitalType(aChkWord, m_pCharClass.get());
+ if (ct == CapType::INITCAP || ct == CapType::ALLCAP) {
+ Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_pCharClass.get()), nLanguage ) );
+ if (xTmp2.is()) {
+ bRes = !xTmp2->isNegative();
+ }
+ }
+ }
+ }
+ }
+
+ return bRes;
+}
+
+
+Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl(
+ const OUString& rWord,
+ LanguageType nLanguage,
+ const PropertyValues& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Reference< XSpellAlternatives > xRes;
+
+ if (LinguIsUnspecified( nLanguage) || rWord.isEmpty())
+ return xRes;
+
+ // search for entry with that language
+ SpellSvcByLangMap_t::iterator aIt( m_aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ if (pEntry)
+ {
+ OUString aChkWord( rWord );
+ Locale aLocale( LanguageTag::convertToLocale( nLanguage ) );
+
+ // replace typographical apostroph by ascii apostroph
+ OUString aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
+ DBG_ASSERT( 1 == aSingleQuote.getLength(), "unexpected length of quotation mark" );
+ if (!aSingleQuote.isEmpty())
+ aChkWord = aChkWord.replace( aSingleQuote[0], '\'' );
+
+ RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ RemoveControlChars( aChkWord );
+
+ sal_Int32 nLen = pEntry->aSvcRefs.getLength();
+ DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
+ "lng : sequence length mismatch");
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+ Reference< XSpellAlternatives > xTmpRes;
+ bool bTmpResValid = false;
+
+ // try already instantiated services first
+ {
+ const Reference< XSpellChecker > *pRef = pEntry->aSvcRefs.getConstArray();
+ sal_Int32 nNumSuggestions = -1;
+ while (i <= pEntry->nLastTriedSvcIndex
+ && (!bTmpResValid || xTmpRes.is()) )
+ {
+ bTmpResValid = true;
+ if (pRef[i].is() && pRef[i]->hasLocale( aLocale ))
+ {
+ bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
+ if (bOK)
+ xTmpRes = nullptr;
+ else
+ {
+ xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
+
+ // Add correct words to the cache.
+ // But not those that are correct only because of
+ // the temporary supplied settings.
+ if (!xTmpRes.is() && !rProperties.hasElements())
+ GetCache().AddWord( aChkWord, nLanguage );
+ }
+ }
+ else
+ bTmpResValid = false;
+
+ // return first found result if the word is not known by any checker.
+ // But if that result has no suggestions use the first one that does
+ // provide suggestions for the misspelled word.
+ if (!xRes.is() && bTmpResValid)
+ {
+ xRes = xTmpRes;
+ nNumSuggestions = 0;
+ if (xRes.is())
+ nNumSuggestions = xRes->getAlternatives().getLength();
+ }
+ sal_Int32 nTmpNumSuggestions = 0;
+ if (xTmpRes.is() && bTmpResValid)
+ nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
+ if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
+ {
+ xRes = xTmpRes;
+ nNumSuggestions = nTmpNumSuggestions;
+ }
+
+ ++i;
+ }
+ }
+
+ // if still no result instantiate new services and try those
+ if ((!bTmpResValid || xTmpRes.is())
+ && pEntry->nLastTriedSvcIndex < nLen - 1)
+ {
+ const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
+ Reference< XSpellChecker > *pRef = pEntry->aSvcRefs .getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(2);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ sal_Int32 nNumSuggestions = -1;
+ while (i < nLen && (!bTmpResValid || xTmpRes.is()))
+ {
+ // create specific service via it's implementation name
+ Reference< XSpellChecker > xSpell;
+ try
+ {
+ xSpell.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pImplNames[i], aArgs, xContext ),
+ UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+ pRef [i] = xSpell;
+
+ Reference< XLinguServiceEventBroadcaster >
+ xBroadcaster( xSpell, UNO_QUERY );
+ if (xBroadcaster.is())
+ m_rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
+
+ bTmpResValid = true;
+ if (xSpell.is() && xSpell->hasLocale( aLocale ))
+ {
+ bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
+ if (bOK)
+ xTmpRes = nullptr;
+ else
+ {
+ xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
+
+ // Add correct words to the cache.
+ // But not those that are correct only because of
+ // the temporary supplied settings.
+ if (!xTmpRes.is() && !rProperties.hasElements())
+ GetCache().AddWord( aChkWord, nLanguage );
+ }
+ }
+ else
+ bTmpResValid = false;
+
+ // return first found result if the word is not known by any checker.
+ // But if that result has no suggestions use the first one that does
+ // provide suggestions for the misspelled word.
+ if (!xRes.is() && bTmpResValid)
+ {
+ xRes = xTmpRes;
+ nNumSuggestions = 0;
+ if (xRes.is())
+ nNumSuggestions = xRes->getAlternatives().getLength();
+ }
+ sal_Int32 nTmpNumSuggestions = 0;
+ if (xTmpRes.is() && bTmpResValid)
+ nTmpNumSuggestions = xTmpRes->getAlternatives().getLength();
+ if (xRes.is() && nNumSuggestions == 0 && nTmpNumSuggestions > 0)
+ {
+ xRes = xTmpRes;
+ nNumSuggestions = nTmpNumSuggestions;
+ }
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+ }
+
+ // if language is not supported by any of the services
+ // remove it from the list.
+ if (i == nLen)
+ {
+ if (!SvcListHasLanguage( *pEntry, nLanguage ))
+ m_aSvcMap.erase( nLanguage );
+ }
+ }
+
+ // if word is finally found to be correct
+ // clear previously remembered alternatives
+ if (bTmpResValid && !xTmpRes.is())
+ xRes = nullptr;
+
+ // list of proposals found (to be checked against entries of
+ // negative dictionaries)
+ ProposalList aProposalList;
+ sal_Int16 eFailureType = -1; // no failure
+ if (xRes.is())
+ {
+ aProposalList.Append( xRes->getAlternatives() );
+ eFailureType = xRes->getFailureType();
+ }
+ Reference< XSearchableDictionaryList > xDList;
+ if (GetDicList().is() && IsUseDicList( rProperties, GetPropSet() ))
+ xDList = GetDicList();
+
+ // cross-check against results from user-dictionaries which have precedence!
+ if (xDList.is())
+ {
+ Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
+ if (xTmp.is())
+ {
+ if (xTmp->isNegative()) // negative entry found
+ {
+ eFailureType = SpellFailure::IS_NEGATIVE_WORD;
+
+ // replacement text to be added to suggestions, if not empty
+ OUString aAddRplcTxt( xTmp->getReplacementText() );
+
+ // replacement text must not be in negative dictionary itself
+ if (!aAddRplcTxt.isEmpty() &&
+ !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
+ {
+ aProposalList.Prepend( aAddRplcTxt );
+ }
+ }
+ else // positive entry found
+ {
+ xRes = nullptr;
+ eFailureType = -1; // no failure
+ }
+ }
+ else
+ {
+ setCharClass(LanguageTag(nLanguage));
+ CapType ct = capitalType(aChkWord, m_pCharClass.get());
+ if (ct == CapType::INITCAP || ct == CapType::ALLCAP)
+ {
+ Reference< XDictionaryEntry > xTmp2( lcl_GetRulingDictionaryEntry( makeLowerCase(aChkWord, m_pCharClass.get()), nLanguage ) );
+ if (xTmp2.is())
+ {
+ if (xTmp2->isNegative()) // negative entry found
+ {
+ eFailureType = SpellFailure::IS_NEGATIVE_WORD;
+
+ // replacement text to be added to suggestions, if not empty
+ OUString aAddRplcTxt( xTmp2->getReplacementText() );
+
+ // replacement text must not be in negative dictionary itself
+ if (!aAddRplcTxt.isEmpty() &&
+ !SearchDicList( xDList, aAddRplcTxt, nLanguage, false, true ).is())
+ {
+ switch ( ct )
+ {
+ case CapType::INITCAP:
+ aProposalList.Prepend( m_pCharClass->titlecase(aAddRplcTxt) );
+ break;
+ case CapType::ALLCAP:
+ aProposalList.Prepend( m_pCharClass->uppercase(aAddRplcTxt) );
+ break;
+ default:
+ /* can't happen because of if ct == above */
+ break;
+ }
+ }
+ }
+ else // positive entry found
+ {
+ xRes = nullptr;
+ eFailureType = -1; // no failure
+ }
+ }
+ }
+ }
+ }
+
+ if (eFailureType != -1) // word misspelled or found in negative user-dictionary
+ {
+ // search suitable user-dictionaries for suggestions that are
+ // similar to the misspelled word
+ std::vector< OUString > aDicListProps; // list of proposals from user-dictionaries
+ SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
+ aProposalList.Append( aDicListProps );
+ std::vector< OUString > aProposals = aProposalList.GetVector();
+
+ // remove entries listed in negative dictionaries
+ // (we don't want to display suggestions that will be regarded as misspelled later on)
+ if (xDList.is())
+ SeqRemoveNegEntries( aProposals, xDList, nLanguage );
+
+ uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
+ if (xSetAlt.is())
+ {
+ xSetAlt->setAlternatives( comphelper::containerToSequence(aProposals) );
+ xSetAlt->setFailureType( eFailureType );
+ }
+ else
+ {
+ if (xRes.is())
+ {
+ SAL_WARN( "linguistic", "XSetSpellAlternatives not implemented!" );
+ }
+ else if (!aProposals.empty())
+ {
+ // no xRes but Proposals found from the user-dictionaries.
+ // Thus we need to create an xRes...
+ xRes = new linguistic::SpellAlternatives( rWord, nLanguage,
+ comphelper::containerToSequence(aProposals) );
+ }
+ }
+ }
+ }
+
+ return xRes;
+}
+
+uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages( )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ uno::Sequence< Locale > aTmp( getLocales() );
+ uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
+ return aRes;
+}
+
+
+sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage(
+ sal_Int16 nLanguage )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return hasLocale( LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))));
+}
+
+
+sal_Bool SAL_CALL SpellCheckerDispatcher::isValid(
+ const OUString& rWord,
+ sal_Int16 nLanguage,
+ const uno::Sequence< beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return isValid( rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
+}
+
+
+uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell(
+ const OUString& rWord,
+ sal_Int16 nLanguage,
+ const uno::Sequence< beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return spell(rWord, LanguageTag::convertToLocale(LanguageType(static_cast<sal_uInt16>(nLanguage))), rProperties);
+}
+
+
+void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
+ const Sequence< OUString > &rSvcImplNames )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ if (m_pCache)
+ m_pCache->Flush(); // new services may spell differently...
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+
+ sal_Int32 nLen = rSvcImplNames.getLength();
+ if (0 == nLen)
+ // remove entry
+ m_aSvcMap.erase( nLanguage );
+ else
+ {
+ // modify/add entry
+ LangSvcEntries_Spell *pEntry = m_aSvcMap[ nLanguage ].get();
+ if (pEntry)
+ {
+ pEntry->Clear();
+ pEntry->aSvcImplNames = rSvcImplNames;
+ pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
+ }
+ else
+ {
+ auto pTmpEntry = std::make_shared<LangSvcEntries_Spell>( rSvcImplNames );
+ pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
+ m_aSvcMap[ nLanguage ] = pTmpEntry;
+ }
+ }
+}
+
+
+Sequence< OUString >
+ SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Sequence< OUString > aRes;
+
+ // search for entry with that language and use data from that
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ const SpellSvcByLangMap_t::const_iterator aIt( m_aSvcMap.find( nLanguage ) );
+ const LangSvcEntries_Spell *pEntry = aIt != m_aSvcMap.end() ? aIt->second.get() : nullptr;
+ if (pEntry)
+ aRes = pEntry->aSvcImplNames;
+
+ return aRes;
+}
+
+
+void SpellCheckerDispatcher::FlushSpellCache()
+{
+ if (m_pCache)
+ m_pCache->Flush();
+}
+
+void SpellCheckerDispatcher::setCharClass(const LanguageTag& rLanguageTag)
+{
+ if (m_pCharClass && m_pCharClass->getLanguageTag() == rLanguageTag)
+ return;
+ m_pCharClass.reset( new CharClass(rLanguageTag) );
+}
+
+
+OUString SpellCheckerDispatcher::makeLowerCase(const OUString& aTerm, CharClass const * pCC)
+{
+ if (pCC)
+ return pCC->lowercase(aTerm);
+ return aTerm;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/spelldsp.hxx b/linguistic/source/spelldsp.hxx
new file mode 100644
index 000000000..b617f7498
--- /dev/null
+++ b/linguistic/source/spelldsp.hxx
@@ -0,0 +1,141 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_SPELLDSP_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_SPELLDSP_HXX
+
+#include "defs.hxx"
+#include <linguistic/misc.hxx>
+#include <iprcache.hxx>
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/beans/PropertyValues.hpp>
+#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
+#include <com/sun/star/linguistic2/XSpellChecker.hpp>
+#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
+
+#include <map>
+#include <memory>
+#include <unotools/charclass.hxx>
+
+class LngSvcMgr;
+
+
+class SpellCheckerDispatcher :
+ public cppu::WeakImplHelper
+ <
+ css::linguistic2::XSpellChecker1,
+ css::linguistic2::XSpellChecker
+ >,
+ public LinguDispatcher
+{
+ typedef std::shared_ptr< LangSvcEntries_Spell > LangSvcEntries_Spell_Ptr_t;
+ typedef std::map< LanguageType, LangSvcEntries_Spell_Ptr_t > SpellSvcByLangMap_t;
+ SpellSvcByLangMap_t m_aSvcMap;
+
+ css::uno::Reference< css::linguistic2::XLinguProperties > m_xPropSet;
+ css::uno::Reference< css::linguistic2::XSearchableDictionaryList > m_xDicList;
+
+ LngSvcMgr &m_rMgr;
+ mutable std::unique_ptr<linguistic::SpellCache> m_pCache; // Spell Cache (holds known words)
+ std::unique_ptr<CharClass> m_pCharClass;
+
+ SpellCheckerDispatcher(const SpellCheckerDispatcher &) = delete;
+ SpellCheckerDispatcher & operator = (const SpellCheckerDispatcher &) = delete;
+
+ inline linguistic::SpellCache & GetCache() const;
+
+ inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ GetPropSet();
+ inline const css::uno::Reference< css::linguistic2::XSearchableDictionaryList > &
+ GetDicList();
+
+ /// @throws css::uno::RuntimeException
+ /// @throws css::lang::IllegalArgumentException
+ bool isValid_Impl(const OUString& aWord, LanguageType nLanguage,
+ const css::beans::PropertyValues& aProperties);
+
+ /// @throws css::uno::RuntimeException
+ /// @throws css::lang::IllegalArgumentException
+ css::uno::Reference<
+ css::linguistic2::XSpellAlternatives >
+ spell_Impl(const OUString& aWord, LanguageType nLanguage,
+ const css::beans::PropertyValues& aProperties);
+
+public:
+ explicit SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr );
+ virtual ~SpellCheckerDispatcher() override;
+
+ // XSupportedLocales (for XSpellChecker)
+ virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getLocales() override;
+ virtual sal_Bool SAL_CALL hasLocale( const css::lang::Locale& aLocale ) override;
+
+ // XSpellChecker
+ virtual sal_Bool SAL_CALL isValid( const OUString& aWord, const css::lang::Locale& aLocale, const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+ virtual css::uno::Reference< css::linguistic2::XSpellAlternatives > SAL_CALL spell( const OUString& aWord, const css::lang::Locale& aLocale, const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+
+ // XSupportedLanguages
+ virtual css::uno::Sequence< ::sal_Int16 > SAL_CALL getLanguages( ) override;
+ virtual sal_Bool SAL_CALL hasLanguage( ::sal_Int16 nLanguage ) override;
+
+ // XSpellChecker1
+ virtual sal_Bool SAL_CALL isValid( const OUString& aWord, ::sal_Int16 nLanguage, const css::uno::Sequence< css::beans::PropertyValue >& aProperties ) override;
+ virtual css::uno::Reference< css::linguistic2::XSpellAlternatives > SAL_CALL spell( const OUString& aWord, ::sal_Int16 nLanguage, const css::uno::Sequence< css::beans::PropertyValue >& aProperties ) override;
+
+ // LinguDispatcher
+ virtual void SetServiceList( const css::lang::Locale &rLocale, const css::uno::Sequence< OUString > &rSvcImplNames ) override;
+ virtual css::uno::Sequence< OUString > GetServiceList( const css::lang::Locale &rLocale ) const override;
+
+ void FlushSpellCache();
+
+private:
+ void setCharClass(const LanguageTag& rLanguageTag);
+ static OUString makeLowerCase(const OUString&, CharClass const *);
+
+};
+
+inline linguistic::SpellCache & SpellCheckerDispatcher::GetCache() const
+{
+ if (!m_pCache)
+ m_pCache.reset(new linguistic::SpellCache());
+ return *m_pCache;
+}
+
+
+inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ SpellCheckerDispatcher::GetPropSet()
+{
+ if (!m_xPropSet.is())
+ m_xPropSet = linguistic::GetLinguProperties();
+ return m_xPropSet;
+}
+
+
+inline const css::uno::Reference< css::linguistic2::XSearchableDictionaryList > &
+ SpellCheckerDispatcher::GetDicList()
+{
+ if (!m_xDicList.is())
+ m_xDicList = linguistic::GetDictionaryList();
+ return m_xDicList;
+}
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/spelldta.cxx b/linguistic/source/spelldta.cxx
new file mode 100644
index 000000000..6aa99f8b7
--- /dev/null
+++ b/linguistic/source/spelldta.cxx
@@ -0,0 +1,270 @@
+/* -*- 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 <com/sun/star/uno/Reference.h>
+#include <com/sun/star/linguistic2/SpellFailure.hpp>
+#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
+#include <osl/mutex.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <linguistic/misc.hxx>
+#include <linguistic/spelldta.hxx>
+#include <rtl/ref.hxx>
+
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+
+
+namespace linguistic
+{
+
+#define MAX_PROPOSALS 40
+
+static bool SeqHasEntry(
+ const std::vector< OUString > &rSeq,
+ std::u16string_view rTxt)
+{
+ bool bRes = false;
+ sal_Int32 nLen = rSeq.size();
+ for (sal_Int32 i = 0; i < nLen && !bRes; ++i)
+ {
+ if (rTxt == rSeq[i])
+ bRes = true;
+ }
+ return bRes;
+}
+
+
+void SearchSimilarText( const OUString &rText, LanguageType nLanguage,
+ Reference< XSearchableDictionaryList > const &xDicList,
+ std::vector< OUString > & rDicListProps )
+{
+ if (!xDicList.is())
+ return;
+
+ const uno::Sequence< Reference< XDictionary > >
+ aDics( xDicList->getDictionaries() );
+ const Reference< XDictionary >
+ *pDic = aDics.getConstArray();
+ sal_Int32 nDics = xDicList->getCount();
+
+ for (sal_Int32 i = 0; i < nDics; i++)
+ {
+ Reference< XDictionary > xDic = pDic[i];
+
+ LanguageType nLang = LinguLocaleToLanguage( xDic->getLocale() );
+
+ if ( xDic.is() && xDic->isActive()
+ && (nLang == nLanguage || LinguIsUnspecified( nLang)) )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ DictionaryType eType = xDic->getDictionaryType();
+ (void) eType;
+ assert( eType != DictionaryType_MIXED && "unexpected dictionary type" );
+#endif
+ const Sequence< Reference< XDictionaryEntry > > aEntries = xDic->getEntries();
+ for (const Reference<XDictionaryEntry>& rEntry : aEntries)
+ {
+ OUString aEntryTxt;
+ if (rEntry.is())
+ {
+ // remove characters used to determine hyphenation positions
+ aEntryTxt = rEntry->getDictionaryWord().replaceAll("=", "");
+ }
+ if (!aEntryTxt.isEmpty() && aEntryTxt.getLength() > 1 && LevDistance( rText, aEntryTxt ) <= 2)
+ rDicListProps.push_back( aEntryTxt );
+ }
+ }
+ }
+}
+
+
+void SeqRemoveNegEntries( std::vector< OUString > &rSeq,
+ Reference< XSearchableDictionaryList > const &rxDicList,
+ LanguageType nLanguage )
+{
+ bool bSthRemoved = false;
+ sal_Int32 nLen = rSeq.size();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ Reference< XDictionaryEntry > xNegEntry( SearchDicList( rxDicList,
+ rSeq[i], nLanguage, false, true ) );
+ if (xNegEntry.is())
+ {
+ rSeq[i].clear();
+ bSthRemoved = true;
+ }
+ }
+ if (bSthRemoved)
+ {
+ std::vector< OUString > aNew;
+ // merge sequence without duplicates and empty strings in new empty sequence
+ rSeq = MergeProposalSeqs( aNew, rSeq );
+ }
+}
+
+
+std::vector< OUString > MergeProposalSeqs(
+ std::vector< OUString > &rAlt1,
+ std::vector< OUString > &rAlt2 )
+{
+ std::vector< OUString > aMerged;
+
+ size_t nAltCount1 = rAlt1.size();
+ size_t nAltCount2 = rAlt2.size();
+
+ sal_Int32 nCountNew = std::min<sal_Int32>( nAltCount1 + nAltCount2, sal_Int32(MAX_PROPOSALS) );
+ aMerged.resize( nCountNew );
+
+ sal_Int32 nIndex = 0;
+ sal_Int32 i = 0;
+ for (int j = 0; j < 2; j++)
+ {
+ sal_Int32 nCount = j == 0 ? nAltCount1 : nAltCount2;
+ std::vector< OUString >& rAlt = j == 0 ? rAlt1 : rAlt2;
+ for (i = 0; i < nCount && nIndex < MAX_PROPOSALS; i++)
+ {
+ if (!rAlt[i].isEmpty() &&
+ !SeqHasEntry(aMerged, rAlt[i] ))
+ aMerged[ nIndex++ ] = rAlt[ i ];
+ }
+ }
+ aMerged.resize( nIndex );
+
+ return aMerged;
+}
+
+
+SpellAlternatives::SpellAlternatives()
+{
+ nLanguage = LANGUAGE_NONE;
+ nType = SpellFailure::IS_NEGATIVE_WORD;
+}
+
+
+SpellAlternatives::SpellAlternatives(
+ OUString aWord_, LanguageType nLang,
+ const Sequence< OUString > &rAlternatives ) :
+ aAlt (rAlternatives),
+ aWord (std::move(aWord_)),
+ nType (SpellFailure::IS_NEGATIVE_WORD),
+ nLanguage (nLang)
+{
+}
+
+
+SpellAlternatives::~SpellAlternatives()
+{
+}
+
+
+OUString SAL_CALL SpellAlternatives::getWord()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return aWord;
+}
+
+
+Locale SAL_CALL SpellAlternatives::getLocale()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return LanguageTag::convertToLocale( nLanguage );
+}
+
+
+sal_Int16 SAL_CALL SpellAlternatives::getFailureType()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return nType;
+}
+
+
+sal_Int16 SAL_CALL SpellAlternatives::getAlternativesCount()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return static_cast<sal_Int16>(aAlt.getLength());
+}
+
+
+Sequence< OUString > SAL_CALL SpellAlternatives::getAlternatives()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ return aAlt;
+}
+
+
+void SAL_CALL SpellAlternatives::setAlternatives( const uno::Sequence< OUString >& rAlternatives )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ aAlt = rAlternatives;
+}
+
+
+void SAL_CALL SpellAlternatives::setFailureType( sal_Int16 nFailureType )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ nType = nFailureType;
+}
+
+
+void SpellAlternatives::SetWordLanguage(const OUString &rWord, LanguageType nLang)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ aWord = rWord;
+ nLanguage = nLang;
+}
+
+
+void SpellAlternatives::SetFailureType(sal_Int16 nTypeP)
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ nType = nTypeP;
+}
+
+
+void SpellAlternatives::SetAlternatives( const Sequence< OUString > &rAlt )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ aAlt = rAlt;
+}
+
+
+css::uno::Reference < css::linguistic2::XSpellAlternatives > SpellAlternatives::CreateSpellAlternatives(
+ const OUString &rWord, LanguageType nLang, sal_Int16 nTypeP, const css::uno::Sequence< OUString > &rAlt )
+{
+ rtl::Reference<SpellAlternatives> pAlt = new SpellAlternatives;
+ pAlt->SetWordLanguage( rWord, nLang );
+ pAlt->SetFailureType( nTypeP );
+ pAlt->SetAlternatives( rAlt );
+ return pAlt;
+}
+
+
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/thesdsp.cxx b/linguistic/source/thesdsp.cxx
new file mode 100644
index 000000000..2ffe3642e
--- /dev/null
+++ b/linguistic/source/thesdsp.cxx
@@ -0,0 +1,240 @@
+/* -*- 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 <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/debug.hxx>
+#include <svl/lngmisc.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <osl/mutex.hxx>
+#include <sal/log.hxx>
+
+#include "thesdsp.hxx"
+#include <linguistic/misc.hxx>
+
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::linguistic2;
+using namespace linguistic;
+
+
+static bool SvcListHasLanguage(
+ const Sequence< Reference< XThesaurus > > &rRefs,
+ const Locale &rLocale )
+{
+ return std::any_of(rRefs.begin(), rRefs.end(),
+ [&rLocale](const Reference<XThesaurus>& rRef) {
+ return rRef.is() && rRef->hasLocale( rLocale ); });
+}
+
+
+ThesaurusDispatcher::ThesaurusDispatcher()
+{
+}
+
+
+ThesaurusDispatcher::~ThesaurusDispatcher()
+{
+ ClearSvcList();
+}
+
+
+void ThesaurusDispatcher::ClearSvcList()
+{
+ // release memory for each table entry
+ ThesSvcByLangMap_t().swap(aSvcMap);
+}
+
+
+Sequence< Locale > SAL_CALL
+ ThesaurusDispatcher::getLocales()
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ std::vector<Locale> aLocales;
+ aLocales.reserve(aSvcMap.size());
+
+ std::transform(aSvcMap.begin(), aSvcMap.end(), std::back_inserter(aLocales),
+ [](ThesSvcByLangMap_t::const_reference elem) { return LanguageTag::convertToLocale(elem.first); });
+
+ return comphelper::containerToSequence(aLocales);
+}
+
+
+sal_Bool SAL_CALL
+ ThesaurusDispatcher::hasLocale( const Locale& rLocale )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+ ThesSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LinguLocaleToLanguage( rLocale ) ) );
+ return aIt != aSvcMap.end();
+}
+
+
+Sequence< Reference< XMeaning > > SAL_CALL
+ ThesaurusDispatcher::queryMeanings(
+ const OUString& rTerm, const Locale& rLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& rProperties )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Sequence< Reference< XMeaning > > aMeanings;
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ if (LinguIsUnspecified( nLanguage) || rTerm.isEmpty())
+ return aMeanings;
+
+ // search for entry with that language
+ ThesSvcByLangMap_t::iterator aIt( aSvcMap.find( nLanguage ) );
+ LangSvcEntries_Thes *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+
+ if (pEntry)
+ {
+ OUString aChkWord = rTerm.replace( SVT_HARD_SPACE, ' ' );
+ RemoveHyphens( aChkWord );
+ if (IsIgnoreControlChars( rProperties, GetPropSet() ))
+ RemoveControlChars( aChkWord );
+
+ sal_Int32 nLen = pEntry->aSvcRefs.getLength();
+ DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(),
+ "lng : sequence length mismatch");
+ DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen,
+ "lng : index out of range");
+
+ sal_Int32 i = 0;
+
+ // try already instantiated services first
+ {
+ const Reference< XThesaurus > *pRef = pEntry->aSvcRefs.getConstArray();
+ while (i <= pEntry->nLastTriedSvcIndex
+ && !aMeanings.hasElements())
+ {
+ if (pRef[i].is() && pRef[i]->hasLocale( rLocale ))
+ aMeanings = pRef[i]->queryMeanings( aChkWord, rLocale, rProperties );
+ ++i;
+ }
+ }
+
+ // if still no result instantiate new services and try those
+ if (!aMeanings.hasElements()
+ && pEntry->nLastTriedSvcIndex < nLen - 1)
+ {
+ const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
+ Reference< XThesaurus > *pRef = pEntry->aSvcRefs.getArray();
+
+ Reference< XComponentContext > xContext(
+ comphelper::getProcessComponentContext() );
+
+ // build service initialization argument
+ Sequence< Any > aArgs(1);
+ aArgs.getArray()[0] <<= GetPropSet();
+
+ while (i < nLen && !aMeanings.hasElements())
+ {
+ // create specific service via it's implementation name
+ Reference< XThesaurus > xThes;
+ try
+ {
+ xThes.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ pImplNames[i], aArgs, xContext ),
+ UNO_QUERY );
+ }
+ catch (uno::Exception &)
+ {
+ SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
+ }
+ pRef[i] = xThes;
+
+ if (xThes.is() && xThes->hasLocale( rLocale ))
+ aMeanings = xThes->queryMeanings( aChkWord, rLocale, rProperties );
+
+ pEntry->nLastTriedSvcIndex = static_cast<sal_Int16>(i);
+ ++i;
+ }
+
+ // if language is not supported by any of the services
+ // remove it from the list.
+ if (i == nLen && !aMeanings.hasElements())
+ {
+ if (!SvcListHasLanguage( pEntry->aSvcRefs, rLocale ))
+ aSvcMap.erase( nLanguage );
+ }
+ }
+ }
+
+ return aMeanings;
+}
+
+
+void ThesaurusDispatcher::SetServiceList( const Locale &rLocale,
+ const Sequence< OUString > &rSvcImplNames )
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+
+ sal_Int32 nLen = rSvcImplNames.getLength();
+ if (0 == nLen)
+ // remove entry
+ aSvcMap.erase( nLanguage );
+ else
+ {
+ // modify/add entry
+ LangSvcEntries_Thes *pEntry = aSvcMap[ nLanguage ].get();
+ if (pEntry)
+ {
+ pEntry->Clear();
+ pEntry->aSvcImplNames = rSvcImplNames;
+ pEntry->aSvcRefs = Sequence< Reference < XThesaurus > >( nLen );
+ }
+ else
+ {
+ auto pTmpEntry = std::make_shared<LangSvcEntries_Thes>( rSvcImplNames );
+ pTmpEntry->aSvcRefs = Sequence< Reference < XThesaurus > >( nLen );
+ aSvcMap[ nLanguage ] = pTmpEntry;
+ }
+ }
+}
+
+
+Sequence< OUString >
+ ThesaurusDispatcher::GetServiceList( const Locale &rLocale ) const
+{
+ MutexGuard aGuard( GetLinguMutex() );
+
+ Sequence< OUString > aRes;
+
+ // search for entry with that language and use data from that
+ LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
+ const ThesSvcByLangMap_t::const_iterator aIt( aSvcMap.find( nLanguage ) );
+ const LangSvcEntries_Thes *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : nullptr;
+ if (pEntry)
+ aRes = pEntry->aSvcImplNames;
+
+ return aRes;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/linguistic/source/thesdsp.hxx b/linguistic/source/thesdsp.hxx
new file mode 100644
index 000000000..80cafe29c
--- /dev/null
+++ b/linguistic/source/thesdsp.hxx
@@ -0,0 +1,92 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_LINGUISTIC_SOURCE_THESDSP_HXX
+#define INCLUDED_LINGUISTIC_SOURCE_THESDSP_HXX
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/uno/Sequence.h>
+
+#include <com/sun/star/linguistic2/XLinguProperties.hpp>
+#include <com/sun/star/linguistic2/XThesaurus.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <linguistic/misc.hxx>
+
+#include <map>
+#include <memory>
+
+#include "defs.hxx"
+
+
+class ThesaurusDispatcher :
+ public cppu::WeakImplHelper< css::linguistic2::XThesaurus >,
+ public LinguDispatcher
+{
+ typedef std::shared_ptr< LangSvcEntries_Thes > LangSvcEntries_Thes_Ptr_t;
+ typedef std::map< LanguageType, LangSvcEntries_Thes_Ptr_t > ThesSvcByLangMap_t;
+ ThesSvcByLangMap_t aSvcMap;
+
+ css::uno::Reference< css::linguistic2::XLinguProperties > xPropSet;
+
+ ThesaurusDispatcher(const ThesaurusDispatcher &) = delete;
+ ThesaurusDispatcher & operator = (const ThesaurusDispatcher &) = delete;
+
+ inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ GetPropSet();
+
+ void ClearSvcList();
+
+public:
+ ThesaurusDispatcher();
+ virtual ~ThesaurusDispatcher() override;
+
+ // XSupportedLocales
+ virtual css::uno::Sequence< css::lang::Locale > SAL_CALL
+ getLocales() override;
+ virtual sal_Bool SAL_CALL
+ hasLocale( const css::lang::Locale& aLocale ) override;
+
+ // XThesaurus
+ virtual css::uno::Sequence< css::uno::Reference< css::linguistic2::XMeaning > > SAL_CALL
+ queryMeanings( const OUString& aTerm,
+ const css::lang::Locale& aLocale,
+ const css::uno::Sequence< ::css::beans::PropertyValue >& aProperties ) override;
+
+ // LinguDispatcher
+ virtual void
+ SetServiceList( const css::lang::Locale &rLocale,
+ const css::uno::Sequence< OUString > &rSvcImplNames ) override;
+ virtual css::uno::Sequence< OUString >
+ GetServiceList( const css::lang::Locale &rLocale ) const override;
+};
+
+
+inline const css::uno::Reference< css::linguistic2::XLinguProperties > &
+ ThesaurusDispatcher::GetPropSet()
+{
+ if (!xPropSet.is())
+ xPropSet = linguistic::GetLinguProperties();
+ return xPropSet;
+}
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */