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 <memory>
21 #include "docxattributeoutput.hxx"
22 #include "docxhelper.hxx"
23 #include "docxsdrexport.hxx"
24 #include "docxexportfilter.hxx"
25 #include "docxfootnotes.hxx"
26 #include "writerwordglue.hxx"
27 #include "ww8par.hxx"
28 #include <fmtcntnt.hxx>
29 #include <fmtftn.hxx>
30 #include <fchrfmt.hxx>
31 #include <tgrditem.hxx>
32 #include <fmtruby.hxx>
33 #include <fmtfollowtextflow.hxx>
34 #include <fmtanchr.hxx>
35 #include <breakit.hxx>
36 #include <redline.hxx>
37 #include <unocoll.hxx>
38 #include <unoframe.hxx>
39 #include <textboxhelper.hxx>
40 #include <rdfhelper.hxx>
41 #include "wrtww8.hxx"
42 
43 #include <comphelper/processfactory.hxx>
44 #include <comphelper/random.hxx>
45 #include <comphelper/string.hxx>
46 #include <comphelper/flagguard.hxx>
47 #include <comphelper/sequence.hxx>
48 #include <oox/token/namespaces.hxx>
49 #include <oox/token/tokens.hxx>
50 #include <oox/export/utils.hxx>
51 #include <oox/mathml/export.hxx>
52 #include <oox/drawingml/drawingmltypes.hxx>
53 #include <oox/token/relationship.hxx>
54 #include <oox/export/vmlexport.hxx>
55 #include <oox/ole/olehelper.hxx>
56 
57 #include <editeng/autokernitem.hxx>
58 #include <editeng/unoprnms.hxx>
59 #include <editeng/fontitem.hxx>
60 #include <editeng/tstpitem.hxx>
61 #include <editeng/spltitem.hxx>
62 #include <editeng/widwitem.hxx>
63 #include <editeng/shaditem.hxx>
64 #include <editeng/brushitem.hxx>
65 #include <editeng/postitem.hxx>
66 #include <editeng/wghtitem.hxx>
67 #include <editeng/kernitem.hxx>
68 #include <editeng/crossedoutitem.hxx>
69 #include <editeng/cmapitem.hxx>
70 #include <editeng/udlnitem.hxx>
71 #include <editeng/langitem.hxx>
72 #include <editeng/lspcitem.hxx>
73 #include <editeng/escapementitem.hxx>
74 #include <editeng/fhgtitem.hxx>
75 #include <editeng/colritem.hxx>
76 #include <editeng/hyphenzoneitem.hxx>
77 #include <editeng/ulspitem.hxx>
78 #include <editeng/contouritem.hxx>
79 #include <editeng/shdditem.hxx>
80 #include <editeng/emphasismarkitem.hxx>
81 #include <editeng/twolinesitem.hxx>
82 #include <editeng/charscaleitem.hxx>
83 #include <editeng/charrotateitem.hxx>
84 #include <editeng/charreliefitem.hxx>
85 #include <editeng/paravertalignitem.hxx>
86 #include <editeng/pgrditem.hxx>
87 #include <editeng/frmdiritem.hxx>
88 #include <editeng/blinkitem.hxx>
89 #include <editeng/charhiddenitem.hxx>
90 #include <editeng/editobj.hxx>
91 #include <editeng/keepitem.hxx>
92 #include <editeng/borderline.hxx>
93 #include <editeng/prntitem.hxx>
94 #include <sax/tools/converter.hxx>
95 #include <svx/xdef.hxx>
96 #include <svx/xfillit0.hxx>
97 #include <svx/xflclit.hxx>
98 #include <svx/xflgrit.hxx>
99 #include <svx/svdouno.hxx>
100 #include <svx/unobrushitemhelper.hxx>
101 #include <svl/grabbagitem.hxx>
102 #include <sfx2/sfxbasemodel.hxx>
103 #include <tools/date.hxx>
104 #include <tools/datetime.hxx>
105 #include <tools/datetimeutils.hxx>
106 #include <tools/UnitConversion.hxx>
107 #include <svl/whiter.hxx>
108 #include <rtl/tencinfo.h>
109 #include <sal/log.hxx>
110 #include <sot/exchange.hxx>
111 
112 #include <docufld.hxx>
113 #include <authfld.hxx>
114 #include <flddropdown.hxx>
115 #include <fmtclds.hxx>
116 #include <fmtinfmt.hxx>
117 #include <fmtrowsplt.hxx>
118 #include <fmtline.hxx>
119 #include <ftninfo.hxx>
120 #include <htmltbl.hxx>
121 #include <lineinfo.hxx>
122 #include <ndgrf.hxx>
123 #include <ndole.hxx>
124 #include <ndtxt.hxx>
125 #include <pagedesc.hxx>
126 #include <paratr.hxx>
127 #include <swmodule.hxx>
128 #include <swtable.hxx>
129 #include <txtftn.hxx>
130 #include <fmtautofmt.hxx>
131 #include <docsh.hxx>
132 #include <docary.hxx>
133 #include <fmtclbl.hxx>
134 #include <IDocumentSettingAccess.hxx>
135 #include <IDocumentRedlineAccess.hxx>
136 #include <grfatr.hxx>
137 #include <frmatr.hxx>
138 #include <txtatr.hxx>
139 #include <frameformats.hxx>
140 
141 #include <o3tl/string_view.hxx>
142 #include <o3tl/unit_conversion.hxx>
143 #include <osl/file.hxx>
144 #include <utility>
145 #include <vcl/embeddedfontshelper.hxx>
146 
147 #include <com/sun/star/i18n/ScriptType.hpp>
148 #include <com/sun/star/i18n/XBreakIterator.hpp>
149 #include <com/sun/star/chart2/XChartDocument.hpp>
150 #include <com/sun/star/drawing/ShadingPattern.hpp>
151 #include <com/sun/star/text/GraphicCrop.hpp>
152 #include <com/sun/star/embed/EmbedStates.hpp>
153 #include <com/sun/star/embed/Aspects.hpp>
154 
155 #include <algorithm>
156 #include <cstddef>
157 #include <stdarg.h>
158 #include <string_view>
159 
160 #include <toolkit/helper/vclunohelper.hxx>
161 #include <unicode/regex.h>
162 
163 using ::editeng::SvxBorderLine;
164 
165 using namespace oox;
166 using namespace docx;
167 using namespace sax_fastparser;
168 using namespace nsSwDocInfoSubType;
169 using namespace sw::util;
170 using namespace ::com::sun::star;
171 using namespace ::com::sun::star::drawing;
172 
173 const sal_Int32 Tag_StartParagraph_1 = 1;
174 const sal_Int32 Tag_StartParagraph_2 = 2;
175 const sal_Int32 Tag_WriteSdtBlock = 3;
176 const sal_Int32 Tag_StartParagraphProperties = 4;
177 const sal_Int32 Tag_InitCollectedParagraphProperties = 5;
178 const sal_Int32 Tag_StartRun_1 = 6;
179 const sal_Int32 Tag_StartRun_2 = 7;
180 const sal_Int32 Tag_StartRun_3 = 8;
181 const sal_Int32 Tag_EndRun_1 = 9;
182 const sal_Int32 Tag_EndRun_2 = 10;
183 const sal_Int32 Tag_StartRunProperties = 11;
184 const sal_Int32 Tag_InitCollectedRunProperties = 12;
185 const sal_Int32 Tag_Redline_1 = 13;
186 const sal_Int32 Tag_Redline_2 = 14;
187 const sal_Int32 Tag_TableDefinition = 15;
188 const sal_Int32 Tag_OutputFlyFrame = 16;
189 const sal_Int32 Tag_StartSection = 17;
190 
191 namespace {
192 
193 class FFDataWriterHelper
194 {
195     ::sax_fastparser::FSHelperPtr m_pSerializer;
196     void writeCommonStart( const OUString& rName,
197                            const OUString& rEntryMacro,
198                            const OUString& rExitMacro,
199                            const OUString& rHelp,
200                            const OUString& rHint )
201     {
202         m_pSerializer->startElementNS(XML_w, XML_ffData);
203         m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
204         m_pSerializer->singleElementNS(XML_w, XML_enabled);
205         m_pSerializer->singleElementNS(XML_w, XML_calcOnExit, FSNS(XML_w, XML_val), "0");
206 
207         if ( !rEntryMacro.isEmpty() )
208             m_pSerializer->singleElementNS( XML_w, XML_entryMacro,
209                 FSNS(XML_w, XML_val), rEntryMacro );
210 
211         if ( !rExitMacro.isEmpty() )
212             m_pSerializer->singleElementNS(XML_w, XML_exitMacro, FSNS(XML_w, XML_val), rExitMacro);
213 
214         if ( !rHelp.isEmpty() )
215             m_pSerializer->singleElementNS( XML_w, XML_helpText,
216                 FSNS(XML_w, XML_type), "text",
217                 FSNS(XML_w, XML_val), rHelp );
218 
219         if ( !rHint.isEmpty() )
220             m_pSerializer->singleElementNS( XML_w, XML_statusText,
221                 FSNS(XML_w, XML_type), "text",
222                 FSNS(XML_w, XML_val), rHint );
223 
224     }
225     void writeFinish()
226     {
227         m_pSerializer->endElementNS( XML_w, XML_ffData );
228     }
229 public:
230     explicit FFDataWriterHelper( ::sax_fastparser::FSHelperPtr  rSerializer ) : m_pSerializer(std::move( rSerializer )){}
231     void WriteFormCheckbox( const OUString& rName,
232                             const OUString& rEntryMacro,
233                             const OUString& rExitMacro,
234                             const OUString& rHelp,
235                             const OUString& rHint,
236                             bool bChecked )
237     {
238         writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
239         // Checkbox specific bits
240         m_pSerializer->startElementNS(XML_w, XML_checkBox);
241         // currently hardcoding autosize
242         // #TODO check if this defaulted
243         m_pSerializer->startElementNS(XML_w, XML_sizeAuto);
244         m_pSerializer->endElementNS( XML_w, XML_sizeAuto );
245         if ( bChecked )
246             m_pSerializer->singleElementNS(XML_w, XML_checked);
247         m_pSerializer->endElementNS( XML_w, XML_checkBox );
248         writeFinish();
249     }
250 
251     void WriteFormText(  const OUString& rName,
252                          const OUString& rEntryMacro,
253                          const OUString& rExitMacro,
254                          const OUString& rHelp,
255                          const OUString& rHint,
256                          const OUString& rType,
257                          const OUString& rDefaultText,
258                          sal_uInt16 nMaxLength,
259                          const OUString& rFormat )
260     {
261         writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
262 
263         m_pSerializer->startElementNS(XML_w, XML_textInput);
264         if ( !rType.isEmpty() )
265             m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), rType);
266         if ( !rDefaultText.isEmpty() )
267             m_pSerializer->singleElementNS(XML_w, XML_default, FSNS(XML_w, XML_val), rDefaultText);
268         if ( nMaxLength )
269             m_pSerializer->singleElementNS( XML_w, XML_maxLength,
270                 FSNS(XML_w, XML_val), OString::number(nMaxLength) );
271         if ( !rFormat.isEmpty() )
272             m_pSerializer->singleElementNS(XML_w, XML_format, FSNS(XML_w, XML_val), rFormat);
273         m_pSerializer->endElementNS( XML_w, XML_textInput );
274 
275         writeFinish();
276     }
277 };
278 
279 class FieldMarkParamsHelper
280 {
281     const sw::mark::IFieldmark& mrFieldmark;
282     public:
283     explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {}
284     OUString const & getName() const { return mrFieldmark.GetName(); }
285     template < typename T >
286     bool extractParam( const OUString& rKey, T& rResult )
287     {
288         bool bResult = false;
289         if ( mrFieldmark.GetParameters() )
290         {
291             sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey );
292             if ( it != mrFieldmark.GetParameters()->end() )
293                 bResult = ( it->second >>= rResult );
294         }
295         return bResult;
296     }
297 };
298 
299 // [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value)
300 OUString NumberToHexBinary(sal_Int32 n)
301 {
302     OUStringBuffer aBuf;
303     sax::Converter::convertNumberToHexBinary(aBuf, n);
304     return aBuf.makeStringAndClear();
305 }
306 
307 }
308 
309 void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
310 {
311     if (bIsRTL)
312         m_pSerializer->singleElementNS(XML_w, XML_rtl, FSNS(XML_w, XML_val), "true");
313 }
314 
315 /// Are multiple paragraphs disallowed inside this type of SDT?
316 static bool lcl_isOnelinerSdt(std::u16string_view rName)
317 {
318     return rName == u"Title" || rName == u"Subtitle" || rName == u"Company";
319 }
320 
321 static void AddToAttrList(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nAttrs, ...)
322 {
323     if (!pAttrList.is())
324         pAttrList = FastSerializerHelper::createAttrList();
325 
326     va_list args;
327     va_start(args, nAttrs);
328     for (sal_Int32 i = 0; i < nAttrs; i++)
329     {
330         sal_Int32 nName = va_arg(args, sal_Int32);
331         const char* pValue = va_arg(args, const char*);
332         if (pValue)
333             pAttrList->add(nName, pValue);
334     }
335     va_end(args);
336 }
337 
338 static void AddToAttrList(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, sal_Int32 nAttrName, const char* sAttrValue)
339 {
340     AddToAttrList(pAttrList, 1, nAttrName, sAttrValue);
341 }
342 
343 // write a floating table directly to docx without the surrounding frame
344 void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame)
345 {
346     const SwFrameFormat& rFrameFormat = pParentFrame->GetFrameFormat();
347     m_aFloatingTablesOfParagraph.insert(&rFrameFormat);
348     const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
349 
350     SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0);
351     SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
352 
353     //Save data here and restore when out of scope
354     ExportDataSaveRestore aDataGuard(GetExport(), nStt, nEnd, pParentFrame);
355 
356     // set a floatingTableFrame AND unset parent frame,
357     // otherwise exporter thinks we are still in a frame
358     m_rExport.SetFloatingTableFrame(pParentFrame);
359     m_rExport.m_pParentFrame = nullptr;
360 
361     GetExport().WriteText();
362 
363     m_rExport.SetFloatingTableFrame(nullptr);
364 }
365 
366 static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput)
367 {
368     const auto& rExport = rDocxAttributeOutput.GetExport();
369     // iterate though all SpzFrameFormats and check whether they are anchored to the current text node
370     for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; )
371     {
372         const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
373         const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
374         const SwPosition* pPosition = rAnchor.GetContentAnchor();
375 
376         if (!pPosition || ! rExport.m_pCurPam->GetNode().GetTextNode())
377             continue;
378 
379         if (pPosition->nNode != rExport.m_pCurPam->GetNode().GetTextNode()->GetIndex())
380             continue;
381 
382         const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx();
383         if (!pStartNode)
384             continue;
385 
386         SwNodeIndex aStartNode = *pStartNode;
387 
388         // go to the next node (actual content)
389         ++aStartNode;
390 
391         // this has to be a table
392         if (!aStartNode.GetNode().IsTableNode())
393             continue;
394 
395         // go to the end of the table
396         SwNodeOffset aEndIndex = aStartNode.GetNode().EndOfSectionIndex();
397         // go one deeper
398         aEndIndex++;
399         // this has to be the end of the content
400         if (aEndIndex != pFrameFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionIndex())
401             continue;
402 
403         // check for a grabBag and "TablePosition" attribute -> then we can export the table directly
404         SwTableNode* pTableNode = aStartNode.GetNode().GetTableNode();
405         SwTable& rTable = pTableNode->GetTable();
406         SwFrameFormat* pTableFormat = rTable.GetFrameFormat();
407         const SfxGrabBagItem* pTableGrabBag = pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG);
408         std::map<OUString, css::uno::Any> aTableGrabBag = pTableGrabBag->GetGrabBag();
409         // no grabbag?
410         if (aTableGrabBag.find("TablePosition") == aTableGrabBag.end())
411             continue;
412 
413         // write table to docx
414         ww8::Frame aFrame(*pFrameFormat,*pPosition);
415         rDocxAttributeOutput.WriteFloatingTable(&aFrame);
416     }
417 }
418 
419 sal_Int32 DocxAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo,
420                                               bool bGenerateParaId)
421 {
422     // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes.
423     // So a stack is needed to keep track of each paragraph's status separately.
424     // Complication: Word can't handle nested text boxes, so those need to be collected together.
425     if ( !m_aFramesOfParagraph.size() || !m_nTextFrameLevel )
426         m_aFramesOfParagraph.push(std::vector<ww8::Frame>());
427 
428     // look ahead for floating tables that were put into a frame during import
429     // floating tables in shapes are not supported: exclude this case
430     if (!pTextNodeInfo && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
431     {
432         checkAndWriteFloatingTables(*this);
433     }
434 
435     if ( m_nColBreakStatus == COLBRK_POSTPONE )
436         m_nColBreakStatus = COLBRK_WRITE;
437 
438     // Output table/table row/table cell starts if needed
439     if ( pTextNodeInfo )
440     {
441         // New cell/row?
442         if ( m_tableReference->m_nTableDepth > 0 && !m_tableReference->m_bTableCellOpen )
443         {
444             ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference->m_nTableDepth ) );
445             if ( pDeepInner->getCell() == 0 )
446                 StartTableRow( pDeepInner );
447 
448             const sal_uInt32 nCell = pDeepInner->getCell();
449             const sal_uInt32 nRow = pDeepInner->getRow();
450 
451             SyncNodelessCells(pDeepInner, nCell, nRow);
452             StartTableCell(pDeepInner, nCell, nRow);
453         }
454 
455         sal_uInt32 nRow = pTextNodeInfo->getRow();
456         sal_uInt32 nCell = pTextNodeInfo->getCell();
457         if (nCell == 0)
458         {
459             // Do we have to start the table?
460             // [If we are at the right depth already, it means that we
461             // continue the table cell]
462             sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth();
463 
464             if ( nCurrentDepth > m_tableReference->m_nTableDepth )
465             {
466                 // Start all the tables that begin here
467                 for ( sal_uInt32 nDepth = m_tableReference->m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth )
468                 {
469                     ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) );
470 
471                     StartTable( pInner );
472                     StartTableRow( pInner );
473 
474                     StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0);
475                 }
476 
477                 m_tableReference->m_nTableDepth = nCurrentDepth;
478             }
479         }
480     }
481 
482     // Look up the "sdt end before this paragraph" property early, when it
483     // would normally arrive, it would be too late (would be after the
484     // paragraph start has been written).
485     bool bEndParaSdt = false;
486     if (m_aParagraphSdt.m_bStartedSdt)
487     {
488         SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode();
489         if (pTextNode && pTextNode->GetpSwAttrSet())
490         {
491             const SfxItemSet* pSet = pTextNode->GetpSwAttrSet();
492             if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG))
493             {
494                 const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem);
495                 const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag();
496                 bEndParaSdt = m_aParagraphSdt.m_bStartedSdt && rMap.find("ParaSdtEndBefore") != rMap.end();
497             }
498         }
499     }
500     // TODO also avoid multiline paragraphs in those SDT types for shape text
501     bool bOneliner = m_aParagraphSdt.m_bStartedSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias);
502     if (bEndParaSdt || (m_aParagraphSdt.m_bStartedSdt && m_bHadSectPr) || bOneliner)
503     {
504         // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph.
505         m_aParagraphSdt.EndSdtBlock(m_pSerializer);
506         m_aStartedParagraphSdtPrAlias.clear();
507     }
508     m_bHadSectPr = false;
509 
510     // this mark is used to be able to enclose the paragraph inside a sdr tag.
511     // We will only know if we have to do that later.
512     m_pSerializer->mark(Tag_StartParagraph_1);
513 
514     std::optional<OUString> aParaId;
515     sal_Int32 nParaId = 0;
516     if (bGenerateParaId)
517     {
518         nParaId = m_nNextParaId++;
519         aParaId = NumberToHexBinary(nParaId);
520     }
521     m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
522 
523     // postpone the output of the run (we get it before the paragraph
524     // properties, but must write it after them)
525     m_pSerializer->mark(Tag_StartParagraph_2);
526 
527     // no section break in this paragraph yet; can be set in SectionBreak()
528     m_pSectionInfo.reset();
529 
530     m_bParagraphOpened = true;
531     m_bIsFirstParagraph = false;
532 
533     return nParaId;
534 }
535 
536 static OString convertToOOXMLVertOrient(sal_Int16 nOrient)
537 {
538     switch( nOrient )
539     {
540         case text::VertOrientation::CENTER:
541         case text::VertOrientation::LINE_CENTER:
542             return "center";
543         case text::VertOrientation::BOTTOM:
544             return "bottom";
545         case text::VertOrientation::LINE_BOTTOM:
546             return "outside";
547         case text::VertOrientation::TOP:
548             return "top";
549         case text::VertOrientation::LINE_TOP:
550             return "inside";
551         default:
552             return OString();
553     }
554 }
555 
556 static OString convertToOOXMLHoriOrient(sal_Int16 nOrient, bool bIsPosToggle)
557 {
558     switch( nOrient )
559     {
560         case text::HoriOrientation::LEFT:
561             return bIsPosToggle ? "inside" : "left";
562         case text::HoriOrientation::INSIDE:
563             return "inside";
564         case text::HoriOrientation::RIGHT:
565             return bIsPosToggle ? "outside" : "right";
566         case text::HoriOrientation::OUTSIDE:
567             return "outside";
568         case text::HoriOrientation::CENTER:
569         case text::HoriOrientation::FULL:
570             return "center";
571         default:
572             return OString();
573     }
574 }
575 
576 static OString convertToOOXMLVertOrientRel(sal_Int16 nOrientRel)
577 {
578     switch (nOrientRel)
579     {
580         case text::RelOrientation::PAGE_PRINT_AREA:
581             return "margin";
582         case text::RelOrientation::PAGE_FRAME:
583             return "page";
584         case text::RelOrientation::FRAME:
585         case text::RelOrientation::TEXT_LINE:
586         default:
587             return "text";
588     }
589 }
590 
591 static OString convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel)
592 {
593     switch (nOrientRel)
594     {
595         case text::RelOrientation::PAGE_PRINT_AREA:
596             return "margin";
597         case text::RelOrientation::PAGE_FRAME:
598             return "page";
599         case text::RelOrientation::CHAR:
600         case text::RelOrientation::PAGE_RIGHT:
601         case text::RelOrientation::FRAME:
602         default:
603             return "text";
604     }
605 }
606 
607 void SdtBlockHelper::DeleteAndResetTheLists()
608 {
609     if (m_pTokenChildren.is() )
610         m_pTokenChildren.clear();
611     if (m_pDataBindingAttrs.is() )
612         m_pDataBindingAttrs.clear();
613     if (m_pTextAttrs.is())
614         m_pTextAttrs.clear();
615     if (!m_aAlias.isEmpty())
616         m_aAlias.clear();
617     if (!m_aPlaceHolderDocPart.isEmpty())
618         m_aPlaceHolderDocPart.clear();
619     if (!m_aColor.isEmpty())
620         m_aColor.clear();
621     m_bHasId = false;
622 }
623 
624 void SdtBlockHelper::WriteSdtBlock(::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing)
625 {
626     if (m_nSdtPrToken <= 0 && !m_pDataBindingAttrs.is() && !m_bHasId)
627         return;
628 
629     // sdt start mark
630     pSerializer->mark(Tag_WriteSdtBlock);
631 
632     pSerializer->startElementNS(XML_w, XML_sdt);
633 
634     // output sdt properties
635     pSerializer->startElementNS(XML_w, XML_sdtPr);
636 
637     if (m_nSdtPrToken > 0 && m_pTokenChildren.is())
638     {
639         if (!m_pTokenAttributes.is())
640             pSerializer->startElement(m_nSdtPrToken);
641         else
642         {
643             rtl::Reference<FastAttributeList> xAttrList = std::move(m_pTokenAttributes);
644             pSerializer->startElement(m_nSdtPrToken, xAttrList);
645         }
646 
647         if (m_nSdtPrToken == FSNS(XML_w, XML_date) || m_nSdtPrToken == FSNS(XML_w, XML_docPartObj) || m_nSdtPrToken == FSNS(XML_w, XML_docPartList) || m_nSdtPrToken == FSNS(XML_w14, XML_checkbox)) {
648             const uno::Sequence<xml::FastAttribute> aChildren = m_pTokenChildren->getFastAttributes();
649             for (const auto& rChild : aChildren)
650                 pSerializer->singleElement(rChild.Token, FSNS(XML_w, XML_val), rChild.Value);
651         }
652 
653         pSerializer->endElement(m_nSdtPrToken);
654     }
655     else if ((m_nSdtPrToken > 0) && m_nSdtPrToken != FSNS(XML_w, XML_id) && !(bRunTextIsOn && bParagraphHasDrawing))
656     {
657         if (!m_pTokenAttributes.is())
658             pSerializer->singleElement(m_nSdtPrToken);
659         else
660         {
661             rtl::Reference<FastAttributeList> xAttrList = std::move(m_pTokenAttributes);
662             pSerializer->singleElement(m_nSdtPrToken, xAttrList);
663         }
664     }
665 
666     WriteExtraParams(pSerializer);
667 
668     pSerializer->endElementNS(XML_w, XML_sdtPr);
669 
670     // sdt contents start tag
671     pSerializer->startElementNS(XML_w, XML_sdtContent);
672 
673     // prepend the tags since the sdt start mark before the paragraph
674     pSerializer->mergeTopMarks(Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND);
675 
676     // write the ending tags after the paragraph
677     m_bStartedSdt = true;
678 
679     // clear sdt status
680     m_nSdtPrToken = 0;
681     m_pTokenChildren.clear();
682     m_pDataBindingAttrs.clear();
683     m_pTextAttrs.clear();
684     m_aAlias.clear();
685     m_bHasId = false;
686 }
687 
688 void SdtBlockHelper::WriteExtraParams(::sax_fastparser::FSHelperPtr& pSerializer)
689 {
690     if (m_nSdtPrToken == FSNS(XML_w, XML_id) || m_bHasId)
691         //Word won't open a document with an empty id tag, we fill it with a random number
692         pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
693             OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())));
694 
695     if (m_pDataBindingAttrs.is())
696     {
697         rtl::Reference<FastAttributeList> xAttrList = std::move(m_pDataBindingAttrs);
698         pSerializer->singleElementNS(XML_w, XML_dataBinding, xAttrList);
699     }
700 
701     if (m_pTextAttrs.is())
702     {
703         rtl::Reference<FastAttributeList> xAttrList = std::move(m_pTextAttrs);
704         pSerializer->singleElementNS(XML_w, XML_text, xAttrList);
705     }
706 
707     if (!m_aPlaceHolderDocPart.isEmpty())
708     {
709         pSerializer->startElementNS(XML_w, XML_placeholder);
710         pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), m_aPlaceHolderDocPart);
711         pSerializer->endElementNS(XML_w, XML_placeholder);
712     }
713     if (!m_aColor.isEmpty())
714     {
715         pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), m_aColor);
716     }
717 
718     if (!m_aAlias.isEmpty())
719         pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), m_aAlias);
720 }
721 
722 void SdtBlockHelper::EndSdtBlock(::sax_fastparser::FSHelperPtr& pSerializer)
723 {
724     pSerializer->endElementNS(XML_w, XML_sdtContent);
725     pSerializer->endElementNS(XML_w, XML_sdt);
726     m_bStartedSdt = false;
727 }
728 
729 void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
730 {
731     for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt)
732     {
733         if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
734         {
735             m_nSdtPrToken = FSNS(XML_w14, XML_checkbox);
736             uno::Sequence<beans::PropertyValue> aGrabBag;
737             aPropertyValue.Value >>= aGrabBag;
738             for (const auto& rProp : std::as_const(aGrabBag))
739             {
740                 OUString sValue = rProp.Value.get<OUString>();
741                 if (rProp.Name == "ooxml:CT_SdtCheckbox_checked")
742                     AddToAttrList(m_pTokenChildren,
743                         FSNS(XML_w14, XML_checked),
744                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
745                 else if (rProp.Name == "ooxml:CT_SdtCheckbox_checkedState")
746                     AddToAttrList(m_pTokenChildren,
747                         FSNS(XML_w14, XML_checkedState),
748                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
749                 else if (rProp.Name == "ooxml:CT_SdtCheckbox_uncheckedState")
750                     AddToAttrList(m_pTokenChildren,
751                         FSNS(XML_w14, XML_uncheckedState),
752                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
753             }
754         }
755         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pDataBindingAttrs.is())
756         {
757             uno::Sequence<beans::PropertyValue> aGrabBag;
758             aPropertyValue.Value >>= aGrabBag;
759             for (const auto& rProp : std::as_const(aGrabBag))
760             {
761                 OUString sValue = rProp.Value.get<OUString>();
762                 if (rProp.Name == "ooxml:CT_DataBinding_prefixMappings")
763                     AddToAttrList( m_pDataBindingAttrs,
764                                     FSNS( XML_w, XML_prefixMappings ),
765                                     OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
766                 else if (rProp.Name == "ooxml:CT_DataBinding_xpath")
767                     AddToAttrList( m_pDataBindingAttrs,
768                                     FSNS( XML_w, XML_xpath ),
769                                     OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
770                 else if (rProp.Name == "ooxml:CT_DataBinding_storeItemID")
771                     AddToAttrList( m_pDataBindingAttrs,
772                                     FSNS( XML_w, XML_storeItemID ),
773                                     OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr() );
774             }
775         }
776         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text")
777         {
778             uno::Sequence<beans::PropertyValue> aGrabBag;
779             aPropertyValue.Value >>= aGrabBag;
780             if (aGrabBag.hasElements())
781             {
782                 for (const auto& rProp : std::as_const(aGrabBag))
783                 {
784                     OUString sValue = rProp.Value.get<OUString>();
785                     if (rProp.Name == "ooxml:CT_SdtText_multiLine")
786                         AddToAttrList(m_pTextAttrs,
787                             FSNS(XML_w, XML_multiLine),
788                             OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
789                 }
790             }
791             else
792             {
793                 // We still have w:text, but no attrs
794                 m_nSdtPrToken = FSNS(XML_w, XML_text);
795             }
796         }
797         else if (aPropertyValue.Name == "ooxml:CT_SdtPlaceholder_docPart")
798         {
799             uno::Sequence<beans::PropertyValue> aGrabBag;
800             aPropertyValue.Value >>= aGrabBag;
801             for (const auto& rProp : std::as_const(aGrabBag))
802             {
803                 OUString sValue = rProp.Value.get<OUString>();
804                 if (rProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
805                     m_aPlaceHolderDocPart = sValue;
806             }
807         }
808         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_color")
809         {
810             uno::Sequence<beans::PropertyValue> aGrabBag;
811             aPropertyValue.Value >>= aGrabBag;
812             for (const auto& rProp : std::as_const(aGrabBag))
813             {
814                 OUString sValue = rProp.Value.get<OUString>();
815                 if (rProp.Name == "ooxml:CT_SdtColor_val")
816                     m_aColor = sValue;
817             }
818         }
819         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aAlias.isEmpty())
820         {
821             if (!(aPropertyValue.Value >>= m_aAlias))
822                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt alias value");
823         }
824         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
825             m_bHasId = true;
826         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
827             m_nSdtPrToken = FSNS(XML_w, XML_citation);
828         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" ||
829             aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
830         {
831             if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj")
832                 m_nSdtPrToken = FSNS(XML_w, XML_docPartObj);
833             else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
834                 m_nSdtPrToken = FSNS(XML_w, XML_docPartList);
835 
836             uno::Sequence<beans::PropertyValue> aGrabBag;
837             aPropertyValue.Value >>= aGrabBag;
838             for (const auto& rProp : std::as_const(aGrabBag))
839             {
840                 OUString sValue = rProp.Value.get<OUString>();
841                 if (rProp.Name == "ooxml:CT_SdtDocPart_docPartGallery")
842                     AddToAttrList(m_pTokenChildren,
843                         FSNS(XML_w, XML_docPartGallery),
844                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
845                 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartCategory")
846                     AddToAttrList(m_pTokenChildren,
847                         FSNS(XML_w, XML_docPartCategory),
848                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
849                 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartUnique")
850                 {
851                     if (sValue.isEmpty())
852                         sValue = "true";
853                     AddToAttrList(m_pTokenChildren, FSNS(XML_w, XML_docPartUnique),
854                         OUStringToOString(sValue, RTL_TEXTENCODING_UTF8).getStr());
855                 }
856             }
857         }
858         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation")
859             m_nSdtPrToken = FSNS(XML_w, XML_equation);
860         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture")
861             m_nSdtPrToken = FSNS(XML_w, XML_picture);
862         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group")
863             m_nSdtPrToken = FSNS(XML_w, XML_group);
864         else
865             SAL_WARN("sw.ww8", "GetSdtParamsFromGrabBag unhandled SdtPr grab bag property " << aPropertyValue.Name);
866     }
867 }
868 
869 void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize)
870 {
871     rtl::Reference<sax_fastparser::FastAttributeList> attrList = FastSerializerHelper::createAttrList();
872 
873     awt::Point aPos(pFrameFormat->GetHoriOrient().GetPos(), pFrameFormat->GetVertOrient().GetPos());
874 
875     attrList->add( FSNS( XML_w, XML_w), OString::number(rSize.Width()));
876     attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height()));
877 
878     attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X));
879     attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y));
880 
881     sal_Int16 nLeft = pFrameFormat->GetLRSpace().GetLeft();
882     sal_Int16 nRight = pFrameFormat->GetLRSpace().GetRight();
883     sal_Int16 nUpper = pFrameFormat->GetULSpace().GetUpper();
884     sal_Int16 nLower = pFrameFormat->GetULSpace().GetLower();
885 
886     attrList->add(FSNS(XML_w, XML_hSpace), OString::number((nLeft + nRight) / 2));
887     attrList->add(FSNS(XML_w, XML_vSpace), OString::number((nUpper + nLower) / 2));
888 
889     OString relativeFromH = convertToOOXMLHoriOrientRel( pFrameFormat->GetHoriOrient().GetRelationOrient() );
890     OString relativeFromV = convertToOOXMLVertOrientRel( pFrameFormat->GetVertOrient().GetRelationOrient() );
891 
892     switch (pFrameFormat->GetSurround().GetValue())
893     {
894     case css::text::WrapTextMode_NONE:
895         attrList->add( FSNS( XML_w, XML_wrap), "notBeside");
896         break;
897     case css::text::WrapTextMode_DYNAMIC:
898         attrList->add(FSNS(XML_w, XML_wrap), "auto");
899         break;
900     case css::text::WrapTextMode_PARALLEL:
901     default:
902         attrList->add(FSNS(XML_w, XML_wrap), "around");
903         break;
904     }
905     attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV );
906     attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH );
907     attrList->add( FSNS( XML_w, XML_hRule), "exact");
908 
909     m_pSerializer->singleElementNS( XML_w, XML_framePr, attrList );
910 }
911 
912 bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat)
913 {
914     uno::Reference< drawing::XShape > xShape;
915     const SdrObject* pSdrObj = rFrameFormat.FindRealSdrObject();
916     if (pSdrObj)
917         xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
918     uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
919     uno::Reference< beans::XPropertySetInfo > xPropSetInfo;
920     if (xPropertySet.is())
921         xPropSetInfo = xPropertySet->getPropertySetInfo();
922     uno::Any aFrameProperties ;
923     if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
924     {
925         uno::Sequence< beans::PropertyValue > propList;
926         xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= propList;
927         auto pProp = std::find_if(std::cbegin(propList), std::cend(propList),
928             [](const beans::PropertyValue& rProp) { return rProp.Name == "ParaFrameProperties"; });
929         if (pProp != std::cend(propList))
930             aFrameProperties = pProp->Value;
931     }
932     bool bFrameProperties = false;
933     aFrameProperties >>= bFrameProperties;
934     return bFrameProperties;
935 }
936 
937 void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
938 {
939     // write the paragraph properties + the run, already in the correct order
940     m_pSerializer->mergeTopMarks(Tag_StartParagraph_2);
941     std::vector<  std::shared_ptr <ww8::Frame> > aFramePrTextbox;
942     // Write the anchored frame if any
943     // Word can't handle nested text boxes, so write them on the same level.
944     ++m_nTextFrameLevel;
945     if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
946     {
947         comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_aParagraphSdt.m_bStartedSdt, false);
948 
949         assert(!m_pPostponedCustomShape);
950         m_pPostponedCustomShape.reset(new std::vector<PostponedDrawing>);
951 
952         // The for loop can change the size of m_aFramesOfParagraph, so the max size cannot be set in stone before the loop.
953         size_t nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
954         for (size_t nIndex = 0; nIndex < nFrames; ++nIndex)
955         {
956             m_bParagraphFrameOpen = true;
957             ww8::Frame aFrame = m_aFramesOfParagraph.top()[nIndex];
958             const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat();
959 
960             if (!TextBoxIsFramePr(rFrameFormat) || m_bWritingHeaderFooter)
961             {
962                 if (m_aRunSdt.m_bStartedSdt)
963                 {
964                     // Run-level SDT still open? Close it before AlternateContent.
965                     m_aRunSdt.EndSdtBlock(m_pSerializer);
966                 }
967                 m_pSerializer->startElementNS(XML_w, XML_r);
968                 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
969                 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps");
970                 /**
971                     This is to avoid AlternateContent within another AlternateContent.
972                        So when Choice is Open, only write the DML Drawing instead of both DML
973                        and VML Drawing in another AlternateContent.
974                  **/
975                 SetAlternateContentChoiceOpen( true );
976                 /** Save the table info's before writing the shape
977                         as there might be a new table that might get
978                         spawned from within the VML & DML block and alter
979                         the contents.
980                 */
981                 ww8::WW8TableInfo::Pointer_t pOldTableInfo = m_rExport.m_pTableInfo;
982                 //Reset the table infos after saving.
983                 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
984 
985                 /** FDO#71834 :
986                        Save the table reference attributes before calling WriteDMLTextFrame,
987                        otherwise the StartParagraph function will use the previous existing
988                        table reference attributes since the variable is being shared.
989                 */
990                 {
991                     DocxTableExportContext aDMLTableExportContext(*this);
992                     m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++);
993                 }
994                 m_pSerializer->endElementNS(XML_mc, XML_Choice);
995                 SetAlternateContentChoiceOpen( false );
996 
997                 // Reset table infos, otherwise the depth of the cells will be incorrect,
998                 // in case the text frame had table(s) and we try to export the
999                 // same table second time.
1000                 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1001                 //reset the tableReference.
1002 
1003                 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
1004                 {
1005                     DocxTableExportContext aVMLTableExportContext(*this);
1006                     m_rExport.SdrExporter().writeVMLTextFrame(&aFrame);
1007                 }
1008                 m_rExport.m_pTableInfo = pOldTableInfo;
1009 
1010                 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
1011                 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
1012                 m_pSerializer->endElementNS( XML_w, XML_r );
1013                 m_bParagraphFrameOpen = false;
1014             }
1015             else
1016             {
1017                 std::shared_ptr<ww8::Frame> pFramePr = std::make_shared<ww8::Frame>(aFrame);
1018                 aFramePrTextbox.push_back(pFramePr);
1019             }
1020 
1021             nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1022         }
1023         if (!m_pPostponedCustomShape->empty())
1024         {
1025             m_pSerializer->startElementNS(XML_w, XML_r);
1026             WritePostponedCustomShape();
1027             m_pSerializer->endElementNS( XML_w, XML_r );
1028         }
1029         m_pPostponedCustomShape.reset();
1030 
1031         if ( m_aFramesOfParagraph.size() )
1032             m_aFramesOfParagraph.top().clear();
1033 
1034         if (!pTextNodeInfoInner)
1035         {
1036             // Ending a non-table paragraph, clear floating tables before paragraph.
1037             m_aFloatingTablesOfParagraph.clear();
1038         }
1039     }
1040 
1041     --m_nTextFrameLevel;
1042     if ( m_aFramesOfParagraph.size() && !m_nTextFrameLevel )
1043         m_aFramesOfParagraph.pop();
1044 
1045     /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed.
1046      * This is due to nested hyperlink tags. So close it before end of paragraph.
1047      */
1048     if(m_nHyperLinkCount > 0)
1049     {
1050         for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount; ++nHyperLinkToClose)
1051             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1052         m_nHyperLinkCount = 0;
1053     }
1054 
1055     if (m_aRunSdt.m_bStartedSdt)
1056     {
1057         // Run-level SDT still open? Close it now.
1058         m_aRunSdt.EndSdtBlock(m_pSerializer);
1059     }
1060 
1061     if (m_bPageBreakAfter)
1062     {
1063         // tdf#128889 Trailing page break
1064         SectionBreak(msword::PageBreak, false);
1065         m_bPageBreakAfter = false;
1066     }
1067 
1068     m_pSerializer->endElementNS( XML_w, XML_p );
1069     // on export sdt blocks are never nested ATM
1070     if (!m_bAnchorLinkedToNode && !m_aParagraphSdt.m_bStartedSdt)
1071     {
1072         m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
1073 
1074         if (m_aParagraphSdt.m_bStartedSdt)
1075         {
1076             if (m_tableReference->m_bTableCellOpen)
1077                 m_tableReference->m_bTableCellParaSdtOpen = true;
1078             if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1079                 m_rExport.SdrExporter().setParagraphSdtOpen(true);
1080         }
1081     }
1082     else
1083     {
1084         //These should be written out to the actual Node and not to the anchor.
1085         //Clear them as they will be repopulated when the node is processed.
1086         m_aParagraphSdt.m_nSdtPrToken = 0;
1087         m_aParagraphSdt.m_bHasId = false;
1088         m_aParagraphSdt.DeleteAndResetTheLists();
1089     }
1090 
1091     m_pSerializer->mark(Tag_StartParagraph_2);
1092 
1093     // Write framePr
1094     for ( const auto & pFrame : aFramePrTextbox )
1095     {
1096         DocxTableExportContext aTableExportContext(*this);
1097         m_pCurrentFrame = pFrame.get();
1098         m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get());
1099         m_pCurrentFrame = nullptr;
1100     }
1101 
1102     m_pSerializer->mergeTopMarks(Tag_StartParagraph_2, sax_fastparser::MergeMarks::PREPEND);
1103 
1104     //sdtcontent is written so Set m_bParagraphHasDrawing to false
1105     m_rExport.SdrExporter().setParagraphHasDrawing(false);
1106     m_bRunTextIsOn = false;
1107     m_pSerializer->mergeTopMarks(Tag_StartParagraph_1);
1108 
1109     aFramePrTextbox.clear();
1110     // Check for end of cell, rows, tables here
1111     FinishTableRowCell( pTextNodeInfoInner );
1112 
1113     if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1114         m_bParagraphOpened = false;
1115 
1116     // Clear bookmarks of the current paragraph
1117     m_aBookmarksOfParagraphStart.clear();
1118     m_aBookmarksOfParagraphEnd.clear();
1119 }
1120 
1121 #define MAX_CELL_IN_WORD 62
1122 
1123 void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow)
1124 {
1125     sal_Int32 nOpenCell = lastOpenCell.back();
1126     if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD)
1127         EndTableCell(nOpenCell);
1128 
1129     sal_Int32 nClosedCell = lastClosedCell.back();
1130     for (sal_Int32 i = nClosedCell+1; i < nCell; ++i)
1131     {
1132         if (i >= MAX_CELL_IN_WORD)
1133             break;
1134 
1135         if (i == 0)
1136             StartTableRow(pInner);
1137 
1138         StartTableCell(pInner, i, nRow);
1139         m_pSerializer->singleElementNS(XML_w, XML_p);
1140         EndTableCell(i);
1141     }
1142 }
1143 
1144 void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph )
1145 {
1146     if ( !pInner )
1147         return;
1148 
1149     // Where are we in the table
1150     sal_uInt32 nRow = pInner->getRow();
1151     sal_Int32 nCell = pInner->getCell();
1152 
1153     InitTableHelper( pInner );
1154 
1155     // HACK
1156     // msoffice seems to have an internal limitation of 63 columns for tables
1157     // and refuses to load .docx with more, even though the spec seems to allow that;
1158     // so simply if there are more columns, don't close the last one msoffice will handle
1159     // and merge the contents of the remaining ones into it (since we don't close the cell
1160     // here, following ones will not be opened)
1161     const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine());
1162     const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround;
1163     const bool bEndRow = pInner->isEndOfLine();
1164 
1165     if (bEndCell)
1166     {
1167         while (pInner->getDepth() < m_tableReference->m_nTableDepth)
1168         {
1169             //we expect that the higher depth row was closed, and
1170             //we are just missing the table close
1171             assert(lastOpenCell.back() == -1 && lastClosedCell.back() == -1);
1172             EndTable();
1173         }
1174 
1175         SyncNodelessCells(pInner, nCell, nRow);
1176 
1177         sal_Int32 nClosedCell = lastClosedCell.back();
1178         if (nCell == nClosedCell)
1179         {
1180             //Start missing trailing cell(s)
1181             ++nCell;
1182             StartTableCell(pInner, nCell, nRow);
1183 
1184             //Continue on missing next trailing cell(s)
1185             ww8::RowSpansPtr xRowSpans = pInner->getRowSpansOfRow();
1186             sal_Int32 nRemainingCells = xRowSpans->size() - nCell;
1187             for (sal_Int32 i = 1; i < nRemainingCells; ++i)
1188             {
1189                 if (bForceEmptyParagraph)
1190                 {
1191                     m_pSerializer->singleElementNS(XML_w, XML_p);
1192                 }
1193 
1194                 EndTableCell(nCell);
1195 
1196                 StartTableCell(pInner, nCell, nRow);
1197             }
1198         }
1199 
1200         if (bForceEmptyParagraph)
1201         {
1202             m_pSerializer->singleElementNS(XML_w, XML_p);
1203         }
1204 
1205         EndTableCell(nCell);
1206     }
1207 
1208     // This is a line end
1209     if (bEndRow)
1210         EndTableRow();
1211 
1212     // This is the end of the table
1213     if (pInner->isFinalEndOfLine())
1214         EndTable();
1215 }
1216 
1217 void DocxAttributeOutput::EmptyParagraph()
1218 {
1219     m_pSerializer->singleElementNS(XML_w, XML_p);
1220 }
1221 
1222 void DocxAttributeOutput::SectionBreaks(const SwNode& rNode)
1223 {
1224     // output page/section breaks
1225     // Writer can have them at the beginning of a paragraph, or at the end, but
1226     // in docx, we have to output them in the paragraph properties of the last
1227     // paragraph in a section.  To get it right, we have to switch to the next
1228     // paragraph, and detect the section breaks there.
1229     SwNodeIndex aNextIndex( rNode, 1 );
1230 
1231     if (rNode.IsTextNode() || rNode.IsSectionNode())
1232     {
1233         if (aNextIndex.GetNode().IsTextNode())
1234         {
1235             const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode());
1236             m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen);
1237         }
1238         else if (aNextIndex.GetNode().IsTableNode())
1239         {
1240             const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1241             const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1242             m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1243         }
1244     }
1245     else if (rNode.IsEndNode())
1246     {
1247         if (aNextIndex.GetNode().IsTextNode())
1248         {
1249             // Handle section break between a table and a text node following it.
1250             // Also handle section endings
1251             const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode();
1252             if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode())
1253                 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen);
1254         }
1255         else if (aNextIndex.GetNode().IsTableNode())
1256         {
1257             // Handle section break between tables.
1258             const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1259             const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1260             m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1261         }
1262     }
1263 }
1264 
1265 void DocxAttributeOutput::StartParagraphProperties()
1266 {
1267     m_pSerializer->mark(Tag_StartParagraphProperties);
1268 
1269     m_pSerializer->startElementNS(XML_w, XML_pPr);
1270 
1271     // and output the section break now (if it appeared)
1272     if (m_pSectionInfo && m_rExport.m_nTextTyp == TXT_MAINTEXT)
1273     {
1274         m_rExport.SectionProperties( *m_pSectionInfo );
1275         m_pSectionInfo.reset();
1276     }
1277 
1278     InitCollectedParagraphProperties();
1279 }
1280 
1281 void DocxAttributeOutput::InitCollectedParagraphProperties()
1282 {
1283     m_pParagraphSpacingAttrList.clear();
1284 
1285     // Write the elements in the spec order
1286     static const sal_Int32 aOrder[] =
1287     {
1288         FSNS( XML_w, XML_pStyle ),
1289         FSNS( XML_w, XML_keepNext ),
1290         FSNS( XML_w, XML_keepLines ),
1291         FSNS( XML_w, XML_pageBreakBefore ),
1292         FSNS( XML_w, XML_framePr ),
1293         FSNS( XML_w, XML_widowControl ),
1294         FSNS( XML_w, XML_numPr ),
1295         FSNS( XML_w, XML_suppressLineNumbers ),
1296         FSNS( XML_w, XML_pBdr ),
1297         FSNS( XML_w, XML_shd ),
1298         FSNS( XML_w, XML_tabs ),
1299         FSNS( XML_w, XML_suppressAutoHyphens ),
1300         FSNS( XML_w, XML_kinsoku ),
1301         FSNS( XML_w, XML_wordWrap ),
1302         FSNS( XML_w, XML_overflowPunct ),
1303         FSNS( XML_w, XML_topLinePunct ),
1304         FSNS( XML_w, XML_autoSpaceDE ),
1305         FSNS( XML_w, XML_autoSpaceDN ),
1306         FSNS( XML_w, XML_bidi ),
1307         FSNS( XML_w, XML_adjustRightInd ),
1308         FSNS( XML_w, XML_snapToGrid ),
1309         FSNS( XML_w, XML_spacing ),
1310         FSNS( XML_w, XML_ind ),
1311         FSNS( XML_w, XML_contextualSpacing ),
1312         FSNS( XML_w, XML_mirrorIndents ),
1313         FSNS( XML_w, XML_suppressOverlap ),
1314         FSNS( XML_w, XML_jc ),
1315         FSNS( XML_w, XML_textDirection ),
1316         FSNS( XML_w, XML_textAlignment ),
1317         FSNS( XML_w, XML_textboxTightWrap ),
1318         FSNS( XML_w, XML_outlineLvl ),
1319         FSNS( XML_w, XML_divId ),
1320         FSNS( XML_w, XML_cnfStyle ),
1321         FSNS( XML_w, XML_rPr ),
1322         FSNS( XML_w, XML_sectPr ),
1323         FSNS( XML_w, XML_pPrChange )
1324     };
1325 
1326     // postpone the output so that we can later [in EndParagraphProperties()]
1327     // prepend the properties before the run
1328     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
1329     m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder));
1330 }
1331 
1332 void DocxAttributeOutput::WriteCollectedParagraphProperties()
1333 {
1334     if ( m_rExport.SdrExporter().getFlyAttrList().is() )
1335     {
1336         rtl::Reference<FastAttributeList> xAttrList( m_rExport.SdrExporter().getFlyAttrList() );
1337         m_rExport.SdrExporter().getFlyAttrList().clear();
1338 
1339         m_pSerializer->singleElementNS( XML_w, XML_framePr, xAttrList );
1340     }
1341 
1342     if ( m_pParagraphSpacingAttrList.is() )
1343     {
1344         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pParagraphSpacingAttrList );
1345         m_pSerializer->singleElementNS( XML_w, XML_spacing, xAttrList );
1346     }
1347 
1348     if ( m_pBackgroundAttrList.is() )
1349     {
1350         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pBackgroundAttrList );
1351         m_pSerializer->singleElementNS( XML_w, XML_shd, xAttrList );
1352     }
1353 }
1354 
1355 namespace
1356 {
1357 
1358 /// Outputs an item set, that contains the formatting of the paragraph marker.
1359 void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties)
1360 {
1361     const SfxItemSet* pOldI = rAttributeOutput.GetExport().GetCurItemSet();
1362     rAttributeOutput.GetExport().SetCurItemSet(&rParagraphMarkerProperties);
1363 
1364     SfxWhichIter aIter(rParagraphMarkerProperties);
1365     sal_uInt16 nWhichId = aIter.FirstWhich();
1366     const SfxPoolItem* pItem = nullptr;
1367     // Did we already produce a <w:sz> element?
1368     bool bFontSizeWritten = false;
1369     while (nWhichId)
1370     {
1371         if (rParagraphMarkerProperties.GetItemState(nWhichId, true, &pItem) == SfxItemState::SET)
1372         {
1373             if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT)
1374             {
1375                 // Will this item produce a <w:sz> element?
1376                 bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE;
1377                 if (!bFontSizeWritten || !bFontSizeItem)
1378                     rAttributeOutput.OutputItem(*pItem);
1379                 if (bFontSizeItem)
1380                     bFontSizeWritten = true;
1381             }
1382             else if (nWhichId == RES_TXTATR_AUTOFMT)
1383             {
1384                 const SwFormatAutoFormat* pAutoFormat = static_cast<const SwFormatAutoFormat*>(pItem);
1385                 lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle());
1386             }
1387         }
1388         nWhichId = aIter.NextWhich();
1389     }
1390     rAttributeOutput.GetExport().SetCurItemSet(pOldI);
1391 }
1392 
1393 const char *RubyAlignValues[] =
1394 {
1395     "center",
1396     "distributeLetter",
1397     "distributeSpace",
1398     "left",
1399     "right",
1400     "rightVertical"
1401 };
1402 
1403 
1404 const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)
1405 {
1406     const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues);
1407     if ( nJC >=0 && nJC < nElements )
1408         return RubyAlignValues[nJC];
1409     return RubyAlignValues[0];
1410 }
1411 
1412 }
1413 
1414 void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted)
1415 {
1416     // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties.
1417     // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
1418 
1419     // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline().
1420     // As there will be another pPr for redline and LO might mix both.
1421     if(pRedlineData)
1422         WriteCollectedParagraphProperties();
1423     Redline( pRedlineData );
1424 
1425     WriteCollectedParagraphProperties();
1426 
1427     // Merge the marks for the ordered elements
1428     m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
1429 
1430     // Write 'Paragraph Mark' properties
1431     m_pSerializer->startElementNS(XML_w, XML_rPr);
1432     // mark() before paragraph mark properties child elements.
1433     InitCollectedRunProperties();
1434 
1435     // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
1436     // that should be collected by different properties in the core, and are all flushed together
1437     // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
1438     // So we need to store the current status of these lists, so that we can revert back to them when
1439     // we are done exporting the redline attributes.
1440     rtl::Reference<sax_fastparser::FastAttributeList> pFontsAttrList_Original(m_pFontsAttrList);
1441     m_pFontsAttrList.clear();
1442     rtl::Reference<sax_fastparser::FastAttributeList> pEastAsianLayoutAttrList_Original(m_pEastAsianLayoutAttrList);
1443     m_pEastAsianLayoutAttrList.clear();
1444     rtl::Reference<sax_fastparser::FastAttributeList> pCharLangAttrList_Original(m_pCharLangAttrList);
1445     m_pCharLangAttrList.clear();
1446 
1447     lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties);
1448 
1449     // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1450     WriteCollectedRunProperties();
1451 
1452     // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1453     m_pFontsAttrList = pFontsAttrList_Original.get();
1454     m_pEastAsianLayoutAttrList = pEastAsianLayoutAttrList_Original.get();
1455     m_pCharLangAttrList = pCharLangAttrList_Original.get();
1456 
1457     if ( pRedlineParagraphMarkerDeleted )
1458     {
1459         StartRedline( pRedlineParagraphMarkerDeleted );
1460         EndRedline( pRedlineParagraphMarkerDeleted );
1461     }
1462     if ( pRedlineParagraphMarkerInserted )
1463     {
1464         StartRedline( pRedlineParagraphMarkerInserted );
1465         EndRedline( pRedlineParagraphMarkerInserted );
1466     }
1467 
1468     // mergeTopMarks() after paragraph mark properties child elements.
1469     m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
1470     m_pSerializer->endElementNS( XML_w, XML_rPr );
1471 
1472     if (!m_bWritingHeaderFooter && m_pCurrentFrame)
1473     {
1474         const SwFrameFormat& rFrameFormat = m_pCurrentFrame->GetFrameFormat();
1475         const SvxBoxItem& rBox = rFrameFormat.GetBox();
1476         if (TextBoxIsFramePr(rFrameFormat))
1477         {
1478             const Size aSize = m_pCurrentFrame->GetSize();
1479             PopulateFrameProperties(&rFrameFormat, aSize);
1480             FormatBox(rBox);
1481         }
1482     }
1483 
1484     m_pSerializer->endElementNS( XML_w, XML_pPr );
1485 
1486     // RDF metadata for this text node.
1487     SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode();
1488     std::map<OUString, OUString> aStatements = SwRDFHelper::getTextNodeStatements("urn:bails", *pTextNode);
1489     if (!aStatements.empty())
1490     {
1491         m_pSerializer->startElementNS(XML_w, XML_smartTag,
1492                                       FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
1493                                       FSNS(XML_w, XML_element), "RDF");
1494         m_pSerializer->startElementNS(XML_w, XML_smartTagPr);
1495         for (const auto& rStatement : aStatements)
1496             m_pSerializer->singleElementNS(XML_w, XML_attr,
1497                                            FSNS(XML_w, XML_name), rStatement.first,
1498                                            FSNS(XML_w, XML_val), rStatement.second);
1499         m_pSerializer->endElementNS(XML_w, XML_smartTagPr);
1500         m_pSerializer->endElementNS(XML_w, XML_smartTag);
1501     }
1502 
1503     if ( m_nColBreakStatus == COLBRK_WRITE || m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE )
1504     {
1505         m_pSerializer->startElementNS(XML_w, XML_r);
1506         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "column");
1507         m_pSerializer->endElementNS( XML_w, XML_r );
1508 
1509         if ( m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE )
1510             m_nColBreakStatus = COLBRK_POSTPONE;
1511         else
1512             m_nColBreakStatus = COLBRK_NONE;
1513     }
1514 
1515     if ( m_bPostponedPageBreak && !m_bWritingHeaderFooter )
1516     {
1517         m_pSerializer->startElementNS(XML_w, XML_r);
1518         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
1519         m_pSerializer->endElementNS( XML_w, XML_r );
1520 
1521         m_bPostponedPageBreak = false;
1522     }
1523 
1524     // merge the properties _before_ the run (strictly speaking, just
1525     // after the start of the paragraph)
1526     m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND);
1527 }
1528 
1529 void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame )
1530 {
1531     m_nStateOfFlyFrame = nStateOfFlyFrame;
1532 }
1533 
1534 void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
1535 {
1536     m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
1537 }
1538 
1539 void DocxAttributeOutput::ResetFlyProcessingFlag()
1540 {
1541     m_bPostponedProcessingFly = false ;
1542 }
1543 
1544 bool DocxAttributeOutput::IsFlyProcessingPostponed()
1545 {
1546     return m_bPostponedProcessingFly;
1547 }
1548 
1549 void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
1550 {
1551     // Don't start redline data here, possibly there is a hyperlink later, and
1552     // that has to be started first.
1553     m_pRedlineData = pRedlineData;
1554 
1555     // this mark is used to be able to enclose the run inside a sdr tag.
1556     m_pSerializer->mark(Tag_StartRun_1);
1557 
1558     // postpone the output of the start of a run (there are elements that need
1559     // to be written before the start of the run, but we learn which they are
1560     // _inside_ of the run)
1561     m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start"
1562 
1563     // postpone the output of the text (we get it before the run properties,
1564     // but must write it after them)
1565     m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
1566 }
1567 
1568 void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, bool /*bLastRun*/)
1569 {
1570     int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
1571     // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
1572     if ( m_pHyperlinkAttrList.is() )
1573     {
1574         m_nFieldsInHyperlink = 0;
1575     }
1576 
1577     // Write field starts
1578     for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); )
1579     {
1580         // Add the fields starts for all but hyperlinks and TOCs
1581         if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN &&
1582             // it is not an input field with extra grabbag params (sdt field)
1583             (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))
1584             )
1585         {
1586             StartField_Impl( pNode, nPos, *pIt );
1587 
1588             // Remove the field from the stack if only the start has to be written
1589             // Unknown fields should be removed too
1590             if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) )
1591             {
1592                 pIt = m_Fields.erase( pIt );
1593                 continue;
1594             }
1595 
1596             if (m_startedHyperlink || m_pHyperlinkAttrList.is())
1597             {
1598                 ++m_nFieldsInHyperlink;
1599             }
1600         }
1601         ++pIt;
1602     }
1603 
1604     // write the run properties + the text, already in the correct order
1605     m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above
1606 
1607     // level down, to be able to prepend the actual run start attribute (just
1608     // before "postponed run start")
1609     m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start"
1610     bool bCloseEarlierSDT = false;
1611 
1612     if (m_bEndCharSdt)
1613     {
1614         // This is the common case: "close sdt before the current run" was requested by the next run.
1615 
1616         // if another sdt starts in this run, then wait
1617         // as closing the sdt now, might cause nesting of sdts
1618         if (m_aRunSdt.m_nSdtPrToken > 0)
1619             bCloseEarlierSDT = true;
1620         else
1621             m_aRunSdt.EndSdtBlock(m_pSerializer);
1622         m_bEndCharSdt = false;
1623     }
1624 
1625     if ( m_closeHyperlinkInPreviousRun )
1626     {
1627         if ( m_startedHyperlink )
1628         {
1629             for ( int i = 0; i < nFieldsInPrevHyperlink; i++ )
1630             {
1631                 // If fields begin before hyperlink then
1632                 // it should end before hyperlink close
1633                 EndField_Impl( pNode, nPos, m_Fields.back( ) );
1634                 m_Fields.pop_back();
1635             }
1636             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1637             m_startedHyperlink = false;
1638             m_endPageRef = false;
1639             m_nHyperLinkCount--;
1640         }
1641         m_closeHyperlinkInPreviousRun = false;
1642     }
1643 
1644     // Write the hyperlink and toc fields starts
1645     for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); )
1646     {
1647         // Add the fields starts for hyperlinks, TOCs and index marks
1648         if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN ||
1649             // InputField with extra grabbag params - it is sdt field
1650             (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())))
1651         {
1652             StartRedline( m_pRedlineData );
1653             StartField_Impl( pNode, nPos, *pIt, true );
1654             EndRedline( m_pRedlineData );
1655 
1656             if (m_startedHyperlink)
1657                 ++m_nFieldsInHyperlink;
1658 
1659             // Remove the field if no end needs to be written
1660             if (!pIt->bSep)
1661             {
1662                 pIt = m_Fields.erase( pIt );
1663                 continue;
1664             }
1665         }
1666         if (pIt->bSep && !pIt->pField)
1667         {
1668             // for TOXMark:
1669             // Word ignores bookmarks in field result that is empty;
1670             // work around this by writing bookmark into field command.
1671             if (!m_sFieldBkm.isEmpty())
1672             {
1673                 DoWriteBookmarkTagStart(m_sFieldBkm);
1674                 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
1675                 m_nNextBookmarkId++;
1676                 m_sFieldBkm.clear();
1677             }
1678             CmdEndField_Impl(pNode, nPos, true);
1679             // Remove the field if no end needs to be written
1680             if (!pIt->bClose)
1681             {
1682                 pIt = m_Fields.erase( pIt );
1683                 continue;
1684             }
1685         }
1686         ++pIt;
1687     }
1688 
1689     // Start the hyperlink after the fields separators or we would generate invalid file
1690     bool newStartedHyperlink(false);
1691     if ( m_pHyperlinkAttrList.is() )
1692     {
1693         // if we are ending a hyperlink and there's another one starting here,
1694         // don't do this, so that the fields are closed further down when
1695         // the end hyperlink is handled, which is more likely to put the end in
1696         // the right place, as far as i can tell (not very far in this muck)
1697         if (!m_closeHyperlinkInThisRun)
1698         {
1699             // end ToX fields that want to end _before_ starting the hyperlink
1700             for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
1701             {
1702                 if (it->bClose && !it->pField)
1703                 {
1704                     EndField_Impl( pNode, nPos, *it );
1705                     it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
1706                 }
1707                 else
1708                 {
1709                     ++it;
1710                 }
1711             }
1712         }
1713         newStartedHyperlink = true;
1714 
1715         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pHyperlinkAttrList );
1716 
1717         m_pSerializer->startElementNS( XML_w, XML_hyperlink, xAttrList );
1718         m_startedHyperlink = true;
1719         m_nHyperLinkCount++;
1720     }
1721 
1722     // if there is some redlining in the document, output it
1723     StartRedline( m_pRedlineData );
1724 
1725     // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks.
1726     // The same is applied for permission ranges.
1727     // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end.
1728     DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData);
1729     DoWriteBookmarksEnd(m_rBookmarksEnd);
1730     DoWritePermissionsStart();
1731     DoWriteAnnotationMarks();
1732 
1733     if( m_closeHyperlinkInThisRun && m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc"))
1734     {
1735         OUString sToken;
1736         m_pSerializer->startElementNS(XML_w, XML_r);
1737         m_pSerializer->startElementNS(XML_w, XML_rPr);
1738         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1739         m_pSerializer->endElementNS( XML_w, XML_rPr );
1740         m_pSerializer->startElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "begin");
1741         m_pSerializer->endElementNS( XML_w, XML_fldChar );
1742         m_pSerializer->endElementNS( XML_w, XML_r );
1743 
1744 
1745         m_pSerializer->startElementNS(XML_w, XML_r);
1746         m_pSerializer->startElementNS(XML_w, XML_rPr);
1747         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1748         m_pSerializer->endElementNS( XML_w, XML_rPr );
1749         sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph.
1750         DoWriteCmd( sToken );
1751         m_pSerializer->endElementNS( XML_w, XML_r );
1752 
1753         // Write the Field separator
1754         m_pSerializer->startElementNS(XML_w, XML_r);
1755         m_pSerializer->startElementNS(XML_w, XML_rPr);
1756         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1757         m_pSerializer->endElementNS( XML_w, XML_rPr );
1758         m_pSerializer->singleElementNS( XML_w, XML_fldChar,
1759                 FSNS( XML_w, XML_fldCharType ), "separate" );
1760         m_pSerializer->endElementNS( XML_w, XML_r );
1761         // At start of every "PAGEREF" field m_endPageRef value should be true.
1762         m_endPageRef = true;
1763     }
1764 
1765     DoWriteBookmarkStartIfExist(nPos);
1766 
1767     m_pSerializer->startElementNS(XML_w, XML_r);
1768     if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is())
1769     {
1770         RunText("\t") ;
1771     }
1772     m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above
1773 
1774     if ( !m_sRawText.isEmpty() )
1775     {
1776         RunText( m_sRawText );
1777         m_sRawText.clear();
1778     }
1779 
1780     // write the run start + the run content
1781     m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start"
1782     // append the actual run end
1783     m_pSerializer->endElementNS( XML_w, XML_r );
1784 
1785     // if there is some redlining in the document, output it
1786     // (except in the case of fields with multiple runs)
1787     EndRedline( m_pRedlineData );
1788 
1789     // enclose in a sdt block, if necessary: if one is already started, then don't do it for now
1790     // (so on export sdt blocks are never nested ATM)
1791     if ( !m_bAnchorLinkedToNode && !m_aRunSdt.m_bStartedSdt)
1792     {
1793         m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
1794     }
1795     else
1796     {
1797         //These should be written out to the actual Node and not to the anchor.
1798         //Clear them as they will be repopulated when the node is processed.
1799         m_aRunSdt.m_nSdtPrToken = 0;
1800         m_aRunSdt.DeleteAndResetTheLists();
1801     }
1802 
1803     if (bCloseEarlierSDT)
1804     {
1805         m_pSerializer->mark(Tag_EndRun_2);
1806         m_aRunSdt.EndSdtBlock(m_pSerializer);
1807         m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND);
1808     }
1809 
1810     m_pSerializer->mergeTopMarks(Tag_StartRun_1);
1811 
1812     // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission.
1813     DoWritePermissionsEnd();
1814 
1815     for (const auto& rpMath : m_aPostponedMaths)
1816         WritePostponedMath(rpMath.pMathObject, rpMath.nMathObjAlignment);
1817     m_aPostponedMaths.clear();
1818 
1819     for (const auto& rpControl : m_aPostponedFormControls)
1820         WritePostponedFormControl(rpControl);
1821     m_aPostponedFormControls.clear();
1822 
1823     WritePostponedActiveXControl(false);
1824 
1825     WritePendingPlaceholder();
1826 
1827     if ( !m_bWritingField )
1828     {
1829         m_pRedlineData = nullptr;
1830     }
1831 
1832     if ( m_closeHyperlinkInThisRun )
1833     {
1834         if ( m_startedHyperlink )
1835         {
1836             if( m_endPageRef )
1837             {
1838                 // Hyperlink is started and fldchar "end" needs to be written for PAGEREF
1839                 m_pSerializer->startElementNS(XML_w, XML_r);
1840                 m_pSerializer->startElementNS(XML_w, XML_rPr);
1841                 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1842                 m_pSerializer->endElementNS( XML_w, XML_rPr );
1843                 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
1844                         FSNS( XML_w, XML_fldCharType ), "end" );
1845                 m_pSerializer->endElementNS( XML_w, XML_r );
1846                 m_endPageRef = false;
1847                 m_hyperLinkAnchor.clear();
1848             }
1849             for ( int i = 0; i < m_nFieldsInHyperlink; i++ )
1850             {
1851                 // If fields begin after hyperlink start then
1852                 // it should end before hyperlink close
1853                 EndField_Impl( pNode, nPos, m_Fields.back( ) );
1854                 m_Fields.pop_back();
1855             }
1856             m_nFieldsInHyperlink = 0;
1857 
1858             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1859             m_startedHyperlink = false;
1860             m_nHyperLinkCount--;
1861         }
1862         m_closeHyperlinkInThisRun = false;
1863     }
1864 
1865     if (!newStartedHyperlink)
1866     {
1867         while ( m_Fields.begin() != m_Fields.end() )
1868         {
1869             EndField_Impl( pNode, nPos, m_Fields.front( ) );
1870             m_Fields.erase( m_Fields.begin( ) );
1871         }
1872         m_nFieldsInHyperlink = 0;
1873     }
1874 
1875     // end ToX fields
1876     for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
1877     {
1878         if (it->bClose && !it->pField)
1879         {
1880             EndField_Impl( pNode, nPos, *it );
1881             it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
1882         }
1883         else
1884         {
1885             ++it;
1886         }
1887     }
1888 
1889     if ( m_pRedlineData )
1890     {
1891         EndRedline( m_pRedlineData );
1892         m_pRedlineData = nullptr;
1893     }
1894 
1895     DoWriteBookmarksStart(m_rFinalBookmarksStart);
1896     DoWriteBookmarksEnd(m_rFinalBookmarksEnd);
1897     DoWriteBookmarkEndIfExist(nPos);
1898 }
1899 
1900 void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString & bookmarkName)
1901 {
1902     m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart,
1903         FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
1904         FSNS(XML_w, XML_name), BookmarkToWord(bookmarkName));
1905 }
1906 
1907 void DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId)
1908 {
1909     m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd,
1910         FSNS(XML_w, XML_id), OString::number(nId));
1911 }
1912 
1913 void DocxAttributeOutput::DoWriteMoveRangeTagStart(const OString & bookmarkName,
1914     bool bFrom, const SwRedlineData* pRedlineData)
1915 {
1916     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
1917     OString aDate( DateTimeToOString( pRedlineData->GetTimeStamp() ) );
1918 
1919     m_pSerializer->singleElementNS(XML_w, bFrom
1920                 ? XML_moveFromRangeStart
1921                 : XML_moveToRangeStart,
1922         FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
1923         FSNS(XML_w, XML_author ), OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8),
1924         FSNS(XML_w, XML_date ), aDate,
1925         FSNS(XML_w, XML_name), bookmarkName);
1926 }
1927 
1928 void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool bFrom)
1929 {
1930     m_pSerializer->singleElementNS(XML_w, bFrom
1931             ? XML_moveFromRangeEnd
1932             : XML_moveToRangeEnd,
1933         FSNS(XML_w, XML_id), OString::number(nId));
1934 }
1935 
1936 void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
1937 {
1938     auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
1939     for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
1940     {
1941         DoWriteBookmarkTagStart(aIter->second);
1942         m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId;
1943         m_sLastOpenedBookmark = OUStringToOString(BookmarkToWord(aIter->second), RTL_TEXTENCODING_UTF8);
1944         m_nNextBookmarkId++;
1945     }
1946 }
1947 
1948 void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
1949 {
1950     auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
1951     for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
1952     {
1953         // Get the id of the bookmark
1954         auto pPos = m_rOpenedBookmarksIds.find(aIter->second);
1955         if (pPos != m_rOpenedBookmarksIds.end())
1956         {
1957             // Output the bookmark
1958             DoWriteBookmarkTagEnd(pPos->second);
1959             m_rOpenedBookmarksIds.erase(aIter->second);
1960         }
1961     }
1962 }
1963 
1964 /// Write the start bookmarks
1965 void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData)
1966 {
1967     for (const OUString & bookmarkName : rStarts)
1968     {
1969         // Output the bookmark (including MoveBookmark of the tracked moving)
1970         bool bMove = false;
1971         bool bFrom = false;
1972         OString sBookmarkName = OUStringToOString(
1973                 BookmarkToWord(bookmarkName, &bMove, &bFrom), RTL_TEXTENCODING_UTF8);
1974         if ( bMove )
1975         {
1976             // TODO: redline data of MoveBookmark is restored from the first redline of the bookmark
1977             // range. But a later deletion within a tracked moving is still imported as plain
1978             // deletion, so check IsMoved() and skip the export of the tracked moving to avoid
1979             // export with bad author or date
1980             if ( pRedlineData && pRedlineData->IsMoved() )
1981                 DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData);
1982         }
1983         else
1984             DoWriteBookmarkTagStart(bookmarkName);
1985 
1986         m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
1987         m_sLastOpenedBookmark = sBookmarkName;
1988         m_nNextBookmarkId++;
1989     }
1990     rStarts.clear();
1991 }
1992 
1993 /// export the end bookmarks
1994 void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
1995 {
1996     for (const OUString & bookmarkName : rEnds)
1997     {
1998         // Get the id of the bookmark
1999         auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
2000 
2001         if (pPos != m_rOpenedBookmarksIds.end())
2002         {
2003             bool bMove = false;
2004             bool bFrom = false;
2005             BookmarkToWord(bookmarkName, &bMove, &bFrom);
2006             // Output the bookmark (including MoveBookmark of the tracked moving)
2007             if ( bMove )
2008                 DoWriteMoveRangeTagEnd(pPos->second, bFrom);
2009             else
2010                 DoWriteBookmarkTagEnd(pPos->second);
2011 
2012             m_rOpenedBookmarksIds.erase(bookmarkName);
2013         }
2014     }
2015     rEnds.clear();
2016 }
2017 
2018 // For construction of the special bookmark name template for permissions:
2019 // see, PermInsertPosition::createBookmarkName()
2020 //
2021 // Syntax:
2022 // - "permission-for-user:<permission-id>:<permission-user-name>"
2023 // - "permission-for-group:<permission-id>:<permission-group-name>"
2024 //
2025 void DocxAttributeOutput::DoWritePermissionTagStart(std::u16string_view permission)
2026 {
2027     std::u16string_view permissionIdAndName;
2028 
2029     if (o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName))
2030     {
2031         const std::size_t separatorIndex = permissionIdAndName.find(u':');
2032         assert(separatorIndex != std::u16string_view::npos);
2033         const std::u16string_view permissionId   = permissionIdAndName.substr(0, separatorIndex);
2034         const std::u16string_view permissionName = permissionIdAndName.substr(separatorIndex + 1);
2035 
2036         m_pSerializer->singleElementNS(XML_w, XML_permStart,
2037             FSNS(XML_w, XML_id), BookmarkToWord(OUString(permissionId)),
2038             FSNS(XML_w, XML_edGrp), BookmarkToWord(OUString(permissionName)));
2039     }
2040     else
2041     {
2042         auto const ok = o3tl::starts_with(
2043             permission, u"permission-for-user:", &permissionIdAndName);
2044         assert(ok); (void)ok;
2045         const std::size_t separatorIndex = permissionIdAndName.find(u':');
2046         assert(separatorIndex != std::u16string_view::npos);
2047         const std::u16string_view permissionId   = permissionIdAndName.substr(0, separatorIndex);
2048         const std::u16string_view permissionName = permissionIdAndName.substr(separatorIndex + 1);
2049 
2050         m_pSerializer->singleElementNS(XML_w, XML_permStart,
2051             FSNS(XML_w, XML_id), BookmarkToWord(OUString(permissionId)),
2052             FSNS(XML_w, XML_ed), BookmarkToWord(OUString(permissionName)));
2053     }
2054 }
2055 
2056 
2057 // For construction of the special bookmark name template for permissions:
2058 // see, PermInsertPosition::createBookmarkName()
2059 //
2060 // Syntax:
2061 // - "permission-for-user:<permission-id>:<permission-user-name>"
2062 // - "permission-for-group:<permission-id>:<permission-group-name>"
2063 //
2064 void DocxAttributeOutput::DoWritePermissionTagEnd(std::u16string_view permission)
2065 {
2066     std::u16string_view permissionIdAndName;
2067 
2068     auto const ok = o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName) ||
2069         o3tl::starts_with(permission, u"permission-for-user:", &permissionIdAndName);
2070     assert(ok); (void)ok;
2071 
2072     const std::size_t separatorIndex = permissionIdAndName.find(u':');
2073     assert(separatorIndex != std::u16string_view::npos);
2074     const std::u16string_view permissionId   = permissionIdAndName.substr(0, separatorIndex);
2075 
2076     m_pSerializer->singleElementNS(XML_w, XML_permEnd,
2077         FSNS(XML_w, XML_id), BookmarkToWord(OUString(permissionId)));
2078 }
2079 
2080 /// Write the start permissions
2081 void DocxAttributeOutput::DoWritePermissionsStart()
2082 {
2083     for (const OUString & permission : m_rPermissionsStart)
2084     {
2085         DoWritePermissionTagStart(permission);
2086     }
2087     m_rPermissionsStart.clear();
2088 }
2089 
2090 /// export the end permissions
2091 void DocxAttributeOutput::DoWritePermissionsEnd()
2092 {
2093     for (const OUString & permission : m_rPermissionsEnd)
2094     {
2095         DoWritePermissionTagEnd(permission);
2096     }
2097     m_rPermissionsEnd.clear();
2098 }
2099 
2100 void DocxAttributeOutput::DoWriteAnnotationMarks()
2101 {
2102     // Write the start annotation marks
2103     for ( const auto & rName : m_rAnnotationMarksStart )
2104     {
2105         // Output the annotation mark
2106         /* Ensure that the existing Annotation Marks are not overwritten
2107            as it causes discrepancy when DocxAttributeOutput::PostitField
2108            refers to this map & while mapping comment id's in document.xml &
2109            comment.xml.
2110         */
2111         if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) )
2112         {
2113             const sal_Int32 nId = m_nNextAnnotationMarkId++;
2114             m_rOpenedAnnotationMarksIds[rName] = nId;
2115             m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart,
2116                 FSNS( XML_w, XML_id ), OString::number(nId) );
2117             m_sLastOpenedAnnotationMark = rName;
2118         }
2119     }
2120     m_rAnnotationMarksStart.clear();
2121 
2122     // export the end annotation marks
2123     for ( const auto & rName : m_rAnnotationMarksEnd )
2124     {
2125         // Get the id of the annotation mark
2126         std::map< OString, sal_Int32 >::iterator pPos = m_rOpenedAnnotationMarksIds.find( rName );
2127         if ( pPos != m_rOpenedAnnotationMarksIds.end(  ) )
2128         {
2129             const sal_Int32 nId = ( *pPos ).second;
2130             m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd,
2131                 FSNS( XML_w, XML_id ), OString::number(nId) );
2132             m_rOpenedAnnotationMarksIds.erase( rName );
2133 
2134             m_pSerializer->startElementNS(XML_w, XML_r);
2135             m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ),
2136                                             OString::number(nId) );
2137             m_pSerializer->endElementNS(XML_w, XML_r);
2138         }
2139     }
2140     m_rAnnotationMarksEnd.clear();
2141 }
2142 
2143 void DocxAttributeOutput::WriteFFData(  const FieldInfos& rInfos )
2144 {
2145     const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark;
2146     FieldMarkParamsHelper params( rFieldmark );
2147 
2148     OUString sEntryMacro;
2149     params.extractParam("EntryMacro", sEntryMacro);
2150     OUString sExitMacro;
2151     params.extractParam("ExitMacro", sExitMacro);
2152     OUString sHelp;
2153     params.extractParam("Help", sHelp);
2154     OUString sHint;
2155     params.extractParam("Hint", sHint); // .docx StatusText
2156     if ( sHint.isEmpty() )
2157         params.extractParam("Description", sHint); // .doc StatusText
2158 
2159     if ( rInfos.eType == ww::eFORMDROPDOWN )
2160     {
2161         uno::Sequence< OUString> vListEntries;
2162         OUString sName, sSelected;
2163 
2164         params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries );
2165         if (vListEntries.getLength() > ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT)
2166             vListEntries = uno::Sequence< OUString>(vListEntries.getArray(), ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT);
2167 
2168         sName = params.getName();
2169         sal_Int32 nSelectedIndex = 0;
2170 
2171         if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) )
2172         {
2173             if (nSelectedIndex < vListEntries.getLength() )
2174                 sSelected = vListEntries[ nSelectedIndex ];
2175         }
2176 
2177         GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries );
2178     }
2179     else if ( rInfos.eType == ww::eFORMCHECKBOX )
2180     {
2181         OUString sName;
2182         bool bChecked = false;
2183 
2184         params.extractParam( ODF_FORMCHECKBOX_NAME, sName );
2185 
2186         const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::ICheckboxFieldmark*>(&rFieldmark);
2187         if ( pCheckboxFm && pCheckboxFm->IsChecked() )
2188             bChecked = true;
2189 
2190         FFDataWriterHelper ffdataOut( m_pSerializer );
2191         ffdataOut.WriteFormCheckbox( sName, sEntryMacro, sExitMacro, sHelp, sHint, bChecked );
2192     }
2193     else if ( rInfos.eType == ww::eFORMTEXT )
2194     {
2195         OUString sType;
2196         params.extractParam("Type", sType);
2197         OUString sDefaultText;
2198         params.extractParam("Content", sDefaultText);
2199         sal_uInt16 nMaxLength = 0;
2200         params.extractParam("MaxLength", nMaxLength);
2201         OUString sFormat;
2202         params.extractParam("Format", sFormat);
2203         FFDataWriterHelper ffdataOut( m_pSerializer );
2204         ffdataOut.WriteFormText( params.getName(), sEntryMacro, sExitMacro, sHelp, sHint,
2205                                  sType, sDefaultText, nMaxLength, sFormat );
2206     }
2207 }
2208 
2209 void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2210 {
2211     m_pSerializer->startElementNS(XML_w, XML_sdt);
2212     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2213 
2214     if(!sFullDate.isEmpty())
2215         m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sFullDate);
2216     else
2217         m_pSerializer->startElementNS(XML_w, XML_date);
2218 
2219     // Replace quotation mark used for marking static strings in date format
2220     OUString sDateFormat1 = sDateFormat.replaceAll("\"", "'");
2221     m_pSerializer->singleElementNS(XML_w, XML_dateFormat,
2222                                    FSNS(XML_w, XML_val), sDateFormat1);
2223     m_pSerializer->singleElementNS(XML_w, XML_lid,
2224                                    FSNS(XML_w, XML_val), sLang);
2225     m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
2226                                    FSNS(XML_w, XML_val), "dateTime");
2227     m_pSerializer->singleElementNS(XML_w, XML_calendar,
2228                                    FSNS(XML_w, XML_val), "gregorian");
2229     m_pSerializer->endElementNS(XML_w, XML_date);
2230 
2231     if (aGrabBagSdt.hasElements())
2232     {
2233         // There are some extra sdt parameters came from grab bag
2234         SdtBlockHelper aSdtBlock;
2235         aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2236         aSdtBlock.WriteExtraParams(m_pSerializer);
2237     }
2238 
2239     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2240 
2241     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2242 }
2243 
2244 void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2245 {
2246     m_pSerializer->startElementNS(XML_w, XML_sdt);
2247     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2248 
2249     if (aGrabBagSdt.hasElements())
2250     {
2251         // There are some extra sdt parameters came from grab bag
2252         SdtBlockHelper aSdtBlock;
2253         aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2254         aSdtBlock.WriteExtraParams(m_pSerializer);
2255 
2256         if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id))
2257         {
2258             // Write <w:text/> or whatsoever from grabbag
2259             m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken);
2260         }
2261 
2262         // Store databindings data for later writing to corresponding XMLs
2263         OUString sPrefixMapping, sXpath;
2264         for (const auto& rProp : std::as_const(aGrabBagSdt))
2265         {
2266             if (rProp.Name == "ooxml:CT_SdtPr_dataBinding")
2267             {
2268                 uno::Sequence<beans::PropertyValue> aDataBindingProps;
2269                 rProp.Value >>= aDataBindingProps;
2270                 for (const auto& rDBProp : std::as_const(aDataBindingProps))
2271                 {
2272                     if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings")
2273                         sPrefixMapping = rDBProp.Value.get<OUString>();
2274                     else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath")
2275                         sXpath = rDBProp.Value.get<OUString>();
2276                 }
2277             }
2278         }
2279 
2280         if (sXpath.getLength())
2281         {
2282             // Given xpath is sufficient
2283             m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue);
2284         }
2285     }
2286 
2287     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2288 
2289     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2290 }
2291 
2292 void DocxAttributeOutput::WriteSdtEnd()
2293 {
2294     m_pSerializer->endElementNS(XML_w, XML_sdtContent);
2295     m_pSerializer->endElementNS(XML_w, XML_sdt);
2296 }
2297 
2298 void DocxAttributeOutput::WriteSdtDropDownStart(
2299         std::u16string_view rName,
2300         OUString const& rSelected,
2301         uno::Sequence<OUString> const& rListItems)
2302 {
2303     m_pSerializer->startElementNS(XML_w, XML_sdt);
2304     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2305 
2306     m_pSerializer->singleElementNS(XML_w, XML_alias,
2307         FSNS(XML_w, XML_val), OUStringToOString(rName, RTL_TEXTENCODING_UTF8));
2308 
2309     sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2310     if (nId == -1)
2311     {
2312         nId = 0;
2313     }
2314 
2315     m_pSerializer->startElementNS(XML_w, XML_dropDownList,
2316             FSNS(XML_w, XML_lastValue), OString::number(nId));
2317 
2318     for (auto const& rItem : rListItems)
2319     {
2320         auto const item(OUStringToOString(rItem, RTL_TEXTENCODING_UTF8));
2321         m_pSerializer->singleElementNS(XML_w, XML_listItem,
2322                 FSNS(XML_w, XML_value), item,
2323                 FSNS(XML_w, XML_displayText), item);
2324     }
2325 
2326     m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2327     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2328 
2329     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2330 }
2331 
2332 void DocxAttributeOutput::WriteSdtDropDownEnd(OUString const& rSelected,
2333         uno::Sequence<OUString> const& rListItems)
2334 {
2335     // note: rSelected might be empty?
2336     sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2337     if (nId == -1)
2338     {
2339         nId = 0;
2340     }
2341 
2342     // the lastValue only identifies the entry in the list, also export
2343     // currently selected item's displayText as run content (if one exists)
2344     if (rListItems.size())
2345     {
2346         m_pSerializer->startElementNS(XML_w, XML_r);
2347         m_pSerializer->startElementNS(XML_w, XML_t);
2348         m_pSerializer->writeEscaped(rListItems[nId]);
2349         m_pSerializer->endElementNS(XML_w, XML_t);
2350         m_pSerializer->endElementNS(XML_w, XML_r);
2351     }
2352 
2353     WriteSdtEnd();
2354 }
2355 
2356 void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2357 {
2358     if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN )
2359     {
2360         // Expand unsupported fields
2361         RunText( rInfos.pField->GetFieldName() );
2362         return;
2363     }
2364     else if ( rInfos.eType == ww::eFORMDATE )
2365     {
2366         const sw::mark::IDateFieldmark& rFieldmark = dynamic_cast<const sw::mark::IDateFieldmark&>(*rInfos.pFieldmark);
2367         FieldMarkParamsHelper params(rFieldmark);
2368 
2369         OUString sFullDate;
2370         OUString sCurrentDate;
2371         params.extractParam( ODF_FORMDATE_CURRENTDATE, sCurrentDate );
2372         if(!sCurrentDate.isEmpty())
2373         {
2374             sFullDate = sCurrentDate + "T00:00:00Z";
2375         }
2376         else
2377         {
2378             std::pair<bool, double> aResult = rFieldmark.GetCurrentDate();
2379             if(aResult.first)
2380             {
2381                 sFullDate = rFieldmark.GetDateInStandardDateFormat(aResult.second) + "T00:00:00Z";
2382             }
2383         }
2384 
2385         OUString sDateFormat;
2386         params.extractParam( ODF_FORMDATE_DATEFORMAT, sDateFormat );
2387         OUString sLang;
2388         params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang );
2389 
2390         uno::Sequence<beans::PropertyValue> aSdtParams;
2391         params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams);
2392 
2393         WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams);
2394         return;
2395     }
2396     else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
2397     {
2398         assert(!rInfos.pFieldmark);
2399         SwDropDownField const& rField2(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
2400         WriteSdtDropDownStart(rField2.GetName(),
2401                 rField2.GetSelectedItem(),
2402                 rField2.GetItemSequence());
2403         return;
2404     }
2405     else if (rInfos.eType == ww::eFILLIN)
2406     {
2407         SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
2408         if (rField.getGrabBagParams().hasElements())
2409         {
2410             WriteSdtPlainText(rField.GetPar1(), rField.getGrabBagParams());
2411             m_sRawText = rField.GetPar1();  // Write field content also as a fallback
2412             return;
2413         }
2414     }
2415 
2416     if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands
2417     {
2418         if ( bWriteRun )
2419             m_pSerializer->startElementNS(XML_w, XML_r);
2420 
2421         if ( rInfos.eType == ww::eFORMDROPDOWN )
2422         {
2423             m_pSerializer->startElementNS( XML_w, XML_fldChar,
2424                 FSNS( XML_w, XML_fldCharType ), "begin" );
2425             assert( rInfos.pFieldmark && !rInfos.pField );
2426             WriteFFData(rInfos);
2427             m_pSerializer->endElementNS( XML_w, XML_fldChar );
2428 
2429             if ( bWriteRun )
2430                 m_pSerializer->endElementNS( XML_w, XML_r );
2431 
2432             CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2433         }
2434         else
2435         {
2436             // Write the field start
2437             if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD )
2438             {
2439                 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2440                     FSNS( XML_w, XML_fldCharType ), "begin",
2441                     FSNS( XML_w, XML_fldLock ), "true" );
2442             }
2443             else
2444             {
2445                 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2446                     FSNS( XML_w, XML_fldCharType ), "begin" );
2447             }
2448 
2449             if ( rInfos.pFieldmark )
2450                 WriteFFData(  rInfos );
2451 
2452             m_pSerializer->endElementNS( XML_w, XML_fldChar );
2453 
2454             if ( bWriteRun )
2455                 m_pSerializer->endElementNS( XML_w, XML_r );
2456 
2457             // The hyperlinks fields can't be expanded: the value is
2458             // normally in the text run
2459             if ( !rInfos.pField )
2460                 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2461             else
2462                 m_bWritingField = true;
2463         }
2464     }
2465 }
2466 
2467 void DocxAttributeOutput::DoWriteCmd( const OUString& rCmd )
2468 {
2469     OUString sCmd = rCmd.trim();
2470     if (sCmd.startsWith("SEQ"))
2471     {
2472         OUString sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\').trim();
2473         m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark);
2474     }
2475     // Write the Field command
2476     sal_Int32 nTextToken = XML_instrText;
2477     if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
2478         nTextToken = XML_delInstrText;
2479 
2480     m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
2481     m_pSerializer->writeEscaped( rCmd );
2482     m_pSerializer->endElementNS( XML_w, nTextToken );
2483 
2484 }
2485 
2486 void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2487 {
2488     // Write the Field instruction
2489     if ( bWriteRun )
2490     {
2491         bool bWriteCombChars(false);
2492         m_pSerializer->startElementNS(XML_w, XML_r);
2493 
2494         if (rInfos.eType == ww::eEQ)
2495             bWriteCombChars = true;
2496 
2497         DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars );
2498     }
2499 
2500     sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 };
2501     while ( nIdx >= 0 )
2502     {
2503         OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx );
2504         if ( rInfos.eType ==  ww::eCREATEDATE
2505           || rInfos.eType ==  ww::eSAVEDATE
2506           || rInfos.eType ==  ww::ePRINTDATE
2507           || rInfos.eType ==  ww::eDATE
2508           || rInfos.eType ==  ww::eTIME )
2509         {
2510            sToken = sToken.replaceAll("NNNN", "dddd");
2511            sToken = sToken.replaceAll("NN", "ddd");
2512         }
2513         else if ( rInfos.eType == ww::eEquals )
2514         {
2515             // Use original OOXML formula, if it exists and its conversion hasn't been changed
2516             bool bIsChanged = true;
2517             if ( pNode->GetTableBox() )
2518             {
2519                 if ( const SfxGrabBagItem* pItem = pNode->GetTableBox()->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG) )
2520                 {
2521                     OUString sActualFormula = sToken.trim();
2522                     const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
2523                     std::map<OUString, uno::Any>::const_iterator aStoredFormula = rGrabBag.find("CellFormulaConverted");
2524                     if ( aStoredFormula != rGrabBag.end() && sActualFormula.indexOf('=') == 0 &&
2525                                     sActualFormula.copy(1).trim() == aStoredFormula->second.get<OUString>().trim() )
2526                     {
2527                         aStoredFormula = rGrabBag.find("CellFormula");
2528                         if ( aStoredFormula != rGrabBag.end() )
2529                         {
2530                             sToken = " =" + aStoredFormula->second.get<OUString>();
2531                             bIsChanged = false;
2532                         }
2533                     }
2534                 }
2535             }
2536 
2537             if ( bIsChanged )
2538             {
2539                 UErrorCode nErr(U_ZERO_ERROR);
2540                 icu::UnicodeString sInput(sToken.getStr());
2541                 // remove < and > around cell references, e.g. <A1> to A1, <A1:B2> to A1:B2
2542                 icu::RegexMatcher aMatcher("<([A-Z]{1,3}[0-9]+(:[A-Z]{1,3}[0-9]+)?)>", sInput, 0, nErr);
2543                 sInput = aMatcher.replaceAll(icu::UnicodeString("$1"), nErr);
2544                 // convert MEAN to AVERAGE
2545                 icu::RegexMatcher aMatcher2("\\bMEAN\\b", sInput, UREGEX_CASE_INSENSITIVE, nErr);
2546                 sToken = aMatcher2.replaceAll(icu::UnicodeString("AVERAGE"), nErr).getTerminatedBuffer();
2547             }
2548         }
2549 
2550         // Write the Field command
2551         DoWriteCmd( sToken );
2552 
2553         // Replace tabs by </instrText><tab/><instrText>
2554         if ( nIdx > 0 ) // Is another token expected?
2555             RunText( "\t" );
2556     }
2557 
2558     if ( bWriteRun )
2559     {
2560         m_pSerializer->endElementNS( XML_w, XML_r );
2561     }
2562 }
2563 
2564 void DocxAttributeOutput::CmdEndField_Impl(SwTextNode const*const pNode,
2565         sal_Int32 const nPos, bool const bWriteRun)
2566 {
2567     // Write the Field separator
2568         if ( bWriteRun )
2569         {
2570             m_pSerializer->startElementNS(XML_w, XML_r);
2571             DoWriteFieldRunProperties( pNode, nPos );
2572         }
2573 
2574         m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2575               FSNS( XML_w, XML_fldCharType ), "separate" );
2576 
2577         if ( bWriteRun )
2578         {
2579             m_pSerializer->endElementNS( XML_w, XML_r );
2580         }
2581 }
2582 
2583 /// Writes properties for run that is used to separate field implementation.
2584 /// There are several runs are used:
2585 ///     <w:r>
2586 ///         <w:rPr>
2587 ///             <!-- properties written with StartRunProperties() / EndRunProperties().
2588 ///         </w:rPr>
2589 ///         <w:fldChar w:fldCharType="begin" />
2590 ///     </w:r>
2591 ///         <w:r>
2592 ///         <w:rPr>
2593 ///             <!-- properties written with DoWriteFieldRunProperties()
2594 ///         </w:rPr>
2595 ///         <w:instrText>TIME \@"HH:mm:ss"</w:instrText>
2596 ///     </w:r>
2597 ///     <w:r>
2598 ///         <w:rPr>
2599 ///             <!-- properties written with DoWriteFieldRunProperties()
2600 ///         </w:rPr>
2601 ///         <w:fldChar w:fldCharType="separate" />
2602 ///     </w:r>
2603 ///     <w:r>
2604 ///         <w:rPr>
2605 ///             <!-- properties written with DoWriteFieldRunProperties()
2606 ///         </w:rPr>
2607 ///         <w:t>14:01:13</w:t>
2608 ///         </w:r>
2609 ///     <w:r>
2610 ///         <w:rPr>
2611 ///             <!-- properties written with DoWriteFieldRunProperties()
2612 ///         </w:rPr>
2613 ///         <w:fldChar w:fldCharType="end" />
2614 ///     </w:r>
2615 /// See, tdf#38778
2616 void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars)
2617 {
2618     if (! pNode)
2619     {
2620         // nothing to do
2621         return;
2622     }
2623 
2624     m_bPreventDoubleFieldsHandling = true;
2625 
2626     {
2627         m_pSerializer->startElementNS(XML_w, XML_rPr);
2628 
2629         // 1. output webHidden flag
2630         if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
2631         {
2632             m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2633         }
2634 
2635         // 2. find all active character properties
2636         SwWW8AttrIter aAttrIt( m_rExport, *pNode );
2637         aAttrIt.OutAttr( nPos, bWriteCombChars );
2638 
2639         // 3. write the character properties
2640         WriteCollectedRunProperties();
2641 
2642         m_pSerializer->endElementNS( XML_w, XML_rPr );
2643     }
2644 
2645     m_bPreventDoubleFieldsHandling = false;
2646 }
2647 
2648 void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos )
2649 {
2650     if (rInfos.eType == ww::eFORMDATE)
2651     {
2652         WriteSdtEnd();
2653         return;
2654     }
2655     else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
2656     {
2657         // write selected item from End not Start to ensure that any bookmarks
2658         // precede it
2659         SwDropDownField const& rField(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
2660         WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence());
2661         return;
2662     }
2663     else if (rInfos.eType == ww::eFILLIN && rInfos.pField)
2664     {
2665         SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
2666         if (rField.getGrabBagParams().hasElements())
2667         {
2668             WriteSdtEnd();
2669             return;
2670         }
2671     }
2672     // The command has to be written before for the hyperlinks
2673     if ( rInfos.pField )
2674     {
2675         CmdField_Impl( pNode, nPos, rInfos, true );
2676         CmdEndField_Impl( pNode, nPos, true );
2677     }
2678 
2679     // Write the bookmark start if any
2680     if ( !m_sFieldBkm.isEmpty() )
2681     {
2682         DoWriteBookmarkTagStart(m_sFieldBkm);
2683     }
2684 
2685     if (rInfos.pField ) // For hyperlinks and TOX
2686     {
2687         // Write the Field latest value
2688         m_pSerializer->startElementNS(XML_w, XML_r);
2689         DoWriteFieldRunProperties( pNode, nPos );
2690 
2691         OUString sExpand;
2692         if(rInfos.eType == ww::eCITATION)
2693         {
2694             sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get())
2695                         ->ExpandCitation(AUTH_FIELD_TITLE, nullptr);
2696         }
2697         else if(rInfos.eType != ww::eFORMDROPDOWN)
2698         {
2699             sExpand = rInfos.pField->ExpandField(true, nullptr);
2700         }
2701         // newlines embedded in fields are 0x0B in MSO and 0x0A for us
2702         RunText(sExpand.replace(0x0A, 0x0B));
2703 
2704         m_pSerializer->endElementNS( XML_w, XML_r );
2705     }
2706 
2707     // Write the bookmark end if any
2708     if ( !m_sFieldBkm.isEmpty() )
2709     {
2710         DoWriteBookmarkTagEnd(m_nNextBookmarkId);
2711 
2712         m_nNextBookmarkId++;
2713     }
2714 
2715     // Write the Field end
2716     if ( rInfos.bClose  )
2717     {
2718         m_bWritingField = false;
2719         m_pSerializer->startElementNS(XML_w, XML_r);
2720         DoWriteFieldRunProperties( pNode, nPos );
2721         m_pSerializer->singleElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "end");
2722         m_pSerializer->endElementNS( XML_w, XML_r );
2723     }
2724     // Write the ref field if a bookmark had to be set and the field
2725     // should be visible
2726     if ( !rInfos.pField )
2727     {
2728         m_sFieldBkm.clear();
2729         return;
2730     }
2731 
2732     sal_uInt16 nSubType = rInfos.pField->GetSubType( );
2733     bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp;
2734     bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0;
2735 
2736     if (!bShowRef)
2737     {
2738         m_sFieldBkm.clear();
2739     }
2740 
2741     if (m_sFieldBkm.isEmpty())
2742         return;
2743 
2744     // Write the field beginning
2745     m_pSerializer->startElementNS(XML_w, XML_r);
2746     m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2747         FSNS( XML_w, XML_fldCharType ), "begin" );
2748     m_pSerializer->endElementNS( XML_w, XML_r );
2749 
2750     rInfos.sCmd = FieldString( ww::eREF );
2751     rInfos.sCmd += "\"";
2752     rInfos.sCmd += m_sFieldBkm;
2753     rInfos.sCmd += "\" ";
2754 
2755     // Clean the field bookmark data to avoid infinite loop
2756     m_sFieldBkm = OUString( );
2757 
2758     // Write the end of the field
2759     EndField_Impl( pNode, nPos, rInfos );
2760 }
2761 
2762 void DocxAttributeOutput::StartRunProperties()
2763 {
2764     // postpone the output so that we can later [in EndRunProperties()]
2765     // prepend the properties before the text
2766     m_pSerializer->mark(Tag_StartRunProperties);
2767 
2768     m_pSerializer->startElementNS(XML_w, XML_rPr);
2769 
2770     if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
2771     {
2772         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2773     }
2774     InitCollectedRunProperties();
2775 
2776     assert( !m_pPostponedGraphic );
2777     m_pPostponedGraphic.reset(new std::vector<PostponedGraphic>);
2778 
2779     assert( !m_pPostponedDiagrams );
2780     m_pPostponedDiagrams.reset(new std::vector<PostponedDiagram>);
2781 
2782     assert(!m_pPostponedDMLDrawings);
2783     m_pPostponedDMLDrawings.reset(new std::vector<PostponedDrawing>);
2784 
2785     assert( !m_pPostponedOLEs );
2786     m_pPostponedOLEs.reset(new std::vector<PostponedOLE>);
2787 }
2788 
2789 void DocxAttributeOutput::InitCollectedRunProperties()
2790 {
2791     m_pFontsAttrList = nullptr;
2792     m_pEastAsianLayoutAttrList = nullptr;
2793     m_pCharLangAttrList = nullptr;
2794 
2795     // Write the elements in the spec order
2796     static const sal_Int32 aOrder[] =
2797     {
2798         FSNS( XML_w, XML_rStyle ),
2799         FSNS( XML_w, XML_rFonts ),
2800         FSNS( XML_w, XML_b ),
2801         FSNS( XML_w, XML_bCs ),
2802         FSNS( XML_w, XML_i ),
2803         FSNS( XML_w, XML_iCs ),
2804         FSNS( XML_w, XML_caps ),
2805         FSNS( XML_w, XML_smallCaps ),
2806         FSNS( XML_w, XML_strike ),
2807         FSNS( XML_w, XML_dstrike ),
2808         FSNS( XML_w, XML_outline ),
2809         FSNS( XML_w, XML_shadow ),
2810         FSNS( XML_w, XML_emboss ),
2811         FSNS( XML_w, XML_imprint ),
2812         FSNS( XML_w, XML_noProof ),
2813         FSNS( XML_w, XML_snapToGrid ),
2814         FSNS( XML_w, XML_vanish ),
2815         FSNS( XML_w, XML_webHidden ),
2816         FSNS( XML_w, XML_color ),
2817         FSNS( XML_w, XML_spacing ),
2818         FSNS( XML_w, XML_w ),
2819         FSNS( XML_w, XML_kern ),
2820         FSNS( XML_w, XML_position ),
2821         FSNS( XML_w, XML_sz ),
2822         FSNS( XML_w, XML_szCs ),
2823         FSNS( XML_w, XML_highlight ),
2824         FSNS( XML_w, XML_u ),
2825         FSNS( XML_w, XML_effect ),
2826         FSNS( XML_w, XML_bdr ),
2827         FSNS( XML_w, XML_shd ),
2828         FSNS( XML_w, XML_fitText ),
2829         FSNS( XML_w, XML_vertAlign ),
2830         FSNS( XML_w, XML_rtl ),
2831         FSNS( XML_w, XML_cs ),
2832         FSNS( XML_w, XML_em ),
2833         FSNS( XML_w, XML_lang ),
2834         FSNS( XML_w, XML_eastAsianLayout ),
2835         FSNS( XML_w, XML_specVanish ),
2836         FSNS( XML_w, XML_oMath ),
2837         FSNS( XML_w, XML_rPrChange ),
2838         FSNS( XML_w, XML_del ),
2839         FSNS( XML_w14, XML_glow ),
2840         FSNS( XML_w14, XML_shadow ),
2841         FSNS( XML_w14, XML_reflection ),
2842         FSNS( XML_w14, XML_textOutline ),
2843         FSNS( XML_w14, XML_textFill ),
2844         FSNS( XML_w14, XML_scene3d ),
2845         FSNS( XML_w14, XML_props3d ),
2846         FSNS( XML_w14, XML_ligatures ),
2847         FSNS( XML_w14, XML_numForm ),
2848         FSNS( XML_w14, XML_numSpacing ),
2849         FSNS( XML_w14, XML_stylisticSets ),
2850         FSNS( XML_w14, XML_cntxtAlts ),
2851     };
2852 
2853     // postpone the output so that we can later [in EndParagraphProperties()]
2854     // prepend the properties before the run
2855     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
2856     m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder));
2857 }
2858 
2859 namespace
2860 {
2861 
2862 struct NameToId
2863 {
2864     OUString  maName;
2865     sal_Int32 maId;
2866 };
2867 
2868 const NameToId constNameToIdMapping[] =
2869 {
2870     { OUString("glow"),         FSNS( XML_w14, XML_glow ) },
2871     { OUString("shadow"),       FSNS( XML_w14, XML_shadow ) },
2872     { OUString("reflection"),   FSNS( XML_w14, XML_reflection ) },
2873     { OUString("textOutline"),  FSNS( XML_w14, XML_textOutline ) },
2874     { OUString("textFill"),     FSNS( XML_w14, XML_textFill ) },
2875     { OUString("scene3d"),      FSNS( XML_w14, XML_scene3d ) },
2876     { OUString("props3d"),      FSNS( XML_w14, XML_props3d ) },
2877     { OUString("ligatures"),    FSNS( XML_w14, XML_ligatures ) },
2878     { OUString("numForm"),      FSNS( XML_w14, XML_numForm ) },
2879     { OUString("numSpacing"),   FSNS( XML_w14, XML_numSpacing ) },
2880     { OUString("stylisticSets"),FSNS( XML_w14, XML_stylisticSets ) },
2881     { OUString("cntxtAlts"),    FSNS( XML_w14, XML_cntxtAlts ) },
2882 
2883     { OUString("val"),          FSNS( XML_w14, XML_val ) },
2884     { OUString("rad"),          FSNS( XML_w14, XML_rad ) },
2885     { OUString("blurRad"),      FSNS( XML_w14, XML_blurRad ) },
2886     { OUString("stA"),          FSNS( XML_w14, XML_stA ) },
2887     { OUString("stPos"),        FSNS( XML_w14, XML_stPos ) },
2888     { OUString("endA"),         FSNS( XML_w14, XML_endA ) },
2889     { OUString("endPos"),       FSNS( XML_w14, XML_endPos ) },
2890     { OUString("dist"),         FSNS( XML_w14, XML_dist ) },
2891     { OUString("dir"),          FSNS( XML_w14, XML_dir ) },
2892     { OUString("fadeDir"),      FSNS( XML_w14, XML_fadeDir ) },
2893     { OUString("sx"),           FSNS( XML_w14, XML_sx ) },
2894     { OUString("sy"),           FSNS( XML_w14, XML_sy ) },
2895     { OUString("kx"),           FSNS( XML_w14, XML_kx ) },
2896     { OUString("ky"),           FSNS( XML_w14, XML_ky ) },
2897     { OUString("algn"),         FSNS( XML_w14, XML_algn ) },
2898     { OUString("w"),            FSNS( XML_w14, XML_w ) },
2899     { OUString("cap"),          FSNS( XML_w14, XML_cap ) },
2900     { OUString("cmpd"),         FSNS( XML_w14, XML_cmpd ) },
2901     { OUString("pos"),          FSNS( XML_w14, XML_pos ) },
2902     { OUString("ang"),          FSNS( XML_w14, XML_ang ) },
2903     { OUString("scaled"),       FSNS( XML_w14, XML_scaled ) },
2904     { OUString("path"),         FSNS( XML_w14, XML_path ) },
2905     { OUString("l"),            FSNS( XML_w14, XML_l ) },
2906     { OUString("t"),            FSNS( XML_w14, XML_t ) },
2907     { OUString("r"),            FSNS( XML_w14, XML_r ) },
2908     { OUString("b"),            FSNS( XML_w14, XML_b ) },
2909     { OUString("lim"),          FSNS( XML_w14, XML_lim ) },
2910     { OUString("prst"),         FSNS( XML_w14, XML_prst ) },
2911     { OUString("rig"),          FSNS( XML_w14, XML_rig ) },
2912     { OUString("lat"),          FSNS( XML_w14, XML_lat ) },
2913     { OUString("lon"),          FSNS( XML_w14, XML_lon ) },
2914     { OUString("rev"),          FSNS( XML_w14, XML_rev ) },
2915     { OUString("h"),            FSNS( XML_w14, XML_h ) },
2916     { OUString("extrusionH"),   FSNS( XML_w14, XML_extrusionH ) },
2917     { OUString("contourW"),     FSNS( XML_w14, XML_contourW ) },
2918     { OUString("prstMaterial"), FSNS( XML_w14, XML_prstMaterial ) },
2919     { OUString("id"),           FSNS( XML_w14, XML_id ) },
2920 
2921     { OUString("schemeClr"),    FSNS( XML_w14, XML_schemeClr ) },
2922     { OUString("srgbClr"),      FSNS( XML_w14, XML_srgbClr ) },
2923     { OUString("tint"),         FSNS( XML_w14, XML_tint ) },
2924     { OUString("shade"),        FSNS( XML_w14, XML_shade ) },
2925     { OUString("alpha"),        FSNS( XML_w14, XML_alpha ) },
2926     { OUString("hueMod"),       FSNS( XML_w14, XML_hueMod ) },
2927     { OUString("sat"),          FSNS( XML_w14, XML_sat ) },
2928     { OUString("satOff"),       FSNS( XML_w14, XML_satOff ) },
2929     { OUString("satMod"),       FSNS( XML_w14, XML_satMod ) },
2930     { OUString("lum"),          FSNS( XML_w14, XML_lum ) },
2931     { OUString("lumOff"),       FSNS( XML_w14, XML_lumOff ) },
2932     { OUString("lumMod"),       FSNS( XML_w14, XML_lumMod ) },
2933     { OUString("noFill"),       FSNS( XML_w14, XML_noFill ) },
2934     { OUString("solidFill"),    FSNS( XML_w14, XML_solidFill ) },
2935     { OUString("gradFill"),     FSNS( XML_w14, XML_gradFill ) },
2936     { OUString("gsLst"),        FSNS( XML_w14, XML_gsLst ) },
2937     { OUString("gs"),           FSNS( XML_w14, XML_gs ) },
2938     { OUString("pos"),          FSNS( XML_w14, XML_pos ) },
2939     { OUString("lin"),          FSNS( XML_w14, XML_lin ) },
2940     { OUString("path"),         FSNS( XML_w14, XML_path ) },
2941     { OUString("fillToRect"),   FSNS( XML_w14, XML_fillToRect ) },
2942     { OUString("prstDash"),     FSNS( XML_w14, XML_prstDash ) },
2943     { OUString("round"),        FSNS( XML_w14, XML_round ) },
2944     { OUString("bevel"),        FSNS( XML_w14, XML_bevel ) },
2945     { OUString("miter"),        FSNS( XML_w14, XML_miter ) },
2946     { OUString("camera"),       FSNS( XML_w14, XML_camera ) },
2947     { OUString("lightRig"),     FSNS( XML_w14, XML_lightRig ) },
2948     { OUString("rot"),          FSNS( XML_w14, XML_rot ) },
2949     { OUString("bevelT"),       FSNS( XML_w14, XML_bevelT ) },
2950     { OUString("bevelB"),       FSNS( XML_w14, XML_bevelB ) },
2951     { OUString("extrusionClr"), FSNS( XML_w14, XML_extrusionClr ) },
2952     { OUString("contourClr"),   FSNS( XML_w14, XML_contourClr ) },
2953     { OUString("styleSet"),     FSNS( XML_w14, XML_styleSet ) },
2954 };
2955 
2956 std::optional<sal_Int32> lclGetElementIdForName(std::u16string_view rName)
2957 {
2958     for (auto const & i : constNameToIdMapping)
2959     {
2960         if (rName == i.maName)
2961         {
2962             return i.maId;
2963         }
2964     }
2965     return std::optional<sal_Int32>();
2966 }
2967 
2968 void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer)
2969 {
2970     css::uno::Sequence<css::beans::PropertyValue> aAttributes;
2971     rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
2972 
2973     for (const auto& rElement : rElements)
2974     {
2975         if (rElement.Name == "attributes")
2976         {
2977             rElement.Value >>= aAttributes;
2978         }
2979     }
2980 
2981     for (const auto& rAttribute : std::as_const(aAttributes))
2982     {
2983         uno::Any aAny = rAttribute.Value;
2984         OString aValue;
2985 
2986         if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get())
2987         {
2988             aValue = OString::number(aAny.get<sal_Int32>());
2989         }
2990         else if(aAny.getValueType() == cppu::UnoType<OUString>::get())
2991         {
2992             aValue =  OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US);
2993         }
2994 
2995         std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rAttribute.Name);
2996         if(aSubElementId)
2997             pAttributes->add(*aSubElementId, aValue);
2998     }
2999 
3000     pSerializer->startElement(aElementId, pAttributes);
3001 
3002     for (const auto& rElement : rElements)
3003     {
3004         css::uno::Sequence<css::beans::PropertyValue> aSumElements;
3005 
3006         std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElement.Name);
3007         if(aSubElementId)
3008         {
3009             rElement.Value >>= aSumElements;
3010             lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer);
3011         }
3012     }
3013 
3014     pSerializer->endElement(aElementId);
3015 }
3016 
3017 }
3018 
3019 void DocxAttributeOutput::WriteCollectedRunProperties()
3020 {
3021     // Write all differed properties
3022     if ( m_pFontsAttrList.is() )
3023     {
3024         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pFontsAttrList );
3025         m_pSerializer->singleElementNS( XML_w, XML_rFonts, xAttrList );
3026     }
3027 
3028     if ( m_pColorAttrList.is() )
3029     {
3030         rtl::Reference<FastAttributeList> xAttrList( m_pColorAttrList );
3031 
3032         m_pSerializer->singleElementNS( XML_w, XML_color, xAttrList );
3033     }
3034 
3035     if ( m_pEastAsianLayoutAttrList.is() )
3036     {
3037         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pEastAsianLayoutAttrList );
3038         m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout, xAttrList );
3039     }
3040 
3041     if ( m_pCharLangAttrList.is() )
3042     {
3043         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pCharLangAttrList );
3044         m_pSerializer->singleElementNS( XML_w, XML_lang, xAttrList );
3045     }
3046 
3047     if (m_nCharTransparence != 0 && m_pColorAttrList && m_aTextEffectsGrabBag.empty())
3048     {
3049         const char* pVal = nullptr;
3050         m_pColorAttrList->getAsChar(FSNS(XML_w, XML_val), pVal);
3051         if (std::string_view("auto") != pVal)
3052         {
3053             m_pSerializer->startElementNS(XML_w14, XML_textFill);
3054             m_pSerializer->startElementNS(XML_w14, XML_solidFill);
3055             m_pSerializer->startElementNS(XML_w14, XML_srgbClr, FSNS(XML_w14, XML_val), pVal);
3056             sal_Int32 nTransparence = m_nCharTransparence * oox::drawingml::MAX_PERCENT / 255.0;
3057             m_pSerializer->singleElementNS(XML_w14, XML_alpha, FSNS(XML_w14, XML_val), OString::number(nTransparence));
3058             m_pSerializer->endElementNS(XML_w14, XML_srgbClr);
3059             m_pSerializer->endElementNS(XML_w14, XML_solidFill);
3060             m_pSerializer->endElementNS(XML_w14, XML_textFill);
3061             m_nCharTransparence = 0;
3062         }
3063     }
3064     m_pColorAttrList.clear();
3065     for (const beans::PropertyValue & i : m_aTextEffectsGrabBag)
3066     {
3067         std::optional<sal_Int32> aElementId = lclGetElementIdForName(i.Name);
3068         if(aElementId)
3069         {
3070             uno::Sequence<beans::PropertyValue> aGrabBagSeq;
3071             i.Value >>= aGrabBagSeq;
3072             lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer);
3073         }
3074     }
3075     m_aTextEffectsGrabBag.clear();
3076 }
3077 
3078 void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
3079 {
3080     // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties.
3081     // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
3082 
3083     // If there is RedlineData present, call WriteCollectedRunProperties() for writing rPr before calling Redline().
3084     // As there will be another rPr for redline and LO might mix both.
3085     if(pRedlineData)
3086         WriteCollectedRunProperties();
3087     Redline( pRedlineData );
3088 
3089     WriteCollectedRunProperties();
3090 
3091     // Merge the marks for the ordered elements
3092     m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
3093 
3094     m_pSerializer->endElementNS( XML_w, XML_rPr );
3095 
3096     // write footnotes/endnotes if we have any
3097     FootnoteEndnoteReference();
3098 
3099     // merge the properties _before_ the run text (strictly speaking, just
3100     // after the start of the run)
3101     m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND);
3102 
3103     WritePostponedGraphic();
3104 
3105     WritePostponedDiagram();
3106     //We need to write w:drawing tag after the w:rPr.
3107     WritePostponedChart();
3108 
3109     //We need to write w:pict tag after the w:rPr.
3110     WritePostponedDMLDrawing();
3111 
3112     WritePostponedOLE();
3113 
3114     WritePostponedActiveXControl(true);
3115 }
3116 
3117 void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
3118 {
3119     if (!pSdrObj)
3120         return;
3121 
3122     uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape());
3123     uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
3124     if( !xPropSet.is() )
3125         return;
3126 
3127     uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
3128     uno::Sequence< beans::PropertyValue > aGrabBag;
3129     if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
3130     {
3131         xPropSet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
3132     }
3133     else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
3134     {
3135         xPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3136     }
3137 
3138     auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
3139         [this](const beans::PropertyValue& rProp) {
3140             return "SdtEndBefore" == rProp.Name && m_aRunSdt.m_bStartedSdt && !m_bEndCharSdt; });
3141     if (pProp != std::cend(aGrabBag))
3142         pProp->Value >>= m_bEndCharSdt;
3143 }
3144 
3145 void DocxAttributeOutput::WritePostponedGraphic()
3146 {
3147     for (const auto & rPostponedDiagram : *m_pPostponedGraphic)
3148         FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size,
3149             nullptr, nullptr,
3150             rPostponedDiagram.pSdrObj);
3151     m_pPostponedGraphic.reset();
3152 }
3153 
3154 void DocxAttributeOutput::WritePostponedDiagram()
3155 {
3156     for( const auto & rPostponedDiagram : *m_pPostponedDiagrams )
3157         m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object,
3158             *rPostponedDiagram.frame, m_anchorId++);
3159     m_pPostponedDiagrams.reset();
3160 }
3161 
3162 bool DocxAttributeOutput::FootnoteEndnoteRefTag()
3163 {
3164     if( m_footnoteEndnoteRefTag == 0 )
3165         return false;
3166 
3167     // output the character style for MS Word's benefit
3168     const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ?
3169         m_rExport.m_rDoc.GetFootnoteInfo() : m_rExport.m_rDoc.GetEndNoteInfo();
3170     const SwCharFormat* pCharFormat = rInfo.GetCharFormat( m_rExport.m_rDoc );
3171     if ( pCharFormat )
3172     {
3173         const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
3174         m_pSerializer->startElementNS(XML_w, XML_rPr);
3175         m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
3176         m_pSerializer->endElementNS( XML_w, XML_rPr );
3177     }
3178 
3179     if (m_footnoteCustomLabel.isEmpty())
3180         m_pSerializer->singleElementNS(XML_w, m_footnoteEndnoteRefTag);
3181     else
3182         RunText(m_footnoteCustomLabel);
3183     m_footnoteEndnoteRefTag = 0;
3184     return true;
3185 }
3186 
3187 /** Output sal_Unicode* as a run text (<t>the text</t>).
3188 
3189     When bMove is true, update rBegin to point _after_ the end of the text +
3190     1, meaning that it skips one character after the text.  This is to make
3191     the switch in DocxAttributeOutput::RunText() nicer ;-)
3192  */
3193 static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken,
3194         const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true )
3195 {
3196     const sal_Unicode *pBegin = rBegin;
3197 
3198     // skip one character after the end
3199     if ( bMove )
3200         rBegin = pEnd + 1;
3201 
3202     if ( pBegin >= pEnd )
3203         return false; // we want to write at least one character
3204 
3205     // we have to add 'preserve' when starting/ending with space
3206     if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' )
3207     {
3208         pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
3209     }
3210     else
3211         pSerializer->startElementNS(XML_w, nTextToken);
3212 
3213     pSerializer->writeEscaped( std::u16string_view( pBegin, pEnd - pBegin ) );
3214 
3215     pSerializer->endElementNS( XML_w, nTextToken );
3216 
3217     return true;
3218 }
3219 
3220 void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/ )
3221 {
3222     if( m_closeHyperlinkInThisRun )
3223     {
3224         m_closeHyperlinkInPreviousRun = true;
3225     }
3226     m_bRunTextIsOn = true;
3227     // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
3228     const sal_Unicode *pBegin = rText.getStr();
3229     const sal_Unicode *pEnd = pBegin + rText.getLength();
3230 
3231     // the text run is usually XML_t, with the exception of the deleted (and not moved) text
3232     sal_Int32 nTextToken = XML_t;
3233     if ( m_pRedlineData && !m_pRedlineData->IsMoved() &&
3234             m_pRedlineData->GetType() == RedlineType::Delete )
3235     {
3236         nTextToken = XML_delText;
3237     }
3238 
3239     sal_Unicode prevUnicode = *pBegin;
3240 
3241     for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt )
3242     {
3243         switch ( *pIt )
3244         {
3245             case 0x09: // tab
3246                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3247                 m_pSerializer->singleElementNS(XML_w, XML_tab);
3248                 prevUnicode = *pIt;
3249                 break;
3250             case 0x0b: // line break
3251                 {
3252                     if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || prevUnicode < 0x0020)
3253                     {
3254                         m_pSerializer->singleElementNS(XML_w, XML_br);
3255                         prevUnicode = *pIt;
3256                     }
3257                 }
3258                 break;
3259             case 0x1E: //non-breaking hyphen
3260                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3261                 m_pSerializer->singleElementNS(XML_w, XML_noBreakHyphen);
3262                 prevUnicode = *pIt;
3263                 break;
3264             case 0x1F: //soft (on demand) hyphen
3265                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3266                 m_pSerializer->singleElementNS(XML_w, XML_softHyphen);
3267                 prevUnicode = *pIt;
3268                 break;
3269             default:
3270                 if ( *pIt < 0x0020 ) // filter out the control codes
3271                 {
3272                     impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3273                     SAL_INFO("sw.ww8", "Ignored control code in a text run: " << unsigned(*pIt) );
3274                 }
3275                 prevUnicode = *pIt;
3276                 break;
3277         }
3278     }
3279 
3280     impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false );
3281 }
3282 
3283 void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/)
3284 {
3285     m_sRawText = rText;
3286 }
3287 
3288 void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby )
3289 {
3290     WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() );
3291     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" );
3292     EndRun( &rNode, nPos ); // end run before starting ruby to avoid nested runs, and overlap
3293     assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby
3294     assert(!m_closeHyperlinkInPreviousRun);
3295     m_pSerializer->startElementNS(XML_w, XML_r);
3296     m_pSerializer->startElementNS(XML_w, XML_ruby);
3297     m_pSerializer->startElementNS(XML_w, XML_rubyPr);
3298 
3299     m_pSerializer->singleElementNS( XML_w, XML_rubyAlign,
3300             FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()) );
3301     sal_uInt32   nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10;
3302     sal_uInt32   nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10;
3303     m_pSerializer->singleElementNS(XML_w, XML_hps, FSNS(XML_w, XML_val), OString::number(nHps));
3304 
3305     m_pSerializer->singleElementNS( XML_w, XML_hpsRaise,
3306             FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3307 
3308     m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText,
3309             FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3310 
3311     lang::Locale aLocale( SwBreakIt::Get()->GetLocale(
3312                 rNode.GetLang( nPos ) ) );
3313     OUString sLang( LanguageTag::convertToBcp47( aLocale) );
3314     m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), sLang);
3315 
3316     m_pSerializer->endElementNS( XML_w, XML_rubyPr );
3317 
3318     m_pSerializer->startElementNS(XML_w, XML_rt);
3319     StartRun( nullptr, nPos );
3320     StartRunProperties( );
3321 
3322     if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat())
3323     {
3324         const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat();
3325         sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3326         sal_uInt16 nWhichFont = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONT : RES_CHRATR_CJK_FONT;
3327         sal_uInt16 nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE;
3328 
3329         CharFont(ItemGet<SvxFontItem>(*pFormat, nWhichFont));
3330         CharFontSize(ItemGet<SvxFontHeightItem>(*pFormat, nWhichFontSize));
3331         CharFontSize(ItemGet<SvxFontHeightItem>(*pFormat, RES_CHRATR_CTL_FONTSIZE));
3332     }
3333 
3334     EndRunProperties( nullptr );
3335     RunText( rRuby.GetText( ) );
3336     EndRun( &rNode, nPos );
3337     m_pSerializer->endElementNS( XML_w, XML_rt );
3338 
3339     m_pSerializer->startElementNS(XML_w, XML_rubyBase);
3340     StartRun( nullptr, nPos );
3341 }
3342 
3343 void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
3344 {
3345     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" );
3346     EndRun( &rNode, nPos );
3347     m_pSerializer->endElementNS( XML_w, XML_rubyBase );
3348     m_pSerializer->endElementNS( XML_w, XML_ruby );
3349     m_pSerializer->endElementNS( XML_w, XML_r );
3350     StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
3351 }
3352 
3353 bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
3354 {
3355     bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
3356 
3357     if ( !pMark->isEmpty() )
3358     {
3359         OUString sURL = *pLinkURL;
3360 
3361         if ( bBookMarkOnly )
3362             sURL = FieldString( ww::eHYPERLINK );
3363         else
3364             sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
3365 
3366         sURL += " \\l \"" + *pMark + "\"";
3367 
3368         if ( !rTarget.isEmpty() )
3369             sURL += " \\n " + rTarget;
3370 
3371         *pLinkURL = sURL;
3372     }
3373 
3374     return bBookMarkOnly;
3375 }
3376 
3377 void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
3378 {
3379     m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
3380     m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
3381 }
3382 
3383 bool DocxAttributeOutput::StartURL( const OUString& rUrl, const OUString& rTarget )
3384 {
3385     OUString sMark;
3386     OUString sUrl;
3387 
3388     bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark );
3389 
3390     m_hyperLinkAnchor = sMark;
3391 
3392     if ( !sMark.isEmpty() && !bBookmarkOnly )
3393     {
3394         m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl );
3395     }
3396     else
3397     {
3398         // Output a hyperlink XML element
3399         m_pHyperlinkAttrList = FastSerializerHelper::createAttrList();
3400 
3401         if ( !bBookmarkOnly )
3402         {
3403             OString sId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
3404                         oox::getRelationship(Relationship::HYPERLINK),
3405                         sUrl, true ), RTL_TEXTENCODING_UTF8 );
3406 
3407             m_pHyperlinkAttrList->add(FSNS(XML_r, XML_id), sId);
3408         }
3409         else
3410         {
3411             // Is this a link to a sequence? Then try to replace that with a
3412             // normal bookmark, as Word won't understand our special
3413             // <seqname>!<index>|sequence syntax.
3414             if (sMark.endsWith("|sequence"))
3415             {
3416                 sal_Int32 nPos = sMark.indexOf('!');
3417                 if (nPos != -1)
3418                 {
3419                     // Extract <seqname>, the field instruction text has the name quoted.
3420                     OUString aSequenceName = sMark.copy(0, nPos);
3421                     // Extract <index>.
3422                     sal_uInt32 nIndex = sMark.copy(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence")).toUInt32();
3423                     std::map<OUString, std::vector<OString> >::iterator it = m_aSeqBookmarksNames.find(aSequenceName);
3424                     if (it != m_aSeqBookmarksNames.end())
3425                     {
3426                         std::vector<OString>& rNames = it->second;
3427                         if (rNames.size() > nIndex)
3428                             // We know the bookmark name for this sequence and this index, do the replacement.
3429                             sMark = OStringToOUString(rNames[nIndex], RTL_TEXTENCODING_UTF8);
3430                     }
3431                 }
3432             }
3433             else if (sMark.endsWith("|toxmark"))
3434             {
3435                 if (auto const it = GetExport().m_TOXMarkBookmarksByURL.find(sMark);
3436                     it != GetExport().m_TOXMarkBookmarksByURL.end())
3437                 {
3438                     sMark = it->second;
3439                 }
3440             }
3441             // Spaces are prohibited in bookmark name.
3442             sMark = sMark.replace(' ', '_');
3443             m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ),
3444                     OUStringToOString( sMark, RTL_TEXTENCODING_UTF8 ) );
3445         }
3446 
3447         if ( !rTarget.isEmpty() )
3448         {
3449             OString soTarget = OUStringToOString( rTarget, RTL_TEXTENCODING_UTF8 );
3450             m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tgtFrame), soTarget);
3451         }
3452     }
3453 
3454     return true;
3455 }
3456 
3457 bool DocxAttributeOutput::EndURL(bool const)
3458 {
3459     m_closeHyperlinkInThisRun = true;
3460     if(m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc"))
3461     {
3462         m_endPageRef = true;
3463     }
3464     return true;
3465 }
3466 
3467 void DocxAttributeOutput::FieldVanish(const OUString& rText,
3468         ww::eField const eType, OUString const*const pBookmarkName)
3469 {
3470     WriteField_Impl(nullptr, eType, rText, FieldFlags::All, pBookmarkName);
3471 }
3472 
3473 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
3474 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
3475 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
3476 void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
3477 {
3478     if ( !pRedlineData )
3479         return;
3480 
3481     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
3482         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
3483 
3484     OString aId( OString::number( pRedlineData->GetSeqNo() ) );
3485     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
3486     OString aDate( DateTimeToOString( bRemovePersonalInfo
3487             ? DateTime(Date( 1, 1, 1970 )) // Epoch time
3488             : pRedlineData->GetTimeStamp() ) );
3489 
3490     switch( pRedlineData->GetType() )
3491     {
3492     case RedlineType::Insert:
3493         break;
3494 
3495     case RedlineType::Delete:
3496         break;
3497 
3498     case RedlineType::Format:
3499         m_pSerializer->startElementNS( XML_w, XML_rPrChange,
3500                 FSNS( XML_w, XML_id ), aId,
3501                 FSNS( XML_w, XML_author ), bRemovePersonalInfo
3502                     ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
3503                     : rAuthor,
3504                 FSNS( XML_w, XML_date ), aDate );
3505 
3506         // Check if there is any extra data stored in the redline object
3507         if (pRedlineData->GetExtraData())
3508         {
3509             const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
3510             const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
3511 
3512             // Check if the extra data is of type 'formatting changes'
3513             if (pFormattingChanges)
3514             {
3515                  // Get the item set that holds all the changes properties
3516                 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
3517                 if (pChangesSet)
3518                 {
3519                     m_pSerializer->mark(Tag_Redline_1);
3520 
3521                     m_pSerializer->startElementNS(XML_w, XML_rPr);
3522 
3523                     // Output the redline item set
3524                     if (pChangesSet)
3525                         m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
3526 
3527                     m_pSerializer->endElementNS( XML_w, XML_rPr );
3528 
3529                     m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND);
3530                 }
3531             }
3532         }
3533 
3534         m_pSerializer->endElementNS( XML_w, XML_rPrChange );
3535         break;
3536 
3537     case RedlineType::ParagraphFormat:
3538         m_pSerializer->startElementNS( XML_w, XML_pPrChange,
3539                 FSNS( XML_w, XML_id ), aId,
3540                 FSNS( XML_w, XML_author ), bRemovePersonalInfo
3541                     ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
3542                     : rAuthor,
3543                 FSNS( XML_w, XML_date ), aDate );
3544 
3545         // Check if there is any extra data stored in the redline object
3546         if (pRedlineData->GetExtraData())
3547         {
3548             const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
3549             const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
3550 
3551             // Check if the extra data is of type 'formatting changes'
3552             if (pFormattingChanges)
3553             {
3554                 // Get the item set that holds all the changes properties
3555                 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
3556                 const OUString & sParaStyleName = pFormattingChanges->GetFormatName();
3557                 if (pChangesSet || !sParaStyleName.isEmpty())
3558                 {
3559                     m_pSerializer->mark(Tag_Redline_2);
3560 
3561                     m_pSerializer->startElementNS(XML_w, XML_pPr);
3562 
3563                     OString sStyleName = MSWordStyles::CreateStyleId( sParaStyleName );
3564                     if ( !sStyleName.isEmpty() )
3565                         m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName);
3566 
3567                     // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information
3568                     // that should be collected by different properties in the core, and are all flushed together
3569                     // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called.
3570                     // So we need to store the current status of these lists, so that we can revert back to them when
3571                     // we are done exporting the redline attributes.
3572                     rtl::Reference<sax_fastparser::FastAttributeList> pFlyAttrList_Original(m_rExport.SdrExporter().getFlyAttrList());
3573                     m_rExport.SdrExporter().getFlyAttrList().clear();
3574                     rtl::Reference<sax_fastparser::FastAttributeList> pParagraphSpacingAttrList_Original(m_pParagraphSpacingAttrList);
3575                     m_pParagraphSpacingAttrList.clear();
3576 
3577                     // Output the redline item set
3578                     if (pChangesSet)
3579                         m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
3580 
3581                     // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
3582                     WriteCollectedParagraphProperties();
3583 
3584                     // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
3585                     m_rExport.SdrExporter().getFlyAttrList() = pFlyAttrList_Original;
3586                     m_pParagraphSpacingAttrList = pParagraphSpacingAttrList_Original;
3587 
3588                     m_pSerializer->endElementNS( XML_w, XML_pPr );
3589 
3590                     m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND);
3591                 }
3592             }
3593         }
3594         m_pSerializer->endElementNS( XML_w, XML_pPrChange );
3595         break;
3596 
3597     default:
3598         SAL_WARN("sw.ww8", "Unhandled redline type for export " << SwRedlineTypeToOUString(pRedlineData->GetType()));
3599         break;
3600     }
3601 }
3602 
3603 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
3604 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
3605 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
3606 void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData )
3607 {
3608     if ( !pRedlineData )
3609         return;
3610 
3611     // write out stack of this redline recursively (first the oldest)
3612     StartRedline( pRedlineData->Next() );
3613 
3614     OString aId( OString::number( m_nRedlineId++ ) );
3615 
3616     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
3617         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
3618 
3619     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
3620     OString aAuthor( OUStringToOString( bRemovePersonalInfo
3621                         ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
3622                         : rAuthor, RTL_TEXTENCODING_UTF8 ) );
3623 
3624     OString aDate( DateTimeToOString( bRemovePersonalInfo
3625             ? DateTime(Date( 1, 1, 1970 )) // Epoch time
3626             : pRedlineData->GetTimeStamp() ) );
3627 
3628     bool bMoved = pRedlineData->IsMoved();
3629     switch ( pRedlineData->GetType() )
3630     {
3631         case RedlineType::Insert:
3632             m_pSerializer->startElementNS( XML_w, bMoved ? XML_moveTo : XML_ins,
3633                     FSNS( XML_w, XML_id ), aId,
3634                     FSNS( XML_w, XML_author ), aAuthor,
3635                     FSNS( XML_w, XML_date ), aDate );
3636             break;
3637 
3638         case RedlineType::Delete:
3639             m_pSerializer->startElementNS( XML_w, bMoved ? XML_moveFrom : XML_del,
3640                     FSNS( XML_w, XML_id ), aId,
3641                     FSNS( XML_w, XML_author ), aAuthor,
3642                     FSNS( XML_w, XML_date ), aDate );
3643             break;
3644 
3645         case RedlineType::Format:
3646             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" );
3647             break;
3648         default:
3649             break;
3650     }
3651 }
3652 
3653 void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData )
3654 {
3655     if ( !pRedlineData || m_bWritingField )
3656         return;
3657 
3658     bool bMoved = pRedlineData->IsMoved();
3659     switch ( pRedlineData->GetType() )
3660     {
3661         case RedlineType::Insert:
3662             m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins );
3663             break;
3664 
3665         case RedlineType::Delete:
3666             m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : XML_del );
3667             break;
3668 
3669         case RedlineType::Format:
3670             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" );
3671             break;
3672         default:
3673             break;
3674     }
3675 
3676     // write out stack of this redline recursively (first the newest)
3677     EndRedline( pRedlineData->Next() );
3678 }
3679 
3680 void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t )
3681 {
3682     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" );
3683 }
3684 
3685 void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle )
3686 {
3687     OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle));
3688 
3689     m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), aStyleId);
3690 }
3691 
3692 static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist,
3693                              bool bWriteShadow, const table::BorderLine2* rStyleProps = nullptr )
3694 {
3695     // Compute val attribute value
3696     // Can be one of:
3697     //      single, double,
3698     //      basicWideOutline, basicWideInline
3699     // OOXml also supports those types of borders, but we'll try to play with the first ones.
3700     //      thickThinMediumGap, thickThinLargeGap, thickThinSmallGap
3701     //      thinThickLargeGap, thinThickMediumGap, thinThickSmallGap
3702     const char* pVal = "nil";
3703     if ( pBorderLine && !pBorderLine->isEmpty( ) )
3704     {
3705         switch (pBorderLine->GetBorderLineStyle())
3706         {
3707             case SvxBorderLineStyle::SOLID:
3708                 pVal = "single";
3709                 break;
3710             case SvxBorderLineStyle::DOTTED:
3711                 pVal = "dotted";
3712                 break;
3713             case SvxBorderLineStyle::DASHED:
3714                 pVal = "dashed";
3715                 break;
3716             case SvxBorderLineStyle::DOUBLE:
3717             case SvxBorderLineStyle::DOUBLE_THIN:
3718                 pVal = "double";
3719                 break;
3720             case SvxBorderLineStyle::THINTHICK_SMALLGAP:
3721                 pVal = "thinThickSmallGap";
3722                 break;
3723             case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
3724                 pVal = "thinThickMediumGap";
3725                 break;
3726             case SvxBorderLineStyle::THINTHICK_LARGEGAP:
3727                 pVal = "thinThickLargeGap";
3728                 break;
3729             case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
3730                 pVal = "thickThinSmallGap";
3731                 break;
3732             case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
3733                 pVal = "thickThinMediumGap";
3734                 break;
3735             case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
3736                 pVal = "thickThinLargeGap";
3737                 break;
3738             case SvxBorderLineStyle::EMBOSSED:
3739                 pVal = "threeDEmboss";
3740                 break;
3741             case SvxBorderLineStyle::ENGRAVED:
3742                 pVal = "threeDEngrave";
3743                 break;
3744             case SvxBorderLineStyle::OUTSET:
3745                 pVal = "outset";
3746                 break;
3747             case SvxBorderLineStyle::INSET:
3748                 pVal = "inset";
3749                 break;
3750             case SvxBorderLineStyle::FINE_DASHED:
3751                 pVal = "dashSmallGap";
3752                 break;
3753             case SvxBorderLineStyle::DASH_DOT:
3754                 pVal = "dotDash";
3755                 break;
3756             case SvxBorderLineStyle::DASH_DOT_DOT:
3757                 pVal = "dotDotDash";
3758                 break;
3759             case SvxBorderLineStyle::NONE:
3760             default:
3761                 break;
3762         }
3763     }
3764     else if ( !rStyleProps || !rStyleProps->LineWidth )
3765         // no line, and no line set by the style either:
3766         // there is no need to write the property
3767         return;
3768 
3769     // compare the properties with the theme properties before writing them:
3770     // if they are equal, it means that they were style-defined and there is
3771     // no need to write them.
3772     if( rStyleProps != nullptr && pBorderLine && !pBorderLine->isEmpty() &&
3773             pBorderLine->GetBorderLineStyle() == static_cast<SvxBorderLineStyle>(rStyleProps->LineStyle) &&
3774             pBorderLine->GetColor() == Color(ColorTransparency, rStyleProps->Color) &&
3775             pBorderLine->GetWidth() == o3tl::toTwips(rStyleProps->LineWidth, o3tl::Length::mm100) )
3776         return;
3777 
3778     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
3779     pAttr->add( FSNS( XML_w, XML_val ), OString( pVal ) );
3780 
3781     if ( pBorderLine && !pBorderLine->isEmpty() )
3782     {
3783         // Compute the sz attribute
3784 
3785         double const fConverted( ::editeng::ConvertBorderWidthToWord(
3786                 pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth()));
3787         // The unit is the 8th of point
3788         sal_Int32 nWidth = sal_Int32( fConverted / 2.5 );
3789         const sal_Int32 nMinWidth = 2;
3790         const sal_Int32 nMaxWidth = 96;
3791 
3792         if ( nWidth > nMaxWidth )
3793             nWidth = nMaxWidth;
3794         else if ( nWidth < nMinWidth )
3795             nWidth = nMinWidth;
3796 
3797         pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) );
3798 
3799         // Get the distance (in pt)
3800         pAttr->add(FSNS(XML_w, XML_space), OString::number(rtl::math::round(nDist / 20.0)));
3801 
3802         // Get the color code as an RRGGBB hex value
3803         OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) );
3804         pAttr->add( FSNS( XML_w, XML_color ), sColor );
3805     }
3806 
3807     if (bWriteShadow)
3808     {
3809         // Set the shadow value
3810         pAttr->add( FSNS( XML_w, XML_shadow ), "1" );
3811     }
3812 
3813     pSerializer->singleElementNS( XML_w, elementToken, pAttr );
3814 }
3815 
3816 static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma)
3817 {
3818     OutputBorderOptions rOptions;
3819 
3820     rOptions.tag = XML_tcBorders;
3821     rOptions.bUseStartEnd = !bEcma;
3822     rOptions.bWriteTag = true;
3823     rOptions.bWriteDistance = false;
3824 
3825     return rOptions;
3826 }
3827 
3828 static OutputBorderOptions lcl_getBoxBorderOptions()
3829 {
3830     OutputBorderOptions rOptions;
3831 
3832     rOptions.tag = XML_pBdr;
3833     rOptions.bUseStartEnd = false;
3834     rOptions.bWriteTag = false;
3835     rOptions.bWriteDistance = true;
3836 
3837     return rOptions;
3838 }
3839 
3840 static void impl_borders( FSHelperPtr const & pSerializer,
3841                           const SvxBoxItem& rBox,
3842                           const OutputBorderOptions& rOptions,
3843                           std::map<SvxBoxItemLine,
3844                           css::table::BorderLine2> &rTableStyleConf )
3845 {
3846     static const SvxBoxItemLine aBorders[] =
3847     {
3848         SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
3849     };
3850 
3851     const sal_Int32 aXmlElements[] =
3852     {
3853         XML_top,
3854         rOptions.bUseStartEnd ? XML_start : XML_left,
3855         XML_bottom,
3856         rOptions.bUseStartEnd ? XML_end : XML_right
3857     };
3858     bool tagWritten = false;
3859     const SvxBoxItemLine* pBrd = aBorders;
3860 
3861     for( int i = 0; i < 4; ++i, ++pBrd )
3862     {
3863         const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
3864         const table::BorderLine2 *aStyleProps = nullptr;
3865         if( rTableStyleConf.find( *pBrd ) != rTableStyleConf.end() )
3866             aStyleProps = &rTableStyleConf[ *pBrd ];
3867 
3868         if (!tagWritten && rOptions.bWriteTag)
3869         {
3870             pSerializer->startElementNS(XML_w, rOptions.tag);
3871             tagWritten = true;
3872         }
3873 
3874         bool bWriteShadow = false;
3875         if (rOptions.aShadowLocation == SvxShadowLocation::NONE)
3876         {
3877             // The border has no shadow
3878         }
3879         else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight)
3880         {
3881             // Special case of 'Bottom-Right' shadow:
3882             // If the shadow location is 'Bottom-Right' - then turn on the shadow
3883             // for ALL the sides. This is because in Word - if you select a shadow
3884             // for a border - it turn on the shadow for ALL the sides (but shows only
3885             // the bottom-right one).
3886             // This is so that no information will be lost if passed through LibreOffice
3887             bWriteShadow = true;
3888         }
3889         else
3890         {
3891             // If there is a shadow, and it's not the regular 'Bottom-Right',
3892             // then write only the 'shadowed' sides of the border
3893             if  (
3894                     ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft    || rOptions.aShadowLocation == SvxShadowLocation::TopRight  ) && *pBrd == SvxBoxItemLine::TOP   ) ||
3895                     ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft    || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft) && *pBrd == SvxBoxItemLine::LEFT  ) ||
3896                     ((rOptions.aShadowLocation == SvxShadowLocation::BottomLeft                                                             ) && *pBrd == SvxBoxItemLine::BOTTOM) ||
3897                     ((rOptions.aShadowLocation == SvxShadowLocation::TopRight                                                               ) && *pBrd == SvxBoxItemLine::RIGHT )
3898                 )
3899             {
3900                 bWriteShadow = true;
3901             }
3902         }
3903 
3904         sal_uInt16 nDist = 0;
3905         if (rOptions.bWriteDistance)
3906         {
3907             if (rOptions.pDistances)
3908             {
3909                 if ( *pBrd == SvxBoxItemLine::TOP)
3910                     nDist = rOptions.pDistances->nTop;
3911                 else if ( *pBrd == SvxBoxItemLine::LEFT)
3912                     nDist = rOptions.pDistances->nLeft;
3913                 else if ( *pBrd == SvxBoxItemLine::BOTTOM)
3914                     nDist = rOptions.pDistances->nBottom;
3915                 else if ( *pBrd == SvxBoxItemLine::RIGHT)
3916                     nDist = rOptions.pDistances->nRight;
3917             }
3918             else
3919             {
3920                 nDist = rBox.GetDistance(*pBrd);
3921             }
3922         }
3923 
3924         impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps );
3925     }
3926     if (tagWritten && rOptions.bWriteTag) {
3927         pSerializer->endElementNS( XML_w, rOptions.tag );
3928     }
3929 }
3930 
3931 static void impl_cellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins = nullptr)
3932 {
3933     static const SvxBoxItemLine aBorders[] =
3934     {
3935         SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
3936     };
3937 
3938     const sal_Int32 aXmlElements[] =
3939     {
3940         XML_top,
3941         bUseStartEnd ? XML_start : XML_left,
3942         XML_bottom,
3943         bUseStartEnd ? XML_end : XML_right
3944     };
3945     bool tagWritten = false;
3946     const SvxBoxItemLine* pBrd = aBorders;
3947     for( int i = 0; i < 4; ++i, ++pBrd )
3948     {
3949         sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) );
3950 
3951         if (pDefaultMargins)
3952         {
3953             // Skip output if cell margin == table default margin
3954             if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist)
3955                 continue;
3956         }
3957 
3958         if (!tagWritten) {
3959             pSerializer->startElementNS(XML_w, tag);
3960             tagWritten = true;
3961         }
3962         pSerializer->singleElementNS( XML_w, aXmlElements[i],
3963                FSNS( XML_w, XML_w ), OString::number(nDist),
3964                FSNS( XML_w, XML_type ), "dxa" );
3965     }
3966     if (tagWritten) {
3967         pSerializer->endElementNS( XML_w, tag );
3968     }
3969 }
3970 
3971 void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
3972 {
3973     m_pSerializer->startElementNS(XML_w, XML_tcPr);
3974 
3975     const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
3976 
3977     bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;
3978 
3979     // Output any table cell redlines if there are any attached to this specific cell
3980     TableCellRedline( pTableTextNodeInfoInner );
3981 
3982     // Cell preferred width
3983     SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell );
3984     if ( nCell )
3985         nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 );
3986     m_pSerializer->singleElementNS( XML_w, XML_tcW,
3987            FSNS( XML_w, XML_w ), OString::number(nWidth),
3988            FSNS( XML_w, XML_type ), "dxa" );
3989 
3990     // Horizontal spans
3991     const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
3992     SwWriteTableRow *pRow = rRows[ nRow ].get();
3993     const SwWriteTableCells& rTableCells =  pRow->GetCells();
3994     if (nCell < rTableCells.size() )
3995     {
3996         const SwWriteTableCell& rCell = *rTableCells[nCell];
3997         const sal_uInt16 nColSpan = rCell.GetColSpan();
3998         if ( nColSpan > 1 )
3999             m_pSerializer->singleElementNS( XML_w, XML_gridSpan,
4000                     FSNS( XML_w, XML_val ), OString::number(nColSpan) );
4001     }
4002 
4003     // Vertical merges
4004     ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow();
4005     sal_Int32 vSpan = (*xRowSpans)[nCell];
4006     if ( vSpan > 1 )
4007     {
4008         m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "restart");
4009     }
4010     else if ( vSpan < 0 )
4011     {
4012         m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "continue");
4013     }
4014 
4015     if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4016     {
4017         const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4018         std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("CellCnfStyle");
4019         if (it != rGrabBag.end())
4020         {
4021             uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4022             m_pTableStyleExport->CnfStyle(aAttributes);
4023         }
4024     }
4025 
4026 
4027     const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( );
4028     const SvxBoxItem& rDefaultBox = (*tableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( );
4029     {
4030         // The cell borders
4031         impl_borders(m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma),
4032                      m_aTableStyleConfs.back());
4033     }
4034 
4035     TableBackgrounds( pTableTextNodeInfoInner );
4036 
4037     {
4038         // Cell margins
4039         impl_cellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox );
4040     }
4041 
4042     TableVerticalCell( pTableTextNodeInfoInner );
4043 
4044     m_pSerializer->endElementNS( XML_w, XML_tcPr );
4045 }
4046 
4047 void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4048 {
4049     const SwTable* pTable = pTableTextNodeInfoInner->getTable();
4050     if (m_xTableWrt && pTable == m_xTableWrt->GetTable())
4051         return;
4052 
4053     tools::Long nPageSize = 0;
4054     bool bRelBoxSize = false;
4055 
4056     // Create the SwWriteTable instance to use col spans (and maybe other infos)
4057     GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
4058 
4059     const SwFrameFormat *pFormat = pTable->GetFrameFormat( );
4060     const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( ));
4061 
4062     const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout();
4063     if( pLayout && pLayout->IsExportable() )
4064         m_xTableWrt.reset(new SwWriteTable(pTable, pLayout));
4065     else
4066         m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false));
4067 }
4068 
4069 void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4070 {
4071     m_aTableStyleConfs.push_back({});
4072 
4073     // In case any paragraph SDT's are open, close them here.
4074     EndParaSdtBlock();
4075 
4076     m_pSerializer->startElementNS(XML_w, XML_tbl);
4077 
4078     tableFirstCells.push_back(pTableTextNodeInfoInner);
4079     lastOpenCell.push_back(-1);
4080     lastClosedCell.push_back(-1);
4081 
4082     InitTableHelper( pTableTextNodeInfoInner );
4083     TableDefinition( pTableTextNodeInfoInner );
4084 }
4085 
4086 void DocxAttributeOutput::EndTable()
4087 {
4088     m_pSerializer->endElementNS( XML_w, XML_tbl );
4089 
4090     if ( m_tableReference->m_nTableDepth > 0 )
4091         --m_tableReference->m_nTableDepth;
4092 
4093     lastClosedCell.pop_back();
4094     lastOpenCell.pop_back();
4095     tableFirstCells.pop_back();
4096 
4097     // We closed the table; if it is a nested table, the cell that contains it
4098     // still continues
4099     // set to true only if we were in a nested table, not otherwise.
4100     if( !tableFirstCells.empty() )
4101         m_tableReference->m_bTableCellOpen = true;
4102 
4103     // Cleans the table helper
4104     m_xTableWrt.reset();
4105 
4106     m_aTableStyleConfs.pop_back();
4107 }
4108 
4109 void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4110 {
4111     m_pSerializer->startElementNS(XML_w, XML_tr);
4112 
4113     // Output the row properties
4114     m_pSerializer->startElementNS(XML_w, XML_trPr);
4115 
4116     // Header row: tblHeader
4117     const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
4118     if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
4119         m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false
4120 
4121     TableRowRedline( pTableTextNodeInfoInner );
4122     TableHeight( pTableTextNodeInfoInner );
4123     TableCanSplit( pTableTextNodeInfoInner );
4124 
4125     const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox();
4126     const SwTableLine* pTableLine = pTableBox->GetUpper();
4127     if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4128     {
4129         const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4130         std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("RowCnfStyle");
4131         if (it != rGrabBag.end())
4132         {
4133             uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4134             m_pTableStyleExport->CnfStyle(aAttributes);
4135         }
4136     }
4137 
4138 
4139     m_pSerializer->endElementNS( XML_w, XML_trPr );
4140 }
4141 
4142 void DocxAttributeOutput::EndTableRow( )
4143 {
4144     m_pSerializer->endElementNS( XML_w, XML_tr );
4145     lastOpenCell.back() = -1;
4146     lastClosedCell.back() = -1;
4147 }
4148 
4149 void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4150 {
4151     lastOpenCell.back() = nCell;
4152 
4153     InitTableHelper( pTableTextNodeInfoInner );
4154 
4155     m_pSerializer->startElementNS(XML_w, XML_tc);
4156 
4157     // Write the cell properties here
4158     TableCellProperties( pTableTextNodeInfoInner, nCell, nRow );
4159 
4160     m_tableReference->m_bTableCellOpen = true;
4161 }
4162 
4163 void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell)
4164 {
4165     lastClosedCell.back() = nCell;
4166     lastOpenCell.back() = -1;
4167 
4168     if (m_tableReference->m_bTableCellParaSdtOpen)
4169         EndParaSdtBlock();
4170 
4171     m_pSerializer->endElementNS( XML_w, XML_tc );
4172 
4173     m_tableReference->m_bTableCellOpen = false;
4174     m_tableReference->m_bTableCellParaSdtOpen = false;
4175 }
4176 
4177 void DocxAttributeOutput::TableInfoCell( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
4178 {
4179 }
4180 
4181 void DocxAttributeOutput::TableInfoRow( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfo*/ )
4182 {
4183 }
4184 
4185 namespace
4186 {
4187 
4188 /// Does the same as comphelper::string::padToLength(), but extends the start, not the end.
4189 OString lcl_padStartToLength(OString const & aString, sal_Int32 nLen, char cFill)
4190 {
4191     if (nLen > aString.getLength())
4192     {
4193         sal_Int32 nDiff = nLen - aString.getLength();
4194         OStringBuffer aBuffer;
4195         comphelper::string::padToLength(aBuffer, nDiff, cFill);
4196         aBuffer.append(aString);
4197         return aBuffer.makeStringAndClear();
4198     }
4199     else
4200         return aString;
4201 }
4202 
4203 //Keep this function in-sync with the one in writerfilter/.../SettingsTable.cxx
4204 //Since this is not import code, "-1" needs to be handled as the mode that LO will save as.
4205 //To identify how your code should handle a "-1", look in DocxExport::WriteSettings().
4206 sal_Int32 lcl_getWordCompatibilityMode(const DocxExport& rDocExport)
4207 {
4208     sal_Int32 nWordCompatibilityMode = rDocExport.getWordCompatibilityModeFromGrabBag();
4209 
4210     // TODO: this is duplicated, better store it in DocxExport member?
4211     if (!rDocExport.m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_EXT_LEADING))
4212     {
4213         if (nWordCompatibilityMode == -1 || 14 < nWordCompatibilityMode)
4214         {
4215             nWordCompatibilityMode = 14;
4216         }
4217     }
4218 
4219     return nWordCompatibilityMode;
4220 }
4221 
4222 }
4223 
4224 void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4225 {
4226     bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;
4227 
4228     // Write the table properties
4229     m_pSerializer->startElementNS(XML_w, XML_tblPr);
4230 
4231     static const sal_Int32 aOrder[] =
4232     {
4233         FSNS( XML_w, XML_tblStyle ),
4234         FSNS( XML_w, XML_tblpPr ),
4235         FSNS( XML_w, XML_tblOverlap ),
4236         FSNS( XML_w, XML_bidiVisual ),
4237         FSNS( XML_w, XML_tblStyleRowBandSize ),
4238         FSNS( XML_w, XML_tblStyleColBandSize ),
4239         FSNS( XML_w, XML_tblW ),
4240         FSNS( XML_w, XML_jc ),
4241         FSNS( XML_w, XML_tblCellSpacing ),
4242         FSNS( XML_w, XML_tblInd ),
4243         FSNS( XML_w, XML_tblBorders ),
4244         FSNS( XML_w, XML_shd ),
4245         FSNS( XML_w, XML_tblLayout ),
4246         FSNS( XML_w, XML_tblCellMar ),
4247         FSNS( XML_w, XML_tblLook ),
4248         FSNS( XML_w, XML_tblPrChange )
4249     };
4250 
4251     // postpone the output so that we can later []
4252     // prepend the properties before the run
4253     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
4254     m_pSerializer->mark(Tag_TableDefinition, comphelper::containerToSequence(aOrder));
4255 
4256     tools::Long nPageSize = 0;
4257     const char* widthType = "dxa";
4258 
4259     // If actual width of table is relative it should export is as "pct".`
4260     const SwTable *pTable = pTableTextNodeInfoInner->getTable();
4261     SwFrameFormat *pTableFormat = pTable->GetFrameFormat( );
4262     const SwFormatFrameSize &rSize = pTableFormat->GetFrameSize();
4263     int nWidthPercent = rSize.GetWidthPercent();
4264     // If we export a floating table: we use the widthPercent of the surrounding frame
4265     const ww8::Frame* pFloatingTableFrame = m_rExport.GetFloatingTableFrame();
4266     if (pFloatingTableFrame)
4267     {
4268         const SwFormatFrameSize &rFrameSize = pFloatingTableFrame->GetFrameFormat().GetFrameSize();
4269         nWidthPercent = rFrameSize.GetWidthPercent();
4270     }
4271 
4272     uno::Reference<beans::XPropertySet> xPropertySet(SwXTextTables::GetObject(*pTable->GetFrameFormat( )),uno::UNO_QUERY);
4273     bool isWidthRelative = false;
4274     xPropertySet->getPropertyValue("IsWidthRelative") >>= isWidthRelative;
4275     if (!isWidthRelative && !nWidthPercent)
4276     {
4277         // The best fit for "automatic" table placement is relative 100%
4278         short nHoriOrient = -1;
4279         xPropertySet->getPropertyValue("HoriOrient") >>= nHoriOrient;
4280         isWidthRelative = nHoriOrient == text::HoriOrientation::FULL;
4281         if (isWidthRelative)
4282             nWidthPercent = 100;
4283     }
4284 
4285     if(isWidthRelative)
4286     {
4287        /**
4288        * As per ECMA Specification : ECMA-376, Second Edition, Part 1 - Fundamentals And Markup Language Reference [ 17.18.90 ST_TableWidth (Table Width Units)]
4289        * http://www.schemacentral.com/sc/ooxml/a-w_type-7.html
4290        *
4291        * Fiftieths of a Percent :
4292        * http://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
4293        * pct Width is in Fiftieths of a Percent
4294        *
4295        * ex. If the Table width is 50% then
4296        * Width in Fiftieths of a percent is (50 * 50) % or 0.5 * 5000 = 2500pct
4297        **/
4298         nPageSize = nWidthPercent * 50 ;
4299         widthType = "pct" ;
4300     }
4301     else
4302     {
4303         bool bRelBoxSize = false;
4304         // Create the SwWriteTable instance to use col spans (and maybe other infos)
4305         GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
4306         if(nPageSize == 0)
4307             widthType = "auto";
4308     }
4309 
4310     // Output the table preferred width
4311     m_pSerializer->singleElementNS( XML_w, XML_tblW,
4312             FSNS( XML_w, XML_w ), OString::number(nPageSize),
4313             FSNS( XML_w, XML_type ), widthType );
4314 
4315     // Disable layout autofit, as it does not exist in LibreOffice yet
4316     m_pSerializer->singleElementNS( XML_w, XML_tblLayout,
4317             FSNS( XML_w, XML_type ), "fixed" );
4318 
4319     // Look for the table style property in the table grab bag
4320     std::map<OUString, css::uno::Any> aGrabBag =
4321             pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)->GetGrabBag();
4322 
4323     // We should clear the TableStyle map. In case of Table inside multiple tables it contains the
4324     // table border style of the previous table.
4325     std::map<SvxBoxItemLine, css::table::BorderLine2>& rTableStyleConf = m_aTableStyleConfs.back();
4326     rTableStyleConf.clear();
4327 
4328     // Extract properties from grab bag
4329     for( const auto & rGrabBagElement : aGrabBag )
4330     {
4331         if( rGrabBagElement.first == "TableStyleName")
4332         {
4333             OString sStyleName = OUStringToOString( rGrabBagElement.second.get<OUString>(), RTL_TEXTENCODING_UTF8 );
4334             m_pSerializer->singleElementNS(XML_w, XML_tblStyle, FSNS(XML_w, XML_val), sStyleName);
4335         }
4336         else if( rGrabBagElement.first == "TableStyleTopBorder" )
4337             rTableStyleConf[SvxBoxItemLine::TOP] = rGrabBagElement.second.get<table::BorderLine2>();
4338         else if( rGrabBagElement.first == "TableStyleBottomBorder" )
4339             rTableStyleConf[SvxBoxItemLine::BOTTOM]
4340                 = rGrabBagElement.second.get<table::BorderLine2>();
4341         else if( rGrabBagElement.first == "TableStyleLeftBorder" )
4342             rTableStyleConf[SvxBoxItemLine::LEFT]
4343                 = rGrabBagElement.second.get<table::BorderLine2>();
4344         else if( rGrabBagElement.first == "TableStyleRightBorder" )
4345             rTableStyleConf[SvxBoxItemLine::RIGHT]
4346                 = rGrabBagElement.second.get<table::BorderLine2>();
4347         else if (rGrabBagElement.first == "TableStyleLook")
4348         {
4349             rtl::Reference<FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList();
4350             const uno::Sequence<beans::PropertyValue> aAttributeList = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
4351 
4352             for (const auto& rAttribute : aAttributeList)
4353             {
4354                 if (rAttribute.Name == "val")
4355                     pAttributeList->add(FSNS(XML_w, XML_val), lcl_padStartToLength(OString::number(rAttribute.Value.get<sal_Int32>(), 16), 4, '0'));
4356                 else
4357                 {
4358                     static DocxStringTokenMap const aTokens[] =
4359                     {
4360                         {"firstRow", XML_firstRow},
4361                         {"lastRow", XML_lastRow},
4362                         {"firstColumn", XML_firstColumn},
4363                         {"lastColumn", XML_lastColumn},
4364                         {"noHBand", XML_noHBand},
4365                         {"noVBand", XML_noVBand},
4366                         {nullptr, 0}
4367                     };
4368 
4369                     if (sal_Int32 nToken = DocxStringGetToken(aTokens, rAttribute.Name))
4370                         pAttributeList->add(FSNS(XML_w, nToken), (rAttribute.Value.get<sal_Int32>() ? "1" : "0"));
4371                 }
4372             }
4373 
4374             m_pSerializer->singleElementNS(XML_w, XML_tblLook, pAttributeList);
4375         }
4376         else if (rGrabBagElement.first == "TablePosition" &&
4377                         // skip empty table position (tables in footnotes converted to
4378                         // floating tables temporarily, don't export this)
4379                         rGrabBagElement.second != uno::Any() )
4380         {
4381             rtl::Reference<FastAttributeList> attrListTablePos = FastSerializerHelper::createAttrList( );
4382             const uno::Sequence<beans::PropertyValue> aTablePosition = rGrabBagElement.second.get<uno::Sequence<beans::PropertyValue> >();
4383             // look for a surrounding frame and take it's position values
4384             const ww8::Frame* pFrame = m_rExport.GetFloatingTableFrame();
4385             if( pFrame )
4386             {
4387                 // we export the values of the surrounding Frame
4388                 OString sOrientation;
4389                 sal_Int32 nValue;
4390 
4391                 // If tblpXSpec or tblpYSpec are present, we do not write tblpX or tblpY!
4392                 OString sTblpXSpec = convertToOOXMLHoriOrient( pFrame->GetFrameFormat().GetHoriOrient().GetHoriOrient(), pFrame->GetFrameFormat().GetHoriOrient().IsPosToggle() );
4393                 OString sTblpYSpec = convertToOOXMLVertOrient( pFrame->GetFrameFormat().GetVertOrient().GetVertOrient() );
4394 
4395                 sOrientation = convertToOOXMLVertOrientRel( pFrame->GetFrameFormat().GetVertOrient().GetRelationOrient() );
4396                 attrListTablePos->add(FSNS(XML_w, XML_vertAnchor), sOrientation);
4397 
4398                 if( !sTblpYSpec.isEmpty() )
4399                     attrListTablePos->add(FSNS(XML_w, XML_tblpYSpec), sTblpYSpec);
4400 
4401                 sOrientation = convertToOOXMLHoriOrientRel( pFrame->GetFrameFormat().GetHoriOrient().GetRelationOrient() );
4402                 attrListTablePos->add(FSNS(XML_w, XML_horzAnchor), sOrientation);
4403 
4404                 if( !sTblpXSpec.isEmpty() )
4405                     attrListTablePos->add(FSNS(XML_w, XML_tblpXSpec), sTblpXSpec);
4406 
4407                 nValue = pFrame->GetFrameFormat().GetULSpace().GetLower();
4408                 if( nValue != 0 )
4409                     attrListTablePos->add( FSNS( XML_w, XML_bottomFromText ), OString::number( nValue ) );
4410 
4411                 nValue = pFrame->GetFrameFormat().GetLRSpace().GetLeft();
4412                 if( nValue != 0 )
4413                     attrListTablePos->add( FSNS( XML_w, XML_leftFromText ), OString::number( nValue ) );
4414 
4415                 nValue = pFrame->GetFrameFormat().GetLRSpace().GetRight();
4416                 if( nValue != 0 )
4417                     attrListTablePos->add( FSNS( XML_w, XML_rightFromText ), OString::number( nValue ) );
4418 
4419                 nValue = pFrame->GetFrameFormat().GetULSpace().GetUpper();
4420                 if( nValue != 0 )
4421                     attrListTablePos->add( FSNS( XML_w, XML_topFromText ), OString::number( nValue ) );
4422 
4423                 if( sTblpXSpec.isEmpty() ) // do not write tblpX if tblpXSpec is present
4424                 {
4425                     nValue = pFrame->GetFrameFormat().GetHoriOrient().GetPos();
4426                     // we need to revert the additional shift introduced by
4427                     // lcl_DecrementHoriOrientPosition() in writerfilter
4428                     // 1st: left distance of the table
4429                     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4430                     const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat();
4431                     const SvxBoxItem& rBox = pFrameFormat->GetBox( );
4432                     sal_uInt16 nLeftDistance = rBox.GetDistance(SvxBoxItemLine::LEFT);
4433                     nValue += nLeftDistance;
4434 
4435                     // 2nd: if a left border is given, revert the shift by half the width
4436                     // from lcl_DecrementHoriOrientPosition() in writerfilter
4437                     if (const editeng::SvxBorderLine* pLeftBorder = rBox.GetLeft())
4438                     {
4439                         tools::Long nWidth = pLeftBorder->GetWidth();
4440                         nValue += (nWidth / 2);
4441                     }
4442 
4443                     attrListTablePos->add( FSNS( XML_w, XML_tblpX ), OString::number( nValue ) );
4444                 }
4445 
4446                 if( sTblpYSpec.isEmpty() ) // do not write tblpY if tblpYSpec is present
4447                 {
4448                     nValue = pFrame->GetFrameFormat().GetVertOrient().GetPos();
4449                     attrListTablePos->add( FSNS( XML_w, XML_tblpY ), OString::number( nValue ) );
4450                 }
4451             }
4452             else // ( pFrame = 0 )
4453             {
4454                 // we export the values from the grabBag
4455                 for (const auto& rProp : aTablePosition)
4456                 {
4457                     if (rProp.Name == "vertAnchor" && !rProp.Value.get<OUString>().isEmpty())
4458                     {
4459                         OString sOrientation = OUStringToOString( rProp.Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
4460                         attrListTablePos->add(FSNS(XML_w, XML_vertAnchor), sOrientation);
4461                     }
4462                     else if (rProp.Name == "tblpYSpec" && !rProp.Value.get<OUString>().isEmpty())
4463                     {
4464                         OString sOrientation = OUStringToOString( rProp.Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
4465                         attrListTablePos->add(FSNS(XML_w, XML_tblpYSpec), sOrientation);
4466                     }
4467                     else if (rProp.Name == "horzAnchor" && !rProp.Value.get<OUString>().isEmpty())
4468                     {
4469                         OString sOrientation = OUStringToOString( rProp.Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
4470                         attrListTablePos->add(FSNS(XML_w, XML_horzAnchor), sOrientation);
4471                     }
4472                     else if (rProp.Name == "tblpXSpec" && !rProp.Value.get<OUString>().isEmpty())
4473                     {
4474                         OString sOrientation = OUStringToOString( rProp.Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
4475                         attrListTablePos->add(FSNS(XML_w, XML_tblpXSpec), sOrientation);
4476                     }
4477                     else if (rProp.Name == "bottomFromText")
4478                     {
4479                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4480                         attrListTablePos->add( FSNS( XML_w, XML_bottomFromText ), OString::number( nValue ) );
4481                     }
4482                     else if (rProp.Name == "leftFromText")
4483                     {
4484                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4485                         attrListTablePos->add( FSNS( XML_w, XML_leftFromText ), OString::number( nValue ) );
4486                     }
4487                     else if (rProp.Name == "rightFromText")
4488                     {
4489                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4490                         attrListTablePos->add( FSNS( XML_w, XML_rightFromText ), OString::number( nValue ) );
4491                     }
4492                     else if (rProp.Name == "topFromText")
4493                     {
4494                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4495                         attrListTablePos->add( FSNS( XML_w, XML_topFromText ), OString::number( nValue ) );
4496                     }
4497                     else if (rProp.Name == "tblpX")
4498                     {
4499                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4500                         attrListTablePos->add( FSNS( XML_w, XML_tblpX ), OString::number( nValue ) );
4501                     }
4502                     else if (rProp.Name == "tblpY")
4503                     {
4504                         sal_Int32 nValue = rProp.Value.get<sal_Int32>();
4505                         attrListTablePos->add( FSNS( XML_w, XML_tblpY ), OString::number( nValue ) );
4506                     }
4507                 }
4508             }
4509 
4510             m_pSerializer->singleElementNS( XML_w, XML_tblpPr, attrListTablePos);
4511             attrListTablePos = nullptr;
4512         }
4513         else
4514             SAL_WARN("sw.ww8", "DocxAttributeOutput::TableDefinition: unhandled property: " << rGrabBagElement.first);
4515     }
4516 
4517     // Output the table alignment
4518     const char* pJcVal;
4519     sal_Int32 nIndent = 0;
4520     switch ( pTableFormat->GetHoriOrient( ).GetHoriOrient( ) )
4521     {
4522         case text::HoriOrientation::CENTER:
4523             pJcVal = "center";
4524             break;
4525         case text::HoriOrientation::RIGHT:
4526             if ( bEcma )
4527                 pJcVal = "right";
4528             else
4529                 pJcVal = "end";
4530             break;
4531         default:
4532         case text::HoriOrientation::NONE:
4533         case text::HoriOrientation::LEFT_AND_WIDTH:
4534         {
4535             if ( bEcma )
4536                 pJcVal = "left";
4537             else
4538                 pJcVal = "start";
4539             nIndent = sal_Int32( pTableFormat->GetLRSpace().GetLeft() );
4540 
4541             // Table indentation has different meaning in Word, depending if the table is nested or not.
4542             // If nested, tblInd is added to parent table's left spacing and defines left edge position
4543             // If not nested, text position of left-most cell must be at absolute X = tblInd
4544             // so, table_spacing + table_spacing_to_content = tblInd
4545 
4546             // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables;
4547             // if no compatibilityMode is defined (which now should only happen on a new export to .docx),
4548             // LO uses a higher compatibility than 2010's 14.
4549             sal_Int32 nMode = lcl_getWordCompatibilityMode(m_rExport);
4550 
4551             const SwFrameFormat* pFrameFormat = pTableTextNodeInfoInner->getTableBox()->GetFrameFormat();
4552             if ((0 < nMode && nMode <= 14) && m_tableReference->m_nTableDepth == 0)
4553                 nIndent += pFrameFormat->GetBox().GetDistance( SvxBoxItemLine::LEFT );
4554             else
4555             {
4556                 // adjust for SW considering table to start mid-border instead of nested/2013's left-side-of-border.
4557                 nIndent -= pFrameFormat->GetBox().CalcLineWidth( SvxBoxItemLine::LEFT ) / 2;
4558             }
4559 
4560             break;
4561         }
4562     }
4563     m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pJcVal);
4564 
4565     // Output the table background color (although cell value still needs to be specified)
4566     const SvxBrushItem *pColorProp = pTableFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
4567     Color aColor = pColorProp ? pColorProp->GetColor() : COL_AUTO;
4568     if ( aColor != COL_AUTO )
4569     {
4570         OString sColor = msfilter::util::ConvertColor( aColor );
4571         m_pSerializer->singleElementNS( XML_w, XML_shd,
4572                 FSNS( XML_w, XML_fill ), sColor,
4573                 FSNS( XML_w, XML_val ), "clear" );
4574     }
4575 
4576     // Output the table borders
4577     TableDefaultBorders( pTableTextNodeInfoInner );
4578 
4579     // Output the default cell margins
4580     TableDefaultCellMargins( pTableTextNodeInfoInner );
4581 
4582     TableBidi( pTableTextNodeInfoInner );
4583 
4584     // Table indent (need to get written even if == 0)
4585     m_pSerializer->singleElementNS( XML_w, XML_tblInd,
4586             FSNS( XML_w, XML_w ), OString::number(nIndent),
4587             FSNS( XML_w, XML_type ), "dxa" );
4588 
4589     // Merge the marks for the ordered elements
4590     m_pSerializer->mergeTopMarks(Tag_TableDefinition);
4591 
4592     m_pSerializer->endElementNS( XML_w, XML_tblPr );
4593 
4594     // Write the table grid infos
4595     m_pSerializer->startElementNS(XML_w, XML_tblGrid);
4596     sal_Int32 nPrv = 0;
4597     ww8::WidthsPtr pColumnWidths = GetColumnWidths( pTableTextNodeInfoInner );
4598     for ( auto aColumnWidth : *pColumnWidths )
4599     {
4600         sal_Int32 nWidth  =  sal_Int32( aColumnWidth ) - nPrv;
4601         m_pSerializer->singleElementNS( XML_w, XML_gridCol,
4602                FSNS( XML_w, XML_w ), OString::number(nWidth) );
4603         nPrv = sal_Int32( aColumnWidth );
4604     }
4605 
4606     m_pSerializer->endElementNS( XML_w, XML_tblGrid );
4607 }
4608 
4609 void DocxAttributeOutput::TableDefaultBorders( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
4610 {
4611     // Table defaults should only be created IF m_aTableStyleConf contents haven't come from a table style.
4612     // Previously this function wrote out Cell A1 as the table default, causing problems with no benefit.
4613 }
4614 
4615 void DocxAttributeOutput::TableDefaultCellMargins( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4616 {
4617     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4618     const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat();
4619     const SvxBoxItem& rBox = pFrameFormat->GetBox( );
4620     const bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;
4621 
4622     impl_cellMargins(m_pSerializer, rBox, XML_tblCellMar, !bEcma);
4623 }
4624 
4625 void DocxAttributeOutput::TableBackgrounds( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4626 {
4627     const SwTable *pTable = pTableTextNodeInfoInner->getTable();
4628     const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
4629     const SwTableLine *pTableRow = pTableBox->GetUpper();
4630     const SwFrameFormat *pFormat = pTableBox->GetFrameFormat( );
4631 
4632     const SvxBrushItem *pColorProp = pFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
4633     Color aColor = pColorProp ? pColorProp->GetColor() : COL_AUTO;
4634 
4635     const SwFrameFormat *pRowFormat = pTableRow->GetFrameFormat( );
4636     const SvxBrushItem *pRowColorProp = pRowFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
4637     if ( pRowColorProp && aColor == COL_AUTO)
4638         aColor = pRowColorProp->GetColor();
4639 
4640     const SwFrameFormat *pTableFormat = pTable->GetFrameFormat( );
4641     const SvxBrushItem *pTableColorProp = pTableFormat->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND);
4642     if ( pTableColorProp && aColor == COL_AUTO )
4643         aColor = pTableColorProp->GetColor();
4644 
4645     const OString sColor = msfilter::util::ConvertColor( aColor );
4646 
4647     std::map<OUString, css::uno::Any> aGrabBag =
4648             pFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)->GetGrabBag();
4649 
4650     OString sOriginalColor;
4651     std::map<OUString, css::uno::Any>::iterator aGrabBagElement = aGrabBag.find("originalColor");
4652     if( aGrabBagElement != aGrabBag.end() )
4653         sOriginalColor = OUStringToOString( aGrabBagElement->second.get<OUString>(), RTL_TEXTENCODING_UTF8 );
4654 
4655     if ( sOriginalColor != sColor )
4656     {
4657         // color changed by the user, or no grab bag: write sColor
4658         if ( sColor != "auto" )
4659         {
4660             m_pSerializer->singleElementNS( XML_w, XML_shd,
4661                 FSNS( XML_w, XML_fill ), sColor,
4662                 FSNS( XML_w, XML_val ), "clear" );
4663         }
4664     }
4665     else
4666     {
4667         rtl::Reference<sax_fastparser::FastAttributeList> pAttrList;
4668 
4669         for( const auto & rGrabBagElement : aGrabBag )
4670         {
4671             if (!rGrabBagElement.second.has<OUString>())
4672                 continue;
4673 
4674             OString sValue = OUStringToOString( rGrabBagElement.second.get<OUString>(), RTL_TEXTENCODING_UTF8 );
4675             if( rGrabBagElement.first == "themeFill")
4676                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFill ), sValue.getStr() );
4677             else if( rGrabBagElement.first == "themeFillTint")
4678                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFillTint ), sValue.getStr() );
4679             else if( rGrabBagElement.first == "themeFillShade")
4680                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeFillShade ), sValue.getStr() );
4681             else if( rGrabBagElement.first == "fill" )
4682                 AddToAttrList( pAttrList, FSNS( XML_w, XML_fill ), sValue.getStr() );
4683             else if( rGrabBagElement.first == "themeColor")
4684                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeColor ), sValue.getStr() );
4685             else if( rGrabBagElement.first == "themeTint")
4686                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeTint ), sValue.getStr() );
4687             else if( rGrabBagElement.first == "themeShade")
4688                 AddToAttrList( pAttrList, FSNS( XML_w, XML_themeShade ), sValue.getStr() );
4689             else if( rGrabBagElement.first == "color")
4690                 AddToAttrList( pAttrList, FSNS( XML_w, XML_color ), sValue.getStr() );
4691             else if( rGrabBagElement.first == "val")
4692                 AddToAttrList( pAttrList, FSNS( XML_w, XML_val ), sValue.getStr() );
4693         }
4694         m_pSerializer->singleElementNS( XML_w, XML_shd, pAttrList.get() );
4695     }
4696 }
4697 
4698 void DocxAttributeOutput::TableRowRedline( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4699 {
4700     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4701     const SwTableLine * pTabLine = pTabBox->GetUpper();
4702 
4703     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4704         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
4705 
4706     // check table row property "HasTextChangesOnly"
4707     SwRedlineTable::size_type nPos(0);
4708     SwRedlineTable::size_type nChange = pTabLine->UpdateTextChangesOnly(nPos);
4709     if ( nChange != SwRedlineTable::npos )
4710     {
4711         const SwRedlineTable& aRedlineTable = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
4712         const SwRangeRedline* pRedline = aRedlineTable[ nChange ];
4713         SwTableRowRedline* pTableRowRedline = nullptr;
4714         bool bIsInExtra = false;
4715 
4716         // use the original DOCX redline data stored in ExtraRedlineTable,
4717         // if it exists and its type wasn't changed
4718         const SwExtraRedlineTable& aExtraRedlineTable = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable();
4719         for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < aExtraRedlineTable.GetSize(); ++nCurRedlinePos )
4720         {
4721             SwExtraRedline* pExtraRedline = aExtraRedlineTable.GetRedline(nCurRedlinePos);
4722             pTableRowRedline = dynamic_cast<SwTableRowRedline*>(pExtraRedline);
4723             if (pTableRowRedline && &pTableRowRedline->GetTableLine() == pTabLine)
4724             {
4725                 bIsInExtra = true;
4726                 break;
4727             }
4728         }
4729 
4730         const SwRedlineData& aRedlineData = bIsInExtra &&
4731             // still the same type (an inserted row could become a tracked deleted one)
4732             pTableRowRedline->GetRedlineData().GetType() == pRedline->GetRedlineData().GetType()
4733                 ? pTableRowRedline->GetRedlineData()
4734                 : pRedline->GetRedlineData();
4735 
4736         // Note: all redline ranges and table row redline (with the same author and timestamp)
4737         // use the same redline id in OOXML exported by MSO, but it seems, the recent solution
4738         // (different IDs for different ranges, also row changes) is also portable.
4739         OString aId( OString::number( m_nRedlineId++ ) );
4740         const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) );
4741         OString aAuthor( OUStringToOString( bRemovePersonalInfo
4742                         ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
4743                         : rAuthor, RTL_TEXTENCODING_UTF8 ) );
4744 
4745         OString aDate( DateTimeToOString( bRemovePersonalInfo
4746                     ? DateTime(Date( 1, 1, 1970 )) // Epoch time
4747                     : aRedlineData.GetTimeStamp() ) );
4748 
4749         m_pSerializer->singleElementNS( XML_w,
4750                             RedlineType::Delete == pRedline->GetType() ? XML_del : XML_ins,
4751                             FSNS( XML_w, XML_id ), aId,
4752                             FSNS( XML_w, XML_author ), aAuthor,
4753                             FSNS( XML_w, XML_date ), aDate );
4754         return;
4755     }
4756 }
4757 
4758 void DocxAttributeOutput::TableCellRedline( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4759 {
4760     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4761 
4762     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4763         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
4764 
4765     // search next Redline
4766     const SwExtraRedlineTable& aExtraRedlineTable = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable();
4767     for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < aExtraRedlineTable.GetSize(); ++nCurRedlinePos )
4768     {
4769         SwExtraRedline* pExtraRedline = aExtraRedlineTable.GetRedline(nCurRedlinePos);
4770         const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline);
4771         if (pTableCellRedline && &pTableCellRedline->GetTableBox() == pTabBox)
4772         {
4773             // Redline for this table cell
4774             const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData();
4775             RedlineType nRedlineType = aRedlineData.GetType();
4776             switch (nRedlineType)
4777             {
4778                 case RedlineType::TableCellInsert:
4779                 case RedlineType::TableCellDelete:
4780                 {
4781                     OString aId( OString::number( m_nRedlineId++ ) );
4782                     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( aRedlineData.GetAuthor() ) );
4783                     OString aAuthor( OUStringToOString( bRemovePersonalInfo
4784                         ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
4785                         : rAuthor, RTL_TEXTENCODING_UTF8 ) );
4786 
4787                     OString aDate( DateTimeToOString( bRemovePersonalInfo
4788                             ? DateTime(Date( 1, 1, 1970 )) // Epoch time
4789                             : aRedlineData.GetTimeStamp() ) );
4790 
4791                     if (nRedlineType == RedlineType::TableCellInsert)
4792                         m_pSerializer->singleElementNS( XML_w, XML_cellIns,
4793                             FSNS( XML_w, XML_id ), aId,
4794                             FSNS( XML_w, XML_author ), aAuthor,
4795                             FSNS( XML_w, XML_date ), aDate );
4796                     else if (nRedlineType == RedlineType::TableCellDelete)
4797                         m_pSerializer->singleElementNS( XML_w, XML_cellDel,
4798                             FSNS( XML_w, XML_id ), aId,
4799                             FSNS( XML_w, XML_author ), aAuthor,
4800                             FSNS( XML_w, XML_date ), aDate );
4801                 }
4802                 break;
4803                 default: break;
4804             }
4805         }
4806     }
4807 }
4808 
4809 void DocxAttributeOutput::TableHeight( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4810 {
4811     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4812     const SwTableLine * pTabLine = pTabBox->GetUpper();
4813     const SwFrameFormat * pLineFormat = pTabLine->GetFrameFormat();
4814 
4815     const SwFormatFrameSize& rLSz = pLineFormat->GetFrameSize();
4816     if ( !(SwFrameSize::Variable != rLSz.GetHeightSizeType() && rLSz.GetHeight()) )
4817         return;
4818 
4819     sal_Int32 nHeight = rLSz.GetHeight();
4820     const char *pRule = nullptr;
4821 
4822     switch ( rLSz.GetHeightSizeType() )
4823     {
4824         case SwFrameSize::Fixed: pRule = "exact"; break;
4825         case SwFrameSize::Minimum: pRule = "atLeast"; break;
4826         default:           break;
4827     }
4828 
4829     if ( pRule )
4830         m_pSerializer->singleElementNS( XML_w, XML_trHeight,
4831                 FSNS( XML_w, XML_val ), OString::number(nHeight),
4832                 FSNS( XML_w, XML_hRule ), pRule );
4833 }
4834 
4835 void DocxAttributeOutput::TableCanSplit( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4836 {
4837     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4838     const SwTableLine * pTabLine = pTabBox->GetUpper();
4839     const SwFrameFormat * pLineFormat = pTabLine->GetFrameFormat();
4840 
4841     const SwFormatRowSplit& rSplittable = pLineFormat->GetRowSplit( );
4842     // if rSplittable is true then no need to write <w:cantSplit w:val="false"/>
4843     // as default row prop is allow row to break across page.
4844     if( !rSplittable.GetValue( ) )
4845         m_pSerializer->singleElementNS(XML_w, XML_cantSplit, FSNS(XML_w, XML_val), "true");
4846 }
4847 
4848 void DocxAttributeOutput::TableBidi( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4849 {
4850     const SwTable * pTable = pTableTextNodeInfoInner->getTable();
4851     const SwFrameFormat * pFrameFormat = pTable->GetFrameFormat();
4852 
4853     if ( m_rExport.TrueFrameDirection( *pFrameFormat ) == SvxFrameDirection::Horizontal_RL_TB )
4854     {
4855         m_pSerializer->singleElementNS(XML_w, XML_bidiVisual, FSNS(XML_w, XML_val), "true");
4856     }
4857 }
4858 
4859 void DocxAttributeOutput::TableVerticalCell( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
4860 {
4861     const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
4862     const SwFrameFormat *pFrameFormat = pTabBox->GetFrameFormat( );
4863 
4864     if ( SvxFrameDirection::Vertical_RL_TB == m_rExport.TrueFrameDirection( *pFrameFormat ) )
4865         m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), "tbRl");
4866     else if ( SvxFrameDirection::Vertical_LR_BT == m_rExport.TrueFrameDirection( *pFrameFormat ) )
4867     {
4868         m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), "btLr");
4869     }
4870 
4871     const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
4872     SwWriteTableRow *pRow = rRows[ pTableTextNodeInfoInner->getRow( ) ].get();
4873     sal_uInt32 nCell = pTableTextNodeInfoInner->getCell();
4874     const SwWriteTableCells& rTableCells =  pRow->GetCells();
4875     if (nCell >= rTableCells.size() )
4876         return;
4877 
4878     const SwWriteTableCell *const pCell = pRow->GetCells()[ nCell ].get();
4879     switch( pCell->GetVertOri())
4880     {
4881     case text::VertOrientation::TOP:
4882         break;
4883     case text::VertOrientation::CENTER:
4884         m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center");
4885         break;
4886     case text::VertOrientation::BOTTOM:
4887         m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom");
4888         break;
4889     }
4890 }
4891 
4892 void DocxAttributeOutput::TableNodeInfoInner( ww8::WW8TableNodeInfoInner::Pointer_t pNodeInfoInner )
4893 {
4894     // This is called when the nested table ends in a cell, and there's no
4895     // paragraph behind that; so we must check for the ends of cell, rows,
4896     // tables
4897     // ['true' to write an empty paragraph, MS Word insists on that]
4898     FinishTableRowCell( pNodeInfoInner, true );
4899 }
4900 
4901 void DocxAttributeOutput::TableOrientation( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
4902 {
4903     SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableOrientation( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )" );
4904 }
4905 
4906 void DocxAttributeOutput::TableSpacing( ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/ )
4907 {
4908     SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableSpacing( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )" );
4909 }
4910 
4911 void DocxAttributeOutput::TableRowEnd( sal_uInt32 /*nDepth*/ )
4912 {
4913     SAL_INFO("sw.ww8", "TODO: DocxAttributeOutput::TableRowEnd( sal_uInt32 nDepth = 1 )" );
4914 }
4915 
4916 void DocxAttributeOutput::StartStyles()
4917 {
4918     m_pSerializer->startElementNS( XML_w, XML_styles,
4919             FSNS( XML_xmlns, XML_w ),   GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)),
4920             FSNS( XML_xmlns, XML_w14 ), GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)),
4921             FSNS( XML_xmlns, XML_mc ),  GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)),
4922             FSNS( XML_mc, XML_Ignorable ), "w14" );
4923 
4924     DocDefaults();
4925     LatentStyles();
4926 }
4927 
4928 sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, std::u16string_view rName)
4929 {
4930     OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8);
4931     while (pMap->pToken)
4932     {
4933         if (sName == pMap->pToken)
4934             return pMap->nToken;
4935         ++pMap;
4936     }
4937     return 0;
4938 }
4939 
4940 namespace
4941 {
4942 
4943 DocxStringTokenMap const aDefaultTokens[] = {
4944     {"defQFormat", XML_defQFormat},
4945     {"defUnhideWhenUsed", XML_defUnhideWhenUsed},
4946     {"defSemiHidden", XML_defSemiHidden},
4947     {"count", XML_count},
4948     {"defUIPriority", XML_defUIPriority},
4949     {"defLockedState", XML_defLockedState},
4950     {nullptr, 0}
4951 };
4952 
4953 DocxStringTokenMap const aExceptionTokens[] = {
4954     {"name", XML_name},
4955     {"locked", XML_locked},
4956     {"uiPriority", XML_uiPriority},
4957     {"semiHidden", XML_semiHidden},
4958     {"unhideWhenUsed", XML_unhideWhenUsed},
4959     {"qFormat", XML_qFormat},
4960     {nullptr, 0}
4961 };
4962 
4963 }
4964 
4965 void DocxAttributeOutput::LatentStyles()
4966 {
4967     // Do we have latent styles available?
4968     uno::Reference<beans::XPropertySet> xPropertySet(m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW);
4969     uno::Sequence<beans::PropertyValue> aInteropGrabBag;
4970     xPropertySet->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag;
4971     uno::Sequence<beans::PropertyValue> aLatentStyles;
4972     auto pProp = std::find_if(std::cbegin(aInteropGrabBag), std::cend(aInteropGrabBag),
4973         [](const beans::PropertyValue& rProp) { return rProp.Name == "latentStyles"; });
4974     if (pProp != std::cend(aInteropGrabBag))
4975         pProp->Value >>= aLatentStyles;
4976     if (!aLatentStyles.hasElements())
4977         return;
4978 
4979     // Extract default attributes first.
4980     rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList();
4981     uno::Sequence<beans::PropertyValue> aLsdExceptions;
4982     for (const auto& rLatentStyle : std::as_const(aLatentStyles))
4983     {
4984         if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, rLatentStyle.Name))
4985             pAttributeList->add(FSNS(XML_w, nToken), OUStringToOString(rLatentStyle.Value.get<OUString>(), RTL_TEXTENCODING_UTF8));
4986         else if (rLatentStyle.Name == "lsdExceptions")
4987             rLatentStyle.Value >>= aLsdExceptions;
4988     }
4989 
4990     m_pSerializer->startElementNS(XML_w, XML_latentStyles, pAttributeList);
4991     pAttributeList = nullptr;
4992 
4993     // Then handle the exceptions.
4994     for (const auto& rLsdException : std::as_const(aLsdExceptions))
4995     {
4996         pAttributeList = FastSerializerHelper::createAttrList();
4997 
4998         uno::Sequence<beans::PropertyValue> aAttributes;
4999         rLsdException.Value >>= aAttributes;
5000         for (const auto& rAttribute : std::as_const(aAttributes))
5001             if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, rAttribute.Name))
5002                 pAttributeList->add(FSNS(XML_w, nToken), OUStringToOString(rAttribute.Value.get<OUString>(), RTL_TEXTENCODING_UTF8));
5003 
5004         m_pSerializer->singleElementNS(XML_w, XML_lsdException, pAttributeList);
5005         pAttributeList = nullptr;
5006     }
5007 
5008     m_pSerializer->endElementNS(XML_w, XML_latentStyles);
5009 }
5010 
5011 void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt)
5012 {
5013     bool bMustWrite = true;
5014     switch (rHt.Which())
5015     {
5016         case RES_CHRATR_CASEMAP:
5017             bMustWrite = static_cast< const SvxCaseMapItem& >(rHt).GetCaseMap() != SvxCaseMap::NotMapped;
5018             break;
5019         case RES_CHRATR_COLOR:
5020             bMustWrite = static_cast< const SvxColorItem& >(rHt).GetValue() != COL_AUTO;
5021             break;
5022         case RES_CHRATR_CONTOUR:
5023             bMustWrite = static_cast< const SvxContourItem& >(rHt).GetValue();
5024             break;
5025         case RES_CHRATR_CROSSEDOUT:
5026             bMustWrite = static_cast< const SvxCrossedOutItem& >(rHt).GetStrikeout() != STRIKEOUT_NONE;
5027             break;
5028         case RES_CHRATR_ESCAPEMENT:
5029             bMustWrite = static_cast< const SvxEscapementItem& >(rHt).GetEscapement() != SvxEscapement::Off;
5030             break;
5031         case RES_CHRATR_FONT:
5032             bMustWrite = true;
5033             break;
5034         case RES_CHRATR_FONTSIZE:
5035             bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
5036             break;
5037         case RES_CHRATR_KERNING:
5038             bMustWrite = static_cast< const SvxKerningItem& >(rHt).GetValue() != 0;
5039             break;
5040         case RES_CHRATR_LANGUAGE:
5041             bMustWrite = true;
5042             break;
5043         case RES_CHRATR_POSTURE:
5044             bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
5045             break;
5046         case RES_CHRATR_SHADOWED:
5047             bMustWrite = static_cast< const SvxShadowedItem& >(rHt).GetValue();
5048             break;
5049         case RES_CHRATR_UNDERLINE:
5050             bMustWrite = static_cast< const SvxUnderlineItem& >(rHt).GetLineStyle() != LINESTYLE_NONE;
5051             break;
5052         case RES_CHRATR_WEIGHT:
5053             bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
5054             break;
5055         case RES_CHRATR_AUTOKERN:
5056             bMustWrite = static_cast< const SvxAutoKernItem& >(rHt).GetValue();
5057             break;
5058         case RES_CHRATR_BLINK:
5059             bMustWrite = static_cast< const SvxBlinkItem& >(rHt).GetValue();
5060             break;
5061         case RES_CHRATR_BACKGROUND:
5062             {
5063                 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
5064                 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
5065                               rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
5066                               rBrushItem.GetGraphic() != nullptr ||
5067                               rBrushItem.GetGraphicObject() != nullptr);
5068             }
5069             break;
5070 
5071         case RES_CHRATR_CJK_FONT:
5072             bMustWrite = true;
5073             break;
5074         case RES_CHRATR_CJK_FONTSIZE:
5075             bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE
5076             break;
5077         case RES_CHRATR_CJK_LANGUAGE:
5078             bMustWrite = true;
5079             break;
5080         case RES_CHRATR_CJK_POSTURE:
5081             bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE
5082             break;
5083         case RES_CHRATR_CJK_WEIGHT:
5084             bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT
5085             break;
5086 
5087         case RES_CHRATR_CTL_FONT:
5088             bMustWrite = true;
5089             break;
5090         case RES_CHRATR_CTL_FONTSIZE:
5091             bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
5092             break;
5093         case RES_CHRATR_CTL_LANGUAGE:
5094             bMustWrite = true;
5095             break;
5096         case RES_CHRATR_CTL_POSTURE:
5097             bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
5098             break;
5099         case RES_CHRATR_CTL_WEIGHT:
5100             bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
5101             break;
5102 
5103         case RES_CHRATR_ROTATE:
5104             bMustWrite = static_cast< const SvxCharRotateItem& >(rHt).GetValue() != 0_deg10;
5105             break;
5106         case RES_CHRATR_EMPHASIS_MARK:
5107             bMustWrite = static_cast< const SvxEmphasisMarkItem& >(rHt).GetEmphasisMark() != FontEmphasisMark::NONE;
5108             break;
5109         case RES_CHRATR_TWO_LINES:
5110             bMustWrite = static_cast< const SvxTwoLinesItem& >(rHt).GetValue();
5111             break;
5112         case RES_CHRATR_SCALEW:
5113             bMustWrite = static_cast< const SvxCharScaleWidthItem& >(rHt).GetValue() != 100;
5114             break;
5115         case RES_CHRATR_RELIEF:
5116             bMustWrite = static_cast< const SvxCharReliefItem& >(rHt).GetValue() != FontRelief::NONE;
5117             break;
5118         case RES_CHRATR_HIDDEN:
5119             bMustWrite = static_cast< const SvxCharHiddenItem& >(rHt).GetValue();
5120             break;
5121         case RES_CHRATR_BOX:
5122             {
5123                 const SvxBoxItem& rBoxItem = static_cast< const SvxBoxItem& >(rHt);
5124                 bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() ||
5125                              rBoxItem.GetBottom() || rBoxItem.GetRight() ||
5126                              rBoxItem.GetSmallestDistance();
5127             }
5128             break;
5129         case RES_CHRATR_HIGHLIGHT:
5130             {
5131                 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
5132                 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
5133                               rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
5134                               rBrushItem.GetGraphic() != nullptr ||
5135                               rBrushItem.GetGraphicObject() != nullptr);
5136             }
5137             break;
5138 
5139         case RES_PARATR_LINESPACING:
5140             bMustWrite = static_cast< const SvxLineSpacingItem& >(rHt).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off;
5141             break;
5142         case RES_PARATR_ADJUST:
5143             bMustWrite = static_cast< const SvxAdjustItem& >(rHt).GetAdjust() != SvxAdjust::Left;
5144             break;
5145         case RES_PARATR_SPLIT:
5146             bMustWrite = !static_cast< const SvxFormatSplitItem& >(rHt).GetValue();
5147             break;
5148         case RES_PARATR_WIDOWS:
5149             bMustWrite = static_cast< const SvxWidowsItem& >(rHt).GetValue();
5150             break;
5151         case RES_PARATR_TABSTOP:
5152             bMustWrite = static_cast< const SvxTabStopItem& >(rHt).Count() != 0;
5153             break;
5154         case RES_PARATR_HYPHENZONE:
5155             bMustWrite = true;
5156             break;
5157         case RES_PARATR_NUMRULE:
5158             bMustWrite = !static_cast< const SwNumRuleItem& >(rHt).GetValue().isEmpty();
5159             break;
5160         case RES_PARATR_SCRIPTSPACE:
5161             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5162             break;
5163         case RES_PARATR_HANGINGPUNCTUATION:
5164             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5165             break;
5166         case RES_PARATR_FORBIDDEN_RULES:
5167             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5168             break;
5169         case RES_PARATR_VERTALIGN:
5170             bMustWrite = static_cast< const SvxParaVertAlignItem& >(rHt).GetValue() != SvxParaVertAlignItem::Align::Automatic;
5171             break;
5172         case RES_PARATR_SNAPTOGRID:
5173             bMustWrite = !static_cast< const SvxParaGridItem& >(rHt).GetValue();
5174             break;
5175         case RES_CHRATR_GRABBAG:
5176             bMustWrite = true;
5177             break;
5178 
5179         default:
5180             SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() );
5181             break;
5182     }
5183 
5184     if (bMustWrite)
5185         OutputItem(rHt);
5186 }
5187 
5188 void DocxAttributeOutput::DocDefaults( )
5189 {
5190     // Write the '<w:docDefaults>' section here
5191     m_pSerializer->startElementNS(XML_w, XML_docDefaults);
5192 
5193     // Output the default run properties
5194     m_pSerializer->startElementNS(XML_w, XML_rPrDefault);
5195 
5196     StartStyleProperties(false, 0);
5197 
5198     for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i)
5199         OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5200 
5201     EndStyleProperties(false);
5202 
5203     m_pSerializer->endElementNS(XML_w, XML_rPrDefault);
5204 
5205     // Output the default paragraph properties
5206     m_pSerializer->startElementNS(XML_w, XML_pPrDefault);
5207 
5208     StartStyleProperties(true, 0);
5209 
5210     for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i)
5211         OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5212 
5213     EndStyleProperties(true);
5214 
5215     m_pSerializer->endElementNS(XML_w, XML_pPrDefault);
5216 
5217     m_pSerializer->endElementNS(XML_w, XML_docDefaults);
5218 }
5219 
5220 void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles )
5221 {
5222     // HACK
5223     // Ms Office seems to have an internal limitation of 4091 styles
5224     // and refuses to load .docx with more, even though the spec seems to allow that;
5225     // so simply if there are more styles, don't export those
5226     const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles;
5227     m_pTableStyleExport->TableStyles(nCountStylesToWrite);
5228     m_pSerializer->endElementNS( XML_w, XML_styles );
5229 }
5230 
5231 void DocxAttributeOutput::DefaultStyle()
5232 {
5233     // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)?
5234     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()");
5235 }
5236 
5237 /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image.
5238 *  NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF.
5239 */
5240 void DocxAttributeOutput::WriteSrcRect(
5241     const css::uno::Reference<css::beans::XPropertySet>& xShapePropSet,
5242     const SwFrameFormat* pFrameFormat)
5243 {
5244     uno::Reference<graphic::XGraphic> xGraphic;
5245     xShapePropSet->getPropertyValue("Graphic") >>= xGraphic;
5246     const Graphic aGraphic(xGraphic);
5247 
5248     Size aOriginalSize(aGraphic.GetPrefSize());
5249 
5250     const MapMode aMap100mm( MapUnit::Map100thMM );
5251     const MapMode& rMapMode = aGraphic.GetPrefMapMode();
5252     if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
5253     {
5254         aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm);
5255     }
5256 
5257     css::text::GraphicCrop aGraphicCropStruct;
5258     xShapePropSet->getPropertyValue("GraphicCrop") >>= aGraphicCropStruct;
5259     sal_Int32 nCropL = aGraphicCropStruct.Left;
5260     sal_Int32 nCropR = aGraphicCropStruct.Right;
5261     sal_Int32 nCropT = aGraphicCropStruct.Top;
5262     sal_Int32 nCropB = aGraphicCropStruct.Bottom;
5263 
5264     // simulate border padding as a negative crop.
5265     const SfxPoolItem* pItem;
5266     if (pFrameFormat && SfxItemState::SET == pFrameFormat->GetItemState(RES_BOX, false, &pItem))
5267     {
5268         const SvxBoxItem& rBox = *static_cast<const SvxBoxItem*>(pItem);
5269         nCropL -= rBox.GetDistance( SvxBoxItemLine::LEFT );
5270         nCropR -= rBox.GetDistance( SvxBoxItemLine::RIGHT );
5271         nCropT -= rBox.GetDistance( SvxBoxItemLine::TOP );
5272         nCropB -= rBox.GetDistance( SvxBoxItemLine::BOTTOM );
5273     }
5274 
5275     if ( !((0 != nCropL) || (0 != nCropT) || (0 != nCropR) || (0 != nCropB)) )
5276         return;
5277 
5278     double  widthMultiplier  = 100000.0/aOriginalSize.Width();
5279     double  heightMultiplier = 100000.0/aOriginalSize.Height();
5280 
5281     sal_Int32 left   = static_cast<sal_Int32>(rtl::math::round(nCropL * widthMultiplier));
5282     sal_Int32 right  = static_cast<sal_Int32>(rtl::math::round(nCropR * widthMultiplier));
5283     sal_Int32 top    = static_cast<sal_Int32>(rtl::math::round(nCropT * heightMultiplier));
5284     sal_Int32 bottom = static_cast<sal_Int32>(rtl::math::round(nCropB * heightMultiplier));
5285 
5286     m_pSerializer->singleElementNS( XML_a, XML_srcRect,
5287          XML_l, OString::number(left),
5288          XML_t, OString::number(top),
5289          XML_r, OString::number(right),
5290          XML_b, OString::number(bottom) );
5291 }
5292 
5293 void DocxAttributeOutput::PushRelIdCache()
5294 {
5295     m_aRelIdCache.emplace();
5296     m_aSdrRelIdCache.emplace();
5297 }
5298 
5299 OUString DocxAttributeOutput::FindRelId(BitmapChecksum nChecksum)
5300 {
5301     OUString aRet;
5302 
5303     if (!m_aSdrRelIdCache.empty() && m_aSdrRelIdCache.top().find(nChecksum) != m_aSdrRelIdCache.top().end())
5304         aRet = m_aSdrRelIdCache.top()[nChecksum].first;
5305 
5306     return aRet;
5307 }
5308 
5309 OUString DocxAttributeOutput::FindFileName(BitmapChecksum nChecksum)
5310 {
5311     OUString aRet;
5312 
5313     if (!m_aSdrRelIdCache.empty() && m_aSdrRelIdCache.top().find(nChecksum) != m_aSdrRelIdCache.top().end())
5314         aRet = m_aSdrRelIdCache.top()[nChecksum].second;
5315 
5316     return aRet;
5317 }
5318 
5319 void DocxAttributeOutput::CacheRelId(BitmapChecksum nChecksum, const OUString& rRelId, const OUString& rFileName)
5320 {
5321     if (!m_aSdrRelIdCache.empty())
5322         m_aSdrRelIdCache.top()[nChecksum] = std::pair(rRelId, rFileName);
5323 }
5324 
5325 uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame(
5326     css::uno::Reference<css::drawing::XShape> xShape)
5327 {
5328     return SwTextBoxHelper::getUnoTextFrame(xShape);
5329 }
5330 
5331 std::pair<OString, OUString> DocxAttributeOutput::getExistingGraphicRelId(BitmapChecksum nChecksum)
5332 {
5333     std::pair<OString, OUString> aResult;
5334 
5335     if (m_aRelIdCache.empty())
5336         return aResult;
5337 
5338     auto pIterator = m_aRelIdCache.top().find(nChecksum);
5339 
5340     if (pIterator != m_aRelIdCache.top().end())
5341     {
5342         aResult = pIterator->second;
5343     }
5344 
5345     return aResult;
5346 }
5347 
5348 void DocxAttributeOutput::cacheGraphicRelId(BitmapChecksum nChecksum, OString const & rRelId, OUString const & rFileName)
5349 {
5350     if (!m_aRelIdCache.empty())
5351         m_aRelIdCache.top().emplace(nChecksum, std::pair(rRelId, rFileName));
5352 }
5353 
5354 void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj )
5355 {
5356     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj  ) - some stuff still missing" );
5357 
5358     GetSdtEndBefore(pSdrObj);
5359 
5360     // detect mis-use of the API
5361     assert(pGrfNode || (pOLEFrameFormat && pOLENode));
5362     const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat;
5363     // create the relation ID
5364     OString aRelId;
5365     sal_Int32 nImageType;
5366     if ( pGrfNode && pGrfNode->IsLinkedFile() )
5367     {
5368         // linked image, just create the relation
5369         OUString aFileName;
5370         pGrfNode->GetFileFilterNms( &aFileName, nullptr );
5371 
5372         sal_Int32 const nFragment(aFileName.indexOf('#'));
5373         sal_Int32 const nForbiddenU(aFileName.indexOf("%5C"));
5374         sal_Int32 const nForbiddenL(aFileName.indexOf("%5c"));
5375         if (   (nForbiddenU != -1 && (nFragment == -1 || nForbiddenU < nFragment))
5376             || (nForbiddenL != -1 && (nFragment == -1 || nForbiddenL < nFragment)))
5377         {
5378             SAL_WARN("sw.ww8", "DocxAttributeOutput::FlyFrameGraphic: ignoring image with invalid link URL");
5379             return;
5380         }
5381 
5382         // TODO Convert the file name to relative for better interoperability
5383 
5384         aRelId = m_rExport.AddRelation(
5385                     oox::getRelationship(Relationship::IMAGE),
5386                     aFileName );
5387 
5388         nImageType = XML_link;
5389     }
5390     else
5391     {
5392         // inline, we also have to write the image itself
5393         Graphic aGraphic;
5394         if (pGrfNode)
5395             aGraphic = pGrfNode->GetGrf();
5396         else
5397             aGraphic = *pOLENode->GetGraphic();
5398 
5399         BitmapChecksum aChecksum = aGraphic.GetChecksum();
5400         OUString aFileName;
5401         std::tie(aRelId, aFileName) = getExistingGraphicRelId(aChecksum);
5402         OUString aImageId;
5403 
5404         if (aRelId.isEmpty())
5405         {
5406             // Not in cache, then need to write it.
5407             m_rDrawingML.SetFS( m_pSerializer ); // to be sure that we write to the right stream
5408 
5409             aImageId = m_rDrawingML.WriteImage(aGraphic, false, &aFileName);
5410 
5411             aRelId = OUStringToOString( aImageId, RTL_TEXTENCODING_UTF8 );
5412             cacheGraphicRelId(aChecksum, aRelId, aFileName);
5413         }
5414         else
5415         {
5416             // Include the same relation again. This makes it possible to
5417             // reuse an image across different headers.
5418             aImageId = m_rDrawingML.GetFB()->addRelation( m_pSerializer->getOutputStream(),
5419                 oox::getRelationship(Relationship::IMAGE),
5420                 aFileName );
5421 
5422             aRelId = OUStringToOString( aImageId, RTL_TEXTENCODING_UTF8 );
5423         }
5424 
5425         nImageType = XML_embed;
5426     }
5427 
5428     // In case there are any grab-bag items on the graphic frame, emit them now.
5429     // These are always character grab-bags, as graphics are at-char or as-char in Word.
5430     const SfxPoolItem* pItem = nullptr;
5431     if (pFrameFormat->GetAttrSet().HasItem(RES_FRMATR_GRABBAG, &pItem))
5432     {
5433         const SfxGrabBagItem* pGrabBag = static_cast<const SfxGrabBagItem*>(pItem);
5434         CharGrabBag(*pGrabBag);
5435     }
5436 
5437     rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes(
5438         FastSerializerHelper::createAttrList());
5439     if (pGrfNode)
5440     {
5441         const SwAttrSet& rSet = pGrfNode->GetSwAttrSet();
5442         MirrorGraph eMirror = rSet.Get(RES_GRFATR_MIRRORGRF).GetValue();
5443         if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both)
5444             // Mirror on the vertical axis is a horizontal flip.
5445             xFrameAttributes->add(XML_flipH, "1");
5446         // RES_GRFATR_ROTATION is sal_uInt16; use sal_uInt32 for multiplication later
5447         if (Degree10 nRot = rSet.Get(RES_GRFATR_ROTATION).GetValue())
5448         {
5449             // RES_GRFATR_ROTATION is in 10ths of degree; convert to 100ths for macro
5450             sal_uInt32 mOOXMLRot = oox::drawingml::ExportRotateClockwisify(to<Degree100>(nRot));
5451             xFrameAttributes->add(XML_rot, OString::number(mOOXMLRot));
5452         }
5453     }
5454 
5455     css::uno::Reference<css::beans::XPropertySet> xShapePropSet;
5456     if (pSdrObj)
5457     {
5458         css::uno::Reference<css::drawing::XShape> xShape(
5459             const_cast<SdrObject*>(pSdrObj)->getUnoShape(), css::uno::UNO_QUERY);
5460         xShapePropSet.set(xShape, css::uno::UNO_QUERY);
5461         assert(xShapePropSet);
5462     }
5463 
5464     Size aSize = rSize;
5465     // We need the original (cropped, but unrotated) size of object. So prefer the object data,
5466     // and only use passed frame size as fallback.
5467     if (xShapePropSet)
5468     {
5469         if (css::awt::Size val; xShapePropSet->getPropertyValue("Size") >>= val)
5470             aSize = Size(o3tl::toTwips(val.Width, o3tl::Length::mm100), o3tl::toTwips(val.Height, o3tl::Length::mm100));
5471     }
5472 
5473     m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, aSize);
5474 
5475     // picture description (used for pic:cNvPr later too)
5476     rtl::Reference<::sax_fastparser::FastAttributeList> docPrattrList = FastSerializerHelper::createAttrList();
5477     docPrattrList->add( XML_id, OString::number( m_anchorId++).getStr());
5478     docPrattrList->add( XML_name, OUStringToOString( pFrameFormat->GetName(), RTL_TEXTENCODING_UTF8 ) );
5479     docPrattrList->add( XML_descr, OUStringToOString( pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription(), RTL_TEXTENCODING_UTF8 ));
5480     if( GetExport().GetFilter().getVersion( ) != oox::core::ECMA_DIALECT )
5481         docPrattrList->add( XML_title, OUStringToOString( pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle(), RTL_TEXTENCODING_UTF8 ));
5482     m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList );
5483 
5484     OUString sURL, sRelId;
5485     if (xShapePropSet)
5486     {
5487         xShapePropSet->getPropertyValue("HyperLinkURL") >>= sURL;
5488         if(!sURL.isEmpty())
5489         {
5490             if (sURL.startsWith("#") && sURL.indexOf(' ') != -1 && !sURL.endsWith("|outline") && !sURL.endsWith("|table") &&
5491                 !sURL.endsWith("|frame") && !sURL.endsWith("|graphic") && !sURL.endsWith("|ole") && !sURL.endsWith("|region"))
5492             {
5493                 // Spaces are prohibited in bookmark name.
5494                 sURL = sURL.replace(' ', '_');
5495             }
5496             sRelId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
5497                         oox::getRelationship(Relationship::HYPERLINK),
5498                         sURL, !sURL.startsWith("#") );
5499             m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
5500                 FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main",
5501                 FSNS( XML_r, XML_id ), sRelId);
5502         }
5503     }
5504 
5505     m_pSerializer->endElementNS( XML_wp, XML_docPr );
5506 
5507     m_pSerializer->startElementNS(XML_wp, XML_cNvGraphicFramePr);
5508     // TODO change aspect?
5509     m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks,
5510             FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)),
5511             XML_noChangeAspect, "1" );
5512     m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr );
5513 
5514     m_pSerializer->startElementNS( XML_a, XML_graphic,
5515             FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5516     m_pSerializer->startElementNS( XML_a, XML_graphicData,
5517             XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture" );
5518 
5519     m_pSerializer->startElementNS( XML_pic, XML_pic,
5520             FSNS( XML_xmlns, XML_pic ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)) );
5521 
5522     m_pSerializer->startElementNS(XML_pic, XML_nvPicPr);
5523     // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes
5524     m_pSerializer->startElementNS(XML_pic, XML_cNvPr, docPrattrList);
5525 
5526     if(!sURL.isEmpty())
5527         m_pSerializer->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
5528 
5529     m_pSerializer->endElementNS( XML_pic, XML_cNvPr );
5530 
5531     m_pSerializer->startElementNS(XML_pic, XML_cNvPicPr);
5532     // TODO change aspect?
5533     m_pSerializer->singleElementNS( XML_a, XML_picLocks,
5534             XML_noChangeAspect, "1", XML_noChangeArrowheads, "1" );
5535     m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr );
5536     m_pSerializer->endElementNS( XML_pic, XML_nvPicPr );
5537 
5538     // the actual picture
5539     m_pSerializer->startElementNS(XML_pic, XML_blipFill);
5540 
5541 /* At this point we are certain that, WriteImage returns empty RelId
5542    for unhandled graphic type. Therefore we write the picture description
5543    and not the relation( coz there ain't any), so that the user knows
5544    there is an image/graphic in the doc but it is broken instead of
5545    completely discarding it.
5546 */
5547     if ( aRelId.isEmpty() )
5548         m_pSerializer->startElementNS(XML_a, XML_blip);
5549     else
5550         m_pSerializer->startElementNS(XML_a, XML_blip, FSNS(XML_r, nImageType), aRelId);
5551 
5552     pItem = nullptr;
5553 
5554     if ( pGrfNode && SfxItemState::SET == pGrfNode->GetSwAttrSet().GetItemState(RES_GRFATR_DRAWMODE, true, &pItem))
5555     {
5556         GraphicDrawMode nMode = static_cast<GraphicDrawMode>(static_cast<const SfxEnumItemInterface*>(pItem)->GetEnumValue());
5557         if (nMode == GraphicDrawMode::Greys)
5558             m_pSerializer->singleElementNS (XML_a, XML_grayscl);
5559         else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice
5560             m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000));
5561         else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
5562             m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000) );
5563     }
5564     m_pSerializer->endElementNS( XML_a, XML_blip );
5565 
5566     if (xShapePropSet)
5567         WriteSrcRect(xShapePropSet, pFrameFormat);
5568 
5569     m_pSerializer->startElementNS(XML_a, XML_stretch);
5570     m_pSerializer->singleElementNS(XML_a, XML_fillRect);
5571     m_pSerializer->endElementNS( XML_a, XML_stretch );
5572     m_pSerializer->endElementNS( XML_pic, XML_blipFill );
5573 
5574     // TODO setup the right values below
5575     m_pSerializer->startElementNS(XML_pic, XML_spPr, XML_bwMode, "auto");
5576 
5577     m_pSerializer->startElementNS(XML_a, XML_xfrm, xFrameAttributes);
5578 
5579     m_pSerializer->singleElementNS(XML_a, XML_off, XML_x, "0", XML_y, "0");
5580     OString aWidth( OString::number( TwipsToEMU( aSize.Width() ) ) );
5581     OString aHeight( OString::number( TwipsToEMU( aSize.Height() ) ) );
5582     m_pSerializer->singleElementNS(XML_a, XML_ext, XML_cx, aWidth, XML_cy, aHeight);
5583     m_pSerializer->endElementNS( XML_a, XML_xfrm );
5584     m_pSerializer->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect");
5585     m_pSerializer->singleElementNS(XML_a, XML_avLst);
5586     m_pSerializer->endElementNS( XML_a, XML_prstGeom );
5587 
5588     const SvxBoxItem& rBoxItem = pFrameFormat->GetBox();
5589     const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT);
5590     const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT);
5591     const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP);
5592     const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM);
5593     if (pLeft || pRight || pTop || pBottom)
5594         m_rExport.SdrExporter().writeBoxItemLine(rBoxItem);
5595 
5596     m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat);
5597 
5598     m_pSerializer->endElementNS( XML_pic, XML_spPr );
5599 
5600     m_pSerializer->endElementNS( XML_pic, XML_pic );
5601 
5602     m_pSerializer->endElementNS( XML_a, XML_graphicData );
5603     m_pSerializer->endElementNS( XML_a, XML_graphic );
5604     m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat);
5605 }
5606 
5607 void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat, const sal_Int8 nFormulaAlignment )
5608 {
5609     if( WriteOLEChart( pSdrObj, rSize, pFlyFrameFormat ))
5610         return;
5611     if( WriteOLEMath( rOLENode , nFormulaAlignment))
5612         return;
5613     PostponeOLE( rOLENode, rSize, pFlyFrameFormat );
5614 }
5615 
5616 bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5617 {
5618     uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
5619     if (!xShape.is())
5620         return false;
5621 
5622     uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY);
5623     if (!xPropSet.is())
5624         return false;
5625 
5626     OUString clsid; // why is the property of type string, not sequence<byte>?
5627     xPropSet->getPropertyValue("CLSID") >>= clsid;
5628     assert(!clsid.isEmpty());
5629     SvGlobalName aClassID;
5630     bool const isValid(aClassID.MakeId(clsid));
5631     assert(isValid); (void)isValid;
5632 
5633     if (!SotExchange::IsChart(aClassID))
5634         return false;
5635 
5636     m_aPostponedCharts.push_back(PostponedChart(pSdrObj, rSize, pFlyFrameFormat));
5637     return true;
5638 }
5639 
5640 /*
5641  * Write chart hierarchy in w:drawing after end element of w:rPr tag.
5642  */
5643 void DocxAttributeOutput::WritePostponedChart()
5644 {
5645     if (m_aPostponedCharts.empty())
5646         return;
5647 
5648     for (const PostponedChart& rChart : m_aPostponedCharts)
5649     {
5650         uno::Reference< chart2::XChartDocument > xChartDoc;
5651         uno::Reference< drawing::XShape > xShape(const_cast<SdrObject*>(rChart.object)->getUnoShape(), uno::UNO_QUERY );
5652         if( xShape.is() )
5653         {
5654             uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
5655             if( xPropSet.is() )
5656                 xChartDoc.set( xPropSet->getPropertyValue( "Model" ), uno::UNO_QUERY );
5657         }
5658 
5659         if( xChartDoc.is() )
5660         {
5661             SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart ");
5662 
5663             m_rExport.SdrExporter().startDMLAnchorInline(rChart.frame, rChart.size);
5664 
5665             OUString sName("Object 1");
5666             uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY );
5667             if( xNamed.is() )
5668                 sName = xNamed->getName();
5669 
5670             /* If there is a scenario where a chart is followed by a shape
5671                which is being exported as an alternate content then, the
5672                docPr Id is being repeated, ECMA 20.4.2.5 says that the
5673                docPr Id should be unique, ensuring the same here.
5674                */
5675             m_pSerializer->singleElementNS( XML_wp, XML_docPr,
5676                     XML_id, OString::number(m_anchorId++),
5677                     XML_name, sName );
5678 
5679             m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
5680 
5681             m_pSerializer->startElementNS( XML_a, XML_graphic,
5682                     FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5683 
5684             m_pSerializer->startElementNS( XML_a, XML_graphicData,
5685                     XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
5686 
5687             OString aRelId;
5688             m_nChartCount++;
5689             aRelId = m_rExport.OutputChart( xChartDoc, m_nChartCount, m_pSerializer );
5690 
5691             m_pSerializer->singleElementNS( XML_c, XML_chart,
5692                     FSNS( XML_xmlns, XML_c ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)),
5693                     FSNS( XML_xmlns, XML_r ), GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)),
5694                     FSNS( XML_r, XML_id ), aRelId );
5695 
5696             m_pSerializer->endElementNS( XML_a, XML_graphicData );
5697             m_pSerializer->endElementNS( XML_a, XML_graphic );
5698 
5699             m_rExport.SdrExporter().endDMLAnchorInline(rChart.frame);
5700         }
5701     }
5702 
5703     m_aPostponedCharts.clear();
5704 }
5705 
5706 bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode ,const sal_Int8 nAlign)
5707 {
5708     uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef());
5709     SvGlobalName aObjName(xObj->getClassID());
5710 
5711     if( !SotExchange::IsMath(aObjName) )
5712         return false;
5713 
5714     try
5715     {
5716         PostponedMathObjects aPostponedMathObject;
5717         aPostponedMathObject.pMathObject = const_cast<SwOLENode*>( &rOLENode);
5718         aPostponedMathObject.nMathObjAlignment = nAlign;
5719         m_aPostponedMaths.push_back(aPostponedMathObject);
5720     }
5721     catch (const uno::Exception&)
5722     {
5723     }
5724     return true;
5725 }
5726 
5727 void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath, sal_Int8 nAlign)
5728 {
5729     uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef());
5730     if (embed::EmbedStates::LOADED == xObj->getCurrentState())
5731     {
5732         // must be running so there is a Component
5733         try
5734         {
5735             xObj->changeState(embed::EmbedStates::RUNNING);
5736         }
5737         catch (const uno::Exception&)
5738         {
5739         }
5740     }
5741     uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY );
5742     if (!xInterface.is())
5743     {
5744         SAL_WARN("sw.ww8", "Broken math object");
5745         return;
5746     }
5747 // gcc4.4 (and 4.3 and possibly older) have a problem with dynamic_cast directly to the target class,
5748 // so help it with an intermediate cast. I'm not sure what exactly the problem is, seems to be unrelated
5749 // to RTLD_GLOBAL, so most probably a gcc bug.
5750     oox::FormulaExportBase* formulaexport = dynamic_cast<oox::FormulaExportBase*>(dynamic_cast<SfxBaseModel*>(xInterface.get()));
5751     assert( formulaexport != nullptr );
5752     if (formulaexport)
5753         formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(),
5754                 oox::drawingml::DOCUMENT_DOCX, nAlign);
5755 }
5756 
5757 void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject)
5758 {
5759     if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm)
5760         return;
5761 
5762     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5763     if (!pFormObj)
5764         return;
5765 
5766     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5767     uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5768     if (!xInfo.is())
5769         return;
5770 
5771     if (xInfo->supportsService("com.sun.star.form.component.DateField"))
5772     {
5773         // gather component properties
5774 
5775         OUString sDateFormat;
5776         uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5777 
5778         OString sDate;
5779         OUString aContentText;
5780         bool bHasDate = false;
5781         css::util::Date aUNODate;
5782         if (xPropertySet->getPropertyValue("Date") >>= aUNODate)
5783         {
5784             bHasDate = true;
5785             Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year);
5786             sDate = DateToOString(aDate);
5787             aContentText = OUString::createFromAscii(DateToDDMMYYYYOString(aDate).getStr());
5788             sDateFormat = "dd/MM/yyyy";
5789         }
5790         else
5791         {
5792             aContentText = xPropertySet->getPropertyValue("HelpText").get<OUString>();
5793             if(sDateFormat.isEmpty())
5794                 sDateFormat = "dd/MM/yyyy"; // Need to set date format even if there is no date set
5795         }
5796 
5797         // output component
5798 
5799         m_pSerializer->startElementNS(XML_w, XML_sdt);
5800         m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5801 
5802         if (bHasDate)
5803             m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sDate);
5804         else
5805             m_pSerializer->startElementNS(XML_w, XML_date);
5806 
5807         m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), sDateFormat);
5808         m_pSerializer->singleElementNS(XML_w, XML_lid,
5809                                        FSNS(XML_w, XML_val), "en-US");
5810         m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
5811                                        FSNS(XML_w, XML_val), "dateTime");
5812         m_pSerializer->singleElementNS(XML_w, XML_calendar,
5813                                        FSNS(XML_w, XML_val), "gregorian");
5814 
5815         m_pSerializer->endElementNS(XML_w, XML_date);
5816         m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5817 
5818         m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5819         m_pSerializer->startElementNS(XML_w, XML_r);
5820 
5821         RunText(aContentText);
5822         m_pSerializer->endElementNS(XML_w, XML_r);
5823         m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5824 
5825         m_pSerializer->endElementNS(XML_w, XML_sdt);
5826     }
5827     else if (xInfo->supportsService("com.sun.star.form.component.ComboBox"))
5828     {
5829         // gather component properties
5830 
5831         uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5832         OUString sText = xPropertySet->getPropertyValue("Text").get<OUString>();
5833         const uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue("StringItemList").get< uno::Sequence<OUString> >();
5834 
5835         // output component
5836 
5837         m_pSerializer->startElementNS(XML_w, XML_sdt);
5838         m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5839 
5840         m_pSerializer->startElementNS(XML_w, XML_dropDownList);
5841 
5842         for (const auto& rItem : aItems)
5843         {
5844             m_pSerializer->singleElementNS(XML_w, XML_listItem,
5845                                            FSNS(XML_w, XML_displayText), rItem,
5846                                            FSNS(XML_w, XML_value), rItem);
5847         }
5848 
5849         m_pSerializer->endElementNS(XML_w, XML_dropDownList);
5850         m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5851 
5852         m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5853         m_pSerializer->startElementNS(XML_w, XML_r);
5854         RunText(sText);
5855         m_pSerializer->endElementNS(XML_w, XML_r);
5856         m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5857 
5858         m_pSerializer->endElementNS(XML_w, XML_sdt);
5859     }
5860 }
5861 
5862 void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun)
5863 {
5864     for( const auto & rPostponedDrawing : m_aPostponedActiveXControls )
5865     {
5866         WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun);
5867     }
5868     m_aPostponedActiveXControls.clear();
5869 }
5870 
5871 
5872 void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun)
5873 {
5874     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5875     if (!pFormObj)
5876         return;
5877 
5878     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5879     if (!xControlModel.is())
5880         return;
5881 
5882     const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER);
5883 
5884     if(!bInsideRun)
5885     {
5886         m_pSerializer->startElementNS(XML_w, XML_r);
5887     }
5888 
5889     // w:pict for floating embedded control and w:object for inline embedded control
5890     if(bAnchoredInline)
5891         m_pSerializer->startElementNS(XML_w, XML_object);
5892     else
5893         m_pSerializer->startElementNS(XML_w, XML_pict);
5894 
5895     // write ActiveX fragment and ActiveX binary
5896     uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY);
5897     std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel);
5898 
5899     // VML shape definition
5900     m_rExport.VMLExporter().SetSkipwzName(true);
5901     m_rExport.VMLExporter().SetHashMarkForType(true);
5902     m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_");
5903     OString sShapeId;
5904     if(bAnchoredInline)
5905     {
5906         sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true);
5907     }
5908     else
5909     {
5910         SwFormatFollowTextFlow const& rFlow(rFrameFormat.GetFollowTextFlow());
5911         const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient();
5912         const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient();
5913         SwFormatSurround const& rSurround(rFrameFormat.GetSurround());
5914         rtl::Reference<sax_fastparser::FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
5915         sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject,
5916             rFlow.GetValue(),
5917             rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(),
5918             rHoriOri.GetRelationOrient(),
5919             rVertOri.GetRelationOrient(),
5920             pAttrList.get(),
5921             true);
5922     }
5923     // Restore default values
5924     m_rExport.VMLExporter().SetSkipwzName(false);
5925     m_rExport.VMLExporter().SetHashMarkForType(false);
5926     m_rExport.VMLExporter().OverrideShapeIDGen(false);
5927 
5928     // control
5929     m_pSerializer->singleElementNS(XML_w, XML_control,
5930                                     FSNS(XML_r, XML_id), sRelIdAndName.first,
5931                                     FSNS(XML_w, XML_name), sRelIdAndName.second,
5932                                     FSNS(XML_w, XML_shapeid), sShapeId);
5933 
5934     if(bAnchoredInline)
5935         m_pSerializer->endElementNS(XML_w, XML_object);
5936     else
5937         m_pSerializer->endElementNS(XML_w, XML_pict);
5938 
5939     if(!bInsideRun)
5940     {
5941         m_pSerializer->endElementNS(XML_w, XML_r);
5942     }
5943 }
5944 
5945 bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const
5946 {
5947     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5948     if (!pFormObj)
5949         return false;
5950 
5951     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5952     if (!xControlModel.is())
5953         return false;
5954 
5955     uno::Reference< css::frame::XModel > xModel( m_rExport.m_rDoc.GetDocShell() ? m_rExport.m_rDoc.GetDocShell()->GetModel() : nullptr );
5956     if (!xModel.is())
5957         return false;
5958 
5959     uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5960     if (!xInfo.is())
5961         return false;
5962 
5963     // See WritePostponedFormControl
5964     // By now date field and combobox is handled on a different way, so let's not interfere with the other method.
5965     if(xInfo->supportsService("com.sun.star.form.component.DateField") ||
5966        xInfo->supportsService("com.sun.star.form.component.ComboBox"))
5967         return false;
5968 
5969     oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel);
5970     return exportHelper.isValid();
5971 }
5972 
5973 void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5974 {
5975     if( !m_pPostponedOLEs )
5976         //cannot be postponed, try to write now
5977         WriteOLE( rNode, rSize, pFlyFrameFormat );
5978     else
5979         m_pPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) );
5980 }
5981 
5982 /*
5983  * Write w:object hierarchy for embedded objects after end element of w:rPr tag.
5984  */
5985 void DocxAttributeOutput::WritePostponedOLE()
5986 {
5987     if( !m_pPostponedOLEs )
5988         return;
5989 
5990     for( const auto & rPostponedOLE : *m_pPostponedOLEs )
5991     {
5992         WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame );
5993     }
5994 
5995     // clear list of postponed objects
5996     m_pPostponedOLEs.reset();
5997 }
5998 
5999 void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
6000 {
6001     OSL_ASSERT(pFlyFrameFormat);
6002 
6003     // get interoperability information about embedded objects
6004     uno::Reference< beans::XPropertySet > xPropSet( m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
6005     uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes;
6006     xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag;
6007     auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
6008         [](const beans::PropertyValue& rProp) { return rProp.Name == "EmbeddedObjects"; });
6009     if (pProp != std::cend(aGrabBag))
6010         pProp->Value >>= aObjectsInteropList;
6011 
6012     SwOLEObj& aObject = rNode.GetOLEObj();
6013     uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() );
6014     comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer();
6015     OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj );
6016 
6017     // set some attributes according to the type of the embedded object
6018     OUString sProgID, sDrawAspect;
6019     switch (rNode.GetAspect())
6020     {
6021         case embed::Aspects::MSOLE_CONTENT: sDrawAspect = "Content"; break;
6022         case embed::Aspects::MSOLE_DOCPRINT: sDrawAspect = "DocPrint"; break;
6023         case embed::Aspects::MSOLE_ICON: sDrawAspect = "Icon"; break;
6024         case embed::Aspects::MSOLE_THUMBNAIL: sDrawAspect = "Thumbnail"; break;
6025         default:
6026             SAL_WARN("sw.ww8", "DocxAttributeOutput::WriteOLE: invalid aspect value");
6027     }
6028     auto pObjectsInterop = std::find_if(std::cbegin(aObjectsInteropList), std::cend(aObjectsInteropList),
6029         [&sObjectName](const beans::PropertyValue& rProp) { return rProp.Name == sObjectName; });
6030     if (pObjectsInterop != std::cend(aObjectsInteropList))
6031         pObjectsInterop->Value >>= aObjectInteropAttributes;
6032 
6033     for( const auto& rObjectInteropAttribute : std::as_const(aObjectInteropAttributes) )
6034     {
6035         if ( rObjectInteropAttribute.Name == "ProgID" )
6036         {
6037             rObjectInteropAttribute.Value >>= sProgID;
6038         }
6039     }
6040 
6041     // write embedded file
6042     OString sId = m_rExport.WriteOLEObject(aObject, sProgID);
6043 
6044     if( sId.isEmpty() )
6045     {
6046         // the embedded file could not be saved
6047         // fallback: save as an image
6048         FlyFrameGraphic( nullptr, rSize, pFlyFrameFormat, &rNode );
6049         return;
6050     }
6051 
6052     // write preview image
6053     const Graphic* pGraphic = rNode.GetGraphic();
6054     m_rDrawingML.SetFS(m_pSerializer);
6055     OUString sImageId = m_rDrawingML.WriteImage( *pGraphic );
6056 
6057     if ( sDrawAspect == "Content" )
6058     {
6059         try
6060         {
6061             awt::Size aSize = xObj->getVisualAreaSize( rNode.GetAspect() );
6062 
6063             MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rNode.GetAspect() ) );
6064             Size aOriginalSize( OutputDevice::LogicToLogic(Size( aSize.Width, aSize.Height),
6065                                                 MapMode(aUnit), MapMode(MapUnit::MapTwip)));
6066 
6067             m_pSerializer->startElementNS( XML_w, XML_object,
6068                                    FSNS(XML_w, XML_dxaOrig), OString::number(aOriginalSize.Width()),
6069                                    FSNS(XML_w, XML_dyaOrig), OString::number(aOriginalSize.Height()) );
6070         }
6071         catch ( uno::Exception& )
6072         {
6073             m_pSerializer->startElementNS(XML_w, XML_object);
6074         }
6075     }
6076     else
6077     {
6078         m_pSerializer->startElementNS(XML_w, XML_object);
6079     }
6080 
6081     OString sShapeId = "ole_" + sId;
6082 
6083     //OLE Shape definition
6084     WriteOLEShape(*pFlyFrameFormat, rSize, sShapeId, sImageId);
6085 
6086     //OLE Object definition
6087     m_pSerializer->singleElementNS(XML_o, XML_OLEObject,
6088                                    XML_Type, "Embed",
6089                                    XML_ProgID, sProgID,
6090                                    XML_ShapeID, sShapeId.getStr(),
6091                                    XML_DrawAspect, sDrawAspect,
6092                                    XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
6093                                    FSNS( XML_r, XML_id ), sId );
6094 
6095     m_pSerializer->endElementNS(XML_w, XML_object);
6096 }
6097 
6098 void DocxAttributeOutput::WriteOLEShape(const SwFlyFrameFormat& rFrameFormat, const Size& rSize,
6099                                         const OString& rShapeId, const OUString& rImageId)
6100 {
6101     assert(m_pSerializer);
6102 
6103     //Here is an attribute list where we collect the attributes what we want to export
6104     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
6105     pAttr->add(XML_id, rShapeId);
6106 
6107     //export the fixed shape type for picture frame
6108     m_pSerializer->write(vml::VMLExport::GetVMLShapeTypeDefinition(rShapeId, true));
6109     pAttr->add(XML_type, "_x0000_t" + rShapeId);
6110 
6111     //Export the style attribute for position and size
6112     pAttr->add(XML_style, GetOLEStyle(rFrameFormat, rSize));
6113     //Get the OLE frame
6114     const SvxBoxItem& rBox = rFrameFormat.GetAttrSet().GetBox();
6115     OString sLineType;
6116     OString sDashType;
6117     //Word does not handle differently the four sides,
6118     //so we have to choose, and the left one is the winner:
6119     if (rBox.GetLeft())
6120     {
6121         //Get the left border color and width
6122         const Color aLineColor = rBox.GetLeft()->GetColor();
6123         const tools::Long aLineWidth = rBox.GetLeft()->GetWidth();
6124 
6125         //Convert the left OLE border style to OOXML
6126         //FIXME improve if it's necessary
6127         switch (rBox.GetLeft()->GetBorderLineStyle())
6128         {
6129             case SvxBorderLineStyle::SOLID:
6130                 sLineType = OString("Single");
6131                 sDashType = OString("Solid");
6132                 break;
6133             case SvxBorderLineStyle::DASHED:
6134                 sLineType = OString("Single");
6135                 sDashType = OString("Dash");
6136                 break;
6137             case SvxBorderLineStyle::DASH_DOT:
6138                 sLineType = OString("Single");
6139                 sDashType = OString("DashDot");
6140                 break;
6141             case SvxBorderLineStyle::DASH_DOT_DOT:
6142                 sLineType = OString("Single");
6143                 sDashType = OString("ShortDashDotDot");
6144                 break;
6145             case SvxBorderLineStyle::DOTTED:
6146                 sLineType = OString("Single");
6147                 sDashType = OString("Dot");
6148                 break;
6149             case SvxBorderLineStyle::DOUBLE:
6150                 sLineType = OString("ThinThin");
6151                 sDashType = OString("Solid");
6152                 break;
6153             case SvxBorderLineStyle::DOUBLE_THIN:
6154                 sLineType = OString("ThinThin");
6155                 sDashType = OString("Solid");
6156                 break;
6157             case SvxBorderLineStyle::EMBOSSED:
6158                 sLineType = OString("Single");
6159                 sDashType = OString("Solid");
6160                 break;
6161             case SvxBorderLineStyle::ENGRAVED:
6162                 sLineType = OString("Single");
6163                 sDashType = OString("Solid");
6164                 break;
6165             case SvxBorderLineStyle::FINE_DASHED:
6166                 sLineType = OString("Single");
6167                 sDashType = OString("Dot");
6168                 break;
6169             case SvxBorderLineStyle::INSET:
6170                 sLineType = OString("Single");
6171                 sDashType = OString("Solid");
6172                 break;
6173             case SvxBorderLineStyle::OUTSET:
6174                 sLineType = OString("Single");
6175                 sDashType = OString("Solid");
6176                 break;
6177             case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
6178             case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
6179             case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
6180                 sLineType = OString("ThickThin");
6181                 sDashType = OString("Solid");
6182                 break;
6183             case SvxBorderLineStyle::THINTHICK_LARGEGAP:
6184             case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
6185             case SvxBorderLineStyle::THINTHICK_SMALLGAP:
6186                 sLineType = OString("ThinThick");
6187                 sDashType = OString("Solid");
6188                 break;
6189             case SvxBorderLineStyle::NONE:
6190                 sLineType = OString("");
6191                 sDashType = OString("");
6192                 break;
6193             default:
6194                 SAL_WARN("sw.ww8", "Unknown line type on OOXML ELE export!");
6195                 break;
6196         }
6197 
6198         //If there is a line add it for export
6199         if (!sLineType.isEmpty() && !sDashType.isEmpty())
6200         {
6201             pAttr->add(XML_stroked, "t");
6202             pAttr->add(XML_strokecolor, "#" + msfilter::util::ConvertColor(aLineColor));
6203             pAttr->add(XML_strokeweight, OString::number(aLineWidth / 20) + "pt");
6204         }
6205     }
6206 
6207     //Let's check the filltype of the OLE
6208     switch (rFrameFormat.GetAttrSet().Get(XATTR_FILLSTYLE).GetValue())
6209     {
6210         case drawing::FillStyle::FillStyle_SOLID:
6211         {
6212             //If solid, we get the color and add it to the exporter
6213             const Color rShapeColor = rFrameFormat.GetAttrSet().Get(XATTR_FILLCOLOR).GetColorValue();
6214             pAttr->add(XML_filled, "t");
6215             pAttr->add(XML_fillcolor, "#" + msfilter::util::ConvertColor(rShapeColor));
6216             break;
6217         }
6218         case drawing::FillStyle::FillStyle_GRADIENT:
6219         case drawing::FillStyle::FillStyle_HATCH:
6220         case drawing::FillStyle::FillStyle_BITMAP:
6221             //TODO
6222             break;
6223         case drawing::FillStyle::FillStyle_NONE:
6224         {
6225             pAttr->add(XML_filled, "f");
6226             break;
6227         }
6228         default:
6229             SAL_WARN("sw.ww8", "Unknown fill type on OOXML OLE export!");
6230             break;
6231     }
6232     pAttr->addNS(XML_o, XML_ole, ""); //compulsory, even if it's empty
6233     m_pSerializer->startElementNS(XML_v, XML_shape, pAttr);//Write the collected attrs...
6234 
6235     if (!sLineType.isEmpty() && !sDashType.isEmpty()) //If there is a line/dash style it is time to export it
6236     {
6237         m_pSerializer->singleElementNS(XML_v, XML_stroke, XML_linestyle, sLineType, XML_dashstyle, sDashType);
6238     }
6239 
6240     // shape filled with the preview image
6241     m_pSerializer->singleElementNS(XML_v, XML_imagedata,
6242                                    FSNS(XML_r, XML_id), rImageId,
6243                                    FSNS(XML_o, XML_title), "");
6244 
6245     //export wrap settings
6246     if (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) //As-char objs does not have surround.
6247         ExportOLESurround(rFrameFormat.GetSurround());
6248 
6249     m_pSerializer->endElementNS(XML_v, XML_shape);
6250 }
6251 
6252 OString DocxAttributeOutput::GetOLEStyle(const SwFlyFrameFormat& rFormat, const Size& rSize)
6253 {
6254     //tdf#131539: Export OLE positions in docx:
6255     //This string will store the position output for the xml
6256     OString aPos;
6257     //This string will store the relative position for aPos
6258     OString aAnch;
6259 
6260     if (rFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
6261     {
6262         //Get the horizontal alignment of the OLE via the frame format, to aHAlign
6263         OString aHAlign = convertToOOXMLHoriOrient(rFormat.GetHoriOrient().GetHoriOrient(),
6264             rFormat.GetHoriOrient().IsPosToggle());
6265         //Get the vertical alignment of the OLE via the frame format to aVAlign
6266         OString aVAlign = convertToOOXMLVertOrient(rFormat.GetVertOrient().GetVertOrient());
6267 
6268         // Check if the OLE anchored to page:
6269         const bool bIsPageAnchor = rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6270 
6271         //Get the relative horizontal positions for the anchors
6272         OString aHAnch
6273             = bIsPageAnchor
6274                   ? OString("page")
6275                   : convertToOOXMLHoriOrientRel(rFormat.GetHoriOrient().GetRelationOrient());
6276         //Get the relative vertical positions for the anchors
6277         OString aVAnch = convertToOOXMLVertOrientRel(rFormat.GetVertOrient().GetRelationOrient());
6278 
6279         //Choice that the horizontal position is relative or not
6280         if (!aHAlign.isEmpty())
6281             aHAlign = ";mso-position-horizontal:" + aHAlign;
6282         aHAlign = ";mso-position-horizontal-relative:" + aHAnch;
6283 
6284         //Choice that the vertical position is relative or not
6285         if (!aVAlign.isEmpty())
6286             aVAlign = ";mso-position-vertical:" + aVAlign;
6287         aVAlign = ";mso-position-vertical-relative:" + aVAnch;
6288 
6289         //Set the anchoring information into one string for aPos
6290         aAnch = aHAlign + aVAlign;
6291 
6292         //Query the positions to aPos from frameformat
6293         aPos =
6294             "position:absolute;margin-left:" + OString::number(double(rFormat.GetHoriOrient().GetPos()) / 20) +
6295             "pt;margin-top:" + OString::number(double(rFormat.GetVertOrient().GetPos()) / 20) + "pt;";
6296     }
6297 
6298     OString sShapeStyle = "width:" + OString::number( double( rSize.Width() ) / 20 ) +
6299                         "pt;height:" + OString::number( double( rSize.Height() ) / 20 ) +
6300                         "pt"; //from VMLExport::AddRectangleDimensions(), it does: value/20
6301 
6302     const SvxLRSpaceItem& rLRSpace = rFormat.GetLRSpace();
6303     if (rLRSpace.IsExplicitZeroMarginValLeft() || rLRSpace.GetLeft())
6304         sShapeStyle += ";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt";
6305     if (rLRSpace.IsExplicitZeroMarginValRight() || rLRSpace.GetRight())
6306         sShapeStyle += ";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt";
6307     const SvxULSpaceItem& rULSpace = rFormat.GetULSpace();
6308     if (rULSpace.GetUpper())
6309         sShapeStyle += ";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt";
6310     if (rULSpace.GetLower())
6311         sShapeStyle += ";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt";
6312 
6313     //Export anchor setting, if it exists
6314     if (!aPos.isEmpty() && !aAnch.isEmpty())
6315         sShapeStyle = aPos + sShapeStyle  + aAnch;
6316 
6317     return sShapeStyle;
6318 }
6319 
6320 void DocxAttributeOutput::ExportOLESurround(const SwFormatSurround& rWrap)
6321 {
6322     const bool bIsContour = rWrap.IsContour(); //Has the shape contour or not
6323     OString sSurround;
6324     OString sSide;
6325 
6326     //Map the ODF wrap settings to OOXML one
6327     switch (rWrap.GetSurround())
6328     {
6329         case text::WrapTextMode::WrapTextMode_NONE:
6330             sSurround = OString("topAndBottom");
6331             break;
6332         case text::WrapTextMode::WrapTextMode_PARALLEL:
6333             sSurround = bIsContour ? OString("tight") : OString("square");
6334             break;
6335         case text::WrapTextMode::WrapTextMode_DYNAMIC:
6336             sSide = OString("largest");
6337             sSurround = bIsContour ? OString("tight") : OString("square");
6338             break;
6339         case text::WrapTextMode::WrapTextMode_LEFT:
6340             sSide = OString("left");
6341             sSurround = bIsContour ? OString("tight") : OString("square");
6342             break;
6343         case text::WrapTextMode::WrapTextMode_RIGHT:
6344             sSide = OString("right");
6345             sSurround = bIsContour ? OString("tight") : OString("square");
6346             break;
6347         default:
6348             SAL_WARN("sw.ww8", "Unknown surround type on OOXML export!");
6349             break;
6350     }
6351 
6352     //if there is a setting export it:
6353     if (!sSurround.isEmpty())
6354     {
6355         if (sSide.isEmpty())
6356             m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround);
6357         else
6358             m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround, XML_side, sSide);
6359     }
6360 }
6361 
6362 void DocxAttributeOutput::WritePostponedCustomShape()
6363 {
6364     if (!m_pPostponedCustomShape)
6365         return;
6366 
6367     for( const auto & rPostponedDrawing : *m_pPostponedCustomShape)
6368     {
6369         if ( IsAlternateContentChoiceOpen() )
6370             m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6371         else
6372             m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6373     }
6374     m_pPostponedCustomShape.reset();
6375 }
6376 
6377 void DocxAttributeOutput::WritePostponedDMLDrawing()
6378 {
6379     if (!m_pPostponedDMLDrawings)
6380         return;
6381 
6382     // Clear the list early, this method may be called recursively.
6383     std::unique_ptr< std::vector<PostponedDrawing> > pPostponedDMLDrawings(std::move(m_pPostponedDMLDrawings));
6384     std::unique_ptr< std::vector<PostponedOLE> > pPostponedOLEs(std::move(m_pPostponedOLEs));
6385 
6386     for( const auto & rPostponedDrawing : *pPostponedDMLDrawings )
6387     {
6388         // Avoid w:drawing within another w:drawing.
6389         if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) )
6390            m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6391         else
6392             m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6393     }
6394 
6395     m_pPostponedOLEs = std::move(pPostponedOLEs);
6396 }
6397 
6398 void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
6399 {
6400     m_pSerializer->mark(Tag_OutputFlyFrame);
6401 
6402     switch ( rFrame.GetWriterType() )
6403     {
6404         case ww8::Frame::eGraphic:
6405             {
6406                 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6407                 const SwNode *pNode = rFrame.GetContent();
6408                 const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr;
6409                 if ( pGrfNode )
6410                 {
6411                     if (!m_pPostponedGraphic)
6412                     {
6413                         m_bPostponedProcessingFly = false ;
6414                         FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj);
6415                     }
6416                     else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6417                     {    // so write it out later
6418                         m_bPostponedProcessingFly = true ;
6419                         m_pPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj));
6420                     }
6421                 }
6422             }
6423             break;
6424         case ww8::Frame::eDrawing:
6425             {
6426                 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6427                 if ( pSdrObj )
6428                 {
6429                     uno::Reference<drawing::XShape> xShape(
6430                         const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
6431 
6432                     if (xShape.is() && oox::drawingml::DrawingML::IsDiagram(xShape))
6433                     {
6434                         if ( !m_pPostponedDiagrams )
6435                         {
6436                             m_bPostponedProcessingFly = false ;
6437                             m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6438                         }
6439                         else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6440                         {    // so write it out later
6441                             m_bPostponedProcessingFly = true ;
6442                             m_pPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) ));
6443                         }
6444                     }
6445                     else
6446                     {
6447                         if (!m_pPostponedDMLDrawings)
6448                         {
6449                             if ( IsAlternateContentChoiceOpen() )
6450                             {
6451                                 // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing.
6452                                 if( m_rExport.SdrExporter().IsDrawingOpen() )
6453                                     m_pPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6454                                 else
6455                                     m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++);
6456                             }
6457                             else
6458                                 m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6459 
6460                             m_bPostponedProcessingFly = false ;
6461                         }
6462                         // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added
6463                         // m_bParagraphFrameOpen: check if the frame is open.
6464                         else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen)
6465                             m_pPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6466                         else
6467                         {
6468                             // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
6469                             m_bPostponedProcessingFly = true ;
6470                             m_pPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6471                         }
6472                     }
6473                 }
6474             }
6475             break;
6476         case ww8::Frame::eTextBox:
6477             {
6478                 // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox().
6479                 if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat()))
6480                     break;
6481 
6482                 // If this is a TextBox containing a table which we already exported directly, ignore it
6483                 if (m_aFloatingTablesOfParagraph.find(&rFrame.GetFrameFormat()) != m_aFloatingTablesOfParagraph.end())
6484                     break;
6485 
6486                 // The frame output is postponed to the end of the anchor paragraph
6487                 bool bDuplicate = false;
6488                 const OUString& rName = rFrame.GetFrameFormat().GetName();
6489                 unsigned nSize = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
6490                 for( unsigned nIndex = 0; nIndex < nSize; ++nIndex )
6491                 {
6492                     const OUString& rNameExisting = m_aFramesOfParagraph.top()[nIndex].GetFrameFormat().GetName();
6493 
6494                     if (!rName.isEmpty() && !rNameExisting.isEmpty())
6495                     {
6496                         if (rName == rNameExisting)
6497                             bDuplicate = true;
6498                     }
6499                 }
6500 
6501                 if( !bDuplicate )
6502                 {
6503                     m_bPostponedProcessingFly = true ;
6504                     if ( m_aFramesOfParagraph.size() )
6505                         m_aFramesOfParagraph.top().emplace_back(rFrame);
6506                 }
6507             }
6508             break;
6509         case ww8::Frame::eOle:
6510             {
6511                 const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat();
6512                 const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject();
6513                 if ( pSdrObj )
6514                 {
6515                     SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1);
6516                     SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
6517 
6518                     //output variable for the formula alignment (default inline)
6519                     sal_Int8 nAlign(FormulaExportBase::eFormulaAlign::INLINE);
6520                     auto xObj(rOLENd.GetOLEObj().GetOleRef()); //get the xObject of the formula
6521 
6522                     //tdf133030: Export formula position
6523                     //If we have a formula with inline anchor...
6524                     if(SotExchange::IsMath(xObj->getClassID()) && rFrame.IsInline())
6525                     {
6526                         SwPosition const* const aAPos = rFrameFormat.GetAnchor().GetContentAnchor();
6527                         if(aAPos)
6528                         {
6529                             //Get the text node what the formula anchored to
6530                             const SwTextNode* pTextNode = aAPos->nNode.GetNode().GetTextNode();
6531                             if(pTextNode && pTextNode->Len() == 1)
6532                             {
6533                                 //Get the paragraph alignment
6534                                 auto aParaAdjust = pTextNode->GetSwAttrSet().GetAdjust().GetAdjust();
6535                                 //And set the formula according to the paragraph alignment
6536                                 if (aParaAdjust == SvxAdjust::Center)
6537                                     nAlign = FormulaExportBase::eFormulaAlign::CENTER;
6538                                 else if (aParaAdjust == SvxAdjust::Right)
6539                                     nAlign = FormulaExportBase::eFormulaAlign::RIGHT;
6540                                 else // left in the case of left and justified paragraph alignments
6541                                     nAlign = FormulaExportBase::eFormulaAlign::LEFT;
6542                             }
6543                         }
6544                     }
6545                     WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ), nAlign);
6546                     m_bPostponedProcessingFly = false ;
6547                 }
6548             }
6549             break;
6550         case ww8::Frame::eFormControl:
6551             {
6552                 const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject();
6553                 if(ExportAsActiveXControl(pObject))
6554                     m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat()));
6555                 else
6556                     m_aPostponedFormControls.push_back(pObject);
6557                 m_bPostponedProcessingFly = true ;
6558             }
6559             break;
6560         default:
6561             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " <<
6562                     ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox":
6563                       ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) );
6564             break;
6565     }
6566 
6567     m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
6568 }
6569 
6570 void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
6571 {
6572     /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
6573     /// Now if a frame anchored inside another frame, it will
6574     /// not be exported immediately, because OOXML does not
6575     /// support that feature, instead it postponed and exported
6576     /// later when the original shape closed.
6577 
6578     if (rFrame.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
6579         || rFrame.IsInline())
6580     {
6581         m_nEmbedFlyLevel++;
6582         WriteFlyFrame(rFrame);
6583         m_nEmbedFlyLevel--;
6584         return;
6585     }
6586 
6587     if (m_nEmbedFlyLevel == 0)
6588     {
6589         if (m_vPostponedFlys.empty())
6590         {
6591             m_nEmbedFlyLevel++;
6592             WriteFlyFrame(rFrame);
6593             m_nEmbedFlyLevel--;
6594         }
6595         else
6596             for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
6597             {
6598                 m_nEmbedFlyLevel++;
6599                 WriteFlyFrame(*it);
6600                 it = m_vPostponedFlys.erase(it);
6601                 m_nEmbedFlyLevel--;
6602             }
6603     }
6604     else
6605     {
6606         bool bFound = false;
6607         for (const auto& i : m_vPostponedFlys)
6608         {
6609             if (i.RefersToSameFrameAs(rFrame))
6610             {
6611                 bFound = true;
6612                 break;
6613             }
6614         }
6615         if (!bFound)
6616         {
6617             if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
6618             {
6619                 auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
6620                 aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
6621                 auto aVori(rFrame.GetFrameFormat().GetVertOrient());
6622                 aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());
6623 
6624                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
6625                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
6626                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());
6627 
6628                 m_vPostponedFlys.push_back(rFrame);
6629             }
6630 
6631         }
6632     }
6633 }
6634 
6635 void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
6636 {
6637     const EditTextObject& rEditObj = rParaObj.GetTextObject();
6638     MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );
6639 
6640     sal_Int32 nPara = rEditObj.GetParagraphCount();
6641 
6642     m_pSerializer->startElementNS(XML_w, XML_txbxContent);
6643     for (sal_Int32 n = 0; n < nPara; ++n)
6644     {
6645         if( n )
6646             aAttrIter.NextPara( n );
6647 
6648         OUString aStr( rEditObj.GetText( n ));
6649         sal_Int32 nCurrentPos = 0;
6650         sal_Int32 nEnd = aStr.getLength();
6651 
6652         StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false);
6653 
6654         // Write paragraph properties.
6655         StartParagraphProperties();
6656         aAttrIter.OutParaAttr(false);
6657         SfxItemSet aParagraphMarkerProperties(m_rExport.m_rDoc.GetAttrPool());
6658         EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr);
6659 
6660         do {
6661             const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);
6662 
6663             m_pSerializer->startElementNS(XML_w, XML_r);
6664 
6665             // Write run properties.
6666             m_pSerializer->startElementNS(XML_w, XML_rPr);
6667             aAttrIter.OutAttr(nCurrentPos);
6668             WriteCollectedRunProperties();
6669             m_pSerializer->endElementNS(XML_w, XML_rPr);
6670 
6671             bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
6672             if( !bTextAtr )
6673             {
6674                 OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) );
6675                 RunText(aOut);
6676             }
6677 
6678             if ( !m_sRawText.isEmpty() )
6679             {
6680                 RunText( m_sRawText );
6681                 m_sRawText.clear();
6682             }
6683 
6684             m_pSerializer->endElementNS( XML_w, XML_r );
6685 
6686             nCurrentPos = nNextAttr;
6687             aAttrIter.NextPos();
6688         }
6689         while( nCurrentPos < nEnd );
6690         EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
6691     }
6692     m_pSerializer->endElementNS( XML_w, XML_txbxContent );
6693 }
6694 
6695 void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext)
6696 {
6697     rContext.m_pTableInfo = m_rExport.m_pTableInfo;
6698     m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
6699 
6700     rContext.m_bTableCellOpen = m_tableReference->m_bTableCellOpen;
6701     m_tableReference->m_bTableCellOpen = false;
6702 
6703     rContext.m_nTableDepth = m_tableReference->m_nTableDepth;
6704     m_tableReference->m_nTableDepth = 0;
6705 
6706     rContext.m_bStartedParaSdt = m_aParagraphSdt.m_bStartedSdt;
6707     m_aParagraphSdt.m_bStartedSdt = false;
6708 }
6709 
6710 void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext)
6711 {
6712     m_rExport.m_pTableInfo = rContext.m_pTableInfo;
6713     m_tableReference->m_bTableCellOpen = rContext.m_bTableCellOpen;
6714     m_tableReference->m_nTableDepth = rContext.m_nTableDepth;
6715     m_aParagraphSdt.m_bStartedSdt = rContext.m_bStartedParaSdt;
6716 }
6717 
6718 void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape)
6719 {
6720     DocxTableExportContext aTableExportContext(*this);
6721 
6722     SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6723     assert(pTextBox);
6724     const SwPosition* pAnchor = nullptr;
6725     const bool bFlyAtPage = pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6726     if (bFlyAtPage) //tdf135711
6727     {
6728         auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6729         if (pNdIdx) //Is that possible it is null?
6730             pAnchor = new SwPosition(*pNdIdx);
6731     }
6732     else
6733     {
6734         pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6735     }
6736 
6737     if (pAnchor) //pAnchor can be null, so that's why not assert here.
6738     {
6739         ww8::Frame aFrame(*pTextBox, *pAnchor);
6740         m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true);
6741         if (bFlyAtPage)
6742         {
6743             delete pAnchor;
6744         }
6745     }
6746 }
6747 
6748 void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)
6749 {
6750     DocxTableExportContext aTableExportContext(*this);
6751 
6752     SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6753     assert(pTextBox);
6754     const SwPosition* pAnchor = nullptr;
6755     if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) //tdf135711
6756     {
6757         auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6758         if (pNdIdx) //Is that possible it is null?
6759             pAnchor = new SwPosition(*pNdIdx);
6760     }
6761     else
6762     {
6763         pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6764     }
6765 
6766     if (pAnchor) //pAnchor can be null, so that's why not assert here.
6767     {
6768         ww8::Frame aFrame(*pTextBox, *pAnchor);
6769         m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true);
6770         if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE)
6771         {
6772             delete pAnchor;
6773         }
6774     }
6775 }
6776 
6777 oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML()
6778 {
6779     return m_rDrawingML;
6780 }
6781 
6782 bool DocxAttributeOutput::MaybeOutputBrushItem(SfxItemSet const& rSet)
6783 {
6784     const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
6785 
6786     if ((pXFillStyleItem && pXFillStyleItem->GetValue() != drawing::FillStyle_NONE)
6787         || !m_rExport.SdrExporter().getDMLTextFrameSyntax())
6788     {
6789         return false;
6790     }
6791 
6792     // sw text frames are opaque by default, even with fill none!
6793     std::unique_ptr<SfxItemSet> const pClone(rSet.Clone());
6794     XFillColorItem const aColor(OUString(), COL_WHITE);
6795     pClone->Put(aColor);
6796     // call getSvxBrushItemForSolid - this also takes XFillTransparenceItem into account
6797     XFillStyleItem const aSolid(drawing::FillStyle_SOLID);
6798     pClone->Put(aSolid);
6799     std::unique_ptr<SvxBrushItem> const pBrush(getSvxBrushItemFromSourceSet(*pClone, RES_BACKGROUND));
6800     FormatBackground(*pBrush);
6801     return true;
6802 }
6803 
6804 namespace {
6805 
6806 /// Functor to do case-insensitive ordering of OUString instances.
6807 struct OUStringIgnoreCase
6808 {
6809     bool operator() (const OUString& lhs, std::u16string_view rhs) const
6810     {
6811         return lhs.compareToIgnoreAsciiCase(rhs) < 0;
6812     }
6813 };
6814 
6815 }
6816 
6817 /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not.
6818 static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId)
6819 {
6820     // If the style has no dedicated STI number, then it's probably a custom style -> qFormat.
6821     if (nWwId == ww::stiUser)
6822         return true;
6823 
6824     // Allow exported built-in styles UI language neutral
6825     if ( nWwId == ww::stiNormal ||
6826         ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) ||
6827             nWwId == ww::stiCaption || nWwId == ww::stiTitle ||
6828             nWwId == ww::stiSubtitle || nWwId == ww::stiStrong ||
6829             nWwId == ww::stiEmphasis )
6830         return true;
6831 
6832     static o3tl::sorted_vector<OUString, OUStringIgnoreCase> const aAllowlist
6833     {
6834         "No Spacing",
6835         "List Paragraph",
6836         "Quote",
6837         "Intense Quote",
6838         "Subtle Emphasis,",
6839         "Intense Emphasis",
6840         "Subtle Reference",
6841         "Intense Reference",
6842         "Book Title",
6843         "TOC Heading",
6844     };
6845     // Not custom style? Then we have a list of standard styles which should be qFormat.
6846     return aAllowlist.find(rName) != aAllowlist.end();
6847 }
6848 
6849 void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
6850         sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nLink, sal_uInt16 nWwId, sal_uInt16 nId, bool bAutoUpdate )
6851 {
6852     bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false;
6853     OUString aRsid, aUiPriority;
6854     rtl::Reference<FastAttributeList> pStyleAttributeList = FastSerializerHelper::createAttrList();
6855     uno::Any aAny;
6856     if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)
6857     {
6858         const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nId);
6859         pFormat->GetGrabBagItem(aAny);
6860     }
6861     else
6862     {
6863         const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nId);
6864         pRule->GetGrabBagItem(aAny);
6865     }
6866     const uno::Sequence<beans::PropertyValue>& rGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >();
6867 
6868     for (const auto& rProp : rGrabBag)
6869     {
6870         if (rProp.Name == "uiPriority")
6871             aUiPriority = rProp.Value.get<OUString>();
6872         else if (rProp.Name == "qFormat")
6873             bQFormat = true;
6874         else if (rProp.Name == "rsid")
6875             aRsid = rProp.Value.get<OUString>();
6876         else if (rProp.Name == "unhideWhenUsed")
6877             bUnhideWhenUsed = true;
6878         else if (rProp.Name == "semiHidden")
6879             bSemiHidden = true;
6880         else if (rProp.Name == "locked")
6881             bLocked = true;
6882         else if (rProp.Name == "default")
6883             bDefault = rProp.Value.get<bool>();
6884         else if (rProp.Name == "customStyle")
6885             bCustomStyle = rProp.Value.get<bool>();
6886         else
6887             SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name);
6888     }
6889 
6890     // MSO exports English names and writerfilter only recognize them.
6891     const char *pEnglishName = nullptr;
6892     const char* pType = nullptr;
6893     switch (eType)
6894     {
6895         case STYLE_TYPE_PARA:
6896             pType = "paragraph";
6897             if ( nWwId < ww::stiMax)
6898                 pEnglishName = ww::GetEnglishNameFromSti( static_cast<ww::sti>(nWwId ) );
6899             break;
6900         case STYLE_TYPE_CHAR: pType = "character"; break;
6901         case STYLE_TYPE_LIST: pType = "numbering"; break;
6902     }
6903     pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType);
6904     pStyleAttributeList->add(FSNS(XML_w, XML_styleId), m_rExport.m_pStyles->GetStyleId(nId));
6905     if (bDefault)
6906         pStyleAttributeList->add(FSNS(XML_w, XML_default), "1");
6907     if (bCustomStyle)
6908         pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
6909     m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList);
6910     m_pSerializer->singleElementNS( XML_w, XML_name,
6911             FSNS( XML_w, XML_val ), pEnglishName ? pEnglishName : rName.toUtf8() );
6912 
6913     if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
6914     {
6915         m_pSerializer->singleElementNS( XML_w, XML_basedOn,
6916                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase) );
6917     }
6918 
6919     if ( nNext != nId && eType != STYLE_TYPE_LIST)
6920     {
6921         m_pSerializer->singleElementNS( XML_w, XML_next,
6922                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext) );
6923     }
6924 
6925     if (nLink != 0x0FFF && (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR))
6926     {
6927         m_pSerializer->singleElementNS(XML_w, XML_link, FSNS(XML_w, XML_val),
6928                                        m_rExport.m_pStyles->GetStyleId(nLink));
6929     }
6930 
6931     if ( bAutoUpdate )
6932         m_pSerializer->singleElementNS(XML_w, XML_autoRedefine);
6933 
6934     if (!aUiPriority.isEmpty())
6935         m_pSerializer->singleElementNS(XML_w, XML_uiPriority, FSNS(XML_w, XML_val), aUiPriority);
6936     if (bSemiHidden)
6937         m_pSerializer->singleElementNS(XML_w, XML_semiHidden);
6938     if (bUnhideWhenUsed)
6939         m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed);
6940 
6941     if (bQFormat || lcl_guessQFormat(rName, nWwId))
6942         m_pSerializer->singleElementNS(XML_w, XML_qFormat);
6943     if (bLocked)
6944         m_pSerializer->singleElementNS(XML_w, XML_locked);
6945     if (!aRsid.isEmpty())
6946         m_pSerializer->singleElementNS(XML_w, XML_rsid, FSNS(XML_w, XML_val), aRsid);
6947 }
6948 
6949 void DocxAttributeOutput::EndStyle()
6950 {
6951     m_pSerializer->endElementNS( XML_w, XML_style );
6952 }
6953 
6954 void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ )
6955 {
6956     if ( bParProp )
6957     {
6958         m_pSerializer->startElementNS(XML_w, XML_pPr);
6959         InitCollectedParagraphProperties();
6960     }
6961     else
6962     {
6963         m_pSerializer->startElementNS(XML_w, XML_rPr);
6964         InitCollectedRunProperties();
6965     }
6966 }
6967 
6968 void DocxAttributeOutput::EndStyleProperties( bool bParProp )
6969 {
6970     if ( bParProp )
6971     {
6972         WriteCollectedParagraphProperties();
6973 
6974         // Merge the marks for the ordered elements
6975         m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
6976 
6977         m_pSerializer->endElementNS( XML_w, XML_pPr );
6978     }
6979     else
6980     {
6981         WriteCollectedRunProperties();
6982 
6983         // Merge the marks for the ordered elements
6984         m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
6985 
6986         m_pSerializer->endElementNS( XML_w, XML_rPr );
6987     }
6988 }
6989 
6990 void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/)
6991 {
6992     // Handled by ParaOutlineLevel() instead.
6993 }
6994 
6995 void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem)
6996 {
6997     sal_uInt16 nOutLvl = std::min(rItem.GetValue(), sal_uInt16(WW8ListManager::nMaxLevel));
6998     // Outline Level: in LO Body Text = 0, in MS Body Text = 9
6999     nOutLvl = nOutLvl ? nOutLvl - 1 : 9;
7000     m_pSerializer->singleElementNS(XML_w, XML_outlineLvl, FSNS(XML_w, XML_val), OString::number(nOutLvl));
7001 }
7002 
7003 void DocxAttributeOutput::PageBreakBefore( bool bBreak )
7004 {
7005     if ( bBreak )
7006         m_pSerializer->singleElementNS(XML_w, XML_pageBreakBefore);
7007     else
7008         m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore,
7009                 FSNS( XML_w, XML_val ), "false" );
7010 }
7011 
7012 void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak)
7013 {
7014     switch ( nC )
7015     {
7016         case msword::ColumnBreak:
7017             // The column break should be output in the next paragraph...
7018             if ( m_nColBreakStatus == COLBRK_WRITE )
7019                 m_nColBreakStatus = COLBRK_WRITEANDPOSTPONE;
7020             else
7021                 m_nColBreakStatus = COLBRK_POSTPONE;
7022             break;
7023         case msword::PageBreak:
7024             if ( pSectionInfo )
7025             {
7026                 // Detect when the current node is the last node in the
7027                 // document: the last section is written explicitly in
7028                 // DocxExport::WriteMainText(), don't duplicate that here.
7029                 SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetNode());
7030                 SwNodeIndex aLastNode(m_rExport.m_rDoc.GetNodes().GetEndOfContent(), -1);
7031                 bool bEmit = aCurrentNode != aLastNode;
7032 
7033                 if (!bEmit)
7034                 {
7035                     // Need to still emit an empty section at the end of the
7036                     // document in case balanced columns are wanted, since the last
7037                     // section in Word is always balanced.
7038                     sal_uInt16 nColumns = 1;
7039                     bool bBalance = false;
7040                     if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat)
7041                     {
7042                         if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1)))
7043                         {
7044                             nColumns = pFormat->GetCol().GetNumCols();
7045                             const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns();
7046                             bBalance = !rNoBalanced.GetValue();
7047                         }
7048                     }
7049                     bEmit = (nColumns > 1 && bBalance);
7050                 }
7051 
7052                 // don't add section properties if this will be the first
7053                 // paragraph in the document
7054                 if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit )
7055                 {
7056                     // Create a dummy paragraph if needed
7057                     m_pSerializer->startElementNS(XML_w, XML_p);
7058                     m_pSerializer->startElementNS(XML_w, XML_pPr);
7059 
7060                     m_rExport.SectionProperties( *pSectionInfo );
7061 
7062                     m_pSerializer->endElementNS( XML_w, XML_pPr );
7063                     if (bExtraPageBreak)
7064                     {
7065                         m_pSerializer->startElementNS(XML_w, XML_r);
7066                         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7067                         m_pSerializer->endElementNS(XML_w, XML_r);
7068                     }
7069                     m_pSerializer->endElementNS( XML_w, XML_p );
7070                 }
7071                 else
7072                 {
7073                     if (bExtraPageBreak && m_bParagraphOpened)
7074                     {
7075                         m_pSerializer->startElementNS(XML_w, XML_r);
7076                         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7077                         m_pSerializer->endElementNS(XML_w, XML_r);
7078                     }
7079                     // postpone the output of this; it has to be done inside the
7080                     // paragraph properties, so remember it until then
7081                     m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo ));
7082                 }
7083             }
7084             else if ( m_bParagraphOpened )
7085             {
7086                 if (bBreakAfter)
7087                     // tdf#128889
7088                     m_bPageBreakAfter = true;
7089                 else
7090                 {
7091                     m_pSerializer->startElementNS(XML_w, XML_r);
7092                     m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7093                     m_pSerializer->endElementNS(XML_w, XML_r);
7094                 }
7095             }
7096             else
7097                 m_bPostponedPageBreak = true;
7098 
7099             break;
7100         default:
7101             SAL_INFO("sw.ww8", "Unknown section break to write: " << nC );
7102             break;
7103     }
7104 }
7105 
7106 void DocxAttributeOutput::EndParaSdtBlock()
7107 {
7108     if (m_aParagraphSdt.m_bStartedSdt)
7109     {
7110         // Paragraph-level SDT still open? Close it now.
7111         m_aParagraphSdt.EndSdtBlock(m_pSerializer);
7112     }
7113 }
7114 
7115 void DocxAttributeOutput::StartSection()
7116 {
7117     m_pSerializer->startElementNS(XML_w, XML_sectPr);
7118     m_bOpenedSectPr = true;
7119 
7120     // Write the elements in the spec order
7121     static const sal_Int32 aOrder[] =
7122     {
7123         FSNS( XML_w, XML_headerReference ),
7124         FSNS( XML_w, XML_footerReference ),
7125         FSNS( XML_w, XML_footnotePr ),
7126         FSNS( XML_w, XML_endnotePr ),
7127         FSNS( XML_w, XML_type ),
7128         FSNS( XML_w, XML_pgSz ),
7129         FSNS( XML_w, XML_pgMar ),
7130         FSNS( XML_w, XML_paperSrc ),
7131         FSNS( XML_w, XML_pgBorders ),
7132         FSNS( XML_w, XML_lnNumType ),
7133         FSNS( XML_w, XML_pgNumType ),
7134         FSNS( XML_w, XML_cols ),
7135         FSNS( XML_w, XML_formProt ),
7136         FSNS( XML_w, XML_vAlign ),
7137         FSNS( XML_w, XML_noEndnote ),
7138         FSNS( XML_w, XML_titlePg ),
7139         FSNS( XML_w, XML_textDirection ),
7140         FSNS( XML_w, XML_bidi ),
7141         FSNS( XML_w, XML_rtlGutter ),
7142         FSNS( XML_w, XML_docGrid ),
7143         FSNS( XML_w, XML_printerSettings ),
7144         FSNS( XML_w, XML_sectPrChange )
7145     };
7146 
7147     // postpone the output so that we can later [in EndParagraphProperties()]
7148     // prepend the properties before the run
7149     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7150     m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder));
7151     m_bHadSectPr = true;
7152 }
7153 
7154 void DocxAttributeOutput::EndSection()
7155 {
7156     // Write the section properties
7157     if ( m_pSectionSpacingAttrList.is() )
7158     {
7159         rtl::Reference<FastAttributeList> xAttrList = std::move( m_pSectionSpacingAttrList );
7160         m_pSerializer->singleElementNS( XML_w, XML_pgMar, xAttrList );
7161     }
7162 
7163     // Order the elements
7164     m_pSerializer->mergeTopMarks(Tag_StartSection);
7165 
7166     m_pSerializer->endElementNS( XML_w, XML_sectPr );
7167     m_bOpenedSectPr = false;
7168 }
7169 
7170 void DocxAttributeOutput::SectionFormProtection( bool bProtected )
7171 {
7172     if ( bProtected )
7173         m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "true");
7174     else
7175         m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "false");
7176 }
7177 
7178 void DocxAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter)
7179 {
7180     if (!rRtlGutter.GetValue())
7181     {
7182         return;
7183     }
7184 
7185     m_pSerializer->singleElementNS(XML_w, XML_rtlGutter);
7186 }
7187 
7188 void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo )
7189 {
7190     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7191     pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy()).getStr());
7192     pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" );
7193     if( rLnNumInfo.GetPosFromLeft())
7194         pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft()).getStr());
7195     if (nRestartNo > 0)
7196         // Writer is 1-based, Word is 0-based.
7197         pAttr->add(FSNS(XML_w, XML_start), OString::number(nRestartNo - 1).getStr());
7198     m_pSerializer->singleElementNS( XML_w, XML_lnNumType, pAttr );
7199 }
7200 
7201 void DocxAttributeOutput::SectionTitlePage()
7202 {
7203     m_pSerializer->singleElementNS(XML_w, XML_titlePg);
7204 }
7205 
7206 void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ )
7207 {
7208     // Output the margins
7209 
7210     const SvxBoxItem& rBox = pFormat->GetBox( );
7211 
7212     const SvxBorderLine* pLeft = rBox.GetLeft( );
7213     const SvxBorderLine* pTop = rBox.GetTop( );
7214     const SvxBorderLine* pRight = rBox.GetRight( );
7215     const SvxBorderLine* pBottom = rBox.GetBottom( );
7216 
7217     if ( !(pBottom || pTop || pLeft || pRight) )
7218         return;
7219 
7220     OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
7221 
7222     // Check if there is a shadow item
7223     const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
7224     if ( pItem )
7225     {
7226         const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
7227         aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
7228     }
7229 
7230     // By top margin, impl_borders() means the distance between the top of the page and the header frame.
7231     editeng::WordPageMargins aMargins = m_pageMargins;
7232     HdFtDistanceGlue aGlue(pFormat->GetAttrSet());
7233     if (aGlue.HasHeader())
7234         aMargins.nTop = aGlue.dyaHdrTop;
7235     // Ditto for bottom margin.
7236     if (aGlue.HasFooter())
7237         aMargins.nBottom = aGlue.dyaHdrBottom;
7238 
7239     if (pFormat->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::GUTTER_AT_TOP))
7240     {
7241         aMargins.nTop += pFormat->GetLRSpace().GetGutterMargin();
7242     }
7243     else
7244     {
7245         aMargins.nLeft += pFormat->GetLRSpace().GetGutterMargin();
7246     }
7247 
7248     aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>();
7249     editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances);
7250 
7251     // All distances are relative to the text margins
7252     m_pSerializer->startElementNS(XML_w, XML_pgBorders,
7253         FSNS(XML_w, XML_display), "allPages",
7254         FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text");
7255 
7256     std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map
7257     impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap );
7258 
7259     m_pSerializer->endElementNS( XML_w, XML_pgBorders );
7260 
7261 }
7262 
7263 void DocxAttributeOutput::SectionBiDi( bool bBiDi )
7264 {
7265     if ( bBiDi )
7266         m_pSerializer->singleElementNS(XML_w, XML_bidi);
7267 }
7268 
7269 // Converting Numbering Format Code to string
7270 static OString lcl_ConvertNumberingType(sal_Int16 nNumberingType, const SfxItemSet* pOutSet, OString& rFormat, const OString& sDefault = "" )
7271 {
7272     OString aType = sDefault;
7273 
7274     switch ( nNumberingType )
7275     {
7276         case SVX_NUM_CHARS_UPPER_LETTER:
7277         case SVX_NUM_CHARS_UPPER_LETTER_N:  aType = "upperLetter"; break;
7278 
7279         case SVX_NUM_CHARS_LOWER_LETTER:
7280         case SVX_NUM_CHARS_LOWER_LETTER_N:  aType = "lowerLetter"; break;
7281 
7282         case SVX_NUM_ROMAN_UPPER:           aType = "upperRoman";  break;
7283         case SVX_NUM_ROMAN_LOWER:           aType = "lowerRoman";  break;
7284         case SVX_NUM_ARABIC:                aType = "decimal";     break;
7285 
7286         case SVX_NUM_BITMAP:
7287         case SVX_NUM_CHAR_SPECIAL:          aType = "bullet";      break;
7288 
7289         case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"; break;
7290         case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"; break;
7291         case style::NumberingType::NUMBER_NONE: aType = "none"; break;
7292         case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"; break;
7293         case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"; break;
7294         case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"; break;
7295         case style::NumberingType::NUMBER_LOWER_ZH:
7296             aType="taiwaneseCountingThousand";
7297             if (pOutSet) {
7298                 const SvxLanguageItem& rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE);
7299                 const LanguageType eLang = rLang.GetLanguage();
7300 
7301                 if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) {
7302                     aType="chineseCountingThousand";
7303                 }
7304             }
7305         break;
7306         case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional";break;
7307         case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"; break;
7308         case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal";break;
7309         case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth";break;
7310         case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo";break;
7311         case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha";break;
7312         case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth";break;
7313         case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada";break;
7314         case style::NumberingType::HANGUL_JAMO_KO: aType="chosung";break;
7315         case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanCounting"; break;
7316         case style::NumberingType::NUMBER_LEGAL_KO: aType = "koreanLegal"; break;
7317         case style::NumberingType::NUMBER_DIGITAL_KO: aType = "koreanDigital"; break;
7318         case style::NumberingType::NUMBER_DIGITAL2_KO: aType = "koreanDigital2"; break;
7319         case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"; break;
7320         case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"; break;
7321         case style::NumberingType::CHARS_ARABIC_ABJAD: aType="arabicAbjad"; break;
7322         case style::NumberingType::CHARS_THAI: aType="thaiLetters"; break;
7323         case style::NumberingType::CHARS_PERSIAN:
7324         case style::NumberingType::CHARS_NEPALI: aType="hindiVowels"; break;
7325         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU:
7326         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU: aType = "russianUpper"; break;
7327         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU:
7328         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU: aType = "russianLower"; break;
7329         case style::NumberingType::TEXT_NUMBER: aType="ordinal"; break;
7330         case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"; break;
7331         case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"; break;
7332         case style::NumberingType::SYMBOL_CHICAGO: aType="chicago"; break;
7333         case style::NumberingType::ARABIC_ZERO: aType = "decimalZero"; break;
7334         case style::NumberingType::ARABIC_ZERO3:
7335             aType = "custom";
7336             rFormat = "001, 002, 003, ...";
7337             break;
7338         case style::NumberingType::ARABIC_ZERO4:
7339             aType = "custom";
7340             rFormat = "0001, 0002, 0003, ...";
7341             break;
7342         case style::NumberingType::ARABIC_ZERO5:
7343             aType = "custom";
7344             rFormat = "00001, 00002, 00003, ...";
7345             break;
7346 /*
7347         Fallback the rest to the suggested default.
7348         case style::NumberingType::NATIVE_NUMBERING:
7349         case style::NumberingType::HANGUL_CIRCLED_JAMO_KO:
7350         case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO:
7351         case style::NumberingType::CHARS_GREEK_UPPER_LETTER:
7352         case style::NumberingType::CHARS_GREEK_LOWER_LETTER:
7353         case style::NumberingType::PAGE_DESCRIPTOR:
7354         case style::NumberingType::TRANSLITERATION:
7355         case style::NumberingType::CHARS_KHMER:
7356         case style::NumberingType::CHARS_LAO:
7357         case style::NumberingType::CHARS_TIBETAN:
7358         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG:
7359         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG:
7360         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG:
7361         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG:
7362         case style::NumberingType::CHARS_MYANMAR:
7363         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR:
7364         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR:
7365         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR:
7366         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR:
7367 */
7368         default: break;
7369     }
7370     return aType;
7371 }
7372 
7373 
7374 void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::std::optional<sal_uInt16>& oPageRestartNumber )
7375 {
7376     // FIXME Not called properly with page styles like "First Page"
7377 
7378     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7379 
7380     // std::nullopt means no restart: then don't output that attribute if it is negative
7381     if ( oPageRestartNumber )
7382        pAttr->add( FSNS( XML_w, XML_start ), OString::number( *oPageRestartNumber ) );
7383 
7384     // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions
7385     OString aCustomFormat;
7386     OString aFormat(lcl_ConvertNumberingType(nNumType, nullptr, aCustomFormat));
7387     if (!aFormat.isEmpty() && aCustomFormat.isEmpty())
7388         pAttr->add(FSNS(XML_w, XML_fmt), aFormat);
7389 
7390     m_pSerializer->singleElementNS( XML_w, XML_pgNumType, pAttr );
7391 
7392     // see 2.6.12 pgNumType (Page Numbering Settings)
7393     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" );
7394 }
7395 
7396 void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode )
7397 {
7398     /*  break code:   0 No break, 1 New column
7399         2 New page, 3 Even page, 4 Odd page
7400         */
7401     const char* pType;
7402     switch ( nBreakCode )
7403     {
7404         case 1:  pType = "nextColumn"; break;
7405         case 2:  pType = "nextPage";   break;
7406         case 3:  pType = "evenPage";   break;
7407         case 4:  pType = "oddPage";    break;
7408         default: pType = "continuous"; break;
7409     }
7410 
7411     m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), pType);
7412 }
7413 
7414 void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA )
7415 {
7416     switch( nVA )
7417     {
7418         case drawing::TextVerticalAdjust_CENTER:
7419             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center");
7420             break;
7421         case drawing::TextVerticalAdjust_BOTTOM:
7422             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom");
7423             break;
7424         case drawing::TextVerticalAdjust_BLOCK:  //justify
7425             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "both");
7426             break;
7427         default:
7428             break;
7429     }
7430 }
7431 
7432 void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const
7433 {
7434     m_pSerializer->startElementNS(XML_w, XML_font, FSNS(XML_w, XML_name), rFamilyName);
7435 }
7436 
7437 void DocxAttributeOutput::EndFont() const
7438 {
7439     m_pSerializer->endElementNS( XML_w, XML_font );
7440 }
7441 
7442 void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const
7443 {
7444     m_pSerializer->singleElementNS(XML_w, XML_altName, FSNS(XML_w, XML_val), rName);
7445 }
7446 
7447 void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const
7448 {
7449     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7450 
7451     OString aCharSet( OString::number( nCharSet, 16 ) );
7452     if ( aCharSet.getLength() == 1 )
7453         aCharSet = "0" + aCharSet;
7454     pAttr->add(FSNS(XML_w, XML_val), aCharSet);
7455 
7456     if( GetExport().GetFilter().getVersion( ) != oox::core::ECMA_DIALECT )
7457     {
7458         if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding ))
7459             pAttr->add( FSNS( XML_w, XML_characterSet ), charset );
7460     }
7461 
7462     m_pSerializer->singleElementNS( XML_w, XML_charset, pAttr );
7463 }
7464 
7465 void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const
7466 {
7467     const char* pFamily;
7468     switch ( eFamily )
7469     {
7470         case FAMILY_ROMAN:      pFamily = "roman"; break;
7471         case FAMILY_SWISS:      pFamily = "swiss"; break;
7472         case FAMILY_MODERN:     pFamily = "modern"; break;
7473         case FAMILY_SCRIPT:     pFamily = "script"; break;
7474         case FAMILY_DECORATIVE: pFamily = "decorative"; break;
7475         default:                pFamily = "auto"; break; // no font family
7476     }
7477 
7478     m_pSerializer->singleElementNS(XML_w, XML_family, FSNS(XML_w, XML_val), pFamily);
7479 }
7480 
7481 void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const
7482 {
7483     const char* pPitch;
7484     switch ( ePitch )
7485     {
7486         case PITCH_VARIABLE: pPitch = "variable"; break;
7487         case PITCH_FIXED:    pPitch = "fixed"; break;
7488         default:             pPitch = "default"; break; // no info about the pitch
7489     }
7490 
7491     m_pSerializer->singleElementNS(XML_w, XML_pitch, FSNS(XML_w, XML_val), pPitch);
7492 }
7493 
7494 void DocxAttributeOutput::EmbedFont( std::u16string_view name, FontFamily family, FontPitch pitch )
7495 {
7496     if( !m_rExport.m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS ))
7497         return; // no font embedding with this document
7498     EmbedFontStyle( name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch );
7499     EmbedFontStyle( name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch );
7500     EmbedFontStyle( name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch );
7501     EmbedFontStyle( name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch );
7502 }
7503 
7504 static char toHexChar( int value )
7505 {
7506     return value >= 10 ? value + 'A' - 10 : value + '0';
7507 }
7508 
7509 void DocxAttributeOutput::EmbedFontStyle( std::u16string_view name, int tag, FontFamily family, FontItalic italic,
7510     FontWeight weight, FontPitch pitch )
7511 {
7512     // Embed font if at least viewing is allowed (in which case the opening app must check
7513     // the font license rights too and open either read-only or not use the font for editing).
7514     OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch,
7515         EmbeddedFontsHelper::FontRights::ViewingAllowed );
7516     if( fontUrl.isEmpty())
7517         return;
7518     // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS
7519     if( !fontFilesMap.count( fontUrl ))
7520     {
7521         osl::File file( fontUrl );
7522         if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None )
7523             return;
7524         uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream(
7525             "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf",
7526             "application/vnd.openxmlformats-officedocument.obfuscatedFont" );
7527         // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway,
7528         // so just alter the first and last part of the key.
7529         char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}";
7530         sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E,
7531             0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 };
7532         fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256;
7533         fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 );
7534         fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 );
7535         unsigned char buffer[ 4096 ];
7536         sal_uInt64 readSize;
7537         file.read( buffer, 32, readSize );
7538         if( readSize < 32 )
7539         {
7540             SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" );
7541             xOutStream->closeOutput();
7542             return;
7543         }
7544         for( int i = 0;
7545              i < 16;
7546              ++i )
7547         {
7548             buffer[ i ] ^= fontKey[ i ];
7549             buffer[ i + 16 ] ^= fontKey[ i ];
7550         }
7551         xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 ));
7552         for(;;)
7553         {
7554             sal_Bool eof;
7555             if( file.isEndOfFile( &eof ) != osl::File::E_None )
7556             {
7557                 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7558                 xOutStream->closeOutput();
7559                 return;
7560             }
7561             if( eof )
7562                 break;
7563             if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
7564             {
7565                 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7566                 xOutStream->closeOutput();
7567                 return;
7568             }
7569             if( readSize == 0 )
7570                 break;
7571             // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7572             xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize ));
7573         }
7574         xOutStream->closeOutput();
7575         OString relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
7576             oox::getRelationship(Relationship::FONT),
7577             OUStringConcatenation("fonts/font" + OUString::number( m_nextFontId ) + ".odttf") ), RTL_TEXTENCODING_UTF8 );
7578         EmbeddedFontRef ref;
7579         ref.relId = relId;
7580         ref.fontKey = fontKeyStr;
7581         fontFilesMap[ fontUrl ] = ref;
7582         ++m_nextFontId;
7583     }
7584     m_pSerializer->singleElementNS( XML_w, tag,
7585         FSNS( XML_r, XML_id ), fontFilesMap[ fontUrl ].relId,
7586         FSNS( XML_w, XML_fontKey ), fontFilesMap[ fontUrl ].fontKey );
7587 }
7588 
7589 OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco )
7590 {
7591     switch (nIco)
7592     {
7593         case 0: return "none"; break;
7594         case 1: return "black"; break;
7595         case 2: return "blue"; break;
7596         case 3: return "cyan"; break;
7597         case 4: return "green"; break;
7598         case 5: return "magenta"; break;
7599         case 6: return "red"; break;
7600         case 7: return "yellow"; break;
7601         case 8: return "white"; break;
7602         case 9: return "darkBlue"; break;
7603         case 10: return "darkCyan"; break;
7604         case 11: return "darkGreen"; break;
7605         case 12: return "darkMagenta"; break;
7606         case 13: return "darkRed"; break;
7607         case 14: return "darkYellow"; break;
7608         case 15: return "darkGray"; break;
7609         case 16: return "lightGray"; break;
7610         default: return OString(); break;
7611     }
7612 }
7613 
7614 void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule )
7615 {
7616     // nId is the same both for abstract numbering definition as well as the
7617     // numbering definition itself
7618     // TODO check that this is actually true & fix if not ;-)
7619     OString aId( OString::number( nId ) );
7620 
7621     m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), aId);
7622 
7623     m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), aId);
7624 
7625 #if OSL_DEBUG_LEVEL > 1
7626     // TODO ww8 version writes this, anything to do about it here?
7627     if ( rRule.IsContinusNum() )
7628         SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" );
7629 #else
7630     (void) rRule; // to quiet the warning...
7631 #endif
7632 
7633     m_pSerializer->endElementNS( XML_w, XML_num );
7634 }
7635 
7636 // Not all attributes of SwNumFormat are important for export, so can't just use embedded in
7637 // that classes comparison.
7638 static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2)
7639 {
7640     if (rFormat1 == rFormat2)
7641         // They are equal, nothing to do
7642         return false;
7643 
7644     if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat())
7645         // One has charformat, other not. they are different
7646         return true;
7647 
7648     if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat())
7649     {
7650         const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet();
7651         const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet();
7652 
7653         if (!(a1 == a2))
7654             // Difference in charformat: they are different
7655             return true;
7656     }
7657 
7658     // Compare numformats with empty charformats
7659     SwNumFormat modified1 = rFormat1;
7660     SwNumFormat modified2 = rFormat2;
7661     modified1.SetCharFormatName(OUString());
7662     modified2.SetCharFormatName(OUString());
7663     modified1.SetCharFormat(nullptr);
7664     modified2.SetCharFormat(nullptr);
7665     return modified1 != modified2;
7666 }
7667 
7668 void DocxAttributeOutput::OverrideNumberingDefinition(
7669         SwNumRule const& rRule,
7670         sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides )
7671 {
7672     m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), OString::number(nNum));
7673 
7674     m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), OString::number(nAbstractNum));
7675 
7676     SwNumRule const& rAbstractRule = *(*m_rExport.m_pUsedNumTable)[nAbstractNum - 1];
7677     sal_uInt8 const nLevels = static_cast<sal_uInt8>(rRule.IsContinusNum()
7678         ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel);
7679     for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel)
7680     {
7681         const auto levelOverride = rLevelOverrides.find(nLevel);
7682         bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel));
7683 
7684         // Export list override only if it is different to abstract one
7685         // or we have a level numbering override
7686         if (bListsAreDifferent || levelOverride != rLevelOverrides.end())
7687         {
7688             m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7689 
7690             if (bListsAreDifferent)
7691             {
7692                 GetExport().NumberingLevel(rRule, nLevel);
7693             }
7694             if (levelOverride != rLevelOverrides.end())
7695             {
7696                 // list numbering restart override
7697                 m_pSerializer->singleElementNS(XML_w, XML_startOverride,
7698                     FSNS(XML_w, XML_val), OString::number(levelOverride->second));
7699             }
7700 
7701             m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7702         }
7703     }
7704 
7705     m_pSerializer->endElementNS( XML_w, XML_num );
7706 }
7707 
7708 void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId )
7709 {
7710     const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1];
7711     m_bExportingOutline = pRule && pRule->IsOutlineRule();
7712     m_pSerializer->startElementNS( XML_w, XML_abstractNum,
7713             FSNS( XML_w, XML_abstractNumId ), OString::number(nId) );
7714 }
7715 
7716 void DocxAttributeOutput::EndAbstractNumbering()
7717 {
7718     m_pSerializer->endElementNS( XML_w, XML_abstractNum );
7719 }
7720 
7721 void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel,
7722         sal_uInt16 nStart,
7723         sal_uInt16 nNumberingType,
7724         SvxAdjust eAdjust,
7725         const sal_uInt8 * /*pNumLvlPos*/,
7726         sal_uInt8 nFollow,
7727         const wwFont *pFont,
7728         const SfxItemSet *pOutSet,
7729         sal_Int16 nIndentAt,
7730         sal_Int16 nFirstLineIndex,
7731         sal_Int16 nListTabPos,
7732         const OUString &rNumberingString,
7733         const SvxBrushItem* pBrush)
7734 {
7735     m_pSerializer->startElementNS(XML_w, XML_lvl, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7736 
7737     // start with the nStart value. Do not write w:start if Numbered Lists
7738     // starts from zero.As it's an optional parameter.
7739     // refer ECMA 376 Second edition Part-1
7740     if(0 != nLevel || 0 != nStart)
7741     {
7742         m_pSerializer->singleElementNS( XML_w, XML_start,
7743                 FSNS( XML_w, XML_val ), OString::number(nStart) );
7744     }
7745 
7746     if (m_bExportingOutline)
7747     {
7748         sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel );
7749         if ( nId != SAL_MAX_UINT16 )
7750             m_pSerializer->singleElementNS( XML_w, XML_pStyle ,
7751                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId) );
7752     }
7753     // format
7754     OString aCustomFormat;
7755     OString aFormat(lcl_ConvertNumberingType(nNumberingType, pOutSet, aCustomFormat, "decimal"));
7756 
7757     {
7758         if (aCustomFormat.isEmpty())
7759         {
7760             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat);
7761         }
7762         else
7763         {
7764             m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
7765             m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "w14");
7766 
7767             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat,
7768                                            FSNS(XML_w, XML_format), aCustomFormat);
7769 
7770             m_pSerializer->endElementNS(XML_mc, XML_Choice);
7771             m_pSerializer->startElementNS(XML_mc, XML_Fallback);
7772             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), "decimal");
7773             m_pSerializer->endElementNS(XML_mc, XML_Fallback);
7774             m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
7775         }
7776     }
7777 
7778     // suffix
7779     const char *pSuffix = nullptr;
7780     switch ( nFollow )
7781     {
7782         case 1:  pSuffix = "space";   break;
7783         case 2:  pSuffix = "nothing"; break;
7784         default: /*pSuffix = "tab";*/ break;
7785     }
7786     if ( pSuffix )
7787         m_pSerializer->singleElementNS(XML_w, XML_suff, FSNS(XML_w, XML_val), pSuffix);
7788 
7789     // text
7790     OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel );
7791 
7792     const sal_Unicode *pPrev = rNumberingString.getStr();
7793     const sal_Unicode *pIt = rNumberingString.getStr();
7794     while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() )
7795     {
7796         // convert the level values to %NUMBER form
7797         // (we don't use pNumLvlPos at all)
7798         // FIXME so far we support the ww8 limit of levels only
7799         if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) )
7800         {
7801             aBuffer.append( pPrev, pIt - pPrev );
7802             aBuffer.append( '%' );
7803             aBuffer.append( sal_Int32( *pIt ) + 1 );
7804 
7805             pPrev = pIt + 1;
7806         }
7807         ++pIt;
7808     }
7809     if ( pPrev < pIt )
7810         aBuffer.append( pPrev, pIt - pPrev );
7811 
7812     // If bullet char is empty, set lvlText as empty
7813     if ( rNumberingString == OUStringChar('\0') && nNumberingType == SVX_NUM_CHAR_SPECIAL )
7814     {
7815         m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), "");
7816     }
7817     else
7818     {
7819         // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that.
7820         OUString aLevelText = aBuffer.makeStringAndClear();
7821         static OUString aZeroWidthSpace(u'\x200B');
7822         if (aLevelText == aZeroWidthSpace)
7823             aLevelText.clear();
7824         m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText);
7825     }
7826 
7827     // bullet
7828     if (nNumberingType == SVX_NUM_BITMAP && pBrush)
7829     {
7830         int nIndex = m_rExport.GetGrfIndex(*pBrush);
7831         if (nIndex != -1)
7832         {
7833             m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId,
7834                     FSNS(XML_w, XML_val), OString::number(nIndex));
7835         }
7836     }
7837 
7838     // justification
7839     const char *pJc;
7840     bool ecmaDialect = ( m_rExport.GetFilter().getVersion() == oox::core::ECMA_DIALECT );
7841     switch ( eAdjust )
7842     {
7843         case SvxAdjust::Center: pJc = "center"; break;
7844         case SvxAdjust::Right:  pJc = !ecmaDialect ? "end" : "right";  break;
7845         default:                pJc = !ecmaDialect ? "start" : "left";   break;
7846     }
7847     m_pSerializer->singleElementNS(XML_w, XML_lvlJc, FSNS(XML_w, XML_val), pJc);
7848 
7849     // indentation
7850     m_pSerializer->startElementNS(XML_w, XML_pPr);
7851     if( nListTabPos >= 0 )
7852     {
7853         m_pSerializer->startElementNS(XML_w, XML_tabs);
7854         m_pSerializer->singleElementNS( XML_w, XML_tab,
7855                 FSNS( XML_w, XML_val ), "num",
7856                 FSNS( XML_w, XML_pos ), OString::number(nListTabPos) );
7857         m_pSerializer->endElementNS( XML_w, XML_tabs );
7858     }
7859 
7860     sal_Int32 nToken = ecmaDialect ? XML_left : XML_start;
7861     sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging;
7862     m_pSerializer->singleElementNS( XML_w, XML_ind,
7863             FSNS( XML_w, nToken ), OString::number(nIndentAt),
7864             FSNS( XML_w, nIndentToken ), OString::number(abs(nFirstLineIndex)) );
7865     m_pSerializer->endElementNS( XML_w, XML_pPr );
7866 
7867     // font
7868     if ( pOutSet )
7869     {
7870         m_pSerializer->startElementNS(XML_w, XML_rPr);
7871 
7872         SfxItemSet aTempSet(*pOutSet);
7873         if ( pFont )
7874         {
7875             GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml
7876             OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
7877             m_pSerializer->singleElementNS( XML_w, XML_rFonts,
7878                     FSNS( XML_w, XML_ascii ), aFamilyName,
7879                     FSNS( XML_w, XML_hAnsi ), aFamilyName,
7880                     FSNS( XML_w, XML_cs ), aFamilyName,
7881                     FSNS( XML_w, XML_hint ), "default" );
7882             aTempSet.ClearItem(RES_CHRATR_FONT);
7883             aTempSet.ClearItem(RES_CHRATR_CTL_FONT);
7884         }
7885         m_rExport.OutputItemSet(aTempSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF);
7886 
7887         WriteCollectedRunProperties();
7888 
7889         m_pSerializer->endElementNS( XML_w, XML_rPr );
7890     }
7891 
7892     // TODO anything to do about nListTabPos?
7893 
7894     m_pSerializer->endElementNS( XML_w, XML_lvl );
7895 }
7896 
7897 void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap )
7898 {
7899     switch ( rCaseMap.GetValue() )
7900     {
7901         case SvxCaseMap::SmallCaps:
7902             m_pSerializer->singleElementNS(XML_w, XML_smallCaps);
7903             break;
7904         case SvxCaseMap::Uppercase:
7905             m_pSerializer->singleElementNS(XML_w, XML_caps);
7906             break;
7907         default: // Something that ooxml does not support
7908             m_pSerializer->singleElementNS(XML_w, XML_smallCaps, FSNS(XML_w, XML_val), "false");
7909             m_pSerializer->singleElementNS(XML_w, XML_caps, FSNS(XML_w, XML_val), "false");
7910             break;
7911     }
7912 }
7913 
7914 void DocxAttributeOutput::CharColor( const SvxColorItem& rColor )
7915 {
7916     const Color aColor( rColor.GetValue() );
7917     OString aColorString = msfilter::util::ConvertColor( aColor );
7918 
7919     const char* pExistingValue(nullptr);
7920     if (m_pColorAttrList.is() && m_pColorAttrList->getAsChar(FSNS(XML_w, XML_val), pExistingValue))
7921     {
7922         assert(aColorString.equalsL(pExistingValue, rtl_str_getLength(pExistingValue)));
7923         return;
7924     }
7925 
7926     AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_val ), aColorString.getStr() );
7927     m_nCharTransparence = 255 - aColor.GetAlpha();
7928 }
7929 
7930 void DocxAttributeOutput::CharContour( const SvxContourItem& rContour )
7931 {
7932     if ( rContour.GetValue() )
7933         m_pSerializer->singleElementNS(XML_w, XML_outline);
7934     else
7935         m_pSerializer->singleElementNS(XML_w, XML_outline, FSNS(XML_w, XML_val), "false");
7936 }
7937 
7938 void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut )
7939 {
7940     switch ( rCrossedOut.GetStrikeout() )
7941     {
7942         case STRIKEOUT_DOUBLE:
7943             m_pSerializer->singleElementNS(XML_w, XML_dstrike);
7944             break;
7945         case STRIKEOUT_NONE:
7946             m_pSerializer->singleElementNS(XML_w, XML_dstrike, FSNS(XML_w, XML_val), "false");
7947             m_pSerializer->singleElementNS(XML_w, XML_strike, FSNS(XML_w, XML_val), "false");
7948             break;
7949         default:
7950             m_pSerializer->singleElementNS(XML_w, XML_strike);
7951             break;
7952     }
7953 }
7954 
7955 void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
7956 {
7957     OString sIss;
7958     short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
7959 
7960     // Simplify styles to avoid impossible complexity. Import and export as defaults only
7961     if ( m_rExport.m_bStyDef && nEsc )
7962     {
7963         nProp = DFLT_ESC_PROP;
7964         nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB;
7965     }
7966 
7967     if ( !nEsc )
7968     {
7969         sIss = OString( "baseline" );
7970         nEsc = 0;
7971         nProp = 100;
7972     }
7973     else if ( DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100 )
7974     {
7975         if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc )
7976             sIss = OString( "subscript" );
7977         else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc )
7978             sIss = OString( "superscript" );
7979     }
7980     else if ( DFLT_ESC_AUTO_SUPER == nEsc )
7981     {
7982         // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
7983         // The ascent is generally about 80% of the total font height.
7984         // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
7985         nEsc = .8 * (100 - nProp);
7986     }
7987     else if ( DFLT_ESC_AUTO_SUB == nEsc )
7988     {
7989         // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
7990         // The descent is generally about 20% of the total font height.
7991         // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
7992         nEsc = .2 * -(100 - nProp);
7993     }
7994 
7995     if ( !sIss.isEmpty() )
7996         m_pSerializer->singleElementNS(XML_w, XML_vertAlign, FSNS(XML_w, XML_val), sIss);
7997 
7998     if (!(sIss.isEmpty() || sIss.match("baseline")))
7999         return;
8000 
8001     const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE);
8002     float fHeight = rItem.GetHeight();
8003     OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) );
8004     m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos);
8005 
8006     if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_rExport.m_bFontSizeWritten )
8007     {
8008         OString sSize = OString::number( round(( fHeight * nProp ) / 1000) );
8009         m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), sSize);
8010     }
8011 }
8012 
8013 void DocxAttributeOutput::CharFont( const SvxFontItem& rFont)
8014 {
8015     GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml
8016     const OUString& sFontName(rFont.GetFamilyName());
8017     const OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
8018     if (sFontNameUtf8.isEmpty())
8019         return;
8020 
8021     if (m_pFontsAttrList &&
8022         (   m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) ||
8023             m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi ))    )
8024         )
8025     {
8026         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8027         // that all sub runs of the field will have correct font inside.
8028         // For DOCX we should do not add the same font information twice in the same node
8029         return;
8030     }
8031 
8032     AddToAttrList( m_pFontsAttrList, 2,
8033         FSNS( XML_w, XML_ascii ), sFontNameUtf8.getStr(),
8034         FSNS( XML_w, XML_hAnsi ), sFontNameUtf8.getStr() );
8035 }
8036 
8037 void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize)
8038 {
8039     OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 );
8040 
8041     switch ( rFontSize.Which() )
8042     {
8043         case RES_CHRATR_FONTSIZE:
8044         case RES_CHRATR_CJK_FONTSIZE:
8045             m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), fontSize);
8046             break;
8047         case RES_CHRATR_CTL_FONTSIZE:
8048             m_pSerializer->singleElementNS(XML_w, XML_szCs, FSNS(XML_w, XML_val), fontSize);
8049             break;
8050     }
8051 }
8052 
8053 void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning )
8054 {
8055     OString aKerning = OString::number(  rKerning.GetValue() );
8056     m_pSerializer->singleElementNS(XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning);
8057 }
8058 
8059 void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage )
8060 {
8061     OString aLanguageCode( OUStringToOString(
8062                 LanguageTag( rLanguage.GetLanguage()).getBcp47MS(),
8063                 RTL_TEXTENCODING_UTF8));
8064 
8065     switch ( rLanguage.Which() )
8066     {
8067         case RES_CHRATR_LANGUAGE:
8068             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode.getStr() );
8069             break;
8070         case RES_CHRATR_CJK_LANGUAGE:
8071             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode.getStr() );
8072             break;
8073         case RES_CHRATR_CTL_LANGUAGE:
8074             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode.getStr() );
8075             break;
8076     }
8077 }
8078 
8079 void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture )
8080 {
8081     if ( rPosture.GetPosture() != ITALIC_NONE )
8082         m_pSerializer->singleElementNS(XML_w, XML_i);
8083     else
8084         m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8085 }
8086 
8087 void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow )
8088 {
8089     if ( rShadow.GetValue() )
8090         m_pSerializer->singleElementNS(XML_w, XML_shadow);
8091     else
8092         m_pSerializer->singleElementNS(XML_w, XML_shadow, FSNS(XML_w, XML_val), "false");
8093 }
8094 
8095 void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline )
8096 {
8097     const char *pUnderlineValue;
8098 
8099     switch ( rUnderline.GetLineStyle() )
8100     {
8101         case LINESTYLE_SINGLE:         pUnderlineValue = "single";          break;
8102         case LINESTYLE_BOLD:           pUnderlineValue = "thick";           break;
8103         case LINESTYLE_DOUBLE:         pUnderlineValue = "double";          break;
8104         case LINESTYLE_DOTTED:         pUnderlineValue = "dotted";          break;
8105         case LINESTYLE_DASH:           pUnderlineValue = "dash";            break;
8106         case LINESTYLE_DASHDOT:        pUnderlineValue = "dotDash";         break;
8107         case LINESTYLE_DASHDOTDOT:     pUnderlineValue = "dotDotDash";      break;
8108         case LINESTYLE_WAVE:           pUnderlineValue = "wave";            break;
8109         case LINESTYLE_BOLDDOTTED:     pUnderlineValue = "dottedHeavy";     break;
8110         case LINESTYLE_BOLDDASH:       pUnderlineValue = "dashedHeavy";     break;
8111         case LINESTYLE_LONGDASH:       pUnderlineValue = "dashLongHeavy";   break;
8112         case LINESTYLE_BOLDLONGDASH:   pUnderlineValue = "dashLongHeavy";   break;
8113         case LINESTYLE_BOLDDASHDOT:    pUnderlineValue = "dashDotHeavy";    break;
8114         case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break;
8115         case LINESTYLE_BOLDWAVE:       pUnderlineValue = "wavyHeavy";       break;
8116         case LINESTYLE_DOUBLEWAVE:     pUnderlineValue = "wavyDouble";      break;
8117         case LINESTYLE_NONE:           // fall through
8118         default:                       pUnderlineValue = "none";            break;
8119     }
8120 
8121     Color aUnderlineColor = rUnderline.GetColor();
8122     bool  bUnderlineHasColor = !aUnderlineColor.IsTransparent();
8123     if (bUnderlineHasColor)
8124     {
8125         // Underline has a color
8126         m_pSerializer->singleElementNS( XML_w, XML_u,
8127                                         FSNS( XML_w, XML_val ), pUnderlineValue,
8128                                         FSNS( XML_w, XML_color ), msfilter::util::ConvertColor(aUnderlineColor) );
8129     }
8130     else
8131     {
8132         // Underline has no color
8133         m_pSerializer->singleElementNS(XML_w, XML_u, FSNS(XML_w, XML_val), pUnderlineValue);
8134     }
8135 }
8136 
8137 void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight )
8138 {
8139     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8140         m_pSerializer->singleElementNS(XML_w, XML_b);
8141     else
8142         m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8143 }
8144 
8145 void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern )
8146 {
8147     // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-(
8148     // kerning is based on half-point sizes, so 2 enables kerning for fontsize 1pt or higher. (1 is treated as size 12, and 0 is treated as disabled.)
8149     const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 );
8150     m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), sFontSize);
8151 }
8152 
8153 void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
8154 {
8155     if ( rBlink.GetValue() )
8156         m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "blinkBackground");
8157     else
8158         m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "none");
8159 }
8160 
8161 constexpr OUStringLiteral MSWORD_CH_SHADING_FILL = u"FFFFFF"; // The attribute w:fill of w:shd, for MS-Word's character shading,
8162 constexpr OUStringLiteral MSWORD_CH_SHADING_COLOR = u"auto"; // The attribute w:color of w:shd, for MS-Word's character shading,
8163 constexpr OUStringLiteral MSWORD_CH_SHADING_VAL = u"pct15"; // The attribute w:value of w:shd, for MS-Word's character shading,
8164 
8165 void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush )
8166 {
8167     // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX
8168     if (rBrush.GetShadingValue() == ShadingPattern::PCT15)
8169     {
8170         m_pSerializer->singleElementNS( XML_w, XML_shd,
8171             FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL,
8172             FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR,
8173             FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL );
8174     }
8175     else
8176     {
8177         m_pSerializer->singleElementNS( XML_w, XML_shd,
8178             FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor(rBrush.GetColor()),
8179             FSNS( XML_w, XML_val ), "clear" );
8180     }
8181 }
8182 
8183 void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont )
8184 {
8185     if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia)))
8186     {
8187         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8188         // that all sub runs of the field will have correct font inside.
8189         // For DOCX we should do not add the same font information twice in the same node
8190         return;
8191     }
8192 
8193     const OUString& sFontName(rFont.GetFamilyName());
8194     OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
8195     AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), sFontNameUtf8.getStr() );
8196 }
8197 
8198 void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture )
8199 {
8200     if ( rPosture.GetPosture() != ITALIC_NONE )
8201         m_pSerializer->singleElementNS(XML_w, XML_i);
8202     else
8203         m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8204 }
8205 
8206 void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight )
8207 {
8208     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8209         m_pSerializer->singleElementNS(XML_w, XML_b);
8210     else
8211         m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8212 }
8213 
8214 void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont )
8215 {
8216     if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs)))
8217     {
8218         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8219         // that all sub runs of the field will have correct font inside.
8220         // For DOCX we should do not add the same font information twice in the same node
8221         return;
8222     }
8223 
8224     const OUString& sFontName(rFont.GetFamilyName());
8225     OString sFontNameUtf8 = OUStringToOString(sFontName, RTL_TEXTENCODING_UTF8);
8226     AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), sFontNameUtf8.getStr() );
8227 }
8228 
8229 void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture)
8230 {
8231     if ( rPosture.GetPosture() != ITALIC_NONE )
8232         m_pSerializer->singleElementNS(XML_w, XML_iCs);
8233     else
8234         m_pSerializer->singleElementNS(XML_w, XML_iCs, FSNS(XML_w, XML_val), "false");
8235 }
8236 
8237 void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight )
8238 {
8239     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8240         m_pSerializer->singleElementNS(XML_w, XML_bCs);
8241     else
8242         m_pSerializer->singleElementNS(XML_w, XML_bCs, FSNS(XML_w, XML_val), "false");
8243 }
8244 
8245 void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& )
8246 {
8247 }
8248 
8249 void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& )
8250 {
8251 }
8252 
8253 void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate)
8254 {
8255     // Not rotated?
8256     if ( !rRotate.GetValue())
8257         return;
8258 
8259     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" );
8260 
8261     if (rRotate.IsFitToLine())
8262         AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" );
8263 }
8264 
8265 void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark )
8266 {
8267     const char *pEmphasis;
8268     const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark();
8269 
8270     if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove))
8271         pEmphasis = "dot";
8272     else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove))
8273         pEmphasis = "comma";
8274     else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove))
8275         pEmphasis = "circle";
8276     else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow))
8277         pEmphasis = "underDot";
8278     else
8279         pEmphasis = "none";
8280 
8281     m_pSerializer->singleElementNS(XML_w, XML_em, FSNS(XML_w, XML_val), pEmphasis);
8282 }
8283 
8284 void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines )
8285 {
8286     if ( !rTwoLines.GetValue() )
8287         return;
8288 
8289     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" );
8290 
8291     sal_Unicode cStart = rTwoLines.GetStartBracket();
8292     sal_Unicode cEnd = rTwoLines.GetEndBracket();
8293 
8294     if (!cStart && !cEnd)
8295         return;
8296 
8297     OString sBracket;
8298     if ((cStart == '{') || (cEnd == '}'))
8299         sBracket = const_cast<char *>("curly");
8300     else if ((cStart == '<') || (cEnd == '>'))
8301         sBracket = const_cast<char *>("angle");
8302     else if ((cStart == '[') || (cEnd == ']'))
8303         sBracket = const_cast<char *>("square");
8304     else
8305         sBracket = const_cast<char *>("round");
8306     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket.getStr() );
8307 }
8308 
8309 void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth )
8310 {
8311     // Clamp CharScaleWidth to OOXML limits ([1..600])
8312     const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1,
8313         std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) );
8314     m_pSerializer->singleElementNS( XML_w, XML_w,
8315         FSNS( XML_w, XML_val ), OString::number(nScaleWidth) );
8316 }
8317 
8318 void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief )
8319 {
8320     switch ( rRelief.GetValue() )
8321     {
8322         case FontRelief::Embossed:
8323             m_pSerializer->singleElementNS(XML_w, XML_emboss);
8324             break;
8325         case FontRelief::Engraved:
8326             m_pSerializer->singleElementNS(XML_w, XML_imprint);
8327             break;
8328         default:
8329             m_pSerializer->singleElementNS(XML_w, XML_emboss, FSNS(XML_w, XML_val), "false");
8330             m_pSerializer->singleElementNS(XML_w, XML_imprint, FSNS(XML_w, XML_val), "false");
8331             break;
8332     }
8333 }
8334 
8335 void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden )
8336 {
8337     if ( rHidden.GetValue() )
8338         m_pSerializer->singleElementNS(XML_w, XML_vanish);
8339     else
8340         m_pSerializer->singleElementNS(XML_w, XML_vanish, FSNS(XML_w, XML_val), "false");
8341 }
8342 
8343 void DocxAttributeOutput::CharBorder(
8344     const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow )
8345 {
8346     css::table::BorderLine2 rStyleBorder;
8347     const SvxBoxItem* pInherited = nullptr;
8348     if ( GetExport().m_bStyDef && GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8349         pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_CHRATR_BOX);
8350     else if ( m_rExport.m_pChpIter ) // incredibly undocumented, but this is the character-style info, right?
8351     {
8352         if (const SfxPoolItem* pPoolItem = GetExport().m_pChpIter->HasTextItem(RES_CHRATR_BOX))
8353         {
8354             pInherited = &pPoolItem->StaticWhichCast(RES_CHRATR_BOX);
8355         }
8356     }
8357 
8358     if ( pInherited )
8359         rStyleBorder = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
8360 
8361     impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow, &rStyleBorder );
8362 }
8363 
8364 void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight )
8365 {
8366     const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) );
8367     if ( !sColor.isEmpty() )
8368     {
8369         m_pSerializer->singleElementNS(XML_w, XML_highlight, FSNS(XML_w, XML_val), sColor);
8370     }
8371 }
8372 
8373 void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink )
8374 {
8375     OString aStyleId = MSWordStyles::CreateStyleId(rLink.GetINetFormat());
8376     if (!aStyleId.isEmpty() && !aStyleId.equalsIgnoreAsciiCase("DefaultStyle"))
8377         m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8378 }
8379 
8380 void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat )
8381 {
8382     OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat())));
8383 
8384     m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8385 }
8386 
8387 void DocxAttributeOutput::RefField( const SwField&  rField, const OUString& rRef )
8388 {
8389     SwFieldIds nType = rField.GetTyp( )->Which( );
8390     if ( nType == SwFieldIds::GetExp )
8391     {
8392         OUString sCmd = FieldString( ww::eREF ) +
8393             "\"" + rRef + "\" ";
8394 
8395         m_rExport.OutputField( &rField, ww::eREF, sCmd );
8396     }
8397 
8398     // There is nothing to do here for the set fields
8399 }
8400 
8401 void DocxAttributeOutput::HiddenField(const SwField& rField)
8402 {
8403     auto eSubType = static_cast<SwFieldTypesEnum>(rField.GetSubType());
8404     if (eSubType == SwFieldTypesEnum::ConditionalText)
8405     {
8406         OUString aCond = rField.GetPar1();
8407         OUString aTrueFalse = rField.GetPar2();
8408         sal_Int32 nPos = aTrueFalse.indexOf('|');
8409         OUString aTrue;
8410         OUString aFalse;
8411         if (nPos == -1)
8412         {
8413             aTrue = aTrueFalse;
8414         }
8415         else
8416         {
8417             aTrue = aTrueFalse.subView(0, nPos);
8418             aFalse = aTrueFalse.subView(nPos + 1);
8419         }
8420         OUString aCmd = FieldString(ww::eIF) + aCond + " \"" + aTrue + "\" \"" + aFalse + "\"";
8421         m_rExport.OutputField(&rField, ww::eIF, aCmd);
8422         return;
8423     }
8424 
8425     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" );
8426 }
8427 
8428 void DocxAttributeOutput::PostitField( const SwField* pField )
8429 {
8430     assert( dynamic_cast< const SwPostItField* >( pField ));
8431     const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
8432     OString aName = OUStringToOString(pPostItField->GetName(), RTL_TEXTENCODING_UTF8);
8433     sal_Int32 nId = 0;
8434     std::map< OString, sal_Int32 >::iterator it = m_rOpenedAnnotationMarksIds.find(aName);
8435     if (it != m_rOpenedAnnotationMarksIds.end())
8436         // If the postit field has an annotation mark associated, we already have an id.
8437         nId = it->second;
8438     else
8439         // Otherwise get a new one.
8440         nId = m_nNextAnnotationMarkId++;
8441     m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId });
8442 }
8443 
8444 void DocxAttributeOutput::WritePostitFieldReference()
8445 {
8446     while( m_postitFieldsMaxId < m_postitFields.size())
8447     {
8448         OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id);
8449 
8450         // In case this file is inside annotation marks, we want to write the
8451         // comment reference after the annotation mark is closed, not here.
8452         OString idname = OUStringToOString(m_postitFields[m_postitFieldsMaxId].first->GetName(), RTL_TEXTENCODING_UTF8);
8453         std::map< OString, sal_Int32 >::iterator it = m_rOpenedAnnotationMarksIds.find( idname );
8454         if ( it == m_rOpenedAnnotationMarksIds.end(  ) )
8455             m_pSerializer->singleElementNS(XML_w, XML_commentReference, FSNS(XML_w, XML_id), idstr);
8456         ++m_postitFieldsMaxId;
8457     }
8458 }
8459 
8460 DocxAttributeOutput::hasResolved DocxAttributeOutput::WritePostitFields()
8461 {
8462     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
8463         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
8464 
8465     hasResolved eResult = hasResolved::no;
8466     for (auto& [f, data] : m_postitFields)
8467     {
8468         OString idstr = OString::number(data.id);
8469         m_pSerializer->startElementNS( XML_w, XML_comment, FSNS( XML_w, XML_id ), idstr,
8470             FSNS( XML_w, XML_author ), bRemovePersonalInfo
8471                      ? "Author" + OUString::number( GetExport().GetInfoID(f->GetPar1()) )
8472                      : f->GetPar1(),
8473             FSNS( XML_w, XML_date ), DateTimeToOString( bRemovePersonalInfo
8474                      ? util::DateTime() // "no date" time
8475                      : f->GetDateTime() ),
8476             FSNS( XML_w, XML_initials ), bRemovePersonalInfo
8477                      ? OUString::number( GetExport().GetInfoID(f->GetInitials()) )
8478                      : f->GetInitials() );
8479 
8480         const bool bNeedParaId = f->GetResolved();
8481         if (bNeedParaId)
8482             eResult = hasResolved::yes;
8483 
8484         if (f->GetTextObject() != nullptr)
8485         {
8486             // richtext
8487             data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId);
8488         }
8489         else
8490         {
8491             // just plain text - eg. when the field was created via the
8492             // .uno:InsertAnnotation API
8493             std::optional<OUString> aParaId;
8494             if (bNeedParaId)
8495             {
8496                 data.lastParaId = m_nNextParaId++;
8497                 aParaId = NumberToHexBinary(data.lastParaId);
8498             }
8499             m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
8500             m_pSerializer->startElementNS(XML_w, XML_r);
8501             RunText(f->GetText());
8502             m_pSerializer->endElementNS(XML_w, XML_r);
8503             m_pSerializer->endElementNS(XML_w, XML_p);
8504         }
8505 
8506         m_pSerializer->endElementNS( XML_w, XML_comment );
8507     }
8508     return eResult;
8509 }
8510 
8511 void DocxAttributeOutput::WritePostItFieldsResolved()
8512 {
8513     for (auto& [f, data] : m_postitFields)
8514     {
8515         if (!f->GetResolved())
8516             continue;
8517         OUString idstr = NumberToHexBinary(data.lastParaId);
8518         m_pSerializer->singleElementNS(XML_w15, XML_commentEx, FSNS(XML_w15, XML_paraId), idstr,
8519                                        FSNS(XML_w15, XML_done), "1");
8520     }
8521 }
8522 
8523 bool DocxAttributeOutput::DropdownField( const SwField* pField )
8524 {
8525     ww::eField eType = ww::eFORMDROPDOWN;
8526     OUString sCmd = FieldString( eType  );
8527     GetExport( ).OutputField( pField, eType, sCmd );
8528 
8529     return false;
8530 }
8531 
8532 bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
8533 {
8534     assert( pendingPlaceholder == nullptr );
8535     pendingPlaceholder = pField;
8536     return false; // do not expand
8537 }
8538 
8539 void DocxAttributeOutput::WritePendingPlaceholder()
8540 {
8541     if( pendingPlaceholder == nullptr )
8542         return;
8543     const SwField* pField = pendingPlaceholder;
8544     pendingPlaceholder = nullptr;
8545     m_pSerializer->startElementNS(XML_w, XML_sdt);
8546     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
8547     if( !pField->GetPar2().isEmpty())
8548         m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), pField->GetPar2());
8549     m_pSerializer->singleElementNS(XML_w, XML_temporary);
8550     m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
8551     m_pSerializer->singleElementNS(XML_w, XML_text);
8552     m_pSerializer->endElementNS( XML_w, XML_sdtPr );
8553     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
8554     m_pSerializer->startElementNS(XML_w, XML_r);
8555     RunText( pField->GetPar1());
8556     m_pSerializer->endElementNS( XML_w, XML_r );
8557     m_pSerializer->endElementNS( XML_w, XML_sdtContent );
8558     m_pSerializer->endElementNS( XML_w, XML_sdt );
8559 }
8560 
8561 void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd )
8562 {
8563     // field bookmarks are handled in the EndRun method
8564     GetExport().OutputField(&rField, eType, rCmd );
8565 }
8566 
8567 void DocxAttributeOutput::WriteExpand( const SwField* pField )
8568 {
8569     // Will be written in the next End Run
8570     m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() );
8571 }
8572 
8573 void DocxAttributeOutput::WriteField_Impl(const SwField *const pField,
8574     ww::eField const eType, const OUString& rFieldCmd, FieldFlags const nMode,
8575     OUString const*const pBookmarkName)
8576 {
8577     if (m_bPreventDoubleFieldsHandling)
8578         return;
8579 
8580     struct FieldInfos infos;
8581     if (pField)
8582         infos.pField = pField->CopyField();
8583     infos.sCmd = rFieldCmd;
8584     infos.eType = eType;
8585     infos.bClose = bool(FieldFlags::Close & nMode);
8586     infos.bSep = bool(FieldFlags::CmdEnd & nMode);
8587     infos.bOpen = bool(FieldFlags::Start & nMode);
8588     m_Fields.push_back( infos );
8589 
8590     if (pBookmarkName)
8591     {
8592         m_sFieldBkm = *pBookmarkName;
8593     }
8594 
8595     if ( !pField )
8596         return;
8597 
8598     SwFieldIds nType = pField->GetTyp( )->Which( );
8599     sal_uInt16 nSubType = pField->GetSubType();
8600 
8601     // TODO Any other field types here ?
8602     if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) )
8603     {
8604         const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField );
8605         m_sFieldBkm = pSet->GetPar1( );
8606     }
8607     else if ( nType == SwFieldIds::Dropdown )
8608     {
8609         const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField );
8610         m_sFieldBkm = pDropDown->GetName( );
8611     }
8612 }
8613 
8614 void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark )
8615 {
8616     if ( !m_Fields.empty() )
8617         m_Fields.begin()->pFieldmark = &rFieldmark;
8618 }
8619 
8620 void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData )
8621 {
8622     for ( const OUString & name : rStarts )
8623     {
8624         if (name.startsWith("permission-for-group:") ||
8625             name.startsWith("permission-for-user:"))
8626         {
8627             m_rPermissionsStart.push_back(name);
8628         }
8629         else
8630         {
8631             m_rBookmarksStart.push_back(name);
8632             m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData);
8633         }
8634     }
8635     rStarts.clear();
8636 
8637     for ( const OUString & name : rEnds )
8638     {
8639         if (name.startsWith("permission-for-group:") ||
8640             name.startsWith("permission-for-user:"))
8641         {
8642             m_rPermissionsEnd.push_back(name);
8643         }
8644         else
8645         {
8646             m_rBookmarksEnd.push_back(name);
8647         }
8648     }
8649     rEnds.clear();
8650 }
8651 
8652 void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
8653 {
8654     for ( const OUString & name : rStarts )
8655     {
8656         if (name.startsWith("permission-for-group:") ||
8657             name.startsWith("permission-for-user:"))
8658         {
8659             m_rPermissionsStart.push_back(name);
8660         }
8661         else
8662         {
8663             m_rFinalBookmarksStart.push_back(name);
8664         }
8665     }
8666     rStarts.clear();
8667 
8668     for ( const OUString & name : rEnds )
8669     {
8670         if (name.startsWith("permission-for-group:") ||
8671             name.startsWith("permission-for-user:"))
8672         {
8673             m_rPermissionsEnd.push_back(name);
8674         }
8675         else
8676         {
8677             m_rFinalBookmarksEnd.push_back(name);
8678         }
8679     }
8680     rEnds.clear();
8681 }
8682 
8683 void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
8684         std::vector< OUString >& rEnds )
8685 {
8686     for ( const auto & rAnnotationName : rStarts )
8687     {
8688         OString rName = OUStringToOString(rAnnotationName, RTL_TEXTENCODING_UTF8 ).getStr( );
8689         m_rAnnotationMarksStart.push_back( rName );
8690     }
8691     rStarts.clear();
8692 
8693     for ( const auto & rAnnotationName : rEnds )
8694     {
8695         OString rName = OUStringToOString( rAnnotationName, RTL_TEXTENCODING_UTF8 ).getStr( );
8696         m_rAnnotationMarksEnd.push_back( rName );
8697     }
8698     rEnds.clear();
8699 }
8700 
8701 void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote )
8702 {
8703     const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()?
8704         m_rExport.m_rDoc.GetEndNoteInfo(): m_rExport.m_rDoc.GetFootnoteInfo();
8705 
8706     // footnote/endnote run properties
8707     const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( m_rExport.m_rDoc );
8708 
8709     OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
8710 
8711     m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8712 
8713     // remember the footnote/endnote to
8714     // 1) write the footnoteReference/endnoteReference in EndRunProperties()
8715     // 2) be able to dump them all to footnotes.xml/endnotes.xml
8716     if ( !rFootnote.IsEndNote() && m_rExport.m_rDoc.GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER )
8717         m_pFootnotesList->add( rFootnote );
8718     else
8719         m_pEndnotesList->add( rFootnote );
8720 }
8721 
8722 void DocxAttributeOutput::FootnoteEndnoteReference()
8723 {
8724     sal_Int32 nId;
8725     const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId );
8726     sal_Int32 nToken = XML_footnoteReference;
8727 
8728     // both cannot be set at the same time - if they are, it's a bug
8729     if ( !pFootnote )
8730     {
8731         pFootnote = m_pEndnotesList->getCurrent( nId );
8732         nToken = XML_endnoteReference;
8733     }
8734 
8735     if ( !pFootnote )
8736         return;
8737 
8738     // write it
8739     if ( pFootnote->GetNumStr().isEmpty() )
8740     {
8741         // autonumbered
8742         m_pSerializer->singleElementNS(XML_w, nToken, FSNS(XML_w, XML_id), OString::number(nId));
8743     }
8744     else
8745     {
8746         // not autonumbered
8747         m_pSerializer->singleElementNS( XML_w, nToken,
8748                 FSNS( XML_w, XML_customMarkFollows ), "1",
8749                 FSNS( XML_w, XML_id ), OString::number(nId) );
8750 
8751         RunText( pFootnote->GetNumStr() );
8752     }
8753 }
8754 
8755 static void WriteFootnoteSeparatorHeight(
8756     ::sax_fastparser::FSHelperPtr const& pSerializer, SwTwips const nHeight)
8757 {
8758     // try to get the height by setting font size of the paragraph
8759     if (nHeight != 0)
8760     {
8761         pSerializer->startElementNS(XML_w, XML_pPr);
8762         pSerializer->startElementNS(XML_w, XML_rPr);
8763         pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val),
8764             OString::number((nHeight + 5) / 10));
8765         pSerializer->endElementNS(XML_w, XML_rPr);
8766         pSerializer->endElementNS(XML_w, XML_pPr);
8767     }
8768 }
8769 
8770 void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes )
8771 {
8772     const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector();
8773 
8774     sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes;
8775     sal_Int32 nItem = bFootnotes? XML_footnote:  XML_endnote;
8776 
8777     m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() );
8778 
8779     sal_Int32 nIndex = 0;
8780 
8781     // separator
8782     // note: can only be defined for the whole document, not per section
8783     m_pSerializer->startElementNS( XML_w, nItem,
8784             FSNS( XML_w, XML_id ), OString::number(nIndex++),
8785             FSNS( XML_w, XML_type ), "separator" );
8786     m_pSerializer->startElementNS(XML_w, XML_p);
8787 
8788     bool bSeparator = true;
8789     SwTwips nHeight(0);
8790     if (bFootnotes)
8791     {
8792         const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_rDoc.GetPageDesc(0).GetFootnoteInfo();
8793         // Request separator only if both width and thickness are non-zero.
8794         bSeparator = rFootnoteInfo.GetLineStyle() != SvxBorderLineStyle::NONE
8795                   && rFootnoteInfo.GetLineWidth() > 0
8796                   && double(rFootnoteInfo.GetWidth()) > 0;
8797         nHeight = sw::FootnoteSeparatorHeight(rFootnoteInfo);
8798     }
8799 
8800     WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8801 
8802     m_pSerializer->startElementNS(XML_w, XML_r);
8803     if (bSeparator)
8804         m_pSerializer->singleElementNS(XML_w, XML_separator);
8805     m_pSerializer->endElementNS( XML_w, XML_r );
8806     m_pSerializer->endElementNS( XML_w, XML_p );
8807     m_pSerializer->endElementNS( XML_w, nItem );
8808 
8809     // separator
8810     m_pSerializer->startElementNS( XML_w, nItem,
8811             FSNS( XML_w, XML_id ), OString::number(nIndex++),
8812             FSNS( XML_w, XML_type ), "continuationSeparator" );
8813     m_pSerializer->startElementNS(XML_w, XML_p);
8814 
8815     WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8816 
8817     m_pSerializer->startElementNS(XML_w, XML_r);
8818     if (bSeparator)
8819     {
8820         m_pSerializer->singleElementNS(XML_w, XML_continuationSeparator);
8821     }
8822     m_pSerializer->endElementNS( XML_w, XML_r );
8823     m_pSerializer->endElementNS( XML_w, XML_p );
8824     m_pSerializer->endElementNS( XML_w, nItem );
8825 
8826     // if new special ones are added, update also WriteFootnoteEndnotePr()
8827 
8828     // footnotes/endnotes themselves
8829     for ( const auto& rpItem : rVector )
8830     {
8831         m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef;
8832         m_footnoteCustomLabel = rpItem->GetNumStr();
8833 
8834         m_pSerializer->startElementNS(XML_w, nItem, FSNS(XML_w, XML_id), OString::number(nIndex));
8835 
8836         const SwNodeIndex* pIndex = rpItem->GetTextFootnote()->GetStartNode();
8837         m_rExport.WriteSpecialText( pIndex->GetIndex() + 1,
8838                 pIndex->GetNode().EndOfSectionIndex(),
8839                 bFootnotes? TXT_FTN: TXT_EDN );
8840 
8841         m_pSerializer->endElementNS( XML_w, nItem );
8842         ++nIndex;
8843     }
8844 
8845     m_pSerializer->endElementNS( XML_w, nBody );
8846 
8847 }
8848 
8849 void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag,
8850     const SwEndNoteInfo& info, int listtag )
8851 {
8852     fs->startElementNS(XML_w, tag);
8853     OString aCustomFormat;
8854     OString fmt = lcl_ConvertNumberingType(info.m_aFormat.GetNumberingType(), nullptr, aCustomFormat);
8855     if (!fmt.isEmpty() && aCustomFormat.isEmpty())
8856         fs->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), fmt);
8857     if( info.m_nFootnoteOffset != 0 )
8858         fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ),
8859             OString::number(info.m_nFootnoteOffset + 1) );
8860 
8861     const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info);
8862     if( pFootnoteInfo )
8863     {
8864         switch( pFootnoteInfo->m_eNum )
8865         {
8866             case FTNNUM_PAGE:       fmt = "eachPage"; break;
8867             case FTNNUM_CHAPTER:    fmt = "eachSect"; break;
8868             default:                fmt.clear();      break;
8869         }
8870         if (!fmt.isEmpty())
8871             fs->singleElementNS(XML_w, XML_numRestart, FSNS(XML_w, XML_val), fmt);
8872     }
8873 
8874     if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list
8875     { // there are currently only two hardcoded ones ( see FootnotesEndnotes())
8876         fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "0");
8877         fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "1");
8878     }
8879     fs->endElementNS( XML_w, tag );
8880 }
8881 
8882 void DocxAttributeOutput::SectFootnoteEndnotePr()
8883 {
8884     if( HasFootnotes())
8885         WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_rDoc.GetFootnoteInfo(), 0 );
8886     if( HasEndnotes())
8887         WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_rDoc.GetEndNoteInfo(), 0 );
8888 }
8889 
8890 void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti )
8891 {
8892     if ( nSpace < 0 )
8893     {
8894         AddToAttrList( m_pParagraphSpacingAttrList, 2,
8895                 FSNS( XML_w, XML_lineRule ), "exact",
8896                 FSNS( XML_w, XML_line ), OString::number( -nSpace ).getStr() );
8897     }
8898     else if( nSpace > 0 && nMulti )
8899     {
8900         AddToAttrList( m_pParagraphSpacingAttrList, 2,
8901                 FSNS( XML_w, XML_lineRule ), "auto",
8902                 FSNS( XML_w, XML_line ), OString::number( nSpace ).getStr() );
8903     }
8904     else
8905     {
8906         AddToAttrList( m_pParagraphSpacingAttrList, 2,
8907                 FSNS( XML_w, XML_lineRule ), "atLeast",
8908                 FSNS( XML_w, XML_line ), OString::number( nSpace ).getStr() );
8909     }
8910 }
8911 
8912 void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust )
8913 {
8914     const char *pAdjustString;
8915 
8916     bool bEcma = GetExport().GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;
8917 
8918     const SfxItemSet* pItems = GetExport().GetCurItemSet();
8919     const SvxFrameDirectionItem* rFrameDir = pItems?
8920         pItems->GetItem( RES_FRAMEDIR ) : nullptr;
8921 
8922     SvxFrameDirection nDir = SvxFrameDirection::Environment;
8923     if( rFrameDir != nullptr )
8924         nDir = rFrameDir->GetValue();
8925     if ( nDir == SvxFrameDirection::Environment )
8926         nDir = GetExport( ).GetDefaultFrameDirection( );
8927     bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB );
8928 
8929     switch ( rAdjust.GetAdjust() )
8930     {
8931         case SvxAdjust::Left:
8932             if ( bEcma )
8933             {
8934                 if ( bRtl )
8935                     pAdjustString = "right";
8936                 else
8937                     pAdjustString = "left";
8938             }
8939             else if ( bRtl )
8940                 pAdjustString = "end";
8941             else
8942                 pAdjustString = "start";
8943             break;
8944         case SvxAdjust::Right:
8945             if ( bEcma )
8946             {
8947                 if ( bRtl )
8948                     pAdjustString = "left";
8949                 else
8950                     pAdjustString = "right";
8951             }
8952             else if ( bRtl )
8953                 pAdjustString = "start";
8954             else
8955                 pAdjustString = "end";
8956             break;
8957         case SvxAdjust::BlockLine:
8958         case SvxAdjust::Block:
8959             if (rAdjust.GetLastBlock() == SvxAdjust::Block)
8960                 pAdjustString = "distribute";
8961             else
8962                 pAdjustString = "both";
8963             break;
8964         case SvxAdjust::Center:
8965             pAdjustString = "center";
8966             break;
8967         default:
8968             return; // not supported attribute
8969     }
8970     m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pAdjustString);
8971 }
8972 
8973 void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit )
8974 {
8975     if (rSplit.GetValue())
8976         m_pSerializer->singleElementNS(XML_w, XML_keepLines, FSNS(XML_w, XML_val), "false");
8977     else
8978         m_pSerializer->singleElementNS(XML_w, XML_keepLines);
8979 }
8980 
8981 void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows )
8982 {
8983     if (rWidows.GetValue())
8984         m_pSerializer->singleElementNS(XML_w, XML_widowControl);
8985     else
8986         m_pSerializer->singleElementNS(XML_w, XML_widowControl, FSNS(XML_w, XML_val), "false");
8987 }
8988 
8989 static void impl_WriteTabElement( FSHelperPtr const & pSerializer,
8990                                   const SvxTabStop& rTab, tools::Long tabsOffset )
8991 {
8992     rtl::Reference<FastAttributeList> pTabElementAttrList = FastSerializerHelper::createAttrList();
8993 
8994     switch (rTab.GetAdjustment())
8995     {
8996     case SvxTabAdjust::Right:
8997         pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "right" ) );
8998         break;
8999     case SvxTabAdjust::Decimal:
9000         pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "decimal" ) );
9001         break;
9002     case SvxTabAdjust::Center:
9003         pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "center" ) );
9004         break;
9005     case SvxTabAdjust::Default:
9006     case SvxTabAdjust::Left:
9007     default:
9008         pTabElementAttrList->add( FSNS( XML_w, XML_val ), OString( "left" ) );
9009         break;
9010     }
9011 
9012     // Write position according to used offset of the whole paragraph.
9013     // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
9014     // But in ODT, zero position could be page margins or paragraph indent according to used settings.
9015     // This is handled outside of this method and provided for us in tabsOffset parameter.
9016     pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos() + tabsOffset ) );
9017 
9018     sal_Unicode cFillChar = rTab.GetFill();
9019 
9020     if ('.' == cFillChar )
9021         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "dot" ) );
9022     else if ( '-' == cFillChar )
9023         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "hyphen" ) );
9024     else if ( u'\x00B7' == cFillChar ) // middle dot
9025         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "middleDot" ) );
9026     else if ( '_' == cFillChar )
9027         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "underscore" ) );
9028     else
9029         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), OString( "none" ) );
9030 
9031     pSerializer->singleElementNS(XML_w, XML_tab, pTabElementAttrList);
9032 }
9033 
9034 void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop )
9035 {
9036     const SvxTabStopItem* pInheritedTabs = nullptr;
9037     if ( GetExport().m_pStyAttr )
9038         pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
9039     else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
9040         pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
9041     const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0;
9042     const sal_uInt16 nCount = rTabStop.Count();
9043 
9044     // <w:tabs> must contain at least one <w:tab>, so don't write it empty
9045     if ( !nCount && !nInheritedTabCount )
9046         return;
9047     if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default )
9048     {
9049         GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos());
9050         return;
9051     }
9052 
9053     // do not output inherited tabs twice (inside styles and inside inline properties)
9054     if ( nCount == nInheritedTabCount && nCount > 0 )
9055     {
9056         if ( *pInheritedTabs == rTabStop )
9057             return;
9058     }
9059 
9060     m_pSerializer->startElementNS(XML_w, XML_tabs);
9061 
9062     // Get offset for tabs
9063     // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
9064     // But in ODT, zero position could be page margins or paragraph indent according to used settings.
9065     tools::Long tabsOffset = 0;
9066     if (m_rExport.m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT))
9067         tabsOffset = m_rExport.GetItem(RES_LR_SPACE).GetTextLeft();
9068 
9069     // clear unused inherited tabs - otherwise the style will add them back in
9070     sal_Int32 nCurrTab = 0;
9071     for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i )
9072     {
9073         while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) )
9074             ++nCurrTab;
9075 
9076         if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] )
9077         {
9078             m_pSerializer->singleElementNS( XML_w, XML_tab,
9079                 FSNS( XML_w, XML_val ), "clear",
9080                 FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) );
9081         }
9082     }
9083 
9084     for (sal_uInt16 i = 0; i < nCount; i++ )
9085     {
9086         if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default )
9087             impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset );
9088         else
9089             GetExport().setDefaultTabStop( rTabStop[i].GetTabPos());
9090     }
9091 
9092     m_pSerializer->endElementNS( XML_w, XML_tabs );
9093 }
9094 
9095 void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone )
9096 {
9097     m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens,
9098             FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ) );
9099 }
9100 
9101 void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId )
9102 {
9103     if ( USHRT_MAX == nNumId )
9104         return;
9105 
9106     // LibreOffice is not very flexible with "Outline Numbering" (aka "Outline" numbering style).
9107     // Only ONE numbering rule ("Outline") can be associated with a style-assigned-listLevel,
9108     // and no other style is able to inherit these numId/nLvl settings - only text nodes can.
9109     // So listLevel only exists in paragraph properties EXCEPT for up to ten styles that have been
9110     // assigned to one of these special Chapter Numbering listlevels (by default Heading 1-10).
9111     const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0;
9112     const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr;
9113     const SwTextFormatColl* pColl = pTextNd ? pTextNd->GetTextColl() : nullptr;
9114     // Do not duplicate numbering that is inherited from the (Chapter numbering) style
9115     // (since on import we duplicate style numbering/listlevel to the paragraph).
9116     if (pColl && pColl->IsAssignedToListLevelOfOutlineStyle()
9117         && nLvl == pColl->GetAssignedOutlineStyleLevel() && pRule && pRule->IsOutlineRule())
9118     {
9119         // By definition of how LO is implemented, assignToListLevel is only possible
9120         // when the style is also using OutlineRule for numbering. Adjust logic if that changes.
9121         assert(pRule->GetName() == pColl->GetNumRule(true).GetValue());
9122         return;
9123     }
9124 
9125     m_pSerializer->startElementNS(XML_w, XML_numPr);
9126     m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), OString::number(nLvl));
9127     m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), OString::number(nNumId));
9128     m_pSerializer->endElementNS(XML_w, XML_numPr);
9129 }
9130 
9131 void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace )
9132 {
9133     m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE,
9134            FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ) );
9135 }
9136 
9137 void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem )
9138 {
9139     m_pSerializer->singleElementNS( XML_w, XML_overflowPunct,
9140            FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9141 }
9142 
9143 void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem )
9144 {
9145     m_pSerializer->singleElementNS( XML_w, XML_kinsoku,
9146            FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9147 }
9148 
9149 void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign )
9150 {
9151     const char *pAlignString;
9152 
9153     switch ( rAlign.GetValue() )
9154     {
9155         case SvxParaVertAlignItem::Align::Baseline:
9156             pAlignString = "baseline";
9157             break;
9158         case SvxParaVertAlignItem::Align::Top:
9159             pAlignString = "top";
9160             break;
9161         case SvxParaVertAlignItem::Align::Center:
9162             pAlignString = "center";
9163             break;
9164         case SvxParaVertAlignItem::Align::Bottom:
9165             pAlignString = "bottom";
9166             break;
9167         case SvxParaVertAlignItem::Align::Automatic:
9168             pAlignString = "auto";
9169             break;
9170         default:
9171             return; // not supported attribute
9172     }
9173     m_pSerializer->singleElementNS(XML_w, XML_textAlignment, FSNS(XML_w, XML_val), pAlignString);
9174 }
9175 
9176 void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid )
9177 {
9178     m_pSerializer->singleElementNS( XML_w, XML_snapToGrid,
9179             FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ) );
9180 }
9181 
9182 void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize )
9183 {
9184     if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize())
9185     {
9186         const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize();
9187         m_rExport.SdrExporter().getTextFrameStyle().append(";width:" + OString::number(double(pSize->Width()) / 20));
9188         m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:" + OString::number(double(pSize->Height()) / 20) + "pt");
9189     }
9190     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9191     {
9192     }
9193     else if ( m_rExport.m_bOutFlyFrameAttrs )
9194     {
9195         if ( rSize.GetWidth() && rSize.GetWidthSizeType() == SwFrameSize::Fixed )
9196             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9197                     FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ).getStr() );
9198 
9199         if ( rSize.GetHeight() )
9200         {
9201             OString sRule( "exact" );
9202             if ( rSize.GetHeightSizeType() == SwFrameSize::Minimum )
9203                 sRule = OString( "atLeast" );
9204             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
9205                     FSNS( XML_w, XML_hRule ), sRule.getStr(),
9206                     FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ).getStr() );
9207         }
9208     }
9209     else if ( m_rExport.m_bOutPageDescs )
9210     {
9211         rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9212         if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) )
9213             attrList->add( FSNS( XML_w, XML_orient ), "landscape" );
9214 
9215         attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9216         attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9217 
9218         m_pSerializer->singleElementNS( XML_w, XML_pgSz, attrList );
9219     }
9220 }
9221 
9222 void DocxAttributeOutput::FormatPaperBin( const SvxPaperBinItem& )
9223 {
9224     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatPaperBin()" );
9225 }
9226 
9227 void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace )
9228 {
9229     bool bEcma = m_rExport.GetFilter().getVersion( ) == oox::core::ECMA_DIALECT;
9230 
9231     if (m_rExport.SdrExporter().getTextFrameSyntax())
9232     {
9233         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt");
9234         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt");
9235     }
9236     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9237     {
9238     }
9239     else if ( m_rExport.m_bOutFlyFrameAttrs )
9240     {
9241         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hSpace ),
9242                 OString::number(
9243                     ( rLRSpace.GetLeft() + rLRSpace.GetRight() ) / 2 ).getStr() );
9244     }
9245     else if ( m_rExport.m_bOutPageDescs )
9246     {
9247         m_pageMargins.nLeft = 0;
9248         m_pageMargins.nRight = 0;
9249 
9250         const SfxPoolItem* pPoolItem = m_rExport.HasItem(RES_BOX);
9251         const SvxBoxItem* pBoxItem = pPoolItem ? &pPoolItem->StaticWhichCast(RES_BOX) : nullptr;
9252         if (pBoxItem)
9253         {
9254             m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true );
9255             m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true );
9256         }
9257 
9258         m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft());
9259         m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight());
9260         sal_uInt16 nGutter = rLRSpace.GetGutterMargin();
9261 
9262         AddToAttrList( m_pSectionSpacingAttrList, 3,
9263                 FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ).getStr(),
9264                 FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ).getStr(),
9265                 FSNS( XML_w, XML_gutter ), OString::number( nGutter ).getStr() );
9266     }
9267     else
9268     {
9269         SvxLRSpaceItem const* pLRSpace(&rLRSpace);
9270         ::std::optional<SvxLRSpaceItem> oLRSpace;
9271         if (dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) != nullptr)
9272         {
9273             auto pTextNd(static_cast<SwTextNode const*>(GetExport().m_pOutFormatNode));
9274             // WW doesn't have a concept of a pararaph that's in a list but not
9275             // counted in the list - see AttributeOutputBase::ParaNumRule()
9276             // forcing non-existent numId="0" in this case.
9277             // This means WW won't apply the indents from the numbering,
9278             // so try to add them as paragraph properties here.
9279             if (!pTextNd->IsCountedInList())
9280             {
9281                 SfxItemSetFixed<RES_LR_SPACE, RES_LR_SPACE> temp(m_rExport.m_rDoc.GetAttrPool());
9282                 pTextNd->GetParaAttr(temp, 0, 0, false, true, true, nullptr);
9283                 if (auto *const pItem = temp.GetItem(RES_LR_SPACE))
9284                 {
9285                     // but don't use first-line offset from list (should it be 0 or from node?)
9286                     oLRSpace.emplace(*pItem);
9287                     oLRSpace->SetTextFirstLineOffset(pLRSpace->GetTextFirstLineOffset());
9288                     pLRSpace = &*oLRSpace;
9289                 }
9290             }
9291         }
9292         rtl::Reference<FastAttributeList> pLRSpaceAttrList = FastSerializerHelper::createAttrList();
9293         if ((0 != pLRSpace->GetTextLeft()) || (pLRSpace->IsExplicitZeroMarginValLeft()))
9294         {
9295             pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_left : XML_start)), OString::number(pLRSpace->GetTextLeft()) );
9296         }
9297         if ((0 != pLRSpace->GetRight()) || (pLRSpace->IsExplicitZeroMarginValRight()))
9298         {
9299             pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_right : XML_end)), OString::number(pLRSpace->GetRight()) );
9300         }
9301         sal_Int32 const nFirstLineAdjustment = pLRSpace->GetTextFirstLineOffset();
9302         if (nFirstLineAdjustment > 0)
9303             pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) );
9304         else
9305             pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) );
9306         m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList );
9307     }
9308 }
9309 
9310 void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace )
9311 {
9312 
9313     if (m_rExport.SdrExporter().getTextFrameSyntax())
9314     {
9315         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt");
9316         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt");
9317     }
9318     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9319     {
9320     }
9321     else if ( m_rExport.m_bOutFlyFrameAttrs )
9322     {
9323         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ),
9324                 OString::number(
9325                     ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ).getStr() );
9326     }
9327     else if (m_rExport.m_bOutPageDescs )
9328     {
9329         OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" );
9330         if ( !m_rExport.GetCurItemSet() )
9331             return;
9332 
9333         HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() );
9334 
9335         sal_Int32 nHeader = 0;
9336         if ( aDistances.HasHeader() )
9337             nHeader = sal_Int32( aDistances.dyaHdrTop );
9338         else if (m_rExport.m_pFirstPageFormat)
9339         {
9340             HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9341             if (aFirstPageDistances.HasHeader())
9342             {
9343                 // The follow page style has no header, but the first page style has. In Word terms,
9344                 // this means that the header margin of "the" section is coming from the first page
9345                 // style.
9346                 nHeader = sal_Int32(aFirstPageDistances.dyaHdrTop);
9347             }
9348         }
9349 
9350         // Page top
9351         m_pageMargins.nTop = aDistances.dyaTop;
9352 
9353         sal_Int32 nFooter = 0;
9354         if ( aDistances.HasFooter() )
9355             nFooter = sal_Int32( aDistances.dyaHdrBottom );
9356         else if (m_rExport.m_pFirstPageFormat)
9357         {
9358             HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9359             if (aFirstPageDistances.HasFooter())
9360             {
9361                 // The follow page style has no footer, but the first page style has. In Word terms,
9362                 // this means that the footer margin of "the" section is coming from the first page
9363                 // style.
9364                 nFooter = sal_Int32(aFirstPageDistances.dyaHdrBottom);
9365             }
9366         }
9367 
9368         // Page Bottom
9369         m_pageMargins.nBottom = aDistances.dyaBottom;
9370 
9371         AddToAttrList( m_pSectionSpacingAttrList, 4,
9372                 FSNS( XML_w, XML_header ), OString::number( nHeader ).getStr(),
9373                 FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ).getStr(),
9374                 FSNS( XML_w, XML_footer ), OString::number( nFooter ).getStr(),
9375                 FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ).getStr() );
9376     }
9377     else
9378     {
9379         SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() );
9380         // check if before auto spacing was set during import and spacing we get from actual object is same
9381         // that we set in import. If yes just write beforeAutoSpacing tag.
9382         if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper())
9383         {
9384             AddToAttrList( m_pParagraphSpacingAttrList,
9385                     FSNS( XML_w, XML_beforeAutospacing ), "1" );
9386         }
9387         else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1)
9388         {
9389             AddToAttrList( m_pParagraphSpacingAttrList,
9390                     FSNS( XML_w, XML_beforeAutospacing ), "0" );
9391             AddToAttrList( m_pParagraphSpacingAttrList,
9392                     FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ).getStr() );
9393         }
9394         else
9395         {
9396             AddToAttrList( m_pParagraphSpacingAttrList,
9397                     FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ).getStr() );
9398         }
9399         m_bParaBeforeAutoSpacing = false;
9400         // check if after auto spacing was set during import and spacing we get from actual object is same
9401         // that we set in import. If yes just write afterAutoSpacing tag.
9402         if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower())
9403         {
9404             AddToAttrList( m_pParagraphSpacingAttrList,
9405                     FSNS( XML_w, XML_afterAutospacing ), "1" );
9406         }
9407         else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1)
9408         {
9409             AddToAttrList( m_pParagraphSpacingAttrList,
9410                     FSNS( XML_w, XML_afterAutospacing ), "0" );
9411             AddToAttrList( m_pParagraphSpacingAttrList,
9412                                 FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()).getStr() );
9413         }
9414         else
9415         {
9416             AddToAttrList( m_pParagraphSpacingAttrList,
9417                     FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()).getStr() );
9418         }
9419         m_bParaAfterAutoSpacing = false;
9420 
9421         if (rULSpace.GetContext())
9422             m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing);
9423         else
9424         {
9425             // Write out Contextual Spacing = false if it would have inherited a true.
9426             const SvxULSpaceItem* pInherited = nullptr;
9427             if (auto pNd = dynamic_cast<const SwContentNode*>(m_rExport.m_pOutFormatNode)) //paragraph
9428                 pInherited = &static_cast<SwTextFormatColl&>(pNd->GetAnyFormatColl()).GetAttrSet().GetULSpace();
9429             else if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle && m_rExport.m_pCurrentStyle->DerivedFrom()) //style
9430                 pInherited = &m_rExport.m_pCurrentStyle->DerivedFrom()->GetULSpace();
9431 
9432             if (pInherited && pInherited->GetContext())
9433                 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing, FSNS(XML_w, XML_val), "false");
9434         }
9435     }
9436 }
9437 
9438 namespace docx {
9439 
9440 rtl::Reference<FastAttributeList> SurroundToVMLWrap(SwFormatSurround const& rSurround)
9441 {
9442     rtl::Reference<FastAttributeList> pAttrList;
9443     OString sType;
9444     OString sSide;
9445     switch (rSurround.GetSurround())
9446     {
9447         case css::text::WrapTextMode_NONE:
9448             sType = "topAndBottom";
9449             break;
9450         case css::text::WrapTextMode_PARALLEL:
9451             sType = "square";
9452             break;
9453         case css::text::WrapTextMode_DYNAMIC:
9454             sType = "square";
9455             sSide = "largest";
9456             break;
9457         case css::text::WrapTextMode_LEFT:
9458             sType = "square";
9459             sSide = "left";
9460             break;
9461         case css::text::WrapTextMode_RIGHT:
9462             sType = "square";
9463             sSide = "right";
9464             break;
9465         case css::text::WrapTextMode_THROUGH:
9466             /* empty type and side means through */
9467         default:
9468             sType = "none";
9469             break;
9470     }
9471     if (!sType.isEmpty() || !sSide.isEmpty())
9472     {
9473         pAttrList = FastSerializerHelper::createAttrList();
9474         if (!sType.isEmpty())
9475         {
9476             pAttrList->add(XML_type, sType);
9477         }
9478         if (!sSide.isEmpty())
9479         {
9480             pAttrList->add(XML_side, sSide);
9481         }
9482     }
9483     return pAttrList;
9484 }
9485 
9486 } // namespace docx
9487 
9488 void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround )
9489 {
9490     if (m_rExport.SdrExporter().getTextFrameSyntax())
9491     {
9492         rtl::Reference<FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
9493         if (pAttrList)
9494         {
9495             m_rExport.SdrExporter().setFlyWrapAttrList(pAttrList);
9496         }
9497     }
9498     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9499     {
9500     }
9501     else if ( m_rExport.m_bOutFlyFrameAttrs )
9502     {
9503         OString sWrap( "auto" );
9504         switch ( rSurround.GetSurround( ) )
9505         {
9506             case css::text::WrapTextMode_NONE:
9507                 sWrap = OString( "none" );
9508                 break;
9509             case css::text::WrapTextMode_THROUGH:
9510                 sWrap = OString( "through" );
9511                 break;
9512             case css::text::WrapTextMode_DYNAMIC:
9513             case css::text::WrapTextMode_PARALLEL:
9514             case css::text::WrapTextMode_LEFT:
9515             case css::text::WrapTextMode_RIGHT:
9516             default:
9517                 sWrap = OString( "around" );
9518         }
9519 
9520         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap.getStr() );
9521     }
9522 }
9523 
9524 void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert )
9525 {
9526     OString sAlign   = convertToOOXMLVertOrient( rFlyVert.GetVertOrient() );
9527     OString sVAnchor = convertToOOXMLVertOrientRel( rFlyVert.GetRelationOrient() );
9528 
9529     if (m_rExport.SdrExporter().getTextFrameSyntax())
9530     {
9531         m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:" + OString::number(double(rFlyVert.GetPos()) / 20) + "pt");
9532         if ( !sAlign.isEmpty() )
9533             m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:" + sAlign);
9534         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:" + sVAnchor);
9535     }
9536     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9537     {
9538     }
9539     else if ( m_rExport.m_bOutFlyFrameAttrs )
9540     {
9541         if ( !sAlign.isEmpty() )
9542             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign.getStr() );
9543         else
9544             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ),
9545                 OString::number( rFlyVert.GetPos() ).getStr() );
9546         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor.getStr() );
9547     }
9548 }
9549 
9550 void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori )
9551 {
9552     OString sAlign   = convertToOOXMLHoriOrient( rFlyHori.GetHoriOrient(), rFlyHori.IsPosToggle() );
9553     OString sHAnchor = convertToOOXMLHoriOrientRel( rFlyHori.GetRelationOrient() );
9554 
9555     if (m_rExport.SdrExporter().getTextFrameSyntax())
9556     {
9557         m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:" + OString::number(double(rFlyHori.GetPos()) / 20) + "pt");
9558         if ( !sAlign.isEmpty() )
9559             m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:" + sAlign);
9560         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:" + sHAnchor);
9561     }
9562     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9563     {
9564     }
9565     else if ( m_rExport.m_bOutFlyFrameAttrs )
9566     {
9567         if ( !sAlign.isEmpty() )
9568             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign.getStr() );
9569         else
9570             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ),
9571                 OString::number( rFlyHori.GetPos() ).getStr() );
9572         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor.getStr() );
9573     }
9574 }
9575 
9576 void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& )
9577 {
9578     // Fly frames: anchors here aren't matching the anchors in docx
9579 }
9580 
9581 static std::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush)
9582 {
9583     std::optional<sal_Int32> oRet;
9584     sal_Int32 nTransparency = 255 - rBrush.GetColor().GetAlpha();
9585     if (nTransparency)
9586     {
9587         // Convert transparency to percent
9588         sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency);
9589 
9590         // Calculate alpha value
9591         // Consider oox/source/drawingml/color.cxx : getTransparency() function.
9592         sal_Int32 nAlpha = ::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent );
9593         oRet = nAlpha;
9594     }
9595     return oRet;
9596 }
9597 
9598 void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush )
9599 {
9600     const Color aColor = rBrush.GetColor();
9601     OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() );
9602     std::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush);
9603     if (m_rExport.SdrExporter().getTextFrameSyntax())
9604     {
9605         // Handle 'Opacity'
9606         if (oAlpha)
9607         {
9608             // Calculate opacity value
9609             // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function.
9610             double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT;
9611             OUString sOpacity = OUString::number(fOpacity) + "f";
9612 
9613             AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OUStringToOString(sOpacity, RTL_TEXTENCODING_UTF8).getStr() );
9614         }
9615 
9616         sColor = "#" + sColor;
9617         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, sColor.getStr() );
9618     }
9619     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9620     {
9621         bool bImageBackground = false;
9622         const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9623         if (pItem)
9624         {
9625             const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9626             if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP)
9627             {
9628                 bImageBackground = true;
9629             }
9630         }
9631         if (!bImageBackground)
9632         {
9633             m_pSerializer->startElementNS(XML_a, XML_solidFill);
9634             m_pSerializer->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
9635             if (oAlpha)
9636                 m_pSerializer->singleElementNS(XML_a, XML_alpha,
9637                                               XML_val, OString::number(*oAlpha));
9638             m_pSerializer->endElementNS(XML_a, XML_srgbClr);
9639             m_pSerializer->endElementNS(XML_a, XML_solidFill);
9640         }
9641     }
9642     else if ( !m_rExport.m_bOutPageDescs )
9643     {
9644         // compare fill color with the original fill color
9645         OString sOriginalFill = OUStringToOString(
9646                 m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 );
9647 
9648         if ( aColor == COL_AUTO )
9649             sColor = "auto";
9650 
9651         if( !m_pBackgroundAttrList.is() )
9652         {
9653             m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9654             m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9655             m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9656         }
9657         else if ( sOriginalFill != sColor )
9658         {
9659             // fill was modified during edition, theme fill attribute must be dropped
9660             m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9661             m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9662             m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9663         }
9664         m_sOriginalBackgroundColor.clear();
9665     }
9666 }
9667 
9668 void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle )
9669 {
9670     if (!m_bIgnoreNextFill)
9671         m_oFillStyle = rFillStyle.GetValue();
9672     else
9673         m_bIgnoreNextFill = false;
9674 
9675     // Don't round-trip grabbag OriginalBackground if the background has been cleared.
9676     if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE )
9677         m_pBackgroundAttrList.clear();
9678 }
9679 
9680 void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient )
9681 {
9682     if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax())
9683     {
9684         AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient" );
9685 
9686         const XGradient& rGradient = rFillGradient.GetGradientValue();
9687         OString sStartColor = msfilter::util::ConvertColor(rGradient.GetStartColor());
9688         OString sEndColor = msfilter::util::ConvertColor(rGradient.GetEndColor());
9689 
9690         // Calculate the angle that was originally in the imported DOCX file
9691         // (reverse calculate the angle that was converted in the file
9692         //     /oox/source/vml/vmlformatting.cxx :: FillModel::pushToPropMap
9693         // and also in
9694         //     /oox/source/drawingml/fillproperties.cxx :: FillProperties::pushToPropMap
9695         sal_Int32 nReverseAngle = toDegrees(4500_deg10 - rGradient.GetAngle());
9696         nReverseAngle = (270 - nReverseAngle) % 360;
9697         if (nReverseAngle != 0)
9698             AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(),
9699                     XML_angle, OString::number( nReverseAngle ).getStr() );
9700 
9701         OString sColor1 = sStartColor;
9702         OString sColor2 = sEndColor;
9703 
9704         switch (rGradient.GetGradientStyle())
9705         {
9706             case css::awt::GradientStyle_AXIAL:
9707                 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" );
9708                 // If it is an 'axial' gradient - swap the colors
9709                 // (because in the import process they were imported swapped)
9710                 sColor1 = sEndColor;
9711                 sColor2 = sStartColor;
9712                 break;
9713             case css::awt::GradientStyle_LINEAR: break;
9714             case css::awt::GradientStyle_RADIAL: break;
9715             case css::awt::GradientStyle_ELLIPTICAL: break;
9716             case css::awt::GradientStyle_SQUARE: break;
9717             case css::awt::GradientStyle_RECT: break;
9718             default:
9719                 break;
9720         }
9721 
9722         sColor1 = "#" + sColor1;
9723         sColor2 = "#" + sColor2;
9724         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, sColor1.getStr() );
9725         AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, sColor2.getStr() );
9726     }
9727     else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax())
9728     {
9729         SwFrameFormat & rFormat(
9730                 const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat()));
9731         uno::Reference<beans::XPropertySet> const xPropertySet(
9732             SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat),
9733             uno::UNO_QUERY);
9734         m_rDrawingML.SetFS(m_pSerializer);
9735         m_rDrawingML.WriteGradientFill(xPropertySet);
9736     }
9737     m_oFillStyle.reset();
9738 }
9739 
9740 void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox )
9741 {
9742     if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9743     {
9744         // ugh, exporting fill here is quite some hack... this OutputItemSet abstraction is quite leaky
9745         // <a:gradFill> should be before <a:ln>.
9746         const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9747         if (pItem)
9748         {
9749             const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9750             FormatFillStyle(*pFillStyle);
9751             if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP)
9752             {
9753                 const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject();
9754                 if (pSdrObj)
9755                 {
9756                     uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
9757                     uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY );
9758                     m_rDrawingML.SetFS(m_pSerializer);
9759                     m_rDrawingML.WriteBlipFill(xPropertySet, "BackGraphic");
9760                 }
9761             }
9762         }
9763 
9764         pItem = GetExport().HasItem(XATTR_FILLGRADIENT);
9765         if (pItem)
9766         {
9767             const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem);
9768             FormatFillGradient(*pFillGradient);
9769         }
9770         m_bIgnoreNextFill = true;
9771     }
9772     if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax())
9773     {
9774         const SvxBorderLine* pLeft = rBox.GetLeft( );
9775         const SvxBorderLine* pTop = rBox.GetTop( );
9776         const SvxBorderLine* pRight = rBox.GetRight( );
9777         const SvxBorderLine* pBottom = rBox.GetBottom( );
9778 
9779         if (pLeft && pRight && pTop && pBottom &&
9780                 *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom)
9781         {
9782             // Check border style
9783             SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle();
9784             if (eBorderStyle == SvxBorderLineStyle::NONE)
9785             {
9786                 if (m_rExport.SdrExporter().getTextFrameSyntax())
9787                 {
9788                     AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
9789                             XML_stroked, "f", XML_strokeweight, "0pt" );
9790                 }
9791             }
9792             else
9793             {
9794                 OString sColor(msfilter::util::ConvertColor(pTop->GetColor()));
9795                 double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth()));
9796 
9797                 if (m_rExport.SdrExporter().getTextFrameSyntax())
9798                 {
9799                     sColor = "#" + sColor;
9800                     sal_Int32 nWidth = sal_Int32(fConverted / 20);
9801                     OString sWidth = OString::number(nWidth) + "pt";
9802                     AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), 2,
9803                             XML_strokecolor, sColor.getStr(),
9804                             XML_strokeweight, sWidth.getStr() );
9805                     if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type
9806                         AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(),
9807                             XML_dashstyle, "dash" );
9808                 }
9809                 else
9810                     m_rExport.SdrExporter().writeBoxItemLine(rBox);
9811             }
9812         }
9813 
9814         if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9815         {
9816             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT))));
9817             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP))));
9818             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT))));
9819             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM))));
9820             return;
9821         }
9822 
9823         // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values
9824         double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT));
9825         double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP));
9826         double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT));
9827         double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM));
9828 
9829         // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches)
9830         double fDistanceLeftInch = o3tl::convert(fDistanceLeftTwips, o3tl::Length::twip, o3tl::Length::in);
9831         double fDistanceTopInch = o3tl::convert(fDistanceTopTwips, o3tl::Length::twip, o3tl::Length::in);
9832         double fDistanceRightInch = o3tl::convert(fDistanceRightTwips, o3tl::Length::twip, o3tl::Length::in);
9833         double fDistanceBottomInch = o3tl::convert(fDistanceBottomTwips, o3tl::Length::twip, o3tl::Length::in);
9834 
9835         // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order.
9836         // so 'bottom' is checked if it is default and if it is non-default - all the values will be written
9837         // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written
9838         // and so on.
9839         OStringBuffer aInset;
9840         if(!aInset.isEmpty() || fDistanceBottomInch != 0.05)
9841             aInset.insert(0, OStringConcatenation("," + OString::number(fDistanceBottomInch) + "in"));
9842 
9843         if(!aInset.isEmpty() || fDistanceRightInch != 0.1)
9844             aInset.insert(0, OStringConcatenation("," + OString::number(fDistanceRightInch) + "in"));
9845 
9846         if(!aInset.isEmpty() || fDistanceTopInch != 0.05)
9847             aInset.insert(0, OStringConcatenation("," + OString::number(fDistanceTopInch) + "in"));
9848 
9849         if(!aInset.isEmpty() || fDistanceLeftInch != 0.1)
9850             aInset.insert(0, OStringConcatenation(OString::number(fDistanceLeftInch) + "in"));
9851 
9852         if (!aInset.isEmpty())
9853             m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset.makeStringAndClear());
9854 
9855         return;
9856     }
9857 
9858     OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
9859     // Check if there is a shadow item
9860     const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
9861     if ( pItem )
9862     {
9863         const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
9864         aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
9865     }
9866 
9867     if ( m_bOpenedSectPr && !GetWritingHeaderFooter())
9868         return;
9869 
9870     // Not inside a section
9871 
9872     // Open the paragraph's borders tag
9873     m_pSerializer->startElementNS(XML_w, XML_pBdr);
9874 
9875     std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders;
9876     const SvxBoxItem* pInherited = nullptr;
9877     if ( GetExport().m_pStyAttr )
9878         pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX);
9879     else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
9880         pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX);
9881 
9882     if ( pInherited )
9883     {
9884         aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false);
9885         aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false);
9886         aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false);
9887         aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
9888     }
9889 
9890     impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders );
9891 
9892     // Close the paragraph's borders tag
9893     m_pSerializer->endElementNS( XML_w, XML_pBdr );
9894 }
9895 
9896 void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize )
9897 {
9898     // Get the columns attributes
9899     rtl::Reference<FastAttributeList> pColsAttrList = FastSerializerHelper::createAttrList();
9900 
9901     pColsAttrList->add( FSNS( XML_w, XML_num ),
9902             OString::number( nCols ). getStr( ) );
9903 
9904     const char* pEquals = "false";
9905     if ( bEven )
9906     {
9907         sal_uInt16 nWidth = rCol.GetGutterWidth( true );
9908         pColsAttrList->add( FSNS( XML_w, XML_space ),
9909                OString::number( nWidth ).getStr( ) );
9910 
9911         pEquals = "true";
9912     }
9913 
9914     pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals );
9915 
9916     bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj());
9917 
9918     pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) );
9919 
9920     // Write the element
9921     m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList );
9922 
9923     // Write the columns width if non-equals
9924     const SwColumns & rColumns = rCol.GetColumns(  );
9925     if ( !bEven )
9926     {
9927         for ( sal_uInt16 n = 0; n < nCols; ++n )
9928         {
9929             rtl::Reference<FastAttributeList> pColAttrList = FastSerializerHelper::createAttrList();
9930             sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, o3tl::narrowing<sal_uInt16>(nPageSize) );
9931             pColAttrList->add( FSNS( XML_w, XML_w ),
9932                     OString::number( nWidth ).getStr( ) );
9933 
9934             if ( n + 1 != nCols )
9935             {
9936                 sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( );
9937                 pColAttrList->add( FSNS( XML_w, XML_space ),
9938                     OString::number( nSpacing ).getStr( ) );
9939             }
9940 
9941             m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList );
9942         }
9943     }
9944 
9945     m_pSerializer->endElementNS( XML_w, XML_cols );
9946 }
9947 
9948 void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem )
9949 {
9950     m_pSerializer->singleElementNS( XML_w, XML_keepNext,
9951             FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9952 }
9953 
9954 void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid )
9955 {
9956     rtl::Reference<FastAttributeList> pGridAttrList = FastSerializerHelper::createAttrList();
9957 
9958     OString sGridType;
9959     switch ( rGrid.GetGridType( ) )
9960     {
9961         default:
9962         case GRID_NONE:
9963             sGridType = OString( "default" );
9964             break;
9965         case GRID_LINES_ONLY:
9966             sGridType = OString( "lines" );
9967             break;
9968         case GRID_LINES_CHARS:
9969             if ( rGrid.IsSnapToChars( ) )
9970                 sGridType = OString( "snapToChars" );
9971             else
9972                 sGridType = OString( "linesAndChars" );
9973             break;
9974     }
9975     pGridAttrList->add(FSNS(XML_w, XML_type), sGridType);
9976 
9977     sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight();
9978     pGridAttrList->add( FSNS( XML_w, XML_linePitch ),
9979             OString::number( nHeight ).getStr( ) );
9980 
9981     pGridAttrList->add( FSNS( XML_w, XML_charSpace ),
9982             OString::number( GridCharacterPitch( rGrid ) ).getStr( ) );
9983 
9984     m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList );
9985 }
9986 
9987 void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering )
9988 {
9989     if ( !rNumbering.IsCount( ) )
9990         m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers);
9991 }
9992 
9993 void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection )
9994 {
9995     OString sTextFlow;
9996     bool bBiDi = false;
9997     SvxFrameDirection nDir = rDirection.GetValue();
9998 
9999     if ( nDir == SvxFrameDirection::Environment )
10000         nDir = GetExport( ).GetDefaultFrameDirection( );
10001 
10002     switch ( nDir )
10003     {
10004         default:
10005         case SvxFrameDirection::Horizontal_LR_TB:
10006             sTextFlow = OString( "lrTb" );
10007             break;
10008         case SvxFrameDirection::Horizontal_RL_TB:
10009             sTextFlow = OString( "lrTb" );
10010             bBiDi = true;
10011             break;
10012         case SvxFrameDirection::Vertical_LR_TB: // many things but not this one
10013         case SvxFrameDirection::Vertical_RL_TB:
10014             sTextFlow = OString( "tbRl" );
10015             break;
10016     }
10017 
10018     if ( m_rExport.m_bOutPageDescs )
10019     {
10020         m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), sTextFlow);
10021         if ( bBiDi )
10022             m_pSerializer->singleElementNS(XML_w, XML_bidi);
10023     }
10024     else if ( !m_rExport.m_bOutFlyFrameAttrs )
10025     {
10026         if ( bBiDi )
10027             m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "1");
10028         else
10029             m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "0");
10030     }
10031 }
10032 
10033 void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem)
10034 {
10035     const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag();
10036     for ( const auto & rGrabBagElement : rMap )
10037     {
10038         if (rGrabBagElement.first == "MirrorIndents")
10039             m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents);
10040         else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing")
10041         {
10042             m_bParaBeforeAutoSpacing = true;
10043             // get fixed value which was set during import
10044             rGrabBagElement.second >>= m_nParaBeforeSpacing;
10045             m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100);
10046             SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing);
10047         }
10048         else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing")
10049         {
10050             m_bParaAfterAutoSpacing = true;
10051             // get fixed value which was set during import
10052             rGrabBagElement.second >>= m_nParaAfterSpacing;
10053             m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100);
10054             SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing);
10055         }
10056         else if (rGrabBagElement.first == "CharThemeFill")
10057         {
10058             uno::Sequence<beans::PropertyValue> aGrabBagSeq;
10059             rGrabBagElement.second >>= aGrabBagSeq;
10060 
10061             for (const auto& rProp : std::as_const(aGrabBagSeq))
10062             {
10063                 OString sVal = OUStringToOString(rProp.Value.get<OUString>(), RTL_TEXTENCODING_UTF8);
10064 
10065                 if (sVal.isEmpty())
10066                     continue;
10067 
10068                 if (rProp.Name == "val")
10069                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal.getStr());
10070                 else if (rProp.Name == "color")
10071                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal.getStr());
10072                 else if (rProp.Name == "themeColor")
10073                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal.getStr());
10074                 else if (rProp.Name == "themeTint")
10075                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal.getStr());
10076                 else if (rProp.Name == "themeShade")
10077                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal.getStr());
10078                 else if (rProp.Name == "fill")
10079                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal.getStr());
10080                 else if (rProp.Name == "themeFill")
10081                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal.getStr());
10082                 else if (rProp.Name == "themeFillTint")
10083                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal.getStr());
10084                 else if (rProp.Name == "themeFillShade")
10085                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal.getStr());
10086                 else if (rProp.Name == "originalColor")
10087                     rProp.Value >>= m_sOriginalBackgroundColor;
10088             }
10089         }
10090         else if (rGrabBagElement.first == "SdtPr")
10091         {
10092             const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10093                     rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10094             m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10095             m_aStartedParagraphSdtPrAlias = m_aParagraphSdt.m_aAlias;
10096         }
10097         else if (rGrabBagElement.first == "ParaCnfStyle")
10098         {
10099             uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10100             m_pTableStyleExport->CnfStyle(aAttributes);
10101         }
10102         else if (rGrabBagElement.first == "ParaSdtEndBefore")
10103         {
10104             // Handled already in StartParagraph().
10105         }
10106         else
10107             SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first );
10108     }
10109 }
10110 
10111 void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem )
10112 {
10113     if (m_bPreventDoubleFieldsHandling)
10114         return;
10115 
10116     const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag();
10117 
10118     // get original values of theme-derived properties to check if they have changed during the edition
10119     bool bWriteCSTheme = true;
10120     bool bWriteAsciiTheme = true;
10121     bool bWriteEastAsiaTheme = true;
10122     bool bWriteThemeFontColor = true;
10123     OUString sOriginalValue;
10124     for ( const auto & rGrabBagElement : rMap )
10125     {
10126         if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" )
10127         {
10128             if ( rGrabBagElement.second >>= sOriginalValue )
10129                 bWriteCSTheme =
10130                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue );
10131         }
10132         else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" )
10133         {
10134             if ( rGrabBagElement.second >>= sOriginalValue )
10135                 bWriteAsciiTheme =
10136                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue );
10137         }
10138         else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" )
10139         {
10140             if ( rGrabBagElement.second >>= sOriginalValue )
10141                 bWriteEastAsiaTheme =
10142                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue );
10143         }
10144         else if ( m_pColorAttrList.is() && rGrabBagElement.first == "CharThemeOriginalColor" )
10145         {
10146             if ( rGrabBagElement.second >>= sOriginalValue )
10147                 bWriteThemeFontColor =
10148                         ( m_pColorAttrList->getOptionalValue( FSNS( XML_w, XML_val ) ) == sOriginalValue );
10149         }
10150     }
10151 
10152     // save theme attributes back to the run properties
10153     OUString str;
10154     for ( const auto & rGrabBagElement : rMap )
10155     {
10156         if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme )
10157         {
10158             rGrabBagElement.second >>= str;
10159             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ),
10160                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10161         }
10162         else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme )
10163         {
10164             rGrabBagElement.second >>= str;
10165             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ),
10166                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10167         }
10168         else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme )
10169         {
10170             rGrabBagElement.second >>= str;
10171             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ),
10172                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10173         }
10174         else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme )
10175         // this is not a mistake: in LibO we don't directly support the hAnsi family
10176         // of attributes so we save the same value from ascii attributes instead
10177         {
10178             rGrabBagElement.second >>= str;
10179             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ),
10180                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10181         }
10182         else if ( rGrabBagElement.first == "CharThemeColor" && bWriteThemeFontColor )
10183         {
10184             rGrabBagElement.second >>= str;
10185             AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeColor ),
10186                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10187         }
10188         else if ( rGrabBagElement.first == "CharThemeColorShade" )
10189         {
10190             rGrabBagElement.second >>= str;
10191             AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeShade ),
10192                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10193         }
10194         else if ( rGrabBagElement.first == "CharThemeColorTint" )
10195         {
10196             rGrabBagElement.second >>= str;
10197             AddToAttrList( m_pColorAttrList, FSNS( XML_w, XML_themeTint ),
10198                     OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
10199         }
10200         else if( rGrabBagElement.first == "CharThemeFontNameCs"   ||
10201                 rGrabBagElement.first == "CharThemeFontNameAscii" ||
10202                 rGrabBagElement.first == "CharThemeFontNameEastAsia" ||
10203                 rGrabBagElement.first == "CharThemeOriginalColor" )
10204         {
10205             // just skip these, they were processed before
10206         }
10207         else if(rGrabBagElement.first == "CharGlowTextEffect" ||
10208                 rGrabBagElement.first == "CharShadowTextEffect" ||
10209                 rGrabBagElement.first == "CharReflectionTextEffect" ||
10210                 rGrabBagElement.first == "CharTextOutlineTextEffect" ||
10211                 rGrabBagElement.first == "CharTextFillTextEffect" ||
10212                 rGrabBagElement.first == "CharScene3DTextEffect" ||
10213                 rGrabBagElement.first == "CharProps3DTextEffect" ||
10214                 rGrabBagElement.first == "CharLigaturesTextEffect" ||
10215                 rGrabBagElement.first == "CharNumFormTextEffect" ||
10216                 rGrabBagElement.first == "CharNumSpacingTextEffect" ||
10217                 rGrabBagElement.first == "CharStylisticSetsTextEffect" ||
10218                 rGrabBagElement.first == "CharCntxtAltsTextEffect")
10219         {
10220             beans::PropertyValue aPropertyValue;
10221             rGrabBagElement.second >>= aPropertyValue;
10222             m_aTextEffectsGrabBag.push_back(aPropertyValue);
10223         }
10224         else if (rGrabBagElement.first == "SdtEndBefore")
10225         {
10226             if (m_aRunSdt.m_bStartedSdt)
10227                 m_bEndCharSdt = true;
10228         }
10229         else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame )
10230         {
10231             const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10232                     rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10233             m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10234         }
10235         else
10236             SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first);
10237     }
10238 }
10239 
10240 DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML )
10241     : AttributeOutputBase(rExport.GetFilter().getFileUrl()),
10242       m_rExport( rExport ),
10243       m_pSerializer( pSerializer ),
10244       m_rDrawingML( *pDrawingML ),
10245       m_bEndCharSdt(false),
10246       m_endPageRef( false ),
10247       m_pFootnotesList( new ::docx::FootnotesList() ),
10248       m_pEndnotesList( new ::docx::FootnotesList() ),
10249       m_footnoteEndnoteRefTag( 0 ),
10250       m_pRedlineData( nullptr ),
10251       m_nRedlineId( 0 ),
10252       m_bOpenedSectPr( false ),
10253       m_bHadSectPr(false),
10254       m_bRunTextIsOn( false ),
10255       m_bWritingHeaderFooter( false ),
10256       m_bAnchorLinkedToNode(false),
10257       m_bWritingField( false ),
10258       m_bPreventDoubleFieldsHandling( false ),
10259       m_nNextBookmarkId( 0 ),
10260       m_nNextAnnotationMarkId( 0 ),
10261       m_nEmbedFlyLevel(0),
10262       m_pMoveRedlineData(nullptr),
10263       m_pCurrentFrame( nullptr ),
10264       m_bParagraphOpened( false ),
10265       m_bParagraphFrameOpen( false ),
10266       m_bIsFirstParagraph( true ),
10267       m_bAlternateContentChoiceOpen( false ),
10268       m_bPostponedProcessingFly( false ),
10269       m_nColBreakStatus( COLBRK_NONE ),
10270       m_bPostponedPageBreak( false ),
10271       m_nTextFrameLevel( 0 ),
10272       m_closeHyperlinkInThisRun( false ),
10273       m_closeHyperlinkInPreviousRun( false ),
10274       m_startedHyperlink( false ),
10275       m_nHyperLinkCount(0),
10276       m_nFieldsInHyperlink( 0 ),
10277       m_bExportingOutline(false),
10278       m_nChartCount(0),
10279       pendingPlaceholder( nullptr ),
10280       m_postitFieldsMaxId( 0 ),
10281       m_anchorId( 1 ),
10282       m_nextFontId( 1 ),
10283       m_tableReference(new TableReference()),
10284       m_bIgnoreNextFill(false),
10285       m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)),
10286       m_bParaBeforeAutoSpacing(false),
10287       m_bParaAfterAutoSpacing(false),
10288       m_nParaBeforeSpacing(0),
10289       m_nParaAfterSpacing(0)
10290     , m_nStateOfFlyFrame( FLY_NOT_PROCESSED )
10291 {
10292     // Push initial items to the RelId cache. In case the document contains no
10293     // special streams (headers, footers, etc.) then these items are used
10294     // during the full export.
10295     PushRelIdCache();
10296 }
10297 
10298 DocxAttributeOutput::~DocxAttributeOutput()
10299 {
10300 }
10301 
10302 DocxExport& DocxAttributeOutput::GetExport()
10303 {
10304     return m_rExport;
10305 }
10306 
10307 void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer )
10308 {
10309     m_pSerializer = pSerializer;
10310     m_pTableStyleExport->SetSerializer(pSerializer);
10311 }
10312 
10313 bool DocxAttributeOutput::HasFootnotes() const
10314 {
10315     return !m_pFootnotesList->isEmpty();
10316 }
10317 
10318 bool DocxAttributeOutput::HasEndnotes() const
10319 {
10320     return !m_pEndnotesList->isEmpty();
10321 }
10322 
10323 bool DocxAttributeOutput::HasPostitFields() const
10324 {
10325     return !m_postitFields.empty();
10326 }
10327 
10328 void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize)
10329 {
10330     m_pSerializer->startElementNS(XML_w, XML_numPicBullet,
10331             FSNS(XML_w, XML_numPicBulletId), OString::number(nId));
10332 
10333     OStringBuffer aStyle;
10334     // Size is in twips, we need it in points.
10335     aStyle.append("width:" + OString::number(double(aSize.Width()) / 20));
10336     aStyle.append("pt;height:" + OString::number(double(aSize.Height()) / 20) + "pt");
10337     m_pSerializer->startElementNS(XML_w, XML_pict);
10338     m_pSerializer->startElementNS( XML_v, XML_shape,
10339             XML_style, aStyle.getStr(),
10340             FSNS(XML_o, XML_bullet), "t");
10341 
10342     OUString aRelId = m_rDrawingML.WriteImage(rGraphic);
10343     m_pSerializer->singleElementNS( XML_v, XML_imagedata,
10344             FSNS(XML_r, XML_id), OUStringToOString(aRelId, RTL_TEXTENCODING_UTF8),
10345             FSNS(XML_o, XML_title), "");
10346 
10347     m_pSerializer->endElementNS(XML_v, XML_shape);
10348     m_pSerializer->endElementNS(XML_w, XML_pict);
10349 
10350     m_pSerializer->endElementNS(XML_w, XML_numPicBullet);
10351 }
10352 
10353 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
10354