bump product version to 7.2.5.1
[LibreOffice.git] / linguistic / source / convdic.cxx
blob573e2eb045171d78237bfd1e38cb88cae978e729
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 std;
56 using namespace utl;
57 using namespace osl;
58 using namespace com::sun::star;
59 using namespace com::sun::star::lang;
60 using namespace com::sun::star::uno;
61 using namespace com::sun::star::linguistic2;
62 using namespace linguistic;
65 static void ReadThroughDic( const OUString &rMainURL, ConvDicXMLImport &rImport )
67 if (rMainURL.isEmpty())
68 return;
69 DBG_ASSERT(!INetURLObject( rMainURL ).HasError(), "invalid URL");
71 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
73 // get xInputStream stream
74 uno::Reference< io::XInputStream > xIn;
75 try
77 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
78 xIn = xAccess->openFileRead( rMainURL );
80 catch (const uno::Exception &)
82 SAL_WARN( "linguistic", "failed to get input stream" );
84 if (!xIn.is())
85 return;
87 // prepare ParserInputSource
88 xml::sax::InputSource aParserInput;
89 aParserInput.aInputStream = xIn;
91 // finally, parser the stream
92 try
94 rImport.parseStream( aParserInput ); // implicitly calls ConvDicXMLImport::CreateContext
96 catch( xml::sax::SAXParseException& )
98 TOOLS_WARN_EXCEPTION("linguistic", "");
100 catch( xml::sax::SAXException& )
102 TOOLS_WARN_EXCEPTION("linguistic", "");
104 catch( io::IOException& )
106 TOOLS_WARN_EXCEPTION("linguistic", "");
110 bool IsConvDic( const OUString &rFileURL, LanguageType &nLang, sal_Int16 &nConvType )
112 bool bRes = false;
114 if (rFileURL.isEmpty())
115 return bRes;
117 // check if file extension matches CONV_DIC_EXT
118 OUString aExt;
119 sal_Int32 nPos = rFileURL.lastIndexOf( '.' );
120 if (-1 != nPos)
121 aExt = rFileURL.copy( nPos + 1 ).toAsciiLowerCase();
122 if (aExt != CONV_DIC_EXT)
123 return bRes;
125 // first argument being 0 should stop the file from being parsed
126 // up to the end (reading all entries) when the required
127 // data (language, conversion type) is found.
128 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( nullptr );
130 ReadThroughDic( rFileURL, *pImport ); // will implicitly add the entries
131 bRes = !LinguIsUnspecified( pImport->GetLanguage()) &&
132 pImport->GetConversionType() != -1;
133 DBG_ASSERT( bRes, "conversion dictionary corrupted?" );
135 if (bRes)
137 nLang = pImport->GetLanguage();
138 nConvType = pImport->GetConversionType();
141 return bRes;
145 ConvDic::ConvDic(
146 const OUString &rName,
147 LanguageType nLang,
148 sal_Int16 nConvType,
149 bool bBiDirectional,
150 const OUString &rMainURL) :
151 aFlushListeners( GetLinguMutex() ),
152 aMainURL(rMainURL), aName(rName), nLanguage(nLang),
153 nConversionType(nConvType),
154 nMaxLeftCharCount(0), nMaxRightCharCount(0),
155 bMaxCharCountIsValid(true),
156 bNeedEntries(true),
157 bIsModified(false), bIsActive(false)
160 if (bBiDirectional)
161 pFromRight.reset( new ConvMap );
162 if (nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL)
163 pConvPropType.reset( new PropTypeMap );
165 if( !rMainURL.isEmpty() )
167 bool bExists = false;
168 IsReadOnly( rMainURL, &bExists );
170 if( !bExists ) // new empty dictionary
172 bNeedEntries = false;
173 //! create physical representation of an **empty** dictionary
174 //! that could be found by the dictionary-list implementation
175 // (Note: empty dictionaries are not just empty files!)
176 Save();
179 else
181 bNeedEntries = false;
186 ConvDic::~ConvDic()
191 void ConvDic::Load()
193 DBG_ASSERT( !bIsModified, "dictionary is modified. Really do 'Load'?" );
195 //!! prevent function from being called recursively via HasEntry, AddEntry
196 bNeedEntries = false;
197 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( this );
198 ReadThroughDic( aMainURL, *pImport ); // will implicitly add the entries
199 bIsModified = false;
203 void ConvDic::Save()
205 DBG_ASSERT( !bNeedEntries, "saving while entries missing" );
206 if (aMainURL.isEmpty() || bNeedEntries)
207 return;
208 DBG_ASSERT(!INetURLObject( aMainURL ).HasError(), "invalid URL");
210 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
212 // get XOutputStream stream
213 uno::Reference< io::XStream > xStream;
216 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
217 xStream = xAccess->openFileReadWrite( aMainURL );
219 catch (const uno::Exception &)
221 SAL_WARN( "linguistic", "failed to get input stream" );
223 if (!xStream.is())
224 return;
226 std::unique_ptr<SvStream> pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
228 // get XML writer
229 uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(xContext);
231 if (xStream.is())
233 // connect XML writer to output stream
234 xSaxWriter->setOutputStream( xStream->getOutputStream() );
236 // prepare arguments (prepend doc handler to given arguments)
237 rtl::Reference<ConvDicXMLExport> pExport = new ConvDicXMLExport( *this, aMainURL, xSaxWriter );
238 bool bRet = pExport->Export(); // write entries to file
239 DBG_ASSERT( !pStream->GetError(), "I/O error while writing to stream" );
240 if (bRet)
241 bIsModified = false;
243 DBG_ASSERT( !bIsModified, "dictionary still modified after save. Save failed?" );
247 ConvMap::iterator ConvDic::GetEntry( ConvMap &rMap, const OUString &rFirstText, std::u16string_view rSecondText )
249 pair< ConvMap::iterator, ConvMap::iterator > aRange =
250 rMap.equal_range( rFirstText );
251 ConvMap::iterator aPos = rMap.end();
252 for (ConvMap::iterator aIt = aRange.first;
253 aIt != aRange.second && aPos == rMap.end();
254 ++aIt)
256 if ((*aIt).second == rSecondText)
257 aPos = aIt;
259 return aPos;
263 bool ConvDic::HasEntry( const OUString &rLeftText, std::u16string_view rRightText )
265 if (bNeedEntries)
266 Load();
267 ConvMap::iterator aIt = GetEntry( aFromLeft, rLeftText, rRightText );
268 return aIt != aFromLeft.end();
272 void ConvDic::AddEntry( const OUString &rLeftText, const OUString &rRightText )
274 if (bNeedEntries)
275 Load();
277 DBG_ASSERT(!HasEntry( rLeftText, rRightText), "entry already exists" );
278 aFromLeft .emplace( rLeftText, rRightText );
279 if (pFromRight)
280 pFromRight->emplace( rRightText, rLeftText );
282 if (bMaxCharCountIsValid)
284 if (rLeftText.getLength() > nMaxLeftCharCount)
285 nMaxLeftCharCount = static_cast<sal_Int16>(rLeftText.getLength());
286 if (pFromRight && rRightText.getLength() > nMaxRightCharCount)
287 nMaxRightCharCount = static_cast<sal_Int16>(rRightText.getLength());
290 bIsModified = true;
294 void ConvDic::RemoveEntry( const OUString &rLeftText, const OUString &rRightText )
296 if (bNeedEntries)
297 Load();
299 ConvMap::iterator aLeftIt = GetEntry( aFromLeft, rLeftText, rRightText );
300 DBG_ASSERT( aLeftIt != aFromLeft.end(), "left map entry missing" );
301 aFromLeft .erase( aLeftIt );
303 if (pFromRight)
305 ConvMap::iterator aRightIt = GetEntry( *pFromRight, rRightText, rLeftText );
306 DBG_ASSERT( aRightIt != pFromRight->end(), "right map entry missing" );
307 pFromRight->erase( aRightIt );
310 bIsModified = true;
311 bMaxCharCountIsValid = false;
315 OUString SAL_CALL ConvDic::getName( )
317 MutexGuard aGuard( GetLinguMutex() );
318 return aName;
322 Locale SAL_CALL ConvDic::getLocale( )
324 MutexGuard aGuard( GetLinguMutex() );
325 return LanguageTag::convertToLocale( nLanguage );
329 sal_Int16 SAL_CALL ConvDic::getConversionType( )
331 MutexGuard aGuard( GetLinguMutex() );
332 return nConversionType;
336 void SAL_CALL ConvDic::setActive( sal_Bool bActivate )
338 MutexGuard aGuard( GetLinguMutex() );
339 bIsActive = bActivate;
343 sal_Bool SAL_CALL ConvDic::isActive( )
345 MutexGuard aGuard( GetLinguMutex() );
346 return bIsActive;
350 void SAL_CALL ConvDic::clear( )
352 MutexGuard aGuard( GetLinguMutex() );
353 aFromLeft .clear();
354 if (pFromRight)
355 pFromRight->clear();
356 bNeedEntries = false;
357 bIsModified = true;
358 nMaxLeftCharCount = 0;
359 nMaxRightCharCount = 0;
360 bMaxCharCountIsValid = true;
364 uno::Sequence< OUString > SAL_CALL ConvDic::getConversions(
365 const OUString& aText,
366 sal_Int32 nStartPos,
367 sal_Int32 nLength,
368 ConversionDirection eDirection,
369 sal_Int32 /*nTextConversionOptions*/ )
371 MutexGuard aGuard( GetLinguMutex() );
373 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
374 return uno::Sequence< OUString >();
376 if (bNeedEntries)
377 Load();
379 OUString aLookUpText( aText.copy(nStartPos, nLength) );
380 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
381 aFromLeft : *pFromRight;
382 pair< ConvMap::iterator, ConvMap::iterator > aRange =
383 rConvMap.equal_range( aLookUpText );
385 std::vector<OUString> aRes;
386 auto nCount = static_cast<size_t>(std::distance(aRange.first, aRange.second));
387 aRes.reserve(nCount);
389 std::transform(aRange.first, aRange.second, std::back_inserter(aRes),
390 [](ConvMap::const_reference rEntry) { return rEntry.second; });
392 return comphelper::containerToSequence(aRes);
396 uno::Sequence< OUString > SAL_CALL ConvDic::getConversionEntries(
397 ConversionDirection eDirection )
399 MutexGuard aGuard( GetLinguMutex() );
401 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
402 return uno::Sequence< OUString >();
404 if (bNeedEntries)
405 Load();
407 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
408 aFromLeft : *pFromRight;
409 std::vector<OUString> aRes;
410 aRes.reserve(rConvMap.size());
411 for (auto const& elem : rConvMap)
413 OUString aCurEntry( elem.first );
414 // skip duplicate entries ( duplicate = duplicate entries
415 // respective to the evaluated side (FROM_LEFT or FROM_RIGHT).
416 // Thus if FROM_LEFT is evaluated for pairs (A,B) and (A,C)
417 // only one entry for A will be returned in the result)
418 if (std::find(aRes.begin(), aRes.end(), aCurEntry) == aRes.end())
419 aRes.push_back(aCurEntry);
422 return comphelper::containerToSequence(aRes);
426 void SAL_CALL ConvDic::addEntry(
427 const OUString& aLeftText,
428 const OUString& aRightText )
430 MutexGuard aGuard( GetLinguMutex() );
431 if (bNeedEntries)
432 Load();
433 if (HasEntry( aLeftText, aRightText ))
434 throw container::ElementExistException();
435 AddEntry( aLeftText, aRightText );
439 void SAL_CALL ConvDic::removeEntry(
440 const OUString& aLeftText,
441 const OUString& aRightText )
443 MutexGuard aGuard( GetLinguMutex() );
444 if (bNeedEntries)
445 Load();
446 if (!HasEntry( aLeftText, aRightText ))
447 throw container::NoSuchElementException();
448 RemoveEntry( aLeftText, aRightText );
452 sal_Int16 SAL_CALL ConvDic::getMaxCharCount( ConversionDirection eDirection )
454 MutexGuard aGuard( GetLinguMutex() );
456 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
458 DBG_ASSERT( nMaxRightCharCount == 0, "max right char count should be 0" );
459 return 0;
462 if (bNeedEntries)
463 Load();
465 if (!bMaxCharCountIsValid)
467 nMaxLeftCharCount = 0;
468 for (auto const& elem : aFromLeft)
470 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
471 if (nTmp > nMaxLeftCharCount)
472 nMaxLeftCharCount = nTmp;
475 nMaxRightCharCount = 0;
476 if (pFromRight)
478 for (auto const& elem : *pFromRight)
480 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
481 if (nTmp > nMaxRightCharCount)
482 nMaxRightCharCount = nTmp;
486 bMaxCharCountIsValid = true;
488 sal_Int16 nRes = eDirection == ConversionDirection_FROM_LEFT ?
489 nMaxLeftCharCount : nMaxRightCharCount;
490 DBG_ASSERT( nRes >= 0, "invalid MaxCharCount" );
491 return nRes;
495 void SAL_CALL ConvDic::setPropertyType(
496 const OUString& rLeftText,
497 const OUString& rRightText,
498 sal_Int16 nPropertyType )
500 bool bHasElement = HasEntry( rLeftText, rRightText);
501 if (!bHasElement)
502 throw container::NoSuchElementException();
504 // currently we assume that entries with the same left text have the
505 // same PropertyType even if the right text is different...
506 if (pConvPropType)
507 pConvPropType->emplace( rLeftText, nPropertyType );
508 bIsModified = true;
512 sal_Int16 SAL_CALL ConvDic::getPropertyType(
513 const OUString& rLeftText,
514 const OUString& rRightText )
516 bool bHasElement = HasEntry( rLeftText, rRightText);
517 if (!bHasElement)
518 throw container::NoSuchElementException();
520 sal_Int16 nRes = ConversionPropertyType::NOT_DEFINED;
521 if (pConvPropType)
523 // still assuming that entries with same left text have same PropertyType
524 // even if they have different right text...
525 PropTypeMap::iterator aIt = pConvPropType->find( rLeftText );
526 if (aIt != pConvPropType->end())
527 nRes = (*aIt).second;
529 return nRes;
533 void SAL_CALL ConvDic::flush( )
535 MutexGuard aGuard( GetLinguMutex() );
537 if (!bIsModified)
538 return;
540 Save();
542 // notify listeners
543 EventObject aEvtObj;
544 aEvtObj.Source = uno::Reference< XFlushable >( this );
545 aFlushListeners.notifyEach( &util::XFlushListener::flushed, aEvtObj );
549 void SAL_CALL ConvDic::addFlushListener(
550 const uno::Reference< util::XFlushListener >& rxListener )
552 MutexGuard aGuard( GetLinguMutex() );
553 if (rxListener.is())
554 aFlushListeners.addInterface( rxListener );
558 void SAL_CALL ConvDic::removeFlushListener(
559 const uno::Reference< util::XFlushListener >& rxListener )
561 MutexGuard aGuard( GetLinguMutex() );
562 if (rxListener.is())
563 aFlushListeners.removeInterface( rxListener );
567 OUString SAL_CALL ConvDic::getImplementationName( )
569 return "com.sun.star.lingu2.ConvDic";
572 sal_Bool SAL_CALL ConvDic::supportsService( const OUString& rServiceName )
574 return cppu::supportsService(this, rServiceName);
577 uno::Sequence< OUString > SAL_CALL ConvDic::getSupportedServiceNames( )
579 return { SN_CONV_DICTIONARY };
583 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */