summaryrefslogtreecommitdiffstats
path: root/editeng/source/misc
diff options
context:
space:
mode:
Diffstat (limited to 'editeng/source/misc')
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectExport.cxx110
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectExport.hxx60
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectImport.cxx163
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectImport.hxx113
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx54
-rw-r--r--editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx45
-rw-r--r--editeng/source/misc/acorrcfg.cxx698
-rw-r--r--editeng/source/misc/edtdlg.cxx29
-rw-r--r--editeng/source/misc/forbiddencharacterstable.cxx65
-rw-r--r--editeng/source/misc/hangulhanja.cxx1002
-rw-r--r--editeng/source/misc/splwrap.cxx472
-rw-r--r--editeng/source/misc/svxacorr.cxx3161
-rw-r--r--editeng/source/misc/swafopt.cxx81
-rw-r--r--editeng/source/misc/tokens.txt7
-rw-r--r--editeng/source/misc/txtrange.cxx667
-rw-r--r--editeng/source/misc/unolingu.cxx754
-rw-r--r--editeng/source/misc/urlfieldhelper.cxx49
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: */