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 <string_view>
23 #include <com/sun/star/container/XNamed.hpp>
24 #include <comphelper/servicehelper.hxx>
26 #include <unotools/transliterationwrapper.hxx>
28 #include <vcl/errinf.hxx>
29 #include <osl/diagnose.h>
30 #include <rtl/character.hxx>
31 #include <svl/urihelper.hxx>
32 #include <svl/fstathelper.hxx>
33 #include <unotools/pathoptions.hxx>
34 #include <unotools/tempfile.hxx>
35 #include <o3tl/string_view.hxx>
36 #include <swtypes.hxx>
37 #include <glosdoc.hxx>
38 #include <shellio.hxx>
39 #include <swunohelper.hxx>
41 #include <unoatxt.hxx>
46 using namespace ::com::sun::star
;
47 using namespace ::com::sun::star::uno
;
52 OUString
lcl_FullPathName(std::u16string_view sPath
, std::u16string_view sName
)
54 return OUString::Concat(sPath
) + "/" + sName
+ SwGlossaries::GetExtension();
57 OUString
lcl_CheckFileName( const OUString
& rNewFilePath
,
58 std::u16string_view aNewGroupName
)
60 const sal_Int32 nLen
= aNewGroupName
.size();
61 OUStringBuffer
aBuf(nLen
);
62 //group name should contain only A-Z and a-z and spaces
63 for( sal_Int32 i
=0; i
< nLen
; ++i
)
65 const sal_Unicode cChar
= aNewGroupName
[i
];
66 if (rtl::isAsciiAlphanumeric(cChar
) ||
67 cChar
== '_' || cChar
== 0x20)
73 const OUString sRet
= aBuf
.makeStringAndClear().trim();
74 if ( !sRet
.isEmpty() )
76 if (!FStatHelper::IsDocument( lcl_FullPathName(rNewFilePath
, sRet
) ))
80 //generate generic name
81 utl::TempFileNamed
aTemp(u
"group", true, SwGlossaries::GetExtension(), &rNewFilePath
);
82 aTemp
.EnableKillingFile();
84 INetURLObject
aTempURL( aTemp
.GetURL() );
85 return aTempURL
.GetBase();
90 // supplies the default group's name
91 OUString
SwGlossaries::GetDefName()
97 // supplies the number of text block groups
98 size_t SwGlossaries::GetGroupCnt()
100 return GetNameList().size();
103 // supplies the group's name
104 bool SwGlossaries::FindGroupName(OUString
& rGroup
)
106 // if the group name doesn't contain a path, a suitable group entry
107 // can the searched for here;
108 const size_t nCount
= GetGroupCnt();
109 for(size_t i
= 0; i
< nCount
; ++i
)
111 const OUString
sTemp(GetGroupName(i
));
112 if (rGroup
== o3tl::getToken(sTemp
, 0, GLOS_DELIM
))
118 // you can search two times because for more directories the case sensitive
119 // name could occur multiple times
120 const ::utl::TransliterationWrapper
& rSCmp
= GetAppCmpStrIgnore();
121 for(size_t i
= 0; i
< nCount
; ++i
)
123 const OUString
sTemp( GetGroupName( i
));
124 sal_uInt16 nPath
= o3tl::toUInt32(o3tl::getToken(sTemp
, 1, GLOS_DELIM
));
126 if (!SWUnoHelper::UCB_IsCaseSensitiveFileName( m_PathArr
[nPath
] )
127 && rSCmp
.isEqual( rGroup
, sTemp
.getToken( 0, GLOS_DELIM
) ) )
136 OUString
const & SwGlossaries::GetGroupName(size_t nGroupId
)
138 OSL_ENSURE(nGroupId
< m_GlosArr
.size(),
139 "SwGlossaries::GetGroupName: index out of bounds");
140 return m_GlosArr
[nGroupId
];
143 OUString
SwGlossaries::GetGroupTitle( const OUString
& rGroupName
)
146 OUString
sGroup(rGroupName
);
147 if (sGroup
.indexOf(GLOS_DELIM
)<0)
148 FindGroupName(sGroup
);
149 std::unique_ptr
<SwTextBlocks
> pGroup
= GetGroupDoc(sGroup
);
152 sRet
= pGroup
->GetName();
157 // supplies the group rName's text block document
158 std::unique_ptr
<SwTextBlocks
> SwGlossaries::GetGroupDoc(const OUString
&rName
,
161 // insert to the list of text blocks if applicable
162 if(bCreate
&& !m_GlosArr
.empty())
164 if (std::none_of(m_GlosArr
.begin(), m_GlosArr
.end(),
165 [&rName
](const OUString
& rEntry
) { return rEntry
== rName
; }))
166 { // block not in the list
167 m_GlosArr
.push_back(rName
);
170 return GetGlosDoc( rName
, bCreate
);
173 // Creates a new document with the group name. temporarily also created as file
174 // so that groups remain there later (without access).
175 bool SwGlossaries::NewGroupDoc(OUString
& rGroupName
, const OUString
& rTitle
)
177 const std::u16string_view
sNewPath(o3tl::getToken(rGroupName
, 1, GLOS_DELIM
));
178 sal_uInt16 nNewPath
= o3tl::narrowing
<sal_uInt16
>(o3tl::toInt32(sNewPath
));
179 if (static_cast<size_t>(nNewPath
) >= m_PathArr
.size())
181 const OUString
sNewFilePath(m_PathArr
[nNewPath
]);
182 const OUString sNewGroup
= lcl_CheckFileName(sNewFilePath
, o3tl::getToken(rGroupName
, 0, GLOS_DELIM
))
183 + OUStringChar(GLOS_DELIM
) + sNewPath
;
184 std::unique_ptr
<SwTextBlocks
> pBlock
= GetGlosDoc( sNewGroup
);
187 GetNameList().push_back(sNewGroup
);
188 pBlock
->SetName(rTitle
);
189 rGroupName
= sNewGroup
;
195 bool SwGlossaries::RenameGroupDoc(
196 const OUString
& rOldGroup
, OUString
& rNewGroup
, const OUString
& rNewTitle
)
198 sal_uInt16 nOldPath
= o3tl::narrowing
<sal_uInt16
>(o3tl::toInt32(o3tl::getToken(rOldGroup
, 1, GLOS_DELIM
)));
199 if (static_cast<size_t>(nOldPath
) >= m_PathArr
.size())
202 const OUString sOldFileURL
=
203 lcl_FullPathName(m_PathArr
[nOldPath
], o3tl::getToken(rOldGroup
, 0, GLOS_DELIM
));
205 if (!FStatHelper::IsDocument( sOldFileURL
))
207 OSL_FAIL("group doesn't exist!");
211 sal_uInt16 nNewPath
= o3tl::narrowing
<sal_uInt16
>(o3tl::toInt32(o3tl::getToken(rNewGroup
, 1, GLOS_DELIM
)));
212 if (static_cast<size_t>(nNewPath
) >= m_PathArr
.size())
215 const OUString sNewFileName
= lcl_CheckFileName(m_PathArr
[nNewPath
],
216 o3tl::getToken(rNewGroup
, 0, GLOS_DELIM
));
217 const OUString sNewFileURL
= lcl_FullPathName(m_PathArr
[nNewPath
], sNewFileName
);
219 if (FStatHelper::IsDocument( sNewFileURL
))
221 OSL_FAIL("group already exists!");
225 if (!SWUnoHelper::UCB_MoveFile(sOldFileURL
, sNewFileURL
))
228 RemoveFileFromList( rOldGroup
);
230 rNewGroup
= sNewFileName
+ OUStringChar(GLOS_DELIM
) + OUString::number(nNewPath
);
231 if (m_GlosArr
.empty())
237 m_GlosArr
.push_back(rNewGroup
);
240 SwTextBlocks
aNewBlock( sNewFileURL
);
241 aNewBlock
.SetName(rNewTitle
);
246 // Deletes a text block group
247 bool SwGlossaries::DelGroupDoc(std::u16string_view rName
)
249 sal_uInt16 nPath
= o3tl::narrowing
<sal_uInt16
>(o3tl::toInt32(o3tl::getToken(rName
, 1, GLOS_DELIM
)));
250 if (static_cast<size_t>(nPath
) >= m_PathArr
.size())
252 const std::u16string_view
sBaseName(o3tl::getToken(rName
, 0, GLOS_DELIM
));
253 const OUString sFileURL
= lcl_FullPathName(m_PathArr
[nPath
], sBaseName
);
254 const OUString aName
= sBaseName
+ OUStringChar(GLOS_DELIM
) + OUString::number(nPath
);
255 // Even if the file doesn't exist it has to be deleted from
256 // the list of text block regions
257 // no && because of CFfront
258 bool bRemoved
= SWUnoHelper::UCB_DeleteFile( sFileURL
);
259 OSL_ENSURE(bRemoved
, "file has not been removed");
260 RemoveFileFromList( aName
);
264 SwGlossaries::~SwGlossaries()
266 InvalidateUNOOjects();
269 // read a block document
270 std::unique_ptr
<SwTextBlocks
> SwGlossaries::GetGlosDoc( const OUString
&rName
, bool bCreate
) const
272 sal_uInt16 nPath
= o3tl::narrowing
<sal_uInt16
>(o3tl::toInt32(o3tl::getToken(rName
, 1, GLOS_DELIM
)));
273 std::unique_ptr
<SwTextBlocks
> pTmp
;
274 if (static_cast<size_t>(nPath
) < m_PathArr
.size())
276 const OUString sFileURL
=
277 lcl_FullPathName(m_PathArr
[nPath
], o3tl::getToken(rName
, 0, GLOS_DELIM
));
281 bExist
= FStatHelper::IsDocument( sFileURL
);
283 if (bCreate
|| bExist
)
285 pTmp
.reset(new SwTextBlocks( sFileURL
));
287 if( pTmp
->GetError() )
289 ErrorHandler::HandleError( pTmp
->GetError() );
290 bOk
= ! pTmp
->GetError().IsError();
293 if( bOk
&& pTmp
->GetName().isEmpty() )
294 pTmp
->SetName( rName
);
301 // access to the list of names; read in if applicable
302 std::vector
<OUString
> & SwGlossaries::GetNameList()
304 if (m_GlosArr
.empty())
306 const OUString
sExt( SwGlossaries::GetExtension() );
307 for (size_t i
= 0; i
< m_PathArr
.size(); ++i
)
309 std::vector
<OUString
> aFiles
;
311 SWUnoHelper::UCB_GetFileListOfFolder(m_PathArr
[i
], aFiles
, &sExt
);
312 for (const OUString
& aTitle
: aFiles
)
314 const OUString
sName( aTitle
.subView( 0, aTitle
.getLength() - sExt
.getLength() )
315 + OUStringChar(GLOS_DELIM
) + OUString::number( static_cast<sal_Int16
>(i
) ));
316 m_GlosArr
.push_back(sName
);
319 if (m_GlosArr
.empty())
321 // the standard block is inside of the path's first part
322 m_GlosArr
.emplace_back(SwGlossaries::GetDefName() + OUStringChar(GLOS_DELIM
) + "0" );
328 SwGlossaries::SwGlossaries()
330 UpdateGlosPath(true);
333 // set new path and recreate internal array
334 static OUString
lcl_makePath(const std::vector
<OUString
>& rPaths
)
336 std::vector
<OUString
>::const_iterator
aIt(rPaths
.begin());
337 const std::vector
<OUString
>::const_iterator
aEnd(rPaths
.end());
338 OUStringBuffer
aPath(*aIt
);
339 for (++aIt
; aIt
!= aEnd
; ++aIt
)
341 aPath
.append(SVT_SEARCHPATH_DELIMITER
);
342 const INetURLObject
aTemp(*aIt
);
343 aPath
.append(aTemp
.GetFull());
345 return aPath
.makeStringAndClear();
348 void SwGlossaries::UpdateGlosPath(bool bFull
)
350 SvtPathOptions aPathOpt
;
351 const OUString
& aNewPath( aPathOpt
.GetAutoTextPath() );
352 bool bPathChanged
= m_aPath
!= aNewPath
;
353 if (!(bFull
|| bPathChanged
))
360 std::vector
<OUString
> aDirArr
;
361 std::vector
<OUString
> aInvalidPaths
;
362 if (!m_aPath
.isEmpty())
364 sal_Int32 nIndex
= 0;
367 const OUString sPth
= URIHelper::SmartRel2Abs(
369 m_aPath
.getToken(0, SVT_SEARCHPATH_DELIMITER
, nIndex
),
370 URIHelper::GetMaybeFileHdl());
371 if (!aDirArr
.empty() &&
372 std::find(aDirArr
.begin(), aDirArr
.end(), sPth
) != aDirArr
.end())
376 aDirArr
.push_back(sPth
);
377 if( !FStatHelper::IsFolder( sPth
) )
378 aInvalidPaths
.push_back(sPth
);
380 m_PathArr
.push_back(sPth
);
385 if (m_aPath
.isEmpty() || !aInvalidPaths
.empty())
387 std::sort(aInvalidPaths
.begin(), aInvalidPaths
.end());
388 aInvalidPaths
.erase(std::unique(aInvalidPaths
.begin(), aInvalidPaths
.end()), aInvalidPaths
.end());
389 if (bPathChanged
|| (m_aInvalidPaths
!= aInvalidPaths
))
391 m_aInvalidPaths
= aInvalidPaths
;
392 // wrong path, that means AutoText directory doesn't exist
394 ErrorHandler::HandleError( *new StringErrorInfo(
395 ERR_AUTOPATH_ERROR
, lcl_makePath(m_aInvalidPaths
),
396 DialogMask::ButtonsOk
| DialogMask::MessageError
) );
405 if (!m_GlosArr
.empty())
412 void SwGlossaries::ShowError()
414 ErrCode nPathError
= *new StringErrorInfo(ERR_AUTOPATH_ERROR
,
415 lcl_makePath(m_aInvalidPaths
), DialogMask::ButtonsOk
);
416 ErrorHandler::HandleError( nPathError
);
419 OUString
SwGlossaries::GetExtension()
424 void SwGlossaries::RemoveFileFromList( const OUString
& rGroup
)
426 if (m_GlosArr
.empty())
429 auto it
= std::find(m_GlosArr
.begin(), m_GlosArr
.end(), rGroup
);
430 if (it
== m_GlosArr
.end())
434 // tell the UNO AutoTextGroup object that it's not valid anymore
435 for ( UnoAutoTextGroups::iterator aLoop
= m_aGlossaryGroups
.begin();
436 aLoop
!= m_aGlossaryGroups
.end();
439 rtl::Reference
< SwXAutoTextGroup
> xNamed( aLoop
->get() );
442 aLoop
= m_aGlossaryGroups
.erase(aLoop
);
444 else if ( xNamed
->getName() == rGroup
)
446 xNamed
->Invalidate();
447 // note that this static_cast works because we know that the array only
448 // contains SwXAutoTextGroup implementation
449 m_aGlossaryGroups
.erase( aLoop
);
457 // tell all our UNO AutoTextEntry objects that they're not valid anymore
458 for ( UnoAutoTextEntries::iterator aLoop
= m_aGlossaryEntries
.begin();
459 aLoop
!= m_aGlossaryEntries
.end();
462 rtl::Reference
<SwXAutoTextEntry
> pEntry
= aLoop
->get();
463 if ( pEntry
&& ( pEntry
->GetGroupName() == rGroup
) )
465 pEntry
->Invalidate();
466 aLoop
= m_aGlossaryEntries
.erase( aLoop
);
476 OUString
SwGlossaries::GetCompleteGroupName( std::u16string_view rGroupName
)
478 const size_t nCount
= GetGroupCnt();
479 // when the group name was created internally the path is here as well
480 sal_Int32 nIndex
= 0;
481 const std::u16string_view
sGroupName(o3tl::getToken(rGroupName
, 0, GLOS_DELIM
, nIndex
));
482 const bool bPathLen
= !o3tl::getToken(rGroupName
, 0, GLOS_DELIM
, nIndex
).empty();
483 for ( size_t i
= 0; i
< nCount
; i
++ )
485 const OUString sGrpName
= GetGroupName(i
);
488 if (rGroupName
== sGrpName
)
493 if (sGroupName
== o3tl::getToken(sGrpName
, 0, GLOS_DELIM
))
500 void SwGlossaries::InvalidateUNOOjects()
502 // invalidate all the AutoTextGroup-objects
503 for (const auto& rGroup
: m_aGlossaryGroups
)
505 rtl::Reference
< SwXAutoTextGroup
> xGroup( rGroup
.get() );
507 xGroup
->Invalidate();
509 UnoAutoTextGroups().swap(m_aGlossaryGroups
);
511 // invalidate all the AutoTextEntry-objects
512 for (const auto& rEntry
: m_aGlossaryEntries
)
514 rtl::Reference
<SwXAutoTextEntry
> pEntry
= rEntry
.get();
516 pEntry
->Invalidate();
518 UnoAutoTextEntries().swap(m_aGlossaryEntries
);
521 Reference
< text::XAutoTextGroup
> SwGlossaries::GetAutoTextGroup( std::u16string_view _rGroupName
)
523 bool _bCreate
= true;
524 // first, find the name with path-extension
525 const OUString sCompleteGroupName
= GetCompleteGroupName( _rGroupName
);
527 rtl::Reference
< SwXAutoTextGroup
> xGroup
;
529 // look up the group in the cache
530 UnoAutoTextGroups::iterator aSearch
= m_aGlossaryGroups
.begin();
531 for ( ; aSearch
!= m_aGlossaryGroups
.end(); )
533 rtl::Reference
<SwXAutoTextGroup
> pSwGroup
= aSearch
->get();
536 // the object is dead in the meantime -> remove from cache
537 aSearch
= m_aGlossaryGroups
.erase( aSearch
);
541 if ( _rGroupName
== pSwGroup
->getName() )
542 { // the group is already cached
543 if ( !sCompleteGroupName
.isEmpty() )
544 { // the group still exists -> return it
550 // this group does not exist (anymore) -> release the cached UNO object for it
551 aSearch
= m_aGlossaryGroups
.erase( aSearch
);
552 // so it won't be created below
561 if ( !xGroup
.is() && _bCreate
)
563 xGroup
= new SwXAutoTextGroup( sCompleteGroupName
, this );
565 m_aGlossaryGroups
.emplace_back( xGroup
);
571 Reference
< text::XAutoTextEntry
> SwGlossaries::GetAutoTextEntry(
572 const OUString
& rCompleteGroupName
,
573 const OUString
& rGroupName
,
574 const OUString
& rEntryName
)
576 //standard must be created
577 bool bCreate
= ( rCompleteGroupName
== GetDefName() );
578 std::unique_ptr
< SwTextBlocks
> pGlosGroup( GetGroupDoc( rCompleteGroupName
, bCreate
) );
580 if (!pGlosGroup
|| pGlosGroup
->GetError())
581 throw lang::WrappedTargetException();
583 sal_uInt16 nIdx
= pGlosGroup
->GetIndex( rEntryName
);
584 if ( USHRT_MAX
== nIdx
)
585 throw container::NoSuchElementException();
587 rtl::Reference
< SwXAutoTextEntry
> xReturn
;
589 UnoAutoTextEntries::iterator
aSearch( m_aGlossaryEntries
.begin() );
590 for ( ; aSearch
!= m_aGlossaryEntries
.end(); )
592 rtl::Reference
< SwXAutoTextEntry
> pEntry( aSearch
->get() );
596 // the object is dead in the meantime -> remove from cache
597 aSearch
= m_aGlossaryEntries
.erase( aSearch
);
602 && pEntry
->GetGroupName() == rGroupName
603 && pEntry
->GetEntryName() == rEntryName
615 xReturn
= new SwXAutoTextEntry( this, rGroupName
, rEntryName
);
617 m_aGlossaryEntries
.emplace_back( xReturn
);
623 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */