diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /linguistic/source | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'linguistic/source')
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: */ |