Avoid potential negative array index access to cached text.
[LibreOffice.git] / writerfilter / source / rtftok / rtfsprm.cxx
blob148d39c2e48b310584345c7a2f60ec67ba587ca1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include "rtfsprm.hxx"
11 #include <ooxml/resourceids.hxx>
12 #include <ooxml/QNameToString.hxx>
13 #include <rtl/strbuf.hxx>
14 #include "rtfdocumentimpl.hxx"
15 #include <algorithm>
17 namespace writerfilter::rtftok
19 RTFSprm::RTFSprm(Id nKeyword, RTFValue::Pointer_t& pValue)
20 : m_nKeyword(nKeyword)
21 , m_pValue(pValue)
25 sal_uInt32 RTFSprm::getId() const { return m_nKeyword; }
27 Value::Pointer_t RTFSprm::getValue() { return Value::Pointer_t(m_pValue->Clone()); }
29 writerfilter::Reference<Properties>::Pointer_t RTFSprm::getProps()
31 return m_pValue->getProperties();
34 #ifdef DBG_UTIL
35 std::string RTFSprm::getName() const { return "RTFSprm"; }
36 #endif
38 #ifdef DBG_UTIL
39 std::string RTFSprm::toString() const
41 OStringBuffer aBuf("RTFSprm");
43 std::string sResult = QNameToString(m_nKeyword);
45 aBuf.append(" ('");
46 if (sResult.length() == 0)
47 aBuf.append(sal_Int32(m_nKeyword));
48 else
49 aBuf.append(sResult.c_str());
50 aBuf.append("', '" + m_pValue->toString() + "')");
52 return std::string(aBuf);
54 #endif
56 namespace
58 class RTFSprms_compare
60 Id m_keyword;
62 public:
63 RTFSprms_compare(Id keyword)
64 : m_keyword{ keyword }
67 bool operator()(const std::pair<Id, RTFValue::Pointer_t>& raPair) const
69 return raPair.first == m_keyword;
74 RTFValue::Pointer_t RTFSprms::find(Id nKeyword, bool bFirst, bool bForWrite)
76 if (bForWrite)
77 ensureCopyBeforeWrite();
79 RTFSprms_compare cmp{ nKeyword };
81 if (bFirst)
83 auto it = std::find_if(m_pSprms->begin(), m_pSprms->end(), cmp);
84 if (it != m_pSprms->end())
85 return it->second;
87 else
88 // find last
90 auto rit = std::find_if(m_pSprms->rbegin(), m_pSprms->rend(), cmp);
91 if (rit != m_pSprms->rend())
92 return rit->second;
95 return RTFValue::Pointer_t{};
98 void RTFSprms::set(Id nKeyword, const RTFValue::Pointer_t& pValue, RTFOverwrite eOverwrite)
100 ensureCopyBeforeWrite();
102 switch (eOverwrite)
104 case RTFOverwrite::YES_PREPEND:
106 std::erase_if(*m_pSprms, RTFSprms_compare{ nKeyword });
107 m_pSprms->emplace(m_pSprms->cbegin(), nKeyword, pValue);
108 break;
110 case RTFOverwrite::YES:
112 auto it
113 = std::find_if(m_pSprms->begin(), m_pSprms->end(), RTFSprms_compare{ nKeyword });
114 if (it != m_pSprms->end())
115 it->second = pValue;
116 else
117 m_pSprms->emplace_back(nKeyword, pValue);
118 break;
120 case RTFOverwrite::NO_IGNORE:
122 if (std::none_of(m_pSprms->cbegin(), m_pSprms->cend(), RTFSprms_compare{ nKeyword }))
123 m_pSprms->emplace_back(nKeyword, pValue);
124 break;
126 case RTFOverwrite::NO_APPEND:
128 m_pSprms->emplace_back(nKeyword, pValue);
129 break;
134 bool RTFSprms::erase(Id nKeyword)
136 ensureCopyBeforeWrite();
138 auto i = std::find_if(m_pSprms->begin(), m_pSprms->end(), RTFSprms_compare{ nKeyword });
139 if (i != m_pSprms->end())
141 m_pSprms->erase(i);
142 return true;
144 return false;
147 void RTFSprms::eraseLast(Id nKeyword)
149 ensureCopyBeforeWrite();
151 auto i = std::find_if(m_pSprms->rbegin(), m_pSprms->rend(), RTFSprms_compare{ nKeyword });
152 if (i != m_pSprms->rend())
153 m_pSprms->erase(std::next(i).base());
156 static RTFValue::Pointer_t getDefaultSPRM(Id const id, Id nStyleType)
158 if (nStyleType == NS_ooxml::LN_Value_ST_StyleType_character)
160 switch (id)
162 case NS_ooxml::LN_EG_RPrBase_szCs:
163 case NS_ooxml::LN_EG_RPrBase_sz:
164 return new RTFValue(24);
165 case NS_ooxml::LN_CT_Color_val:
166 return new RTFValue(0);
167 case NS_ooxml::LN_EG_RPrBase_b:
168 case NS_ooxml::LN_EG_RPrBase_i:
169 return new RTFValue(0);
170 case NS_ooxml::LN_CT_Underline_val:
171 return new RTFValue(NS_ooxml::LN_Value_ST_Underline_none);
172 case NS_ooxml::LN_CT_Fonts_ascii:
173 case NS_ooxml::LN_CT_Fonts_eastAsia:
174 case NS_ooxml::LN_CT_Fonts_cs:
175 return new RTFValue("Times New Roman");
176 default:
177 break;
181 if (!nStyleType || nStyleType == NS_ooxml::LN_Value_ST_StyleType_paragraph)
183 switch (id)
185 case NS_ooxml::LN_CT_Spacing_before:
186 case NS_ooxml::LN_CT_Spacing_after:
187 case NS_ooxml::LN_CT_Ind_left:
188 case NS_ooxml::LN_CT_Ind_right:
189 case NS_ooxml::LN_CT_Ind_firstLine:
190 return new RTFValue(0);
191 case NS_ooxml::LN_CT_Spacing_lineRule:
192 return new RTFValue(NS_ooxml::LN_Value_doc_ST_LineSpacingRule_auto);
193 case NS_ooxml::LN_CT_Spacing_line:
194 // presumably this means 100%, cf. static const int nSingleLineSpacing = 240;
195 return new RTFValue(240);
196 case NS_ooxml::LN_CT_NumPr_numId:
197 return new RTFValue(0);
198 case NS_ooxml::LN_CT_PrBase_pBdr:
199 { // tdf#150382 default all paragraph borders to none
200 RTFSprms attributes;
201 RTFSprms sprms;
202 for (int i = 0; i < 4; ++i)
204 auto const nBorder = getParagraphBorder(i);
205 RTFSprms aAttributes;
206 RTFSprms aSprms;
207 aAttributes.set(NS_ooxml::LN_CT_Border_val,
208 new RTFValue(NS_ooxml::LN_Value_ST_Border_none));
209 sprms.set(nBorder, new RTFValue(aAttributes, aSprms));
211 return new RTFValue(attributes, sprms);
214 default:
215 break;
219 return RTFValue::Pointer_t();
222 /// Is it problematic to deduplicate this SPRM?
223 static bool isSPRMDeduplicateDenylist(Id nId, RTFSprms* pDirect)
225 switch (nId)
227 // See the NS_ooxml::LN_CT_PPrBase_tabs handler in DomainMapper,
228 // deduplication is explicitly not wanted for these tokens.
229 case NS_ooxml::LN_CT_TabStop_val:
230 case NS_ooxml::LN_CT_TabStop_leader:
231 case NS_ooxml::LN_CT_TabStop_pos:
232 // \htmautsp arrives after the style table, so only the non-style value is
233 // correct, keep these.
234 case NS_ooxml::LN_CT_Spacing_beforeAutospacing:
235 case NS_ooxml::LN_CT_Spacing_afterAutospacing:
236 // \chbrdr requires *all* of the border settings to be present,
237 // otherwise a default (NONE) border is created from the removed
238 // attributes which then overrides the style-defined border.
239 // See BorderHandler.cxx and NS_ooxml::LN_EG_RPrBase_bdr in DomainMapper.
240 // This also is needed for NS_ooxml::LN_CT_PBdr_top etc.
241 case NS_ooxml::LN_CT_Border_sz:
242 case NS_ooxml::LN_CT_Border_val:
243 case NS_ooxml::LN_CT_Border_color:
244 case NS_ooxml::LN_CT_Border_space:
245 case NS_ooxml::LN_CT_Border_shadow:
246 case NS_ooxml::LN_CT_Border_frame:
247 case NS_ooxml::LN_CT_Border_themeTint:
248 case NS_ooxml::LN_CT_Border_themeColor:
249 return true;
250 // Removing \fi and \li if the style has the same value would mean taking these values from
251 // \ls, while deduplication would be done to take the values from the style.
252 case NS_ooxml::LN_CT_Ind_firstLine:
253 case NS_ooxml::LN_CT_Ind_left:
254 return pDirect && pDirect->find(NS_ooxml::LN_CT_PPrBase_numPr);
256 default:
257 return false;
261 /// Should this SPRM be removed if all its children are removed?
262 static bool isSPRMChildrenExpected(Id nId)
264 switch (nId)
266 case NS_ooxml::LN_CT_PBdr_top:
267 case NS_ooxml::LN_CT_PBdr_left:
268 case NS_ooxml::LN_CT_PBdr_bottom:
269 case NS_ooxml::LN_CT_PBdr_right:
270 // Expected children are NS_ooxml::LN_CT_Border_*.
271 case NS_ooxml::LN_CT_PrBase_shd:
272 // Expected children are NS_ooxml::LN_CT_Shd_*.
273 case NS_ooxml::LN_CT_PPrBase_ind:
274 // Expected children are NS_ooxml::LN_CT_Ind_*.
275 return true;
277 default:
278 return false;
282 /// Does the clone / deduplication of a single sprm.
283 static void cloneAndDeduplicateSprm(std::pair<Id, RTFValue::Pointer_t> const& rSprm, RTFSprms& ret,
284 Id nStyleType, RTFSprms* pDirect = nullptr)
286 RTFValue::Pointer_t const pValue(ret.find(rSprm.first));
287 if (pValue)
289 if (rSprm.second->equals(*pValue))
291 //this removes properties that are equal at the style and at the sprm
292 //don't do that for paragraph styles
293 if (nStyleType == NS_ooxml::LN_Value_ST_StyleType_character)
295 if (!isSPRMDeduplicateDenylist(rSprm.first, pDirect))
297 ret.erase(rSprm.first); // duplicate to style
301 else if (!rSprm.second->getSprms().empty() || !rSprm.second->getAttributes().empty())
303 RTFSprms const sprms(pValue->getSprms().cloneAndDeduplicate(
304 rSprm.second->getSprms(), nStyleType, /*bImplicitPPr =*/false, pDirect));
305 RTFSprms const attributes(pValue->getAttributes().cloneAndDeduplicate(
306 rSprm.second->getAttributes(), nStyleType, /*bImplicitPPr =*/false, pDirect));
307 // Don't copy the sprm in case we expect it to have children but it doesn't have some.
308 if (!isSPRMChildrenExpected(rSprm.first) || !sprms.empty() || !attributes.empty())
309 ret.set(rSprm.first,
310 RTFValue::Pointer_t(pValue->CloneWithSprms(attributes, sprms)));
313 else
315 // not found - try to override style with default
316 RTFValue::Pointer_t const pDefault(getDefaultSPRM(rSprm.first, nStyleType));
317 if (pDefault)
319 ret.set(rSprm.first, pDefault);
321 else if (!rSprm.second->getSprms().empty() || !rSprm.second->getAttributes().empty())
323 RTFSprms const sprms(
324 RTFSprms().cloneAndDeduplicate(rSprm.second->getSprms(), nStyleType));
325 RTFSprms const attributes(
326 RTFSprms().cloneAndDeduplicate(rSprm.second->getAttributes(), nStyleType));
327 if (!sprms.empty() || !attributes.empty())
329 ret.set(rSprm.first, new RTFValue(attributes, sprms));
335 /// Extracts the list level matching nLevel from pAbstract.
336 static RTFValue::Pointer_t getListLevel(const RTFValue::Pointer_t& pAbstract, int nLevel)
338 for (const auto& rPair : pAbstract->getSprms())
340 if (rPair.first != NS_ooxml::LN_CT_AbstractNum_lvl)
341 continue;
343 RTFValue::Pointer_t pLevel = rPair.second->getAttributes().find(NS_ooxml::LN_CT_Lvl_ilvl);
344 if (!pLevel)
345 continue;
347 if (pLevel->getInt() != nLevel)
348 continue;
350 return rPair.second;
353 return RTFValue::Pointer_t();
356 void RTFSprms::deduplicateList(const std::map<int, int>& rInvalidListLevelFirstIndents)
358 int nLevel = 0;
359 RTFValue::Pointer_t pLevelId
360 = getNestedSprm(*this, NS_ooxml::LN_CT_PPrBase_numPr, NS_ooxml::LN_CT_NumPr_ilvl);
361 if (pLevelId)
362 nLevel = pLevelId->getInt();
364 auto it = rInvalidListLevelFirstIndents.find(nLevel);
365 if (it == rInvalidListLevelFirstIndents.end())
366 return;
368 int nListValue = it->second;
370 RTFValue::Pointer_t pParagraphValue
371 = getNestedAttribute(*this, NS_ooxml::LN_CT_PPrBase_ind, NS_ooxml::LN_CT_Ind_firstLine);
372 if (!pParagraphValue)
373 return;
375 int nParagraphValue = pParagraphValue->getInt();
377 if (nParagraphValue == nListValue)
378 eraseNestedAttribute(*this, NS_ooxml::LN_CT_PPrBase_ind, NS_ooxml::LN_CT_Ind_firstLine);
381 void RTFSprms::duplicateList(const RTFValue::Pointer_t& pAbstract)
383 int nLevel = 0;
384 RTFValue::Pointer_t pLevelId
385 = getNestedSprm(*this, NS_ooxml::LN_CT_PPrBase_numPr, NS_ooxml::LN_CT_NumPr_ilvl);
386 if (pLevelId)
387 nLevel = pLevelId->getInt();
389 RTFValue::Pointer_t pLevel = getListLevel(pAbstract, nLevel);
390 if (!pLevel)
391 return;
393 RTFValue::Pointer_t pLevelInd = pLevel->getSprms().find(NS_ooxml::LN_CT_PPrBase_ind);
394 if (!pLevelInd)
395 return;
397 for (const auto& rListLevelPair : pLevelInd->getAttributes())
399 switch (rListLevelPair.first)
401 case NS_ooxml::LN_CT_Ind_left:
402 case NS_ooxml::LN_CT_Ind_right:
403 case NS_ooxml::LN_CT_Ind_firstLine:
404 RTFValue::Pointer_t pParagraphValue
405 = getNestedAttribute(*this, NS_ooxml::LN_CT_PPrBase_ind, rListLevelPair.first);
406 if (!pParagraphValue)
407 putNestedAttribute(*this, NS_ooxml::LN_CT_PPrBase_ind, rListLevelPair.first,
408 getDefaultSPRM(rListLevelPair.first, 0));
410 break;
415 RTFSprms RTFSprms::cloneAndDeduplicate(RTFSprms& rReference, Id const nStyleType,
416 bool const bImplicitPPr, RTFSprms* pDirect) const
418 RTFSprms ret(*this);
419 ret.ensureCopyBeforeWrite();
421 // Note: apparently some attributes are set with OVERWRITE_NO_APPEND;
422 // it is probably a bad idea to mess with those in any way here?
423 for (auto& rSprm : rReference)
425 // Paragraph formatting sprms are directly contained in case of
426 // paragraphs, but they are below NS_ooxml::LN_CT_Style_pPr in case of
427 // styles. So handle those children directly, to avoid unexpected
428 // addition of direct formatting sprms at the paragraph level.
429 if (bImplicitPPr && rSprm.first == NS_ooxml::LN_CT_Style_pPr)
431 for (const auto& i : rSprm.second->getSprms())
432 cloneAndDeduplicateSprm(i, ret, nStyleType, pDirect);
434 else
435 cloneAndDeduplicateSprm(rSprm, ret, nStyleType, pDirect);
437 return ret;
440 bool RTFSprms::equals(const RTFSprms& rOther) const
442 auto it1 = m_pSprms->cbegin();
443 auto it1End = m_pSprms->cend();
444 auto it2 = rOther.m_pSprms->cbegin();
445 auto it2End = rOther.m_pSprms->cend();
446 while (it1 != it1End && it2 != it2End)
448 if (it1->first != it2->first)
449 return false;
450 if (!it1->second->equals(*it2->second))
451 return false;
452 ++it1;
453 ++it2;
455 return it1 == it1End && it2 == it2End;
458 void RTFSprms::ensureCopyBeforeWrite()
460 if (m_pSprms->GetRefCount() > 1)
462 tools::SvRef<RTFSprmsImpl> pClone(new RTFSprmsImpl);
463 for (auto& rSprm : *m_pSprms)
464 pClone->push_back(
465 std::make_pair(rSprm.first, RTFValue::Pointer_t(rSprm.second->Clone())));
466 m_pSprms = pClone;
470 RTFSprms::RTFSprms()
471 : m_pSprms(new RTFSprmsImpl)
475 RTFSprms::~RTFSprms() = default;
477 void RTFSprms::clear()
479 if (m_pSprms->GetRefCount() == 1)
480 return m_pSprms->clear();
482 m_pSprms = tools::SvRef<RTFSprmsImpl>(new RTFSprmsImpl);
485 } // namespace writerfilter::rtftok
487 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */