tdf#147298: Add a simple test case for formula cell tracking by column.
[LibreOffice.git] / linguistic / source / convdic.cxx
blob8d0f5bf50d26973509282d0bbfd7e9755985e8b7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <cppuhelper/factory.hxx>
22 #include <i18nlangtag/lang.h>
23 #include <i18nlangtag/languagetag.hxx>
24 #include <osl/mutex.hxx>
25 #include <sal/log.hxx>
26 #include <tools/debug.hxx>
27 #include <tools/stream.hxx>
28 #include <tools/urlobj.hxx>
29 #include <tools/diagnose_ex.h>
30 #include <comphelper/processfactory.hxx>
31 #include <comphelper/sequence.hxx>
32 #include <cppuhelper/supportsservice.hxx>
33 #include <unotools/streamwrap.hxx>
34 #include <unotools/ucbstreamhelper.hxx>
36 #include <com/sun/star/linguistic2/ConversionPropertyType.hpp>
37 #include <com/sun/star/util/XFlushable.hpp>
38 #include <com/sun/star/lang/EventObject.hpp>
39 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
40 #include <com/sun/star/uno/Reference.h>
41 #include <com/sun/star/util/XFlushListener.hpp>
42 #include <com/sun/star/io/IOException.hpp>
43 #include <com/sun/star/io/XInputStream.hpp>
44 #include <com/sun/star/xml/sax/Writer.hpp>
45 #include <com/sun/star/xml/sax/InputSource.hpp>
46 #include <com/sun/star/xml/sax/SAXParseException.hpp>
47 #include <com/sun/star/container/NoSuchElementException.hpp>
48 #include <com/sun/star/container/ElementExistException.hpp>
51 #include "convdic.hxx"
52 #include "convdicxml.hxx"
53 #include <linguistic/misc.hxx>
55 using namespace utl;
56 using namespace osl;
57 using namespace com::sun::star;
58 using namespace com::sun::star::lang;
59 using namespace com::sun::star::uno;
60 using namespace com::sun::star::linguistic2;
61 using namespace linguistic;
64 static void ReadThroughDic( const OUString &rMainURL, ConvDicXMLImport &rImport )
66 if (rMainURL.isEmpty())
67 return;
68 DBG_ASSERT(!INetURLObject( rMainURL ).HasError(), "invalid URL");
70 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
72 // get xInputStream stream
73 uno::Reference< io::XInputStream > xIn;
74 try
76 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
77 xIn = xAccess->openFileRead( rMainURL );
79 catch (const uno::Exception &)
81 SAL_WARN( "linguistic", "failed to get input stream" );
83 if (!xIn.is())
84 return;
86 // prepare ParserInputSource
87 xml::sax::InputSource aParserInput;
88 aParserInput.aInputStream = xIn;
90 // finally, parser the stream
91 try
93 rImport.parseStream( aParserInput ); // implicitly calls ConvDicXMLImport::CreateContext
95 catch( xml::sax::SAXParseException& )
97 TOOLS_WARN_EXCEPTION("linguistic", "");
99 catch( xml::sax::SAXException& )
101 TOOLS_WARN_EXCEPTION("linguistic", "");
103 catch( io::IOException& )
105 TOOLS_WARN_EXCEPTION("linguistic", "");
109 bool IsConvDic( const OUString &rFileURL, LanguageType &nLang, sal_Int16 &nConvType )
111 bool bRes = false;
113 if (rFileURL.isEmpty())
114 return bRes;
116 // check if file extension matches CONV_DIC_EXT
117 OUString aExt;
118 sal_Int32 nPos = rFileURL.lastIndexOf( '.' );
119 if (-1 != nPos)
120 aExt = rFileURL.copy( nPos + 1 ).toAsciiLowerCase();
121 if (aExt != CONV_DIC_EXT)
122 return bRes;
124 // first argument being 0 should stop the file from being parsed
125 // up to the end (reading all entries) when the required
126 // data (language, conversion type) is found.
127 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( nullptr );
129 ReadThroughDic( rFileURL, *pImport ); // will implicitly add the entries
130 bRes = !LinguIsUnspecified( pImport->GetLanguage()) &&
131 pImport->GetConversionType() != -1;
132 DBG_ASSERT( bRes, "conversion dictionary corrupted?" );
134 if (bRes)
136 nLang = pImport->GetLanguage();
137 nConvType = pImport->GetConversionType();
140 return bRes;
144 ConvDic::ConvDic(
145 const OUString &rName,
146 LanguageType nLang,
147 sal_Int16 nConvType,
148 bool bBiDirectional,
149 const OUString &rMainURL) :
150 aFlushListeners( GetLinguMutex() ),
151 aMainURL(rMainURL), aName(rName), nLanguage(nLang),
152 nConversionType(nConvType),
153 nMaxLeftCharCount(0), nMaxRightCharCount(0),
154 bMaxCharCountIsValid(true),
155 bNeedEntries(true),
156 bIsModified(false), bIsActive(false)
159 if (bBiDirectional)
160 pFromRight.reset( new ConvMap );
161 if (nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL)
162 pConvPropType.reset( new PropTypeMap );
164 if( !rMainURL.isEmpty() )
166 bool bExists = false;
167 IsReadOnly( rMainURL, &bExists );
169 if( !bExists ) // new empty dictionary
171 bNeedEntries = false;
172 //! create physical representation of an **empty** dictionary
173 //! that could be found by the dictionary-list implementation
174 // (Note: empty dictionaries are not just empty files!)
175 Save();
178 else
180 bNeedEntries = false;
185 ConvDic::~ConvDic()
190 void ConvDic::Load()
192 DBG_ASSERT( !bIsModified, "dictionary is modified. Really do 'Load'?" );
194 //!! prevent function from being called recursively via HasEntry, AddEntry
195 bNeedEntries = false;
196 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( this );
197 ReadThroughDic( aMainURL, *pImport ); // will implicitly add the entries
198 bIsModified = false;
202 void ConvDic::Save()
204 DBG_ASSERT( !bNeedEntries, "saving while entries missing" );
205 if (aMainURL.isEmpty() || bNeedEntries)
206 return;
207 DBG_ASSERT(!INetURLObject( aMainURL ).HasError(), "invalid URL");
209 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
211 // get XOutputStream stream
212 uno::Reference< io::XStream > xStream;
215 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
216 xStream = xAccess->openFileReadWrite( aMainURL );
218 catch (const uno::Exception &)
220 SAL_WARN( "linguistic", "failed to get input stream" );
222 if (!xStream.is())
223 return;
225 std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
227 // get XML writer
228 uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(xContext);
230 if (xStream.is())
232 // connect XML writer to output stream
233 xSaxWriter->setOutputStream( xStream->getOutputStream() );
235 // prepare arguments (prepend doc handler to given arguments)
236 rtl::Reference<ConvDicXMLExport> pExport = new ConvDicXMLExport( *this, aMainURL, xSaxWriter );
237 bool bRet = pExport->Export(); // write entries to file
238 DBG_ASSERT( !pStream->GetError(), "I/O error while writing to stream" );
239 if (bRet)
240 bIsModified = false;
242 DBG_ASSERT( !bIsModified, "dictionary still modified after save. Save failed?" );
246 ConvMap::iterator ConvDic::GetEntry( ConvMap &rMap, const OUString &rFirstText, std::u16string_view rSecondText )
248 std::pair< ConvMap::iterator, ConvMap::iterator > aRange =
249 rMap.equal_range( rFirstText );
250 ConvMap::iterator aPos = rMap.end();
251 for (ConvMap::iterator aIt = aRange.first;
252 aIt != aRange.second && aPos == rMap.end();
253 ++aIt)
255 if ((*aIt).second == rSecondText)
256 aPos = aIt;
258 return aPos;
262 bool ConvDic::HasEntry( const OUString &rLeftText, std::u16string_view rRightText )
264 if (bNeedEntries)
265 Load();
266 ConvMap::iterator aIt = GetEntry( aFromLeft, rLeftText, rRightText );
267 return aIt != aFromLeft.end();
271 void ConvDic::AddEntry( const OUString &rLeftText, const OUString &rRightText )
273 if (bNeedEntries)
274 Load();
276 DBG_ASSERT(!HasEntry( rLeftText, rRightText), "entry already exists" );
277 aFromLeft .emplace( rLeftText, rRightText );
278 if (pFromRight)
279 pFromRight->emplace( rRightText, rLeftText );
281 if (bMaxCharCountIsValid)
283 if (rLeftText.getLength() > nMaxLeftCharCount)
284 nMaxLeftCharCount = static_cast<sal_Int16>(rLeftText.getLength());
285 if (pFromRight && rRightText.getLength() > nMaxRightCharCount)
286 nMaxRightCharCount = static_cast<sal_Int16>(rRightText.getLength());
289 bIsModified = true;
293 void ConvDic::RemoveEntry( const OUString &rLeftText, const OUString &rRightText )
295 if (bNeedEntries)
296 Load();
298 ConvMap::iterator aLeftIt = GetEntry( aFromLeft, rLeftText, rRightText );
299 DBG_ASSERT( aLeftIt != aFromLeft.end(), "left map entry missing" );
300 aFromLeft .erase( aLeftIt );
302 if (pFromRight)
304 ConvMap::iterator aRightIt = GetEntry( *pFromRight, rRightText, rLeftText );
305 DBG_ASSERT( aRightIt != pFromRight->end(), "right map entry missing" );
306 pFromRight->erase( aRightIt );
309 bIsModified = true;
310 bMaxCharCountIsValid = false;
314 OUString SAL_CALL ConvDic::getName( )
316 MutexGuard aGuard( GetLinguMutex() );
317 return aName;
321 Locale SAL_CALL ConvDic::getLocale( )
323 MutexGuard aGuard( GetLinguMutex() );
324 return LanguageTag::convertToLocale( nLanguage );
328 sal_Int16 SAL_CALL ConvDic::getConversionType( )
330 MutexGuard aGuard( GetLinguMutex() );
331 return nConversionType;
335 void SAL_CALL ConvDic::setActive( sal_Bool bActivate )
337 MutexGuard aGuard( GetLinguMutex() );
338 bIsActive = bActivate;
342 sal_Bool SAL_CALL ConvDic::isActive( )
344 MutexGuard aGuard( GetLinguMutex() );
345 return bIsActive;
349 void SAL_CALL ConvDic::clear( )
351 MutexGuard aGuard( GetLinguMutex() );
352 aFromLeft .clear();
353 if (pFromRight)
354 pFromRight->clear();
355 bNeedEntries = false;
356 bIsModified = true;
357 nMaxLeftCharCount = 0;
358 nMaxRightCharCount = 0;
359 bMaxCharCountIsValid = true;
363 uno::Sequence< OUString > SAL_CALL ConvDic::getConversions(
364 const OUString& aText,
365 sal_Int32 nStartPos,
366 sal_Int32 nLength,
367 ConversionDirection eDirection,
368 sal_Int32 /*nTextConversionOptions*/ )
370 MutexGuard aGuard( GetLinguMutex() );
372 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
373 return uno::Sequence< OUString >();
375 if (bNeedEntries)
376 Load();
378 OUString aLookUpText( aText.copy(nStartPos, nLength) );
379 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
380 aFromLeft : *pFromRight;
381 std::pair< ConvMap::iterator, ConvMap::iterator > aRange =
382 rConvMap.equal_range( aLookUpText );
384 std::vector<OUString> aRes;
385 auto nCount = static_cast<size_t>(std::distance(aRange.first, aRange.second));
386 aRes.reserve(nCount);
388 std::transform(aRange.first, aRange.second, std::back_inserter(aRes),
389 [](ConvMap::const_reference rEntry) { return rEntry.second; });
391 return comphelper::containerToSequence(aRes);
395 uno::Sequence< OUString > SAL_CALL ConvDic::getConversionEntries(
396 ConversionDirection eDirection )
398 MutexGuard aGuard( GetLinguMutex() );
400 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
401 return uno::Sequence< OUString >();
403 if (bNeedEntries)
404 Load();
406 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
407 aFromLeft : *pFromRight;
408 std::vector<OUString> aRes;
409 aRes.reserve(rConvMap.size());
410 for (auto const& elem : rConvMap)
412 OUString aCurEntry( elem.first );
413 // skip duplicate entries ( duplicate = duplicate entries
414 // respective to the evaluated side (FROM_LEFT or FROM_RIGHT).
415 // Thus if FROM_LEFT is evaluated for pairs (A,B) and (A,C)
416 // only one entry for A will be returned in the result)
417 if (std::find(aRes.begin(), aRes.end(), aCurEntry) == aRes.end())
418 aRes.push_back(aCurEntry);
421 return comphelper::containerToSequence(aRes);
425 void SAL_CALL ConvDic::addEntry(
426 const OUString& aLeftText,
427 const OUString& aRightText )
429 MutexGuard aGuard( GetLinguMutex() );
430 if (bNeedEntries)
431 Load();
432 if (HasEntry( aLeftText, aRightText ))
433 throw container::ElementExistException();
434 AddEntry( aLeftText, aRightText );
438 void SAL_CALL ConvDic::removeEntry(
439 const OUString& aLeftText,
440 const OUString& aRightText )
442 MutexGuard aGuard( GetLinguMutex() );
443 if (bNeedEntries)
444 Load();
445 if (!HasEntry( aLeftText, aRightText ))
446 throw container::NoSuchElementException();
447 RemoveEntry( aLeftText, aRightText );
451 sal_Int16 SAL_CALL ConvDic::getMaxCharCount( ConversionDirection eDirection )
453 MutexGuard aGuard( GetLinguMutex() );
455 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
457 DBG_ASSERT( nMaxRightCharCount == 0, "max right char count should be 0" );
458 return 0;
461 if (bNeedEntries)
462 Load();
464 if (!bMaxCharCountIsValid)
466 nMaxLeftCharCount = 0;
467 for (auto const& elem : aFromLeft)
469 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
470 if (nTmp > nMaxLeftCharCount)
471 nMaxLeftCharCount = nTmp;
474 nMaxRightCharCount = 0;
475 if (pFromRight)
477 for (auto const& elem : *pFromRight)
479 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
480 if (nTmp > nMaxRightCharCount)
481 nMaxRightCharCount = nTmp;
485 bMaxCharCountIsValid = true;
487 sal_Int16 nRes = eDirection == ConversionDirection_FROM_LEFT ?
488 nMaxLeftCharCount : nMaxRightCharCount;
489 DBG_ASSERT( nRes >= 0, "invalid MaxCharCount" );
490 return nRes;
494 void SAL_CALL ConvDic::setPropertyType(
495 const OUString& rLeftText,
496 const OUString& rRightText,
497 sal_Int16 nPropertyType )
499 bool bHasElement = HasEntry( rLeftText, rRightText);
500 if (!bHasElement)
501 throw container::NoSuchElementException();
503 // currently we assume that entries with the same left text have the
504 // same PropertyType even if the right text is different...
505 if (pConvPropType)
506 pConvPropType->emplace( rLeftText, nPropertyType );
507 bIsModified = true;
511 sal_Int16 SAL_CALL ConvDic::getPropertyType(
512 const OUString& rLeftText,
513 const OUString& rRightText )
515 bool bHasElement = HasEntry( rLeftText, rRightText);
516 if (!bHasElement)
517 throw container::NoSuchElementException();
519 sal_Int16 nRes = ConversionPropertyType::NOT_DEFINED;
520 if (pConvPropType)
522 // still assuming that entries with same left text have same PropertyType
523 // even if they have different right text...
524 PropTypeMap::iterator aIt = pConvPropType->find( rLeftText );
525 if (aIt != pConvPropType->end())
526 nRes = (*aIt).second;
528 return nRes;
532 void SAL_CALL ConvDic::flush( )
534 MutexGuard aGuard( GetLinguMutex() );
536 if (!bIsModified)
537 return;
539 Save();
541 // notify listeners
542 EventObject aEvtObj;
543 aEvtObj.Source = uno::Reference< XFlushable >( this );
544 aFlushListeners.notifyEach( &util::XFlushListener::flushed, aEvtObj );
548 void SAL_CALL ConvDic::addFlushListener(
549 const uno::Reference< util::XFlushListener >& rxListener )
551 MutexGuard aGuard( GetLinguMutex() );
552 if (rxListener.is())
553 aFlushListeners.addInterface( rxListener );
557 void SAL_CALL ConvDic::removeFlushListener(
558 const uno::Reference< util::XFlushListener >& rxListener )
560 MutexGuard aGuard( GetLinguMutex() );
561 if (rxListener.is())
562 aFlushListeners.removeInterface( rxListener );
566 OUString SAL_CALL ConvDic::getImplementationName( )
568 return "com.sun.star.lingu2.ConvDic";
571 sal_Bool SAL_CALL ConvDic::supportsService( const OUString& rServiceName )
573 return cppu::supportsService(this, rServiceName);
576 uno::Sequence< OUString > SAL_CALL ConvDic::getSupportedServiceNames( )
578 return { SN_CONV_DICTIONARY };
582 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */