diff options
Diffstat (limited to 'editeng/source/misc')
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectExport.cxx | 110 | ||||
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectExport.hxx | 60 | ||||
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectImport.cxx | 163 | ||||
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectImport.hxx | 113 | ||||
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx | 54 | ||||
-rw-r--r-- | editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx | 45 | ||||
-rw-r--r-- | editeng/source/misc/acorrcfg.cxx | 698 | ||||
-rw-r--r-- | editeng/source/misc/edtdlg.cxx | 29 | ||||
-rw-r--r-- | editeng/source/misc/forbiddencharacterstable.cxx | 65 | ||||
-rw-r--r-- | editeng/source/misc/hangulhanja.cxx | 1002 | ||||
-rw-r--r-- | editeng/source/misc/splwrap.cxx | 472 | ||||
-rw-r--r-- | editeng/source/misc/svxacorr.cxx | 3161 | ||||
-rw-r--r-- | editeng/source/misc/swafopt.cxx | 81 | ||||
-rw-r--r-- | editeng/source/misc/tokens.txt | 7 | ||||
-rw-r--r-- | editeng/source/misc/txtrange.cxx | 667 | ||||
-rw-r--r-- | editeng/source/misc/unolingu.cxx | 754 | ||||
-rw-r--r-- | editeng/source/misc/urlfieldhelper.cxx | 49 |
17 files changed, 7530 insertions, 0 deletions
diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.cxx b/editeng/source/misc/SvXMLAutoCorrectExport.cxx new file mode 100644 index 0000000000..8faf434116 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.cxx @@ -0,0 +1,110 @@ +/* -*- 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 "SvXMLAutoCorrectExport.hxx" + +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <xmloff/namespacemap.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +SvXMLAutoCorrectExport::SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + pAutocorr_List( pNewAutocorr_List ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLAutoCorrectExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + const SvxAutocorrWordList::AutocorrWordSetType& rContent = pAutocorr_List->getSortedContent(); + for (auto const& content : rContent) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + content.GetShort()); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_NAME, + content.IsTextOnly() ? content.GetLong() : content.GetShort()); + + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +SvXMLExceptionListExport::SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + rList( rNewList ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST ), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLExceptionListExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + sal_uInt16 nBlocks= rList.size(); + for ( sal_uInt16 i = 0; i < nBlocks; i++) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + rList[i] ); + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.hxx b/editeng/source/misc/SvXMLAutoCorrectExport.hxx new file mode 100644 index 0000000000..58570e3ad1 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ +#pragma once + +#include <xmloff/xmlexp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectExport : public SvXMLExport +{ +private: + const SvxAutocorrWordList *pAutocorr_List; +public: + SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +class SvStringsISortDtor; + +class SvXMLExceptionListExport : public SvXMLExport +{ +private: + const SvStringsISortDtor & rList; +public: + SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.cxx b/editeng/source/misc/SvXMLAutoCorrectImport.cxx new file mode 100644 index 0000000000..baeef88612 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <utility> + +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" + +using namespace css; +using namespace css::xml::sax; + +SvXMLAutoCorrectImport::SvXMLAutoCorrectImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage) +: SvXMLImport( xContext, "" ), + pAutocorr_List (pNewAutocorr_List), + rAutoCorrect ( rNewAutoCorrect ), + xStorage (std::move( xNewStorage )) +{ +} + +SvXMLAutoCorrectImport::~SvXMLAutoCorrectImport() noexcept +{ +} + +SvXMLImportContext *SvXMLAutoCorrectImport::CreateFastContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLWordListContext( *this ); + return nullptr; +} + +SvXMLWordListContext::SvXMLWordListContext( + SvXMLAutoCorrectImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ + rLocalRef.rAutoCorrect.refreshBlockList( rLocalRef.xStorage ); +} + +css::uno::Reference<XFastContextHandler> SAL_CALL SvXMLWordListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLWordContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLWordListContext::~SvXMLWordListContext() +{ +} + +SvXMLWordContext::SvXMLWordContext( + SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWrong, sRight; + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWrong = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::NAME ) ) + sRight = xAttrList->getValue( SvXMLAutoCorrectToken::NAME ); + + if ( sWrong.isEmpty() || sRight.isEmpty()) + return; + + bool bOnlyTxt = sRight != sWrong; + if( !bOnlyTxt ) + { + const OUString sLongSave( sRight ); + if( !rImport.rAutoCorrect.GetLongText( sWrong, sRight ) && + !sLongSave.isEmpty() ) + { + sRight = sLongSave; + bOnlyTxt = true; + } + } + rImport.pAutocorr_List->LoadEntry( sWrong, sRight, bOnlyTxt ); +} + +SvXMLWordContext::~SvXMLWordContext() +{ +} + +SvXMLExceptionListImport::SvXMLExceptionListImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ) +: SvXMLImport( xContext, "" ), + rList (rNewList) +{ +} + +SvXMLExceptionListImport::~SvXMLExceptionListImport() noexcept +{ +} + +SvXMLImportContext *SvXMLExceptionListImport::CreateFastContext(sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLExceptionListContext( *this ); + return nullptr; +} + +SvXMLExceptionListContext::SvXMLExceptionListContext( + SvXMLExceptionListImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ +} + +css::uno::Reference<xml::sax::XFastContextHandler> SAL_CALL SvXMLExceptionListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLExceptionContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLExceptionListContext::~SvXMLExceptionListContext() +{ +} + +SvXMLExceptionContext::SvXMLExceptionContext( + SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWord; + if( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWord = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if (sWord.isEmpty()) + return; + + rImport.rList.insert( sWord ); +} + +SvXMLExceptionContext::~SvXMLExceptionContext() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.hxx b/editeng/source/misc/SvXMLAutoCorrectImport.hxx new file mode 100644 index 0000000000..961e6963d7 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.hxx @@ -0,0 +1,113 @@ +/* -*- 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 . + */ +#pragma once + +#include <sot/storage.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/xmlimp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + +public: + SvxAutocorrWordList *pAutocorr_List; + SvxAutoCorrect &rAutoCorrect; + css::uno::Reference < css::embed::XStorage > xStorage; + + SvXMLAutoCorrectImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage); + + virtual ~SvXMLAutoCorrectImport() noexcept override; +}; + +class SvXMLWordListContext : public SvXMLImportContext +{ +private: + SvXMLAutoCorrectImport & rLocalRef; +public: + SvXMLWordListContext ( SvXMLAutoCorrectImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLWordListContext() override; +}; + +class SvXMLWordContext : public SvXMLImportContext +{ +public: + SvXMLWordContext ( SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLWordContext() override; +}; + + +class SvXMLExceptionListImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, const css::uno::Reference< + css::xml::sax::XFastAttributeList > & xAttrList ) override; +public: + SvStringsISortDtor &rList; + + SvXMLExceptionListImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ); + + virtual ~SvXMLExceptionListImport() noexcept override; +}; + +class SvXMLExceptionListContext : public SvXMLImportContext +{ +private: + SvXMLExceptionListImport & rLocalRef; +public: + SvXMLExceptionListContext ( SvXMLExceptionListImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLExceptionListContext() override; +}; + +class SvXMLExceptionContext : public SvXMLImportContext +{ +public: + SvXMLExceptionContext ( SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLExceptionContext() override; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx new file mode 100644 index 0000000000..4bdadcdcde --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx @@ -0,0 +1,54 @@ +/* -*- 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/. + */ + +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <xmloff/xmltoken.hxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-register" +#endif +#endif +#include <tokens.cxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic pop +#endif +#endif + +using namespace css::uno; +using namespace ::xmloff::token; + +SvXMLAutoCorrectTokenHandler::SvXMLAutoCorrectTokenHandler() +{ +} + +SvXMLAutoCorrectTokenHandler::~SvXMLAutoCorrectTokenHandler() +{ +} + +sal_Int32 SAL_CALL SvXMLAutoCorrectTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& Identifier ) +{ + return getTokenDirect( reinterpret_cast< const char* >( Identifier.getConstArray() ), Identifier.getLength() ); +} + +Sequence< sal_Int8 > SAL_CALL SvXMLAutoCorrectTokenHandler::getUTF8Identifier( sal_Int32 ) +{ + return Sequence< sal_Int8 >(); +} + +sal_Int32 SvXMLAutoCorrectTokenHandler::getTokenDirect( const char *pTag, sal_Int32 nLength ) const +{ + if( !nLength ) + nLength = strlen( pTag ); + const struct xmltoken* pToken = Perfect_Hash::in_word_set( pTag, nLength ); + return pToken ? pToken->nToken : XML_TOKEN_INVALID; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx new file mode 100644 index 0000000000..df913dbe6b --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.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/. + */ + +#pragma once + +#include <sal/types.h> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <sax/fastattribs.hxx> + +using namespace css::xml::sax; +using namespace ::xmloff::token; + +enum SvXMLAutoCorrectToken : sal_Int32 +{ + NAMESPACE = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST, //65553 + ABBREVIATED_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_ABBREVIATED_NAME, //65655 + BLOCK = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK, //65791 + BLOCKLIST = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK_LIST, //65792 + NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_NAME //66737 +}; + +class SvXMLAutoCorrectTokenHandler : + public sax_fastparser::FastTokenHandlerBase +{ +public: + explicit SvXMLAutoCorrectTokenHandler(); + virtual ~SvXMLAutoCorrectTokenHandler() override; + + //XFastTokenHandler + virtual sal_Int32 SAL_CALL getTokenFromUTF8( const css::uno::Sequence< sal_Int8 >& Identifier ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getUTF8Identifier( sal_Int32 Token ) override; + + // Much faster direct C++ shortcut to the method that matters + virtual sal_Int32 getTokenDirect( const char *pToken, sal_Int32 nLength ) const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/acorrcfg.cxx b/editeng/source/misc/acorrcfg.cxx new file mode 100644 index 0000000000..616d75c696 --- /dev/null +++ b/editeng/source/misc/acorrcfg.cxx @@ -0,0 +1,698 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <editeng/acorrcfg.hxx> +#include <o3tl/any.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/ucbhelper.hxx> +#include <svtools/langtab.hxx> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> + +#include <editeng/svxacorr.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace utl; +using namespace com::sun::star; +using namespace com::sun::star::uno; + + +/** An autocorrection file dropped into such directory may create a language + list entry if one didn't exist already. + */ +static void scanAutoCorrectDirForLanguageTags( const OUString& rURL ) +{ + // Silently ignore all errors. + try + { + ::ucbhelper::Content aContent( rURL, + uno::Reference<ucb::XCommandEnvironment>(), comphelper::getProcessComponentContext()); + if (aContent.isFolder()) + { + // Title is file name here. + uno::Reference<sdbc::XResultSet> xResultSet = aContent.createCursor( + {"Title"}, ::ucbhelper::INCLUDE_DOCUMENTS_ONLY); + uno::Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY); + if (xResultSet.is() && xRow.is()) + { + while (xResultSet->next()) + { + try + { + const OUString aTitle( xRow->getString(1)); + if (aTitle.getLength() <= 9 || !(aTitle.startsWith("acor_") && aTitle.endsWith(".dat"))) + continue; + + const OUString aBcp47( aTitle.copy( 5, aTitle.getLength() - 9)); + OUString aCanonicalized; + // Ignore invalid langtags and canonicalize for good, + // allow private-use tags. + if (!LanguageTag::isValidBcp47( aBcp47, &aCanonicalized)) + continue; + + const LanguageTag aLanguageTag( aCanonicalized); + if (SvtLanguageTable::HasLanguageType( aLanguageTag.getLanguageType())) + continue; + + // Insert language(-script)-only tags only if there is + // no known matching fallback locale, otherwise we'd + // end up with unwanted entries where a language + // autocorrection file covers several locales. We do + // know a few art-x-... though so exclude those and any + // other private-use tag (which should not fallback, + // but avoid). + if (aLanguageTag.getCountry().isEmpty() + && LanguageTag::isValidBcp47( aCanonicalized, nullptr, + LanguageTag::PrivateUse::DISALLOW)) + { + LanguageTag aFallback( aLanguageTag); + aFallback.makeFallback(); + if (aFallback.getLanguageAndScript() == aLanguageTag.getLanguageAndScript()) + continue; + } + + // Finally add this one. + SvtLanguageTable::AddLanguageTag( aLanguageTag); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("editeng", "Unable to get a directory entry from '" << rURL << "'"); + } + } + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("editeng", "Unable to iterate directory '" << rURL << "'"); + } +} + +SvxAutoCorrCfg::SvxAutoCorrCfg() : + aBaseConfig(*this), + aSwConfig(*this), + bFileRel(true), + bNetRel(true), + bAutoTextTip(true), + bAutoTextPreview(false), + bAutoFmtByInput(true), + bSearchInAllCategories(false) +{ + SvtPathOptions aPathOpt; + OUString sSharePath, sUserPath; + OUString const & sAutoPath( aPathOpt.GetAutoCorrectPath() ); + + sSharePath = sAutoPath.getToken(0, ';'); + sUserPath = sAutoPath.getToken(1, ';'); + + //fdo#67743 ensure the userdir exists so that any later attempt to copy the + //shared autocorrect file into the user dir will succeed + ::ucbhelper::Content aContent; + Reference < ucb::XCommandEnvironment > xEnv; + ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, sUserPath, aContent); + + for( OUString* pS : { &sSharePath, &sUserPath } ) + { + INetURLObject aPath( *pS ); + scanAutoCorrectDirForLanguageTags( aPath.GetMainURL(INetURLObject::DecodeMechanism::ToIUri)); + aPath.insertName(u"acor"); + *pS = aPath.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + } + pAutoCorrect.reset( new SvxAutoCorrect( sSharePath, sUserPath ) ); + + aBaseConfig.Load(true); + aSwConfig.Load(true); +} + +SvxAutoCorrCfg::~SvxAutoCorrCfg() +{ +} + +void SvxAutoCorrCfg::SetAutoCorrect(SvxAutoCorrect *const pNew) +{ + if (pNew != pAutoCorrect.get()) + { + if (pNew && (pAutoCorrect->GetFlags() != pNew->GetFlags())) + { + aBaseConfig.SetModified(); + aSwConfig.SetModified(); + } + pAutoCorrect.reset( pNew ); + } +} + +Sequence<OUString> SvxBaseAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Exceptions/TwoCapitalsAtStart", // 0 + "Exceptions/CapitalAtStartSentence", // 1 + "UseReplacementTable", // 2 + "TwoCapitalsAtStart", // 3 + "CapitalAtStartSentence", // 4 + "ChangeUnderlineWeight", // 5 + "SetInetAttribute", // 6 + "ChangeOrdinalNumber", // 7 + "AddNonBreakingSpace", // 8 + "ChangeDash", // 9 + "RemoveDoubleSpaces", // 10 + "ReplaceSingleQuote", // 11 + "SingleQuoteAtStart", // 12 + "SingleQuoteAtEnd", // 13 + "ReplaceDoubleQuote", // 14 + "DoubleQuoteAtStart", // 15 + "DoubleQuoteAtEnd", // 16 + "CorrectAccidentalCapsLock", // 17 + "TransliterateRTL", // 18 + "ChangeAngleQuotes", // 19 + "SetDOIAttribute", // 20 + }; + const int nCount = 21; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxBaseAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + ACFlags nFlags = ACFlags::NONE; // default all off + sal_Int32 nTemp = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordCplSttLst; + break;//"Exceptions/TwoCapitalsAtStart", + case 1: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordWordStartLst; + break;//"Exceptions/CapitalAtStartSentence", + case 2: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::Autocorrect; + break;//"UseReplacementTable", + case 3: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartWord; + break;//"TwoCapitalsAtStart", + case 4: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartSentence; + break;//"CapitalAtStartSentence", + case 5: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgWeightUnderl; + break;//"ChangeUnderlineWeight", + case 6: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SetINetAttr; + break;//"SetInetAttribute", + case 7: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgOrdinalNumber; + break;//"ChangeOrdinalNumber", + case 8: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::AddNonBrkSpace; + break;//"AddNonBreakingSpace" + case 9: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgToEnEmDash; + break;//"ChangeDash", + case 10: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::IgnoreDoubleSpace; + break;//"RemoveDoubleSpaces", + case 11: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgSglQuotes; + break;//"ReplaceSingleQuote", + case 12: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtStart", + case 13: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtEnd", + case 14: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgQuotes; + break;//"ReplaceDoubleQuote", + case 15: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtStart", + case 16: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtEnd" + case 17: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CorrectCapsLock; + break;//"CorrectAccidentalCapsLock" + case 18: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::TransliterateRTL; + break;//"TransliterateRTL" + case 19: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgAngleQuotes; + break;//"ChangeAngleQuotes" + case 20: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SetDOIAttr; + break;//"SetDOIAttr", + } + } + } + if( nFlags != ACFlags::NONE ) + rParent.pAutoCorrect->SetAutoCorrFlag( nFlags ); + rParent.pAutoCorrect->SetAutoCorrFlag( ( static_cast<ACFlags>(0xffff) & ~nFlags ), false ); +} + +SvxBaseAutoCorrCfg::SvxBaseAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Common/AutoCorrect"), + rParent(rPar) +{ +} + +SvxBaseAutoCorrCfg::~SvxBaseAutoCorrCfg() +{ +} + +void SvxBaseAutoCorrCfg::ImplCommit() +{ + const ACFlags nFlags = rParent.pAutoCorrect->GetFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(bool(nFlags & ACFlags::SaveWordCplSttLst)), + // "Exceptions/TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::SaveWordWordStartLst)), + // "Exceptions/CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::Autocorrect)), // "UseReplacementTable" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartWord)), + // "TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartSentence)), + // "CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::ChgWeightUnderl)), + // "ChangeUnderlineWeight" + css::uno::Any(bool(nFlags & ACFlags::SetINetAttr)), // "SetInetAttribute" + css::uno::Any(bool(nFlags & ACFlags::ChgOrdinalNumber)), + // "ChangeOrdinalNumber" + css::uno::Any(bool(nFlags & ACFlags::AddNonBrkSpace)), // "AddNonBreakingSpace" + css::uno::Any(bool(nFlags & ACFlags::ChgToEnEmDash)), // "ChangeDash" + css::uno::Any(bool(nFlags & ACFlags::IgnoreDoubleSpace)), + // "RemoveDoubleSpaces" + css::uno::Any(bool(nFlags & ACFlags::ChgSglQuotes)), // "ReplaceSingleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartSingleQuote())), + // "SingleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndSingleQuote())), + // "SingleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::ChgQuotes)), // "ReplaceDoubleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartDoubleQuote())), + // "DoubleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndDoubleQuote())), + // "DoubleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::CorrectCapsLock)), + // "CorrectAccidentalCapsLock" + css::uno::Any(bool(nFlags & ACFlags::TransliterateRTL)), + // "TransliterateRTL" + css::uno::Any(bool(nFlags & ACFlags::ChgAngleQuotes)), + // "ChangeAngleQuotes" + css::uno::Any(bool(nFlags & ACFlags::SetDOIAttr)), // "SetDOIAttribute" + }); +} + +void SvxBaseAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */) +{ + Load(false); +} + +Sequence<OUString> SvxSwAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Text/FileLinks", // 0 + "Text/InternetLinks", // 1 + "Text/ShowPreview", // 2 + "Text/ShowToolTip", // 3 + "Text/SearchInAllCategories", // 4 + "Format/Option/UseReplacementTable", // 5 + "Format/Option/TwoCapitalsAtStart", // 6 + "Format/Option/CapitalAtStartSentence", // 7 + "Format/Option/ChangeUnderlineWeight", // 8 + "Format/Option/SetInetAttribute", // 9 + "Format/Option/ChangeOrdinalNumber", //10 + "Format/Option/AddNonBreakingSpace", //11 + "Format/Option/ChangeDash", //12 + "Format/Option/DelEmptyParagraphs", //13 + "Format/Option/ReplaceUserStyle", //14 + "Format/Option/ChangeToBullets/Enable", //15 + "Format/Option/ChangeToBullets/SpecialCharacter/Char", //16 + "Format/Option/ChangeToBullets/SpecialCharacter/Font", //17 + "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", //18 + "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", //19 + "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", //20 + "Format/Option/CombineParagraphs", //21 + "Format/Option/CombineValue", //22 + "Format/Option/DelSpacesAtStartEnd", //23 + "Format/Option/DelSpacesBetween", //24 + "Format/ByInput/Enable", //25 + "Format/ByInput/ChangeDash", //26 + "Format/ByInput/ApplyNumbering/Enable", //27 + "Format/ByInput/ChangeToBorders", //28 + "Format/ByInput/ChangeToTable", //29 + "Format/ByInput/ReplaceStyle", //30 + "Format/ByInput/DelSpacesAtStartEnd", //31 + "Format/ByInput/DelSpacesBetween", //32 + "Completion/Enable", //33 + "Completion/MinWordLen", //34 + "Completion/MaxListLen", //35 + "Completion/CollectWords", //36 + "Completion/EndlessList", //37 + "Completion/AppendBlank", //38 + "Completion/ShowAsTip", //39 + "Completion/AcceptKey", //40 + "Completion/KeepList", //41 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", //42 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", //43 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", //44 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", //45 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", //46 + "Format/Option/SetDOIAttribute", //47 + "Format/ByInput/ApplyBulletsAfterSpace", //48 + }; + const int nCount = 49; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxSwAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: rParent.bFileRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/FileLinks", + case 1: rParent.bNetRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/InternetLinks", + case 2: rParent.bAutoTextPreview = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowPreview", + case 3: rParent.bAutoTextTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowToolTip", + case 4: rParent.bSearchInAllCategories = *o3tl::doAccess<bool>(pValues[nProp]); break; //"Text/SearchInAllCategories" + case 5: rSwFlags.bAutoCorrect = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/UseReplacementTable", + case 6: rSwFlags.bCapitalStartSentence = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/TwoCapitalsAtStart", + case 7: rSwFlags.bCapitalStartWord = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CapitalAtStartSentence", + case 8: rSwFlags.bChgWeightUnderl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeUnderlineWeight", + case 9: rSwFlags.bSetINetAttr = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/SetInetAttribute", + case 10: rSwFlags.bChgOrdinalNumber = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeOrdinalNumber", + case 11: rSwFlags.bAddNonBrkSpace = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/AddNonBreakingSpace", +// it doesn't exist here - the common flags are used for that -> LM +// case 12: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeDash", + case 13: rSwFlags.bDelEmptyNode = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelEmptyParagraphs", + case 14: rSwFlags.bChgUserColl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ReplaceUserStyle", + case 15: rSwFlags.bChgEnumNum = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeToBullets/Enable", + case 16: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Char", + case 17: + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aBulletFont.SetFamilyName(sTemp); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Font", + case 18: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetFamily(FontFamily(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", + case 19: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", + case 20: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetPitch(FontPitch(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", + case 21: rSwFlags.bRightMargin = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CombineParagraphs", + case 22: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nRightMargin = + sal::static_int_cast< sal_uInt8 >(nVal); + } + break; // "Format/Option/CombineValue", + case 23: rSwFlags.bAFormatDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesAtStartEnd", + case 24: rSwFlags.bAFormatDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesBetween", + case 25: rParent.bAutoFmtByInput = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/Enable", + case 26: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeDash", + case 27: rSwFlags.bSetNumRule = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ApplyNumbering/Enable", + case 28: rSwFlags.bSetBorder = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToBorders", + case 29: rSwFlags.bCreateTable = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToTable", + case 30: rSwFlags.bReplaceStyles = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ReplaceStyle", + case 31: rSwFlags.bAFormatByInpDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesAtStartEnd", + case 32: rSwFlags.bAFormatByInpDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesBetween", + case 33: rSwFlags.bAutoCompleteWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/Enable", + case 34: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltWordLen = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/MinWordLen", + case 35: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltListLen = + sal::static_int_cast< sal_uInt32 >(nVal); + } + break; // "Completion/MaxListLen", + case 36: rSwFlags.bAutoCmpltCollectWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/CollectWords", + case 37: rSwFlags.bAutoCmpltEndless = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/EndlessList", + case 38: rSwFlags.bAutoCmpltAppendBlank = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/AppendBlank", + case 39: rSwFlags.bAutoCmpltShowAsTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/ShowAsTip", + case 40: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltExpandKey = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/AcceptKey" + case 41 :rSwFlags.bAutoCmpltKeepList = *o3tl::doAccess<bool>(pValues[nProp]); break;//"Completion/KeepList" + case 42 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cByInputBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", + case 43 : + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aByInputBulletFont.SetFamilyName(sTemp); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", + case 44 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetFamily(FontFamily(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", + case 45 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", + case 46 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetPitch(FontPitch(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", + case 47: rSwFlags.bSetDOIAttr = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/SetDOIAttribute", + case 48 : rSwFlags.bSetNumRuleAfterSpace = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ApplyNumberingAfterSpace", + } + } + } +} + +SvxSwAutoCorrCfg::SvxSwAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Writer/AutoFunction"), + rParent(rPar) +{ +} + +SvxSwAutoCorrCfg::~SvxSwAutoCorrCfg() +{ +} + +void SvxSwAutoCorrCfg::ImplCommit() +{ + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(rParent.bFileRel), // "Text/FileLinks" + css::uno::Any(rParent.bNetRel), // "Text/InternetLinks" + css::uno::Any(rParent.bAutoTextPreview), // "Text/ShowPreview" + css::uno::Any(rParent.bAutoTextTip), // "Text/ShowToolTip" + css::uno::Any(rParent.bSearchInAllCategories), + // "Text/SearchInAllCategories" + css::uno::Any(rSwFlags.bAutoCorrect), + // "Format/Option/UseReplacementTable" + css::uno::Any(rSwFlags.bCapitalStartSentence), + // "Format/Option/TwoCapitalsAtStart" + css::uno::Any(rSwFlags.bCapitalStartWord), + // "Format/Option/CapitalAtStartSentence" + css::uno::Any(rSwFlags.bChgWeightUnderl), + // "Format/Option/ChangeUnderlineWeight" + css::uno::Any(rSwFlags.bSetINetAttr), + // "Format/Option/SetInetAttribute" + css::uno::Any(rSwFlags.bChgOrdinalNumber), + // "Format/Option/ChangeOrdinalNumber" + css::uno::Any(rSwFlags.bAddNonBrkSpace), + // "Format/Option/AddNonBreakingSpace" + css::uno::Any(true), + // "Format/Option/ChangeDash"; it doesn't exist here - the common + // flags are used for that -> LM + css::uno::Any(rSwFlags.bDelEmptyNode), + // "Format/Option/DelEmptyParagraphs" + css::uno::Any(rSwFlags.bChgUserColl), + // "Format/Option/ReplaceUserStyle" + css::uno::Any(rSwFlags.bChgEnumNum), + // "Format/Option/ChangeToBullets/Enable" + css::uno::Any(sal_Int32(rSwFlags.cBullet)), + // "Format/Option/ChangeToBullets/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aBulletFont.GetFamilyName()), + // "Format/Option/ChangeToBullets/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetFamilyType())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetCharSet())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetPitch())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch" + css::uno::Any(rSwFlags.bRightMargin), + // "Format/Option/CombineParagraphs" + css::uno::Any(sal_Int32(rSwFlags.nRightMargin)), + // "Format/Option/CombineValue" + css::uno::Any(rSwFlags.bAFormatDelSpacesAtSttEnd), + // "Format/Option/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatDelSpacesBetweenLines), + // "Format/Option/DelSpacesBetween" + css::uno::Any(rParent.bAutoFmtByInput), // "Format/ByInput/Enable" + css::uno::Any(rSwFlags.bChgToEnEmDash), // "Format/ByInput/ChangeDash" + css::uno::Any(rSwFlags.bSetNumRule), + // "Format/ByInput/ApplyNumbering/Enable" + css::uno::Any(rSwFlags.bSetBorder), // "Format/ByInput/ChangeToBorders" + css::uno::Any(rSwFlags.bCreateTable), // "Format/ByInput/ChangeToTable" + css::uno::Any(rSwFlags.bReplaceStyles), + // "Format/ByInput/ReplaceStyle" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesAtSttEnd), + // "Format/ByInput/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesBetweenLines), + // "Format/ByInput/DelSpacesBetween" + css::uno::Any(rSwFlags.bAutoCompleteWords), // "Completion/Enable" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltWordLen)), + // "Completion/MinWordLen" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltListLen)), + // "Completion/MaxListLen" + css::uno::Any(rSwFlags.bAutoCmpltCollectWords), + // "Completion/CollectWords" + css::uno::Any(rSwFlags.bAutoCmpltEndless), // "Completion/EndlessList" + css::uno::Any(rSwFlags.bAutoCmpltAppendBlank), + // "Completion/AppendBlank" + css::uno::Any(rSwFlags.bAutoCmpltShowAsTip), // "Completion/ShowAsTip" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltExpandKey)), + // "Completion/AcceptKey" + css::uno::Any(rSwFlags.bAutoCmpltKeepList), // "Completion/KeepList" + css::uno::Any(sal_Int32(rSwFlags.cByInputBullet)), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aByInputBulletFont.GetFamilyName()), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetFamilyType())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetCharSet())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetPitch())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch" + css::uno::Any(rSwFlags.bSetDOIAttr), + css::uno::Any(rSwFlags.bSetNumRuleAfterSpace), // "Format/ByInput/ApplyNumberingAfterSpace" + }); + // "Format/Option/SetDOIAttribute" +} + +void SvxSwAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */ ) +{ + Load(false); +} + +SvxAutoCorrCfg& SvxAutoCorrCfg::Get() +{ + static SvxAutoCorrCfg theSvxAutoCorrCfg; + return theSvxAutoCorrCfg; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/edtdlg.cxx b/editeng/source/misc/edtdlg.cxx new file mode 100644 index 0000000000..a3f4390ab0 --- /dev/null +++ b/editeng/source/misc/edtdlg.cxx @@ -0,0 +1,29 @@ +/* -*- 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 <editeng/edtdlg.hxx> + +EditAbstractDialogFactory* EditAbstractDialogFactory::Create() +{ + return dynamic_cast<EditAbstractDialogFactory*>(VclAbstractDialogFactory::Create()); +} + +EditAbstractDialogFactory::~EditAbstractDialogFactory() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/forbiddencharacterstable.cxx b/editeng/source/misc/forbiddencharacterstable.cxx new file mode 100644 index 0000000000..7276da584b --- /dev/null +++ b/editeng/source/misc/forbiddencharacterstable.cxx @@ -0,0 +1,65 @@ +/* -*- 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 <editeng/forbiddencharacterstable.hxx> + +#include <unotools/localedatawrapper.hxx> +#include <utility> + +SvxForbiddenCharactersTable::SvxForbiddenCharactersTable( + css::uno::Reference<css::uno::XComponentContext> xContext) + : m_xContext(std::move(xContext)) +{ +} + +std::shared_ptr<SvxForbiddenCharactersTable> +SvxForbiddenCharactersTable::makeForbiddenCharactersTable( + const css::uno::Reference<css::uno::XComponentContext>& rxContext) +{ + return std::shared_ptr<SvxForbiddenCharactersTable>(new SvxForbiddenCharactersTable(rxContext)); +} + +const css::i18n::ForbiddenCharacters* +SvxForbiddenCharactersTable::GetForbiddenCharacters(LanguageType nLanguage, bool bGetDefault) +{ + css::i18n::ForbiddenCharacters* pForbiddenCharacters = nullptr; + Map::iterator it = maMap.find(nLanguage); + if (it != maMap.end()) + pForbiddenCharacters = &(it->second); + else if (bGetDefault && m_xContext.is()) + { + LocaleDataWrapper aWrapper(m_xContext, LanguageTag(nLanguage)); + maMap[nLanguage] = aWrapper.getForbiddenCharacters(); + pForbiddenCharacters = &maMap[nLanguage]; + } + return pForbiddenCharacters; +} + +void SvxForbiddenCharactersTable::SetForbiddenCharacters( + LanguageType nLanguage, const css::i18n::ForbiddenCharacters& rForbiddenChars) +{ + maMap[nLanguage] = rForbiddenChars; +} + +void SvxForbiddenCharactersTable::ClearForbiddenCharacters(LanguageType nLanguage) +{ + maMap.erase(nLanguage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/hangulhanja.cxx b/editeng/source/misc/hangulhanja.cxx new file mode 100644 index 0000000000..5a9a8c1034 --- /dev/null +++ b/editeng/source/misc/hangulhanja.cxx @@ -0,0 +1,1002 @@ +/* -*- 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 <editeng/hangulhanja.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> + +#include <set> +#include <map> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/TextConversion.hpp> +#include <com/sun/star/i18n/XExtendedTextConversion.hpp> +#include <com/sun/star/i18n/TextConversionType.hpp> +#include <com/sun/star/i18n/TextConversionOption.hpp> +#include <vcl/weld.hxx> +#include <unotools/charclass.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <editeng/edtdlg.hxx> + +#define HHC HangulHanjaConversion + + +namespace editeng +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::i18n; + using namespace ::com::sun::star::i18n::TextConversionOption; + using namespace ::com::sun::star::i18n::TextConversionType; + + class HangulHanjaConversion_Impl + { + private: + typedef std::set<OUString> StringBag; + typedef std::map<OUString, OUString> StringMap; + + private: + StringBag m_sIgnoreList; + StringMap m_aChangeList; + static StringMap m_aRecentlyUsedList; + + // general + VclPtr<AbstractHangulHanjaConversionDialog> + m_pConversionDialog; // the dialog to display for user interaction + weld::Widget* m_pUIParent; // the parent window for any UI we raise + Reference< XComponentContext > + m_xContext; // the service factory to use + Reference< XExtendedTextConversion > + m_xConverter; // the text conversion service + lang::Locale m_aSourceLocale; // the locale we're working with + + // additions for Chinese simplified / traditional conversion + HHC::ConversionType m_eConvType; // conversion type (Hangul/Hanja, simplified/traditional Chinese,...) + LanguageType m_nSourceLang; // just a 'copy' of m_aSourceLocale in order to + // save the applications from always converting to this + // type in their implementations + LanguageType m_nTargetLang; // target language of new replacement text + const vcl::Font* m_pTargetFont; // target font of new replacement text + sal_Int32 m_nConvOptions; // text conversion options (as used by 'getConversions') + bool m_bIsInteractive; // specifies if the conversion requires user interaction + // (and likely a specialised dialog) or if it is to run + // automatically without any user interaction. + // True for Hangul / Hanja conversion + // False for Chinese simplified / traditional conversion + + HangulHanjaConversion* m_pAntiImpl; // our "anti-impl" instance + + // options + bool m_bByCharacter; // are we in "by character" mode currently? + HHC::ConversionFormat m_eConversionFormat; // the current format for the conversion + HHC::ConversionDirection m_ePrimaryConversionDirection; // the primary conversion direction + HHC::ConversionDirection m_eCurrentConversionDirection; // the primary conversion direction + + //options from Hangul/Hanja Options dialog (also saved to configuration) + bool m_bIgnorePostPositionalWord; + bool m_bShowRecentlyUsedFirst; + bool m_bAutoReplaceUnique; + + // state + OUString m_sCurrentPortion; // the text which we are currently working on + LanguageType m_nCurrentPortionLang; // language of m_sCurrentPortion found + sal_Int32 m_nCurrentStartIndex; // the start index within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nCurrentEndIndex; // the end index (excluding) within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nReplacementBaseIndex;// index which ReplaceUnit-calls need to be relative to + sal_Int32 m_nCurrentConversionOption; + sal_Int16 m_nCurrentConversionType; + Sequence< OUString > + m_aCurrentSuggestions; // the suggestions for the current unit + // (means for the text [m_nCurrentStartIndex, m_nCurrentEndIndex) in m_sCurrentPortion) + bool m_bTryBothDirections; // specifies if other conversion directions should be tried when looking for convertible characters + + + public: + HangulHanjaConversion_Impl( + weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nConvOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ); + + public: + void DoDocumentConversion( ); + + bool IsValid() const { return m_xConverter.is(); } + + weld::Widget* GetUIParent() const { return m_pUIParent; } + LanguageType GetSourceLang() const { return m_nSourceLang; } + LanguageType GetTargetLang() const { return m_nTargetLang; } + const vcl::Font * GetTargetFont() const { return m_pTargetFont; } + sal_Int32 GetConvOptions() const { return m_nConvOptions; } + bool IsInteractive() const { return m_bIsInteractive; } + + protected: + void createDialog(); + + /** continue with the conversion, return <TRUE/> if and only if the complete conversion is done + @param _bRepeatCurrentUnit + if <TRUE/>, an implNextConvertible will be called initially to advance to the next convertible. + if <FALSE/>, the method will initially work with the current convertible unit + */ + bool ContinueConversion( bool _bRepeatCurrentUnit ); + + private: + DECL_LINK( OnOptionsChanged, LinkParamNone*, void ); + DECL_LINK( OnIgnore, weld::Button&, void ); + DECL_LINK( OnIgnoreAll, weld::Button&, void ); + DECL_LINK( OnChange, weld::Button&, void ); + DECL_LINK( OnChangeAll, weld::Button&, void ); + DECL_LINK( OnByCharClicked, weld::Toggleable&, void ); + DECL_LINK( OnConversionTypeChanged, weld::Toggleable&, void ); + DECL_LINK( OnFind, weld::Button&, void ); + + /** proceed, after the current convertible has been handled + + <p><b>Attention:</b> + When returning from this method, the dialog may have been deleted!</p> + + @param _bRepeatCurrentUnit + will be passed to the <member>ContinueConversion</member> call + */ + void implProceed( bool _bRepeatCurrentUnit ); + + // change the current convertible, and do _not_ proceed + void implChange( const OUString& _rChangeInto ); + + /** find the next convertible piece of text, with possibly advancing to the next portion + + @see HangulHanjaConversion::GetNextPortion + */ + bool implNextConvertible( bool _bRepeatUnit ); + + /** find the next convertible unit within the current portion + @param _bRepeatUnit + if <TRUE/>, the search will start at the beginning of the current unit, + if <FALSE/>, it will start at the end of the current unit + */ + bool implNextConvertibleUnit( const sal_Int32 _nStartAt ); + + /** retrieves the next portion, with setting the index members properly + @return + <TRUE/> if and only if there is a next portion + */ + bool implRetrieveNextPortion( ); + + /** determine the ConversionDirection for m_sCurrentPortion + @return + <FALSE/> if and only if something went wrong + */ + bool implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ); + + /** member m_aCurrentSuggestions and m_nCurrentEndIndex are updated according to the other settings and current dictionaries + + if _bAllowSearchNextConvertibleText is true _nStartAt is used as starting point to search the next + convertible text portion. This may result in changing of the member m_nCurrentStartIndex additionally. + + @return + <TRUE/> if Suggestions were found + */ + bool implUpdateSuggestions( const bool _bAllowSearchNextConvertibleText=false, const sal_Int32 _nStartAt=-1 ); + + /** reads the options from Hangul/Hanja Options dialog that are saved to configuration + */ + void implReadOptionsFromConfiguration(); + + /** get the string currently considered to be replaced or ignored + */ + OUString GetCurrentUnit() const; + + /** read options from configuration, update suggestion list and dialog content + */ + void implUpdateData(); + + /** get the conversion direction dependent from m_eConvType and m_eCurrentConversionDirection + in case of switching the direction is allowed this can be triggered with parameter bSwitchDirection + */ + sal_Int16 implGetConversionType( bool bSwitchDirection=false ) const; + }; + + HangulHanjaConversion_Impl::StringMap HangulHanjaConversion_Impl::m_aRecentlyUsedList = HangulHanjaConversion_Impl::StringMap(); + + HangulHanjaConversion_Impl::HangulHanjaConversion_Impl( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ) + : m_pUIParent( pUIParent ) + , m_xContext( rxContext ) + , m_aSourceLocale( _rSourceLocale ) + , m_nSourceLang( LanguageTag::convertToLanguageType( _rSourceLocale ) ) + , m_nTargetLang( LanguageTag::convertToLanguageType( _rTargetLocale ) ) + , m_pTargetFont( _pTargetFont ) + , m_nConvOptions(_nOptions) + , m_bIsInteractive( _bIsInteractive ) + , m_pAntiImpl( _pAntiImpl ) + , m_bByCharacter((_nOptions & CHARACTER_BY_CHARACTER) != 0) + , m_eConversionFormat( HHC::eSimpleConversion) + , m_ePrimaryConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_eCurrentConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_nCurrentPortionLang( LANGUAGE_NONE ) + , m_nCurrentStartIndex( 0 ) + , m_nCurrentEndIndex( 0 ) + , m_nReplacementBaseIndex( 0 ) + , m_nCurrentConversionOption( TextConversionOption::NONE ) + , m_nCurrentConversionType( -1 ) // not yet known + , m_bTryBothDirections( true ) + { + implReadOptionsFromConfiguration(); + + DBG_ASSERT( m_xContext.is(), "HangulHanjaConversion_Impl::HangulHanjaConversion_Impl: no ORB!" ); + + // determine conversion type + if (m_nSourceLang == LANGUAGE_KOREAN && m_nTargetLang == LANGUAGE_KOREAN) + m_eConvType = HHC::eConvHangulHanja; + else if ( (m_nSourceLang == LANGUAGE_CHINESE_TRADITIONAL && m_nTargetLang == LANGUAGE_CHINESE_SIMPLIFIED) || + (m_nSourceLang == LANGUAGE_CHINESE_SIMPLIFIED && m_nTargetLang == LANGUAGE_CHINESE_TRADITIONAL) ) + m_eConvType = HHC::eConvSimplifiedTraditional; + else + { + m_eConvType = HHC::eConvHangulHanja; + OSL_FAIL( "failed to determine conversion type from languages" ); + } + + m_xConverter = TextConversion::create( m_xContext ); + } + + void HangulHanjaConversion_Impl::createDialog() + { + DBG_ASSERT( m_bIsInteractive, "createDialog when the conversion should not be interactive?" ); + if ( !m_bIsInteractive || m_pConversionDialog ) + return; + + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + m_pConversionDialog = pFact->CreateHangulHanjaConversionDialog(m_pUIParent); + + m_pConversionDialog->EnableRubySupport( m_pAntiImpl->HasRubySupport() ); + + m_pConversionDialog->SetByCharacter( m_bByCharacter ); + m_pConversionDialog->SetConversionFormat( m_eConversionFormat ); + m_pConversionDialog->SetConversionDirectionState( m_bTryBothDirections, m_ePrimaryConversionDirection ); + + // the handlers + m_pConversionDialog->SetOptionsChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnOptionsChanged ) ); + m_pConversionDialog->SetIgnoreHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnore ) ); + m_pConversionDialog->SetIgnoreAllHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnoreAll ) ); + m_pConversionDialog->SetChangeHdl( LINK( this, HangulHanjaConversion_Impl, OnChange ) ); + m_pConversionDialog->SetChangeAllHdl( LINK( this, HangulHanjaConversion_Impl, OnChangeAll ) ); + m_pConversionDialog->SetClickByCharacterHdl( LINK( this, HangulHanjaConversion_Impl, OnByCharClicked ) ); + m_pConversionDialog->SetConversionFormatChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnConversionTypeChanged ) ); + m_pConversionDialog->SetFindHdl( LINK( this, HangulHanjaConversion_Impl, OnFind ) ); + } + + sal_Int16 HangulHanjaConversion_Impl::implGetConversionType( bool bSwitchDirection ) const + { + sal_Int16 nConversionType = -1; + if (m_eConvType == HHC::eConvHangulHanja) + nConversionType = ( HHC::eHangulToHanja == m_eCurrentConversionDirection && !bSwitchDirection ) ? TO_HANJA : TO_HANGUL; + else if (m_eConvType == HHC::eConvSimplifiedTraditional) + nConversionType = LANGUAGE_CHINESE_SIMPLIFIED == m_nTargetLang ? TO_SCHINESE : TO_TCHINESE; + DBG_ASSERT( nConversionType != -1, "unexpected conversion type" ); + return nConversionType; + } + + bool HangulHanjaConversion_Impl::implUpdateSuggestions( bool _bAllowSearchNextConvertibleText, const sal_Int32 _nStartAt ) + { + // parameters for the converter + sal_Int32 nStartSearch = m_nCurrentStartIndex; + if( _bAllowSearchNextConvertibleText ) + nStartSearch = _nStartAt; + + sal_Int32 nLength = m_sCurrentPortion.getLength() - nStartSearch; + m_nCurrentConversionType = implGetConversionType(); + m_nCurrentConversionOption = m_bByCharacter ? CHARACTER_BY_CHARACTER : css::i18n::TextConversionOption::NONE; + if( m_bIgnorePostPositionalWord ) + m_nCurrentConversionOption = m_nCurrentConversionOption | IGNORE_POST_POSITIONAL_WORD; + + // no need to check both directions for chinese conversion (saves time) + if (m_eConvType == HHC::eConvSimplifiedTraditional) + m_bTryBothDirections = false; + + bool bFoundAny = true; + try + { + TextConversionResult aResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption + ); + const bool bFoundPrimary = aResult.Boundary.startPos < aResult.Boundary.endPos; + bFoundAny = bFoundPrimary; + + if ( m_bTryBothDirections ) + { // see if we find another convertible when assuming the other direction + TextConversionResult aSecondResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + implGetConversionType( true ), // switched! + m_nCurrentConversionOption + ); + if ( aSecondResult.Boundary.startPos < aSecondResult.Boundary.endPos ) + { // we indeed found such a convertible + + // in case the first attempt (with the original conversion direction) + // didn't find anything + if ( !bFoundPrimary + // or if the second location is _before_ the first one + || ( aSecondResult.Boundary.startPos < aResult.Boundary.startPos ) + ) + { + // then use the second finding + aResult = aSecondResult; + + // our current conversion direction changed now + m_eCurrentConversionDirection = ( HHC::eHangulToHanja == m_eCurrentConversionDirection ) + ? HHC::eHanjaToHangul : HHC::eHangulToHanja; + bFoundAny = true; + } + } + } + + if( _bAllowSearchNextConvertibleText ) + { + //this might change the current position + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentStartIndex = aResult.Boundary.startPos; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + //the change of starting position is not allowed + if( m_nCurrentStartIndex == aResult.Boundary.startPos + && aResult.Boundary.endPos != aResult.Boundary.startPos ) + { + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + m_aCurrentSuggestions.realloc( 0 ); + if( m_sCurrentPortion.getLength() >= m_nCurrentStartIndex+1 ) + m_nCurrentEndIndex = m_nCurrentStartIndex+1; + } + } + + //put recently used string to front: + if( m_bShowRecentlyUsedFirst && m_aCurrentSuggestions.getLength()>1 ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + StringMap::const_iterator aRecentlyUsed = m_aRecentlyUsedList.find( sCurrentUnit ); + bool bUsedBefore = aRecentlyUsed != m_aRecentlyUsedList.end(); + if( bUsedBefore && m_aCurrentSuggestions[0] != aRecentlyUsed->second ) + { + sal_Int32 nCount = m_aCurrentSuggestions.getLength(); + Sequence< OUString > aTmp(nCount); + auto pTmp = aTmp.getArray(); + pTmp[0]=aRecentlyUsed->second; + sal_Int32 nDiff = 1; + for( sal_Int32 n=1; n<nCount; n++)//we had 0 already + { + if( nDiff && m_aCurrentSuggestions[n-nDiff]==aRecentlyUsed->second ) + nDiff=0; + pTmp[n]=m_aCurrentSuggestions[n-nDiff]; + } + m_aCurrentSuggestions = aTmp; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implNextConvertibleUnit" ); + + //!!! at least we want to move on in the text in order + //!!! to avoid an endless loop... + return false; + } + return bFoundAny; + } + + bool HangulHanjaConversion_Impl::implNextConvertibleUnit( const sal_Int32 _nStartAt ) + { + m_aCurrentSuggestions.realloc( 0 ); + + // ask the TextConversion service for the next convertible piece of text + + // get current values from dialog + if( m_eConvType == HHC::eConvHangulHanja && m_pConversionDialog ) + { + m_bTryBothDirections = m_pConversionDialog->GetUseBothDirections(); + HHC::ConversionDirection eDialogDirection = m_pConversionDialog->GetDirection( HHC::eHangulToHanja ); + + if( !m_bTryBothDirections && eDialogDirection != m_eCurrentConversionDirection ) + { + m_eCurrentConversionDirection = eDialogDirection; + } + + // save currently used value for possible later use + HangulHanjaConversion::m_bTryBothDirectionsSave = m_bTryBothDirections; + HangulHanjaConversion::m_ePrimaryConversionDirectionSave = m_eCurrentConversionDirection; + } + + bool bFoundAny = implUpdateSuggestions( true, _nStartAt ); + + return bFoundAny && + (m_nCurrentStartIndex < m_sCurrentPortion.getLength()); + } + + bool HangulHanjaConversion_Impl::implRetrieveNextPortion( ) + { + const bool bAllowImplicitChanges = m_eConvType == HHC::eConvSimplifiedTraditional; + + m_sCurrentPortion.clear(); + m_nCurrentPortionLang = LANGUAGE_NONE; + m_pAntiImpl->GetNextPortion( m_sCurrentPortion, m_nCurrentPortionLang, bAllowImplicitChanges ); + m_nReplacementBaseIndex = 0; + m_nCurrentStartIndex = m_nCurrentEndIndex = 0; + + bool bRet = !m_sCurrentPortion.isEmpty(); + + if (m_eConvType == HHC::eConvHangulHanja && m_bTryBothDirections) + implGetConversionDirectionForCurrentPortion( m_eCurrentConversionDirection ); + + return bRet; + } + + bool HangulHanjaConversion_Impl::implNextConvertible( bool _bRepeatUnit ) + { + if ( _bRepeatUnit || ( m_nCurrentEndIndex < m_sCurrentPortion.getLength() ) ) + { + if ( implNextConvertibleUnit( + _bRepeatUnit + ? m_nCurrentStartIndex + : m_nCurrentEndIndex + ) ) + return true; + } + + // no convertible text in the current portion anymore + // -> advance to the next portion + do + { + // next portion + if ( implRetrieveNextPortion( ) ) + { // there is a next portion + // -> find the next convertible unit in the current portion + if ( implNextConvertibleUnit( 0 ) ) + return true; + } + } + while ( !m_sCurrentPortion.isEmpty() ); + + // no more portions + return false; + } + + OUString HangulHanjaConversion_Impl::GetCurrentUnit() const + { + DBG_ASSERT( m_nCurrentStartIndex < m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentEndIndex <= m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentStartIndex <= m_nCurrentEndIndex, + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid interval!" ); + + OUString sCurrentUnit = m_sCurrentPortion.copy( m_nCurrentStartIndex, m_nCurrentEndIndex - m_nCurrentStartIndex ); + return sCurrentUnit; + } + + bool HangulHanjaConversion_Impl::ContinueConversion( bool _bRepeatCurrentUnit ) + { + while ( implNextConvertible( _bRepeatCurrentUnit ) ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + // do we need to ignore it? + const bool bAlwaysIgnoreThis = m_sIgnoreList.end() != m_sIgnoreList.find( sCurrentUnit ); + + // do we need to change it? + StringMap::const_iterator aChangeListPos = m_aChangeList.find( sCurrentUnit ); + const bool bAlwaysChangeThis = m_aChangeList.end() != aChangeListPos; + + // do we automatically change this? + const bool bAutoChange = m_bAutoReplaceUnique && m_aCurrentSuggestions.getLength() == 1; + + if (!m_bIsInteractive) + { + // silent conversion (e.g. for simplified/traditional Chinese)... + if(m_aCurrentSuggestions.hasElements()) + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if (bAutoChange) + { + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if ( bAlwaysChangeThis ) + { + implChange( aChangeListPos->second ); + } + else if ( !bAlwaysIgnoreThis ) + { + // here we need to ask the user for what to do with the text + // for this, allow derivees to highlight the current text unit in a possible document view + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + + // do not look for the next convertible: We have to wait for the user to interactively + // decide what happens with the current convertible + return false; + } + } + + return true; + } + + bool HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ) + { + // - For eConvHangulHanja the direction is determined by + // the first encountered Korean character. + // - For eConvSimplifiedTraditional the conversion direction + // is already specified by the source language. + + bool bSuccess = true; + + if (m_eConvType == HHC::eConvHangulHanja) + { + bSuccess = false; + try + { + // get the break iterator service + Reference< XBreakIterator > xBreakIter = i18n::BreakIterator::create( m_xContext ); + sal_Int32 nNextAsianScript = xBreakIter->beginOfScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( -1 == nNextAsianScript ) + nNextAsianScript = xBreakIter->nextScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( ( nNextAsianScript >= m_nCurrentStartIndex ) && ( nNextAsianScript < m_sCurrentPortion.getLength() ) ) + { // found asian text + + // determine if it's Hangul + CharClass aCharClassificaton( m_xContext, LanguageTag( m_aSourceLocale) ); + css::i18n::UnicodeScript nScript = aCharClassificaton.getScript( m_sCurrentPortion, sal::static_int_cast< sal_uInt16 >(nNextAsianScript) ); + if ( ( UnicodeScript_kHangulJamo == nScript ) + || ( UnicodeScript_kHangulCompatibilityJamo == nScript ) + || ( UnicodeScript_kHangulSyllable == nScript ) + ) + { + rDirection = HHC::eHangulToHanja; + } + else + { + rDirection = HHC::eHanjaToHangul; + } + + bSuccess = true; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion" ); + } + } + + return bSuccess; + } + + void HangulHanjaConversion_Impl::DoDocumentConversion( ) + { + // clear the change-all list - it's to be re-initialized for every single document + StringMap().swap(m_aChangeList); + + // first of all, we need to guess the direction of our conversion - it is determined by the first + // hangul or hanja character in the first text + if ( !implRetrieveNextPortion() ) + { + SAL_INFO( "editeng", "HangulHanjaConversion_Impl::DoDocumentConversion: why did you call me if you do have nothing to convert?" ); + // nothing to do + return; + } + if( m_eConvType == HHC::eConvHangulHanja ) + { + //init conversion direction from saved value + HHC::ConversionDirection eDirection = HHC::eHangulToHanja; + if(!implGetConversionDirectionForCurrentPortion( eDirection )) + // something went wrong, has already been asserted + return; + + if (HangulHanjaConversion::IsUseSavedConversionDirectionState()) + { + m_ePrimaryConversionDirection = HangulHanjaConversion::m_ePrimaryConversionDirectionSave; + m_bTryBothDirections = HangulHanjaConversion::m_bTryBothDirectionsSave; + if( m_bTryBothDirections ) + m_eCurrentConversionDirection = eDirection; + else + m_eCurrentConversionDirection = m_ePrimaryConversionDirection; + } + else + { + m_ePrimaryConversionDirection = eDirection; + m_eCurrentConversionDirection = eDirection; + } + } + + if (m_bIsInteractive && m_eConvType == HHC::eConvHangulHanja) + { + //always open dialog if at least having a hangul or hanja text portion + createDialog(); + if(HangulHanjaConversion::IsUseSavedConversionDirectionState()) + ContinueConversion( false ); + else + implUpdateData(); + m_pConversionDialog->Execute(); + m_pConversionDialog.disposeAndClear(); + } + else + { + const bool bCompletelyDone = ContinueConversion( false ); + DBG_ASSERT( bCompletelyDone, "HangulHanjaConversion_Impl::DoDocumentConversion: ContinueConversion should have returned true here!" ); + } + } + + void HangulHanjaConversion_Impl::implProceed( bool _bRepeatCurrentUnit ) + { + if ( ContinueConversion( _bRepeatCurrentUnit ) ) + { // we're done with the whole document + DBG_ASSERT( !m_bIsInteractive || m_pConversionDialog, "HangulHanjaConversion_Impl::implProceed: we should not reach this here without dialog!" ); + if ( m_pConversionDialog ) + m_pConversionDialog->EndDialog( RET_OK ); + } + } + + void HangulHanjaConversion_Impl::implChange( const OUString& _rChangeInto ) + { + if( _rChangeInto.isEmpty() ) + return; + + // translate the conversion format into a replacement action + // this translation depends on whether we have a Hangul original, or a Hanja original + + HHC::ReplacementAction eAction( HHC::eExchange ); + + if (m_eConvType == HHC::eConvHangulHanja) + { + // is the original we're about to change in Hangul? + const bool bOriginalIsHangul = HHC::eHangulToHanja == m_eCurrentConversionDirection; + + switch ( m_eConversionFormat ) + { + case HHC::eSimpleConversion: eAction = HHC::eExchange; break; + case HHC::eHangulBracketed: eAction = bOriginalIsHangul ? HHC::eOriginalBracketed : HHC::eReplacementBracketed; break; + case HHC::eHanjaBracketed: eAction = bOriginalIsHangul ? HHC::eReplacementBracketed : HHC::eOriginalBracketed; break; + case HHC::eRubyHanjaAbove: eAction = bOriginalIsHangul ? HHC::eReplacementAbove : HHC::eOriginalAbove; break; + case HHC::eRubyHanjaBelow: eAction = bOriginalIsHangul ? HHC::eReplacementBelow : HHC::eOriginalBelow; break; + case HHC::eRubyHangulAbove: eAction = bOriginalIsHangul ? HHC::eOriginalAbove : HHC::eReplacementAbove; break; + case HHC::eRubyHangulBelow: eAction = bOriginalIsHangul ? HHC::eOriginalBelow : HHC::eReplacementBelow; break; + default: + OSL_FAIL( "HangulHanjaConversion_Impl::implChange: invalid/unexpected conversion format!" ); + } + } + + // the proper indices (the wrapper implementation needs indices relative to the + // previous replacement) + DBG_ASSERT( ( m_nReplacementBaseIndex <= m_nCurrentStartIndex ) && ( m_nReplacementBaseIndex <= m_nCurrentEndIndex ), + "HangulHanjaConversion_Impl::implChange: invalid replacement base!" ); + + sal_Int32 nStartIndex = m_nCurrentStartIndex - m_nReplacementBaseIndex; + sal_Int32 nEndIndex = m_nCurrentEndIndex - m_nReplacementBaseIndex; + + //remind this decision + m_aRecentlyUsedList[ GetCurrentUnit() ] = _rChangeInto; + + LanguageType *pNewUnitLang = nullptr; + LanguageType nNewUnitLang = LANGUAGE_NONE; + if (m_eConvType == HHC::eConvSimplifiedTraditional) + { + // check if language needs to be changed + if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL && + !HangulHanjaConversion::IsTraditional( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_TRADITIONAL; + else if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED && + !HangulHanjaConversion::IsSimplified( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_SIMPLIFIED; + if (nNewUnitLang != LANGUAGE_NONE) + pNewUnitLang = &nNewUnitLang; + } + + // according to FT we should not (yet) bother about Hangul/Hanja conversion here + // + // aOffsets is needed in ReplaceUnit below in order to find out + // exactly which characters are really changed in order to keep as much + // from attributation for the text as possible. + Sequence< sal_Int32 > aOffsets; + if (m_eConvType == HHC::eConvSimplifiedTraditional && m_xConverter.is()) + { + try + { + m_xConverter->getConversionWithOffset( + m_sCurrentPortion, + m_nCurrentStartIndex, + m_nCurrentEndIndex - m_nCurrentStartIndex, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption, + aOffsets + ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implChange: caught unexpected exception!" ); + aOffsets.realloc(0); + } + } + + // do the replacement + m_pAntiImpl->ReplaceUnit( nStartIndex, nEndIndex, m_sCurrentPortion, + _rChangeInto, aOffsets, eAction, pNewUnitLang ); + + + // adjust the replacement base + m_nReplacementBaseIndex = m_nCurrentEndIndex; + } + + void HangulHanjaConversion_Impl::implReadOptionsFromConfiguration() + { + SvtLinguConfig aLngCfg; + aLngCfg.GetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD ) >>= m_bIgnorePostPositionalWord; + aLngCfg.GetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST ) >>= m_bShowRecentlyUsedFirst; + aLngCfg.GetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES ) >>= m_bAutoReplaceUnique; + } + + void HangulHanjaConversion_Impl::implUpdateData() + { + implReadOptionsFromConfiguration(); + implUpdateSuggestions(); + + if(m_pConversionDialog) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + m_pConversionDialog->FocusSuggestion(); + } + + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnOptionsChanged, LinkParamNone*, void) + { + //options and dictionaries might have been changed + //-> update our internal settings and the dialog + implUpdateData(); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnore, weld::Button&, void) + { + // simply ignore, and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnoreAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnIgnoreAll: no dialog! How this?" ); + + if ( m_pConversionDialog ) + { + OUString sCurrentUnit = m_pConversionDialog->GetCurrentString(); + DBG_ASSERT( m_sIgnoreList.end() == m_sIgnoreList.find( sCurrentUnit ), + "HangulHanjaConversion_Impl, OnIgnoreAll: shouldn't this have been ignored before" ); + + // put into the "ignore all" list + m_sIgnoreList.insert( sCurrentUnit ); + + // and proceed + implProceed( false ); + } + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChange, weld::Button&, void) + { + // change + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + implChange( m_pConversionDialog->GetCurrentSuggestion( ) ); + // and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChangeAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnChangeAll: no dialog! How this?" ); + if ( !m_pConversionDialog ) + return; + + OUString sCurrentUnit( m_pConversionDialog->GetCurrentString() ); + OUString sChangeInto( m_pConversionDialog->GetCurrentSuggestion( ) ); + + if( !sChangeInto.isEmpty() ) + { + // change the current occurrence + implChange( sChangeInto ); + + // put into the "change all" list + m_aChangeList.emplace( sCurrentUnit, sChangeInto ); + } + + // and proceed + implProceed( false ); + } + + IMPL_LINK(HangulHanjaConversion_Impl, OnByCharClicked, weld::Toggleable&, rBox, void) + { + m_bByCharacter = rBox.get_active(); + + // continue conversion, without advancing to the next unit, but instead continuing with the current unit + implProceed( true ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnConversionTypeChanged, weld::Toggleable&, void) + { + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_eConversionFormat = m_pConversionDialog->GetConversionFormat( ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnFind, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnFind: where did this come from?" ); + if ( !m_pConversionDialog ) + return; + + try + { + OUString sNewOriginal( m_pConversionDialog->GetCurrentSuggestion( ) ); + Sequence< OUString > aSuggestions; + + DBG_ASSERT( m_xConverter.is(), "HangulHanjaConversion_Impl::OnFind: no converter!" ); + TextConversionResult aToHanja = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANJA, + TextConversionOption::NONE + ); + TextConversionResult aToHangul = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANGUL, + TextConversionOption::NONE + ); + + bool bHaveToHanja = ( aToHanja.Boundary.startPos < aToHanja.Boundary.endPos ); + bool bHaveToHangul = ( aToHangul.Boundary.startPos < aToHangul.Boundary.endPos ); + + TextConversionResult* pResult = nullptr; + if ( bHaveToHanja && bHaveToHangul ) + { // it found convertibles in both directions -> use the first + if ( aToHangul.Boundary.startPos < aToHanja.Boundary.startPos ) + pResult = &aToHangul; + else + pResult = &aToHanja; + } + else if ( bHaveToHanja ) + { // only found toHanja + pResult = &aToHanja; + } + else + { // only found toHangul + pResult = &aToHangul; + } + if ( pResult ) + aSuggestions = pResult->Candidates; + + m_pConversionDialog->SetCurrentString( sNewOriginal, aSuggestions, false ); + m_pConversionDialog->FocusSuggestion(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::OnFind" ); + } + } + + bool HangulHanjaConversion::m_bUseSavedValues = false; + bool HangulHanjaConversion::m_bTryBothDirectionsSave = false; + HHC::ConversionDirection HangulHanjaConversion::m_ePrimaryConversionDirectionSave = HHC::eHangulToHanja; + + HangulHanjaConversion::HangulHanjaConversion( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, bool _bIsInteractive) + :m_pImpl( new HangulHanjaConversion_Impl( pUIParent, rxContext, _rSourceLocale, _rTargetLocale, _pTargetFont, _nOptions, _bIsInteractive, this ) ) + { + } + + HangulHanjaConversion::~HangulHanjaConversion() COVERITY_NOEXCEPT_FALSE + { + } + + void HangulHanjaConversion::SetUseSavedConversionDirectionState( bool bVal ) + { + m_bUseSavedValues = bVal; + } + + bool HangulHanjaConversion::IsUseSavedConversionDirectionState() + { + return m_bUseSavedValues; + } + + weld::Widget* HangulHanjaConversion::GetUIParent() const + { + return m_pImpl->GetUIParent(); + } + + LanguageType HangulHanjaConversion::GetSourceLanguage( ) const + { + return m_pImpl->GetSourceLang(); + } + + LanguageType HangulHanjaConversion::GetTargetLanguage( ) const + { + return m_pImpl->GetTargetLang(); + } + + const vcl::Font * HangulHanjaConversion::GetTargetFont( ) const + { + return m_pImpl->GetTargetFont(); + } + + sal_Int32 HangulHanjaConversion::GetConversionOptions( ) const + { + return m_pImpl->GetConvOptions(); + } + + bool HangulHanjaConversion::IsInteractive( ) const + { + return m_pImpl->IsInteractive(); + } + + void HangulHanjaConversion::ConvertDocument() + { + if ( m_pImpl->IsValid() ) + m_pImpl->DoDocumentConversion( ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/splwrap.cxx b/editeng/source/misc/splwrap.cxx new file mode 100644 index 0000000000..dd59113a69 --- /dev/null +++ b/editeng/source/misc/splwrap.cxx @@ -0,0 +1,472 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <rtl/ustring.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svtools/langtab.hxx> + +#include <vcl/errinf.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> + +#include <editeng/svxenum.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/edtdlg.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <editeng/editerr.hxx> + +#include <map> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + + +// misc functions --------------------------------------------- + +void SvxPrepareAutoCorrect( OUString &rOldText, std::u16string_view rNewText ) +{ + // This function should be used to strip (or add) trailing '.' from + // the strings before passing them on to the autocorrect function in + // order that the autocorrect function will hopefully + // works properly with normal words and abbreviations (with trailing '.') + // independent of if they are at the end of the sentence or not. + // + // rOldText: text to be replaced + // rNewText: replacement text + + sal_Int32 nOldLen = rOldText.getLength(); + sal_Int32 nNewLen = rNewText.size(); + if (nOldLen && nNewLen) + { + bool bOldHasDot = '.' == rOldText[ nOldLen - 1 ], + bNewHasDot = '.' == rNewText[ nNewLen - 1 ]; + if (bOldHasDot && !bNewHasDot + /*this is: !(bOldHasDot && bNewHasDot) && bOldHasDot*/) + rOldText = rOldText.copy( 0, nOldLen - 1 ); + } +} + +#define SVX_LANG_NEED_CHECK 0 +#define SVX_LANG_OK 1 +#define SVX_LANG_MISSING 2 +#define SVX_LANG_MISSING_DO_WARN 3 + +typedef std::map< LanguageType, sal_uInt16 > LangCheckState_map_t; + +static LangCheckState_map_t & GetLangCheckState() +{ + static LangCheckState_map_t aLangCheckState; + return aLangCheckState; +} + +void SvxSpellWrapper::ShowLanguageErrors() +{ + // display message boxes for languages not available for + // spellchecking or hyphenation + LangCheckState_map_t &rLCS = GetLangCheckState(); + for (auto const& elem : rLCS) + { + LanguageType nLang = elem.first; + sal_uInt16 nVal = elem.second; + sal_uInt16 nTmpSpell = nVal & 0x00FF; + sal_uInt16 nTmpHyph = (nVal >> 8) & 0x00FF; + + if (SVX_LANG_MISSING_DO_WARN == nTmpSpell) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + ErrCodeMsg( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpSpell = SVX_LANG_MISSING; + } + if (SVX_LANG_MISSING_DO_WARN == nTmpHyph) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + ErrCodeMsg( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpHyph = SVX_LANG_MISSING; + } + + rLCS[ nLang ] = (nTmpHyph << 8) | nTmpSpell; + } + +} + +SvxSpellWrapper::~SvxSpellWrapper() +{ +} + +/*-------------------------------------------------------------------- + * Description: Constructor, the test sequence is determined + * + * !bStart && !bOtherCntnt: BODY_END, BODY_START, OTHER + * !bStart && bOtherCntnt: OTHER, BODY + * bStart && !bOtherCntnt: BODY_END, OTHER + * bStart && bOtherCntnt: OTHER + * + --------------------------------------------------------------------*/ + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + const bool bStart, const bool bIsAllRight ) : + + pWin ( pWn ), + bOtherCntnt ( false ), + bStartChk ( false ), + bRevAllowed ( true ), + bAllRight ( bIsAllRight ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bReverse = bWrapReverse; + bStartDone = !bReverse && bStart; + bEndDone = bReverse && bStart; +} + + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + Reference< XHyphenator > const &xHyphenator, + const bool bStart, const bool bOther ) : + pWin ( pWn ), + xHyph ( xHyphenator ), + bOtherCntnt ( bOther ), + bReverse ( false ), + bStartDone ( bOther || bStart ), + bEndDone ( false ), + bStartChk ( bOther ), + bRevAllowed ( false ), + bAllRight ( true ) +{ +} + + +sal_Int16 SvxSpellWrapper::CheckSpellLang( + Reference< XSpellChecker1 > const & xSpell, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? SVX_LANG_NEED_CHECK : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == (nVal & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(nLang) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0xFF00; + nVal |= nTmpVal; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + +sal_Int16 SvxSpellWrapper::CheckHyphLang( + Reference< XHyphenator > const & xHyph, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? 0 : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == ((nVal >> 8) & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xHyph.is() && xHyph->hasLocale( LanguageTag::convertToLocale( nLang ) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0x00FF; + nVal |= nTmpVal << 8; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + + +void SvxSpellWrapper::SpellStart( SvxSpellArea /*eSpell*/ ) +{ // Here, the necessary preparations be made for SpellContinue in the +} // given area. + + +bool SvxSpellWrapper::SpellMore() +{ + return false; // Should additional documents be examined? +} + + +void SvxSpellWrapper::SpellEnd() +{ // Area is complete, tidy up if necessary + + // display error for last language not found + ShowLanguageErrors(); +} + +void SvxSpellWrapper::SpellContinue() +{ +} + +void SvxSpellWrapper::ReplaceAll( const OUString & ) +{ // Replace Word from the Replace list +} + +void SvxSpellWrapper::InsertHyphen( const sal_Int32 ) +{ // inserting and deleting Hyphen +} + +// Testing of the document areas in the order specified by the flags +void SvxSpellWrapper::SpellDocument( ) +{ +#if ENABLE_WASM_STRIP_HUNSPELL + return; +#else + if ( bOtherCntnt ) + { + bReverse = false; + SpellStart( SvxSpellArea::Other ); + } + else + { + bStartChk = bReverse; + SpellStart( bReverse ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + } + + if ( !FindSpellError() ) + return; + + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xHyphWord.is()) + { + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractHyphenWordDialog> pDlg(pFact->CreateHyphenWordDialog( + pWin, + xHyphWord->getWord(), + LanguageTag( xHyphWord->getLocale() ).getLanguageType(), + xHyph, this )); + pDlg->Execute(); + } +#endif +} + + +// Select the next area + + +bool SvxSpellWrapper::SpellNext( ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bool bActRev = bRevAllowed && bWrapReverse; + + // bActRev is the direction after Spell checking, bReverse is the one + // at the beginning. + if( bActRev == bReverse ) + { // No change of direction, thus is the + if( bStartChk ) // desired area ( bStartChk ) + bStartDone = true; // completely processed. + else + bEndDone = true; + } + else if( bReverse == bStartChk ) //For a change of direction, an area can + { // be processed during certain circumstances + if( bStartChk ) // If the first part is spell checked in backwards + bEndDone = true; // and this is reversed in the process, then + else // then the end part is processed (and vice-versa). + bStartDone = true; + } + + bReverse = bActRev; + if( bOtherCntnt && bStartDone && bEndDone ) // Document has been fully checked? + { + if ( SpellMore() ) // spell check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + return false; + } + + bool bGoOn = false; + + if ( bOtherCntnt ) + { + bStartChk = false; + SpellStart( SvxSpellArea::Body ); + bGoOn = true; + } + else if ( bStartDone && bEndDone ) + { + if ( SpellMore() ) // check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + } + else + { + // a BODY_area done, ask for the other BODY_area + xWait.reset(); + + TranslateId pResId = bReverse ? RID_SVXSTR_QUERY_BW_CONTINUE : RID_SVXSTR_QUERY_CONTINUE; + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Question, VclButtonsType::YesNo, + EditResId(pResId))); + if (xBox->run() != RET_YES) + { + // sacrifice the other area if necessary ask for special area + xWait.reset(new weld::WaitObject(pWin)); + bStartDone = bEndDone = true; + return SpellNext(); + } + else + { + bStartChk = !bStartDone; + SpellStart( bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + bGoOn = true; + } + xWait.reset(new weld::WaitObject(pWin)); + } + return bGoOn; +} + + +Reference< XDictionary > SvxSpellWrapper::GetAllRightDic() +{ + Reference< XDictionary > xDic; + + Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + { + Sequence< Reference< XDictionary > > aDics( xDicList->getDictionaries() ); + const Reference< XDictionary > *pDic = aDics.getConstArray(); + sal_Int32 nCount = aDics.getLength(); + + sal_Int32 i = 0; + while (!xDic.is() && i < nCount) + { + Reference< XDictionary > xTmp = pDic[i]; + if (xTmp.is()) + { + if ( xTmp->isActive() && + xTmp->getDictionaryType() != DictionaryType_NEGATIVE && + LanguageTag( xTmp->getLocale() ).getLanguageType() == LANGUAGE_NONE ) + { + Reference< frame::XStorable > xStor( xTmp, UNO_QUERY ); + if (xStor.is() && xStor->hasLocation() && !xStor->isReadonly()) + { + xDic = xTmp; + } + } + } + ++i; + } + + if (!xDic.is()) + { + xDic = LinguMgr::GetStandardDic(); + if (xDic.is()) + xDic->setActive( true ); + } + } + + return xDic; +} + + +bool SvxSpellWrapper::FindSpellError() +{ + ShowLanguageErrors(); + + xWait.reset(new weld::WaitObject(pWin)); + bool bSpell = true; + + Reference< XDictionary > xAllRightDic; + if (IsAllRight()) + xAllRightDic = GetAllRightDic(); + + while ( bSpell ) + { + SpellContinue(); + + Reference< XSpellAlternatives > xAlt( GetLast(), UNO_QUERY ); + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xAlt.is()) + { + if (IsAllRight() && xAllRightDic.is()) + { + xAllRightDic->add( xAlt->getWord(), false, OUString() ); + } + else + { + // look up in ChangeAllList for misspelled word + Reference< XDictionary > xChangeAllList = + LinguMgr::GetChangeAllList(); + Reference< XDictionaryEntry > xEntry; + if (xChangeAllList.is()) + xEntry = xChangeAllList->getEntry( xAlt->getWord() ); + + if (xEntry.is()) + { + // replace word without asking + ReplaceAll( xEntry->getReplacementText() ); + } + else + bSpell = false; + } + } + else if (xHyphWord.is()) + bSpell = false; + else + { + SpellEnd(); + bSpell = SpellNext(); + } + } + xWait.reset(); + return GetLast().is(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx new file mode 100644 index 0000000000..ff2c4518aa --- /dev/null +++ b/editeng/source/misc/svxacorr.cxx @@ -0,0 +1,3161 @@ +/* -*- 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 <memory> +#include <utility> +#include <algorithm> +#include <string_view> +#include <sal/config.h> + +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <tools/urlobj.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nutil/transliteration.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svl/fstathelper.hxx> +#include <svl/urihelper.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/OrdinalSuffix.hpp> +#include <unotools/localedatawrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <o3tl/string_view.hxx> +#include <editeng/editids.hrc> +#include <sot/storage.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/unolingu.hxx> +#include <vcl/window.hxx> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <unotools/streamwrap.hxx> +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectExport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <ucbhelper/content.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <xmloff/xmltoken.hxx> +#include <unordered_map> +#include <rtl/character.hxx> + +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star; +using namespace ::xmloff::token; +using namespace ::utl; + +namespace { + +enum class Flags { + NONE = 0x00, + FullStop = 0x01, + ExclamationMark = 0x02, + QuestionMark = 0x04, +}; + +} + +namespace o3tl { + template<> struct typed_flags<Flags> : is_typed_flags<Flags, 0x07> {}; +} +const sal_Unicode cNonBreakingSpace = 0xA0; // UNICODE code for no break space + +constexpr OUString pXMLImplWordStart_ExcptLstStr = u"WordExceptList.xml"_ustr; +constexpr OUString pXMLImplCplStt_ExcptLstStr = u"SentenceExceptList.xml"_ustr; +constexpr OUString pXMLImplAutocorr_ListStr = u"DocumentList.xml"_ustr; + +// tdf#54409 check also typographical quotation marks in the case of skipped ASCII quotation marks +// Curious, why these \u0083\u0084\u0089\u0091\u0092\u0093\u0094 are handled as "begin characters"? +constexpr std::u16string_view + /* also at these beginnings - Brackets and all kinds of begin characters */ + sImplSttSkipChars = u"\"'([{\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u0083\u0084\u0089\u0091\u0092\u0093\u0094", + /* also at these ends - Brackets and all kinds of begin characters */ + sImplEndSkipChars = u"\"')]}\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u0083\u0084\u0089\u0091\u0092\u0093\u0094"; + +static OUString EncryptBlockName_Imp(std::u16string_view rName); + +static bool NonFieldWordDelim( const sal_Unicode c ) +{ + return ' ' == c || '\t' == c || 0x0a == c || + cNonBreakingSpace == c || 0x2011 == c; +} + +static bool IsWordDelim( const sal_Unicode c ) +{ + return c == 0x1 || NonFieldWordDelim(c); +} + + +static bool IsLowerLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::LOWER & nCharType); +} + +static bool IsUpperLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::UPPER & nCharType); +} + +static bool lcl_IsUnsupportedUnicodeChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + css::i18n::UnicodeScript nScript = rCC.getScript( rTxt, nStt ); + switch( nScript ) + { + case css::i18n::UnicodeScript_kCJKRadicalsSupplement: + case css::i18n::UnicodeScript_kHangulJamo: + case css::i18n::UnicodeScript_kCJKSymbolPunctuation: + case css::i18n::UnicodeScript_kHiragana: + case css::i18n::UnicodeScript_kKatakana: + case css::i18n::UnicodeScript_kHangulCompatibilityJamo: + case css::i18n::UnicodeScript_kEnclosedCJKLetterMonth: + case css::i18n::UnicodeScript_kCJKCompatibility: + case css::i18n::UnicodeScript_kCJKUnifiedIdeographsExtensionA: + case css::i18n::UnicodeScript_kCJKUnifiedIdeograph: + case css::i18n::UnicodeScript_kHangulSyllable: + case css::i18n::UnicodeScript_kCJKCompatibilityIdeograph: + case css::i18n::UnicodeScript_kHalfwidthFullwidthForm: + return true; + default: ; //do nothing + } + } + return false; +} + +static bool lcl_IsSymbolChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + if( css::i18n::UnicodeType::PRIVATE_USE == rCC.getType( rTxt, nStt )) + return true; + } + return false; +} + +static bool lcl_IsInArr(std::u16string_view arr, const sal_uInt32 c) +{ + return std::any_of(arr.begin(), arr.end(), [c](const auto c1) { return c1 == c; }); +} + +SvxAutoCorrDoc::~SvxAutoCorrDoc() +{ +} + +// Called by the functions: +// - FnCapitalStartWord +// - FnCapitalStartSentence +// after the exchange of characters. Then the words, if necessary, can be inserted +// into the exception list. +void SvxAutoCorrDoc::SaveCpltSttWord( ACFlags, sal_Int32, const OUString&, + sal_Unicode ) +{ +} + +LanguageType SvxAutoCorrDoc::GetLanguage( sal_Int32 ) const +{ + return LANGUAGE_SYSTEM; +} + +static const LanguageTag& GetAppLang() +{ + return Application::GetSettings().GetLanguageTag(); +} + +/// Never use an unresolved LANGUAGE_SYSTEM. +static LanguageType GetDocLanguage( const SvxAutoCorrDoc& rDoc, sal_Int32 nPos ) +{ + LanguageType eLang = rDoc.GetLanguage( nPos ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); // the current work locale + return eLang; +} + +static LocaleDataWrapper& GetLocaleDataWrapper( LanguageType nLang ) +{ + static std::unique_ptr<LocaleDataWrapper> xLclDtWrp; + LanguageTag aLcl( nLang ); + if (!xLclDtWrp || xLclDtWrp->getLoadedLanguageTag() != aLcl) + xLclDtWrp.reset(new LocaleDataWrapper(std::move(aLcl))); + return *xLclDtWrp; +} +static TransliterationWrapper& GetIgnoreTranslWrapper() +{ + static int bIsInit = 0; + static TransliterationWrapper aWrp( ::comphelper::getProcessComponentContext(), + TransliterationFlags::IGNORE_KANA | + TransliterationFlags::IGNORE_WIDTH ); + if( !bIsInit ) + { + aWrp.loadModuleIfNeeded( GetAppLang().getLanguageType() ); + bIsInit = 1; + } + return aWrp; +} +static CollatorWrapper& GetCollatorWrapper() +{ + static CollatorWrapper aCollWrp = []() + { + CollatorWrapper tmp( ::comphelper::getProcessComponentContext() ); + tmp.loadDefaultCollator( GetAppLang().getLocale(), 0 ); + return tmp; + }(); + return aCollWrp; +} + +bool SvxAutoCorrect::IsAutoCorrectChar( sal_Unicode cChar ) +{ + return cChar == '\0' || cChar == '\t' || cChar == 0x0a || + cChar == ' ' || cChar == '\'' || cChar == '\"' || + cChar == '*' || cChar == '_' || cChar == '%' || + cChar == '.' || cChar == ',' || cChar == ';' || + cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '<' || cChar == '>' || + cChar == '/' || cChar == '-'; +} + +namespace +{ + bool IsCompoundWordDelimChar(sal_Unicode cChar) + { + return cChar == '-' || SvxAutoCorrect::IsAutoCorrectChar(cChar); + } +} + +bool SvxAutoCorrect::NeedsHardspaceAutocorr( sal_Unicode cChar ) +{ + return cChar == '%' || cChar == ';' || cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '/' /*case for the urls exception*/; +} + +ACFlags SvxAutoCorrect::GetDefaultFlags() +{ + ACFlags nRet = ACFlags::Autocorrect + | ACFlags::CapitalStartSentence + | ACFlags::CapitalStartWord + | ACFlags::ChgOrdinalNumber + | ACFlags::ChgToEnEmDash + | ACFlags::AddNonBrkSpace + | ACFlags::TransliterateRTL + | ACFlags::ChgAngleQuotes + | ACFlags::ChgWeightUnderl + | ACFlags::SetINetAttr + | ACFlags::SetDOIAttr + | ACFlags::ChgQuotes + | ACFlags::SaveWordCplSttLst + | ACFlags::SaveWordWordStartLst + | ACFlags::CorrectCapsLock; + LanguageType eLang = GetAppLang().getLanguageType(); + if( eLang.anyOf( + LANGUAGE_ENGLISH, + LANGUAGE_ENGLISH_US, + LANGUAGE_ENGLISH_UK, + LANGUAGE_ENGLISH_AUS, + LANGUAGE_ENGLISH_CAN, + LANGUAGE_ENGLISH_NZ, + LANGUAGE_ENGLISH_EIRE, + LANGUAGE_ENGLISH_SAFRICA, + LANGUAGE_ENGLISH_JAMAICA, + LANGUAGE_ENGLISH_CARIBBEAN)) + nRet &= ~ACFlags(ACFlags::ChgQuotes|ACFlags::ChgSglQuotes); + return nRet; +} + +constexpr sal_Unicode cEmDash = 0x2014; +constexpr sal_Unicode cEnDash = 0x2013; +constexpr OUString sEmDash(u"\u2014"_ustr); +constexpr OUString sEnDash(u"\u2013"_ustr); +constexpr sal_Unicode cApostrophe = 0x2019; +constexpr sal_Unicode cLeftDoubleAngleQuote = 0xAB; +constexpr sal_Unicode cRightDoubleAngleQuote = 0xBB; +constexpr sal_Unicode cLeftSingleAngleQuote = 0x2039; +constexpr sal_Unicode cRightSingleAngleQuote = 0x203A; +// stop characters for searching preceding quotes +// (the first character is also the opening quote we are looking for) +const sal_Unicode aStopDoubleAngleQuoteStart[] = { 0x201E, 0x201D, 0x201C, 0 }; // preceding ,, +const sal_Unicode aStopDoubleAngleQuoteEnd[] = { cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0x201D, 0x201E, 0 }; // preceding >> +// preceding << for Romanian, handle also alternative primary closing quotation mark U+201C +const sal_Unicode aStopDoubleAngleQuoteEndRo[] = { cLeftDoubleAngleQuote, cRightDoubleAngleQuote, 0x201D, 0x201E, 0x201C, 0 }; +const sal_Unicode aStopSingleQuoteEnd[] = { 0x201A, 0x2018, 0x201C, 0x201E, 0 }; +const sal_Unicode aStopSingleQuoteEndRuUa[] = { 0x201E, 0x201C, cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0 }; + +SvxAutoCorrect::SvxAutoCorrect( OUString aShareAutocorrFile, + OUString aUserAutocorrFile ) + : sShareAutoCorrFile(std::move( aShareAutocorrFile )) + , sUserAutoCorrFile(std::move( aUserAutocorrFile )) + , eCharClassLang( LANGUAGE_DONTKNOW ) + , nFlags(SvxAutoCorrect::GetDefaultFlags()) + , cStartDQuote( 0 ) + , cEndDQuote( 0 ) + , cStartSQuote( 0 ) + , cEndSQuote( 0 ) +{ +} + +SvxAutoCorrect::SvxAutoCorrect( const SvxAutoCorrect& rCpy ) + : sShareAutoCorrFile( rCpy.sShareAutoCorrFile ) + , sUserAutoCorrFile( rCpy.sUserAutoCorrFile ) + , aSwFlags( rCpy.aSwFlags ) + , eCharClassLang(rCpy.eCharClassLang) + , nFlags( rCpy.nFlags & ~ACFlags(ACFlags::ChgWordLstLoad|ACFlags::CplSttLstLoad|ACFlags::WordStartLstLoad)) + , cStartDQuote( rCpy.cStartDQuote ) + , cEndDQuote( rCpy.cEndDQuote ) + , cStartSQuote( rCpy.cStartSQuote ) + , cEndSQuote( rCpy.cEndSQuote ) +{ +} + + +SvxAutoCorrect::~SvxAutoCorrect() +{ +} + +void SvxAutoCorrect::GetCharClass_( LanguageType eLang ) +{ + moCharClass.emplace( LanguageTag( eLang) ); + eCharClassLang = eLang; +} + +void SvxAutoCorrect::SetAutoCorrFlag( ACFlags nFlag, bool bOn ) +{ + ACFlags nOld = nFlags; + nFlags = bOn ? nFlags | nFlag + : nFlags & ~nFlag; + + if( !bOn ) + { + if( (nOld & ACFlags::CapitalStartSentence) != (nFlags & ACFlags::CapitalStartSentence) ) + nFlags &= ~ACFlags::CplSttLstLoad; + if( (nOld & ACFlags::CapitalStartWord) != (nFlags & ACFlags::CapitalStartWord) ) + nFlags &= ~ACFlags::WordStartLstLoad; + if( (nOld & ACFlags::Autocorrect) != (nFlags & ACFlags::Autocorrect) ) + nFlags &= ~ACFlags::ChgWordLstLoad; + } +} + + +// Correct TWo INitial CApitals +void SvxAutoCorrect::FnCapitalStartWord( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + CharClass& rCC = GetCharClass( eLang ); + + // Delete all non alphanumeric. Test the characters at the beginning/end of + // the word ( recognizes: "(min.", "/min.", and so on.) + for( ; nSttPos < nEndPos; ++nSttPos ) + if( rCC.isLetterNumeric( rTxt, nSttPos )) + break; + for( ; nSttPos < nEndPos; --nEndPos ) + if( rCC.isLetterNumeric( rTxt, nEndPos - 1 )) + break; + + // Is the word a compounded word separated by delimiters? + // If so, keep track of all delimiters so each constituent + // word can be checked for two initial capital letters. + std::deque<sal_Int32> aDelimiters; + + // Always check for two capitals at the beginning + // of the entire word, so start at nSttPos. + aDelimiters.push_back(nSttPos); + + // Find all compound word delimiters + for (sal_Int32 n = nSttPos; n < nEndPos; ++n) + { + if (IsCompoundWordDelimChar(rTxt[ n ])) + { + aDelimiters.push_back( n + 1 ); // Get position of char after delimiter + } + } + + // Decide where to put the terminating delimiter. + // If the last AutoCorrect char was a newline, then the AutoCorrect + // char will not be included in rTxt. + // If the last AutoCorrect char was not a newline, then the AutoCorrect + // character will be the last character in rTxt. + if (!IsCompoundWordDelimChar(rTxt[nEndPos-1])) + aDelimiters.push_back(nEndPos); + + // Iterate through the word and all words that compose it. + // Two capital letters at the beginning of word? + for (size_t nI = 0; nI < aDelimiters.size() - 1; ++nI) + { + nSttPos = aDelimiters[nI]; + nEndPos = aDelimiters[nI + 1]; + + if( nSttPos+2 < nEndPos && + IsUpperLetter( rCC.getCharacterType( rTxt, nSttPos )) && + IsUpperLetter( rCC.getCharacterType( rTxt, ++nSttPos )) && + // Is the third character a lower case + IsLowerLetter( rCC.getCharacterType( rTxt, nSttPos +1 )) && + // Do not replace special attributes + 0x1 != rTxt[ nSttPos ] && 0x2 != rTxt[ nSttPos ]) + { + // test if the word is in an exception list + OUString sWord( rTxt.copy( nSttPos - 1, nEndPos - nSttPos + 1 )); + if( !FindInWordStartExceptList(eLang, sWord) ) + { + // Check that word isn't correctly spelt before correcting: + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller = + LinguMgr::GetSpellChecker(); + if( xSpeller->hasLanguage(static_cast<sal_uInt16>(eLang)) ) + { + Sequence< css::beans::PropertyValue > aEmptySeq; + if (xSpeller->isValid(sWord, static_cast<sal_uInt16>(eLang), aEmptySeq)) + { + return; + } + } + sal_Unicode cSave = rTxt[ nSttPos ]; + OUString sChar = rCC.lowercase( OUString(cSave) ); + if( sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar )) + { + if( ACFlags::SaveWordWordStartLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartWord, nSttPos, sWord, cSave ); + } + } + } + } +} + +// Format ordinal numbers suffixes (1st -> 1^st) +bool SvxAutoCorrect::FnChgOrdinalNumber( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang) +{ + // 1st, 2nd, 3rd, 4 - 0th + // 201th or 201st + // 12th or 12nd + bool bChg = false; + + // In some languages ordinal suffixes should never be + // changed to superscript. Let's break for those languages. + if (!eLang.anyOf( + LANGUAGE_SWEDISH, + LANGUAGE_SWEDISH_FINLAND)) + { + CharClass& rCC = GetCharClass(eLang); + + for (; nSttPos < nEndPos; ++nSttPos) + if (!lcl_IsInArr(sImplSttSkipChars, rTxt[nSttPos])) + break; + for (; nSttPos < nEndPos; --nEndPos) + if (!lcl_IsInArr(sImplEndSkipChars, rTxt[nEndPos - 1])) + break; + + + // Get the last number in the string to check + sal_Int32 nNumEnd = nEndPos; + bool bFoundEnd = false; + bool isValidNumber = true; + sal_Int32 i = nEndPos; + while (i > nSttPos) + { + i--; + bool isDigit = rCC.isDigit(rTxt, i); + if (bFoundEnd) + isValidNumber &= (isDigit || !rCC.isLetter(rTxt, i)); + + if (isDigit && !bFoundEnd) + { + bFoundEnd = true; + nNumEnd = i; + } + } + + if (bFoundEnd && isValidNumber) { + sal_Int32 nNum = o3tl::toInt32(rTxt.subView(nSttPos, nNumEnd - nSttPos + 1)); + + // Check if the characters after that number correspond to the ordinal suffix + uno::Reference< i18n::XOrdinalSuffix > xOrdSuffix + = i18n::OrdinalSuffix::create(comphelper::getProcessComponentContext()); + + const uno::Sequence< OUString > aSuffixes = xOrdSuffix->getOrdinalSuffix(nNum, rCC.getLanguageTag().getLocale()); + for (OUString const & sSuffix : aSuffixes) + { + std::u16string_view sEnd = rTxt.subView(nNumEnd + 1, nEndPos - nNumEnd - 1); + + if (sSuffix == sEnd) + { + // Check if the ordinal suffix has to be set as super script + if (rCC.isLetter(sSuffix)) + { + // Do the change + SvxEscapementItem aSvxEscapementItem(DFLT_ESC_AUTO_SUPER, + DFLT_ESC_PROP, SID_ATTR_CHAR_ESCAPEMENT); + rDoc.SetAttr(nNumEnd + 1, nEndPos, + SID_ATTR_CHAR_ESCAPEMENT, + aSvxEscapementItem); + bChg = true; + } + } + } + } + } + return bChg; +} + +// Replace dashes +bool SvxAutoCorrect::FnChgToEnEmDash( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + bool bRet = false; + CharClass& rCC = GetCharClass( eLang ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); + bool bAlwaysUseEmDash = (eLang == LANGUAGE_RUSSIAN || eLang == LANGUAGE_UKRAINIAN); + + // rTxt may refer to the frame text that will change in the calls to rDoc.Delete / rDoc.Insert; + // keep a local copy for later use + OUString aOrigTxt = rTxt; + sal_Int32 nFirstReplacementTextLengthChange = 0; + + // replace " - " or " --" with "enDash" + if( 1 < nSttPos && 1 <= nEndPos - nSttPos ) + { + sal_Unicode cCh = rTxt[ nSttPos ]; + if( '-' == cCh ) + { + if( 1 < nEndPos - nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos+1 ]) + { + sal_Int32 n; + for( n = nSttPos+2; n < nEndPos && lcl_IsInArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + for( n = nSttPos-1; n && lcl_IsInArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + + // found: "[A-z0-9][<AnyEndChars>] --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nSttPos, nSttPos + 2 ); + rDoc.Insert( nSttPos, bAlwaysUseEmDash ? sEmDash : sEnDash ); + nFirstReplacementTextLengthChange = -1; // 2 ch -> 1 ch + bRet = true; + } + } + } + } + else if( 3 < nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos-2 ]) + { + sal_Int32 n, nLen = 1, nTmpPos = nSttPos - 2; + if( '-' == ( cCh = rTxt[ nTmpPos-1 ]) ) + { + --nTmpPos; + ++nLen; + cCh = rTxt[ nTmpPos-1 ]; + } + if( ' ' == cCh ) + { + for( n = nSttPos; n < nEndPos && lcl_IsInArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + cCh = ' '; + for( n = nTmpPos-1; n && lcl_IsInArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + // found: "[A-z0-9][<AnyEndChars>] - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nTmpPos, nTmpPos + nLen ); + rDoc.Insert( nTmpPos, bAlwaysUseEmDash ? sEmDash : sEnDash ); + nFirstReplacementTextLengthChange = 1 - nLen; // nLen ch -> 1 ch + bRet = true; + } + } + } + } + } + + // Replace [A-z0-9]--[A-z0-9] double dash with "emDash" or "enDash" + // [0-9]--[0-9] double dash always replaced with "enDash" + // Finnish and Hungarian use enDash instead of emDash. + bool bEnDash = (eLang == LANGUAGE_HUNGARIAN || eLang == LANGUAGE_FINNISH); + if( 4 <= nEndPos - nSttPos ) + { + std::u16string_view sTmpView( aOrigTxt.subView( nSttPos, nEndPos - nSttPos ) ); + size_t nFndPos = sTmpView.find(u"--"); + if (nFndPos > 0 && nFndPos < sTmpView.size() - 2) + { + // Use proper codepoints. Currently, CharClass::isLetterNumeric is broken, it + // uses the index *both* as code unit index (when checking it as ASCII), *and* + // as code point index (when passes to css::i18n::XCharacterClassification). + // Oh well... Anyway, single-codepoint strings will workaround it. + sal_Int32 nStart = nSttPos + nFndPos; + sal_uInt32 chStart = aOrigTxt.iterateCodePoints(&nStart, -1); + OUString sStart(&chStart, 1); + // No idea why sImplEndSkipChars is checked at start + if (rCC.isLetterNumeric(sStart, 0) || lcl_IsInArr(sImplEndSkipChars, chStart)) + { + sal_Int32 nEnd = nSttPos + nFndPos + 2; + sal_uInt32 chEnd = aOrigTxt.iterateCodePoints(&nEnd, 1); + OUString sEnd(&chEnd, 1); + // No idea why sImplSttSkipChars is checked at end + if (rCC.isLetterNumeric(sEnd, 0) || lcl_IsInArr(sImplSttSkipChars, chEnd)) + { + nSttPos = nSttPos + nFndPos + nFirstReplacementTextLengthChange; + rDoc.Delete(nSttPos, nSttPos + 2); + rDoc.Insert(nSttPos, + (bEnDash || (rCC.isDigit(sStart, 0) && rCC.isDigit(sEnd, 0)) + ? sEnDash + : sEmDash)); + bRet = true; + } + } + } + } + return bRet; +} + +// Add non-breaking space before specific punctuation marks in French text +sal_Int32 SvxAutoCorrect::FnAddNonBrkSpace( + SvxAutoCorrDoc& rDoc, std::u16string_view rTxt, + sal_Int32 nEndPos, + LanguageType eLang, bool& io_bNbspRunNext ) +{ + sal_Int32 nRet = -1; + + CharClass& rCC = GetCharClass( eLang ); + + if ( rCC.getLanguageTag().getLanguage() == "fr" ) + { + bool bFrCA = (rCC.getLanguageTag().getCountry() == "CA"); + OUString allChars = ":;?!%"; + OUString chars( allChars ); + if ( bFrCA ) + chars = ":"; + + sal_Unicode cChar = rTxt[ nEndPos ]; + bool bHasSpace = chars.indexOf( cChar ) != -1; + bool bIsSpecial = allChars.indexOf( cChar ) != -1; + if ( bIsSpecial ) + { + // Get the last word delimiter position + sal_Int32 nSttWdPos = nEndPos; + bool bWasWordDelim = false; + while( nSttWdPos ) + { + bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]); + if (bWasWordDelim) + break; + } + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + size_t nIndex = nSttWdPos + (bWasWordDelim ? 1 : 0); + size_t nProtocolLen = nEndPos - nSttWdPos + 1; + if (nIndex + nProtocolLen <= rTxt.size()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.substr(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return -1; + } + + // Check the presence of "://" in the word + size_t nStrPos = rTxt.find( u"://", nSttWdPos + 1 ); + if ( nStrPos == std::u16string_view::npos && nEndPos > 0 ) + { + // Check the previous char + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + if ( ( chars.indexOf( cPrevChar ) == -1 ) && cPrevChar != '\t' ) + { + // Remove any previous normal space + sal_Int32 nPos = nEndPos - 1; + while ( cPrevChar == ' ' || cPrevChar == cNonBreakingSpace ) + { + if ( nPos == 0 ) break; + nPos--; + cPrevChar = rTxt[ nPos ]; + } + + nPos++; + if ( nEndPos - nPos > 0 ) + rDoc.Delete( nPos, nEndPos ); + + // Add the non-breaking space at the end pos + if ( bHasSpace ) + rDoc.Insert( nPos, OUString(cNonBreakingSpace) ); + io_bNbspRunNext = true; + nRet = nPos; + } + else if ( chars.indexOf( cPrevChar ) != -1 ) + io_bNbspRunNext = true; + } + } + else if ( cChar == '/' && nEndPos > 1 && static_cast<sal_Int32>(rTxt.size()) > (nEndPos - 1) ) + { + // Remove the hardspace right before to avoid formatting URLs + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + sal_Unicode cMaybeSpaceChar = rTxt[ nEndPos - 2 ]; + if ( cPrevChar == ':' && cMaybeSpaceChar == cNonBreakingSpace ) + { + rDoc.Delete( nEndPos - 2, nEndPos - 1 ); + nRet = nEndPos - 1; + } + } + } + + return nRet; +} + +// URL recognition +bool SvxAutoCorrect::FnSetINetAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + OUString sURL( URIHelper::FindFirstURLInText( rTxt, nSttPos, nEndPos, + GetCharClass( eLang ) )); + bool bRet = !sURL.isEmpty(); + if( bRet ) // so, set attribute: + rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); + return bRet; +} + +// DOI citation recognition +bool SvxAutoCorrect::FnSetDOIAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + OUString sURL( URIHelper::FindFirstDOIInText( rTxt, nSttPos, nEndPos, GetCharClass( eLang ) )); + bool bRet = !sURL.isEmpty(); + if( bRet ) // so, set attribute: + rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); + return bRet; +} + +// Automatic *bold*, /italic/, -strikeout- and _underline_ +bool SvxAutoCorrect::FnChgWeightUnderl( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nEndPos ) +{ + // Condition: + // at the beginning: _, *, / or ~ after Space with the following !Space + // at the end: _, *, / or ~ before Space (word delimiter?) + + sal_Unicode cInsChar = rTxt[ nEndPos ]; // underline, bold, italic or strikeout + if( ++nEndPos != rTxt.getLength() && + !IsWordDelim( rTxt[ nEndPos ] ) ) + return false; + + --nEndPos; + + bool bAlphaNum = false; + sal_Int32 nPos = nEndPos; + sal_Int32 nFndPos = -1; + CharClass& rCC = GetCharClass( LANGUAGE_SYSTEM ); + + while( nPos ) + { + switch( sal_Unicode c = rTxt[ --nPos ] ) + { + case '_': + case '-': + case '/': + case '*': + if( c == cInsChar ) + { + if( bAlphaNum && nPos+1 < nEndPos && ( !nPos || + IsWordDelim( rTxt[ nPos-1 ])) && + !IsWordDelim( rTxt[ nPos+1 ])) + nFndPos = nPos; + else + // Condition is not satisfied, so cancel + nFndPos = -1; + nPos = 0; + } + break; + default: + if( !bAlphaNum ) + bAlphaNum = rCC.isLetterNumeric( rTxt, nPos ); + } + } + + if( -1 != nFndPos ) + { + // first delete the Character at the end - this allows insertion + // of an empty hint in SetAttr which would be removed by Delete + // (fdo#62536, AUTOFMT in Writer) + rDoc.Delete( nEndPos, nEndPos + 1 ); + + // Span the Attribute over the area + // the end. + if( '*' == cInsChar ) // Bold + { + SvxWeightItem aSvxWeightItem( WEIGHT_BOLD, SID_ATTR_CHAR_WEIGHT ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_WEIGHT, + aSvxWeightItem); + } + else if( '/' == cInsChar ) // Italic + { + SvxPostureItem aSvxPostureItem( ITALIC_NORMAL, SID_ATTR_CHAR_POSTURE ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_POSTURE, + aSvxPostureItem); + } + else if( '-' == cInsChar ) // Strikeout + { + SvxCrossedOutItem aSvxCrossedOutItem( STRIKEOUT_SINGLE, SID_ATTR_CHAR_STRIKEOUT ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_STRIKEOUT, + aSvxCrossedOutItem); + } + else // Underline + { + SvxUnderlineItem aSvxUnderlineItem( LINESTYLE_SINGLE, SID_ATTR_CHAR_UNDERLINE ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_UNDERLINE, + aSvxUnderlineItem); + } + rDoc.Delete( nFndPos, nFndPos + 1 ); + } + + return -1 != nFndPos; +} + +// Capitalize first letter of every sentence +void SvxAutoCorrect::FnCapitalStartSentence( SvxAutoCorrDoc& rDoc, + const OUString& rTxt, bool bNormalPos, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + + if( rTxt.isEmpty() || nEndPos <= nSttPos ) + return; + + CharClass& rCC = GetCharClass( eLang ); + OUString aText( rTxt ); + const sal_Unicode *pStart = aText.getStr(), + *pStr = pStart + nEndPos, + *pWordStt = nullptr, + *pDelim = nullptr; + + bool bAtStart = false; + do { + --pStr; + if (rCC.isLetter(aText, pStr - pStart)) + { + if( !pWordStt ) + pDelim = pStr+1; + pWordStt = pStr; + } + else if (pWordStt && !rCC.isDigit(aText, pStr - pStart)) + { + if( (lcl_IsInArr( u"-'", *pStr ) || *pStr == cApostrophe) && // These characters are allowed in words + pWordStt - 1 == pStr && + // Installation at beginning of paragraph. Replaced < by <= (#i38971#) + (pStart + 1) <= pStr && + rCC.isLetter(aText, pStr-1 - pStart)) + pWordStt = --pStr; + else + break; + } + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if (!pWordStt) + return; // no character to be replaced + + + if (rCC.isDigit(aText, pStr - pStart)) + return; // already ok + + if (IsUpperLetter(rCC.getCharacterType(aText, pWordStt - pStart))) + return; // already ok + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + sal_Int32 nIndex = pWordStt - pStart; + sal_Int32 nProtocolLen = pDelim - pWordStt + 1; + if (nIndex + nProtocolLen <= rTxt.getLength()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.subView(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return; // already ok + } + + if (0x1 == *pWordStt || 0x2 == *pWordStt) + return; // already ok + + // Only capitalize, if string before specified characters is long enough + if( *pDelim && 2 >= pDelim - pWordStt && + lcl_IsInArr( u".-)>", *pDelim ) ) + return; + + // tdf#59666 don't capitalize single Greek letters (except in Greek texts) + if ( 1 == pDelim - pWordStt && 0x03B1 <= *pWordStt && *pWordStt <= 0x03C9 && eLang != LANGUAGE_GREEK ) + return; + + if( !bAtStart ) // Still no beginning of a paragraph? + { + if (NonFieldWordDelim(*pStr)) + { + for (;;) + { + bAtStart = (pStart == pStr--); + if (bAtStart || !NonFieldWordDelim(*pStr)) + break; + } + } + // Asian full stop, full width full stop, full width exclamation mark + // and full width question marks are treated as word delimiters + else if ( 0x3002 != *pStr && 0xFF0E != *pStr && 0xFF01 != *pStr && + 0xFF1F != *pStr ) + return; // no valid separator -> no replacement + } + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, OUString(pWordStt, pDelim - pWordStt))) + return; + + if( bAtStart ) // at the beginning of a paragraph? + { + // Check out the previous paragraph, if it exists. + // If so, then check to paragraph separator at the end. + OUString const*const pPrevPara = rDoc.GetPrevPara(bNormalPos); + if (!pPrevPara) + { + // valid separator -> replace + OUString sChar( *pWordStt ); + sChar = rCC.titlecase(sChar); //see fdo#56740 + if (sChar != OUStringChar(*pWordStt)) + rDoc.ReplaceRange( pWordStt - pStart, 1, sChar ); + return; + } + + aText = *pPrevPara; + bAtStart = false; + pStart = aText.getStr(); + pStr = pStart + aText.getLength(); + + do { // overwrite all blanks + --pStr; + if (!NonFieldWordDelim(*pStr)) + break; + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if( bAtStart ) + return; // no valid separator -> no replacement + } + + // Found [ \t]+[A-Z0-9]+ until here. Test now on the paragraph separator. + // all three can happen, but not more than once! + const sal_Unicode* pExceptStt = nullptr; + bool bContinue = true; + Flags nFlag = Flags::NONE; + do + { + switch (*pStr) + { + // Western and Asian full stop + case '.': + case 0x3002: + case 0xFF0E: + { + if (pStr >= pStart + 2 && *(pStr - 2) == '.') + { + //e.g. text "f.o.o. word": Now currently considering + //capitalizing word but second last character of + //previous word is a . So probably last word is an + //anagram that ends in . and not truly the end of a + //previous sentence, so don't autocapitalize this word + return; + } + if (nFlag & Flags::FullStop) + return; // no valid separator -> no replacement + nFlag |= Flags::FullStop; + pExceptStt = pStr; + } + break; + case '!': + case 0xFF01: + { + if (nFlag & Flags::ExclamationMark) + return; // no valid separator -> no replacement + nFlag |= Flags::ExclamationMark; + } + break; + case '?': + case 0xFF1F: + { + if (nFlag & Flags::QuestionMark) + return; // no valid separator -> no replacement + nFlag |= Flags::QuestionMark; + } + break; + default: + if (nFlag == Flags::NONE) + return; // no valid separator -> no replacement + else + bContinue = false; + break; + } + + if (bContinue && pStr-- == pStart) + { + return; // no valid separator -> no replacement + } + } while (bContinue); + if (Flags::FullStop != nFlag) + pExceptStt = nullptr; + + // Only capitalize, if string is long enough + if( 2 > ( pStr - pStart ) ) + return; + + if (!rCC.isLetterNumeric(aText, pStr-- - pStart)) + { + bool bValid = false, bAlphaFnd = false; + const sal_Unicode* pTmpStr = pStr; + while( !bValid ) + { + if( rCC.isDigit( aText, pTmpStr - pStart ) ) + { + bValid = true; + pStr = pTmpStr - 1; + } + else if( rCC.isLetter( aText, pTmpStr - pStart ) ) + { + if( bAlphaFnd ) + { + bValid = true; + pStr = pTmpStr; + } + else + bAlphaFnd = true; + } + else if (bAlphaFnd || NonFieldWordDelim(*pTmpStr)) + break; + + if( pTmpStr == pStart ) + break; + + --pTmpStr; + } + + if( !bValid ) + return; // no valid separator -> no replacement + } + + bool bNumericOnly = '0' <= *(pStr+1) && *(pStr+1) <= '9'; + + // Search for the beginning of the word + while (!NonFieldWordDelim(*pStr)) + { + if( bNumericOnly && rCC.isLetter( aText, pStr - pStart ) ) + bNumericOnly = false; + + if( pStart == pStr ) + break; + + --pStr; + } + + if( bNumericOnly ) // consists of only numbers, then not + return; + + if (NonFieldWordDelim(*pStr)) + ++pStr; + + OUString sWord; + + // check on the basis of the exception list + if( pExceptStt ) + { + sWord = OUString(pStr, pExceptStt - pStr + 1); + if( FindInCplSttExceptList(eLang, sWord) ) + return; + + // Delete all non alphanumeric. Test the characters at the + // beginning/end of the word ( recognizes: "(min.", "/min.", and so on.) + OUString sTmp( sWord ); + while( !sTmp.isEmpty() && + !rCC.isLetterNumeric( sTmp, 0 ) ) + sTmp = sTmp.copy(1); + + // Remove all non alphanumeric characters towards the end up until + // the last one. + sal_Int32 nLen = sTmp.getLength(); + while( nLen && !rCC.isLetterNumeric( sTmp, nLen-1 ) ) + --nLen; + if( nLen + 1 < sTmp.getLength() ) + sTmp = sTmp.copy( 0, nLen + 1 ); + + if( !sTmp.isEmpty() && sTmp.getLength() != sWord.getLength() && + FindInCplSttExceptList(eLang, sTmp)) + return; + + if(FindInCplSttExceptList(eLang, sWord, true)) + return; + } + + // Ok, then replace + sal_Unicode cSave = *pWordStt; + nSttPos = pWordStt - rTxt.getStr(); + OUString sChar = rCC.titlecase(OUString(cSave)); //see fdo#56740 + bool bRet = sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar ); + + // Perhaps someone wants to have the word + if( bRet && ACFlags::SaveWordCplSttLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartSentence, nSttPos, sWord, cSave ); +} + +// Correct accidental use of cAPS LOCK key +bool SvxAutoCorrect::FnCorrectCapsLock( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + if (nEndPos - nSttPos < 2) + // string must be at least 2-character long. + return false; + + CharClass& rCC = GetCharClass( eLang ); + + // Check the first 2 letters. + if ( !IsLowerLetter(rCC.getCharacterType(rTxt, nSttPos)) ) + return false; + + if ( !IsUpperLetter(rCC.getCharacterType(rTxt, nSttPos+1)) ) + return false; + + OUStringBuffer aConverted; + aConverted.append( rCC.uppercase(OUString(rTxt[nSttPos])) ); + aConverted.append( rCC.lowercase(OUString(rTxt[nSttPos+1])) ); + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, rTxt.copy(nSttPos, nEndPos - nSttPos))) + return false; + + for( sal_Int32 i = nSttPos+2; i < nEndPos; ++i ) + { + if ( IsLowerLetter(rCC.getCharacterType(rTxt, i)) ) + // A lowercase letter disqualifies the whole text. + return false; + + if ( IsUpperLetter(rCC.getCharacterType(rTxt, i)) ) + // Another uppercase letter. Convert it. + aConverted.append( rCC.lowercase(OUString(rTxt[i])) ); + else + // This is not an alphabetic letter. Leave it as-is. + aConverted.append( rTxt[i] ); + } + + // Replace the word. + rDoc.Delete(nSttPos, nEndPos); + rDoc.Insert(nSttPos, aConverted.makeStringAndClear()); + + return true; +} + + +sal_Unicode SvxAutoCorrect::GetQuote( sal_Unicode cInsChar, bool bSttQuote, + LanguageType eLang ) const +{ + sal_Unicode cRet = bSttQuote ? ( '\"' == cInsChar + ? GetStartDoubleQuote() + : GetStartSingleQuote() ) + : ( '\"' == cInsChar + ? GetEndDoubleQuote() + : GetEndSingleQuote() ); + if( !cRet ) + { + // then through the Language find the right character + if( LANGUAGE_NONE == eLang ) + cRet = cInsChar; + else + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + OUString sRet( bSttQuote + ? ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkStart() + : rLcl.getQuotationMarkStart() ) + : ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkEnd() + : rLcl.getQuotationMarkEnd() )); + cRet = !sRet.isEmpty() ? sRet[0] : cInsChar; + } + } + return cRet; +} + +void SvxAutoCorrect::InsertQuote( SvxAutoCorrDoc& rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote, + bool bIns, LanguageType eLang, ACQuotes eType ) const +{ + sal_Unicode cRet; + + if ( eType == ACQuotes::DoubleAngleQuote ) + { + bool bSwiss = eLang == LANGUAGE_FRENCH_SWISS; + // pressing " inside a quotation -> use second level angle quotes + bool bLeftQuote = '\"' == cInsChar && + // start position and Romanian OR + // not start position and Hungarian + bSttQuote == (eLang != LANGUAGE_HUNGARIAN); + cRet = ( '<' == cInsChar || bLeftQuote ) + ? ( bSwiss ? cLeftSingleAngleQuote : cLeftDoubleAngleQuote ) + : ( bSwiss ? cRightSingleAngleQuote : cRightDoubleAngleQuote ); + } + else if ( eType == ACQuotes::UseApostrophe ) + cRet = cApostrophe; + else + cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sChg( cInsChar ); + if( bIns ) + rDoc.Insert( nInsPos, sChg ); + else + rDoc.Replace( nInsPos, sChg ); + + sChg = OUString(cRet); + + if( eType == ACQuotes::NonBreakingSpace ) + { + if( rDoc.Insert( bSttQuote ? nInsPos+1 : nInsPos, OUStringChar(cNonBreakingSpace) )) + { + if( !bSttQuote ) + ++nInsPos; + } + } + else if( eType == ACQuotes::DoubleAngleQuote && cInsChar != '\"' ) + { + rDoc.Delete( nInsPos-1, nInsPos); + --nInsPos; + } + + rDoc.Replace( nInsPos, sChg ); + + // i' -> I' in English (last step for the Undo) + if( eType == ACQuotes::CapitalizeIAm ) + rDoc.Replace( nInsPos-1, "I" ); +} + +OUString SvxAutoCorrect::GetQuote( SvxAutoCorrDoc const & rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote ) +{ + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sRet(cRet); + + if( '\"' == cInsChar ) + { + if (primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS) + { + if( bSttQuote ) + sRet += " "; + else + sRet = " " + sRet; + } + } + return sRet; +} + +// search preceding opening quote in the paragraph before the insert position +static bool lcl_HasPrecedingChar( std::u16string_view rTxt, sal_Int32 nPos, + const sal_Unicode sPrecedingChar, const sal_Unicode sStopChar, const sal_Unicode* aStopChars ) +{ + sal_Unicode cTmpChar; + + do { + cTmpChar = rTxt[ --nPos ]; + if ( cTmpChar == sPrecedingChar ) + return true; + + if ( cTmpChar == sStopChar ) + return false; + + for ( const sal_Unicode* pCh = aStopChars; *pCh; ++pCh ) + if ( cTmpChar == *pCh ) + return false; + + } while ( nPos > 0 ); + + return false; +} + +// WARNING: rText may become invalid, see comment below +void SvxAutoCorrect::DoAutoCorrect( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nInsPos, sal_Unicode cChar, + bool bInsert, bool& io_bNbspRunNext, vcl::Window const * pFrameWin ) +{ + bool bIsNextRun = io_bNbspRunNext; + io_bNbspRunNext = false; // if it was set, then it has to be turned off + + do{ // only for middle check loop !! + if( cChar ) + { + // Prevent double space + if( nInsPos && ' ' == cChar && + IsAutoCorrFlag( ACFlags::IgnoreDoubleSpace ) && + ' ' == rTxt[ nInsPos - 1 ]) + { + break; + } + + bool bSingle = '\'' == cChar; + bool bIsReplaceQuote = + (IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == cChar )) || + (IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && bSingle ); + if( bIsReplaceQuote ) + { + bool bSttQuote = !nInsPos; + ACQuotes eType = ACQuotes::NONE; + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if (!bSttQuote) + { + sal_Unicode cPrev = rTxt[ nInsPos-1 ]; + bSttQuote = NonFieldWordDelim(cPrev) || + lcl_IsInArr( u"([{", cPrev ) || + ( cEmDash == cPrev ) || + ( cEnDash == cPrev ); + // tdf#38394 use opening quotation mark << in French l'<<word>> + if ( !bSingle && !bSttQuote && cPrev == cApostrophe && + primary(eLang) == primary(LANGUAGE_FRENCH) && + ( ( ( nInsPos == 2 || ( nInsPos > 2 && IsWordDelim( rTxt[ nInsPos-3 ] ) ) ) && + // abbreviated form of ce, de, je, la, le, ne, me, te, se or si + OUString("cdjlnmtsCDJLNMTS").indexOf( rTxt[ nInsPos-2 ] ) > -1 ) || + ( ( nInsPos == 3 || (nInsPos > 3 && IsWordDelim( rTxt[ nInsPos-4 ] ) ) ) && + // abbreviated form of que + ( rTxt[ nInsPos-2 ] == 'u' || rTxt[ nInsPos-2 ] == 'U' ) && + ( rTxt[ nInsPos-3 ] == 'q' || rTxt[ nInsPos-3 ] == 'Q' ) ) ) ) + { + bSttQuote = true; + } + // tdf#108423 for capitalization of English i'm + else if ( bSingle && ( cPrev == 'i' ) && + primary(eLang) == primary(LANGUAGE_ENGLISH) && + ( nInsPos == 1 || IsWordDelim( rTxt[ nInsPos-2 ] ) ) ) + { + eType = ACQuotes::CapitalizeIAm; + } + // tdf#133524 support >>Hungarian<< and <<Romanian>> secondary level quotations + else if ( !bSingle && nInsPos && + ( ( eLang == LANGUAGE_HUNGARIAN && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEnd[0], + bSttQuote ? aStopDoubleAngleQuoteStart[1] : aStopDoubleAngleQuoteEnd[1], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEnd + 2 ) ) || + ( eLang.anyOf( + LANGUAGE_ROMANIAN, + LANGUAGE_ROMANIAN_MOLDOVA ) && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEndRo[0], + bSttQuote ? aStopDoubleAngleQuoteStart[1] : aStopDoubleAngleQuoteEndRo[1], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEndRo + 2 ) ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + // only if the opening double quotation mark is the default one + if ( rLcl.getDoubleQuotationMarkStart() == OUStringChar(aStopDoubleAngleQuoteStart[0]) ) + eType = ACQuotes::DoubleAngleQuote; + } + else if ( bSingle && nInsPos && !bSttQuote && + // tdf#128860 use apostrophe outside of second level quotation in Czech, German, Icelandic, + // Slovak and Slovenian instead of the – in this case, bad – closing quotation mark U+2018. + // tdf#123786 the same for Russian and Ukrainian + ( eLang.anyOf ( + LANGUAGE_CZECH, + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN, + LANGUAGE_ICELANDIC, + LANGUAGE_SLOVAK, + LANGUAGE_SLOVENIAN ) ) ) + { + sal_Unicode sStartChar = GetStartSingleQuote(); + sal_Unicode sEndChar = GetEndSingleQuote(); + if ( !sStartChar || !sEndChar ) { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + if ( !sStartChar ) sStartChar = rLcl.getQuotationMarkStart()[0]; + if ( !sEndChar ) sEndChar = rLcl.getQuotationMarkStart()[0]; + } + if ( !lcl_HasPrecedingChar( rTxt, nInsPos, sStartChar, sEndChar, aStopSingleQuoteEnd + 1 ) ) + { + CharClass& rCC = GetCharClass( eLang ); + if ( rCC.isLetter(rTxt, nInsPos-1) ) + { + eType = ACQuotes::UseApostrophe; + } + } + } + else if ( bSingle && nInsPos && !bSttQuote && + ( eLang.anyOf ( + LANGUAGE_RUSSIAN, + LANGUAGE_UKRAINIAN ) && + !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEndRuUa[0], aStopSingleQuoteEndRuUa[1], aStopSingleQuoteEndRuUa + 2 ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + CharClass& rCC = GetCharClass( eLang ); + if ( rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEndRuUa[0]) && + // use apostrophe only after letters, not after digits or punctuation + rCC.isLetter(rTxt, nInsPos-1) ) + { + eType = ACQuotes::UseApostrophe; + } + } + } + + if ( eType == ACQuotes::NONE && !bSingle && + ( primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS ) ) + eType = ACQuotes::NonBreakingSpace; + + InsertQuote( rDoc, nInsPos, cChar, bSttQuote, bInsert, eLang, eType ); + break; + } + // tdf#133524 change "<<" and ">>" to double angle quotation marks + else if ( IsAutoCorrFlag( ACFlags::ChgQuotes ) && + IsAutoCorrFlag( ACFlags::ChgAngleQuotes ) && + ('<' == cChar || '>' == cChar) && + nInsPos > 0 && cChar == rTxt[ nInsPos-1 ] ) + { + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if ( eLang.anyOf( + LANGUAGE_CATALAN, // primary level + LANGUAGE_CATALAN_VALENCIAN, // primary level + LANGUAGE_FINNISH, // alternative primary level + LANGUAGE_FRENCH_SWISS, // second level + LANGUAGE_GALICIAN, // primary level + LANGUAGE_HUNGARIAN, // second level + LANGUAGE_POLISH, // second level + LANGUAGE_PORTUGUESE, // primary level + LANGUAGE_PORTUGUESE_BRAZILIAN, // primary level + LANGUAGE_ROMANIAN, // second level + LANGUAGE_ROMANIAN_MOLDOVA, // second level + LANGUAGE_SWEDISH, // alternative primary level + LANGUAGE_SWEDISH_FINLAND, // alternative primary level + LANGUAGE_UKRAINIAN, // primary level + LANGUAGE_USER_ARAGONESE, // primary level + LANGUAGE_USER_ASTURIAN ) || // primary level + primary(eLang) == primary(LANGUAGE_GERMAN) || // alternative primary level + primary(eLang) == primary(LANGUAGE_SPANISH) ) // primary level + { + InsertQuote( rDoc, nInsPos, cChar, false, bInsert, eLang, ACQuotes::DoubleAngleQuote ); + break; + } + } + + if( bInsert ) + rDoc.Insert( nInsPos, OUString(cChar) ); + else + rDoc.Replace( nInsPos, OUString(cChar) ); + + // Hardspaces autocorrection + if ( IsAutoCorrFlag( ACFlags::AddNonBrkSpace ) ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and its length may change (even become shorter) if FnAddNonBrkSpace succeeds! + sal_Int32 nUpdatedPos = -1; + if (NeedsHardspaceAutocorr(cChar)) + nUpdatedPos = FnAddNonBrkSpace( rDoc, rTxt, nInsPos, GetDocLanguage( rDoc, nInsPos ), io_bNbspRunNext ); + if (nUpdatedPos >= 0) + { + nInsPos = nUpdatedPos; + } + else if ( bIsNextRun && !IsAutoCorrectChar( cChar ) ) + { + // Remove the NBSP if it wasn't an autocorrection + if ( nInsPos != 0 && NeedsHardspaceAutocorr( rTxt[ nInsPos - 1 ] ) && + cChar != ' ' && cChar != '\t' && cChar != cNonBreakingSpace ) + { + // Look for the last HARD_SPACE + sal_Int32 nPos = nInsPos - 1; + bool bContinue = true; + while ( bContinue ) + { + const sal_Unicode cTmpChar = rTxt[ nPos ]; + if ( cTmpChar == cNonBreakingSpace ) + { + rDoc.Delete( nPos, nPos + 1 ); + bContinue = false; + } + else if ( !NeedsHardspaceAutocorr( cTmpChar ) || nPos == 0 ) + bContinue = false; + nPos--; + } + } + } + } + } + + if( !nInsPos ) + break; + + sal_Int32 nPos = nInsPos - 1; + + if( IsWordDelim( rTxt[ nPos ])) + break; + + // Set bold or underline automatically? + if (('*' == cChar || '_' == cChar || '/' == cChar || '-' == cChar) && (nPos+1 < rTxt.getLength())) + { + if( IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) ) + { + FnChgWeightUnderl( rDoc, rTxt, nPos+1 ); + } + break; + } + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // begin of paragraph and no blank + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + CharClass& rCC = GetCharClass( eLang ); + + // no symbol characters + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nInsPos )) + break; + + if( IsAutoCorrFlag( ACFlags::Autocorrect ) && + // tdf#134940 fix regression of arrow "-->" resulted by premature + // replacement of "--" since '>' was added to IsAutoCorrectChar() + '>' != cChar ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if ChgAutoCorrWord returns true! + // => use aPara/pPara to create a valid copy of the string! + OUString aPara; + OUString* pPara = IsAutoCorrFlag(ACFlags::CapitalStartSentence) ? &aPara : nullptr; + + bool bChgWord = rDoc.ChgAutoCorrWord( nCapLttrPos, nInsPos, + *this, pPara ); + if( !bChgWord ) + { + sal_Int32 nCapLttrPos1 = nCapLttrPos, nInsPos1 = nInsPos; + while( nCapLttrPos1 < nInsPos && + lcl_IsInArr( sImplSttSkipChars, rTxt[ nCapLttrPos1 ] ) + ) + ++nCapLttrPos1; + while( nCapLttrPos1 < nInsPos1 && nInsPos1 && + lcl_IsInArr( sImplEndSkipChars, rTxt[ nInsPos1-1 ] ) + ) + --nInsPos1; + + if( (nCapLttrPos1 != nCapLttrPos || nInsPos1 != nInsPos ) && + nCapLttrPos1 < nInsPos1 && + rDoc.ChgAutoCorrWord( nCapLttrPos1, nInsPos1, *this, pPara )) + { + bChgWord = true; + nCapLttrPos = nCapLttrPos1; + } + } + + if( bChgWord ) + { + if( !aPara.isEmpty() ) + { + sal_Int32 nEnd = nCapLttrPos; + while( nEnd < aPara.getLength() && + !IsWordDelim( aPara[ nEnd ])) + ++nEnd; + + // Capital letter at beginning of paragraph? + if( IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, aPara, false, + nCapLttrPos, nEnd, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, aPara, nCapLttrPos, nEnd, eLang ); + } + } + break; + } + } + + if( IsAutoCorrFlag( ACFlags::TransliterateRTL ) && GetDocLanguage( rDoc, nInsPos ) == LANGUAGE_HUNGARIAN ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if TransliterateRTLWord returns true! + if ( rDoc.TransliterateRTLWord( nCapLttrPos, nInsPos ) ) + break; + } + + if( ( IsAutoCorrFlag( ACFlags::ChgOrdinalNumber ) && + (nInsPos >= 2 ) && // fdo#69762 avoid autocorrect for 2e-3 + ( '-' != cChar || 'E' != rtl::toAsciiUpperCase(rTxt[nInsPos-1]) || '0' > rTxt[nInsPos-2] || '9' < rTxt[nInsPos-2] ) && + FnChgOrdinalNumber( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || + ( IsAutoCorrFlag( ACFlags::SetINetAttr ) && + ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && + FnSetINetAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || + ( IsAutoCorrFlag( ACFlags::SetDOIAttr ) && + ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && + FnSetDOIAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) ) + ; + else + { + bool bLockKeyOn = pFrameWin && (pFrameWin->GetIndicatorState() & KeyIndicatorState::CAPSLOCK); + bool bUnsupported = lcl_IsUnsupportedUnicodeChar( rCC, rTxt, nCapLttrPos, nInsPos ); + + if ( bLockKeyOn && IsAutoCorrFlag( ACFlags::CorrectCapsLock ) && + FnCorrectCapsLock( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) + { + // Correct accidental use of cAPS LOCK key (do this only when + // the caps or shift lock key is pressed). Turn off the caps + // lock afterwards. + pFrameWin->SimulateKeyPress( KEY_CAPSLOCK ); + } + + // Capital letter at beginning of paragraph ? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, rTxt, true, nCapLttrPos, nInsPos, eLang ); + } + + // Two capital letters at beginning of word ?? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartWord ) ) + { + FnCapitalStartWord( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + } + + } while( false ); +} + +SvxAutoCorrectLanguageLists& SvxAutoCorrect::GetLanguageList_( + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + if (m_aLangTable.find(aLanguageTag) == m_aLangTable.end()) + (void)CreateLanguageFile(aLanguageTag); + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + return iter->second; +} + +void SvxAutoCorrect::SaveCplSttExceptList( LanguageType eLang ) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + iter->second.SaveCplSttExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +void SvxAutoCorrect::SaveWordStartExceptList(LanguageType eLang) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + iter->second.SaveWordStartExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddCplSttException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + // either the right language is present or it will be this in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else if(CreateLanguageFile(aLangTagUndetermined)) + { + iter = m_aLangTable.find(aLangTagUndetermined); + assert(iter != m_aLangTable.end()); + pLists = &iter->second; + } + } + OSL_ENSURE(pLists, "No auto correction data"); + return pLists && pLists->AddToCplSttExceptList(rNew); +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddWordStartException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + //either the right language is present or it is set in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else if(CreateLanguageFile(aLangTagUndetermined)) + { + iter = m_aLangTable.find(aLangTagUndetermined); + assert(iter != m_aLangTable.end()); + pLists = &iter->second; + } + } + OSL_ENSURE(pLists, "No auto correction file!"); + return pLists && pLists->AddToWordStartExceptList(rNew); +} + +OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, + sal_Int32 nPos) +{ + OUString sRet; + if( !nPos ) + return sRet; + + sal_Int32 nEnd = nPos; + + // it must be followed by a blank or tab! + if( ( nPos < rTxt.getLength() && + !IsWordDelim( rTxt[ nPos ])) || + IsWordDelim( rTxt[ --nPos ])) + return sRet; + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // Beginning of paragraph and no Blank! + + while( lcl_IsInArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) ) + if( ++nCapLttrPos >= nEnd ) + return sRet; + + if( 3 > nEnd - nCapLttrPos ) + return sRet; + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + + CharClass& rCC = GetCharClass(eLang); + + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnd )) + return sRet; + + sRet = rTxt.copy( nCapLttrPos, nEnd - nCapLttrPos ); + return sRet; +} + +// static +std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(std::u16string_view rTxt, + const sal_Int32 nPos) +{ + constexpr sal_Int32 nMinLen = 3; + constexpr sal_Int32 nMaxLen = 9; + std::vector<OUString> aRes; + if (nPos >= nMinLen) + { + sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0); + // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation) + if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1])) + { + while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin])) + ++nBegin; + } + if (nBegin + nMinLen <= nPos) + { + OUString sRes( rTxt.substr(nBegin, nPos - nBegin) ); + aRes.push_back(sRes); + bool bLastStartedWithDelim = IsWordDelim(sRes[0]); + for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i) + { + bool bAdd = bLastStartedWithDelim; + bLastStartedWithDelim = IsWordDelim(sRes[i]); + bAdd = bAdd || bLastStartedWithDelim; + if (bAdd) + aRes.push_back(sRes.copy(i)); + } + } + } + return aRes; +} + +bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile ) +{ + OSL_ENSURE(m_aLangTable.find(rLanguageTag) == m_aLangTable.end(), "Language already exists "); + + OUString sUserDirFile( GetAutoCorrFileName( rLanguageTag, true )); + OUString sShareDirFile( sUserDirFile ); + + SvxAutoCorrectLanguageLists* pLists = nullptr; + + tools::Time nMinTime( 0, 2 ), nAktTime( tools::Time::SYSTEM ), nLastCheckTime( tools::Time::EMPTY ); + + auto nFndPos = aLastFileTable.find(rLanguageTag); + if(nFndPos != aLastFileTable.end() && + (nLastCheckTime.SetTime(nFndPos->second), nLastCheckTime < nAktTime) && + nAktTime - nLastCheckTime < nMinTime) + { + // no need to test the file, because the last check is not older then + // 2 minutes. + if( bNewFile ) + { + sShareDirFile = sUserDirFile; + auto itBool = m_aLangTable.emplace(std::piecewise_construct, + std::forward_as_tuple(rLanguageTag), + std::forward_as_tuple(*this, sShareDirFile, sUserDirFile)); + pLists = &itBool.first->second; + aLastFileTable.erase(nFndPos); + } + } + else if( + ( FStatHelper::IsDocument( sUserDirFile ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag ) ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag, false, false, true) ) + ) || + ( sShareDirFile = sUserDirFile, bNewFile ) + ) + { + auto itBool = m_aLangTable.emplace(std::piecewise_construct, + std::forward_as_tuple(rLanguageTag), + std::forward_as_tuple(*this, sShareDirFile, sUserDirFile)); + pLists = &itBool.first->second; + if (nFndPos != aLastFileTable.end()) + aLastFileTable.erase(nFndPos); + } + else if( !bNewFile ) + { + aLastFileTable[rLanguageTag] = nAktTime.GetTime(); + } + return pLists != nullptr; +} + +bool SvxAutoCorrect::PutText( const OUString& rShort, const OUString& rLong, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + if (auto const iter = m_aLangTable.find(aLanguageTag); iter != m_aLangTable.end()) + return iter->second.PutText(rShort, rLong); + if (CreateLanguageFile(aLanguageTag)) + { + auto const iter = m_aLangTable.find(aLanguageTag); + assert (iter != m_aLangTable.end()); + return iter->second.PutText(rShort, rLong); + } + return false; +} + +void SvxAutoCorrect::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, + std::vector<SvxAutocorrWord>& aDeleteEntries, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + auto iter = m_aLangTable.find(aLanguageTag); + if (iter != m_aLangTable.end()) + { + iter->second.MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } + else if(CreateLanguageFile( aLanguageTag )) + { + iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + iter->second.MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } +} + +// - return the replacement text (only for SWG-Format, all other +// can be taken from the word list!) +bool SvxAutoCorrect::GetLongText( const OUString&, OUString& ) +{ + return false; +} + +void SvxAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& ) +{ +} + +// Text with attribution (only the SWG - SWG format!) +bool SvxAutoCorrect::PutText( const css::uno::Reference < css::embed::XStorage >&, + const OUString&, const OUString&, SfxObjectShell&, OUString& ) +{ + return false; +} + +OUString EncryptBlockName_Imp(std::u16string_view rName) +{ + OUStringBuffer aName; + aName.append('#').append(rName); + for (size_t nLen = rName.size(), nPos = 1; nPos < nLen; ++nPos) + { + if (lcl_IsInArr( u"!/:.\\", aName[nPos])) + aName[nPos] &= 0x0f; + } + return aName.makeStringAndClear(); +} + +/* This code is copied from SwXMLTextBlocks::GeneratePackageName */ +static void GeneratePackageName ( std::u16string_view rShort, OUString& rPackageName ) +{ + OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7)); + OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US)); + + for (sal_Int32 nPos = 0; nPos < aBuf.getLength(); ++nPos) + { + switch (aBuf[nPos]) + { + case '!': + case '/': + case ':': + case '.': + case '\\': + aBuf[nPos] = '_'; + break; + default: + break; + } + } + + rPackageName = aBuf.makeStringAndClear(); +} + +static const SvxAutocorrWord* lcl_SearchWordsInList( + SvxAutoCorrectLanguageLists* pList, std::u16string_view rTxt, + sal_Int32& rStt, sal_Int32 nEndPos) +{ + const SvxAutocorrWordList* pAutoCorrWordList = pList->GetAutocorrWordList(); + return pAutoCorrWordList->SearchWordsInList( rTxt, rStt, nEndPos ); +} + +// the search for the words in the substitution table +const SvxAutocorrWord* SvxAutoCorrect::SearchWordsInList( + std::u16string_view rTxt, sal_Int32& rStt, sal_Int32 nEndPos, + SvxAutoCorrDoc&, LanguageTag& rLang ) +{ + const SvxAutocorrWord* pRet = nullptr; + LanguageTag aLanguageTag( rLang); + if( aLanguageTag.isSystemLocale() ) + aLanguageTag.reset( MsLangId::getConfiguredSystemLanguage()); + + /* TODO-BCP47: this is so ugly, should all maybe be a proper fallback + * list instead? */ + + // First search for eLang, then US-English -> English + // and last in LANGUAGE_UNDETERMINED + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + SvxAutoCorrectLanguageLists & rList = iter->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + else + return nullptr; + } + + // If it still could not be found here, then keep on searching + LanguageType eLang = aLanguageTag.getLanguageType(); + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + SvxAutoCorrectLanguageLists& rList = m_aLangTable.find(aLanguageTag)->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + SvxAutoCorrectLanguageLists& rList = iter->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + return nullptr; +} + +bool SvxAutoCorrect::FindInWordStartExceptList( LanguageType eLang, + const OUString& sWord ) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: again horrible ugliness */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + return false; +} + +static bool lcl_FindAbbreviation(const SvStringsISortDtor* pList, const OUString& sWord) +{ + SvStringsISortDtor::const_iterator it = pList->find( "~" ); + SvStringsISortDtor::size_type nPos = it - pList->begin(); + if( nPos < pList->size() ) + { + OUString sLowerWord(sWord.toAsciiLowerCase()); + OUString sAbr; + for( SvStringsISortDtor::size_type n = nPos; n < pList->size(); ++n ) + { + sAbr = (*pList)[ n ]; + if (sAbr[0] != '~') + break; + // ~ and ~. are not allowed! + if( 2 < sAbr.getLength() && sAbr.getLength() - 1 <= sWord.getLength() ) + { + OUString sLowerAbk(sAbr.toAsciiLowerCase()); + for (sal_Int32 i = sLowerAbk.getLength(), ii = sLowerWord.getLength(); i;) + { + if( !--i ) // agrees + return true; + + if( sLowerAbk[i] != sLowerWord[--ii]) + break; + } + } + } + } + OSL_ENSURE( !(nPos && '~' == (*pList)[ --nPos ][ 0 ] ), + "Wrongly sorted exception list?" ); + return false; +} + +bool SvxAutoCorrect::FindInCplSttExceptList(LanguageType eLang, + const OUString& sWord, bool bAbbreviation) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: did I mention terrible horrible ugliness? */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + return false; +} + +OUString SvxAutoCorrect::GetAutoCorrFileName( const LanguageTag& rLanguageTag, + bool bNewFile, bool bTst, bool bUnlocalized ) const +{ + OUString sRet, sExt( rLanguageTag.getBcp47() ); + if (bUnlocalized) + { + // we don't want variant, so we'll take "fr" instead of "fr-CA" for example + std::vector< OUString > vecFallBackStrings = rLanguageTag.getFallbackStrings(false); + if (!vecFallBackStrings.empty()) + sExt = vecFallBackStrings[0]; + } + + sExt = "_" + sExt + ".dat"; + if( bNewFile ) + sRet = sUserAutoCorrFile + sExt; + else if( !bTst ) + sRet = sShareAutoCorrFile + sExt; + else + { + // test first in the user directory - if not exist, then + sRet = sUserAutoCorrFile + sExt; + if( !FStatHelper::IsDocument( sRet )) + sRet = sShareAutoCorrFile + sExt; + } + return sRet; +} + +SvxAutoCorrectLanguageLists::SvxAutoCorrectLanguageLists( + SvxAutoCorrect& rParent, + OUString aShareAutoCorrectFile, + OUString aUserAutoCorrectFile) +: sShareAutoCorrFile(std::move( aShareAutoCorrectFile )), + sUserAutoCorrFile(std::move( aUserAutoCorrectFile )), + aModifiedDate( Date::EMPTY ), + aModifiedTime( tools::Time::EMPTY ), + aLastCheckTime( tools::Time::EMPTY ), + rAutoCorrect(rParent), + nFlags(ACFlags::NONE) +{ +} + +SvxAutoCorrectLanguageLists::~SvxAutoCorrectLanguageLists() +{ +} + +bool SvxAutoCorrectLanguageLists::IsFileChanged_Imp() +{ + // Access the file system only every 2 minutes to check the date stamp + bool bRet = false; + + tools::Time nMinTime( 0, 2 ); + tools::Time nAktTime( tools::Time::SYSTEM ); + if( aLastCheckTime <= nAktTime) // overflow? + return false; + nAktTime -= aLastCheckTime; + if( nAktTime > nMinTime ) // min time past + { + Date aTstDate( Date::EMPTY ); tools::Time aTstTime( tools::Time::EMPTY ); + if( FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aTstDate, &aTstTime ) && + ( aModifiedDate != aTstDate || aModifiedTime != aTstTime )) + { + bRet = true; + // then remove all the lists fast! + if( (ACFlags::CplSttLstLoad & nFlags) && pCplStt_ExcptLst ) + { + pCplStt_ExcptLst.reset(); + } + if( (ACFlags::WordStartLstLoad & nFlags) && pWordStart_ExcptLst ) + { + pWordStart_ExcptLst.reset(); + } + if( (ACFlags::ChgWordLstLoad & nFlags) && pAutocorr_List ) + { + pAutocorr_List.reset(); + } + nFlags &= ~ACFlags(ACFlags::CplSttLstLoad | ACFlags::WordStartLstLoad | ACFlags::ChgWordLstLoad ); + } + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::LoadXMLExceptList_Imp( + std::unique_ptr<SvStringsISortDtor>& rpLst, + const OUString& sStrmName, + tools::SvRef<SotStorage>& rStg) +{ + if( rpLst ) + rpLst->clear(); + else + rpLst.reset( new SvStringsISortDtor ); + + { + if( rStg.is() && rStg->IsStream( sStrmName ) ) + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE ) ); + if( ERRCODE_NONE != xStrm->GetError()) + { + xStrm.clear(); + rStg.clear(); + RemoveStream_Imp( sStrmName ); + } + else + { + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = sStrmName; + + xStrm->Seek( 0 ); + xStrm->SetBufferSize( 8 * 1024 ); + aParserInput.aInputStream = new utl::OInputStreamWrapper( *xStrm ); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLExceptionListImport ( xContext, *rpLst ); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create( xContext ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler( xTokenHandler ); + + // parse + try + { + xParser->parseStream( aParserInput ); + } + catch( const xml::sax::SAXParseException& ) + { + // re throw ? + } + catch( const xml::sax::SAXException& ) + { + // re throw ? + } + catch( const io::IOException& ) + { + // re throw ? + } + } + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + +} + +void SvxAutoCorrectLanguageLists::SaveExceptList_Imp( + const SvStringsISortDtor& rLst, + const OUString& sStrmName, + tools::SvRef<SotStorage> const &rStg, + bool bConvert ) +{ + if( !rStg.is() ) + return; + + if( rLst.empty() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + else + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( xStrm.is() ) + { + xStrm->SetSize( 0 ); + xStrm->SetBufferSize( 8192 ); + xStrm->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *xStrm ); + xWriter->setOutputStream(xOut); + + uno::Reference < xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); + rtl::Reference< SvXMLExceptionListExport > xExp( new SvXMLExceptionListExport( xContext, rLst, sStrmName, xHandler ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + xStrm->Commit(); + if( xStrm->GetError() == ERRCODE_NONE ) + { + xStrm.clear(); + if (!bConvert) + { + rStg->Commit(); + if( ERRCODE_NONE != rStg->GetError() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + } + } + } + } +} + +SvxAutocorrWordList* SvxAutoCorrectLanguageLists::LoadAutocorrWordList() +{ + if( pAutocorr_List ) + pAutocorr_List->DeleteAndDestroyAll(); + else + pAutocorr_List.reset( new SvxAutocorrWordList() ); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sShareAutoCorrFile, embed::ElementModes::READ ); + uno::Reference < io::XStream > xStrm = xStg->openStreamElement( pXMLImplAutocorr_ListStr, embed::ElementModes::READ ); + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = pXMLImplAutocorr_ListStr; + aParserInput.aInputStream = xStrm->getInputStream(); + + // get parser + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + SAL_INFO("editeng", "AutoCorrect Import" ); + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLAutoCorrectImport( xContext, pAutocorr_List.get(), rAutoCorrect, xStg ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + + // connect parser and filter + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler(xTokenHandler); + + // parse + xParser->parseStream( aParserInput ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("editeng", "when loading " << sShareAutoCorrFile); + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + + return pAutocorr_List.get(); +} + +const SvxAutocorrWordList* SvxAutoCorrectLanguageLists::GetAutocorrWordList() +{ + if( !( ACFlags::ChgWordLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadAutocorrWordList(); + if( !pAutocorr_List ) + { + OSL_ENSURE( false, "No valid list" ); + pAutocorr_List.reset( new SvxAutocorrWordList() ); + } + nFlags |= ACFlags::ChgWordLstLoad; + } + return pAutocorr_List.get(); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetCplSttExceptList() +{ + if( !( ACFlags::CplSttLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadCplSttExceptList(); + if( !pCplStt_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pCplStt_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::CplSttLstLoad; + } + return pCplStt_ExcptLst.get(); +} + +bool SvxAutoCorrectLanguageLists::AddToCplSttExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetCplSttExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::AddToWordStartExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetWordStartExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadCplSttExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException&) + { + } + return pCplStt_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveCplSttExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadWordStartExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("editeng", "SvxAutoCorrectLanguageLists::LoadWordStartExceptList"); + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveWordStartExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetWordStartExceptList() +{ + if( !( ACFlags::WordStartLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadWordStartExceptList(); + if( !pWordStart_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pWordStart_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::WordStartLstLoad; + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::RemoveStream_Imp( const OUString& rName ) +{ + if( sShareAutoCorrFile != sUserAutoCorrFile ) + { + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + if( xStg.is() && ERRCODE_NONE == xStg->GetError() && + xStg->IsStream( rName ) ) + { + xStg->Remove( rName ); + xStg->Commit(); + + xStg = nullptr; + } + } +} + +void SvxAutoCorrectLanguageLists::MakeUserStorage_Impl() +{ + // The conversion needs to happen if the file is already in the user + // directory and is in the old format. Additionally it needs to + // happen when the file is being copied from share to user. + + bool bError = false, bConvert = false, bCopy = false; + INetURLObject aDest; + INetURLObject aSource; + + if (sUserAutoCorrFile != sShareAutoCorrFile ) + { + aSource = INetURLObject ( sShareAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + if ( SotStorage::IsOLEStorage ( sShareAutoCorrFile ) ) + { + aDest.SetExtension ( u"bak" ); + bConvert = true; + } + bCopy = true; + } + else if ( SotStorage::IsOLEStorage ( sUserAutoCorrFile ) ) + { + aSource = INetURLObject ( sUserAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + aDest.SetExtension ( u"bak" ); + bCopy = bConvert = true; + } + if (bCopy) + { + try + { + OUString sMain(aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri )); + sal_Int32 nSlashPos = sMain.lastIndexOf('/'); + sMain = sMain.copy(0, nSlashPos); + ::ucbhelper::Content aNewContent( sMain, uno::Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + TransferInfo aInfo; + aInfo.NameClash = NameClash::OVERWRITE; + aInfo.NewTitle = aDest.GetLastName(); + aInfo.SourceURL = aSource.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + aInfo.MoveData = false; + aNewContent.executeCommand( "transfer", Any(aInfo)); + } + catch (...) + { + bError = true; + } + } + if (bConvert && !bError) + { + tools::SvRef<SotStorage> xSrcStg = new SotStorage( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), StreamMode::READ ); + tools::SvRef<SotStorage> xDstStg = new SotStorage( sUserAutoCorrFile, StreamMode::WRITE ); + + if( xSrcStg.is() && xDstStg.is() ) + { + std::unique_ptr<SvStringsISortDtor> pTmpWordList; + + if (xSrcStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplWordStart_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplWordStart_ExcptLstStr, xDstStg, true ); + pTmpWordList.reset(); + } + + + if (xSrcStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplCplStt_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplCplStt_ExcptLstStr, xDstStg, true ); + pTmpWordList->clear(); + } + + GetAutocorrWordList(); + MakeBlocklist_Imp( *xDstStg ); + sShareAutoCorrFile = sUserAutoCorrFile; + xDstStg = nullptr; + try + { + ::ucbhelper::Content aContent ( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), uno::Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + aContent.executeCommand ( "delete", Any ( true ) ); + } + catch (...) + { + } + } + } + else if( bCopy && !bError ) + sShareAutoCorrFile = sUserAutoCorrFile; +} + +bool SvxAutoCorrectLanguageLists::MakeBlocklist_Imp( SotStorage& rStg ) +{ + bool bRet = true, bRemove = !pAutocorr_List || pAutocorr_List->empty(); + if( !bRemove ) + { + tools::SvRef<SotStorageStream> refList = rStg.OpenSotStream( pXMLImplAutocorr_ListStr, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( refList.is() ) + { + refList->SetSize( 0 ); + refList->SetBufferSize( 8192 ); + refList->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *refList ); + xWriter->setOutputStream(xOut); + + rtl::Reference< SvXMLAutoCorrectExport > xExp( new SvXMLAutoCorrectExport( xContext, pAutocorr_List.get(), pXMLImplAutocorr_ListStr, xWriter ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + refList->Commit(); + bRet = ERRCODE_NONE == refList->GetError(); + if( bRet ) + { + refList.clear(); + rStg.Commit(); + if( ERRCODE_NONE != rStg.GetError() ) + { + bRemove = true; + bRet = false; + } + } + } + else + bRet = false; + } + + if( bRemove ) + { + rStg.Remove( pXMLImplAutocorr_ListStr ); + rStg.Commit(); + } + + return bRet; +} + +bool SvxAutoCorrectLanguageLists::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, std::vector<SvxAutocorrWord>& aDeleteEntries ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStorage = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStorage.is() && ERRCODE_NONE == xStorage->GetError(); + + if( bRet ) + { + for (SvxAutocorrWord & aWordToDelete : aDeleteEntries) + { + std::optional<SvxAutocorrWord> xFoundEntry = pAutocorr_List->FindAndRemove( &aWordToDelete ); + if( xFoundEntry ) + { + if( !xFoundEntry->IsTextOnly() ) + { + OUString aName( aWordToDelete.GetShort() ); + if (xStorage->IsOLEStorage()) + aName = EncryptBlockName_Imp(aName); + else + GeneratePackageName ( aWordToDelete.GetShort(), aName ); + + if( xStorage->IsContained( aName ) ) + { + xStorage->Remove( aName ); + bRet = xStorage->Commit(); + } + } + } + } + + for (const SvxAutocorrWord & aNewEntrie : aNewEntries) + { + SvxAutocorrWord aWordToAdd(aNewEntrie.GetShort(), aNewEntrie.GetLong(), true ); + std::optional<SvxAutocorrWord> xRemoved = pAutocorr_List->FindAndRemove( &aWordToAdd ); + if( xRemoved ) + { + if( !xRemoved->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStorageName( aWordToAdd.GetShort() ); + if (xStorage->IsOLEStorage()) + sStorageName = EncryptBlockName_Imp(sStorageName); + else + GeneratePackageName ( aWordToAdd.GetShort(), sStorageName); + + if( xStorage->IsContained( sStorageName ) ) + xStorage->Remove( sStorageName ); + } + } + bRet = pAutocorr_List->Insert( std::move(aWordToAdd) ); + + if ( !bRet ) + { + break; + } + } + + if ( bRet ) + { + bRet = MakeBlocklist_Imp( *xStorage ); + } + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, const OUString& rLong ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStg.is() && ERRCODE_NONE == xStg->GetError(); + + // Update the word list + if( bRet ) + { + SvxAutocorrWord aNew(rShort, rLong, true ); + std::optional<SvxAutocorrWord> xRemove = pAutocorr_List->FindAndRemove( &aNew ); + if( xRemove ) + { + if( !xRemove->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStgNm( rShort ); + if (xStg->IsOLEStorage()) + sStgNm = EncryptBlockName_Imp(sStgNm); + else + GeneratePackageName ( rShort, sStgNm); + + if( xStg->IsContained( sStgNm ) ) + xStg->Remove( sStgNm ); + } + } + + if( pAutocorr_List->Insert( std::move(aNew) ) ) + { + bRet = MakeBlocklist_Imp( *xStg ); + xStg = nullptr; + } + else + { + bRet = false; + } + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, + SfxObjectShell& rShell ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sUserAutoCorrFile, embed::ElementModes::READWRITE ); + OUString sLong; + bool bRet = rAutoCorrect.PutText( xStg, sUserAutoCorrFile, rShort, rShell, sLong ); + xStg = nullptr; + + // Update the word list + if( bRet ) + { + if( pAutocorr_List->Insert( SvxAutocorrWord(rShort, sLong, false) ) ) + { + tools::SvRef<SotStorage> xStor = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + MakeBlocklist_Imp( *xStor ); + } + } + } + catch ( const uno::Exception& ) + { + } +} + +// Keep the list sorted ... +struct SvxAutocorrWordList::CompareSvxAutocorrWordList +{ + bool operator()( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) const + { + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + return rCmp.compareString( lhs.GetShort(), rhs.GetShort() ) < 0; + } +}; + +namespace { + +typedef std::unordered_map<OUString, SvxAutocorrWord> AutocorrWordHashType; + +} + +struct SvxAutocorrWordList::Impl +{ + + // only one of these contains the data + // maSortedVector is manually sorted so we can optimise data movement + mutable AutocorrWordSetType maSortedVector; + mutable AutocorrWordHashType maHash; // key is 'Short' + + void DeleteAndDestroyAll() + { + maHash.clear(); + maSortedVector.clear(); + } +}; + +SvxAutocorrWordList::SvxAutocorrWordList() : mpImpl(new Impl) {} + +SvxAutocorrWordList::~SvxAutocorrWordList() +{ +} + +void SvxAutocorrWordList::DeleteAndDestroyAll() +{ + mpImpl->DeleteAndDestroyAll(); +} + +// returns true if inserted +const SvxAutocorrWord* SvxAutocorrWordList::Insert(SvxAutocorrWord aWord) const +{ + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + OUString aShort = aWord.GetShort(); + auto [it,inserted] = mpImpl->maHash.emplace( std::move(aShort), std::move(aWord) ); + if (inserted) + return &(it->second); + return nullptr; + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), aWord, CompareSvxAutocorrWordList()); + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + if (it == mpImpl->maSortedVector.end() || rCmp.compareString( aWord.GetShort(), it->GetShort() ) != 0) + { + it = mpImpl->maSortedVector.insert(it, std::move(aWord)); + return &*it; + } + return nullptr; + } +} + +void SvxAutocorrWordList::LoadEntry(const OUString& sWrong, const OUString& sRight, bool bOnlyTxt) +{ + (void)Insert(SvxAutocorrWord( sWrong, sRight, bOnlyTxt )); +} + +bool SvxAutocorrWordList::empty() const +{ + return mpImpl->maHash.empty() && mpImpl->maSortedVector.empty(); +} + +std::optional<SvxAutocorrWord> SvxAutocorrWordList::FindAndRemove(const SvxAutocorrWord *pWord) +{ + + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + AutocorrWordHashType::iterator it = mpImpl->maHash.find( pWord->GetShort() ); + if( it != mpImpl->maHash.end() ) + { + SvxAutocorrWord pMatch = std::move(it->second); + mpImpl->maHash.erase (it); + return pMatch; + } + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), *pWord, CompareSvxAutocorrWordList()); + if (it != mpImpl->maSortedVector.end() && !CompareSvxAutocorrWordList()(*pWord, *it)) + { + SvxAutocorrWord pMatch = std::move(*it); + mpImpl->maSortedVector.erase (it); + return pMatch; + } + } + return std::optional<SvxAutocorrWord>(); +} + +// return the sorted contents - defer sorting until we have to. +const SvxAutocorrWordList::AutocorrWordSetType& SvxAutocorrWordList::getSortedContent() const +{ + // convert from hash to set permanently + if ( mpImpl->maSortedVector.empty() ) + { + std::vector<SvxAutocorrWord> tmp; + tmp.reserve(mpImpl->maHash.size()); + for (auto & rPair : mpImpl->maHash) + tmp.emplace_back(std::move(rPair.second)); + mpImpl->maHash.clear(); + // sort twice - this gets the list into mostly-sorted order, which + // reduces the number of times we need to invoke the expensive ICU collate fn. + std::sort(tmp.begin(), tmp.end(), + [] ( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) + { + return lhs.GetShort() < rhs.GetShort(); + }); + // This beast has some O(N log(N)) in a terribly slow ICU collate fn. + // stable_sort is twice as fast as sort in this situation because it does + // fewer comparison operations. + std::stable_sort(tmp.begin(), tmp.end(), CompareSvxAutocorrWordList()); + mpImpl->maSortedVector = std::move(tmp); + } + return mpImpl->maSortedVector; +} + +const SvxAutocorrWord* SvxAutocorrWordList::WordMatches(const SvxAutocorrWord *pFnd, + std::u16string_view rTxt, + sal_Int32 &rStt, + sal_Int32 nEndPos) const +{ + const OUString& rChk = pFnd->GetShort(); + + sal_Int32 left_wildcard = rChk.startsWith( ".*" ) ? 2 : 0; // ".*word" pattern? + sal_Int32 right_wildcard = rChk.endsWith( ".*" ) ? 2 : 0; // "word.*" pattern? + assert(nEndPos >= 0); + size_t nSttWdPos = nEndPos; + + // direct replacement of keywords surrounded by colons (for example, ":name:") + bool bColonNameColon = static_cast<sal_Int32>(rTxt.size()) > nEndPos && + rTxt[nEndPos] == ':' && rChk[0] == ':' && rChk.endsWith(":"); + if ( nEndPos + (bColonNameColon ? 1 : 0) < rChk.getLength() - left_wildcard - right_wildcard ) + return nullptr; + + bool bWasWordDelim = false; + sal_Int32 nCalcStt = nEndPos - rChk.getLength() + left_wildcard; + if (bColonNameColon) + nCalcStt++; + if( !right_wildcard && ( !nCalcStt || nCalcStt == rStt || left_wildcard || bColonNameColon || + ( nCalcStt < rStt && + IsWordDelim( rTxt[ nCalcStt - 1 ] ))) ) + { + TransliterationWrapper& rCmp = GetIgnoreTranslWrapper(); + OUString sWord( rTxt.substr(nCalcStt, rChk.getLength() - left_wildcard) ); + if( (!left_wildcard && rCmp.isEqual( rChk, sWord )) || (left_wildcard && rCmp.isEqual( rChk.copy(left_wildcard), sWord) )) + { + rStt = nCalcStt; + if (!left_wildcard) + { + // fdo#33899 avoid "1/2", "1/3".. to be replaced by fractions in dates, eg. 1/2/14 + if (static_cast<sal_Int32>(rTxt.size()) > nEndPos && rTxt[nEndPos] == '/' && rChk.indexOf('/') != -1) + return nullptr; + return pFnd; + } + // get the first word delimiter position before the matching ".*word" pattern + while( rStt && !(bWasWordDelim = IsWordDelim( rTxt[ --rStt ]))) + ; + if (bWasWordDelim) rStt++; + OUString left_pattern( rTxt.substr(rStt, nEndPos - rStt - rChk.getLength() + left_wildcard) ); + // avoid double spaces before simple "word" replacement + left_pattern += (left_pattern.getLength() == 0 && pFnd->GetLong()[0] == 0x20) ? pFnd->GetLong().subView(1) : pFnd->GetLong(); + if( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(OUString(rTxt.substr(rStt, nEndPos - rStt)), left_pattern) ) ) + return pNew; + } + } else + // match "word.*" or ".*word.*" patterns, eg. "i18n.*", ".*---.*", TODO: add transliteration support + if ( right_wildcard ) + { + + OUString sTmp( rChk.copy( left_wildcard, rChk.getLength() - left_wildcard - right_wildcard ) ); + // Get the last word delimiter position + bool not_suffix; + + while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]))) + ; + // search the first occurrence (with a left word delimitation, if needed) + size_t nFndPos = std::u16string_view::npos; + do { + nFndPos = rTxt.find( sTmp, nFndPos + 1); + if (nFndPos == std::u16string_view::npos) + break; + not_suffix = bWasWordDelim && (nSttWdPos >= (nFndPos + sTmp.getLength())); + } while ( (!left_wildcard && nFndPos && !IsWordDelim( rTxt[ nFndPos - 1 ])) || not_suffix ); + + if ( nFndPos != std::u16string_view::npos ) + { + sal_Int32 extra_repl = static_cast<sal_Int32>(nFndPos) + sTmp.getLength() > nEndPos ? 1: 0; // for patterns with terminating characters, eg. "a:" + + if ( left_wildcard ) + { + // get the first word delimiter position before the matching ".*word.*" pattern + while( nFndPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nFndPos ]))) + ; + if (bWasWordDelim) nFndPos++; + } + if (nEndPos + extra_repl <= static_cast<sal_Int32>(nFndPos)) + { + return nullptr; + } + // store matching pattern and its replacement as a new list item, eg. "i18ns" -> "internationalizations" + OUString aShort( rTxt.substr(nFndPos, nEndPos - nFndPos + extra_repl) ); + + OUString aLong; + rStt = nFndPos; + if ( !left_wildcard ) + { + sal_Int32 siz = nEndPos - nFndPos - sTmp.getLength(); + aLong = pFnd->GetLong() + (siz > 0 ? rTxt.substr(nFndPos + sTmp.getLength(), siz) : u""); + } else { + OUStringBuffer buf; + do { + nSttWdPos = rTxt.find( sTmp, nFndPos); + if (nSttWdPos != std::u16string_view::npos) + { + sal_Int32 nTmp(nFndPos); + while (nTmp < static_cast<sal_Int32>(nSttWdPos) && !IsWordDelim(rTxt[nTmp])) + nTmp++; + if (nTmp < static_cast<sal_Int32>(nSttWdPos)) + break; // word delimiter found + buf.append(rTxt.substr(nFndPos, nSttWdPos - nFndPos)).append(pFnd->GetLong()); + nFndPos = nSttWdPos + sTmp.getLength(); + } + } while (nSttWdPos != std::u16string_view::npos); + if (static_cast<sal_Int32>(nEndPos - nFndPos) > extra_repl) + buf.append(rTxt.substr(nFndPos, nEndPos - nFndPos)); + aLong = buf.makeStringAndClear(); + } + if ( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(aShort, aLong) ) ) + { + if ( (static_cast<sal_Int32>(rTxt.size()) > nEndPos && IsWordDelim(rTxt[nEndPos])) || static_cast<sal_Int32>(rTxt.size()) == nEndPos ) + return pNew; + } + } + } + return nullptr; +} + +const SvxAutocorrWord* SvxAutocorrWordList::SearchWordsInList(std::u16string_view rTxt, sal_Int32& rStt, + sal_Int32 nEndPos) const +{ + for (auto const& elem : mpImpl->maHash) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem.second, rTxt, rStt, nEndPos ) ) + return pTmp; + } + + for (auto const& elem : mpImpl->maSortedVector) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem, rTxt, rStt, nEndPos ) ) + return pTmp; + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/swafopt.cxx b/editeng/source/misc/swafopt.cxx new file mode 100644 index 0000000000..25f3b1b466 --- /dev/null +++ b/editeng/source/misc/swafopt.cxx @@ -0,0 +1,81 @@ +/* -*- 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 <editeng/swafopt.hxx> +#include <tools/gen.hxx> +#include <vcl/keycodes.hxx> + +SvxSwAutoFormatFlags::SvxSwAutoFormatFlags() + : aBulletFont( "OpenSymbol", Size( 0, 14 ) ) +{ + bAutoCorrect = + bCapitalStartSentence = + bCapitalStartWord = + bChgEnumNum = + bAddNonBrkSpace = + bChgOrdinalNumber = + bTransliterateRTL = + bChgAngleQuotes = + bChgToEnEmDash = + bChgWeightUnderl = + bSetINetAttr = + bSetDOIAttr = + bAFormatDelSpacesAtSttEnd = + bAFormatDelSpacesBetweenLines = + bAFormatByInpDelSpacesAtSttEnd = + bAFormatByInpDelSpacesBetweenLines = true; + + bChgUserColl = + bReplaceStyles = + bDelEmptyNode = + bWithRedlining = + bAutoCmpltEndless = + bSetNumRuleAfterSpace = + bAutoCmpltAppendBlank = false; + + bAutoCmpltShowAsTip = + bSetBorder = + bCreateTable = + bSetNumRule = + bAFormatByInput = + bRightMargin = + bAutoCompleteWords = + bAutoCmpltCollectWords = + bAutoCmpltKeepList = true; + + nRightMargin = 50; // default 50% + nAutoCmpltExpandKey = KEY_RETURN; + + aBulletFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + aBulletFont.SetFamily( FAMILY_DONTKNOW ); + aBulletFont.SetPitch( PITCH_DONTKNOW ); + aBulletFont.SetWeight( WEIGHT_DONTKNOW ); + aBulletFont.SetTransparent( true ); + + cBullet = 0x2022; + cByInputBullet = cBullet; + aByInputBulletFont = aBulletFont; + + nAutoCmpltWordLen = 8; + nAutoCmpltListLen = 1000; + m_pAutoCompleteList = nullptr; + pSmartTagMgr = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/tokens.txt b/editeng/source/misc/tokens.txt new file mode 100644 index 0000000000..0b5a64607b --- /dev/null +++ b/editeng/source/misc/tokens.txt @@ -0,0 +1,7 @@ +abbreviated-name +block +block-list +list-name +name +package-name +unformatted-text diff --git a/editeng/source/misc/txtrange.cxx b/editeng/source/misc/txtrange.cxx new file mode 100644 index 0000000000..2f02a1150f --- /dev/null +++ b/editeng/source/misc/txtrange.cxx @@ -0,0 +1,667 @@ +/* -*- 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 <editeng/txtrange.hxx> +#include <math.h> +#include <tools/poly.hxx> +#include <tools/debug.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <vector> + +TextRanger::TextRanger( const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DPolyPolygon* pLinePolyPolygon, + sal_uInt16 nCacheSz, sal_uInt16 nLft, sal_uInt16 nRght, + bool bSimpl, bool bInnr, bool bVert ) : + maPolyPolygon( rPolyPolygon.count() ), + nCacheSize( nCacheSz ), + nRight( nRght ), + nLeft( nLft ), + nUpper( 0 ), + nLower( 0 ), + nPointCount( 0 ), + bSimple( bSimpl ), + bInner( bInnr ), + bVertical( bVert ) +{ + sal_uInt32 nCount(rPolyPolygon.count()); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(rPolyPolygon.getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + maPolyPolygon.Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + + if( pLinePolyPolygon ) + { + nCount = pLinePolyPolygon->count(); + mpLinePolyPolygon = tools::PolyPolygon(nCount); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(pLinePolyPolygon->getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + mpLinePolyPolygon->Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + } + else + mpLinePolyPolygon.reset(); +} + + +TextRanger::~TextRanger() +{ + mRangeCache.clear(); +} + +/* TextRanger::SetVertical(..) + If there's is a change in the writing direction, + the cache has to be cleared. +*/ +void TextRanger::SetVertical( bool bNew ) +{ + if( IsVertical() != bNew ) + { + bVertical = bNew; + mRangeCache.clear(); + } +} + +namespace { + +//! SvxBoundArgs is used to perform temporary calculations on a range array. +//! Temporary instances are created in TextRanger::GetTextRanges() +class SvxBoundArgs +{ + std::vector<bool> aBoolArr; + std::deque<tools::Long>* pLongArr; + TextRanger *pTextRanger; + tools::Long nMin; + tools::Long nMax; + tools::Long nTop; + tools::Long nBottom; + tools::Long nUpDiff; + tools::Long nLowDiff; + tools::Long nUpper; + tools::Long nLower; + tools::Long nStart; + tools::Long nEnd; + sal_uInt16 nCut; + sal_uInt16 nLast; + sal_uInt16 nNext; + sal_uInt8 nAct; + sal_uInt8 nFirst; + bool bClosed : 1; + bool bInner : 1; + bool bMultiple : 1; + bool bConcat : 1; + bool bRotate : 1; + void NoteRange( bool bToggle ); + tools::Long Cut( tools::Long nY, const Point& rPt1, const Point& rPt2 ); + void Add(); + void NoteFarPoint_( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ); + void NoteFarPoint( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ) + { if( nDiff ) NoteFarPoint_( nPx, nPyDiff, nDiff ); } + tools::Long CalcMax( const Point& rPt1, const Point& rPt2, tools::Long nRange, tools::Long nFar ); + void CheckCut( const Point& rLst, const Point& rNxt ); + tools::Long A( const Point& rP ) const { return bRotate ? rP.Y() : rP.X(); } + tools::Long B( const Point& rP ) const { return bRotate ? rP.X() : rP.Y(); } +public: + SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, const Range& rRange ); + void NotePoint( const tools::Long nA ) { NoteMargin( nA - nStart, nA + nEnd ); } + void NoteMargin( const tools::Long nL, const tools::Long nR ) + { if( nMin > nL ) nMin = nL; if( nMax < nR ) nMax = nR; } + sal_uInt16 Area( const Point& rPt ); + void NoteUpLow( tools::Long nA, const sal_uInt8 nArea ); + void Calc( const tools::PolyPolygon& rPoly ); + void Concat( const tools::PolyPolygon* pPoly ); + // inlines + void NoteLast() { if( bMultiple ) NoteRange( nAct == nFirst ); } + void SetConcat( const bool bNew ){ bConcat = bNew; } + bool IsConcat() const { return bConcat; } +}; + +} + +SvxBoundArgs::SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, + const Range& rRange ) + : pLongArr(pLong) + , pTextRanger(pRanger) + , nMin(0) + , nMax(0) + , nTop(rRange.Min()) + , nBottom(rRange.Max()) + , nCut(0) + , nLast(0) + , nNext(0) + , nAct(0) + , nFirst(0) + , bClosed(false) + , bInner(pRanger->IsInner()) + , bMultiple(bInner || !pRanger->IsSimple()) + , bConcat(false) + , bRotate(pRanger->IsVertical()) +{ + if( bRotate ) + { + nStart = pRanger->GetUpper(); + nEnd = pRanger->GetLower(); + nLowDiff = pRanger->GetLeft(); + nUpDiff = pRanger->GetRight(); + } + else + { + nStart = pRanger->GetLeft(); + nEnd = pRanger->GetRight(); + nLowDiff = pRanger->GetUpper(); + nUpDiff = pRanger->GetLower(); + } + nUpper = nTop - nUpDiff; + nLower = nBottom + nLowDiff; + pLongArr->clear(); +} + +tools::Long SvxBoundArgs::CalcMax( const Point& rPt1, const Point& rPt2, + tools::Long nRange, tools::Long nFarRange ) +{ + double nDa = Cut( nRange, rPt1, rPt2 ) - Cut( nFarRange, rPt1, rPt2 ); + double nB; + if( nDa < 0 ) + { + nDa = -nDa; + nB = nEnd; + } + else + nB = nStart; + + nB = std::hypot(nB, nDa); + + if (nB == 0) // avoid div / 0 + return 0; + + nB = nRange + nDa * ( nFarRange - nRange ) / nB; + + bool bNote; + if( nB < B(rPt2) ) + bNote = nB > B(rPt1); + else + bNote = nB < B(rPt1); + if( bNote ) + return( tools::Long( nB ) ); + return 0; +} + +void SvxBoundArgs::CheckCut( const Point& rLst, const Point& rNxt ) +{ + if( nCut & 1 ) + NotePoint( Cut( nBottom, rLst, rNxt ) ); + if( nCut & 2 ) + NotePoint( Cut( nTop, rLst, rNxt ) ); + if( rLst.X() == rNxt.X() || rLst.Y() == rNxt.Y() ) + return; + + tools::Long nYps; + if( nLowDiff && ( ( nCut & 1 ) || nLast == 1 || nNext == 1 ) ) + { + nYps = CalcMax( rLst, rNxt, nBottom, nLower ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nLower-nYps, nLowDiff ); + } + if( nUpDiff && ( ( nCut & 2 ) || nLast == 2 || nNext == 2 ) ) + { + nYps = CalcMax( rLst, rNxt, nTop, nUpper ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nYps-nUpper, nUpDiff ); + } +} + +void SvxBoundArgs::NoteFarPoint_( tools::Long nPa, tools::Long nPbDiff, tools::Long nDiff ) +{ + tools::Long nTmpA; + double nQuot = 2 * nDiff - nPbDiff; + nQuot *= nPbDiff; + nQuot = sqrt( nQuot ); + nQuot /= nDiff; + nTmpA = nPa - tools::Long( nStart * nQuot ); + nPbDiff = nPa + tools::Long( nEnd * nQuot ); + NoteMargin( nTmpA, nPbDiff ); +} + +void SvxBoundArgs::NoteRange( bool bToggle ) +{ + DBG_ASSERT( nMax >= nMin || bInner, "NoteRange: Min > Max?"); + if( nMax < nMin ) + return; + if( !bClosed ) + bToggle = false; + sal_uInt16 nIdx = 0; + sal_uInt16 nCount = pLongArr->size(); + DBG_ASSERT( nCount == 2 * aBoolArr.size(), "NoteRange: Incompatible Sizes" ); + while( nIdx < nCount && (*pLongArr)[ nIdx ] < nMin ) + ++nIdx; + bool bOdd = (nIdx % 2) != 0; + // No overlap with existing intervals? + if( nIdx == nCount || ( !bOdd && nMax < (*pLongArr)[ nIdx ] ) ) + { // Then a new one is inserted ... + pLongArr->insert( pLongArr->begin() + nIdx, nMin ); + pLongArr->insert( pLongArr->begin() + nIdx + 1, nMax ); + aBoolArr.insert( aBoolArr.begin() + (nIdx/2), bToggle ); + } + else + { // expand an existing interval ... + sal_uInt16 nMaxIdx = nIdx; + // If we end up on a left interval boundary, it must be reduced to nMin. + if( bOdd ) + --nIdx; + else + (*pLongArr)[ nIdx ] = nMin; + while( nMaxIdx < nCount && (*pLongArr)[ nMaxIdx ] < nMax ) + ++nMaxIdx; + DBG_ASSERT( nMaxIdx > nIdx || nMin == nMax, "NoteRange: Funny Situation." ); + if( nMaxIdx ) + --nMaxIdx; + if( nMaxIdx < nIdx ) + nMaxIdx = nIdx; + // If we end up on a right interval boundary, it must be raised to nMax. + if( nMaxIdx % 2 ) + (*pLongArr)[ nMaxIdx-- ] = nMax; + // Possible merge of intervals. + sal_uInt16 nDiff = nMaxIdx - nIdx; + nMaxIdx = nIdx / 2; // From here on is nMaxIdx the Index in BoolArray. + if( nDiff ) + { + pLongArr->erase( pLongArr->begin() + nIdx + 1, pLongArr->begin() + nIdx + 1 + nDiff ); + nDiff /= 2; + sal_uInt16 nStop = nMaxIdx + nDiff; + for( sal_uInt16 i = nMaxIdx; i < nStop; ++i ) + bToggle ^= aBoolArr[ i ]; + aBoolArr.erase( aBoolArr.begin() + nMaxIdx, aBoolArr.begin() + (nMaxIdx + nDiff) ); + } + DBG_ASSERT( nMaxIdx < aBoolArr.size(), "NoteRange: Too much deleted" ); + aBoolArr[ nMaxIdx ] = aBoolArr[ nMaxIdx ] != bToggle; + } +} + +void SvxBoundArgs::Calc( const tools::PolyPolygon& rPoly ) +{ + sal_uInt16 nCount; + nAct = 0; + for( sal_uInt16 i = 0; i < rPoly.Count(); ++i ) + { + const tools::Polygon& rPol = rPoly[ i ]; + nCount = rPol.GetSize(); + if( nCount ) + { + const Point& rNull = rPol[ 0 ]; + bClosed = IsConcat() || ( rNull == rPol[ nCount - 1 ] ); + nLast = Area( rNull ); + if( nLast & 12 ) + { + nFirst = 3; + if( bMultiple ) + nAct = 0; + } + else + { + // The first point of the polygon is within the line. + if( nLast ) + { + if( bMultiple || !nAct ) + { + nMin = USHRT_MAX; + nMax = 0; + } + if( nLast & 1 ) + NoteFarPoint( A(rNull), nLower - B(rNull), nLowDiff ); + else + NoteFarPoint( A(rNull), B(rNull) - nUpper, nUpDiff ); + } + else + { + if( bMultiple || !nAct ) + { + nMin = A(rNull); + nMax = nMin + nEnd; + nMin -= nStart; + } + else + NotePoint( A(rNull) ); + } + nFirst = 0; // leaving the line in which direction? + nAct = 3; // we are within the line at the moment. + } + if( nCount > 1 ) + { + sal_uInt16 nIdx = 1; + while( true ) + { + const Point& rLast = rPol[ nIdx - 1 ]; + if( nIdx == nCount ) + nIdx = 0; + const Point& rNext = rPol[ nIdx ]; + nNext = Area( rNext ); + nCut = nNext ^ nLast; + sal_uInt16 nOldAct = nAct; + if( nAct ) + CheckCut( rLast, rNext ); + if( nCut & 4 ) + { + NoteUpLow( Cut( nLower, rLast, rNext ), 2 ); + if( nAct && nAct != nOldAct ) + { + nOldAct = nAct; + CheckCut( rLast, rNext ); + } + } + if( nCut & 8 ) + { + NoteUpLow( Cut( nUpper, rLast, rNext ), 1 ); + if( nAct && nAct != nOldAct ) + CheckCut( rLast, rNext ); + } + if( !nIdx ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + if( !( nNext & 12 ) ) + { + if( !nNext ) + NotePoint( A(rNext) ); + else if( nNext & 1 ) + NoteFarPoint( A(rNext), nLower-B(rNext), nLowDiff ); + else + NoteFarPoint( A(rNext), B(rNext)-nUpper, nUpDiff ); + } + nLast = nNext; + if( ++nIdx == nCount && !bClosed ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + } + } + if( bMultiple && IsConcat() ) + { + Add(); + nAct = 0; + } + } + } + if( !bMultiple ) + { + DBG_ASSERT( pLongArr->empty(), "I said: Simple!" ); + if( nAct ) + { + if( bInner ) + { + tools::Long nTmpMin = nMin + 2 * nStart; + tools::Long nTmpMax = nMax - 2 * nEnd; + if( nTmpMin <= nTmpMax ) + { + pLongArr->push_front(nTmpMax); + pLongArr->push_front(nTmpMin); + } + } + else + { + pLongArr->push_front(nMax); + pLongArr->push_front(nMin); + } + } + } + else if( !IsConcat() ) + Add(); +} + +void SvxBoundArgs::Add() +{ + size_t nCount = aBoolArr.size(); + if( nCount && ( !bInner || !pTextRanger->IsSimple() ) ) + { + bool bDelete = aBoolArr.front(); + if( bInner ) + bDelete = !bDelete; + sal_uInt16 nLongIdx = 1; + for( size_t nBoolIdx = 1; nBoolIdx < nCount; ++nBoolIdx ) + { + if( bDelete ) + { + sal_uInt16 next = 2; + while( nBoolIdx < nCount && !aBoolArr[ nBoolIdx++ ] && + (!bInner || nBoolIdx < nCount ) ) + next += 2; + pLongArr->erase( pLongArr->begin() + nLongIdx, pLongArr->begin() + nLongIdx + next ); + next /= 2; + nBoolIdx = nBoolIdx - next; + nCount = nCount - next; + aBoolArr.erase( aBoolArr.begin() + nBoolIdx, aBoolArr.begin() + (nBoolIdx + next) ); + if( nBoolIdx ) + aBoolArr[ nBoolIdx - 1 ] = false; +#if OSL_DEBUG_LEVEL > 1 + else + ++next; +#endif + } + bDelete = nBoolIdx < nCount && aBoolArr[ nBoolIdx ]; + nLongIdx += 2; + DBG_ASSERT( nLongIdx == 2*nBoolIdx+1, "BoundArgs: Array-Idx Confusion" ); + DBG_ASSERT( aBoolArr.size()*2 == pLongArr->size(), + "BoundArgs: Array-Count: Confusion" ); + } + } + if( pLongArr->empty() ) + return; + + if( !bInner ) + return; + + pLongArr->pop_front(); + pLongArr->pop_back(); + + // Here the line is held inside a large rectangle for "simple" + // contour wrap. Currently (April 1999) the EditEngine evaluates + // only the first rectangle. If it one day is able to output a line + // in several parts, it may be advisable to delete the following lines. + if( pTextRanger->IsSimple() && pLongArr->size() > 2 ) + pLongArr->erase( pLongArr->begin() + 1, pLongArr->end() - 1 ); +} + +void SvxBoundArgs::Concat( const tools::PolyPolygon* pPoly ) +{ + SetConcat( true ); + DBG_ASSERT( pPoly, "Nothing to do?" ); + std::deque<tools::Long>* pOld = pLongArr; + pLongArr = new std::deque<tools::Long>; + aBoolArr.clear(); + bInner = false; + Calc( *pPoly ); // Note that this updates pLongArr, which is why we swapped it out earlier. + std::deque<tools::Long>::size_type nCount = pLongArr->size(); + std::deque<tools::Long>::size_type nIdx = 0; + std::deque<tools::Long>::size_type i = 0; + bool bSubtract = pTextRanger->IsInner(); + while( i < nCount ) + { + std::deque<tools::Long>::size_type nOldCount = pOld->size(); + if( nIdx == nOldCount ) + { // Reached the end of the old Array... + if( !bSubtract ) + pOld->insert( pOld->begin() + nIdx, pLongArr->begin() + i, pLongArr->end() ); + break; + } + tools::Long nLeft = (*pLongArr)[ i++ ]; + tools::Long nRight = (*pLongArr)[ i++ ]; + std::deque<tools::Long>::size_type nLeftPos = nIdx + 1; + while( nLeftPos < nOldCount && nLeft > (*pOld)[ nLeftPos ] ) + nLeftPos += 2; + if( nLeftPos >= nOldCount ) + { // The current interval belongs to the end of the old array ... + if( !bSubtract ) + pOld->insert( pOld->begin() + nOldCount, pLongArr->begin() + i - 2, pLongArr->end() ); + break; + } + std::deque<tools::Long>::size_type nRightPos = nLeftPos - 1; + while( nRightPos < nOldCount && nRight >= (*pOld)[ nRightPos ] ) + nRightPos += 2; + if( nRightPos < nLeftPos ) + { // The current interval belongs between two old intervals + if( !bSubtract ) + pOld->insert( pOld->begin() + nRightPos, pLongArr->begin() + i - 2, pLongArr->begin() + i ); + } + else if( bSubtract ) // Subtract, if necessary separate + { + const tools::Long nOld = (*pOld)[nLeftPos - 1]; + if (nLeft > nOld) + { // Now we split the left part... + if( nLeft - 1 > nOld ) + { + pOld->insert( pOld->begin() + nLeftPos - 1, nOld ); + pOld->insert( pOld->begin() + nLeftPos, nLeft - 1 ); + nLeftPos += 2; + nRightPos += 2; + } + } + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + if (++nRight >= (*pOld)[nLeftPos]) + pOld->erase( pOld->begin() + nLeftPos - 1, pOld->begin() + nLeftPos + 1 ); + else + (*pOld)[ nLeftPos - 1 ] = nRight; + } + else // Merge + { + if( nLeft < (*pOld)[ nLeftPos - 1 ] ) + (*pOld)[ nLeftPos - 1 ] = nLeft; + if( nRight > (*pOld)[ nRightPos - 1 ] ) + (*pOld)[ nRightPos - 1 ] = nRight; + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + + } + nIdx = nLeftPos - 1; + } + delete pLongArr; +} + +/************************************************************************* + * SvxBoundArgs::Area returns the area in which the point is located. + * 0 = within the line + * 1 = below, but within the upper edge + * 2 = above, but within the lower edge + * 5 = below the upper edge + *10 = above the lower edge + *************************************************************************/ + +sal_uInt16 SvxBoundArgs::Area( const Point& rPt ) +{ + tools::Long nB = B( rPt ); + if( nB >= nBottom ) + { + if( nB >= nLower ) + return 5; + return 1; + } + if( nB <= nTop ) + { + if( nB <= nUpper ) + return 10; + return 2; + } + return 0; +} + +/************************************************************************* + * lcl_Cut calculates the X-Coordinate of the distance (Pt1-Pt2) at the + * Y-Coordinate nY. + * It is assumed that the one of the points are located above and the other + * one below the Y-Coordinate. + *************************************************************************/ + +tools::Long SvxBoundArgs::Cut( tools::Long nB, const Point& rPt1, const Point& rPt2 ) +{ + if( pTextRanger->IsVertical() ) + { + double nQuot = nB - rPt1.X(); + nQuot /= ( rPt2.X() - rPt1.X() ); + nQuot *= ( rPt2.Y() - rPt1.Y() ); + return tools::Long( rPt1.Y() + nQuot ); + } + double nQuot = nB - rPt1.Y(); + nQuot /= ( rPt2.Y() - rPt1.Y() ); + nQuot *= ( rPt2.X() - rPt1.X() ); + return tools::Long( rPt1.X() + nQuot ); +} + +void SvxBoundArgs::NoteUpLow( tools::Long nA, const sal_uInt8 nArea ) +{ + if( nAct ) + { + NoteMargin( nA, nA ); + if( bMultiple ) + { + NoteRange( nArea != nAct ); + nAct = 0; + } + if( !nFirst ) + nFirst = nArea; + } + else + { + nAct = nArea; + nMin = nA; + nMax = nA; + } +} + +std::deque<tools::Long>* TextRanger::GetTextRanges( const Range& rRange ) +{ + DBG_ASSERT( rRange.Min() || rRange.Max(), "Zero-Range not allowed, Bye Bye" ); + //Can we find the result we need in the cache? + for (auto & elem : mRangeCache) + { + if (elem.range == rRange) + return &(elem.results); + } + //Calculate a new result + RangeCacheItem rngCache(rRange); + SvxBoundArgs aArg( this, &(rngCache.results), rRange ); + aArg.Calc( maPolyPolygon ); + if( mpLinePolyPolygon ) + aArg.Concat( &*mpLinePolyPolygon ); + //Add new result to the cache + mRangeCache.push_back(std::move(rngCache)); + if (mRangeCache.size() > nCacheSize) + mRangeCache.pop_front(); + return &(mRangeCache.back().results); +} + +const tools::Rectangle& TextRanger::GetBoundRect_() const +{ + DBG_ASSERT( !mxBound, "Don't call twice." ); + mxBound = maPolyPolygon.GetBoundRect(); + return *mxBound; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/unolingu.cxx b/editeng/source/misc/unolingu.cxx new file mode 100644 index 0000000000..1e7b69a25f --- /dev/null +++ b/editeng/source/misc/unolingu.cxx @@ -0,0 +1,754 @@ +/* -*- 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 <memory> +#include <editeng/unolingu.hxx> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <com/sun/star/linguistic2/DictionaryList.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> +#include <com/sun/star/linguistic2/LinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/lingucfg.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <linguistic/misc.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <svtools/strings.hrc> +#include <unotools/resmgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace ::comphelper; +using namespace ::linguistic; +using namespace ::com::sun::star; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::linguistic2; + +static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl() +{ + uno::Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create(xContext); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL +//! when only the XSupportedLocales interface is used. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when "real" work needs to be done only. +class ThesDummy_Impl : + public cppu::WeakImplHelper< XThesaurus > +{ + uno::Reference< XThesaurus > xThes; // the real one... + std::unique_ptr<Sequence< lang::Locale >> pLocaleSeq; + + void GetCfgLocales(); + + void GetThes_Impl(); + +public: + ThesDummy_Impl() {} + + // XSupportedLocales + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XThesaurus + virtual css::uno::Sequence< + css::uno::Reference< css::linguistic2::XMeaning > > SAL_CALL + queryMeanings( const OUString& rTerm, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void ThesDummy_Impl::GetCfgLocales() +{ + if (pLocaleSeq) + return; + + SvtLinguConfig aCfg; + Sequence < OUString > aNodeNames( aCfg.GetNodeNames( "ServiceManager/ThesaurusList" ) ); + const OUString *pNodeNames = aNodeNames.getConstArray(); + sal_Int32 nLen = aNodeNames.getLength(); + pLocaleSeq.reset( new Sequence< lang::Locale >( nLen ) ); + lang::Locale *pLocale = pLocaleSeq->getArray(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + pLocale[i] = LanguageTag::convertToLocaleWithFallback( pNodeNames[i] ); + } +} + + +void ThesDummy_Impl::GetThes_Impl() +{ + if (!xThes.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xThes = xLngSvcMgr->getThesaurus(); + + if (xThes.is()) + { + // no longer needed... + pLocaleSeq.reset(); + } + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + ThesDummy_Impl::getLocales() +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->getLocales(); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + return *pLocaleSeq; +} + + +sal_Bool SAL_CALL + ThesDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->hasLocale( rLocale ); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + bool bFound = false; + sal_Int32 nLen = pLocaleSeq->getLength(); + const lang::Locale *pLocale = pLocaleSeq->getConstArray(); + const lang::Locale *pEnd = pLocale + nLen; + for ( ; pLocale < pEnd && !bFound; ++pLocale) + { + bFound = pLocale->Language == rLocale.Language && + pLocale->Country == rLocale.Country && + pLocale->Variant == rLocale.Variant; + } + return bFound; +} + + +uno::Sequence< uno::Reference< linguistic2::XMeaning > > SAL_CALL + ThesDummy_Impl::queryMeanings( + const OUString& rTerm, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetThes_Impl(); + uno::Sequence< uno::Reference< linguistic2::XMeaning > > aRes; + OSL_ENSURE( xThes.is(), "Thesaurus missing" ); + if (xThes.is()) + aRes = xThes->queryMeanings( rTerm, rLocale, rProperties ); + return aRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class SpellDummy_Impl : + public cppu::WeakImplHelper< XSpellChecker1 > +{ + uno::Reference< XSpellChecker1 > xSpell; // the real one... + + void GetSpell_Impl(); + +public: + + // XSupportedLanguages (for XSpellChecker1) + virtual css::uno::Sequence< sal_Int16 > SAL_CALL + getLanguages() override; + virtual sal_Bool SAL_CALL + hasLanguage( sal_Int16 nLanguage ) override; + + // XSpellChecker1 (same as XSpellChecker but sal_Int16 for language) + virtual sal_Bool SAL_CALL + isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< css::linguistic2::XSpellAlternatives > SAL_CALL + spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void SpellDummy_Impl::GetSpell_Impl() +{ + if (!xSpell.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY ); + } +} + + +uno::Sequence< sal_Int16 > SAL_CALL + SpellDummy_Impl::getLanguages() +{ + GetSpell_Impl(); + if (xSpell.is()) + return xSpell->getLanguages(); + else + return uno::Sequence< sal_Int16 >(); +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::hasLanguage( sal_Int16 nLanguage ) +{ + GetSpell_Impl(); + bool bRes = false; + if (xSpell.is()) + bRes = xSpell->hasLanguage( nLanguage ); + return bRes; +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + bool bRes = true; + if (xSpell.is()) + bRes = xSpell->isValid( rWord, nLanguage, rProperties ); + return bRes; +} + + +uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL + SpellDummy_Impl::spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + uno::Reference< linguistic2::XSpellAlternatives > xRes; + if (xSpell.is()) + xRes = xSpell->spell( rWord, nLanguage, rProperties ); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class HyphDummy_Impl : + public cppu::WeakImplHelper< XHyphenator > +{ + uno::Reference< XHyphenator > xHyph; // the real one... + + void GetHyph_Impl(); + +public: + + // XSupportedLocales + virtual css::uno::Sequence< + css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XHyphenator + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + hyphenate( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + queryAlternativeSpelling( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XPossibleHyphens > SAL_CALL + createPossibleHyphens( + const OUString& rWord, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void HyphDummy_Impl::GetHyph_Impl() +{ + if (!xHyph.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xHyph = xLngSvcMgr->getHyphenator(); + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + HyphDummy_Impl::getLocales() +{ + GetHyph_Impl(); + if (xHyph.is()) + return xHyph->getLocales(); + else + return uno::Sequence< lang::Locale >(); +} + + +sal_Bool SAL_CALL + HyphDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetHyph_Impl(); + bool bRes = false; + if (xHyph.is()) + bRes = xHyph->hasLocale( rLocale ); + return bRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::hyphenate( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->hyphenate( rWord, rLocale, nMaxLeading, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::queryAlternativeSpelling( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->queryAlternativeSpelling( rWord, rLocale, nIndex, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XPossibleHyphens > SAL_CALL + HyphDummy_Impl::createPossibleHyphens( + const OUString& rWord, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XPossibleHyphens > xRes; + if (xHyph.is()) + xRes = xHyph->createPossibleHyphens( rWord, rLocale, rProperties ); + return xRes; +} + +class LinguMgrExitLstnr : public cppu::WeakImplHelper<XEventListener> +{ + uno::Reference< XDesktop2 > xDesktop; + + static void AtExit(); + +public: + LinguMgrExitLstnr(); + virtual ~LinguMgrExitLstnr() override; + + // lang::XEventListener + virtual void SAL_CALL disposing(const EventObject& rSource) override; +}; + +LinguMgrExitLstnr::LinguMgrExitLstnr() +{ + // add object to frame::Desktop EventListeners in order to properly call + // the AtExit function at application exit. + + uno::Reference< XComponentContext > xContext = getProcessComponentContext(); + xDesktop = Desktop::create( xContext ); + xDesktop->addEventListener( this ); +} + +LinguMgrExitLstnr::~LinguMgrExitLstnr() +{ + if (xDesktop.is()) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + } + OSL_ENSURE(!xDesktop.is(), "reference to desktop should be released"); +} + +void LinguMgrExitLstnr::disposing(const EventObject& rSource) +{ + if (xDesktop.is() && rSource.Source == xDesktop) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + + AtExit(); + } +} + +void LinguMgrExitLstnr::AtExit() +{ + SolarMutexGuard g; + + // release references + LinguMgr::xLngSvcMgr = nullptr; + LinguMgr::xSpell = nullptr; + LinguMgr::xHyph = nullptr; + LinguMgr::xThes = nullptr; + LinguMgr::xDicList = nullptr; + LinguMgr::xProp = nullptr; + LinguMgr::xIgnoreAll = nullptr; + LinguMgr::xChangeAll = nullptr; + + LinguMgr::bExiting = true; + + LinguMgr::pExitLstnr = nullptr; +} + + +rtl::Reference<LinguMgrExitLstnr> LinguMgr::pExitLstnr; +bool LinguMgr::bExiting = false; +uno::Reference< XLinguServiceManager2 > LinguMgr::xLngSvcMgr; +uno::Reference< XSpellChecker1 > LinguMgr::xSpell; +uno::Reference< XHyphenator > LinguMgr::xHyph; +uno::Reference< XThesaurus > LinguMgr::xThes; +uno::Reference< XSearchableDictionaryList > LinguMgr::xDicList; +uno::Reference< XLinguProperties > LinguMgr::xProp; +uno::Reference< XDictionary > LinguMgr::xIgnoreAll; +uno::Reference< XDictionary > LinguMgr::xChangeAll; + + +uno::Reference< XLinguServiceManager2 > LinguMgr::GetLngSvcMgr() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + if (!xLngSvcMgr.is()) + xLngSvcMgr = GetLngSvcMgr_Impl(); + + return xLngSvcMgr; +} + + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpellChecker() +{ + return xSpell.is() ? xSpell : GetSpell(); +} + +uno::Reference< XHyphenator > LinguMgr::GetHyphenator() +{ + return xHyph.is() ? xHyph : GetHyph(); +} + +uno::Reference< XThesaurus > LinguMgr::GetThesaurus() +{ + return xThes.is() ? xThes : GetThes(); +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDictionaryList() +{ + return xDicList.is() ? xDicList : GetDicList(); +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetLinguPropertySet() +{ + return xProp.is() ? xProp : GetProp(); +} + +uno::Reference< XDictionary > LinguMgr::GetStandardDic() +{ + //! don't hold reference to this + //! (it may be removed from dictionary list and needs to be + //! created empty if accessed again) + return GetStandard(); +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAllList() +{ + return xIgnoreAll.is() ? xIgnoreAll : GetIgnoreAll(); +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAllList() +{ + return xChangeAll.is() ? xChangeAll : GetChangeAll(); +} + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpell() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xSpell = new SpellDummy_Impl; + return xSpell; +} + +uno::Reference< XHyphenator > LinguMgr::GetHyph() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xHyph = new HyphDummy_Impl; + return xHyph; +} + +uno::Reference< XThesaurus > LinguMgr::GetThes() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + //! when only the XSupportedLocales interface is used. + //! The dummy accesses the real implementation (and thus loading the DLL) + //! when "real" work needs to be done only. + xThes = new ThesDummy_Impl; + return xThes; +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDicList() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xDicList = linguistic2::DictionaryList::create( getProcessComponentContext() ); + return xDicList; +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetProp() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xProp = linguistic2::LinguProperties::create( getProcessComponentContext() ); + return xProp; +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (xTmpDicList.is()) + { + std::locale loc(Translate::Create("svt")); + xIgnoreAll = xTmpDicList->getDictionaryByName( + Translate::get(STR_DESCRIPTION_IGNOREALLLIST, loc) ); + } + return xIgnoreAll; +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > _xDicList = GetDictionaryList(); + if (_xDicList.is()) + { + xChangeAll = _xDicList->createDictionary( + "ChangeAllList", + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_NEGATIVE, OUString() ); + } + return xChangeAll; +} + +uno::Reference< XDictionary > LinguMgr::GetStandard() +{ + // Tries to return a dictionary which may hold positive entries is + // persistent and not read-only. + + if (bExiting) + return nullptr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (!xTmpDicList.is()) + return nullptr; + + static constexpr OUString aDicName( u"standard.dic"_ustr ); + uno::Reference< XDictionary > xDic = xTmpDicList->getDictionaryByName( aDicName ); + if (!xDic.is()) + { + // try to create standard dictionary + uno::Reference< XDictionary > xTmp; + try + { + xTmp = xTmpDicList->createDictionary( aDicName, + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_POSITIVE, + linguistic::GetWritableDictionaryURL( aDicName ) ); + } + catch(const css::uno::Exception &) + { + } + + // add new dictionary to list + if (xTmp.is()) + { + xTmpDicList->addDictionary( xTmp ); + xTmp->setActive( true ); + } + xDic = xTmp; + } +#if OSL_DEBUG_LEVEL > 1 + uno::Reference< XStorable > xStor( xDic, UNO_QUERY ); + OSL_ENSURE( xDic.is() && xDic->getDictionaryType() == DictionaryType_POSITIVE, + "wrong dictionary type"); + OSL_ENSURE( xDic.is() && LanguageTag( xDic->getLocale() ).getLanguageType() == LANGUAGE_NONE, + "wrong dictionary language"); + OSL_ENSURE( !xStor.is() || (xStor->hasLocation() && !xStor->isReadonly()), + "dictionary not editable" ); +#endif + + return xDic; +} + +SvxAlternativeSpelling SvxGetAltSpelling( + const css::uno::Reference< css::linguistic2::XHyphenatedWord > & rHyphWord ) +{ + SvxAlternativeSpelling aRes; + if (rHyphWord.is() && rHyphWord->isAlternativeSpelling()) + { + OUString aWord( rHyphWord->getWord() ), + aAltWord( rHyphWord->getHyphenatedWord() ); + sal_Int16 nHyphenationPos = rHyphWord->getHyphenationPos(), + nHyphenPos = rHyphWord->getHyphenPos(); + sal_Int16 nLen = static_cast<sal_Int16>(aWord.getLength()); + sal_Int16 nAltLen = static_cast<sal_Int16>(aAltWord.getLength()); + const sal_Unicode *pWord = aWord.getStr(), + *pAltWord = aAltWord.getStr(); + + // count number of chars from the left to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nL = 0; + while (nL <= nHyphenationPos && nL <= nHyphenPos + && pWord[ nL ] == pAltWord[ nL ]) + ++nL; + // count number of chars from the right to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nR = 0; + sal_Int32 nIdx = nLen - 1; + sal_Int32 nAltIdx = nAltLen - 1; + while (nIdx > nHyphenationPos && nAltIdx > nHyphenPos + && pWord[ nIdx-- ] == pAltWord[ nAltIdx-- ]) + ++nR; + + aRes.aReplacement = aAltWord.copy( nL, nAltLen - nL - nR ); + aRes.nChangedPos = nL; + aRes.nChangedLength = nLen - nL - nR; + aRes.bIsAltSpelling = true; + } + return aRes; +} + + +SvxDicListChgClamp::SvxDicListChgClamp( uno::Reference< XSearchableDictionaryList > _xDicList ) : + xDicList (std::move( _xDicList )) +{ + if (xDicList.is()) + { + xDicList->beginCollectEvents(); + } +} + +SvxDicListChgClamp::~SvxDicListChgClamp() +{ + if (xDicList.is()) + { + xDicList->endCollectEvents(); + } +} + +short SvxDicError(weld::Window *pParent, linguistic::DictionaryError nError) +{ + short nRes = 0; + if (linguistic::DictionaryError::NONE != nError) + { + TranslateId pRid; + switch (nError) + { + case linguistic::DictionaryError::FULL : pRid = RID_SVXSTR_DIC_ERR_FULL; break; + case linguistic::DictionaryError::READONLY : pRid = RID_SVXSTR_DIC_ERR_READONLY; break; + default: + pRid = RID_SVXSTR_DIC_ERR_UNKNOWN; + SAL_WARN("editeng", "unexpected case"); + } + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + EditResId(pRid))); + nRes = xInfoBox->run(); + + } + return nRes; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/urlfieldhelper.cxx b/editeng/source/misc/urlfieldhelper.cxx new file mode 100644 index 0000000000..57f2a042c6 --- /dev/null +++ b/editeng/source/misc/urlfieldhelper.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <editeng/urlfieldhelper.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> + +void URLFieldHelper::RemoveURLField(EditView& pEditView) +{ + pEditView.SelectFieldAtCursor(); + const SvxFieldItem* pFieldItem = pEditView.GetFieldAtSelection(); + const SvxFieldData* pField = pFieldItem ? pFieldItem->GetField() : nullptr; + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pField)) + { + ESelection aSel = pEditView.GetSelection(); + pEditView.GetEditEngine()->QuickInsertText(pUrlField->GetRepresentation(), aSel); + pEditView.Invalidate(); + } +} + +bool URLFieldHelper::IsCursorAtURLField(const EditView& pEditView, bool bAlsoCheckBeforeCursor) +{ + // tdf#128666 Make sure only URL field (or nothing) is selected + ESelection aSel = pEditView.GetSelection(); + auto nSelectedParas = aSel.nEndPara - aSel.nStartPara; + auto nSelectedChars = aSel.nEndPos - aSel.nStartPos; + bool bIsValidSelection + = nSelectedParas == 0 + && (nSelectedChars == 0 || nSelectedChars == 1 || nSelectedChars == -1); + if (!bIsValidSelection) + return false; + + const SvxFieldData* pField + = pEditView.GetFieldUnderMouseOrInSelectionOrAtCursor(bAlsoCheckBeforeCursor); + if (dynamic_cast<const SvxURLField*>(pField)) + return true; + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |