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 <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>
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())
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
;
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" );
90 SvStreamPtr
pStream( utl::UcbStreamHelper::CreateStream( xIn
) );
92 // prepare ParserInputSource
93 xml::sax::InputSource aParserInput
;
94 aParserInput
.aInputStream
= xIn
;
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
)
127 if (rFileURL
.isEmpty())
130 // check if file extension matches CONV_DIC_EXT
132 sal_Int32 nPos
= rFileURL
.lastIndexOf( '.' );
134 aExt
= rFileURL
.copy( nPos
+ 1 ).toAsciiLowerCase();
135 if (aExt
!= CONV_DIC_EXT
)
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?" );
150 nLang
= pImport
->GetLanguage();
151 nConvType
= pImport
->GetConversionType();
159 const OUString
&rName
,
163 const OUString
&rMainURL
) :
164 aFlushListeners( GetLinguMutex() )
168 nConversionType
= nConvType
;
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;
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!)
198 bNeedEntries
= false;
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
222 DBG_ASSERT( !bNeedEntries
, "saving while entries missing" );
223 if (aMainURL
.isEmpty() || bNeedEntries
)
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" );
243 SvStreamPtr
pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
246 uno::Reference
< xml::sax::XWriter
> xSaxWriter
= xml::sax::Writer::create(xContext
);
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" );
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();
273 if ((*aIt
).second
== rSecondText
)
280 bool ConvDic::HasEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
284 ConvMap::iterator aIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
285 return aIt
!= aFromLeft
.end();
289 void ConvDic::AddEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
294 DBG_ASSERT(!HasEntry( rLeftText
, rRightText
), "entry already exists" );
295 aFromLeft
.emplace( rLeftText
, rRightText
);
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());
311 void ConvDic::RemoveEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
316 ConvMap::iterator aLeftIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
317 DBG_ASSERT( aLeftIt
!= aFromLeft
.end(), "left map entry missing" );
318 aFromLeft
.erase( aLeftIt
);
322 ConvMap::iterator aRightIt
= GetEntry( *pFromRight
, rRightText
, rLeftText
);
323 DBG_ASSERT( aRightIt
!= pFromRight
->end(), "right map entry missing" );
324 pFromRight
->erase( aRightIt
);
328 bMaxCharCountIsValid
= false;
332 OUString SAL_CALL
ConvDic::getName( )
334 MutexGuard
aGuard( GetLinguMutex() );
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() );
367 void SAL_CALL
ConvDic::clear( )
369 MutexGuard
aGuard( GetLinguMutex() );
373 bNeedEntries
= false;
375 nMaxLeftCharCount
= 0;
376 nMaxRightCharCount
= 0;
377 bMaxCharCountIsValid
= true;
381 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getConversions(
382 const OUString
& aText
,
385 ConversionDirection eDirection
,
386 sal_Int32
/*nTextConversionOptions*/ )
388 MutexGuard
aGuard( GetLinguMutex() );
390 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
391 return uno::Sequence
< OUString
>();
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
>();
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() );
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() );
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" );
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;
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" );
512 void SAL_CALL
ConvDic::setPropertyType(
513 const OUString
& rLeftText
,
514 const OUString
& rRightText
,
515 sal_Int16 nPropertyType
)
517 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
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...
524 pConvPropType
->emplace( rLeftText
, nPropertyType
);
529 sal_Int16 SAL_CALL
ConvDic::getPropertyType(
530 const OUString
& rLeftText
,
531 const OUString
& rRightText
)
533 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
535 throw container::NoSuchElementException();
537 sal_Int16 nRes
= ConversionPropertyType::NOT_DEFINED
;
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
;
550 void SAL_CALL
ConvDic::flush( )
552 MutexGuard
aGuard( GetLinguMutex() );
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() );
571 aFlushListeners
.addInterface( rxListener
);
575 void SAL_CALL
ConvDic::removeFlushListener(
576 const uno::Reference
< util::XFlushListener
>& rxListener
)
578 MutexGuard
aGuard( GetLinguMutex() );
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: */