1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include "xmlstyli.hxx"
21 #include <xmloff/xmlnamespace.hxx>
22 #include <xmloff/xmlimppr.hxx>
23 #include <xmloff/families.hxx>
24 #include <xmloff/xmlnumfi.hxx>
25 #include <xmloff/XMLGraphicsDefaultStyle.hxx>
26 #include <xmloff/xmltoken.hxx>
27 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
28 #include <com/sun/star/table/BorderLine2.hpp>
29 #include <comphelper/extract.hxx>
30 #include <xmloff/xmlprcon.hxx>
31 #include "XMLTableHeaderFooterContext.hxx"
32 #include "XMLConverter.hxx"
33 #include "XMLTableShapeImportHelper.hxx"
34 #include <sheetdata.hxx>
35 #include "xmlannoi.hxx"
36 #include <textuno.hxx>
37 #include <cellsuno.hxx>
38 #include "xmlstyle.hxx"
39
40 #include <docuno.hxx>
41 #include <unonames.hxx>
42 #include <document.hxx>
43 #include <conditio.hxx>
44 #include <rangelst.hxx>
45
46 #define XML_LINE_LEFT 0
47 #define XML_LINE_RIGHT 1
48 #define XML_LINE_TOP 2
49 #define XML_LINE_BOTTOM 3
50
51 #define XML_LINE_TLBR 0
52 #define XML_LINE_BLTR 1
53
54 using namespace ::com::sun::star;
55 using namespace ::com::sun::star::xml::sax;
56 using namespace ::com::sun::star::style;
57 using namespace ::com::sun::star::beans;
58 using namespace ::com::sun::star::container;
59 using namespace xmloff::token;
60 using namespace ::formula;
61
62 using com::sun::star::uno::UNO_QUERY;
ScXMLCellImportPropertyMapper(const rtl::Reference<XMLPropertySetMapper> & rMapper,SvXMLImport & rImportP)63 ScXMLCellImportPropertyMapper::ScXMLCellImportPropertyMapper(
64 const rtl::Reference< XMLPropertySetMapper >& rMapper,
65 SvXMLImport& rImportP) :
66 SvXMLImportPropertyMapper( rMapper, rImportP )
67 {
68 }
69
~ScXMLCellImportPropertyMapper()70 ScXMLCellImportPropertyMapper::~ScXMLCellImportPropertyMapper()
71 {
72 }
73
finished(::std::vector<XMLPropertyState> & rProperties,sal_Int32 nStartIndex,sal_Int32 nEndIndex) const74 void ScXMLCellImportPropertyMapper::finished(::std::vector< XMLPropertyState >& rProperties, sal_Int32 nStartIndex, sal_Int32 nEndIndex ) const
75 {
76 static const sal_Int16 aPaddingCTF[4] = { CTF_SC_LEFTPADDING, CTF_SC_RIGHTPADDING,
77 CTF_SC_TOPPADDING, CTF_SC_BOTTOMPADDING };
78 static const sal_Int16 aBorderCTF[4] = { CTF_SC_LEFTBORDER, CTF_SC_RIGHTBORDER,
79 CTF_SC_TOPBORDER, CTF_SC_BOTTOMBORDER };
80
81 SvXMLImportPropertyMapper::finished(rProperties, nStartIndex, nEndIndex);
82 XMLPropertyState* pAllPaddingProperty(nullptr);
83 XMLPropertyState* pPadding[4] = { nullptr, nullptr, nullptr, nullptr };
84 XMLPropertyState* pNewPadding[4] = { nullptr, nullptr, nullptr, nullptr };
85 XMLPropertyState* pAllBorderProperty = nullptr;
86 XMLPropertyState* pBorders[4] = { nullptr, nullptr, nullptr, nullptr };
87 XMLPropertyState* pNewBorders[4] = { nullptr, nullptr, nullptr, nullptr };
88 XMLPropertyState* pAllBorderWidthProperty = nullptr;
89 XMLPropertyState* pBorderWidths[4] = { nullptr, nullptr, nullptr, nullptr };
90 XMLPropertyState* pDiagBorders[2] = { nullptr };
91 XMLPropertyState* pOldDiagBorderWidths[2] = { nullptr }; // old attribute names without "s"
92 XMLPropertyState* pDiagBorderWidths[2] = { nullptr };
93
94 for (auto& rProperty : rProperties)
95 {
96 XMLPropertyState*property = &rProperty;
97 if (property->mnIndex != -1)
98 {
99 sal_Int16 nContextID = getPropertySetMapper()->GetEntryContextId(property->mnIndex);
100 switch (nContextID)
101 {
102 case CTF_SC_ALLPADDING : pAllPaddingProperty = property; break;
103 case CTF_SC_LEFTPADDING : pPadding[XML_LINE_LEFT] = property; break;
104 case CTF_SC_RIGHTPADDING : pPadding[XML_LINE_RIGHT] = property; break;
105 case CTF_SC_TOPPADDING : pPadding[XML_LINE_TOP] = property; break;
106 case CTF_SC_BOTTOMPADDING : pPadding[XML_LINE_BOTTOM] = property; break;
107 case CTF_SC_ALLBORDER : pAllBorderProperty = property; break;
108 case CTF_SC_LEFTBORDER : pBorders[XML_LINE_LEFT] = property; break;
109 case CTF_SC_RIGHTBORDER : pBorders[XML_LINE_RIGHT] = property; break;
110 case CTF_SC_TOPBORDER : pBorders[XML_LINE_TOP] = property; break;
111 case CTF_SC_BOTTOMBORDER : pBorders[XML_LINE_BOTTOM] = property; break;
112 case CTF_SC_ALLBORDERWIDTH : pAllBorderWidthProperty = property; break;
113 case CTF_SC_LEFTBORDERWIDTH : pBorderWidths[XML_LINE_LEFT] = property; break;
114 case CTF_SC_RIGHTBORDERWIDTH : pBorderWidths[XML_LINE_RIGHT] = property; break;
115 case CTF_SC_TOPBORDERWIDTH : pBorderWidths[XML_LINE_TOP] = property; break;
116 case CTF_SC_BOTTOMBORDERWIDTH : pBorderWidths[XML_LINE_BOTTOM] = property; break;
117 case CTF_SC_DIAGONALTLBR : pDiagBorders[XML_LINE_TLBR] = property; break;
118 case CTF_SC_DIAGONALBLTR : pDiagBorders[XML_LINE_BLTR] = property; break;
119 case CTF_SC_DIAGONALTLBRWIDTH : pOldDiagBorderWidths[XML_LINE_TLBR] = property; break;
120 case CTF_SC_DIAGONALTLBRWIDTHS : pDiagBorderWidths[XML_LINE_TLBR] = property; break;
121 case CTF_SC_DIAGONALBLTRWIDTH : pOldDiagBorderWidths[XML_LINE_BLTR] = property; break;
122 case CTF_SC_DIAGONALBLTRWIDTHS : pDiagBorderWidths[XML_LINE_BLTR] = property; break;
123 }
124 }
125 }
126 sal_uInt16 i;
127
128 // #i27594#; copy Value, but don't insert
129 if (pAllBorderWidthProperty)
130 pAllBorderWidthProperty->mnIndex = -1;
131 if (pAllBorderProperty)
132 pAllBorderProperty->mnIndex = -1;
133 if (pAllPaddingProperty)
134 pAllPaddingProperty->mnIndex = -1;
135
136 for (i = 0; i < 4; ++i)
137 {
138 if (pAllPaddingProperty && !pPadding[i])
139 pNewPadding[i] = new XMLPropertyState(maPropMapper->FindEntryIndex(aPaddingCTF[i]), pAllPaddingProperty->maValue);
140 if (pAllBorderProperty && !pBorders[i])
141 {
142 pNewBorders[i] = new XMLPropertyState(maPropMapper->FindEntryIndex(aBorderCTF[i]), pAllBorderProperty->maValue);
143 pBorders[i] = pNewBorders[i];
144 }
145 if( !pBorderWidths[i] )
146 pBorderWidths[i] = pAllBorderWidthProperty;
147 else
148 pBorderWidths[i]->mnIndex = -1;
149 if( pBorders[i] )
150 {
151 table::BorderLine2 aBorderLine;
152 pBorders[i]->maValue >>= aBorderLine;
153 if( pBorderWidths[i] )
154 {
155 // Merge style:border-line-width values to fo:border values. Do
156 // not override fo:border line width or line style with an
157 // empty value!
158 table::BorderLine2 aBorderLineWidth;
159 pBorderWidths[i]->maValue >>= aBorderLineWidth;
160 aBorderLine.OuterLineWidth = aBorderLineWidth.OuterLineWidth;
161 aBorderLine.InnerLineWidth = aBorderLineWidth.InnerLineWidth;
162 aBorderLine.LineDistance = aBorderLineWidth.LineDistance;
163 pBorders[i]->maValue <<= aBorderLine;
164 }
165 }
166 }
167 for( i = 0; i < 2; ++i )
168 {
169 if( pDiagBorders[i] && ( pDiagBorderWidths[i] || pOldDiagBorderWidths[i] ) )
170 {
171 table::BorderLine2 aBorderLine;
172 pDiagBorders[i]->maValue >>= aBorderLine;
173 table::BorderLine2 aBorderLineWidth;
174 if (pDiagBorderWidths[i])
175 pDiagBorderWidths[i]->maValue >>= aBorderLineWidth; // prefer new attribute
176 else
177 pOldDiagBorderWidths[i]->maValue >>= aBorderLineWidth;
178 aBorderLine.OuterLineWidth = aBorderLineWidth.OuterLineWidth;
179 aBorderLine.InnerLineWidth = aBorderLineWidth.InnerLineWidth;
180 aBorderLine.LineDistance = aBorderLineWidth.LineDistance;
181 pDiagBorders[i]->maValue <<= aBorderLine;
182 if (pDiagBorderWidths[i])
183 pDiagBorderWidths[i]->mnIndex = -1;
184 if (pOldDiagBorderWidths[i])
185 pOldDiagBorderWidths[i]->mnIndex = -1; // reset mnIndex for old and new attribute if both are present
186 }
187 }
188
189 for (i = 0; i < 4; ++i)
190 {
191 if (pNewPadding[i])
192 {
193 rProperties.push_back(*pNewPadding[i]);
194 delete pNewPadding[i];
195 }
196 if (pNewBorders[i])
197 {
198 rProperties.push_back(*pNewBorders[i]);
199 delete pNewBorders[i];
200 }
201 }
202 }
203
ScXMLRowImportPropertyMapper(const rtl::Reference<XMLPropertySetMapper> & rMapper,SvXMLImport & rImportP)204 ScXMLRowImportPropertyMapper::ScXMLRowImportPropertyMapper(
205 const rtl::Reference< XMLPropertySetMapper >& rMapper,
206 SvXMLImport& rImportP) :
207 SvXMLImportPropertyMapper( rMapper, rImportP )
208 {
209 }
210
~ScXMLRowImportPropertyMapper()211 ScXMLRowImportPropertyMapper::~ScXMLRowImportPropertyMapper()
212 {
213 }
214
finished(::std::vector<XMLPropertyState> & rProperties,sal_Int32 nStartIndex,sal_Int32 nEndIndex) const215 void ScXMLRowImportPropertyMapper::finished(::std::vector< XMLPropertyState >& rProperties, sal_Int32 nStartIndex, sal_Int32 nEndIndex ) const
216 {
217 SvXMLImportPropertyMapper::finished(rProperties, nStartIndex, nEndIndex);
218 XMLPropertyState* pHeight(nullptr);
219 XMLPropertyState* pOptimalHeight(nullptr);
220 XMLPropertyState* pPageBreak(nullptr);
221 for (auto& rProperty : rProperties)
222 {
223 XMLPropertyState* property = &rProperty;
224 if (property->mnIndex != -1)
225 {
226 sal_Int16 nContextID = getPropertySetMapper()->GetEntryContextId(property->mnIndex);
227 switch (nContextID)
228 {
229 case CTF_SC_ROWHEIGHT : pHeight = property; break;
230 case CTF_SC_ROWOPTIMALHEIGHT : pOptimalHeight = property; break;
231 case CTF_SC_ROWBREAKBEFORE : pPageBreak = property; break;
232 }
233 }
234 }
235 if (pPageBreak)
236 {
237 if(!(::cppu::any2bool(pPageBreak->maValue)))
238 pPageBreak->mnIndex = -1;
239 }
240 if (pOptimalHeight)
241 {
242 if (::cppu::any2bool(pOptimalHeight->maValue))
243 {
244 if (pHeight)
245 {
246 // set the stored height, but keep "optimal" flag:
247 // pass the height value as OptimalHeight property (only allowed while loading!)
248 pOptimalHeight->maValue = pHeight->maValue;
249 pHeight->mnIndex = -1;
250 }
251 else
252 pOptimalHeight->mnIndex = -1;
253 }
254 }
255 else if (pHeight)
256 {
257 rProperties.emplace_back(maPropMapper->FindEntryIndex(CTF_SC_ROWOPTIMALHEIGHT), css::uno::Any(false));
258 }
259 // don't access pointers to rProperties elements after push_back!
260 }
261
262 namespace {
263
264 class XMLTableCellPropsContext : public SvXMLPropertySetContext
265 {
266 public:
267 XMLTableCellPropsContext(
268 SvXMLImport& rImport, sal_Int32 nElement,
269 const uno::Reference< xml::sax::XFastAttributeList >& xAttrList,
270 sal_uInt32 nFamily,
271 ::std::vector< XMLPropertyState > &rProps,
272 const rtl::Reference < SvXMLImportPropertyMapper > &rMap);
273
274 using SvXMLPropertySetContext::createFastChildContext;
275 virtual css::uno::Reference< css::xml::sax::XFastContextHandler > createFastChildContext(
276 sal_Int32 nElement,
277 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList,
278 ::std::vector< XMLPropertyState > &rProperties,
279 const XMLPropertyState& rProp ) override;
280 };
281
282 }
283
XMLTableCellPropsContext(SvXMLImport & rImport,sal_Int32 nElement,const uno::Reference<xml::sax::XFastAttributeList> & xAttrList,sal_uInt32 nFamily,::std::vector<XMLPropertyState> & rProps,const rtl::Reference<SvXMLImportPropertyMapper> & rMap)284 XMLTableCellPropsContext::XMLTableCellPropsContext(
285 SvXMLImport& rImport, sal_Int32 nElement,
286 const uno::Reference< xml::sax::XFastAttributeList >& xAttrList,
287 sal_uInt32 nFamily,
288 ::std::vector< XMLPropertyState > &rProps,
289 const rtl::Reference < SvXMLImportPropertyMapper > &rMap)
290 : SvXMLPropertySetContext( rImport, nElement, xAttrList, nFamily,
291 rProps, rMap )
292 {
293 }
294
createFastChildContext(sal_Int32 nElement,const css::uno::Reference<css::xml::sax::XFastAttributeList> & xAttrList,::std::vector<XMLPropertyState> & rProperties,const XMLPropertyState & rProp)295 css::uno::Reference< css::xml::sax::XFastContextHandler > XMLTableCellPropsContext::createFastChildContext(
296 sal_Int32 nElement,
297 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList,
298 ::std::vector< XMLPropertyState > &rProperties,
299 const XMLPropertyState& rProp )
300 {
301 // no need for a custom context or indeed a SvXMLTokenMap to grab just the
302 // single attribute ( href ) that we are interested in.
303 // still though, we will check namespaces etc.
304 if (nElement == XML_ELEMENT(STYLE, XML_HYPERLINK) ||
305 nElement == XML_ELEMENT(LO_EXT, XML_HYPERLINK) )
306 {
307 OUString sURL;
308 for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
309 {
310 if ( aIter.getToken() == XML_ELEMENT(XLINK, XML_HREF) )
311 sURL = aIter.toString();
312 else
313 XMLOFF_WARN_UNKNOWN("sc", aIter);
314 }
315 if ( !sURL.isEmpty() )
316 {
317 XMLPropertyState aProp( rProp );
318 aProp.maValue <<= sURL;
319 rProperties.push_back( aProp );
320 }
321 }
322 return SvXMLPropertySetContext::createFastChildContext( nElement, xAttrList, rProperties, rProp );
323 }
324
325 namespace {
326
327 class ScXMLMapContext : public SvXMLImportContext
328 {
329 OUString msApplyStyle;
330 OUString msCondition;
331 OUString msBaseCell;
332
GetScImport()333 ScXMLImport& GetScImport() { return static_cast<ScXMLImport&>(GetImport()); }
334 public:
335
336 ScXMLMapContext(
337 SvXMLImport& rImport, sal_Int32 nElement,
338 const uno::Reference< xml::sax::XFastAttributeList > & xAttrList );
339
340 ScCondFormatEntry* CreateConditionEntry();
341 };
342
343 }
344
ScXMLMapContext(SvXMLImport & rImport,sal_Int32,const uno::Reference<xml::sax::XFastAttributeList> & xAttrList)345 ScXMLMapContext::ScXMLMapContext(SvXMLImport& rImport, sal_Int32 /*nElement*/,
346 const uno::Reference< xml::sax::XFastAttributeList > & xAttrList )
347 : SvXMLImportContext( rImport )
348 {
349 for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) )
350 {
351 OUString sValue = aIter.toString();
352 switch (aIter.getToken())
353 {
354 case XML_ELEMENT(STYLE, XML_CONDITION):
355 msCondition = sValue;
356 break;
357 case XML_ELEMENT(STYLE, XML_APPLY_STYLE_NAME):
358 msApplyStyle = GetImport().GetStyleDisplayName( XmlStyleFamily::TABLE_CELL, sValue);
359 break;
360 case XML_ELEMENT(STYLE, XML_BASE_CELL_ADDRESS):
361 msBaseCell = sValue;
362 break;
363 default:
364 XMLOFF_WARN_UNKNOWN("sc", aIter);
365 }
366 }
367 }
368
CreateConditionEntry()369 ScCondFormatEntry* ScXMLMapContext::CreateConditionEntry()
370 {
371 OUString aCondition, aConditionNmsp;
372 FormulaGrammar::Grammar eGrammar = FormulaGrammar::GRAM_UNSPECIFIED;
373 GetScImport().ExtractFormulaNamespaceGrammar( aCondition, aConditionNmsp, eGrammar, msCondition );
374 bool bHasNmsp = aCondition.getLength() < msCondition.getLength();
375
376 // parse a condition from the attribute string
377 ScXMLConditionParseResult aParseResult;
378 ScXMLConditionHelper::parseCondition( aParseResult, aCondition, 0 );
379
380 if( !bHasNmsp )
381 {
382 // the attribute does not contain a namespace: try to find a namespace of an external grammar
383 FormulaGrammar::Grammar eNewGrammar = FormulaGrammar::GRAM_UNSPECIFIED;
384 GetScImport().ExtractFormulaNamespaceGrammar( aCondition, aConditionNmsp, eNewGrammar, aCondition, true );
385 if( eNewGrammar != FormulaGrammar::GRAM_EXTERNAL )
386 eGrammar = eNewGrammar;
387 }
388
389 ScConditionMode eMode = ScConditionEntry::GetModeFromApi(aParseResult.meOperator);
390 ScDocument* pDoc = GetScImport().GetDocument();
391
392 ScCondFormatEntry* pEntry = new ScCondFormatEntry(eMode, aParseResult.maOperand1, aParseResult.maOperand2, *pDoc, ScAddress(), msApplyStyle,
393 OUString(), OUString(), eGrammar, eGrammar);
394
395 pEntry->SetSrcString(msBaseCell);
396 return pEntry;
397 }
398
SetAttribute(sal_Int32 nElement,const OUString & rValue)399 void XMLTableStyleContext::SetAttribute( sal_Int32 nElement,
400 const OUString& rValue )
401 {
402 switch(nElement & TOKEN_MASK)
403 {
404 case XML_DATA_STYLE_NAME:
405 sDataStyleName = rValue;
406 break;
407 case XML_MASTER_PAGE_NAME:
408 sPageStyle = rValue;
409 break;
410 default:
411 XMLPropStyleContext::SetAttribute( nElement, rValue );
412 }
413 }
414
415
XMLTableStyleContext(ScXMLImport & rImport,SvXMLStylesContext & rStyles,XmlStyleFamily nFamily,bool bDefaultStyle)416 XMLTableStyleContext::XMLTableStyleContext( ScXMLImport& rImport,
417 SvXMLStylesContext& rStyles, XmlStyleFamily nFamily, bool bDefaultStyle ) :
418 XMLPropStyleContext( rImport, rStyles, nFamily, bDefaultStyle ),
419 sDataStyleName(),
420 pStyles(&rStyles),
421 nNumberFormat(-1),
422 nLastSheet(-1),
423 bParentSet(false),
424 mpCondFormat(nullptr),
425 mbDeleteCondFormat(true)
426 {
427 }
428
~XMLTableStyleContext()429 XMLTableStyleContext::~XMLTableStyleContext()
430 {
431 if(mbDeleteCondFormat)
432 delete mpCondFormat;
433 }
434
createFastChildContext(sal_Int32 nElement,const css::uno::Reference<css::xml::sax::XFastAttributeList> & xAttrList)435 css::uno::Reference< css::xml::sax::XFastContextHandler > XMLTableStyleContext::createFastChildContext(
436 sal_Int32 nElement,
437 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
438 {
439 css::uno::Reference< css::xml::sax::XFastContextHandler > xContext;
440
441 if( nElement == XML_ELEMENT(STYLE, XML_MAP) )
442 {
443 if(!mpCondFormat)
444 mpCondFormat = new ScConditionalFormat( 0, GetScImport().GetDocument() );
445 ScXMLMapContext* pMapContext = new ScXMLMapContext(GetImport(), nElement, xAttrList);
446 xContext = pMapContext;
447 mpCondFormat->AddEntry(pMapContext->CreateConditionEntry());
448 }
449 else if ( nElement == XML_ELEMENT(STYLE, XML_TABLE_CELL_PROPERTIES) )
450 {
451 rtl::Reference < SvXMLImportPropertyMapper > xImpPrMap =
452 GetStyles()->GetImportPropertyMapper(
453 GetFamily() );
454 if( xImpPrMap.is() )
455 xContext = new XMLTableCellPropsContext( GetImport(), nElement,
456 xAttrList,
457 XML_TYPE_PROP_TABLE_CELL,
458 GetProperties(),
459 xImpPrMap );
460 }
461
462 if (!xContext)
463 xContext = XMLPropStyleContext::createFastChildContext( nElement, xAttrList );
464 return xContext;
465 }
466
ApplyCondFormat(const uno::Sequence<table::CellRangeAddress> & xCellRanges)467 void XMLTableStyleContext::ApplyCondFormat( const uno::Sequence<table::CellRangeAddress>& xCellRanges )
468 {
469 if(!mpCondFormat || GetScImport().HasNewCondFormatData())
470 return;
471
472 ScRangeList aRangeList;
473 for(const table::CellRangeAddress& aAddress : xCellRanges)
474 {
475 ScRange aRange( aAddress.StartColumn, aAddress.StartRow, aAddress.Sheet, aAddress.EndColumn, aAddress.EndRow, aAddress.Sheet );
476 aRangeList.Join( aRange );
477 }
478
479 ScDocument* pDoc = GetScImport().GetDocument();
480 SCTAB nTab = GetScImport().GetTables().GetCurrentSheet();
481 ScConditionalFormatList* pFormatList = pDoc->GetCondFormList(nTab);
482 auto itr = std::find_if(pFormatList->begin(), pFormatList->end(),
483 [this](const std::unique_ptr<ScConditionalFormat>& rxFormat) { return rxFormat->EqualEntries(*mpCondFormat); });
484 if (itr != pFormatList->end())
485 {
486 ScRangeList& rRangeList = (*itr)->GetRangeList();
487 sal_uInt32 nCondId = (*itr)->GetKey();
488 size_t n = aRangeList.size();
489 for(size_t i = 0; i < n; ++i)
490 {
491 const ScRange & rRange = aRangeList[i];
492 rRangeList.Join(rRange);
493 }
494
495 pDoc->AddCondFormatData( aRangeList, nTab, nCondId );
496 return;
497 }
498
499 if(mpCondFormat && mbDeleteCondFormat)
500 {
501 sal_uLong nIndex = pDoc->AddCondFormat(std::unique_ptr<ScConditionalFormat>(mpCondFormat), nTab );
502 mpCondFormat->SetKey(nIndex);
503 mpCondFormat->SetRange(aRangeList);
504
505 pDoc->AddCondFormatData( aRangeList, nTab, nIndex );
506 mbDeleteCondFormat = false;
507 }
508
509 }
510
FillPropertySet(const uno::Reference<XPropertySet> & rPropSet)511 void XMLTableStyleContext::FillPropertySet(
512 const uno::Reference< XPropertySet > & rPropSet )
513 {
514 if (!IsDefaultStyle())
515 {
516 if (GetFamily() == XmlStyleFamily::TABLE_CELL)
517 {
518 if (!bParentSet)
519 {
520 AddProperty(CTF_SC_CELLSTYLE, uno::makeAny(GetImport().GetStyleDisplayName( XmlStyleFamily::TABLE_CELL, GetParentName() )));
521 bParentSet = true;
522 }
523 sal_Int32 nNumFmt = GetNumberFormat();
524 if (nNumFmt >= 0)
525 AddProperty(CTF_SC_NUMBERFORMAT, uno::makeAny(nNumFmt));
526 }
527 else if (GetFamily() == XmlStyleFamily::TABLE_TABLE)
528 {
529 if (!sPageStyle.isEmpty())
530 AddProperty(CTF_SC_MASTERPAGENAME, uno::makeAny(GetImport().GetStyleDisplayName( XmlStyleFamily::MASTER_PAGE, sPageStyle )));
531 }
532 }
533 XMLPropStyleContext::FillPropertySet(rPropSet);
534 }
535
SetDefaults()536 void XMLTableStyleContext::SetDefaults()
537 {
538 if ((GetFamily() == XmlStyleFamily::TABLE_CELL) && GetImport().GetModel().is())
539 {
540 uno::Reference <lang::XMultiServiceFactory> xMultiServiceFactory(GetImport().GetModel(), uno::UNO_QUERY);
541 if (xMultiServiceFactory.is())
542 {
543 uno::Reference <beans::XPropertySet> xProperties(xMultiServiceFactory->createInstance("com.sun.star.sheet.Defaults"), uno::UNO_QUERY);
544 if (xProperties.is())
545 FillPropertySet(xProperties);
546 }
547 }
548 }
549
AddProperty(const sal_Int16 nContextID,const uno::Any & rValue)550 void XMLTableStyleContext::AddProperty(const sal_Int16 nContextID, const uno::Any& rValue)
551 {
552 XMLPropertyState* property = FindProperty(nContextID);
553 if (property)
554 property->mnIndex = -1; // #i46996# remove old property, so it isn't double
555 sal_Int32 nIndex(static_cast<XMLTableStylesContext *>(pStyles)->GetIndex(nContextID));
556 OSL_ENSURE(nIndex != -1, "Property not found in Map");
557 XMLPropertyState aPropState(nIndex, rValue);
558 GetProperties().push_back(aPropState); // has to be inserted in a sort order later
559 }
560
FindProperty(const sal_Int16 nContextID)561 XMLPropertyState* XMLTableStyleContext::FindProperty(const sal_Int16 nContextID)
562 {
563 XMLPropertyState* pRet = nullptr;
564 rtl::Reference < XMLPropertySetMapper > xPrMap;
565 rtl::Reference < SvXMLImportPropertyMapper > xImpPrMap =
566 pStyles->GetImportPropertyMapper( GetFamily() );
567 OSL_ENSURE( xImpPrMap.is(), "There is the import prop mapper" );
568 if( xImpPrMap.is() )
569 xPrMap = xImpPrMap->getPropertySetMapper();
570 if( xPrMap.is() )
571 {
572 auto aIter = std::find_if(GetProperties().begin(), GetProperties().end(),
573 [&xPrMap, &nContextID](const XMLPropertyState& rProp) {
574 return rProp.mnIndex != -1 && xPrMap->GetEntryContextId(rProp.mnIndex) == nContextID;
575 });
576 if (aIter != GetProperties().end())
577 pRet = &(*aIter);
578 }
579 return pRet;
580 }
581
GetNumberFormat()582 sal_Int32 XMLTableStyleContext::GetNumberFormat()
583 {
584 if (nNumberFormat < 0 && !sDataStyleName.isEmpty())
585 {
586 const SvXMLNumFormatContext* pStyle = static_cast<const SvXMLNumFormatContext*>(
587 pStyles->FindStyleChildContext(XmlStyleFamily::DATA_STYLE, sDataStyleName, true));
588
589 if (!pStyle)
590 {
591 XMLTableStylesContext* pMyStyles = static_cast<XMLTableStylesContext*>(GetScImport().GetStyles());
592 if (pMyStyles)
593 pStyle = static_cast<const SvXMLNumFormatContext*>(
594 pMyStyles->FindStyleChildContext(XmlStyleFamily::DATA_STYLE, sDataStyleName, true));
595 else
596 {
597 OSL_FAIL("not possible to get style");
598 }
599 }
600 if (pStyle)
601 nNumberFormat = const_cast<SvXMLNumFormatContext*>(pStyle)->GetKey();
602 }
603 return nNumberFormat;
604 }
605
CreateStyleStyleChildContext(XmlStyleFamily nFamily,sal_Int32 nElement,const uno::Reference<xml::sax::XFastAttributeList> & xAttrList)606 SvXMLStyleContext *XMLTableStylesContext::CreateStyleStyleChildContext(
607 XmlStyleFamily nFamily, sal_Int32 nElement,
608 const uno::Reference< xml::sax::XFastAttributeList > & xAttrList )
609 {
610 SvXMLStyleContext *pStyle;
611 // use own wrapper for text and paragraph, to record style usage
612 if (nFamily == XmlStyleFamily::TEXT_PARAGRAPH || nFamily == XmlStyleFamily::TEXT_TEXT)
613 pStyle = new ScCellTextStyleContext( GetImport(),*this, nFamily );
614 else
615 pStyle = SvXMLStylesContext::CreateStyleStyleChildContext(
616 nFamily, nElement, xAttrList );
617
618 if (!pStyle)
619 {
620 switch( nFamily )
621 {
622 case XmlStyleFamily::TABLE_CELL:
623 case XmlStyleFamily::TABLE_COLUMN:
624 case XmlStyleFamily::TABLE_ROW:
625 case XmlStyleFamily::TABLE_TABLE:
626 pStyle = new XMLTableStyleContext( GetScImport(), *this, nFamily );
627 break;
628 default: break;
629 }
630 }
631
632 return pStyle;
633 }
634
CreateDefaultStyleStyleChildContext(XmlStyleFamily nFamily,sal_Int32 nElement,const uno::Reference<xml::sax::XFastAttributeList> & xAttrList)635 SvXMLStyleContext *XMLTableStylesContext::CreateDefaultStyleStyleChildContext(
636 XmlStyleFamily nFamily, sal_Int32 nElement,
637 const uno::Reference< xml::sax::XFastAttributeList > & xAttrList )
638 {
639 SvXMLStyleContext *pStyle(SvXMLStylesContext::CreateDefaultStyleStyleChildContext( nFamily, nElement,
640 xAttrList ));
641 if (!pStyle)
642 {
643 switch( nFamily )
644 {
645 case XmlStyleFamily::TABLE_CELL:
646 pStyle = new XMLTableStyleContext( GetScImport(), *this, nFamily, true);
647 break;
648 case XmlStyleFamily::SD_GRAPHICS_ID:
649 pStyle = new XMLGraphicsDefaultStyle( GetScImport(), *this);
650 break;
651 default: break;
652 }
653 }
654
655 return pStyle;
656 }
657
658 constexpr OUStringLiteral gsCellStyleServiceName(u"com.sun.star.style.CellStyle");
659 constexpr OUStringLiteral gsColumnStyleServiceName(u"" XML_STYLE_FAMILY_TABLE_COLUMN_STYLES_NAME);
660 constexpr OUStringLiteral gsRowStyleServiceName(u"" XML_STYLE_FAMILY_TABLE_ROW_STYLES_NAME);
661 constexpr OUStringLiteral gsTableStyleServiceName(u"" XML_STYLE_FAMILY_TABLE_TABLE_STYLES_NAME);
662
XMLTableStylesContext(SvXMLImport & rImport,const bool bTempAutoStyles)663 XMLTableStylesContext::XMLTableStylesContext( SvXMLImport& rImport,
664 const bool bTempAutoStyles )
665 : SvXMLStylesContext( rImport )
666 , nNumberFormatIndex(-1)
667 , nConditionalFormatIndex(-1)
668 , nCellStyleIndex(-1)
669 , nMasterPageNameIndex(-1)
670 , bAutoStyles(bTempAutoStyles)
671 {
672 }
673
~XMLTableStylesContext()674 XMLTableStylesContext::~XMLTableStylesContext()
675 {
676 }
677
endFastElement(sal_Int32)678 void XMLTableStylesContext::endFastElement(sal_Int32 )
679 {
680 if (bAutoStyles)
681 GetImport().GetTextImport()->SetAutoStyles( this );
682 else
683 GetScImport().InsertStyles();
684 }
685
686 rtl::Reference < SvXMLImportPropertyMapper >
GetImportPropertyMapper(XmlStyleFamily nFamily) const687 XMLTableStylesContext::GetImportPropertyMapper(
688 XmlStyleFamily nFamily ) const
689 {
690 rtl::Reference < SvXMLImportPropertyMapper > xMapper(SvXMLStylesContext::GetImportPropertyMapper(nFamily));
691
692 if (!xMapper.is())
693 {
694 switch( nFamily )
695 {
696 case XmlStyleFamily::TABLE_CELL:
697 {
698 if( !xCellImpPropMapper.is() )
699 {
700 const_cast<XMLTableStylesContext *>(this)->xCellImpPropMapper =
701 new ScXMLCellImportPropertyMapper( GetScImport().GetCellStylesPropertySetMapper(), const_cast<SvXMLImport&>(GetImport()) );
702 xCellImpPropMapper->ChainImportMapper(XMLTextImportHelper::CreateParaExtPropMapper(const_cast<SvXMLImport&>(GetImport())));
703 }
704 xMapper = xCellImpPropMapper;
705 }
706 break;
707 case XmlStyleFamily::TABLE_COLUMN:
708 {
709 if( !xColumnImpPropMapper.is() )
710 const_cast<XMLTableStylesContext *>(this)->xColumnImpPropMapper =
711 new SvXMLImportPropertyMapper( GetScImport().GetColumnStylesPropertySetMapper(), const_cast<SvXMLImport&>(GetImport()) );
712 xMapper = xColumnImpPropMapper;
713 }
714 break;
715 case XmlStyleFamily::TABLE_ROW:
716 {
717 if( !xRowImpPropMapper.is() )
718 const_cast<XMLTableStylesContext *>(this)->xRowImpPropMapper =
719 new ScXMLRowImportPropertyMapper( GetScImport().GetRowStylesPropertySetMapper(), const_cast<SvXMLImport&>(GetImport()) );
720 xMapper = xRowImpPropMapper;
721 }
722 break;
723 case XmlStyleFamily::TABLE_TABLE:
724 {
725 if( !xTableImpPropMapper.is() )
726 const_cast<XMLTableStylesContext *>(this)->xTableImpPropMapper =
727 new SvXMLImportPropertyMapper( GetScImport().GetTableStylesPropertySetMapper(), const_cast<SvXMLImport&>(GetImport()) );
728 xMapper = xTableImpPropMapper;
729 }
730 break;
731 default: break;
732 }
733 }
734
735 return xMapper;
736 }
737
738 uno::Reference < XNameContainer >
GetStylesContainer(XmlStyleFamily nFamily) const739 XMLTableStylesContext::GetStylesContainer( XmlStyleFamily nFamily ) const
740 {
741 uno::Reference < XNameContainer > xStyles(SvXMLStylesContext::GetStylesContainer(nFamily));
742 if (!xStyles.is())
743 {
744 OUString sName;
745 switch( nFamily )
746 {
747 case XmlStyleFamily::TABLE_TABLE:
748 {
749 if( xTableStyles.is() )
750 xStyles.set(xTableStyles);
751 else
752 sName = "TableStyles";
753 }
754 break;
755 case XmlStyleFamily::TABLE_CELL:
756 {
757 if( xCellStyles.is() )
758 xStyles.set(xCellStyles);
759 else
760 sName = "CellStyles";
761 }
762 break;
763 case XmlStyleFamily::TABLE_COLUMN:
764 {
765 if( xColumnStyles.is() )
766 xStyles.set(xColumnStyles);
767 else
768 sName = "ColumnStyles";
769 }
770 break;
771 case XmlStyleFamily::TABLE_ROW:
772 {
773 if( xRowStyles.is() )
774 xStyles.set(xRowStyles);
775 else
776 sName = "RowStyles";
777 }
778 break;
779 default: break;
780 }
781 if( !xStyles.is() && !sName.isEmpty() && GetScImport().GetModel().is() )
782 {
783 uno::Reference< XStyleFamiliesSupplier > xFamiliesSupp(
784 GetScImport().GetModel(), UNO_QUERY );
785 if (xFamiliesSupp.is())
786 {
787 uno::Reference< XNameAccess > xFamilies(xFamiliesSupp->getStyleFamilies());
788
789 try
790 {
791 xStyles.set(xFamilies->getByName( sName ), uno::UNO_QUERY);
792 }
793 catch ( uno::Exception& )
794 {
795 // #i97680# Named table/column/row styles aren't supported, getByName will throw an exception.
796 // For better interoperability, these styles should then be handled as automatic styles.
797 // For now, NULL is returned (and the style is ignored).
798 }
799 switch( nFamily )
800 {
801 case XmlStyleFamily::TABLE_TABLE:
802 const_cast<XMLTableStylesContext *>(this)->xTableStyles.set(xStyles);
803 break;
804 case XmlStyleFamily::TABLE_CELL:
805 const_cast<XMLTableStylesContext *>(this)->xCellStyles.set(xStyles);
806 break;
807 case XmlStyleFamily::TABLE_COLUMN:
808 const_cast<XMLTableStylesContext *>(this)->xColumnStyles.set(xStyles);
809 break;
810 case XmlStyleFamily::TABLE_ROW:
811 const_cast<XMLTableStylesContext *>(this)->xRowStyles.set(xStyles);
812 break;
813 default: break;
814 }
815 }
816 }
817 }
818
819 return xStyles;
820 }
821
GetServiceName(XmlStyleFamily nFamily) const822 OUString XMLTableStylesContext::GetServiceName( XmlStyleFamily nFamily ) const
823 {
824 OUString sServiceName(SvXMLStylesContext::GetServiceName(nFamily));
825 if (sServiceName.isEmpty())
826 {
827 switch( nFamily )
828 {
829 case XmlStyleFamily::TABLE_COLUMN:
830 sServiceName = gsColumnStyleServiceName;
831 break;
832 case XmlStyleFamily::TABLE_ROW:
833 sServiceName = gsRowStyleServiceName;
834 break;
835 case XmlStyleFamily::TABLE_CELL:
836 sServiceName = gsCellStyleServiceName;
837 break;
838 case XmlStyleFamily::TABLE_TABLE:
839 sServiceName = gsTableStyleServiceName;
840 break;
841 default: break;
842 }
843 }
844 return sServiceName;
845 }
846
GetIndex(const sal_Int16 nContextID)847 sal_Int32 XMLTableStylesContext::GetIndex(const sal_Int16 nContextID)
848 {
849 if (nContextID == CTF_SC_CELLSTYLE)
850 {
851 if (nCellStyleIndex == -1)
852 nCellStyleIndex =
853 GetImportPropertyMapper(XmlStyleFamily::TABLE_CELL)->getPropertySetMapper()->FindEntryIndex(nContextID);
854 return nCellStyleIndex;
855 }
856 else if (nContextID == CTF_SC_NUMBERFORMAT)
857 {
858 if (nNumberFormatIndex == -1)
859 nNumberFormatIndex =
860 GetImportPropertyMapper(XmlStyleFamily::TABLE_CELL)->getPropertySetMapper()->FindEntryIndex(nContextID);
861 return nNumberFormatIndex;
862 }
863 else if (nContextID == CTF_SC_IMPORT_MAP)
864 {
865 if (nConditionalFormatIndex == -1)
866 nConditionalFormatIndex =
867 GetImportPropertyMapper(XmlStyleFamily::TABLE_CELL)->getPropertySetMapper()->FindEntryIndex(nContextID);
868 return nConditionalFormatIndex;
869 }
870 else if (nContextID == CTF_SC_MASTERPAGENAME)
871 {
872 if (nMasterPageNameIndex == -1)
873 nMasterPageNameIndex =
874 GetImportPropertyMapper(XmlStyleFamily::TABLE_TABLE)->getPropertySetMapper()->FindEntryIndex(nContextID);
875 return nMasterPageNameIndex;
876 }
877 else
878 return -1;
879 }
880
881
InsertStyleFamily(XmlStyleFamily) const882 bool ScXMLMasterStylesContext::InsertStyleFamily( XmlStyleFamily ) const
883 {
884 return true;
885 }
886
ScXMLMasterStylesContext(SvXMLImport & rImport)887 ScXMLMasterStylesContext::ScXMLMasterStylesContext( SvXMLImport& rImport ) :
888 SvXMLStylesContext( rImport )
889 {
890 }
891
~ScXMLMasterStylesContext()892 ScXMLMasterStylesContext::~ScXMLMasterStylesContext()
893 {
894 }
895
CreateStyleChildContext(sal_Int32 nElement,const uno::Reference<XFastAttributeList> & xAttrList)896 SvXMLStyleContext *ScXMLMasterStylesContext::CreateStyleChildContext(
897 sal_Int32 nElement,
898 const uno::Reference< XFastAttributeList > & xAttrList )
899 {
900 SvXMLStyleContext *pContext(nullptr);
901
902 if( nElement == XML_ELEMENT(STYLE, XML_MASTER_PAGE) &&
903 InsertStyleFamily( XmlStyleFamily::MASTER_PAGE ) )
904 pContext = new ScMasterPageContext(
905 GetImport(), nElement, xAttrList,
906 !GetImport().GetTextImport()->IsInsertMode() );
907
908 // any other style will be ignored here!
909
910 return pContext;
911 }
912
CreateStyleStyleChildContext(XmlStyleFamily,sal_Int32,const uno::Reference<XFastAttributeList> &)913 SvXMLStyleContext *ScXMLMasterStylesContext::CreateStyleStyleChildContext(
914 XmlStyleFamily /* nFamily */,
915 sal_Int32 /* nElement */,
916 const uno::Reference< XFastAttributeList > & /* xAttrList */ )
917 {
918 return nullptr;
919 }
920
endFastElement(sal_Int32)921 void ScXMLMasterStylesContext::endFastElement(sal_Int32 )
922 {
923 FinishStyles(true);
924 }
925
926
ScMasterPageContext(SvXMLImport & rImport,sal_Int32 nElement,const uno::Reference<XFastAttributeList> & xAttrList,bool bOverwrite)927 ScMasterPageContext::ScMasterPageContext( SvXMLImport& rImport,
928 sal_Int32 nElement,
929 const uno::Reference< XFastAttributeList > & xAttrList,
930 bool bOverwrite ) :
931 XMLTextMasterPageContext( rImport, nElement, xAttrList, bOverwrite ),
932 bContainsRightHeader(false),
933 bContainsRightFooter(false)
934 {
935 }
936
~ScMasterPageContext()937 ScMasterPageContext::~ScMasterPageContext()
938 {
939 }
940
CreateHeaderFooterContext(sal_Int32 nElement,const css::uno::Reference<css::xml::sax::XFastAttributeList> & xAttrList,const bool bFooter,const bool bLeft,const bool bFirst)941 SvXMLImportContext *ScMasterPageContext::CreateHeaderFooterContext(
942 sal_Int32 nElement,
943 const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList,
944 const bool bFooter,
945 const bool bLeft,
946 const bool bFirst )
947 {
948 if (!bLeft)
949 {
950 if (bFooter)
951 bContainsRightFooter = true;
952 else
953 bContainsRightHeader = true;
954 }
955 if (!xPropSet.is())
956 xPropSet.set(GetStyle(), UNO_QUERY );
957 return new XMLTableHeaderFooterContext( GetImport(),
958 nElement,
959 xAttrList,
960 xPropSet,
961 bFooter, bLeft, bFirst );
962 }
963
ClearContent(const OUString & rContent)964 void ScMasterPageContext::ClearContent(const OUString& rContent)
965 {
966 if (!xPropSet.is())
967 xPropSet.set(GetStyle(), UNO_QUERY );
968
969 if (xPropSet.is())
970 {
971 uno::Reference < sheet::XHeaderFooterContent > xHeaderFooterContent(xPropSet->getPropertyValue( rContent ), uno::UNO_QUERY);
972 if (xHeaderFooterContent.is())
973 {
974 xHeaderFooterContent->getLeftText()->setString("");
975 xHeaderFooterContent->getCenterText()->setString("");
976 xHeaderFooterContent->getRightText()->setString("");
977 xPropSet->setPropertyValue( rContent, uno::makeAny(xHeaderFooterContent) );
978 }
979 }
980 }
981
Finish(bool bOverwrite)982 void ScMasterPageContext::Finish( bool bOverwrite )
983 {
984 XMLTextMasterPageContext::Finish(bOverwrite);
985 if (!bContainsRightFooter)
986 ClearContent(SC_UNO_PAGE_RIGHTFTRCON);
987 if (!bContainsRightHeader)
988 ClearContent(SC_UNO_PAGE_RIGHTHDRCON);
989 }
990
ScCellTextStyleContext(SvXMLImport & rImport,SvXMLStylesContext & rStyles,XmlStyleFamily nFamily)991 ScCellTextStyleContext::ScCellTextStyleContext( SvXMLImport& rImport,
992 SvXMLStylesContext& rStyles, XmlStyleFamily nFamily ) :
993 XMLTextStyleContext( rImport, rStyles, nFamily, false/*bDefaultStyle*/ ),
994 nLastSheet(-1)
995 {
996 }
997
~ScCellTextStyleContext()998 ScCellTextStyleContext::~ScCellTextStyleContext()
999 {
1000 }
1001
FillPropertySet(const uno::Reference<beans::XPropertySet> & xPropSet)1002 void ScCellTextStyleContext::FillPropertySet( const uno::Reference<beans::XPropertySet>& xPropSet )
1003 {
1004 XMLTextStyleContext::FillPropertySet( xPropSet );
1005
1006 ScXMLImport& rXMLImport = GetScImport();
1007
1008 ScCellTextCursor* pCellImp = comphelper::getUnoTunnelImplementation<ScCellTextCursor>( xPropSet );
1009 if (pCellImp)
1010 {
1011 ScAddress aPos = pCellImp->GetCellObj().GetPosition();
1012 if ( aPos.Tab() != nLastSheet )
1013 {
1014 ESelection aSel = pCellImp->GetSelection();
1015
1016 ScSheetSaveData* pSheetData = comphelper::getUnoTunnelImplementation<ScModelObj>(GetImport().GetModel())->GetSheetSaveData();
1017 pSheetData->AddTextStyle( GetName(), aPos, aSel );
1018
1019 nLastSheet = aPos.Tab();
1020 }
1021 }
1022 else if ( rXMLImport.GetTables().GetCurrentSheet() != nLastSheet )
1023 {
1024 ScDrawTextCursor* pDrawImp = comphelper::getUnoTunnelImplementation<ScDrawTextCursor>( xPropSet );
1025 if (pDrawImp)
1026 {
1027 XMLTableShapeImportHelper* pTableShapeImport = static_cast<XMLTableShapeImportHelper*>(GetScImport().GetShapeImport().get());
1028 ScXMLAnnotationContext* pAnnotationContext = pTableShapeImport->GetAnnotationContext();
1029 if (pAnnotationContext)
1030 {
1031 pAnnotationContext->AddContentStyle( GetFamily(), GetName(), pDrawImp->GetSelection() );
1032 nLastSheet = rXMLImport.GetTables().GetCurrentSheet();
1033 }
1034 }
1035
1036 // if it's a different shape, BlockSheet is called from XMLTableShapeImportHelper::finishShape
1037 // formatted text in page headers/footers can be ignored
1038 }
1039 }
1040
1041 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1042