android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / filter / ww8 / docxattributeoutput.cxx
blob8847573ebb80dd158602ec46272af55bfad6ee90
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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 <unoframe.hxx>
38 #include <textboxhelper.hxx>
39 #include <rdfhelper.hxx>
40 #include "wrtww8.hxx"
42 #include <comphelper/processfactory.hxx>
43 #include <comphelper/random.hxx>
44 #include <comphelper/string.hxx>
45 #include <comphelper/flagguard.hxx>
46 #include <comphelper/sequence.hxx>
47 #include <oox/token/namespaces.hxx>
48 #include <oox/token/tokens.hxx>
49 #include <oox/export/utils.hxx>
50 #include <oox/mathml/imexport.hxx>
51 #include <oox/drawingml/drawingmltypes.hxx>
52 #include <oox/token/relationship.hxx>
53 #include <oox/export/vmlexport.hxx>
54 #include <oox/ole/olehelper.hxx>
56 #include <editeng/autokernitem.hxx>
57 #include <editeng/unoprnms.hxx>
58 #include <editeng/fontitem.hxx>
59 #include <editeng/tstpitem.hxx>
60 #include <editeng/spltitem.hxx>
61 #include <editeng/widwitem.hxx>
62 #include <editeng/shaditem.hxx>
63 #include <editeng/brushitem.hxx>
64 #include <editeng/postitem.hxx>
65 #include <editeng/wghtitem.hxx>
66 #include <editeng/kernitem.hxx>
67 #include <editeng/crossedoutitem.hxx>
68 #include <editeng/cmapitem.hxx>
69 #include <editeng/udlnitem.hxx>
70 #include <editeng/langitem.hxx>
71 #include <editeng/lspcitem.hxx>
72 #include <editeng/escapementitem.hxx>
73 #include <editeng/fhgtitem.hxx>
74 #include <editeng/colritem.hxx>
75 #include <editeng/hyphenzoneitem.hxx>
76 #include <editeng/ulspitem.hxx>
77 #include <editeng/contouritem.hxx>
78 #include <editeng/shdditem.hxx>
79 #include <editeng/emphasismarkitem.hxx>
80 #include <editeng/twolinesitem.hxx>
81 #include <editeng/charscaleitem.hxx>
82 #include <editeng/charrotateitem.hxx>
83 #include <editeng/charreliefitem.hxx>
84 #include <editeng/paravertalignitem.hxx>
85 #include <editeng/pgrditem.hxx>
86 #include <editeng/frmdiritem.hxx>
87 #include <editeng/blinkitem.hxx>
88 #include <editeng/charhiddenitem.hxx>
89 #include <editeng/editobj.hxx>
90 #include <editeng/keepitem.hxx>
91 #include <editeng/borderline.hxx>
92 #include <sax/tools/converter.hxx>
93 #include <svx/xdef.hxx>
94 #include <svx/xfillit0.hxx>
95 #include <svx/xflclit.hxx>
96 #include <svx/xflgrit.hxx>
97 #include <svx/svdouno.hxx>
98 #include <svx/unobrushitemhelper.hxx>
99 #include <svl/grabbagitem.hxx>
100 #include <tools/date.hxx>
101 #include <tools/datetime.hxx>
102 #include <tools/datetimeutils.hxx>
103 #include <svl/whiter.hxx>
104 #include <rtl/tencinfo.h>
105 #include <sal/log.hxx>
106 #include <sot/exchange.hxx>
108 #include <docufld.hxx>
109 #include <authfld.hxx>
110 #include <flddropdown.hxx>
111 #include <fmtclds.hxx>
112 #include <fmtinfmt.hxx>
113 #include <fmtline.hxx>
114 #include <ftninfo.hxx>
115 #include <htmltbl.hxx>
116 #include <lineinfo.hxx>
117 #include <ndgrf.hxx>
118 #include <ndole.hxx>
119 #include <ndtxt.hxx>
120 #include <pagedesc.hxx>
121 #include <paratr.hxx>
122 #include <swmodule.hxx>
123 #include <swtable.hxx>
124 #include <txtftn.hxx>
125 #include <fmtautofmt.hxx>
126 #include <docsh.hxx>
127 #include <docary.hxx>
128 #include <fmtclbl.hxx>
129 #include <IDocumentSettingAccess.hxx>
130 #include <IDocumentRedlineAccess.hxx>
131 #include <grfatr.hxx>
132 #include <frmatr.hxx>
133 #include <txtatr.hxx>
134 #include <frameformats.hxx>
135 #include <textcontentcontrol.hxx>
136 #include <formatflysplit.hxx>
138 #include <o3tl/string_view.hxx>
139 #include <o3tl/unit_conversion.hxx>
140 #include <osl/file.hxx>
141 #include <utility>
142 #include <vcl/embeddedfontshelper.hxx>
144 #include <com/sun/star/i18n/ScriptType.hpp>
145 #include <com/sun/star/i18n/XBreakIterator.hpp>
146 #include <com/sun/star/chart2/XChartDocument.hpp>
147 #include <com/sun/star/drawing/ShadingPattern.hpp>
148 #include <com/sun/star/text/GraphicCrop.hpp>
149 #include <com/sun/star/embed/EmbedStates.hpp>
150 #include <com/sun/star/embed/Aspects.hpp>
151 #include <com/sun/star/text/ControlCharacter.hpp>
153 #include <algorithm>
154 #include <cstddef>
155 #include <stdarg.h>
156 #include <string_view>
158 #include <toolkit/helper/vclunohelper.hxx>
159 #include <unicode/regex.h>
161 using ::editeng::SvxBorderLine;
163 using namespace oox;
164 using namespace docx;
165 using namespace sax_fastparser;
166 using namespace nsSwDocInfoSubType;
167 using namespace sw::util;
168 using namespace ::com::sun::star;
169 using namespace ::com::sun::star::drawing;
171 namespace {
173 class FFDataWriterHelper
175 ::sax_fastparser::FSHelperPtr m_pSerializer;
176 void writeCommonStart( const OUString& rName,
177 const OUString& rEntryMacro,
178 const OUString& rExitMacro,
179 const OUString& rHelp,
180 const OUString& rHint )
182 m_pSerializer->startElementNS(XML_w, XML_ffData);
183 m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
184 m_pSerializer->singleElementNS(XML_w, XML_enabled);
185 m_pSerializer->singleElementNS(XML_w, XML_calcOnExit, FSNS(XML_w, XML_val), "0");
187 if ( !rEntryMacro.isEmpty() )
188 m_pSerializer->singleElementNS( XML_w, XML_entryMacro,
189 FSNS(XML_w, XML_val), rEntryMacro );
191 if ( !rExitMacro.isEmpty() )
192 m_pSerializer->singleElementNS(XML_w, XML_exitMacro, FSNS(XML_w, XML_val), rExitMacro);
194 if ( !rHelp.isEmpty() )
195 m_pSerializer->singleElementNS( XML_w, XML_helpText,
196 FSNS(XML_w, XML_type), "text",
197 FSNS(XML_w, XML_val), rHelp );
199 if ( !rHint.isEmpty() )
200 m_pSerializer->singleElementNS( XML_w, XML_statusText,
201 FSNS(XML_w, XML_type), "text",
202 FSNS(XML_w, XML_val), rHint );
205 void writeFinish()
207 m_pSerializer->endElementNS( XML_w, XML_ffData );
209 public:
210 explicit FFDataWriterHelper( ::sax_fastparser::FSHelperPtr rSerializer ) : m_pSerializer(std::move( rSerializer )){}
211 void WriteFormCheckbox( const OUString& rName,
212 const OUString& rEntryMacro,
213 const OUString& rExitMacro,
214 const OUString& rHelp,
215 const OUString& rHint,
216 bool bChecked )
218 writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
219 // Checkbox specific bits
220 m_pSerializer->startElementNS(XML_w, XML_checkBox);
221 // currently hardcoding autosize
222 // #TODO check if this defaulted
223 m_pSerializer->startElementNS(XML_w, XML_sizeAuto);
224 m_pSerializer->endElementNS( XML_w, XML_sizeAuto );
225 if ( bChecked )
226 m_pSerializer->singleElementNS(XML_w, XML_checked);
227 m_pSerializer->endElementNS( XML_w, XML_checkBox );
228 writeFinish();
231 void WriteFormText( const OUString& rName,
232 const OUString& rEntryMacro,
233 const OUString& rExitMacro,
234 const OUString& rHelp,
235 const OUString& rHint,
236 const OUString& rType,
237 const OUString& rDefaultText,
238 sal_uInt16 nMaxLength,
239 const OUString& rFormat )
241 writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
243 m_pSerializer->startElementNS(XML_w, XML_textInput);
244 if ( !rType.isEmpty() )
245 m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), rType);
246 if ( !rDefaultText.isEmpty() )
247 m_pSerializer->singleElementNS(XML_w, XML_default, FSNS(XML_w, XML_val), rDefaultText);
248 if ( nMaxLength )
249 m_pSerializer->singleElementNS( XML_w, XML_maxLength,
250 FSNS(XML_w, XML_val), OString::number(nMaxLength) );
251 if ( !rFormat.isEmpty() )
252 m_pSerializer->singleElementNS(XML_w, XML_format, FSNS(XML_w, XML_val), rFormat);
253 m_pSerializer->endElementNS( XML_w, XML_textInput );
255 writeFinish();
259 class FieldMarkParamsHelper
261 const sw::mark::IFieldmark& mrFieldmark;
262 public:
263 explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {}
264 OUString const & getName() const { return mrFieldmark.GetName(); }
265 template < typename T >
266 bool extractParam( const OUString& rKey, T& rResult )
268 bool bResult = false;
269 if ( mrFieldmark.GetParameters() )
271 sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey );
272 if ( it != mrFieldmark.GetParameters()->end() )
273 bResult = ( it->second >>= rResult );
275 return bResult;
279 // [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value)
280 OUString NumberToHexBinary(sal_Int32 n)
282 OUStringBuffer aBuf;
283 sax::Converter::convertNumberToHexBinary(aBuf, n);
284 return aBuf.makeStringAndClear();
287 // Returns a new reference with the previous content of src; src is empty after this
288 auto detachFrom(rtl::Reference<sax_fastparser::FastAttributeList>& src)
290 return rtl::Reference(std::move(src));
293 void lclAddThemeValuesToCustomAttributes(
294 rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor,
295 sal_Int32 nThemeAttrId, sal_Int32 nThemeTintAttrId, sal_Int32 nThemeShadeAttrId)
297 static std::unordered_map<model::ThemeColorType, const char*> constThemeColorTypeTokenMap = {
298 { model::ThemeColorType::Dark1, "dark1" },
299 { model::ThemeColorType::Light1, "light1" },
300 { model::ThemeColorType::Dark2, "dark2" },
301 { model::ThemeColorType::Light2, "light2" },
302 { model::ThemeColorType::Accent1, "accent1" },
303 { model::ThemeColorType::Accent2, "accent2" },
304 { model::ThemeColorType::Accent3, "accent3" },
305 { model::ThemeColorType::Accent4, "accent4" },
306 { model::ThemeColorType::Accent5, "accent5" },
307 { model::ThemeColorType::Accent6, "accent6" },
308 { model::ThemeColorType::Hyperlink, "hyperlink" },
309 { model::ThemeColorType::FollowedHyperlink, "followedHyperlink" }
312 if (rComplexColor.getType() == model::ColorType::Scheme &&
313 rComplexColor.getSchemeType() != model::ThemeColorType::Unknown)
315 OString sSchemeType = constThemeColorTypeTokenMap[rComplexColor.getSchemeType()];
316 if (rComplexColor.meThemeColorUsage == model::ThemeColorUsage::Text)
318 if (rComplexColor.getSchemeType() == model::ThemeColorType::Dark1)
319 sSchemeType = "text1";
320 else if (rComplexColor.getSchemeType() == model::ThemeColorType::Dark2)
321 sSchemeType = "text2";
323 else if (rComplexColor.meThemeColorUsage == model::ThemeColorUsage::Background)
325 if (rComplexColor.getSchemeType() == model::ThemeColorType::Light1)
326 sSchemeType = "background1";
327 else if (rComplexColor.getSchemeType() == model::ThemeColorType::Light2)
328 sSchemeType = "background2";
331 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeAttrId), sSchemeType);
333 sal_Int16 nLumMod = 10'000;
334 sal_Int16 nLumOff = 0;
335 sal_Int16 nTint = 0;
336 sal_Int16 nShade = 0;
338 for (auto const& rTransform : rComplexColor.getTransformations())
340 if (rTransform.meType == model::TransformationType::LumMod)
341 nLumMod = rTransform.mnValue;
342 if (rTransform.meType == model::TransformationType::LumOff)
343 nLumOff = rTransform.mnValue;
344 if (rTransform.meType == model::TransformationType::Tint)
345 nTint = rTransform.mnValue;
346 if (rTransform.meType == model::TransformationType::Shade)
347 nShade = rTransform.mnValue;
349 if (nLumMod == 10'000 && nLumOff == 0)
351 if (nTint != 0)
353 // Convert from 0-100 into 0-255
354 sal_Int16 nTint255 = std::round(255.0 - (double(nTint) / 10000.0) * 255.0);
355 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTint255, 16));
357 else if (nShade != 0)
359 // Convert from 0-100 into 0-255
360 sal_Int16 nShade255 = std::round(255.0 - (double(nShade) / 10000.0) * 255.0);
361 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nShade255, 16));
364 else
366 double nPercentage = 0.0;
368 if (nLumOff > 0)
369 nPercentage = double(nLumOff) / 100.0;
370 else
371 nPercentage = (-10'000 + double(nLumMod)) / 100.0;
373 // Convert from 0-100 into 0-255
374 sal_Int16 nTintShade255 = std::round(255.0 - (std::abs(nPercentage) / 100.0) * 255.0);
376 if (nPercentage > 0)
377 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTintShade255, 16));
378 else if (nPercentage < 0)
379 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nTintShade255, 16));
384 void lclAddThemeFillColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
386 lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeFill, XML_themeFillTint, XML_themeFillShade);
389 void lclAddThemeColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
391 lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeColor, XML_themeTint, XML_themeShade);
394 } // end anonymous namespace
396 void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
398 if (bIsRTL)
399 m_pSerializer->singleElementNS(XML_w, XML_rtl, FSNS(XML_w, XML_val), "true");
402 /// Are multiple paragraphs disallowed inside this type of SDT?
403 static bool lcl_isOnelinerSdt(std::u16string_view rName)
405 return rName == u"Title" || rName == u"Subtitle" || rName == u"Company";
408 // write a floating table directly to docx without the surrounding frame
409 void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame)
411 const SwFrameFormat& rFrameFormat = pParentFrame->GetFrameFormat();
412 m_aFloatingTablesOfParagraph.insert(&rFrameFormat);
413 const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
415 SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0);
416 SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
418 //Save data here and restore when out of scope
419 ExportDataSaveRestore aDataGuard(GetExport(), nStt, nEnd, pParentFrame);
421 // Stash away info about the current table, so m_tableReference is clean.
422 DocxTableExportContext aTableExportContext(*this);
424 // set a floatingTableFrame AND unset parent frame,
425 // otherwise exporter thinks we are still in a frame
426 m_rExport.SetFloatingTableFrame(pParentFrame);
427 m_rExport.m_pParentFrame = nullptr;
429 GetExport().WriteText();
431 m_rExport.SetFloatingTableFrame(nullptr);
434 static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput)
436 const auto& rExport = rDocxAttributeOutput.GetExport();
437 // iterate though all SpzFrameFormats and check whether they are anchored to the current text node
438 for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; )
440 const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
441 const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
442 const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
444 if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode())
445 continue;
447 if (*pAnchorNode != *rExport.m_pCurPam->GetPointNode().GetTextNode())
448 continue;
450 const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx();
451 if (!pStartNode)
452 continue;
454 SwNodeIndex aStartNode = *pStartNode;
456 // go to the next node (actual content)
457 ++aStartNode;
459 // this has to be a table
460 if (!aStartNode.GetNode().IsTableNode())
461 continue;
463 // go to the end of the table
464 SwNodeOffset aEndIndex = aStartNode.GetNode().EndOfSectionIndex();
465 // go one deeper
466 aEndIndex++;
467 // this has to be the end of the content
468 if (aEndIndex != pFrameFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionIndex())
469 continue;
471 // check for a grabBag and "TablePosition" attribute -> then we can export the table directly
472 SwTableNode* pTableNode = aStartNode.GetNode().GetTableNode();
473 SwTable& rTable = pTableNode->GetTable();
474 SwFrameFormat* pTableFormat = rTable.GetFrameFormat();
475 const SfxGrabBagItem* pTableGrabBag = pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG);
476 const std::map<OUString, css::uno::Any> & rTableGrabBag = pTableGrabBag->GetGrabBag();
477 // no grabbag?
478 if (rTableGrabBag.find("TablePosition") == rTableGrabBag.end())
480 if (pFrameFormat->GetFlySplit().GetValue())
482 ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
483 rDocxAttributeOutput.WriteFloatingTable(&aFrame);
485 continue;
488 // write table to docx
489 ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
490 rDocxAttributeOutput.WriteFloatingTable(&aFrame);
494 sal_Int32 DocxAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo,
495 bool bGenerateParaId)
497 // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes.
498 // So a stack is needed to keep track of each paragraph's status separately.
499 // Complication: Word can't handle nested text boxes, so those need to be collected together.
500 if ( !m_aFramesOfParagraph.size() || !m_nTextFrameLevel )
501 m_aFramesOfParagraph.push(std::vector<ww8::Frame>());
503 if ( m_nColBreakStatus == COLBRK_POSTPONE )
504 m_nColBreakStatus = COLBRK_WRITE;
506 // Output table/table row/table cell starts if needed
507 if ( pTextNodeInfo )
509 // New cell/row?
510 if ( m_tableReference.m_nTableDepth > 0 && !m_tableReference.m_bTableCellOpen )
512 ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference.m_nTableDepth ) );
513 if ( pDeepInner->getCell() == 0 )
514 StartTableRow( pDeepInner );
516 const sal_uInt32 nCell = pDeepInner->getCell();
517 const sal_uInt32 nRow = pDeepInner->getRow();
519 SyncNodelessCells(pDeepInner, nCell, nRow);
520 StartTableCell(pDeepInner, nCell, nRow);
523 sal_uInt32 nRow = pTextNodeInfo->getRow();
524 sal_uInt32 nCell = pTextNodeInfo->getCell();
525 if (nCell == 0)
527 // Do we have to start the table?
528 // [If we are at the right depth already, it means that we
529 // continue the table cell]
530 sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth();
532 if ( nCurrentDepth > m_tableReference.m_nTableDepth )
534 // Start all the tables that begin here
535 for ( sal_uInt32 nDepth = m_tableReference.m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth )
537 ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) );
539 StartTable( pInner );
540 StartTableRow( pInner );
542 StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0);
545 m_tableReference.m_nTableDepth = nCurrentDepth;
550 // look ahead for floating tables that were put into a frame during import
551 // floating tables in shapes are not supported: exclude this case
552 if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
554 // Do this after opening table/row/cell, so floating tables anchored at cell start go inside
555 // the cell, not outside.
556 checkAndWriteFloatingTables(*this);
559 // Look up the "sdt end before this paragraph" property early, when it
560 // would normally arrive, it would be too late (would be after the
561 // paragraph start has been written).
562 bool bEndParaSdt = false;
563 if (m_aParagraphSdt.m_bStartedSdt)
565 SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
566 if (pTextNode && pTextNode->GetpSwAttrSet())
568 const SfxItemSet* pSet = pTextNode->GetpSwAttrSet();
569 if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG))
571 const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem);
572 const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag();
573 bEndParaSdt = m_aParagraphSdt.m_bStartedSdt && rMap.find("ParaSdtEndBefore") != rMap.end();
577 // TODO also avoid multiline paragraphs in those SDT types for shape text
578 bool bOneliner = m_aParagraphSdt.m_bStartedSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias);
579 if (bEndParaSdt || (m_aParagraphSdt.m_bStartedSdt && m_bHadSectPr) || bOneliner)
581 // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph.
582 m_aParagraphSdt.EndSdtBlock(m_pSerializer);
583 m_aStartedParagraphSdtPrAlias.clear();
585 m_bHadSectPr = false;
587 // this mark is used to be able to enclose the paragraph inside a sdr tag.
588 // We will only know if we have to do that later.
589 m_pSerializer->mark(Tag_StartParagraph_1);
591 std::optional<OUString> aParaId;
592 sal_Int32 nParaId = 0;
593 if (bGenerateParaId)
595 nParaId = m_nNextParaId++;
596 aParaId = NumberToHexBinary(nParaId);
598 m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
600 // postpone the output of the run (we get it before the paragraph
601 // properties, but must write it after them)
602 m_pSerializer->mark(Tag_StartParagraph_2);
604 // no section break in this paragraph yet; can be set in SectionBreak()
605 m_pSectionInfo.reset();
607 m_bParagraphOpened = true;
608 m_bIsFirstParagraph = false;
609 m_nHyperLinkCount.push_back(0);
611 return nParaId;
614 OString DocxAttributeOutput::convertToOOXMLVertOrient(sal_Int16 nOrient)
616 switch( nOrient )
618 case text::VertOrientation::CENTER:
619 case text::VertOrientation::LINE_CENTER:
620 return "center";
621 case text::VertOrientation::BOTTOM:
622 return "bottom";
623 case text::VertOrientation::LINE_BOTTOM:
624 return "outside";
625 case text::VertOrientation::TOP:
626 return "top";
627 case text::VertOrientation::LINE_TOP:
628 return "inside";
629 default:
630 return OString();
634 OString DocxAttributeOutput::convertToOOXMLHoriOrient(sal_Int16 nOrient, bool bIsPosToggle)
636 switch( nOrient )
638 case text::HoriOrientation::LEFT:
639 return bIsPosToggle ? "inside" : "left";
640 case text::HoriOrientation::INSIDE:
641 return "inside";
642 case text::HoriOrientation::RIGHT:
643 return bIsPosToggle ? "outside" : "right";
644 case text::HoriOrientation::OUTSIDE:
645 return "outside";
646 case text::HoriOrientation::CENTER:
647 case text::HoriOrientation::FULL:
648 return "center";
649 default:
650 return OString();
654 OString DocxAttributeOutput::convertToOOXMLVertOrientRel(sal_Int16 nOrientRel)
656 switch (nOrientRel)
658 case text::RelOrientation::PAGE_PRINT_AREA:
659 return "margin";
660 case text::RelOrientation::PAGE_FRAME:
661 return "page";
662 case text::RelOrientation::FRAME:
663 case text::RelOrientation::TEXT_LINE:
664 default:
665 return "text";
669 OString DocxAttributeOutput::convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel)
671 switch (nOrientRel)
673 case text::RelOrientation::PAGE_PRINT_AREA:
674 return "margin";
675 case text::RelOrientation::PAGE_FRAME:
676 return "page";
677 case text::RelOrientation::CHAR:
678 case text::RelOrientation::PAGE_RIGHT:
679 case text::RelOrientation::FRAME:
680 default:
681 return "text";
685 void FramePrHelper::SetFrame(ww8::Frame* pFrame, sal_Int32 nTableDepth)
687 assert(!pFrame || !m_pFrame);
688 m_pFrame = pFrame;
689 m_nTableDepth = nTableDepth;
690 if (m_pFrame)
692 m_bUseFrameBorders = true;
693 m_bUseFrameBackground = true;
694 m_bUseFrameTextDirection = true;
698 bool FramePrHelper::UseFrameBorders(sal_Int32 nTableDepth)
700 if (!m_pFrame || m_nTableDepth < nTableDepth)
701 return false;
703 return m_bUseFrameBorders;
706 bool FramePrHelper::UseFrameBackground()
708 if (!m_pFrame)
709 return false;
711 return m_bUseFrameBackground;
714 bool FramePrHelper::UseFrameTextDirection(sal_Int32 nTableDepth)
716 if (!m_pFrame || m_nTableDepth < nTableDepth)
717 return false;
719 return m_bUseFrameTextDirection;
722 void SdtBlockHelper::DeleteAndResetTheLists()
724 if (m_pTokenChildren.is() )
725 m_pTokenChildren.clear();
726 if (m_pDataBindingAttrs.is() )
727 m_pDataBindingAttrs.clear();
728 if (m_pTextAttrs.is())
729 m_pTextAttrs.clear();
730 if (!m_aAlias.isEmpty())
731 m_aAlias.clear();
732 if (!m_aTag.isEmpty())
733 m_aTag.clear();
734 if (!m_aLock.isEmpty())
735 m_aLock.clear();
736 if (!m_aPlaceHolderDocPart.isEmpty())
737 m_aPlaceHolderDocPart.clear();
738 if (!m_aColor.isEmpty())
739 m_aColor.clear();
740 if (!m_aAppearance.isEmpty())
741 m_aAppearance.clear();
742 m_bShowingPlaceHolder = false;
743 m_nId = 0;
744 m_nTabIndex = 0;
747 void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing)
749 if (m_nSdtPrToken <= 0 && !m_pDataBindingAttrs.is() && !m_nId)
750 return;
752 // sdt start mark
753 pSerializer->mark(DocxAttributeOutput::Tag_WriteSdtBlock);
755 pSerializer->startElementNS(XML_w, XML_sdt);
757 // output sdt properties
758 pSerializer->startElementNS(XML_w, XML_sdtPr);
760 if (m_nSdtPrToken > 0 && m_pTokenChildren.is())
762 if (!m_pTokenAttributes.is())
763 pSerializer->startElement(m_nSdtPrToken);
764 else
766 pSerializer->startElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
769 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)) {
770 const uno::Sequence<xml::FastAttribute> aChildren = m_pTokenChildren->getFastAttributes();
771 for (const auto& rChild : aChildren)
772 pSerializer->singleElement(rChild.Token, FSNS(XML_w, XML_val), rChild.Value);
775 pSerializer->endElement(m_nSdtPrToken);
777 else if ((m_nSdtPrToken > 0) && m_nSdtPrToken != FSNS(XML_w, XML_id) && !(bRunTextIsOn && bParagraphHasDrawing))
779 if (!m_pTokenAttributes.is())
780 pSerializer->singleElement(m_nSdtPrToken);
781 else
783 pSerializer->singleElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
787 WriteExtraParams(pSerializer);
789 pSerializer->endElementNS(XML_w, XML_sdtPr);
791 // sdt contents start tag
792 pSerializer->startElementNS(XML_w, XML_sdtContent);
794 // prepend the tags since the sdt start mark before the paragraph
795 pSerializer->mergeTopMarks(DocxAttributeOutput::Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND);
797 // write the ending tags after the paragraph
798 m_bStartedSdt = true;
800 // clear sdt status
801 m_nSdtPrToken = 0;
802 DeleteAndResetTheLists();
805 void SdtBlockHelper::WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer)
807 if (m_nId)
809 pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), OString::number(m_nId));
812 if (m_pDataBindingAttrs.is())
814 pSerializer->singleElementNS(XML_w, XML_dataBinding, detachFrom(m_pDataBindingAttrs));
817 if (m_pTextAttrs.is())
819 pSerializer->singleElementNS(XML_w, XML_text, detachFrom(m_pTextAttrs));
822 if (!m_aPlaceHolderDocPart.isEmpty())
824 pSerializer->startElementNS(XML_w, XML_placeholder);
825 pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), m_aPlaceHolderDocPart);
826 pSerializer->endElementNS(XML_w, XML_placeholder);
829 if (m_bShowingPlaceHolder)
830 pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
832 if (!m_aColor.isEmpty())
834 pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), m_aColor);
837 if (!m_aAppearance.isEmpty())
839 pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val), m_aAppearance);
842 if (!m_aAlias.isEmpty())
843 pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), m_aAlias);
845 if (!m_aTag.isEmpty())
846 pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), m_aTag);
848 if (m_nTabIndex)
849 pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
850 OString::number(m_nTabIndex));
852 if (!m_aLock.isEmpty())
853 pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), m_aLock);
856 void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer)
858 pSerializer->endElementNS(XML_w, XML_sdtContent);
859 pSerializer->endElementNS(XML_w, XML_sdt);
860 m_bStartedSdt = false;
863 void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
865 for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt)
867 if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
869 m_nSdtPrToken = FSNS(XML_w14, XML_checkbox);
870 uno::Sequence<beans::PropertyValue> aGrabBag;
871 aPropertyValue.Value >>= aGrabBag;
872 for (const auto& rProp : aGrabBag)
874 if (rProp.Name == "ooxml:CT_SdtCheckbox_checked")
875 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
876 FSNS(XML_w14, XML_checked), rProp.Value.get<OUString>());
877 else if (rProp.Name == "ooxml:CT_SdtCheckbox_checkedState")
878 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
879 FSNS(XML_w14, XML_checkedState), rProp.Value.get<OUString>());
880 else if (rProp.Name == "ooxml:CT_SdtCheckbox_uncheckedState")
881 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
882 FSNS(XML_w14, XML_uncheckedState), rProp.Value.get<OUString>());
885 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pDataBindingAttrs.is())
887 uno::Sequence<beans::PropertyValue> aGrabBag;
888 aPropertyValue.Value >>= aGrabBag;
889 for (const auto& rProp : aGrabBag)
891 if (rProp.Name == "ooxml:CT_DataBinding_prefixMappings")
892 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
893 FSNS( XML_w, XML_prefixMappings ), rProp.Value.get<OUString>());
894 else if (rProp.Name == "ooxml:CT_DataBinding_xpath")
895 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
896 FSNS( XML_w, XML_xpath ), rProp.Value.get<OUString>());
897 else if (rProp.Name == "ooxml:CT_DataBinding_storeItemID")
898 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
899 FSNS( XML_w, XML_storeItemID ), rProp.Value.get<OUString>());
902 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text")
904 uno::Sequence<beans::PropertyValue> aGrabBag;
905 aPropertyValue.Value >>= aGrabBag;
906 if (aGrabBag.hasElements())
908 for (const auto& rProp : aGrabBag)
910 if (rProp.Name == "ooxml:CT_SdtText_multiLine")
911 DocxAttributeOutput::AddToAttrList(m_pTextAttrs,
912 FSNS(XML_w, XML_multiLine), rProp.Value.get<OUString>());
915 else
917 // We still have w:text, but no attrs
918 m_nSdtPrToken = FSNS(XML_w, XML_text);
921 else if (aPropertyValue.Name == "ooxml:CT_SdtPlaceholder_docPart")
923 uno::Sequence<beans::PropertyValue> aGrabBag;
924 aPropertyValue.Value >>= aGrabBag;
925 for (const auto& rProp : std::as_const(aGrabBag))
927 OUString sValue = rProp.Value.get<OUString>();
928 if (rProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
929 m_aPlaceHolderDocPart = sValue;
932 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_color")
934 uno::Sequence<beans::PropertyValue> aGrabBag;
935 aPropertyValue.Value >>= aGrabBag;
936 for (const auto& rProp : std::as_const(aGrabBag))
938 OUString sValue = rProp.Value.get<OUString>();
939 if (rProp.Name == "ooxml:CT_SdtColor_val")
940 m_aColor = sValue;
943 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_appearance")
945 if (!(aPropertyValue.Value >>= m_aAppearance))
946 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt appearance value");
948 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_showingPlcHdr")
950 if (!(aPropertyValue.Value >>= m_bShowingPlaceHolder))
951 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt ShowingPlcHdr");
953 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aAlias.isEmpty())
955 if (!(aPropertyValue.Value >>= m_aAlias))
956 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt alias value");
958 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tag" && m_aTag.isEmpty())
960 if (!(aPropertyValue.Value >>= m_aTag))
961 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tag value");
963 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
965 if (!(aPropertyValue.Value >>= m_nId))
966 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt id value");
968 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tabIndex" && !m_nTabIndex)
970 if (!(aPropertyValue.Value >>= m_nTabIndex))
971 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tabIndex value");
973 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && m_aLock.isEmpty())
975 if (!(aPropertyValue.Value >>= m_aLock))
976 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt lock value");
978 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
979 m_nSdtPrToken = FSNS(XML_w, XML_citation);
980 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" ||
981 aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
983 if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj")
984 m_nSdtPrToken = FSNS(XML_w, XML_docPartObj);
985 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
986 m_nSdtPrToken = FSNS(XML_w, XML_docPartList);
988 uno::Sequence<beans::PropertyValue> aGrabBag;
989 aPropertyValue.Value >>= aGrabBag;
990 for (const auto& rProp : aGrabBag)
992 if (rProp.Name == "ooxml:CT_SdtDocPart_docPartGallery")
993 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
994 FSNS(XML_w, XML_docPartGallery), rProp.Value.get<OUString>());
995 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartCategory")
996 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
997 FSNS(XML_w, XML_docPartCategory), rProp.Value.get<OUString>());
998 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartUnique")
1000 OUString sValue = rProp.Value.get<OUString>();
1001 if (sValue.isEmpty())
1002 sValue = "true";
1003 DocxAttributeOutput::AddToAttrList(m_pTokenChildren, FSNS(XML_w, XML_docPartUnique),
1004 sValue);
1008 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation")
1009 m_nSdtPrToken = FSNS(XML_w, XML_equation);
1010 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture")
1011 m_nSdtPrToken = FSNS(XML_w, XML_picture);
1012 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group")
1013 m_nSdtPrToken = FSNS(XML_w, XML_group);
1014 else
1015 SAL_WARN("sw.ww8", "GetSdtParamsFromGrabBag unhandled SdtPr grab bag property " << aPropertyValue.Name);
1019 void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize)
1021 rtl::Reference<sax_fastparser::FastAttributeList> attrList = FastSerializerHelper::createAttrList();
1023 const SwFormatHoriOrient& rHoriOrient = pFrameFormat->GetHoriOrient();
1024 const SwFormatVertOrient& rVertOrient = pFrameFormat->GetVertOrient();
1025 awt::Point aPos(rHoriOrient.GetPos(), rVertOrient.GetPos());
1027 // A few assumptions need to be made here, because framePr is a confused mixture
1028 // of (multiple) paragraph's border properties being transferred to/from a frame.
1029 // The frame size describes the size BEFORE the PARAGRAPH border spacing is applied.
1030 // However, we can't actually look at all the paragraphs' borders because they might be
1031 // different, and all MUST specify the same frame width in order to belong to the same frame.
1032 // In order for them all to be consistent, the only choice is to use the frame's border spacing.
1033 // During import, the frame was assigned border spacing based on the contained paragraphs.
1034 // So now at export time we have to assume that none of this has been changed by the user.
1036 // 620 (31pt) is the maximum paragraph border spacing allowed in MS Formats,
1037 // so if the value is greater than that, avoid adjusting the size - the user has interfered.
1038 const sal_uInt32 nLeftBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::LEFT);
1039 const sal_uInt32 nRighttBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::RIGHT);
1040 sal_uInt32 nAdjustedWidth = rSize.Width();
1041 if (nLeftBorderSpacing < 621 && nRighttBorderSpacing < 621
1042 && nAdjustedWidth > nLeftBorderSpacing + nRighttBorderSpacing)
1044 nAdjustedWidth -= nLeftBorderSpacing + nRighttBorderSpacing;
1046 attrList->add( FSNS( XML_w, XML_w), OString::number(nAdjustedWidth));
1047 attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height()));
1049 attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X));
1050 attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y));
1052 OString aXAlign = convertToOOXMLHoriOrient(rHoriOrient.GetHoriOrient(), /*bIsPosToggle=*/false);
1053 OString aYAlign = convertToOOXMLVertOrient(rVertOrient.GetVertOrient());
1054 if (!aXAlign.isEmpty())
1055 attrList->add(FSNS(XML_w, XML_xAlign), aXAlign);
1056 if (!aYAlign.isEmpty())
1057 attrList->add(FSNS(XML_w, XML_yAlign), aYAlign);
1059 sal_Int16 nLeft = pFrameFormat->GetLRSpace().GetLeft();
1060 sal_Int16 nRight = pFrameFormat->GetLRSpace().GetRight();
1061 sal_Int16 nUpper = pFrameFormat->GetULSpace().GetUpper();
1062 sal_Int16 nLower = pFrameFormat->GetULSpace().GetLower();
1064 // To emulate, on import left was ignored (set to zero) if aligned to left,
1065 // so just double up the right spacing in order to prevent cutting in half each round-trip.
1066 if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::LEFT)
1067 nLeft = nRight;
1068 else if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::RIGHT)
1069 nRight = nLeft;
1071 attrList->add(FSNS(XML_w, XML_hSpace), OString::number((nLeft + nRight) / 2));
1072 attrList->add(FSNS(XML_w, XML_vSpace), OString::number((nUpper + nLower) / 2));
1074 OString relativeFromH = convertToOOXMLHoriOrientRel(rHoriOrient.GetRelationOrient());
1075 OString relativeFromV = convertToOOXMLVertOrientRel(rVertOrient.GetRelationOrient());
1077 switch (pFrameFormat->GetSurround().GetValue())
1079 case css::text::WrapTextMode_NONE:
1080 attrList->add( FSNS( XML_w, XML_wrap), "notBeside");
1081 break;
1082 case css::text::WrapTextMode_DYNAMIC:
1083 attrList->add(FSNS(XML_w, XML_wrap), "auto");
1084 break;
1085 case css::text::WrapTextMode_PARALLEL:
1086 default:
1087 attrList->add(FSNS(XML_w, XML_wrap), "around");
1088 break;
1090 attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV );
1091 attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH );
1092 attrList->add( FSNS( XML_w, XML_hRule), "exact");
1094 m_pSerializer->singleElementNS( XML_w, XML_framePr, attrList );
1097 bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat)
1099 SdrObject* pSdrObj = const_cast<SdrObject*>(rFrameFormat.FindRealSdrObject());
1100 if (!pSdrObj)
1101 return false;
1103 uno::Reference<beans::XPropertySet> xPropertySet(pSdrObj->getUnoShape(), uno::UNO_QUERY);
1104 if (!xPropertySet.is())
1105 return false;
1107 uno::Reference<beans::XPropertySetInfo> xPropSetInfo(xPropertySet->getPropertySetInfo());
1108 if (!xPropSetInfo.is() || !xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
1109 return false;
1111 bool bRet = false;
1112 uno::Sequence<beans::PropertyValue> propList;
1113 xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= propList;
1114 auto pProp = std::find_if(std::cbegin(propList), std::cend(propList),
1115 [](const beans::PropertyValue& rProp) { return rProp.Name == "ParaFrameProperties"; });
1116 if (pProp != std::cend(propList))
1117 pProp->Value >>= bRet;
1119 return bRet;
1122 void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
1124 // write the paragraph properties + the run, already in the correct order
1125 m_pSerializer->mergeTopMarks(Tag_StartParagraph_2);
1126 std::vector< std::shared_ptr <ww8::Frame> > aFramePrTextbox;
1127 // Write the anchored frame if any
1128 // Word can't handle nested text boxes, so write them on the same level.
1129 ++m_nTextFrameLevel;
1130 if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1132 comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_aParagraphSdt.m_bStartedSdt, false);
1134 assert(!m_oPostponedCustomShape);
1135 m_oPostponedCustomShape.emplace();
1137 // The for loop can change the size of m_aFramesOfParagraph, so the max size cannot be set in stone before the loop.
1138 size_t nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1139 for (size_t nIndex = 0; nIndex < nFrames; ++nIndex)
1141 m_bParagraphFrameOpen = true;
1142 ww8::Frame aFrame = m_aFramesOfParagraph.top()[nIndex];
1143 const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat();
1145 if (!m_bWritingHeaderFooter && TextBoxIsFramePr(rFrameFormat))
1147 std::shared_ptr<ww8::Frame> pFramePr = std::make_shared<ww8::Frame>(aFrame);
1148 aFramePrTextbox.push_back(pFramePr);
1150 else
1152 if (m_aRunSdt.m_bStartedSdt)
1154 // Run-level SDT still open? Close it before AlternateContent.
1155 m_aRunSdt.EndSdtBlock(m_pSerializer);
1157 m_pSerializer->startElementNS(XML_w, XML_r);
1158 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
1159 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps");
1161 This is to avoid AlternateContent within another AlternateContent.
1162 So when Choice is Open, only write the DML Drawing instead of both DML
1163 and VML Drawing in another AlternateContent.
1165 SetAlternateContentChoiceOpen( true );
1166 /** Save the table info's before writing the shape
1167 as there might be a new table that might get
1168 spawned from within the VML & DML block and alter
1169 the contents.
1171 ww8::WW8TableInfo::Pointer_t pOldTableInfo = m_rExport.m_pTableInfo;
1172 //Reset the table infos after saving.
1173 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1175 /** FDO#71834 :
1176 Save the table reference attributes before calling WriteDMLTextFrame,
1177 otherwise the StartParagraph function will use the previous existing
1178 table reference attributes since the variable is being shared.
1181 DocxTableExportContext aDMLTableExportContext(*this);
1182 m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++);
1184 m_pSerializer->endElementNS(XML_mc, XML_Choice);
1185 SetAlternateContentChoiceOpen( false );
1187 // Reset table infos, otherwise the depth of the cells will be incorrect,
1188 // in case the text frame had table(s) and we try to export the
1189 // same table second time.
1190 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1191 //reset the tableReference.
1193 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
1195 DocxTableExportContext aVMLTableExportContext(*this);
1196 m_rExport.SdrExporter().writeVMLTextFrame(&aFrame);
1198 m_rExport.m_pTableInfo = pOldTableInfo;
1200 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
1201 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
1202 m_pSerializer->endElementNS( XML_w, XML_r );
1203 m_bParagraphFrameOpen = false;
1206 nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1208 if (!m_oPostponedCustomShape->empty())
1210 m_pSerializer->startElementNS(XML_w, XML_r);
1211 WritePostponedCustomShape();
1212 m_pSerializer->endElementNS( XML_w, XML_r );
1214 m_oPostponedCustomShape.reset();
1216 if ( m_aFramesOfParagraph.size() )
1217 m_aFramesOfParagraph.top().clear();
1219 if (!pTextNodeInfoInner)
1221 // Ending a non-table paragraph, clear floating tables before paragraph.
1222 m_aFloatingTablesOfParagraph.clear();
1226 --m_nTextFrameLevel;
1227 if ( m_aFramesOfParagraph.size() && !m_nTextFrameLevel )
1228 m_aFramesOfParagraph.pop();
1230 /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed.
1231 * This is due to nested hyperlink tags. So close it before end of paragraph.
1233 if(m_nHyperLinkCount.back() > 0)
1235 for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount.back(); ++nHyperLinkToClose)
1236 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1238 m_nHyperLinkCount.pop_back();
1240 if (m_aRunSdt.m_bStartedSdt)
1242 // Run-level SDT still open? Close it now.
1243 m_aRunSdt.EndSdtBlock(m_pSerializer);
1246 if (m_bPageBreakAfter)
1248 // tdf#128889 Trailing page break
1249 SectionBreak(msword::PageBreak, false);
1250 m_bPageBreakAfter = false;
1253 m_pSerializer->endElementNS( XML_w, XML_p );
1254 // on export sdt blocks are never nested ATM
1255 if (!m_bAnchorLinkedToNode && !m_aParagraphSdt.m_bStartedSdt)
1257 m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
1259 if (m_aParagraphSdt.m_bStartedSdt)
1261 if (m_tableReference.m_bTableCellOpen)
1262 m_tableReference.m_bTableCellParaSdtOpen = true;
1263 if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1264 m_rExport.SdrExporter().setParagraphSdtOpen(true);
1267 else
1269 //These should be written out to the actual Node and not to the anchor.
1270 //Clear them as they will be repopulated when the node is processed.
1271 m_aParagraphSdt.m_nSdtPrToken = 0;
1272 m_aParagraphSdt.DeleteAndResetTheLists();
1275 m_pSerializer->mark(Tag_StartParagraph_2);
1277 // Write framePr
1278 for ( const auto & pFrame : aFramePrTextbox )
1280 DocxTableExportContext aTableExportContext(*this);
1281 m_aFramePr.SetFrame(pFrame.get(), !m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
1282 m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get());
1283 m_aFramePr.SetFrame(nullptr);
1286 m_pSerializer->mergeTopMarks(Tag_StartParagraph_2, sax_fastparser::MergeMarks::PREPEND);
1288 //sdtcontent is written so Set m_bParagraphHasDrawing to false
1289 m_rExport.SdrExporter().setParagraphHasDrawing(false);
1290 m_bRunTextIsOn = false;
1291 m_pSerializer->mergeTopMarks(Tag_StartParagraph_1);
1293 aFramePrTextbox.clear();
1294 // Check for end of cell, rows, tables here
1295 FinishTableRowCell( pTextNodeInfoInner );
1297 if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1298 m_bParagraphOpened = false;
1300 // Clear bookmarks of the current paragraph
1301 m_aBookmarksOfParagraphStart.clear();
1302 m_aBookmarksOfParagraphEnd.clear();
1305 #define MAX_CELL_IN_WORD 62
1307 void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow)
1309 sal_Int32 nOpenCell = m_LastOpenCell.back();
1310 if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD)
1311 EndTableCell(nOpenCell);
1313 sal_Int32 nClosedCell = m_LastClosedCell.back();
1314 for (sal_Int32 i = nClosedCell+1; i < nCell; ++i)
1316 if (i >= MAX_CELL_IN_WORD)
1317 break;
1319 if (i == 0)
1320 StartTableRow(pInner);
1322 StartTableCell(pInner, i, nRow);
1323 m_pSerializer->singleElementNS(XML_w, XML_p);
1324 EndTableCell(i);
1328 void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph )
1330 if ( !pInner )
1331 return;
1333 // Where are we in the table
1334 sal_uInt32 nRow = pInner->getRow();
1335 sal_Int32 nCell = pInner->getCell();
1337 InitTableHelper( pInner );
1339 // HACK
1340 // msoffice seems to have an internal limitation of 63 columns for tables
1341 // and refuses to load .docx with more, even though the spec seems to allow that;
1342 // so simply if there are more columns, don't close the last one msoffice will handle
1343 // and merge the contents of the remaining ones into it (since we don't close the cell
1344 // here, following ones will not be opened)
1345 const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine());
1346 const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround;
1347 const bool bEndRow = pInner->isEndOfLine();
1349 if (bEndCell)
1351 while (pInner->getDepth() < m_tableReference.m_nTableDepth)
1353 //we expect that the higher depth row was closed, and
1354 //we are just missing the table close
1355 assert(m_LastOpenCell.back() == -1 && m_LastClosedCell.back() == -1);
1356 EndTable();
1359 SyncNodelessCells(pInner, nCell, nRow);
1361 sal_Int32 nClosedCell = m_LastClosedCell.back();
1362 if (nCell == nClosedCell)
1364 //Start missing trailing cell(s)
1365 ++nCell;
1366 StartTableCell(pInner, nCell, nRow);
1368 //Continue on missing next trailing cell(s)
1369 ww8::RowSpansPtr xRowSpans = pInner->getRowSpansOfRow();
1370 sal_Int32 nRemainingCells = xRowSpans->size() - nCell;
1371 for (sal_Int32 i = 1; i < nRemainingCells; ++i)
1373 if (bForceEmptyParagraph)
1375 m_pSerializer->singleElementNS(XML_w, XML_p);
1378 EndTableCell(nCell);
1380 StartTableCell(pInner, nCell, nRow);
1384 if (bForceEmptyParagraph)
1386 m_pSerializer->singleElementNS(XML_w, XML_p);
1389 EndTableCell(nCell);
1392 // This is a line end
1393 if (bEndRow)
1394 EndTableRow();
1396 // This is the end of the table
1397 if (pInner->isFinalEndOfLine())
1398 EndTable();
1401 void DocxAttributeOutput::EmptyParagraph()
1403 m_pSerializer->singleElementNS(XML_w, XML_p);
1406 void DocxAttributeOutput::SectionBreaks(const SwNode& rNode)
1408 // output page/section breaks
1409 // Writer can have them at the beginning of a paragraph, or at the end, but
1410 // in docx, we have to output them in the paragraph properties of the last
1411 // paragraph in a section. To get it right, we have to switch to the next
1412 // paragraph, and detect the section breaks there.
1413 SwNodeIndex aNextIndex( rNode, 1 );
1415 if (rNode.IsTextNode() || rNode.IsSectionNode())
1417 if (aNextIndex.GetNode().IsTextNode())
1419 const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode());
1420 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1422 else if (aNextIndex.GetNode().IsTableNode())
1424 const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1425 const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1426 m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1429 else if (rNode.IsEndNode())
1431 if (aNextIndex.GetNode().IsTextNode())
1433 // Handle section break between a table and a text node following it.
1434 // Also handle section endings
1435 const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode();
1436 if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode())
1437 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1439 else if (aNextIndex.GetNode().IsTableNode())
1441 // Handle section break between tables.
1442 const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1443 const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1444 m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1449 void DocxAttributeOutput::StartParagraphProperties()
1451 m_pSerializer->mark(Tag_StartParagraphProperties);
1453 m_pSerializer->startElementNS(XML_w, XML_pPr);
1455 // and output the section break now (if it appeared)
1456 if (m_pSectionInfo && m_rExport.m_nTextTyp == TXT_MAINTEXT)
1458 m_rExport.SectionProperties( *m_pSectionInfo );
1459 m_pSectionInfo.reset();
1462 InitCollectedParagraphProperties();
1465 void DocxAttributeOutput::InitCollectedParagraphProperties()
1467 m_pLRSpaceAttrList.clear();
1468 m_pParagraphSpacingAttrList.clear();
1470 // Write the elements in the spec order
1471 static const sal_Int32 aOrder[] =
1473 FSNS( XML_w, XML_pStyle ),
1474 FSNS( XML_w, XML_keepNext ),
1475 FSNS( XML_w, XML_keepLines ),
1476 FSNS( XML_w, XML_pageBreakBefore ),
1477 FSNS( XML_w, XML_framePr ),
1478 FSNS( XML_w, XML_widowControl ),
1479 FSNS( XML_w, XML_numPr ),
1480 FSNS( XML_w, XML_suppressLineNumbers ),
1481 FSNS( XML_w, XML_pBdr ),
1482 FSNS( XML_w, XML_shd ),
1483 FSNS( XML_w, XML_tabs ),
1484 FSNS( XML_w, XML_suppressAutoHyphens ),
1485 FSNS( XML_w, XML_kinsoku ),
1486 FSNS( XML_w, XML_wordWrap ),
1487 FSNS( XML_w, XML_overflowPunct ),
1488 FSNS( XML_w, XML_topLinePunct ),
1489 FSNS( XML_w, XML_autoSpaceDE ),
1490 FSNS( XML_w, XML_autoSpaceDN ),
1491 FSNS( XML_w, XML_bidi ),
1492 FSNS( XML_w, XML_adjustRightInd ),
1493 FSNS( XML_w, XML_snapToGrid ),
1494 FSNS( XML_w, XML_spacing ),
1495 FSNS( XML_w, XML_ind ),
1496 FSNS( XML_w, XML_contextualSpacing ),
1497 FSNS( XML_w, XML_mirrorIndents ),
1498 FSNS( XML_w, XML_suppressOverlap ),
1499 FSNS( XML_w, XML_jc ),
1500 FSNS( XML_w, XML_textDirection ),
1501 FSNS( XML_w, XML_textAlignment ),
1502 FSNS( XML_w, XML_textboxTightWrap ),
1503 FSNS( XML_w, XML_outlineLvl ),
1504 FSNS( XML_w, XML_divId ),
1505 FSNS( XML_w, XML_cnfStyle ),
1506 FSNS( XML_w, XML_rPr ),
1507 FSNS( XML_w, XML_sectPr ),
1508 FSNS( XML_w, XML_pPrChange )
1511 // postpone the output so that we can later [in EndParagraphProperties()]
1512 // prepend the properties before the run
1513 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
1514 m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder));
1517 void DocxAttributeOutput::WriteCollectedParagraphProperties()
1519 if ( m_rExport.SdrExporter().getFlyAttrList().is() )
1521 m_pSerializer->singleElementNS( XML_w, XML_framePr,
1522 detachFrom(m_rExport.SdrExporter().getFlyAttrList() ) );
1525 if (m_pLRSpaceAttrList.is())
1527 m_pSerializer->singleElementNS(XML_w, XML_ind, detachFrom(m_pLRSpaceAttrList));
1530 if ( m_pParagraphSpacingAttrList.is() )
1532 m_pSerializer->singleElementNS( XML_w, XML_spacing, detachFrom( m_pParagraphSpacingAttrList ) );
1535 if ( m_pBackgroundAttrList.is() )
1537 m_pSerializer->singleElementNS( XML_w, XML_shd, detachFrom( m_pBackgroundAttrList ) );
1538 m_aFramePr.SetUseFrameBackground(false);
1542 namespace
1545 /// Outputs an item set, that contains the formatting of the paragraph marker.
1546 void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties)
1548 const SfxItemSet* pOldI = rAttributeOutput.GetExport().GetCurItemSet();
1549 rAttributeOutput.GetExport().SetCurItemSet(&rParagraphMarkerProperties);
1551 SfxWhichIter aIter(rParagraphMarkerProperties);
1552 sal_uInt16 nWhichId = aIter.FirstWhich();
1553 const SfxPoolItem* pItem = nullptr;
1554 // Did we already produce a <w:sz> element?
1555 bool bFontSizeWritten = false;
1556 bool bBoldWritten = false;
1557 while (nWhichId)
1559 if (aIter.GetItemState(true, &pItem) == SfxItemState::SET)
1561 if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT)
1563 // Will this item produce a <w:sz> element?
1564 bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE;
1565 bool bBoldItem = nWhichId == RES_CHRATR_WEIGHT || nWhichId == RES_CHRATR_CJK_WEIGHT;
1566 if (!(bFontSizeWritten && bFontSizeItem) && !(bBoldWritten && bBoldItem))
1567 rAttributeOutput.OutputItem(*pItem);
1568 if (bFontSizeItem)
1569 bFontSizeWritten = true;
1570 if (bBoldItem)
1571 bBoldWritten = true;
1573 else if (nWhichId == RES_TXTATR_AUTOFMT)
1575 const SwFormatAutoFormat* pAutoFormat = static_cast<const SwFormatAutoFormat*>(pItem);
1576 lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle());
1579 nWhichId = aIter.NextWhich();
1581 rAttributeOutput.GetExport().SetCurItemSet(pOldI);
1584 const char *RubyAlignValues[] =
1586 "center",
1587 "distributeLetter",
1588 "distributeSpace",
1589 "left",
1590 "right",
1591 "rightVertical"
1595 const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)
1597 const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues);
1598 if ( nJC >=0 && nJC < nElements )
1599 return RubyAlignValues[nJC];
1600 return RubyAlignValues[0];
1605 void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted)
1607 // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties.
1608 // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
1610 // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline().
1611 // As there will be another pPr for redline and LO might mix both.
1612 if(pRedlineData)
1613 WriteCollectedParagraphProperties();
1614 Redline( pRedlineData );
1616 WriteCollectedParagraphProperties();
1618 // Merge the marks for the ordered elements
1619 m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
1621 // Write 'Paragraph Mark' properties
1622 m_pSerializer->startElementNS(XML_w, XML_rPr);
1623 // mark() before paragraph mark properties child elements.
1624 InitCollectedRunProperties();
1626 // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
1627 // that should be collected by different properties in the core, and are all flushed together
1628 // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
1629 // So we need to store the current status of these lists, so that we can revert back to them when
1630 // we are done exporting the redline attributes.
1631 auto pFontsAttrList_Original(detachFrom(m_pFontsAttrList));
1632 auto pEastAsianLayoutAttrList_Original(detachFrom(m_pEastAsianLayoutAttrList));
1633 auto pCharLangAttrList_Original(detachFrom(m_pCharLangAttrList));
1635 lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties);
1637 // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1638 WriteCollectedRunProperties();
1640 // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1641 m_pFontsAttrList = std::move(pFontsAttrList_Original);
1642 m_pEastAsianLayoutAttrList = std::move(pEastAsianLayoutAttrList_Original);
1643 m_pCharLangAttrList = std::move(pCharLangAttrList_Original);
1645 if ( pRedlineParagraphMarkerDeleted )
1647 StartRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1648 EndRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1650 if ( pRedlineParagraphMarkerInserted )
1652 StartRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1653 EndRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1656 // mergeTopMarks() after paragraph mark properties child elements.
1657 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
1658 m_pSerializer->endElementNS( XML_w, XML_rPr );
1660 if (!m_bWritingHeaderFooter && m_aFramePr.Frame())
1662 const SwFrameFormat& rFrameFormat = m_aFramePr.Frame()->GetFrameFormat();
1663 assert(TextBoxIsFramePr(rFrameFormat) && "by definition, because Frame()");
1665 const Size aSize = m_aFramePr.Frame()->GetSize();
1666 PopulateFrameProperties(&rFrameFormat, aSize);
1668 // if the paragraph itself never called FormatBox, do so now
1669 if (m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1670 FormatBox(rFrameFormat.GetBox());
1672 if (m_aFramePr.UseFrameBackground())
1674 // The frame is usually imported as 100% transparent. Ignore in that case.
1675 // Background only exports as fully opaque. Emulate - ignore transparency more than 50%
1676 const SwAttrSet& rSet = rFrameFormat.GetAttrSet();
1677 const XFillStyleItem* pFillStyle(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
1678 if (pFillStyle && pFillStyle->GetValue() != drawing::FillStyle_NONE)
1680 std::unique_ptr<SvxBrushItem> pBrush(
1681 getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND));
1682 if (pBrush->GetColor().GetAlpha() > 127) // more opaque than transparent
1684 FormatBackground(*pBrush);
1685 WriteCollectedParagraphProperties();
1690 if (m_aFramePr.UseFrameTextDirection(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1692 const SvxFrameDirectionItem& rFrameDir = rFrameFormat.GetFrameDir();
1693 if (rFrameDir.GetValue() != SvxFrameDirection::Environment)
1695 assert(!m_rExport.m_bOutPageDescs);
1696 // hack: use existing variable to write out the full TextDirection attribute.
1697 // This is valid for paragraphs/styles - just not native in LO, so hack for now.
1698 m_rExport.m_bOutPageDescs = true;
1699 FormatFrameDirection(rFrameDir);
1700 m_rExport.m_bOutPageDescs = false;
1704 // reset to true in preparation for the next paragraph in the frame
1705 m_aFramePr.SetUseFrameBorders(true);
1706 m_aFramePr.SetUseFrameBackground(true);
1707 m_aFramePr.SetUseFrameTextDirection(true);
1710 m_pSerializer->endElementNS( XML_w, XML_pPr );
1712 // RDF metadata for this text node.
1713 SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
1714 std::map<OUString, OUString> aStatements;
1715 if (pTextNode)
1716 aStatements = SwRDFHelper::getTextNodeStatements("urn:bails", *pTextNode);
1717 if (!aStatements.empty())
1719 m_pSerializer->startElementNS(XML_w, XML_smartTag,
1720 FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
1721 FSNS(XML_w, XML_element), "RDF");
1722 m_pSerializer->startElementNS(XML_w, XML_smartTagPr);
1723 for (const auto& rStatement : aStatements)
1724 m_pSerializer->singleElementNS(XML_w, XML_attr,
1725 FSNS(XML_w, XML_name), rStatement.first,
1726 FSNS(XML_w, XML_val), rStatement.second);
1727 m_pSerializer->endElementNS(XML_w, XML_smartTagPr);
1728 m_pSerializer->endElementNS(XML_w, XML_smartTag);
1731 if ((m_nColBreakStatus == COLBRK_WRITE || m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE)
1732 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1734 m_pSerializer->startElementNS(XML_w, XML_r);
1735 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "column");
1736 m_pSerializer->endElementNS( XML_w, XML_r );
1738 if ( m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE )
1739 m_nColBreakStatus = COLBRK_POSTPONE;
1740 else
1741 m_nColBreakStatus = COLBRK_NONE;
1744 if (m_bPostponedPageBreak && !m_bWritingHeaderFooter
1745 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1747 m_pSerializer->startElementNS(XML_w, XML_r);
1748 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
1749 m_pSerializer->endElementNS( XML_w, XML_r );
1751 m_bPostponedPageBreak = false;
1754 // merge the properties _before_ the run (strictly speaking, just
1755 // after the start of the paragraph)
1756 m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND);
1759 void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame )
1761 m_nStateOfFlyFrame = nStateOfFlyFrame;
1764 void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
1766 m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
1769 void DocxAttributeOutput::ResetFlyProcessingFlag()
1771 m_bPostponedProcessingFly = false ;
1774 bool DocxAttributeOutput::IsFlyProcessingPostponed()
1776 return m_bPostponedProcessingFly;
1779 void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
1781 // Don't start redline data here, possibly there is a hyperlink later, and
1782 // that has to be started first.
1783 m_pRedlineData = pRedlineData;
1785 // this mark is used to be able to enclose the run inside a sdr tag.
1786 m_pSerializer->mark(Tag_StartRun_1);
1788 // postpone the output of the start of a run (there are elements that need
1789 // to be written before the start of the run, but we learn which they are
1790 // _inside_ of the run)
1791 m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start"
1793 // postpone the output of the text (we get it before the run properties,
1794 // but must write it after them)
1795 m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
1798 void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_Int32 nLen, bool bLastRun)
1800 int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
1801 // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
1802 if ( m_pHyperlinkAttrList.is() )
1804 m_nFieldsInHyperlink = 0;
1807 // Write field starts
1808 for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); )
1810 // Add the fields starts for all but hyperlinks and TOCs
1811 if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN &&
1812 // it is not an input field with extra grabbag params (sdt field)
1813 (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))
1816 StartField_Impl( pNode, nPos, *pIt );
1818 // Remove the field from the stack if only the start has to be written
1819 // Unknown fields should be removed too
1820 if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) )
1822 pIt = m_Fields.erase( pIt );
1823 continue;
1826 if (m_nHyperLinkCount.back() > 0 || m_pHyperlinkAttrList.is())
1828 ++m_nFieldsInHyperlink;
1831 ++pIt;
1834 // write the run properties + the text, already in the correct order
1835 m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above
1837 // level down, to be able to prepend the actual run start attribute (just
1838 // before "postponed run start")
1839 m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start"
1840 bool bCloseEarlierSDT = false;
1842 if (m_bEndCharSdt)
1844 // This is the common case: "close sdt before the current run" was requested by the next run.
1846 // if another sdt starts in this run, then wait
1847 // as closing the sdt now, might cause nesting of sdts
1848 if (m_aRunSdt.m_nSdtPrToken > 0)
1849 bCloseEarlierSDT = true;
1850 else
1851 m_aRunSdt.EndSdtBlock(m_pSerializer);
1852 m_bEndCharSdt = false;
1855 if ( m_closeHyperlinkInPreviousRun )
1857 if (m_nHyperLinkCount.back() > 0)
1859 for ( int i = 0; i < nFieldsInPrevHyperlink; i++ )
1861 // If fields begin before hyperlink then
1862 // it should end before hyperlink close
1863 EndField_Impl( pNode, nPos, m_Fields.back( ) );
1864 m_Fields.pop_back();
1866 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1867 m_endPageRef = false;
1868 m_nHyperLinkCount.back()--;
1869 m_closeHyperlinkInPreviousRun = false;
1871 else
1873 bool bIsStartedHyperlink = false;
1874 for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
1876 if (nLinkCount > 0)
1878 bIsStartedHyperlink = true;
1879 break;
1882 if (!bIsStartedHyperlink)
1883 m_closeHyperlinkInPreviousRun = false;
1887 // Write the hyperlink and toc fields starts
1888 for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); )
1890 // Add the fields starts for hyperlinks, TOCs and index marks
1891 if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN ||
1892 // InputField with extra grabbag params - it is sdt field
1893 (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())))
1895 StartRedline( m_pRedlineData, bLastRun );
1896 StartField_Impl( pNode, nPos, *pIt, true );
1897 EndRedline( m_pRedlineData, bLastRun );
1899 if (m_nHyperLinkCount.back() > 0)
1900 ++m_nFieldsInHyperlink;
1902 // Remove the field if no end needs to be written
1903 if (!pIt->bSep)
1905 pIt = m_Fields.erase( pIt );
1906 continue;
1909 if (pIt->bSep && !pIt->pField)
1911 // for TOXMark:
1912 // Word ignores bookmarks in field result that is empty;
1913 // work around this by writing bookmark into field command.
1914 if (!m_sFieldBkm.isEmpty())
1916 DoWriteBookmarkTagStart(m_sFieldBkm);
1917 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
1918 m_nNextBookmarkId++;
1919 m_sFieldBkm.clear();
1921 CmdEndField_Impl(pNode, nPos, true);
1922 // Remove the field if no end needs to be written
1923 if (!pIt->bClose)
1925 pIt = m_Fields.erase( pIt );
1926 continue;
1929 ++pIt;
1932 // Start the hyperlink after the fields separators or we would generate invalid file
1933 bool newStartedHyperlink(false);
1934 if ( m_pHyperlinkAttrList.is() )
1936 // if we are ending a hyperlink and there's another one starting here,
1937 // don't do this, so that the fields are closed further down when
1938 // the end hyperlink is handled, which is more likely to put the end in
1939 // the right place, as far as i can tell (not very far in this muck)
1940 if (!m_closeHyperlinkInThisRun)
1942 // end ToX fields that want to end _before_ starting the hyperlink
1943 for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
1945 if (it->bClose && !it->pField)
1947 EndField_Impl( pNode, nPos, *it );
1948 it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
1950 else
1952 ++it;
1956 newStartedHyperlink = true;
1958 m_pSerializer->startElementNS( XML_w, XML_hyperlink, detachFrom( m_pHyperlinkAttrList ) );
1959 m_nHyperLinkCount.back()++;
1962 // if there is some redlining in the document, output it
1963 StartRedline( m_pRedlineData, bLastRun );
1965 // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks.
1966 // The same is applied for permission ranges.
1967 // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end.
1968 DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData);
1969 DoWriteBookmarksEnd(m_rBookmarksEnd);
1970 DoWritePermissionsStart();
1971 DoWriteAnnotationMarks();
1973 if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
1974 && m_hyperLinkAnchor.startsWith("_Toc"))
1976 OUString sToken;
1977 m_pSerializer->startElementNS(XML_w, XML_r);
1978 m_pSerializer->startElementNS(XML_w, XML_rPr);
1979 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1980 m_pSerializer->endElementNS( XML_w, XML_rPr );
1981 m_pSerializer->startElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "begin");
1982 m_pSerializer->endElementNS( XML_w, XML_fldChar );
1983 m_pSerializer->endElementNS( XML_w, XML_r );
1986 m_pSerializer->startElementNS(XML_w, XML_r);
1987 m_pSerializer->startElementNS(XML_w, XML_rPr);
1988 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1989 m_pSerializer->endElementNS( XML_w, XML_rPr );
1990 sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph.
1991 DoWriteCmd( sToken );
1992 m_pSerializer->endElementNS( XML_w, XML_r );
1994 // Write the Field separator
1995 m_pSerializer->startElementNS(XML_w, XML_r);
1996 m_pSerializer->startElementNS(XML_w, XML_rPr);
1997 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1998 m_pSerializer->endElementNS( XML_w, XML_rPr );
1999 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2000 FSNS( XML_w, XML_fldCharType ), "separate" );
2001 m_pSerializer->endElementNS( XML_w, XML_r );
2002 // At start of every "PAGEREF" field m_endPageRef value should be true.
2003 m_endPageRef = true;
2006 DoWriteBookmarkStartIfExist(nPos);
2008 if (nLen != -1)
2010 SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2011 if (pAttr && pAttr->GetStart() == nPos)
2013 auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
2014 m_pContentControl = pTextContentControl->GetContentControl().GetContentControl();
2015 if (!m_tableReference.m_bTableCellChanged)
2017 WriteContentControlStart();
2022 m_pSerializer->startElementNS(XML_w, XML_r);
2023 if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is())
2025 RunText("\t") ;
2027 m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above
2029 if ( !m_sRawText.isEmpty() )
2031 RunText( m_sRawText );
2032 m_sRawText.clear();
2035 // write the run start + the run content
2036 m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start"
2037 // append the actual run end
2038 m_pSerializer->endElementNS( XML_w, XML_r );
2040 if (nLen != -1)
2042 sal_Int32 nEnd = nPos + nLen;
2043 SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2044 if (pAttr && *pAttr->GetEnd() == nEnd && !m_tableReference.m_bTableCellChanged)
2046 WriteContentControlEnd();
2050 // if there is some redlining in the document, output it
2051 // (except in the case of fields with multiple runs)
2052 EndRedline( m_pRedlineData, bLastRun );
2054 // enclose in a sdt block, if necessary: if one is already started, then don't do it for now
2055 // (so on export sdt blocks are never nested ATM)
2056 if ( !m_bAnchorLinkedToNode && !m_aRunSdt.m_bStartedSdt)
2058 m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
2060 else
2062 //These should be written out to the actual Node and not to the anchor.
2063 //Clear them as they will be repopulated when the node is processed.
2064 m_aRunSdt.m_nSdtPrToken = 0;
2065 m_aRunSdt.DeleteAndResetTheLists();
2068 if (bCloseEarlierSDT)
2070 m_pSerializer->mark(Tag_EndRun_2);
2071 m_aRunSdt.EndSdtBlock(m_pSerializer);
2072 m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND);
2075 m_pSerializer->mergeTopMarks(Tag_StartRun_1);
2077 // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission.
2078 DoWritePermissionsEnd();
2080 for (const auto& rpMath : m_aPostponedMaths)
2081 WritePostponedMath(rpMath.pMathObject, rpMath.nMathObjAlignment);
2082 m_aPostponedMaths.clear();
2084 for (const auto& rpControl : m_aPostponedFormControls)
2085 WritePostponedFormControl(rpControl);
2086 m_aPostponedFormControls.clear();
2088 WritePostponedActiveXControl(false);
2090 WritePendingPlaceholder();
2092 if ( !m_bWritingField )
2094 m_pRedlineData = nullptr;
2097 if ( m_closeHyperlinkInThisRun )
2099 if (m_nHyperLinkCount.back() > 0)
2101 if( m_endPageRef )
2103 // Hyperlink is started and fldchar "end" needs to be written for PAGEREF
2104 m_pSerializer->startElementNS(XML_w, XML_r);
2105 m_pSerializer->startElementNS(XML_w, XML_rPr);
2106 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2107 m_pSerializer->endElementNS( XML_w, XML_rPr );
2108 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2109 FSNS( XML_w, XML_fldCharType ), "end" );
2110 m_pSerializer->endElementNS( XML_w, XML_r );
2111 m_endPageRef = false;
2112 m_hyperLinkAnchor.clear();
2114 for ( int i = 0; i < m_nFieldsInHyperlink; i++ )
2116 // If fields begin after hyperlink start then
2117 // it should end before hyperlink close
2118 EndField_Impl( pNode, nPos, m_Fields.back( ) );
2119 m_Fields.pop_back();
2121 m_nFieldsInHyperlink = 0;
2123 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
2124 m_nHyperLinkCount.back()--;
2125 m_closeHyperlinkInThisRun = false;
2127 else
2129 bool bIsStartedHyperlink = false;
2130 for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
2132 if (nLinkCount > 0)
2134 bIsStartedHyperlink = true;
2135 break;
2138 if (!bIsStartedHyperlink)
2139 m_closeHyperlinkInThisRun = false;
2143 if (!newStartedHyperlink)
2145 while ( m_Fields.begin() != m_Fields.end() )
2147 EndField_Impl( pNode, nPos, m_Fields.front( ) );
2148 m_Fields.erase( m_Fields.begin( ) );
2150 m_nFieldsInHyperlink = 0;
2153 // end ToX fields
2154 for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
2156 if (it->bClose && !it->pField)
2158 EndField_Impl( pNode, nPos, *it );
2159 it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
2161 else
2163 ++it;
2167 if ( m_pRedlineData )
2169 EndRedline( m_pRedlineData, bLastRun );
2170 m_pRedlineData = nullptr;
2173 DoWriteBookmarksStart(m_rFinalBookmarksStart);
2174 DoWriteBookmarksEnd(m_rFinalBookmarksEnd);
2175 DoWriteBookmarkEndIfExist(nPos);
2178 void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString& bookmarkName)
2180 m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart,
2181 FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
2182 FSNS(XML_w, XML_name), GetExport().BookmarkToWord(bookmarkName));
2185 void DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId)
2187 m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd,
2188 FSNS(XML_w, XML_id), OString::number(nId));
2191 void DocxAttributeOutput::DoWriteMoveRangeTagStart(std::u16string_view bookmarkName,
2192 bool bFrom, const SwRedlineData* pRedlineData)
2194 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
2195 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
2197 const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
2198 const DateTime aDateTime = pRedlineData->GetTimeStamp();
2199 bool bNoDate = bRemovePersonalInfo ||
2200 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
2202 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
2203 = sax_fastparser::FastSerializerHelper::createAttrList();
2205 pAttributeList->add(FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId));
2206 pAttributeList->add(FSNS(XML_w, XML_author ), bRemovePersonalInfo
2207 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
2208 : OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8));
2209 if (!bNoDate)
2210 pAttributeList->add(FSNS(XML_w, XML_date ), DateTimeToOString( aDateTime ));
2211 pAttributeList->add(FSNS(XML_w, XML_name), bookmarkName);
2212 m_pSerializer->singleElementNS( XML_w, bFrom ? XML_moveFromRangeStart : XML_moveToRangeStart, pAttributeList );
2214 // tdf#150166 avoid of unpaired moveRangeEnd at moved ToC
2215 m_rSavedBookmarksIds.insert(m_nNextBookmarkId);
2218 void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool bFrom)
2220 if ( m_rSavedBookmarksIds.count(nId) )
2222 m_pSerializer->singleElementNS(XML_w, bFrom
2223 ? XML_moveFromRangeEnd
2224 : XML_moveToRangeEnd,
2225 FSNS(XML_w, XML_id), OString::number(nId));
2227 m_rSavedBookmarksIds.erase(nId);
2231 void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
2233 auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
2234 for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2236 DoWriteBookmarkTagStart(aIter->second);
2237 m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId;
2238 m_sLastOpenedBookmark = GetExport().BookmarkToWord(aIter->second);
2239 m_nNextBookmarkId++;
2243 void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
2245 auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
2246 for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2248 // Get the id of the bookmark
2249 auto pPos = m_rOpenedBookmarksIds.find(aIter->second);
2250 if (pPos != m_rOpenedBookmarksIds.end())
2252 // Output the bookmark
2253 DoWriteBookmarkTagEnd(pPos->second);
2254 m_rOpenedBookmarksIds.erase(aIter->second);
2259 /// Write the start bookmarks
2260 void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData)
2262 for (const OUString & bookmarkName : rStarts)
2264 // Output the bookmark (including MoveBookmark of the tracked moving)
2265 bool bMove = false;
2266 bool bFrom = false;
2267 OUString sBookmarkName = GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2268 if ( bMove )
2270 // TODO: redline data of MoveBookmark is restored from the first redline of the bookmark
2271 // range. But a later deletion within a tracked moving is still imported as plain
2272 // deletion, so check IsMoved() and skip the export of the tracked moving to avoid
2273 // export with bad author or date
2274 if ( pRedlineData && pRedlineData->IsMoved() )
2275 DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData);
2277 else
2278 DoWriteBookmarkTagStart(bookmarkName);
2280 m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
2281 m_sLastOpenedBookmark = sBookmarkName;
2282 m_nNextBookmarkId++;
2284 rStarts.clear();
2287 /// export the end bookmarks
2288 void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
2290 for (const OUString & bookmarkName : rEnds)
2292 // Get the id of the bookmark
2293 auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
2295 if (pPos != m_rOpenedBookmarksIds.end())
2297 bool bMove = false;
2298 bool bFrom = false;
2299 GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2300 // Output the bookmark (including MoveBookmark of the tracked moving)
2301 if ( bMove )
2302 DoWriteMoveRangeTagEnd(pPos->second, bFrom);
2303 else
2304 DoWriteBookmarkTagEnd(pPos->second);
2306 m_rOpenedBookmarksIds.erase(bookmarkName);
2309 rEnds.clear();
2312 // For construction of the special bookmark name template for permissions:
2313 // see, PermInsertPosition::createBookmarkName()
2315 // Syntax:
2316 // - "permission-for-user:<permission-id>:<permission-user-name>"
2317 // - "permission-for-group:<permission-id>:<permission-group-name>"
2319 void DocxAttributeOutput::DoWritePermissionTagStart(std::u16string_view permission)
2321 std::u16string_view permissionIdAndName;
2323 if (o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName))
2325 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2326 assert(separatorIndex != std::u16string_view::npos);
2327 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2328 const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2330 m_pSerializer->singleElementNS(XML_w, XML_permStart,
2331 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2332 FSNS(XML_w, XML_edGrp), GetExport().BookmarkToWord(permissionName));
2334 else
2336 auto const ok = o3tl::starts_with(
2337 permission, u"permission-for-user:", &permissionIdAndName);
2338 assert(ok); (void)ok;
2339 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2340 assert(separatorIndex != std::u16string_view::npos);
2341 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2342 const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2344 m_pSerializer->singleElementNS(XML_w, XML_permStart,
2345 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2346 FSNS(XML_w, XML_ed), GetExport().BookmarkToWord(permissionName));
2351 // For construction of the special bookmark name template for permissions:
2352 // see, PermInsertPosition::createBookmarkName()
2354 // Syntax:
2355 // - "permission-for-user:<permission-id>:<permission-user-name>"
2356 // - "permission-for-group:<permission-id>:<permission-group-name>"
2358 void DocxAttributeOutput::DoWritePermissionTagEnd(std::u16string_view permission)
2360 std::u16string_view permissionIdAndName;
2362 auto const ok = o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName) ||
2363 o3tl::starts_with(permission, u"permission-for-user:", &permissionIdAndName);
2364 assert(ok); (void)ok;
2366 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2367 assert(separatorIndex != std::u16string_view::npos);
2368 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2370 m_pSerializer->singleElementNS(XML_w, XML_permEnd,
2371 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId));
2374 /// Write the start permissions
2375 void DocxAttributeOutput::DoWritePermissionsStart()
2377 for (const OUString & permission : m_rPermissionsStart)
2379 DoWritePermissionTagStart(permission);
2381 m_rPermissionsStart.clear();
2384 /// export the end permissions
2385 void DocxAttributeOutput::DoWritePermissionsEnd()
2387 for (const OUString & permission : m_rPermissionsEnd)
2389 DoWritePermissionTagEnd(permission);
2391 m_rPermissionsEnd.clear();
2394 void DocxAttributeOutput::DoWriteAnnotationMarks()
2396 // Write the start annotation marks
2397 for ( const auto & rName : m_rAnnotationMarksStart )
2399 // Output the annotation mark
2400 /* Ensure that the existing Annotation Marks are not overwritten
2401 as it causes discrepancy when DocxAttributeOutput::PostitField
2402 refers to this map & while mapping comment id's in document.xml &
2403 comment.xml.
2405 if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) )
2407 const sal_Int32 nId = m_nNextAnnotationMarkId++;
2408 m_rOpenedAnnotationMarksIds[rName] = nId;
2409 m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart,
2410 FSNS( XML_w, XML_id ), OString::number(nId) );
2411 m_sLastOpenedAnnotationMark = rName;
2414 m_rAnnotationMarksStart.clear();
2416 // export the end annotation marks
2417 for ( const auto & rName : m_rAnnotationMarksEnd )
2419 // Get the id of the annotation mark
2420 auto pPos = m_rOpenedAnnotationMarksIds.find( rName );
2421 if ( pPos != m_rOpenedAnnotationMarksIds.end( ) )
2423 const sal_Int32 nId = ( *pPos ).second;
2424 m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd,
2425 FSNS( XML_w, XML_id ), OString::number(nId) );
2426 m_rOpenedAnnotationMarksIds.erase( rName );
2428 m_pSerializer->startElementNS(XML_w, XML_r);
2429 m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ),
2430 OString::number(nId) );
2431 m_pSerializer->endElementNS(XML_w, XML_r);
2434 m_rAnnotationMarksEnd.clear();
2437 void DocxAttributeOutput::WriteFFData( const FieldInfos& rInfos )
2439 const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark;
2440 FieldMarkParamsHelper params( rFieldmark );
2442 OUString sEntryMacro;
2443 params.extractParam("EntryMacro", sEntryMacro);
2444 OUString sExitMacro;
2445 params.extractParam("ExitMacro", sExitMacro);
2446 OUString sHelp;
2447 params.extractParam("Help", sHelp);
2448 OUString sHint;
2449 params.extractParam("Hint", sHint); // .docx StatusText
2450 if ( sHint.isEmpty() )
2451 params.extractParam("Description", sHint); // .doc StatusText
2453 if ( rInfos.eType == ww::eFORMDROPDOWN )
2455 uno::Sequence< OUString> vListEntries;
2456 OUString sName, sSelected;
2458 params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries );
2459 if (vListEntries.getLength() > ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT)
2460 vListEntries = uno::Sequence< OUString>(vListEntries.getArray(), ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT);
2462 sName = params.getName();
2463 sal_Int32 nSelectedIndex = 0;
2465 if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) )
2467 if (nSelectedIndex < vListEntries.getLength() )
2468 sSelected = vListEntries[ nSelectedIndex ];
2471 GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries );
2473 else if ( rInfos.eType == ww::eFORMCHECKBOX )
2475 const OUString sName = params.getName();
2476 bool bChecked = false;
2478 const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::ICheckboxFieldmark*>(&rFieldmark);
2479 if ( pCheckboxFm && pCheckboxFm->IsChecked() )
2480 bChecked = true;
2482 FFDataWriterHelper ffdataOut( m_pSerializer );
2483 ffdataOut.WriteFormCheckbox( sName, sEntryMacro, sExitMacro, sHelp, sHint, bChecked );
2485 else if ( rInfos.eType == ww::eFORMTEXT )
2487 OUString sType;
2488 params.extractParam("Type", sType);
2489 OUString sDefaultText;
2490 params.extractParam("Content", sDefaultText);
2491 sal_uInt16 nMaxLength = 0;
2492 params.extractParam("MaxLength", nMaxLength);
2493 OUString sFormat;
2494 params.extractParam("Format", sFormat);
2495 FFDataWriterHelper ffdataOut( m_pSerializer );
2496 ffdataOut.WriteFormText( params.getName(), sEntryMacro, sExitMacro, sHelp, sHint,
2497 sType, sDefaultText, nMaxLength, sFormat );
2501 void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2503 m_pSerializer->startElementNS(XML_w, XML_sdt);
2504 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2506 if(!sFullDate.isEmpty())
2507 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sFullDate);
2508 else
2509 m_pSerializer->startElementNS(XML_w, XML_date);
2511 // Replace quotation mark used for marking static strings in date format
2512 OUString sDateFormat1 = sDateFormat.replaceAll("\"", "'");
2513 m_pSerializer->singleElementNS(XML_w, XML_dateFormat,
2514 FSNS(XML_w, XML_val), sDateFormat1);
2515 m_pSerializer->singleElementNS(XML_w, XML_lid,
2516 FSNS(XML_w, XML_val), sLang);
2517 m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
2518 FSNS(XML_w, XML_val), "dateTime");
2519 m_pSerializer->singleElementNS(XML_w, XML_calendar,
2520 FSNS(XML_w, XML_val), "gregorian");
2521 m_pSerializer->endElementNS(XML_w, XML_date);
2523 if (aGrabBagSdt.hasElements())
2525 // There are some extra sdt parameters came from grab bag
2526 SdtBlockHelper aSdtBlock;
2527 aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2528 aSdtBlock.WriteExtraParams(m_pSerializer);
2531 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2533 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2536 void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2538 m_pSerializer->startElementNS(XML_w, XML_sdt);
2539 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2541 if (aGrabBagSdt.hasElements())
2543 // There are some extra sdt parameters came from grab bag
2544 SdtBlockHelper aSdtBlock;
2545 aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2546 aSdtBlock.WriteExtraParams(m_pSerializer);
2548 if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id))
2550 // Write <w:text/> or whatsoever from grabbag
2551 m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken);
2554 // Store databindings data for later writing to corresponding XMLs
2555 OUString sPrefixMapping, sXpath;
2556 for (const auto& rProp : std::as_const(aGrabBagSdt))
2558 if (rProp.Name == "ooxml:CT_SdtPr_dataBinding")
2560 uno::Sequence<beans::PropertyValue> aDataBindingProps;
2561 rProp.Value >>= aDataBindingProps;
2562 for (const auto& rDBProp : std::as_const(aDataBindingProps))
2564 if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings")
2565 sPrefixMapping = rDBProp.Value.get<OUString>();
2566 else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath")
2567 sXpath = rDBProp.Value.get<OUString>();
2572 if (sXpath.getLength())
2574 // Given xpath is sufficient
2575 m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue);
2579 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2581 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2584 void DocxAttributeOutput::WriteContentControlStart()
2586 if (!m_pContentControl)
2588 return;
2591 m_pSerializer->startElementNS(XML_w, XML_sdt);
2592 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2593 if (!m_pContentControl->GetPlaceholderDocPart().isEmpty())
2595 m_pSerializer->startElementNS(XML_w, XML_placeholder);
2596 m_pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val),
2597 m_pContentControl->GetPlaceholderDocPart());
2598 m_pSerializer->endElementNS(XML_w, XML_placeholder);
2601 if (!m_pContentControl->GetDataBindingPrefixMappings().isEmpty() || !m_pContentControl->GetDataBindingXpath().isEmpty() || !m_pContentControl->GetDataBindingStoreItemID().isEmpty())
2603 m_pSerializer->singleElementNS( XML_w, XML_dataBinding,
2604 FSNS(XML_w, XML_prefixMappings), m_pContentControl->GetDataBindingPrefixMappings(),
2605 FSNS(XML_w, XML_xpath), m_pContentControl->GetDataBindingXpath(),
2606 FSNS(XML_w, XML_storeItemID), m_pContentControl->GetDataBindingStoreItemID());
2609 if (!m_pContentControl->GetColor().isEmpty())
2611 m_pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val),
2612 m_pContentControl->GetColor());
2615 if (!m_pContentControl->GetAppearance().isEmpty())
2617 m_pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val),
2618 m_pContentControl->GetAppearance());
2621 if (!m_pContentControl->GetAlias().isEmpty())
2623 m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val),
2624 m_pContentControl->GetAlias());
2627 if (!m_pContentControl->GetTag().isEmpty())
2629 m_pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val),
2630 m_pContentControl->GetTag());
2633 if (m_pContentControl->GetId())
2635 m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
2636 OString::number(m_pContentControl->GetId()));
2639 if (m_pContentControl->GetTabIndex())
2641 // write the unsigned value as if it were signed since that is all we can import
2642 const sal_Int32 nTabIndex = static_cast<sal_Int32>(m_pContentControl->GetTabIndex());
2643 m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
2644 OString::number(nTabIndex));
2647 if (!m_pContentControl->GetLock().isEmpty())
2649 m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val),
2650 m_pContentControl->GetLock());
2653 if (m_pContentControl->GetShowingPlaceHolder())
2655 m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
2658 if (m_pContentControl->GetPicture())
2660 m_pSerializer->singleElementNS(XML_w, XML_picture);
2663 if (m_pContentControl->GetCheckbox())
2665 m_pSerializer->startElementNS(XML_w14, XML_checkbox);
2666 m_pSerializer->singleElementNS(XML_w14, XML_checked, FSNS(XML_w14, XML_val),
2667 OString::number(int(m_pContentControl->GetChecked())));
2668 OUString aCheckedState = m_pContentControl->GetCheckedState();
2669 if (!aCheckedState.isEmpty())
2671 m_pSerializer->singleElementNS(XML_w14, XML_checkedState, FSNS(XML_w14, XML_val),
2672 OString::number(aCheckedState[0], /*radix=*/16));
2674 OUString aUncheckedState = m_pContentControl->GetUncheckedState();
2675 if (!aUncheckedState.isEmpty())
2677 m_pSerializer->singleElementNS(XML_w14, XML_uncheckedState, FSNS(XML_w14, XML_val),
2678 OString::number(aUncheckedState[0], /*radix=*/16));
2680 m_pSerializer->endElementNS(XML_w14, XML_checkbox);
2683 if (m_pContentControl->GetComboBox() || m_pContentControl->GetDropDown())
2685 if (m_pContentControl->GetComboBox())
2687 m_pSerializer->startElementNS(XML_w, XML_comboBox);
2689 else
2691 m_pSerializer->startElementNS(XML_w, XML_dropDownList);
2693 for (const auto& rItem : m_pContentControl->GetListItems())
2695 rtl::Reference<FastAttributeList> xAttributes = FastSerializerHelper::createAttrList();
2696 if (!rItem.m_aDisplayText.isEmpty())
2698 // If there is no display text, need to omit the attribute, not write an empty one.
2699 xAttributes->add(FSNS(XML_w, XML_displayText), rItem.m_aDisplayText);
2701 xAttributes->add(FSNS(XML_w, XML_value), rItem.m_aValue);
2702 m_pSerializer->singleElementNS(XML_w, XML_listItem, xAttributes);
2704 if (m_pContentControl->GetComboBox())
2706 m_pSerializer->endElementNS(XML_w, XML_comboBox);
2708 else
2710 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2714 if (m_pContentControl->GetDate())
2716 OUString aCurrentDate = m_pContentControl->GetCurrentDate();
2717 if (aCurrentDate.isEmpty())
2719 m_pSerializer->startElementNS(XML_w, XML_date);
2721 else
2723 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), aCurrentDate);
2725 OUString aDateFormat = m_pContentControl->GetDateFormat().replaceAll("\"", "'");
2726 if (!aDateFormat.isEmpty())
2728 m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val),
2729 aDateFormat);
2731 OUString aDateLanguage = m_pContentControl->GetDateLanguage();
2732 if (!aDateLanguage.isEmpty())
2734 m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val),
2735 aDateLanguage);
2737 m_pSerializer->endElementNS(XML_w, XML_date);
2740 if (m_pContentControl->GetPlainText())
2742 m_pSerializer->singleElementNS(XML_w, XML_text);
2745 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2746 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2748 const OUString& rPrefixMapping = m_pContentControl->GetDataBindingPrefixMappings();
2749 const OUString& rXpath = m_pContentControl->GetDataBindingXpath();
2750 if (!rXpath.isEmpty())
2752 // This content control has a data binding, update the data source.
2753 SwTextContentControl* pTextAttr = m_pContentControl->GetTextAttr();
2754 SwTextNode* pTextNode = m_pContentControl->GetTextNode();
2755 if (pTextNode && pTextAttr)
2757 SwPosition aPoint(*pTextNode, pTextAttr->GetStart());
2758 SwPosition aMark(*pTextNode, *pTextAttr->GetEnd());
2759 SwPaM aPam(aMark, aPoint);
2760 OUString aSnippet = aPam.GetText();
2761 static sal_Unicode const aForbidden[] = {
2762 CH_TXTATR_BREAKWORD,
2765 aSnippet = comphelper::string::removeAny(aSnippet, aForbidden);
2766 m_rExport.AddSdtData(rPrefixMapping, rXpath, aSnippet);
2770 m_pContentControl = nullptr;
2773 void DocxAttributeOutput::WriteContentControlEnd()
2775 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
2776 m_pSerializer->endElementNS(XML_w, XML_sdt);
2779 void DocxAttributeOutput::WriteSdtDropDownStart(
2780 OUString const& rName,
2781 OUString const& rSelected,
2782 uno::Sequence<OUString> const& rListItems)
2784 m_pSerializer->startElementNS(XML_w, XML_sdt);
2785 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2787 m_pSerializer->singleElementNS(XML_w, XML_alias,
2788 FSNS(XML_w, XML_val), rName);
2790 sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2791 if (nId == -1)
2793 nId = 0;
2796 m_pSerializer->startElementNS(XML_w, XML_dropDownList,
2797 FSNS(XML_w, XML_lastValue), OString::number(nId));
2799 for (auto const& rItem : rListItems)
2801 auto const item(OUStringToOString(rItem, RTL_TEXTENCODING_UTF8));
2802 m_pSerializer->singleElementNS(XML_w, XML_listItem,
2803 FSNS(XML_w, XML_value), item,
2804 FSNS(XML_w, XML_displayText), item);
2807 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2808 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2810 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2813 void DocxAttributeOutput::WriteSdtDropDownEnd(OUString const& rSelected,
2814 uno::Sequence<OUString> const& rListItems)
2816 // note: rSelected might be empty?
2817 sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2818 if (nId == -1)
2820 nId = 0;
2823 // the lastValue only identifies the entry in the list, also export
2824 // currently selected item's displayText as run content (if one exists)
2825 if (rListItems.size())
2827 m_pSerializer->startElementNS(XML_w, XML_r);
2828 m_pSerializer->startElementNS(XML_w, XML_t);
2829 m_pSerializer->writeEscaped(rListItems[nId]);
2830 m_pSerializer->endElementNS(XML_w, XML_t);
2831 m_pSerializer->endElementNS(XML_w, XML_r);
2834 WriteContentControlEnd();
2837 void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2839 if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN )
2841 // Expand unsupported fields
2842 RunText(rInfos.pField->ExpandField(/*bCached=*/true, nullptr));
2843 return;
2845 else if ( rInfos.eType == ww::eFORMDATE )
2847 const sw::mark::IDateFieldmark& rFieldmark = dynamic_cast<const sw::mark::IDateFieldmark&>(*rInfos.pFieldmark);
2848 FieldMarkParamsHelper params(rFieldmark);
2850 OUString sFullDate;
2851 OUString sCurrentDate;
2852 params.extractParam( ODF_FORMDATE_CURRENTDATE, sCurrentDate );
2853 if(!sCurrentDate.isEmpty())
2855 sFullDate = sCurrentDate + "T00:00:00Z";
2857 else
2859 std::pair<bool, double> aResult = rFieldmark.GetCurrentDate();
2860 if(aResult.first)
2862 sFullDate = rFieldmark.GetDateInStandardDateFormat(aResult.second) + "T00:00:00Z";
2866 OUString sDateFormat;
2867 params.extractParam( ODF_FORMDATE_DATEFORMAT, sDateFormat );
2868 OUString sLang;
2869 params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang );
2871 uno::Sequence<beans::PropertyValue> aSdtParams;
2872 params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams);
2874 WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams);
2875 return;
2877 else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
2879 assert(!rInfos.pFieldmark);
2880 SwDropDownField const& rField2(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
2881 WriteSdtDropDownStart(rField2.GetName(),
2882 rField2.GetSelectedItem(),
2883 rField2.GetItemSequence());
2884 return;
2886 else if (rInfos.eType == ww::eFILLIN)
2888 SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
2889 if (rField.getGrabBagParams().hasElements())
2891 WriteSdtPlainText(rField.GetPar1(), rField.getGrabBagParams());
2892 m_sRawText = rField.GetPar1(); // Write field content also as a fallback
2893 return;
2897 if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands
2899 if ( bWriteRun )
2900 m_pSerializer->startElementNS(XML_w, XML_r);
2902 if ( rInfos.eType == ww::eFORMDROPDOWN )
2904 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2905 FSNS( XML_w, XML_fldCharType ), "begin" );
2906 assert( rInfos.pFieldmark && !rInfos.pField );
2907 WriteFFData(rInfos);
2908 m_pSerializer->endElementNS( XML_w, XML_fldChar );
2910 if ( bWriteRun )
2911 m_pSerializer->endElementNS( XML_w, XML_r );
2913 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2915 else
2917 // Write the field start
2918 if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD )
2920 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2921 FSNS( XML_w, XML_fldCharType ), "begin",
2922 FSNS( XML_w, XML_fldLock ), "true" );
2924 else
2926 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2927 FSNS( XML_w, XML_fldCharType ), "begin" );
2930 if ( rInfos.pFieldmark )
2931 WriteFFData( rInfos );
2933 m_pSerializer->endElementNS( XML_w, XML_fldChar );
2935 if ( bWriteRun )
2936 m_pSerializer->endElementNS( XML_w, XML_r );
2938 // The hyperlinks fields can't be expanded: the value is
2939 // normally in the text run
2940 if ( !rInfos.pField )
2941 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2942 else
2943 m_bWritingField = true;
2948 void DocxAttributeOutput::DoWriteCmd( std::u16string_view rCmd )
2950 std::u16string_view sCmd = o3tl::trim(rCmd);
2951 if (o3tl::starts_with(sCmd, u"SEQ"))
2953 OUString sSeqName( o3tl::trim(msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\')) );
2954 m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark);
2956 // Write the Field command
2957 sal_Int32 nTextToken = XML_instrText;
2958 if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
2959 nTextToken = XML_delInstrText;
2961 m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
2962 m_pSerializer->writeEscaped( rCmd );
2963 m_pSerializer->endElementNS( XML_w, nTextToken );
2967 void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2969 // Write the Field instruction
2970 if ( bWriteRun )
2972 bool bWriteCombChars(false);
2973 m_pSerializer->startElementNS(XML_w, XML_r);
2975 if (rInfos.eType == ww::eEQ)
2976 bWriteCombChars = true;
2978 DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars );
2981 sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 };
2982 while ( nIdx >= 0 )
2984 OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx );
2985 if ( rInfos.eType == ww::eCREATEDATE
2986 || rInfos.eType == ww::eSAVEDATE
2987 || rInfos.eType == ww::ePRINTDATE
2988 || rInfos.eType == ww::eDATE
2989 || rInfos.eType == ww::eTIME )
2991 sToken = sToken.replaceAll("NNNN", "dddd");
2992 sToken = sToken.replaceAll("NN", "ddd");
2994 else if ( rInfos.eType == ww::eEquals )
2996 // Use original OOXML formula, if it exists and its conversion hasn't been changed
2997 bool bIsChanged = true;
2998 if ( pNode->GetTableBox() )
3000 if ( const SfxGrabBagItem* pItem = pNode->GetTableBox()->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG) )
3002 OUString sActualFormula = sToken.trim();
3003 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
3004 std::map<OUString, uno::Any>::const_iterator aStoredFormula = rGrabBag.find("CellFormulaConverted");
3005 if ( aStoredFormula != rGrabBag.end() && sActualFormula.indexOf('=') == 0 &&
3006 o3tl::trim(sActualFormula.subView(1)) == o3tl::trim(aStoredFormula->second.get<OUString>()) )
3008 aStoredFormula = rGrabBag.find("CellFormula");
3009 if ( aStoredFormula != rGrabBag.end() )
3011 sToken = " =" + aStoredFormula->second.get<OUString>();
3012 bIsChanged = false;
3018 if ( bIsChanged )
3020 UErrorCode nErr(U_ZERO_ERROR);
3021 icu::UnicodeString sInput(sToken.getStr());
3022 // remove < and > around cell references, e.g. <A1> to A1, <A1:B2> to A1:B2
3023 icu::RegexMatcher aMatcher("<([A-Z]{1,3}[0-9]+(:[A-Z]{1,3}[0-9]+)?)>", sInput, 0, nErr);
3024 sInput = aMatcher.replaceAll(icu::UnicodeString("$1"), nErr);
3025 // convert MEAN to AVERAGE
3026 icu::RegexMatcher aMatcher2("\\bMEAN\\b", sInput, UREGEX_CASE_INSENSITIVE, nErr);
3027 sToken = aMatcher2.replaceAll(icu::UnicodeString("AVERAGE"), nErr).getTerminatedBuffer();
3031 // Write the Field command
3032 DoWriteCmd( sToken );
3034 // Replace tabs by </instrText><tab/><instrText>
3035 if ( nIdx > 0 ) // Is another token expected?
3036 RunText( "\t" );
3039 if ( bWriteRun )
3041 m_pSerializer->endElementNS( XML_w, XML_r );
3045 void DocxAttributeOutput::CmdEndField_Impl(SwTextNode const*const pNode,
3046 sal_Int32 const nPos, bool const bWriteRun)
3048 // Write the Field separator
3049 if ( bWriteRun )
3051 m_pSerializer->startElementNS(XML_w, XML_r);
3052 DoWriteFieldRunProperties( pNode, nPos );
3055 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3056 FSNS( XML_w, XML_fldCharType ), "separate" );
3058 if ( bWriteRun )
3060 m_pSerializer->endElementNS( XML_w, XML_r );
3064 /// Writes properties for run that is used to separate field implementation.
3065 /// There are several runs are used:
3066 /// <w:r>
3067 /// <w:rPr>
3068 /// <!-- properties written with StartRunProperties() / EndRunProperties().
3069 /// </w:rPr>
3070 /// <w:fldChar w:fldCharType="begin" />
3071 /// </w:r>
3072 /// <w:r>
3073 /// <w:rPr>
3074 /// <!-- properties written with DoWriteFieldRunProperties()
3075 /// </w:rPr>
3076 /// <w:instrText>TIME \@"HH:mm:ss"</w:instrText>
3077 /// </w:r>
3078 /// <w:r>
3079 /// <w:rPr>
3080 /// <!-- properties written with DoWriteFieldRunProperties()
3081 /// </w:rPr>
3082 /// <w:fldChar w:fldCharType="separate" />
3083 /// </w:r>
3084 /// <w:r>
3085 /// <w:rPr>
3086 /// <!-- properties written with DoWriteFieldRunProperties()
3087 /// </w:rPr>
3088 /// <w:t>14:01:13</w:t>
3089 /// </w:r>
3090 /// <w:r>
3091 /// <w:rPr>
3092 /// <!-- properties written with DoWriteFieldRunProperties()
3093 /// </w:rPr>
3094 /// <w:fldChar w:fldCharType="end" />
3095 /// </w:r>
3096 /// See, tdf#38778
3097 void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars)
3099 if (! pNode)
3101 // nothing to do
3102 return;
3105 m_bPreventDoubleFieldsHandling = true;
3108 m_pSerializer->startElementNS(XML_w, XML_rPr);
3110 // 1. output webHidden flag
3111 if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3113 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3116 // 2. find all active character properties
3117 SwWW8AttrIter aAttrIt( m_rExport, *pNode );
3118 aAttrIt.OutAttr( nPos, bWriteCombChars );
3120 // 3. write the character properties
3121 WriteCollectedRunProperties();
3123 m_pSerializer->endElementNS( XML_w, XML_rPr );
3126 m_bPreventDoubleFieldsHandling = false;
3129 void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos )
3131 if (rInfos.eType == ww::eFORMDATE)
3133 WriteContentControlEnd();
3134 return;
3136 else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
3138 // write selected item from End not Start to ensure that any bookmarks
3139 // precede it
3140 SwDropDownField const& rField(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
3141 WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence());
3142 return;
3144 else if (rInfos.eType == ww::eFILLIN && rInfos.pField)
3146 SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
3147 if (rField.getGrabBagParams().hasElements())
3149 WriteContentControlEnd();
3150 return;
3153 // The command has to be written before for the hyperlinks
3154 if ( rInfos.pField )
3156 CmdField_Impl( pNode, nPos, rInfos, true );
3157 CmdEndField_Impl( pNode, nPos, true );
3160 // Write the bookmark start if any
3161 if ( !m_sFieldBkm.isEmpty() )
3163 DoWriteBookmarkTagStart(m_sFieldBkm);
3166 if (rInfos.pField ) // For hyperlinks and TOX
3168 // Write the Field latest value
3169 m_pSerializer->startElementNS(XML_w, XML_r);
3170 DoWriteFieldRunProperties( pNode, nPos );
3172 OUString sExpand;
3173 if(rInfos.eType == ww::eCITATION)
3175 sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get())
3176 ->ExpandCitation(AUTH_FIELD_TITLE, nullptr);
3178 else if(rInfos.eType != ww::eFORMDROPDOWN)
3180 sExpand = rInfos.pField->ExpandField(true, nullptr);
3182 // newlines embedded in fields are 0x0B in MSO and 0x0A for us
3183 RunText(sExpand.replace(0x0A, 0x0B));
3185 m_pSerializer->endElementNS( XML_w, XML_r );
3188 // Write the bookmark end if any
3189 if ( !m_sFieldBkm.isEmpty() )
3191 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
3193 m_nNextBookmarkId++;
3196 // Write the Field end
3197 if ( rInfos.bClose )
3199 m_bWritingField = false;
3200 m_pSerializer->startElementNS(XML_w, XML_r);
3201 DoWriteFieldRunProperties( pNode, nPos );
3202 m_pSerializer->singleElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "end");
3203 m_pSerializer->endElementNS( XML_w, XML_r );
3205 // Write the ref field if a bookmark had to be set and the field
3206 // should be visible
3207 if ( !rInfos.pField )
3209 m_sFieldBkm.clear();
3210 return;
3213 sal_uInt16 nSubType = rInfos.pField->GetSubType( );
3214 bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp;
3215 bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0;
3217 if (!bShowRef)
3219 m_sFieldBkm.clear();
3222 if (m_sFieldBkm.isEmpty())
3223 return;
3225 // Write the field beginning
3226 m_pSerializer->startElementNS(XML_w, XML_r);
3227 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3228 FSNS( XML_w, XML_fldCharType ), "begin" );
3229 m_pSerializer->endElementNS( XML_w, XML_r );
3231 rInfos.sCmd = FieldString( ww::eREF );
3232 rInfos.sCmd += "\"";
3233 rInfos.sCmd += m_sFieldBkm;
3234 rInfos.sCmd += "\" ";
3236 // Clean the field bookmark data to avoid infinite loop
3237 m_sFieldBkm = OUString( );
3239 // Write the end of the field
3240 EndField_Impl( pNode, nPos, rInfos );
3243 void DocxAttributeOutput::StartRunProperties()
3245 // postpone the output so that we can later [in EndRunProperties()]
3246 // prepend the properties before the text
3247 m_pSerializer->mark(Tag_StartRunProperties);
3249 m_pSerializer->startElementNS(XML_w, XML_rPr);
3251 if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3253 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3255 InitCollectedRunProperties();
3257 assert( !m_oPostponedGraphic );
3258 m_oPostponedGraphic.emplace();
3260 assert( !m_oPostponedDiagrams );
3261 m_oPostponedDiagrams.emplace();
3263 assert(!m_oPostponedDMLDrawings);
3264 m_oPostponedDMLDrawings.emplace();
3266 assert( !m_oPostponedOLEs );
3267 m_oPostponedOLEs.emplace();
3270 void DocxAttributeOutput::InitCollectedRunProperties()
3272 m_pFontsAttrList = nullptr;
3273 m_pEastAsianLayoutAttrList = nullptr;
3274 m_pCharLangAttrList = nullptr;
3276 // Write the elements in the spec order
3277 static const sal_Int32 aOrder[] =
3279 FSNS( XML_w, XML_rStyle ),
3280 FSNS( XML_w, XML_rFonts ),
3281 FSNS( XML_w, XML_b ),
3282 FSNS( XML_w, XML_bCs ),
3283 FSNS( XML_w, XML_i ),
3284 FSNS( XML_w, XML_iCs ),
3285 FSNS( XML_w, XML_caps ),
3286 FSNS( XML_w, XML_smallCaps ),
3287 FSNS( XML_w, XML_strike ),
3288 FSNS( XML_w, XML_dstrike ),
3289 FSNS( XML_w, XML_outline ),
3290 FSNS( XML_w, XML_shadow ),
3291 FSNS( XML_w, XML_emboss ),
3292 FSNS( XML_w, XML_imprint ),
3293 FSNS( XML_w, XML_noProof ),
3294 FSNS( XML_w, XML_snapToGrid ),
3295 FSNS( XML_w, XML_vanish ),
3296 FSNS( XML_w, XML_webHidden ),
3297 FSNS( XML_w, XML_color ),
3298 FSNS( XML_w, XML_spacing ),
3299 FSNS( XML_w, XML_w ),
3300 FSNS( XML_w, XML_kern ),
3301 FSNS( XML_w, XML_position ),
3302 FSNS( XML_w, XML_sz ),
3303 FSNS( XML_w, XML_szCs ),
3304 FSNS( XML_w, XML_highlight ),
3305 FSNS( XML_w, XML_u ),
3306 FSNS( XML_w, XML_effect ),
3307 FSNS( XML_w, XML_bdr ),
3308 FSNS( XML_w, XML_shd ),
3309 FSNS( XML_w, XML_fitText ),
3310 FSNS( XML_w, XML_vertAlign ),
3311 FSNS( XML_w, XML_rtl ),
3312 FSNS( XML_w, XML_cs ),
3313 FSNS( XML_w, XML_em ),
3314 FSNS( XML_w, XML_lang ),
3315 FSNS( XML_w, XML_eastAsianLayout ),
3316 FSNS( XML_w, XML_specVanish ),
3317 FSNS( XML_w, XML_oMath ),
3318 FSNS( XML_w, XML_rPrChange ),
3319 FSNS( XML_w, XML_del ),
3320 FSNS( XML_w, XML_ins ),
3321 FSNS( XML_w, XML_moveFrom ),
3322 FSNS( XML_w, XML_moveTo ),
3323 FSNS( XML_w14, XML_glow ),
3324 FSNS( XML_w14, XML_shadow ),
3325 FSNS( XML_w14, XML_reflection ),
3326 FSNS( XML_w14, XML_textOutline ),
3327 FSNS( XML_w14, XML_textFill ),
3328 FSNS( XML_w14, XML_scene3d ),
3329 FSNS( XML_w14, XML_props3d ),
3330 FSNS( XML_w14, XML_ligatures ),
3331 FSNS( XML_w14, XML_numForm ),
3332 FSNS( XML_w14, XML_numSpacing ),
3333 FSNS( XML_w14, XML_stylisticSets ),
3334 FSNS( XML_w14, XML_cntxtAlts ),
3337 // postpone the output so that we can later [in EndParagraphProperties()]
3338 // prepend the properties before the run
3339 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
3340 m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder));
3343 namespace
3346 struct NameToId
3348 OUString maName;
3349 sal_Int32 maId;
3352 const NameToId constNameToIdMapping[] =
3354 { OUString("glow"), FSNS( XML_w14, XML_glow ) },
3355 { OUString("shadow"), FSNS( XML_w14, XML_shadow ) },
3356 { OUString("reflection"), FSNS( XML_w14, XML_reflection ) },
3357 { OUString("textOutline"), FSNS( XML_w14, XML_textOutline ) },
3358 { OUString("textFill"), FSNS( XML_w14, XML_textFill ) },
3359 { OUString("scene3d"), FSNS( XML_w14, XML_scene3d ) },
3360 { OUString("props3d"), FSNS( XML_w14, XML_props3d ) },
3361 { OUString("ligatures"), FSNS( XML_w14, XML_ligatures ) },
3362 { OUString("numForm"), FSNS( XML_w14, XML_numForm ) },
3363 { OUString("numSpacing"), FSNS( XML_w14, XML_numSpacing ) },
3364 { OUString("stylisticSets"),FSNS( XML_w14, XML_stylisticSets ) },
3365 { OUString("cntxtAlts"), FSNS( XML_w14, XML_cntxtAlts ) },
3367 { OUString("val"), FSNS( XML_w14, XML_val ) },
3368 { OUString("rad"), FSNS( XML_w14, XML_rad ) },
3369 { OUString("blurRad"), FSNS( XML_w14, XML_blurRad ) },
3370 { OUString("stA"), FSNS( XML_w14, XML_stA ) },
3371 { OUString("stPos"), FSNS( XML_w14, XML_stPos ) },
3372 { OUString("endA"), FSNS( XML_w14, XML_endA ) },
3373 { OUString("endPos"), FSNS( XML_w14, XML_endPos ) },
3374 { OUString("dist"), FSNS( XML_w14, XML_dist ) },
3375 { OUString("dir"), FSNS( XML_w14, XML_dir ) },
3376 { OUString("fadeDir"), FSNS( XML_w14, XML_fadeDir ) },
3377 { OUString("sx"), FSNS( XML_w14, XML_sx ) },
3378 { OUString("sy"), FSNS( XML_w14, XML_sy ) },
3379 { OUString("kx"), FSNS( XML_w14, XML_kx ) },
3380 { OUString("ky"), FSNS( XML_w14, XML_ky ) },
3381 { OUString("algn"), FSNS( XML_w14, XML_algn ) },
3382 { OUString("w"), FSNS( XML_w14, XML_w ) },
3383 { OUString("cap"), FSNS( XML_w14, XML_cap ) },
3384 { OUString("cmpd"), FSNS( XML_w14, XML_cmpd ) },
3385 { OUString("pos"), FSNS( XML_w14, XML_pos ) },
3386 { OUString("ang"), FSNS( XML_w14, XML_ang ) },
3387 { OUString("scaled"), FSNS( XML_w14, XML_scaled ) },
3388 { OUString("path"), FSNS( XML_w14, XML_path ) },
3389 { OUString("l"), FSNS( XML_w14, XML_l ) },
3390 { OUString("t"), FSNS( XML_w14, XML_t ) },
3391 { OUString("r"), FSNS( XML_w14, XML_r ) },
3392 { OUString("b"), FSNS( XML_w14, XML_b ) },
3393 { OUString("lim"), FSNS( XML_w14, XML_lim ) },
3394 { OUString("prst"), FSNS( XML_w14, XML_prst ) },
3395 { OUString("rig"), FSNS( XML_w14, XML_rig ) },
3396 { OUString("lat"), FSNS( XML_w14, XML_lat ) },
3397 { OUString("lon"), FSNS( XML_w14, XML_lon ) },
3398 { OUString("rev"), FSNS( XML_w14, XML_rev ) },
3399 { OUString("h"), FSNS( XML_w14, XML_h ) },
3400 { OUString("extrusionH"), FSNS( XML_w14, XML_extrusionH ) },
3401 { OUString("contourW"), FSNS( XML_w14, XML_contourW ) },
3402 { OUString("prstMaterial"), FSNS( XML_w14, XML_prstMaterial ) },
3403 { OUString("id"), FSNS( XML_w14, XML_id ) },
3405 { OUString("schemeClr"), FSNS( XML_w14, XML_schemeClr ) },
3406 { OUString("srgbClr"), FSNS( XML_w14, XML_srgbClr ) },
3407 { OUString("tint"), FSNS( XML_w14, XML_tint ) },
3408 { OUString("shade"), FSNS( XML_w14, XML_shade ) },
3409 { OUString("alpha"), FSNS( XML_w14, XML_alpha ) },
3410 { OUString("hueMod"), FSNS( XML_w14, XML_hueMod ) },
3411 { OUString("sat"), FSNS( XML_w14, XML_sat ) },
3412 { OUString("satOff"), FSNS( XML_w14, XML_satOff ) },
3413 { OUString("satMod"), FSNS( XML_w14, XML_satMod ) },
3414 { OUString("lum"), FSNS( XML_w14, XML_lum ) },
3415 { OUString("lumOff"), FSNS( XML_w14, XML_lumOff ) },
3416 { OUString("lumMod"), FSNS( XML_w14, XML_lumMod ) },
3417 { OUString("noFill"), FSNS( XML_w14, XML_noFill ) },
3418 { OUString("solidFill"), FSNS( XML_w14, XML_solidFill ) },
3419 { OUString("gradFill"), FSNS( XML_w14, XML_gradFill ) },
3420 { OUString("gsLst"), FSNS( XML_w14, XML_gsLst ) },
3421 { OUString("gs"), FSNS( XML_w14, XML_gs ) },
3422 { OUString("pos"), FSNS( XML_w14, XML_pos ) },
3423 { OUString("lin"), FSNS( XML_w14, XML_lin ) },
3424 { OUString("path"), FSNS( XML_w14, XML_path ) },
3425 { OUString("fillToRect"), FSNS( XML_w14, XML_fillToRect ) },
3426 { OUString("prstDash"), FSNS( XML_w14, XML_prstDash ) },
3427 { OUString("round"), FSNS( XML_w14, XML_round ) },
3428 { OUString("bevel"), FSNS( XML_w14, XML_bevel ) },
3429 { OUString("miter"), FSNS( XML_w14, XML_miter ) },
3430 { OUString("camera"), FSNS( XML_w14, XML_camera ) },
3431 { OUString("lightRig"), FSNS( XML_w14, XML_lightRig ) },
3432 { OUString("rot"), FSNS( XML_w14, XML_rot ) },
3433 { OUString("bevelT"), FSNS( XML_w14, XML_bevelT ) },
3434 { OUString("bevelB"), FSNS( XML_w14, XML_bevelB ) },
3435 { OUString("extrusionClr"), FSNS( XML_w14, XML_extrusionClr ) },
3436 { OUString("contourClr"), FSNS( XML_w14, XML_contourClr ) },
3437 { OUString("styleSet"), FSNS( XML_w14, XML_styleSet ) },
3440 std::optional<sal_Int32> lclGetElementIdForName(std::u16string_view rName)
3442 for (auto const & i : constNameToIdMapping)
3444 if (rName == i.maName)
3446 return i.maId;
3449 return std::optional<sal_Int32>();
3452 void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer)
3454 css::uno::Sequence<css::beans::PropertyValue> aAttributes;
3455 rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
3457 for (const auto& rElement : rElements)
3459 if (rElement.Name == "attributes")
3461 rElement.Value >>= aAttributes;
3465 for (const auto& rAttribute : std::as_const(aAttributes))
3467 uno::Any aAny = rAttribute.Value;
3468 OString aValue;
3470 if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get())
3472 aValue = OString::number(aAny.get<sal_Int32>());
3474 else if(aAny.getValueType() == cppu::UnoType<OUString>::get())
3476 aValue = OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US);
3479 std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rAttribute.Name);
3480 if(aSubElementId)
3481 pAttributes->add(*aSubElementId, aValue);
3484 pSerializer->startElement(aElementId, pAttributes);
3486 for (const auto& rElement : rElements)
3488 css::uno::Sequence<css::beans::PropertyValue> aSumElements;
3490 std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElement.Name);
3491 if(aSubElementId)
3493 rElement.Value >>= aSumElements;
3494 lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer);
3498 pSerializer->endElement(aElementId);
3503 void DocxAttributeOutput::WriteCollectedRunProperties()
3505 // Write all differed properties
3506 if ( m_pFontsAttrList.is() )
3508 m_pSerializer->singleElementNS( XML_w, XML_rFonts, detachFrom( m_pFontsAttrList ) );
3511 if ( m_pColorAttrList.is() )
3513 m_pSerializer->singleElementNS( XML_w, XML_color, m_pColorAttrList );
3516 if ( m_pEastAsianLayoutAttrList.is() )
3518 m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout,
3519 detachFrom(m_pEastAsianLayoutAttrList ) );
3522 if ( m_pCharLangAttrList.is() )
3524 m_pSerializer->singleElementNS( XML_w, XML_lang, detachFrom( m_pCharLangAttrList ) );
3527 if (m_nCharTransparence != 0 && m_pColorAttrList && m_aTextEffectsGrabBag.empty())
3529 std::string_view pVal;
3530 m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pVal);
3531 if (!pVal.empty() && pVal != "auto")
3533 m_pSerializer->startElementNS(XML_w14, XML_textFill);
3534 m_pSerializer->startElementNS(XML_w14, XML_solidFill);
3535 m_pSerializer->startElementNS(XML_w14, XML_srgbClr, FSNS(XML_w14, XML_val), pVal.data());
3536 sal_Int32 nTransparence = m_nCharTransparence * oox::drawingml::MAX_PERCENT / 255.0;
3537 m_pSerializer->singleElementNS(XML_w14, XML_alpha, FSNS(XML_w14, XML_val), OString::number(nTransparence));
3538 m_pSerializer->endElementNS(XML_w14, XML_srgbClr);
3539 m_pSerializer->endElementNS(XML_w14, XML_solidFill);
3540 m_pSerializer->endElementNS(XML_w14, XML_textFill);
3541 m_nCharTransparence = 0;
3544 m_pColorAttrList.clear();
3545 for (const beans::PropertyValue & i : m_aTextEffectsGrabBag)
3547 std::optional<sal_Int32> aElementId = lclGetElementIdForName(i.Name);
3548 if(aElementId)
3550 uno::Sequence<beans::PropertyValue> aGrabBagSeq;
3551 i.Value >>= aGrabBagSeq;
3552 lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer);
3555 m_aTextEffectsGrabBag.clear();
3558 void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
3560 // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties.
3561 // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
3563 // If there is RedlineData present, call WriteCollectedRunProperties() for writing rPr before calling Redline().
3564 // As there will be another rPr for redline and LO might mix both.
3565 if(pRedlineData)
3566 WriteCollectedRunProperties();
3567 Redline( pRedlineData );
3569 WriteCollectedRunProperties();
3571 // Merge the marks for the ordered elements
3572 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
3574 m_pSerializer->endElementNS( XML_w, XML_rPr );
3576 // write footnotes/endnotes if we have any
3577 FootnoteEndnoteReference();
3579 WriteLineBreak();
3581 // merge the properties _before_ the run text (strictly speaking, just
3582 // after the start of the run)
3583 m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND);
3585 WritePostponedGraphic();
3587 WritePostponedDiagram();
3588 //We need to write w:drawing tag after the w:rPr.
3589 WritePostponedChart();
3591 //We need to write w:pict tag after the w:rPr.
3592 WritePostponedDMLDrawing();
3594 WritePostponedOLE();
3596 WritePostponedActiveXControl(true);
3599 void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
3601 if (!pSdrObj)
3602 return;
3604 uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape());
3605 uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
3606 if( !xPropSet.is() )
3607 return;
3609 uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
3610 uno::Sequence< beans::PropertyValue > aGrabBag;
3611 if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag"))
3613 xPropSet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
3615 else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
3617 xPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3620 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
3621 [this](const beans::PropertyValue& rProp) {
3622 return "SdtEndBefore" == rProp.Name && m_aRunSdt.m_bStartedSdt && !m_bEndCharSdt; });
3623 if (pProp != std::cend(aGrabBag))
3624 pProp->Value >>= m_bEndCharSdt;
3627 void DocxAttributeOutput::WritePostponedGraphic()
3629 for (const auto & rPostponedDiagram : *m_oPostponedGraphic)
3630 FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size,
3631 nullptr, nullptr,
3632 rPostponedDiagram.pSdrObj);
3633 m_oPostponedGraphic.reset();
3636 void DocxAttributeOutput::WritePostponedDiagram()
3638 for( const auto & rPostponedDiagram : *m_oPostponedDiagrams )
3639 m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object,
3640 *rPostponedDiagram.frame, m_anchorId++);
3641 m_oPostponedDiagrams.reset();
3644 bool DocxAttributeOutput::FootnoteEndnoteRefTag()
3646 if( m_footnoteEndnoteRefTag == 0 )
3647 return false;
3649 // output the character style for MS Word's benefit
3650 const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ?
3651 m_rExport.m_rDoc.GetFootnoteInfo() : m_rExport.m_rDoc.GetEndNoteInfo();
3652 const SwCharFormat* pCharFormat = rInfo.GetCharFormat( m_rExport.m_rDoc );
3653 if ( pCharFormat )
3655 const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
3656 m_pSerializer->startElementNS(XML_w, XML_rPr);
3657 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
3658 m_pSerializer->endElementNS( XML_w, XML_rPr );
3661 if (m_footnoteCustomLabel.isEmpty())
3662 m_pSerializer->singleElementNS(XML_w, m_footnoteEndnoteRefTag);
3663 else
3664 RunText(m_footnoteCustomLabel);
3665 m_footnoteEndnoteRefTag = 0;
3666 return true;
3669 /** Output sal_Unicode* as a run text (<t>the text</t>).
3671 When bMove is true, update rBegin to point _after_ the end of the text +
3672 1, meaning that it skips one character after the text. This is to make
3673 the switch in DocxAttributeOutput::RunText() nicer ;-)
3675 static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken,
3676 const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true,
3677 const OUString& rSymbolFont = OUString() )
3679 const sal_Unicode *pBegin = rBegin;
3681 // skip one character after the end
3682 if ( bMove )
3683 rBegin = pEnd + 1;
3685 if ( pBegin >= pEnd )
3686 return false; // we want to write at least one character
3688 bool bIsSymbol = !rSymbolFont.isEmpty();
3690 std::u16string_view aView( pBegin, pEnd - pBegin );
3691 if (bIsSymbol)
3693 for (char16_t aChar : aView)
3695 pSerializer->singleElementNS(XML_w, XML_sym,
3696 FSNS(XML_w, XML_font), rSymbolFont,
3697 FSNS(XML_w, XML_char), OString::number(aChar, 16));
3700 else
3702 // we have to add 'preserve' when starting/ending with space
3703 if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' )
3704 pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
3705 else
3706 pSerializer->startElementNS(XML_w, nTextToken);
3708 pSerializer->writeEscaped( aView );
3709 pSerializer->endElementNS( XML_w, nTextToken );
3712 return true;
3715 void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/, const OUString& rSymbolFont )
3717 if( m_closeHyperlinkInThisRun )
3719 m_closeHyperlinkInPreviousRun = true;
3721 m_bRunTextIsOn = true;
3722 // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
3723 const sal_Unicode *pBegin = rText.getStr();
3724 const sal_Unicode *pEnd = pBegin + rText.getLength();
3726 // the text run is usually XML_t, with the exception of the deleted (and not moved) text
3727 sal_Int32 nTextToken = XML_t;
3729 bool bMoved = m_pRedlineData && m_pRedlineData->IsMoved() &&
3730 // tdf#150166 save tracked moving around TOC as w:ins, w:del
3731 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
3733 if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete && !bMoved )
3735 nTextToken = XML_delText;
3738 sal_Unicode prevUnicode = *pBegin;
3740 for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt )
3742 switch ( *pIt )
3744 case 0x09: // tab
3745 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3746 m_pSerializer->singleElementNS(XML_w, XML_tab);
3747 prevUnicode = *pIt;
3748 break;
3749 case 0x0b: // line break
3750 case static_cast<sal_Unicode>(text::ControlCharacter::LINE_BREAK):
3752 if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || prevUnicode < 0x0020)
3754 m_pSerializer->singleElementNS(XML_w, XML_br);
3755 prevUnicode = *pIt;
3758 break;
3759 case 0x1E: //non-breaking hyphen
3760 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3761 m_pSerializer->singleElementNS(XML_w, XML_noBreakHyphen);
3762 prevUnicode = *pIt;
3763 break;
3764 case 0x1F: //soft (on demand) hyphen
3765 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3766 m_pSerializer->singleElementNS(XML_w, XML_softHyphen);
3767 prevUnicode = *pIt;
3768 break;
3769 default:
3770 if ( *pIt < 0x0020 ) // filter out the control codes
3772 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3773 SAL_INFO("sw.ww8", "Ignored control code in a text run: " << unsigned(*pIt) );
3775 prevUnicode = *pIt;
3776 break;
3780 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false, rSymbolFont );
3783 void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/)
3785 m_sRawText = rText;
3788 void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby )
3790 WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() );
3791 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" );
3792 EndRun( &rNode, nPos, -1 ); // end run before starting ruby to avoid nested runs, and overlap
3793 assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby
3794 assert(!m_closeHyperlinkInPreviousRun);
3795 m_pSerializer->startElementNS(XML_w, XML_r);
3796 m_pSerializer->startElementNS(XML_w, XML_ruby);
3797 m_pSerializer->startElementNS(XML_w, XML_rubyPr);
3799 m_pSerializer->singleElementNS( XML_w, XML_rubyAlign,
3800 FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()) );
3801 sal_uInt32 nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10;
3802 sal_uInt32 nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10;
3803 m_pSerializer->singleElementNS(XML_w, XML_hps, FSNS(XML_w, XML_val), OString::number(nHps));
3805 m_pSerializer->singleElementNS( XML_w, XML_hpsRaise,
3806 FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3808 m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText,
3809 FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3811 lang::Locale aLocale( SwBreakIt::Get()->GetLocale(
3812 rNode.GetLang( nPos ) ) );
3813 OUString sLang( LanguageTag::convertToBcp47( aLocale) );
3814 m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), sLang);
3816 m_pSerializer->endElementNS( XML_w, XML_rubyPr );
3818 m_pSerializer->startElementNS(XML_w, XML_rt);
3819 StartRun( nullptr, nPos );
3820 StartRunProperties( );
3822 if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat())
3824 const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat();
3825 sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3826 TypedWhichId<SvxFontItem> nWhichFont = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONT : RES_CHRATR_CJK_FONT;
3827 TypedWhichId<SvxFontHeightItem> nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE;
3829 CharFont(pFormat->GetFormatAttr(nWhichFont));
3830 CharFontSize(pFormat->GetFormatAttr(nWhichFontSize));
3831 CharFontSize(pFormat->GetFormatAttr(RES_CHRATR_CTL_FONTSIZE));
3834 EndRunProperties( nullptr );
3835 RunText( rRuby.GetText( ) );
3836 EndRun( &rNode, nPos, -1 );
3837 m_pSerializer->endElementNS( XML_w, XML_rt );
3839 m_pSerializer->startElementNS(XML_w, XML_rubyBase);
3840 StartRun( nullptr, nPos );
3843 void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
3845 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" );
3846 EndRun( &rNode, nPos, -1 );
3847 m_pSerializer->endElementNS( XML_w, XML_rubyBase );
3848 m_pSerializer->endElementNS( XML_w, XML_ruby );
3849 m_pSerializer->endElementNS( XML_w, XML_r );
3850 StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
3853 bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
3855 bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
3856 if (bBookMarkOnly)
3857 *pMark = GetExport().BookmarkToWord(*pMark);
3859 if (!pMark->isEmpty() && (bBookMarkOnly || rTarget.isEmpty()))
3861 OUString sURL = *pLinkURL;
3863 if ( bBookMarkOnly )
3864 sURL = FieldString( ww::eHYPERLINK );
3865 else
3866 sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
3868 sURL += " \\l \"" + *pMark + "\"";
3870 if ( !rTarget.isEmpty() )
3871 sURL += " \\n " + rTarget;
3873 *pLinkURL = sURL;
3876 return bBookMarkOnly;
3879 void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
3881 m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
3882 m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
3885 bool DocxAttributeOutput::StartURL( const OUString& rUrl, const OUString& rTarget )
3887 OUString sMark;
3888 OUString sUrl;
3890 bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark );
3892 m_hyperLinkAnchor = sMark;
3894 if (!sMark.isEmpty() && !bBookmarkOnly && rTarget.isEmpty())
3896 m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl );
3898 else
3900 // Output a hyperlink XML element
3901 m_pHyperlinkAttrList = FastSerializerHelper::createAttrList();
3903 if ( !bBookmarkOnly )
3905 OUString sId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
3906 oox::getRelationship(Relationship::HYPERLINK),
3907 sUrl, true );
3909 m_pHyperlinkAttrList->add(FSNS(XML_r, XML_id), sId);
3910 if (!sMark.isEmpty())
3912 sMark = sMark.replace(' ', '_');
3913 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_anchor), sMark);
3916 else
3918 // Is this a link to a sequence? Then try to replace that with a
3919 // normal bookmark, as Word won't understand our special
3920 // <seqname>!<index>|sequence syntax.
3921 if (sMark.endsWith("|sequence"))
3923 sal_Int32 nPos = sMark.indexOf('!');
3924 if (nPos != -1)
3926 // Extract <seqname>, the field instruction text has the name quoted.
3927 OUString aSequenceName = sMark.copy(0, nPos);
3928 // Extract <index>.
3929 sal_uInt32 nIndex = o3tl::toUInt32(sMark.subView(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence")));
3930 auto it = m_aSeqBookmarksNames.find(aSequenceName);
3931 if (it != m_aSeqBookmarksNames.end())
3933 std::vector<OUString>& rNames = it->second;
3934 if (rNames.size() > nIndex)
3935 // We know the bookmark name for this sequence and this index, do the replacement.
3936 sMark = rNames[nIndex];
3940 else if (sMark.endsWith("|toxmark"))
3942 if (auto const it = GetExport().m_TOXMarkBookmarksByURL.find(sMark);
3943 it != GetExport().m_TOXMarkBookmarksByURL.end())
3945 sMark = it->second;
3948 // Spaces are prohibited in bookmark name.
3949 sMark = sMark.replace(' ', '_');
3950 m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ), sMark );
3953 if ( !rTarget.isEmpty() )
3955 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tgtFrame), rTarget);
3959 return true;
3962 bool DocxAttributeOutput::EndURL(bool const)
3964 m_closeHyperlinkInThisRun = true;
3965 if (m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
3966 && m_hyperLinkAnchor.startsWith("_Toc"))
3968 m_endPageRef = true;
3970 return true;
3973 void DocxAttributeOutput::FieldVanish(const OUString& rText,
3974 ww::eField const eType, OUString const*const pBookmarkName)
3976 WriteField_Impl(nullptr, eType, rText, FieldFlags::All, pBookmarkName);
3979 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
3980 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
3981 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
3982 void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
3984 if ( !pRedlineData )
3985 return;
3987 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
3988 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
3990 OString aId( OString::number( pRedlineData->GetSeqNo() ) );
3991 const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
3992 const DateTime aDateTime = pRedlineData->GetTimeStamp();
3993 bool bNoDate = bRemovePersonalInfo ||
3994 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
3996 switch( pRedlineData->GetType() )
3998 case RedlineType::Insert:
3999 break;
4001 case RedlineType::Delete:
4002 break;
4004 case RedlineType::Format:
4006 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4007 = sax_fastparser::FastSerializerHelper::createAttrList();
4009 pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4010 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4011 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4012 : rAuthor.toUtf8());
4013 if (!bNoDate)
4014 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4015 m_pSerializer->startElementNS( XML_w, XML_rPrChange, pAttributeList );
4017 // Check if there is any extra data stored in the redline object
4018 if (pRedlineData->GetExtraData())
4020 const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4021 const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4023 // Check if the extra data is of type 'formatting changes'
4024 if (pFormattingChanges)
4026 // Get the item set that holds all the changes properties
4027 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4028 if (pChangesSet)
4030 m_pSerializer->mark(Tag_Redline_1);
4032 m_pSerializer->startElementNS(XML_w, XML_rPr);
4034 // Output the redline item set
4035 if (pChangesSet)
4036 m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4038 m_pSerializer->endElementNS( XML_w, XML_rPr );
4040 m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND);
4045 m_pSerializer->endElementNS( XML_w, XML_rPrChange );
4046 break;
4048 case RedlineType::ParagraphFormat:
4050 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4051 = sax_fastparser::FastSerializerHelper::createAttrList();
4053 pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4054 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4055 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4056 : rAuthor.toUtf8());
4057 if (!bNoDate)
4058 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4059 m_pSerializer->startElementNS( XML_w, XML_pPrChange, pAttributeList );
4061 // Check if there is any extra data stored in the redline object
4062 if (pRedlineData->GetExtraData())
4064 const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4065 const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4067 // Check if the extra data is of type 'formatting changes'
4068 if (pFormattingChanges)
4070 // Get the item set that holds all the changes properties
4071 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4072 const OUString & sParaStyleName = pFormattingChanges->GetFormatName();
4073 if (pChangesSet || !sParaStyleName.isEmpty())
4075 m_pSerializer->mark(Tag_Redline_2);
4077 m_pSerializer->startElementNS(XML_w, XML_pPr);
4079 if (!sParaStyleName.isEmpty())
4081 OString sStyleName;
4082 if (auto format = m_rExport.m_rDoc.FindTextFormatCollByName(sParaStyleName))
4083 if (auto slot = m_rExport.m_pStyles->GetSlot(format); slot != 0xfff)
4084 sStyleName = m_rExport.m_pStyles->GetStyleId(slot);
4085 // The resolved style name can be empty at this point, sParaStyleName can be
4086 // an arbitrary string from the original document.
4087 // Note that Word does *not* roundtrip unknown style names in redlines!
4088 if (sStyleName.isEmpty())
4089 sStyleName = MSWordStyles::CreateStyleId(sParaStyleName);
4090 if (!sStyleName.isEmpty())
4091 m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName);
4094 // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information
4095 // that should be collected by different properties in the core, and are all flushed together
4096 // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called.
4097 // So we need to store the current status of these lists, so that we can revert back to them when
4098 // we are done exporting the redline attributes.
4099 auto pFlyAttrList_Original(detachFrom(m_rExport.SdrExporter().getFlyAttrList()));
4100 auto pLRSpaceAttrList_Original(detachFrom(m_pLRSpaceAttrList));
4101 auto pParagraphSpacingAttrList_Original(detachFrom(m_pParagraphSpacingAttrList));
4103 // Output the redline item set
4104 if (pChangesSet)
4105 m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4107 // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4108 WriteCollectedParagraphProperties();
4110 // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4111 m_rExport.SdrExporter().getFlyAttrList() = std::move(pFlyAttrList_Original);
4112 m_pLRSpaceAttrList = std::move(pLRSpaceAttrList_Original);
4113 m_pParagraphSpacingAttrList = std::move(pParagraphSpacingAttrList_Original);
4115 m_pSerializer->endElementNS( XML_w, XML_pPr );
4117 m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND);
4121 m_pSerializer->endElementNS( XML_w, XML_pPrChange );
4122 break;
4124 default:
4125 SAL_WARN("sw.ww8", "Unhandled redline type for export " << SwRedlineTypeToOUString(pRedlineData->GetType()));
4126 break;
4130 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
4131 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
4132 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
4133 void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4135 if ( !pRedlineData )
4136 return;
4138 // write out stack of this redline recursively (first the oldest)
4139 if ( !bLastRun )
4140 StartRedline( pRedlineData->Next(), false );
4142 OString aId( OString::number( m_nRedlineId++ ) );
4144 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4145 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
4147 const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
4148 OString aAuthor( OUStringToOString( bRemovePersonalInfo
4149 ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
4150 : rAuthor, RTL_TEXTENCODING_UTF8 ) );
4152 const DateTime aDateTime = pRedlineData->GetTimeStamp();
4153 bool bNoDate = bRemovePersonalInfo ||
4154 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
4155 bool bMoved = pRedlineData->IsMoved() &&
4156 // tdf#150166 save tracked moving around TOC as w:ins, w:del
4157 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4158 switch ( pRedlineData->GetType() )
4160 case RedlineType::Insert:
4161 case RedlineType::Delete:
4163 sal_Int32 eElement = RedlineType::Insert == pRedlineData->GetType()
4164 ? ( bMoved ? XML_moveTo : XML_ins )
4165 : ( bMoved ? XML_moveFrom : XML_del );
4166 if ( bNoDate )
4167 m_pSerializer->startElementNS( XML_w, eElement,
4168 FSNS( XML_w, XML_id ), aId,
4169 FSNS( XML_w, XML_author ), aAuthor );
4170 else
4171 m_pSerializer->startElementNS( XML_w, eElement,
4172 FSNS( XML_w, XML_id ), aId,
4173 FSNS( XML_w, XML_author ), aAuthor,
4174 FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ) );
4175 break;
4177 case RedlineType::Format:
4178 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" );
4179 break;
4180 default:
4181 break;
4185 void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4187 if ( !pRedlineData || m_bWritingField )
4188 return;
4190 bool bMoved = pRedlineData->IsMoved() &&
4191 // tdf#150166 save tracked moving around TOC as w:ins, w:del
4192 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4193 switch ( pRedlineData->GetType() )
4195 case RedlineType::Insert:
4196 m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins );
4197 break;
4199 case RedlineType::Delete:
4200 m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : XML_del );
4201 break;
4203 case RedlineType::Format:
4204 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" );
4205 break;
4206 default:
4207 break;
4210 // write out stack of this redline recursively (first the newest)
4211 if ( !bLastRun )
4212 EndRedline( pRedlineData->Next(), false );
4215 void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t )
4217 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" );
4220 void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle )
4222 OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle));
4224 m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), aStyleId);
4227 static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist,
4228 bool bWriteShadow, const table::BorderLine2* pStyleProps = nullptr)
4230 // Compute val attribute value
4231 // Can be one of:
4232 // single, double,
4233 // basicWideOutline, basicWideInline
4234 // OOXml also supports those types of borders, but we'll try to play with the first ones.
4235 // thickThinMediumGap, thickThinLargeGap, thickThinSmallGap
4236 // thinThickLargeGap, thinThickMediumGap, thinThickSmallGap
4237 const char* pVal = "nil";
4238 if ( pBorderLine && !pBorderLine->isEmpty( ) )
4240 switch (pBorderLine->GetBorderLineStyle())
4242 case SvxBorderLineStyle::SOLID:
4243 pVal = "single";
4244 break;
4245 case SvxBorderLineStyle::DOTTED:
4246 pVal = "dotted";
4247 break;
4248 case SvxBorderLineStyle::DASHED:
4249 pVal = "dashed";
4250 break;
4251 case SvxBorderLineStyle::DOUBLE:
4252 case SvxBorderLineStyle::DOUBLE_THIN:
4253 pVal = "double";
4254 break;
4255 case SvxBorderLineStyle::THINTHICK_SMALLGAP:
4256 pVal = "thinThickSmallGap";
4257 break;
4258 case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
4259 pVal = "thinThickMediumGap";
4260 break;
4261 case SvxBorderLineStyle::THINTHICK_LARGEGAP:
4262 pVal = "thinThickLargeGap";
4263 break;
4264 case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
4265 pVal = "thickThinSmallGap";
4266 break;
4267 case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
4268 pVal = "thickThinMediumGap";
4269 break;
4270 case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
4271 pVal = "thickThinLargeGap";
4272 break;
4273 case SvxBorderLineStyle::EMBOSSED:
4274 pVal = "threeDEmboss";
4275 break;
4276 case SvxBorderLineStyle::ENGRAVED:
4277 pVal = "threeDEngrave";
4278 break;
4279 case SvxBorderLineStyle::OUTSET:
4280 pVal = "outset";
4281 break;
4282 case SvxBorderLineStyle::INSET:
4283 pVal = "inset";
4284 break;
4285 case SvxBorderLineStyle::FINE_DASHED:
4286 pVal = "dashSmallGap";
4287 break;
4288 case SvxBorderLineStyle::DASH_DOT:
4289 pVal = "dotDash";
4290 break;
4291 case SvxBorderLineStyle::DASH_DOT_DOT:
4292 pVal = "dotDotDash";
4293 break;
4294 case SvxBorderLineStyle::NONE:
4295 default:
4296 break;
4299 else if (!pStyleProps || !pStyleProps->LineWidth)
4300 // no line, and no line set by the style either:
4301 // there is no need to write the property
4302 return;
4304 // compare the properties with the theme properties before writing them:
4305 // if they are equal, it means that they were style-defined and there is
4306 // no need to write them.
4307 if (pStyleProps && pBorderLine && !pBorderLine->isEmpty()
4308 && pBorderLine->GetBorderLineStyle()
4309 == static_cast<SvxBorderLineStyle>(pStyleProps->LineStyle)
4310 && pBorderLine->GetColor() == Color(ColorTransparency, pStyleProps->Color)
4311 && pBorderLine->GetWidth() == o3tl::toTwips(pStyleProps->LineWidth, o3tl::Length::mm100))
4313 return;
4316 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
4317 pAttr->add( FSNS( XML_w, XML_val ), pVal );
4319 if ( pBorderLine && !pBorderLine->isEmpty() )
4321 // Compute the sz attribute
4323 double const fConverted( ::editeng::ConvertBorderWidthToWord(
4324 pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth()));
4325 // The unit is the 8th of point
4326 sal_Int32 nWidth = sal_Int32( fConverted / 2.5 );
4327 const sal_Int32 nMinWidth = 2;
4328 const sal_Int32 nMaxWidth = 96;
4330 if ( nWidth > nMaxWidth )
4331 nWidth = nMaxWidth;
4332 else if ( nWidth < nMinWidth )
4333 nWidth = nMinWidth;
4335 pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) );
4337 // Get the distance (in pt)
4338 pAttr->add(FSNS(XML_w, XML_space), OString::number(rtl::math::round(nDist / 20.0)));
4340 // Get the color code as an RRGGBB hex value
4341 OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) );
4342 pAttr->add( FSNS(XML_w, XML_color), sColor);
4344 model::ComplexColor const& rComplexColor = pBorderLine->getComplexColor();
4345 lclAddThemeColorAttributes(pAttr, rComplexColor);
4348 if (bWriteShadow)
4350 // Set the shadow value
4351 pAttr->add( FSNS( XML_w, XML_shadow ), "1" );
4354 pSerializer->singleElementNS( XML_w, elementToken, pAttr );
4357 static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma)
4359 OutputBorderOptions rOptions;
4361 rOptions.tag = XML_tcBorders;
4362 rOptions.bUseStartEnd = !bEcma;
4363 rOptions.bWriteTag = true;
4364 rOptions.bWriteDistance = false;
4366 return rOptions;
4369 static OutputBorderOptions lcl_getBoxBorderOptions()
4371 OutputBorderOptions rOptions;
4373 rOptions.tag = XML_pBdr;
4374 rOptions.bUseStartEnd = false;
4375 rOptions.bWriteTag = false;
4376 rOptions.bWriteDistance = true;
4378 return rOptions;
4381 static void impl_borders( FSHelperPtr const & pSerializer,
4382 const SvxBoxItem& rBox,
4383 const OutputBorderOptions& rOptions,
4384 std::map<SvxBoxItemLine,
4385 css::table::BorderLine2> &rTableStyleConf,
4386 ww8::Frame* pFramePr = nullptr)
4388 static const SvxBoxItemLine aBorders[] =
4390 SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4393 const sal_Int32 aXmlElements[] =
4395 XML_top,
4396 rOptions.bUseStartEnd ? XML_start : XML_left,
4397 XML_bottom,
4398 rOptions.bUseStartEnd ? XML_end : XML_right
4400 bool tagWritten = false;
4401 const SvxBoxItemLine* pBrd = aBorders;
4403 for( int i = 0; i < 4; ++i, ++pBrd )
4405 const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
4406 const table::BorderLine2 *aStyleProps = nullptr;
4407 if( rTableStyleConf.find( *pBrd ) != rTableStyleConf.end() )
4408 aStyleProps = &rTableStyleConf[ *pBrd ];
4410 if (!tagWritten && rOptions.bWriteTag)
4412 pSerializer->startElementNS(XML_w, rOptions.tag);
4413 tagWritten = true;
4416 bool bWriteShadow = false;
4417 if (rOptions.aShadowLocation == SvxShadowLocation::NONE)
4419 // The border has no shadow
4421 else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight)
4423 // Special case of 'Bottom-Right' shadow:
4424 // If the shadow location is 'Bottom-Right' - then turn on the shadow
4425 // for ALL the sides. This is because in Word - if you select a shadow
4426 // for a border - it turn on the shadow for ALL the sides (but shows only
4427 // the bottom-right one).
4428 // This is so that no information will be lost if passed through LibreOffice
4429 bWriteShadow = true;
4431 else
4433 // If there is a shadow, and it's not the regular 'Bottom-Right',
4434 // then write only the 'shadowed' sides of the border
4435 if (
4436 ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::TOP ) ||
4437 ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft) && *pBrd == SvxBoxItemLine::LEFT ) ||
4438 ((rOptions.aShadowLocation == SvxShadowLocation::BottomLeft ) && *pBrd == SvxBoxItemLine::BOTTOM) ||
4439 ((rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::RIGHT )
4442 bWriteShadow = true;
4446 sal_uInt16 nDist = 0;
4447 if (rOptions.bWriteDistance)
4449 if (rOptions.pDistances)
4451 if ( *pBrd == SvxBoxItemLine::TOP)
4452 nDist = rOptions.pDistances->nTop;
4453 else if ( *pBrd == SvxBoxItemLine::LEFT)
4454 nDist = rOptions.pDistances->nLeft;
4455 else if ( *pBrd == SvxBoxItemLine::BOTTOM)
4456 nDist = rOptions.pDistances->nBottom;
4457 else if ( *pBrd == SvxBoxItemLine::RIGHT)
4458 nDist = rOptions.pDistances->nRight;
4460 else
4462 nDist = rBox.GetDistance(*pBrd);
4466 if (pFramePr)
4468 assert(rOptions.bWriteDistance && !rOptions.pDistances);
4470 // In addition to direct properties, and paragraph styles,
4471 // for framePr-floated paragraphs the frame borders also affect the exported values.
4473 // For border spacing, there is a special situation to consider
4474 // because a compat setting ignores left/right paragraph spacing on layout.
4475 const SwFrameFormat& rFormat = pFramePr->GetFrameFormat();
4476 const SvxBoxItem& rFramePrBox = rFormat.GetBox();
4477 const IDocumentSettingAccess& rIDSA = rFormat.GetDoc()->getIDocumentSettingAccess();
4478 if (rIDSA.get(DocumentSettingId::INVERT_BORDER_SPACING)
4479 && (*pBrd == SvxBoxItemLine::LEFT || *pBrd == SvxBoxItemLine::RIGHT))
4481 // only the frame's border spacing affects layout - so use that value instead.
4482 nDist = rFramePrBox.GetDistance(*pBrd);
4484 else
4486 nDist += rFramePrBox.GetDistance(*pBrd);
4489 // Unless the user added a paragraph border, the border normally comes from the frame.
4490 if (!pLn)
4491 pLn = rFramePrBox.GetLine(*pBrd);
4494 impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps );
4496 if (tagWritten && rOptions.bWriteTag) {
4497 pSerializer->endElementNS( XML_w, rOptions.tag );
4501 void DocxAttributeOutput::ImplCellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins)
4503 static const SvxBoxItemLine aBorders[] =
4505 SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4508 const sal_Int32 aXmlElements[] =
4510 XML_top,
4511 bUseStartEnd ? XML_start : XML_left,
4512 XML_bottom,
4513 bUseStartEnd ? XML_end : XML_right
4515 bool tagWritten = false;
4516 const SvxBoxItemLine* pBrd = aBorders;
4517 for( int i = 0; i < 4; ++i, ++pBrd )
4519 sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) );
4521 if (pDefaultMargins)
4523 // Skip output if cell margin == table default margin
4524 if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist)
4525 continue;
4528 if (!tagWritten) {
4529 pSerializer->startElementNS(XML_w, tag);
4530 tagWritten = true;
4532 pSerializer->singleElementNS( XML_w, aXmlElements[i],
4533 FSNS( XML_w, XML_w ), OString::number(nDist),
4534 FSNS( XML_w, XML_type ), "dxa" );
4536 if (tagWritten) {
4537 pSerializer->endElementNS( XML_w, tag );
4541 void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4543 m_pSerializer->startElementNS(XML_w, XML_tcPr);
4545 const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
4547 bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
4549 // Output any table cell redlines if there are any attached to this specific cell
4550 TableCellRedline( pTableTextNodeInfoInner );
4552 // Cell preferred width
4553 SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell );
4554 if ( nCell )
4555 nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 );
4556 m_pSerializer->singleElementNS( XML_w, XML_tcW,
4557 FSNS( XML_w, XML_w ), OString::number(nWidth),
4558 FSNS( XML_w, XML_type ), "dxa" );
4560 // Horizontal spans
4561 const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
4562 if (nRow >= rRows.size())
4563 SAL_WARN("sw.ww8", "DocxAttributeOutput::TableCellProperties: out of range row: " << nRow);
4564 else
4566 SwWriteTableRow *pRow = rRows[ nRow ].get();
4567 const SwWriteTableCells& rTableCells = pRow->GetCells();
4568 if (nCell < rTableCells.size() )
4570 const SwWriteTableCell& rCell = *rTableCells[nCell];
4571 const sal_uInt16 nColSpan = rCell.GetColSpan();
4572 if ( nColSpan > 1 )
4573 m_pSerializer->singleElementNS( XML_w, XML_gridSpan,
4574 FSNS( XML_w, XML_val ), OString::number(nColSpan) );
4578 // Vertical merges
4579 ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow();
4580 sal_Int32 vSpan = (*xRowSpans)[nCell];
4581 if ( vSpan > 1 )
4583 m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "restart");
4585 else if ( vSpan < 0 )
4587 m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "continue");
4590 if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4592 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4593 std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("CellCnfStyle");
4594 if (it != rGrabBag.end())
4596 uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4597 m_pTableStyleExport->CnfStyle(aAttributes);
4602 const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( );
4603 const SvxBoxItem& rDefaultBox = (*m_TableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( );
4605 // The cell borders
4606 impl_borders(m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma),
4607 m_aTableStyleConfs.back());
4610 TableBackgrounds( pTableTextNodeInfoInner );
4613 // Cell margins
4614 DocxAttributeOutput::ImplCellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox );
4617 TableVerticalCell( pTableTextNodeInfoInner );
4619 m_pSerializer->endElementNS( XML_w, XML_tcPr );
4622 void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4624 const SwTable* pTable = pTableTextNodeInfoInner->getTable();
4625 if (m_xTableWrt && pTable == m_xTableWrt->GetTable())
4626 return;
4628 tools::Long nPageSize = 0;
4629 bool bRelBoxSize = false;
4631 // Create the SwWriteTable instance to use col spans (and maybe other infos)
4632 GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
4634 const SwFrameFormat *pFormat = pTable->GetFrameFormat( );
4635 const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( ));
4637 const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout();
4638 if( pLayout && pLayout->IsExportable() )
4639 m_xTableWrt.reset(new SwWriteTable(pTable, pLayout));
4640 else
4641 m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false));
4644 void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4646 m_aTableStyleConfs.push_back({});
4648 // In case any paragraph SDT's are open, close them here.
4649 EndParaSdtBlock();
4651 m_pSerializer->startElementNS(XML_w, XML_tbl);
4653 m_TableFirstCells.push_back(pTableTextNodeInfoInner);
4654 m_LastOpenCell.push_back(-1);
4655 m_LastClosedCell.push_back(-1);
4657 InitTableHelper( pTableTextNodeInfoInner );
4658 TableDefinition( pTableTextNodeInfoInner );
4661 void DocxAttributeOutput::EndTable()
4663 m_pSerializer->endElementNS( XML_w, XML_tbl );
4665 if ( m_tableReference.m_nTableDepth > 0 )
4666 --m_tableReference.m_nTableDepth;
4668 m_LastClosedCell.pop_back();
4669 m_LastOpenCell.pop_back();
4670 m_TableFirstCells.pop_back();
4672 // We closed the table; if it is a nested table, the cell that contains it
4673 // still continues
4674 // set to true only if we were in a nested table, not otherwise.
4675 if( !m_TableFirstCells.empty() )
4676 m_tableReference.m_bTableCellOpen = true;
4678 // Cleans the table helper
4679 m_xTableWrt.reset();
4681 m_aTableStyleConfs.pop_back();
4684 void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4686 m_pSerializer->startElementNS(XML_w, XML_tr);
4688 // Output the row properties
4689 m_pSerializer->startElementNS(XML_w, XML_trPr);
4691 // Header row: tblHeader
4692 const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
4693 if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
4694 m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false
4696 TableRowRedline( pTableTextNodeInfoInner );
4697 TableHeight( pTableTextNodeInfoInner );
4698 TableCanSplit( pTableTextNodeInfoInner );
4700 const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox();
4701 const SwTableLine* pTableLine = pTableBox->GetUpper();
4702 if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4704 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4705 std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find("RowCnfStyle");
4706 if (it != rGrabBag.end())
4708 uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4709 m_pTableStyleExport->CnfStyle(aAttributes);
4714 m_pSerializer->endElementNS( XML_w, XML_trPr );
4717 void DocxAttributeOutput::EndTableRow( )
4719 m_pSerializer->endElementNS( XML_w, XML_tr );
4720 m_LastOpenCell.back() = -1;
4721 m_LastClosedCell.back() = -1;
4724 void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4726 m_LastOpenCell.back() = nCell;
4728 InitTableHelper( pTableTextNodeInfoInner );
4730 // check tracked table column deletion or insertion
4731 const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox();
4732 SwRedlineTable::size_type nChange = pTabBox->GetRedline();
4733 if (nChange != SwRedlineTable::npos)
4734 m_tableReference.m_bTableCellChanged = true;
4736 m_pSerializer->startElementNS(XML_w, XML_tc);
4738 // Write the cell properties here
4739 TableCellProperties( pTableTextNodeInfoInner, nCell, nRow );
4741 m_tableReference.m_bTableCellOpen = true;
4744 void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell)
4746 m_LastClosedCell.back() = nCell;
4747 m_LastOpenCell.back() = -1;
4749 if (m_tableReference.m_bTableCellParaSdtOpen)
4750 EndParaSdtBlock();
4752 m_pSerializer->endElementNS( XML_w, XML_tc );
4754 m_tableReference.m_bTableCellOpen = false;
4755 m_tableReference.m_bTableCellParaSdtOpen = false;
4756 m_tableReference.m_bTableCellChanged = false;
4759 void DocxAttributeOutput::StartStyles()
4761 m_pSerializer->startElementNS( XML_w, XML_styles,
4762 FSNS( XML_xmlns, XML_w ), GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)),
4763 FSNS( XML_xmlns, XML_w14 ), GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)),
4764 FSNS( XML_xmlns, XML_mc ), GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)),
4765 FSNS( XML_mc, XML_Ignorable ), "w14" );
4767 DocDefaults();
4768 LatentStyles();
4771 sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, std::u16string_view rName)
4773 OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8);
4774 while (pMap->pToken)
4776 if (sName == pMap->pToken)
4777 return pMap->nToken;
4778 ++pMap;
4780 return 0;
4783 namespace
4786 DocxStringTokenMap const aDefaultTokens[] = {
4787 {"defQFormat", XML_defQFormat},
4788 {"defUnhideWhenUsed", XML_defUnhideWhenUsed},
4789 {"defSemiHidden", XML_defSemiHidden},
4790 {"count", XML_count},
4791 {"defUIPriority", XML_defUIPriority},
4792 {"defLockedState", XML_defLockedState},
4793 {nullptr, 0}
4796 DocxStringTokenMap const aExceptionTokens[] = {
4797 {"name", XML_name},
4798 {"locked", XML_locked},
4799 {"uiPriority", XML_uiPriority},
4800 {"semiHidden", XML_semiHidden},
4801 {"unhideWhenUsed", XML_unhideWhenUsed},
4802 {"qFormat", XML_qFormat},
4803 {nullptr, 0}
4808 void DocxAttributeOutput::LatentStyles()
4810 // Do we have latent styles available?
4811 uno::Reference<beans::XPropertySet> xPropertySet(m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW);
4812 uno::Sequence<beans::PropertyValue> aInteropGrabBag;
4813 xPropertySet->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag;
4814 uno::Sequence<beans::PropertyValue> aLatentStyles;
4815 auto pProp = std::find_if(std::cbegin(aInteropGrabBag), std::cend(aInteropGrabBag),
4816 [](const beans::PropertyValue& rProp) { return rProp.Name == "latentStyles"; });
4817 if (pProp != std::cend(aInteropGrabBag))
4818 pProp->Value >>= aLatentStyles;
4819 if (!aLatentStyles.hasElements())
4820 return;
4822 // Extract default attributes first.
4823 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList();
4824 uno::Sequence<beans::PropertyValue> aLsdExceptions;
4825 for (const auto& rLatentStyle : std::as_const(aLatentStyles))
4827 if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, rLatentStyle.Name))
4828 pAttributeList->add(FSNS(XML_w, nToken), rLatentStyle.Value.get<OUString>());
4829 else if (rLatentStyle.Name == "lsdExceptions")
4830 rLatentStyle.Value >>= aLsdExceptions;
4833 m_pSerializer->startElementNS(XML_w, XML_latentStyles, detachFrom(pAttributeList));
4835 // Then handle the exceptions.
4836 for (const auto& rLsdException : std::as_const(aLsdExceptions))
4838 pAttributeList = FastSerializerHelper::createAttrList();
4840 uno::Sequence<beans::PropertyValue> aAttributes;
4841 rLsdException.Value >>= aAttributes;
4842 for (const auto& rAttribute : std::as_const(aAttributes))
4843 if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, rAttribute.Name))
4844 pAttributeList->add(FSNS(XML_w, nToken), rAttribute.Value.get<OUString>());
4846 m_pSerializer->singleElementNS(XML_w, XML_lsdException, detachFrom(pAttributeList));
4849 m_pSerializer->endElementNS(XML_w, XML_latentStyles);
4852 void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt)
4854 bool bMustWrite = true;
4855 switch (rHt.Which())
4857 case RES_CHRATR_CASEMAP:
4858 bMustWrite = static_cast< const SvxCaseMapItem& >(rHt).GetCaseMap() != SvxCaseMap::NotMapped;
4859 break;
4860 case RES_CHRATR_COLOR:
4861 bMustWrite = static_cast< const SvxColorItem& >(rHt).GetValue() != COL_AUTO;
4862 break;
4863 case RES_CHRATR_CONTOUR:
4864 bMustWrite = static_cast< const SvxContourItem& >(rHt).GetValue();
4865 break;
4866 case RES_CHRATR_CROSSEDOUT:
4867 bMustWrite = static_cast< const SvxCrossedOutItem& >(rHt).GetStrikeout() != STRIKEOUT_NONE;
4868 break;
4869 case RES_CHRATR_ESCAPEMENT:
4870 bMustWrite = static_cast< const SvxEscapementItem& >(rHt).GetEscapement() != SvxEscapement::Off;
4871 break;
4872 case RES_CHRATR_FONT:
4873 bMustWrite = true;
4874 break;
4875 case RES_CHRATR_FONTSIZE:
4876 bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
4877 break;
4878 case RES_CHRATR_KERNING:
4879 bMustWrite = static_cast< const SvxKerningItem& >(rHt).GetValue() != 0;
4880 break;
4881 case RES_CHRATR_LANGUAGE:
4882 bMustWrite = true;
4883 break;
4884 case RES_CHRATR_POSTURE:
4885 bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
4886 break;
4887 case RES_CHRATR_SHADOWED:
4888 bMustWrite = static_cast< const SvxShadowedItem& >(rHt).GetValue();
4889 break;
4890 case RES_CHRATR_UNDERLINE:
4891 bMustWrite = static_cast< const SvxUnderlineItem& >(rHt).GetLineStyle() != LINESTYLE_NONE;
4892 break;
4893 case RES_CHRATR_WEIGHT:
4894 bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
4895 break;
4896 case RES_CHRATR_AUTOKERN:
4897 bMustWrite = static_cast< const SvxAutoKernItem& >(rHt).GetValue();
4898 break;
4899 case RES_CHRATR_BLINK:
4900 bMustWrite = static_cast< const SvxBlinkItem& >(rHt).GetValue();
4901 break;
4902 case RES_CHRATR_BACKGROUND:
4904 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
4905 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
4906 rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
4907 rBrushItem.GetGraphic() != nullptr ||
4908 rBrushItem.GetGraphicObject() != nullptr);
4910 break;
4912 case RES_CHRATR_CJK_FONT:
4913 bMustWrite = true;
4914 break;
4915 case RES_CHRATR_CJK_FONTSIZE:
4916 bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE
4917 break;
4918 case RES_CHRATR_CJK_LANGUAGE:
4919 bMustWrite = true;
4920 break;
4921 case RES_CHRATR_CJK_POSTURE:
4922 bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE
4923 break;
4924 case RES_CHRATR_CJK_WEIGHT:
4925 bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT
4926 break;
4928 case RES_CHRATR_CTL_FONT:
4929 bMustWrite = true;
4930 break;
4931 case RES_CHRATR_CTL_FONTSIZE:
4932 bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
4933 break;
4934 case RES_CHRATR_CTL_LANGUAGE:
4935 bMustWrite = true;
4936 break;
4937 case RES_CHRATR_CTL_POSTURE:
4938 bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
4939 break;
4940 case RES_CHRATR_CTL_WEIGHT:
4941 bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
4942 break;
4944 case RES_CHRATR_ROTATE:
4945 bMustWrite = static_cast< const SvxCharRotateItem& >(rHt).GetValue() != 0_deg10;
4946 break;
4947 case RES_CHRATR_EMPHASIS_MARK:
4948 bMustWrite = static_cast< const SvxEmphasisMarkItem& >(rHt).GetEmphasisMark() != FontEmphasisMark::NONE;
4949 break;
4950 case RES_CHRATR_TWO_LINES:
4951 bMustWrite = static_cast< const SvxTwoLinesItem& >(rHt).GetValue();
4952 break;
4953 case RES_CHRATR_SCALEW:
4954 bMustWrite = static_cast< const SvxCharScaleWidthItem& >(rHt).GetValue() != 100;
4955 break;
4956 case RES_CHRATR_RELIEF:
4957 bMustWrite = static_cast< const SvxCharReliefItem& >(rHt).GetValue() != FontRelief::NONE;
4958 break;
4959 case RES_CHRATR_HIDDEN:
4960 bMustWrite = static_cast< const SvxCharHiddenItem& >(rHt).GetValue();
4961 break;
4962 case RES_CHRATR_BOX:
4964 const SvxBoxItem& rBoxItem = static_cast< const SvxBoxItem& >(rHt);
4965 bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() ||
4966 rBoxItem.GetBottom() || rBoxItem.GetRight() ||
4967 rBoxItem.GetSmallestDistance();
4969 break;
4970 case RES_CHRATR_HIGHLIGHT:
4972 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
4973 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
4974 rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
4975 rBrushItem.GetGraphic() != nullptr ||
4976 rBrushItem.GetGraphicObject() != nullptr);
4978 break;
4980 case RES_PARATR_LINESPACING:
4981 bMustWrite = static_cast< const SvxLineSpacingItem& >(rHt).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off;
4982 break;
4983 case RES_PARATR_ADJUST:
4984 bMustWrite = static_cast< const SvxAdjustItem& >(rHt).GetAdjust() != SvxAdjust::Left;
4985 break;
4986 case RES_PARATR_SPLIT:
4987 bMustWrite = !static_cast< const SvxFormatSplitItem& >(rHt).GetValue();
4988 break;
4989 case RES_PARATR_WIDOWS:
4990 bMustWrite = static_cast< const SvxWidowsItem& >(rHt).GetValue();
4991 break;
4992 case RES_PARATR_TABSTOP:
4993 bMustWrite = static_cast< const SvxTabStopItem& >(rHt).Count() != 0;
4994 break;
4995 case RES_PARATR_HYPHENZONE:
4996 bMustWrite = true;
4997 break;
4998 case RES_PARATR_NUMRULE:
4999 bMustWrite = !static_cast< const SwNumRuleItem& >(rHt).GetValue().isEmpty();
5000 break;
5001 case RES_PARATR_SCRIPTSPACE:
5002 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5003 break;
5004 case RES_PARATR_HANGINGPUNCTUATION:
5005 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5006 break;
5007 case RES_PARATR_FORBIDDEN_RULES:
5008 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5009 break;
5010 case RES_PARATR_VERTALIGN:
5011 bMustWrite = static_cast< const SvxParaVertAlignItem& >(rHt).GetValue() != SvxParaVertAlignItem::Align::Automatic;
5012 break;
5013 case RES_PARATR_SNAPTOGRID:
5014 bMustWrite = !static_cast< const SvxParaGridItem& >(rHt).GetValue();
5015 break;
5016 case RES_CHRATR_GRABBAG:
5017 bMustWrite = true;
5018 break;
5020 default:
5021 SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() );
5022 break;
5025 if (bMustWrite)
5026 OutputItem(rHt);
5029 void DocxAttributeOutput::DocDefaults( )
5031 // Write the '<w:docDefaults>' section here
5032 m_pSerializer->startElementNS(XML_w, XML_docDefaults);
5034 // Output the default run properties
5035 m_pSerializer->startElementNS(XML_w, XML_rPrDefault);
5037 StartStyleProperties(false, 0);
5039 for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i)
5040 OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5042 EndStyleProperties(false);
5044 m_pSerializer->endElementNS(XML_w, XML_rPrDefault);
5046 // Output the default paragraph properties
5047 m_pSerializer->startElementNS(XML_w, XML_pPrDefault);
5049 StartStyleProperties(true, 0);
5051 for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i)
5052 OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5054 EndStyleProperties(true);
5056 m_pSerializer->endElementNS(XML_w, XML_pPrDefault);
5058 m_pSerializer->endElementNS(XML_w, XML_docDefaults);
5061 void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles )
5063 // HACK
5064 // Ms Office seems to have an internal limitation of 4091 styles
5065 // and refuses to load .docx with more, even though the spec seems to allow that;
5066 // so simply if there are more styles, don't export those
5067 const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles;
5068 m_pTableStyleExport->TableStyles(nCountStylesToWrite);
5069 m_pSerializer->endElementNS( XML_w, XML_styles );
5072 void DocxAttributeOutput::DefaultStyle()
5074 // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)?
5075 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()");
5078 /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image.
5079 * NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF.
5081 void DocxAttributeOutput::WriteSrcRect(
5082 const css::uno::Reference<css::beans::XPropertySet>& xShapePropSet,
5083 const SwFrameFormat* pFrameFormat)
5085 uno::Reference<graphic::XGraphic> xGraphic;
5086 xShapePropSet->getPropertyValue("Graphic") >>= xGraphic;
5087 const Graphic aGraphic(xGraphic);
5089 Size aOriginalSize(aGraphic.GetPrefSize());
5091 const MapMode aMap100mm( MapUnit::Map100thMM );
5092 const MapMode& rMapMode = aGraphic.GetPrefMapMode();
5093 if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
5095 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm);
5098 css::text::GraphicCrop aGraphicCropStruct;
5099 xShapePropSet->getPropertyValue("GraphicCrop") >>= aGraphicCropStruct;
5100 sal_Int32 nCropL = aGraphicCropStruct.Left;
5101 sal_Int32 nCropR = aGraphicCropStruct.Right;
5102 sal_Int32 nCropT = aGraphicCropStruct.Top;
5103 sal_Int32 nCropB = aGraphicCropStruct.Bottom;
5105 // simulate border padding as a negative crop.
5106 const SvxBoxItem* pBoxItem;
5107 if (pFrameFormat && (pBoxItem = pFrameFormat->GetItemIfSet(RES_BOX, false)))
5109 nCropL -= pBoxItem->GetDistance( SvxBoxItemLine::LEFT );
5110 nCropR -= pBoxItem->GetDistance( SvxBoxItemLine::RIGHT );
5111 nCropT -= pBoxItem->GetDistance( SvxBoxItemLine::TOP );
5112 nCropB -= pBoxItem->GetDistance( SvxBoxItemLine::BOTTOM );
5115 if ( !((0 != nCropL) || (0 != nCropT) || (0 != nCropR) || (0 != nCropB)) )
5116 return;
5118 double widthMultiplier = 100000.0/aOriginalSize.Width();
5119 double heightMultiplier = 100000.0/aOriginalSize.Height();
5121 sal_Int32 left = static_cast<sal_Int32>(rtl::math::round(nCropL * widthMultiplier));
5122 sal_Int32 right = static_cast<sal_Int32>(rtl::math::round(nCropR * widthMultiplier));
5123 sal_Int32 top = static_cast<sal_Int32>(rtl::math::round(nCropT * heightMultiplier));
5124 sal_Int32 bottom = static_cast<sal_Int32>(rtl::math::round(nCropB * heightMultiplier));
5126 m_pSerializer->singleElementNS( XML_a, XML_srcRect,
5127 XML_l, OString::number(left),
5128 XML_t, OString::number(top),
5129 XML_r, OString::number(right),
5130 XML_b, OString::number(bottom) );
5133 uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame(
5134 css::uno::Reference<css::drawing::XShape> xShape)
5136 return SwTextBoxHelper::getUnoTextFrame(xShape);
5139 static rtl::Reference<::sax_fastparser::FastAttributeList> CreateDocPrAttrList(
5140 DocxExport & rExport, int const nAnchorId, std::u16string_view const& rName,
5141 std::u16string_view const& rTitle, std::u16string_view const& rDescription)
5143 rtl::Reference<::sax_fastparser::FastAttributeList> const pAttrs(FastSerializerHelper::createAttrList());
5144 pAttrs->add(XML_id, OString::number(nAnchorId));
5145 pAttrs->add(XML_name, rName);
5146 if (rExport.GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
5148 pAttrs->add(XML_descr, rDescription);
5149 pAttrs->add(XML_title, rTitle);
5151 else
5152 { // tdf#148952 no title attribute, merge it into descr
5153 OUString const value(rTitle.empty()
5154 ? OUString(rDescription)
5155 : rDescription.empty()
5156 ? OUString(rTitle)
5157 : OUString::Concat(rTitle) + "\n" + rDescription);
5158 pAttrs->add(XML_descr, value);
5160 return pAttrs;
5163 void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj )
5165 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" );
5167 GetSdtEndBefore(pSdrObj);
5169 // detect mis-use of the API
5170 assert(pGrfNode || (pOLEFrameFormat && pOLENode));
5171 const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat;
5172 // create the relation ID
5173 OString aRelId;
5174 sal_Int32 nImageType;
5175 if ( pGrfNode && pGrfNode->IsLinkedFile() )
5177 // linked image, just create the relation
5178 OUString aFileName;
5179 pGrfNode->GetFileFilterNms( &aFileName, nullptr );
5181 sal_Int32 const nFragment(aFileName.indexOf('#'));
5182 sal_Int32 const nForbiddenU(aFileName.indexOf("%5C"));
5183 sal_Int32 const nForbiddenL(aFileName.indexOf("%5c"));
5184 if ( (nForbiddenU != -1 && (nFragment == -1 || nForbiddenU < nFragment))
5185 || (nForbiddenL != -1 && (nFragment == -1 || nForbiddenL < nFragment)))
5187 SAL_WARN("sw.ww8", "DocxAttributeOutput::FlyFrameGraphic: ignoring image with invalid link URL");
5188 return;
5191 // TODO Convert the file name to relative for better interoperability
5193 aRelId = m_rExport.AddRelation(
5194 oox::getRelationship(Relationship::IMAGE),
5195 aFileName );
5197 nImageType = XML_link;
5199 else
5201 // inline, we also have to write the image itself
5202 Graphic aGraphic;
5203 if (pGrfNode)
5204 aGraphic = pGrfNode->GetGrf();
5205 else
5206 aGraphic = *pOLENode->GetGraphic();
5208 m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream
5209 OUString aImageId = m_rDrawingML.WriteImage(aGraphic, false);
5210 aRelId = OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8);
5212 nImageType = XML_embed;
5215 // In case there are any grab-bag items on the graphic frame, emit them now.
5216 // These are always character grab-bags, as graphics are at-char or as-char in Word.
5217 if (const SfxGrabBagItem* pGrabBag = pFrameFormat->GetAttrSet().GetItemIfSet(RES_FRMATR_GRABBAG))
5219 CharGrabBag(*pGrabBag);
5222 rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes(
5223 FastSerializerHelper::createAttrList());
5224 if (pGrfNode)
5226 const SwAttrSet& rSet = pGrfNode->GetSwAttrSet();
5227 MirrorGraph eMirror = rSet.Get(RES_GRFATR_MIRRORGRF).GetValue();
5228 if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both)
5229 // Mirror on the vertical axis is a horizontal flip.
5230 xFrameAttributes->add(XML_flipH, "1");
5231 // RES_GRFATR_ROTATION is sal_uInt16; use sal_uInt32 for multiplication later
5232 if (Degree10 nRot = rSet.Get(RES_GRFATR_ROTATION).GetValue())
5234 // RES_GRFATR_ROTATION is in 10ths of degree; convert to 100ths for macro
5235 sal_uInt32 mOOXMLRot = oox::drawingml::ExportRotateClockwisify(to<Degree100>(nRot));
5236 xFrameAttributes->add(XML_rot, OString::number(mOOXMLRot));
5240 css::uno::Reference<css::beans::XPropertySet> xShapePropSet;
5241 if (pSdrObj)
5243 css::uno::Reference<css::drawing::XShape> xShape(
5244 const_cast<SdrObject*>(pSdrObj)->getUnoShape(), css::uno::UNO_QUERY);
5245 xShapePropSet.set(xShape, css::uno::UNO_QUERY);
5246 assert(xShapePropSet);
5249 Size aSize = rSize;
5250 // We need the original (cropped, but unrotated) size of object. So prefer the object data,
5251 // and only use passed frame size as fallback.
5252 if (xShapePropSet)
5254 if (css::awt::Size val; xShapePropSet->getPropertyValue("Size") >>= val)
5255 aSize = Size(o3tl::toTwips(val.Width, o3tl::Length::mm100), o3tl::toTwips(val.Height, o3tl::Length::mm100));
5258 m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, aSize);
5260 // picture description (used for pic:cNvPr later too)
5261 OUString const descr(pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription());
5262 OUString const title(pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle());
5263 auto const docPrattrList(CreateDocPrAttrList(
5264 GetExport(), m_anchorId++, pFrameFormat->GetName(), title, descr));
5265 m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList );
5267 OUString sURL, sRelId;
5268 if (xShapePropSet)
5270 xShapePropSet->getPropertyValue("HyperLinkURL") >>= sURL;
5271 if(!sURL.isEmpty())
5273 if (sURL.startsWith("#") && sURL.indexOf(' ') != -1 && !sURL.endsWith("|outline") && !sURL.endsWith("|table") &&
5274 !sURL.endsWith("|frame") && !sURL.endsWith("|graphic") && !sURL.endsWith("|ole") && !sURL.endsWith("|region"))
5276 // Spaces are prohibited in bookmark name.
5277 sURL = sURL.replace(' ', '_');
5279 sRelId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
5280 oox::getRelationship(Relationship::HYPERLINK),
5281 sURL, !sURL.startsWith("#") );
5282 m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
5283 FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main",
5284 FSNS( XML_r, XML_id ), sRelId);
5286 AddExtLst(m_pSerializer, GetExport(), xShapePropSet);
5289 m_pSerializer->endElementNS( XML_wp, XML_docPr );
5291 m_pSerializer->startElementNS(XML_wp, XML_cNvGraphicFramePr);
5292 // TODO change aspect?
5293 m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks,
5294 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)),
5295 XML_noChangeAspect, "1" );
5296 m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr );
5298 m_pSerializer->startElementNS( XML_a, XML_graphic,
5299 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5300 m_pSerializer->startElementNS( XML_a, XML_graphicData,
5301 XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture" );
5303 m_pSerializer->startElementNS( XML_pic, XML_pic,
5304 FSNS( XML_xmlns, XML_pic ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)) );
5306 m_pSerializer->startElementNS(XML_pic, XML_nvPicPr);
5307 // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes
5308 m_pSerializer->startElementNS(XML_pic, XML_cNvPr, docPrattrList);
5310 if(!sURL.isEmpty())
5311 m_pSerializer->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
5313 m_pSerializer->endElementNS( XML_pic, XML_cNvPr );
5315 m_pSerializer->startElementNS(XML_pic, XML_cNvPicPr);
5316 // TODO change aspect?
5317 m_pSerializer->singleElementNS( XML_a, XML_picLocks,
5318 XML_noChangeAspect, "1", XML_noChangeArrowheads, "1" );
5319 m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr );
5320 m_pSerializer->endElementNS( XML_pic, XML_nvPicPr );
5322 // the actual picture
5323 m_pSerializer->startElementNS(XML_pic, XML_blipFill);
5325 /* At this point we are certain that, WriteImage returns empty RelId
5326 for unhandled graphic type. Therefore we write the picture description
5327 and not the relation( coz there ain't any), so that the user knows
5328 there is an image/graphic in the doc but it is broken instead of
5329 completely discarding it.
5331 if ( aRelId.isEmpty() )
5332 m_pSerializer->startElementNS(XML_a, XML_blip);
5333 else
5334 m_pSerializer->startElementNS(XML_a, XML_blip, FSNS(XML_r, nImageType), aRelId);
5336 const SfxEnumItemInterface* pGrafModeItem = nullptr;
5337 if ( pGrfNode && (pGrafModeItem = pGrfNode->GetSwAttrSet().GetItemIfSet(RES_GRFATR_DRAWMODE)))
5339 GraphicDrawMode nMode = static_cast<GraphicDrawMode>(pGrafModeItem->GetEnumValue());
5340 if (nMode == GraphicDrawMode::Greys)
5341 m_pSerializer->singleElementNS (XML_a, XML_grayscl);
5342 else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice
5343 m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000));
5344 else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
5345 m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000) );
5347 m_pSerializer->endElementNS( XML_a, XML_blip );
5349 if (xShapePropSet)
5350 WriteSrcRect(xShapePropSet, pFrameFormat);
5352 m_pSerializer->startElementNS(XML_a, XML_stretch);
5353 m_pSerializer->singleElementNS(XML_a, XML_fillRect);
5354 m_pSerializer->endElementNS( XML_a, XML_stretch );
5355 m_pSerializer->endElementNS( XML_pic, XML_blipFill );
5357 // TODO setup the right values below
5358 m_pSerializer->startElementNS(XML_pic, XML_spPr, XML_bwMode, "auto");
5360 m_pSerializer->startElementNS(XML_a, XML_xfrm, xFrameAttributes);
5362 m_pSerializer->singleElementNS(XML_a, XML_off, XML_x, "0", XML_y, "0");
5363 OString aWidth( OString::number( TwipsToEMU( aSize.Width() ) ) );
5364 OString aHeight( OString::number( TwipsToEMU( aSize.Height() ) ) );
5365 m_pSerializer->singleElementNS(XML_a, XML_ext, XML_cx, aWidth, XML_cy, aHeight);
5366 m_pSerializer->endElementNS( XML_a, XML_xfrm );
5367 m_pSerializer->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect");
5368 m_pSerializer->singleElementNS(XML_a, XML_avLst);
5369 m_pSerializer->endElementNS( XML_a, XML_prstGeom );
5371 const SvxBoxItem& rBoxItem = pFrameFormat->GetBox();
5372 const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT);
5373 const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT);
5374 const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP);
5375 const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM);
5376 if (pLeft || pRight || pTop || pBottom)
5377 m_rExport.SdrExporter().writeBoxItemLine(rBoxItem);
5379 m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat);
5381 m_pSerializer->endElementNS( XML_pic, XML_spPr );
5383 m_pSerializer->endElementNS( XML_pic, XML_pic );
5385 m_pSerializer->endElementNS( XML_a, XML_graphicData );
5386 m_pSerializer->endElementNS( XML_a, XML_graphic );
5387 m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat);
5390 void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat, const sal_Int8 nFormulaAlignment )
5392 if( WriteOLEChart( pSdrObj, rSize, pFlyFrameFormat ))
5393 return;
5394 if( WriteOLEMath( rOLENode , nFormulaAlignment))
5395 return;
5396 PostponeOLE( rOLENode, rSize, pFlyFrameFormat );
5399 bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5401 uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
5402 if (!xShape.is())
5403 return false;
5405 uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY);
5406 if (!xPropSet.is())
5407 return false;
5409 OUString clsid; // why is the property of type string, not sequence<byte>?
5410 xPropSet->getPropertyValue("CLSID") >>= clsid;
5411 assert(!clsid.isEmpty());
5412 SvGlobalName aClassID;
5413 bool const isValid(aClassID.MakeId(clsid));
5414 assert(isValid); (void)isValid;
5416 if (!SotExchange::IsChart(aClassID))
5417 return false;
5419 m_aPostponedCharts.push_back(PostponedChart(pSdrObj, rSize, pFlyFrameFormat));
5420 return true;
5424 * Write chart hierarchy in w:drawing after end element of w:rPr tag.
5426 void DocxAttributeOutput::WritePostponedChart()
5428 if (m_aPostponedCharts.empty())
5429 return;
5431 for (const PostponedChart& rChart : m_aPostponedCharts)
5433 uno::Reference< chart2::XChartDocument > xChartDoc;
5434 uno::Reference< drawing::XShape > xShape(const_cast<SdrObject*>(rChart.object)->getUnoShape(), uno::UNO_QUERY );
5435 if( xShape.is() )
5437 uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
5438 if( xPropSet.is() )
5439 xChartDoc.set( xPropSet->getPropertyValue( "Model" ), uno::UNO_QUERY );
5442 if( xChartDoc.is() )
5444 SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart ");
5446 m_rExport.SdrExporter().startDMLAnchorInline(rChart.frame, rChart.size);
5448 OUString sName("Object 1");
5449 uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY );
5450 if( xNamed.is() )
5451 sName = xNamed->getName();
5453 // tdf#153203 export a11y related properties
5454 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
5455 OUString const title(xShapeProps->getPropertyValue("Title").get<OUString>());
5456 OUString const descr(xShapeProps->getPropertyValue("Description").get<OUString>());
5458 /* If there is a scenario where a chart is followed by a shape
5459 which is being exported as an alternate content then, the
5460 docPr Id is being repeated, ECMA 20.4.2.5 says that the
5461 docPr Id should be unique, ensuring the same here.
5463 auto const docPrattrList(CreateDocPrAttrList(
5464 GetExport(), m_anchorId++, sName, title, descr));
5465 m_pSerializer->singleElementNS(XML_wp, XML_docPr, docPrattrList);
5467 m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
5469 m_pSerializer->startElementNS( XML_a, XML_graphic,
5470 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5472 m_pSerializer->startElementNS( XML_a, XML_graphicData,
5473 XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
5475 OString aRelId;
5476 m_nChartCount++;
5477 aRelId = m_rExport.OutputChart( xChartDoc, m_nChartCount, m_pSerializer );
5479 m_pSerializer->singleElementNS( XML_c, XML_chart,
5480 FSNS( XML_xmlns, XML_c ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)),
5481 FSNS( XML_xmlns, XML_r ), GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)),
5482 FSNS( XML_r, XML_id ), aRelId );
5484 m_pSerializer->endElementNS( XML_a, XML_graphicData );
5485 m_pSerializer->endElementNS( XML_a, XML_graphic );
5487 m_rExport.SdrExporter().endDMLAnchorInline(rChart.frame);
5491 m_aPostponedCharts.clear();
5494 bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode ,const sal_Int8 nAlign)
5496 uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef());
5497 SvGlobalName aObjName(xObj->getClassID());
5499 if( !SotExchange::IsMath(aObjName) )
5500 return false;
5504 PostponedMathObjects aPostponedMathObject;
5505 aPostponedMathObject.pMathObject = const_cast<SwOLENode*>( &rOLENode);
5506 aPostponedMathObject.nMathObjAlignment = nAlign;
5507 m_aPostponedMaths.push_back(aPostponedMathObject);
5509 catch (const uno::Exception&)
5512 return true;
5515 void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath, sal_Int8 nAlign)
5517 uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef());
5518 if (embed::EmbedStates::LOADED == xObj->getCurrentState())
5520 // must be running so there is a Component
5523 xObj->changeState(embed::EmbedStates::RUNNING);
5525 catch (const uno::Exception&)
5529 uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY );
5530 if (!xInterface.is())
5532 SAL_WARN("sw.ww8", "Broken math object");
5533 return;
5535 if( oox::FormulaImExportBase* formulaexport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
5536 formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(),
5537 oox::drawingml::DOCUMENT_DOCX, nAlign);
5538 else
5539 OSL_FAIL( "Math OLE object cannot write out OOXML" );
5542 void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject)
5544 if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm)
5545 return;
5547 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5548 if (!pFormObj)
5549 return;
5551 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5552 uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5553 if (!xInfo.is())
5554 return;
5556 if (xInfo->supportsService("com.sun.star.form.component.DateField"))
5558 // gather component properties
5560 OUString sDateFormat;
5561 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5563 OString sDate;
5564 OUString aContentText;
5565 bool bHasDate = false;
5566 css::util::Date aUNODate;
5567 if (xPropertySet->getPropertyValue("Date") >>= aUNODate)
5569 bHasDate = true;
5570 Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year);
5571 sDate = DateToOString(aDate);
5572 aContentText = OUString::createFromAscii(DateToDDMMYYYYOString(aDate));
5573 sDateFormat = "dd/MM/yyyy";
5575 else
5577 aContentText = xPropertySet->getPropertyValue("HelpText").get<OUString>();
5578 if(sDateFormat.isEmpty())
5579 sDateFormat = "dd/MM/yyyy"; // Need to set date format even if there is no date set
5582 // output component
5584 m_pSerializer->startElementNS(XML_w, XML_sdt);
5585 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5587 if (bHasDate)
5588 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sDate);
5589 else
5590 m_pSerializer->startElementNS(XML_w, XML_date);
5592 m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), sDateFormat);
5593 m_pSerializer->singleElementNS(XML_w, XML_lid,
5594 FSNS(XML_w, XML_val), "en-US");
5595 m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
5596 FSNS(XML_w, XML_val), "dateTime");
5597 m_pSerializer->singleElementNS(XML_w, XML_calendar,
5598 FSNS(XML_w, XML_val), "gregorian");
5600 m_pSerializer->endElementNS(XML_w, XML_date);
5601 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5603 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5604 m_pSerializer->startElementNS(XML_w, XML_r);
5606 RunText(aContentText);
5607 m_pSerializer->endElementNS(XML_w, XML_r);
5608 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5610 m_pSerializer->endElementNS(XML_w, XML_sdt);
5612 else if (xInfo->supportsService("com.sun.star.form.component.ComboBox"))
5614 // gather component properties
5616 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5617 OUString sText = xPropertySet->getPropertyValue("Text").get<OUString>();
5618 const uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue("StringItemList").get< uno::Sequence<OUString> >();
5620 // output component
5622 m_pSerializer->startElementNS(XML_w, XML_sdt);
5623 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5625 m_pSerializer->startElementNS(XML_w, XML_dropDownList);
5627 for (const auto& rItem : aItems)
5629 m_pSerializer->singleElementNS(XML_w, XML_listItem,
5630 FSNS(XML_w, XML_displayText), rItem,
5631 FSNS(XML_w, XML_value), rItem);
5634 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
5635 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5637 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5638 m_pSerializer->startElementNS(XML_w, XML_r);
5639 RunText(sText);
5640 m_pSerializer->endElementNS(XML_w, XML_r);
5641 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5643 m_pSerializer->endElementNS(XML_w, XML_sdt);
5647 void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun)
5649 for( const auto & rPostponedDrawing : m_aPostponedActiveXControls )
5651 WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun);
5653 m_aPostponedActiveXControls.clear();
5657 void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun)
5659 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5660 if (!pFormObj)
5661 return;
5663 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5664 if (!xControlModel.is())
5665 return;
5667 const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER);
5669 if(!bInsideRun)
5671 m_pSerializer->startElementNS(XML_w, XML_r);
5674 // w:pict for floating embedded control and w:object for inline embedded control
5675 if(bAnchoredInline)
5676 m_pSerializer->startElementNS(XML_w, XML_object);
5677 else
5678 m_pSerializer->startElementNS(XML_w, XML_pict);
5680 // write ActiveX fragment and ActiveX binary
5681 uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY);
5682 std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel);
5684 // VML shape definition
5685 m_rExport.VMLExporter().SetSkipwzName(true);
5686 m_rExport.VMLExporter().SetHashMarkForType(true);
5687 m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_");
5688 OString sShapeId;
5689 if(bAnchoredInline)
5691 sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true);
5693 else
5695 SwFormatFollowTextFlow const& rFlow(rFrameFormat.GetFollowTextFlow());
5696 const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient();
5697 const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient();
5698 SwFormatSurround const& rSurround(rFrameFormat.GetSurround());
5699 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
5700 sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject,
5701 rFlow.GetValue(),
5702 rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(),
5703 rHoriOri.GetRelationOrient(),
5704 rVertOri.GetRelationOrient(),
5705 pAttrList.get(),
5706 true);
5708 // Restore default values
5709 m_rExport.VMLExporter().SetSkipwzName(false);
5710 m_rExport.VMLExporter().SetHashMarkForType(false);
5711 m_rExport.VMLExporter().OverrideShapeIDGen(false);
5713 // control
5714 m_pSerializer->singleElementNS(XML_w, XML_control,
5715 FSNS(XML_r, XML_id), sRelIdAndName.first,
5716 FSNS(XML_w, XML_name), sRelIdAndName.second,
5717 FSNS(XML_w, XML_shapeid), sShapeId);
5719 if(bAnchoredInline)
5720 m_pSerializer->endElementNS(XML_w, XML_object);
5721 else
5722 m_pSerializer->endElementNS(XML_w, XML_pict);
5724 if(!bInsideRun)
5726 m_pSerializer->endElementNS(XML_w, XML_r);
5730 bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const
5732 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5733 if (!pFormObj)
5734 return false;
5736 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5737 if (!xControlModel.is())
5738 return false;
5740 uno::Reference< css::frame::XModel > xModel( m_rExport.m_rDoc.GetDocShell() ? m_rExport.m_rDoc.GetDocShell()->GetModel() : nullptr );
5741 if (!xModel.is())
5742 return false;
5744 uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5745 if (!xInfo.is())
5746 return false;
5748 // See WritePostponedFormControl
5749 // By now date field and combobox is handled on a different way, so let's not interfere with the other method.
5750 if(xInfo->supportsService("com.sun.star.form.component.DateField") ||
5751 xInfo->supportsService("com.sun.star.form.component.ComboBox"))
5752 return false;
5754 oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel);
5755 return exportHelper.isValid();
5758 void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5760 if( !m_oPostponedOLEs )
5761 //cannot be postponed, try to write now
5762 WriteOLE( rNode, rSize, pFlyFrameFormat );
5763 else
5764 m_oPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) );
5768 * Write w:object hierarchy for embedded objects after end element of w:rPr tag.
5770 void DocxAttributeOutput::WritePostponedOLE()
5772 if( !m_oPostponedOLEs )
5773 return;
5775 for( const auto & rPostponedOLE : *m_oPostponedOLEs )
5777 WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame );
5780 // clear list of postponed objects
5781 m_oPostponedOLEs.reset();
5784 void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5786 OSL_ASSERT(pFlyFrameFormat);
5788 // get interoperability information about embedded objects
5789 uno::Reference< beans::XPropertySet > xPropSet( m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
5790 uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes;
5791 xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag;
5792 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5793 [](const beans::PropertyValue& rProp) { return rProp.Name == "EmbeddedObjects"; });
5794 if (pProp != std::cend(aGrabBag))
5795 pProp->Value >>= aObjectsInteropList;
5797 SwOLEObj& aObject = rNode.GetOLEObj();
5798 uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() );
5799 comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer();
5800 OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj );
5802 // set some attributes according to the type of the embedded object
5803 OUString sProgID, sDrawAspect;
5804 switch (rNode.GetAspect())
5806 case embed::Aspects::MSOLE_CONTENT: sDrawAspect = "Content"; break;
5807 case embed::Aspects::MSOLE_DOCPRINT: sDrawAspect = "DocPrint"; break;
5808 case embed::Aspects::MSOLE_ICON: sDrawAspect = "Icon"; break;
5809 case embed::Aspects::MSOLE_THUMBNAIL: sDrawAspect = "Thumbnail"; break;
5810 default:
5811 SAL_WARN("sw.ww8", "DocxAttributeOutput::WriteOLE: invalid aspect value");
5813 auto pObjectsInterop = std::find_if(std::cbegin(aObjectsInteropList), std::cend(aObjectsInteropList),
5814 [&sObjectName](const beans::PropertyValue& rProp) { return rProp.Name == sObjectName; });
5815 if (pObjectsInterop != std::cend(aObjectsInteropList))
5816 pObjectsInterop->Value >>= aObjectInteropAttributes;
5818 for( const auto& rObjectInteropAttribute : std::as_const(aObjectInteropAttributes) )
5820 if ( rObjectInteropAttribute.Name == "ProgID" )
5822 rObjectInteropAttribute.Value >>= sProgID;
5826 // write embedded file
5827 OString sId = m_rExport.WriteOLEObject(aObject, sProgID);
5829 if( sId.isEmpty() )
5831 // the embedded file could not be saved
5832 // fallback: save as an image
5833 FlyFrameGraphic( nullptr, rSize, pFlyFrameFormat, &rNode );
5834 return;
5837 // write preview image
5838 const Graphic* pGraphic = rNode.GetGraphic();
5839 m_rDrawingML.SetFS(m_pSerializer);
5840 OUString sImageId = m_rDrawingML.WriteImage( *pGraphic );
5842 if ( sDrawAspect == "Content" )
5846 awt::Size aSize = xObj->getVisualAreaSize( rNode.GetAspect() );
5848 MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rNode.GetAspect() ) );
5849 Size aOriginalSize( OutputDevice::LogicToLogic(Size( aSize.Width, aSize.Height),
5850 MapMode(aUnit), MapMode(MapUnit::MapTwip)));
5852 m_pSerializer->startElementNS( XML_w, XML_object,
5853 FSNS(XML_w, XML_dxaOrig), OString::number(aOriginalSize.Width()),
5854 FSNS(XML_w, XML_dyaOrig), OString::number(aOriginalSize.Height()) );
5856 catch ( uno::Exception& )
5858 m_pSerializer->startElementNS(XML_w, XML_object);
5861 else
5863 m_pSerializer->startElementNS(XML_w, XML_object);
5866 OString sShapeId = "ole_" + sId;
5868 //OLE Shape definition
5869 WriteOLEShape(*pFlyFrameFormat, rSize, sShapeId, sImageId);
5871 //OLE Object definition
5872 m_pSerializer->singleElementNS(XML_o, XML_OLEObject,
5873 XML_Type, "Embed",
5874 XML_ProgID, sProgID,
5875 XML_ShapeID, sShapeId,
5876 XML_DrawAspect, sDrawAspect,
5877 XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
5878 FSNS( XML_r, XML_id ), sId );
5880 m_pSerializer->endElementNS(XML_w, XML_object);
5883 void DocxAttributeOutput::WriteOLEShape(const SwFlyFrameFormat& rFrameFormat, const Size& rSize,
5884 std::string_view rShapeId, const OUString& rImageId)
5886 assert(m_pSerializer);
5888 //Here is an attribute list where we collect the attributes what we want to export
5889 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
5890 pAttr->add(XML_id, rShapeId);
5892 //export the fixed shape type for picture frame
5893 m_pSerializer->write(vml::VMLExport::GetVMLShapeTypeDefinition(rShapeId, true));
5894 pAttr->add(XML_type, OString::Concat("_x0000_t") + rShapeId);
5896 //Export the style attribute for position and size
5897 pAttr->add(XML_style, GetOLEStyle(rFrameFormat, rSize));
5898 //Get the OLE frame
5899 const SvxBoxItem& rBox = rFrameFormat.GetAttrSet().GetBox();
5900 OString sLineType;
5901 OString sDashType;
5902 //Word does not handle differently the four sides,
5903 //so we have to choose, and the left one is the winner:
5904 if (rBox.GetLeft())
5906 //Get the left border color and width
5907 const Color aLineColor = rBox.GetLeft()->GetColor();
5908 const tools::Long aLineWidth = rBox.GetLeft()->GetWidth();
5910 //Convert the left OLE border style to OOXML
5911 //FIXME improve if it's necessary
5912 switch (rBox.GetLeft()->GetBorderLineStyle())
5914 case SvxBorderLineStyle::SOLID:
5915 sLineType = OString("Single");
5916 sDashType = OString("Solid");
5917 break;
5918 case SvxBorderLineStyle::DASHED:
5919 sLineType = OString("Single");
5920 sDashType = OString("Dash");
5921 break;
5922 case SvxBorderLineStyle::DASH_DOT:
5923 sLineType = OString("Single");
5924 sDashType = OString("DashDot");
5925 break;
5926 case SvxBorderLineStyle::DASH_DOT_DOT:
5927 sLineType = OString("Single");
5928 sDashType = OString("ShortDashDotDot");
5929 break;
5930 case SvxBorderLineStyle::DOTTED:
5931 sLineType = OString("Single");
5932 sDashType = OString("Dot");
5933 break;
5934 case SvxBorderLineStyle::DOUBLE:
5935 sLineType = OString("ThinThin");
5936 sDashType = OString("Solid");
5937 break;
5938 case SvxBorderLineStyle::DOUBLE_THIN:
5939 sLineType = OString("ThinThin");
5940 sDashType = OString("Solid");
5941 break;
5942 case SvxBorderLineStyle::EMBOSSED:
5943 sLineType = OString("Single");
5944 sDashType = OString("Solid");
5945 break;
5946 case SvxBorderLineStyle::ENGRAVED:
5947 sLineType = OString("Single");
5948 sDashType = OString("Solid");
5949 break;
5950 case SvxBorderLineStyle::FINE_DASHED:
5951 sLineType = OString("Single");
5952 sDashType = OString("Dot");
5953 break;
5954 case SvxBorderLineStyle::INSET:
5955 sLineType = OString("Single");
5956 sDashType = OString("Solid");
5957 break;
5958 case SvxBorderLineStyle::OUTSET:
5959 sLineType = OString("Single");
5960 sDashType = OString("Solid");
5961 break;
5962 case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
5963 case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
5964 case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
5965 sLineType = OString("ThickThin");
5966 sDashType = OString("Solid");
5967 break;
5968 case SvxBorderLineStyle::THINTHICK_LARGEGAP:
5969 case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
5970 case SvxBorderLineStyle::THINTHICK_SMALLGAP:
5971 sLineType = OString("ThinThick");
5972 sDashType = OString("Solid");
5973 break;
5974 case SvxBorderLineStyle::NONE:
5975 sLineType = OString("");
5976 sDashType = OString("");
5977 break;
5978 default:
5979 SAL_WARN("sw.ww8", "Unknown line type on OOXML ELE export!");
5980 break;
5983 //If there is a line add it for export
5984 if (!sLineType.isEmpty() && !sDashType.isEmpty())
5986 pAttr->add(XML_stroked, "t");
5987 pAttr->add(XML_strokecolor, "#" + msfilter::util::ConvertColor(aLineColor));
5988 pAttr->add(XML_strokeweight, OString::number(aLineWidth / 20) + "pt");
5992 //Let's check the filltype of the OLE
5993 switch (rFrameFormat.GetAttrSet().Get(XATTR_FILLSTYLE).GetValue())
5995 case drawing::FillStyle::FillStyle_SOLID:
5997 //If solid, we get the color and add it to the exporter
5998 const Color rShapeColor = rFrameFormat.GetAttrSet().Get(XATTR_FILLCOLOR).GetColorValue();
5999 pAttr->add(XML_filled, "t");
6000 pAttr->add(XML_fillcolor, "#" + msfilter::util::ConvertColor(rShapeColor));
6001 break;
6003 case drawing::FillStyle::FillStyle_GRADIENT:
6004 case drawing::FillStyle::FillStyle_HATCH:
6005 case drawing::FillStyle::FillStyle_BITMAP:
6006 //TODO
6007 break;
6008 case drawing::FillStyle::FillStyle_NONE:
6010 pAttr->add(XML_filled, "f");
6011 break;
6013 default:
6014 SAL_WARN("sw.ww8", "Unknown fill type on OOXML OLE export!");
6015 break;
6017 pAttr->addNS(XML_o, XML_ole, ""); //compulsory, even if it's empty
6018 m_pSerializer->startElementNS(XML_v, XML_shape, pAttr);//Write the collected attrs...
6020 if (!sLineType.isEmpty() && !sDashType.isEmpty()) //If there is a line/dash style it is time to export it
6022 m_pSerializer->singleElementNS(XML_v, XML_stroke, XML_linestyle, sLineType, XML_dashstyle, sDashType);
6025 // shape filled with the preview image
6026 m_pSerializer->singleElementNS(XML_v, XML_imagedata,
6027 FSNS(XML_r, XML_id), rImageId,
6028 FSNS(XML_o, XML_title), "");
6030 //export wrap settings
6031 if (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) //As-char objs does not have surround.
6032 ExportOLESurround(rFrameFormat.GetSurround());
6034 m_pSerializer->endElementNS(XML_v, XML_shape);
6037 OString DocxAttributeOutput::GetOLEStyle(const SwFlyFrameFormat& rFormat, const Size& rSize)
6039 //tdf#131539: Export OLE positions in docx:
6040 //This string will store the position output for the xml
6041 OString aPos;
6042 //This string will store the relative position for aPos
6043 OString aAnch;
6045 if (rFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
6047 //Get the horizontal alignment of the OLE via the frame format, to aHAlign
6048 OString aHAlign = convertToOOXMLHoriOrient(rFormat.GetHoriOrient().GetHoriOrient(),
6049 rFormat.GetHoriOrient().IsPosToggle());
6050 //Get the vertical alignment of the OLE via the frame format to aVAlign
6051 OString aVAlign = convertToOOXMLVertOrient(rFormat.GetVertOrient().GetVertOrient());
6053 // Check if the OLE anchored to page:
6054 const bool bIsPageAnchor = rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6056 //Get the relative horizontal positions for the anchors
6057 OString aHAnch
6058 = bIsPageAnchor
6059 ? OString("page")
6060 : convertToOOXMLHoriOrientRel(rFormat.GetHoriOrient().GetRelationOrient());
6061 //Get the relative vertical positions for the anchors
6062 OString aVAnch = convertToOOXMLVertOrientRel(rFormat.GetVertOrient().GetRelationOrient());
6064 //Choice that the horizontal position is relative or not
6065 if (!aHAlign.isEmpty())
6066 aHAlign = ";mso-position-horizontal:" + aHAlign;
6067 aHAlign = ";mso-position-horizontal-relative:" + aHAnch;
6069 //Choice that the vertical position is relative or not
6070 if (!aVAlign.isEmpty())
6071 aVAlign = ";mso-position-vertical:" + aVAlign;
6072 aVAlign = ";mso-position-vertical-relative:" + aVAnch;
6074 //Set the anchoring information into one string for aPos
6075 aAnch = aHAlign + aVAlign;
6077 //Query the positions to aPos from frameformat
6078 aPos =
6079 "position:absolute;margin-left:" + OString::number(double(rFormat.GetHoriOrient().GetPos()) / 20) +
6080 "pt;margin-top:" + OString::number(double(rFormat.GetVertOrient().GetPos()) / 20) + "pt;";
6083 OString sShapeStyle = "width:" + OString::number( double( rSize.Width() ) / 20 ) +
6084 "pt;height:" + OString::number( double( rSize.Height() ) / 20 ) +
6085 "pt"; //from VMLExport::AddRectangleDimensions(), it does: value/20
6087 const SvxLRSpaceItem& rLRSpace = rFormat.GetLRSpace();
6088 if (rLRSpace.IsExplicitZeroMarginValLeft() || rLRSpace.GetLeft())
6089 sShapeStyle += ";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt";
6090 if (rLRSpace.IsExplicitZeroMarginValRight() || rLRSpace.GetRight())
6091 sShapeStyle += ";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt";
6092 const SvxULSpaceItem& rULSpace = rFormat.GetULSpace();
6093 if (rULSpace.GetUpper())
6094 sShapeStyle += ";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt";
6095 if (rULSpace.GetLower())
6096 sShapeStyle += ";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt";
6098 //Export anchor setting, if it exists
6099 if (!aPos.isEmpty() && !aAnch.isEmpty())
6100 sShapeStyle = aPos + sShapeStyle + aAnch;
6102 return sShapeStyle;
6105 void DocxAttributeOutput::ExportOLESurround(const SwFormatSurround& rWrap)
6107 const bool bIsContour = rWrap.IsContour(); //Has the shape contour or not
6108 OString sSurround;
6109 OString sSide;
6111 //Map the ODF wrap settings to OOXML one
6112 switch (rWrap.GetSurround())
6114 case text::WrapTextMode::WrapTextMode_NONE:
6115 sSurround = OString("topAndBottom");
6116 break;
6117 case text::WrapTextMode::WrapTextMode_PARALLEL:
6118 sSurround = bIsContour ? OString("tight") : OString("square");
6119 break;
6120 case text::WrapTextMode::WrapTextMode_DYNAMIC:
6121 sSide = OString("largest");
6122 sSurround = bIsContour ? OString("tight") : OString("square");
6123 break;
6124 case text::WrapTextMode::WrapTextMode_LEFT:
6125 sSide = OString("left");
6126 sSurround = bIsContour ? OString("tight") : OString("square");
6127 break;
6128 case text::WrapTextMode::WrapTextMode_RIGHT:
6129 sSide = OString("right");
6130 sSurround = bIsContour ? OString("tight") : OString("square");
6131 break;
6132 default:
6133 SAL_WARN("sw.ww8", "Unknown surround type on OOXML export!");
6134 break;
6137 //if there is a setting export it:
6138 if (!sSurround.isEmpty())
6140 if (sSide.isEmpty())
6141 m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround);
6142 else
6143 m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround, XML_side, sSide);
6147 void DocxAttributeOutput::WritePostponedCustomShape()
6149 if (!m_oPostponedCustomShape)
6150 return;
6152 for( const auto & rPostponedDrawing : *m_oPostponedCustomShape)
6154 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6155 if ( IsAlternateContentChoiceOpen() )
6156 m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6157 else
6158 m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6159 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6161 m_oPostponedCustomShape.reset();
6164 void DocxAttributeOutput::WritePostponedDMLDrawing()
6166 if (!m_oPostponedDMLDrawings)
6167 return;
6169 // Clear the list early, this method may be called recursively.
6170 std::optional< std::vector<PostponedDrawing> > pPostponedDMLDrawings(std::move(m_oPostponedDMLDrawings));
6171 std::optional< std::vector<PostponedOLE> > pPostponedOLEs(std::move(m_oPostponedOLEs));
6172 m_oPostponedDMLDrawings.reset();
6173 m_oPostponedOLEs.reset();
6175 for( const auto & rPostponedDrawing : *pPostponedDMLDrawings )
6177 // Avoid w:drawing within another w:drawing.
6178 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6179 if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) )
6180 m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6181 else
6182 m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6183 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6186 m_oPostponedOLEs = std::move(pPostponedOLEs);
6189 void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
6191 m_pSerializer->mark(Tag_OutputFlyFrame);
6193 switch ( rFrame.GetWriterType() )
6195 case ww8::Frame::eGraphic:
6197 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6198 const SwNode *pNode = rFrame.GetContent();
6199 const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr;
6200 if ( pGrfNode )
6202 if (!m_oPostponedGraphic)
6204 m_bPostponedProcessingFly = false ;
6205 FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj);
6207 else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6208 { // so write it out later
6209 m_bPostponedProcessingFly = true ;
6210 m_oPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj));
6214 break;
6215 case ww8::Frame::eDrawing:
6217 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6218 if ( pSdrObj )
6220 const bool bIsDiagram(nullptr != pSdrObj && pSdrObj->isDiagram());
6222 if (bIsDiagram)
6224 if ( !m_oPostponedDiagrams )
6226 m_bPostponedProcessingFly = false ;
6227 m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6229 else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6230 { // so write it out later
6231 m_bPostponedProcessingFly = true ;
6232 m_oPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) ));
6235 else
6237 if (!m_oPostponedDMLDrawings)
6239 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6240 if ( IsAlternateContentChoiceOpen() )
6242 // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing.
6243 if( m_rExport.SdrExporter().IsDrawingOpen() )
6244 m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6245 else
6246 m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++);
6248 else
6249 m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6250 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6252 m_bPostponedProcessingFly = false ;
6254 // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added
6255 // m_bParagraphFrameOpen: check if the frame is open.
6256 else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen)
6257 m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6258 else
6260 // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
6261 m_bPostponedProcessingFly = true ;
6262 m_oPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6267 break;
6268 case ww8::Frame::eTextBox:
6270 // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox().
6271 if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat()))
6272 break;
6274 // If this is a TextBox containing a table which we already exported directly, ignore it
6275 if (m_aFloatingTablesOfParagraph.find(&rFrame.GetFrameFormat()) != m_aFloatingTablesOfParagraph.end())
6276 break;
6278 // The frame output is postponed to the end of the anchor paragraph
6279 bool bDuplicate = false;
6280 const OUString& rName = rFrame.GetFrameFormat().GetName();
6281 unsigned nSize = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
6282 for( unsigned nIndex = 0; nIndex < nSize; ++nIndex )
6284 const OUString& rNameExisting = m_aFramesOfParagraph.top()[nIndex].GetFrameFormat().GetName();
6286 if (!rName.isEmpty() && !rNameExisting.isEmpty())
6288 if (rName == rNameExisting)
6289 bDuplicate = true;
6293 if( !bDuplicate )
6295 m_bPostponedProcessingFly = true ;
6296 if ( m_aFramesOfParagraph.size() )
6297 m_aFramesOfParagraph.top().emplace_back(rFrame);
6300 break;
6301 case ww8::Frame::eOle:
6303 const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat();
6304 const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject();
6305 if ( pSdrObj )
6307 SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1);
6308 SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
6310 //output variable for the formula alignment (default inline)
6311 sal_Int8 nAlign(FormulaImExportBase::eFormulaAlign::INLINE);
6312 auto xObj(rOLENd.GetOLEObj().GetOleRef()); //get the xObject of the formula
6314 //tdf133030: Export formula position
6315 //If we have a formula with inline anchor...
6316 if(SotExchange::IsMath(xObj->getClassID()) && rFrame.IsInline())
6318 SwNode const* const pAnchorNode = rFrameFormat.GetAnchor().GetAnchorNode();
6319 if(pAnchorNode)
6321 //Get the text node what the formula anchored to
6322 const SwTextNode* pTextNode = pAnchorNode->GetTextNode();
6323 if(pTextNode && pTextNode->Len() == 1)
6325 //Get the paragraph alignment
6326 auto aParaAdjust = pTextNode->GetSwAttrSet().GetAdjust().GetAdjust();
6327 //And set the formula according to the paragraph alignment
6328 if (aParaAdjust == SvxAdjust::Center)
6329 nAlign = FormulaImExportBase::eFormulaAlign::CENTER;
6330 else if (aParaAdjust == SvxAdjust::Right)
6331 nAlign = FormulaImExportBase::eFormulaAlign::RIGHT;
6332 else // left in the case of left and justified paragraph alignments
6333 nAlign = FormulaImExportBase::eFormulaAlign::LEFT;
6337 WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ), nAlign);
6338 m_bPostponedProcessingFly = false ;
6341 break;
6342 case ww8::Frame::eFormControl:
6344 const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject();
6345 if(ExportAsActiveXControl(pObject))
6346 m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat()));
6347 else
6348 m_aPostponedFormControls.push_back(pObject);
6349 m_bPostponedProcessingFly = true ;
6351 break;
6352 default:
6353 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " <<
6354 ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox":
6355 ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) );
6356 break;
6359 m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
6362 void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
6364 /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
6365 /// Now if a frame anchored inside another frame, it will
6366 /// not be exported immediately, because OOXML does not
6367 /// support that feature, instead it postponed and exported
6368 /// later when the original shape closed.
6370 if (rFrame.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
6371 || rFrame.IsInline())
6373 m_nEmbedFlyLevel++;
6374 WriteFlyFrame(rFrame);
6375 m_nEmbedFlyLevel--;
6376 return;
6379 if (m_nEmbedFlyLevel == 0)
6381 if (m_vPostponedFlys.empty())
6383 m_nEmbedFlyLevel++;
6384 WriteFlyFrame(rFrame);
6385 m_nEmbedFlyLevel--;
6387 else
6388 for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
6390 m_nEmbedFlyLevel++;
6391 WriteFlyFrame(*it);
6392 it = m_vPostponedFlys.erase(it);
6393 m_nEmbedFlyLevel--;
6396 else
6398 bool bFound = false;
6399 for (const auto& i : m_vPostponedFlys)
6401 if (i.RefersToSameFrameAs(rFrame))
6403 bFound = true;
6404 break;
6407 if (!bFound)
6409 if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
6411 auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
6412 aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
6413 auto aVori(rFrame.GetFrameFormat().GetVertOrient());
6414 aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());
6416 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
6417 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
6418 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());
6420 m_vPostponedFlys.push_back(rFrame);
6427 void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
6429 const EditTextObject& rEditObj = rParaObj.GetTextObject();
6430 MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );
6432 sal_Int32 nPara = rEditObj.GetParagraphCount();
6434 m_pSerializer->startElementNS(XML_w, XML_txbxContent);
6435 for (sal_Int32 n = 0; n < nPara; ++n)
6437 if( n )
6438 aAttrIter.NextPara( n );
6440 OUString aStr( rEditObj.GetText( n ));
6441 sal_Int32 nCurrentPos = 0;
6442 sal_Int32 nEnd = aStr.getLength();
6444 StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false);
6446 // Write paragraph properties.
6447 StartParagraphProperties();
6448 aAttrIter.OutParaAttr(false);
6449 SfxItemSet aParagraphMarkerProperties(m_rExport.m_rDoc.GetAttrPool());
6450 EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr);
6452 do {
6453 const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);
6455 m_pSerializer->startElementNS(XML_w, XML_r);
6457 // Write run properties.
6458 m_pSerializer->startElementNS(XML_w, XML_rPr);
6459 aAttrIter.OutAttr(nCurrentPos);
6460 WriteCollectedRunProperties();
6461 m_pSerializer->endElementNS(XML_w, XML_rPr);
6463 bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
6464 if( !bTextAtr )
6466 OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) );
6467 RunText(aOut);
6470 if ( !m_sRawText.isEmpty() )
6472 RunText( m_sRawText );
6473 m_sRawText.clear();
6476 m_pSerializer->endElementNS( XML_w, XML_r );
6478 nCurrentPos = nNextAttr;
6479 aAttrIter.NextPos();
6481 while( nCurrentPos < nEnd );
6482 EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
6484 m_pSerializer->endElementNS( XML_w, XML_txbxContent );
6487 void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext)
6489 rContext.m_pTableInfo = m_rExport.m_pTableInfo;
6490 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
6492 rContext.m_bTableCellOpen = m_tableReference.m_bTableCellOpen;
6493 m_tableReference.m_bTableCellOpen = false;
6495 rContext.m_nTableDepth = m_tableReference.m_nTableDepth;
6496 m_tableReference.m_nTableDepth = 0;
6498 rContext.m_bStartedParaSdt = m_aParagraphSdt.m_bStartedSdt;
6499 m_aParagraphSdt.m_bStartedSdt = false;
6500 rContext.m_bStartedRunSdt = m_aRunSdt.m_bStartedSdt;
6501 m_aRunSdt.m_bStartedSdt = false;
6503 rContext.m_nHyperLinkCount = m_nHyperLinkCount.back();
6504 m_nHyperLinkCount.back() = 0;
6507 void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext)
6509 m_rExport.m_pTableInfo = rContext.m_pTableInfo;
6510 m_tableReference.m_bTableCellOpen = rContext.m_bTableCellOpen;
6511 m_tableReference.m_nTableDepth = rContext.m_nTableDepth;
6512 m_aParagraphSdt.m_bStartedSdt = rContext.m_bStartedParaSdt;
6513 m_aRunSdt.m_bStartedSdt = rContext.m_bStartedRunSdt;
6514 m_nHyperLinkCount.back() = rContext.m_nHyperLinkCount;
6517 void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape)
6519 DocxTableExportContext aTableExportContext(*this);
6521 SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6522 assert(pTextBox);
6523 const SwPosition* pAnchor = nullptr;
6524 const bool bFlyAtPage = pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6525 if (bFlyAtPage) //tdf135711
6527 auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6528 if (pNdIdx) //Is that possible it is null?
6529 pAnchor = new SwPosition(*pNdIdx);
6531 else
6533 pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6536 if (pAnchor) //pAnchor can be null, so that's why not assert here.
6538 ww8::Frame aFrame(*pTextBox, *pAnchor);
6539 m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true);
6540 if (bFlyAtPage)
6542 delete pAnchor;
6547 void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)
6549 DocxTableExportContext aTableExportContext(*this);
6551 SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6552 assert(pTextBox);
6553 const SwPosition* pAnchor = nullptr;
6554 if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) //tdf135711
6556 auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6557 if (pNdIdx) //Is that possible it is null?
6558 pAnchor = new SwPosition(*pNdIdx);
6560 else
6562 pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6565 if (pAnchor) //pAnchor can be null, so that's why not assert here.
6567 ww8::Frame aFrame(*pTextBox, *pAnchor);
6568 m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true);
6569 if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE)
6571 delete pAnchor;
6576 oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML()
6578 return m_rDrawingML;
6581 bool DocxAttributeOutput::MaybeOutputBrushItem(SfxItemSet const& rSet)
6583 const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
6585 if ((pXFillStyleItem && pXFillStyleItem->GetValue() != drawing::FillStyle_NONE)
6586 || !m_rExport.SdrExporter().getDMLTextFrameSyntax())
6588 return false;
6591 // sw text frames are opaque by default, even with fill none!
6592 std::unique_ptr<SfxItemSet> const pClone(rSet.Clone());
6593 XFillColorItem const aColor(OUString(), COL_WHITE);
6594 pClone->Put(aColor);
6595 // call getSvxBrushItemForSolid - this also takes XFillTransparenceItem into account
6596 XFillStyleItem const aSolid(drawing::FillStyle_SOLID);
6597 pClone->Put(aSolid);
6598 std::unique_ptr<SvxBrushItem> const pBrush(getSvxBrushItemFromSourceSet(*pClone, RES_BACKGROUND));
6599 FormatBackground(*pBrush);
6600 return true;
6603 namespace {
6605 /// Functor to do case-insensitive ordering of OUString instances.
6606 struct OUStringIgnoreCase
6608 bool operator() (std::u16string_view lhs, std::u16string_view rhs) const
6610 return o3tl::compareToIgnoreAsciiCase(lhs, rhs) < 0;
6616 /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not.
6617 static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId)
6619 // If the style has no dedicated STI number, then it's probably a custom style -> qFormat.
6620 if (nWwId == ww::stiUser)
6621 return true;
6623 // Allow exported built-in styles UI language neutral
6624 if ( nWwId == ww::stiNormal ||
6625 ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) ||
6626 nWwId == ww::stiCaption || nWwId == ww::stiTitle ||
6627 nWwId == ww::stiSubtitle || nWwId == ww::stiStrong ||
6628 nWwId == ww::stiEmphasis )
6629 return true;
6631 static o3tl::sorted_vector<OUString, OUStringIgnoreCase> const aAllowlist
6633 "No Spacing",
6634 "List Paragraph",
6635 "Quote",
6636 "Intense Quote",
6637 "Subtle Emphasis",
6638 "Intense Emphasis",
6639 "Subtle Reference",
6640 "Intense Reference",
6641 "Book Title",
6642 "TOC Heading",
6644 // Not custom style? Then we have a list of standard styles which should be qFormat.
6645 return aAllowlist.find(rName) != aAllowlist.end();
6648 void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
6649 sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nLink, sal_uInt16 nWwId, sal_uInt16 nSlot, bool bAutoUpdate )
6651 bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false;
6652 OUString aRsid, aUiPriority;
6653 rtl::Reference<FastAttributeList> pStyleAttributeList = FastSerializerHelper::createAttrList();
6654 uno::Any aAny;
6655 if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)
6657 const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nSlot);
6658 pFormat->GetGrabBagItem(aAny);
6660 else
6662 const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nSlot);
6663 pRule->GetGrabBagItem(aAny);
6665 const uno::Sequence<beans::PropertyValue>& rGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >();
6667 for (const auto& rProp : rGrabBag)
6669 if (rProp.Name == "uiPriority")
6670 aUiPriority = rProp.Value.get<OUString>();
6671 else if (rProp.Name == "qFormat")
6672 bQFormat = true;
6673 else if (rProp.Name == "rsid")
6674 aRsid = rProp.Value.get<OUString>();
6675 else if (rProp.Name == "unhideWhenUsed")
6676 bUnhideWhenUsed = true;
6677 else if (rProp.Name == "semiHidden")
6678 bSemiHidden = true;
6679 else if (rProp.Name == "locked")
6680 bLocked = true;
6681 else if (rProp.Name == "default")
6682 bDefault = rProp.Value.get<bool>();
6683 else if (rProp.Name == "customStyle")
6684 bCustomStyle = rProp.Value.get<bool>();
6685 else
6686 SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name);
6689 const char* pType = nullptr;
6690 switch (eType)
6692 case STYLE_TYPE_PARA:
6693 pType = "paragraph";
6694 break;
6695 case STYLE_TYPE_CHAR:
6696 pType = "character";
6697 break;
6698 case STYLE_TYPE_LIST: pType = "numbering"; break;
6700 pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType);
6701 pStyleAttributeList->add(FSNS(XML_w, XML_styleId), m_rExport.m_pStyles->GetStyleId(nSlot));
6702 if (bDefault)
6703 pStyleAttributeList->add(FSNS(XML_w, XML_default), "1");
6704 if (bCustomStyle)
6705 pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
6706 m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList);
6707 m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
6709 if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
6711 m_pSerializer->singleElementNS( XML_w, XML_basedOn,
6712 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase) );
6715 if ( nNext != nSlot && eType != STYLE_TYPE_LIST)
6717 m_pSerializer->singleElementNS( XML_w, XML_next,
6718 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext) );
6721 if (nLink != 0x0FFF && (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR))
6723 m_pSerializer->singleElementNS(XML_w, XML_link, FSNS(XML_w, XML_val),
6724 m_rExport.m_pStyles->GetStyleId(nLink));
6727 if ( bAutoUpdate )
6728 m_pSerializer->singleElementNS(XML_w, XML_autoRedefine);
6730 if (!aUiPriority.isEmpty())
6731 m_pSerializer->singleElementNS(XML_w, XML_uiPriority, FSNS(XML_w, XML_val), aUiPriority);
6732 if (bSemiHidden)
6733 m_pSerializer->singleElementNS(XML_w, XML_semiHidden);
6734 if (bUnhideWhenUsed)
6735 m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed);
6737 if (bQFormat || lcl_guessQFormat(rName, nWwId))
6738 m_pSerializer->singleElementNS(XML_w, XML_qFormat);
6739 if (bLocked)
6740 m_pSerializer->singleElementNS(XML_w, XML_locked);
6741 if (!aRsid.isEmpty())
6742 m_pSerializer->singleElementNS(XML_w, XML_rsid, FSNS(XML_w, XML_val), aRsid);
6745 void DocxAttributeOutput::EndStyle()
6747 m_pSerializer->endElementNS( XML_w, XML_style );
6750 void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ )
6752 if ( bParProp )
6754 m_pSerializer->startElementNS(XML_w, XML_pPr);
6755 InitCollectedParagraphProperties();
6757 else
6759 m_pSerializer->startElementNS(XML_w, XML_rPr);
6760 InitCollectedRunProperties();
6764 void DocxAttributeOutput::EndStyleProperties( bool bParProp )
6766 if ( bParProp )
6768 WriteCollectedParagraphProperties();
6770 // Merge the marks for the ordered elements
6771 m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
6773 m_pSerializer->endElementNS( XML_w, XML_pPr );
6775 else
6777 WriteCollectedRunProperties();
6779 // Merge the marks for the ordered elements
6780 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
6782 m_pSerializer->endElementNS( XML_w, XML_rPr );
6786 void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/)
6788 // Handled by ParaOutlineLevel() instead.
6791 void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem)
6793 sal_uInt16 nOutLvl = std::min(rItem.GetValue(), sal_uInt16(WW8ListManager::nMaxLevel));
6794 // Outline Level: in LO Body Text = 0, in MS Body Text = 9
6795 nOutLvl = nOutLvl ? nOutLvl - 1 : 9;
6796 m_pSerializer->singleElementNS(XML_w, XML_outlineLvl, FSNS(XML_w, XML_val), OString::number(nOutLvl));
6799 void DocxAttributeOutput::PageBreakBefore( bool bBreak )
6801 if ( bBreak )
6802 m_pSerializer->singleElementNS(XML_w, XML_pageBreakBefore);
6803 else
6804 m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore,
6805 FSNS( XML_w, XML_val ), "false" );
6808 void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak)
6810 switch ( nC )
6812 case msword::ColumnBreak:
6813 // The column break should be output in the next paragraph...
6814 if ( m_nColBreakStatus == COLBRK_WRITE )
6815 m_nColBreakStatus = COLBRK_WRITEANDPOSTPONE;
6816 else
6817 m_nColBreakStatus = COLBRK_POSTPONE;
6818 break;
6819 case msword::PageBreak:
6820 if ( pSectionInfo )
6822 // Detect when the current node is the last node in the
6823 // document: the last section is written explicitly in
6824 // DocxExport::WriteMainText(), don't duplicate that here.
6825 SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetPointNode());
6826 SwNodeIndex aLastNode(m_rExport.m_rDoc.GetNodes().GetEndOfContent(), -1);
6827 bool bEmit = aCurrentNode != aLastNode;
6829 if (!bEmit)
6831 // Need to still emit an empty section at the end of the
6832 // document in case balanced columns are wanted, since the last
6833 // section in Word is always balanced.
6834 sal_uInt16 nColumns = 1;
6835 bool bBalance = false;
6836 if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat)
6838 if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1)))
6840 nColumns = pFormat->GetCol().GetNumCols();
6841 const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns();
6842 bBalance = !rNoBalanced.GetValue();
6845 bEmit = (nColumns > 1 && bBalance);
6848 // don't add section properties if this will be the first
6849 // paragraph in the document
6850 if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit )
6852 // Create a dummy paragraph if needed
6853 m_pSerializer->startElementNS(XML_w, XML_p);
6854 m_pSerializer->startElementNS(XML_w, XML_pPr);
6856 m_rExport.SectionProperties( *pSectionInfo );
6858 m_pSerializer->endElementNS( XML_w, XML_pPr );
6859 if (bExtraPageBreak)
6861 m_pSerializer->startElementNS(XML_w, XML_r);
6862 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6863 m_pSerializer->endElementNS(XML_w, XML_r);
6865 m_pSerializer->endElementNS( XML_w, XML_p );
6867 else
6869 if (bExtraPageBreak && m_bParagraphOpened)
6871 m_pSerializer->startElementNS(XML_w, XML_r);
6872 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6873 m_pSerializer->endElementNS(XML_w, XML_r);
6875 // postpone the output of this; it has to be done inside the
6876 // paragraph properties, so remember it until then
6877 m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo ));
6880 else if ( m_bParagraphOpened )
6882 if (bBreakAfter)
6883 // tdf#128889
6884 m_bPageBreakAfter = true;
6885 else
6887 m_pSerializer->startElementNS(XML_w, XML_r);
6888 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6889 m_pSerializer->endElementNS(XML_w, XML_r);
6892 else
6893 m_bPostponedPageBreak = true;
6895 break;
6896 default:
6897 SAL_INFO("sw.ww8", "Unknown section break to write: " << nC );
6898 break;
6902 void DocxAttributeOutput::EndParaSdtBlock()
6904 if (m_aParagraphSdt.m_bStartedSdt)
6906 // Paragraph-level SDT still open? Close it now.
6907 m_aParagraphSdt.EndSdtBlock(m_pSerializer);
6911 void DocxAttributeOutput::StartSection()
6913 m_pSerializer->startElementNS(XML_w, XML_sectPr);
6914 m_bOpenedSectPr = true;
6916 // Write the elements in the spec order
6917 static const sal_Int32 aOrder[] =
6919 FSNS( XML_w, XML_headerReference ),
6920 FSNS( XML_w, XML_footerReference ),
6921 FSNS( XML_w, XML_footnotePr ),
6922 FSNS( XML_w, XML_endnotePr ),
6923 FSNS( XML_w, XML_type ),
6924 FSNS( XML_w, XML_pgSz ),
6925 FSNS( XML_w, XML_pgMar ),
6926 FSNS( XML_w, XML_paperSrc ),
6927 FSNS( XML_w, XML_pgBorders ),
6928 FSNS( XML_w, XML_lnNumType ),
6929 FSNS( XML_w, XML_pgNumType ),
6930 FSNS( XML_w, XML_cols ),
6931 FSNS( XML_w, XML_formProt ),
6932 FSNS( XML_w, XML_vAlign ),
6933 FSNS( XML_w, XML_noEndnote ),
6934 FSNS( XML_w, XML_titlePg ),
6935 FSNS( XML_w, XML_textDirection ),
6936 FSNS( XML_w, XML_bidi ),
6937 FSNS( XML_w, XML_rtlGutter ),
6938 FSNS( XML_w, XML_docGrid ),
6939 FSNS( XML_w, XML_printerSettings ),
6940 FSNS( XML_w, XML_sectPrChange )
6943 // postpone the output so that we can later [in EndParagraphProperties()]
6944 // prepend the properties before the run
6945 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
6946 m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder));
6947 m_bHadSectPr = true;
6950 void DocxAttributeOutput::EndSection()
6952 // Write the section properties
6953 if ( m_pSectionSpacingAttrList.is() )
6955 m_pSerializer->singleElementNS( XML_w, XML_pgMar, detachFrom( m_pSectionSpacingAttrList ) );
6958 // Order the elements
6959 m_pSerializer->mergeTopMarks(Tag_StartSection);
6961 m_pSerializer->endElementNS( XML_w, XML_sectPr );
6962 m_bOpenedSectPr = false;
6965 void DocxAttributeOutput::SectionFormProtection( bool bProtected )
6967 if ( bProtected )
6968 m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "true");
6969 else
6970 m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "false");
6973 void DocxAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter)
6975 if (!rRtlGutter.GetValue())
6977 return;
6980 m_pSerializer->singleElementNS(XML_w, XML_rtlGutter);
6983 void DocxAttributeOutput::TextLineBreak(const SwFormatLineBreak& rLineBreak)
6985 m_oLineBreakClear = rLineBreak.GetValue();
6988 void DocxAttributeOutput::WriteLineBreak()
6990 if (!m_oLineBreakClear.has_value())
6992 return;
6995 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
6996 pAttr->add(FSNS(XML_w, XML_type), "textWrapping");
6997 switch (*m_oLineBreakClear)
6999 case SwLineBreakClear::NONE:
7000 pAttr->add(FSNS(XML_w, XML_clear), "none");
7001 break;
7002 case SwLineBreakClear::LEFT:
7003 pAttr->add(FSNS(XML_w, XML_clear), "left");
7004 break;
7005 case SwLineBreakClear::RIGHT:
7006 pAttr->add(FSNS(XML_w, XML_clear), "right");
7007 break;
7008 case SwLineBreakClear::ALL:
7009 pAttr->add(FSNS(XML_w, XML_clear), "all");
7010 break;
7012 m_oLineBreakClear.reset();
7014 m_pSerializer->singleElementNS(XML_w, XML_br, pAttr);
7017 void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo )
7019 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7020 pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy()));
7021 pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" );
7022 if( rLnNumInfo.GetPosFromLeft())
7023 pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft()));
7024 if (nRestartNo > 0)
7025 // Writer is 1-based, Word is 0-based.
7026 pAttr->add(FSNS(XML_w, XML_start), OString::number(nRestartNo - 1));
7027 m_pSerializer->singleElementNS( XML_w, XML_lnNumType, pAttr );
7030 void DocxAttributeOutput::SectionTitlePage()
7032 m_pSerializer->singleElementNS(XML_w, XML_titlePg);
7035 void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ )
7037 // Output the margins
7039 const SvxBoxItem& rBox = pFormat->GetBox( );
7041 const SvxBorderLine* pLeft = rBox.GetLeft( );
7042 const SvxBorderLine* pTop = rBox.GetTop( );
7043 const SvxBorderLine* pRight = rBox.GetRight( );
7044 const SvxBorderLine* pBottom = rBox.GetBottom( );
7046 if ( !(pBottom || pTop || pLeft || pRight) )
7047 return;
7049 OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
7051 // Check if there is a shadow item
7052 const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
7053 if ( pItem )
7055 const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
7056 aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
7059 // By top margin, impl_borders() means the distance between the top of the page and the header frame.
7060 editeng::WordPageMargins aMargins = m_pageMargins;
7061 HdFtDistanceGlue aGlue(pFormat->GetAttrSet());
7062 if (aGlue.HasHeader())
7063 aMargins.nTop = aGlue.m_DyaHdrTop;
7064 // Ditto for bottom margin.
7065 if (aGlue.HasFooter())
7066 aMargins.nBottom = aGlue.m_DyaHdrBottom;
7068 if (pFormat->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::GUTTER_AT_TOP))
7070 aMargins.nTop += pFormat->GetLRSpace().GetGutterMargin();
7072 else
7074 aMargins.nLeft += pFormat->GetLRSpace().GetGutterMargin();
7077 aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>();
7078 editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances);
7080 // All distances are relative to the text margins
7081 m_pSerializer->startElementNS(XML_w, XML_pgBorders,
7082 FSNS(XML_w, XML_display), "allPages",
7083 FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text");
7085 std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map
7086 impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap );
7088 m_pSerializer->endElementNS( XML_w, XML_pgBorders );
7092 void DocxAttributeOutput::SectionBiDi( bool bBiDi )
7094 if ( bBiDi )
7095 m_pSerializer->singleElementNS(XML_w, XML_bidi);
7098 // Converting Numbering Format Code to string
7099 static OString lcl_ConvertNumberingType(sal_Int16 nNumberingType, const SfxItemSet* pOutSet, OString& rFormat, const OString& sDefault = "" )
7101 OString aType = sDefault;
7103 switch ( nNumberingType )
7105 case SVX_NUM_CHARS_UPPER_LETTER:
7106 case SVX_NUM_CHARS_UPPER_LETTER_N: aType = "upperLetter"; break;
7108 case SVX_NUM_CHARS_LOWER_LETTER:
7109 case SVX_NUM_CHARS_LOWER_LETTER_N: aType = "lowerLetter"; break;
7111 case SVX_NUM_ROMAN_UPPER: aType = "upperRoman"; break;
7112 case SVX_NUM_ROMAN_LOWER: aType = "lowerRoman"; break;
7113 case SVX_NUM_ARABIC: aType = "decimal"; break;
7115 case SVX_NUM_BITMAP:
7116 case SVX_NUM_CHAR_SPECIAL: aType = "bullet"; break;
7118 case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"; break;
7119 case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"; break;
7120 case style::NumberingType::NUMBER_NONE: aType = "none"; break;
7121 case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"; break;
7122 case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"; break;
7123 case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"; break;
7124 case style::NumberingType::NUMBER_LOWER_ZH:
7125 aType="taiwaneseCountingThousand";
7126 if (pOutSet) {
7127 const SvxLanguageItem& rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE);
7128 const LanguageType eLang = rLang.GetLanguage();
7130 if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) {
7131 aType="chineseCountingThousand";
7134 break;
7135 case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional";break;
7136 case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"; break;
7137 case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal";break;
7138 case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth";break;
7139 case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo";break;
7140 case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha";break;
7141 case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth";break;
7142 case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada";break;
7143 case style::NumberingType::HANGUL_JAMO_KO: aType="chosung";break;
7144 case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanCounting"; break;
7145 case style::NumberingType::NUMBER_LEGAL_KO: aType = "koreanLegal"; break;
7146 case style::NumberingType::NUMBER_DIGITAL_KO: aType = "koreanDigital"; break;
7147 case style::NumberingType::NUMBER_DIGITAL2_KO: aType = "koreanDigital2"; break;
7148 case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"; break;
7149 case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"; break;
7150 case style::NumberingType::CHARS_ARABIC_ABJAD: aType="arabicAbjad"; break;
7151 case style::NumberingType::CHARS_THAI: aType="thaiLetters"; break;
7152 case style::NumberingType::CHARS_PERSIAN:
7153 case style::NumberingType::CHARS_NEPALI: aType="hindiVowels"; break;
7154 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU:
7155 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU: aType = "russianUpper"; break;
7156 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU:
7157 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU: aType = "russianLower"; break;
7158 case style::NumberingType::TEXT_NUMBER: aType="ordinal"; break;
7159 case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"; break;
7160 case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"; break;
7161 case style::NumberingType::SYMBOL_CHICAGO: aType="chicago"; break;
7162 case style::NumberingType::ARABIC_ZERO: aType = "decimalZero"; break;
7163 case style::NumberingType::ARABIC_ZERO3:
7164 aType = "custom";
7165 rFormat = "001, 002, 003, ...";
7166 break;
7167 case style::NumberingType::ARABIC_ZERO4:
7168 aType = "custom";
7169 rFormat = "0001, 0002, 0003, ...";
7170 break;
7171 case style::NumberingType::ARABIC_ZERO5:
7172 aType = "custom";
7173 rFormat = "00001, 00002, 00003, ...";
7174 break;
7176 Fallback the rest to the suggested default.
7177 case style::NumberingType::NATIVE_NUMBERING:
7178 case style::NumberingType::HANGUL_CIRCLED_JAMO_KO:
7179 case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO:
7180 case style::NumberingType::CHARS_GREEK_UPPER_LETTER:
7181 case style::NumberingType::CHARS_GREEK_LOWER_LETTER:
7182 case style::NumberingType::PAGE_DESCRIPTOR:
7183 case style::NumberingType::TRANSLITERATION:
7184 case style::NumberingType::CHARS_KHMER:
7185 case style::NumberingType::CHARS_LAO:
7186 case style::NumberingType::CHARS_TIBETAN:
7187 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG:
7188 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG:
7189 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG:
7190 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG:
7191 case style::NumberingType::CHARS_MYANMAR:
7192 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR:
7193 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR:
7194 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR:
7195 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR:
7196 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_UK:
7197 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_UK:
7198 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_UK:
7199 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_UK:
7201 default: break;
7203 return aType;
7207 void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::std::optional<sal_uInt16>& oPageRestartNumber )
7209 // FIXME Not called properly with page styles like "First Page"
7211 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7213 // std::nullopt means no restart: then don't output that attribute if it is negative
7214 if ( oPageRestartNumber )
7215 pAttr->add( FSNS( XML_w, XML_start ), OString::number( *oPageRestartNumber ) );
7217 // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions
7218 OString aCustomFormat;
7219 OString aFormat(lcl_ConvertNumberingType(nNumType, nullptr, aCustomFormat));
7220 if (!aFormat.isEmpty() && aCustomFormat.isEmpty())
7221 pAttr->add(FSNS(XML_w, XML_fmt), aFormat);
7223 m_pSerializer->singleElementNS( XML_w, XML_pgNumType, pAttr );
7225 // see 2.6.12 pgNumType (Page Numbering Settings)
7226 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" );
7229 void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode )
7231 /* break code: 0 No break, 1 New column
7232 2 New page, 3 Even page, 4 Odd page
7234 const char* pType;
7235 switch ( nBreakCode )
7237 case 1: pType = "nextColumn"; break;
7238 case 2: pType = "nextPage"; break;
7239 case 3: pType = "evenPage"; break;
7240 case 4: pType = "oddPage"; break;
7241 default: pType = "continuous"; break;
7244 m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), pType);
7247 void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA )
7249 switch( nVA )
7251 case drawing::TextVerticalAdjust_CENTER:
7252 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center");
7253 break;
7254 case drawing::TextVerticalAdjust_BOTTOM:
7255 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom");
7256 break;
7257 case drawing::TextVerticalAdjust_BLOCK: //justify
7258 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "both");
7259 break;
7260 default:
7261 break;
7265 void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const
7267 m_pSerializer->startElementNS(XML_w, XML_font, FSNS(XML_w, XML_name), rFamilyName);
7270 void DocxAttributeOutput::EndFont() const
7272 m_pSerializer->endElementNS( XML_w, XML_font );
7275 void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const
7277 m_pSerializer->singleElementNS(XML_w, XML_altName, FSNS(XML_w, XML_val), rName);
7280 void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const
7282 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7284 OString aCharSet( OString::number( nCharSet, 16 ) );
7285 if ( aCharSet.getLength() == 1 )
7286 aCharSet = "0" + aCharSet;
7287 pAttr->add(FSNS(XML_w, XML_val), aCharSet);
7289 if (GetExport().GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
7291 if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding ))
7292 pAttr->add( FSNS( XML_w, XML_characterSet ), charset );
7295 m_pSerializer->singleElementNS( XML_w, XML_charset, pAttr );
7298 void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const
7300 const char* pFamily;
7301 switch ( eFamily )
7303 case FAMILY_ROMAN: pFamily = "roman"; break;
7304 case FAMILY_SWISS: pFamily = "swiss"; break;
7305 case FAMILY_MODERN: pFamily = "modern"; break;
7306 case FAMILY_SCRIPT: pFamily = "script"; break;
7307 case FAMILY_DECORATIVE: pFamily = "decorative"; break;
7308 default: pFamily = "auto"; break; // no font family
7311 m_pSerializer->singleElementNS(XML_w, XML_family, FSNS(XML_w, XML_val), pFamily);
7314 void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const
7316 const char* pPitch;
7317 switch ( ePitch )
7319 case PITCH_VARIABLE: pPitch = "variable"; break;
7320 case PITCH_FIXED: pPitch = "fixed"; break;
7321 default: pPitch = "default"; break; // no info about the pitch
7324 m_pSerializer->singleElementNS(XML_w, XML_pitch, FSNS(XML_w, XML_val), pPitch);
7327 void DocxAttributeOutput::EmbedFont( std::u16string_view name, FontFamily family, FontPitch pitch )
7329 if( !m_rExport.m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS ))
7330 return; // no font embedding with this document
7331 EmbedFontStyle( name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch );
7332 EmbedFontStyle( name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch );
7333 EmbedFontStyle( name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch );
7334 EmbedFontStyle( name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch );
7337 static char toHexChar( int value )
7339 return value >= 10 ? value + 'A' - 10 : value + '0';
7342 void DocxAttributeOutput::EmbedFontStyle( std::u16string_view name, int tag, FontFamily family, FontItalic italic,
7343 FontWeight weight, FontPitch pitch )
7345 // Embed font if at least viewing is allowed (in which case the opening app must check
7346 // the font license rights too and open either read-only or not use the font for editing).
7347 OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch,
7348 EmbeddedFontsHelper::FontRights::ViewingAllowed );
7349 if( fontUrl.isEmpty())
7350 return;
7351 // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS
7352 if( !m_FontFilesMap.count( fontUrl ))
7354 osl::File file( fontUrl );
7355 if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None )
7356 return;
7357 uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream(
7358 "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf",
7359 "application/vnd.openxmlformats-officedocument.obfuscatedFont" );
7360 // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway,
7361 // so just alter the first and last part of the key.
7362 char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}";
7363 sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E,
7364 0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 };
7365 fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256;
7366 fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 );
7367 fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 );
7368 unsigned char buffer[ 4096 ];
7369 sal_uInt64 readSize;
7370 file.read( buffer, 32, readSize );
7371 if( readSize < 32 )
7373 SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" );
7374 xOutStream->closeOutput();
7375 return;
7377 for( int i = 0;
7378 i < 16;
7379 ++i )
7381 buffer[ i ] ^= fontKey[ i ];
7382 buffer[ i + 16 ] ^= fontKey[ i ];
7384 xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 ));
7385 for(;;)
7387 sal_Bool eof;
7388 if( file.isEndOfFile( &eof ) != osl::File::E_None )
7390 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7391 xOutStream->closeOutput();
7392 return;
7394 if( eof )
7395 break;
7396 if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
7398 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7399 xOutStream->closeOutput();
7400 return;
7402 if( readSize == 0 )
7403 break;
7404 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7405 xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize ));
7407 xOutStream->closeOutput();
7408 OString relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
7409 oox::getRelationship(Relationship::FONT),
7410 Concat2View("fonts/font" + OUString::number( m_nextFontId ) + ".odttf") ), RTL_TEXTENCODING_UTF8 );
7411 EmbeddedFontRef ref;
7412 ref.relId = relId;
7413 ref.fontKey = fontKeyStr;
7414 m_FontFilesMap[ fontUrl ] = ref;
7415 ++m_nextFontId;
7417 m_pSerializer->singleElementNS( XML_w, tag,
7418 FSNS( XML_r, XML_id ), m_FontFilesMap[ fontUrl ].relId,
7419 FSNS( XML_w, XML_fontKey ), m_FontFilesMap[ fontUrl ].fontKey );
7422 OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco )
7424 switch (nIco)
7426 case 0: return "none"; break;
7427 case 1: return "black"; break;
7428 case 2: return "blue"; break;
7429 case 3: return "cyan"; break;
7430 case 4: return "green"; break;
7431 case 5: return "magenta"; break;
7432 case 6: return "red"; break;
7433 case 7: return "yellow"; break;
7434 case 8: return "white"; break;
7435 case 9: return "darkBlue"; break;
7436 case 10: return "darkCyan"; break;
7437 case 11: return "darkGreen"; break;
7438 case 12: return "darkMagenta"; break;
7439 case 13: return "darkRed"; break;
7440 case 14: return "darkYellow"; break;
7441 case 15: return "darkGray"; break;
7442 case 16: return "lightGray"; break;
7443 default: return OString(); break;
7447 void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule )
7449 // nId is the same both for abstract numbering definition as well as the
7450 // numbering definition itself
7451 // TODO check that this is actually true & fix if not ;-)
7452 OString aId( OString::number( nId ) );
7454 m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), aId);
7456 m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), aId);
7458 #if OSL_DEBUG_LEVEL > 1
7459 // TODO ww8 version writes this, anything to do about it here?
7460 if ( rRule.IsContinusNum() )
7461 SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" );
7462 #else
7463 (void) rRule; // to quiet the warning...
7464 #endif
7466 m_pSerializer->endElementNS( XML_w, XML_num );
7469 // Not all attributes of SwNumFormat are important for export, so can't just use embedded in
7470 // that classes comparison.
7471 static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2)
7473 if (rFormat1 == rFormat2)
7474 // They are equal, nothing to do
7475 return false;
7477 if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat())
7478 // One has charformat, other not. they are different
7479 return true;
7481 if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat())
7483 const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet();
7484 const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet();
7486 if (!(a1 == a2))
7487 // Difference in charformat: they are different
7488 return true;
7491 // Compare numformats with empty charformats
7492 SwNumFormat modified1 = rFormat1;
7493 SwNumFormat modified2 = rFormat2;
7494 modified1.SetCharFormatName(OUString());
7495 modified2.SetCharFormatName(OUString());
7496 modified1.SetCharFormat(nullptr);
7497 modified2.SetCharFormat(nullptr);
7498 return modified1 != modified2;
7501 void DocxAttributeOutput::OverrideNumberingDefinition(
7502 SwNumRule const& rRule,
7503 sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides )
7505 m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), OString::number(nNum));
7507 m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), OString::number(nAbstractNum));
7509 SwNumRule const& rAbstractRule = *(*m_rExport.m_pUsedNumTable)[nAbstractNum - 1];
7510 sal_uInt8 const nLevels = static_cast<sal_uInt8>(rRule.IsContinusNum()
7511 ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel);
7512 sal_uInt8 nPreviousOverrideLevel = 0;
7513 for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel)
7515 const auto levelOverride = rLevelOverrides.find(nLevel);
7516 bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel));
7518 // Export list override only if it is different to abstract one
7519 // or we have a level numbering override
7520 if (bListsAreDifferent || levelOverride != rLevelOverrides.end())
7522 // If there are "gaps" in w:lvlOverride numbers, MS Word can have issues with numbering.
7523 // So we need to emit default override tokens up to current one.
7524 while (nPreviousOverrideLevel < nLevel)
7526 const SwNumFormat& rFormat = rRule.Get(nPreviousOverrideLevel);
7527 m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nPreviousOverrideLevel));
7528 // tdf#153104: absent startOverride is treated by Word as "startOverride value 0".
7529 m_pSerializer->singleElementNS(XML_w, XML_startOverride, FSNS(XML_w, XML_val), OString::number(rFormat.GetStart()));
7530 m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7531 nPreviousOverrideLevel++;
7534 m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7536 if (bListsAreDifferent)
7538 GetExport().NumberingLevel(rRule, nLevel);
7540 if (levelOverride != rLevelOverrides.end())
7542 // list numbering restart override
7543 m_pSerializer->singleElementNS(XML_w, XML_startOverride,
7544 FSNS(XML_w, XML_val), OString::number(levelOverride->second));
7547 m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7551 m_pSerializer->endElementNS( XML_w, XML_num );
7554 void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId )
7556 const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1];
7557 m_bExportingOutline = pRule && pRule->IsOutlineRule();
7558 m_pSerializer->startElementNS( XML_w, XML_abstractNum,
7559 FSNS( XML_w, XML_abstractNumId ), OString::number(nId) );
7562 void DocxAttributeOutput::EndAbstractNumbering()
7564 m_pSerializer->endElementNS( XML_w, XML_abstractNum );
7567 void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel,
7568 sal_uInt16 nStart,
7569 sal_uInt16 nNumberingType,
7570 SvxAdjust eAdjust,
7571 const sal_uInt8 * /*pNumLvlPos*/,
7572 sal_uInt8 nFollow,
7573 const wwFont *pFont,
7574 const SfxItemSet *pOutSet,
7575 sal_Int16 nIndentAt,
7576 sal_Int16 nFirstLineIndex,
7577 sal_Int16 nListTabPos,
7578 const OUString &rNumberingString,
7579 const SvxBrushItem* pBrush)
7581 m_pSerializer->startElementNS(XML_w, XML_lvl, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7583 // start with the nStart value. Do not write w:start if Numbered Lists
7584 // starts from zero.As it's an optional parameter.
7585 // refer ECMA 376 Second edition Part-1
7586 if(0 != nLevel || 0 != nStart)
7588 m_pSerializer->singleElementNS( XML_w, XML_start,
7589 FSNS( XML_w, XML_val ), OString::number(nStart) );
7592 if (m_bExportingOutline)
7594 sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel );
7595 if ( nId != SAL_MAX_UINT16 )
7596 m_pSerializer->singleElementNS( XML_w, XML_pStyle ,
7597 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId) );
7599 // format
7600 OString aCustomFormat;
7601 OString aFormat(lcl_ConvertNumberingType(nNumberingType, pOutSet, aCustomFormat, "decimal"));
7604 if (aCustomFormat.isEmpty())
7606 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat);
7608 else
7610 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
7611 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "w14");
7613 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat,
7614 FSNS(XML_w, XML_format), aCustomFormat);
7616 m_pSerializer->endElementNS(XML_mc, XML_Choice);
7617 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
7618 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), "decimal");
7619 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
7620 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
7624 // suffix
7625 const char *pSuffix = nullptr;
7626 switch ( nFollow )
7628 case 1: pSuffix = "space"; break;
7629 case 2: pSuffix = "nothing"; break;
7630 default: /*pSuffix = "tab";*/ break;
7632 if ( pSuffix )
7633 m_pSerializer->singleElementNS(XML_w, XML_suff, FSNS(XML_w, XML_val), pSuffix);
7635 // text
7636 OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel );
7638 const sal_Unicode *pPrev = rNumberingString.getStr();
7639 const sal_Unicode *pIt = rNumberingString.getStr();
7640 while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() )
7642 // convert the level values to %NUMBER form
7643 // (we don't use pNumLvlPos at all)
7644 // FIXME so far we support the ww8 limit of levels only
7645 if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) )
7647 aBuffer.append( OUString::Concat(std::u16string_view(pPrev, pIt - pPrev))
7648 + "%"
7649 + OUString::number(sal_Int32( *pIt ) + 1 ));
7651 pPrev = pIt + 1;
7653 ++pIt;
7655 if ( pPrev < pIt )
7656 aBuffer.append( pPrev, pIt - pPrev );
7658 // If bullet char is empty, set lvlText as empty
7659 if ( rNumberingString == OUStringChar('\0') && nNumberingType == SVX_NUM_CHAR_SPECIAL )
7661 m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), "");
7663 else
7665 // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that.
7666 OUString aLevelText = aBuffer.makeStringAndClear();
7667 static OUString aZeroWidthSpace(u'\x200B');
7668 if (aLevelText == aZeroWidthSpace)
7669 aLevelText.clear();
7670 m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText);
7673 // bullet
7674 if (nNumberingType == SVX_NUM_BITMAP && pBrush)
7676 int nIndex = m_rExport.GetGrfIndex(*pBrush);
7677 if (nIndex != -1)
7679 m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId,
7680 FSNS(XML_w, XML_val), OString::number(nIndex));
7684 // justification
7685 const char *pJc;
7686 bool const ecmaDialect = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
7687 switch ( eAdjust )
7689 case SvxAdjust::Center: pJc = "center"; break;
7690 case SvxAdjust::Right: pJc = !ecmaDialect ? "end" : "right"; break;
7691 default: pJc = !ecmaDialect ? "start" : "left"; break;
7693 m_pSerializer->singleElementNS(XML_w, XML_lvlJc, FSNS(XML_w, XML_val), pJc);
7695 // indentation
7696 m_pSerializer->startElementNS(XML_w, XML_pPr);
7697 if( nListTabPos >= 0 )
7699 m_pSerializer->startElementNS(XML_w, XML_tabs);
7700 m_pSerializer->singleElementNS( XML_w, XML_tab,
7701 FSNS( XML_w, XML_val ), "num",
7702 FSNS( XML_w, XML_pos ), OString::number(nListTabPos) );
7703 m_pSerializer->endElementNS( XML_w, XML_tabs );
7706 sal_Int32 nToken = ecmaDialect ? XML_left : XML_start;
7707 sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging;
7708 m_pSerializer->singleElementNS( XML_w, XML_ind,
7709 FSNS( XML_w, nToken ), OString::number(nIndentAt),
7710 FSNS( XML_w, nIndentToken ), OString::number(abs(nFirstLineIndex)) );
7711 m_pSerializer->endElementNS( XML_w, XML_pPr );
7713 // font
7714 if ( pOutSet )
7716 m_pSerializer->startElementNS(XML_w, XML_rPr);
7718 SfxItemSet aTempSet(*pOutSet);
7719 if ( pFont )
7721 GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml
7722 OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
7723 m_pSerializer->singleElementNS( XML_w, XML_rFonts,
7724 FSNS( XML_w, XML_ascii ), aFamilyName,
7725 FSNS( XML_w, XML_hAnsi ), aFamilyName,
7726 FSNS( XML_w, XML_cs ), aFamilyName,
7727 FSNS( XML_w, XML_hint ), "default" );
7728 aTempSet.ClearItem(RES_CHRATR_FONT);
7729 aTempSet.ClearItem(RES_CHRATR_CTL_FONT);
7731 m_rExport.OutputItemSet(aTempSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF);
7733 WriteCollectedRunProperties();
7735 m_pSerializer->endElementNS( XML_w, XML_rPr );
7738 // TODO anything to do about nListTabPos?
7740 m_pSerializer->endElementNS( XML_w, XML_lvl );
7743 void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap )
7745 switch ( rCaseMap.GetValue() )
7747 case SvxCaseMap::SmallCaps:
7748 m_pSerializer->singleElementNS(XML_w, XML_smallCaps);
7749 break;
7750 case SvxCaseMap::Uppercase:
7751 m_pSerializer->singleElementNS(XML_w, XML_caps);
7752 break;
7753 default: // Something that ooxml does not support
7754 m_pSerializer->singleElementNS(XML_w, XML_smallCaps, FSNS(XML_w, XML_val), "false");
7755 m_pSerializer->singleElementNS(XML_w, XML_caps, FSNS(XML_w, XML_val), "false");
7756 break;
7760 void DocxAttributeOutput::CharColor(const SvxColorItem& rColorItem)
7762 const Color aColor = rColorItem.getColor();
7763 const model::ComplexColor aComplexColor = rColorItem.getComplexColor();
7765 OString aColorString = msfilter::util::ConvertColor(aColor);
7767 std::string_view pExistingValue;
7768 if (m_pColorAttrList.is() && m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pExistingValue))
7770 assert(aColorString.equalsL(pExistingValue.data(), pExistingValue.size()));
7771 return;
7774 lclAddThemeColorAttributes(m_pColorAttrList, aComplexColor);
7776 AddToAttrList(m_pColorAttrList, FSNS(XML_w, XML_val), aColorString);
7777 m_nCharTransparence = 255 - aColor.GetAlpha();
7780 void DocxAttributeOutput::CharContour( const SvxContourItem& rContour )
7782 if ( rContour.GetValue() )
7783 m_pSerializer->singleElementNS(XML_w, XML_outline);
7784 else
7785 m_pSerializer->singleElementNS(XML_w, XML_outline, FSNS(XML_w, XML_val), "false");
7788 void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut )
7790 switch ( rCrossedOut.GetStrikeout() )
7792 case STRIKEOUT_DOUBLE:
7793 m_pSerializer->singleElementNS(XML_w, XML_dstrike);
7794 break;
7795 case STRIKEOUT_NONE:
7796 m_pSerializer->singleElementNS(XML_w, XML_dstrike, FSNS(XML_w, XML_val), "false");
7797 m_pSerializer->singleElementNS(XML_w, XML_strike, FSNS(XML_w, XML_val), "false");
7798 break;
7799 default:
7800 m_pSerializer->singleElementNS(XML_w, XML_strike);
7801 break;
7805 void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
7807 OString sIss;
7808 short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
7810 bool bParaStyle = false;
7811 if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle)
7813 bParaStyle = m_rExport.m_pCurrentStyle->Which() == RES_TXTFMTCOLL;
7816 // Simplify styles to avoid impossible complexity. Import and export as defaults only
7817 if ( m_rExport.m_bStyDef && nEsc && !(bParaStyle && nEsc < 0))
7819 nProp = DFLT_ESC_PROP;
7820 nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB;
7823 if ( !nEsc )
7825 sIss = OString( "baseline" );
7826 nEsc = 0;
7827 nProp = 100;
7829 else if ( DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100 )
7831 if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc )
7832 sIss = OString( "subscript" );
7833 else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc )
7834 sIss = OString( "superscript" );
7836 else if ( DFLT_ESC_AUTO_SUPER == nEsc )
7838 // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
7839 // The ascent is generally about 80% of the total font height.
7840 // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
7841 nEsc = .8 * (100 - nProp);
7843 else if ( DFLT_ESC_AUTO_SUB == nEsc )
7845 // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
7846 // The descent is generally about 20% of the total font height.
7847 // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
7848 nEsc = .2 * -(100 - nProp);
7851 if ( !sIss.isEmpty() )
7852 m_pSerializer->singleElementNS(XML_w, XML_vertAlign, FSNS(XML_w, XML_val), sIss);
7854 if (!(sIss.isEmpty() || sIss.match("baseline")))
7855 return;
7857 const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE);
7858 float fHeight = rItem.GetHeight();
7859 OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) );
7860 m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos);
7862 if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_rExport.m_bFontSizeWritten )
7864 OString sSize = OString::number( round(( fHeight * nProp ) / 1000) );
7865 m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), sSize);
7869 void DocxAttributeOutput::CharFont( const SvxFontItem& rFont)
7871 GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml
7872 const OUString& sFontName(rFont.GetFamilyName());
7873 if (sFontName.isEmpty())
7874 return;
7876 if (m_pFontsAttrList &&
7877 ( m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) ||
7878 m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi )) )
7881 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
7882 // that all sub runs of the field will have correct font inside.
7883 // For DOCX we should do not add the same font information twice in the same node
7884 return;
7887 AddToAttrList( m_pFontsAttrList,
7888 FSNS( XML_w, XML_ascii ), sFontName,
7889 FSNS( XML_w, XML_hAnsi ), sFontName );
7892 void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize)
7894 OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 );
7896 switch ( rFontSize.Which() )
7898 case RES_CHRATR_FONTSIZE:
7899 case RES_CHRATR_CJK_FONTSIZE:
7900 m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), fontSize);
7901 break;
7902 case RES_CHRATR_CTL_FONTSIZE:
7903 m_pSerializer->singleElementNS(XML_w, XML_szCs, FSNS(XML_w, XML_val), fontSize);
7904 break;
7908 void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning )
7910 OString aKerning = OString::number( rKerning.GetValue() );
7911 m_pSerializer->singleElementNS(XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning);
7914 void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage )
7916 OUString aLanguageCode(LanguageTag( rLanguage.GetLanguage()).getBcp47MS());
7918 switch ( rLanguage.Which() )
7920 case RES_CHRATR_LANGUAGE:
7921 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode );
7922 break;
7923 case RES_CHRATR_CJK_LANGUAGE:
7924 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode );
7925 break;
7926 case RES_CHRATR_CTL_LANGUAGE:
7927 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode );
7928 break;
7932 void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture )
7934 if ( rPosture.GetPosture() != ITALIC_NONE )
7935 m_pSerializer->singleElementNS(XML_w, XML_i);
7936 else
7937 m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
7940 void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow )
7942 if ( rShadow.GetValue() )
7943 m_pSerializer->singleElementNS(XML_w, XML_shadow);
7944 else
7945 m_pSerializer->singleElementNS(XML_w, XML_shadow, FSNS(XML_w, XML_val), "false");
7948 void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline )
7950 const char *pUnderlineValue;
7952 switch ( rUnderline.GetLineStyle() )
7954 case LINESTYLE_SINGLE: pUnderlineValue = "single"; break;
7955 case LINESTYLE_BOLD: pUnderlineValue = "thick"; break;
7956 case LINESTYLE_DOUBLE: pUnderlineValue = "double"; break;
7957 case LINESTYLE_DOTTED: pUnderlineValue = "dotted"; break;
7958 case LINESTYLE_DASH: pUnderlineValue = "dash"; break;
7959 case LINESTYLE_DASHDOT: pUnderlineValue = "dotDash"; break;
7960 case LINESTYLE_DASHDOTDOT: pUnderlineValue = "dotDotDash"; break;
7961 case LINESTYLE_WAVE: pUnderlineValue = "wave"; break;
7962 case LINESTYLE_BOLDDOTTED: pUnderlineValue = "dottedHeavy"; break;
7963 case LINESTYLE_BOLDDASH: pUnderlineValue = "dashedHeavy"; break;
7964 case LINESTYLE_LONGDASH: pUnderlineValue = "dashLongHeavy"; break;
7965 case LINESTYLE_BOLDLONGDASH: pUnderlineValue = "dashLongHeavy"; break;
7966 case LINESTYLE_BOLDDASHDOT: pUnderlineValue = "dashDotHeavy"; break;
7967 case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break;
7968 case LINESTYLE_BOLDWAVE: pUnderlineValue = "wavyHeavy"; break;
7969 case LINESTYLE_DOUBLEWAVE: pUnderlineValue = "wavyDouble"; break;
7970 case LINESTYLE_NONE: // fall through
7971 default: pUnderlineValue = "none"; break;
7974 Color aUnderlineColor = rUnderline.GetColor();
7975 bool bUnderlineHasColor = !aUnderlineColor.IsTransparent();
7976 if (bUnderlineHasColor)
7978 model::ComplexColor const& rComplexColor = rUnderline.getComplexColor();
7979 // Underline has a color
7980 rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
7981 pAttrList->add(FSNS(XML_w, XML_val), pUnderlineValue);
7982 pAttrList->add(FSNS(XML_w, XML_color), msfilter::util::ConvertColor(aUnderlineColor));
7983 lclAddThemeColorAttributes(pAttrList, rComplexColor);
7984 m_pSerializer->singleElementNS(XML_w, XML_u, pAttrList);
7987 else
7989 // Underline has no color
7990 m_pSerializer->singleElementNS(XML_w, XML_u, FSNS(XML_w, XML_val), pUnderlineValue);
7994 void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight )
7996 if ( rWeight.GetWeight() == WEIGHT_BOLD )
7997 m_pSerializer->singleElementNS(XML_w, XML_b);
7998 else
7999 m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8002 void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern )
8004 // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-(
8005 // 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.)
8006 const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 );
8007 m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), sFontSize);
8010 void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
8012 if ( rBlink.GetValue() )
8013 m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "blinkBackground");
8014 else
8015 m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "none");
8018 constexpr OUStringLiteral MSWORD_CH_SHADING_FILL = u"FFFFFF"; // The attribute w:fill of w:shd, for MS-Word's character shading,
8019 constexpr OUStringLiteral MSWORD_CH_SHADING_COLOR = u"auto"; // The attribute w:color of w:shd, for MS-Word's character shading,
8020 constexpr OUStringLiteral MSWORD_CH_SHADING_VAL = u"pct15"; // The attribute w:value of w:shd, for MS-Word's character shading,
8022 void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush )
8024 // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX
8025 if (rBrush.GetShadingValue() == ShadingPattern::PCT15)
8027 m_pSerializer->singleElementNS( XML_w, XML_shd,
8028 FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL,
8029 FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR,
8030 FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL );
8032 else
8034 m_pSerializer->singleElementNS( XML_w, XML_shd,
8035 FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor(rBrush.GetColor()),
8036 FSNS( XML_w, XML_val ), "clear" );
8040 void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont )
8042 if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia)))
8044 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8045 // that all sub runs of the field will have correct font inside.
8046 // For DOCX we should do not add the same font information twice in the same node
8047 return;
8050 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), rFont.GetFamilyName() );
8053 void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture )
8055 if ( rPosture.GetPosture() != ITALIC_NONE )
8056 m_pSerializer->singleElementNS(XML_w, XML_i);
8057 else
8058 m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8061 void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight )
8063 if ( rWeight.GetWeight() == WEIGHT_BOLD )
8064 m_pSerializer->singleElementNS(XML_w, XML_b);
8065 else
8066 m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8069 void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont )
8071 if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs)))
8073 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8074 // that all sub runs of the field will have correct font inside.
8075 // For DOCX we should do not add the same font information twice in the same node
8076 return;
8079 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), rFont.GetFamilyName() );
8082 void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture)
8084 if ( rPosture.GetPosture() != ITALIC_NONE )
8085 m_pSerializer->singleElementNS(XML_w, XML_iCs);
8086 else
8087 m_pSerializer->singleElementNS(XML_w, XML_iCs, FSNS(XML_w, XML_val), "false");
8090 void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight )
8092 if ( rWeight.GetWeight() == WEIGHT_BOLD )
8093 m_pSerializer->singleElementNS(XML_w, XML_bCs);
8094 else
8095 m_pSerializer->singleElementNS(XML_w, XML_bCs, FSNS(XML_w, XML_val), "false");
8098 void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& )
8102 void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& )
8106 void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate)
8108 // Not rotated?
8109 if ( !rRotate.GetValue())
8110 return;
8112 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" );
8114 if (rRotate.IsFitToLine())
8115 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" );
8118 void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark )
8120 const char *pEmphasis;
8121 const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark();
8123 if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove))
8124 pEmphasis = "dot";
8125 else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove))
8126 pEmphasis = "comma";
8127 else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove))
8128 pEmphasis = "circle";
8129 else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow))
8130 pEmphasis = "underDot";
8131 else
8132 pEmphasis = "none";
8134 m_pSerializer->singleElementNS(XML_w, XML_em, FSNS(XML_w, XML_val), pEmphasis);
8137 void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines )
8139 if ( !rTwoLines.GetValue() )
8140 return;
8142 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" );
8144 sal_Unicode cStart = rTwoLines.GetStartBracket();
8145 sal_Unicode cEnd = rTwoLines.GetEndBracket();
8147 if (!cStart && !cEnd)
8148 return;
8150 std::string_view sBracket;
8151 if ((cStart == '{') || (cEnd == '}'))
8152 sBracket = "curly";
8153 else if ((cStart == '<') || (cEnd == '>'))
8154 sBracket = "angle";
8155 else if ((cStart == '[') || (cEnd == ']'))
8156 sBracket = "square";
8157 else
8158 sBracket = "round";
8159 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket );
8162 void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth )
8164 // Clamp CharScaleWidth to OOXML limits ([1..600])
8165 const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1,
8166 std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) );
8167 m_pSerializer->singleElementNS( XML_w, XML_w,
8168 FSNS( XML_w, XML_val ), OString::number(nScaleWidth) );
8171 void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief )
8173 switch ( rRelief.GetValue() )
8175 case FontRelief::Embossed:
8176 m_pSerializer->singleElementNS(XML_w, XML_emboss);
8177 break;
8178 case FontRelief::Engraved:
8179 m_pSerializer->singleElementNS(XML_w, XML_imprint);
8180 break;
8181 default:
8182 m_pSerializer->singleElementNS(XML_w, XML_emboss, FSNS(XML_w, XML_val), "false");
8183 m_pSerializer->singleElementNS(XML_w, XML_imprint, FSNS(XML_w, XML_val), "false");
8184 break;
8188 void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden )
8190 if ( rHidden.GetValue() )
8191 m_pSerializer->singleElementNS(XML_w, XML_vanish);
8192 else
8193 m_pSerializer->singleElementNS(XML_w, XML_vanish, FSNS(XML_w, XML_val), "false");
8196 void DocxAttributeOutput::CharBorder(
8197 const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow )
8199 css::table::BorderLine2 rStyleBorder;
8200 const SvxBoxItem* pInherited = nullptr;
8201 if ( GetExport().m_bStyDef && GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8202 pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_CHRATR_BOX);
8203 else if ( m_rExport.m_pChpIter ) // incredibly undocumented, but this is the character-style info, right?
8205 if (const SvxBoxItem* pPoolItem = GetExport().m_pChpIter->HasTextItem(RES_CHRATR_BOX))
8207 pInherited = pPoolItem;
8211 if ( pInherited )
8212 rStyleBorder = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
8214 impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow, &rStyleBorder );
8217 void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight )
8219 const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) );
8220 if ( !sColor.isEmpty() )
8222 m_pSerializer->singleElementNS(XML_w, XML_highlight, FSNS(XML_w, XML_val), sColor);
8226 void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink )
8228 const SwCharFormat* pFormat = m_rExport.m_rDoc.FindCharFormatByName(rLink.GetINetFormat());
8229 if (pFormat)
8231 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pFormat)));
8232 if (!aStyleId.equalsIgnoreAsciiCase("DefaultStyle"))
8233 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8237 void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat )
8239 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat())));
8241 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8244 void DocxAttributeOutput::RefField( const SwField& rField, const OUString& rRef )
8246 SwFieldIds nType = rField.GetTyp( )->Which( );
8247 if ( nType == SwFieldIds::GetExp )
8249 OUString sCmd = FieldString( ww::eREF ) +
8250 "\"" + rRef + "\" ";
8252 m_rExport.OutputField( &rField, ww::eREF, sCmd );
8255 // There is nothing to do here for the set fields
8258 void DocxAttributeOutput::HiddenField(const SwField& /*rField*/)
8260 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" );
8263 void DocxAttributeOutput::PostitField( const SwField* pField )
8265 assert( dynamic_cast< const SwPostItField* >( pField ));
8266 const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
8267 sal_Int32 nId = 0;
8268 auto it = m_rOpenedAnnotationMarksIds.find(pPostItField->GetName());
8269 if (it != m_rOpenedAnnotationMarksIds.end())
8270 // If the postit field has an annotation mark associated, we already have an id.
8271 nId = it->second;
8272 else
8273 // Otherwise get a new one.
8274 nId = m_nNextAnnotationMarkId++;
8275 m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId });
8278 void DocxAttributeOutput::WritePostitFieldReference()
8280 while( m_postitFieldsMaxId < m_postitFields.size())
8282 OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id);
8284 // In case this file is inside annotation marks, we want to write the
8285 // comment reference after the annotation mark is closed, not here.
8286 const OUString& idname = m_postitFields[m_postitFieldsMaxId].first->GetName();
8287 auto it = m_rOpenedAnnotationMarksIds.find( idname );
8288 if ( it == m_rOpenedAnnotationMarksIds.end( ) )
8289 m_pSerializer->singleElementNS(XML_w, XML_commentReference, FSNS(XML_w, XML_id), idstr);
8290 ++m_postitFieldsMaxId;
8294 DocxAttributeOutput::hasProperties DocxAttributeOutput::WritePostitFields()
8296 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
8297 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo );
8299 hasProperties eResult = hasProperties::no;
8300 for (auto& [f1, data1] : m_postitFields)
8302 if (f1->GetParentId() != 0)
8304 for (size_t i = 0; i < m_postitFields.size(); i++)
8306 auto& [f2, data2] = m_postitFields[i];
8307 if (f2->GetParaId() == f1->GetParentId())
8309 data2.parentStatus = ParentStatus::IsParent;
8310 data1.parentStatus = ParentStatus::HasParent;
8311 data1.parentIndex = i;
8312 break;
8317 for (auto& [f, data] : m_postitFields)
8319 const DateTime aDateTime = f->GetDateTime();
8320 bool bNoDate = bRemovePersonalInfo ||
8321 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
8323 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
8324 = sax_fastparser::FastSerializerHelper::createAttrList();
8326 pAttributeList->add(FSNS( XML_w, XML_id ), OString::number(data.id));
8327 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
8328 ? "Author" + OString::number( GetExport().GetInfoID(f->GetPar1()) )
8329 : f->GetPar1().toUtf8());
8330 if (!bNoDate)
8331 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
8332 pAttributeList->add(FSNS( XML_w, XML_initials ), bRemovePersonalInfo
8333 ? OString::number( GetExport().GetInfoID(f->GetInitials()) )
8334 : f->GetInitials().toUtf8());
8335 m_pSerializer->startElementNS( XML_w, XML_comment, pAttributeList );
8337 // Make sure to give parent/child fields a paraId
8338 const bool bNeedParaId = f->GetResolved() || data.parentStatus != ParentStatus::None;
8339 if (bNeedParaId)
8340 eResult = hasProperties::yes;
8342 if (f->GetTextObject() != nullptr)
8344 // richtext
8345 data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId);
8347 else
8349 // just plain text - eg. when the field was created via the
8350 // .uno:InsertAnnotation API
8351 std::optional<OUString> aParaId;
8352 if (bNeedParaId)
8354 data.lastParaId = m_nNextParaId++;
8355 aParaId = NumberToHexBinary(data.lastParaId);
8357 m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
8358 m_pSerializer->startElementNS(XML_w, XML_r);
8359 RunText(f->GetText());
8360 m_pSerializer->endElementNS(XML_w, XML_r);
8361 m_pSerializer->endElementNS(XML_w, XML_p);
8364 m_pSerializer->endElementNS( XML_w, XML_comment );
8366 return eResult;
8369 void DocxAttributeOutput::WritePostItFieldsResolved()
8371 for (auto& [f, data] : m_postitFields)
8373 // Parent fields don't need to be exported here if they don't have a resolved attribute
8374 if (!f->GetResolved() && data.parentStatus != ParentStatus::HasParent)
8375 continue;
8376 OUString idstr = NumberToHexBinary(data.lastParaId);
8377 std::optional<OUString> sDone, sParentId;
8378 if (f->GetParentId() != 0)
8380 if (data.parentStatus == ParentStatus::HasParent)
8382 // Since parent fields have been resolved first, they should already have an id
8383 const PostItDOCXData& aParentFieldData = m_postitFields[data.parentIndex].second;
8384 sParentId = NumberToHexBinary(aParentFieldData.lastParaId);
8386 else
8388 SAL_WARN("sw.ww8", "SwPostItField has a parent id, but a matching parent was not found");
8391 if (f->GetResolved())
8392 sDone = "1";
8393 m_pSerializer->singleElementNS(XML_w15, XML_commentEx,
8394 FSNS(XML_w15, XML_paraId), idstr,
8395 FSNS(XML_w15, XML_done), sDone,
8396 FSNS(XML_w15, XML_paraIdParent), sParentId);
8400 bool DocxAttributeOutput::DropdownField( const SwField* pField )
8402 ww::eField eType = ww::eFORMDROPDOWN;
8403 OUString sCmd = FieldString( eType );
8404 GetExport( ).OutputField( pField, eType, sCmd );
8406 return false;
8409 bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
8411 assert( m_PendingPlaceholder == nullptr );
8412 m_PendingPlaceholder = pField;
8413 return false; // do not expand
8416 void DocxAttributeOutput::WritePendingPlaceholder()
8418 if( m_PendingPlaceholder == nullptr )
8419 return;
8420 const SwField* pField = m_PendingPlaceholder;
8421 m_PendingPlaceholder = nullptr;
8422 m_pSerializer->startElementNS(XML_w, XML_sdt);
8423 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
8424 if( !pField->GetPar2().isEmpty())
8425 m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), pField->GetPar2());
8426 m_pSerializer->singleElementNS(XML_w, XML_temporary);
8427 m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
8428 m_pSerializer->singleElementNS(XML_w, XML_text);
8429 m_pSerializer->endElementNS( XML_w, XML_sdtPr );
8430 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
8431 m_pSerializer->startElementNS(XML_w, XML_r);
8432 RunText( pField->GetPar1());
8433 m_pSerializer->endElementNS( XML_w, XML_r );
8434 m_pSerializer->endElementNS( XML_w, XML_sdtContent );
8435 m_pSerializer->endElementNS( XML_w, XML_sdt );
8438 void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd )
8440 // field bookmarks are handled in the EndRun method
8441 GetExport().OutputField(&rField, eType, rCmd );
8444 void DocxAttributeOutput::WriteExpand( const SwField* pField )
8446 // Will be written in the next End Run
8447 m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() );
8450 void DocxAttributeOutput::WriteField_Impl(const SwField *const pField,
8451 ww::eField const eType, const OUString& rFieldCmd, FieldFlags const nMode,
8452 OUString const*const pBookmarkName)
8454 if (m_bPreventDoubleFieldsHandling)
8455 return;
8457 struct FieldInfos infos;
8458 if (pField)
8459 infos.pField = pField->CopyField();
8460 infos.sCmd = rFieldCmd;
8461 infos.eType = eType;
8462 infos.bClose = bool(FieldFlags::Close & nMode);
8463 infos.bSep = bool(FieldFlags::CmdEnd & nMode);
8464 infos.bOpen = bool(FieldFlags::Start & nMode);
8465 m_Fields.push_back( infos );
8467 if (pBookmarkName)
8469 m_sFieldBkm = *pBookmarkName;
8472 if ( !pField )
8473 return;
8475 SwFieldIds nType = pField->GetTyp( )->Which( );
8476 sal_uInt16 nSubType = pField->GetSubType();
8478 // TODO Any other field types here ?
8479 if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) )
8481 const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField );
8482 m_sFieldBkm = pSet->GetPar1( );
8484 else if ( nType == SwFieldIds::Dropdown )
8486 const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField );
8487 m_sFieldBkm = pDropDown->GetName( );
8491 void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark )
8493 if ( !m_Fields.empty() )
8494 m_Fields.begin()->pFieldmark = &rFieldmark;
8497 void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData )
8499 for ( const OUString & name : rStarts )
8501 if (name.startsWith("permission-for-group:") ||
8502 name.startsWith("permission-for-user:"))
8504 m_rPermissionsStart.push_back(name);
8506 else
8508 m_rBookmarksStart.push_back(name);
8509 m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData);
8512 rStarts.clear();
8514 for ( const OUString & name : rEnds )
8516 if (name.startsWith("permission-for-group:") ||
8517 name.startsWith("permission-for-user:"))
8519 m_rPermissionsEnd.push_back(name);
8521 else
8523 m_rBookmarksEnd.push_back(name);
8526 rEnds.clear();
8529 void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
8531 for ( const OUString & name : rStarts )
8533 if (name.startsWith("permission-for-group:") ||
8534 name.startsWith("permission-for-user:"))
8536 m_rPermissionsStart.push_back(name);
8538 else
8540 m_rFinalBookmarksStart.push_back(name);
8543 rStarts.clear();
8545 for ( const OUString & name : rEnds )
8547 if (name.startsWith("permission-for-group:") ||
8548 name.startsWith("permission-for-user:"))
8550 m_rPermissionsEnd.push_back(name);
8552 else
8554 m_rFinalBookmarksEnd.push_back(name);
8557 rEnds.clear();
8560 void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
8561 std::vector< OUString >& rEnds )
8563 m_rAnnotationMarksStart.insert(m_rAnnotationMarksStart.end(), rStarts.begin(), rStarts.end());
8564 rStarts.clear();
8566 m_rAnnotationMarksEnd.insert(m_rAnnotationMarksEnd.end(), rEnds.begin(), rEnds.end());
8567 rEnds.clear();
8570 void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote )
8572 const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()?
8573 m_rExport.m_rDoc.GetEndNoteInfo(): m_rExport.m_rDoc.GetFootnoteInfo();
8575 // footnote/endnote run properties
8576 const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( m_rExport.m_rDoc );
8578 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
8580 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8582 // remember the footnote/endnote to
8583 // 1) write the footnoteReference/endnoteReference in EndRunProperties()
8584 // 2) be able to dump them all to footnotes.xml/endnotes.xml
8585 if ( !rFootnote.IsEndNote() && m_rExport.m_rDoc.GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER )
8586 m_pFootnotesList->add( rFootnote );
8587 else
8588 m_pEndnotesList->add( rFootnote );
8591 void DocxAttributeOutput::FootnoteEndnoteReference()
8593 sal_Int32 nId;
8594 const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId );
8595 sal_Int32 nToken = XML_footnoteReference;
8597 // both cannot be set at the same time - if they are, it's a bug
8598 if ( !pFootnote )
8600 pFootnote = m_pEndnotesList->getCurrent( nId );
8601 nToken = XML_endnoteReference;
8604 if ( !pFootnote )
8605 return;
8607 // write it
8608 if ( pFootnote->GetNumStr().isEmpty() )
8610 // autonumbered
8611 m_pSerializer->singleElementNS(XML_w, nToken, FSNS(XML_w, XML_id), OString::number(nId));
8613 else
8615 // not autonumbered
8616 m_pSerializer->singleElementNS( XML_w, nToken,
8617 FSNS( XML_w, XML_customMarkFollows ), "1",
8618 FSNS( XML_w, XML_id ), OString::number(nId) );
8620 RunText( pFootnote->GetNumStr() );
8624 static void WriteFootnoteSeparatorHeight(
8625 ::sax_fastparser::FSHelperPtr const& pSerializer, SwTwips const nHeight)
8627 // try to get the height by setting font size of the paragraph
8628 if (nHeight != 0)
8630 pSerializer->startElementNS(XML_w, XML_pPr);
8631 pSerializer->startElementNS(XML_w, XML_rPr);
8632 pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val),
8633 OString::number((nHeight + 5) / 10));
8634 pSerializer->endElementNS(XML_w, XML_rPr);
8635 pSerializer->endElementNS(XML_w, XML_pPr);
8639 void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes )
8641 const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector();
8643 sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes;
8644 sal_Int32 nItem = bFootnotes? XML_footnote: XML_endnote;
8646 m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() );
8648 sal_Int32 nIndex = 0;
8650 // separator
8651 // note: can only be defined for the whole document, not per section
8652 m_pSerializer->startElementNS( XML_w, nItem,
8653 FSNS( XML_w, XML_id ), OString::number(nIndex++),
8654 FSNS( XML_w, XML_type ), "separator" );
8655 m_pSerializer->startElementNS(XML_w, XML_p);
8657 bool bSeparator = true;
8658 SwTwips nHeight(0);
8659 if (bFootnotes)
8661 const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_rDoc.GetPageDesc(0).GetFootnoteInfo();
8662 // Request separator only if both width and thickness are non-zero.
8663 bSeparator = rFootnoteInfo.GetLineStyle() != SvxBorderLineStyle::NONE
8664 && rFootnoteInfo.GetLineWidth() > 0
8665 && double(rFootnoteInfo.GetWidth()) > 0;
8666 nHeight = sw::FootnoteSeparatorHeight(rFootnoteInfo);
8669 WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8671 m_pSerializer->startElementNS(XML_w, XML_r);
8672 if (bSeparator)
8673 m_pSerializer->singleElementNS(XML_w, XML_separator);
8674 m_pSerializer->endElementNS( XML_w, XML_r );
8675 m_pSerializer->endElementNS( XML_w, XML_p );
8676 m_pSerializer->endElementNS( XML_w, nItem );
8678 // separator
8679 m_pSerializer->startElementNS( XML_w, nItem,
8680 FSNS( XML_w, XML_id ), OString::number(nIndex++),
8681 FSNS( XML_w, XML_type ), "continuationSeparator" );
8682 m_pSerializer->startElementNS(XML_w, XML_p);
8684 WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8686 m_pSerializer->startElementNS(XML_w, XML_r);
8687 if (bSeparator)
8689 m_pSerializer->singleElementNS(XML_w, XML_continuationSeparator);
8691 m_pSerializer->endElementNS( XML_w, XML_r );
8692 m_pSerializer->endElementNS( XML_w, XML_p );
8693 m_pSerializer->endElementNS( XML_w, nItem );
8695 // if new special ones are added, update also WriteFootnoteEndnotePr()
8697 // footnotes/endnotes themselves
8698 for ( const auto& rpItem : rVector )
8700 m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef;
8701 m_footnoteCustomLabel = rpItem->GetNumStr();
8703 m_pSerializer->startElementNS(XML_w, nItem, FSNS(XML_w, XML_id), OString::number(nIndex));
8705 const SwNodeIndex* pIndex = rpItem->GetTextFootnote()->GetStartNode();
8706 m_rExport.WriteSpecialText( pIndex->GetIndex() + 1,
8707 pIndex->GetNode().EndOfSectionIndex(),
8708 bFootnotes? TXT_FTN: TXT_EDN );
8710 m_pSerializer->endElementNS( XML_w, nItem );
8711 ++nIndex;
8714 m_pSerializer->endElementNS( XML_w, nBody );
8718 void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag,
8719 const SwEndNoteInfo& info, int listtag )
8721 fs->startElementNS(XML_w, tag);
8722 OString aCustomFormat;
8723 OString fmt = lcl_ConvertNumberingType(info.m_aFormat.GetNumberingType(), nullptr, aCustomFormat);
8724 if (!fmt.isEmpty() && aCustomFormat.isEmpty())
8725 fs->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), fmt);
8726 if( info.m_nFootnoteOffset != 0 )
8727 fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ),
8728 OString::number(info.m_nFootnoteOffset + 1) );
8730 const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info);
8731 if( pFootnoteInfo )
8733 switch( pFootnoteInfo->m_eNum )
8735 case FTNNUM_PAGE: fmt = "eachPage"; break;
8736 case FTNNUM_CHAPTER: fmt = "eachSect"; break;
8737 default: fmt.clear(); break;
8739 if (!fmt.isEmpty())
8740 fs->singleElementNS(XML_w, XML_numRestart, FSNS(XML_w, XML_val), fmt);
8743 if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list
8744 { // there are currently only two hardcoded ones ( see FootnotesEndnotes())
8745 fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "0");
8746 fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "1");
8748 fs->endElementNS( XML_w, tag );
8751 void DocxAttributeOutput::SectFootnoteEndnotePr()
8753 if( HasFootnotes())
8754 WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_rDoc.GetFootnoteInfo(), 0 );
8755 if( HasEndnotes())
8756 WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_rDoc.GetEndNoteInfo(), 0 );
8759 void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti )
8761 if ( nSpace < 0 )
8763 AddToAttrList( m_pParagraphSpacingAttrList,
8764 FSNS( XML_w, XML_lineRule ), "exact",
8765 FSNS( XML_w, XML_line ), OString::number( -nSpace ) );
8767 else if( nSpace > 0 && nMulti )
8769 AddToAttrList( m_pParagraphSpacingAttrList,
8770 FSNS( XML_w, XML_lineRule ), "auto",
8771 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8773 else
8775 AddToAttrList( m_pParagraphSpacingAttrList,
8776 FSNS( XML_w, XML_lineRule ), "atLeast",
8777 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8781 void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust )
8783 const char *pAdjustString;
8785 bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
8787 const SfxItemSet* pItems = GetExport().GetCurItemSet();
8788 const SvxFrameDirectionItem* rFrameDir = pItems?
8789 pItems->GetItem( RES_FRAMEDIR ) : nullptr;
8791 SvxFrameDirection nDir = SvxFrameDirection::Environment;
8792 if( rFrameDir != nullptr )
8793 nDir = rFrameDir->GetValue();
8794 if ( nDir == SvxFrameDirection::Environment )
8795 nDir = GetExport( ).GetDefaultFrameDirection( );
8796 bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB );
8798 switch ( rAdjust.GetAdjust() )
8800 case SvxAdjust::Left:
8801 if ( bEcma )
8803 if ( bRtl )
8804 pAdjustString = "right";
8805 else
8806 pAdjustString = "left";
8808 else if ( bRtl )
8809 pAdjustString = "end";
8810 else
8811 pAdjustString = "start";
8812 break;
8813 case SvxAdjust::Right:
8814 if ( bEcma )
8816 if ( bRtl )
8817 pAdjustString = "left";
8818 else
8819 pAdjustString = "right";
8821 else if ( bRtl )
8822 pAdjustString = "start";
8823 else
8824 pAdjustString = "end";
8825 break;
8826 case SvxAdjust::BlockLine:
8827 case SvxAdjust::Block:
8828 if (rAdjust.GetLastBlock() == SvxAdjust::Block)
8829 pAdjustString = "distribute";
8830 else
8831 pAdjustString = "both";
8832 break;
8833 case SvxAdjust::Center:
8834 pAdjustString = "center";
8835 break;
8836 default:
8837 return; // not supported attribute
8839 m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pAdjustString);
8842 void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit )
8844 if (rSplit.GetValue())
8845 m_pSerializer->singleElementNS(XML_w, XML_keepLines, FSNS(XML_w, XML_val), "false");
8846 else
8847 m_pSerializer->singleElementNS(XML_w, XML_keepLines);
8850 void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows )
8852 if (rWidows.GetValue())
8853 m_pSerializer->singleElementNS(XML_w, XML_widowControl);
8854 else
8855 m_pSerializer->singleElementNS(XML_w, XML_widowControl, FSNS(XML_w, XML_val), "false");
8858 static void impl_WriteTabElement( FSHelperPtr const & pSerializer,
8859 const SvxTabStop& rTab, tools::Long tabsOffset )
8861 rtl::Reference<FastAttributeList> pTabElementAttrList = FastSerializerHelper::createAttrList();
8863 switch (rTab.GetAdjustment())
8865 case SvxTabAdjust::Right:
8866 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "right" );
8867 break;
8868 case SvxTabAdjust::Decimal:
8869 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "decimal" );
8870 break;
8871 case SvxTabAdjust::Center:
8872 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "center" );
8873 break;
8874 case SvxTabAdjust::Default:
8875 case SvxTabAdjust::Left:
8876 default:
8877 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "left" );
8878 break;
8881 // Write position according to used offset of the whole paragraph.
8882 // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
8883 // But in ODT, zero position could be page margins or paragraph indent according to used settings.
8884 // This is handled outside of this method and provided for us in tabsOffset parameter.
8885 pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos() + tabsOffset ) );
8887 sal_Unicode cFillChar = rTab.GetFill();
8889 if ('.' == cFillChar )
8890 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "dot" );
8891 else if ( '-' == cFillChar )
8892 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "hyphen" );
8893 else if ( u'\x00B7' == cFillChar ) // middle dot
8894 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "middleDot" );
8895 else if ( '_' == cFillChar )
8896 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "underscore" );
8897 else
8898 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "none" );
8900 pSerializer->singleElementNS(XML_w, XML_tab, pTabElementAttrList);
8903 void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop )
8905 const SvxTabStopItem* pInheritedTabs = nullptr;
8906 if ( GetExport().m_pStyAttr )
8907 pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
8908 else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8909 pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
8910 const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0;
8911 const sal_uInt16 nCount = rTabStop.Count();
8913 // <w:tabs> must contain at least one <w:tab>, so don't write it empty
8914 if ( !nCount && !nInheritedTabCount )
8915 return;
8916 if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default )
8918 GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos());
8919 return;
8922 // do not output inherited tabs twice (inside styles and inside inline properties)
8923 if ( nCount == nInheritedTabCount && nCount > 0 )
8925 if ( *pInheritedTabs == rTabStop )
8926 return;
8929 m_pSerializer->startElementNS(XML_w, XML_tabs);
8931 // Get offset for tabs
8932 // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
8933 // But in ODT, zero position could be page margins or paragraph indent according to used settings.
8934 tools::Long tabsOffset = m_rExport.GetParaTabStopOffset();
8936 // clear unused inherited tabs - otherwise the style will add them back in
8937 sal_Int32 nCurrTab = 0;
8938 for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i )
8940 while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) )
8941 ++nCurrTab;
8943 if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] )
8945 m_pSerializer->singleElementNS( XML_w, XML_tab,
8946 FSNS( XML_w, XML_val ), "clear",
8947 FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) );
8951 for (sal_uInt16 i = 0; i < nCount; i++ )
8953 if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default )
8954 impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset );
8955 else
8956 GetExport().setDefaultTabStop( rTabStop[i].GetTabPos());
8959 m_pSerializer->endElementNS( XML_w, XML_tabs );
8962 void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone )
8964 m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens,
8965 FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ) );
8968 void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId )
8970 if ( USHRT_MAX == nNumId )
8971 return;
8973 // LibreOffice is not very flexible with "Outline Numbering" (aka "Outline" numbering style).
8974 // Only ONE numbering rule ("Outline") can be associated with a style-assigned-listLevel,
8975 // and no other style is able to inherit these numId/nLvl settings - only text nodes can.
8976 // So listLevel only exists in paragraph properties EXCEPT for up to ten styles that have been
8977 // assigned to one of these special Chapter Numbering listlevels (by default Heading 1-10).
8978 const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0;
8979 const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr;
8980 const SwTextFormatColl* pColl = pTextNd ? pTextNd->GetTextColl() : nullptr;
8981 // Do not duplicate numbering that is inherited from the (Chapter numbering) style
8982 // (since on import we duplicate style numbering/listlevel to the paragraph).
8983 if (pColl && pColl->IsAssignedToListLevelOfOutlineStyle()
8984 && nLvl == pColl->GetAssignedOutlineStyleLevel() && pRule && pRule->IsOutlineRule())
8986 // By definition of how LO is implemented, assignToListLevel is only possible
8987 // when the style is also using OutlineRule for numbering. Adjust logic if that changes.
8988 assert(pRule->GetName() == pColl->GetNumRule(true).GetValue());
8989 return;
8992 m_pSerializer->startElementNS(XML_w, XML_numPr);
8993 m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), OString::number(nLvl));
8994 m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), OString::number(nNumId));
8995 m_pSerializer->endElementNS(XML_w, XML_numPr);
8998 void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace )
9000 m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE,
9001 FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ) );
9004 void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem )
9006 m_pSerializer->singleElementNS( XML_w, XML_overflowPunct,
9007 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9010 void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem )
9012 m_pSerializer->singleElementNS( XML_w, XML_kinsoku,
9013 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9016 void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign )
9018 const char *pAlignString;
9020 switch ( rAlign.GetValue() )
9022 case SvxParaVertAlignItem::Align::Baseline:
9023 pAlignString = "baseline";
9024 break;
9025 case SvxParaVertAlignItem::Align::Top:
9026 pAlignString = "top";
9027 break;
9028 case SvxParaVertAlignItem::Align::Center:
9029 pAlignString = "center";
9030 break;
9031 case SvxParaVertAlignItem::Align::Bottom:
9032 pAlignString = "bottom";
9033 break;
9034 case SvxParaVertAlignItem::Align::Automatic:
9035 pAlignString = "auto";
9036 break;
9037 default:
9038 return; // not supported attribute
9040 m_pSerializer->singleElementNS(XML_w, XML_textAlignment, FSNS(XML_w, XML_val), pAlignString);
9043 void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid )
9045 m_pSerializer->singleElementNS( XML_w, XML_snapToGrid,
9046 FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ) );
9049 void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize )
9051 if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize())
9053 const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize();
9054 m_rExport.SdrExporter().getTextFrameStyle().append(";width:" + OString::number(double(pSize->Width()) / 20));
9055 m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:" + OString::number(double(pSize->Height()) / 20) + "pt");
9057 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9060 else if ( m_rExport.m_bOutFlyFrameAttrs )
9062 if ( rSize.GetWidth() && rSize.GetWidthSizeType() == SwFrameSize::Fixed )
9063 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9064 FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9066 if ( rSize.GetHeight() )
9068 std::string_view sRule( "exact" );
9069 if ( rSize.GetHeightSizeType() == SwFrameSize::Minimum )
9070 sRule = "atLeast";
9071 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9072 FSNS( XML_w, XML_hRule ), sRule,
9073 FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9076 else if ( m_rExport.m_bOutPageDescs )
9078 rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9079 if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) )
9080 attrList->add( FSNS( XML_w, XML_orient ), "landscape" );
9082 attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9083 attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9085 m_pSerializer->singleElementNS( XML_w, XML_pgSz, attrList );
9089 void DocxAttributeOutput::FormatPaperBin( const SvxPaperBinItem& )
9091 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatPaperBin()" );
9094 void DocxAttributeOutput::FormatFirstLineIndent(SvxFirstLineIndentItem const& rFirstLine)
9096 sal_Int32 const nFirstLineAdjustment(rFirstLine.GetTextFirstLineOffset());
9097 if (nFirstLineAdjustment > 0)
9099 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_firstLine),
9100 OString::number(nFirstLineAdjustment));
9102 else
9104 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_hanging),
9105 OString::number(- nFirstLineAdjustment));
9109 void DocxAttributeOutput::FormatTextLeftMargin(SvxTextLeftMarginItem const& rTextLeftMargin)
9111 SvxTextLeftMarginItem const* pTextLeftMargin(&rTextLeftMargin);
9112 ::std::optional<SvxTextLeftMarginItem> oCopy;
9113 if (dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) != nullptr)
9115 auto pTextNd(static_cast<SwTextNode const*>(GetExport().m_pOutFormatNode));
9116 // WW doesn't have a concept of a paragraph that's in a list but not
9117 // counted in the list - see AttributeOutputBase::ParaNumRule()
9118 // forcing non-existent numId="0" in this case.
9119 // This means WW won't apply the indents from the numbering,
9120 // so try to add them as paragraph properties here.
9121 if (!pTextNd->IsCountedInList())
9123 SfxItemSetFixed<RES_MARGIN_TEXTLEFT, RES_MARGIN_TEXTLEFT> temp(m_rExport.m_rDoc.GetAttrPool());
9124 pTextNd->GetParaAttr(temp, 0, 0, false, true, true, nullptr);
9125 if (auto *const pItem = temp.GetItem(RES_MARGIN_TEXTLEFT))
9127 oCopy.emplace(*pItem);
9128 pTextLeftMargin = &*oCopy;
9132 bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9133 AddToAttrList(m_pLRSpaceAttrList,
9134 FSNS(XML_w, (bEcma1st ? XML_left : XML_start)),
9135 OString::number(pTextLeftMargin->GetTextLeft()));
9138 void DocxAttributeOutput::FormatRightMargin(SvxRightMarginItem const& rRightMargin)
9140 // (paragraph case, this will be an else branch once others are converted)
9141 #if 0
9142 else
9143 #endif
9145 bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9146 AddToAttrList(m_pLRSpaceAttrList,
9147 FSNS(XML_w, (bEcma1st ? XML_right : XML_end)),
9148 OString::number(rRightMargin.GetRight()));
9152 void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace )
9154 bool const bEcma = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
9155 if (m_rExport.SdrExporter().getTextFrameSyntax())
9157 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt");
9158 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt");
9160 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9163 else if ( m_rExport.m_bOutFlyFrameAttrs )
9165 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hSpace ),
9166 OString::number(
9167 ( rLRSpace.GetLeft() + rLRSpace.GetRight() ) / 2 ) );
9169 else if ( m_rExport.m_bOutPageDescs )
9171 m_pageMargins.nLeft = 0;
9172 m_pageMargins.nRight = 0;
9174 const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX);
9175 if (pBoxItem)
9177 m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true );
9178 m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true );
9181 m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft());
9182 m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight());
9183 sal_uInt16 nGutter = rLRSpace.GetGutterMargin();
9185 AddToAttrList( m_pSectionSpacingAttrList,
9186 FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ),
9187 FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ),
9188 FSNS( XML_w, XML_gutter ), OString::number( nGutter ) );
9190 else
9192 // note: this is not possible for SwTextNode but is for EditEngine!
9193 SvxLRSpaceItem const* pLRSpace(&rLRSpace);
9194 ::std::optional<SvxLRSpaceItem> oLRSpace;
9195 assert(dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) == nullptr);
9196 rtl::Reference<FastAttributeList> pLRSpaceAttrList = FastSerializerHelper::createAttrList();
9197 if ((0 != pLRSpace->GetTextLeft()) || (pLRSpace->IsExplicitZeroMarginValLeft()))
9199 pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_left : XML_start)), OString::number(pLRSpace->GetTextLeft()) );
9201 if ((0 != pLRSpace->GetRight()) || (pLRSpace->IsExplicitZeroMarginValRight()))
9203 pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_right : XML_end)), OString::number(pLRSpace->GetRight()) );
9205 sal_Int32 const nFirstLineAdjustment = pLRSpace->GetTextFirstLineOffset();
9206 if (nFirstLineAdjustment > 0)
9207 pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) );
9208 else
9209 pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) );
9210 m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList );
9214 void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace )
9217 if (m_rExport.SdrExporter().getTextFrameSyntax())
9219 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt");
9220 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt");
9222 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9225 else if ( m_rExport.m_bOutFlyFrameAttrs )
9227 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ),
9228 OString::number(
9229 ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ) );
9231 else if (m_rExport.m_bOutPageDescs )
9233 OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" );
9234 if ( !m_rExport.GetCurItemSet() )
9235 return;
9237 HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() );
9239 sal_Int32 nHeader = 0;
9240 if ( aDistances.HasHeader() )
9241 nHeader = sal_Int32( aDistances.m_DyaHdrTop );
9242 else if (m_rExport.m_pFirstPageFormat)
9244 HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9245 if (aFirstPageDistances.HasHeader())
9247 // The follow page style has no header, but the first page style has. In Word terms,
9248 // this means that the header margin of "the" section is coming from the first page
9249 // style.
9250 nHeader = sal_Int32(aFirstPageDistances.m_DyaHdrTop);
9254 // Page top
9255 m_pageMargins.nTop = aDistances.m_DyaTop;
9257 sal_Int32 nFooter = 0;
9258 if ( aDistances.HasFooter() )
9259 nFooter = sal_Int32( aDistances.m_DyaHdrBottom );
9260 else if (m_rExport.m_pFirstPageFormat)
9262 HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9263 if (aFirstPageDistances.HasFooter())
9265 // The follow page style has no footer, but the first page style has. In Word terms,
9266 // this means that the footer margin of "the" section is coming from the first page
9267 // style.
9268 nFooter = sal_Int32(aFirstPageDistances.m_DyaHdrBottom);
9272 // Page Bottom
9273 m_pageMargins.nBottom = aDistances.m_DyaBottom;
9275 AddToAttrList( m_pSectionSpacingAttrList,
9276 FSNS( XML_w, XML_header ), OString::number( nHeader ),
9277 FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ),
9278 FSNS( XML_w, XML_footer ), OString::number( nFooter ),
9279 FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ) );
9281 else
9283 SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() );
9284 // check if before auto spacing was set during import and spacing we get from actual object is same
9285 // that we set in import. If yes just write beforeAutoSpacing tag.
9286 if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper())
9288 AddToAttrList( m_pParagraphSpacingAttrList,
9289 FSNS( XML_w, XML_beforeAutospacing ), "1" );
9291 else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1)
9293 AddToAttrList( m_pParagraphSpacingAttrList,
9294 FSNS( XML_w, XML_beforeAutospacing ), "0",
9295 FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9297 else
9299 AddToAttrList( m_pParagraphSpacingAttrList,
9300 FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9302 m_bParaBeforeAutoSpacing = false;
9303 // check if after auto spacing was set during import and spacing we get from actual object is same
9304 // that we set in import. If yes just write afterAutoSpacing tag.
9305 if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower())
9307 AddToAttrList( m_pParagraphSpacingAttrList,
9308 FSNS( XML_w, XML_afterAutospacing ), "1" );
9310 else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1)
9312 AddToAttrList( m_pParagraphSpacingAttrList,
9313 FSNS( XML_w, XML_afterAutospacing ), "0",
9314 FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9316 else
9318 AddToAttrList( m_pParagraphSpacingAttrList,
9319 FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9321 m_bParaAfterAutoSpacing = false;
9323 if (rULSpace.GetContext())
9324 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing);
9325 else
9327 // Write out Contextual Spacing = false if it would have inherited a true.
9328 const SvxULSpaceItem* pInherited = nullptr;
9329 if (auto pNd = dynamic_cast<const SwContentNode*>(m_rExport.m_pOutFormatNode)) //paragraph
9330 pInherited = &static_cast<SwTextFormatColl&>(pNd->GetAnyFormatColl()).GetAttrSet().GetULSpace();
9331 else if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle && m_rExport.m_pCurrentStyle->DerivedFrom()) //style
9332 pInherited = &m_rExport.m_pCurrentStyle->DerivedFrom()->GetULSpace();
9334 if (pInherited && pInherited->GetContext())
9335 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing, FSNS(XML_w, XML_val), "false");
9340 namespace docx {
9342 rtl::Reference<FastAttributeList> SurroundToVMLWrap(SwFormatSurround const& rSurround)
9344 std::string_view sType;
9345 std::string_view sSide;
9346 switch (rSurround.GetSurround())
9348 case css::text::WrapTextMode_NONE:
9349 sType = "topAndBottom";
9350 break;
9351 case css::text::WrapTextMode_PARALLEL:
9352 sType = "square";
9353 break;
9354 case css::text::WrapTextMode_DYNAMIC:
9355 sType = "square";
9356 sSide = "largest";
9357 break;
9358 case css::text::WrapTextMode_LEFT:
9359 sType = "square";
9360 sSide = "left";
9361 break;
9362 case css::text::WrapTextMode_RIGHT:
9363 sType = "square";
9364 sSide = "right";
9365 break;
9366 case css::text::WrapTextMode_THROUGH:
9367 /* empty type and side means through */
9368 default:
9369 sType = "none";
9370 break;
9372 rtl::Reference<FastAttributeList> pAttrList;
9373 if (!sType.empty())
9374 DocxAttributeOutput::AddToAttrList(pAttrList, XML_type, sType);
9375 if (!sSide.empty())
9376 DocxAttributeOutput::AddToAttrList(pAttrList, XML_side, sSide);
9377 return pAttrList;
9380 } // namespace docx
9382 void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround )
9384 if (m_rExport.SdrExporter().getTextFrameSyntax())
9386 rtl::Reference<FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
9387 if (pAttrList)
9389 m_rExport.SdrExporter().setFlyWrapAttrList(pAttrList);
9392 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9395 else if ( m_rExport.m_bOutFlyFrameAttrs )
9397 std::string_view sWrap;
9398 switch ( rSurround.GetSurround( ) )
9400 case css::text::WrapTextMode_NONE:
9401 sWrap = "none";
9402 break;
9403 case css::text::WrapTextMode_THROUGH:
9404 sWrap = "through";
9405 break;
9406 case css::text::WrapTextMode_DYNAMIC:
9407 case css::text::WrapTextMode_PARALLEL:
9408 case css::text::WrapTextMode_LEFT:
9409 case css::text::WrapTextMode_RIGHT:
9410 default:
9411 sWrap = "around";
9414 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap );
9418 void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert )
9420 OString sAlign = convertToOOXMLVertOrient( rFlyVert.GetVertOrient() );
9421 OString sVAnchor = convertToOOXMLVertOrientRel( rFlyVert.GetRelationOrient() );
9423 if (m_rExport.SdrExporter().getTextFrameSyntax())
9425 m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:" + OString::number(double(rFlyVert.GetPos()) / 20) + "pt");
9426 if ( !sAlign.isEmpty() )
9427 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:" + sAlign);
9428 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:" + sVAnchor);
9430 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9433 else if ( m_rExport.m_bOutFlyFrameAttrs )
9435 if ( !sAlign.isEmpty() )
9436 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign );
9437 else
9438 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ),
9439 OString::number( rFlyVert.GetPos() ) );
9440 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor );
9444 void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori )
9446 OString sAlign = convertToOOXMLHoriOrient( rFlyHori.GetHoriOrient(), rFlyHori.IsPosToggle() );
9447 OString sHAnchor = convertToOOXMLHoriOrientRel( rFlyHori.GetRelationOrient() );
9449 if (m_rExport.SdrExporter().getTextFrameSyntax())
9451 m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:" + OString::number(double(rFlyHori.GetPos()) / 20) + "pt");
9452 if ( !sAlign.isEmpty() )
9453 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:" + sAlign);
9454 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:" + sHAnchor);
9456 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9459 else if ( m_rExport.m_bOutFlyFrameAttrs )
9461 if ( !sAlign.isEmpty() )
9462 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign );
9463 else
9464 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ),
9465 OString::number( rFlyHori.GetPos() ) );
9466 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor );
9470 void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& )
9472 // Fly frames: anchors here aren't matching the anchors in docx
9475 static std::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush)
9477 std::optional<sal_Int32> oRet;
9478 sal_Int32 nTransparency = 255 - rBrush.GetColor().GetAlpha();
9479 if (nTransparency)
9481 // Convert transparency to percent
9482 sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency);
9484 // Calculate alpha value
9485 // Consider oox/source/drawingml/color.cxx : getTransparency() function.
9486 sal_Int32 nAlpha = ::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent );
9487 oRet = nAlpha;
9489 return oRet;
9492 void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush )
9494 const Color aColor = rBrush.GetColor();
9495 model::ComplexColor const& rComplexColor = rBrush.getComplexColor();
9496 OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() );
9497 std::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush);
9498 if (m_rExport.SdrExporter().getTextFrameSyntax())
9500 // Handle 'Opacity'
9501 if (oAlpha)
9503 // Calculate opacity value
9504 // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function.
9505 double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT;
9507 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OString::number(fOpacity) + "f" );
9510 AddToAttrList(m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor );
9511 lclAddThemeFillColorAttributes(m_rExport.SdrExporter().getFlyAttrList(), rComplexColor);
9513 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9515 bool bImageBackground = false;
9516 const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9517 if (pItem)
9519 const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9520 if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP)
9522 bImageBackground = true;
9525 if (!bImageBackground)
9527 m_pSerializer->startElementNS(XML_a, XML_solidFill);
9528 m_pSerializer->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
9529 if (oAlpha)
9530 m_pSerializer->singleElementNS(XML_a, XML_alpha,
9531 XML_val, OString::number(*oAlpha));
9532 m_pSerializer->endElementNS(XML_a, XML_srgbClr);
9533 m_pSerializer->endElementNS(XML_a, XML_solidFill);
9536 else if ( !m_rExport.m_bOutPageDescs )
9538 // compare fill color with the original fill color
9539 OString sOriginalFill = OUStringToOString(
9540 m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 );
9542 if ( aColor == COL_AUTO )
9543 sColor = "auto";
9545 if( !m_pBackgroundAttrList.is() )
9547 m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9548 m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9549 m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9551 else if ( sOriginalFill != sColor )
9553 // fill was modified during edition, theme fill attribute must be dropped
9554 m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9555 m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9556 m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9558 m_sOriginalBackgroundColor.clear();
9562 void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle )
9564 if (!m_bIgnoreNextFill)
9565 m_oFillStyle = rFillStyle.GetValue();
9566 else
9567 m_bIgnoreNextFill = false;
9569 // Don't round-trip grabbag OriginalBackground if the background has been cleared.
9570 if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE )
9571 m_pBackgroundAttrList.clear();
9574 void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient )
9576 if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax())
9578 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient" );
9580 const basegfx::BGradient& rGradient = rFillGradient.GetGradientValue();
9581 OString sStartColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().front().getStopColor()));
9582 OString sEndColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().back().getStopColor()));
9584 // Calculate the angle that was originally in the imported DOCX file
9585 // (reverse calculate the angle that was converted in the file
9586 // /oox/source/vml/vmlformatting.cxx :: FillModel::pushToPropMap
9587 // and also in
9588 // /oox/source/drawingml/fillproperties.cxx :: FillProperties::pushToPropMap
9589 sal_Int32 nReverseAngle = toDegrees(4500_deg10 - rGradient.GetAngle());
9590 nReverseAngle = (270 - nReverseAngle) % 360;
9591 if (nReverseAngle != 0)
9592 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(),
9593 XML_angle, OString::number( nReverseAngle ) );
9595 OString sColor1 = sStartColor;
9596 OString sColor2 = sEndColor;
9598 switch (rGradient.GetGradientStyle())
9600 case css::awt::GradientStyle_AXIAL:
9601 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" );
9602 // If it is an 'axial' gradient - swap the colors
9603 // (because in the import process they were imported swapped)
9604 sColor1 = sEndColor;
9605 sColor2 = sStartColor;
9606 break;
9607 case css::awt::GradientStyle_LINEAR: break;
9608 case css::awt::GradientStyle_RADIAL: break;
9609 case css::awt::GradientStyle_ELLIPTICAL: break;
9610 case css::awt::GradientStyle_SQUARE: break;
9611 case css::awt::GradientStyle_RECT: break;
9612 default:
9613 break;
9616 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor1 );
9617 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, "#" + sColor2 );
9619 else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax())
9621 SwFrameFormat & rFormat(
9622 const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat()));
9623 uno::Reference<beans::XPropertySet> const xPropertySet(
9624 static_cast<cppu::OWeakObject*>(SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat).get()),
9625 uno::UNO_QUERY);
9626 m_rDrawingML.SetFS(m_pSerializer);
9627 m_rDrawingML.WriteGradientFill(xPropertySet);
9629 m_oFillStyle.reset();
9632 void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox )
9634 if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9636 // ugh, exporting fill here is quite some hack... this OutputItemSet abstraction is quite leaky
9637 // <a:gradFill> should be before <a:ln>.
9638 const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9639 if (pItem)
9641 const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9642 FormatFillStyle(*pFillStyle);
9643 if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP)
9645 const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject();
9646 if (pSdrObj)
9648 uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
9649 uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY );
9650 m_rDrawingML.SetFS(m_pSerializer);
9651 m_rDrawingML.WriteBlipFill(xPropertySet, "BackGraphic");
9656 pItem = GetExport().HasItem(XATTR_FILLGRADIENT);
9657 if (pItem)
9659 const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem);
9660 FormatFillGradient(*pFillGradient);
9662 m_bIgnoreNextFill = true;
9664 if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax())
9666 const SvxBorderLine* pLeft = rBox.GetLeft( );
9667 const SvxBorderLine* pTop = rBox.GetTop( );
9668 const SvxBorderLine* pRight = rBox.GetRight( );
9669 const SvxBorderLine* pBottom = rBox.GetBottom( );
9671 if (pLeft && pRight && pTop && pBottom &&
9672 *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom)
9674 // Check border style
9675 SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle();
9676 if (eBorderStyle == SvxBorderLineStyle::NONE)
9678 if (m_rExport.SdrExporter().getTextFrameSyntax())
9680 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9681 XML_stroked, "f", XML_strokeweight, "0pt" );
9684 else
9686 OString sColor(msfilter::util::ConvertColor(pTop->GetColor()));
9687 double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth()));
9689 if (m_rExport.SdrExporter().getTextFrameSyntax())
9691 sal_Int32 nWidth = sal_Int32(fConverted / 20);
9692 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9693 XML_strokecolor, "#" + sColor,
9694 XML_strokeweight, OString::number(nWidth) + "pt" );
9695 if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type
9696 AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(),
9697 XML_dashstyle, "dash" );
9699 else
9700 m_rExport.SdrExporter().writeBoxItemLine(rBox);
9704 if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9706 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT))));
9707 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP))));
9708 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT))));
9709 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM))));
9710 return;
9713 // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values
9714 double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT));
9715 double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP));
9716 double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT));
9717 double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM));
9719 // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches)
9720 double fDistanceLeftInch = o3tl::convert(fDistanceLeftTwips, o3tl::Length::twip, o3tl::Length::in);
9721 double fDistanceTopInch = o3tl::convert(fDistanceTopTwips, o3tl::Length::twip, o3tl::Length::in);
9722 double fDistanceRightInch = o3tl::convert(fDistanceRightTwips, o3tl::Length::twip, o3tl::Length::in);
9723 double fDistanceBottomInch = o3tl::convert(fDistanceBottomTwips, o3tl::Length::twip, o3tl::Length::in);
9725 // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order.
9726 // so 'bottom' is checked if it is default and if it is non-default - all the values will be written
9727 // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written
9728 // and so on.
9729 OStringBuffer aInset;
9730 if(!aInset.isEmpty() || fDistanceBottomInch != 0.05)
9731 aInset.insert(0, Concat2View("," + OString::number(fDistanceBottomInch) + "in"));
9733 if(!aInset.isEmpty() || fDistanceRightInch != 0.1)
9734 aInset.insert(0, Concat2View("," + OString::number(fDistanceRightInch) + "in"));
9736 if(!aInset.isEmpty() || fDistanceTopInch != 0.05)
9737 aInset.insert(0, Concat2View("," + OString::number(fDistanceTopInch) + "in"));
9739 if(!aInset.isEmpty() || fDistanceLeftInch != 0.1)
9740 aInset.insert(0, Concat2View(OString::number(fDistanceLeftInch) + "in"));
9742 if (!aInset.isEmpty())
9743 m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset);
9745 return;
9748 OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
9749 // Check if there is a shadow item
9750 const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
9751 if ( pItem )
9753 const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
9754 aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
9757 if ( m_bOpenedSectPr && !GetWritingHeaderFooter())
9758 return;
9760 // Not inside a section
9762 // Open the paragraph's borders tag
9763 m_pSerializer->startElementNS(XML_w, XML_pBdr);
9765 std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders;
9766 const SvxBoxItem* pInherited = nullptr;
9767 if ( GetExport().m_pStyAttr )
9768 pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX);
9769 else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
9770 pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX);
9772 if ( pInherited )
9774 aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false);
9775 aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false);
9776 aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false);
9777 aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
9779 bool bUseFrame = m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
9780 impl_borders(m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders,
9781 bUseFrame ? m_aFramePr.Frame() : nullptr);
9783 // Close the paragraph's borders tag
9784 m_pSerializer->endElementNS( XML_w, XML_pBdr );
9786 m_aFramePr.SetUseFrameBorders(false);
9789 void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize )
9791 // Get the columns attributes
9792 rtl::Reference<FastAttributeList> pColsAttrList = FastSerializerHelper::createAttrList();
9794 pColsAttrList->add( FSNS( XML_w, XML_num ), OString::number( nCols ) );
9796 std::string_view pEquals = "false";
9797 if ( bEven )
9799 sal_uInt16 nWidth = rCol.GetGutterWidth( true );
9800 pColsAttrList->add( FSNS( XML_w, XML_space ), OString::number( nWidth ) );
9802 pEquals = "true";
9805 pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals );
9807 bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj());
9809 pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) );
9811 // Write the element
9812 m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList );
9814 // Write the columns width if non-equals
9815 const SwColumns & rColumns = rCol.GetColumns( );
9816 if ( !bEven )
9818 for ( sal_uInt16 n = 0; n < nCols; ++n )
9820 rtl::Reference<FastAttributeList> pColAttrList = FastSerializerHelper::createAttrList();
9821 sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, o3tl::narrowing<sal_uInt16>(nPageSize) );
9822 pColAttrList->add( FSNS( XML_w, XML_w ), OString::number( nWidth ) );
9824 if ( n + 1 != nCols )
9826 sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( );
9827 pColAttrList->add( FSNS( XML_w, XML_space ), OString::number( nSpacing ) );
9830 m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList );
9834 m_pSerializer->endElementNS( XML_w, XML_cols );
9837 void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem )
9839 m_pSerializer->singleElementNS( XML_w, XML_keepNext,
9840 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9843 void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid )
9845 rtl::Reference<FastAttributeList> pGridAttrList = FastSerializerHelper::createAttrList();
9847 std::string_view sGridType;
9848 switch ( rGrid.GetGridType( ) )
9850 default:
9851 case GRID_NONE:
9852 sGridType = "default";
9853 break;
9854 case GRID_LINES_ONLY:
9855 sGridType = "lines";
9856 break;
9857 case GRID_LINES_CHARS:
9858 if ( rGrid.IsSnapToChars( ) )
9859 sGridType = "snapToChars";
9860 else
9861 sGridType = "linesAndChars";
9862 break;
9864 pGridAttrList->add(FSNS(XML_w, XML_type), sGridType);
9866 sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight();
9867 pGridAttrList->add( FSNS( XML_w, XML_linePitch ),
9868 OString::number( nHeight ) );
9870 pGridAttrList->add( FSNS( XML_w, XML_charSpace ),
9871 OString::number( GridCharacterPitch( rGrid ) ) );
9873 m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList );
9876 void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering )
9878 if ( !rNumbering.IsCount( ) )
9879 m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers);
9882 void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection )
9884 OString sTextFlow;
9885 bool bBiDi = false;
9886 SvxFrameDirection nDir = rDirection.GetValue();
9888 if ( nDir == SvxFrameDirection::Environment )
9889 nDir = GetExport( ).GetDefaultFrameDirection( );
9891 switch ( nDir )
9893 default:
9894 case SvxFrameDirection::Horizontal_LR_TB:
9895 sTextFlow = OString( "lrTb" );
9896 break;
9897 case SvxFrameDirection::Horizontal_RL_TB:
9898 sTextFlow = OString( "lrTb" );
9899 bBiDi = true;
9900 break;
9901 case SvxFrameDirection::Vertical_LR_TB: // ~ vert="mongolianVert"
9902 sTextFlow = OString("tbLrV");
9903 break;
9904 case SvxFrameDirection::Vertical_RL_TB: // ~ vert="eaVert"
9905 sTextFlow = OString( "tbRl" );
9906 break;
9907 case SvxFrameDirection::Vertical_LR_BT: // ~ vert="vert270"
9908 sTextFlow = OString("btLr");
9909 break;
9910 case SvxFrameDirection::Vertical_RL_TB90: // ~ vert="vert"
9911 sTextFlow = OString("tbRlV");
9912 break;
9915 if ( m_rExport.m_bOutPageDescs )
9917 m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), sTextFlow);
9918 if ( bBiDi )
9919 m_pSerializer->singleElementNS(XML_w, XML_bidi);
9921 else if ( !m_rExport.m_bOutFlyFrameAttrs )
9923 if ( bBiDi )
9924 m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "1");
9925 else
9926 m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "0");
9927 m_aFramePr.SetUseFrameTextDirection(false);
9931 void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem)
9933 const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag();
9934 for ( const auto & rGrabBagElement : rMap )
9936 if (rGrabBagElement.first == "MirrorIndents")
9937 m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents);
9938 else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing")
9940 m_bParaBeforeAutoSpacing = true;
9941 // get fixed value which was set during import
9942 rGrabBagElement.second >>= m_nParaBeforeSpacing;
9943 m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100);
9944 SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing);
9946 else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing")
9948 m_bParaAfterAutoSpacing = true;
9949 // get fixed value which was set during import
9950 rGrabBagElement.second >>= m_nParaAfterSpacing;
9951 m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100);
9952 SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing);
9954 else if (rGrabBagElement.first == "CharThemeFill")
9956 uno::Sequence<beans::PropertyValue> aGrabBagSeq;
9957 rGrabBagElement.second >>= aGrabBagSeq;
9959 for (const auto& rProp : std::as_const(aGrabBagSeq))
9961 OUString sVal = rProp.Value.get<OUString>();
9963 if (sVal.isEmpty())
9964 continue;
9966 if (rProp.Name == "val")
9967 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal);
9968 else if (rProp.Name == "color")
9969 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal);
9970 else if (rProp.Name == "themeColor")
9971 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal);
9972 else if (rProp.Name == "themeTint")
9973 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal);
9974 else if (rProp.Name == "themeShade")
9975 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal);
9976 else if (rProp.Name == "fill")
9977 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal);
9978 else if (rProp.Name == "themeFill")
9979 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal);
9980 else if (rProp.Name == "themeFillTint")
9981 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal);
9982 else if (rProp.Name == "themeFillShade")
9983 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal);
9984 else if (rProp.Name == "originalColor")
9985 rProp.Value >>= m_sOriginalBackgroundColor;
9988 else if (rGrabBagElement.first == "SdtPr")
9990 const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
9991 rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
9992 m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
9993 m_aStartedParagraphSdtPrAlias = m_aParagraphSdt.m_aAlias;
9995 else if (rGrabBagElement.first == "ParaCnfStyle")
9997 uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
9998 m_pTableStyleExport->CnfStyle(aAttributes);
10000 else if (rGrabBagElement.first == "ParaSdtEndBefore")
10002 // Handled already in StartParagraph().
10004 else
10005 SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first );
10009 void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem )
10011 if (m_bPreventDoubleFieldsHandling)
10012 return;
10014 const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag();
10016 // get original values of theme-derived properties to check if they have changed during the edition
10017 bool bWriteCSTheme = true;
10018 bool bWriteAsciiTheme = true;
10019 bool bWriteEastAsiaTheme = true;
10020 OUString sOriginalValue;
10021 for ( const auto & rGrabBagElement : rMap )
10023 if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" )
10025 if ( rGrabBagElement.second >>= sOriginalValue )
10026 bWriteCSTheme =
10027 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue );
10029 else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" )
10031 if ( rGrabBagElement.second >>= sOriginalValue )
10032 bWriteAsciiTheme =
10033 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue );
10035 else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" )
10037 if ( rGrabBagElement.second >>= sOriginalValue )
10038 bWriteEastAsiaTheme =
10039 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue );
10043 // save theme attributes back to the run properties
10044 OUString str;
10045 for ( const auto & rGrabBagElement : rMap )
10047 if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme )
10049 rGrabBagElement.second >>= str;
10050 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ), str );
10052 else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme )
10054 rGrabBagElement.second >>= str;
10055 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ), str );
10057 else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme )
10059 rGrabBagElement.second >>= str;
10060 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ), str );
10062 else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme )
10063 // this is not a mistake: in LibO we don't directly support the hAnsi family
10064 // of attributes so we save the same value from ascii attributes instead
10066 rGrabBagElement.second >>= str;
10067 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ), str );
10069 else if( rGrabBagElement.first == "CharThemeFontNameCs" ||
10070 rGrabBagElement.first == "CharThemeFontNameAscii" ||
10071 rGrabBagElement.first == "CharThemeFontNameEastAsia" ||
10072 rGrabBagElement.first == "CharThemeOriginalColor" )
10074 // just skip these, they were processed before
10076 else if(rGrabBagElement.first == "CharGlowTextEffect" ||
10077 rGrabBagElement.first == "CharShadowTextEffect" ||
10078 rGrabBagElement.first == "CharReflectionTextEffect" ||
10079 rGrabBagElement.first == "CharTextOutlineTextEffect" ||
10080 rGrabBagElement.first == "CharTextFillTextEffect" ||
10081 rGrabBagElement.first == "CharScene3DTextEffect" ||
10082 rGrabBagElement.first == "CharProps3DTextEffect" ||
10083 rGrabBagElement.first == "CharLigaturesTextEffect" ||
10084 rGrabBagElement.first == "CharNumFormTextEffect" ||
10085 rGrabBagElement.first == "CharNumSpacingTextEffect" ||
10086 rGrabBagElement.first == "CharStylisticSetsTextEffect" ||
10087 rGrabBagElement.first == "CharCntxtAltsTextEffect")
10089 beans::PropertyValue aPropertyValue;
10090 rGrabBagElement.second >>= aPropertyValue;
10091 m_aTextEffectsGrabBag.push_back(aPropertyValue);
10093 else if (rGrabBagElement.first == "SdtEndBefore")
10095 if (m_aRunSdt.m_bStartedSdt)
10096 m_bEndCharSdt = true;
10098 else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame )
10100 const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10101 rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10102 m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10104 else
10105 SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first);
10109 DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML )
10110 : AttributeOutputBase(rExport.GetFilter().getFileUrl()),
10111 m_rExport( rExport ),
10112 m_pSerializer( pSerializer ),
10113 m_rDrawingML( *pDrawingML ),
10114 m_bEndCharSdt(false),
10115 m_endPageRef( false ),
10116 m_pFootnotesList( new ::docx::FootnotesList() ),
10117 m_pEndnotesList( new ::docx::FootnotesList() ),
10118 m_footnoteEndnoteRefTag( 0 ),
10119 m_pRedlineData( nullptr ),
10120 m_nRedlineId( 0 ),
10121 m_bOpenedSectPr( false ),
10122 m_bHadSectPr(false),
10123 m_bRunTextIsOn( false ),
10124 m_bWritingHeaderFooter( false ),
10125 m_bAnchorLinkedToNode(false),
10126 m_bWritingField( false ),
10127 m_bPreventDoubleFieldsHandling( false ),
10128 m_nNextBookmarkId( 0 ),
10129 m_nNextAnnotationMarkId( 0 ),
10130 m_nEmbedFlyLevel(0),
10131 m_pMoveRedlineData(nullptr),
10132 m_bParagraphOpened( false ),
10133 m_bParagraphFrameOpen( false ),
10134 m_bIsFirstParagraph( true ),
10135 m_bAlternateContentChoiceOpen( false ),
10136 m_bPostponedProcessingFly( false ),
10137 m_nColBreakStatus( COLBRK_NONE ),
10138 m_bPostponedPageBreak( false ),
10139 m_nTextFrameLevel( 0 ),
10140 m_closeHyperlinkInThisRun( false ),
10141 m_closeHyperlinkInPreviousRun( false ),
10142 m_nFieldsInHyperlink( 0 ),
10143 m_bExportingOutline(false),
10144 m_nChartCount(0),
10145 m_PendingPlaceholder( nullptr ),
10146 m_postitFieldsMaxId( 0 ),
10147 m_anchorId( 1 ),
10148 m_nextFontId( 1 ),
10149 m_bIgnoreNextFill(false),
10150 m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)),
10151 m_bParaBeforeAutoSpacing(false),
10152 m_bParaAfterAutoSpacing(false),
10153 m_nParaBeforeSpacing(0),
10154 m_nParaAfterSpacing(0)
10155 , m_nStateOfFlyFrame( FLY_NOT_PROCESSED )
10157 m_nHyperLinkCount.push_back(0);
10160 DocxAttributeOutput::~DocxAttributeOutput()
10164 DocxExport& DocxAttributeOutput::GetExport()
10166 return m_rExport;
10169 void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer )
10171 m_pSerializer = pSerializer;
10172 m_pTableStyleExport->SetSerializer(pSerializer);
10175 bool DocxAttributeOutput::HasFootnotes() const
10177 return !m_pFootnotesList->isEmpty();
10180 bool DocxAttributeOutput::HasEndnotes() const
10182 return !m_pEndnotesList->isEmpty();
10185 bool DocxAttributeOutput::HasPostitFields() const
10187 return !m_postitFields.empty();
10190 void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize)
10192 m_pSerializer->startElementNS(XML_w, XML_numPicBullet,
10193 FSNS(XML_w, XML_numPicBulletId), OString::number(nId));
10195 // Size is in twips, we need it in points.
10196 OString aStyle = "width:" + OString::number(double(aSize.Width()) / 20)+ "pt;"
10197 "height:" + OString::number(double(aSize.Height()) / 20) + "pt";
10198 m_pSerializer->startElementNS(XML_w, XML_pict);
10199 m_pSerializer->startElementNS( XML_v, XML_shape,
10200 XML_style, aStyle,
10201 FSNS(XML_o, XML_bullet), "t");
10203 OUString aRelId = m_rDrawingML.WriteImage(rGraphic);
10204 m_pSerializer->singleElementNS( XML_v, XML_imagedata,
10205 FSNS(XML_r, XML_id), aRelId,
10206 FSNS(XML_o, XML_title), "");
10208 m_pSerializer->endElementNS(XML_v, XML_shape);
10209 m_pSerializer->endElementNS(XML_w, XML_pict);
10211 m_pSerializer->endElementNS(XML_w, XML_numPicBullet);
10214 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */