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>
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())
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
;
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" );
86 // prepare ParserInputSource
87 xml::sax::InputSource aParserInput
;
88 aParserInput
.aInputStream
= xIn
;
90 // finally, parser the stream
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
)
113 if (rFileURL
.isEmpty())
116 // check if file extension matches CONV_DIC_EXT
118 sal_Int32 nPos
= rFileURL
.lastIndexOf( '.' );
120 aExt
= rFileURL
.copy( nPos
+ 1 ).toAsciiLowerCase();
121 if (aExt
!= CONV_DIC_EXT
)
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?" );
136 nLang
= pImport
->GetLanguage();
137 nConvType
= pImport
->GetConversionType();
145 const OUString
&rName
,
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),
156 bIsModified(false), bIsActive(false)
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!)
180 bNeedEntries
= false;
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
204 DBG_ASSERT( !bNeedEntries
, "saving while entries missing" );
205 if (aMainURL
.isEmpty() || bNeedEntries
)
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" );
225 std::unique_ptr
<SvStream
> pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
228 uno::Reference
< xml::sax::XWriter
> xSaxWriter
= xml::sax::Writer::create(xContext
);
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" );
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();
255 if ((*aIt
).second
== rSecondText
)
262 bool ConvDic::HasEntry( const OUString
&rLeftText
, std::u16string_view rRightText
)
266 ConvMap::iterator aIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
267 return aIt
!= aFromLeft
.end();
271 void ConvDic::AddEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
276 DBG_ASSERT(!HasEntry( rLeftText
, rRightText
), "entry already exists" );
277 aFromLeft
.emplace( rLeftText
, rRightText
);
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());
293 void ConvDic::RemoveEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
298 ConvMap::iterator aLeftIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
299 DBG_ASSERT( aLeftIt
!= aFromLeft
.end(), "left map entry missing" );
300 aFromLeft
.erase( aLeftIt
);
304 ConvMap::iterator aRightIt
= GetEntry( *pFromRight
, rRightText
, rLeftText
);
305 DBG_ASSERT( aRightIt
!= pFromRight
->end(), "right map entry missing" );
306 pFromRight
->erase( aRightIt
);
310 bMaxCharCountIsValid
= false;
314 OUString SAL_CALL
ConvDic::getName( )
316 MutexGuard
aGuard( GetLinguMutex() );
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() );
349 void SAL_CALL
ConvDic::clear( )
351 MutexGuard
aGuard( GetLinguMutex() );
355 bNeedEntries
= false;
357 nMaxLeftCharCount
= 0;
358 nMaxRightCharCount
= 0;
359 bMaxCharCountIsValid
= true;
363 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getConversions(
364 const OUString
& aText
,
367 ConversionDirection eDirection
,
368 sal_Int32
/*nTextConversionOptions*/ )
370 MutexGuard
aGuard( GetLinguMutex() );
372 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
373 return uno::Sequence
< OUString
>();
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
>();
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() );
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() );
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" );
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;
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" );
494 void SAL_CALL
ConvDic::setPropertyType(
495 const OUString
& rLeftText
,
496 const OUString
& rRightText
,
497 sal_Int16 nPropertyType
)
499 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
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...
506 pConvPropType
->emplace( rLeftText
, nPropertyType
);
511 sal_Int16 SAL_CALL
ConvDic::getPropertyType(
512 const OUString
& rLeftText
,
513 const OUString
& rRightText
)
515 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
517 throw container::NoSuchElementException();
519 sal_Int16 nRes
= ConversionPropertyType::NOT_DEFINED
;
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
;
532 void SAL_CALL
ConvDic::flush( )
534 MutexGuard
aGuard( GetLinguMutex() );
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() );
553 aFlushListeners
.addInterface( rxListener
);
557 void SAL_CALL
ConvDic::removeFlushListener(
558 const uno::Reference
< util::XFlushListener
>& rxListener
)
560 MutexGuard
aGuard( GetLinguMutex() );
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: */