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 <txtlists.hxx>
22 #include <comphelper/random.hxx>
24 #include <o3tl/safeint.hxx>
25 #include <tools/datetime.hxx>
26 #include <tools/long.hxx>
27 #include <sal/log.hxx>
29 #include <xmloff/txtimp.hxx>
30 #include <xmloff/xmlimp.hxx>
31 #include <xmloff/xmlnumi.hxx>
33 #include <com/sun/star/style/XStyle.hpp>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/container/XNameContainer.hpp>
36 #include "XMLTextListItemContext.hxx"
37 #include "XMLTextListBlockContext.hxx"
38 #include "txtparai.hxx"
41 using namespace ::com::sun::star
;
44 XMLTextListsHelper::XMLTextListsHelper()
45 // Inconsistent behavior regarding lists (#i92811#)
49 void XMLTextListsHelper::PushListContext(
50 XMLTextListBlockContext
*i_pListBlock
)
52 mListStack
.emplace(i_pListBlock
,
53 static_cast<XMLTextListItemContext
*>(nullptr),
54 static_cast<XMLNumberedParaContext
*>(nullptr));
57 void XMLTextListsHelper::PushListContext(
58 XMLNumberedParaContext
*i_pNumberedParagraph
)
61 static_cast<XMLTextListBlockContext
*>(nullptr),
62 static_cast<XMLTextListItemContext
*>(nullptr), i_pNumberedParagraph
);
65 void XMLTextListsHelper::PopListContext()
67 assert(mListStack
.size());
68 if ( !mListStack
.empty())
72 void XMLTextListsHelper::ListContextTop(
73 XMLTextListBlockContext
*& o_pListBlockContext
,
74 XMLTextListItemContext
*& o_pListItemContext
,
75 XMLNumberedParaContext
*& o_pNumberedParagraphContext
)
77 if ( !mListStack
.empty() ) {
79 static_cast<XMLTextListBlockContext
*>(std::get
<0>(mListStack
.top()).get());
81 static_cast<XMLTextListItemContext
*>(std::get
<1>(mListStack
.top()).get());
82 o_pNumberedParagraphContext
=
83 static_cast<XMLNumberedParaContext
*>(std::get
<2>(mListStack
.top()).get());
87 void XMLTextListsHelper::SetListItem( XMLTextListItemContext
*i_pListItem
)
89 // may be cleared by ListBlockContext for upper list...
91 assert(mListStack
.size());
92 assert(std::get
<0>(mListStack
.top()).is() &&
93 "internal error: SetListItem: mListStack has no ListBlock");
94 assert(!std::get
<1>(mListStack
.top()).is() &&
95 "error: SetListItem: list item already exists");
97 if ( !mListStack
.empty() ) {
98 std::get
<1>(mListStack
.top()) = i_pListItem
;
102 // Handling for parameter <sListStyleDefaultListId> (#i92811#)
103 void XMLTextListsHelper::KeepListAsProcessed( const OUString
& sListId
,
104 const OUString
& sListStyleName
,
105 const OUString
& sContinueListId
,
106 const OUString
& sListStyleDefaultListId
)
108 if ( IsListProcessed( sListId
) )
111 "<XMLTextListsHelper::KeepListAsProcessed(..)> - list id already added" );
115 if ( !mpProcessedLists
)
117 mpProcessedLists
= std::make_unique
<tMapForLists
>();
120 (*mpProcessedLists
)[ sListId
] = std::make_pair(sListStyleName
, sContinueListId
);
122 msLastProcessedListId
= sListId
;
123 msListStyleOfLastProcessedList
= sListStyleName
;
125 // Remember what is the last list id of this list style.
126 if (!mpStyleNameLastListIds
)
128 mpStyleNameLastListIds
= std::make_unique
<std::map
<OUString
, OUString
>>();
130 (*mpStyleNameLastListIds
)[sListStyleName
] = sListId
;
132 // Inconsistent behavior regarding lists (#i92811#)
133 if ( sListStyleDefaultListId
.isEmpty())
136 if ( !mpMapListIdToListStyleDefaultListId
)
138 mpMapListIdToListStyleDefaultListId
= std::make_unique
<tMapForLists
>();
141 if ( !mpMapListIdToListStyleDefaultListId
->contains( sListStyleName
) )
143 (*mpMapListIdToListStyleDefaultListId
)[ sListStyleName
] =
144 ::std::pair
<OUString
, OUString
>(sListId
, sListStyleDefaultListId
);
148 bool XMLTextListsHelper::IsListProcessed( const OUString
& sListId
) const
150 if ( !mpProcessedLists
)
155 return mpProcessedLists
->contains( sListId
);
158 const OUString
& XMLTextListsHelper::GetListStyleOfProcessedList(
159 const OUString
& sListId
) const
161 if ( mpProcessedLists
)
163 tMapForLists::const_iterator aIter
= mpProcessedLists
->find( sListId
);
164 if ( aIter
!= mpProcessedLists
->end() )
166 return (*aIter
).second
.first
;
170 return EMPTY_OUSTRING
;
173 const OUString
& XMLTextListsHelper::GetContinueListIdOfProcessedList(
174 const OUString
& sListId
) const
176 if ( mpProcessedLists
)
178 tMapForLists::const_iterator aIter
= mpProcessedLists
->find( sListId
);
179 if ( aIter
!= mpProcessedLists
->end() )
181 return (*aIter
).second
.second
;
185 return EMPTY_OUSTRING
;
189 OUString
XMLTextListsHelper::GenerateNewListId() const
191 static bool bHack
= (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
192 OUString
sTmpStr( u
"list"_ustr
);
196 static sal_Int64 nIdCounter
= SAL_CONST_INT64(5000000000);
197 sTmpStr
+= OUString::number(nIdCounter
++);
201 // Value of xml:id in element <text:list> has to be a valid ID type (#i92478#)
202 DateTime
aDateTime( DateTime::SYSTEM
);
203 sal_Int64 n
= aDateTime
.GetTime();
204 n
+= aDateTime
.GetDateUnsigned();
205 n
+= comphelper::rng::uniform_int_distribution(0, std::numeric_limits
<int>::max());
206 // Value of xml:id in element <text:list> has to be a valid ID type (#i92478#)
207 sTmpStr
+= OUString::number( n
);
210 OUString
sNewListId( sTmpStr
);
211 if ( mpProcessedLists
)
213 tools::Long nHitCount
= 0;
214 while ( mpProcessedLists
->contains( sNewListId
) )
217 sNewListId
= sTmpStr
+ OUString::number( nHitCount
);
224 // Provide list id for a certain list block for import (#i92811#)
225 OUString
XMLTextListsHelper::GetListIdForListBlock( XMLTextListBlockContext
const & rListBlock
)
227 OUString
sListBlockListId( rListBlock
.GetContinueListId() );
228 if ( sListBlockListId
.isEmpty() )
230 sListBlockListId
= rListBlock
.GetListId();
233 if ( mpMapListIdToListStyleDefaultListId
)
235 if ( !sListBlockListId
.isEmpty() )
237 const OUString sListStyleName
=
238 GetListStyleOfProcessedList( sListBlockListId
);
240 tMapForLists::const_iterator aIter
=
241 mpMapListIdToListStyleDefaultListId
->find( sListStyleName
);
242 if ( aIter
!= mpMapListIdToListStyleDefaultListId
->end() )
244 if ( (*aIter
).second
.first
== sListBlockListId
)
246 sListBlockListId
= (*aIter
).second
.second
;
252 return sListBlockListId
;
255 void XMLTextListsHelper::StoreLastContinuingList( const OUString
& sListId
,
256 const OUString
& sContinuingListId
)
258 if ( !mpContinuingLists
)
260 mpContinuingLists
= std::make_unique
<tMapForContinuingLists
>();
263 (*mpContinuingLists
)[ sListId
] = sContinuingListId
;
266 OUString
XMLTextListsHelper::GetLastContinuingListId(
267 const OUString
& sListId
) const
269 if ( mpContinuingLists
)
271 tMapForContinuingLists::const_iterator aIter
=
272 mpContinuingLists
->find( sListId
);
273 if ( aIter
!= mpContinuingLists
->end() )
275 return (*aIter
).second
;
282 void XMLTextListsHelper::PushListOnStack( const OUString
& sListId
,
283 const OUString
& sListStyleName
)
287 mpListStack
= std::make_unique
<tStackForLists
>();
289 ::std::pair
< OUString
, OUString
>
290 aListData( sListId
, sListStyleName
);
291 mpListStack
->push_back( aListData
);
293 void XMLTextListsHelper::PopListFromStack()
296 !mpListStack
->empty() )
298 mpListStack
->pop_back();
302 bool XMLTextListsHelper::EqualsToTopListStyleOnStack( std::u16string_view sListId
) const
304 return mpListStack
&& sListId
== mpListStack
->back().second
;
308 XMLTextListsHelper::GetNumberedParagraphListId(
309 const sal_uInt16 i_Level
,
310 std::u16string_view i_StyleName
)
312 if (i_StyleName
.empty()) {
313 SAL_INFO("xmloff.text", "invalid numbered-paragraph: no style-name");
315 if (!i_StyleName
.empty()
316 && (i_Level
< mLastNumberedParagraphs
.size())
317 && (mLastNumberedParagraphs
[i_Level
].first
== i_StyleName
) )
319 assert(!mLastNumberedParagraphs
[i_Level
].second
.isEmpty() &&
320 "internal error: numbered-paragraph style-name but no list-id?");
321 return mLastNumberedParagraphs
[i_Level
].second
;
323 return GenerateNewListId();
328 ClampLevel(uno::Reference
<container::XIndexReplace
> const& i_xNumRules
,
329 sal_Int16
& io_rLevel
)
331 assert(i_xNumRules
.is());
332 if (i_xNumRules
.is()) {
333 const sal_Int32
nLevelCount( i_xNumRules
->getCount() );
334 if ( io_rLevel
>= nLevelCount
) {
335 io_rLevel
= sal::static_int_cast
< sal_Int16
>(nLevelCount
-1);
340 uno::Reference
<container::XIndexReplace
>
341 XMLTextListsHelper::EnsureNumberedParagraph(
342 SvXMLImport
& i_rImport
,
343 const OUString
& i_ListId
,
344 sal_Int16
& io_rLevel
, const OUString
& i_StyleName
)
346 assert(!i_ListId
.isEmpty());
347 assert(io_rLevel
>= 0);
348 NumParaList_t
& rNPList( mNPLists
[i_ListId
] );
349 const OUString none
; // default
350 if ( rNPList
.empty() ) {
351 // create default list style for top level
353 rNPList
.emplace_back(none
,
354 MakeNumRule(i_rImport
, nullptr, none
, none
, lev
) );
356 // create num rule first because this might clamp the level...
357 uno::Reference
<container::XIndexReplace
> xNumRules
;
358 if ((0 == io_rLevel
) || rNPList
.empty() || !i_StyleName
.isEmpty()) {
359 // no parent to inherit from, or explicit style given => new numrules!
360 // index of parent: level - 1, but maybe that does not exist
361 const size_t parent( std::min(static_cast<size_t>(io_rLevel
),
362 rNPList
.size()) - 1 );
363 xNumRules
= MakeNumRule(i_rImport
,
364 io_rLevel
> 0 ? rNPList
[parent
].second
: nullptr,
365 io_rLevel
> 0 ? rNPList
[parent
].first
: none
,
366 i_StyleName
, io_rLevel
);
368 // no style given, but has a parent => reuse parent numrules!
369 ClampLevel(rNPList
.back().second
, io_rLevel
);
371 if (static_cast<sal_uInt16
>(io_rLevel
) + 1U > rNPList
.size()) {
372 // new level: need to enlarge
373 for (size_t i
= rNPList
.size();
374 i
< o3tl::make_unsigned(io_rLevel
); ++i
)
376 NumParaList_t::value_type
const rule(rNPList
.back());
377 rNPList
.push_back(rule
);
379 NumParaList_t::value_type
const rule(rNPList
.back());
380 rNPList
.push_back(xNumRules
.is()
381 ? ::std::make_pair(i_StyleName
, xNumRules
)
384 // old level: no need to enlarge; possibly shrink
385 if (xNumRules
.is()) {
386 rNPList
[io_rLevel
] = std::make_pair(i_StyleName
, xNumRules
);
388 if (static_cast<sal_uInt16
>(io_rLevel
) + 1U < rNPList
.size()) {
389 rNPList
.erase(rNPList
.begin() + io_rLevel
+ 1, rNPList
.end());
392 // remember the list id
393 if (mLastNumberedParagraphs
.size() <= o3tl::make_unsigned(io_rLevel
)) {
394 mLastNumberedParagraphs
.resize(io_rLevel
+1);
396 mLastNumberedParagraphs
[io_rLevel
] = std::make_pair(i_StyleName
, i_ListId
);
397 return rNPList
.back().second
;
400 /** extracted from the XMLTextListBlockContext constructor */
401 uno::Reference
<container::XIndexReplace
>
402 XMLTextListsHelper::MakeNumRule(
403 SvXMLImport
& i_rImport
,
404 const uno::Reference
<container::XIndexReplace
>& i_rNumRule
,
405 std::u16string_view i_ParentStyleName
,
406 const OUString
& i_StyleName
,
407 sal_Int16
& io_rLevel
,
408 bool* o_pRestartNumbering
,
409 bool* io_pSetDefaults
)
411 uno::Reference
<container::XIndexReplace
> xNumRules(i_rNumRule
);
412 if ( !i_StyleName
.isEmpty() && i_StyleName
!= i_ParentStyleName
)
414 const OUString
sDisplayStyleName(
415 i_rImport
.GetStyleDisplayName( XmlStyleFamily::TEXT_LIST
,
417 const uno::Reference
< container::XNameContainer
>& rNumStyles(
418 i_rImport
.GetTextImport()->GetNumberingStyles() );
419 if( rNumStyles
.is() && rNumStyles
->hasByName( sDisplayStyleName
) )
421 uno::Reference
< style::XStyle
> xStyle
;
422 uno::Any any
= rNumStyles
->getByName( sDisplayStyleName
);
425 uno::Reference
< beans::XPropertySet
> xPropSet( xStyle
,
427 any
= xPropSet
->getPropertyValue(u
"NumberingRules"_ustr
);
432 const SvxXMLListStyleContext
*pListStyle(
433 i_rImport
.GetTextImport()->FindAutoListStyle( i_StyleName
) );
436 xNumRules
= pListStyle
->GetNumRules();
437 if( !xNumRules
.is() )
439 pListStyle
->CreateAndInsertAuto();
440 xNumRules
= pListStyle
->GetNumRules();
446 bool bSetDefaults(io_pSetDefaults
&& *io_pSetDefaults
);
447 if ( !xNumRules
.is() )
449 // If no style name has been specified for this style and for any
450 // parent or if no num rule with the specified name exists,
454 SvxXMLListStyleContext::CreateNumRule( i_rImport
.GetModel() );
455 SAL_INFO_IF(xNumRules
.is(), "xmloff.core", "cannot create numrules");
456 if ( !xNumRules
.is() )
459 // Because it is a new num rule, numbering must not be restarted.
460 if (o_pRestartNumbering
) *o_pRestartNumbering
= false;
462 if (io_pSetDefaults
) *io_pSetDefaults
= bSetDefaults
;
465 ClampLevel(xNumRules
, io_rLevel
);
469 // Because there is no list style sheet for this style, a default
470 // format must be set for any level of this num rule.
471 SvxXMLListStyleContext::SetDefaultStyle( xNumRules
, io_rLevel
,
478 OUString
XMLTextListsHelper::GetLastIdOfStyleName(const OUString
& sListStyleName
) const
480 if (!mpStyleNameLastListIds
)
485 auto it
= mpStyleNameLastListIds
->find(sListStyleName
);
486 if (it
== mpStyleNameLastListIds
->end())
494 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */