Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / linguistic / source / convdic.cxx
blob1a7b559a203b63a880ddb4b3602448ecf6f1a133
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 <comphelper/processfactory.hxx>
30 #include <comphelper/sequence.hxx>
31 #include <cppuhelper/supportsservice.hxx>
32 #include <unotools/streamwrap.hxx>
33 #include <unotools/ucbstreamhelper.hxx>
35 #include <com/sun/star/linguistic2/ConversionPropertyType.hpp>
36 #include <com/sun/star/util/XFlushable.hpp>
37 #include <com/sun/star/lang/EventObject.hpp>
38 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
39 #include <com/sun/star/uno/Reference.h>
40 #include <com/sun/star/util/XFlushListener.hpp>
41 #include <com/sun/star/io/IOException.hpp>
42 #include <com/sun/star/io/XInputStream.hpp>
43 #include <com/sun/star/xml/sax/Writer.hpp>
44 #include <com/sun/star/xml/sax/InputSource.hpp>
45 #include <com/sun/star/xml/sax/Parser.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>
54 #include "defs.hxx"
56 using namespace std;
57 using namespace utl;
58 using namespace osl;
59 using namespace com::sun::star;
60 using namespace com::sun::star::lang;
61 using namespace com::sun::star::uno;
62 using namespace com::sun::star::linguistic2;
63 using namespace linguistic;
66 #define SN_CONV_DICTIONARY "com.sun.star.linguistic2.ConversionDictionary"
68 static void ReadThroughDic( const OUString &rMainURL, ConvDicXMLImport &rImport )
70 if (rMainURL.isEmpty())
71 return;
72 DBG_ASSERT(!INetURLObject( rMainURL ).HasError(), "invalid URL");
74 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
76 // get xInputStream stream
77 uno::Reference< io::XInputStream > xIn;
78 try
80 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
81 xIn = xAccess->openFileRead( rMainURL );
83 catch (const uno::Exception &)
85 SAL_WARN( "linguistic", "failed to get input stream" );
87 if (!xIn.is())
88 return;
90 SvStreamPtr pStream( utl::UcbStreamHelper::CreateStream( xIn ) );
92 // prepare ParserInputSource
93 xml::sax::InputSource aParserInput;
94 aParserInput.aInputStream = xIn;
96 // get parser
97 uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create( xContext );
99 //!! keep a reference until everything is done to
100 //!! ensure the proper lifetime of the object
101 uno::Reference < xml::sax::XDocumentHandler > xFilter(
102 static_cast<xml::sax::XExtendedDocumentHandler *>(&rImport), UNO_QUERY );
104 // connect parser and filter
105 xParser->setDocumentHandler( xFilter );
107 // finally, parser the stream
110 xParser->parseStream( aParserInput ); // implicitly calls ConvDicXMLImport::CreateContext
112 catch( xml::sax::SAXParseException& )
115 catch( xml::sax::SAXException& )
118 catch( io::IOException& )
123 bool IsConvDic( const OUString &rFileURL, LanguageType &nLang, sal_Int16 &nConvType )
125 bool bRes = false;
127 if (rFileURL.isEmpty())
128 return bRes;
130 // check if file extension matches CONV_DIC_EXT
131 OUString aExt;
132 sal_Int32 nPos = rFileURL.lastIndexOf( '.' );
133 if (-1 != nPos)
134 aExt = rFileURL.copy( nPos + 1 ).toAsciiLowerCase();
135 if (aExt != CONV_DIC_EXT)
136 return bRes;
138 // first argument being 0 should stop the file from being parsed
139 // up to the end (reading all entries) when the required
140 // data (language, conversion type) is found.
141 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( nullptr );
143 ReadThroughDic( rFileURL, *pImport ); // will implicitly add the entries
144 bRes = !LinguIsUnspecified( pImport->GetLanguage()) &&
145 pImport->GetConversionType() != -1;
146 DBG_ASSERT( bRes, "conversion dictionary corrupted?" );
148 if (bRes)
150 nLang = pImport->GetLanguage();
151 nConvType = pImport->GetConversionType();
154 return bRes;
158 ConvDic::ConvDic(
159 const OUString &rName,
160 LanguageType nLang,
161 sal_Int16 nConvType,
162 bool bBiDirectional,
163 const OUString &rMainURL) :
164 aFlushListeners( GetLinguMutex() )
166 aName = rName;
167 nLanguage = nLang;
168 nConversionType = nConvType;
169 aMainURL = rMainURL;
171 if (bBiDirectional)
172 pFromRight.reset( new ConvMap );
173 if (nLang == LANGUAGE_CHINESE_SIMPLIFIED || nLang == LANGUAGE_CHINESE_TRADITIONAL)
174 pConvPropType.reset( new PropTypeMap );
176 nMaxLeftCharCount = nMaxRightCharCount = 0;
177 bMaxCharCountIsValid = true;
179 bNeedEntries = true;
180 bIsModified = bIsActive = false;
182 if( !rMainURL.isEmpty() )
184 bool bExists = false;
185 IsReadOnly( rMainURL, &bExists );
187 if( !bExists ) // new empty dictionary
189 bNeedEntries = false;
190 //! create physical representation of an **empty** dictionary
191 //! that could be found by the dictionary-list implementation
192 // (Note: empty dictionaries are not just empty files!)
193 Save();
196 else
198 bNeedEntries = false;
203 ConvDic::~ConvDic()
208 void ConvDic::Load()
210 DBG_ASSERT( !bIsModified, "dictionary is modified. Really do 'Load'?" );
212 //!! prevent function from being called recursively via HasEntry, AddEntry
213 bNeedEntries = false;
214 rtl::Reference<ConvDicXMLImport> pImport = new ConvDicXMLImport( this );
215 ReadThroughDic( aMainURL, *pImport ); // will implicitly add the entries
216 bIsModified = false;
220 void ConvDic::Save()
222 DBG_ASSERT( !bNeedEntries, "saving while entries missing" );
223 if (aMainURL.isEmpty() || bNeedEntries)
224 return;
225 DBG_ASSERT(!INetURLObject( aMainURL ).HasError(), "invalid URL");
227 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
229 // get XOutputStream stream
230 uno::Reference< io::XStream > xStream;
233 uno::Reference< ucb::XSimpleFileAccess3 > xAccess( ucb::SimpleFileAccess::create(xContext) );
234 xStream = xAccess->openFileReadWrite( aMainURL );
236 catch (const uno::Exception &)
238 SAL_WARN( "linguistic", "failed to get input stream" );
240 if (!xStream.is())
241 return;
243 SvStreamPtr pStream( utl::UcbStreamHelper::CreateStream( xStream ) );
245 // get XML writer
246 uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(xContext);
248 if (xStream.is())
250 // connect XML writer to output stream
251 xSaxWriter->setOutputStream( xStream->getOutputStream() );
253 // prepare arguments (prepend doc handler to given arguments)
254 rtl::Reference<ConvDicXMLExport> pExport = new ConvDicXMLExport( *this, aMainURL, xSaxWriter );
255 bool bRet = pExport->Export(); // write entries to file
256 DBG_ASSERT( !pStream->GetError(), "I/O error while writing to stream" );
257 if (bRet)
258 bIsModified = false;
260 DBG_ASSERT( !bIsModified, "dictionary still modified after save. Save failed?" );
264 ConvMap::iterator ConvDic::GetEntry( ConvMap &rMap, const OUString &rFirstText, const OUString &rSecondText )
266 pair< ConvMap::iterator, ConvMap::iterator > aRange =
267 rMap.equal_range( rFirstText );
268 ConvMap::iterator aPos = rMap.end();
269 for (ConvMap::iterator aIt = aRange.first;
270 aIt != aRange.second && aPos == rMap.end();
271 ++aIt)
273 if ((*aIt).second == rSecondText)
274 aPos = aIt;
276 return aPos;
280 bool ConvDic::HasEntry( const OUString &rLeftText, const OUString &rRightText )
282 if (bNeedEntries)
283 Load();
284 ConvMap::iterator aIt = GetEntry( aFromLeft, rLeftText, rRightText );
285 return aIt != aFromLeft.end();
289 void ConvDic::AddEntry( const OUString &rLeftText, const OUString &rRightText )
291 if (bNeedEntries)
292 Load();
294 DBG_ASSERT(!HasEntry( rLeftText, rRightText), "entry already exists" );
295 aFromLeft .emplace( rLeftText, rRightText );
296 if (pFromRight)
297 pFromRight->emplace( rRightText, rLeftText );
299 if (bMaxCharCountIsValid)
301 if (rLeftText.getLength() > nMaxLeftCharCount)
302 nMaxLeftCharCount = static_cast<sal_Int16>(rLeftText.getLength());
303 if (pFromRight.get() && rRightText.getLength() > nMaxRightCharCount)
304 nMaxRightCharCount = static_cast<sal_Int16>(rRightText.getLength());
307 bIsModified = true;
311 void ConvDic::RemoveEntry( const OUString &rLeftText, const OUString &rRightText )
313 if (bNeedEntries)
314 Load();
316 ConvMap::iterator aLeftIt = GetEntry( aFromLeft, rLeftText, rRightText );
317 DBG_ASSERT( aLeftIt != aFromLeft.end(), "left map entry missing" );
318 aFromLeft .erase( aLeftIt );
320 if (pFromRight)
322 ConvMap::iterator aRightIt = GetEntry( *pFromRight, rRightText, rLeftText );
323 DBG_ASSERT( aRightIt != pFromRight->end(), "right map entry missing" );
324 pFromRight->erase( aRightIt );
327 bIsModified = true;
328 bMaxCharCountIsValid = false;
332 OUString SAL_CALL ConvDic::getName( )
334 MutexGuard aGuard( GetLinguMutex() );
335 return aName;
339 Locale SAL_CALL ConvDic::getLocale( )
341 MutexGuard aGuard( GetLinguMutex() );
342 return LanguageTag::convertToLocale( nLanguage );
346 sal_Int16 SAL_CALL ConvDic::getConversionType( )
348 MutexGuard aGuard( GetLinguMutex() );
349 return nConversionType;
353 void SAL_CALL ConvDic::setActive( sal_Bool bActivate )
355 MutexGuard aGuard( GetLinguMutex() );
356 bIsActive = bActivate;
360 sal_Bool SAL_CALL ConvDic::isActive( )
362 MutexGuard aGuard( GetLinguMutex() );
363 return bIsActive;
367 void SAL_CALL ConvDic::clear( )
369 MutexGuard aGuard( GetLinguMutex() );
370 aFromLeft .clear();
371 if (pFromRight)
372 pFromRight->clear();
373 bNeedEntries = false;
374 bIsModified = true;
375 nMaxLeftCharCount = 0;
376 nMaxRightCharCount = 0;
377 bMaxCharCountIsValid = true;
381 uno::Sequence< OUString > SAL_CALL ConvDic::getConversions(
382 const OUString& aText,
383 sal_Int32 nStartPos,
384 sal_Int32 nLength,
385 ConversionDirection eDirection,
386 sal_Int32 /*nTextConversionOptions*/ )
388 MutexGuard aGuard( GetLinguMutex() );
390 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
391 return uno::Sequence< OUString >();
393 if (bNeedEntries)
394 Load();
396 OUString aLookUpText( aText.copy(nStartPos, nLength) );
397 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
398 aFromLeft : *pFromRight;
399 pair< ConvMap::iterator, ConvMap::iterator > aRange =
400 rConvMap.equal_range( aLookUpText );
402 std::vector<OUString> aRes;
403 auto nCount = static_cast<size_t>(std::distance(aRange.first, aRange.second));
404 aRes.reserve(nCount);
406 std::transform(aRange.first, aRange.second, std::back_inserter(aRes),
407 [](ConvMap::const_reference rEntry) { return rEntry.second; });
409 return comphelper::containerToSequence(aRes);
413 uno::Sequence< OUString > SAL_CALL ConvDic::getConversionEntries(
414 ConversionDirection eDirection )
416 MutexGuard aGuard( GetLinguMutex() );
418 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
419 return uno::Sequence< OUString >();
421 if (bNeedEntries)
422 Load();
424 ConvMap &rConvMap = eDirection == ConversionDirection_FROM_LEFT ?
425 aFromLeft : *pFromRight;
426 std::vector<OUString> aRes;
427 aRes.reserve(rConvMap.size());
428 for (auto const& elem : rConvMap)
430 OUString aCurEntry( elem.first );
431 // skip duplicate entries ( duplicate = duplicate entries
432 // respective to the evaluated side (FROM_LEFT or FROM_RIGHT).
433 // Thus if FROM_LEFT is evaluated for pairs (A,B) and (A,C)
434 // only one entry for A will be returned in the result)
435 if (std::find(aRes.begin(), aRes.end(), aCurEntry) == aRes.end())
436 aRes.push_back(aCurEntry);
439 return comphelper::containerToSequence(aRes);
443 void SAL_CALL ConvDic::addEntry(
444 const OUString& aLeftText,
445 const OUString& aRightText )
447 MutexGuard aGuard( GetLinguMutex() );
448 if (bNeedEntries)
449 Load();
450 if (HasEntry( aLeftText, aRightText ))
451 throw container::ElementExistException();
452 AddEntry( aLeftText, aRightText );
456 void SAL_CALL ConvDic::removeEntry(
457 const OUString& aLeftText,
458 const OUString& aRightText )
460 MutexGuard aGuard( GetLinguMutex() );
461 if (bNeedEntries)
462 Load();
463 if (!HasEntry( aLeftText, aRightText ))
464 throw container::NoSuchElementException();
465 RemoveEntry( aLeftText, aRightText );
469 sal_Int16 SAL_CALL ConvDic::getMaxCharCount( ConversionDirection eDirection )
471 MutexGuard aGuard( GetLinguMutex() );
473 if (!pFromRight && eDirection == ConversionDirection_FROM_RIGHT)
475 DBG_ASSERT( nMaxRightCharCount == 0, "max right char count should be 0" );
476 return 0;
479 if (bNeedEntries)
480 Load();
482 if (!bMaxCharCountIsValid)
484 nMaxLeftCharCount = 0;
485 for (auto const& elem : aFromLeft)
487 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
488 if (nTmp > nMaxLeftCharCount)
489 nMaxLeftCharCount = nTmp;
492 nMaxRightCharCount = 0;
493 if (pFromRight)
495 for (auto const& elem : *pFromRight)
497 sal_Int16 nTmp = static_cast<sal_Int16>(elem.first.getLength());
498 if (nTmp > nMaxRightCharCount)
499 nMaxRightCharCount = nTmp;
503 bMaxCharCountIsValid = true;
505 sal_Int16 nRes = eDirection == ConversionDirection_FROM_LEFT ?
506 nMaxLeftCharCount : nMaxRightCharCount;
507 DBG_ASSERT( nRes >= 0, "invalid MaxCharCount" );
508 return nRes;
512 void SAL_CALL ConvDic::setPropertyType(
513 const OUString& rLeftText,
514 const OUString& rRightText,
515 sal_Int16 nPropertyType )
517 bool bHasElement = HasEntry( rLeftText, rRightText);
518 if (!bHasElement)
519 throw container::NoSuchElementException();
521 // currently we assume that entries with the same left text have the
522 // same PropertyType even if the right text is different...
523 if (pConvPropType)
524 pConvPropType->emplace( rLeftText, nPropertyType );
525 bIsModified = true;
529 sal_Int16 SAL_CALL ConvDic::getPropertyType(
530 const OUString& rLeftText,
531 const OUString& rRightText )
533 bool bHasElement = HasEntry( rLeftText, rRightText);
534 if (!bHasElement)
535 throw container::NoSuchElementException();
537 sal_Int16 nRes = ConversionPropertyType::NOT_DEFINED;
538 if (pConvPropType)
540 // still assuming that entries with same left text have same PropertyType
541 // even if they have different right text...
542 PropTypeMap::iterator aIt = pConvPropType->find( rLeftText );
543 if (aIt != pConvPropType->end())
544 nRes = (*aIt).second;
546 return nRes;
550 void SAL_CALL ConvDic::flush( )
552 MutexGuard aGuard( GetLinguMutex() );
554 if (!bIsModified)
555 return;
557 Save();
559 // notify listeners
560 EventObject aEvtObj;
561 aEvtObj.Source = uno::Reference< XFlushable >( this );
562 aFlushListeners.notifyEach( &util::XFlushListener::flushed, aEvtObj );
566 void SAL_CALL ConvDic::addFlushListener(
567 const uno::Reference< util::XFlushListener >& rxListener )
569 MutexGuard aGuard( GetLinguMutex() );
570 if (rxListener.is())
571 aFlushListeners.addInterface( rxListener );
575 void SAL_CALL ConvDic::removeFlushListener(
576 const uno::Reference< util::XFlushListener >& rxListener )
578 MutexGuard aGuard( GetLinguMutex() );
579 if (rxListener.is())
580 aFlushListeners.removeInterface( rxListener );
584 OUString SAL_CALL ConvDic::getImplementationName( )
586 return "com.sun.star.lingu2.ConvDic";
589 sal_Bool SAL_CALL ConvDic::supportsService( const OUString& rServiceName )
591 return cppu::supportsService(this, rServiceName);
594 uno::Sequence< OUString > SAL_CALL ConvDic::getSupportedServiceNames( )
596 return { SN_CONV_DICTIONARY };
600 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */