1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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())
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
;
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" );
87 // prepare ParserInputSource
88 xml::sax::InputSource aParserInput
;
89 aParserInput
.aInputStream
= xIn
;
91 // finally, parser the stream
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
)
114 if (rFileURL
.isEmpty())
117 // check if file extension matches CONV_DIC_EXT
119 sal_Int32 nPos
= rFileURL
.lastIndexOf( '.' );
121 aExt
= rFileURL
.copy( nPos
+ 1 ).toAsciiLowerCase();
122 if (aExt
!= CONV_DIC_EXT
)
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?" );
137 nLang
= pImport
->GetLanguage();
138 nConvType
= pImport
->GetConversionType();
146 const OUString
&rName
,
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),
157 bIsModified(false), bIsActive(false)
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!)
181 bNeedEntries
= false;
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
205 DBG_ASSERT( !bNeedEntries
, "saving while entries missing" );
206 if (aMainURL
.isEmpty() || bNeedEntries
)
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" );
226 std::unique_ptr
<SvStream
> pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
229 uno::Reference
< xml::sax::XWriter
> xSaxWriter
= xml::sax::Writer::create(xContext
);
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" );
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();
256 if ((*aIt
).second
== rSecondText
)
263 bool ConvDic::HasEntry( const OUString
&rLeftText
, std::u16string_view rRightText
)
267 ConvMap::iterator aIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
268 return aIt
!= aFromLeft
.end();
272 void ConvDic::AddEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
277 DBG_ASSERT(!HasEntry( rLeftText
, rRightText
), "entry already exists" );
278 aFromLeft
.emplace( rLeftText
, rRightText
);
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());
294 void ConvDic::RemoveEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
299 ConvMap::iterator aLeftIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
300 DBG_ASSERT( aLeftIt
!= aFromLeft
.end(), "left map entry missing" );
301 aFromLeft
.erase( aLeftIt
);
305 ConvMap::iterator aRightIt
= GetEntry( *pFromRight
, rRightText
, rLeftText
);
306 DBG_ASSERT( aRightIt
!= pFromRight
->end(), "right map entry missing" );
307 pFromRight
->erase( aRightIt
);
311 bMaxCharCountIsValid
= false;
315 OUString SAL_CALL
ConvDic::getName( )
317 MutexGuard
aGuard( GetLinguMutex() );
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() );
350 void SAL_CALL
ConvDic::clear( )
352 MutexGuard
aGuard( GetLinguMutex() );
356 bNeedEntries
= false;
358 nMaxLeftCharCount
= 0;
359 nMaxRightCharCount
= 0;
360 bMaxCharCountIsValid
= true;
364 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getConversions(
365 const OUString
& aText
,
368 ConversionDirection eDirection
,
369 sal_Int32
/*nTextConversionOptions*/ )
371 MutexGuard
aGuard( GetLinguMutex() );
373 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
374 return uno::Sequence
< OUString
>();
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
>();
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() );
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() );
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" );
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;
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" );
495 void SAL_CALL
ConvDic::setPropertyType(
496 const OUString
& rLeftText
,
497 const OUString
& rRightText
,
498 sal_Int16 nPropertyType
)
500 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
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...
507 pConvPropType
->emplace( rLeftText
, nPropertyType
);
512 sal_Int16 SAL_CALL
ConvDic::getPropertyType(
513 const OUString
& rLeftText
,
514 const OUString
& rRightText
)
516 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
518 throw container::NoSuchElementException();
520 sal_Int16 nRes
= ConversionPropertyType::NOT_DEFINED
;
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
;
533 void SAL_CALL
ConvDic::flush( )
535 MutexGuard
aGuard( GetLinguMutex() );
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() );
554 aFlushListeners
.addInterface( rxListener
);
558 void SAL_CALL
ConvDic::removeFlushListener(
559 const uno::Reference
< util::XFlushListener
>& rxListener
)
561 MutexGuard
aGuard( GetLinguMutex() );
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: */