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 <ucbhelper/content.hxx>
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/Locale.hpp>
39 #include <com/sun/star/lang/EventObject.hpp>
40 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
41 #include <com/sun/star/uno/Reference.h>
42 #include <com/sun/star/registry/XRegistryKey.hpp>
43 #include <com/sun/star/util/XFlushListener.hpp>
44 #include <com/sun/star/io/IOException.hpp>
45 #include <com/sun/star/io/XActiveDataSource.hpp>
46 #include <com/sun/star/io/XInputStream.hpp>
47 #include <com/sun/star/io/XOutputStream.hpp>
48 #include <com/sun/star/xml/sax/Writer.hpp>
49 #include <com/sun/star/document/XFilter.hpp>
50 #include <com/sun/star/beans/PropertyValue.hpp>
51 #include <com/sun/star/xml/sax/InputSource.hpp>
52 #include <com/sun/star/xml/sax/Parser.hpp>
53 #include <com/sun/star/xml/sax/SAXParseException.hpp>
54 #include <com/sun/star/container/NoSuchElementException.hpp>
55 #include <com/sun/star/container/ElementExistException.hpp>
58 #include "convdic.hxx"
59 #include "convdicxml.hxx"
60 #include <linguistic/misc.hxx>
66 using namespace com::sun::star
;
67 using namespace com::sun::star::lang
;
68 using namespace com::sun::star::uno
;
69 using namespace com::sun::star::linguistic2
;
70 using namespace linguistic
;
73 #define SN_CONV_DICTIONARY "com.sun.star.linguistic2.ConversionDictionary"
75 static void ReadThroughDic( const OUString
&rMainURL
, ConvDicXMLImport
&rImport
)
77 if (rMainURL
.isEmpty())
79 DBG_ASSERT(!INetURLObject( rMainURL
).HasError(), "invalid URL");
81 uno::Reference
< uno::XComponentContext
> xContext( comphelper::getProcessComponentContext() );
83 // get xInputStream stream
84 uno::Reference
< io::XInputStream
> xIn
;
87 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess( ucb::SimpleFileAccess::create(xContext
) );
88 xIn
= xAccess
->openFileRead( rMainURL
);
90 catch (const uno::Exception
&)
92 SAL_WARN( "linguistic", "failed to get input stream" );
97 SvStreamPtr
pStream( utl::UcbStreamHelper::CreateStream( xIn
) );
99 // prepare ParserInputSource
100 xml::sax::InputSource aParserInput
;
101 aParserInput
.aInputStream
= xIn
;
104 uno::Reference
< xml::sax::XParser
> xParser
= xml::sax::Parser::create( xContext
);
106 //!! keep a reference until everything is done to
107 //!! ensure the proper lifetime of the object
108 uno::Reference
< xml::sax::XDocumentHandler
> xFilter(
109 static_cast<xml::sax::XExtendedDocumentHandler
*>(&rImport
), UNO_QUERY
);
111 // connect parser and filter
112 xParser
->setDocumentHandler( xFilter
);
114 // finally, parser the stream
117 xParser
->parseStream( aParserInput
); // implicitly calls ConvDicXMLImport::CreateContext
119 catch( xml::sax::SAXParseException
& )
122 catch( xml::sax::SAXException
& )
125 catch( io::IOException
& )
130 bool IsConvDic( const OUString
&rFileURL
, LanguageType
&nLang
, sal_Int16
&nConvType
)
134 if (rFileURL
.isEmpty())
137 // check if file extension matches CONV_DIC_EXT
139 sal_Int32 nPos
= rFileURL
.lastIndexOf( '.' );
141 aExt
= rFileURL
.copy( nPos
+ 1 ).toAsciiLowerCase();
142 if (aExt
!= CONV_DIC_EXT
)
145 // first argument being 0 should stop the file from being parsed
146 // up to the end (reading all entries) when the required
147 // data (language, conversion type) is found.
148 rtl::Reference
<ConvDicXMLImport
> pImport
= new ConvDicXMLImport( nullptr );
150 ReadThroughDic( rFileURL
, *pImport
); // will implicitly add the entries
151 bRes
= !LinguIsUnspecified( pImport
->GetLanguage()) &&
152 pImport
->GetConversionType() != -1;
153 DBG_ASSERT( bRes
, "conversion dictionary corrupted?" );
157 nLang
= pImport
->GetLanguage();
158 nConvType
= pImport
->GetConversionType();
166 const OUString
&rName
,
170 const OUString
&rMainURL
) :
171 aFlushListeners( GetLinguMutex() )
175 nConversionType
= nConvType
;
179 pFromRight
.reset( new ConvMap
);
180 if (nLang
== LANGUAGE_CHINESE_SIMPLIFIED
|| nLang
== LANGUAGE_CHINESE_TRADITIONAL
)
181 pConvPropType
.reset( new PropTypeMap
);
183 nMaxLeftCharCount
= nMaxRightCharCount
= 0;
184 bMaxCharCountIsValid
= true;
187 bIsModified
= bIsActive
= false;
189 if( !rMainURL
.isEmpty() )
191 bool bExists
= false;
192 IsReadOnly( rMainURL
, &bExists
);
194 if( !bExists
) // new empty dictionary
196 bNeedEntries
= false;
197 //! create physical representation of an **empty** dictionary
198 //! that could be found by the dictionary-list implementation
199 // (Note: empty dictionaries are not just empty files!)
205 bNeedEntries
= false;
217 DBG_ASSERT( !bIsModified
, "dictionary is modified. Really do 'Load'?" );
219 //!! prevent function from being called recursively via HasEntry, AddEntry
220 bNeedEntries
= false;
221 rtl::Reference
<ConvDicXMLImport
> pImport
= new ConvDicXMLImport( this );
222 ReadThroughDic( aMainURL
, *pImport
); // will implicitly add the entries
229 DBG_ASSERT( !bNeedEntries
, "saving while entries missing" );
230 if (aMainURL
.isEmpty() || bNeedEntries
)
232 DBG_ASSERT(!INetURLObject( aMainURL
).HasError(), "invalid URL");
234 uno::Reference
< uno::XComponentContext
> xContext( comphelper::getProcessComponentContext() );
236 // get XOutputStream stream
237 uno::Reference
< io::XStream
> xStream
;
240 uno::Reference
< ucb::XSimpleFileAccess3
> xAccess( ucb::SimpleFileAccess::create(xContext
) );
241 xStream
= xAccess
->openFileReadWrite( aMainURL
);
243 catch (const uno::Exception
&)
245 SAL_WARN( "linguistic", "failed to get input stream" );
250 SvStreamPtr
pStream( utl::UcbStreamHelper::CreateStream( xStream
) );
253 uno::Reference
< xml::sax::XWriter
> xSaxWriter
= xml::sax::Writer::create(xContext
);
257 // connect XML writer to output stream
258 xSaxWriter
->setOutputStream( xStream
->getOutputStream() );
260 // prepare arguments (prepend doc handler to given arguments)
261 rtl::Reference
<ConvDicXMLExport
> pExport
= new ConvDicXMLExport( *this, aMainURL
, xSaxWriter
);
262 bool bRet
= pExport
->Export(); // write entries to file
263 DBG_ASSERT( !pStream
->GetError(), "I/O error while writing to stream" );
267 DBG_ASSERT( !bIsModified
, "dictionary still modified after save. Save failed?" );
271 ConvMap::iterator
ConvDic::GetEntry( ConvMap
&rMap
, const OUString
&rFirstText
, const OUString
&rSecondText
)
273 pair
< ConvMap::iterator
, ConvMap::iterator
> aRange
=
274 rMap
.equal_range( rFirstText
);
275 ConvMap::iterator aPos
= rMap
.end();
276 for (ConvMap::iterator aIt
= aRange
.first
;
277 aIt
!= aRange
.second
&& aPos
== rMap
.end();
280 if ((*aIt
).second
== rSecondText
)
287 bool ConvDic::HasEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
291 ConvMap::iterator aIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
292 return aIt
!= aFromLeft
.end();
296 void ConvDic::AddEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
301 DBG_ASSERT(!HasEntry( rLeftText
, rRightText
), "entry already exists" );
302 aFromLeft
.emplace( rLeftText
, rRightText
);
304 pFromRight
->emplace( rRightText
, rLeftText
);
306 if (bMaxCharCountIsValid
)
308 if (rLeftText
.getLength() > nMaxLeftCharCount
)
309 nMaxLeftCharCount
= static_cast<sal_Int16
>(rLeftText
.getLength());
310 if (pFromRight
.get() && rRightText
.getLength() > nMaxRightCharCount
)
311 nMaxRightCharCount
= static_cast<sal_Int16
>(rRightText
.getLength());
318 void ConvDic::RemoveEntry( const OUString
&rLeftText
, const OUString
&rRightText
)
323 ConvMap::iterator aLeftIt
= GetEntry( aFromLeft
, rLeftText
, rRightText
);
324 DBG_ASSERT( aLeftIt
!= aFromLeft
.end(), "left map entry missing" );
325 aFromLeft
.erase( aLeftIt
);
329 ConvMap::iterator aRightIt
= GetEntry( *pFromRight
, rRightText
, rLeftText
);
330 DBG_ASSERT( aRightIt
!= pFromRight
->end(), "right map entry missing" );
331 pFromRight
->erase( aRightIt
);
335 bMaxCharCountIsValid
= false;
339 OUString SAL_CALL
ConvDic::getName( )
341 MutexGuard
aGuard( GetLinguMutex() );
346 Locale SAL_CALL
ConvDic::getLocale( )
348 MutexGuard
aGuard( GetLinguMutex() );
349 return LanguageTag::convertToLocale( nLanguage
);
353 sal_Int16 SAL_CALL
ConvDic::getConversionType( )
355 MutexGuard
aGuard( GetLinguMutex() );
356 return nConversionType
;
360 void SAL_CALL
ConvDic::setActive( sal_Bool bActivate
)
362 MutexGuard
aGuard( GetLinguMutex() );
363 bIsActive
= bActivate
;
367 sal_Bool SAL_CALL
ConvDic::isActive( )
369 MutexGuard
aGuard( GetLinguMutex() );
374 void SAL_CALL
ConvDic::clear( )
376 MutexGuard
aGuard( GetLinguMutex() );
380 bNeedEntries
= false;
382 nMaxLeftCharCount
= 0;
383 nMaxRightCharCount
= 0;
384 bMaxCharCountIsValid
= true;
388 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getConversions(
389 const OUString
& aText
,
392 ConversionDirection eDirection
,
393 sal_Int32
/*nTextConversionOptions*/ )
395 MutexGuard
aGuard( GetLinguMutex() );
397 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
398 return uno::Sequence
< OUString
>();
403 OUString
aLookUpText( aText
.copy(nStartPos
, nLength
) );
404 ConvMap
&rConvMap
= eDirection
== ConversionDirection_FROM_LEFT
?
405 aFromLeft
: *pFromRight
;
406 pair
< ConvMap::iterator
, ConvMap::iterator
> aRange
=
407 rConvMap
.equal_range( aLookUpText
);
409 std::vector
<OUString
> aRes
;
410 auto nCount
= static_cast<size_t>(std::distance(aRange
.first
, aRange
.second
));
411 aRes
.reserve(nCount
);
413 std::transform(aRange
.first
, aRange
.second
, std::back_inserter(aRes
),
414 [](ConvMap::const_reference rEntry
) { return rEntry
.second
; });
416 return comphelper::containerToSequence(aRes
);
420 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getConversionEntries(
421 ConversionDirection eDirection
)
423 MutexGuard
aGuard( GetLinguMutex() );
425 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
426 return uno::Sequence
< OUString
>();
431 ConvMap
&rConvMap
= eDirection
== ConversionDirection_FROM_LEFT
?
432 aFromLeft
: *pFromRight
;
433 std::vector
<OUString
> aRes
;
434 aRes
.reserve(rConvMap
.size());
435 for (auto const& elem
: rConvMap
)
437 OUString
aCurEntry( elem
.first
);
438 // skip duplicate entries ( duplicate = duplicate entries
439 // respective to the evaluated side (FROM_LEFT or FROM_RIGHT).
440 // Thus if FROM_LEFT is evaluated for pairs (A,B) and (A,C)
441 // only one entry for A will be returned in the result)
442 if (std::find(aRes
.begin(), aRes
.end(), aCurEntry
) == aRes
.end())
443 aRes
.push_back(aCurEntry
);
446 return comphelper::containerToSequence(aRes
);
450 void SAL_CALL
ConvDic::addEntry(
451 const OUString
& aLeftText
,
452 const OUString
& aRightText
)
454 MutexGuard
aGuard( GetLinguMutex() );
457 if (HasEntry( aLeftText
, aRightText
))
458 throw container::ElementExistException();
459 AddEntry( aLeftText
, aRightText
);
463 void SAL_CALL
ConvDic::removeEntry(
464 const OUString
& aLeftText
,
465 const OUString
& aRightText
)
467 MutexGuard
aGuard( GetLinguMutex() );
470 if (!HasEntry( aLeftText
, aRightText
))
471 throw container::NoSuchElementException();
472 RemoveEntry( aLeftText
, aRightText
);
476 sal_Int16 SAL_CALL
ConvDic::getMaxCharCount( ConversionDirection eDirection
)
478 MutexGuard
aGuard( GetLinguMutex() );
480 if (!pFromRight
&& eDirection
== ConversionDirection_FROM_RIGHT
)
482 DBG_ASSERT( nMaxRightCharCount
== 0, "max right char count should be 0" );
489 if (!bMaxCharCountIsValid
)
491 nMaxLeftCharCount
= 0;
492 for (auto const& elem
: aFromLeft
)
494 sal_Int16 nTmp
= static_cast<sal_Int16
>(elem
.first
.getLength());
495 if (nTmp
> nMaxLeftCharCount
)
496 nMaxLeftCharCount
= nTmp
;
499 nMaxRightCharCount
= 0;
502 for (auto const& elem
: *pFromRight
)
504 sal_Int16 nTmp
= static_cast<sal_Int16
>(elem
.first
.getLength());
505 if (nTmp
> nMaxRightCharCount
)
506 nMaxRightCharCount
= nTmp
;
510 bMaxCharCountIsValid
= true;
512 sal_Int16 nRes
= eDirection
== ConversionDirection_FROM_LEFT
?
513 nMaxLeftCharCount
: nMaxRightCharCount
;
514 DBG_ASSERT( nRes
>= 0, "invalid MaxCharCount" );
519 void SAL_CALL
ConvDic::setPropertyType(
520 const OUString
& rLeftText
,
521 const OUString
& rRightText
,
522 sal_Int16 nPropertyType
)
524 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
526 throw container::NoSuchElementException();
528 // currently we assume that entries with the same left text have the
529 // same PropertyType even if the right text is different...
531 pConvPropType
->emplace( rLeftText
, nPropertyType
);
536 sal_Int16 SAL_CALL
ConvDic::getPropertyType(
537 const OUString
& rLeftText
,
538 const OUString
& rRightText
)
540 bool bHasElement
= HasEntry( rLeftText
, rRightText
);
542 throw container::NoSuchElementException();
544 sal_Int16 nRes
= ConversionPropertyType::NOT_DEFINED
;
547 // still assuming that entries with same left text have same PropertyType
548 // even if they have different right text...
549 PropTypeMap::iterator aIt
= pConvPropType
->find( rLeftText
);
550 if (aIt
!= pConvPropType
->end())
551 nRes
= (*aIt
).second
;
557 void SAL_CALL
ConvDic::flush( )
559 MutexGuard
aGuard( GetLinguMutex() );
568 aEvtObj
.Source
= uno::Reference
< XFlushable
>( this );
569 aFlushListeners
.notifyEach( &util::XFlushListener::flushed
, aEvtObj
);
573 void SAL_CALL
ConvDic::addFlushListener(
574 const uno::Reference
< util::XFlushListener
>& rxListener
)
576 MutexGuard
aGuard( GetLinguMutex() );
578 aFlushListeners
.addInterface( rxListener
);
582 void SAL_CALL
ConvDic::removeFlushListener(
583 const uno::Reference
< util::XFlushListener
>& rxListener
)
585 MutexGuard
aGuard( GetLinguMutex() );
587 aFlushListeners
.removeInterface( rxListener
);
591 OUString SAL_CALL
ConvDic::getImplementationName( )
593 return "com.sun.star.lingu2.ConvDic";
596 sal_Bool SAL_CALL
ConvDic::supportsService( const OUString
& rServiceName
)
598 return cppu::supportsService(this, rServiceName
);
601 uno::Sequence
< OUString
> SAL_CALL
ConvDic::getSupportedServiceNames( )
603 return { SN_CONV_DICTIONARY
};
607 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */