use more concrete UNO classes in writerfilter (SwXFieldmark)
[LibreOffice.git] / sw / source / writerfilter / dmapper / DomainMapper_Impl.cxx
blob7f0b57ab483a96946e41bfb0b4e28d9af60d7b90
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 <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
21 #include <com/sun/star/beans/XPropertySet.hpp>
22 #include <com/sun/star/document/XDocumentProperties.hpp>
23 #include <com/sun/star/xml/sax/SAXException.hpp>
24 #include <ooxml/resourceids.hxx>
25 #include "DomainMapper_Impl.hxx"
26 #include "ConversionHelper.hxx"
27 #include "SdtHelper.hxx"
28 #include "DomainMapperTableHandler.hxx"
29 #include "TagLogger.hxx"
30 #include <com/sun/star/uno/XComponentContext.hpp>
31 #include <com/sun/star/graphic/XGraphic.hpp>
32 #include <com/sun/star/beans/PropertyAttribute.hpp>
33 #include <com/sun/star/beans/XPropertyState.hpp>
34 #include <com/sun/star/container/XNamed.hpp>
35 #include <com/sun/star/document/PrinterIndependentLayout.hpp>
36 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
37 #include <com/sun/star/embed/XEmbeddedObject.hpp>
38 #include <com/sun/star/i18n/NumberFormatMapper.hpp>
39 #include <com/sun/star/i18n/NumberFormatIndex.hpp>
40 #include <com/sun/star/lang/XServiceInfo.hpp>
41 #include <com/sun/star/style/CaseMap.hpp>
42 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
43 #include <com/sun/star/style/LineNumberPosition.hpp>
44 #include <com/sun/star/style/LineSpacing.hpp>
45 #include <com/sun/star/style/LineSpacingMode.hpp>
46 #include <com/sun/star/text/ChapterFormat.hpp>
47 #include <com/sun/star/text/FilenameDisplayFormat.hpp>
48 #include <com/sun/star/text/SetVariableType.hpp>
49 #include <com/sun/star/text/XDocumentIndex.hpp>
50 #include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
51 #include <com/sun/star/text/XFootnote.hpp>
52 #include <com/sun/star/text/XEndnotesSupplier.hpp>
53 #include <com/sun/star/text/XFootnotesSupplier.hpp>
54 #include <com/sun/star/text/XLineNumberingProperties.hpp>
55 #include <com/sun/star/style/XStyle.hpp>
56 #include <com/sun/star/text/LabelFollow.hpp>
57 #include <com/sun/star/text/PageNumberType.hpp>
58 #include <com/sun/star/text/HoriOrientation.hpp>
59 #include <com/sun/star/text/VertOrientation.hpp>
60 #include <com/sun/star/text/ReferenceFieldPart.hpp>
61 #include <com/sun/star/text/RelOrientation.hpp>
62 #include <com/sun/star/text/ReferenceFieldSource.hpp>
63 #include <com/sun/star/text/SizeType.hpp>
64 #include <com/sun/star/text/TextContentAnchorType.hpp>
65 #include <com/sun/star/text/WrapTextMode.hpp>
66 #include <com/sun/star/text/XChapterNumberingSupplier.hpp>
67 #include <com/sun/star/text/XDependentTextField.hpp>
68 #include <com/sun/star/text/XParagraphCursor.hpp>
69 #include <com/sun/star/text/XRedline.hpp>
70 #include <com/sun/star/text/XTextFieldsSupplier.hpp>
71 #include <com/sun/star/text/XTextFrame.hpp>
72 #include <com/sun/star/text/XTextTable.hpp>
73 #include <com/sun/star/text/RubyPosition.hpp>
74 #include <com/sun/star/text/XTextRangeCompare.hpp>
75 #include <com/sun/star/style/DropCapFormat.hpp>
76 #include <com/sun/star/util/NumberFormatter.hpp>
77 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
78 #include <com/sun/star/util/XNumberFormatter.hpp>
79 #include <com/sun/star/document/XViewDataSupplier.hpp>
80 #include <com/sun/star/container/XIndexContainer.hpp>
81 #include <com/sun/star/text/ControlCharacter.hpp>
82 #include <com/sun/star/text/XTextColumns.hpp>
83 #include <com/sun/star/awt/CharSet.hpp>
84 #include <com/sun/star/awt/FontRelief.hpp>
85 #include <com/sun/star/awt/FontSlant.hpp>
86 #include <com/sun/star/awt/FontStrikeout.hpp>
87 #include <com/sun/star/awt/FontWeight.hpp>
88 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
89 #include <com/sun/star/embed/XHierarchicalStorageAccess.hpp>
90 #include <com/sun/star/embed/ElementModes.hpp>
91 #include <com/sun/star/document/XImporter.hpp>
92 #include <com/sun/star/document/XFilter.hpp>
93 #include <comphelper/indexedpropertyvalues.hxx>
94 #include <editeng/flditem.hxx>
95 #include <editeng/unotext.hxx>
96 #include <o3tl/deleter.hxx>
97 #include <o3tl/safeint.hxx>
98 #include <o3tl/temporary.hxx>
99 #include <oox/mathml/imexport.hxx>
100 #include <utility>
101 #include <xmloff/odffields.hxx>
102 #include <rtl/uri.hxx>
103 #include <unotools/ucbstreamhelper.hxx>
104 #include <unotools/streamwrap.hxx>
105 #include <comphelper/scopeguard.hxx>
106 #include <comphelper/string.hxx>
108 #include <dmapper/GraphicZOrderHelper.hxx>
110 #include <oox/token/tokens.hxx>
112 #include <cmath>
113 #include <optional>
114 #include <map>
115 #include <tuple>
116 #include <unordered_map>
117 #include <regex>
118 #include <algorithm>
120 #include <officecfg/Office/Common.hxx>
121 #include <filter/msfilter/util.hxx>
122 #include <filter/msfilter/ww8fields.hxx>
123 #include <comphelper/sequence.hxx>
124 #include <comphelper/propertyvalue.hxx>
125 #include <comphelper/propertysequence.hxx>
126 #include <unotools/mediadescriptor.hxx>
127 #include <comphelper/diagnose_ex.hxx>
128 #include <sal/log.hxx>
129 #include <o3tl/string_view.hxx>
130 #include <com/sun/star/drawing/FillStyle.hpp>
132 #include <unicode/errorcode.h>
133 #include <unicode/regex.h>
134 #include <unotxdoc.hxx>
135 #include <SwXDocumentSettings.hxx>
136 #include <SwXTextDefaults.hxx>
137 #include <unobookmark.hxx>
139 #define REFFLDFLAG_STYLE_FROM_BOTTOM 0xc100
140 #define REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL 0xc200
142 using namespace ::com::sun::star;
143 using namespace oox;
144 namespace writerfilter::dmapper{
146 //line numbering for header/footer
147 static void lcl_linenumberingHeaderFooter( const uno::Reference<container::XNameContainer>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper )
149 const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname );
150 if (!pEntry)
151 return;
152 const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->m_pProperties.get();
153 if ( !pStyleSheetProperties )
154 return;
155 sal_Int32 nListId = pStyleSheetProperties->props().GetListId();
156 if( xStyles.is() )
158 if( xStyles->hasByName( rname ) )
160 uno::Reference< style::XStyle > xStyle;
161 xStyles->getByName( rname ) >>= xStyle;
162 if( !xStyle.is() )
163 return;
164 uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY );
165 xPropertySet->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::Any( nListId >= 0 ) );
170 // Populate Dropdown Field properties from FFData structure
171 static void lcl_handleDropdownField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
173 if ( !rxFieldProps.is() )
174 return;
176 if ( !pFFDataHandler->getName().isEmpty() )
177 rxFieldProps->setPropertyValue( "Name", uno::Any( pFFDataHandler->getName() ) );
179 const FFDataHandler::DropDownEntries_t& rEntries = pFFDataHandler->getDropDownEntries();
180 uno::Sequence< OUString > sItems( rEntries.size() );
181 ::std::copy( rEntries.begin(), rEntries.end(), sItems.getArray());
182 if ( sItems.hasElements() )
183 rxFieldProps->setPropertyValue( "Items", uno::Any( sItems ) );
185 sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32();
186 if (nResult > 0 && o3tl::make_unsigned(nResult) < sItems.size())
187 rxFieldProps->setPropertyValue("SelectedItem", uno::Any(sItems[nResult]));
188 if ( !pFFDataHandler->getHelpText().isEmpty() )
189 rxFieldProps->setPropertyValue( "Help", uno::Any( pFFDataHandler->getHelpText() ) );
192 static void lcl_handleTextField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
194 if ( rxFieldProps.is() && pFFDataHandler )
196 rxFieldProps->setPropertyValue
197 (getPropertyName(PROP_HINT),
198 uno::Any(pFFDataHandler->getStatusText()));
199 rxFieldProps->setPropertyValue
200 (getPropertyName(PROP_HELP),
201 uno::Any(pFFDataHandler->getHelpText()));
202 rxFieldProps->setPropertyValue
203 (getPropertyName(PROP_CONTENT),
204 uno::Any(pFFDataHandler->getTextDefault()));
209 Very similar to DomainMapper_Impl::GetPropertyFromStyleSheet
210 It is focused on paragraph properties search in current & parent stylesheet entries.
211 But it will not take into account properties with listid: these are "list paragraph styles" and
212 not used in some cases.
214 static uno::Any lcl_GetPropertyFromParaStyleSheetNoNum(PropertyIds eId, StyleSheetEntryPtr pEntry, const StyleSheetTablePtr& rStyleSheet)
216 while (pEntry)
218 if (pEntry->m_pProperties)
220 std::optional<PropertyMap::Property> aProperty =
221 pEntry->m_pProperties->getProperty(eId);
222 if (aProperty)
224 if (pEntry->m_pProperties->props().GetListId())
225 // It is a paragraph style with list. Paragraph list styles are not taken into account
226 return uno::Any();
227 else
228 return aProperty->second;
231 //search until the property is set or no parent is available
232 StyleSheetEntryPtr pNewEntry;
233 if (!pEntry->m_sBaseStyleIdentifier.isEmpty())
234 pNewEntry = rStyleSheet->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
236 SAL_WARN_IF(pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
238 if (pEntry == pNewEntry) //fdo#49587
239 break;
241 pEntry = pNewEntry;
243 return uno::Any();
247 namespace {
249 struct FieldConversion
251 const char* cFieldServiceName;
252 FieldId eFieldId;
257 typedef std::unordered_map<OUString, FieldConversion> FieldConversionMap_t;
259 /// Gives access to the parent field context of the topmost one, if there is any.
260 static FieldContextPtr GetParentFieldContext(const std::deque<FieldContextPtr>& rFieldStack)
262 if (rFieldStack.size() < 2)
264 return nullptr;
267 return rFieldStack[rFieldStack.size() - 2];
270 /// Decides if the pInner field inside pOuter is allowed in Writer core, depending on their type.
271 static bool IsFieldNestingAllowed(const FieldContextPtr& pOuter, const FieldContextPtr& pInner)
273 if (!pInner->GetFieldId())
275 return true;
278 std::optional<FieldId> oOuterFieldId = pOuter->GetFieldId();
279 if (!oOuterFieldId)
281 OUString aCommand = pOuter->GetCommand();
283 // Ignore leading space before the field name, but don't accept IFF when we check for IF.
284 while (aCommand.getLength() > 3 && aCommand[0] == ' ')
285 aCommand = aCommand.subView(1);
287 if (aCommand.startsWith("IF "))
289 // This will be FIELD_IF once the command is closed.
290 oOuterFieldId = FIELD_IF;
294 if (!oOuterFieldId)
296 return true;
299 switch (*oOuterFieldId)
301 case FIELD_IF:
303 switch (*pInner->GetFieldId())
305 case FIELD_DOCVARIABLE:
306 case FIELD_FORMTEXT:
307 case FIELD_FORMULA:
308 case FIELD_IF:
309 case FIELD_MERGEFIELD:
310 case FIELD_REF:
311 case FIELD_PAGE:
312 case FIELD_NUMPAGES:
314 // LO does not currently know how to evaluate these as conditions or results
315 return false;
317 default:
318 break;
320 break;
322 default:
323 break;
326 return true;
329 DomainMapper_Impl::DomainMapper_Impl(
330 DomainMapper& rDMapper,
331 uno::Reference<uno::XComponentContext> xContext,
332 rtl::Reference<SwXTextDocument> const& xModel,
333 SourceDocumentType eDocumentType,
334 utl::MediaDescriptor const & rMediaDesc) :
335 m_eDocumentType( eDocumentType ),
336 m_rDMapper( rDMapper ),
337 m_pOOXMLDocument(nullptr),
338 m_xTextDocument( xModel ),
339 m_xComponentContext(std::move( xContext )),
340 m_bForceGenericFields(officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get()),
341 m_bIsDecimalComma( false ),
342 m_bIsFirstSection( true ),
343 m_bStartTOC(false),
344 m_bStartTOCHeaderFooter(false),
345 m_bStartedTOC(false),
346 m_bStartIndex(false),
347 m_bStartBibliography(false),
348 m_nStartGenericField(0),
349 m_bTextDeleted(false),
350 m_nLastRedlineMovedID(1),
351 m_sCurrentPermId(0),
352 m_bFrameDirectionSet(false),
353 m_bInDocDefaultsImport(false),
354 m_bInStyleSheetImport( false ),
355 m_bInNumberingImport(false),
356 m_bInAnyTableImport( false ),
357 m_bDiscardHeaderFooter( false ),
358 m_eSkipFootnoteState(SkipFootnoteSeparator::OFF),
359 m_nFootnotes(-1),
360 m_nEndnotes(-1),
361 m_nFirstFootnoteIndex(-1),
362 m_nFirstEndnoteIndex(-1),
363 m_bLineNumberingSet( false ),
364 m_bIsParaMarkerChange( false ),
365 m_bIsParaMarkerMove( false ),
366 m_bRedlineImageInPreviousRun( false ),
367 m_bDummyParaAddedForTableInSection( false ),
368 m_bIsLastSectionGroup( false ),
369 m_bUsingEnhancedFields( false ),
370 m_nAnnotationId( -1 ),
371 m_aSmartTagHandler(m_xComponentContext, m_xTextDocument),
372 m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault("TextInsertModeRange", uno::Reference<text::XTextRange>())),
373 m_xAltChunkStartingRange(rMediaDesc.getUnpackedValueOrDefault("AltChunkStartingRange", uno::Reference<text::XTextRange>())),
374 m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)),
375 m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault("AltChunkMode", false)),
376 m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)),
377 m_bHasFtnSep(false),
378 m_bIsSplitPara(false),
379 m_bIsActualParagraphFramed( false ),
380 m_bSaxError(false)
382 m_StreamStateStack.emplace(); // add state for document body
383 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
384 utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString());
385 if (m_aBaseUrl.isEmpty()) {
386 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
387 utl::MediaDescriptor::PROP_URL, OUString());
390 appendTableManager( );
391 GetBodyText();
392 if (!m_bIsNewDoc && !m_xBodyText)
394 throw uno::Exception("failed to find body text of the insert position", nullptr);
397 uno::Reference< text::XTextAppend > xBodyTextAppend( m_xBodyText, uno::UNO_QUERY );
398 m_aTextAppendStack.push(TextAppendContext(xBodyTextAppend,
399 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(m_xInsertTextRange)));
401 //todo: does it makes sense to set the body text as static text interface?
402 uno::Reference< text::XTextAppendAndConvert > xBodyTextAppendAndConvert( m_xBodyText, uno::UNO_QUERY );
403 m_pTableHandler = new DomainMapperTableHandler(xBodyTextAppendAndConvert, *this);
404 getTableManager( ).setHandler(m_pTableHandler);
406 getTableManager( ).startLevel();
407 m_bUsingEnhancedFields = !comphelper::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get();
409 m_pSdtHelper = new SdtHelper(*this, m_xComponentContext);
411 m_aRedlines.push(std::vector<RedlineParamsPtr>());
413 if (m_bIsAltChunk)
415 m_bIsFirstSection = false;
420 DomainMapper_Impl::~DomainMapper_Impl()
422 assert(!m_StreamStateStack.empty());
423 ChainTextFrames();
424 // Don't remove last paragraph when pasting, sw expects that empty paragraph.
425 if (m_bIsNewDoc)
427 RemoveLastParagraph();
428 suppress_fun_call_w_exception(GetStyleSheetTable()->ApplyClonedTOCStyles());
430 if (hasTableManager())
432 getTableManager().endLevel();
433 popTableManager();
437 writerfilter::ooxml::OOXMLDocument* DomainMapper_Impl::getDocumentReference() const
439 return m_pOOXMLDocument;
442 uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetPageStyles()
444 if(!m_xPageStyles1.is() && m_xTextDocument)
445 m_xTextDocument->getStyleFamilies()->getByName("PageStyles") >>= m_xPageStyles1;
446 return m_xPageStyles1;
449 OUString DomainMapper_Impl::GetUnusedPageStyleName()
451 static const char DEFAULT_STYLE[] = "Converted";
452 if (!m_xNextUnusedPageStyleNo)
454 const uno::Sequence< OUString > aPageStyleNames = GetPageStyles()->getElementNames();
455 sal_Int32 nMaxIndex = 0;
456 // find the highest number x in each style with the name "DEFAULT_STYLE+x" and
457 // return an incremented name
459 for ( const auto& rStyleName : aPageStyleNames )
461 if ( rStyleName.startsWith( DEFAULT_STYLE ) )
463 sal_Int32 nIndex = o3tl::toInt32(rStyleName.subView( strlen( DEFAULT_STYLE ) ));
464 if ( nIndex > nMaxIndex )
465 nMaxIndex = nIndex;
468 m_xNextUnusedPageStyleNo = nMaxIndex + 1;
471 OUString sPageStyleName = DEFAULT_STYLE + OUString::number( *m_xNextUnusedPageStyleNo );
472 *m_xNextUnusedPageStyleNo = *m_xNextUnusedPageStyleNo + 1;
473 return sPageStyleName;
476 uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetCharacterStyles()
478 if(!m_xCharacterStyles.is() && m_xTextDocument)
479 m_xTextDocument->getStyleFamilies()->getByName("CharacterStyles") >>= m_xCharacterStyles;
480 return m_xCharacterStyles;
483 uno::Reference<container::XNameContainer> const& DomainMapper_Impl::GetParagraphStyles()
485 if (!m_xParagraphStyles.is() && m_xTextDocument)
486 m_xTextDocument->getStyleFamilies()->getByName("ParagraphStyles") >>= m_xParagraphStyles;
487 return m_xParagraphStyles;
490 OUString DomainMapper_Impl::GetUnusedCharacterStyleName()
492 static const char cListLabel[] = "ListLabel ";
493 if (!m_xNextUnusedCharacterStyleNo)
495 //search for all character styles with the name sListLabel + <index>
496 const uno::Sequence< OUString > aCharacterStyleNames = GetCharacterStyles()->getElementNames();
497 sal_Int32 nMaxIndex = 0;
498 for ( const auto& rStyleName : aCharacterStyleNames )
500 OUString sSuffix;
501 if ( rStyleName.startsWith( cListLabel, &sSuffix ) )
503 sal_Int32 nSuffix = sSuffix.toInt32();
504 if( nSuffix > 0 && nSuffix > nMaxIndex )
505 nMaxIndex = nSuffix;
508 m_xNextUnusedCharacterStyleNo = nMaxIndex + 1;
511 OUString sPageStyleName = cListLabel + OUString::number( *m_xNextUnusedCharacterStyleNo );
512 *m_xNextUnusedCharacterStyleNo = *m_xNextUnusedCharacterStyleNo + 1;
513 return sPageStyleName;
516 uno::Reference< text::XText > const & DomainMapper_Impl::GetBodyText()
518 if(!m_xBodyText.is())
520 if (m_xInsertTextRange.is())
521 m_xBodyText = m_xInsertTextRange->getText();
522 else if (m_xTextDocument.is())
523 m_xBodyText = m_xTextDocument->getText();
525 return m_xBodyText;
529 rtl::Reference<SwXDocumentSettings> const & DomainMapper_Impl::GetDocumentSettings()
531 if( !m_xDocumentSettings.is() && m_xTextDocument.is())
533 m_xDocumentSettings = m_xTextDocument->createDocumentSettings();
535 return m_xDocumentSettings;
539 void DomainMapper_Impl::SetDocumentSettingsProperty( const OUString& rPropName, const uno::Any& rValue )
541 uno::Reference< beans::XPropertySet > xSettings = GetDocumentSettings();
542 if( xSettings.is() )
546 xSettings->setPropertyValue( rPropName, rValue );
548 catch( const uno::Exception& )
554 namespace
556 void CopyPageDescNameToNextParagraph(const uno::Reference<lang::XComponent>& xParagraph,
557 const uno::Reference<text::XTextCursor>& xCursor)
559 // First check if xParagraph has a non-empty page style name to copy from.
560 uno::Reference<beans::XPropertySet> xParagraphProps(xParagraph, uno::UNO_QUERY);
561 if (!xParagraphProps.is())
563 return;
566 uno::Any aPageDescName = xParagraphProps->getPropertyValue("PageDescName");
567 OUString sPageDescName;
568 aPageDescName >>= sPageDescName;
569 if (sPageDescName.isEmpty())
571 return;
574 // If so, search for the next paragraph.
575 uno::Reference<text::XParagraphCursor> xParaCursor(xCursor, uno::UNO_QUERY);
576 if (!xParaCursor.is())
578 return;
581 // Create a range till the next paragraph and then enumerate on the range.
582 if (!xParaCursor->gotoNextParagraph(/*bExpand=*/true))
584 return;
587 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xParaCursor, uno::UNO_QUERY);
588 if (!xEnumerationAccess.is())
590 return;
593 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
594 if (!xEnumeration.is())
596 return;
599 xEnumeration->nextElement();
600 if (!xEnumeration->hasMoreElements())
602 return;
605 // We found a next item in the enumeration: it's usually a paragraph, but may be a table as
606 // well.
607 uno::Reference<beans::XPropertySet> xNextParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
608 if (!xNextParagraph.is())
610 return;
613 // See if there is page style set already: if so, don't touch it.
614 OUString sNextPageDescName;
615 xNextParagraph->getPropertyValue("PageDescName") >>= sNextPageDescName;
616 if (!sNextPageDescName.isEmpty())
618 return;
621 // Finally copy it over, so it's not lost.
622 xNextParagraph->setPropertyValue("PageDescName", aPageDescName);
626 void DomainMapper_Impl::RemoveDummyParaForTableInSection()
628 SetIsDummyParaAddedForTableInSection(false);
629 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
630 SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
631 if (!pSectionContext)
632 return;
634 if (m_aTextAppendStack.empty())
635 return;
636 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
637 if (!xTextAppend.is())
638 return;
640 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(pSectionContext->GetStartingRange());
642 // Remove the extra NumPicBullets from the document,
643 // which get attached to the first paragraph in the
644 // document
645 ListsManager::Pointer pListTable = GetListTable();
646 pListTable->DisposeNumPicBullets();
648 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY);
649 if (xEnumerationAccess.is() && m_aTextAppendStack.size() == 1 )
651 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
652 uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
653 // Make sure no page breaks are lost.
654 CopyPageDescNameToNextParagraph(xParagraph, xCursor);
655 xParagraph->dispose();
658 void DomainMapper_Impl::AddDummyParaForTableInSection()
660 // Shapes and textboxes can't have sections.
661 if (IsInShape() || m_StreamStateStack.top().bIsInTextBox)
662 return;
664 if (!m_aTextAppendStack.empty())
666 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
667 if (xTextAppend.is())
669 xTextAppend->finishParagraph( uno::Sequence< beans::PropertyValue >() );
670 SetIsDummyParaAddedForTableInSection(true);
675 static OUString lcl_FindLastBookmark(const uno::Reference<text::XTextCursor>& xCursor,
676 bool bAlreadyExpanded)
678 OUString sName;
679 if (!xCursor.is())
680 return sName;
682 // Select 1 previous element
683 if (!bAlreadyExpanded)
684 xCursor->goLeft(1, true);
685 comphelper::ScopeGuard unselectGuard(
686 [xCursor, bAlreadyExpanded]()
688 if (!bAlreadyExpanded)
689 xCursor->goRight(1, true);
692 uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY);
693 if (!xParaEnumAccess.is())
694 return sName;
696 // Iterate through selection paragraphs
697 uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
698 if (!xParaEnum->hasMoreElements())
699 return sName;
701 // Iterate through first para portions
702 uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(),
703 uno::UNO_QUERY_THROW);
704 uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
705 while (xRunEnum->hasMoreElements())
707 uno::Reference<beans::XPropertySet> xProps(xRunEnum->nextElement(), uno::UNO_QUERY_THROW);
708 uno::Any aType(xProps->getPropertyValue("TextPortionType"));
709 OUString sType;
710 aType >>= sType;
711 if (sType == "Bookmark")
713 uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue("Bookmark"),
714 uno::UNO_QUERY_THROW);
715 sName = xBookmark->getName();
716 // Do not stop the scan here. Maybe there are 2 bookmarks?
720 return sName;
723 static void reanchorObjects(const uno::Reference<uno::XInterface>& xFrom,
724 const uno::Reference<text::XTextRange>& xTo,
725 const uno::Reference<drawing::XDrawPage>& xDrawPage)
727 std::vector<uno::Reference<text::XTextContent>> aShapes;
728 bool bFastPathDone = false;
729 if (uno::Reference<beans::XPropertySet> xProps{ xFrom, uno::UNO_QUERY })
733 // See SwXParagraph::Impl::GetPropertyValues_Impl
734 uno::Sequence<uno::Reference<text::XTextContent>> aSeq;
735 xProps->getPropertyValue(u"OOXMLImport_AnchoredShapes"_ustr) >>= aSeq;
736 aShapes.insert(aShapes.end(), aSeq.begin(), aSeq.end());
737 bFastPathDone = true;
739 catch (const uno::Exception&)
744 if (!bFastPathDone)
746 // Can this happen? Fallback to slow DrawPage iteration and range comparison
747 uno::Reference<text::XTextRange> xRange(xFrom, uno::UNO_QUERY_THROW);
748 uno::Reference<text::XTextRangeCompare> xCompare(xRange->getText(), uno::UNO_QUERY_THROW);
750 const sal_Int32 count = xDrawPage->getCount();
751 for (sal_Int32 i = 0; i < count; ++i)
755 uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i),
756 uno::UNO_QUERY_THROW);
757 uno::Reference<text::XTextRange> xAnchor(xShape->getAnchor(), uno::UNO_SET_THROW);
758 if (xCompare->compareRegionStarts(xAnchor, xRange) <= 0
759 && xCompare->compareRegionEnds(xAnchor, xRange) >= 0)
761 aShapes.push_back(xShape);
764 catch (const uno::Exception&)
766 // Can happen e.g. in compareRegion*, when the shape is in a header,
767 // and paragraph in body
772 for (const auto& xShape : aShapes)
773 xShape->attach(xTo);
776 void DomainMapper_Impl::RemoveLastParagraph( )
778 if (m_bDiscardHeaderFooter)
779 return;
781 if (m_aTextAppendStack.empty())
782 return;
784 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
785 if (!xTextAppend.is())
786 return;
788 if (hasTableManager() && getTableManager().getCurrentTablePosition().getLength() != 0)
790 // If we have an open floating table, then don't remove this paragraph, since that'll be the
791 // anchor of the floating table. Otherwise we would lose the table.
792 return;
797 uno::Reference< text::XTextCursor > xCursor;
798 if (m_bIsNewDoc)
800 xCursor = xTextAppend->createTextCursor();
801 xCursor->gotoEnd(false);
803 else
804 xCursor.set(m_aTextAppendStack.top().xCursor, uno::UNO_SET_THROW);
806 // Keep the character properties of the last but one paragraph, even if
807 // it's empty. This works for headers/footers, and maybe in other cases
808 // as well, but surely not in textboxes.
809 // fdo#58327: also do this at the end of the document: when pasting,
810 // a table before the cursor position would be deleted
811 bool const bEndOfDocument(m_aTextAppendStack.size() == 1);
813 uno::Reference<lang::XComponent> xParagraph;
814 if (IsInHeaderFooter() || bEndOfDocument)
816 if (uno::Reference<container::XEnumerationAccess> xEA{ xCursor, uno::UNO_QUERY })
818 xParagraph.set(xEA->createEnumeration()->nextElement(), uno::UNO_QUERY);
822 xCursor->goLeft(1, true);
823 // If this is a text on a shape, possibly the text has the trailing
824 // newline removed already. RTF may also not have the trailing newline.
825 if (!(xCursor->getString() == SAL_NEWLINE_STRING ||
826 // tdf#105444 comments need an exception, if SAL_NEWLINE_STRING defined as "\r\n"
827 (sizeof(SAL_NEWLINE_STRING) - 1 == 2 && xCursor->getString() == "\n")))
828 return;
830 if (!m_xTextDocument)
831 return;
833 static constexpr OUString RecordChanges(u"RecordChanges"_ustr);
835 comphelper::ScopeGuard redlineRestore(
836 [this, aPreviousValue = m_xTextDocument->getPropertyValue(RecordChanges)]()
837 { m_xTextDocument->setPropertyValue(RecordChanges, aPreviousValue); });
839 // disable redlining, otherwise we might end up with an unwanted recorded operations
840 m_xTextDocument->setPropertyValue(RecordChanges, uno::Any(false));
842 if (xParagraph)
844 // move all anchored objects to the previous paragraph
845 auto xDrawPage = m_xTextDocument->getDrawPage();
846 if (xDrawPage && xDrawPage->hasElements())
848 // Cursor already spans two paragraphs
849 uno::Reference<container::XEnumerationAccess> xEA(xCursor,
850 uno::UNO_QUERY_THROW);
851 auto xEnumeration = xEA->createEnumeration();
852 uno::Reference<text::XTextRange> xPrevParagraph(xEnumeration->nextElement(),
853 uno::UNO_QUERY_THROW);
854 reanchorObjects(xParagraph, xPrevParagraph, xDrawPage);
857 xParagraph->dispose();
859 else
861 // Try to find and remember last bookmark in document: it potentially
862 // can be deleted by xCursor->setString() but not by xParagraph->dispose()
863 OUString sLastBookmarkName;
864 if (bEndOfDocument)
865 sLastBookmarkName = lcl_FindLastBookmark(xCursor, true);
867 // The cursor already selects across the paragraph break
868 // delete
869 xCursor->setString(OUString());
871 // call to xCursor->setString possibly did remove final bookmark
872 // from previous paragraph. We need to restore it, if there was any.
873 if (sLastBookmarkName.getLength())
875 OUString sBookmarkNameAfterRemoval = lcl_FindLastBookmark(xCursor, false);
876 if (sBookmarkNameAfterRemoval.isEmpty())
878 // Yes, it was removed. Restore
879 rtl::Reference<SwXBookmark> xBookmark(m_xTextDocument->createBookmark());
880 xBookmark->setName(sLastBookmarkName);
881 xTextAppend->insertTextContent(xCursor, xBookmark, !xCursor->isCollapsed());
886 catch( const uno::Exception& )
892 void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast )
894 m_bIsLastSectionGroup = bIsLast;
897 void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast )
899 m_StreamStateStack.top().bIsLastParaInSection = bIsLast;
903 void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst )
905 m_StreamStateStack.top().bIsFirstParaInSection = bIsFirst;
908 void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline )
910 m_StreamStateStack.top().bIsFirstParaInSectionAfterRedline = bIsFirstAfterRedline;
913 bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const
915 // Anchored objects may include multiple paragraphs,
916 // and none of them should be considered the first para in section.
917 return (bAfterRedline
918 ? m_StreamStateStack.top().bIsFirstParaInSectionAfterRedline
919 : m_StreamStateStack.top().bIsFirstParaInSection)
920 && !IsInShape()
921 && !IsInComments()
922 && !IsInFootOrEndnote();
925 void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst)
927 m_StreamStateStack.top().bIsFirstParaInShape = bIsFirst;
930 void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded )
932 m_bDummyParaAddedForTableInSection = bIsAdded;
936 void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted )
938 m_StreamStateStack.top().bTextFrameInserted = bIsInserted;
941 void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr)
943 m_StreamStateStack.top().bParaSectpr = bParaSectpr;
946 void DomainMapper_Impl::SetSdt(bool bSdt)
948 m_StreamStateStack.top().bSdt = bSdt;
950 if (m_StreamStateStack.top().bSdt && !m_aTextAppendStack.empty())
952 m_StreamStateStack.top().xSdtEntryStart = GetTopTextAppend()->getEnd();
954 else
956 m_StreamStateStack.top().xSdtEntryStart.clear();
960 void DomainMapper_Impl::PushSdt()
962 if (m_aTextAppendStack.empty())
964 return;
967 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
968 if (!xTextAppend.is())
970 return;
973 // This may delete text, so call it early, before we would set our start position, which may be
974 // invalidated by a delete.
975 MergeAtContentImageRedlineWithNext(xTextAppend);
977 uno::Reference<text::XText> xText = xTextAppend->getText();
978 if (!xText.is())
980 return;
983 uno::Reference<text::XTextCursor> xCursor
984 = xText->createTextCursorByRange(xTextAppend->getEnd());
985 // Offset so the cursor is not adjusted as we import the SDT's content.
986 bool bStart = !xCursor->goLeft(1, /*bExpand=*/false);
987 m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()});
990 const std::stack<BookmarkInsertPosition>& DomainMapper_Impl::GetSdtStarts() const
992 return m_xSdtStarts;
995 void DomainMapper_Impl::PopSdt()
997 if (m_xSdtStarts.empty())
999 return;
1002 BookmarkInsertPosition aPosition = m_xSdtStarts.top();
1003 m_xSdtStarts.pop();
1004 uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange;
1005 uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd();
1006 uno::Reference<text::XText> xText = xEnd->getText();
1008 uno::Reference<text::XTextCursor> xCursor;
1011 xCursor = xText->createTextCursorByRange(xStart);
1013 catch (const uno::RuntimeException&)
1015 TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::DomainMapper_Impl::PopSdt: createTextCursorByRange() failed");
1016 // We redline form controls and that gets us confused when
1017 // we process the SDT around the placeholder. What seems to
1018 // happen is we lose the text-range when we pop the SDT position.
1019 // Here, we reset the text-range when we fail to create the
1020 // cursor from the top SDT position.
1021 if (m_aTextAppendStack.empty())
1023 return;
1026 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
1027 if (!xTextAppend.is())
1029 return;
1032 uno::Reference<text::XText> xText2 = xTextAppend->getText();
1033 if (!xText2.is())
1035 return;
1038 // Reset to the start.
1039 xCursor = xText2->createTextCursorByRange(xTextAppend->getStart());
1042 if (!xCursor)
1044 SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position");
1045 return;
1048 if (aPosition.m_bIsStartOfText)
1050 // Go to the start of the end's paragraph. This helps in case
1051 // DomainMapper_Impl::AddDummyParaForTableInSection() would make our range multi-paragraph,
1052 // while the intention is to keep start/end inside the same paragraph for run SDTs.
1053 uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY);
1054 if (xParagraphCursor.is()
1055 && m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent)
1057 xCursor->gotoRange(xEnd, /*bExpand=*/false);
1058 xParagraphCursor->gotoStartOfParagraph(/*bExpand=*/false);
1061 else
1063 // Undo the goLeft() in DomainMapper_Impl::PushSdt();
1064 xCursor->goRight(1, /*bExpand=*/false);
1066 xCursor->gotoRange(xEnd, /*bExpand=*/true);
1068 std::optional<OUString> oData = m_pSdtHelper->getValueFromDataBinding();
1069 if (oData.has_value())
1071 // Data binding has a value for us, prefer that over the in-document value.
1072 xCursor->setString(*oData);
1074 // Such value is always a plain text string, remove the char style of the placeholder.
1075 uno::Reference<beans::XPropertyState> xPropertyState(xCursor, uno::UNO_QUERY);
1076 if (xPropertyState.is())
1078 xPropertyState->setPropertyToDefault("CharStyleName");
1082 uno::Reference<text::XTextContent> xContentControl(
1083 m_xTextDocument->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY);
1084 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
1085 if (m_pSdtHelper->GetShowingPlcHdr())
1087 xContentControlProps->setPropertyValue("ShowingPlaceHolder",
1088 uno::Any(m_pSdtHelper->GetShowingPlcHdr()));
1091 if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty())
1093 xContentControlProps->setPropertyValue("PlaceholderDocPart",
1094 uno::Any(m_pSdtHelper->GetPlaceholderDocPart()));
1097 if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
1099 xContentControlProps->setPropertyValue("DataBindingPrefixMappings",
1100 uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping()));
1102 if (!m_pSdtHelper->GetDataBindingXPath().isEmpty())
1104 xContentControlProps->setPropertyValue("DataBindingXpath",
1105 uno::Any(m_pSdtHelper->GetDataBindingXPath()));
1107 if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty())
1109 xContentControlProps->setPropertyValue("DataBindingStoreItemID",
1110 uno::Any(m_pSdtHelper->GetDataBindingStoreItemID()));
1113 if (!m_pSdtHelper->GetColor().isEmpty())
1115 xContentControlProps->setPropertyValue("Color",
1116 uno::Any(m_pSdtHelper->GetColor()));
1119 if (!m_pSdtHelper->GetAppearance().isEmpty())
1121 xContentControlProps->setPropertyValue("Appearance",
1122 uno::Any(m_pSdtHelper->GetAppearance()));
1125 if (!m_pSdtHelper->GetAlias().isEmpty())
1127 xContentControlProps->setPropertyValue("Alias",
1128 uno::Any(m_pSdtHelper->GetAlias()));
1131 if (!m_pSdtHelper->GetTag().isEmpty())
1133 xContentControlProps->setPropertyValue("Tag",
1134 uno::Any(m_pSdtHelper->GetTag()));
1137 if (m_pSdtHelper->GetId())
1139 xContentControlProps->setPropertyValue("Id", uno::Any(m_pSdtHelper->GetId()));
1142 if (m_pSdtHelper->GetTabIndex())
1144 xContentControlProps->setPropertyValue("TabIndex", uno::Any(m_pSdtHelper->GetTabIndex()));
1147 if (!m_pSdtHelper->GetLock().isEmpty())
1149 xContentControlProps->setPropertyValue("Lock", uno::Any(m_pSdtHelper->GetLock()));
1152 if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
1154 xContentControlProps->setPropertyValue("Checkbox", uno::Any(true));
1156 xContentControlProps->setPropertyValue("Checked", uno::Any(m_pSdtHelper->GetChecked()));
1158 xContentControlProps->setPropertyValue("CheckedState",
1159 uno::Any(m_pSdtHelper->GetCheckedState()));
1161 xContentControlProps->setPropertyValue("UncheckedState",
1162 uno::Any(m_pSdtHelper->GetUncheckedState()));
1165 if (m_pSdtHelper->getControlType() == SdtControlType::dropDown
1166 || m_pSdtHelper->getControlType() == SdtControlType::comboBox)
1168 std::vector<OUString>& rDisplayTexts = m_pSdtHelper->getDropDownDisplayTexts();
1169 std::vector<OUString>& rValues = m_pSdtHelper->getDropDownItems();
1170 if (rDisplayTexts.size() == rValues.size())
1172 uno::Sequence<beans::PropertyValues> aItems(rValues.size());
1173 beans::PropertyValues* pItems = aItems.getArray();
1174 for (size_t i = 0; i < rValues.size(); ++i)
1176 pItems[i] = {
1177 comphelper::makePropertyValue("DisplayText", rDisplayTexts[i]),
1178 comphelper::makePropertyValue("Value", rValues[i])
1181 xContentControlProps->setPropertyValue("ListItems", uno::Any(aItems));
1182 if (m_pSdtHelper->getControlType() == SdtControlType::dropDown)
1184 xContentControlProps->setPropertyValue("DropDown", uno::Any(true));
1186 else
1188 xContentControlProps->setPropertyValue("ComboBox", uno::Any(true));
1193 if (m_pSdtHelper->getControlType() == SdtControlType::picture)
1195 xContentControlProps->setPropertyValue("Picture", uno::Any(true));
1198 bool bDateFromDataBinding = false;
1199 if (m_pSdtHelper->getControlType() == SdtControlType::datePicker)
1201 xContentControlProps->setPropertyValue("Date", uno::Any(true));
1202 OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear();
1203 xContentControlProps->setPropertyValue("DateFormat",
1204 uno::Any(aDateFormat.replaceAll("'", "\"")));
1205 xContentControlProps->setPropertyValue("DateLanguage",
1206 uno::Any(m_pSdtHelper->getLocale().makeStringAndClear()));
1207 OUString aCurrentDate = m_pSdtHelper->getDate().makeStringAndClear();
1208 if (oData.has_value())
1210 aCurrentDate = *oData;
1211 bDateFromDataBinding = true;
1213 xContentControlProps->setPropertyValue("CurrentDate",
1214 uno::Any(aCurrentDate));
1217 if (m_pSdtHelper->getControlType() == SdtControlType::plainText)
1219 xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
1222 xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
1224 if (bDateFromDataBinding)
1226 OUString aDateString;
1227 xContentControlProps->getPropertyValue("DateString") >>= aDateString;
1228 xCursor->setString(aDateString);
1231 m_pSdtHelper->clear();
1234 void DomainMapper_Impl::PushProperties(ContextType eId)
1236 PropertyMapPtr pInsert(eId == CONTEXT_SECTION ?
1237 (new SectionPropertyMap( m_bIsFirstSection )) :
1238 eId == CONTEXT_PARAGRAPH ? new ParagraphPropertyMap : new PropertyMap);
1239 if(eId == CONTEXT_SECTION)
1241 if( m_bIsFirstSection )
1242 m_bIsFirstSection = false;
1243 // beginning with the second section group a section has to be inserted
1244 // into the document
1245 SectionPropertyMap* pSectionContext_ = dynamic_cast< SectionPropertyMap* >( pInsert.get() );
1246 if (!m_aTextAppendStack.empty())
1248 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
1249 if (xTextAppend.is() && pSectionContext_)
1250 pSectionContext_->SetStart( xTextAppend->getEnd() );
1253 if(eId == CONTEXT_PARAGRAPH && m_bIsSplitPara)
1255 // Some paragraph properties only apply at the beginning of the paragraph - apply only once.
1256 if (!IsFirstRun())
1258 auto pParaContext = static_cast<ParagraphPropertyMap*>(GetTopContextOfType(eId).get());
1259 pParaContext->props().SetListId(-1);
1260 pParaContext->Erase(PROP_NUMBERING_RULES); // only true with column, not page break
1261 pParaContext->Erase(PROP_NUMBERING_LEVEL);
1262 pParaContext->Erase(PROP_NUMBERING_TYPE);
1263 pParaContext->Erase(PROP_START_WITH);
1265 pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(sal_uInt32(0)));
1266 pParaContext->Erase(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
1267 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)));
1270 m_aPropertyStacks[eId].push( GetTopContextOfType(eId));
1271 m_bIsSplitPara = false;
1273 else
1275 m_aPropertyStacks[eId].push( pInsert );
1277 m_aContextStack.push(eId);
1279 m_pTopContext = m_aPropertyStacks[eId].top();
1283 void DomainMapper_Impl::PushStyleProperties( const PropertyMapPtr& pStyleProperties )
1285 m_aPropertyStacks[CONTEXT_STYLESHEET].push( pStyleProperties );
1286 m_aContextStack.push(CONTEXT_STYLESHEET);
1288 m_pTopContext = m_aPropertyStacks[CONTEXT_STYLESHEET].top();
1292 void DomainMapper_Impl::PushListProperties(const PropertyMapPtr& pListProperties)
1294 m_aPropertyStacks[CONTEXT_LIST].push( pListProperties );
1295 m_aContextStack.push(CONTEXT_LIST);
1296 m_pTopContext = m_aPropertyStacks[CONTEXT_LIST].top();
1300 void DomainMapper_Impl::PopProperties(ContextType eId)
1302 OSL_ENSURE(!m_aPropertyStacks[eId].empty(), "section stack already empty");
1303 if ( m_aPropertyStacks[eId].empty() )
1304 return;
1306 if ( eId == CONTEXT_SECTION )
1308 if (m_aPropertyStacks[eId].size() == 1) // tdf#112202 only top level !!!
1310 m_pLastSectionContext = dynamic_cast< SectionPropertyMap* >( m_aPropertyStacks[eId].top().get() );
1311 assert(m_pLastSectionContext);
1314 else if (eId == CONTEXT_CHARACTER)
1316 m_pLastCharacterContext = m_aPropertyStacks[eId].top();
1317 // Sadly an assert about deferredCharacterProperties being empty is not possible
1318 // here, because appendTextPortion() may not be called for every character section.
1319 m_StreamStateStack.top().deferredCharacterProperties.clear();
1322 if (!IsInFootOrEndnote() && IsInCustomFootnote() && !m_aPropertyStacks[eId].empty())
1324 PropertyMapPtr pRet = m_aPropertyStacks[eId].top();
1325 if (pRet->GetFootnote().is() && m_pFootnoteContext.is())
1326 EndCustomFootnote();
1329 m_aPropertyStacks[eId].pop();
1330 m_aContextStack.pop();
1331 if(!m_aContextStack.empty() && !m_aPropertyStacks[m_aContextStack.top()].empty())
1333 m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top();
1334 else
1336 // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section context end");
1337 m_pTopContext.clear();
1342 PropertyMapPtr DomainMapper_Impl::GetTopContextOfType(ContextType eId)
1344 PropertyMapPtr pRet;
1345 if(!m_aPropertyStacks[eId].empty())
1346 pRet = m_aPropertyStacks[eId].top();
1347 return pRet;
1350 bool DomainMapper_Impl::HasTopText() const
1352 return !m_aTextAppendStack.empty();
1355 uno::Reference< text::XTextAppend > const & DomainMapper_Impl::GetTopTextAppend()
1357 OSL_ENSURE(!m_aTextAppendStack.empty(), "text append stack is empty" );
1358 return m_aTextAppendStack.top().xTextAppend;
1361 FieldContextPtr const & DomainMapper_Impl::GetTopFieldContext()
1363 SAL_WARN_IF(m_aFieldStack.empty(), "writerfilter.dmapper", "Field stack is empty");
1364 return m_aFieldStack.back();
1367 bool DomainMapper_Impl::HasTopAnchoredObjects() const
1369 return !m_aTextAppendStack.empty() && !m_aTextAppendStack.top().m_aAnchoredObjects.empty();
1372 void DomainMapper_Impl::InitTabStopFromStyle( const uno::Sequence< style::TabStop >& rInitTabStops )
1374 OSL_ENSURE(m_aCurrentTabStops.empty(), "tab stops already initialized");
1375 for( const auto& rTabStop : rInitTabStops)
1377 m_aCurrentTabStops.emplace_back(rTabStop);
1381 void DomainMapper_Impl::IncorporateTabStop( const DeletableTabStop & rTabStop )
1383 sal_Int32 nConverted = rTabStop.Position;
1384 auto aIt = std::find_if(m_aCurrentTabStops.begin(), m_aCurrentTabStops.end(),
1385 [&nConverted](const DeletableTabStop& rCurrentTabStop) { return rCurrentTabStop.Position == nConverted; });
1386 if( aIt != m_aCurrentTabStops.end() )
1388 if( rTabStop.bDeleted )
1389 m_aCurrentTabStops.erase( aIt );
1390 else
1391 *aIt = rTabStop;
1393 else
1394 m_aCurrentTabStops.push_back( rTabStop );
1398 uno::Sequence< style::TabStop > DomainMapper_Impl::GetCurrentTabStopAndClear()
1400 std::vector<style::TabStop> aRet;
1401 for (const DeletableTabStop& rStop : m_aCurrentTabStops)
1403 if (!rStop.bDeleted)
1404 aRet.push_back(rStop);
1406 m_aCurrentTabStops.clear();
1407 return comphelper::containerToSequence(aRet);
1410 OUString DomainMapper_Impl::GetCurrentParaStyleName()
1412 OUString sName;
1413 // use saved currParaStyleName as a fallback, in case no particular para style name applied.
1414 // tdf#134784 except in the case of first paragraph of shapes to avoid bad fallback.
1415 // TODO fix this "highly inaccurate" m_sCurrentParaStyleName
1416 if ( !IsInShape() )
1417 sName = m_StreamStateStack.top().sCurrentParaStyleName;
1419 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
1420 if ( pParaContext && pParaContext->isSet(PROP_PARA_STYLE_NAME) )
1421 pParaContext->getProperty(PROP_PARA_STYLE_NAME)->second >>= sName;
1423 // In rare situations the name might still be blank, so use the default style,
1424 // despite documentation that states, "If this attribute is not specified for any style,
1425 // then no properties shall be applied to objects of the specified type."
1426 // Word, however, assigns "Normal" style even in these situations.
1427 if ( !m_bInStyleSheetImport && sName.isEmpty() )
1428 sName = GetDefaultParaStyleName();
1430 return sName;
1433 OUString DomainMapper_Impl::GetDefaultParaStyleName()
1435 // After import the default style won't change and is frequently requested: cache the LO style name.
1436 // TODO assert !InStyleSheetImport? This function really only makes sense once import is finished anyway.
1437 if ( m_sDefaultParaStyleName.isEmpty() )
1439 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindDefaultParaStyle();
1440 if ( pEntry && !pEntry->m_sConvertedStyleName.isEmpty() )
1442 if ( !m_bInStyleSheetImport )
1443 m_sDefaultParaStyleName = pEntry->m_sConvertedStyleName;
1444 return pEntry->m_sConvertedStyleName;
1446 else
1447 return "Standard";
1449 return m_sDefaultParaStyleName;
1452 uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault)
1454 while(pEntry)
1456 if(pEntry->m_pProperties)
1458 std::optional<PropertyMap::Property> aProperty =
1459 pEntry->m_pProperties->getProperty(eId);
1460 if( aProperty )
1462 if (pIsDocDefault)
1463 *pIsDocDefault = pEntry->m_pProperties->isDocDefault(eId);
1465 return aProperty->second;
1468 //search until the property is set or no parent is available
1469 StyleSheetEntryPtr pNewEntry;
1470 if ( !pEntry->m_sBaseStyleIdentifier.isEmpty() )
1471 pNewEntry = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
1473 SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
1475 if (pEntry == pNewEntry) //fdo#49587
1476 break;
1478 pEntry = pNewEntry;
1480 // not found in style, try the document's DocDefault properties
1481 if ( bDocDefaults && bPara )
1483 const PropertyMapPtr& pDefaultParaProps = GetStyleSheetTable()->GetDefaultParaProps();
1484 if ( pDefaultParaProps )
1486 std::optional<PropertyMap::Property> aProperty = pDefaultParaProps->getProperty(eId);
1487 if ( aProperty )
1489 if (pIsDocDefault)
1490 *pIsDocDefault = true;
1492 return aProperty->second;
1496 if ( bDocDefaults && isCharacterProperty(eId) )
1498 const PropertyMapPtr& pDefaultCharProps = GetStyleSheetTable()->GetDefaultCharProps();
1499 if ( pDefaultCharProps )
1501 std::optional<PropertyMap::Property> aProperty = pDefaultCharProps->getProperty(eId);
1502 if ( aProperty )
1504 if (pIsDocDefault)
1505 *pIsDocDefault = true;
1507 return aProperty->second;
1512 if (pIsDocDefault)
1513 *pIsDocDefault = false;
1515 return uno::Any();
1518 uno::Any DomainMapper_Impl::GetPropertyFromParaStyleSheet(PropertyIds eId)
1520 StyleSheetEntryPtr pEntry;
1521 if ( m_bInStyleSheetImport )
1522 pEntry = GetStyleSheetTable()->GetCurrentEntry();
1523 else
1524 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName());
1525 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
1528 uno::Any DomainMapper_Impl::GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext)
1530 if ( m_bInStyleSheetImport || eId == PROP_CHAR_STYLE_NAME || !isCharacterProperty(eId) )
1531 return uno::Any();
1533 StyleSheetEntryPtr pEntry;
1534 OUString sCharStyleName;
1535 if ( GetAnyProperty(PROP_CHAR_STYLE_NAME, rContext) >>= sCharStyleName )
1536 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sCharStyleName);
1537 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/false, /*bPara=*/false);
1540 uno::Any DomainMapper_Impl::GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext)
1542 // first look in directly applied attributes
1543 if ( rContext )
1545 std::optional<PropertyMap::Property> aProperty = rContext->getProperty(eId);
1546 if ( aProperty )
1547 return aProperty->second;
1550 // then look whether it was directly applied as a paragraph property
1551 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
1552 if (pParaContext && rContext != pParaContext)
1554 std::optional<PropertyMap::Property> aProperty = pParaContext->getProperty(eId);
1555 if (aProperty)
1556 return aProperty->second;
1559 // then look whether it was inherited from a directly applied character style
1560 if ( eId != PROP_CHAR_STYLE_NAME && isCharacterProperty(eId) )
1562 uno::Any aRet = GetPropertyFromCharStyleSheet(eId, rContext);
1563 if ( aRet.hasValue() )
1564 return aRet;
1567 // then look in current paragraph style, and docDefaults
1568 return GetPropertyFromParaStyleSheet(eId);
1571 OUString DomainMapper_Impl::GetListStyleName(sal_Int32 nListId)
1573 auto const pList(GetListTable()->GetList( nListId ));
1574 return pList ? pList->GetStyleName() : OUString();
1577 ListsManager::Pointer const & DomainMapper_Impl::GetListTable()
1579 if(!m_pListTable)
1580 m_pListTable =
1581 new ListsManager( m_rDMapper, m_xTextDocument );
1582 return m_pListTable;
1586 void DomainMapper_Impl::deferBreak( BreakType deferredBreakType)
1588 assert(!m_StreamStateStack.empty());
1589 switch (deferredBreakType)
1591 case LINE_BREAK:
1592 m_StreamStateStack.top().nLineBreaksDeferred++;
1593 break;
1594 case COLUMN_BREAK:
1595 m_StreamStateStack.top().bIsColumnBreakDeferred = true;
1596 break;
1597 case PAGE_BREAK:
1598 // See SwWW8ImplReader::HandlePageBreakChar(), page break should be
1599 // ignored inside tables.
1600 if (0 < m_StreamStateStack.top().nTableDepth)
1601 return;
1603 m_StreamStateStack.top().bIsPageBreakDeferred = true;
1604 break;
1605 default:
1606 return;
1610 bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType )
1612 assert(!m_StreamStateStack.empty());
1613 switch (deferredBreakType)
1615 case LINE_BREAK:
1616 return 0 < m_StreamStateStack.top().nLineBreaksDeferred;
1617 case COLUMN_BREAK:
1618 return m_StreamStateStack.top().bIsColumnBreakDeferred;
1619 case PAGE_BREAK:
1620 return m_StreamStateStack.top().bIsPageBreakDeferred;
1621 default:
1622 return false;
1626 void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType)
1628 assert(!m_StreamStateStack.empty());
1629 switch (deferredBreakType)
1631 case LINE_BREAK:
1632 assert(0 < m_StreamStateStack.top().nLineBreaksDeferred);
1633 m_StreamStateStack.top().nLineBreaksDeferred--;
1634 break;
1635 case COLUMN_BREAK:
1636 m_StreamStateStack.top().bIsColumnBreakDeferred = false;
1637 break;
1638 case PAGE_BREAK:
1639 m_StreamStateStack.top().bIsPageBreakDeferred = false;
1640 break;
1641 default:
1642 break;
1646 void DomainMapper_Impl::clearDeferredBreaks()
1648 assert(!m_StreamStateStack.empty());
1649 m_StreamStateStack.top().nLineBreaksDeferred = 0;
1650 m_StreamStateStack.top().bIsColumnBreakDeferred = false;
1651 m_StreamStateStack.top().bIsPageBreakDeferred = false;
1654 void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred)
1656 m_StreamStateStack.top().bSdtEndDeferred = bSdtEndDeferred;
1659 bool DomainMapper_Impl::isSdtEndDeferred() const
1661 return m_StreamStateStack.top().bSdtEndDeferred;
1664 void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred)
1666 m_StreamStateStack.top().bParaSdtEndDeferred = bParaSdtEndDeferred;
1669 bool DomainMapper_Impl::isParaSdtEndDeferred() const
1671 return m_StreamStateStack.top().bParaSdtEndDeferred;
1674 static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties,
1675 uno::Reference<text::XTextRange> const& xStartTextRange,
1676 uno::Reference<text::XTextRange> const& xEndTextRange,
1677 bool bIsRTFImport)
1681 if (!xStartTextRange.is()) //rhbz#1077780
1682 return;
1683 uno::Reference<text::XTextCursor> xRangeCursor = xStartTextRange->getText()->createTextCursorByRange( xStartTextRange );
1684 xRangeCursor->gotoRange( xEndTextRange, true );
1686 uno::Reference<beans::XPropertySet> xTextRangeProperties(xRangeCursor, uno::UNO_QUERY);
1687 if(!xTextRangeProperties.is())
1688 return ;
1690 static PropertyIds const aBorderProperties[] =
1692 PROP_LEFT_BORDER,
1693 PROP_RIGHT_BORDER,
1694 PROP_TOP_BORDER,
1695 PROP_BOTTOM_BORDER,
1696 PROP_LEFT_BORDER_DISTANCE,
1697 PROP_RIGHT_BORDER_DISTANCE,
1698 PROP_TOP_BORDER_DISTANCE,
1699 PROP_BOTTOM_BORDER_DISTANCE
1702 // The frame width specified does not include border spacing,
1703 // so the frame needs to be increased by the left/right para border spacing amount
1704 sal_Int32 nWidth = 0;
1705 sal_Int32 nIndexOfWidthProperty = -1;
1706 sal_Int16 nType = text::SizeType::FIX;
1707 for (size_t i = 0; nType == text::SizeType::FIX && i < rFrameProperties.size(); ++i)
1709 if (rFrameProperties[i].Name == "WidthType")
1710 rFrameProperties[i].Value >>= nType;
1711 else if (rFrameProperties[i].Name == "Width")
1712 nIndexOfWidthProperty = i;
1714 if (nIndexOfWidthProperty > -1 && nType == text::SizeType::FIX)
1715 rFrameProperties[nIndexOfWidthProperty].Value >>= nWidth;
1717 for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty)
1719 const OUString & sPropertyName = getPropertyName(aBorderProperties[nProperty]);
1720 beans::PropertyValue aValue;
1721 aValue.Name = sPropertyName;
1722 aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName);
1723 if( nProperty < 4 )
1725 xTextRangeProperties->setPropertyValue( sPropertyName, uno::Any(table::BorderLine2()));
1726 if (!aValue.Value.hasValue())
1727 aValue.Value <<= table::BorderLine2();
1729 else // border spacing
1731 sal_Int32 nDistance = 0;
1732 aValue.Value >>= nDistance;
1734 // left4/right5 need to be duplicated because of INVERT_BORDER_SPACING (DOCX only)
1735 // Do not duplicate the top6/bottom7 border spacing.
1736 if (nProperty > 5 || bIsRTFImport)
1737 aValue.Value <<= sal_Int32(0);
1739 // frames need to be increased by the left/right para border spacing amount
1740 // This is needed for RTF as well, but that requires other export/import fixes.
1741 if (!bIsRTFImport && nProperty < 6 && nWidth && nDistance)
1743 nWidth += nDistance;
1744 rFrameProperties[nIndexOfWidthProperty].Value <<= nWidth;
1747 if (aValue.Value.hasValue())
1748 rFrameProperties.push_back(aValue);
1751 catch( const uno::Exception& )
1757 static void lcl_AddRange(
1758 ParagraphPropertiesPtr const & pToBeSavedProperties,
1759 uno::Reference< text::XTextAppend > const& xTextAppend,
1760 TextAppendContext const & rAppendContext)
1762 uno::Reference<text::XParagraphCursor> xParaCursor(
1763 xTextAppend->createTextCursorByRange( rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()), uno::UNO_QUERY_THROW );
1764 pToBeSavedProperties->SetEndingRange(xParaCursor->getStart());
1765 xParaCursor->gotoStartOfParagraph( false );
1767 pToBeSavedProperties->SetStartingRange(xParaCursor->getStart());
1771 //define some default frame width - 0cm ATM: this allow the frame to be wrapped around the text
1772 constexpr sal_Int32 DEFAULT_FRAME_MIN_WIDTH = 0;
1773 constexpr sal_Int32 DEFAULT_FRAME_MIN_HEIGHT = 0;
1774 constexpr sal_Int32 DEFAULT_VALUE = 0;
1776 std::vector<css::beans::PropertyValue>
1777 DomainMapper_Impl::MakeFrameProperties(const ParagraphProperties& rProps)
1779 std::vector<beans::PropertyValue> aFrameProperties;
1783 // A paragraph's properties come from direct formatting or somewhere in the style hierarchy
1784 std::vector<const ParagraphProperties*> vProps;
1785 vProps.emplace_back(&rProps);
1786 sal_Int8 nSafetyLimit = 16;
1787 StyleSheetEntryPtr pStyle
1788 = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rProps.GetParaStyleName());
1789 while (nSafetyLimit-- && pStyle && pStyle->m_pProperties)
1791 vProps.emplace_back(&pStyle->m_pProperties->props());
1792 assert(pStyle->m_sBaseStyleIdentifier != pStyle->m_sStyleName);
1793 if (pStyle->m_sBaseStyleIdentifier.isEmpty())
1794 break;
1795 pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(pStyle->m_sBaseStyleIdentifier);
1797 SAL_WARN_IF(!nSafetyLimit, "writerfilter.dmapper", "Inheritance loop likely: early exit");
1800 sal_Int32 nWidth = -1;
1801 for (const auto pProp : vProps)
1803 if (pProp->Getw() < 0)
1804 continue;
1805 nWidth = pProp->Getw();
1806 break;
1808 bool bAutoWidth = nWidth < 1;
1809 if (bAutoWidth)
1810 nWidth = DEFAULT_FRAME_MIN_WIDTH;
1811 aFrameProperties.push_back(
1812 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth));
1813 aFrameProperties.push_back(
1814 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE),
1815 bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX));
1817 bool bValidH = false;
1818 sal_Int32 nHeight = DEFAULT_FRAME_MIN_HEIGHT;
1819 for (const auto pProp : vProps)
1821 if (pProp->Geth() < 0)
1822 continue;
1823 nHeight = pProp->Geth();
1824 bValidH = true;
1825 break;
1827 aFrameProperties.push_back(
1828 comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), nHeight));
1830 sal_Int16 nhRule = -1;
1831 for (const auto pProp : vProps)
1833 if (pProp->GethRule() < 0)
1834 continue;
1835 nhRule = pProp->GethRule();
1836 break;
1838 if (nhRule < 0)
1840 if (bValidH && nHeight)
1842 // [MS-OE376] Word uses a default value of "atLeast" for
1843 // this attribute when the value of the h attribute is not 0.
1844 nhRule = text::SizeType::MIN;
1846 else
1848 nhRule = text::SizeType::VARIABLE;
1851 aFrameProperties.push_back(
1852 comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule));
1854 bool bValidX = false;
1855 sal_Int32 nX = DEFAULT_VALUE;
1856 for (const auto pProp : vProps)
1858 bValidX = pProp->IsxValid();
1859 if (!bValidX)
1860 continue;
1861 nX = pProp->Getx();
1862 break;
1864 aFrameProperties.push_back(
1865 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), nX));
1867 sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
1868 for (const auto pProp : vProps)
1870 if (pProp->GetxAlign() < 0)
1871 continue;
1872 nHoriOrient = pProp->GetxAlign();
1873 break;
1875 aFrameProperties.push_back(
1876 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient));
1878 //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11
1879 sal_Int16 nHAnchor = text::RelOrientation::FRAME; // 'text'
1880 for (const auto pProp : vProps)
1882 if (pProp->GethAnchor() < 0)
1883 continue;
1884 nHAnchor = pProp->GethAnchor();
1885 break;
1887 aFrameProperties.push_back(
1888 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), nHAnchor));
1890 bool bValidY = false;
1891 sal_Int32 nY = DEFAULT_VALUE;
1892 for (const auto pProp : vProps)
1894 bValidY = pProp->IsyValid();
1895 if (!bValidY)
1896 continue;
1897 nY = pProp->Gety();
1898 break;
1900 aFrameProperties.push_back(
1901 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), nY));
1903 sal_Int16 nVertOrient = text::VertOrientation::NONE;
1904 // Testing indicates that yAlign should be ignored if there is any specified w:y
1905 if (!bValidY)
1907 for (const auto pProp : vProps)
1909 if (pProp->GetyAlign() < 0)
1910 continue;
1911 nVertOrient = pProp->GetyAlign();
1912 break;
1916 // Default the anchor in case FramePr_vAnchor is missing.
1917 // ECMA 17.3.1.11 says "page",
1918 // but errata documentation MS-OE376 2.1.48 Section 2.3.1.11 says "text"
1919 // while actual testing usually indicates "margin" tdf#157572 tdf#112287
1920 sal_Int16 nVAnchor = text::RelOrientation::PAGE_PRINT_AREA; // 'margin'
1921 if (!nY && (bValidY || nVertOrient == text::VertOrientation::NONE))
1923 // special cases? "auto" position defaults to "paragraph" based on testing when w:y=0
1924 nVAnchor = text::RelOrientation::FRAME; // 'text'
1926 for (const auto pProp : vProps)
1928 if (pProp->GetvAnchor() < 0)
1929 continue;
1930 nVAnchor = pProp->GetvAnchor();
1931 // vAlign is ignored if vAnchor is set to 'text'
1932 if (nVAnchor == text::RelOrientation::FRAME)
1933 nVertOrient = text::VertOrientation::NONE;
1934 break;
1936 aFrameProperties.push_back(
1937 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), nVAnchor));
1938 aFrameProperties.push_back(
1939 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient));
1941 text::WrapTextMode nWrap = text::WrapTextMode_NONE;
1942 for (const auto pProp : vProps)
1944 if (pProp->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
1945 continue;
1946 nWrap = pProp->GetWrap();
1947 break;
1949 aFrameProperties.push_back(
1950 comphelper::makePropertyValue(getPropertyName(PROP_SURROUND), nWrap));
1952 sal_Int32 nRightDist = 0;
1953 sal_Int32 nLeftDist = 0;
1954 for (const auto pProp : vProps)
1956 if (pProp->GethSpace() < 0)
1957 continue;
1958 nLeftDist = nRightDist = pProp->GethSpace();
1959 break;
1961 aFrameProperties.push_back(comphelper::makePropertyValue(
1962 getPropertyName(PROP_LEFT_MARGIN),
1963 nHoriOrient == text::HoriOrientation::LEFT ? 0 : nLeftDist));
1964 aFrameProperties.push_back(comphelper::makePropertyValue(
1965 getPropertyName(PROP_RIGHT_MARGIN),
1966 nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nRightDist));
1968 sal_Int32 nBottomDist = 0;
1969 sal_Int32 nTopDist = 0;
1970 for (const auto pProp : vProps)
1972 if (pProp->GetvSpace() < 0)
1973 continue;
1974 nTopDist = nBottomDist = pProp->GetvSpace();
1975 break;
1977 aFrameProperties.push_back(comphelper::makePropertyValue(
1978 getPropertyName(PROP_TOP_MARGIN),
1979 nVertOrient == text::VertOrientation::TOP ? 0 : nTopDist));
1980 aFrameProperties.push_back(comphelper::makePropertyValue(
1981 getPropertyName(PROP_BOTTOM_MARGIN),
1982 nVertOrient == text::VertOrientation::BOTTOM ? 0 : nBottomDist));
1984 catch (const uno::Exception&)
1988 return aFrameProperties;
1991 void DomainMapper_Impl::CheckUnregisteredFrameConversion(bool bPreventOverlap)
1993 if (m_aTextAppendStack.empty())
1994 return;
1995 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
1996 // n#779642: ignore fly frame inside table as it could lead to messy situations
1997 if (!rAppendContext.pLastParagraphProperties)
1998 return;
1999 if (!rAppendContext.pLastParagraphProperties->IsFrameMode())
2000 return;
2001 if (!hasTableManager())
2002 return;
2003 if (getTableManager().isInTable())
2004 return;
2006 std::vector<beans::PropertyValue> aFrameProperties
2007 = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
2009 if (const std::optional<sal_Int16> nDirection = PopFrameDirection())
2011 aFrameProperties.push_back(
2012 comphelper::makePropertyValue(getPropertyName(PROP_FRM_DIRECTION), *nDirection));
2015 if (bPreventOverlap)
2016 aFrameProperties.push_back(comphelper::makePropertyValue("AllowOverlap", uno::Any(false)));
2018 // If there is no fill, the Word default is 100% transparency.
2019 // Otherwise CellColorHandler has priority, and this setting
2020 // will be ignored.
2021 aFrameProperties.push_back(comphelper::makePropertyValue(
2022 getPropertyName(PROP_BACK_COLOR_TRANSPARENCY), sal_Int32(100)));
2024 uno::Sequence<beans::PropertyValue> aGrabBag(comphelper::InitPropertySequence(
2025 { { "ParaFrameProperties", uno::Any(true) } }));
2026 aFrameProperties.push_back(comphelper::makePropertyValue("FrameInteropGrabBag", aGrabBag));
2028 lcl_MoveBorderPropertiesToFrame(aFrameProperties,
2029 rAppendContext.pLastParagraphProperties->GetStartingRange(),
2030 rAppendContext.pLastParagraphProperties->GetEndingRange(),
2031 IsRTFImport());
2033 //frame conversion has to be executed after table conversion, not now
2034 RegisterFrameConversion(rAppendContext.pLastParagraphProperties->GetStartingRange(),
2035 rAppendContext.pLastParagraphProperties->GetEndingRange(),
2036 std::move(aFrameProperties));
2039 /// Check if the style or its parent has a list id, recursively.
2040 static sal_Int32 lcl_getListId(const StyleSheetEntryPtr& rEntry, const StyleSheetTablePtr& rStyleTable, bool & rNumberingFromBaseStyle)
2042 const StyleSheetPropertyMap* pEntryProperties = rEntry->m_pProperties.get();
2043 if (!pEntryProperties)
2044 return -1;
2046 sal_Int32 nListId = pEntryProperties->props().GetListId();
2047 // The style itself has a list id.
2048 if (nListId >= 0)
2049 return nListId;
2051 // The style has no parent.
2052 if (rEntry->m_sBaseStyleIdentifier.isEmpty())
2053 return -1;
2055 const StyleSheetEntryPtr pParent = rStyleTable->FindStyleSheetByISTD(rEntry->m_sBaseStyleIdentifier);
2056 // No such parent style or loop in the style hierarchy.
2057 if (!pParent || pParent == rEntry)
2058 return -1;
2060 rNumberingFromBaseStyle = true;
2062 return lcl_getListId(pParent, rStyleTable, rNumberingFromBaseStyle);
2065 /// Return the paragraph's list level (from styles, unless pParacontext is provided).
2066 /// -1 indicates the level is not set anywhere. [In that case, with a numId, use 0 (level 1)]
2067 /// 9 indicates that numbering should be at body level (aka disabled) - rarely used by MSWord.
2068 /// 0-8 are the nine valid numbering levels.
2069 sal_Int16 DomainMapper_Impl::GetListLevel(const StyleSheetEntryPtr& pEntry,
2070 const PropertyMapPtr& pParaContext)
2072 sal_Int16 nListLevel = -1;
2073 if (pParaContext)
2075 // Deliberately ignore inherited PROP_NUMBERING_LEVEL. Only trust StyleSheetEntry for that.
2076 std::optional<PropertyMap::Property> aLvl = pParaContext->getProperty(PROP_NUMBERING_LEVEL);
2077 if (aLvl)
2078 aLvl->second >>= nListLevel;
2080 if (nListLevel != -1)
2081 return nListLevel;
2084 if (!pEntry)
2085 return -1;
2087 const StyleSheetPropertyMap* pEntryProperties = pEntry->m_pProperties.get();
2088 if (!pEntryProperties)
2089 return -1;
2091 nListLevel = pEntryProperties->GetListLevel();
2092 // The style itself has a list level.
2093 if (nListLevel >= 0)
2094 return nListLevel;
2096 // The style has no parent.
2097 if (pEntry->m_sBaseStyleIdentifier.isEmpty())
2098 return -1;
2100 const StyleSheetEntryPtr pParent = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
2101 // No such parent style or loop in the style hierarchy.
2102 if (!pParent || pParent == pEntry)
2103 return -1;
2105 return GetListLevel(pParent);
2108 void DomainMapper_Impl::ValidateListLevel(const OUString& sStyleIdentifierD)
2110 StyleSheetEntryPtr pMyStyle = GetStyleSheetTable()->FindStyleSheetByISTD(sStyleIdentifierD);
2111 if (!pMyStyle)
2112 return;
2114 sal_Int8 nListLevel = GetListLevel(pMyStyle);
2115 if (nListLevel < 0 || nListLevel >= WW_OUTLINE_MAX)
2116 return;
2118 bool bDummy = false;
2119 sal_Int16 nListId = lcl_getListId(pMyStyle, GetStyleSheetTable(), bDummy);
2120 if (nListId < 1)
2121 return;
2123 auto const pList(GetListTable()->GetList(nListId));
2124 if (!pList)
2125 return;
2127 auto pLevel = pList->GetLevel(nListLevel);
2128 if (!pLevel && pList->GetAbstractDefinition())
2129 pLevel = pList->GetAbstractDefinition()->GetLevel(nListLevel);
2130 if (!pLevel)
2131 return;
2133 if (!pLevel->GetParaStyle())
2135 // First come, first served, and it hasn't been claimed yet, so claim it now.
2136 pLevel->SetParaStyle(pMyStyle);
2138 else if (pLevel->GetParaStyle() != pMyStyle)
2140 // This level is already used by another style, so prevent numbering via this style
2141 // by setting to body level (9).
2142 pMyStyle->m_pProperties->SetListLevel(WW_OUTLINE_MAX);
2143 // WARNING: PROP_NUMBERING_LEVEL is now out of sync with GetListLevel()
2147 void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove, const bool bNoNumbering )
2149 if (m_bDiscardHeaderFooter)
2150 return;
2152 if (!m_aFieldStack.empty())
2154 FieldContextPtr pFieldContext = m_aFieldStack.back();
2155 if (pFieldContext && !pFieldContext->IsCommandCompleted())
2157 std::vector<OUString> aCommandParts = pFieldContext->GetCommandParts();
2158 if (!aCommandParts.empty() && aCommandParts[0] == "IF")
2160 // Conditional text field conditions don't support linebreaks in Writer.
2161 return;
2165 if (pFieldContext && pFieldContext->IsCommandCompleted())
2167 if (pFieldContext->GetFieldId() == FIELD_IF)
2169 // Conditional text fields can't contain newlines, finish the paragraph later.
2170 FieldParagraph aFinish{pPropertyMap, bRemove};
2171 pFieldContext->GetParagraphsToFinish().push_back(aFinish);
2172 return;
2177 #ifdef DBG_UTIL
2178 TagLogger::getInstance().startElement("finishParagraph");
2179 #endif
2181 ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pPropertyMap.get() );
2182 if (m_aTextAppendStack.empty())
2183 return;
2184 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
2185 uno::Reference< text::XTextAppend > xTextAppend(rAppendContext.xTextAppend);
2186 #ifdef DBG_UTIL
2187 TagLogger::getInstance().attribute("isTextAppend", sal_uInt32(xTextAppend.is()));
2188 #endif
2190 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetCurrentParaStyleName() );
2191 SAL_WARN_IF(!pEntry, "writerfilter.dmapper", "no style sheet found");
2192 const StyleSheetPropertyMap* pStyleSheetProperties = pEntry ? pEntry->m_pProperties.get() : nullptr;
2193 sal_Int32 nListId = pParaContext ? pParaContext->props().GetListId() : -1;
2194 bool isNumberingViaStyle(false);
2195 bool isNumberingViaRule = nListId > -1;
2196 if ( !bRemove && pStyleSheetProperties && pParaContext )
2198 if (!pEntry || pEntry->m_nStyleTypeCode != StyleType::STYLE_TYPE_PARA) {
2199 // We could not resolve paragraph style or it is not a paragraph style
2200 // Remove this style reference, otherwise it will cause exceptions during further
2201 // processing and not all paragraph styles will be initialized.
2202 SAL_WARN("writerfilter.dmapper", "Paragraph style is incorrect. Ignored");
2203 pParaContext->Erase(PROP_PARA_STYLE_NAME);
2206 bool bNumberingFromBaseStyle = false;
2207 if (!isNumberingViaRule)
2208 nListId = lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle);
2210 //apply numbering level/style to paragraph if it was set at the style, but only if the paragraph itself
2211 //does not specify the numbering
2212 sal_Int16 nListLevel = GetListLevel(pEntry, pParaContext);
2213 // Undefined listLevel with a valid numId is treated as a first level numbering.
2214 if (nListLevel == -1 && nListId > (IsOOXMLImport() ? 0 : -1))
2215 nListLevel = 0;
2217 if (!bNoNumbering && nListLevel >= 0 && nListLevel < 9)
2218 pParaContext->Insert( PROP_NUMBERING_LEVEL, uno::Any(nListLevel), false );
2220 auto const pList(GetListTable()->GetList(nListId));
2221 if (pList && !pParaContext->isSet(PROP_NUMBERING_STYLE_NAME))
2223 // ListLevel 9 means Body Level/no numbering.
2224 if (bNoNumbering || nListLevel == 9)
2226 pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(OUString()), true);
2227 pParaContext->Erase(PROP_NUMBERING_LEVEL);
2229 else if ( !isNumberingViaRule )
2231 isNumberingViaStyle = true;
2232 // Since LO7.0/tdf#131321 fixed the loss of numbering in styles, this OUGHT to be obsolete,
2233 // but now other new/critical LO7.0 code expects it, and perhaps some corner cases still need it as well.
2234 pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true);
2236 else
2238 // we have direct numbering, as well as paragraph-style numbering.
2239 // Apply the style if it uses the same list as the direct numbering,
2240 // otherwise the directly-applied-to-paragraph status will be lost,
2241 // and the priority of the numbering-style-indents will be lowered. tdf#133000
2242 bool bDummy;
2243 if (nListId == lcl_getListId(pEntry, GetStyleSheetTable(), bDummy))
2244 pParaContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true );
2248 if ( isNumberingViaStyle )
2250 // When numbering is defined by the paragraph style, then the para-style indents have priority.
2251 // But since import has just copied para-style's PROP_NUMBERING_STYLE_NAME directly onto the paragraph,
2252 // the numbering indents now have the priority.
2253 // So now import must also copy the para-style indents directly onto the paragraph to compensate.
2254 std::optional<PropertyMap::Property> oProperty;
2255 const StyleSheetEntryPtr pParent = (!pEntry->m_sBaseStyleIdentifier.isEmpty()) ? GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier) : nullptr;
2256 const StyleSheetPropertyMap* pParentProperties = pParent ? pParent->m_pProperties.get() : nullptr;
2257 if (!pEntry->m_sBaseStyleIdentifier.isEmpty())
2259 oProperty = pStyleSheetProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT);
2260 if ( oProperty
2261 // If the numbering comes from a base style, indent of the base style has also priority.
2262 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))) )
2263 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false);
2265 oProperty = pStyleSheetProperties->getProperty(PROP_PARA_LEFT_MARGIN);
2266 if ( oProperty
2267 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_LEFT_MARGIN))) )
2268 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, oProperty->second, /*bOverwrite=*/false);
2270 // We're inheriting properties from a numbering style. Make sure a possible right margin is inherited from the base style.
2271 sal_Int32 nParaRightMargin;
2272 if ( pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_RIGHT_MARGIN)) && (nParaRightMargin = oProperty->second.get<sal_Int32>()) != 0 )
2274 // If we're setting the right margin, we should set the first / left margin as well from the numbering style.
2275 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent");
2276 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt");
2277 if (nFirstLineIndent != 0)
2278 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false);
2279 if (nParaLeftMargin != 0)
2280 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false);
2282 // Override right margin value with value from current style, if any
2283 if (pStyleSheetProperties && pStyleSheetProperties->isSet(PROP_PARA_RIGHT_MARGIN))
2284 nParaRightMargin = pStyleSheetProperties->getProperty(PROP_PARA_RIGHT_MARGIN)->second.get<sal_Int32>();
2286 pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, uno::Any(nParaRightMargin), /*bOverwrite=*/false);
2289 // Paragraph style based right paragraph indentation affects not paragraph style based lists in DOCX.
2290 // Apply it as direct formatting, also left and first line indentation of numbering to keep them.
2291 else if (isNumberingViaRule)
2293 uno::Any aRightMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
2294 if ( aRightMargin != uno::Any() )
2296 pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, aRightMargin, /*bOverwrite=*/false);
2298 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent");
2299 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt");
2300 if (nFirstLineIndent != 0)
2301 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false);
2302 if (nParaLeftMargin != 0)
2303 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false);
2307 if (nListId == 0 && !pList)
2309 // listid = 0 and no list definition is used in DOCX to stop numbering
2310 // defined somewhere in parent styles
2311 // And here we should explicitly set left margin and first-line margin.
2312 // They can be taken from referred style, but not from styles with listid!
2313 uno::Any aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_FIRST_LINE_INDENT, pEntry, m_pStyleSheetTable);
2314 if (aProp.hasValue())
2315 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, aProp, false);
2316 else
2317 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)), false);
2319 aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_LEFT_MARGIN, pEntry, m_pStyleSheetTable);
2320 if (aProp.hasValue())
2321 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, aProp, false);
2322 else
2323 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(sal_uInt32(0)), false);
2327 // apply AutoSpacing: it has priority over all other margin settings
2328 // (note that numbering with autoSpacing is handled separately later on)
2329 const bool bAllowAdjustments = !GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing();
2330 sal_Int32 nBeforeAutospacing = -1;
2331 bool bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
2332 const bool bNoTopmargin = pParaContext && !pParaContext->isSet(PROP_PARA_TOP_MARGIN);
2333 // apply INHERITED autospacing only if top margin is not set
2334 if ( bIsAutoSet || bNoTopmargin )
2336 GetAnyProperty(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, pPropertyMap) >>= nBeforeAutospacing;
2337 // tdf#137655 only w:beforeAutospacing=0 was specified, but not PARA_TOP_MARGIN
2338 // (see default_spacing = -1 in processing of LN_CT_Spacing_beforeAutospacing)
2339 if ( bNoTopmargin && nBeforeAutospacing == ConversionHelper::convertTwipToMM100(-1) )
2341 sal_Int32 nStyleAuto = -1;
2342 GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING) >>= nStyleAuto;
2343 if (nStyleAuto > 0)
2344 nBeforeAutospacing = 0;
2347 if ( nBeforeAutospacing > -1 && pParaContext )
2349 if (bAllowAdjustments)
2351 if ( GetIsFirstParagraphInShape() ||
2352 (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) ||
2353 (m_StreamStateStack.top().bFirstParagraphInCell
2354 && 0 < m_StreamStateStack.top().nTableDepth
2355 && m_StreamStateStack.top().nTableDepth == m_StreamStateStack.top().nTableCellDepth))
2357 // export requires grabbag to match top_margin, so keep them in sync
2358 if (nBeforeAutospacing && bIsAutoSet)
2359 pParaContext->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::Any( sal_Int32(0) ),true, PARA_GRAB_BAG );
2360 nBeforeAutospacing = 0;
2363 pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(nBeforeAutospacing));
2366 sal_Int32 nAfterAutospacing = -1;
2367 bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING);
2368 const bool bNoBottomMargin = pParaContext && !pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN);
2369 bool bAppliedBottomAutospacing = false;
2370 if (bIsAutoSet || bNoBottomMargin)
2372 GetAnyProperty(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, pPropertyMap) >>= nAfterAutospacing;
2373 if (bNoBottomMargin && nAfterAutospacing == ConversionHelper::convertTwipToMM100(-1))
2375 sal_Int32 nStyleAuto = -1;
2376 GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING) >>= nStyleAuto;
2377 if (nStyleAuto > 0)
2378 nAfterAutospacing = 0;
2381 if ( nAfterAutospacing > -1 && pParaContext )
2383 pParaContext->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(nAfterAutospacing));
2384 bAppliedBottomAutospacing = bAllowAdjustments;
2387 // tell TableManager to reset the bottom margin if it determines that this is the cell's last paragraph.
2388 if ( hasTableManager() && getTableManager().isInCell() )
2389 getTableManager().setCellLastParaAfterAutospacing(bAppliedBottomAutospacing);
2391 if (xTextAppend.is() && pParaContext && hasTableManager() && !getTableManager().isIgnore())
2395 /*the following combinations of previous and current frame settings can occur:
2396 (1) - no old frame and no current frame -> no special action
2397 (2) - no old frame and current DropCap -> save DropCap for later use, don't call finishParagraph
2398 remove character properties of the DropCap?
2399 (3) - no old frame and current Frame -> save Frame for later use
2400 (4) - old DropCap and no current frame -> add DropCap to the properties of the finished paragraph, delete previous setting
2401 (5) - old DropCap and current frame -> add DropCap to the properties of the finished paragraph, save current frame settings
2402 (6) - old Frame and new DropCap -> add old Frame, save DropCap for later use
2403 (7) - old Frame and new same Frame -> continue
2404 (8) - old Frame and new different Frame -> add old Frame, save new Frame for later use
2405 (9) - old Frame and no current frame -> add old Frame, delete previous settings
2407 old _and_ new DropCap must not occur
2410 // The paragraph style is vital to knowing all the frame properties.
2411 std::optional<PropertyMap::Property> aParaStyle
2412 = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME);
2413 if (aParaStyle)
2415 OUString sName;
2416 aParaStyle->second >>= sName;
2417 pParaContext->props().SetParaStyleName(sName);
2420 bool bIsDropCap =
2421 pParaContext->props().IsFrameMode() &&
2422 sal::static_int_cast<Id>(pParaContext->props().GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none;
2424 style::DropCapFormat aDrop;
2425 ParagraphPropertiesPtr pToBeSavedProperties;
2426 bool bKeepLastParagraphProperties = false;
2427 if( bIsDropCap )
2429 uno::Reference<text::XParagraphCursor> xParaCursor(
2430 xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
2431 //select paragraph
2432 xParaCursor->gotoStartOfParagraph( true );
2433 uno::Reference< beans::XPropertyState > xParaProperties( xParaCursor, uno::UNO_QUERY_THROW );
2434 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT));
2435 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT));
2436 //handles (2) and part of (6)
2437 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2438 sal_Int32 nCount = xParaCursor->getString().getLength();
2439 pToBeSavedProperties->SetDropCapLength(nCount > 0 && nCount < 255 ? static_cast<sal_Int8>(nCount) : 1);
2441 if( rAppendContext.pLastParagraphProperties )
2443 if( sal::static_int_cast<Id>(rAppendContext.pLastParagraphProperties->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none)
2445 //handles (4) and part of (5)
2446 //create a DropCap property, add it to the property sequence of finishParagraph
2447 sal_Int32 nLines = rAppendContext.pLastParagraphProperties->GetLines();
2448 aDrop.Lines = nLines > 0 && nLines < SAL_MAX_INT8 ? static_cast<sal_Int8>(nLines) : 2;
2449 aDrop.Count = rAppendContext.pLastParagraphProperties->GetDropCapLength();
2450 sal_Int32 nHSpace = rAppendContext.pLastParagraphProperties->GethSpace();
2451 aDrop.Distance = nHSpace > 0 && nHSpace < SAL_MAX_INT16 ? static_cast<sal_Int16>(nHSpace) : 0;
2452 //completes (5)
2453 if( pParaContext->props().IsFrameMode() )
2454 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2456 else
2458 const bool bIsFrameMode(pParaContext->props().IsFrameMode());
2459 std::vector<beans::PropertyValue> aCurrFrameProperties;
2460 std::vector<beans::PropertyValue> aPrevFrameProperties;
2461 if (bIsFrameMode)
2463 aCurrFrameProperties = MakeFrameProperties(pParaContext->props());
2464 aPrevFrameProperties
2465 = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
2468 if (bIsFrameMode && aPrevFrameProperties == aCurrFrameProperties)
2470 //handles (7)
2471 rAppendContext.pLastParagraphProperties->SetEndingRange(
2472 rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition
2473 : xTextAppend->getEnd());
2474 bKeepLastParagraphProperties = true;
2476 else
2478 // handles (8)(9) and completes (6)
2480 // RTF has an \overlap flag (which we ignore so far)
2481 // but DOCX has nothing like that for framePr
2482 // Always allow overlap in the RTF case - so there can be no regression.
2484 // In MSO UI, there is no setting for AllowOverlap for this kind of frame.
2485 // Although they CAN overlap with other anchored things,
2486 // they do not _easily_ overlap with other framePr's,
2487 // so when one frame follows another (8), don't let the first be overlapped.
2488 bool bPreventOverlap = !IsRTFImport() && bIsFrameMode && !bIsDropCap;
2490 // Preventing overlap is emulation - so deny overlap as little as possible.
2491 sal_Int16 nVertOrient = text::VertOrientation::NONE;
2492 sal_Int16 nVertOrientRelation = text::RelOrientation::FRAME;
2493 sal_Int32 nCurrVertPos = 0;
2494 sal_Int32 nPrevVertPos = 0;
2495 for (size_t i = 0; bPreventOverlap && i < aCurrFrameProperties.size(); ++i)
2497 if (aCurrFrameProperties[i].Name == "VertOrientRelation")
2499 aCurrFrameProperties[i].Value >>= nVertOrientRelation;
2500 if (nVertOrientRelation != text::RelOrientation::FRAME)
2501 bPreventOverlap = false;
2503 else if (aCurrFrameProperties[i].Name == "VertOrient")
2505 aCurrFrameProperties[i].Value >>= nVertOrient;
2506 if (nVertOrient != text::VertOrientation::NONE)
2507 bPreventOverlap = false;
2509 else if (aCurrFrameProperties[i].Name == "VertOrientPosition")
2511 aCurrFrameProperties[i].Value >>= nCurrVertPos;
2512 // arbitrary value. Assume it must be less than 1st line height
2513 if (nCurrVertPos > 20 || nCurrVertPos < -20)
2514 bPreventOverlap = false;
2517 for (size_t i = 0; bPreventOverlap && i < aPrevFrameProperties.size(); ++i)
2519 if (aPrevFrameProperties[i].Name == "VertOrientRelation")
2521 aPrevFrameProperties[i].Value >>= nVertOrientRelation;
2522 if (nVertOrientRelation != text::RelOrientation::FRAME)
2523 bPreventOverlap = false;
2525 else if (aPrevFrameProperties[i].Name == "VertOrient")
2527 aPrevFrameProperties[i].Value >>= nVertOrient;
2528 if (nVertOrient != text::VertOrientation::NONE)
2529 bPreventOverlap = false;
2531 else if (aPrevFrameProperties[i].Name == "VertOrientPosition")
2533 aPrevFrameProperties[i].Value >>= nPrevVertPos;
2534 if (nPrevVertPos != nCurrVertPos)
2535 bPreventOverlap = false;
2539 CheckUnregisteredFrameConversion(bPreventOverlap);
2541 // If different frame properties are set on this paragraph, keep them.
2542 if (!bIsDropCap && bIsFrameMode)
2544 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2545 lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
2550 else
2552 // (1) doesn't need handling
2554 if( !bIsDropCap && pParaContext->props().IsFrameMode() )
2556 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2557 lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
2560 applyToggleAttributes(pPropertyMap); // for paragraph marker formatting
2561 std::vector<beans::PropertyValue> aProperties;
2562 if (pPropertyMap)
2564 aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues());
2566 // tdf#64222 filter out the "paragraph marker" formatting and
2567 // set it as a separate paragraph property, not a empty hint at
2568 // end of paragraph
2569 std::vector<beans::NamedValue> charProperties;
2570 for (auto it = aProperties.begin(); it != aProperties.end(); )
2572 // this condition isn't ideal but as it happens all
2573 // RES_CHRATR_* have names that start with "Char"
2574 if (it->Name.startsWith("Char"))
2576 charProperties.emplace_back(it->Name, it->Value);
2577 // as testN793262 demonstrates, font size in rPr must
2578 // affect the paragraph size => also insert empty hint!
2579 // it = aProperties.erase(it);
2581 ++it;
2583 if (!charProperties.empty())
2585 aProperties.push_back(beans::PropertyValue("ListAutoFormat",
2586 0, uno::Any(comphelper::containerToSequence(charProperties)), beans::PropertyState_DIRECT_VALUE));
2589 if( !bIsDropCap )
2591 if( aDrop.Lines > 1 )
2593 beans::PropertyValue aValue;
2594 aValue.Name = getPropertyName(PROP_DROP_CAP_FORMAT);
2595 aValue.Value <<= aDrop;
2596 aProperties.push_back(aValue);
2598 uno::Reference< text::XTextRange > xTextRange;
2599 if (rAppendContext.xInsertPosition.is())
2601 xTextRange = xTextAppend->finishParagraphInsert( comphelper::containerToSequence(aProperties), rAppendContext.xInsertPosition );
2602 rAppendContext.xCursor->gotoNextParagraph(false);
2603 if (rAppendContext.pLastParagraphProperties)
2604 rAppendContext.pLastParagraphProperties->SetEndingRange(xTextRange->getEnd());
2606 else
2608 uno::Reference<text::XTextCursor> xCursor;
2609 if (m_StreamStateStack.top().bParaHadField
2610 && !IsInComments() && !m_xTOCMarkerCursor.is())
2612 // Workaround to make sure char props of the field are not lost.
2613 // Not relevant for editeng-based comments.
2614 // Not relevant for fields inside a TOC field.
2615 xCursor = xTextAppend->getText()->createTextCursor();
2616 if (xCursor.is())
2617 xCursor->gotoEnd(false);
2618 PropertyMapPtr pEmpty(new PropertyMap());
2619 appendTextPortion("X", pEmpty);
2622 // Check if top / bottom margin has to be updated, now that we know the numbering status of both the previous and
2623 // the current text node.
2624 auto itNumberingRules = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2626 return rValue.Name == "NumberingRules";
2629 assert( isNumberingViaRule == (itNumberingRules != aProperties.end()) );
2630 isNumberingViaRule = (itNumberingRules != aProperties.end());
2631 if (m_StreamStateStack.top().xPreviousParagraph.is()
2632 && (isNumberingViaRule || isNumberingViaStyle))
2634 // This textnode has numbering. Look up the numbering style name of the current and previous paragraph.
2635 OUString aCurrentNumberingName;
2636 OUString aPreviousNumberingName;
2637 if (isNumberingViaRule)
2639 assert(itNumberingRules != aProperties.end() && "by definition itNumberingRules is valid if isNumberingViaRule is true");
2640 uno::Reference<container::XNamed> xCurrentNumberingRules(itNumberingRules->Value, uno::UNO_QUERY);
2641 if (xCurrentNumberingRules.is())
2642 aCurrentNumberingName = xCurrentNumberingRules->getName();
2645 uno::Reference<container::XNamed> xPreviousNumberingRules(
2646 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("NumberingRules"),
2647 uno::UNO_QUERY_THROW);
2648 aPreviousNumberingName = xPreviousNumberingRules->getName();
2650 catch (const uno::Exception&)
2652 TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::finishParagraph NumberingRules");
2655 else if (m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName")
2656 // don't update before tables
2657 && (m_StreamStateStack.top().nTableDepth == 0
2658 || !m_StreamStateStack.top().bFirstParagraphInCell))
2660 aCurrentNumberingName = GetListStyleName(nListId);
2661 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName;
2664 // tdf#133363: remove extra auto space even for mixed list styles
2665 if (!aPreviousNumberingName.isEmpty()
2666 && (aCurrentNumberingName == aPreviousNumberingName
2667 || !isNumberingViaRule))
2669 uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq;
2670 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("ParaInteropGrabBag") >>= aPrevPropertiesSeq;
2671 const auto & rPrevProperties = aPrevPropertiesSeq;
2672 bool bParaAutoBefore = m_StreamStateStack.top().bParaAutoBefore
2673 || std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue)
2675 return rValue.Name == "ParaTopMarginBeforeAutoSpacing";
2677 // if style based spacing was set to auto in the previous paragraph, style of the actual paragraph must be the same
2678 if (bParaAutoBefore && !m_StreamStateStack.top().bParaAutoBefore
2679 && m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ParaStyleName"))
2681 auto itParaStyle = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2683 return rValue.Name == "ParaStyleName";
2685 bParaAutoBefore = itParaStyle != aProperties.end() &&
2686 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("ParaStyleName") == itParaStyle->Value;
2688 // There was a previous textnode and it had the same numbering.
2689 if (bParaAutoBefore)
2691 // This before spacing is set to auto, set before space to 0.
2692 auto itParaTopMargin = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2694 return rValue.Name == "ParaTopMargin";
2696 if (itParaTopMargin != aProperties.end())
2697 itParaTopMargin->Value <<= static_cast<sal_Int32>(0);
2698 else
2699 aProperties.push_back(comphelper::makePropertyValue("ParaTopMargin", static_cast<sal_Int32>(0)));
2702 bool bPrevParaAutoAfter = std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue)
2704 return rValue.Name == "ParaBottomMarginAfterAutoSpacing";
2706 if (bPrevParaAutoAfter)
2708 // Previous after spacing is set to auto, set previous after space to 0.
2709 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0)));
2714 // apply redlines for inline images
2715 if (IsParaWithInlineObject())
2717 for (const auto& rAnchored : rAppendContext.m_aAnchoredObjects)
2719 // process only inline objects with redlining
2720 if (!rAnchored.m_xRedlineForInline)
2721 continue;
2723 // select the inline image and set its redline
2724 auto xAnchorRange = rAnchored.m_xAnchoredObject->getAnchor();
2725 uno::Reference< text::XTextCursor > xCursorOnImage =
2726 xAnchorRange->getText()->createTextCursorByRange(xAnchorRange);
2727 xCursorOnImage->goRight(1, true);
2728 CreateRedline( xCursorOnImage, rAnchored.m_xRedlineForInline );
2732 xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
2733 m_StreamStateStack.top().xPreviousParagraph.set(xTextRange, uno::UNO_QUERY);
2735 if (m_StreamStateStack.top().xPreviousParagraph.is() && // null for SvxUnoTextBase
2736 (isNumberingViaStyle || isNumberingViaRule))
2738 assert(pParaContext);
2739 if (ListDef::Pointer const& pList = m_pListTable->GetList(nListId))
2740 { // styles could refer to non-existing lists...
2741 AbstractListDef::Pointer const& pAbsList =
2742 pList->GetAbstractDefinition();
2743 if (pAbsList &&
2744 // SvxUnoTextRange doesn't have ListId
2745 m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ListId"))
2747 OUString paraId;
2748 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("ListId") >>= paraId;
2749 if (!paraId.isEmpty()) // must be on some list?
2751 OUString const listId = pAbsList->MapListId(paraId);
2752 if (listId != paraId)
2754 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue("ListId", uno::Any(listId));
2759 sal_Int16 nCurrentLevel = GetListLevel(pEntry, pPropertyMap);
2760 if (nCurrentLevel == -1)
2761 nCurrentLevel = 0;
2763 const ListLevel::Pointer pListLevel = pList->GetLevel(nCurrentLevel);
2764 if (pListLevel)
2766 sal_Int16 nOverrideLevel = pListLevel->GetStartOverride();
2767 if (nOverrideLevel != -1 && m_aListOverrideApplied.find(nListId) == m_aListOverrideApplied.end())
2769 // Apply override: we have override instruction for this level
2770 // And this was not done for this list before: we can do this only once on first occurrence
2771 // of list with override
2772 // TODO: Not tested variant with different levels override in different lists.
2773 // Probably m_aListOverrideApplied as a set of overridden listids is not sufficient
2774 // and we need to register level overrides separately.
2775 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue("ParaIsNumberingRestart", uno::Any(true));
2776 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue("NumberingStartValue", uno::Any(nOverrideLevel));
2777 m_aListOverrideApplied.insert(nListId);
2783 if (!rAppendContext.m_aAnchoredObjects.empty() && !IsInHeaderFooter())
2785 // Remember what objects are anchored to this paragraph.
2786 // That list is only used for Word compat purposes, and
2787 // it is only relevant for body text.
2788 AnchoredObjectsInfo aInfo;
2789 aInfo.m_xParagraph = xTextRange;
2790 aInfo.m_aAnchoredObjects = rAppendContext.m_aAnchoredObjects;
2791 m_aAnchoredObjectAnchors.push_back(aInfo);
2792 rAppendContext.m_aAnchoredObjects.clear();
2795 if (xCursor.is())
2797 xCursor->goLeft(1, true);
2798 xCursor->setString(OUString());
2801 getTableManager( ).handle(xTextRange);
2802 m_aSmartTagHandler.handle(xTextRange);
2804 if (xTextRange.is())
2806 // Get the end of paragraph character inserted
2807 uno::Reference< text::XTextCursor > xCur = xTextRange->getText( )->createTextCursor( );
2808 if (rAppendContext.xInsertPosition.is())
2809 xCur->gotoRange( rAppendContext.xInsertPosition, false );
2810 else
2811 xCur->gotoEnd( false );
2813 // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode
2814 sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode();
2815 if (0 < m_StreamStateStack.top().nTableDepth && 0 < nMode && nMode <= 14)
2817 // skip new line
2818 xCur->goLeft(1, false);
2819 while ( xCur->goLeft(1, true) )
2821 OUString sChar = xCur->getString();
2822 if ( sChar == " " || sChar == "\t" || sChar == OUStringChar(u'\x00A0') )
2823 xCur->setString("");
2824 else
2825 break;
2828 if (rAppendContext.xInsertPosition.is())
2829 xCur->gotoRange(rAppendContext.xInsertPosition, false);
2830 else
2831 xCur->gotoEnd(false);
2834 xCur->goLeft( 1 , true );
2835 // Extend the redline ranges for empty paragraphs
2836 if (!m_StreamStateStack.top().bParaChanged && m_previousRedline)
2837 CreateRedline( xCur, m_previousRedline );
2838 CheckParaMarkerRedline( xCur );
2841 css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY);
2843 // table style precedence and not hidden shapes anchored to hidden empty table paragraphs
2844 if (xParaProps && !IsInComments()
2845 && (0 < m_StreamStateStack.top().nTableDepth
2846 || !m_aAnchoredObjectAnchors.empty()))
2848 // table style has got bigger precedence than docDefault style
2849 // collect these pending paragraph properties to process in endTable()
2850 uno::Reference<text::XTextCursor> xCur = xTextRange->getText( )->createTextCursor( );
2851 xCur->gotoEnd(false);
2852 xCur->goLeft(1, false);
2853 uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur);
2854 uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW);
2855 xParaCursor->gotoStartOfParagraph(false);
2856 if (0 < m_StreamStateStack.top().nTableDepth)
2858 TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps};
2859 getTableManager().getCurrentParagraphs()->push_back(aPending);
2862 // hidden empty paragraph with a not hidden shape, set as not hidden
2863 std::optional<PropertyMap::Property> pHidden;
2864 if ( !m_aAnchoredObjectAnchors.empty() && (pHidden = pParaContext->getProperty(PROP_CHAR_HIDDEN)) )
2866 bool bIsHidden = {}; // -Werror=maybe-uninitialized
2867 pHidden->second >>= bIsHidden;
2868 if (bIsHidden)
2870 bIsHidden = false;
2871 pHidden = GetTopContext()->getProperty(PROP_CHAR_HIDDEN);
2872 if (pHidden)
2873 pHidden->second >>= bIsHidden;
2874 if (!bIsHidden)
2876 uno::Reference<text::XTextCursor> xCur3 = xTextRange->getText()->createTextCursorByRange(xParaCursor);
2877 xCur3->goRight(1, true);
2878 if (xCur3->getString() == SAL_NEWLINE_STRING)
2880 uno::Reference< beans::XPropertySet > xProp( xCur3, uno::UNO_QUERY );
2881 xProp->setPropertyValue(getPropertyName(PROP_CHAR_HIDDEN), uno::Any(false));
2888 // tdf#118521 set paragraph top or bottom margin based on the paragraph style
2889 // if we already set the other margin with direct formatting
2890 if (xParaProps)
2892 const bool bTopSet = pParaContext->isSet(PROP_PARA_TOP_MARGIN);
2893 const bool bBottomSet = pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN);
2894 const bool bContextSet = pParaContext->isSet(PROP_PARA_CONTEXT_MARGIN);
2895 if ( bTopSet != bBottomSet || bBottomSet != bContextSet )
2898 if ( !bTopSet )
2900 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN);
2901 if ( aMargin != uno::Any() )
2902 xParaProps->setPropertyValue("ParaTopMargin", aMargin);
2904 if ( !bBottomSet )
2906 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN);
2907 if ( aMargin != uno::Any() )
2908 xParaProps->setPropertyValue("ParaBottomMargin", aMargin);
2910 if ( !bContextSet )
2912 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN);
2913 if ( aMargin != uno::Any() )
2914 xParaProps->setPropertyValue("ParaContextMargin", aMargin);
2919 // Left, Right, and Hanging settings are also grouped. Ensure that all or none are set.
2920 if (xParaProps)
2922 const bool bLeftSet = pParaContext->isSet(PROP_PARA_LEFT_MARGIN);
2923 const bool bRightSet = pParaContext->isSet(PROP_PARA_RIGHT_MARGIN);
2924 const bool bFirstSet = pParaContext->isSet(PROP_PARA_FIRST_LINE_INDENT);
2925 if (bLeftSet != bRightSet || bRightSet != bFirstSet)
2927 if ( !bLeftSet )
2929 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_LEFT_MARGIN);
2930 if ( aMargin != uno::Any() )
2931 xParaProps->setPropertyValue("ParaLeftMargin", aMargin);
2932 else if (isNumberingViaStyle)
2934 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "IndentAt");
2935 if (nParaLeftMargin != 0)
2936 xParaProps->setPropertyValue("ParaLeftMargin", uno::Any(nParaLeftMargin));
2939 if ( !bRightSet )
2941 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
2942 if ( aMargin != uno::Any() )
2943 xParaProps->setPropertyValue("ParaRightMargin", aMargin);
2945 if ( !bFirstSet )
2947 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT);
2948 if ( aMargin != uno::Any() )
2949 xParaProps->setPropertyValue("ParaFirstLineIndent", aMargin);
2950 else if (isNumberingViaStyle)
2952 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "FirstLineIndent");
2953 if (nFirstLineIndent != 0)
2954 xParaProps->setPropertyValue("ParaFirstLineIndent", uno::Any(nFirstLineIndent));
2960 if( !bKeepLastParagraphProperties )
2961 rAppendContext.pLastParagraphProperties = pToBeSavedProperties;
2963 catch(const lang::IllegalArgumentException&)
2965 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::finishParagraph" );
2967 catch(const uno::Exception&)
2969 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "finishParagraph()" );
2974 bool bIgnoreFrameState = IsInHeaderFooter();
2975 if( (!bIgnoreFrameState && pParaContext && pParaContext->props().IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) )
2976 SetIsPreviousParagraphFramed(true);
2977 else
2978 SetIsPreviousParagraphFramed(false);
2980 m_StreamStateStack.top().bRemoveThisParagraph = false;
2981 if( !IsInHeaderFooter() && !IsInShape()
2982 && (!pParaContext || !pParaContext->props().IsFrameMode()) )
2983 { // If the paragraph is in a frame, shape or header/footer, it's not a paragraph of the section itself.
2984 SetIsFirstParagraphInSection(false);
2985 // don't count an empty deleted paragraph as first paragraph in section to avoid of
2986 // the deletion of the next empty paragraph later, resulting loss of the associated page break
2987 if (!m_previousRedline || m_StreamStateStack.top().bParaChanged)
2989 SetIsFirstParagraphInSectionAfterRedline(false);
2990 SetIsLastParagraphInSection(false);
2993 m_previousRedline.clear();
2994 m_StreamStateStack.top().bParaChanged = false;
2996 if (IsInComments() && pParaContext)
2998 if (const OUString sParaId = pParaContext->props().GetParaId(); !sParaId.isEmpty())
3000 if (const auto& item = m_aCommentProps.find(sParaId); item != m_aCommentProps.end())
3002 m_bAnnotationResolved = item->second.bDone;
3003 m_sAnnotationParent = item->second.sParaIdParent;
3005 m_sAnnotationImportedParaId = sParaId;
3009 if (m_StreamStateStack.top().bIsFirstParaInShape)
3010 m_StreamStateStack.top().bIsFirstParaInShape = false;
3012 if (pParaContext)
3014 // Reset the frame properties for the next paragraph
3015 pParaContext->props().ResetFrameProperties();
3018 SetIsOutsideAParagraph(true);
3019 m_StreamStateStack.top().bParaHadField = false;
3021 // don't overwrite m_bFirstParagraphInCell in table separator nodes
3022 // and in text boxes anchored to the first paragraph of table cells
3023 if (0 < m_StreamStateStack.top().nTableDepth
3024 && m_StreamStateStack.top().nTableDepth == m_StreamStateStack.top().nTableCellDepth
3025 && !IsInShape() && !IsInComments())
3027 m_StreamStateStack.top().bFirstParagraphInCell = false;
3030 m_StreamStateStack.top().bParaAutoBefore = false;
3031 m_StreamStateStack.top().bParaWithInlineObject = false;
3033 #ifdef DBG_UTIL
3034 TagLogger::getInstance().endElement();
3035 #endif
3039 // TODO this does not yet take table styles into account
3040 void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap)
3042 std::optional<PropertyMap::Property> charStyleProperty = pPropertyMap->getProperty(PROP_CHAR_STYLE_NAME);
3043 if (charStyleProperty.has_value())
3045 OUString sCharStyleName;
3046 charStyleProperty->second >>= sCharStyleName;
3047 float fCharStyleBold = css::awt::FontWeight::NORMAL;
3048 float fCharStyleBoldComplex = css::awt::FontWeight::NORMAL;
3049 css::awt::FontSlant eCharStylePosture = css::awt::FontSlant_NONE;
3050 css::awt::FontSlant eCharStylePostureComplex = css::awt::FontSlant_NONE;
3051 sal_Int16 nCharStyleCaseMap = css::style::CaseMap::NONE;
3052 sal_Int16 nCharStyleRelief = css::awt::FontRelief::NONE;
3053 bool bCharStyleContoured = false;//Outline;
3054 bool bCharStyleShadowed = false;
3055 sal_Int16 nCharStyleStrikeThrough = awt::FontStrikeout::NONE;
3056 bool bCharStyleHidden = false;
3058 uno::Reference<beans::XPropertySet> xCharStylePropertySet = GetCharacterStyles()->getByName(sCharStyleName).get<uno::Reference<beans::XPropertySet>>();
3059 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT)) >>= fCharStyleBold;
3060 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT_COMPLEX)) >>= fCharStyleBoldComplex;
3061 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE)) >>= eCharStylePosture;
3062 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE_COMPLEX)) >>= eCharStylePostureComplex;
3063 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CASE_MAP)) >>= nCharStyleCaseMap;
3064 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_RELIEF)) >>= nCharStyleRelief;
3065 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CONTOURED)) >>= bCharStyleContoured;
3066 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_SHADOWED)) >>= bCharStyleShadowed;
3067 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_STRIKEOUT)) >>= nCharStyleStrikeThrough;
3068 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_HIDDEN)) >>= bCharStyleHidden;
3069 if (fCharStyleBold > css::awt::FontWeight::NORMAL || eCharStylePosture != css::awt::FontSlant_NONE|| nCharStyleCaseMap != css::style::CaseMap::NONE ||
3070 nCharStyleRelief != css::awt::FontRelief::NONE || bCharStyleContoured || bCharStyleShadowed ||
3071 nCharStyleStrikeThrough == awt::FontStrikeout::SINGLE || bCharStyleHidden)
3073 uno::Reference<beans::XPropertySet> const xParaStylePropertySet =
3074 GetParagraphStyles()->getByName(m_StreamStateStack.top().sCurrentParaStyleName).get<uno::Reference<beans::XPropertySet>>();
3075 float fParaStyleBold = css::awt::FontWeight::NORMAL;
3076 float fParaStyleBoldComplex = css::awt::FontWeight::NORMAL;
3077 css::awt::FontSlant eParaStylePosture = css::awt::FontSlant_NONE;
3078 css::awt::FontSlant eParaStylePostureComplex = css::awt::FontSlant_NONE;
3079 sal_Int16 nParaStyleCaseMap = css::style::CaseMap::NONE;
3080 sal_Int16 nParaStyleRelief = css::awt::FontRelief::NONE;
3081 bool bParaStyleContoured = false;
3082 bool bParaStyleShadowed = false;
3083 sal_Int16 nParaStyleStrikeThrough = awt::FontStrikeout::NONE;
3084 bool bParaStyleHidden = false;
3085 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT)) >>= fParaStyleBold;
3086 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT_COMPLEX)) >>= fParaStyleBoldComplex;
3087 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE)) >>= eParaStylePosture;
3088 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE_COMPLEX)) >>= eParaStylePostureComplex;
3089 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CASE_MAP)) >>= nParaStyleCaseMap;
3090 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_RELIEF)) >>= nParaStyleRelief;
3091 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_SHADOWED)) >>= bParaStyleShadowed;
3092 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CONTOURED)) >>= bParaStyleContoured;
3093 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_STRIKEOUT)) >>= nParaStyleStrikeThrough;
3094 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_HIDDEN)) >>= bParaStyleHidden;
3095 if (fCharStyleBold > css::awt::FontWeight::NORMAL && fParaStyleBold > css::awt::FontWeight::NORMAL)
3097 std::optional<PropertyMap::Property> charBoldProperty = pPropertyMap->getProperty(PROP_CHAR_WEIGHT);
3098 if (!charBoldProperty.has_value())
3100 pPropertyMap->Insert(PROP_CHAR_WEIGHT, uno::Any(css::awt::FontWeight::NORMAL));
3103 if (fCharStyleBoldComplex > css::awt::FontWeight::NORMAL && fParaStyleBoldComplex > css::awt::FontWeight::NORMAL)
3105 std::optional<PropertyMap::Property> charBoldPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_WEIGHT_COMPLEX);
3106 if (!charBoldPropertyComplex.has_value())
3108 pPropertyMap->Insert(PROP_CHAR_WEIGHT_COMPLEX, uno::Any(css::awt::FontWeight::NORMAL));
3109 pPropertyMap->Insert(PROP_CHAR_WEIGHT_ASIAN, uno::Any(css::awt::FontWeight::NORMAL));
3112 if (eCharStylePosture != css::awt::FontSlant_NONE && eParaStylePosture != css::awt::FontSlant_NONE)
3114 std::optional<PropertyMap::Property> charItalicProperty = pPropertyMap->getProperty(PROP_CHAR_POSTURE);
3115 if (!charItalicProperty.has_value())
3117 pPropertyMap->Insert(PROP_CHAR_POSTURE, uno::Any(css::awt::FontSlant_NONE));
3120 if (eCharStylePostureComplex != css::awt::FontSlant_NONE && eParaStylePostureComplex != css::awt::FontSlant_NONE)
3122 std::optional<PropertyMap::Property> charItalicPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_POSTURE_COMPLEX);
3123 if (!charItalicPropertyComplex.has_value())
3125 pPropertyMap->Insert(PROP_CHAR_POSTURE_COMPLEX, uno::Any(css::awt::FontSlant_NONE));
3126 pPropertyMap->Insert(PROP_CHAR_POSTURE_ASIAN, uno::Any(css::awt::FontSlant_NONE));
3129 if (nCharStyleCaseMap == nParaStyleCaseMap && nCharStyleCaseMap != css::style::CaseMap::NONE)
3131 std::optional<PropertyMap::Property> charCaseMap = pPropertyMap->getProperty(PROP_CHAR_CASE_MAP);
3132 if (!charCaseMap.has_value())
3134 pPropertyMap->Insert(PROP_CHAR_CASE_MAP, uno::Any(css::style::CaseMap::NONE));
3137 if (nParaStyleRelief != css::awt::FontRelief::NONE && nCharStyleRelief == nParaStyleRelief)
3139 std::optional<PropertyMap::Property> charRelief = pPropertyMap->getProperty(PROP_CHAR_RELIEF);
3140 if (!charRelief.has_value())
3142 pPropertyMap->Insert(PROP_CHAR_RELIEF, uno::Any(css::awt::FontRelief::NONE));
3145 if (bParaStyleContoured && bCharStyleContoured)
3147 std::optional<PropertyMap::Property> charContoured = pPropertyMap->getProperty(PROP_CHAR_CONTOURED);
3148 if (!charContoured.has_value())
3150 pPropertyMap->Insert(PROP_CHAR_CONTOURED, uno::Any(false));
3153 if (bParaStyleShadowed && bCharStyleShadowed)
3155 std::optional<PropertyMap::Property> charShadow = pPropertyMap->getProperty(PROP_CHAR_SHADOWED);
3156 if (!charShadow.has_value())
3158 pPropertyMap->Insert(PROP_CHAR_SHADOWED, uno::Any(false));
3161 if (nParaStyleStrikeThrough == css::awt::FontStrikeout::SINGLE && nParaStyleStrikeThrough == nCharStyleStrikeThrough)
3163 std::optional<PropertyMap::Property> charStrikeThrough = pPropertyMap->getProperty(PROP_CHAR_STRIKEOUT);
3164 if (!charStrikeThrough.has_value())
3166 pPropertyMap->Insert(PROP_CHAR_STRIKEOUT, uno::Any(css::awt::FontStrikeout::NONE));
3169 if (bParaStyleHidden && bCharStyleHidden)
3171 std::optional<PropertyMap::Property> charHidden = pPropertyMap->getProperty(PROP_CHAR_HIDDEN);
3172 if (!charHidden.has_value())
3174 pPropertyMap->Insert(PROP_CHAR_HIDDEN, uno::Any(false));
3181 void DomainMapper_Impl::MergeAtContentImageRedlineWithNext(const css::uno::Reference<css::text::XTextAppend>& xTextAppend)
3183 // remove workaround for change tracked images, if they are part of a redline,
3184 // i.e. if the next run is a tracked change with the same type, author and date,
3185 // as in the change tracking of the image.
3186 if ( m_bRedlineImageInPreviousRun )
3188 auto pCurrentRedline = m_aRedlines.top().size() > 0
3189 ? m_aRedlines.top().back()
3190 : GetTopContextOfType(CONTEXT_CHARACTER) &&
3191 GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0
3192 ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back()
3193 : nullptr;
3194 if ( m_previousRedline && pCurrentRedline &&
3195 // same redline
3196 (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) &&
3197 m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor &&
3198 m_previousRedline->m_sDate == pCurrentRedline->m_sDate )
3200 uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( );
3201 assert(xCursor.is());
3202 xCursor->gotoEnd(false);
3203 xCursor->goLeft(2, true);
3204 if ( xCursor->getString() == u"​​" )
3206 xCursor->goRight(1, true);
3207 xCursor->setString("");
3208 xCursor->gotoEnd(false);
3209 xCursor->goLeft(1, true);
3210 xCursor->setString("");
3214 m_bRedlineImageInPreviousRun = false;
3218 void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap )
3220 if (m_bDiscardHeaderFooter)
3221 return;
3223 if (m_aTextAppendStack.empty())
3224 return;
3225 // Before placing call to processDeferredCharacterProperties(), TopContextType should be CONTEXT_CHARACTER
3226 // processDeferredCharacterProperties() invokes only if character inserted
3227 if (pPropertyMap == m_pTopContext
3228 && !m_StreamStateStack.top().deferredCharacterProperties.empty()
3229 && (GetTopContextType() == CONTEXT_CHARACTER))
3231 processDeferredCharacterProperties();
3233 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
3234 if (!xTextAppend.is() || !hasTableManager() || getTableManager().isIgnore())
3235 return;
3239 applyToggleAttributes(pPropertyMap);
3240 // If we are in comments, then disable CharGrabBag, comment text doesn't support that.
3241 uno::Sequence<beans::PropertyValue> aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!IsInComments());
3243 if (IsInTOC() || m_bStartIndex || m_bStartBibliography)
3244 for( auto& rValue : asNonConstRange(aValues) )
3246 if (rValue.Name == "CharHidden")
3247 rValue.Value <<= false;
3250 MergeAtContentImageRedlineWithNext(xTextAppend);
3252 uno::Reference< text::XTextRange > xTextRange;
3253 if (m_aTextAppendStack.top().xInsertPosition.is())
3255 xTextRange = xTextAppend->insertTextPortion(rString, aValues, m_aTextAppendStack.top().xInsertPosition);
3256 m_aTextAppendStack.top().xCursor->gotoRange(xTextRange->getEnd(), true);
3258 else
3260 if (IsInTOC() || m_bStartIndex || m_bStartBibliography || m_nStartGenericField != 0)
3262 if (IsInHeaderFooter() && !m_bStartTOCHeaderFooter)
3264 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
3266 else
3268 m_bStartedTOC = true;
3269 uno::Reference< text::XTextCursor > xTOCTextCursor = xTextAppend->getEnd()->getText( )->createTextCursor( );
3270 assert(xTOCTextCursor.is());
3271 xTOCTextCursor->gotoEnd(false);
3272 if (m_nStartGenericField != 0)
3274 xTOCTextCursor->goLeft(1, false);
3276 if (IsInComments())
3277 xTextRange = xTextAppend->finishParagraphInsert(aValues, xTOCTextCursor);
3278 else
3279 xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor);
3280 SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed");
3281 if (!xTextRange.is())
3282 throw uno::Exception("insertTextPortion failed", nullptr);
3283 m_StreamStateStack.top().bTextInserted = true;
3284 xTOCTextCursor->gotoRange(xTextRange->getEnd(), true);
3285 if (m_nStartGenericField == 0)
3287 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
3291 else
3293 if (IsOpenField() && GetTopFieldContext()->GetFieldId() == FIELD_HYPERLINK)
3295 // It is content of hyperlink field. We need to create and remember
3296 // character style for later applying to hyperlink
3297 PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(GetTopContext()->GetPropertyValues());
3298 OUString sHyperlinkStyleName = GetStyleSheetTable()->getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
3299 GetTopFieldContext()->SetHyperlinkStyle(sHyperlinkStyleName);
3302 #if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed
3303 sal_Int32 nPos = 0;
3304 OUString sFontName;
3305 OUString sDoubleSpace(" ");
3306 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
3307 // tdf#123703 workaround for longer space sequences of the old or compatible RTF documents
3308 if (GetSettingsTable()->GetLongerSpaceSequence() && !IsOpenFieldCommand() && (nPos = rString.indexOf(sDoubleSpace)) != -1 &&
3309 // monospaced fonts have no longer space sequences, regardless of \fprq2 (not monospaced) font setting
3310 // fix for the base monospaced font Courier
3311 (!pContext || !pContext->isSet(PROP_CHAR_FONT_NAME) ||
3312 ((pContext->getProperty(PROP_CHAR_FONT_NAME)->second >>= sFontName) && sFontName.indexOf("Courier") == -1)))
3314 // an RTF space character is longer by an extra six-em-space in an old-style RTF space sequence,
3315 // insert them to keep RTF document layout formatted by consecutive spaces
3316 const sal_Unicode aExtraSpace[5] = { 0x2006, 0x20, 0x2006, 0x20, 0 };
3317 const sal_Unicode aExtraSpace2[4] = { 0x20, 0x2006, 0x20, 0 };
3318 xTextRange = xTextAppend->appendTextPortion(rString.replaceAll(sDoubleSpace, aExtraSpace, nPos)
3319 .replaceAll(sDoubleSpace, aExtraSpace2, nPos), aValues);
3321 else
3322 #endif
3323 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
3327 // reset moveFrom/moveTo data of non-terminating runs of the paragraph
3328 if ( m_pParaMarkerRedlineMove )
3330 m_pParaMarkerRedlineMove.clear();
3332 CheckRedline( xTextRange );
3333 m_StreamStateStack.top().bParaChanged = true;
3335 //getTableManager( ).handle(xTextRange);
3337 catch(const lang::IllegalArgumentException&)
3339 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" );
3341 catch(const uno::Exception&)
3343 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" );
3347 void DomainMapper_Impl::appendTextContent(
3348 const uno::Reference< text::XTextContent >& xContent,
3349 const uno::Sequence< beans::PropertyValue >& xPropertyValues
3352 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text append stack");
3353 if (m_aTextAppendStack.empty())
3354 return;
3355 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY );
3356 OSL_ENSURE( xTextAppendAndConvert.is(), "trying to append a text content without XTextAppendAndConvert" );
3357 if (!xTextAppendAndConvert.is() || !hasTableManager() || getTableManager().isIgnore())
3358 return;
3362 if (m_aTextAppendStack.top().xInsertPosition.is())
3363 xTextAppendAndConvert->insertTextContentWithProperties( xContent, xPropertyValues, m_aTextAppendStack.top().xInsertPosition );
3364 else
3365 xTextAppendAndConvert->appendTextContent( xContent, xPropertyValues );
3367 catch(const lang::IllegalArgumentException&)
3370 catch(const uno::Exception&)
3375 void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOLEHandler )
3379 uno::Reference< text::XTextContent > xOLE( m_xTextDocument->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
3380 uno::Reference< beans::XPropertySet > xOLEProperties(xOLE, uno::UNO_QUERY_THROW);
3382 OUString aCLSID = pOLEHandler->getCLSID();
3383 if (aCLSID.isEmpty())
3384 xOLEProperties->setPropertyValue(getPropertyName( PROP_STREAM_NAME ),
3385 uno::Any( rStreamName ));
3386 else
3387 xOLEProperties->setPropertyValue("CLSID", uno::Any(aCLSID));
3389 OUString aDrawAspect = pOLEHandler->GetDrawAspect();
3390 if(!aDrawAspect.isEmpty())
3391 xOLEProperties->setPropertyValue("DrawAspect", uno::Any(aDrawAspect));
3393 awt::Size aSize = pOLEHandler->getSize();
3394 if( !aSize.Width )
3395 aSize.Width = 1000;
3396 if( !aSize.Height )
3397 aSize.Height = 1000;
3398 xOLEProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
3399 uno::Any(aSize.Width));
3400 xOLEProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
3401 uno::Any(aSize.Height));
3403 OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth();
3404 if(!aVisAreaWidth.isEmpty())
3405 xOLEProperties->setPropertyValue("VisibleAreaWidth", uno::Any(aVisAreaWidth));
3407 OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight();
3408 if(!aVisAreaHeight.isEmpty())
3409 xOLEProperties->setPropertyValue("VisibleAreaHeight", uno::Any(aVisAreaHeight));
3411 uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement();
3412 xOLEProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC ),
3413 uno::Any(xGraphic));
3414 uno::Reference<beans::XPropertySet> xReplacementProperties(pOLEHandler->getShape(), uno::UNO_QUERY);
3415 if (xReplacementProperties.is())
3417 table::BorderLine2 aBorderProps;
3418 xReplacementProperties->getPropertyValue("LineColor") >>= aBorderProps.Color;
3419 xReplacementProperties->getPropertyValue("LineWidth") >>= aBorderProps.LineWidth;
3420 xReplacementProperties->getPropertyValue("LineStyle") >>= aBorderProps.LineStyle;
3422 if (aBorderProps.LineStyle) // Set line props only if LineStyle is set
3424 xOLEProperties->setPropertyValue("RightBorder", uno::Any(aBorderProps));
3425 xOLEProperties->setPropertyValue("TopBorder", uno::Any(aBorderProps));
3426 xOLEProperties->setPropertyValue("LeftBorder", uno::Any(aBorderProps));
3427 xOLEProperties->setPropertyValue("BottomBorder", uno::Any(aBorderProps));
3429 OUString pProperties[] = {
3430 "AnchorType",
3431 "Surround",
3432 "SurroundContour",
3433 "HoriOrient",
3434 "HoriOrientPosition",
3435 "VertOrient",
3436 "VertOrientPosition",
3437 "VertOrientRelation",
3438 "HoriOrientRelation",
3439 "LeftMargin",
3440 "RightMargin",
3441 "TopMargin",
3442 "BottomMargin"
3444 for (const OUString& s : pProperties)
3446 const uno::Any aVal = xReplacementProperties->getPropertyValue(s);
3447 xOLEProperties->setPropertyValue(s, aVal);
3450 if (xReplacementProperties->getPropertyValue("FillStyle").get<css::drawing::FillStyle>()
3451 != css::drawing::FillStyle::FillStyle_NONE) // Apply fill props if style is set
3453 xOLEProperties->setPropertyValue(
3454 "FillStyle", xReplacementProperties->getPropertyValue("FillStyle"));
3455 xOLEProperties->setPropertyValue(
3456 "FillColor", xReplacementProperties->getPropertyValue("FillColor"));
3457 xOLEProperties->setPropertyValue(
3458 "FillColor2", xReplacementProperties->getPropertyValue("FillColor2"));
3461 else
3462 // mimic the treatment of graphics here... it seems anchoring as character
3463 // gives a better ( visually ) result
3464 xOLEProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ), uno::Any( text::TextContentAnchorType_AS_CHARACTER ) );
3465 // remove ( if valid ) associated shape ( used for graphic replacement )
3466 SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
3467 if (!m_aAnchoredStack.empty())
3468 m_aAnchoredStack.top( ).bToRemove = true;
3469 RemoveLastParagraph();
3470 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack");
3471 if (!m_aTextAppendStack.empty())
3472 m_aTextAppendStack.pop();
3474 appendTextContent( xOLE, uno::Sequence< beans::PropertyValue >() );
3476 if (!aCLSID.isEmpty())
3477 pOLEHandler->importStream(m_xComponentContext, GetTextDocument(), xOLE);
3480 catch( const uno::Exception& )
3482 TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of OLE object" );
3487 void DomainMapper_Impl::appendStarMath( const Value& val )
3489 uno::Reference< embed::XEmbeddedObject > formula;
3490 val.getAny() >>= formula;
3491 if( !formula.is() )
3492 return;
3496 uno::Reference< text::XTextContent > xStarMath( m_xTextDocument->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
3497 uno::Reference< beans::XPropertySet > xStarMathProperties(xStarMath, uno::UNO_QUERY_THROW);
3499 xStarMathProperties->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ),
3500 val.getAny());
3501 // tdf#66405: set zero margins for embedded object
3502 xStarMathProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
3503 uno::Any(sal_Int32(0)));
3504 xStarMathProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
3505 uno::Any(sal_Int32(0)));
3506 xStarMathProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
3507 uno::Any(sal_Int32(0)));
3508 xStarMathProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
3509 uno::Any(sal_Int32(0)));
3511 uno::Reference< uno::XInterface > xInterface( formula->getComponent(), uno::UNO_QUERY );
3512 // set zero margins for object's component
3513 uno::Reference< beans::XPropertySet > xComponentProperties( xInterface, uno::UNO_QUERY_THROW );
3514 xComponentProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
3515 uno::Any(sal_Int32(0)));
3516 xComponentProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
3517 uno::Any(sal_Int32(0)));
3518 xComponentProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
3519 uno::Any(sal_Int32(0)));
3520 xComponentProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
3521 uno::Any(sal_Int32(0)));
3522 Size size( 1000, 1000 );
3523 if( oox::FormulaImExportBase* formulaimport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
3524 size = formulaimport->getFormulaSize();
3525 xStarMathProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
3526 uno::Any( sal_Int32(size.Width())));
3527 xStarMathProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
3528 uno::Any( sal_Int32(size.Height())));
3529 xStarMathProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE),
3530 uno::Any(text::TextContentAnchorType_AS_CHARACTER));
3531 // mimic the treatment of graphics here... it seems anchoring as character
3532 // gives a better ( visually ) result
3533 appendTextContent(xStarMath, uno::Sequence<beans::PropertyValue>());
3535 catch( const uno::Exception& )
3537 TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of StarMath object" );
3541 void DomainMapper_Impl::adjustLastPara(sal_Int8 nAlign)
3543 PropertyMapPtr pLastPara = GetTopContextOfType(dmapper::CONTEXT_PARAGRAPH);
3544 pLastPara->Insert(PROP_PARA_ADJUST, uno::Any(nAlign), true);
3547 static void checkAndAddPropVal(const OUString& prop, const css::uno::Any& val,
3548 std::vector<OUString>& props, std::vector<css::uno::Any>& values)
3550 // Avoid well-known reasons for exceptions when setting property values
3551 if (!val.hasValue())
3552 return;
3553 if (prop == "CharStyleName" || prop == "DropCapCharStyleName")
3554 if (OUString val_string; (val >>= val_string) && val_string.isEmpty())
3555 return;
3557 props.push_back(prop);
3558 values.push_back(val);
3561 static uno::Reference<lang::XComponent>
3562 getParagraphOfRange(const css::uno::Reference<css::text::XTextRange>& xRange)
3564 uno::Reference<container::XEnumerationAccess> xEA{ xRange, uno::UNO_QUERY_THROW };
3565 return { xEA->createEnumeration()->nextElement(), uno::UNO_QUERY_THROW };
3568 static void copyAllProps(const css::uno::Reference<css::uno::XInterface>& from,
3569 const css::uno::Reference<css::uno::XInterface>& to)
3571 css::uno::Reference<css::beans::XPropertySet> xFromProps(from, css::uno::UNO_QUERY_THROW);
3572 css::uno::Reference<css::beans::XPropertySetInfo> xFromInfo(xFromProps->getPropertySetInfo(),
3573 css::uno::UNO_SET_THROW);
3574 css::uno::Sequence<css::beans::Property> rawProps(xFromInfo->getProperties());
3575 std::vector<OUString> props;
3576 props.reserve(rawProps.getLength());
3577 for (const auto& prop : rawProps)
3578 if ((prop.Attributes & css::beans::PropertyAttribute::READONLY) == 0)
3579 props.push_back(prop.Name);
3581 if (css::uno::Reference<css::beans::XPropertyState> xFromState{ from, css::uno::UNO_QUERY })
3583 const auto propsSeq = comphelper::containerToSequence(props);
3584 const auto statesSeq = xFromState->getPropertyStates(propsSeq);
3585 assert(propsSeq.getLength() == statesSeq.getLength());
3586 for (sal_Int32 i = 0; i < propsSeq.getLength(); ++i)
3587 if (statesSeq[i] != css::beans::PropertyState_DIRECT_VALUE)
3588 std::erase(props, propsSeq[i]);
3591 std::vector<css::uno::Any> values;
3592 values.reserve(props.size());
3593 if (css::uno::Reference<css::beans::XMultiPropertySet> xFromMulti{ xFromProps,
3594 css::uno::UNO_QUERY })
3596 const auto propsSeq = comphelper::containerToSequence(props);
3597 const auto valuesSeq = xFromMulti->getPropertyValues(propsSeq);
3598 assert(propsSeq.getLength() == valuesSeq.getLength());
3599 props.clear();
3600 for (size_t i = 0; i < propsSeq.size(); ++i)
3601 checkAndAddPropVal(propsSeq[i], valuesSeq[i], props, values);
3603 else
3605 std::vector<OUString> filtered_props;
3606 filtered_props.reserve(props.size());
3607 for (const auto& prop : props)
3608 checkAndAddPropVal(prop, xFromProps->getPropertyValue(prop), filtered_props, values);
3609 filtered_props.swap(props);
3611 assert(props.size() == values.size());
3613 css::uno::Reference<css::beans::XPropertySet> xToProps(to, css::uno::UNO_QUERY_THROW);
3614 if (css::uno::Reference<css::beans::XMultiPropertySet> xToMulti{ xToProps,
3615 css::uno::UNO_QUERY })
3619 xToMulti->setPropertyValues(comphelper::containerToSequence(props),
3620 comphelper::containerToSequence(values));
3621 return;
3623 catch (css::uno::Exception&)
3625 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3627 // Fallback to property-by-property iteration
3630 for (size_t i = 0; i < props.size(); ++i)
3634 xToProps->setPropertyValue(props[i], values[i]);
3636 catch (css::uno::Exception&)
3638 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3643 uno::Reference< beans::XPropertySet > DomainMapper_Impl::appendTextSectionAfter(
3644 uno::Reference< text::XTextRange > const & xBefore )
3646 uno::Reference< beans::XPropertySet > xRet;
3647 if (m_aTextAppendStack.empty())
3648 return xRet;
3649 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
3650 if(xTextAppend.is())
3654 uno::Reference< text::XParagraphCursor > xCursor(
3655 xTextAppend->createTextCursorByRange( xBefore ), uno::UNO_QUERY_THROW);
3656 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
3657 xCursor->gotoStartOfParagraph( false );
3658 if (m_aTextAppendStack.top().xInsertPosition.is())
3659 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
3660 else
3661 xCursor->gotoEnd( true );
3662 // The paragraph after this new section is already inserted. The previous node may be a
3663 // table; then trying to go left would skip the whole table. Split the trailing
3664 // paragraph; let the section span over the first of the two resulting paragraphs;
3665 // destroy the last section's paragraph afterwards.
3666 xTextAppend->insertControlCharacter(
3667 xCursor->getEnd(), css::text::ControlCharacter::PARAGRAPH_BREAK, false);
3668 auto xNewPara = getParagraphOfRange(xCursor->getEnd());
3669 xCursor->gotoPreviousParagraph(true);
3670 auto xEndPara = getParagraphOfRange(xCursor->getEnd());
3671 // xEndPara may already have properties (like page break); make sure to apply them
3672 // to the newly appended paragraph, which will be kept in the end.
3673 copyAllProps(xEndPara, xNewPara);
3675 uno::Reference< text::XTextContent > xSection( m_xTextDocument->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY_THROW );
3676 xSection->attach(xCursor);
3678 // Remove the extra paragraph (last inside the section)
3679 xEndPara->dispose();
3681 xRet.set(xSection, uno::UNO_QUERY );
3683 catch(const uno::Exception&)
3689 return xRet;
3692 void DomainMapper_Impl::appendGlossaryEntry()
3694 appendTextSectionAfter(m_xGlossaryEntryStart);
3697 void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar)
3699 if (bSetAnchorToChar)
3700 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER));
3702 uno::Any aEmptyBorder{table::BorderLine2()};
3703 static const std::vector<PropertyIds> aBorderIds
3704 = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER };
3705 for (size_t i = 0; i < aBorderIds.size(); ++i)
3706 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder));
3708 static const std::vector<PropertyIds> aMarginIds
3709 = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE,
3710 PROP_LEFT_MARGIN, PROP_LEFT_BORDER_DISTANCE,
3711 PROP_RIGHT_MARGIN, PROP_RIGHT_BORDER_DISTANCE,
3712 PROP_TOP_MARGIN, PROP_TOP_BORDER_DISTANCE };
3713 for (size_t i = 0; i < aMarginIds.size(); ++i)
3714 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0)));
3717 bool DomainMapper_Impl::IsInTOC() const
3719 if (IsInHeaderFooter())
3720 return m_bStartTOCHeaderFooter;
3721 else
3722 return m_bStartTOC;
3725 void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom)
3727 while (!m_aHeaderFooterTextAppendStack.empty())
3729 auto& [aTextAppendContext, ePagePartType] = m_aHeaderFooterTextAppendStack.top();
3730 if ((ePagePartType == PagePartType::Header && !bDynamicHeightTop) || (ePagePartType == PagePartType::Footer && !bDynamicHeightBottom))
3732 uno::Reference< text::XTextAppend > xTextAppend = aTextAppendContext.xTextAppend;
3733 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor();
3734 uno::Reference< text::XTextRange > xRangeStart, xRangeEnd;
3736 xRangeStart = xCursor->getStart();
3737 xCursor->gotoEnd(false);
3738 xRangeEnd = xCursor->getStart();
3740 std::vector<beans::PropertyValue> aFrameProperties
3742 comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH),
3743 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT),
3744 comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false),
3745 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN),
3746 comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN),
3747 // tdf#143384 If the header/footer started with a table, convertToTextFrame could not
3748 // convert the table, because it used createTextCursor() -which ignore tables-
3749 // to set the conversion range.
3750 // This dummy property is set to make convertToTextFrame to use another CreateTextCursor
3751 // method that can be parameterized to not ignore tables.
3752 comphelper::makePropertyValue(getPropertyName(PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF), true)
3755 fillEmptyFrameProperties(aFrameProperties, false);
3757 // If it is a footer, then orient the frame to the bottom
3758 if (ePagePartType == PagePartType::Footer)
3760 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM));
3762 uno::Reference<text::XTextAppendAndConvert> xBodyText(xRangeStart->getText(), uno::UNO_QUERY);
3763 xBodyText->convertToTextFrame(xTextAppend, xRangeEnd, comphelper::containerToSequence(aFrameProperties));
3765 m_aHeaderFooterTextAppendStack.pop();
3769 namespace
3771 // Determines if the XText content is empty (no text, no shapes, no tables)
3772 bool isContentEmpty(uno::Reference<text::XText> const& xText)
3774 if (!xText.is())
3775 return true; // no XText means it's empty
3777 uno::Reference<css::lang::XServiceInfo> xTextServiceInfo(xText, uno::UNO_QUERY);
3778 if (xTextServiceInfo && xTextServiceInfo->getImplementationName() == "SwXHeadFootText")
3779 return false;
3781 uno::Reference<container::XEnumerationAccess> xEnumAccess(xText->getText(), uno::UNO_QUERY);
3782 uno::Reference<container::XEnumeration> xEnum = xEnumAccess->createEnumeration();
3783 while (xEnum->hasMoreElements())
3785 auto xObject = xEnum->nextElement();
3786 uno::Reference<text::XTextTable> const xTextTable(xObject, uno::UNO_QUERY);
3787 if (xTextTable.is())
3788 return false;
3790 uno::Reference<text::XTextRange> const xParagraph(xObject, uno::UNO_QUERY);
3791 if (xParagraph.is() && !xParagraph->getString().isEmpty())
3792 return false;
3794 return true;
3797 } // end anonymous namespace
3799 void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageType eType)
3801 bool bHeader = ePagePartType == PagePartType::Header;
3803 const PropertyIds ePropIsOn = bHeader ? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON;
3804 const PropertyIds ePropShared = bHeader ? PROP_HEADER_IS_SHARED: PROP_FOOTER_IS_SHARED;
3805 const PropertyIds ePropTextLeft = bHeader ? PROP_HEADER_TEXT_LEFT: PROP_FOOTER_TEXT_LEFT;
3806 const PropertyIds ePropTextFirst = bHeader ? PROP_HEADER_TEXT_FIRST: PROP_FOOTER_TEXT_FIRST;
3807 const PropertyIds ePropTextRight = bHeader ? PROP_HEADER_TEXT: PROP_FOOTER_TEXT;
3809 m_bDiscardHeaderFooter = true;
3810 m_StreamStateStack.top().eSubstreamType = bHeader ? SubstreamType::Header : SubstreamType::Footer;
3812 //get the section context
3813 SectionPropertyMap* pSectionContext = GetSectionContext();;
3814 if (!pSectionContext)
3815 return;
3817 if (!m_bIsNewDoc)
3818 return; // TODO sw cannot Undo insert header/footer without crashing
3820 uno::Reference<beans::XPropertySet> xPageStyle = pSectionContext->GetPageStyle(*this);
3821 if (!xPageStyle.is())
3822 return;
3824 bool bEvenAndOdd = GetSettingsTable()->GetEvenAndOddHeaders();
3828 // Turn on the headers
3829 xPageStyle->setPropertyValue(getPropertyName(ePropIsOn), uno::Any(true));
3831 // Set both sharing left and first to off so we can import the content regardless of what value
3832 // the "titlePage" or "evenAndOdd" flags are set (which decide what the sharing is set to in the document).
3833 xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false));
3834 xPageStyle->setPropertyValue(getPropertyName(PROP_FIRST_IS_SHARED), uno::Any(false));
3836 if (eType == PageType::LEFT)
3838 if (bHeader)
3840 pSectionContext->m_bLeftHeader = true;
3841 pSectionContext->m_bHadLeftHeader = true;
3843 else
3844 pSectionContext->m_bLeftFooter = true;
3846 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextLeft, bEvenAndOdd);
3848 else if (eType == PageType::FIRST)
3850 if (bHeader)
3852 pSectionContext->m_bFirstHeader = true;
3853 pSectionContext->m_bHadFirstHeader = true;
3855 else
3856 pSectionContext->m_bFirstFooter = true;
3858 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextFirst, true);
3860 else
3862 if (bHeader)
3864 pSectionContext->m_bRightHeader = true;
3865 pSectionContext->m_bHadRightHeader = true;
3867 else
3868 pSectionContext->m_bRightFooter = true;
3870 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextRight, true);
3873 m_bDiscardHeaderFooter = false; // set only on success!
3875 catch( const uno::Exception& )
3877 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3881 /** Prepares the header/footer text content by first removing the existing
3882 * content and adding it to the text append stack. */
3883 void DomainMapper_Impl::prepareHeaderFooterContent(uno::Reference<beans::XPropertySet> const& xPageStyle,
3884 PagePartType ePagePartType, PropertyIds ePropertyID,
3885 bool bAppendToHeaderAndFooterTextStack)
3887 uno::Reference<text::XText> xText;
3888 xPageStyle->getPropertyValue(getPropertyName(ePropertyID)) >>= xText;
3890 //remove the existing content first
3891 SectionPropertyMap::removeXTextContent(xText);
3893 auto xTextCursor = m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xText->createTextCursorByRange(xText->getStart());
3894 uno::Reference<text::XTextAppend> xTextAppend(xText, uno::UNO_QUERY_THROW);
3895 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTextCursor));
3896 if (bAppendToHeaderAndFooterTextStack)
3897 m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(xTextAppend, xTextCursor), ePagePartType));
3900 bool DomainMapper_Impl::SeenHeaderFooter(PagePartType const partType, PageType const pageType) const
3902 return m_HeaderFooterSeen.find({partType, pageType}) != m_HeaderFooterSeen.end();
3905 /** Checks if the header and footer content on the text appended stack is empty.
3907 void DomainMapper_Impl::checkIfHeaderFooterIsEmpty(PagePartType ePagePartType, PageType eType)
3909 if (m_bDiscardHeaderFooter)
3910 return;
3912 if (m_aTextAppendStack.empty())
3913 return;
3915 SectionPropertyMap* pSectionContext = GetSectionContext();
3916 if (!pSectionContext)
3917 return;
3919 bool bHeader = ePagePartType == PagePartType::Header;
3921 uno::Reference<beans::XPropertySet> xPageStyle(pSectionContext->GetPageStyle(*this));
3923 if (!xPageStyle.is())
3924 return;
3926 bool bEmpty = isContentEmpty(m_aTextAppendStack.top().xTextAppend);
3928 if (eType == PageType::FIRST && bEmpty)
3930 if (bHeader)
3931 pSectionContext->m_bFirstHeader = false;
3932 else
3933 pSectionContext->m_bFirstFooter = false;
3935 else if (eType == PageType::LEFT && bEmpty)
3937 if (bHeader)
3938 pSectionContext->m_bLeftHeader = false;
3939 else
3940 pSectionContext->m_bLeftFooter = false;
3942 else if (eType == PageType::RIGHT && bEmpty)
3944 if (bHeader)
3945 pSectionContext->m_bRightHeader = false;
3946 else
3947 pSectionContext->m_bRightFooter = false;
3951 void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType eType)
3953 //header and footer always have an empty paragraph at the end
3954 //this has to be removed
3955 RemoveLastParagraph();
3957 checkIfHeaderFooterIsEmpty(ePagePartType, eType);
3959 // clear the "Link To Previous" flag so that the header/footer
3960 // content is not copied from the previous section
3961 SectionPropertyMap* pSectionContext = GetSectionContext();
3962 if (pSectionContext)
3964 pSectionContext->clearHeaderFooterLinkToPrevious(ePagePartType, eType);
3965 m_HeaderFooterSeen.emplace(ePagePartType, eType);
3968 if (!m_aTextAppendStack.empty())
3970 if (!m_bDiscardHeaderFooter)
3972 m_aTextAppendStack.pop();
3974 m_bDiscardHeaderFooter = false;
3978 void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote )
3980 SAL_WARN_IF(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote");
3981 m_StreamStateStack.top().eSubstreamType = bIsFootnote ? SubstreamType::Footnote : SubstreamType::Endnote;
3982 m_StreamStateStack.top().bCheckFirstFootnoteTab = true;
3985 // Redlines outside the footnote should not affect footnote content
3986 m_aRedlines.push(std::vector< RedlineParamsPtr >());
3988 // IMHO character styles from footnote labels should be ignored in the edit view of Writer.
3989 // This adds a hack on top of the following hack to save the style name in the context.
3990 PropertyMapPtr pTopContext = GetTopContext();
3991 OUString sFootnoteCharStyleName;
3992 std::optional< PropertyMap::Property > aProp = pTopContext->getProperty(PROP_CHAR_STYLE_NAME);
3993 if (aProp)
3994 aProp->second >>= sFootnoteCharStyleName;
3996 // Remove style reference, if any. This reference did appear here as a side effect of tdf#43017
3997 // Seems it is not required by LO, but causes side effects during editing. So remove it
3998 // for footnotes/endnotes to restore original LO behavior here.
3999 pTopContext->Erase(PROP_CHAR_STYLE_NAME);
4001 uno::Reference< text::XText > xFootnoteText;
4002 if (m_xTextDocument)
4003 xFootnoteText.set( m_xTextDocument->createInstance(
4004 bIsFootnote ?
4005 OUString( "com.sun.star.text.Footnote" ) : OUString( "com.sun.star.text.Endnote" )),
4006 uno::UNO_QUERY_THROW );
4007 uno::Reference< text::XFootnote > xFootnote( xFootnoteText, uno::UNO_QUERY_THROW );
4008 pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName);
4009 uno::Sequence< beans::PropertyValue > aFontProperties;
4010 if (GetTopContextOfType(CONTEXT_CHARACTER))
4011 aFontProperties = GetTopContextOfType(CONTEXT_CHARACTER)->GetPropertyValues();
4012 appendTextContent( uno::Reference< text::XTextContent >( xFootnoteText, uno::UNO_QUERY_THROW ), aFontProperties );
4013 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xFootnoteText, uno::UNO_QUERY_THROW ),
4014 xFootnoteText->createTextCursorByRange(xFootnoteText->getStart())));
4016 // Redlines for the footnote anchor in the main text content
4017 std::vector< RedlineParamsPtr > aFootnoteRedline = std::move(m_aRedlines.top());
4018 m_aRedlines.pop();
4019 CheckRedline( xFootnote->getAnchor( ) );
4020 m_aRedlines.push( aFootnoteRedline );
4022 // Try scanning for custom footnote labels
4023 if (!sFootnoteCharStyleName.isEmpty())
4024 StartCustomFootnote(pTopContext);
4025 else
4026 EndCustomFootnote();
4028 catch( const uno::Exception& )
4030 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "PushFootOrEndnote");
4034 void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xRange,
4035 const RedlineParamsPtr& pRedline)
4037 if ( !pRedline )
4038 return;
4040 bool bRedlineMoved = false;
4043 OUString sType;
4044 switch ( pRedline->m_nToken & 0xffff )
4046 case XML_mod:
4047 sType = getPropertyName( PROP_FORMAT );
4048 break;
4049 case XML_moveTo:
4050 bRedlineMoved = true;
4051 m_pParaMarkerRedlineMove = pRedline.get();
4052 [[fallthrough]];
4053 case XML_ins:
4054 sType = getPropertyName( PROP_INSERT );
4055 break;
4056 case XML_moveFrom:
4057 bRedlineMoved = true;
4058 m_pParaMarkerRedlineMove = pRedline.get();
4059 [[fallthrough]];
4060 case XML_del:
4061 sType = getPropertyName( PROP_DELETE );
4062 break;
4063 case XML_ParagraphFormat:
4064 sType = getPropertyName( PROP_PARAGRAPH_FORMAT );
4065 break;
4066 default:
4067 throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0);
4069 beans::PropertyValues aRedlineProperties( 4 );
4070 beans::PropertyValue * pRedlineProperties = aRedlineProperties.getArray( );
4071 pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_AUTHOR );
4072 pRedlineProperties[0].Value <<= pRedline->m_sAuthor;
4073 pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_DATE_TIME );
4074 util::DateTime aDateTime = ConversionHelper::ConvertDateStringToDateTime( pRedline->m_sDate );
4075 // tdf#146171 import not specified w:date (or specified as zero date "0-00-00")
4076 // as Epoch time to avoid of losing change tracking data during ODF roundtrip
4077 if ( aDateTime.Year == 0 && aDateTime.Month == 0 && aDateTime.Day == 0 )
4079 aDateTime.Year = 1970;
4080 aDateTime.Month = 1;
4081 aDateTime.Day = 1;
4083 pRedlineProperties[1].Value <<= aDateTime;
4084 pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES );
4085 pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties;
4087 sal_uInt32 nRedlineMovedID = 0;
4088 if (bRedlineMoved)
4090 if (!m_sCurrentBkmkId.isEmpty())
4092 nRedlineMovedID = 1;
4093 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find(m_sCurrentBkmkId);
4094 if (aBookmarkIter != m_aBookmarkMap.end())
4096 OUString sMoveID = aBookmarkIter->second.m_sBookmarkName;
4097 auto aIter = m_aRedlineMoveIDs.end();
4099 if (sMoveID.indexOf("__RefMoveFrom__") >= 0)
4101 aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
4102 sMoveID.subView(15));
4104 else if (sMoveID.indexOf("__RefMoveTo__") >= 0)
4106 aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
4107 sMoveID.subView(13));
4110 if (aIter != m_aRedlineMoveIDs.end())
4112 nRedlineMovedID = aIter - m_aRedlineMoveIDs.begin() + 2;
4113 m_nLastRedlineMovedID = nRedlineMovedID;
4117 else
4118 nRedlineMovedID = m_nLastRedlineMovedID;
4120 pRedlineProperties[3].Name = "RedlineMoved";
4121 pRedlineProperties[3].Value <<= nRedlineMovedID;
4123 if (!m_bIsActualParagraphFramed)
4125 uno::Reference < text::XRedline > xRedline( xRange, uno::UNO_QUERY_THROW );
4126 xRedline->makeRedline( sType, aRedlineProperties );
4128 // store frame and (possible floating) table redline data for restoring them after frame conversion
4129 enum StoredRedlines eType;
4130 if (m_bIsActualParagraphFramed || 0 < m_StreamStateStack.top().nTableDepth)
4131 eType = StoredRedlines::FRAME;
4132 else if (IsInFootOrEndnote())
4133 eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
4134 else
4135 eType = StoredRedlines::NONE;
4137 if (eType != StoredRedlines::NONE)
4139 m_aStoredRedlines[eType].emplace_back(xRange);
4140 m_aStoredRedlines[eType].emplace_back(sType);
4141 m_aStoredRedlines[eType].emplace_back(aRedlineProperties);
4144 catch( const uno::Exception & )
4146 TOOLS_WARN_EXCEPTION( "writerfilter", "in makeRedline" );
4150 void DomainMapper_Impl::CheckParaMarkerRedline( uno::Reference< text::XTextRange > const& xRange )
4152 if ( m_pParaMarkerRedline )
4154 CreateRedline( xRange, m_pParaMarkerRedline );
4155 if ( m_pParaMarkerRedline )
4157 m_pParaMarkerRedline.clear();
4158 m_currentRedline.clear();
4161 else if ( m_pParaMarkerRedlineMove && m_bIsParaMarkerMove )
4163 // terminating moveFrom/moveTo redline removes also the paragraph mark
4164 CreateRedline( xRange, m_pParaMarkerRedlineMove );
4166 if ( m_pParaMarkerRedlineMove )
4168 m_pParaMarkerRedlineMove.clear();
4169 EndParaMarkerMove();
4173 void DomainMapper_Impl::CheckRedline( uno::Reference< text::XTextRange > const& xRange )
4175 // Writer core "officially" does not like overlapping redlines, and its UNO interface is stupid enough
4176 // to not prevent that. However, in practice in fact everything appears to work fine (except for the debug warnings
4177 // about redline table corruption, which may possibly be harmless in reality). So leave this as it is, since this
4178 // is a better representation of how the changes happened. If this will ever become a problem, overlapping redlines
4179 // will need to be merged into one, just like doing the changes in the UI does, which will lose some information
4180 // (and so if that happens, it may be better to fix Writer).
4181 // Create the redlines here from lowest (formats) to highest (inserts/removals) priority, since the last one is
4182 // what Writer presents graphically, so this will show deletes as deleted text and not as just formatted text being there.
4183 bool bUsedRange = m_aRedlines.top().size() > 0 || (GetTopContextOfType(CONTEXT_CHARACTER) &&
4184 GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0);
4186 // only export ParagraphFormat, when there is no other redline in the same text portion to avoid missing redline compression,
4187 // but always export the first ParagraphFormat redline in a paragraph to keep the paragraph style change data for rejection
4188 if ((!bUsedRange || !m_StreamStateStack.top().bParaChanged)
4189 && GetTopContextOfType(CONTEXT_PARAGRAPH))
4191 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_PARAGRAPH)->Redlines();
4192 for( const auto& rRedline : avRedLines )
4193 CreateRedline( xRange, rRedline );
4195 if( GetTopContextOfType(CONTEXT_CHARACTER) )
4197 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_CHARACTER)->Redlines();
4198 for( const auto& rRedline : avRedLines )
4199 CreateRedline( xRange, rRedline );
4201 for (const auto& rRedline : m_aRedlines.top() )
4202 CreateRedline( xRange, rRedline );
4205 void DomainMapper_Impl::StartParaMarkerChange( )
4207 m_bIsParaMarkerChange = true;
4210 void DomainMapper_Impl::EndParaMarkerChange( )
4212 m_bIsParaMarkerChange = false;
4213 m_previousRedline = m_currentRedline;
4214 m_currentRedline.clear();
4217 void DomainMapper_Impl::StartParaMarkerMove( )
4219 m_bIsParaMarkerMove = true;
4222 void DomainMapper_Impl::EndParaMarkerMove( )
4224 m_bIsParaMarkerMove = false;
4227 void DomainMapper_Impl::StartCustomFootnote(const PropertyMapPtr pContext)
4229 if (pContext == m_pFootnoteContext)
4230 return;
4232 assert(pContext->GetFootnote().is());
4233 m_StreamStateStack.top().bHasFootnoteStyle = true;
4234 m_StreamStateStack.top().bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty();
4235 m_pFootnoteContext = pContext;
4238 void DomainMapper_Impl::EndCustomFootnote()
4240 m_StreamStateStack.top().bHasFootnoteStyle = false;
4241 m_StreamStateStack.top().bCheckFootnoteStyle = false;
4244 void DomainMapper_Impl::PushAnnotation()
4248 m_StreamStateStack.top().eSubstreamType = SubstreamType::Annotation;
4249 if (!m_xTextDocument)
4250 return;
4251 m_xAnnotationField.set( m_xTextDocument->createInstance( "com.sun.star.text.TextField.Annotation" ),
4252 uno::UNO_QUERY_THROW );
4253 uno::Reference< text::XText > xAnnotationText;
4254 m_xAnnotationField->getPropertyValue("TextRange") >>= xAnnotationText;
4255 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xAnnotationText, uno::UNO_QUERY_THROW ),
4256 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xAnnotationText->createTextCursorByRange(xAnnotationText->getStart())));
4258 catch( const uno::Exception&)
4260 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
4264 static void lcl_CopyRedlines(
4265 uno::Reference< text::XText > const& xSrc,
4266 std::deque<css::uno::Any>& rRedlines,
4267 std::vector<sal_Int32>& redPos,
4268 std::vector<sal_Int32>& redLen,
4269 sal_Int32& redIdx)
4271 redIdx = -1;
4272 for( size_t i = 0; i < rRedlines.size(); i+=3)
4274 uno::Reference< text::XTextRange > xRange;
4275 rRedlines[i] >>= xRange;
4277 // is this a redline of the temporary footnote?
4278 uno::Reference<text::XTextCursor> xRangeCursor;
4281 xRangeCursor = xSrc->createTextCursorByRange( xRange );
4283 catch( const uno::Exception& )
4286 if (xRangeCursor.is())
4288 redIdx = i;
4289 sal_Int32 nLen = xRange->getString().getLength();
4290 redLen.push_back(nLen);
4291 xRangeCursor->gotoRange(xSrc->getStart(), true);
4292 redPos.push_back(xRangeCursor->getString().getLength() - nLen);
4294 else
4296 // we have already found all redlines of the footnote,
4297 // skip checking the redlines of the other footnotes
4298 if (redIdx > -1)
4299 break;
4300 // failed createTextCursorByRange(), for example, table inside the frame
4301 redLen.push_back(-1);
4302 redPos.push_back(-1);
4307 static void lcl_PasteRedlines(
4308 uno::Reference< text::XText > const& xDest,
4309 std::deque<css::uno::Any>& rRedlines,
4310 std::vector<sal_Int32>& redPos,
4311 std::vector<sal_Int32>& redLen,
4312 sal_Int32 redIdx)
4314 // create redlines in the copied footnote
4315 for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx); i+=3)
4317 OUString sType;
4318 beans::PropertyValues aRedlineProperties( 3 );
4319 // skip failed createTextCursorByRange()
4320 if (redPos[i/3] == -1)
4321 continue;
4322 rRedlines[i+1] >>= sType;
4323 rRedlines[i+2] >>= aRedlineProperties;
4324 uno::Reference< text::XTextCursor > xCrsr = xDest->getText()->createTextCursor();
4325 xCrsr->goRight(redPos[i/3], false);
4326 xCrsr->goRight(redLen[i/3], true);
4327 uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW );
4328 try {
4329 xRedline->makeRedline( sType, aRedlineProperties );
4331 catch(const uno::Exception&)
4333 // ignore (footnotes of tracked deletions)
4338 bool DomainMapper_Impl::CopyTemporaryNotes(
4339 uno::Reference< text::XFootnote > xNoteSrc,
4340 uno::Reference< text::XFootnote > xNoteDest )
4342 if (!m_bSaxError && xNoteSrc != xNoteDest)
4344 uno::Reference< text::XText > xSrc( xNoteSrc, uno::UNO_QUERY_THROW );
4345 uno::Reference< text::XText > xDest( xNoteDest, uno::UNO_QUERY_THROW );
4346 uno::Reference< text::XTextCopy > xTxt, xTxt2;
4347 xTxt.set( xSrc, uno::UNO_QUERY_THROW );
4348 xTxt2.set( xDest, uno::UNO_QUERY_THROW );
4349 xTxt2->copyText( xTxt );
4351 // copy its redlines
4352 std::vector<sal_Int32> redPos, redLen;
4353 sal_Int32 redIdx;
4354 enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
4355 lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx);
4356 lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx);
4358 // remove processed redlines
4359 for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++)
4360 m_aStoredRedlines[eType].pop_front();
4362 return true;
4365 return false;
4368 void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes()
4370 rtl::Reference< SwXTextDocument> xTextDoc( GetTextDocument() );
4371 uno::Reference< text::XFootnote > xNote;
4372 if (GetFootnoteCount() > 0)
4374 auto xFootnotes = xTextDoc->getFootnotes();
4375 if ( m_nFirstFootnoteIndex > 0 )
4377 uno::Reference< text::XFootnote > xFirstNote;
4378 xFootnotes->getByIndex(0) >>= xFirstNote;
4379 uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW );
4380 xText->setString("");
4381 xFootnotes->getByIndex(m_nFirstFootnoteIndex) >>= xNote;
4382 CopyTemporaryNotes(xNote, xFirstNote);
4384 for (sal_Int32 i = GetFootnoteCount(); i > 0; --i)
4386 xFootnotes->getByIndex(i) >>= xNote;
4387 xNote->getAnchor()->setString("");
4390 if (GetEndnoteCount() > 0)
4392 auto xEndnotes = xTextDoc->getEndnotes();
4393 if ( m_nFirstEndnoteIndex > 0 )
4395 uno::Reference< text::XFootnote > xFirstNote;
4396 xEndnotes->getByIndex(0) >>= xFirstNote;
4397 uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW );
4398 xText->setString("");
4399 xEndnotes->getByIndex(m_nFirstEndnoteIndex) >>= xNote;
4400 CopyTemporaryNotes(xNote, xFirstNote);
4402 for (sal_Int32 i = GetEndnoteCount(); i > 0; --i)
4404 xEndnotes->getByIndex(i) >>= xNote;
4405 xNote->getAnchor()->setString("");
4410 static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds, sal_Int32& rFirstNoteIndex)
4412 // rNoteIds contains XML footnote identifiers in the loaded order of the footnotes
4413 // (the same order as in footnotes.xml), i.e. it maps temporary footnote positions to the
4414 // identifiers. For example: Ids[0] = 100; Ids[1] = -1, Ids[2] = 5.
4415 // To copy the footnotes in their final place, create an array, which map the (normalized)
4416 // footnote identifiers to the temporary footnote positions. Using the previous example,
4417 // Pos[0] = 1; Pos[1] = 2; Pos[2] = 0 (where [0], [1], [2] are the normalized
4418 // -1, 5 and 100 identifiers).
4419 std::deque<sal_Int32> aSortedIds = rNoteIds;
4420 std::sort(aSortedIds.begin(), aSortedIds.end());
4421 std::map<sal_Int32, size_t> aMapIds;
4422 // normalize footnote identifiers to 0, 1, 2 ...
4423 for (size_t i = 0; i < aSortedIds.size(); ++i)
4424 aMapIds[aSortedIds[i]] = i;
4425 // reusing rNoteIds, create the Pos array to map normalized identifiers to the loaded positions
4426 std::deque<sal_Int32> aOrigNoteIds = rNoteIds;
4427 for (size_t i = 0; i < rNoteIds.size(); ++i)
4428 rNoteIds[aMapIds[aOrigNoteIds[i]]] = i;
4429 rFirstNoteIndex = rNoteIds.front();
4430 rNoteIds.pop_front();
4433 void DomainMapper_Impl::PopFootOrEndnote()
4435 // content of the footnotes were inserted after the first footnote in temporary footnotes,
4436 // restore the content of the actual footnote by copying its content from the first
4437 // (remaining) temporary footnote and remove the temporary footnote.
4438 bool bCopied = false;
4439 if ( m_xTextDocument && IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 ) ||
4440 ( !IsInFootnote() && GetEndnoteCount() > -1 ) ) )
4442 uno::Reference< text::XFootnote > xNoteFirst, xNoteLast;
4443 auto xFootnotes = m_xTextDocument->getFootnotes();
4444 auto xEndnotes = m_xTextDocument->getEndnotes();
4445 if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 &&
4446 ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xNoteLast ) ) ||
4447 ( !IsInFootnote() && xEndnotes->getCount() > 1 &&
4448 ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xNoteLast ) )
4449 ) && xNoteLast->getLabel().isEmpty() )
4451 // copy content of the next temporary footnote
4454 if ( IsInFootnote() && !m_aFootnoteIds.empty() )
4456 if ( m_nFirstFootnoteIndex == -1 )
4457 lcl_convertToNoteIndices(m_aFootnoteIds, m_nFirstFootnoteIndex);
4458 if (m_aFootnoteIds.empty()) // lcl_convertToNoteIndices pops m_aFootnoteIds
4459 m_bSaxError = true;
4460 else
4462 xFootnotes->getByIndex(m_aFootnoteIds.front()) >>= xNoteFirst;
4463 m_aFootnoteIds.pop_front();
4466 else if ( !IsInFootnote() && !m_aEndnoteIds.empty() )
4468 if ( m_nFirstEndnoteIndex == -1 )
4469 lcl_convertToNoteIndices(m_aEndnoteIds, m_nFirstEndnoteIndex);
4470 if (m_aEndnoteIds.empty()) // lcl_convertToNoteIndices pops m_aEndnoteIds
4471 m_bSaxError = true;
4472 else
4474 xEndnotes->getByIndex(m_aEndnoteIds.front()) >>= xNoteFirst;
4475 m_aEndnoteIds.pop_front();
4478 else
4479 m_bSaxError = true;
4481 catch (uno::Exception const&)
4483 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote");
4484 m_bSaxError = true;
4487 bCopied = CopyTemporaryNotes(xNoteFirst, xNoteLast);
4491 if (!IsRTFImport() && !bCopied)
4492 RemoveLastParagraph();
4494 if (!m_aTextAppendStack.empty())
4495 m_aTextAppendStack.pop();
4497 if (m_aRedlines.size() == 1)
4499 SAL_WARN("writerfilter.dmapper", "PopFootOrEndnote() is called without PushFootOrEndnote()?");
4500 return;
4502 m_aRedlines.pop();
4503 m_eSkipFootnoteState = SkipFootnoteSeparator::OFF;
4504 m_pFootnoteContext = nullptr;
4507 void DomainMapper_Impl::PopAnnotation()
4509 RemoveLastParagraph();
4511 m_aTextAppendStack.pop();
4515 if (m_bAnnotationResolved)
4516 m_xAnnotationField->setPropertyValue("Resolved", uno::Any(true));
4518 m_xAnnotationField->setPropertyValue("ParaIdParent", uno::Any(m_sAnnotationParent));
4519 m_xAnnotationField->setPropertyValue("ParaId", uno::Any(m_sAnnotationImportedParaId));
4521 // See if the annotation will be a single position or a range.
4522 if (m_nAnnotationId == -1 || !m_aAnnotationPositions[m_nAnnotationId].m_xStart.is() || !m_aAnnotationPositions[m_nAnnotationId].m_xEnd.is())
4524 uno::Sequence< beans::PropertyValue > aEmptyProperties;
4525 uno::Reference< text::XTextContent > xContent( m_xAnnotationField, uno::UNO_QUERY_THROW );
4526 appendTextContent( xContent, aEmptyProperties );
4527 CheckRedline( xContent->getAnchor( ) );
4529 else
4531 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[m_nAnnotationId];
4532 // Create a range that points to the annotation start/end.
4533 uno::Reference<text::XText> const xText = aAnnotationPosition.m_xStart->getText();
4534 uno::Reference<text::XTextCursor> const xCursor = xText->createTextCursorByRange(aAnnotationPosition.m_xStart);
4536 bool bMarker = false;
4537 uno::Reference<text::XTextRangeCompare> xTextRangeCompare(xText, uno::UNO_QUERY);
4538 if (xTextRangeCompare->compareRegionStarts(aAnnotationPosition.m_xStart, aAnnotationPosition.m_xEnd) == 0)
4540 // Insert a marker so that comment around an anchored image is not collapsed during
4541 // insertion.
4542 xText->insertString(xCursor, "x", false);
4543 bMarker = true;
4546 xCursor->gotoRange(aAnnotationPosition.m_xEnd, true);
4547 uno::Reference<text::XTextRange> const xTextRange(xCursor, uno::UNO_QUERY_THROW);
4549 // Attach the annotation to the range.
4550 uno::Reference<text::XTextAppend> const xTextAppend = m_aTextAppendStack.top().xTextAppend;
4551 xTextAppend->insertTextContent(xTextRange, uno::Reference<text::XTextContent>(m_xAnnotationField, uno::UNO_QUERY_THROW), !xCursor->isCollapsed());
4553 if (bMarker)
4555 // Remove the marker.
4556 xCursor->goLeft(1, true);
4557 xCursor->setString(OUString());
4560 m_aAnnotationPositions.erase( m_nAnnotationId );
4562 catch (uno::Exception const&)
4564 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert annotation field");
4567 m_xAnnotationField.clear();
4568 m_sAnnotationParent.clear();
4569 m_sAnnotationImportedParaId.clear();
4570 m_nAnnotationId = -1;
4571 m_bAnnotationResolved = false;
4574 void DomainMapper_Impl::PushPendingShape( const uno::Reference< drawing::XShape > & xShape )
4576 m_aPendingShapes.push_back(xShape);
4579 uno::Reference<drawing::XShape> DomainMapper_Impl::PopPendingShape()
4581 uno::Reference<drawing::XShape> xRet;
4582 if (!m_aPendingShapes.empty())
4584 xRet = m_aPendingShapes.front();
4585 m_aPendingShapes.pop_front();
4587 return xRet;
4590 void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape > & xShape )
4592 // Append these early, so the context and the table manager stack will be
4593 // in sync, even if the text append stack is empty.
4594 appendTableManager();
4595 appendTableHandler();
4596 getTableManager().startLevel();
4598 if (m_aTextAppendStack.empty())
4599 return;
4600 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
4604 uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW );
4605 if (xSInfo->supportsService("com.sun.star.drawing.GroupShape"))
4607 // Textboxes in shapes do not support styles, so check saved style information and apply properties directly to the child shapes.
4608 const uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY);
4609 const sal_uInt32 nShapeCount = xShapes.is() ? xShapes->getCount() : 0;
4610 for ( sal_uInt32 i = 0; i < nShapeCount; ++i )
4614 uno::Reference<text::XTextRange> xFrame(xShapes->getByIndex(i), uno::UNO_QUERY);
4615 uno::Reference<beans::XPropertySet> xFramePropertySet;
4616 if (xFrame)
4617 xFramePropertySet.set(xFrame, uno::UNO_QUERY_THROW);
4618 uno::Reference<beans::XPropertySet> xShapePropertySet(xShapes->getByIndex(i), uno::UNO_QUERY_THROW);
4620 comphelper::SequenceAsHashMap aGrabBag( xShapePropertySet->getPropertyValue("CharInteropGrabBag") );
4622 // only VML import has checked for style. Don't apply default parastyle properties to other imported shapes
4623 // - except for fontsize - to maintain compatibility with previous versions of LibreOffice.
4624 const bool bOnlyApplyCharHeight = !aGrabBag["mso-pStyle"].hasValue();
4626 OUString sStyleName;
4627 aGrabBag["mso-pStyle"] >>= sStyleName;
4628 StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByISTD( sStyleName );
4629 if ( !pEntry )
4631 // Use default style even in ambiguous cases (where multiple styles were defined) since MOST styles inherit
4632 // MOST of their properties from the default style. In the ambiguous case, we have to accept some kind of compromise
4633 // and the default paragraph style ought to be the safest one... (compared to DocDefaults or program defaults)
4634 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetDefaultParaStyleName() );
4636 if ( pEntry )
4638 // The Ids here come from oox/source/vml/vmltextbox.cxx.
4639 // It probably could safely expand to all Ids that shapes support.
4640 const PropertyIds eIds[] = {
4641 PROP_CHAR_HEIGHT,
4642 PROP_CHAR_FONT_NAME,
4643 PROP_CHAR_WEIGHT,
4644 PROP_CHAR_CHAR_KERNING,
4645 PROP_CHAR_COLOR,
4646 PROP_PARA_ADJUST
4648 const uno::Reference<beans::XPropertyState> xShapePropertyState(xShapePropertySet, uno::UNO_QUERY_THROW);
4649 for ( const auto& eId : eIds )
4653 if ( bOnlyApplyCharHeight && eId != PROP_CHAR_HEIGHT )
4654 continue;
4656 const OUString & sPropName = getPropertyName(eId);
4657 if ( beans::PropertyState_DEFAULT_VALUE == xShapePropertyState->getPropertyState(sPropName) )
4659 const uno::Any aProp = GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
4660 if (aProp.hasValue())
4662 if (xFrame)
4663 xFramePropertySet->setPropertyValue(sPropName, aProp);
4664 else
4665 xShapePropertySet->setPropertyValue(sPropName, aProp);
4669 catch (const uno::Exception&)
4671 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext() text stylesheet property exception" );
4676 catch (const uno::Exception&)
4678 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext()" );
4682 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4683 uno::Sequence<beans::PropertyValue> aGrabBag;
4684 xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
4686 for (const auto& rProp : aGrabBag)
4688 if (rProp.Name == "VML-Z-ORDER")
4690 sal_Int64 zOrder(0);
4691 rProp.Value >>= zOrder;
4693 text::TextContentAnchorType nAnchorType
4694 = text::TextContentAnchorType_AT_PARAGRAPH;
4695 xShapePropertySet->getPropertyValue(getPropertyName(PROP_ANCHOR_TYPE))
4696 >>= nAnchorType;
4698 const uno::Any aOpaque(nAnchorType == text::TextContentAnchorType_AS_CHARACTER
4699 || (zOrder >= 0 && !IsInHeaderFooter()));
4700 xShapePropertySet->setPropertyValue(getPropertyName(PROP_OPAQUE), aOpaque);
4703 // A GroupShape doesn't implement text::XTextRange, but appending
4704 // an empty reference to the stacks still makes sense, because this
4705 // way bToRemove can be set, and we won't end up with duplicated
4706 // shapes for OLE objects.
4707 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
4708 uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY);
4709 m_aAnchoredStack.push(AnchoredContext(xTxtContent));
4711 else if (xSInfo->supportsService("com.sun.star.drawing.OLE2Shape"))
4713 // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw.
4714 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
4715 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
4716 m_aAnchoredStack.push(AnchoredContext(xTextContent));
4717 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4719 m_StreamStateStack.top().xEmbedded.set(m_xTextDocument->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW);
4720 uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_StreamStateStack.top().xEmbedded, uno::UNO_QUERY_THROW);
4721 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT)));
4722 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER));
4723 // So that the original bitmap-only shape will be replaced by the embedded object.
4724 m_aAnchoredStack.top().bToRemove = true;
4725 m_aTextAppendStack.pop();
4726 appendTextContent(m_StreamStateStack.top().xEmbedded, uno::Sequence<beans::PropertyValue>());
4728 else
4730 uno::Reference<text::XTextRange> xShapeTextRange(xShape, uno::UNO_QUERY_THROW);
4731 // Add the shape to the text append stack
4732 uno::Reference<text::XTextAppend> xShapeTextAppend(xShape, uno::UNO_QUERY_THROW);
4733 uno::Reference<text::XTextCursor> xTextCursor;
4734 if (!m_bIsNewDoc)
4736 xTextCursor = xShapeTextRange->getText()->createTextCursorByRange(
4737 xShapeTextRange->getStart());
4739 TextAppendContext aContext(xShapeTextAppend, xTextCursor);
4740 m_aTextAppendStack.push(aContext);
4742 // Add the shape to the anchored objects stack
4743 uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW );
4744 m_aAnchoredStack.push( AnchoredContext(xTxtContent) );
4746 uno::Reference< beans::XPropertySet > xProps( xShape, uno::UNO_QUERY_THROW );
4747 #ifdef DBG_UTIL
4748 TagLogger::getInstance().unoPropertySet(xProps);
4749 #endif
4750 text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH);
4751 xProps->getPropertyValue(getPropertyName( PROP_ANCHOR_TYPE )) >>= nAnchorType;
4752 bool checkZOrderStatus = false;
4753 if (xSInfo->supportsService("com.sun.star.text.TextFrame"))
4755 SetIsTextFrameInserted(true);
4756 // Extract the special "btLr text frame" mode, requested by oox, if needed.
4757 // Extract vml ZOrder from FrameInteropGrabBag
4758 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4759 uno::Sequence<beans::PropertyValue> aGrabBag;
4760 xShapePropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
4762 for (const auto& rProp : aGrabBag)
4764 if (rProp.Name == "VML-Z-ORDER")
4766 GraphicZOrderHelper& rZOrderHelper = m_rDMapper.graphicZOrderHelper();
4767 sal_Int64 zOrder(0);
4768 rProp.Value >>= zOrder;
4769 GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
4770 zOrder < 0, IsInHeaderFooter());
4771 xShapePropertySet->setPropertyValue("ZOrder",
4772 uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
4773 rZOrderHelper.addItem(xShapePropertySet, zOrder);
4774 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) );
4775 checkZOrderStatus = true;
4777 else if ( rProp.Name == "TxbxHasLink" )
4779 //Chaining of textboxes will happen in ~DomainMapper_Impl
4780 //i.e when all the textboxes are read and all its attributes
4781 //have been set ( basically the Name/LinkedDisplayName )
4782 //which is set in Graphic Import.
4783 m_vTextFramesForChaining.push_back(xShape);
4787 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY_THROW);
4788 uno::Reference<text::XTextRange> xTextRange(xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
4789 xTextAppend->insertTextContent(xTextRange, xTextContent, false);
4791 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
4792 // we need to re-set this value to xTextContent, then only values are preserved.
4793 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aGrabBag));
4795 else if (nAnchorType == text::TextContentAnchorType_AS_CHARACTER)
4797 // Fix spacing for as-character objects. If the paragraph has CT_Spacing_after set,
4798 // it needs to be set on the object too, as that's what object placement code uses.
4799 PropertyMapPtr paragraphContext = GetTopContextOfType( CONTEXT_PARAGRAPH );
4800 std::optional<PropertyMap::Property> aPropMargin = paragraphContext->getProperty(PROP_PARA_BOTTOM_MARGIN);
4801 if(aPropMargin)
4802 xProps->setPropertyValue( getPropertyName( PROP_BOTTOM_MARGIN ), aPropMargin->second );
4804 else
4806 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4807 uno::Sequence<beans::PropertyValue> aGrabBag;
4808 xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
4809 for (const auto& rProp : aGrabBag)
4811 if (rProp.Name == "VML-Z-ORDER")
4813 GraphicZOrderHelper& rZOrderHelper = m_rDMapper.graphicZOrderHelper();
4814 sal_Int64 zOrder(0);
4815 rProp.Value >>= zOrder;
4816 GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
4817 zOrder < 0, IsInHeaderFooter());
4818 xShapePropertySet->setPropertyValue("ZOrder",
4819 uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
4820 rZOrderHelper.addItem(xShapePropertySet, zOrder);
4821 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) );
4822 checkZOrderStatus = true;
4824 else if ( rProp.Name == "TxbxHasLink" )
4826 //Chaining of textboxes will happen in ~DomainMapper_Impl
4827 //i.e when all the textboxes are read and all its attributes
4828 //have been set ( basically the Name/LinkedDisplayName )
4829 //which is set in Graphic Import.
4830 m_vTextFramesForChaining.push_back(xShape);
4834 if(IsSdtEndBefore())
4836 uno::Reference< beans::XPropertySetInfo > xPropSetInfo;
4837 if(xShapePropertySet.is())
4839 xPropSetInfo = xShapePropertySet->getPropertySetInfo();
4840 if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
4842 uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({
4843 { "SdtEndBefore", uno::Any(true) }
4844 }));
4845 xShapePropertySet->setPropertyValue("InteropGrabBag",uno::Any(aShapeGrabBag));
4850 if (!IsInHeaderFooter() && !checkZOrderStatus)
4851 xProps->setPropertyValue(
4852 getPropertyName( PROP_OPAQUE ),
4853 uno::Any( true ) );
4855 m_StreamStateStack.top().bParaChanged = true;
4856 getTableManager().setIsInShape(true);
4858 catch ( const uno::Exception& )
4860 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Exception when adding shape");
4864 * Updating chart height and width after reading the actual values from wp:extent
4866 void DomainMapper_Impl::UpdateEmbeddedShapeProps(const uno::Reference< drawing::XShape > & xShape)
4868 if (!xShape.is())
4869 return;
4871 uno::Reference<beans::XPropertySet> const xEmbeddedProperties(m_StreamStateStack.top().xEmbedded, uno::UNO_QUERY_THROW);
4872 awt::Size aSize = xShape->getSize( );
4873 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_WIDTH), uno::Any(sal_Int32(aSize.Width)));
4874 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::Any(sal_Int32(aSize.Height)));
4875 uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY);
4876 // tdf#130782 copy a11y related properties
4877 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_DESCRIPTION),
4878 xShapeProps->getPropertyValue(getPropertyName(PROP_DESCRIPTION)));
4879 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_TITLE),
4880 xShapeProps->getPropertyValue(getPropertyName(PROP_TITLE)));
4881 uno::Reference<container::XNamed> const xEmbedName(m_StreamStateStack.top().xEmbedded, uno::UNO_QUERY);
4882 uno::Reference<container::XNamed> const xShapeName(xShape, uno::UNO_QUERY);
4883 OUString const name(xShapeName->getName());
4884 if (!name.isEmpty()) // setting empty name will throw
4888 xEmbedName->setName(name);
4890 catch (uno::RuntimeException const&)
4892 // ignore - document may contain duplicates (testchartoleobjectembeddings.docx)
4897 void DomainMapper_Impl::PopShapeContext()
4899 if (hasTableManager())
4901 getTableManager().endLevel();
4902 popTableManager();
4904 if ( m_aAnchoredStack.empty() )
4905 return;
4907 // For OLE object replacement shape, the text append context was already removed
4908 // or the OLE object couldn't be inserted.
4909 if ( !m_aAnchoredStack.top().bToRemove )
4911 RemoveLastParagraph();
4912 if (!m_aTextAppendStack.empty())
4913 m_aTextAppendStack.pop();
4916 uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent;
4919 appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() );
4921 catch ( const uno::RuntimeException& )
4923 // this is normal: the shape is already attached
4926 const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW );
4927 // Remove the shape if required (most likely replacement shape for OLE object)
4928 // or anchored to a discarded header or footer
4929 if ( m_xTextDocument && (m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter) )
4933 uno::Reference<drawing::XDrawPage> xDrawPage = m_xTextDocument->getDrawPage();
4934 if ( xDrawPage.is() )
4935 xDrawPage->remove( xShape );
4937 catch( const uno::Exception& )
4942 // Relative width calculations deferred until section's margins are defined.
4943 // Being cautious: only deferring undefined/minimum-width shapes in order to avoid causing potential regressions
4944 css::awt::Size aShapeSize;
4947 aShapeSize = xShape->getSize();
4949 catch (const css::uno::RuntimeException& e)
4951 // May happen e.g. when text frame has no frame format
4952 // See sw/qa/extras/ooxmlimport/data/n779627.docx
4953 SAL_WARN("writerfilter.dmapper", "getSize failed. " << e.Message);
4955 if( aShapeSize.Width <= 2 )
4957 const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY );
4958 SectionPropertyMap* pSectionContext = GetSectionContext();
4959 if ( pSectionContext && (!hasTableManager() || !getTableManager().isInTable()) &&
4960 xShapePropertySet->getPropertySetInfo()->hasPropertyByName(getPropertyName(PROP_RELATIVE_WIDTH)) )
4962 pSectionContext->addRelativeWidthShape(xShape);
4966 m_aAnchoredStack.pop();
4969 bool DomainMapper_Impl::IsSdtEndBefore()
4971 bool bIsSdtEndBefore = false;
4972 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
4973 if(pContext)
4975 const uno::Sequence< beans::PropertyValue > currentCharProps = pContext->GetPropertyValues();
4976 for (const auto& rCurrentCharProp : currentCharProps)
4978 if (rCurrentCharProp.Name == "CharInteropGrabBag")
4980 uno::Sequence<beans::PropertyValue> aCharGrabBag;
4981 rCurrentCharProp.Value >>= aCharGrabBag;
4982 for (const auto& rProp : aCharGrabBag)
4984 if(rProp.Name == "SdtEndBefore")
4986 rProp.Value >>= bIsSdtEndBefore;
4992 return bIsSdtEndBefore;
4995 bool DomainMapper_Impl::IsDiscardHeaderFooter() const
4997 return m_bDiscardHeaderFooter;
5000 // called from TableManager::closeCell()
5001 void DomainMapper_Impl::ClearPreviousParagraph()
5003 // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering
5004 if ((m_StreamStateStack.top().nTableDepth == (m_StreamStateStack.top().nTableCellDepth + 1))
5005 && m_StreamStateStack.top().xPreviousParagraph.is()
5006 && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing())
5008 uno::Reference<container::XNamed> xPreviousNumberingRules(m_StreamStateStack.top().xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
5009 if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() )
5010 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0)));
5013 m_StreamStateStack.top().xPreviousParagraph.clear();
5015 // next table paragraph will be first paragraph in a cell
5016 m_StreamStateStack.top().bFirstParagraphInCell = true;
5019 void DomainMapper_Impl::HandleAltChunk(const OUString& rStreamName)
5023 // Create the import filter.
5024 uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(
5025 comphelper::getProcessServiceFactory());
5026 uno::Reference<uno::XInterface> xDocxFilter
5027 = xMultiServiceFactory->createInstance("com.sun.star.comp.Writer.WriterFilter");
5029 // Set the target document.
5030 uno::Reference<document::XImporter> xImporter(xDocxFilter, uno::UNO_QUERY);
5031 xImporter->setTargetDocument(static_cast<SfxBaseModel*>(m_xTextDocument.get()));
5033 // Set the import parameters.
5034 uno::Reference<embed::XHierarchicalStorageAccess> xStorageAccess(m_xDocumentStorage,
5035 uno::UNO_QUERY);
5036 if (!xStorageAccess.is())
5038 return;
5040 // Turn the ZIP stream into a seekable one, as the importer only works with such streams.
5041 uno::Reference<io::XStream> xStream = xStorageAccess->openStreamElementByHierarchicalName(
5042 rStreamName, embed::ElementModes::READ);
5043 std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(xStream, true);
5044 SvMemoryStream aMemory;
5045 aMemory.WriteStream(*pStream);
5046 uno::Reference<io::XStream> xInputStream = new utl::OStreamWrapper(aMemory);
5047 // Not handling AltChunk during paste for now.
5048 uno::Reference<text::XTextRange> xInsertTextRange = GetCurrentXText()->getEnd();
5049 uno::Reference<text::XTextRange> xSectionStartingRange;
5050 SectionPropertyMap* pSectionContext = GetSectionContext();
5051 if (pSectionContext)
5053 xSectionStartingRange = pSectionContext->GetStartingRange();
5055 uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({
5056 { "InputStream", uno::Any(xInputStream) },
5057 { "InsertMode", uno::Any(true) },
5058 { "TextInsertModeRange", uno::Any(xInsertTextRange) },
5059 { "AltChunkMode", uno::Any(true) },
5060 { "AltChunkStartingRange", uno::Any(xSectionStartingRange) },
5061 }));
5063 // Do the actual import.
5064 uno::Reference<document::XFilter> xFilter(xDocxFilter, uno::UNO_QUERY);
5065 xFilter->filter(aDescriptor);
5067 catch (const uno::Exception& rException)
5069 SAL_WARN("writerfilter", "DomainMapper_Impl::HandleAltChunk: failed to handle alt chunk: "
5070 << rException.Message);
5074 void DomainMapper_Impl::HandlePTab(sal_Int32 nAlignment)
5076 // We only handle the case when the line already has content, so the left-aligned ptab is
5077 // equivalent to a line break.
5078 if (nAlignment != NS_ooxml::LN_Value_ST_PTabAlignment_left)
5080 return;
5083 if (m_aTextAppendStack.empty())
5085 return;
5088 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
5089 if (!xTextAppend.is())
5091 return;
5094 uno::Reference<css::text::XTextRange> xInsertPosition
5095 = m_aTextAppendStack.top().xInsertPosition;
5096 if (!xInsertPosition.is())
5098 xInsertPosition = xTextAppend->getEnd();
5100 uno::Reference<text::XTextCursor> xCursor
5101 = xTextAppend->createTextCursorByRange(xInsertPosition);
5103 // Assume that we just inserted a tab character.
5104 xCursor->goLeft(1, true);
5105 if (xCursor->getString() != "\t")
5107 return;
5110 // Assume that there is some content before the tab character.
5111 uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY);
5112 if (!xParagraphCursor.is())
5114 return;
5117 xCursor->collapseToStart();
5118 xParagraphCursor->gotoStartOfParagraph(true);
5119 if (xCursor->isCollapsed())
5121 return;
5124 // Then select the tab again and replace with a line break.
5125 xCursor->collapseToEnd();
5126 xCursor->goRight(1, true);
5127 xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::LINE_BREAK, true);
5130 void DomainMapper_Impl::HandleLineBreakClear(sal_Int32 nClear)
5132 switch (nClear)
5134 case NS_ooxml::LN_Value_ST_BrClear_left:
5135 // SwLineBreakClear::LEFT
5136 m_StreamStateStack.top().oLineBreakClear = 1;
5137 break;
5138 case NS_ooxml::LN_Value_ST_BrClear_right:
5139 // SwLineBreakClear::RIGHT
5140 m_StreamStateStack.top().oLineBreakClear = 2;
5141 break;
5142 case NS_ooxml::LN_Value_ST_BrClear_all:
5143 // SwLineBreakClear::ALL
5144 m_StreamStateStack.top().oLineBreakClear = 3;
5145 break;
5149 void DomainMapper_Impl::HandleLineBreak(const PropertyMapPtr& pPropertyMap)
5151 if (!m_StreamStateStack.top().oLineBreakClear.has_value())
5153 appendTextPortion("\n", pPropertyMap);
5154 return;
5157 if (m_xTextDocument)
5159 uno::Reference<text::XTextContent> xLineBreak(
5160 m_xTextDocument->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
5161 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
5162 xLineBreakProps->setPropertyValue("Clear", uno::Any(*m_StreamStateStack.top().oLineBreakClear));
5163 appendTextContent(xLineBreak, pPropertyMap->GetPropertyValues());
5165 m_StreamStateStack.top().oLineBreakClear.reset();
5168 static sal_Int16 lcl_ParseNumberingType( std::u16string_view rCommand )
5170 sal_Int16 nRet = style::NumberingType::PAGE_DESCRIPTOR;
5172 // The command looks like: " PAGE \* Arabic "
5173 // tdf#132185: but may as well be "PAGE \* Arabic"
5174 OUString sNumber;
5175 constexpr OUString rSeparator(u"\\* "_ustr);
5176 if (size_t nStartIndex = rCommand.find(rSeparator); nStartIndex != std::u16string_view::npos)
5178 sal_Int32 nStartIndex2 = nStartIndex + rSeparator.getLength();
5179 sNumber = o3tl::getToken(rCommand, 0, ' ', nStartIndex2);
5182 if( !sNumber.isEmpty() )
5184 //todo: might make sense to hash this list, too
5185 struct NumberingPairs
5187 const char* cWordName;
5188 sal_Int16 nType;
5190 static const NumberingPairs aNumberingPairs[] =
5192 {"Arabic", style::NumberingType::ARABIC}
5193 ,{"ROMAN", style::NumberingType::ROMAN_UPPER}
5194 ,{"roman", style::NumberingType::ROMAN_LOWER}
5195 ,{"ALPHABETIC", style::NumberingType::CHARS_UPPER_LETTER}
5196 ,{"alphabetic", style::NumberingType::CHARS_LOWER_LETTER}
5197 ,{"CircleNum", style::NumberingType::CIRCLE_NUMBER}
5198 ,{"ThaiArabic", style::NumberingType::CHARS_THAI}
5199 ,{"ThaiCardText", style::NumberingType::CHARS_THAI}
5200 ,{"ThaiLetter", style::NumberingType::CHARS_THAI}
5201 // ,{"SBCHAR", style::NumberingType::}
5202 // ,{"DBCHAR", style::NumberingType::}
5203 // ,{"DBNUM1", style::NumberingType::}
5204 // ,{"DBNUM2", style::NumberingType::}
5205 // ,{"DBNUM3", style::NumberingType::}
5206 // ,{"DBNUM4", style::NumberingType::}
5207 ,{"Aiueo", style::NumberingType::AIU_FULLWIDTH_JA}
5208 ,{"Iroha", style::NumberingType::IROHA_FULLWIDTH_JA}
5209 // ,{"ZODIAC1", style::NumberingType::}
5210 // ,{"ZODIAC2", style::NumberingType::}
5211 // ,{"ZODIAC3", style::NumberingType::}
5212 // ,{"CHINESENUM1", style::NumberingType::}
5213 // ,{"CHINESENUM2", style::NumberingType::}
5214 // ,{"CHINESENUM3", style::NumberingType::}
5215 ,{"ArabicAlpha", style::NumberingType::CHARS_ARABIC}
5216 ,{"ArabicAbjad", style::NumberingType::FULLWIDTH_ARABIC}
5217 ,{"Ganada", style::NumberingType::HANGUL_JAMO_KO}
5218 ,{"Chosung", style::NumberingType::HANGUL_SYLLABLE_KO}
5219 ,{"KoreanCounting", style::NumberingType::NUMBER_HANGUL_KO}
5220 ,{"KoreanLegal", style::NumberingType::NUMBER_LEGAL_KO}
5221 ,{"KoreanDigital", style::NumberingType::NUMBER_DIGITAL_KO}
5222 ,{"KoreanDigital2", style::NumberingType::NUMBER_DIGITAL2_KO}
5223 /* possible values:
5224 style::NumberingType::
5226 CHARS_UPPER_LETTER_N
5227 CHARS_LOWER_LETTER_N
5228 TRANSLITERATION
5229 NATIVE_NUMBERING
5230 CIRCLE_NUMBER
5231 NUMBER_LOWER_ZH
5232 NUMBER_UPPER_ZH
5233 NUMBER_UPPER_ZH_TW
5234 TIAN_GAN_ZH
5235 DI_ZI_ZH
5236 NUMBER_TRADITIONAL_JA
5237 AIU_HALFWIDTH_JA
5238 IROHA_HALFWIDTH_JA
5239 NUMBER_UPPER_KO
5240 NUMBER_HANGUL_KO
5241 HANGUL_JAMO_KO
5242 HANGUL_SYLLABLE_KO
5243 HANGUL_CIRCLED_JAMO_KO
5244 HANGUL_CIRCLED_SYLLABLE_KO
5245 CHARS_HEBREW
5246 CHARS_NEPALI
5247 CHARS_KHMER
5248 CHARS_LAO
5249 CHARS_TIBETAN
5250 CHARS_CYRILLIC_UPPER_LETTER_BG
5251 CHARS_CYRILLIC_LOWER_LETTER_BG
5252 CHARS_CYRILLIC_UPPER_LETTER_N_BG
5253 CHARS_CYRILLIC_LOWER_LETTER_N_BG
5254 CHARS_CYRILLIC_UPPER_LETTER_RU
5255 CHARS_CYRILLIC_LOWER_LETTER_RU
5256 CHARS_CYRILLIC_UPPER_LETTER_N_RU
5257 CHARS_CYRILLIC_LOWER_LETTER_N_RU
5258 CHARS_CYRILLIC_UPPER_LETTER_SR
5259 CHARS_CYRILLIC_LOWER_LETTER_SR
5260 CHARS_CYRILLIC_UPPER_LETTER_N_SR
5261 CHARS_CYRILLIC_LOWER_LETTER_N_SR
5262 CHARS_CYRILLIC_UPPER_LETTER_UK
5263 CHARS_CYRILLIC_LOWER_LETTER_UK
5264 CHARS_CYRILLIC_UPPER_LETTER_N_UK
5265 CHARS_CYRILLIC_LOWER_LETTER_N_UK*/
5268 for(const NumberingPairs& rNumberingPair : aNumberingPairs)
5270 if( /*sCommand*/sNumber.equalsAscii(rNumberingPair.cWordName ))
5272 nRet = rNumberingPair.nType;
5273 break;
5278 return nRet;
5282 static OUString lcl_ParseFormat( const OUString& rCommand )
5284 // The command looks like: " DATE \@"dd MMMM yyyy" or "09/02/2014"
5285 OUString command;
5286 sal_Int32 delimPos = rCommand.indexOf("\\@");
5287 if (delimPos != -1)
5289 // Remove whitespace permitted by standard between \@ and "
5290 const sal_Int32 nQuoteIndex = rCommand.indexOf('\"');
5291 if (nQuoteIndex != -1)
5293 sal_Int32 wsChars = nQuoteIndex - delimPos - 2;
5294 command = rCommand.replaceAt(delimPos+2, wsChars, u"");
5296 else
5298 // turn date \@ MM into date \@"MM"
5299 command = OUString::Concat(rCommand.subView(0, delimPos + 2)) + "\"" + o3tl::trim(rCommand.subView(delimPos + 2)) + "\"";
5301 return OUString(msfilter::util::findQuotedText(command, u"\\@\"", '\"'));
5304 return OUString();
5306 /*-------------------------------------------------------------------------
5307 extract a parameter (with or without quotes) between the command and the following backslash
5308 -----------------------------------------------------------------------*/
5309 static OUString lcl_ExtractToken(std::u16string_view rCommand,
5310 size_t & rIndex, bool & rHaveToken, bool & rIsSwitch)
5312 rHaveToken = false;
5313 rIsSwitch = false;
5315 OUStringBuffer token;
5316 bool bQuoted(false);
5317 for (; rIndex < rCommand.size(); ++rIndex)
5319 sal_Unicode const currentChar(rCommand[rIndex]);
5320 switch (currentChar)
5322 case '\\':
5324 if (rIndex == rCommand.size() - 1)
5326 SAL_INFO("writerfilter.dmapper", "field: trailing escape");
5327 ++rIndex;
5328 return OUString();
5330 sal_Unicode const nextChar(rCommand[rIndex+1]);
5331 if (bQuoted || '\\' == nextChar)
5333 ++rIndex; // read 2 chars
5334 token.append(nextChar);
5336 else // field switch (case insensitive)
5338 rHaveToken = true;
5339 if (token.isEmpty())
5341 rIsSwitch = true;
5342 rIndex += 2; // read 2 chars
5343 return OUString(rCommand.substr(rIndex - 2, 2)).toAsciiUpperCase();
5345 else
5346 { // leave rIndex, read it again next time
5347 return token.makeStringAndClear();
5351 break;
5352 case '\"':
5353 if (bQuoted || !token.isEmpty())
5355 rHaveToken = true;
5356 if (bQuoted)
5358 ++rIndex;
5360 return token.makeStringAndClear();
5362 else
5364 bQuoted = true;
5366 break;
5367 case ' ':
5368 if (bQuoted)
5370 token.append(' ');
5372 else
5374 if (!token.isEmpty())
5376 rHaveToken = true;
5377 ++rIndex;
5378 return token.makeStringAndClear();
5381 break;
5382 case '=':
5383 if (token.isEmpty())
5385 rHaveToken = true;
5386 ++rIndex;
5387 return "FORMULA";
5389 else
5390 token.append('=');
5391 break;
5392 default:
5393 token.append(currentChar);
5394 break;
5397 assert(rIndex == rCommand.size());
5398 if (bQuoted)
5400 // MS Word allows this, so just emit a debug message
5401 SAL_INFO("writerfilter.dmapper",
5402 "field argument with unterminated quote");
5404 rHaveToken = !token.isEmpty();
5405 return token.makeStringAndClear();
5408 std::tuple<OUString, std::vector<OUString>, std::vector<OUString> > splitFieldCommand(std::u16string_view rCommand)
5410 OUString sType;
5411 std::vector<OUString> arguments;
5412 std::vector<OUString> switches;
5413 size_t nStartIndex(0);
5414 // tdf#54584: Field may be prepended by a backslash
5415 // This is not an escapement, but already escaped literal "\"
5416 // MS Word allows this, so just skip it
5417 if ((rCommand.size() >= nStartIndex + 2) &&
5418 (rCommand[nStartIndex] == L'\\') &&
5419 (rCommand[nStartIndex + 1] != L'\\') &&
5420 (rCommand[nStartIndex + 1] != L' '))
5422 ++nStartIndex;
5427 bool bHaveToken;
5428 bool bIsSwitch;
5429 OUString const token =
5430 lcl_ExtractToken(rCommand, nStartIndex, bHaveToken, bIsSwitch);
5431 assert(nStartIndex <= rCommand.size());
5432 if (bHaveToken)
5434 if (sType.isEmpty())
5436 sType = token.toAsciiUpperCase();
5438 else if (bIsSwitch || !switches.empty())
5440 switches.push_back(token);
5442 else
5444 arguments.push_back(token);
5447 } while (nStartIndex < rCommand.size());
5449 return std::make_tuple(sType, arguments, switches);
5452 static OUString lcl_ExtractVariableAndHint( std::u16string_view rCommand, OUString& rHint )
5454 // the first word after "ASK " is the variable
5455 // the text after the variable and before a '\' is the hint
5456 // if no hint is set the variable is used as hint
5457 // the quotes of the hint have to be removed
5458 size_t nIndex = rCommand.find( ' ', 2); //find last space after 'ASK'
5459 if (nIndex == std::u16string_view::npos)
5460 return OUString();
5461 while (nIndex < rCommand.size() && rCommand[nIndex] == ' ')
5462 ++nIndex;
5463 std::u16string_view sShortCommand( rCommand.substr( nIndex ) ); //cut off the " ASK "
5465 sShortCommand = o3tl::getToken(sShortCommand, 0, '\\');
5466 sal_Int32 nIndex2 = 0;
5467 std::u16string_view sRet = o3tl::getToken(sShortCommand, 0, ' ', nIndex2);
5468 if( nIndex2 > 0)
5469 rHint = sShortCommand.substr( nIndex2 );
5470 if( rHint.isEmpty() )
5471 rHint = sRet;
5472 return OUString(sRet);
5475 static size_t nextCode(std::u16string_view rCommand, size_t pos)
5477 bool inQuotes = false;
5478 for (; pos < rCommand.size(); ++pos)
5480 switch (rCommand[pos])
5482 case '"':
5483 inQuotes = !inQuotes;
5484 break;
5485 case '\\':
5486 ++pos;
5487 if (!inQuotes)
5488 return pos;
5489 break;
5492 return std::u16string_view::npos;
5495 // Returns the position of the field code
5496 static size_t findCode(std::u16string_view rCommand, sal_Unicode cSwitch)
5498 for (size_t i = nextCode(rCommand, 0); i < rCommand.size(); i = nextCode(rCommand, i))
5499 if (rCommand[i] == cSwitch)
5500 return i;
5502 return std::u16string_view::npos;
5505 static bool lcl_FindInCommand(
5506 std::u16string_view rCommand,
5507 sal_Unicode cSwitch,
5508 OUString& rValue )
5510 if (size_t i = findCode(rCommand, cSwitch); i < rCommand.size())
5512 ++i;
5513 size_t next = nextCode(rCommand, i);
5514 if (next < rCommand.size())
5515 --next; // get back before the next '\\'
5516 rValue = o3tl::trim(rCommand.substr(i, next - i));
5517 return true;
5520 return false;
5523 static OUString lcl_trim(std::u16string_view sValue)
5525 // it seems, all kind of quotation marks are allowed around index type identifiers
5526 // TODO apply this on bookmarks, too, if needed
5527 return OUString(o3tl::trim(sValue)).replaceAll("\"","").replaceAll(u"“", "").replaceAll(u"”", "");
5530 /*-------------------------------------------------------------------------
5531 extract the number format from the command and apply the resulting number
5532 format to the XPropertySet
5533 -----------------------------------------------------------------------*/
5534 void DomainMapper_Impl::SetNumberFormat( const OUString& rCommand,
5535 uno::Reference< beans::XPropertySet > const& xPropertySet,
5536 bool const bDetectFormat)
5538 OUString sFormatString = lcl_ParseFormat( rCommand );
5539 // find \h - hijri/luna calendar todo: what about saka/era calendar?
5540 bool bHijri = 0 < rCommand.indexOf("\\h ");
5541 lang::Locale aUSLocale;
5542 aUSLocale.Language = "en";
5543 aUSLocale.Country = "US";
5545 lang::Locale aCurrentLocale;
5546 GetAnyProperty(PROP_CHAR_LOCALE, GetTopContext()) >>= aCurrentLocale;
5548 if (sFormatString.isEmpty())
5550 // No format specified. MS Word uses different formats depending on w:lang,
5551 // "M/d/yyyy h:mm:ss AM/PM" for en-US, and "dd/MM/yyyy hh:mm:ss AM/PM" for en-GB.
5552 // ALSO SEE: ww8par5's GetWordDefaultDateStringAsUS.
5553 sal_Int32 nPos = rCommand.indexOf(" \\");
5554 OUString sCommand = nPos == -1 ? rCommand.trim()
5555 : OUString(o3tl::trim(rCommand.subView(0, nPos)));
5556 if (sCommand == "CREATEDATE" || sCommand == "PRINTDATE" || sCommand == "SAVEDATE")
5560 css::uno::Reference<css::i18n::XNumberFormatCode> const& xNumberFormatCode =
5561 i18n::NumberFormatMapper::create(m_xComponentContext);
5562 sFormatString = xNumberFormatCode->getFormatCode(
5563 css::i18n::NumberFormatIndex::DATE_SYSTEM_SHORT, aCurrentLocale).Code;
5564 nPos = sFormatString.indexOf("YYYY");
5565 if (nPos == -1)
5566 sFormatString = sFormatString.replaceFirst("YY", "YYYY");
5567 if (aCurrentLocale == aUSLocale)
5568 sFormatString += " h:mm:ss AM/PM";
5569 else
5570 sFormatString += " hh:mm:ss AM/PM";
5572 catch(const uno::Exception&)
5574 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
5578 OUString sFormat = ConversionHelper::ConvertMSFormatStringToSO( sFormatString, aCurrentLocale, bHijri);
5579 //get the number formatter and convert the string to a format value
5582 sal_Int32 nKey = 0;
5583 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( static_cast<cppu::OWeakObject*>(m_xTextDocument.get()), uno::UNO_QUERY_THROW );
5584 if( bDetectFormat )
5586 uno::Reference< util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
5587 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
5588 nKey = xFormatter->detectNumberFormat( 0, rCommand );
5590 else
5592 nKey = xNumberSupplier->getNumberFormats()->addNewConverted( sFormat, aUSLocale, aCurrentLocale );
5594 xPropertySet->setPropertyValue(
5595 getPropertyName(PROP_NUMBER_FORMAT),
5596 uno::Any( nKey ));
5598 catch(const uno::Exception&)
5603 static uno::Any lcl_getGrabBagValue( const uno::Sequence<beans::PropertyValue>& grabBag, OUString const & name )
5605 auto pProp = std::find_if(grabBag.begin(), grabBag.end(),
5606 [&name](const beans::PropertyValue& rProp) { return rProp.Name == name; });
5607 if (pProp != grabBag.end())
5608 return pProp->Value;
5609 return uno::Any();
5612 //Link the text frames.
5613 void DomainMapper_Impl::ChainTextFrames()
5615 //can't link textboxes if there are not even two of them...
5616 if( 2 > m_vTextFramesForChaining.size() )
5617 return ;
5619 struct TextFramesForChaining {
5620 css::uno::Reference< css::drawing::XShape > xShape;
5621 sal_Int32 nId;
5622 sal_Int32 nSeq;
5623 OUString s_mso_next_textbox;
5624 OUString shapeName;
5625 TextFramesForChaining() : nId(0), nSeq(0) {}
5627 typedef std::map <OUString, TextFramesForChaining> ChainMap;
5631 ChainMap aTextFramesForChainingHelper;
5632 ::std::vector<TextFramesForChaining> chainingWPS;
5633 OUString sChainNextName("ChainNextName");
5635 //learn about ALL of the textboxes and their chaining values first - because frames are processed in no specific order.
5636 for( const auto& rTextFrame : m_vTextFramesForChaining )
5638 uno::Reference<text::XTextContent> xTextContent(rTextFrame, uno::UNO_QUERY_THROW);
5639 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5640 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo;
5641 if( xPropertySet.is() )
5642 xPropertySetInfo = xPropertySet->getPropertySetInfo();
5643 uno::Sequence<beans::PropertyValue> aGrabBag;
5644 uno::Reference<lang::XServiceInfo> xServiceInfo(xPropertySet, uno::UNO_QUERY);
5646 TextFramesForChaining aChainStruct;
5647 OUString sShapeName;
5648 OUString sLinkChainName;
5650 //The chaining name and the shape name CAN be different in .docx.
5651 //MUST use LinkDisplayName/ChainName as the shape name for establishing links.
5652 if ( xServiceInfo->supportsService("com.sun.star.text.TextFrame") )
5654 xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
5655 xPropertySet->getPropertyValue("LinkDisplayName") >>= sShapeName;
5657 else
5659 xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
5660 xPropertySet->getPropertyValue("ChainName") >>= sShapeName;
5663 lcl_getGrabBagValue( aGrabBag, "Txbx-Id") >>= aChainStruct.nId;
5664 lcl_getGrabBagValue( aGrabBag, "Txbx-Seq") >>= aChainStruct.nSeq;
5665 lcl_getGrabBagValue( aGrabBag, "LinkChainName") >>= sLinkChainName;
5666 lcl_getGrabBagValue( aGrabBag, "mso-next-textbox") >>= aChainStruct.s_mso_next_textbox;
5668 //Sometimes the shape names have not been imported. If not, we may have a fallback name.
5669 //Set name later, only if required for linking.
5670 aChainStruct.shapeName = sShapeName;
5672 if (!sLinkChainName.isEmpty())
5674 aChainStruct.xShape = rTextFrame;
5675 aTextFramesForChainingHelper[sLinkChainName] = aChainStruct;
5677 if (aChainStruct.s_mso_next_textbox.isEmpty())
5678 { // no VML chaining => try to chain DrawingML via IDs
5679 aChainStruct.xShape = rTextFrame;
5680 if (!sLinkChainName.isEmpty())
5681 { // for member of group shapes, TestTdf73499
5682 aChainStruct.shapeName = sLinkChainName;
5684 chainingWPS.emplace_back(aChainStruct);
5688 //if mso-next-textbox tags are provided, create those vml-style links first. Afterwards we will make dml-style id/seq links.
5689 for (auto& msoItem : aTextFramesForChainingHelper)
5691 //if no mso-next-textbox, we are done.
5692 //if it points to itself, we are done.
5693 if( !msoItem.second.s_mso_next_textbox.isEmpty()
5694 && msoItem.second.s_mso_next_textbox != msoItem.first )
5696 ChainMap::iterator nextFinder=aTextFramesForChainingHelper.find(msoItem.second.s_mso_next_textbox);
5697 if( nextFinder != aTextFramesForChainingHelper.end() )
5699 //if the frames have no name yet, then set them. LinkDisplayName / ChainName are read-only.
5700 if (msoItem.second.shapeName.isEmpty())
5702 uno::Reference< container::XNamed > xNamed( msoItem.second.xShape, uno::UNO_QUERY );
5703 if ( xNamed.is() )
5705 xNamed->setName( msoItem.first );
5706 msoItem.second.shapeName = msoItem.first;
5709 if (nextFinder->second.shapeName.isEmpty())
5711 uno::Reference< container::XNamed > xNamed( nextFinder->second.xShape, uno::UNO_QUERY );
5712 if ( xNamed.is() )
5714 xNamed->setName( nextFinder->first );
5715 nextFinder->second.shapeName = msoItem.first;
5719 uno::Reference<text::XTextContent> xTextContent(msoItem.second.xShape, uno::UNO_QUERY_THROW);
5720 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5722 //The reverse chaining happens automatically, so only one direction needs to be set
5723 xPropertySet->setPropertyValue(sChainNextName, uno::Any(nextFinder->second.shapeName));
5725 //the last item in an mso-next-textbox chain is indistinguishable from id/seq items. Now that it is handled, remove it.
5726 if( nextFinder->second.s_mso_next_textbox.isEmpty() )
5727 aTextFramesForChainingHelper.erase(nextFinder->first);
5732 //TODO: Perhaps allow reverse sequences when mso-layout-flow-alt = "bottom-to-top"
5733 const sal_Int32 nDirection = 1;
5735 //Finally - go through and attach the chains based on matching ID and incremented sequence number (dml-style).
5736 for (const auto& rOuter : chainingWPS)
5738 for (const auto& rInner : chainingWPS)
5740 if (rInner.nId == rOuter.nId)
5742 if (rInner.nSeq == (rOuter.nSeq + nDirection))
5744 uno::Reference<text::XTextContent> const xTextContent(rOuter.xShape, uno::UNO_QUERY_THROW);
5745 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5747 //The reverse chaining happens automatically, so only one direction needs to be set
5748 xPropertySet->setPropertyValue(sChainNextName, uno::Any(rInner.shapeName));
5749 break ; //there cannot be more than one next frame
5754 m_vTextFramesForChaining.clear(); //clear the vector
5756 catch (const uno::Exception&)
5758 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
5762 void DomainMapper_Impl::PushTextBoxContent()
5764 if (m_StreamStateStack.top().bIsInTextBox)
5765 return;
5769 uno::Reference<text::XTextFrame> xTBoxFrame(
5770 m_xTextDocument->createInstance("com.sun.star.text.TextFrame"), uno::UNO_QUERY_THROW);
5771 uno::Reference<container::XNamed>(xTBoxFrame, uno::UNO_QUERY_THROW)
5772 ->setName("textbox" + OUString::number(m_xPendingTextBoxFrames.size() + 1));
5773 uno::Reference<text::XTextAppendAndConvert>(m_aTextAppendStack.top().xTextAppend,
5774 uno::UNO_QUERY_THROW)
5775 ->appendTextContent(xTBoxFrame, beans::PropertyValues());
5776 m_xPendingTextBoxFrames.push(xTBoxFrame);
5778 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xTBoxFrame, uno::UNO_QUERY_THROW), {}));
5779 m_StreamStateStack.top().bIsInTextBox = true;
5781 appendTableManager();
5782 appendTableHandler();
5783 getTableManager().startLevel();
5785 catch (uno::Exception& e)
5787 SAL_WARN("writerfilter.dmapper", "Exception during creating textbox (" + e.Message + ")!");
5791 void DomainMapper_Impl::PopTextBoxContent()
5793 if (!m_StreamStateStack.top().bIsInTextBox || m_xPendingTextBoxFrames.empty())
5794 return;
5796 if (uno::Reference<text::XTextFrame>(m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY).is())
5798 if (hasTableManager())
5800 getTableManager().endLevel();
5801 popTableManager();
5803 RemoveLastParagraph();
5805 m_aTextAppendStack.pop();
5806 m_StreamStateStack.top().bIsInTextBox = false;
5810 void DomainMapper_Impl::AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape)
5812 // Without textbox or shape pointless to continue
5813 if (m_xPendingTextBoxFrames.empty() || !xShape)
5814 return;
5816 uno::Reference< drawing::XShapes >xGroup(xShape, uno::UNO_QUERY);
5817 uno::Reference< beans::XPropertySet >xProps(xShape, uno::UNO_QUERY);
5819 // If this is a group go inside
5820 if (xGroup)
5821 for (sal_Int32 i = 0; i < xGroup->getCount(); ++i)
5822 AttachTextBoxContentToShape(
5823 uno::Reference<drawing::XShape>(xGroup->getByIndex(i), uno::UNO_QUERY));
5825 // if this shape has to be a textbox, attach the frame
5826 if (!xProps->getPropertyValue("TextBox").get<bool>())
5827 return;
5829 // if this is a textbox there must be a waiting frame
5830 auto xTextBox = m_xPendingTextBoxFrames.front();
5831 if (!xTextBox)
5832 return;
5834 // Pop the pending frames
5835 m_xPendingTextBoxFrames.pop();
5837 // Attach the textbox to the shape
5840 xProps->setPropertyValue("TextBoxContent", uno::Any(xTextBox));
5842 catch (...)
5844 SAL_WARN("writerfilter.dmapper", "Exception while trying to attach textboxes!");
5845 return;
5848 // If attaching is successful, then do the linking
5851 // Get the name of the textbox
5852 OUString sTextBoxName;
5853 uno::Reference<container::XNamed> xName(xTextBox, uno::UNO_QUERY);
5854 if (xName && !xName->getName().isEmpty())
5855 sTextBoxName = xName->getName();
5857 // Try to get the grabbag
5858 uno::Sequence<beans::PropertyValue> aOldGrabBagSeq;
5859 if (xProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
5860 xProps->getPropertyValue("InteropGrabBag") >>= aOldGrabBagSeq;
5862 // If the grabbag successfully get...
5863 if (!aOldGrabBagSeq.hasElements())
5864 return;
5866 // Check for the existing linking information
5867 bool bSuccess = false;
5868 beans::PropertyValues aNewGrabBagSeq;
5869 const auto& aHasLink = lcl_getGrabBagValue(aOldGrabBagSeq, "TxbxHasLink");
5871 // If there must be a link, do it
5872 if (aHasLink.hasValue() && aHasLink.get<bool>())
5874 auto aLinkProp = comphelper::makePropertyValue("LinkChainName", sTextBoxName);
5875 for (sal_uInt32 i = 0; i < aOldGrabBagSeq.size(); ++i)
5877 aNewGrabBagSeq.realloc(i + 1);
5878 // If this is the link name replace it
5879 if (!aOldGrabBagSeq[i].Name.isEmpty() && !aLinkProp.Name.isEmpty()
5880 && (aOldGrabBagSeq[i].Name == aLinkProp.Name))
5882 aNewGrabBagSeq.getArray()[i] = aLinkProp;
5883 bSuccess = true;
5885 // else copy
5886 else
5887 aNewGrabBagSeq.getArray()[i] = aOldGrabBagSeq[i];
5890 // If there was no replacement, append the linking data
5891 if (!bSuccess)
5893 aNewGrabBagSeq.realloc(aNewGrabBagSeq.size() + 1);
5894 aNewGrabBagSeq.getArray()[aNewGrabBagSeq.size() - 1] = aLinkProp;
5895 bSuccess = true;
5899 // If the linking changed the grabbag, apply the modifications
5900 if (aNewGrabBagSeq.hasElements() && bSuccess)
5902 xProps->setPropertyValue("InteropGrabBag", uno::Any(aNewGrabBagSeq));
5903 m_vTextFramesForChaining.push_back(xShape);
5906 catch (...)
5908 SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!");
5912 uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName)
5914 // query master, create if not available
5915 if (!m_xTextDocument)
5916 throw uno::RuntimeException();
5917 uno::Reference< container::XNameAccess > xFieldMasterAccess = m_xTextDocument->getTextFieldMasters();
5918 uno::Reference< beans::XPropertySet > xMaster;
5919 OUString sFieldMasterService( OUString::createFromAscii(pFieldMasterService) );
5920 OUStringBuffer aFieldMasterName;
5921 OUString sDatabaseDataSourceName = GetSettingsTable()->GetCurrentDatabaseDataSource();
5922 bool bIsMergeField = sFieldMasterService.endsWith("Database");
5923 aFieldMasterName.appendAscii( pFieldMasterService );
5924 aFieldMasterName.append('.');
5925 if ( bIsMergeField && !sDatabaseDataSourceName.isEmpty() )
5927 aFieldMasterName.append(sDatabaseDataSourceName + ".");
5929 aFieldMasterName.append(rFieldMasterName);
5930 OUString sFieldMasterName = aFieldMasterName.makeStringAndClear();
5931 if(xFieldMasterAccess->hasByName(sFieldMasterName))
5933 //get the master
5934 xMaster.set(xFieldMasterAccess->getByName(sFieldMasterName), uno::UNO_QUERY_THROW);
5936 else if( m_xTextDocument )
5938 //create the master
5939 xMaster.set( m_xTextDocument->createInstance(sFieldMasterService), uno::UNO_QUERY_THROW);
5940 if ( !bIsMergeField || sDatabaseDataSourceName.isEmpty() )
5942 //set the master's name
5943 xMaster->setPropertyValue(
5944 getPropertyName(PROP_NAME),
5945 uno::Any(rFieldMasterName));
5946 } else {
5947 // set database data, based on the "databasename.tablename" of sDatabaseDataSourceName
5948 xMaster->setPropertyValue(
5949 getPropertyName(PROP_DATABASE_NAME),
5950 uno::Any(sDatabaseDataSourceName.copy(0, sDatabaseDataSourceName.indexOf('.'))));
5951 xMaster->setPropertyValue(
5952 getPropertyName(PROP_COMMAND_TYPE),
5953 uno::Any(sal_Int32(0)));
5954 xMaster->setPropertyValue(
5955 getPropertyName(PROP_DATATABLE_NAME),
5956 uno::Any(sDatabaseDataSourceName.copy(sDatabaseDataSourceName.indexOf('.') + 1)));
5957 xMaster->setPropertyValue(
5958 getPropertyName(PROP_DATACOLUMN_NAME),
5959 uno::Any(rFieldMasterName));
5962 return xMaster;
5965 void DomainMapper_Impl::PushFieldContext()
5967 m_StreamStateStack.top().bParaHadField = true;
5968 if(m_bDiscardHeaderFooter)
5969 return;
5970 #ifdef DBG_UTIL
5971 TagLogger::getInstance().element("pushFieldContext");
5972 #endif
5974 uno::Reference<text::XTextCursor> xCrsr;
5975 if (!m_aTextAppendStack.empty())
5977 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
5978 if (xTextAppend.is())
5979 xCrsr = xTextAppend->createTextCursorByRange(
5980 m_aTextAppendStack.top().xInsertPosition.is()
5981 ? m_aTextAppendStack.top().xInsertPosition
5982 : xTextAppend->getEnd());
5985 uno::Reference< text::XTextRange > xStart;
5986 if (xCrsr.is())
5987 xStart = xCrsr->getStart();
5988 m_aFieldStack.push_back(new FieldContext(xStart));
5990 /*-------------------------------------------------------------------------
5991 //the current field context waits for the completion of the command
5992 -----------------------------------------------------------------------*/
5993 bool DomainMapper_Impl::IsOpenFieldCommand() const
5995 return !m_aFieldStack.empty() && !m_aFieldStack.back()->IsCommandCompleted();
5997 /*-------------------------------------------------------------------------
5998 //the current field context waits for the completion of the command
5999 -----------------------------------------------------------------------*/
6000 bool DomainMapper_Impl::IsOpenField() const
6002 return !m_aFieldStack.empty();
6005 // Mark top field context as containing a fixed field
6006 void DomainMapper_Impl::SetFieldLocked()
6008 if (IsOpenField())
6009 m_aFieldStack.back()->SetFieldLocked();
6013 FieldContext::FieldContext(uno::Reference< text::XTextRange > xStart)
6014 : m_bFieldCommandCompleted(false)
6015 , m_xStartRange(std::move( xStart ))
6016 , m_bFieldLocked( false )
6017 , m_bCommandType(false)
6019 m_pProperties = new PropertyMap();
6023 FieldContext::~FieldContext()
6027 void FieldContext::SetTextField(uno::Reference<text::XTextField> const& xTextField)
6029 #ifndef NDEBUG
6030 if (xTextField.is())
6032 uno::Reference<lang::XServiceInfo> const xServiceInfo(xTextField, uno::UNO_QUERY);
6033 assert(xServiceInfo.is());
6034 // those must be set by SetFormField()
6035 assert(!xServiceInfo->supportsService("com.sun.star.text.Fieldmark")
6036 && !xServiceInfo->supportsService("com.sun.star.text.FormFieldmark"));
6038 #endif
6039 m_xTextField = xTextField;
6042 void FieldContext::CacheVariableValue(const uno::Any& rAny)
6044 rAny >>= m_sVariableValue;
6047 void FieldContext::AppendCommand(std::u16string_view rPart)
6049 m_sCommand[m_bCommandType] += rPart;
6052 ::std::vector<OUString> FieldContext::GetCommandParts() const
6054 ::std::vector<OUString> aResult;
6055 sal_Int32 nIndex = 0;
6056 bool bInString = false;
6057 OUString sPart;
6058 while (nIndex != -1)
6060 OUString sToken = GetCommand().getToken(0, ' ', nIndex);
6061 bool bInStringNext = bInString;
6063 if (sToken.isEmpty())
6064 continue;
6066 if (sToken[0] == '"')
6068 bInStringNext = true;
6069 sToken = sToken.copy(1);
6071 if (sToken.endsWith("\""))
6073 bInStringNext = false;
6074 sToken = sToken.copy(0, sToken.getLength() - 1);
6077 if (bInString)
6079 sPart += " " + sToken;
6080 if (!bInStringNext)
6082 aResult.push_back(sPart);
6085 else
6087 if (bInStringNext)
6089 sPart = sToken;
6091 else
6093 aResult.push_back(sToken);
6097 bInString = bInStringNext;
6100 return aResult;
6103 /*-------------------------------------------------------------------------
6104 //collect the pieces of the command
6105 -----------------------------------------------------------------------*/
6106 void DomainMapper_Impl::AppendFieldCommand(OUString const & rPartOfCommand)
6108 #ifdef DBG_UTIL
6109 TagLogger::getInstance().startElement("appendFieldCommand");
6110 TagLogger::getInstance().chars(rPartOfCommand);
6111 TagLogger::getInstance().endElement();
6112 #endif
6114 FieldContextPtr pContext = m_aFieldStack.back();
6115 OSL_ENSURE( pContext, "no field context available");
6116 if( pContext )
6118 // Set command line type: normal or deleted
6119 pContext->SetCommandType(m_bTextDeleted);
6120 pContext->AppendCommand( rPartOfCommand );
6125 typedef std::multimap < sal_Int32, OUString > TOCStyleMap;
6128 static ww::eField GetWW8FieldId(OUString const& rType)
6130 std::unordered_map<OUString, ww::eField> mapID
6132 {"ADDRESSBLOCK", ww::eADDRESSBLOCK},
6133 {"ADVANCE", ww::eADVANCE},
6134 {"ASK", ww::eASK},
6135 {"AUTONUM", ww::eAUTONUM},
6136 {"AUTONUMLGL", ww::eAUTONUMLGL},
6137 {"AUTONUMOUT", ww::eAUTONUMOUT},
6138 {"AUTOTEXT", ww::eAUTOTEXT},
6139 {"AUTOTEXTLIST", ww::eAUTOTEXTLIST},
6140 {"AUTHOR", ww::eAUTHOR},
6141 {"BARCODE", ww::eBARCODE},
6142 {"BIDIOUTLINE", ww::eBIDIOUTLINE},
6143 {"DATE", ww::eDATE},
6144 {"COMMENTS", ww::eCOMMENTS},
6145 {"COMPARE", ww::eCOMPARE},
6146 {"CONTROL", ww::eCONTROL},
6147 {"CREATEDATE", ww::eCREATEDATE},
6148 {"DATABASE", ww::eDATABASE},
6149 {"DDEAUTOREF", ww::eDDEAUTOREF},
6150 {"DDEREF", ww::eDDEREF},
6151 {"DOCPROPERTY", ww::eDOCPROPERTY},
6152 {"DOCVARIABLE", ww::eDOCVARIABLE},
6153 {"EDITTIME", ww::eEDITTIME},
6154 {"EMBED", ww::eEMBED},
6155 {"EQ", ww::eEQ},
6156 {"FILLIN", ww::eFILLIN},
6157 {"FILENAME", ww::eFILENAME},
6158 {"FILESIZE", ww::eFILESIZE},
6159 {"FOOTREF", ww::eFOOTREF},
6160 // {"FORMULA", ww::},
6161 {"FORMCHECKBOX", ww::eFORMCHECKBOX},
6162 {"FORMDROPDOWN", ww::eFORMDROPDOWN},
6163 {"FORMTEXT", ww::eFORMTEXT},
6164 {"GLOSSREF", ww::eGLOSSREF},
6165 {"GOTOBUTTON", ww::eGOTOBUTTON},
6166 {"GREETINGLINE", ww::eGREETINGLINE},
6167 {"HTMLCONTROL", ww::eHTMLCONTROL},
6168 {"HYPERLINK", ww::eHYPERLINK},
6169 {"IF", ww::eIF},
6170 {"INFO", ww::eINFO},
6171 {"INCLUDEPICTURE", ww::eINCLUDEPICTURE},
6172 {"INCLUDETEXT", ww::eINCLUDETEXT},
6173 {"INCLUDETIFF", ww::eINCLUDETIFF},
6174 {"KEYWORDS", ww::eKEYWORDS},
6175 {"LASTSAVEDBY", ww::eLASTSAVEDBY},
6176 {"LINK", ww::eLINK},
6177 {"LISTNUM", ww::eLISTNUM},
6178 {"MACRO", ww::eMACRO},
6179 {"MACROBUTTON", ww::eMACROBUTTON},
6180 {"MERGEDATA", ww::eMERGEDATA},
6181 {"MERGEFIELD", ww::eMERGEFIELD},
6182 {"MERGEINC", ww::eMERGEINC},
6183 {"MERGEREC", ww::eMERGEREC},
6184 {"MERGESEQ", ww::eMERGESEQ},
6185 {"NEXT", ww::eNEXT},
6186 {"NEXTIF", ww::eNEXTIF},
6187 {"NOTEREF", ww::eNOTEREF},
6188 {"PAGE", ww::ePAGE},
6189 {"PAGEREF", ww::ePAGEREF},
6190 {"PLUGIN", ww::ePLUGIN},
6191 {"PRINT", ww::ePRINT},
6192 {"PRINTDATE", ww::ePRINTDATE},
6193 {"PRIVATE", ww::ePRIVATE},
6194 {"QUOTE", ww::eQUOTE},
6195 {"RD", ww::eRD},
6196 {"REF", ww::eREF},
6197 {"REVNUM", ww::eREVNUM},
6198 {"SAVEDATE", ww::eSAVEDATE},
6199 {"SECTION", ww::eSECTION},
6200 {"SECTIONPAGES", ww::eSECTIONPAGES},
6201 {"SEQ", ww::eSEQ},
6202 {"SET", ww::eSET},
6203 {"SKIPIF", ww::eSKIPIF},
6204 {"STYLEREF", ww::eSTYLEREF},
6205 {"SUBSCRIBER", ww::eSUBSCRIBER},
6206 {"SUBJECT", ww::eSUBJECT},
6207 {"SYMBOL", ww::eSYMBOL},
6208 {"TA", ww::eTA},
6209 {"TEMPLATE", ww::eTEMPLATE},
6210 {"TIME", ww::eTIME},
6211 {"TITLE", ww::eTITLE},
6212 {"TOA", ww::eTOA},
6213 {"USERINITIALS", ww::eUSERINITIALS},
6214 {"USERADDRESS", ww::eUSERADDRESS},
6215 {"USERNAME", ww::eUSERNAME},
6217 {"TOC", ww::eTOC},
6218 {"TC", ww::eTC},
6219 {"NUMCHARS", ww::eNUMCHARS},
6220 {"NUMWORDS", ww::eNUMWORDS},
6221 {"NUMPAGES", ww::eNUMPAGES},
6222 {"INDEX", ww::eINDEX},
6223 {"XE", ww::eXE},
6224 {"BIBLIOGRAPHY", ww::eBIBLIOGRAPHY},
6225 {"CITATION", ww::eCITATION},
6227 auto const it = mapID.find(rType);
6228 return (it == mapID.end()) ? ww::eNONE : it->second;
6231 static const FieldConversionMap_t & lcl_GetFieldConversion()
6233 static const FieldConversionMap_t aFieldConversionMap
6235 // {"ADDRESSBLOCK", {"", FIELD_ADDRESSBLOCK }},
6236 // {"ADVANCE", {"", FIELD_ADVANCE }},
6237 {"ASK", {"SetExpression", FIELD_ASK }},
6238 {"AUTONUM", {"SetExpression", FIELD_AUTONUM }},
6239 {"AUTONUMLGL", {"SetExpression", FIELD_AUTONUMLGL }},
6240 {"AUTONUMOUT", {"SetExpression", FIELD_AUTONUMOUT }},
6241 {"AUTHOR", {"DocInfo.CreateAuthor", FIELD_AUTHOR }},
6242 {"DATE", {"DateTime", FIELD_DATE }},
6243 {"COMMENTS", {"DocInfo.Description", FIELD_COMMENTS }},
6244 {"CREATEDATE", {"DocInfo.CreateDateTime", FIELD_CREATEDATE }},
6245 {"DOCPROPERTY", {"", FIELD_DOCPROPERTY }},
6246 {"DOCVARIABLE", {"User", FIELD_DOCVARIABLE }},
6247 {"EDITTIME", {"DocInfo.EditTime", FIELD_EDITTIME }},
6248 {"EQ", {"", FIELD_EQ }},
6249 {"FILLIN", {"Input", FIELD_FILLIN }},
6250 {"FILENAME", {"FileName", FIELD_FILENAME }},
6251 // {"FILESIZE", {"", FIELD_FILESIZE }},
6252 {"FORMULA", {"TableFormula", FIELD_FORMULA }},
6253 {"FORMCHECKBOX", {"", FIELD_FORMCHECKBOX }},
6254 {"FORMDROPDOWN", {"DropDown", FIELD_FORMDROPDOWN }},
6255 {"FORMTEXT", {"Input", FIELD_FORMTEXT }},
6256 {"GOTOBUTTON", {"", FIELD_GOTOBUTTON }},
6257 {"HYPERLINK", {"", FIELD_HYPERLINK }},
6258 {"IF", {"ConditionalText", FIELD_IF }},
6259 // {"INFO", {"", FIELD_INFO }},
6260 {"INCLUDEPICTURE", {"", FIELD_INCLUDEPICTURE}},
6261 {"KEYWORDS", {"DocInfo.KeyWords", FIELD_KEYWORDS }},
6262 {"LASTSAVEDBY", {"DocInfo.ChangeAuthor", FIELD_LASTSAVEDBY }},
6263 {"MACROBUTTON", {"Macro", FIELD_MACROBUTTON }},
6264 {"MERGEFIELD", {"Database", FIELD_MERGEFIELD }},
6265 {"MERGEREC", {"DatabaseNumberOfSet", FIELD_MERGEREC }},
6266 // {"MERGESEQ", {"", FIELD_MERGESEQ }},
6267 {"NEXT", {"DatabaseNextSet", FIELD_NEXT }},
6268 {"NEXTIF", {"DatabaseNextSet", FIELD_NEXTIF }},
6269 {"PAGE", {"PageNumber", FIELD_PAGE }},
6270 {"PAGEREF", {"GetReference", FIELD_PAGEREF }},
6271 {"PRINTDATE", {"DocInfo.PrintDateTime", FIELD_PRINTDATE }},
6272 {"REF", {"GetReference", FIELD_REF }},
6273 {"REVNUM", {"DocInfo.Revision", FIELD_REVNUM }},
6274 {"SAVEDATE", {"DocInfo.ChangeDateTime", FIELD_SAVEDATE }},
6275 // {"SECTION", {"", FIELD_SECTION }},
6276 // {"SECTIONPAGES", {"", FIELD_SECTIONPAGES }},
6277 {"SEQ", {"SetExpression", FIELD_SEQ }},
6278 {"SET", {"SetExpression", FIELD_SET }},
6279 // {"SKIPIF", {"", FIELD_SKIPIF }},
6280 {"STYLEREF", {"GetReference", FIELD_STYLEREF }},
6281 {"SUBJECT", {"DocInfo.Subject", FIELD_SUBJECT }},
6282 {"SYMBOL", {"", FIELD_SYMBOL }},
6283 {"TEMPLATE", {"TemplateName", FIELD_TEMPLATE }},
6284 {"TIME", {"DateTime", FIELD_TIME }},
6285 {"TITLE", {"DocInfo.Title", FIELD_TITLE }},
6286 {"USERINITIALS", {"Author", FIELD_USERINITIALS }},
6287 // {"USERADDRESS", {"", FIELD_USERADDRESS }},
6288 {"USERNAME", {"Author", FIELD_USERNAME }},
6291 {"TOC", {"com.sun.star.text.ContentIndex", FIELD_TOC }},
6292 {"TC", {"com.sun.star.text.ContentIndexMark", FIELD_TC }},
6293 {"NUMCHARS", {"CharacterCount", FIELD_NUMCHARS }},
6294 {"NUMWORDS", {"WordCount", FIELD_NUMWORDS }},
6295 {"NUMPAGES", {"PageCount", FIELD_NUMPAGES }},
6296 {"INDEX", {"com.sun.star.text.DocumentIndex", FIELD_INDEX }},
6297 {"XE", {"com.sun.star.text.DocumentIndexMark", FIELD_XE }},
6298 {"BIBLIOGRAPHY",{"com.sun.star.text.Bibliography", FIELD_BIBLIOGRAPHY }},
6299 {"CITATION", {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION }},
6302 return aFieldConversionMap;
6305 static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion()
6307 static const FieldConversionMap_t aEnhancedFieldConversionMap =
6309 {"FORMCHECKBOX", {"FormFieldmark", FIELD_FORMCHECKBOX}},
6310 {"FORMDROPDOWN", {"FormFieldmark", FIELD_FORMDROPDOWN}},
6311 {"FORMTEXT", {"Fieldmark", FIELD_FORMTEXT}},
6314 return aEnhancedFieldConversionMap;
6317 void DomainMapper_Impl::handleFieldSet
6318 (const FieldContextPtr& pContext,
6319 uno::Reference< uno::XInterface > const & xFieldInterface,
6320 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6322 OUString sVariable, sHint;
6324 sVariable = lcl_ExtractVariableAndHint(pContext->GetCommand(), sHint);
6326 // remove surrounding "" if exists
6327 if(sHint.getLength() >= 2)
6329 std::u16string_view sTmp = o3tl::trim(sHint);
6330 if (o3tl::starts_with(sTmp, u"\"") && o3tl::ends_with(sTmp, u"\""))
6332 sHint = sTmp.substr(1, sTmp.size() - 2);
6336 // determine field master name
6337 uno::Reference< beans::XPropertySet > xMaster =
6338 FindOrCreateFieldMaster
6339 ("com.sun.star.text.FieldMaster.SetExpression", sVariable);
6341 // a set field is a string
6342 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6344 // attach the master to the field
6345 uno::Reference< text::XDependentTextField > xDependentField
6346 ( xFieldInterface, uno::UNO_QUERY_THROW );
6347 xDependentField->attachTextFieldMaster( xMaster );
6349 uno::Any aAnyHint(sHint);
6350 xFieldProperties->setPropertyValue(getPropertyName(PROP_HINT), aAnyHint);
6351 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), aAnyHint);
6352 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6354 // Mimic MS Word behavior (hide the SET)
6355 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false));
6358 void DomainMapper_Impl::handleFieldAsk
6359 (const FieldContextPtr& pContext,
6360 uno::Reference< uno::XInterface > & xFieldInterface,
6361 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6363 //doesn the command contain a variable name?
6364 OUString sVariable, sHint;
6366 sVariable = lcl_ExtractVariableAndHint( pContext->GetCommand(),
6367 sHint );
6368 if(!sVariable.isEmpty())
6370 // determine field master name
6371 uno::Reference< beans::XPropertySet > xMaster =
6372 FindOrCreateFieldMaster
6373 ("com.sun.star.text.FieldMaster.SetExpression", sVariable );
6374 // An ASK field is always a string of characters
6375 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6377 // attach the master to the field
6378 uno::Reference< text::XDependentTextField > xDependentField
6379 ( xFieldInterface, uno::UNO_QUERY_THROW );
6380 xDependentField->attachTextFieldMaster( xMaster );
6382 // set input flag at the field
6383 xFieldProperties->setPropertyValue(
6384 getPropertyName(PROP_IS_INPUT), uno::Any( true ));
6385 // set the prompt
6386 xFieldProperties->setPropertyValue(
6387 getPropertyName(PROP_HINT),
6388 uno::Any( sHint ));
6389 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6390 // The ASK has no field value to display
6391 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false));
6393 else
6395 //don't insert the field
6396 //todo: maybe import a 'normal' input field here?
6397 xFieldInterface = nullptr;
6402 * Converts a Microsoft Word field formula into LibreOffice syntax
6403 * @param input The Microsoft Word field formula, with no leading '=' sign
6404 * @return An equivalent LibreOffice field formula
6406 OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) {
6408 if (!m_pSettingsTable)
6410 return input;
6413 OUString listSeparator = m_pSettingsTable->GetListSeparator();
6415 /* Replace logical condition functions with LO equivalent operators */
6416 OUString changed = input.replaceAll(" <> ", " NEQ ");
6417 changed = changed.replaceAll(" <= ", " LEQ ");
6418 changed = changed.replaceAll(" >= ", " GEQ ");
6419 changed = changed.replaceAll(" = " , " EQ ");
6420 changed = changed.replaceAll(" < " , " L ");
6421 changed = changed.replaceAll(" > " , " G ");
6423 changed = changed.replaceAll("<>", " NEQ ");
6424 changed = changed.replaceAll("<=", " LEQ ");
6425 changed = changed.replaceAll(">=", " GEQ ");
6426 changed = changed.replaceAll("=" , " EQ ");
6427 changed = changed.replaceAll("<" , " L ");
6428 changed = changed.replaceAll(">" , " G ");
6430 /* Replace function calls with infix keywords for AND(), OR(), and ROUND(). Nothing needs to be
6431 * done for NOT(). This simple regex will work properly with most common cases. However, it may
6432 * not work correctly when the arguments are nested subcalls to other functions, like
6433 * ROUND(MIN(1,2),MAX(3,4)). See TDF#134765. */
6434 icu::ErrorCode status;
6435 icu::UnicodeString usInput(changed.getStr());
6436 const uint32_t rMatcherFlags = UREGEX_CASE_INSENSITIVE;
6437 OUString regex = "\\b(AND|OR|ROUND)\\s*\\(\\s*([^" + listSeparator + "]+)\\s*" + listSeparator + "\\s*([^)]+)\\s*\\)";
6438 icu::UnicodeString usRegex(regex.getStr());
6439 icu::RegexMatcher rmatch1(usRegex, usInput, rMatcherFlags, status);
6440 usInput = rmatch1.replaceAll(icu::UnicodeString("(($2) $1 ($3))"), status);
6442 /* Assumes any remaining list separators separate arguments to functions that accept lists
6443 * (SUM, MIN, MAX, MEAN, etc.) */
6444 usInput.findAndReplace(icu::UnicodeString(listSeparator.getStr()), "|");
6446 /* Surround single cell references with angle brackets.
6447 * If there is ever added a function name that ends with a digit, this regex will need to be revisited. */
6448 icu::RegexMatcher rmatch2("\\b([A-Z]{1,3}[0-9]+)\\b(?![(])", usInput, rMatcherFlags, status);
6449 usInput = rmatch2.replaceAll(icu::UnicodeString("<$1>"), status);
6451 /* Cell references must be upper case
6452 * TODO: convert reference to other tables, e.g. SUM(Table1 A1:B2), where "Table1" is a bookmark of the table,
6453 * TODO: also column range A:A */
6454 icu::RegexMatcher rmatch3("(<[a-z]{1,3}[0-9]+>|\\b(above|below|left|right)\\b)", usInput, rMatcherFlags, status);
6455 icu::UnicodeString replacedCellRefs;
6456 while (rmatch3.find(status) && status.isSuccess()) {
6457 rmatch3.appendReplacement(replacedCellRefs, rmatch3.group(status).toUpper(), status);
6459 rmatch3.appendTail(replacedCellRefs);
6461 /* Fix up cell ranges */
6462 icu::RegexMatcher rmatch4("<([A-Z]{1,3}[0-9]+)>:<([A-Z]{1,3}[0-9]+)>", replacedCellRefs, rMatcherFlags, status);
6463 usInput = rmatch4.replaceAll(icu::UnicodeString("<$1:$2>"), status);
6465 /* Fix up user defined names */
6466 icu::RegexMatcher rmatch5("\\bDEFINED\\s*\\(<([A-Z]+[0-9]+)>\\)", usInput, rMatcherFlags, status);
6467 usInput = rmatch5.replaceAll(icu::UnicodeString("DEFINED($1)"), status);
6469 /* Prepare replace of ABOVE/BELOW/LEFT/RIGHT by adding spaces around them */
6470 icu::RegexMatcher rmatch6("\\b(ABOVE|BELOW|LEFT|RIGHT)\\b", usInput, rMatcherFlags, status);
6471 usInput = rmatch6.replaceAll(icu::UnicodeString(" $1 "), status);
6473 /* DOCX allows to set decimal symbol independently from the locale of the document, so if
6474 * needed, convert decimal comma to get working formula in a document language (locale),
6475 * which doesn't use decimal comma */
6476 if ( m_pSettingsTable->GetDecimalSymbol() == "," && !m_bIsDecimalComma )
6478 icu::RegexMatcher rmatch7("\\b([0-9]+),([0-9]+([eE][-]?[0-9]+)?)\\b", usInput, rMatcherFlags, status);
6479 usInput = rmatch7.replaceAll(icu::UnicodeString("$1.$2"), status);
6482 return OUString(usInput.getTerminatedBuffer());
6485 void DomainMapper_Impl::handleFieldFormula
6486 (const FieldContextPtr& pContext,
6487 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6489 OUString command = pContext->GetCommand().trim();
6491 // Remove number formatting from \# to end of command
6492 // TODO: handle custom number formatting
6493 sal_Int32 delimPos = command.indexOf("\\#");
6494 if (delimPos != -1)
6496 command = command.replaceAt(delimPos, command.getLength() - delimPos, u"").trim();
6499 // command must contains = and at least another char
6500 if (command.getLength() < 2)
6501 return;
6503 // we don't copy the = symbol from the command
6504 OUString formula = convertFieldFormula(command.copy(1));
6506 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(formula));
6507 xFieldProperties->setPropertyValue(getPropertyName(PROP_NUMBER_FORMAT), uno::Any(sal_Int32(0)));
6508 xFieldProperties->setPropertyValue("IsShowFormula", uno::Any(false));
6510 // grab-bag the original and converted formula
6511 if (hasTableManager())
6513 TablePropertyMapPtr pPropMap(new TablePropertyMap());
6514 pPropMap->Insert(PROP_CELL_FORMULA, uno::Any(command.copy(1)), true, CELL_GRAB_BAG);
6515 pPropMap->Insert(PROP_CELL_FORMULA_CONVERTED, uno::Any(formula), true, CELL_GRAB_BAG);
6516 getTableManager().cellProps(pPropMap);
6520 void DomainMapper_Impl::handleRubyEQField( const FieldContextPtr& pContext)
6522 const OUString & rCommand(pContext->GetCommand());
6523 sal_Int32 nIndex = 0, nEnd = 0;
6524 RubyInfo aInfo ;
6525 nIndex = rCommand.indexOf("\\* jc" );
6526 if (nIndex != -1)
6528 nIndex += 5;
6529 sal_uInt32 nJc = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex));
6530 const sal_Int32 aRubyAlignValues[] =
6532 NS_ooxml::LN_Value_ST_RubyAlign_center,
6533 NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter,
6534 NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace,
6535 NS_ooxml::LN_Value_ST_RubyAlign_left,
6536 NS_ooxml::LN_Value_ST_RubyAlign_right,
6537 NS_ooxml::LN_Value_ST_RubyAlign_rightVertical,
6539 aInfo.nRubyAlign = aRubyAlignValues[(nJc<SAL_N_ELEMENTS(aRubyAlignValues))?nJc:0];
6542 // we don't parse or use the font field in rCommand
6544 nIndex = rCommand.indexOf("\\* hps" );
6545 if (nIndex != -1)
6547 nIndex += 6;
6548 aInfo.nHps = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex));
6551 nIndex = rCommand.indexOf("\\o");
6552 if (nIndex == -1)
6553 return;
6554 nIndex = rCommand.indexOf('(', nIndex);
6555 if (nIndex == -1)
6556 return;
6557 nEnd = rCommand.lastIndexOf(')');
6558 if (nEnd == -1)
6559 return;
6560 if (nEnd <= nIndex)
6561 return;
6563 std::u16string_view sRubyParts = rCommand.subView(nIndex+1,nEnd-nIndex-1);
6564 nIndex = 0;
6565 std::u16string_view sPart1 = o3tl::getToken(sRubyParts, 0, ',', nIndex);
6566 std::u16string_view sPart2 = o3tl::getToken(sRubyParts, 0, ',', nIndex);
6567 size_t nIndex2 = 0;
6568 size_t nEnd2 = 0;
6569 if ((nIndex2 = sPart1.find('(')) != std::u16string_view::npos && (nEnd2 = sPart1.rfind(')')) != std::u16string_view::npos && nEnd2 > nIndex2)
6571 aInfo.sRubyText = sPart1.substr(nIndex2+1,nEnd2-nIndex2-1);
6574 PropertyMapPtr pRubyContext(new PropertyMap());
6575 pRubyContext->InsertProps(GetTopContext());
6576 if (aInfo.nHps > 0)
6578 double fVal = double(aInfo.nHps) / 2.;
6579 uno::Any aVal( fVal );
6581 pRubyContext->Insert(PROP_CHAR_HEIGHT, aVal);
6582 pRubyContext->Insert(PROP_CHAR_HEIGHT_ASIAN, aVal);
6584 PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pRubyContext->GetPropertyValues());
6585 aInfo.sRubyStyle = m_rDMapper.getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
6586 PropertyMapPtr pCharContext(new PropertyMap());
6587 if (m_pLastCharacterContext)
6588 pCharContext->InsertProps(m_pLastCharacterContext);
6589 pCharContext->InsertProps(pContext->getProperties());
6590 pCharContext->Insert(PROP_RUBY_TEXT, uno::Any( aInfo.sRubyText ) );
6591 pCharContext->Insert(PROP_RUBY_ADJUST, uno::Any(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign))));
6592 if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical )
6593 pCharContext->Insert(PROP_RUBY_POSITION, uno::Any(css::text::RubyPosition::INTER_CHARACTER));
6594 pCharContext->Insert(PROP_RUBY_STYLE, uno::Any(aInfo.sRubyStyle));
6595 appendTextPortion(OUString(sPart2), pCharContext);
6599 void DomainMapper_Impl::handleAutoNum
6600 (const FieldContextPtr& pContext,
6601 uno::Reference< uno::XInterface > const & xFieldInterface,
6602 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6604 //create a sequence field master "AutoNr"
6605 uno::Reference< beans::XPropertySet > xMaster =
6606 FindOrCreateFieldMaster
6607 ("com.sun.star.text.FieldMaster.SetExpression",
6608 "AutoNr");
6610 xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE),
6611 uno::Any(text::SetVariableType::SEQUENCE));
6613 //apply the numbering type
6614 xFieldProperties->setPropertyValue(
6615 getPropertyName(PROP_NUMBERING_TYPE),
6616 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
6617 // attach the master to the field
6618 uno::Reference< text::XDependentTextField > xDependentField
6619 ( xFieldInterface, uno::UNO_QUERY_THROW );
6620 xDependentField->attachTextFieldMaster( xMaster );
6623 void DomainMapper_Impl::handleAuthor
6624 (std::u16string_view,
6625 uno::Reference< beans::XPropertySet > const& xFieldProperties,
6626 FieldId eFieldId )
6628 if (eFieldId == FIELD_USERNAME)
6629 xFieldProperties->setPropertyValue
6630 ( getPropertyName(PROP_FULL_NAME), uno::Any( true ));
6632 // Always set as FIXED b/c MS Word only updates these fields via user intervention (F9)
6633 // AUTHOR of course never changes and USERNAME is easily mis-used as an original author field.
6634 // Additionally, this was forced as fixed if any special case-formatting was provided.
6636 xFieldProperties->setPropertyValue(
6637 getPropertyName( PROP_IS_FIXED ),
6638 uno::Any( true ));
6639 //PROP_CURRENT_PRESENTATION is set later anyway
6643 void DomainMapper_Impl::handleDocProperty
6644 (const FieldContextPtr& pContext,
6645 OUString const& rFirstParam,
6646 uno::Reference< uno::XInterface > & xFieldInterface)
6648 //some docproperties should be imported as document statistic fields, some as DocInfo fields
6649 //others should be user fields
6650 if (rFirstParam.isEmpty())
6651 return;
6653 constexpr sal_uInt8 SET_ARABIC = 0x01;
6654 constexpr sal_uInt8 SET_DATE = 0x04;
6655 struct DocPropertyMap
6657 const char* pDocPropertyName;
6658 const char* pServiceName;
6659 sal_uInt8 nFlags;
6661 static const DocPropertyMap aDocProperties[] =
6663 {"CreateTime", "DocInfo.CreateDateTime", SET_DATE},
6664 {"Characters", "CharacterCount", SET_ARABIC},
6665 {"Comments", "DocInfo.Description", 0},
6666 {"Keywords", "DocInfo.KeyWords", 0},
6667 {"LastPrinted", "DocInfo.PrintDateTime", 0},
6668 {"LastSavedBy", "DocInfo.ChangeAuthor", 0},
6669 {"LastSavedTime", "DocInfo.ChangeDateTime", SET_DATE},
6670 {"Paragraphs", "ParagraphCount", SET_ARABIC},
6671 {"RevisionNumber", "DocInfo.Revision", 0},
6672 {"Subject", "DocInfo.Subject", 0},
6673 {"Template", "TemplateName", 0},
6674 {"Title", "DocInfo.Title", 0},
6675 {"TotalEditingTime", "DocInfo.EditTime", 0},
6676 {"Words", "WordCount", SET_ARABIC}
6678 //other available DocProperties:
6679 //Bytes, Category, CharactersWithSpaces, Company
6680 //HyperlinkBase,
6681 //Lines, Manager, NameofApplication, ODMADocId, Pages,
6682 //Security,
6684 uno::Reference<document::XDocumentProperties> xDocumentProperties = m_xTextDocument->getDocumentProperties();
6685 uno::Reference<beans::XPropertySet> xUserDefinedProps(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
6686 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xUserDefinedProps->getPropertySetInfo();
6687 //search for a field mapping
6688 OUString sFieldServiceName;
6689 size_t nMap = 0;
6690 if (!xPropertySetInfo->hasPropertyByName(rFirstParam))
6692 for( ; nMap < SAL_N_ELEMENTS(aDocProperties); ++nMap )
6694 if (rFirstParam.equalsAscii(aDocProperties[nMap].pDocPropertyName))
6696 sFieldServiceName = OUString::createFromAscii(aDocProperties[nMap].pServiceName);
6697 break;
6701 else
6702 pContext->CacheVariableValue(xUserDefinedProps->getPropertyValue(rFirstParam));
6704 OUString sServiceName("com.sun.star.text.TextField.");
6705 bool bIsCustomField = false;
6706 if(sFieldServiceName.isEmpty())
6708 //create a custom property field
6709 sServiceName += "DocInfo.Custom";
6710 bIsCustomField = true;
6712 else
6714 sServiceName += sFieldServiceName;
6716 if (m_xTextDocument)
6717 xFieldInterface = m_xTextDocument->createInstance(sServiceName);
6718 uno::Reference<beans::XPropertySet> xFieldProperties( xFieldInterface, uno::UNO_QUERY_THROW);
6719 if( bIsCustomField )
6721 xFieldProperties->setPropertyValue(
6722 getPropertyName(PROP_NAME), uno::Any(rFirstParam));
6723 pContext->SetCustomField( xFieldProperties );
6725 else
6727 if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC))
6728 xFieldProperties->setPropertyValue(
6729 getPropertyName(PROP_NUMBERING_TYPE),
6730 uno::Any( style::NumberingType::ARABIC ));
6731 else if(0 != (aDocProperties[nMap].nFlags & SET_DATE))
6733 xFieldProperties->setPropertyValue(
6734 getPropertyName(PROP_IS_DATE),
6735 uno::Any( true ));
6736 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
6741 static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator,
6742 const uno::Sequence< beans::PropertyValues >& aLevel, const std::optional<style::TabStop> numtab)
6744 //create a copy of the level and add new entries
6746 std::vector<css::beans::PropertyValues> aNewLevel;
6747 aNewLevel.reserve(aLevel.getLength() + 5); // at most 5 added items
6749 static constexpr OUString tokType(u"TokenType"_ustr);
6750 static constexpr OUString tokHStart(u"TokenHyperlinkStart"_ustr);
6751 static constexpr OUString tokHEnd(u"TokenHyperlinkEnd"_ustr);
6752 static constexpr OUStringLiteral tokPNum(u"TokenPageNumber");
6753 static constexpr OUStringLiteral tokENum(u"TokenEntryNumber");
6755 if (bHyperlinks)
6756 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHStart) });
6758 for (const auto& item : aLevel)
6760 OUString tokenType;
6761 if (auto it = std::find_if(item.begin(), item.end(),
6762 [](const auto& p) { return p.Name == tokType; });
6763 it != item.end())
6764 it->Value >>= tokenType;
6766 if (bHyperlinks && (tokenType == tokHStart || tokenType == tokHEnd))
6767 continue; // We add hyperlink ourselves, so just skip existing hyperlink start / end
6769 if (!sChapterNoSeparator.isEmpty() && tokenType == tokPNum)
6771 // This is an existing page number token; insert the chapter and separator before it
6772 aNewLevel.push_back(
6773 { comphelper::makePropertyValue(tokType, OUString("TokenChapterInfo")),
6774 comphelper::makePropertyValue("ChapterFormat", text::ChapterFormat::NUMBER) });
6775 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, OUString("TokenText")),
6776 comphelper::makePropertyValue("Text", sChapterNoSeparator) });
6779 aNewLevel.push_back(item);
6781 if (numtab && tokenType == tokENum)
6783 // There is a fixed tab stop position needed in the level after the numbering
6784 aNewLevel.push_back(
6785 { comphelper::makePropertyValue(tokType, OUString("TokenTabStop")),
6786 comphelper::makePropertyValue("TabStopPosition", numtab->Position) });
6790 if (bHyperlinks)
6791 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHEnd) });
6793 return comphelper::containerToSequence(aNewLevel);
6796 /// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame
6797 OUString DomainMapper_Impl::extractTocTitle()
6799 if (!m_StreamStateStack.top().xSdtEntryStart.is())
6800 return OUString();
6802 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
6803 if(!xTextAppend.is())
6804 return OUString();
6806 // try-catch was added in the same way as inside appendTextSectionAfter()
6809 uno::Reference<text::XParagraphCursor> const xCursor(
6810 xTextAppend->createTextCursorByRange(m_StreamStateStack.top().xSdtEntryStart), uno::UNO_QUERY_THROW);
6811 if (!xCursor.is())
6812 return OUString();
6814 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
6815 xCursor->gotoStartOfParagraph( false );
6816 if (m_aTextAppendStack.top().xInsertPosition.is())
6817 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
6818 else
6819 xCursor->gotoEnd( true );
6821 // the paragraph after this new section might have been already inserted
6822 OUString sResult = xCursor->getString();
6823 if (sResult.endsWith(SAL_NEWLINE_STRING))
6824 sResult = sResult.copy(0, sResult.getLength() - SAL_N_ELEMENTS(SAL_NEWLINE_STRING) + 1);
6826 return sResult;
6828 catch(const uno::Exception&)
6832 return OUString();
6835 css::uno::Reference<css::beans::XPropertySet>
6836 DomainMapper_Impl::StartIndexSectionChecked(const OUString& sServiceName)
6838 if (m_StreamStateStack.top().bParaChanged)
6840 finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets bParaChanged
6841 PopProperties(CONTEXT_PARAGRAPH);
6842 PushProperties(CONTEXT_PARAGRAPH);
6843 SetIsFirstRun(true);
6844 // The first paragraph of the index that is continuation of just finished one needs to be
6845 // removed when finished (unless more content will arrive, which will set bParaChanged)
6846 m_StreamStateStack.top().bRemoveThisParagraph = true;
6848 const auto& xTextAppend = GetTopTextAppend();
6849 const auto xTextRange = xTextAppend->getEnd();
6850 const auto xRet = createSectionForRange(xTextRange, xTextRange, sServiceName, false);
6851 if (!m_aTextAppendStack.top().xInsertPosition)
6855 m_bStartedTOC = true;
6856 uno::Reference<text::XTextCursor> xTOCTextCursor
6857 = xTextRange->getText()->createTextCursor();
6858 assert(xTOCTextCursor.is());
6859 xTOCTextCursor->gotoEnd(false);
6860 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
6862 catch (const uno::Exception&)
6864 TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
6865 "DomainMapper_Impl::StartIndexSectionChecked:");
6868 return xRet;
6872 This is a heuristic to find Word's w:styleId value from localised style name.
6873 It's not clear how exactly it works, but apparently Word stores into
6874 w:styleId some filtered representation of the localised style name.
6875 Tragically there are references to the localised style name itself in TOC
6876 fields.
6877 Hopefully this works and a complete map of >100 built-in style names
6878 localised to all languages isn't needed.
6880 static auto FilterChars(std::u16string_view const& rStyleName) -> OUString
6882 return msfilter::util::CreateDOCXStyleId(rStyleName);
6885 static OUString UnquoteFieldText(std::u16string_view s)
6887 OUStringBuffer result(s.size());
6888 for (size_t i = 0; i < s.size(); ++i)
6890 switch (s[i])
6892 case '"':
6893 continue;
6894 case '\\':
6895 if (i < s.size() - 1)
6896 ++i;
6897 [[fallthrough]];
6898 default:
6899 result.append(s[i]);
6902 return result.makeStringAndClear();
6905 OUString DomainMapper_Impl::ConvertTOCStyleName(OUString const& rTOCStyleName)
6907 assert(!rTOCStyleName.isEmpty());
6908 if (auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(rTOCStyleName))
6909 { // theoretical case: what OOXML says
6910 return pStyle->m_sStyleName;
6912 auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(FilterChars(rTOCStyleName));
6913 if (pStyle && m_bIsNewDoc)
6914 { // practical case: Word wrote i18n name to TOC field, but it doesn't
6915 // exist in styles.xml; tdf#153083 clone it for best roundtrip
6916 SAL_INFO("writerfilter.dmapper", "cloning TOC paragraph style (presumed built-in) " << rTOCStyleName << " from " << pStyle->m_sStyleName);
6917 return GetStyleSheetTable()->CloneTOCStyle(GetFontTable(), pStyle, rTOCStyleName);
6919 else
6921 return GetStyleSheetTable()->ConvertStyleName(rTOCStyleName);
6925 void DomainMapper_Impl::handleToc
6926 (const FieldContextPtr& pContext,
6927 const OUString & sTOCServiceName)
6929 OUString sValue;
6930 if (IsInHeaderFooter())
6931 m_bStartTOCHeaderFooter = true;
6932 bool bTableOfFigures = false;
6933 bool bHyperlinks = false;
6934 bool bFromOutline = false;
6935 bool bFromEntries = false;
6936 bool bHideTabLeaderPageNumbers = false ;
6937 bool bIsTabEntry = false ;
6938 bool bNewLine = false ;
6939 bool bParagraphOutlineLevel = false;
6941 sal_Int16 nMaxLevel = 10;
6942 OUString sTemplate;
6943 OUString sChapterNoSeparator;
6944 OUString sFigureSequence;
6945 OUString aBookmarkName;
6947 // \a Builds a table of figures but does not include the captions's label and number
6948 if( lcl_FindInCommand( pContext->GetCommand(), 'a', sValue ))
6949 { //make it a table of figures
6950 bTableOfFigures = true;
6952 // \b Uses a bookmark to specify area of document from which to build table of contents
6953 if( lcl_FindInCommand( pContext->GetCommand(), 'b', sValue ))
6955 aBookmarkName = sValue.trim().replaceAll("\"","");
6957 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
6958 // \c Builds a table of figures of the given label
6960 //todo: sValue contains the label's name
6961 bTableOfFigures = true;
6962 sFigureSequence = sValue.trim();
6963 sFigureSequence = sFigureSequence.replaceAll("\"", "").replaceAll("'","");
6965 // \d Defines the separator between sequence and page numbers
6966 if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue ))
6968 //todo: insert the chapter number into each level and insert the separator additionally
6969 sChapterNoSeparator = UnquoteFieldText(sValue);
6971 // \f Builds a table of contents using TC entries instead of outline levels
6972 if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
6974 //todo: sValue can contain a TOC entry identifier - use unclear
6975 bFromEntries = true;
6977 // \h Hyperlinks the entries and page numbers within the table of contents
6978 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
6980 //todo: make all entries to hyperlinks
6981 bHyperlinks = true;
6983 // \l Defines the TC entries field level used to build a table of contents
6984 // if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
6985 // {
6986 //todo: entries can only be included completely
6987 // }
6988 // \n Builds a table of contents or a range of entries, such as 1-9 in a table of contents without page numbers
6989 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
6990 // {
6991 //todo: what does the description mean?
6992 // }
6993 // \o Builds a table of contents by using outline levels instead of TC entries
6994 if( lcl_FindInCommand( pContext->GetCommand(), 'o', sValue ))
6996 bFromOutline = true;
6997 if (sValue.isEmpty())
6998 nMaxLevel = WW_OUTLINE_MAX;
6999 else
7001 sal_Int32 nIndex = 0;
7002 o3tl::getToken(sValue, 0, '-', nIndex );
7003 nMaxLevel = static_cast<sal_Int16>(nIndex != -1 ? o3tl::toInt32(sValue.subView(nIndex)) : 0);
7006 // \p Defines the separator between the table entry and its page number
7007 // \s Builds a table of contents by using a sequence type
7008 // \t Builds a table of contents by using style names other than the standard outline styles
7009 if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
7011 OUString sToken = sValue.getToken(1, '"');
7012 sTemplate = sToken.isEmpty() ? sValue : sToken;
7014 // \u Builds a table of contents by using the applied paragraph outline level
7015 if( lcl_FindInCommand( pContext->GetCommand(), 'u', sValue ))
7017 bFromOutline = true;
7018 bParagraphOutlineLevel = true;
7019 //todo: what doesn 'the applied paragraph outline level' refer to?
7021 // \w Preserve tab characters within table entries
7022 if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
7024 bIsTabEntry = true ;
7026 // \x Preserve newline characters within table entries
7027 if( lcl_FindInCommand( pContext->GetCommand(), 'x', sValue ))
7029 bNewLine = true ;
7031 // \z Hides page numbers within the table of contents when shown in Web Layout View
7032 if( lcl_FindInCommand( pContext->GetCommand(), 'z', sValue ))
7034 bHideTabLeaderPageNumbers = true ;
7037 //if there's no option then it should be created from outline
7038 if( !bFromOutline && !bFromEntries && sTemplate.isEmpty() )
7039 bFromOutline = true;
7041 const OUString aTocTitle = extractTocTitle();
7043 uno::Reference<beans::XPropertySet> xTOC;
7045 if (m_xTextDocument && ! m_aTextAppendStack.empty())
7047 const auto& xTextAppend = GetTopTextAppend();
7048 if (aTocTitle.isEmpty() || bTableOfFigures)
7050 // reset marker of the TOC title
7051 m_StreamStateStack.top().xSdtEntryStart.clear();
7053 // Create section before setting m_bStartTOC: finishing paragraph
7054 // inside StartIndexSectionChecked could do the wrong thing otherwise
7055 xTOC = StartIndexSectionChecked(bTableOfFigures ? "com.sun.star.text.IllustrationsIndex"
7056 : sTOCServiceName);
7058 const auto xTextCursor = xTextAppend->getText()->createTextCursor();
7059 if (xTextCursor)
7060 xTextCursor->gotoEnd(false);
7061 m_xTOCMarkerCursor = xTextCursor;
7063 else
7065 // create TOC section
7066 css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd();
7067 xTOC = createSectionForRange(m_StreamStateStack.top().xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false);
7069 // init [xTOCMarkerCursor]
7070 uno::Reference< text::XText > xText = xTextAppend->getText();
7071 m_xTOCMarkerCursor = xText->createTextCursor();
7073 // create header of the TOC with the TOC title inside
7074 createSectionForRange(m_StreamStateStack.top().xSdtEntryStart, xTextRangeEndOfTocHeader, "com.sun.star.text.IndexHeaderSection", true);
7078 m_bStartTOC = true;
7079 pContext->SetTOC(xTOC);
7080 m_StreamStateStack.top().bParaHadField = false;
7082 if (!xTOC)
7083 return;
7085 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(aTocTitle));
7087 if (!aBookmarkName.isEmpty())
7088 xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::Any(aBookmarkName));
7089 if (!bTableOfFigures)
7091 xTOC->setPropertyValue( getPropertyName( PROP_LEVEL ), uno::Any( nMaxLevel ) );
7092 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_OUTLINE ), uno::Any( bFromOutline ));
7093 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_MARKS ), uno::Any( bFromEntries ));
7094 xTOC->setPropertyValue( getPropertyName( PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS ), uno::Any( bHideTabLeaderPageNumbers ));
7095 xTOC->setPropertyValue( getPropertyName( PROP_TAB_IN_TOC ), uno::Any( bIsTabEntry ));
7096 xTOC->setPropertyValue( getPropertyName( PROP_TOC_NEW_LINE ), uno::Any( bNewLine ));
7097 xTOC->setPropertyValue( getPropertyName( PROP_TOC_PARAGRAPH_OUTLINE_LEVEL ), uno::Any( bParagraphOutlineLevel ));
7098 if( !sTemplate.isEmpty() )
7100 //the string contains comma separated the names and related levels
7101 //like: "Heading 1,1,Heading 2,2"
7102 TOCStyleMap aMap;
7103 sal_Int32 nLevel;
7104 sal_Int32 nPosition = 0;
7105 auto const tsep(sTemplate.indexOf(',') != -1 ? ',' : ';');
7106 while( nPosition >= 0)
7108 OUString sStyleName = sTemplate.getToken(0, tsep, nPosition);
7109 //empty tokens should be skipped
7110 while( sStyleName.isEmpty() && nPosition > 0 )
7111 sStyleName = sTemplate.getToken(0, tsep, nPosition);
7112 nLevel = o3tl::toInt32(o3tl::getToken(sTemplate, 0, tsep, nPosition ));
7113 if( !nLevel )
7114 nLevel = 1;
7115 if( !sStyleName.isEmpty() )
7116 aMap.emplace(nLevel, sStyleName);
7118 uno::Reference< container::XIndexReplace> xParaStyles;
7119 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_PARAGRAPH_STYLES)) >>= xParaStyles;
7120 for( nLevel = 1; nLevel < 10; ++nLevel)
7122 sal_Int32 nLevelCount = aMap.count( nLevel );
7123 if( nLevelCount )
7125 TOCStyleMap::iterator aTOCStyleIter = aMap.find( nLevel );
7127 uno::Sequence< OUString> aStyles( nLevelCount );
7128 for ( auto& rStyle : asNonConstRange(aStyles) )
7130 // tdf#153083 must map w:styleId to w:name
7131 rStyle = ConvertTOCStyleName(aTOCStyleIter->second);
7132 ++aTOCStyleIter;
7134 xParaStyles->replaceByIndex(nLevel - 1, uno::Any(aStyles));
7137 xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true ));
7141 uno::Reference<container::XIndexAccess> xChapterNumberingRules;
7142 if (m_xTextDocument)
7143 xChapterNumberingRules = m_xTextDocument->getChapterNumberingRules();
7144 uno::Reference<container::XNameContainer> xStyles;
7145 if (m_xTextDocument)
7147 auto xStyleFamilies = m_xTextDocument->getStyleFamilies();
7148 xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES)) >>= xStyles;
7151 uno::Reference< container::XIndexReplace> xLevelFormats;
7152 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
7153 sal_Int32 nLevelCount = xLevelFormats->getCount();
7154 //start with level 1, 0 is the header level
7155 for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel)
7157 uno::Sequence< beans::PropertyValues > aLevel;
7158 xLevelFormats->getByIndex( nLevel ) >>= aLevel;
7160 // Get the tab stops coming from the styles; store to the level definitions
7161 std::optional<style::TabStop> numTab;
7162 if (xChapterNumberingRules && xStyles)
7164 // This relies on the chapter numbering rules already defined
7165 // (see ListDef::CreateNumberingRules)
7166 uno::Sequence<beans::PropertyValue> props;
7167 xChapterNumberingRules->getByIndex(nLevel - 1) >>= props;
7168 bool bHasNumbering = false;
7169 bool bUseTabStop = false;
7170 for (const auto& propval : props)
7172 // We rely on PositionAndSpaceMode being always equal to LABEL_ALIGNMENT,
7173 // because ListDef::CreateNumberingRules doesn't create legacy lists
7174 if (propval.Name == "NumberingType")
7175 bHasNumbering = propval.Value != style::NumberingType::NUMBER_NONE;
7176 else if (propval.Name == "LabelFollowedBy")
7177 bUseTabStop = propval.Value == text::LabelFollow::LISTTAB;
7178 // Do not use FirstLineIndent property from the rules, because it is unreliable
7180 if (bHasNumbering && bUseTabStop)
7182 OUString style;
7183 xTOC->getPropertyValue("ParaStyleLevel" + OUString::number(nLevel)) >>= style;
7184 uno::Reference<beans::XPropertySet> xStyle;
7185 if (xStyles->getByName(style) >>= xStyle)
7187 if (uno::Reference<beans::XPropertyState> xPropState{ xStyle,
7188 uno::UNO_QUERY })
7190 if (xPropState->getPropertyState("ParaTabStops")
7191 == beans::PropertyState_DIRECT_VALUE)
7193 if (uno::Sequence<style::TabStop> tabStops;
7194 xStyle->getPropertyValue("ParaTabStops") >>= tabStops)
7196 // If the style only has one tab stop, Word uses it for
7197 // page number, and generates the other from defaults
7198 if (tabStops.getLength() > 1)
7199 numTab = tabStops[0];
7204 if (!numTab)
7206 // Generate the default position.
7207 // Word uses multiples of 440 twips for default chapter number tab stops
7208 numTab.emplace();
7209 numTab->Position
7210 = o3tl::convert(440 * nLevel, o3tl::Length::twip, o3tl::Length::mm100);
7215 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
7216 bHyperlinks, sChapterNoSeparator,
7217 aLevel, numTab);
7218 xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) );
7221 else // if (bTableOfFigures)
7223 if (!sFigureSequence.isEmpty())
7224 xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY),
7225 uno::Any(sFigureSequence));
7227 if (!sTemplate.isEmpty())
7229 OUString const sConvertedStyleName(ConvertTOCStyleName(sTemplate));
7230 xTOC->setPropertyValue("CreateFromParagraphStyle", uno::Any(sConvertedStyleName));
7233 if ( bHyperlinks )
7235 uno::Reference< container::XIndexReplace> xLevelFormats;
7236 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
7237 uno::Sequence< beans::PropertyValues > aLevel;
7238 xLevelFormats->getByIndex( 1 ) >>= aLevel;
7240 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
7241 bHyperlinks, sChapterNoSeparator,
7242 aLevel, {});
7243 xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) );
7248 uno::Reference<beans::XPropertySet> DomainMapper_Impl::createSectionForRange(
7249 uno::Reference< css::text::XTextRange > xStart,
7250 uno::Reference< css::text::XTextRange > xEnd,
7251 const OUString & sObjectType,
7252 bool stepLeft)
7254 if (!xStart.is())
7255 return uno::Reference<beans::XPropertySet>();
7256 if (!xEnd.is())
7257 return uno::Reference<beans::XPropertySet>();
7259 uno::Reference< beans::XPropertySet > xRet;
7260 if (m_aTextAppendStack.empty())
7261 return xRet;
7262 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
7263 if(xTextAppend.is())
7267 uno::Reference< text::XParagraphCursor > xCursor(
7268 xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY_THROW);
7269 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
7270 xCursor->gotoStartOfParagraph( false );
7271 xCursor->gotoRange( xEnd, true );
7272 //the paragraph after this new section is already inserted
7273 if (stepLeft)
7274 xCursor->goLeft(1, true);
7275 uno::Reference< text::XTextContent > xSection( m_xTextDocument->createInstance(sObjectType), uno::UNO_QUERY_THROW );
7278 xSection->attach( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW) );
7280 catch(const uno::Exception&)
7283 xRet.set(xSection, uno::UNO_QUERY );
7285 catch(const uno::Exception&)
7290 return xRet;
7293 void DomainMapper_Impl::handleBibliography
7294 (const FieldContextPtr& pContext,
7295 const OUString & sTOCServiceName)
7297 if (m_aTextAppendStack.empty())
7299 // tdf#130214: a workaround to avoid crash on import errors
7300 SAL_WARN("writerfilter.dmapper", "no text append stack");
7301 return;
7303 // Create section before setting m_bStartTOC and m_bStartBibliography: finishing paragraph
7304 // inside StartIndexSectionChecked could do the wrong thing otherwise
7305 const auto xTOC = StartIndexSectionChecked(sTOCServiceName);
7306 m_bStartTOC = true;
7307 m_bStartBibliography = true;
7309 if (xTOC.is())
7310 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString()));
7312 pContext->SetTOC( xTOC );
7313 m_StreamStateStack.top().bParaHadField = false;
7315 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
7316 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
7319 void DomainMapper_Impl::handleIndex
7320 (const FieldContextPtr& pContext,
7321 const OUString & sTOCServiceName)
7323 // only UserIndex can handle user index defined by \f
7324 // e.g. INDEX \f "user-index-id"
7325 OUString sUserIndex;
7326 if ( lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) )
7327 sUserIndex = lcl_trim(sUserIndex);
7329 // Create section before setting m_bStartTOC and m_bStartIndex: finishing paragraph
7330 // inside StartIndexSectionChecked could do the wrong thing otherwise
7331 const auto xTOC = StartIndexSectionChecked( sUserIndex.isEmpty()
7332 ? sTOCServiceName
7333 : "com.sun.star.text.UserIndex");
7335 m_bStartTOC = true;
7336 m_bStartIndex = true;
7337 OUString sValue;
7338 if (xTOC.is())
7340 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString()));
7342 if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
7344 xTOC->setPropertyValue("IsCommaSeparated", uno::Any(true));
7346 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
7348 xTOC->setPropertyValue("UseAlphabeticalSeparators", uno::Any(true));
7350 if( !sUserIndex.isEmpty() )
7352 xTOC->setPropertyValue("UserIndexName", uno::Any(sUserIndex));
7355 pContext->SetTOC( xTOC );
7356 m_StreamStateStack.top().bParaHadField = false;
7358 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
7359 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
7361 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
7363 sValue = sValue.replaceAll("\"", "");
7364 uno::Reference<text::XTextColumns> xTextColumns;
7365 if (xTOC.is())
7367 xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns;
7369 if (xTextColumns.is())
7371 xTextColumns->setColumnCount( sValue.toInt32() );
7372 xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xTextColumns ) );
7377 static auto InsertFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
7378 uno::Reference<text::XFormField> const& xFormField,
7379 uno::Reference<text::XTextRange> const& xStartRange,
7380 std::optional<FieldId> const oFieldId) -> void
7382 uno::Reference<text::XTextContent> const xTextContent(xFormField, uno::UNO_QUERY_THROW);
7383 uno::Reference<text::XTextAppend> const& xTextAppend(rTextAppendStack.top().xTextAppend);
7384 uno::Reference<text::XTextCursor> const xCursor =
7385 xTextAppend->createTextCursorByRange(xStartRange);
7386 if (rTextAppendStack.top().xInsertPosition.is())
7388 uno::Reference<text::XTextRangeCompare> const xCompare(
7389 rTextAppendStack.top().xTextAppend,
7390 uno::UNO_QUERY);
7391 if (xCompare->compareRegionStarts(xStartRange, rTextAppendStack.top().xInsertPosition) < 0)
7393 SAL_WARN("writerfilter.dmapper", "invalid field mark positions");
7394 assert(false);
7396 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, true);
7398 else
7400 xCursor->gotoEnd(true);
7402 xTextAppend->insertTextContent(xCursor, xTextContent, true);
7403 if (oFieldId
7404 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
7406 return; // only a single CH_TXT_ATR_FORMELEMENT!
7408 // problem: the fieldmark must be inserted in CloseFieldCommand(), because
7409 // attach() takes 2 positions, not 3!
7410 // FAIL: AppendTextNode() ignores the content index!
7411 // plan B: insert a spurious paragraph break now and join
7412 // it in PopFieldContext()!
7413 xCursor->gotoRange(xTextContent->getAnchor()->getEnd(), false);
7414 xCursor->goLeft(1, false); // skip CH_TXT_ATR_FIELDEND
7415 xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::PARAGRAPH_BREAK, false);
7416 xCursor->goLeft(1, false); // back to previous paragraph
7417 rTextAppendStack.push(TextAppendContext(xTextAppend, xCursor));
7420 static auto PopFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
7421 uno::Reference<text::XTextCursor> const& xCursor,
7422 std::optional<FieldId> const oFieldId) -> void
7424 if (oFieldId
7425 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
7427 return; // only a single CH_TXT_ATR_FORMELEMENT!
7429 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, false);
7430 xCursor->goRight(1, true);
7431 xCursor->setString(OUString()); // undo SplitNode from CloseFieldCommand()
7432 // note: paragraph properties will be overwritten
7433 // by finishParagraph() anyway so ignore here
7434 rTextAppendStack.pop();
7437 void DomainMapper_Impl::CloseFieldCommand()
7439 if(m_bDiscardHeaderFooter)
7440 return;
7441 #ifdef DBG_UTIL
7442 TagLogger::getInstance().element("closeFieldCommand");
7443 #endif
7445 FieldContextPtr pContext;
7446 if(!m_aFieldStack.empty())
7447 pContext = m_aFieldStack.back();
7448 OSL_ENSURE( pContext, "no field context available");
7449 if( !pContext )
7450 return;
7452 pContext->m_bSetUserFieldContent = false;
7453 pContext->m_bSetCitation = false;
7454 pContext->m_bSetDateValue = false;
7455 // tdf#124472: If the normal command line is not empty, use it,
7456 // otherwise, the last active row is evaluated.
7457 if (!pContext->GetCommandIsEmpty(false))
7458 pContext->SetCommandType(false);
7460 const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion();
7464 const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) };
7465 (void)vSwitches;
7466 OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front());
7468 // apply character properties to the form control
7469 if (!m_aTextAppendStack.empty() && m_pLastCharacterContext)
7471 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
7472 if (xTextAppend.is())
7474 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
7475 if (xCrsr.is())
7477 xCrsr->gotoEnd(false);
7478 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
7479 for (auto& rPropValue : m_pLastCharacterContext->GetPropertyValues(false))
7483 xProp->setPropertyValue(rPropValue.Name, rPropValue.Value);
7485 catch(uno::Exception&)
7487 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Unknown Field PropVal");
7494 FieldConversionMap_t::const_iterator const aIt = aFieldConversionMap.find(sType);
7495 if (aIt != aFieldConversionMap.end()
7496 && (!m_bForceGenericFields
7497 // these need to convert ffData to properties...
7498 || (aIt->second.eFieldId == FIELD_FORMCHECKBOX)
7499 || (aIt->second.eFieldId == FIELD_FORMDROPDOWN)
7500 || (aIt->second.eFieldId == FIELD_FORMTEXT)))
7502 pContext->SetFieldId(aIt->second.eFieldId);
7503 bool bCreateEnhancedField = false;
7504 uno::Reference< beans::XPropertySet > xFieldProperties;
7505 bool bCreateField = true;
7506 switch (aIt->second.eFieldId)
7508 case FIELD_HYPERLINK:
7509 case FIELD_DOCPROPERTY:
7510 case FIELD_TOC:
7511 case FIELD_INDEX:
7512 case FIELD_XE:
7513 case FIELD_BIBLIOGRAPHY:
7514 case FIELD_CITATION:
7515 case FIELD_TC:
7516 case FIELD_EQ:
7517 case FIELD_INCLUDEPICTURE:
7518 case FIELD_SYMBOL:
7519 case FIELD_GOTOBUTTON:
7520 bCreateField = false;
7521 break;
7522 case FIELD_FORMCHECKBOX :
7523 case FIELD_FORMTEXT :
7524 case FIELD_FORMDROPDOWN :
7526 // If we use 'enhanced' fields then FIELD_FORMCHECKBOX,
7527 // FIELD_FORMTEXT & FIELD_FORMDROPDOWN are treated specially
7528 if ( m_bUsingEnhancedFields )
7530 bCreateField = false;
7531 bCreateEnhancedField = true;
7533 // for non enhanced fields checkboxes are displayed
7534 // as an awt control not a field
7535 else if ( aIt->second.eFieldId == FIELD_FORMCHECKBOX )
7536 bCreateField = false;
7537 break;
7539 default:
7541 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
7542 if (pOuter)
7544 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
7546 // Parent field can't host this child field: don't create a child field
7547 // in this case.
7548 bCreateField = false;
7551 break;
7554 if (IsInTOC() && (aIt->second.eFieldId == FIELD_PAGEREF))
7556 bCreateField = false;
7559 uno::Reference< uno::XInterface > xFieldInterface;
7560 if( bCreateField || bCreateEnhancedField )
7562 //add the service prefix
7563 OUString sServiceName("com.sun.star.text.");
7564 if ( bCreateEnhancedField )
7566 const FieldConversionMap_t& aEnhancedFieldConversionMap = lcl_GetEnhancedFieldConversion();
7567 FieldConversionMap_t::const_iterator aEnhancedIt =
7568 aEnhancedFieldConversionMap.find(sType);
7569 if ( aEnhancedIt != aEnhancedFieldConversionMap.end())
7570 sServiceName += OUString::createFromAscii(aEnhancedIt->second.cFieldServiceName );
7572 else
7574 sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName );
7577 #ifdef DBG_UTIL
7578 TagLogger::getInstance().startElement("fieldService");
7579 TagLogger::getInstance().chars(sServiceName);
7580 TagLogger::getInstance().endElement();
7581 #endif
7583 if (m_xTextDocument)
7585 xFieldInterface = m_xTextDocument->createInstance(sServiceName);
7586 xFieldProperties.set( xFieldInterface, uno::UNO_QUERY_THROW);
7589 switch( aIt->second.eFieldId )
7591 case FIELD_ADDRESSBLOCK: break;
7592 case FIELD_ADVANCE : break;
7593 case FIELD_ASK :
7594 handleFieldAsk(pContext, xFieldInterface, xFieldProperties);
7595 break;
7596 case FIELD_AUTONUM :
7597 case FIELD_AUTONUMLGL :
7598 case FIELD_AUTONUMOUT :
7599 handleAutoNum(pContext, xFieldInterface, xFieldProperties);
7600 break;
7601 case FIELD_AUTHOR :
7602 case FIELD_USERNAME :
7603 case FIELD_USERINITIALS :
7604 handleAuthor(sFirstParam,
7605 xFieldProperties,
7606 aIt->second.eFieldId);
7607 break;
7608 case FIELD_DATE:
7609 if (xFieldProperties.is())
7611 // Get field fixed property from the context handler
7612 if (pContext->IsFieldLocked())
7614 xFieldProperties->setPropertyValue(
7615 getPropertyName(PROP_IS_FIXED),
7616 uno::Any( true ));
7617 pContext->m_bSetDateValue = true;
7619 else
7620 xFieldProperties->setPropertyValue(
7621 getPropertyName(PROP_IS_FIXED),
7622 uno::Any( false ));
7624 xFieldProperties->setPropertyValue(
7625 getPropertyName(PROP_IS_DATE),
7626 uno::Any( true ));
7627 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7629 break;
7630 case FIELD_COMMENTS :
7632 // OUString sParam = lcl_ExtractParameter(pContext->GetCommand(), sizeof(" COMMENTS") );
7633 // A parameter with COMMENTS shouldn't set fixed
7634 // ( or at least the binary filter doesn't )
7635 // If we set fixed then we won't export a field cmd.
7636 // Additionally the para in COMMENTS is more like an
7637 // instruction to set the document property comments
7638 // with the param ( e.g. each COMMENT with a param will
7639 // overwrite the Comments document property
7640 // #TODO implement the above too
7641 xFieldProperties->setPropertyValue(
7642 getPropertyName( PROP_IS_FIXED ), uno::Any( false ));
7643 //PROP_CURRENT_PRESENTATION is set later anyway
7645 break;
7646 case FIELD_CREATEDATE :
7647 case FIELD_PRINTDATE:
7648 case FIELD_SAVEDATE:
7650 if (pContext->IsFieldLocked())
7652 xFieldProperties->setPropertyValue(
7653 getPropertyName(PROP_IS_FIXED), uno::Any( true ));
7655 xFieldProperties->setPropertyValue(
7656 getPropertyName( PROP_IS_DATE ), uno::Any( true ));
7657 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7659 break;
7660 case FIELD_DOCPROPERTY :
7661 handleDocProperty(pContext, sFirstParam,
7662 xFieldInterface);
7663 break;
7664 case FIELD_DOCVARIABLE :
7666 if (bCreateField)
7668 //create a user field and type
7669 uno::Reference<beans::XPropertySet> xMaster = FindOrCreateFieldMaster(
7670 "com.sun.star.text.FieldMaster.User", sFirstParam);
7671 uno::Reference<text::XDependentTextField> xDependentField(
7672 xFieldInterface, uno::UNO_QUERY_THROW);
7673 xDependentField->attachTextFieldMaster(xMaster);
7674 pContext->m_bSetUserFieldContent = true;
7677 break;
7678 case FIELD_EDITTIME :
7679 //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7680 break;
7681 case FIELD_EQ:
7683 OUString aCommand = pContext->GetCommand().trim();
7685 msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand));
7686 if (!aResult.sType.isEmpty() && m_xTextDocument)
7688 xFieldInterface = m_xTextDocument->createInstance("com.sun.star.text.TextField." + aResult.sType);
7689 xFieldProperties =
7690 uno::Reference< beans::XPropertySet >( xFieldInterface,
7691 uno::UNO_QUERY_THROW);
7692 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(aResult.sResult));
7694 else
7696 //merge Read_SubF_Ruby into filter/.../util.cxx and reuse that ?
7697 sal_Int32 nSpaceIndex = aCommand.indexOf(' ');
7698 if(nSpaceIndex > 0)
7699 aCommand = o3tl::trim(aCommand.subView(nSpaceIndex));
7700 if (aCommand.startsWith("\\s"))
7702 aCommand = aCommand.copy(2);
7703 if (aCommand.startsWith("\\do"))
7705 aCommand = aCommand.copy(3);
7706 sal_Int32 nStartIndex = aCommand.indexOf('(');
7707 sal_Int32 nEndIndex = aCommand.indexOf(')');
7708 if (nStartIndex > 0 && nEndIndex > 0)
7710 // nDown is the requested "lower by" value in points.
7711 sal_Int32 nDown = o3tl::toInt32(aCommand.subView(0, nStartIndex));
7712 OUString aContent = aCommand.copy(nStartIndex + 1, nEndIndex - nStartIndex - 1);
7713 PropertyMapPtr pCharContext = GetTopContext();
7714 // dHeight is the font size of the current style.
7715 double dHeight = 0;
7716 if ((GetPropertyFromParaStyleSheet(PROP_CHAR_HEIGHT) >>= dHeight) && dHeight != 0)
7717 // Character escapement should be given in negative percents for subscripts.
7718 pCharContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( sal_Int16(- 100 * nDown / dHeight) ) );
7719 appendTextPortion(aContent, pCharContext);
7723 else if (aCommand.startsWith("\\* jc"))
7725 handleRubyEQField(pContext);
7729 break;
7730 case FIELD_FILLIN :
7731 if (xFieldProperties.is())
7732 xFieldProperties->setPropertyValue(
7733 getPropertyName(PROP_HINT), uno::Any( pContext->GetCommand().getToken(1, '\"')));
7734 break;
7735 case FIELD_FILENAME:
7737 sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p");
7738 if (xFieldProperties.is())
7739 xFieldProperties->setPropertyValue(
7740 getPropertyName(PROP_FILE_FORMAT),
7741 uno::Any( nNumberingTypeIndex > 0 ? text::FilenameDisplayFormat::FULL : text::FilenameDisplayFormat::NAME_AND_EXT ));
7743 break;
7744 case FIELD_FILESIZE : break;
7745 case FIELD_FORMULA :
7746 if (bCreateField)
7748 handleFieldFormula(pContext, xFieldProperties);
7750 break;
7751 case FIELD_FORMCHECKBOX :
7752 case FIELD_FORMDROPDOWN :
7753 case FIELD_FORMTEXT :
7755 if (bCreateEnhancedField)
7757 FFDataHandler::Pointer_t
7758 pFFDataHandler(pContext->getFFDataHandler());
7759 FormControlHelper::Pointer_t
7760 pFormControlHelper(new FormControlHelper
7761 (m_bUsingEnhancedFields ? aIt->second.eFieldId : FIELD_FORMCHECKBOX,
7763 m_xTextDocument, pFFDataHandler));
7764 pContext->setFormControlHelper(pFormControlHelper);
7765 uno::Reference< text::XFormField > xFormField( xFieldInterface, uno::UNO_QUERY );
7766 uno::Reference< container::XNamed > xNamed( xFormField, uno::UNO_QUERY );
7767 if ( xNamed.is() )
7769 if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() )
7770 xNamed->setName( pFFDataHandler->getName() );
7771 pContext->SetFormField( xFormField );
7773 InsertFieldmark(m_aTextAppendStack,
7774 xFormField, pContext->GetStartRange(),
7775 pContext->GetFieldId());
7777 else
7779 if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN )
7780 lcl_handleDropdownField( xFieldProperties, pContext->getFFDataHandler() );
7781 else
7782 lcl_handleTextField( xFieldProperties, pContext->getFFDataHandler() );
7785 break;
7786 case FIELD_GOTOBUTTON : break;
7787 case FIELD_HYPERLINK:
7789 ::std::vector<OUString> aParts = pContext->GetCommandParts();
7791 // Syntax is either:
7792 // HYPERLINK "" \l "link"
7793 // or
7794 // HYPERLINK \l "link"
7795 // Make sure "HYPERLINK" doesn't end up as part of link in the second case.
7796 if (!aParts.empty() && aParts[0] == "HYPERLINK")
7797 aParts.erase(aParts.begin());
7799 ::std::vector<OUString>::const_iterator aItEnd = aParts.end();
7800 ::std::vector<OUString>::const_iterator aPartIt = aParts.begin();
7802 OUString sURL;
7803 OUString sTarget;
7805 while (aPartIt != aItEnd)
7807 if ( *aPartIt == "\\l" )
7809 ++aPartIt;
7811 if (aPartIt == aItEnd)
7812 break;
7814 sURL += "#" + *aPartIt;
7816 else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h")
7819 else if ( *aPartIt == "\\o" || *aPartIt == "\\t" )
7821 ++aPartIt;
7823 if (aPartIt == aItEnd)
7824 break;
7826 sTarget = *aPartIt;
7828 else
7830 sURL = *aPartIt;
7833 ++aPartIt;
7836 if (!sURL.isEmpty())
7838 if (sURL.startsWith("file:///"))
7840 // file:///absolute\\path\\to\\file => invalid file URI (Writer cannot open)
7841 // convert all double backslashes to slashes:
7842 sURL = sURL.replaceAll("\\\\", "/");
7844 // file:///absolute\path\to\file => invalid file URI (Writer cannot open)
7845 // convert all backslashes to slashes:
7846 sURL = sURL.replace('\\', '/');
7848 // Try to make absolute any relative URLs, except
7849 // for relative same-document URLs that only contain
7850 // a fragment part:
7851 else if (!sURL.startsWith("#")) {
7852 try {
7853 sURL = rtl::Uri::convertRelToAbs(
7854 m_aBaseUrl, sURL);
7855 } catch (rtl::MalformedUriException & e) {
7856 SAL_WARN(
7857 "writerfilter.dmapper",
7858 "MalformedUriException "
7859 << e.getMessage());
7862 pContext->SetHyperlinkURL(sURL);
7865 if (!sTarget.isEmpty())
7866 pContext->SetHyperlinkTarget(sTarget);
7868 break;
7869 case FIELD_IF:
7871 if (vArguments.size() < 3)
7873 SAL_WARN("writerfilter.dmapper", "IF field requires at least 3 parameters!");
7874 break;
7877 if (xFieldProperties.is())
7879 // Following code assumes that last argument in field is false value
7880 // before it - true value and everything before them is a condition
7881 OUString sCondition;
7882 size_t i = 0;
7883 while (i < vArguments.size() - 2) {
7884 if (!sCondition.isEmpty())
7885 sCondition += " ";
7886 sCondition += vArguments[i++];
7889 xFieldProperties->setPropertyValue(
7890 "TrueContent", uno::Any(vArguments[vArguments.size() - 2]));
7891 xFieldProperties->setPropertyValue(
7892 "FalseContent", uno::Any(vArguments[vArguments.size() - 1]));
7893 xFieldProperties->setPropertyValue(
7894 "Condition", uno::Any(sCondition));
7897 break;
7898 case FIELD_INFO : break;
7899 case FIELD_INCLUDEPICTURE: break;
7900 case FIELD_KEYWORDS :
7902 if (!sFirstParam.isEmpty())
7904 xFieldProperties->setPropertyValue(
7905 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
7906 //PROP_CURRENT_PRESENTATION is set later anyway
7909 break;
7910 case FIELD_LASTSAVEDBY :
7911 xFieldProperties->setPropertyValue(
7912 getPropertyName(PROP_IS_FIXED), uno::Any(true));
7913 break;
7914 case FIELD_MACROBUTTON:
7916 if (xFieldProperties.is())
7918 sal_Int32 nIndex = sizeof(" MACROBUTTON ");
7919 OUString sCommand = pContext->GetCommand();
7921 //extract macro name
7922 if (sCommand.getLength() >= nIndex)
7924 OUString sMacro = sCommand.getToken(0, ' ', nIndex);
7925 xFieldProperties->setPropertyValue(
7926 getPropertyName(PROP_MACRO_NAME), uno::Any( sMacro ));
7929 //extract quick help text
7930 if (sCommand.getLength() > nIndex + 1)
7932 xFieldProperties->setPropertyValue(
7933 getPropertyName(PROP_HINT),
7934 uno::Any( sCommand.copy( nIndex )));
7938 break;
7939 case FIELD_MERGEFIELD :
7941 //todo: create a database field and fieldmaster pointing to a column, only
7942 //create a user field and type
7943 uno::Reference< beans::XPropertySet > xMaster =
7944 FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam);
7946 // xFieldProperties->setPropertyValue(
7947 // "FieldCode",
7948 // uno::makeAny( pContext->GetCommand().copy( nIndex + 1 )));
7949 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
7950 xDependentField->attachTextFieldMaster( xMaster );
7952 break;
7953 case FIELD_MERGEREC : break;
7954 case FIELD_MERGESEQ : break;
7955 case FIELD_NEXT : break;
7956 case FIELD_NEXTIF : break;
7957 case FIELD_PAGE :
7958 if (xFieldProperties.is())
7960 xFieldProperties->setPropertyValue(
7961 getPropertyName(PROP_NUMBERING_TYPE),
7962 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
7963 xFieldProperties->setPropertyValue(
7964 getPropertyName(PROP_SUB_TYPE),
7965 uno::Any( text::PageNumberType_CURRENT ));
7968 break;
7969 case FIELD_PAGEREF:
7970 case FIELD_REF:
7971 case FIELD_STYLEREF:
7972 if (xFieldProperties.is() && !IsInTOC())
7974 bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF;
7975 bool bStyleRef = aIt->second.eFieldId == FIELD_STYLEREF;
7977 // Do we need a GetReference (default) or a GetExpression field?
7978 uno::Reference< container::XNameAccess > xFieldMasterAccess = GetTextDocument()->getTextFieldMasters();
7980 if (!xFieldMasterAccess->hasByName(
7981 "com.sun.star.text.FieldMaster.SetExpression."
7982 + sFirstParam))
7984 if (bStyleRef)
7986 xFieldProperties->setPropertyValue(
7987 getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
7988 uno::Any(sal_Int16(text::ReferenceFieldSource::STYLE)));
7990 OUString sStyleSheetName
7991 = GetStyleSheetTable()->ConvertStyleName(sFirstParam, true);
7993 uno::Any aStyleDisplayName;
7995 uno::Reference<container::XNameAccess> xStyleFamilies
7996 = GetTextDocument()->getStyleFamilies();
7997 uno::Reference<container::XNameAccess> xStyles;
7998 xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES))
7999 >>= xStyles;
8000 uno::Reference<css::beans::XPropertySet> xStyle;
8004 xStyles->getByName(sStyleSheetName) >>= xStyle;
8005 aStyleDisplayName = xStyle->getPropertyValue("DisplayName");
8007 catch (css::container::NoSuchElementException)
8009 aStyleDisplayName <<= sStyleSheetName;
8012 xFieldProperties->setPropertyValue(
8013 getPropertyName(PROP_SOURCE_NAME), aStyleDisplayName);
8015 sal_uInt16 nFlags = 0;
8016 OUString sValue;
8017 if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
8019 //search-below-first
8020 nFlags |= REFFLDFLAG_STYLE_FROM_BOTTOM;
8022 if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
8024 //suppress-nondelimiter
8025 nFlags |= REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL;
8027 xFieldProperties->setPropertyValue(
8028 getPropertyName( PROP_REFERENCE_FIELD_FLAGS ), uno::Any(nFlags) );
8030 else
8032 xFieldProperties->setPropertyValue(
8033 getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
8034 uno::Any( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) );
8036 xFieldProperties->setPropertyValue(
8037 getPropertyName(PROP_SOURCE_NAME),
8038 uno::Any(sFirstParam));
8041 sal_Int16 nFieldPart = (bPageRef ? text::ReferenceFieldPart::PAGE : text::ReferenceFieldPart::TEXT);
8042 OUString sValue;
8043 if( lcl_FindInCommand( pContext->GetCommand(), 'p', sValue ))
8045 //above-below
8046 nFieldPart = text::ReferenceFieldPart::UP_DOWN;
8048 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
8050 //number
8051 nFieldPart = text::ReferenceFieldPart::NUMBER;
8053 else if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
8055 //number-no-context
8056 nFieldPart = text::ReferenceFieldPart::NUMBER_NO_CONTEXT;
8058 else if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
8060 //number-full-context
8061 nFieldPart = text::ReferenceFieldPart::NUMBER_FULL_CONTEXT;
8063 xFieldProperties->setPropertyValue(
8064 getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::Any( nFieldPart ));
8066 else if( m_xTextDocument )
8068 xFieldInterface = m_xTextDocument->createInstance("com.sun.star.text.TextField.GetExpression");
8069 xFieldProperties.set(xFieldInterface, uno::UNO_QUERY);
8070 xFieldProperties->setPropertyValue(
8071 getPropertyName(PROP_CONTENT),
8072 uno::Any(sFirstParam));
8073 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
8076 break;
8077 case FIELD_REVNUM : break;
8078 case FIELD_SECTION : break;
8079 case FIELD_SECTIONPAGES : break;
8080 case FIELD_SEQ :
8082 // command looks like: " SEQ Table \* ARABIC "
8083 OUString sCmd(pContext->GetCommand());
8084 // find the sequence name, e.g. "SEQ"
8085 std::u16string_view sSeqName = msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\');
8086 sSeqName = o3tl::trim(sSeqName);
8088 // create a sequence field master using the sequence name
8089 uno::Reference< beans::XPropertySet > xMaster = FindOrCreateFieldMaster(
8090 "com.sun.star.text.FieldMaster.SetExpression",
8091 OUString(sSeqName));
8093 xMaster->setPropertyValue(
8094 getPropertyName(PROP_SUB_TYPE),
8095 uno::Any(text::SetVariableType::SEQUENCE));
8097 // apply the numbering type
8098 xFieldProperties->setPropertyValue(
8099 getPropertyName(PROP_NUMBERING_TYPE),
8100 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
8102 // attach the master to the field
8103 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
8104 xDependentField->attachTextFieldMaster( xMaster );
8106 OUString sFormula = OUString::Concat(sSeqName) + "+1";
8107 OUString sValue;
8108 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
8110 sFormula = sSeqName;
8112 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
8114 sFormula = sValue;
8116 // TODO \s isn't handled, but the spec isn't easy to understand without
8117 // an example for this one.
8118 xFieldProperties->setPropertyValue(
8119 getPropertyName(PROP_CONTENT),
8120 uno::Any(sFormula));
8122 // Take care of the numeric formatting definition, default is Arabic
8123 sal_Int16 nNumberingType = lcl_ParseNumberingType(pContext->GetCommand());
8124 if (nNumberingType == style::NumberingType::PAGE_DESCRIPTOR)
8125 nNumberingType = style::NumberingType::ARABIC;
8126 xFieldProperties->setPropertyValue(
8127 getPropertyName(PROP_NUMBERING_TYPE),
8128 uno::Any(nNumberingType));
8130 break;
8131 case FIELD_SET :
8132 handleFieldSet(pContext, xFieldInterface, xFieldProperties);
8133 break;
8134 case FIELD_SKIPIF : break;
8135 case FIELD_SUBJECT :
8137 if (!sFirstParam.isEmpty())
8139 xFieldProperties->setPropertyValue(
8140 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
8141 //PROP_CURRENT_PRESENTATION is set later anyway
8144 break;
8145 case FIELD_SYMBOL:
8147 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8148 OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ? o3tl::toUInt32(sFirstParam.subView(2),16) : sFirstParam.toUInt32() ) );
8149 OUString sFont;
8150 bool bHasFont = lcl_FindInCommand( pContext->GetCommand(), 'f', sFont);
8151 if ( bHasFont )
8153 sFont = sFont.trim();
8154 if (sFont.startsWith("\""))
8155 sFont = sFont.copy(1);
8156 if (sFont.endsWith("\""))
8157 sFont = sFont.copy(0,sFont.getLength()-1);
8162 if (xTextAppend.is())
8164 uno::Reference< text::XText > xText = xTextAppend->getText();
8165 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
8166 if (xCrsr.is())
8168 xCrsr->gotoEnd(false);
8169 xText->insertString(xCrsr, sSymbol, true);
8170 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
8171 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::Any(awt::CharSet::SYMBOL));
8172 if(bHasFont)
8174 uno::Any aVal( sFont );
8175 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal);
8176 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal);
8177 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal);
8183 break;
8184 case FIELD_TEMPLATE: break;
8185 case FIELD_TIME :
8187 if (pContext->IsFieldLocked())
8189 xFieldProperties->setPropertyValue(
8190 getPropertyName(PROP_IS_FIXED),
8191 uno::Any( true ));
8192 pContext->m_bSetDateValue = true;
8194 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
8196 break;
8197 case FIELD_TITLE :
8199 if (!sFirstParam.isEmpty())
8201 xFieldProperties->setPropertyValue(
8202 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
8203 //PROP_CURRENT_PRESENTATION is set later anyway
8206 break;
8207 case FIELD_USERADDRESS : //todo: user address collects street, city ...
8208 break;
8209 case FIELD_INDEX:
8210 handleIndex(pContext,
8211 OUString::createFromAscii(aIt->second.cFieldServiceName));
8212 break;
8213 case FIELD_BIBLIOGRAPHY:
8214 handleBibliography(pContext,
8215 OUString::createFromAscii(aIt->second.cFieldServiceName));
8216 break;
8217 case FIELD_TOC:
8218 handleToc(pContext,
8219 OUString::createFromAscii(aIt->second.cFieldServiceName));
8220 break;
8221 case FIELD_XE:
8223 if( !m_xTextDocument )
8224 break;
8226 // only UserIndexMark can handle user index types defined by \f
8227 // e.g. XE "text" \f "user-index-id"
8228 OUString sUserIndex;
8229 OUString sFieldServiceName =
8230 lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex )
8231 ? "com.sun.star.text.UserIndexMark"
8232 : OUString::createFromAscii(aIt->second.cFieldServiceName);
8233 uno::Reference< beans::XPropertySet > xTC(
8234 m_xTextDocument->createInstance(sFieldServiceName),
8235 uno::UNO_QUERY_THROW);
8237 if (!sFirstParam.isEmpty())
8239 xTC->setPropertyValue(sUserIndex.isEmpty()
8240 ? OUString("PrimaryKey")
8241 : OUString("AlternativeText"),
8242 uno::Any(sFirstParam));
8245 sUserIndex = lcl_trim(sUserIndex);
8246 if (!sUserIndex.isEmpty())
8248 xTC->setPropertyValue("UserIndexName",
8249 uno::Any(sUserIndex));
8251 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
8252 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8253 if (xTextAppend.is())
8255 uno::Reference< text::XText > xText = xTextAppend->getText();
8256 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
8257 if (xCrsr.is())
8259 xCrsr->gotoEnd(false);
8260 xText->insertTextContent(uno::Reference< text::XTextRange >( xCrsr, uno::UNO_QUERY_THROW ), xToInsert, false);
8264 break;
8265 case FIELD_CITATION:
8267 if( !m_xTextDocument )
8268 break;
8270 xFieldInterface = m_xTextDocument->createInstance(
8271 OUString::createFromAscii(aIt->second.cFieldServiceName));
8272 uno::Reference< beans::XPropertySet > xTC(xFieldInterface,
8273 uno::UNO_QUERY_THROW);
8274 OUString sCmd(pContext->GetCommand());//sCmd is the entire instrText including the index e.g. CITATION Kra06 \l 1033
8275 if( !sCmd.isEmpty()){
8276 uno::Sequence<beans::PropertyValue> aValues( comphelper::InitPropertySequence({
8277 { "Identifier", uno::Any(sCmd) }
8278 }));
8279 xTC->setPropertyValue("Fields", uno::Any(aValues));
8281 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
8283 uno::Sequence<beans::PropertyValue> aValues
8284 = m_aFieldStack.back()->getProperties()->GetPropertyValues();
8285 appendTextContent(xToInsert, aValues);
8286 pContext->m_bSetCitation = true;
8288 break;
8290 case FIELD_TC :
8292 if( !m_xTextDocument )
8293 break;
8295 uno::Reference< beans::XPropertySet > xTC(
8296 m_xTextDocument->createInstance(
8297 OUString::createFromAscii(aIt->second.cFieldServiceName)),
8298 uno::UNO_QUERY_THROW);
8299 if (!sFirstParam.isEmpty())
8301 xTC->setPropertyValue(getPropertyName(PROP_ALTERNATIVE_TEXT),
8302 uno::Any(sFirstParam));
8304 OUString sValue;
8305 // \f TC entry in doc with multiple tables
8306 // if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
8307 // {
8308 // todo: unsupported
8309 // }
8310 if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
8311 // \l Outline Level
8313 sal_Int32 nLevel = sValue.toInt32();
8314 if( !sValue.isEmpty() && nLevel >= 0 && nLevel <= 10 )
8315 xTC->setPropertyValue(getPropertyName(PROP_LEVEL), uno::Any( static_cast<sal_Int16>(nLevel) ));
8317 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
8318 // \n Suppress page numbers
8319 // {
8320 //todo: unsupported feature
8321 // }
8322 pContext->SetTC( xTC );
8324 break;
8325 case FIELD_NUMCHARS:
8326 case FIELD_NUMWORDS:
8327 case FIELD_NUMPAGES:
8328 if (xFieldProperties.is())
8329 xFieldProperties->setPropertyValue(
8330 getPropertyName(PROP_NUMBERING_TYPE),
8331 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
8332 break;
8335 if (!bCreateEnhancedField)
8337 pContext->SetTextField( uno::Reference<text::XTextField>(xFieldInterface, uno::UNO_QUERY) );
8340 else
8342 /* Unsupported fields will be handled here for docx file.
8343 * To handle unsupported fields used fieldmark API.
8345 OUString aCode( pContext->GetCommand().trim() );
8346 // Don't waste resources on wrapping shapes inside a fieldmark.
8347 if (sType != "SHAPE" && m_xTextDocument && !m_aTextAppendStack.empty())
8349 rtl::Reference<SwXBookmark> xFieldInterface = m_xTextDocument->createFieldmark();
8351 uno::Reference<text::XFormField> const xFormField(static_cast<cppu::OWeakObject*>(xFieldInterface.get()), uno::UNO_QUERY);
8352 InsertFieldmark(m_aTextAppendStack, xFormField, pContext->GetStartRange(),
8353 pContext->GetFieldId());
8354 xFormField->setFieldType(ODF_UNHANDLED);
8355 ++m_nStartGenericField;
8356 pContext->SetFormField( xFormField );
8357 uno::Reference<container::XNameContainer> const xNameCont(xFormField->getParameters());
8358 // note: setting the code to empty string is *required* in
8359 // m_bForceGenericFields mode, or the export will write
8360 // the ODF_UNHANDLED string!
8361 assert(!m_bForceGenericFields || aCode.isEmpty());
8362 xNameCont->insertByName(ODF_CODE_PARAM, uno::Any(aCode));
8363 ww::eField const id(GetWW8FieldId(sType));
8364 if (id != ww::eNONE)
8365 { // tdf#129247 tdf#134264 set WW8 id for WW8 export
8366 xNameCont->insertByName(ODF_ID_PARAM, uno::Any(OUString::number(id)));
8369 else
8370 m_StreamStateStack.top().bParaHadField = false;
8373 catch( const uno::Exception& )
8375 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Exception in CloseFieldCommand()" );
8377 pContext->SetCommandCompleted();
8379 /*-------------------------------------------------------------------------
8380 //the _current_ fields require a string type result while TOCs accept richt results
8381 -----------------------------------------------------------------------*/
8382 bool DomainMapper_Impl::IsFieldResultAsString()
8384 bool bRet = false;
8385 OSL_ENSURE( !m_aFieldStack.empty(), "field stack empty?");
8386 FieldContextPtr pContext = m_aFieldStack.back();
8387 OSL_ENSURE( pContext, "no field context available");
8388 if( pContext )
8390 bRet = pContext->GetTextField().is()
8391 || pContext->GetFieldId() == FIELD_FORMDROPDOWN
8392 || pContext->GetFieldId() == FIELD_FILLIN;
8395 if (!bRet)
8397 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
8398 if (pOuter)
8400 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
8402 // If nesting is not allowed, then the result can only be a string.
8403 bRet = true;
8407 return bRet;
8410 void DomainMapper_Impl::AppendFieldResult(std::u16string_view rString)
8412 assert(!m_aFieldStack.empty());
8413 FieldContextPtr pContext = m_aFieldStack.back();
8414 SAL_WARN_IF(!pContext, "writerfilter.dmapper", "no field context");
8415 if (!pContext)
8416 return;
8418 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
8419 if (pOuter)
8421 if (!IsFieldNestingAllowed(pOuter, pContext))
8423 if (pOuter->IsCommandCompleted())
8425 // Child can't host the field result, forward to parent's result.
8426 pOuter->AppendResult(rString);
8428 return;
8432 pContext->AppendResult(rString);
8435 // Calculates css::DateTime based on ddddd.sssss since 1899-12-30
8436 static util::DateTime lcl_dateTimeFromSerial(const double& dSerial)
8438 DateTime d(Date(30, 12, 1899));
8439 d.AddTime(dSerial);
8440 return d.GetUNODateTime();
8443 void DomainMapper_Impl::SetFieldResult(OUString const& rResult)
8445 #ifdef DBG_UTIL
8446 TagLogger::getInstance().startElement("setFieldResult");
8447 TagLogger::getInstance().chars(rResult);
8448 #endif
8450 FieldContextPtr pContext = m_aFieldStack.back();
8451 OSL_ENSURE( pContext, "no field context available");
8453 if (m_aFieldStack.size() > 1)
8455 // This is a nested field. See if the parent supports nesting on the Writer side.
8456 FieldContextPtr pParentContext = m_aFieldStack[m_aFieldStack.size() - 2];
8457 if (pParentContext)
8459 std::vector<OUString> aParentParts = pParentContext->GetCommandParts();
8460 // Conditional text fields don't support nesting in Writer.
8461 if (!aParentParts.empty() && aParentParts[0] == "IF")
8463 return;
8468 if( !pContext )
8469 return;
8471 uno::Reference<text::XTextField> xTextField = pContext->GetTextField();
8474 OSL_ENSURE( xTextField.is()
8475 //||m_xTOC.is() ||m_xTC.is()
8476 //||m_sHyperlinkURL.getLength()
8477 , "DomainMapper_Impl::SetFieldResult: field not created" );
8478 if(xTextField.is())
8482 if (pContext->m_bSetUserFieldContent)
8484 // user field content has to be set at the field master
8485 uno::Reference< text::XDependentTextField > xDependentField( xTextField, uno::UNO_QUERY_THROW );
8486 xDependentField->getTextFieldMaster()->setPropertyValue(
8487 getPropertyName(PROP_CONTENT),
8488 uno::Any( rResult ));
8490 else if (pContext->m_bSetCitation)
8493 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8494 // In case of SetExpression, the field result contains the content of the variable.
8495 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
8497 bool bIsSetbiblio = xServiceInfo->supportsService("com.sun.star.text.TextField.Bibliography");
8498 if( bIsSetbiblio )
8500 uno::Any aProperty = xFieldProperties->getPropertyValue("Fields");
8501 uno::Sequence<beans::PropertyValue> aValues ;
8502 aProperty >>= aValues;
8503 beans::PropertyValue propertyVal;
8504 sal_Int32 nTitleFoundIndex = -1;
8505 for (sal_Int32 i = 0; i < aValues.getLength(); ++i)
8507 propertyVal = aValues[i];
8508 if (propertyVal.Name == "Title")
8510 nTitleFoundIndex = i;
8511 break;
8514 if (nTitleFoundIndex != -1)
8516 OUString titleStr;
8517 uno::Any aValue(propertyVal.Value);
8518 aValue >>= titleStr;
8519 titleStr += rResult;
8520 propertyVal.Value <<= titleStr;
8521 aValues.getArray()[nTitleFoundIndex] = propertyVal;
8523 else
8525 aValues.realloc(aValues.getLength() + 1);
8526 propertyVal.Name = "Title";
8527 propertyVal.Value <<= rResult;
8528 aValues.getArray()[aValues.getLength() - 1] = propertyVal;
8530 xFieldProperties->setPropertyValue("Fields",
8531 uno::Any(aValues));
8534 else if (pContext->m_bSetDateValue)
8536 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( static_cast<cppu::OWeakObject*>(m_xTextDocument.get()), uno::UNO_QUERY_THROW );
8538 uno::Reference<util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
8539 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
8540 sal_Int32 nKey = 0;
8542 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8544 xFieldProperties->getPropertyValue( "NumberFormat" ) >>= nKey;
8545 xFieldProperties->setPropertyValue(
8546 "DateTimeValue",
8547 uno::Any( lcl_dateTimeFromSerial( xFormatter->convertStringToNumber( nKey, rResult ) ) ) );
8549 else
8551 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8552 // In case of SetExpression, and Input fields the field result contains the content of the variable.
8553 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
8554 // there are fields with a content property, which aren't working correctly with
8555 // a generalized try catch of the content, property, so just restrict content
8556 // handling to these explicit services.
8557 const bool bHasContent = xServiceInfo->supportsService("com.sun.star.text.TextField.SetExpression") ||
8558 xServiceInfo->supportsService("com.sun.star.text.TextField.Input");
8559 // If we already have content set, then use the current presentation
8560 OUString sValue;
8561 if (bHasContent)
8563 // this will throw for field types without Content
8564 uno::Any aValue(xFieldProperties->getPropertyValue(
8565 getPropertyName(PROP_CONTENT)));
8566 aValue >>= sValue;
8568 xFieldProperties->setPropertyValue(
8569 getPropertyName(bHasContent && sValue.isEmpty()? PROP_CONTENT : PROP_CURRENT_PRESENTATION),
8570 uno::Any( rResult ));
8572 // LO always automatically updates a DocInfo field from the File-Properties-Custom Prop
8573 // while MS Word requires the user to manually refresh the field (with F9).
8574 // In other words, Word lets the field to be out of sync with the controlling variable.
8575 // Marking as FIXEDFLD solves the automatic replacement problem, but of course prevents
8576 // Writer from making any changes, even on an F9 refresh.
8577 OUString sVariable = pContext->GetVariableValue();
8578 if (rResult.getLength() != sVariable.getLength())
8580 sal_Int32 nLen = sVariable.indexOf('\x0');
8581 if (nLen >= 0)
8582 sVariable = sVariable.copy(0, nLen);
8584 bool bCustomFixedField = rResult != sVariable &&
8585 xServiceInfo->supportsService("com.sun.star.text.TextField.DocInfo.Custom");
8587 if (bCustomFixedField || xServiceInfo->supportsService(
8588 "com.sun.star.text.TextField.DocInfo.CreateDateTime"))
8590 // Creation time is const, don't try to update it.
8591 xFieldProperties->setPropertyValue("IsFixed", uno::Any(true));
8595 catch( const beans::UnknownPropertyException& )
8597 //some fields don't have a CurrentPresentation (DateTime)
8601 catch (const uno::Exception&)
8603 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "DomainMapper_Impl::SetFieldResult");
8607 void DomainMapper_Impl::SetFieldFFData(const FFDataHandler::Pointer_t& pFFDataHandler)
8609 #ifdef DBG_UTIL
8610 TagLogger::getInstance().startElement("setFieldFFData");
8611 #endif
8613 if (!m_aFieldStack.empty())
8615 FieldContextPtr pContext = m_aFieldStack.back();
8616 if (pContext)
8618 pContext->setFFDataHandler(pFFDataHandler);
8622 #ifdef DBG_UTIL
8623 TagLogger::getInstance().endElement();
8624 #endif
8627 void DomainMapper_Impl::PopFieldContext()
8629 if(m_bDiscardHeaderFooter)
8630 return;
8631 #ifdef DBG_UTIL
8632 TagLogger::getInstance().element("popFieldContext");
8633 #endif
8635 if (m_aFieldStack.empty())
8636 return;
8638 FieldContextPtr pContext = m_aFieldStack.back();
8639 OSL_ENSURE( pContext, "no field context available");
8640 if( pContext )
8642 if( !pContext->IsCommandCompleted() )
8643 CloseFieldCommand();
8645 if (!pContext->GetResult().isEmpty())
8647 uno::Reference< beans::XPropertySet > xFieldProperties = pContext->GetCustomField();
8648 if(xFieldProperties.is())
8649 SetNumberFormat( pContext->GetResult(), xFieldProperties, true );
8650 SetFieldResult( pContext->GetResult() );
8653 //insert the field, TC or TOC
8654 uno::Reference< text::XTextAppend > xTextAppend;
8655 if (!m_aTextAppendStack.empty())
8656 xTextAppend = m_aTextAppendStack.top().xTextAppend;
8657 if(xTextAppend.is())
8661 uno::Reference< text::XTextContent > xToInsert( pContext->GetTOC(), uno::UNO_QUERY );
8662 if( xToInsert.is() )
8664 if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography)
8666 // inside SDT, last empty paragraph is also part of index
8667 if (!m_StreamStateStack.top().bParaChanged && !m_StreamStateStack.top().xSdtEntryStart)
8669 // End of index is the first item on a new paragraph - this paragraph
8670 // should not be part of index
8671 auto xCursor
8672 = xTextAppend->createTextCursorByRange(
8673 m_aTextAppendStack.top().xInsertPosition.is()
8674 ? m_aTextAppendStack.top().xInsertPosition
8675 : xTextAppend->getEnd());
8676 xCursor->goLeft(1, true);
8677 // delete
8678 xCursor->setString(OUString());
8679 // But a new paragraph should be started after the index instead
8680 if (m_bIsNewDoc) // this check - see testTdf129402
8681 { // where finishParagraph inserts between 2 EndNode
8682 xTextAppend->finishParagraph(css::beans::PropertyValues());
8684 else
8686 xTextAppend->finishParagraphInsert(css::beans::PropertyValues(),
8687 m_aTextAppendStack.top().xInsertPosition);
8690 m_bStartedTOC = false;
8691 m_aTextAppendStack.pop();
8692 m_StreamStateStack.top().bTextInserted = false;
8693 m_StreamStateStack.top().bParaChanged = true; // the paragraph must stay anyway
8695 m_bStartTOC = false;
8696 m_bStartIndex = false;
8697 m_bStartBibliography = false;
8698 if (IsInHeaderFooter() && m_bStartTOCHeaderFooter)
8699 m_bStartTOCHeaderFooter = false;
8701 else
8703 xToInsert.set(pContext->GetTC(), uno::UNO_QUERY);
8704 if (!xToInsert.is() && !IsInTOC() && !m_bStartIndex && !m_bStartBibliography)
8705 xToInsert = pContext->GetTextField();
8706 if (xToInsert.is() && !IsInTOC() && !m_bStartIndex && !m_bStartBibliography)
8708 PropertyMap aMap;
8709 // Character properties of the field show up here the
8710 // last (always empty) run. Inherit character
8711 // properties from there.
8712 // Also merge in the properties from the field context,
8713 // e.g. SdtEndBefore.
8714 if (m_pLastCharacterContext)
8715 aMap.InsertProps(m_pLastCharacterContext);
8716 aMap.InsertProps(m_aFieldStack.back()->getProperties());
8717 appendTextContent(xToInsert, aMap.GetPropertyValues());
8718 CheckRedline( xToInsert->getAnchor( ) );
8720 else
8722 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->createTextCursorByRange(pContext->GetStartRange());
8723 FormControlHelper::Pointer_t pFormControlHelper(pContext->getFormControlHelper());
8724 if (pFormControlHelper)
8726 // xCrsr may be empty e.g. when pContext->GetStartRange() is outside of
8727 // xTextAppend, like when a field started in a parent paragraph is being
8728 // closed inside an anchored text box. It could be possible to throw an
8729 // exception here, and abort import, but Word tolerates such invalid
8730 // input, so it makes sense to do the same (tdf#152200)
8731 if (xCrsr.is())
8733 uno::Reference< text::XFormField > xFormField(pContext->GetFormField());
8734 if (pFormControlHelper->hasFFDataHandler())
8736 xToInsert.set(xFormField, uno::UNO_QUERY);
8737 if (xFormField.is() && xToInsert.is())
8739 PopFieldmark(m_aTextAppendStack, xCrsr,
8740 pContext->GetFieldId());
8741 pFormControlHelper->processField(xFormField);
8743 else
8745 pFormControlHelper->insertControl(xCrsr);
8748 else
8750 PopFieldmark(m_aTextAppendStack, xCrsr,
8751 pContext->GetFieldId());
8752 uno::Reference<lang::XComponent>(xFormField, uno::UNO_QUERY_THROW)->dispose(); // presumably invalid?
8756 else if (!pContext->GetHyperlinkURL().isEmpty() && xCrsr.is())
8758 if (m_aTextAppendStack.top().xInsertPosition.is())
8760 xCrsr->gotoRange(m_aTextAppendStack.top().xInsertPosition, true);
8762 else
8764 xCrsr->gotoEnd(true);
8767 // Draw components (like comments) need hyperlinks set differently
8768 SvxUnoTextRangeBase* pDrawText = dynamic_cast<SvxUnoTextRangeBase*>(xCrsr.get());
8769 if ( pDrawText )
8770 pDrawText->attachField( std::make_unique<SvxURLField>(pContext->GetHyperlinkURL(), xCrsr->getString(), SvxURLFormat::AppDefault) );
8771 else
8773 uno::Reference< beans::XPropertySet > xCrsrProperties( xCrsr, uno::UNO_QUERY_THROW );
8774 xCrsrProperties->setPropertyValue(getPropertyName(PROP_HYPER_LINK_U_R_L), uno::
8775 Any(pContext->GetHyperlinkURL()));
8777 if (!pContext->GetHyperlinkTarget().isEmpty())
8778 xCrsrProperties->setPropertyValue("HyperLinkTarget", uno::Any(pContext->GetHyperlinkTarget()));
8780 if (IsInTOC())
8782 OUString sDisplayName("Index Link");
8783 xCrsrProperties->setPropertyValue("VisitedCharStyleName",uno::Any(sDisplayName));
8784 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName",uno::Any(sDisplayName));
8786 else if (!pContext->GetHyperlinkStyle().isEmpty())
8788 uno::Any aAny = xCrsrProperties->getPropertyValue("CharStyleName");
8789 OUString charStyle;
8790 if (css::uno::fromAny(aAny, &charStyle))
8792 if (!charStyle.isEmpty() && charStyle.equalsIgnoreAsciiCase("Internet Link"))
8794 xCrsrProperties->setPropertyValue("CharStyleName", uno::Any(OUString("Default Style")));
8796 else
8798 xCrsrProperties->setPropertyValue("VisitedCharStyleName", uno::Any(pContext->GetHyperlinkStyle()));
8799 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", uno::Any(pContext->GetHyperlinkStyle()));
8806 else if (m_nStartGenericField != 0)
8808 --m_nStartGenericField;
8809 PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId());
8810 if (m_StreamStateStack.top().bTextInserted)
8812 m_StreamStateStack.top().bTextInserted = false;
8818 catch(const lang::IllegalArgumentException&)
8820 TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" );
8822 catch(const uno::Exception&)
8824 TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" );
8828 //TOCs have to include all the imported content
8831 std::vector<FieldParagraph> aParagraphsToFinish;
8832 if (pContext)
8834 aParagraphsToFinish = pContext->GetParagraphsToFinish();
8837 //remove the field context
8838 m_aFieldStack.pop_back();
8840 // Finish the paragraph(s) now that the field is closed.
8841 for (const auto& rFinish : aParagraphsToFinish)
8843 finishParagraph(rFinish.m_pPropertyMap, rFinish.m_bRemove);
8848 void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName )
8850 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( m_sCurrentBkmkId );
8851 if( aBookmarkIter != m_aBookmarkMap.end() )
8853 // fields are internal bookmarks: consume redundant "normal" bookmark
8854 if ( IsOpenField() )
8856 FFDataHandler::Pointer_t pFFDataHandler(GetTopFieldContext()->getFFDataHandler());
8857 if (pFFDataHandler && pFFDataHandler->getName() == rBookmarkName)
8859 // HACK: At the END marker, StartOrEndBookmark will START
8860 // a bookmark which will eventually be abandoned, not created.
8861 m_aBookmarkMap.erase(aBookmarkIter);
8862 return;
8866 if ((m_sCurrentBkmkPrefix == "__RefMoveFrom__"
8867 || m_sCurrentBkmkPrefix == "__RefMoveTo__")
8868 && std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), rBookmarkName)
8869 == m_aRedlineMoveIDs.end())
8871 m_aRedlineMoveIDs.push_back(rBookmarkName);
8874 aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + rBookmarkName;
8875 m_sCurrentBkmkPrefix.clear();
8877 else
8879 m_sCurrentBkmkName = rBookmarkName;
8880 m_sCurrentBkmkPrefix.clear();
8884 // This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation.
8885 void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId )
8888 * Add the dummy paragraph to handle section properties
8889 * iff the first element in the section is a table. If the dummy para is not added yet, then add it;
8890 * So bookmark is not attached to the wrong paragraph.
8892 if (hasTableManager() && getTableManager().isInCell()
8893 && m_StreamStateStack.top().nTableDepth == 0
8894 && GetIsFirstParagraphInSection()
8895 && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted())
8897 AddDummyParaForTableInSection();
8900 bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
8901 if (m_aTextAppendStack.empty())
8902 return;
8903 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8904 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( rId );
8905 //is the bookmark name already registered?
8908 if( aBookmarkIter != m_aBookmarkMap.end() )
8910 if (m_xTextDocument)
8912 rtl::Reference<SwXBookmark> xBookmark( m_xTextDocument->createBookmark() );
8913 uno::Reference< text::XTextCursor > xCursor;
8914 uno::Reference< text::XText > xText = aBookmarkIter->second.m_xTextRange->getText();
8915 if( aBookmarkIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
8917 xCursor = xText->createTextCursorByRange( xText->getStart() );
8919 else
8921 xCursor = xText->createTextCursorByRange( aBookmarkIter->second.m_xTextRange );
8923 if (!aBookmarkIter->second.m_bIsStartOfText)
8925 xCursor->goRight( 1, false );
8928 xCursor->gotoRange( xTextAppend->getEnd(), true );
8929 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
8930 // then move the bookmark-End to the earlier paragraph
8931 if (IsOutsideAParagraph())
8933 // keep bookmark range, if it doesn't exceed cell boundary
8934 uno::Reference< text::XTextRange > xStart = xCursor->getStart();
8935 xCursor->goLeft( 1, false );
8936 if (m_StreamStateStack.top().nTableDepth == 0
8937 || !m_StreamStateStack.top().bFirstParagraphInCell)
8939 xCursor->gotoRange(xStart, true );
8942 SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark");
8943 //todo: make sure the name is not used already!
8944 xBookmark->setName( aBookmarkIter->second.m_sBookmarkName );
8945 xTextAppend->insertTextContent( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW), xBookmark, !xCursor->isCollapsed() );
8947 m_aBookmarkMap.erase( aBookmarkIter );
8948 m_sCurrentBkmkId.clear();
8950 else
8952 //otherwise insert a text range as marker
8953 bool bIsStart = true;
8954 uno::Reference< text::XTextRange > xCurrent;
8955 if (xTextAppend.is())
8957 uno::Reference<text::XTextCursor> const xCursor =
8958 xTextAppend->createTextCursorByRange(
8959 m_aTextAppendStack.top().xInsertPosition.is()
8960 ? m_aTextAppendStack.top().xInsertPosition
8961 : xTextAppend->getEnd() );
8963 if (!xCursor)
8964 return;
8966 if (!bIsAfterDummyPara)
8967 bIsStart = !xCursor->goLeft(1, false);
8968 xCurrent = xCursor->getStart();
8970 m_sCurrentBkmkId = rId;
8971 m_aBookmarkMap.emplace( rId, BookmarkInsertPosition( bIsStart, m_sCurrentBkmkName, xCurrent ) );
8972 m_sCurrentBkmkName.clear();
8975 catch( const uno::Exception& )
8977 //TODO: What happens to bookmarks where start and end are at different XText objects?
8981 void DomainMapper_Impl::SetMoveBookmark( bool bIsFrom )
8983 static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = u"__RefMoveFrom__";
8984 static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = u"__RefMoveTo__";
8985 if ( bIsFrom )
8986 m_sCurrentBkmkPrefix = MoveFrom_Bookmark_NamePrefix;
8987 else
8988 m_sCurrentBkmkPrefix = MoveTo_Bookmark_NamePrefix;
8991 void DomainMapper_Impl::setPermissionRangeEd(const OUString& user)
8993 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
8994 if (aPremIter != m_aPermMap.end())
8995 aPremIter->second.m_Ed = user;
8996 else
8997 m_sCurrentPermEd = user;
9000 void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group)
9002 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
9003 if (aPremIter != m_aPermMap.end())
9004 aPremIter->second.m_EdGrp = group;
9005 else
9006 m_sCurrentPermEdGrp = group;
9009 // This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark()
9010 void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId)
9013 * Add the dummy paragraph to handle section properties
9014 * if the first element in the section is a table. If the dummy para is not added yet, then add it;
9015 * So permission is not attached to the wrong paragraph.
9017 if (getTableManager().isInCell()
9018 && m_StreamStateStack.top().nTableDepth == 0 && GetIsFirstParagraphInSection()
9019 && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted())
9021 AddDummyParaForTableInSection();
9024 if (m_aTextAppendStack.empty())
9025 return;
9027 const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
9029 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
9030 PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId);
9032 //is the bookmark name already registered?
9035 if (aPermIter == m_aPermMap.end())
9037 //otherwise insert a text range as marker
9038 bool bIsStart = true;
9039 uno::Reference< text::XTextRange > xCurrent;
9040 if (xTextAppend.is())
9042 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
9044 if (!bIsAfterDummyPara)
9045 bIsStart = !xCursor->goLeft(1, false);
9046 xCurrent = xCursor->getStart();
9049 // register the start of the new permission
9050 m_sCurrentPermId = permissinId;
9051 m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent));
9053 // clean up
9054 m_sCurrentPermEd.clear();
9055 m_sCurrentPermEdGrp.clear();
9057 else
9059 if (m_xTextDocument)
9061 uno::Reference< text::XTextCursor > xCursor;
9062 uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText();
9063 if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
9065 xCursor = xText->createTextCursorByRange(xText->getStart());
9067 else
9069 xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange);
9071 if (!aPermIter->second.m_bIsStartOfText)
9073 xCursor->goRight(1, false);
9076 xCursor->gotoRange(xTextAppend->getEnd(), true);
9077 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
9078 // then move the bookmark-End to the earlier paragraph
9079 if (IsOutsideAParagraph())
9081 xCursor->goLeft(1, false);
9084 // create a new bookmark using specific bookmark name pattern for permissions
9085 rtl::Reference< SwXBookmark > xPerm(m_xTextDocument->createBookmark());
9086 xPerm->setName(aPermIter->second.createBookmarkName());
9088 // add new bookmark
9089 const bool bAbsorb = !xCursor->isCollapsed();
9090 uno::Reference< text::XTextRange > xCurrent(xCursor, uno::UNO_QUERY_THROW);
9091 xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb);
9094 // remove processed permission
9095 m_aPermMap.erase(aPermIter);
9097 // clean up
9098 m_sCurrentPermId = 0;
9099 m_sCurrentPermEd.clear();
9100 m_sCurrentPermEdGrp.clear();
9103 catch (const uno::Exception&)
9105 //TODO: What happens to bookmarks where start and end are at different XText objects?
9109 void DomainMapper_Impl::AddAnnotationPosition(
9110 const bool bStart,
9111 const sal_Int32 nAnnotationId)
9113 if (m_aTextAppendStack.empty())
9114 return;
9116 // Create a cursor, pointing to the current position.
9117 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
9118 uno::Reference<text::XTextRange> xCurrent;
9119 if (xTextAppend.is())
9121 uno::Reference<text::XTextCursor> xCursor;
9122 if (m_bIsNewDoc)
9123 xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
9124 else
9125 xCursor = m_aTextAppendStack.top().xCursor;
9126 if (xCursor.is())
9127 xCurrent = xCursor->getStart();
9130 // And save it, to be used by PopAnnotation() later.
9131 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[ nAnnotationId ];
9132 if (bStart)
9134 aAnnotationPosition.m_xStart = xCurrent;
9136 else
9138 aAnnotationPosition.m_xEnd = xCurrent;
9140 m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition;
9143 GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport()
9145 if(!m_pGraphicImport)
9147 m_pGraphicImport = new GraphicImport(m_xComponentContext, m_xTextDocument, m_rDMapper, m_eGraphicImportType, m_aPositionOffsets, m_aAligns, m_aPositivePercentages);
9149 return m_pGraphicImport;
9151 /*-------------------------------------------------------------------------
9152 reset graphic import if the last import resulted in a shape, not a graphic
9153 -----------------------------------------------------------------------*/
9154 void DomainMapper_Impl::ResetGraphicImport()
9156 m_pGraphicImport.clear();
9160 void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference<Properties>::Pointer_t& ref)
9162 GetGraphicImport();
9163 if (m_eGraphicImportType != IMPORT_AS_DETECTED_INLINE && m_eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR)
9164 { // this appears impossible?
9165 //create the graphic
9166 ref->resolve( *m_pGraphicImport );
9169 //insert it into the document at the current cursor position
9171 uno::Reference<text::XTextContent> xTextContent
9172 (m_pGraphicImport->GetGraphicObject());
9174 // In case the SDT starts with the text portion of the graphic, then set the SDT properties here.
9175 bool bHasGrabBag = false;
9176 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
9177 if (xPropertySet.is())
9179 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
9180 bHasGrabBag = xPropertySetInfo->hasPropertyByName("FrameInteropGrabBag");
9181 // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one.
9182 if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph())
9184 comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue("FrameInteropGrabBag"));
9185 aFrameGrabBag["SdtPr"] <<= m_pSdtHelper->getInteropGrabBagAndClear();
9186 xPropertySet->setPropertyValue("FrameInteropGrabBag", uno::Any(aFrameGrabBag.getAsConstPropertyValueList()));
9190 /* Set "SdtEndBefore" property on Drawing.
9191 * It is required in a case when Drawing appears immediately after first run i.e.
9192 * there is no text/space/tab in between two runs.
9193 * In this case "SdtEndBefore" property needs to be set on Drawing.
9195 if(IsSdtEndBefore())
9197 if(xPropertySet.is() && bHasGrabBag)
9199 uno::Sequence<beans::PropertyValue> aFrameGrabBag( comphelper::InitPropertySequence({
9200 { "SdtEndBefore", uno::Any(true) }
9201 }));
9202 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aFrameGrabBag));
9207 // Update the shape properties if it is embedded object.
9208 if (m_StreamStateStack.top().xEmbedded.is())
9210 if (m_pGraphicImport->GetXShapeObject())
9211 m_pGraphicImport->GetXShapeObject()->setPosition(
9212 m_pGraphicImport->GetGraphicObjectPosition());
9214 uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject();
9215 UpdateEmbeddedShapeProps(xShape);
9216 if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR)
9218 uno::Reference<beans::XPropertySet> const xEmbeddedProps(m_StreamStateStack.top().xEmbedded, uno::UNO_QUERY);
9219 xEmbeddedProps->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER));
9220 xEmbeddedProps->setPropertyValue("IsFollowingTextFlow", uno::Any(m_pGraphicImport->GetLayoutInCell()));
9221 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
9222 xEmbeddedProps->setPropertyValue("HoriOrient", xShapeProps->getPropertyValue("HoriOrient"));
9223 xEmbeddedProps->setPropertyValue("HoriOrientPosition", xShapeProps->getPropertyValue("HoriOrientPosition"));
9224 xEmbeddedProps->setPropertyValue("HoriOrientRelation", xShapeProps->getPropertyValue("HoriOrientRelation"));
9225 xEmbeddedProps->setPropertyValue("VertOrient", xShapeProps->getPropertyValue("VertOrient"));
9226 xEmbeddedProps->setPropertyValue("VertOrientPosition", xShapeProps->getPropertyValue("VertOrientPosition"));
9227 xEmbeddedProps->setPropertyValue("VertOrientRelation", xShapeProps->getPropertyValue("VertOrientRelation"));
9228 //tdf123873 fix missing textwrap import
9229 xEmbeddedProps->setPropertyValue("TextWrap", xShapeProps->getPropertyValue("TextWrap"));
9231 // GraphicZOrderHelper::findZOrder() was called already, so can just copy it over.
9232 xEmbeddedProps->setPropertyValue("ZOrder", xShapeProps->getPropertyValue("ZOrder"));
9235 //insert it into the document at the current cursor position
9236 OSL_ENSURE( xTextContent.is(), "DomainMapper_Impl::ImportGraphic");
9237 if( xTextContent.is())
9239 bool bAppend = true;
9240 // workaround for images anchored to characters: add ZWSPs around the anchoring point
9241 if (m_eGraphicImportType != IMPORT_AS_DETECTED_INLINE && !m_aRedlines.top().empty())
9243 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
9244 if(xTextAppend.is())
9248 uno::Reference< text::XText > xText = xTextAppend->getText();
9249 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
9250 xCrsr->gotoEnd(false);
9251 PropertyMapPtr pEmpty(new PropertyMap());
9252 appendTextPortion(u"​"_ustr, pEmpty);
9253 appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
9254 bAppend = false;
9255 xCrsr->gotoEnd(false);
9256 appendTextPortion(u"​"_ustr, pEmpty);
9258 m_bRedlineImageInPreviousRun = true;
9259 m_previousRedline = m_currentRedline;
9261 catch( const uno::Exception& )
9267 if ( bAppend )
9268 appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
9270 if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty())
9272 // Remember this object is anchored to the current paragraph.
9273 AnchoredObjectInfo aInfo;
9274 aInfo.m_xAnchoredObject = xTextContent;
9275 if (m_pGraphicImport)
9277 // We still have the graphic import around, remember the original margin, so later
9278 // SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing() can use it.
9279 aInfo.m_nLeftMargin = m_pGraphicImport->GetLeftMarginOrig();
9281 m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo);
9283 else if (m_eGraphicImportType == IMPORT_AS_DETECTED_INLINE)
9285 m_StreamStateStack.top().bParaWithInlineObject = true;
9287 // store inline images with track changes, because the anchor point
9288 // to set redlining is not available yet
9289 if (!m_aTextAppendStack.empty() && !m_aRedlines.top().empty() )
9291 // Remember this object is anchored to the current paragraph.
9292 AnchoredObjectInfo aInfo;
9293 aInfo.m_xAnchoredObject = xTextContent;
9294 aInfo.m_xRedlineForInline = m_aRedlines.top().back();
9295 m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo);
9301 // Clear the reference, so in case the embedded object is inside a
9302 // TextFrame, we won't try to resize it (to match the size of the
9303 // TextFrame) here.
9304 m_StreamStateStack.top().xEmbedded.clear();
9305 m_pGraphicImport.clear();
9309 void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn )
9311 if (!m_xTextDocument)
9312 throw uno::RuntimeException();
9313 if( !m_bLineNumberingSet )
9317 uno::Reference< beans::XPropertySet > xProperties = m_xTextDocument->getLineNumberingProperties();
9318 uno::Any aTrue( uno::Any( true ));
9319 xProperties->setPropertyValue( getPropertyName( PROP_IS_ON ), aTrue);
9320 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_EMPTY_LINES ), aTrue );
9321 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_LINES_IN_FRAMES ), uno::Any( false ) );
9322 xProperties->setPropertyValue( getPropertyName( PROP_INTERVAL ), uno::Any( static_cast< sal_Int16 >( nLnnMod )));
9323 xProperties->setPropertyValue( getPropertyName( PROP_DISTANCE ), uno::Any( ConversionHelper::convertTwipToMM100(ndxaLnn) ));
9324 xProperties->setPropertyValue( getPropertyName( PROP_NUMBER_POSITION ), uno::Any( style::LineNumberPosition::LEFT));
9325 xProperties->setPropertyValue( getPropertyName( PROP_NUMBERING_TYPE ), uno::Any( style::NumberingType::ARABIC));
9326 xProperties->setPropertyValue( getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::Any( nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage ));
9328 catch( const uno::Exception& )
9331 m_bLineNumberingSet = true;
9332 uno::Reference< container::XNameAccess > xStyleFamilies = m_xTextDocument->getStyleFamilies();
9333 uno::Reference<container::XNameContainer> xStyles;
9334 xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xStyles;
9335 lcl_linenumberingHeaderFooter( xStyles, "Header", this );
9336 lcl_linenumberingHeaderFooter( xStyles, "Footer", this );
9340 void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue )
9342 nValue = ConversionHelper::convertTwipToMM100(nValue);
9343 switch(eElement)
9345 case PAGE_MAR_TOP : m_aPageMargins.top = nValue; break;
9346 case PAGE_MAR_RIGHT : m_aPageMargins.right = nValue; break;
9347 case PAGE_MAR_BOTTOM : m_aPageMargins.bottom = nValue; break;
9348 case PAGE_MAR_LEFT : m_aPageMargins.left = nValue; break;
9349 case PAGE_MAR_HEADER : m_aPageMargins.header = nValue; break;
9350 case PAGE_MAR_FOOTER : m_aPageMargins.footer = nValue; break;
9351 case PAGE_MAR_GUTTER:
9352 m_aPageMargins.gutter = nValue;
9353 break;
9357 void DomainMapper_Impl::SetPaperSource(PaperSourceElement eElement, sal_Int32 nValue)
9359 if(eElement == PAPER_SOURCE_FIRST)
9360 m_aPaperSource.first = nValue;
9361 else
9362 m_aPaperSource.other = nValue;
9366 PageMar::PageMar()
9367 : top(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
9368 // This is strange, the RTF spec says it's 1800, but it's clearly 1440 in Word
9369 // OOXML seems not to specify a default value
9370 , right(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
9371 , bottom(top)
9372 , left(right)
9373 , header(ConversionHelper::convertTwipToMM100(sal_Int32(720)))
9374 , footer(header)
9375 , gutter(0)
9380 void DomainMapper_Impl::RegisterFrameConversion(
9381 uno::Reference< text::XTextRange > const& xFrameStartRange,
9382 uno::Reference< text::XTextRange > const& xFrameEndRange,
9383 std::vector<beans::PropertyValue>&& rFrameProperties
9386 OSL_ENSURE(
9387 m_aFrameProperties.empty() && !m_xFrameStartRange.is() && !m_xFrameEndRange.is(),
9388 "frame properties not removed");
9389 m_aFrameProperties = std::move(rFrameProperties);
9390 m_xFrameStartRange = xFrameStartRange;
9391 m_xFrameEndRange = xFrameEndRange;
9395 void DomainMapper_Impl::ExecuteFrameConversion()
9397 if( m_xFrameStartRange.is() && m_xFrameEndRange.is() && !m_bDiscardHeaderFooter )
9399 std::vector<sal_Int32> redPos, redLen;
9402 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( GetTopTextAppend(), uno::UNO_QUERY_THROW );
9403 // convert redline ranges to cursor movement and character length
9404 sal_Int32 redIdx;
9405 lcl_CopyRedlines(GetTopTextAppend(), m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx);
9407 const uno::Reference< text::XTextContent >& xTextContent = xTextAppendAndConvert->convertToTextFrame(
9408 m_xFrameStartRange,
9409 m_xFrameEndRange,
9410 comphelper::containerToSequence(m_aFrameProperties) );
9412 uno::Reference< text::XText > xDest( xTextContent, uno::UNO_QUERY_THROW );
9413 lcl_PasteRedlines(xDest, m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx);
9415 catch( const uno::Exception&)
9417 DBG_UNHANDLED_EXCEPTION( "writerfilter.dmapper", "Exception caught when converting to frame");
9420 m_bIsActualParagraphFramed = false;
9422 if (redPos.size() == m_aStoredRedlines[StoredRedlines::FRAME].size()/3)
9424 for( sal_Int32 i = m_aStoredRedlines[StoredRedlines::FRAME].size() - 1; i >= 0; --i)
9426 // keep redlines of floating tables to process them in CloseSectionGroup()
9427 if ( redPos[i/3] != -1 )
9429 m_aStoredRedlines[StoredRedlines::FRAME].erase(m_aStoredRedlines[StoredRedlines::FRAME].begin() + i);
9433 else
9434 m_aStoredRedlines[StoredRedlines::FRAME].clear();
9436 m_xFrameStartRange = nullptr;
9437 m_xFrameEndRange = nullptr;
9438 m_aFrameProperties.clear();
9441 void DomainMapper_Impl::AddNewRedline( sal_uInt32 sprmId )
9443 RedlineParamsPtr pNew( new RedlineParams );
9444 pNew->m_nToken = XML_mod;
9445 if ( !m_bIsParaMarkerChange )
9447 // <w:rPrChange> applies to the whole <w:r>, <w:pPrChange> applies to the whole <w:p>,
9448 // so keep those two in CONTEXT_CHARACTERS and CONTEXT_PARAGRAPH, which will take
9449 // care of their scope (i.e. when they should be used and discarded).
9450 // Let's keep the rest the same way they used to be handled (explicitly dropped
9451 // from a global stack by endtrackchange), but quite possibly they should not be handled
9452 // that way either (I don't know).
9453 if( sprmId == NS_ooxml::LN_EG_RPrContent_rPrChange )
9454 GetTopContextOfType( CONTEXT_CHARACTER )->Redlines().push_back( pNew );
9455 else if( sprmId == NS_ooxml::LN_CT_PPr_pPrChange )
9456 GetTopContextOfType( CONTEXT_PARAGRAPH )->Redlines().push_back( pNew );
9457 else if( sprmId != NS_ooxml::LN_CT_ParaRPr_rPrChange )
9458 m_aRedlines.top().push_back( pNew );
9460 else
9462 m_pParaMarkerRedline = pNew;
9464 // Newly read data will go into this redline.
9465 m_currentRedline = pNew;
9468 void DomainMapper_Impl::SetCurrentRedlineIsRead()
9470 m_currentRedline.clear();
9473 sal_Int32 DomainMapper_Impl::GetCurrentRedlineToken( ) const
9475 assert(m_currentRedline);
9476 return m_currentRedline->m_nToken;
9479 void DomainMapper_Impl::SetCurrentRedlineAuthor( const OUString& sAuthor )
9481 if (!m_xAnnotationField.is())
9483 if (m_currentRedline)
9484 m_currentRedline->m_sAuthor = sAuthor;
9485 else
9486 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
9488 else
9489 m_xAnnotationField->setPropertyValue("Author", uno::Any(sAuthor));
9492 void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials )
9494 if (m_xAnnotationField.is())
9495 m_xAnnotationField->setPropertyValue("Initials", uno::Any(sInitials));
9498 void DomainMapper_Impl::SetCurrentRedlineDate( const OUString& sDate )
9500 if (!m_xAnnotationField.is())
9502 if (m_currentRedline)
9503 m_currentRedline->m_sDate = sDate;
9504 else
9505 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
9507 else
9508 m_xAnnotationField->setPropertyValue("DateTimeValue", uno::Any(ConversionHelper::ConvertDateStringToDateTime(sDate)));
9511 void DomainMapper_Impl::SetCurrentRedlineId( sal_Int32 sId )
9513 if (m_xAnnotationField.is())
9515 m_nAnnotationId = sId;
9517 else
9519 // This should be an assert, but somebody had the smart idea to reuse this function also for comments and whatnot,
9520 // and in some cases the id is actually not handled, which may be in fact a bug.
9521 if( !m_currentRedline)
9522 SAL_INFO("writerfilter.dmapper", "no current redline");
9526 void DomainMapper_Impl::SetCurrentRedlineToken( sal_Int32 nToken )
9528 assert(m_currentRedline);
9529 m_currentRedline->m_nToken = nToken;
9532 void DomainMapper_Impl::SetCurrentRedlineRevertProperties( const uno::Sequence<beans::PropertyValue>& aProperties )
9534 assert(m_currentRedline);
9535 m_currentRedline->m_aRevertProperties = aProperties;
9539 // This removes only the last redline stored here, those stored in contexts are automatically removed when
9540 // the context is destroyed.
9541 void DomainMapper_Impl::RemoveTopRedline( )
9543 if (m_aRedlines.top().empty())
9545 if (GetFootnoteCount() > -1 || GetEndnoteCount() > -1)
9546 return;
9547 SAL_WARN("writerfilter.dmapper", "RemoveTopRedline called with empty stack");
9548 throw uno::Exception("RemoveTopRedline failed", nullptr);
9550 m_aRedlines.top().pop_back( );
9551 m_currentRedline.clear();
9554 void DomainMapper_Impl::ApplySettingsTable()
9556 if (!(m_pSettingsTable && m_xTextDocument))
9557 return;
9561 rtl::Reference< SwXTextDefaults > xTextDefaults(m_xTextDocument->createTextDefaults());
9562 sal_Int32 nDefTab = m_pSettingsTable->GetDefaultTabStop();
9563 xTextDefaults->setPropertyValue( getPropertyName( PROP_TAB_STOP_DISTANCE ), uno::Any(nDefTab) );
9564 if (m_pSettingsTable->GetLinkStyles())
9566 // If linked styles are enabled, set paragraph defaults from Word's default template
9567 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_BOTTOM_MARGIN), uno::Any(ConversionHelper::convertTwipToMM100(200)));
9568 style::LineSpacing aSpacing;
9569 aSpacing.Mode = style::LineSpacingMode::PROP;
9570 aSpacing.Height = sal_Int16(115);
9571 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_LINE_SPACING), uno::Any(aSpacing));
9574 if (m_pSettingsTable->GetZoomFactor() || m_pSettingsTable->GetView())
9576 std::vector<beans::PropertyValue> aViewProps;
9577 if (m_pSettingsTable->GetZoomFactor())
9579 aViewProps.emplace_back("ZoomFactor", -1, uno::Any(m_pSettingsTable->GetZoomFactor()), beans::PropertyState_DIRECT_VALUE);
9580 aViewProps.emplace_back("VisibleBottom", -1, uno::Any(sal_Int32(0)), beans::PropertyState_DIRECT_VALUE);
9581 aViewProps.emplace_back("ZoomType", -1,
9582 uno::Any(m_pSettingsTable->GetZoomType()),
9583 beans::PropertyState_DIRECT_VALUE);
9585 rtl::Reference< comphelper::IndexedPropertyValuesContainer > xBox = new comphelper::IndexedPropertyValuesContainer();
9586 xBox->insertByIndex(sal_Int32(0), uno::Any(comphelper::containerToSequence(aViewProps)));
9587 m_xTextDocument->setViewData(xBox);
9590 rtl::Reference<SwXDocumentSettings> xSettings(m_xTextDocument->createDocumentSettings());
9592 if (m_pSettingsTable->GetDoNotExpandShiftReturn())
9593 xSettings->setPropertyValue( "DoNotJustifyLinesWithManualBreak", uno::Any(true) );
9594 // new paragraph justification has been introduced in version 15,
9595 // breaking text layout interoperability: new line shrinking needs less space
9596 // i.e. it typesets the same text with less lines and pages.
9597 if (m_pSettingsTable->GetWordCompatibilityMode() >= 15)
9598 xSettings->setPropertyValue("JustifyLinesWithShrinking", uno::Any( true ));
9599 if (m_pSettingsTable->GetUsePrinterMetrics())
9600 xSettings->setPropertyValue("PrinterIndependentLayout", uno::Any(document::PrinterIndependentLayout::DISABLED));
9601 if( m_pSettingsTable->GetEmbedTrueTypeFonts())
9602 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_FONTS ), uno::Any(true) );
9603 if( m_pSettingsTable->GetEmbedSystemFonts())
9604 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_SYSTEM_FONTS ), uno::Any(true) );
9605 xSettings->setPropertyValue("AddParaTableSpacing", uno::Any(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing()));
9606 if (m_pSettingsTable->GetNoLeading())
9608 xSettings->setPropertyValue("AddExternalLeading", uno::Any(!m_pSettingsTable->GetNoLeading()));
9610 if( m_pSettingsTable->GetProtectForm() )
9611 xSettings->setPropertyValue("ProtectForm", uno::Any( true ));
9612 if( m_pSettingsTable->GetReadOnly() )
9613 xSettings->setPropertyValue("LoadReadonly", uno::Any( true ));
9614 if (m_pSettingsTable->GetGutterAtTop())
9616 xSettings->setPropertyValue("GutterAtTop", uno::Any(true));
9618 uno::Sequence<beans::PropertyValue> aWriteProtection
9619 = m_pSettingsTable->GetWriteProtectionSettings();
9620 if (aWriteProtection.hasElements())
9621 xSettings->setPropertyValue("ModifyPasswordInfo", uno::Any(aWriteProtection));
9623 catch(const uno::Exception&)
9628 SectionPropertyMap * DomainMapper_Impl::GetSectionContext()
9630 SectionPropertyMap* pSectionContext = nullptr;
9631 //the section context is not available before the first call of startSectionGroup()
9632 if( !IsAnyTableImport() )
9634 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
9635 pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
9638 return pSectionContext;
9641 void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any& value)
9643 m_StreamStateStack.top().deferredCharacterProperties[ id ] = value;
9646 void DomainMapper_Impl::processDeferredCharacterProperties(bool bCharContext)
9648 // Actually process in DomainMapper, so that it's the same source file like normal processing.
9649 if (!m_StreamStateStack.top().deferredCharacterProperties.empty())
9651 m_rDMapper.processDeferredCharacterProperties(m_StreamStateStack.top().deferredCharacterProperties, bCharContext);
9652 m_StreamStateStack.top().deferredCharacterProperties.clear();
9656 sal_Int32 DomainMapper_Impl::getNumberingProperty(const sal_Int32 nListId, sal_Int32 nNumberingLevel, const OUString& aProp)
9658 sal_Int32 nRet = 0;
9659 if ( nListId < 0 )
9660 return nRet;
9661 if ( !m_xTextDocument )
9662 return nRet;
9666 if (nNumberingLevel < 0) // It seems it's valid to omit numbering level, and in that case it means zero.
9667 nNumberingLevel = 0;
9669 auto const pList(GetListTable()->GetList(nListId));
9670 assert(pList);
9671 const OUString aListName = pList->GetStyleName();
9672 const uno::Reference< container::XNameAccess > xStyleFamilies = m_xTextDocument->getStyleFamilies();
9673 uno::Reference<container::XNameAccess> xNumberingStyles;
9674 xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles;
9675 const uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY);
9676 const uno::Reference<container::XIndexAccess> xNumberingRules(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
9677 if (xNumberingRules.is())
9679 uno::Sequence<beans::PropertyValue> aProps;
9680 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
9681 auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
9682 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
9683 if (pProp != std::cend(aProps))
9684 pProp->Value >>= nRet;
9687 catch( const uno::Exception& )
9689 // This can happen when the doc contains some hand-crafted invalid list level.
9692 return nRet;
9695 sal_Int32 DomainMapper_Impl::getCurrentNumberingProperty(const OUString& aProp)
9697 sal_Int32 nRet = 0;
9699 std::optional<PropertyMap::Property> pProp = m_pTopContext->getProperty(PROP_NUMBERING_RULES);
9700 uno::Reference<container::XIndexAccess> xNumberingRules;
9701 if (pProp)
9702 xNumberingRules.set(pProp->second, uno::UNO_QUERY);
9703 pProp = m_pTopContext->getProperty(PROP_NUMBERING_LEVEL);
9704 // Default numbering level is the first one.
9705 sal_Int32 nNumberingLevel = 0;
9706 if (pProp)
9707 pProp->second >>= nNumberingLevel;
9708 if (xNumberingRules.is())
9710 uno::Sequence<beans::PropertyValue> aProps;
9711 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
9712 auto pPropVal = std::find_if(std::cbegin(aProps), std::cend(aProps),
9713 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
9714 if (pPropVal != std::cend(aProps))
9715 pPropVal->Value >>= nRet;
9718 return nRet;
9722 void DomainMapper_Impl::enableInteropGrabBag(const OUString& aName)
9724 m_aInteropGrabBagName = aName;
9727 void DomainMapper_Impl::disableInteropGrabBag()
9729 m_aInteropGrabBagName.clear();
9730 m_aInteropGrabBag.clear();
9731 m_aSubInteropGrabBag.clear();
9734 bool DomainMapper_Impl::isInteropGrabBagEnabled() const
9736 return !(m_aInteropGrabBagName.isEmpty());
9739 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue)
9741 if (m_aInteropGrabBagName.isEmpty())
9742 return;
9743 beans::PropertyValue aProperty;
9744 aProperty.Name = aKey;
9745 aProperty.Value <<= aValue;
9746 rInteropGrabBag.push_back(aProperty);
9749 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<beans::PropertyValue>& rValue)
9751 if (m_aInteropGrabBagName.isEmpty())
9752 return;
9753 beans::PropertyValue aProperty;
9754 aProperty.Name = aKey;
9755 aProperty.Value <<= comphelper::containerToSequence(rValue);
9756 rValue.clear();
9757 rInteropGrabBag.push_back(aProperty);
9760 void DomainMapper_Impl::substream(Id rName,
9761 ::writerfilter::Reference<Stream>::Pointer_t const& ref)
9763 #ifndef NDEBUG
9764 size_t contextSize(m_aContextStack.size());
9765 size_t propSize[NUMBER_OF_CONTEXTS];
9766 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
9767 propSize[i] = m_aPropertyStacks[i].size();
9769 #endif
9771 //finalize any waiting frames before starting alternate streams
9772 CheckUnregisteredFrameConversion();
9773 ExecuteFrameConversion();
9775 appendTableManager();
9776 // Appending a TableManager resets its TableHandler, so we need to append
9777 // that as well, or tables won't be imported properly in headers/footers.
9778 appendTableHandler();
9779 getTableManager().startLevel();
9781 // Save "has footnote" state, which is specific to a section in the body
9782 // text, so state from substreams is not relevant.
9783 m_StreamStateStack.emplace();
9785 //import of page header/footer
9786 //Ensure that only one header/footer per section is pushed
9788 switch( rName )
9790 case NS_ooxml::LN_headerl:
9791 PushPageHeaderFooter(PagePartType::Header, PageType::LEFT);
9792 break;
9793 case NS_ooxml::LN_headerr:
9794 PushPageHeaderFooter(PagePartType::Header, PageType::RIGHT);
9795 break;
9796 case NS_ooxml::LN_headerf:
9797 PushPageHeaderFooter(PagePartType::Header, PageType::FIRST);
9798 break;
9799 case NS_ooxml::LN_footerl:
9800 PushPageHeaderFooter(PagePartType::Footer, PageType::LEFT);
9801 break;
9802 case NS_ooxml::LN_footerr:
9803 PushPageHeaderFooter(PagePartType::Footer, PageType::RIGHT);
9804 break;
9805 case NS_ooxml::LN_footerf:
9806 PushPageHeaderFooter(PagePartType::Footer, PageType::FIRST);
9807 break;
9808 case NS_ooxml::LN_footnote:
9809 case NS_ooxml::LN_endnote:
9810 PushFootOrEndnote( NS_ooxml::LN_footnote == rName );
9811 break;
9812 case NS_ooxml::LN_annotation :
9813 PushAnnotation();
9814 break;
9815 default:
9816 assert(false); // unexpected?
9819 assert(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body);
9823 ref->resolve(m_rDMapper);
9825 catch (xml::sax::SAXException const&)
9827 m_bSaxError = true;
9828 throw;
9831 switch( rName )
9833 case NS_ooxml::LN_headerl:
9834 PopPageHeaderFooter(PagePartType::Header, PageType::LEFT);
9835 break;
9836 case NS_ooxml::LN_footerl:
9837 PopPageHeaderFooter(PagePartType::Footer, PageType::LEFT);
9838 break;
9839 case NS_ooxml::LN_headerr:
9840 PopPageHeaderFooter(PagePartType::Header, PageType::RIGHT);
9841 break;
9842 case NS_ooxml::LN_footerr:
9843 PopPageHeaderFooter(PagePartType::Footer, PageType::RIGHT);
9844 break;
9845 case NS_ooxml::LN_headerf:
9846 PopPageHeaderFooter(PagePartType::Header, PageType::FIRST);
9847 break;
9848 case NS_ooxml::LN_footerf:
9849 PopPageHeaderFooter(PagePartType::Footer, PageType::FIRST);
9850 break;
9851 case NS_ooxml::LN_footnote:
9852 case NS_ooxml::LN_endnote:
9853 PopFootOrEndnote();
9854 break;
9855 case NS_ooxml::LN_annotation :
9856 PopAnnotation();
9857 break;
9860 m_StreamStateStack.pop();
9861 assert(!m_StreamStateStack.empty());
9863 getTableManager().endLevel();
9864 popTableManager();
9866 switch(rName)
9868 case NS_ooxml::LN_footnote:
9869 case NS_ooxml::LN_endnote:
9870 m_StreamStateStack.top().bHasFtn = true;
9871 break;
9874 // check that stacks are the same as before substream
9875 assert(m_aContextStack.size() == contextSize);
9876 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
9877 assert(m_aPropertyStacks[i].size() == propSize[i]);
9881 void DomainMapper_Impl::commentProps(const OUString& sId, const CommentProperties& rProps)
9883 m_aCommentProps[sId] = rProps;
9887 bool DomainMapper_Impl::handlePreviousParagraphBorderInBetween() const
9889 if (!m_StreamStateStack.top().xPreviousParagraph.is())
9890 return false;
9892 // Connected borders ("ParaIsConnectBorder") are always on by default
9893 // and never changed by DomainMapper. Except one case when border in
9894 // between is used. So this is not the best, but easiest way to check
9895 // is previous paragraph has border in between.
9896 bool bConnectBorders = true;
9897 m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(getPropertyName(PROP_PARA_CONNECT_BORDERS)) >>= bConnectBorders;
9899 if (bConnectBorders)
9900 return false;
9902 // Previous paragraph has border in between. Current one also has (since this
9903 // method is called). So current paragraph will get border above, but
9904 // also need to ensure, that no unexpected bottom border are remaining in previous
9905 // paragraph: since ParaIsConnectBorder=false it will be displayed in unexpected way.
9906 m_StreamStateStack.top().xPreviousParagraph->setPropertyValue(getPropertyName(PROP_BOTTOM_BORDER), uno::Any(table::BorderLine2()));
9908 return true;
9911 OUString DomainMapper_Impl::getFontNameForTheme(const Id id)
9913 auto const& pHandler = getThemeHandler();
9914 if (pHandler)
9915 return pHandler->getFontNameForTheme(id);
9916 return OUString();
9921 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */