Avoid potential negative array index access to cached text.
[LibreOffice.git] / writerfilter / source / dmapper / DomainMapper_Impl.cxx
blob4c5dff02fcf718dbc51c4e712ec50307568adb58
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/configmgr.hxx>
127 #include <unotools/mediadescriptor.hxx>
128 #include <comphelper/diagnose_ex.hxx>
129 #include <sal/log.hxx>
130 #include <o3tl/string_view.hxx>
131 #include <com/sun/star/drawing/FillStyle.hpp>
133 #include <unicode/errorcode.h>
134 #include <unicode/regex.h>
136 #define REFFLDFLAG_STYLE_FROM_BOTTOM 0xc100
137 #define REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL 0xc200
139 using namespace ::com::sun::star;
140 using namespace oox;
141 namespace writerfilter::dmapper{
143 //line numbering for header/footer
144 static void lcl_linenumberingHeaderFooter( const uno::Reference<container::XNameContainer>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper )
146 const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname );
147 if (!pEntry)
148 return;
149 const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->m_pProperties.get();
150 if ( !pStyleSheetProperties )
151 return;
152 sal_Int32 nListId = pStyleSheetProperties->props().GetListId();
153 if( xStyles.is() )
155 if( xStyles->hasByName( rname ) )
157 uno::Reference< style::XStyle > xStyle;
158 xStyles->getByName( rname ) >>= xStyle;
159 if( !xStyle.is() )
160 return;
161 uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY );
162 xPropertySet->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::Any( nListId >= 0 ) );
167 // Populate Dropdown Field properties from FFData structure
168 static void lcl_handleDropdownField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
170 if ( !rxFieldProps.is() )
171 return;
173 if ( !pFFDataHandler->getName().isEmpty() )
174 rxFieldProps->setPropertyValue( "Name", uno::Any( pFFDataHandler->getName() ) );
176 const FFDataHandler::DropDownEntries_t& rEntries = pFFDataHandler->getDropDownEntries();
177 uno::Sequence< OUString > sItems( rEntries.size() );
178 ::std::copy( rEntries.begin(), rEntries.end(), sItems.getArray());
179 if ( sItems.hasElements() )
180 rxFieldProps->setPropertyValue( "Items", uno::Any( sItems ) );
182 sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32();
183 if (nResult > 0 && o3tl::make_unsigned(nResult) < sItems.size())
184 rxFieldProps->setPropertyValue( "SelectedItem", uno::Any( std::as_const(sItems)[ nResult ] ) );
185 if ( !pFFDataHandler->getHelpText().isEmpty() )
186 rxFieldProps->setPropertyValue( "Help", uno::Any( pFFDataHandler->getHelpText() ) );
189 static void lcl_handleTextField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
191 if ( rxFieldProps.is() && pFFDataHandler )
193 rxFieldProps->setPropertyValue
194 (getPropertyName(PROP_HINT),
195 uno::Any(pFFDataHandler->getStatusText()));
196 rxFieldProps->setPropertyValue
197 (getPropertyName(PROP_HELP),
198 uno::Any(pFFDataHandler->getHelpText()));
199 rxFieldProps->setPropertyValue
200 (getPropertyName(PROP_CONTENT),
201 uno::Any(pFFDataHandler->getTextDefault()));
206 Very similar to DomainMapper_Impl::GetPropertyFromStyleSheet
207 It is focused on paragraph properties search in current & parent stylesheet entries.
208 But it will not take into account properties with listid: these are "list paragraph styles" and
209 not used in some cases.
211 static uno::Any lcl_GetPropertyFromParaStyleSheetNoNum(PropertyIds eId, StyleSheetEntryPtr pEntry, const StyleSheetTablePtr& rStyleSheet)
213 while (pEntry)
215 if (pEntry->m_pProperties)
217 std::optional<PropertyMap::Property> aProperty =
218 pEntry->m_pProperties->getProperty(eId);
219 if (aProperty)
221 if (pEntry->m_pProperties->props().GetListId())
222 // It is a paragraph style with list. Paragraph list styles are not taken into account
223 return uno::Any();
224 else
225 return aProperty->second;
228 //search until the property is set or no parent is available
229 StyleSheetEntryPtr pNewEntry;
230 if (!pEntry->m_sBaseStyleIdentifier.isEmpty())
231 pNewEntry = rStyleSheet->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
233 SAL_WARN_IF(pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
235 if (pEntry == pNewEntry) //fdo#49587
236 break;
238 pEntry = pNewEntry;
240 return uno::Any();
244 namespace {
246 struct FieldConversion
248 const char* cFieldServiceName;
249 FieldId eFieldId;
254 typedef std::unordered_map<OUString, FieldConversion> FieldConversionMap_t;
256 /// Gives access to the parent field context of the topmost one, if there is any.
257 static FieldContextPtr GetParentFieldContext(const std::deque<FieldContextPtr>& rFieldStack)
259 if (rFieldStack.size() < 2)
261 return nullptr;
264 return rFieldStack[rFieldStack.size() - 2];
267 /// Decides if the pInner field inside pOuter is allowed in Writer core, depending on their type.
268 static bool IsFieldNestingAllowed(const FieldContextPtr& pOuter, const FieldContextPtr& pInner)
270 if (!pInner->GetFieldId())
272 return true;
275 std::optional<FieldId> oOuterFieldId = pOuter->GetFieldId();
276 if (!oOuterFieldId)
278 OUString aCommand = pOuter->GetCommand();
280 // Ignore leading space before the field name, but don't accept IFF when we check for IF.
281 while (aCommand.getLength() > 3 && aCommand[0] == ' ')
282 aCommand = aCommand.subView(1);
284 if (aCommand.startsWith("IF "))
286 // This will be FIELD_IF once the command is closed.
287 oOuterFieldId = FIELD_IF;
291 if (!oOuterFieldId)
293 return true;
296 switch (*oOuterFieldId)
298 case FIELD_IF:
300 switch (*pInner->GetFieldId())
302 case FIELD_DOCVARIABLE:
303 case FIELD_FORMTEXT:
304 case FIELD_FORMULA:
305 case FIELD_IF:
306 case FIELD_MERGEFIELD:
307 case FIELD_REF:
308 case FIELD_PAGE:
309 case FIELD_NUMPAGES:
311 // LO does not currently know how to evaluate these as conditions or results
312 return false;
314 default:
315 break;
317 break;
319 default:
320 break;
323 return true;
326 DomainMapper_Impl::DomainMapper_Impl(
327 DomainMapper& rDMapper,
328 uno::Reference<uno::XComponentContext> xContext,
329 uno::Reference<lang::XComponent> const& xModel,
330 SourceDocumentType eDocumentType,
331 utl::MediaDescriptor const & rMediaDesc) :
332 m_eDocumentType( eDocumentType ),
333 m_rDMapper( rDMapper ),
334 m_pOOXMLDocument(nullptr),
335 m_xTextDocument( xModel, uno::UNO_QUERY ),
336 m_xTextFactory( xModel, uno::UNO_QUERY ),
337 m_xComponentContext(std::move( xContext )),
338 m_bForceGenericFields(!utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get()),
339 m_bIsDecimalComma( false ),
340 m_bSetUserFieldContent( false ),
341 m_bSetCitation( false ),
342 m_bSetDateValue( false ),
343 m_bIsFirstSection( true ),
344 m_bSdtEndDeferred(false),
345 m_bParaSdtEndDeferred(false),
346 m_bStartTOC(false),
347 m_bStartTOCHeaderFooter(false),
348 m_bStartedTOC(false),
349 m_bStartIndex(false),
350 m_bStartBibliography(false),
351 m_nStartGenericField(0),
352 m_bTextDeleted(false),
353 m_nLastRedlineMovedID(1),
354 m_sCurrentPermId(0),
355 m_bFrameDirectionSet(false),
356 m_bInDocDefaultsImport(false),
357 m_bInStyleSheetImport( false ),
358 m_bInNumberingImport(false),
359 m_bInAnyTableImport( false ),
360 m_bDiscardHeaderFooter( false ),
361 m_bHasFootnoteStyle(false),
362 m_bCheckFootnoteStyle(false),
363 m_eSkipFootnoteState(SkipFootnoteSeparator::OFF),
364 m_nFootnotes(-1),
365 m_nEndnotes(-1),
366 m_nFirstFootnoteIndex(-1),
367 m_nFirstEndnoteIndex(-1),
368 m_bLineNumberingSet( false ),
369 m_bIsInFootnoteProperties( false ),
370 m_bIsParaMarkerChange( false ),
371 m_bIsParaMarkerMove( false ),
372 m_bRedlineImageInPreviousRun( false ),
373 m_bDummyParaAddedForTableInSection( false ),
374 m_bTextFrameInserted(false),
375 m_bIsLastSectionGroup( false ),
376 m_bUsingEnhancedFields( false ),
377 m_bSdt(false),
378 m_bIsFirstRun(false),
379 m_bIsOutsideAParagraph(true),
380 m_nAnnotationId( -1 ),
381 m_aSmartTagHandler(m_xComponentContext, m_xTextDocument),
382 m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault("TextInsertModeRange", uno::Reference<text::XTextRange>())),
383 m_xAltChunkStartingRange(rMediaDesc.getUnpackedValueOrDefault("AltChunkStartingRange", uno::Reference<text::XTextRange>())),
384 m_bIsInTextBox(false),
385 m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)),
386 m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault("AltChunkMode", false)),
387 m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)),
388 m_nTableCellDepth(0),
389 m_bHasFtn(false),
390 m_bHasFtnSep(false),
391 m_bCheckFirstFootnoteTab(false),
392 m_bIgnoreNextTab(false),
393 m_bIsSplitPara(false),
394 m_bIsActualParagraphFramed( false ),
395 m_bParaAutoBefore(false),
396 m_bSaxError(false)
398 m_StreamStateStack.emplace(); // add state for document body
399 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
400 utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString());
401 if (m_aBaseUrl.isEmpty()) {
402 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
403 utl::MediaDescriptor::PROP_URL, OUString());
406 appendTableManager( );
407 GetBodyText();
408 if (!m_bIsNewDoc && !m_xBodyText)
410 throw uno::Exception("failed to find body text of the insert position", nullptr);
413 uno::Reference< text::XTextAppend > xBodyTextAppend( m_xBodyText, uno::UNO_QUERY );
414 m_aTextAppendStack.push(TextAppendContext(xBodyTextAppend,
415 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(m_xInsertTextRange)));
417 //todo: does it makes sense to set the body text as static text interface?
418 uno::Reference< text::XTextAppendAndConvert > xBodyTextAppendAndConvert( m_xBodyText, uno::UNO_QUERY );
419 m_pTableHandler = new DomainMapperTableHandler(xBodyTextAppendAndConvert, *this);
420 getTableManager( ).setHandler(m_pTableHandler);
422 getTableManager( ).startLevel();
423 m_bUsingEnhancedFields = !utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get();
425 m_pSdtHelper = new SdtHelper(*this, m_xComponentContext);
427 m_aRedlines.push(std::vector<RedlineParamsPtr>());
429 if (m_bIsAltChunk)
431 m_bIsFirstSection = false;
436 DomainMapper_Impl::~DomainMapper_Impl()
438 assert(!m_StreamStateStack.empty());
439 ChainTextFrames();
440 // Don't remove last paragraph when pasting, sw expects that empty paragraph.
441 if (m_bIsNewDoc)
443 RemoveLastParagraph();
444 suppress_fun_call_w_exception(GetStyleSheetTable()->ApplyClonedTOCStyles());
446 if (hasTableManager())
448 getTableManager().endLevel();
449 popTableManager();
453 writerfilter::ooxml::OOXMLDocument* DomainMapper_Impl::getDocumentReference() const
455 return m_pOOXMLDocument;
458 uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetPageStyles()
460 if(!m_xPageStyles1.is())
462 uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY );
463 if (xSupplier.is())
464 xSupplier->getStyleFamilies()->getByName("PageStyles") >>= m_xPageStyles1;
466 return m_xPageStyles1;
469 OUString DomainMapper_Impl::GetUnusedPageStyleName()
471 static const char DEFAULT_STYLE[] = "Converted";
472 if (!m_xNextUnusedPageStyleNo)
474 const uno::Sequence< OUString > aPageStyleNames = GetPageStyles()->getElementNames();
475 sal_Int32 nMaxIndex = 0;
476 // find the highest number x in each style with the name "DEFAULT_STYLE+x" and
477 // return an incremented name
479 for ( const auto& rStyleName : aPageStyleNames )
481 if ( rStyleName.startsWith( DEFAULT_STYLE ) )
483 sal_Int32 nIndex = o3tl::toInt32(rStyleName.subView( strlen( DEFAULT_STYLE ) ));
484 if ( nIndex > nMaxIndex )
485 nMaxIndex = nIndex;
488 m_xNextUnusedPageStyleNo = nMaxIndex + 1;
491 OUString sPageStyleName = DEFAULT_STYLE + OUString::number( *m_xNextUnusedPageStyleNo );
492 *m_xNextUnusedPageStyleNo = *m_xNextUnusedPageStyleNo + 1;
493 return sPageStyleName;
496 uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetCharacterStyles()
498 if(!m_xCharacterStyles.is())
500 uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY );
501 if (xSupplier.is())
502 xSupplier->getStyleFamilies()->getByName("CharacterStyles") >>= m_xCharacterStyles;
504 return m_xCharacterStyles;
507 uno::Reference<container::XNameContainer> const& DomainMapper_Impl::GetParagraphStyles()
509 if (!m_xParagraphStyles.is())
511 uno::Reference<style::XStyleFamiliesSupplier> xSupplier(m_xTextDocument, uno::UNO_QUERY);
512 if (xSupplier.is())
513 xSupplier->getStyleFamilies()->getByName("ParagraphStyles") >>= m_xParagraphStyles;
515 return m_xParagraphStyles;
518 OUString DomainMapper_Impl::GetUnusedCharacterStyleName()
520 static const char cListLabel[] = "ListLabel ";
521 if (!m_xNextUnusedCharacterStyleNo)
523 //search for all character styles with the name sListLabel + <index>
524 const uno::Sequence< OUString > aCharacterStyleNames = GetCharacterStyles()->getElementNames();
525 sal_Int32 nMaxIndex = 0;
526 for ( const auto& rStyleName : aCharacterStyleNames )
528 OUString sSuffix;
529 if ( rStyleName.startsWith( cListLabel, &sSuffix ) )
531 sal_Int32 nSuffix = sSuffix.toInt32();
532 if( nSuffix > 0 && nSuffix > nMaxIndex )
533 nMaxIndex = nSuffix;
536 m_xNextUnusedCharacterStyleNo = nMaxIndex + 1;
539 OUString sPageStyleName = cListLabel + OUString::number( *m_xNextUnusedCharacterStyleNo );
540 *m_xNextUnusedCharacterStyleNo = *m_xNextUnusedCharacterStyleNo + 1;
541 return sPageStyleName;
544 uno::Reference< text::XText > const & DomainMapper_Impl::GetBodyText()
546 if(!m_xBodyText.is())
548 if (m_xInsertTextRange.is())
549 m_xBodyText = m_xInsertTextRange->getText();
550 else if (m_xTextDocument.is())
551 m_xBodyText = m_xTextDocument->getText();
553 return m_xBodyText;
557 uno::Reference< beans::XPropertySet > const & DomainMapper_Impl::GetDocumentSettings()
559 if( !m_xDocumentSettings.is() && m_xTextFactory.is())
561 m_xDocumentSettings.set( m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY );
563 return m_xDocumentSettings;
567 void DomainMapper_Impl::SetDocumentSettingsProperty( const OUString& rPropName, const uno::Any& rValue )
569 uno::Reference< beans::XPropertySet > xSettings = GetDocumentSettings();
570 if( xSettings.is() )
574 xSettings->setPropertyValue( rPropName, rValue );
576 catch( const uno::Exception& )
582 namespace
584 void CopyPageDescNameToNextParagraph(const uno::Reference<lang::XComponent>& xParagraph,
585 const uno::Reference<text::XTextCursor>& xCursor)
587 // First check if xParagraph has a non-empty page style name to copy from.
588 uno::Reference<beans::XPropertySet> xParagraphProps(xParagraph, uno::UNO_QUERY);
589 if (!xParagraphProps.is())
591 return;
594 uno::Any aPageDescName = xParagraphProps->getPropertyValue("PageDescName");
595 OUString sPageDescName;
596 aPageDescName >>= sPageDescName;
597 if (sPageDescName.isEmpty())
599 return;
602 // If so, search for the next paragraph.
603 uno::Reference<text::XParagraphCursor> xParaCursor(xCursor, uno::UNO_QUERY);
604 if (!xParaCursor.is())
606 return;
609 // Create a range till the next paragraph and then enumerate on the range.
610 if (!xParaCursor->gotoNextParagraph(/*bExpand=*/true))
612 return;
615 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xParaCursor, uno::UNO_QUERY);
616 if (!xEnumerationAccess.is())
618 return;
621 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
622 if (!xEnumeration.is())
624 return;
627 xEnumeration->nextElement();
628 if (!xEnumeration->hasMoreElements())
630 return;
633 // We found a next item in the enumeration: it's usually a paragraph, but may be a table as
634 // well.
635 uno::Reference<beans::XPropertySet> xNextParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
636 if (!xNextParagraph.is())
638 return;
641 // See if there is page style set already: if so, don't touch it.
642 OUString sNextPageDescName;
643 xNextParagraph->getPropertyValue("PageDescName") >>= sNextPageDescName;
644 if (!sNextPageDescName.isEmpty())
646 return;
649 // Finally copy it over, so it's not lost.
650 xNextParagraph->setPropertyValue("PageDescName", aPageDescName);
654 void DomainMapper_Impl::RemoveDummyParaForTableInSection()
656 SetIsDummyParaAddedForTableInSection(false);
657 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
658 SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
659 if (!pSectionContext)
660 return;
662 if (m_aTextAppendStack.empty())
663 return;
664 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
665 if (!xTextAppend.is())
666 return;
668 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(pSectionContext->GetStartingRange());
670 // Remove the extra NumPicBullets from the document,
671 // which get attached to the first paragraph in the
672 // document
673 ListsManager::Pointer pListTable = GetListTable();
674 pListTable->DisposeNumPicBullets();
676 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY);
677 if (xEnumerationAccess.is() && m_aTextAppendStack.size() == 1 )
679 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
680 uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
681 // Make sure no page breaks are lost.
682 CopyPageDescNameToNextParagraph(xParagraph, xCursor);
683 xParagraph->dispose();
686 void DomainMapper_Impl::AddDummyParaForTableInSection()
688 // Shapes and textboxes can't have sections.
689 if (IsInShape() || m_bIsInTextBox)
690 return;
692 if (!m_aTextAppendStack.empty())
694 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
695 if (xTextAppend.is())
697 xTextAppend->finishParagraph( uno::Sequence< beans::PropertyValue >() );
698 SetIsDummyParaAddedForTableInSection(true);
703 static OUString lcl_FindLastBookmark(const uno::Reference<text::XTextCursor>& xCursor,
704 bool bAlreadyExpanded)
706 OUString sName;
707 if (!xCursor.is())
708 return sName;
710 // Select 1 previous element
711 if (!bAlreadyExpanded)
712 xCursor->goLeft(1, true);
713 comphelper::ScopeGuard unselectGuard(
714 [xCursor, bAlreadyExpanded]()
716 if (!bAlreadyExpanded)
717 xCursor->goRight(1, true);
720 uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY);
721 if (!xParaEnumAccess.is())
722 return sName;
724 // Iterate through selection paragraphs
725 uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
726 if (!xParaEnum->hasMoreElements())
727 return sName;
729 // Iterate through first para portions
730 uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(),
731 uno::UNO_QUERY_THROW);
732 uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration();
733 while (xRunEnum->hasMoreElements())
735 uno::Reference<beans::XPropertySet> xProps(xRunEnum->nextElement(), uno::UNO_QUERY_THROW);
736 uno::Any aType(xProps->getPropertyValue("TextPortionType"));
737 OUString sType;
738 aType >>= sType;
739 if (sType == "Bookmark")
741 uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue("Bookmark"),
742 uno::UNO_QUERY_THROW);
743 sName = xBookmark->getName();
744 // Do not stop the scan here. Maybe there are 2 bookmarks?
748 return sName;
751 static void reanchorObjects(const uno::Reference<uno::XInterface>& xFrom,
752 const uno::Reference<text::XTextRange>& xTo,
753 const uno::Reference<drawing::XDrawPage>& xDrawPage)
755 std::vector<uno::Reference<text::XTextContent>> aShapes;
756 bool bFastPathDone = false;
757 if (uno::Reference<beans::XPropertySet> xProps{ xFrom, uno::UNO_QUERY })
761 // See SwXParagraph::Impl::GetPropertyValues_Impl
762 uno::Sequence<uno::Reference<text::XTextContent>> aSeq;
763 xProps->getPropertyValue(u"OOXMLImport_AnchoredShapes"_ustr) >>= aSeq;
764 aShapes.insert(aShapes.end(), aSeq.begin(), aSeq.end());
765 bFastPathDone = true;
767 catch (const uno::Exception&)
772 if (!bFastPathDone)
774 // Can this happen? Fallback to slow DrawPage iteration and range comparison
775 uno::Reference<text::XTextRange> xRange(xFrom, uno::UNO_QUERY_THROW);
776 uno::Reference<text::XTextRangeCompare> xCompare(xRange->getText(), uno::UNO_QUERY_THROW);
778 const sal_Int32 count = xDrawPage->getCount();
779 for (sal_Int32 i = 0; i < count; ++i)
783 uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i),
784 uno::UNO_QUERY_THROW);
785 uno::Reference<text::XTextRange> xAnchor(xShape->getAnchor(), uno::UNO_SET_THROW);
786 if (xCompare->compareRegionStarts(xAnchor, xRange) <= 0
787 && xCompare->compareRegionEnds(xAnchor, xRange) >= 0)
789 aShapes.push_back(xShape);
792 catch (const uno::Exception&)
794 // Can happen e.g. in compareRegion*, when the shape is in a header,
795 // and paragraph in body
800 for (const auto& xShape : aShapes)
801 xShape->attach(xTo);
804 void DomainMapper_Impl::RemoveLastParagraph( )
806 if (m_bDiscardHeaderFooter)
807 return;
809 if (m_aTextAppendStack.empty())
810 return;
812 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
813 if (!xTextAppend.is())
814 return;
816 if (hasTableManager() && getTableManager().getCurrentTablePosition().getLength() != 0)
818 // If we have an open floating table, then don't remove this paragraph, since that'll be the
819 // anchor of the floating table. Otherwise we would lose the table.
820 return;
825 uno::Reference< text::XTextCursor > xCursor;
826 if (m_bIsNewDoc)
828 xCursor = xTextAppend->createTextCursor();
829 xCursor->gotoEnd(false);
831 else
832 xCursor.set(m_aTextAppendStack.top().xCursor, uno::UNO_SET_THROW);
834 // Keep the character properties of the last but one paragraph, even if
835 // it's empty. This works for headers/footers, and maybe in other cases
836 // as well, but surely not in textboxes.
837 // fdo#58327: also do this at the end of the document: when pasting,
838 // a table before the cursor position would be deleted
839 bool const bEndOfDocument(m_aTextAppendStack.size() == 1);
841 uno::Reference<lang::XComponent> xParagraph;
842 if (IsInHeaderFooter() || bEndOfDocument)
844 if (uno::Reference<container::XEnumerationAccess> xEA{ xCursor, uno::UNO_QUERY })
846 xParagraph.set(xEA->createEnumeration()->nextElement(), uno::UNO_QUERY);
850 xCursor->goLeft(1, true);
851 // If this is a text on a shape, possibly the text has the trailing
852 // newline removed already. RTF may also not have the trailing newline.
853 if (!(xCursor->getString() == SAL_NEWLINE_STRING ||
854 // tdf#105444 comments need an exception, if SAL_NEWLINE_STRING defined as "\r\n"
855 (sizeof(SAL_NEWLINE_STRING) - 1 == 2 && xCursor->getString() == "\n")))
856 return;
858 uno::Reference<beans::XPropertySet> xDocProps(GetTextDocument(), uno::UNO_QUERY_THROW);
859 static constexpr OUString RecordChanges(u"RecordChanges"_ustr);
861 comphelper::ScopeGuard redlineRestore(
862 [xDocProps, aPreviousValue = xDocProps->getPropertyValue(RecordChanges)]()
863 { xDocProps->setPropertyValue(RecordChanges, aPreviousValue); });
865 // disable redlining, otherwise we might end up with an unwanted recorded operations
866 xDocProps->setPropertyValue(RecordChanges, uno::Any(false));
868 if (xParagraph)
870 // move all anchored objects to the previous paragraph
871 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(GetTextDocument(),
872 uno::UNO_QUERY_THROW);
873 auto xDrawPage = xDrawPageSupplier->getDrawPage();
874 if (xDrawPage && xDrawPage->hasElements())
876 // Cursor already spans two paragraphs
877 uno::Reference<container::XEnumerationAccess> xEA(xCursor,
878 uno::UNO_QUERY_THROW);
879 auto xEnumeration = xEA->createEnumeration();
880 uno::Reference<text::XTextRange> xPrevParagraph(xEnumeration->nextElement(),
881 uno::UNO_QUERY_THROW);
882 reanchorObjects(xParagraph, xPrevParagraph, xDrawPage);
885 xParagraph->dispose();
887 else
889 // Try to find and remember last bookmark in document: it potentially
890 // can be deleted by xCursor->setString() but not by xParagraph->dispose()
891 OUString sLastBookmarkName;
892 if (bEndOfDocument)
893 sLastBookmarkName = lcl_FindLastBookmark(xCursor, true);
895 // The cursor already selects across the paragraph break
896 // delete
897 xCursor->setString(OUString());
899 // call to xCursor->setString possibly did remove final bookmark
900 // from previous paragraph. We need to restore it, if there was any.
901 if (sLastBookmarkName.getLength())
903 OUString sBookmarkNameAfterRemoval = lcl_FindLastBookmark(xCursor, false);
904 if (sBookmarkNameAfterRemoval.isEmpty())
906 // Yes, it was removed. Restore
907 uno::Reference<text::XTextContent> xBookmark(
908 m_xTextFactory->createInstance("com.sun.star.text.Bookmark"),
909 uno::UNO_QUERY_THROW);
911 uno::Reference<container::XNamed> xBkmNamed(xBookmark,
912 uno::UNO_QUERY_THROW);
913 xBkmNamed->setName(sLastBookmarkName);
914 xTextAppend->insertTextContent(xCursor, xBookmark, !xCursor->isCollapsed());
919 catch( const uno::Exception& )
925 void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast )
927 m_bIsLastSectionGroup = bIsLast;
930 void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast )
932 m_StreamStateStack.top().bIsLastParaInSection = bIsLast;
936 void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst )
938 m_StreamStateStack.top().bIsFirstParaInSection = bIsFirst;
941 void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline )
943 m_StreamStateStack.top().bIsFirstParaInSectionAfterRedline = bIsFirstAfterRedline;
946 bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const
948 // Anchored objects may include multiple paragraphs,
949 // and none of them should be considered the first para in section.
950 return (bAfterRedline
951 ? m_StreamStateStack.top().bIsFirstParaInSectionAfterRedline
952 : m_StreamStateStack.top().bIsFirstParaInSection)
953 && !IsInShape()
954 && !IsInComments()
955 && !IsInFootOrEndnote();
958 void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst)
960 m_bIsFirstParaInShape = bIsFirst;
963 void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded )
965 m_bDummyParaAddedForTableInSection = bIsAdded;
969 void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted )
971 m_bTextFrameInserted = bIsInserted;
974 void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr)
976 m_StreamStateStack.top().bParaSectpr = bParaSectpr;
979 void DomainMapper_Impl::SetSdt(bool bSdt)
981 m_bSdt = bSdt;
983 if (m_bSdt && !m_aTextAppendStack.empty())
985 m_xSdtEntryStart = GetTopTextAppend()->getEnd();
987 else
989 m_xSdtEntryStart.clear();
993 void DomainMapper_Impl::PushSdt()
995 if (m_aTextAppendStack.empty())
997 return;
1000 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
1001 if (!xTextAppend.is())
1003 return;
1006 // This may delete text, so call it early, before we would set our start position, which may be
1007 // invalidated by a delete.
1008 MergeAtContentImageRedlineWithNext(xTextAppend);
1010 uno::Reference<text::XText> xText = xTextAppend->getText();
1011 if (!xText.is())
1013 return;
1016 uno::Reference<text::XTextCursor> xCursor
1017 = xText->createTextCursorByRange(xTextAppend->getEnd());
1018 // Offset so the cursor is not adjusted as we import the SDT's content.
1019 bool bStart = !xCursor->goLeft(1, /*bExpand=*/false);
1020 m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()});
1023 const std::stack<BookmarkInsertPosition>& DomainMapper_Impl::GetSdtStarts() const
1025 return m_xSdtStarts;
1028 void DomainMapper_Impl::PopSdt()
1030 if (m_xSdtStarts.empty())
1032 return;
1035 BookmarkInsertPosition aPosition = m_xSdtStarts.top();
1036 m_xSdtStarts.pop();
1037 uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange;
1038 uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd();
1039 uno::Reference<text::XText> xText = xEnd->getText();
1041 uno::Reference<text::XTextCursor> xCursor;
1044 xCursor = xText->createTextCursorByRange(xStart);
1046 catch (const uno::RuntimeException&)
1048 TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::DomainMapper_Impl::PopSdt: createTextCursorByRange() failed");
1049 // We redline form controls and that gets us confused when
1050 // we process the SDT around the placeholder. What seems to
1051 // happen is we lose the text-range when we pop the SDT position.
1052 // Here, we reset the text-range when we fail to create the
1053 // cursor from the top SDT position.
1054 if (m_aTextAppendStack.empty())
1056 return;
1059 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
1060 if (!xTextAppend.is())
1062 return;
1065 uno::Reference<text::XText> xText2 = xTextAppend->getText();
1066 if (!xText2.is())
1068 return;
1071 // Reset to the start.
1072 xCursor = xText2->createTextCursorByRange(xTextAppend->getStart());
1075 if (!xCursor)
1077 SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position");
1078 return;
1081 if (aPosition.m_bIsStartOfText)
1083 // Go to the start of the end's paragraph. This helps in case
1084 // DomainMapper_Impl::AddDummyParaForTableInSection() would make our range multi-paragraph,
1085 // while the intention is to keep start/end inside the same paragraph for run SDTs.
1086 uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY);
1087 if (xParagraphCursor.is()
1088 && m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent)
1090 xCursor->gotoRange(xEnd, /*bExpand=*/false);
1091 xParagraphCursor->gotoStartOfParagraph(/*bExpand=*/false);
1094 else
1096 // Undo the goLeft() in DomainMapper_Impl::PushSdt();
1097 xCursor->goRight(1, /*bExpand=*/false);
1099 xCursor->gotoRange(xEnd, /*bExpand=*/true);
1101 std::optional<OUString> oData = m_pSdtHelper->getValueFromDataBinding();
1102 if (oData.has_value())
1104 // Data binding has a value for us, prefer that over the in-document value.
1105 xCursor->setString(*oData);
1107 // Such value is always a plain text string, remove the char style of the placeholder.
1108 uno::Reference<beans::XPropertyState> xPropertyState(xCursor, uno::UNO_QUERY);
1109 if (xPropertyState.is())
1111 xPropertyState->setPropertyToDefault("CharStyleName");
1115 uno::Reference<text::XTextContent> xContentControl(
1116 m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY);
1117 uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
1118 if (m_pSdtHelper->GetShowingPlcHdr())
1120 xContentControlProps->setPropertyValue("ShowingPlaceHolder",
1121 uno::Any(m_pSdtHelper->GetShowingPlcHdr()));
1124 if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty())
1126 xContentControlProps->setPropertyValue("PlaceholderDocPart",
1127 uno::Any(m_pSdtHelper->GetPlaceholderDocPart()));
1130 if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
1132 xContentControlProps->setPropertyValue("DataBindingPrefixMappings",
1133 uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping()));
1135 if (!m_pSdtHelper->GetDataBindingXPath().isEmpty())
1137 xContentControlProps->setPropertyValue("DataBindingXpath",
1138 uno::Any(m_pSdtHelper->GetDataBindingXPath()));
1140 if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty())
1142 xContentControlProps->setPropertyValue("DataBindingStoreItemID",
1143 uno::Any(m_pSdtHelper->GetDataBindingStoreItemID()));
1146 if (!m_pSdtHelper->GetColor().isEmpty())
1148 xContentControlProps->setPropertyValue("Color",
1149 uno::Any(m_pSdtHelper->GetColor()));
1152 if (!m_pSdtHelper->GetAppearance().isEmpty())
1154 xContentControlProps->setPropertyValue("Appearance",
1155 uno::Any(m_pSdtHelper->GetAppearance()));
1158 if (!m_pSdtHelper->GetAlias().isEmpty())
1160 xContentControlProps->setPropertyValue("Alias",
1161 uno::Any(m_pSdtHelper->GetAlias()));
1164 if (!m_pSdtHelper->GetTag().isEmpty())
1166 xContentControlProps->setPropertyValue("Tag",
1167 uno::Any(m_pSdtHelper->GetTag()));
1170 if (m_pSdtHelper->GetId())
1172 xContentControlProps->setPropertyValue("Id", uno::Any(m_pSdtHelper->GetId()));
1175 if (m_pSdtHelper->GetTabIndex())
1177 xContentControlProps->setPropertyValue("TabIndex", uno::Any(m_pSdtHelper->GetTabIndex()));
1180 if (!m_pSdtHelper->GetLock().isEmpty())
1182 xContentControlProps->setPropertyValue("Lock", uno::Any(m_pSdtHelper->GetLock()));
1185 if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
1187 xContentControlProps->setPropertyValue("Checkbox", uno::Any(true));
1189 xContentControlProps->setPropertyValue("Checked", uno::Any(m_pSdtHelper->GetChecked()));
1191 xContentControlProps->setPropertyValue("CheckedState",
1192 uno::Any(m_pSdtHelper->GetCheckedState()));
1194 xContentControlProps->setPropertyValue("UncheckedState",
1195 uno::Any(m_pSdtHelper->GetUncheckedState()));
1198 if (m_pSdtHelper->getControlType() == SdtControlType::dropDown
1199 || m_pSdtHelper->getControlType() == SdtControlType::comboBox)
1201 std::vector<OUString>& rDisplayTexts = m_pSdtHelper->getDropDownDisplayTexts();
1202 std::vector<OUString>& rValues = m_pSdtHelper->getDropDownItems();
1203 if (rDisplayTexts.size() == rValues.size())
1205 uno::Sequence<beans::PropertyValues> aItems(rValues.size());
1206 beans::PropertyValues* pItems = aItems.getArray();
1207 for (size_t i = 0; i < rValues.size(); ++i)
1209 uno::Sequence<beans::PropertyValue> aItem = {
1210 comphelper::makePropertyValue("DisplayText", rDisplayTexts[i]),
1211 comphelper::makePropertyValue("Value", rValues[i]),
1213 pItems[i] = aItem;
1215 xContentControlProps->setPropertyValue("ListItems", uno::Any(aItems));
1216 if (m_pSdtHelper->getControlType() == SdtControlType::dropDown)
1218 xContentControlProps->setPropertyValue("DropDown", uno::Any(true));
1220 else
1222 xContentControlProps->setPropertyValue("ComboBox", uno::Any(true));
1227 if (m_pSdtHelper->getControlType() == SdtControlType::picture)
1229 xContentControlProps->setPropertyValue("Picture", uno::Any(true));
1232 bool bDateFromDataBinding = false;
1233 if (m_pSdtHelper->getControlType() == SdtControlType::datePicker)
1235 xContentControlProps->setPropertyValue("Date", uno::Any(true));
1236 OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear();
1237 xContentControlProps->setPropertyValue("DateFormat",
1238 uno::Any(aDateFormat.replaceAll("'", "\"")));
1239 xContentControlProps->setPropertyValue("DateLanguage",
1240 uno::Any(m_pSdtHelper->getLocale().makeStringAndClear()));
1241 OUString aCurrentDate = m_pSdtHelper->getDate().makeStringAndClear();
1242 if (oData.has_value())
1244 aCurrentDate = *oData;
1245 bDateFromDataBinding = true;
1247 xContentControlProps->setPropertyValue("CurrentDate",
1248 uno::Any(aCurrentDate));
1251 if (m_pSdtHelper->getControlType() == SdtControlType::plainText)
1253 xContentControlProps->setPropertyValue("PlainText", uno::Any(true));
1256 xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
1258 if (bDateFromDataBinding)
1260 OUString aDateString;
1261 xContentControlProps->getPropertyValue("DateString") >>= aDateString;
1262 xCursor->setString(aDateString);
1265 m_pSdtHelper->clear();
1268 void DomainMapper_Impl::PushProperties(ContextType eId)
1270 PropertyMapPtr pInsert(eId == CONTEXT_SECTION ?
1271 (new SectionPropertyMap( m_bIsFirstSection )) :
1272 eId == CONTEXT_PARAGRAPH ? new ParagraphPropertyMap : new PropertyMap);
1273 if(eId == CONTEXT_SECTION)
1275 if( m_bIsFirstSection )
1276 m_bIsFirstSection = false;
1277 // beginning with the second section group a section has to be inserted
1278 // into the document
1279 SectionPropertyMap* pSectionContext_ = dynamic_cast< SectionPropertyMap* >( pInsert.get() );
1280 if (!m_aTextAppendStack.empty())
1282 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
1283 if (xTextAppend.is() && pSectionContext_)
1284 pSectionContext_->SetStart( xTextAppend->getEnd() );
1287 if(eId == CONTEXT_PARAGRAPH && m_bIsSplitPara)
1289 // Some paragraph properties only apply at the beginning of the paragraph - apply only once.
1290 if (!m_bIsFirstRun)
1292 auto pParaContext = static_cast<ParagraphPropertyMap*>(GetTopContextOfType(eId).get());
1293 pParaContext->props().SetListId(-1);
1294 pParaContext->Erase(PROP_NUMBERING_RULES); // only true with column, not page break
1295 pParaContext->Erase(PROP_NUMBERING_LEVEL);
1296 pParaContext->Erase(PROP_NUMBERING_TYPE);
1297 pParaContext->Erase(PROP_START_WITH);
1299 pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(sal_uInt32(0)));
1300 pParaContext->Erase(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
1301 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)));
1304 m_aPropertyStacks[eId].push( GetTopContextOfType(eId));
1305 m_bIsSplitPara = false;
1307 else
1309 m_aPropertyStacks[eId].push( pInsert );
1311 m_aContextStack.push(eId);
1313 m_pTopContext = m_aPropertyStacks[eId].top();
1317 void DomainMapper_Impl::PushStyleProperties( const PropertyMapPtr& pStyleProperties )
1319 m_aPropertyStacks[CONTEXT_STYLESHEET].push( pStyleProperties );
1320 m_aContextStack.push(CONTEXT_STYLESHEET);
1322 m_pTopContext = m_aPropertyStacks[CONTEXT_STYLESHEET].top();
1326 void DomainMapper_Impl::PushListProperties(const PropertyMapPtr& pListProperties)
1328 m_aPropertyStacks[CONTEXT_LIST].push( pListProperties );
1329 m_aContextStack.push(CONTEXT_LIST);
1330 m_pTopContext = m_aPropertyStacks[CONTEXT_LIST].top();
1334 void DomainMapper_Impl::PopProperties(ContextType eId)
1336 OSL_ENSURE(!m_aPropertyStacks[eId].empty(), "section stack already empty");
1337 if ( m_aPropertyStacks[eId].empty() )
1338 return;
1340 if ( eId == CONTEXT_SECTION )
1342 if (m_aPropertyStacks[eId].size() == 1) // tdf#112202 only top level !!!
1344 m_pLastSectionContext = dynamic_cast< SectionPropertyMap* >( m_aPropertyStacks[eId].top().get() );
1345 assert(m_pLastSectionContext);
1348 else if (eId == CONTEXT_CHARACTER)
1350 m_pLastCharacterContext = m_aPropertyStacks[eId].top();
1351 // Sadly an assert about deferredCharacterProperties being empty is not possible
1352 // here, because appendTextPortion() may not be called for every character section.
1353 m_deferredCharacterProperties.clear();
1356 if (!IsInFootOrEndnote() && IsInCustomFootnote() && !m_aPropertyStacks[eId].empty())
1358 PropertyMapPtr pRet = m_aPropertyStacks[eId].top();
1359 if (pRet->GetFootnote().is() && m_pFootnoteContext.is())
1360 EndCustomFootnote();
1363 m_aPropertyStacks[eId].pop();
1364 m_aContextStack.pop();
1365 if(!m_aContextStack.empty() && !m_aPropertyStacks[m_aContextStack.top()].empty())
1367 m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top();
1368 else
1370 // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section context end");
1371 m_pTopContext.clear();
1376 PropertyMapPtr DomainMapper_Impl::GetTopContextOfType(ContextType eId)
1378 PropertyMapPtr pRet;
1379 if(!m_aPropertyStacks[eId].empty())
1380 pRet = m_aPropertyStacks[eId].top();
1381 return pRet;
1384 bool DomainMapper_Impl::HasTopText() const
1386 return !m_aTextAppendStack.empty();
1389 uno::Reference< text::XTextAppend > const & DomainMapper_Impl::GetTopTextAppend()
1391 OSL_ENSURE(!m_aTextAppendStack.empty(), "text append stack is empty" );
1392 return m_aTextAppendStack.top().xTextAppend;
1395 FieldContextPtr const & DomainMapper_Impl::GetTopFieldContext()
1397 SAL_WARN_IF(m_aFieldStack.empty(), "writerfilter.dmapper", "Field stack is empty");
1398 return m_aFieldStack.back();
1401 bool DomainMapper_Impl::HasTopAnchoredObjects() const
1403 return !m_aTextAppendStack.empty() && !m_aTextAppendStack.top().m_aAnchoredObjects.empty();
1406 void DomainMapper_Impl::InitTabStopFromStyle( const uno::Sequence< style::TabStop >& rInitTabStops )
1408 OSL_ENSURE(m_aCurrentTabStops.empty(), "tab stops already initialized");
1409 for( const auto& rTabStop : rInitTabStops)
1411 m_aCurrentTabStops.emplace_back(rTabStop);
1415 void DomainMapper_Impl::IncorporateTabStop( const DeletableTabStop & rTabStop )
1417 sal_Int32 nConverted = rTabStop.Position;
1418 auto aIt = std::find_if(m_aCurrentTabStops.begin(), m_aCurrentTabStops.end(),
1419 [&nConverted](const DeletableTabStop& rCurrentTabStop) { return rCurrentTabStop.Position == nConverted; });
1420 if( aIt != m_aCurrentTabStops.end() )
1422 if( rTabStop.bDeleted )
1423 m_aCurrentTabStops.erase( aIt );
1424 else
1425 *aIt = rTabStop;
1427 else
1428 m_aCurrentTabStops.push_back( rTabStop );
1432 uno::Sequence< style::TabStop > DomainMapper_Impl::GetCurrentTabStopAndClear()
1434 std::vector<style::TabStop> aRet;
1435 for (const DeletableTabStop& rStop : m_aCurrentTabStops)
1437 if (!rStop.bDeleted)
1438 aRet.push_back(rStop);
1440 m_aCurrentTabStops.clear();
1441 return comphelper::containerToSequence(aRet);
1444 OUString DomainMapper_Impl::GetCurrentParaStyleName()
1446 OUString sName;
1447 // use saved currParaStyleName as a fallback, in case no particular para style name applied.
1448 // tdf#134784 except in the case of first paragraph of shapes to avoid bad fallback.
1449 // TODO fix this "highly inaccurate" m_sCurrentParaStyleName
1450 if ( !IsInShape() )
1451 sName = m_sCurrentParaStyleName;
1453 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
1454 if ( pParaContext && pParaContext->isSet(PROP_PARA_STYLE_NAME) )
1455 pParaContext->getProperty(PROP_PARA_STYLE_NAME)->second >>= sName;
1457 // In rare situations the name might still be blank, so use the default style,
1458 // despite documentation that states, "If this attribute is not specified for any style,
1459 // then no properties shall be applied to objects of the specified type."
1460 // Word, however, assigns "Normal" style even in these situations.
1461 if ( !m_bInStyleSheetImport && sName.isEmpty() )
1462 sName = GetDefaultParaStyleName();
1464 return sName;
1467 OUString DomainMapper_Impl::GetDefaultParaStyleName()
1469 // After import the default style won't change and is frequently requested: cache the LO style name.
1470 // TODO assert !InStyleSheetImport? This function really only makes sense once import is finished anyway.
1471 if ( m_sDefaultParaStyleName.isEmpty() )
1473 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindDefaultParaStyle();
1474 if ( pEntry && !pEntry->m_sConvertedStyleName.isEmpty() )
1476 if ( !m_bInStyleSheetImport )
1477 m_sDefaultParaStyleName = pEntry->m_sConvertedStyleName;
1478 return pEntry->m_sConvertedStyleName;
1480 else
1481 return "Standard";
1483 return m_sDefaultParaStyleName;
1486 uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault)
1488 while(pEntry)
1490 if(pEntry->m_pProperties)
1492 std::optional<PropertyMap::Property> aProperty =
1493 pEntry->m_pProperties->getProperty(eId);
1494 if( aProperty )
1496 if (pIsDocDefault)
1497 *pIsDocDefault = pEntry->m_pProperties->isDocDefault(eId);
1499 return aProperty->second;
1502 //search until the property is set or no parent is available
1503 StyleSheetEntryPtr pNewEntry;
1504 if ( !pEntry->m_sBaseStyleIdentifier.isEmpty() )
1505 pNewEntry = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
1507 SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
1509 if (pEntry == pNewEntry) //fdo#49587
1510 break;
1512 pEntry = pNewEntry;
1514 // not found in style, try the document's DocDefault properties
1515 if ( bDocDefaults && bPara )
1517 const PropertyMapPtr& pDefaultParaProps = GetStyleSheetTable()->GetDefaultParaProps();
1518 if ( pDefaultParaProps )
1520 std::optional<PropertyMap::Property> aProperty = pDefaultParaProps->getProperty(eId);
1521 if ( aProperty )
1523 if (pIsDocDefault)
1524 *pIsDocDefault = true;
1526 return aProperty->second;
1530 if ( bDocDefaults && isCharacterProperty(eId) )
1532 const PropertyMapPtr& pDefaultCharProps = GetStyleSheetTable()->GetDefaultCharProps();
1533 if ( pDefaultCharProps )
1535 std::optional<PropertyMap::Property> aProperty = pDefaultCharProps->getProperty(eId);
1536 if ( aProperty )
1538 if (pIsDocDefault)
1539 *pIsDocDefault = true;
1541 return aProperty->second;
1546 if (pIsDocDefault)
1547 *pIsDocDefault = false;
1549 return uno::Any();
1552 uno::Any DomainMapper_Impl::GetPropertyFromParaStyleSheet(PropertyIds eId)
1554 StyleSheetEntryPtr pEntry;
1555 if ( m_bInStyleSheetImport )
1556 pEntry = GetStyleSheetTable()->GetCurrentEntry();
1557 else
1558 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName());
1559 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
1562 uno::Any DomainMapper_Impl::GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext)
1564 if ( m_bInStyleSheetImport || eId == PROP_CHAR_STYLE_NAME || !isCharacterProperty(eId) )
1565 return uno::Any();
1567 StyleSheetEntryPtr pEntry;
1568 OUString sCharStyleName;
1569 if ( GetAnyProperty(PROP_CHAR_STYLE_NAME, rContext) >>= sCharStyleName )
1570 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sCharStyleName);
1571 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/false, /*bPara=*/false);
1574 uno::Any DomainMapper_Impl::GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext)
1576 // first look in directly applied attributes
1577 if ( rContext )
1579 std::optional<PropertyMap::Property> aProperty = rContext->getProperty(eId);
1580 if ( aProperty )
1581 return aProperty->second;
1584 // then look whether it was directly applied as a paragraph property
1585 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
1586 if (pParaContext && rContext != pParaContext)
1588 std::optional<PropertyMap::Property> aProperty = pParaContext->getProperty(eId);
1589 if (aProperty)
1590 return aProperty->second;
1593 // then look whether it was inherited from a directly applied character style
1594 if ( eId != PROP_CHAR_STYLE_NAME && isCharacterProperty(eId) )
1596 uno::Any aRet = GetPropertyFromCharStyleSheet(eId, rContext);
1597 if ( aRet.hasValue() )
1598 return aRet;
1601 // then look in current paragraph style, and docDefaults
1602 return GetPropertyFromParaStyleSheet(eId);
1605 OUString DomainMapper_Impl::GetListStyleName(sal_Int32 nListId)
1607 auto const pList(GetListTable()->GetList( nListId ));
1608 return pList ? pList->GetStyleName() : OUString();
1611 ListsManager::Pointer const & DomainMapper_Impl::GetListTable()
1613 if(!m_pListTable)
1614 m_pListTable =
1615 new ListsManager( m_rDMapper, m_xTextFactory );
1616 return m_pListTable;
1620 void DomainMapper_Impl::deferBreak( BreakType deferredBreakType)
1622 assert(!m_StreamStateStack.empty());
1623 switch (deferredBreakType)
1625 case LINE_BREAK:
1626 m_StreamStateStack.top().nLineBreaksDeferred++;
1627 break;
1628 case COLUMN_BREAK:
1629 m_StreamStateStack.top().bIsColumnBreakDeferred = true;
1630 break;
1631 case PAGE_BREAK:
1632 // See SwWW8ImplReader::HandlePageBreakChar(), page break should be
1633 // ignored inside tables.
1634 if (0 < m_StreamStateStack.top().nTableDepth)
1635 return;
1637 m_StreamStateStack.top().bIsPageBreakDeferred = true;
1638 break;
1639 default:
1640 return;
1644 bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType )
1646 assert(!m_StreamStateStack.empty());
1647 switch (deferredBreakType)
1649 case LINE_BREAK:
1650 return 0 < m_StreamStateStack.top().nLineBreaksDeferred;
1651 case COLUMN_BREAK:
1652 return m_StreamStateStack.top().bIsColumnBreakDeferred;
1653 case PAGE_BREAK:
1654 return m_StreamStateStack.top().bIsPageBreakDeferred;
1655 default:
1656 return false;
1660 void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType)
1662 assert(!m_StreamStateStack.empty());
1663 switch (deferredBreakType)
1665 case LINE_BREAK:
1666 assert(0 < m_StreamStateStack.top().nLineBreaksDeferred);
1667 m_StreamStateStack.top().nLineBreaksDeferred--;
1668 break;
1669 case COLUMN_BREAK:
1670 m_StreamStateStack.top().bIsColumnBreakDeferred = false;
1671 break;
1672 case PAGE_BREAK:
1673 m_StreamStateStack.top().bIsPageBreakDeferred = false;
1674 break;
1675 default:
1676 break;
1680 void DomainMapper_Impl::clearDeferredBreaks()
1682 assert(!m_StreamStateStack.empty());
1683 m_StreamStateStack.top().nLineBreaksDeferred = 0;
1684 m_StreamStateStack.top().bIsColumnBreakDeferred = false;
1685 m_StreamStateStack.top().bIsPageBreakDeferred = false;
1688 void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred)
1690 m_bSdtEndDeferred = bSdtEndDeferred;
1693 bool DomainMapper_Impl::isSdtEndDeferred() const
1695 return m_bSdtEndDeferred;
1698 void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred)
1700 m_bParaSdtEndDeferred = bParaSdtEndDeferred;
1703 bool DomainMapper_Impl::isParaSdtEndDeferred() const
1705 return m_bParaSdtEndDeferred;
1708 static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties,
1709 uno::Reference<text::XTextRange> const& xStartTextRange,
1710 uno::Reference<text::XTextRange> const& xEndTextRange,
1711 bool bIsRTFImport)
1715 if (!xStartTextRange.is()) //rhbz#1077780
1716 return;
1717 uno::Reference<text::XTextCursor> xRangeCursor = xStartTextRange->getText()->createTextCursorByRange( xStartTextRange );
1718 xRangeCursor->gotoRange( xEndTextRange, true );
1720 uno::Reference<beans::XPropertySet> xTextRangeProperties(xRangeCursor, uno::UNO_QUERY);
1721 if(!xTextRangeProperties.is())
1722 return ;
1724 static PropertyIds const aBorderProperties[] =
1726 PROP_LEFT_BORDER,
1727 PROP_RIGHT_BORDER,
1728 PROP_TOP_BORDER,
1729 PROP_BOTTOM_BORDER,
1730 PROP_LEFT_BORDER_DISTANCE,
1731 PROP_RIGHT_BORDER_DISTANCE,
1732 PROP_TOP_BORDER_DISTANCE,
1733 PROP_BOTTOM_BORDER_DISTANCE
1736 // The frame width specified does not include border spacing,
1737 // so the frame needs to be increased by the left/right para border spacing amount
1738 sal_Int32 nWidth = 0;
1739 sal_Int32 nIndexOfWidthProperty = -1;
1740 sal_Int16 nType = text::SizeType::FIX;
1741 for (size_t i = 0; nType == text::SizeType::FIX && i < rFrameProperties.size(); ++i)
1743 if (rFrameProperties[i].Name == "WidthType")
1744 rFrameProperties[i].Value >>= nType;
1745 else if (rFrameProperties[i].Name == "Width")
1746 nIndexOfWidthProperty = i;
1748 if (nIndexOfWidthProperty > -1 && nType == text::SizeType::FIX)
1749 rFrameProperties[nIndexOfWidthProperty].Value >>= nWidth;
1751 for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty)
1753 const OUString & sPropertyName = getPropertyName(aBorderProperties[nProperty]);
1754 beans::PropertyValue aValue;
1755 aValue.Name = sPropertyName;
1756 aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName);
1757 if( nProperty < 4 )
1759 xTextRangeProperties->setPropertyValue( sPropertyName, uno::Any(table::BorderLine2()));
1760 if (!aValue.Value.hasValue())
1761 aValue.Value <<= table::BorderLine2();
1763 else // border spacing
1765 sal_Int32 nDistance = 0;
1766 aValue.Value >>= nDistance;
1768 // left4/right5 need to be duplicated because of INVERT_BORDER_SPACING (DOCX only)
1769 // Do not duplicate the top6/bottom7 border spacing.
1770 if (nProperty > 5 || bIsRTFImport)
1771 aValue.Value <<= sal_Int32(0);
1773 // frames need to be increased by the left/right para border spacing amount
1774 // This is needed for RTF as well, but that requires other export/import fixes.
1775 if (!bIsRTFImport && nProperty < 6 && nWidth && nDistance)
1777 nWidth += nDistance;
1778 rFrameProperties[nIndexOfWidthProperty].Value <<= nWidth;
1781 if (aValue.Value.hasValue())
1782 rFrameProperties.push_back(aValue);
1785 catch( const uno::Exception& )
1791 static void lcl_AddRange(
1792 ParagraphPropertiesPtr const & pToBeSavedProperties,
1793 uno::Reference< text::XTextAppend > const& xTextAppend,
1794 TextAppendContext const & rAppendContext)
1796 uno::Reference<text::XParagraphCursor> xParaCursor(
1797 xTextAppend->createTextCursorByRange( rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()), uno::UNO_QUERY_THROW );
1798 pToBeSavedProperties->SetEndingRange(xParaCursor->getStart());
1799 xParaCursor->gotoStartOfParagraph( false );
1801 pToBeSavedProperties->SetStartingRange(xParaCursor->getStart());
1805 //define some default frame width - 0cm ATM: this allow the frame to be wrapped around the text
1806 constexpr sal_Int32 DEFAULT_FRAME_MIN_WIDTH = 0;
1807 constexpr sal_Int32 DEFAULT_FRAME_MIN_HEIGHT = 0;
1808 constexpr sal_Int32 DEFAULT_VALUE = 0;
1810 std::vector<css::beans::PropertyValue>
1811 DomainMapper_Impl::MakeFrameProperties(const ParagraphProperties& rProps)
1813 std::vector<beans::PropertyValue> aFrameProperties;
1817 // A paragraph's properties come from direct formatting or somewhere in the style hierarchy
1818 std::vector<const ParagraphProperties*> vProps;
1819 vProps.emplace_back(&rProps);
1820 sal_Int8 nSafetyLimit = 16;
1821 StyleSheetEntryPtr pStyle
1822 = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rProps.GetParaStyleName());
1823 while (nSafetyLimit-- && pStyle && pStyle->m_pProperties)
1825 vProps.emplace_back(&pStyle->m_pProperties->props());
1826 assert(pStyle->m_sBaseStyleIdentifier != pStyle->m_sStyleName);
1827 if (pStyle->m_sBaseStyleIdentifier.isEmpty())
1828 break;
1829 pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(pStyle->m_sBaseStyleIdentifier);
1831 SAL_WARN_IF(!nSafetyLimit, "writerfilter.dmapper", "Inheritance loop likely: early exit");
1834 sal_Int32 nWidth = -1;
1835 for (const auto pProp : vProps)
1837 if (pProp->Getw() < 0)
1838 continue;
1839 nWidth = pProp->Getw();
1840 break;
1842 bool bAutoWidth = nWidth < 1;
1843 if (bAutoWidth)
1844 nWidth = DEFAULT_FRAME_MIN_WIDTH;
1845 aFrameProperties.push_back(
1846 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth));
1847 aFrameProperties.push_back(
1848 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE),
1849 bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX));
1851 bool bValidH = false;
1852 sal_Int32 nHeight = DEFAULT_FRAME_MIN_HEIGHT;
1853 for (const auto pProp : vProps)
1855 if (pProp->Geth() < 0)
1856 continue;
1857 nHeight = pProp->Geth();
1858 bValidH = true;
1859 break;
1861 aFrameProperties.push_back(
1862 comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), nHeight));
1864 sal_Int16 nhRule = -1;
1865 for (const auto pProp : vProps)
1867 if (pProp->GethRule() < 0)
1868 continue;
1869 nhRule = pProp->GethRule();
1870 break;
1872 if (nhRule < 0)
1874 if (bValidH && nHeight)
1876 // [MS-OE376] Word uses a default value of "atLeast" for
1877 // this attribute when the value of the h attribute is not 0.
1878 nhRule = text::SizeType::MIN;
1880 else
1882 nhRule = text::SizeType::VARIABLE;
1885 aFrameProperties.push_back(
1886 comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule));
1888 bool bValidX = false;
1889 sal_Int32 nX = DEFAULT_VALUE;
1890 for (const auto pProp : vProps)
1892 bValidX = pProp->IsxValid();
1893 if (!bValidX)
1894 continue;
1895 nX = pProp->Getx();
1896 break;
1898 aFrameProperties.push_back(
1899 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), nX));
1901 sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
1902 for (const auto pProp : vProps)
1904 if (pProp->GetxAlign() < 0)
1905 continue;
1906 nHoriOrient = pProp->GetxAlign();
1907 break;
1909 aFrameProperties.push_back(
1910 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient));
1912 //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11
1913 sal_Int16 nHAnchor = text::RelOrientation::FRAME; // 'text'
1914 for (const auto pProp : vProps)
1916 if (pProp->GethAnchor() < 0)
1917 continue;
1918 nHAnchor = pProp->GethAnchor();
1919 break;
1921 aFrameProperties.push_back(
1922 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), nHAnchor));
1924 bool bValidY = false;
1925 sal_Int32 nY = DEFAULT_VALUE;
1926 for (const auto pProp : vProps)
1928 bValidY = pProp->IsyValid();
1929 if (!bValidY)
1930 continue;
1931 nY = pProp->Gety();
1932 break;
1934 aFrameProperties.push_back(
1935 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), nY));
1937 sal_Int16 nVertOrient = text::VertOrientation::NONE;
1938 // Testing indicates that yAlign should be ignored if there is any specified w:y
1939 if (!bValidY)
1941 for (const auto pProp : vProps)
1943 if (pProp->GetyAlign() < 0)
1944 continue;
1945 nVertOrient = pProp->GetyAlign();
1946 break;
1950 // Default the anchor in case FramePr_vAnchor is missing.
1951 // ECMA 17.3.1.11 says "page",
1952 // but errata documentation MS-OE376 2.1.48 Section 2.3.1.11 says "text"
1953 // while actual testing usually indicates "margin" tdf#157572 tdf#112287
1954 sal_Int16 nVAnchor = text::RelOrientation::PAGE_PRINT_AREA; // 'margin'
1955 if (!nY && (bValidY || nVertOrient == text::VertOrientation::NONE))
1957 // special cases? "auto" position defaults to "paragraph" based on testing when w:y=0
1958 nVAnchor = text::RelOrientation::FRAME; // 'text'
1960 for (const auto pProp : vProps)
1962 if (pProp->GetvAnchor() < 0)
1963 continue;
1964 nVAnchor = pProp->GetvAnchor();
1965 // vAlign is ignored if vAnchor is set to 'text'
1966 if (nVAnchor == text::RelOrientation::FRAME)
1967 nVertOrient = text::VertOrientation::NONE;
1968 break;
1970 aFrameProperties.push_back(
1971 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), nVAnchor));
1972 aFrameProperties.push_back(
1973 comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient));
1975 text::WrapTextMode nWrap = text::WrapTextMode_NONE;
1976 for (const auto pProp : vProps)
1978 if (pProp->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
1979 continue;
1980 nWrap = pProp->GetWrap();
1981 break;
1983 aFrameProperties.push_back(
1984 comphelper::makePropertyValue(getPropertyName(PROP_SURROUND), nWrap));
1986 sal_Int32 nRightDist = 0;
1987 sal_Int32 nLeftDist = 0;
1988 for (const auto pProp : vProps)
1990 if (pProp->GethSpace() < 0)
1991 continue;
1992 nLeftDist = nRightDist = pProp->GethSpace();
1993 break;
1995 aFrameProperties.push_back(comphelper::makePropertyValue(
1996 getPropertyName(PROP_LEFT_MARGIN),
1997 nHoriOrient == text::HoriOrientation::LEFT ? 0 : nLeftDist));
1998 aFrameProperties.push_back(comphelper::makePropertyValue(
1999 getPropertyName(PROP_RIGHT_MARGIN),
2000 nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nRightDist));
2002 sal_Int32 nBottomDist = 0;
2003 sal_Int32 nTopDist = 0;
2004 for (const auto pProp : vProps)
2006 if (pProp->GetvSpace() < 0)
2007 continue;
2008 nTopDist = nBottomDist = pProp->GetvSpace();
2009 break;
2011 aFrameProperties.push_back(comphelper::makePropertyValue(
2012 getPropertyName(PROP_TOP_MARGIN),
2013 nVertOrient == text::VertOrientation::TOP ? 0 : nTopDist));
2014 aFrameProperties.push_back(comphelper::makePropertyValue(
2015 getPropertyName(PROP_BOTTOM_MARGIN),
2016 nVertOrient == text::VertOrientation::BOTTOM ? 0 : nBottomDist));
2018 catch (const uno::Exception&)
2022 return aFrameProperties;
2025 void DomainMapper_Impl::CheckUnregisteredFrameConversion(bool bPreventOverlap)
2027 if (m_aTextAppendStack.empty())
2028 return;
2029 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
2030 // n#779642: ignore fly frame inside table as it could lead to messy situations
2031 if (!rAppendContext.pLastParagraphProperties)
2032 return;
2033 if (!rAppendContext.pLastParagraphProperties->IsFrameMode())
2034 return;
2035 if (!hasTableManager())
2036 return;
2037 if (getTableManager().isInTable())
2038 return;
2040 std::vector<beans::PropertyValue> aFrameProperties
2041 = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
2043 if (const std::optional<sal_Int16> nDirection = PopFrameDirection())
2045 aFrameProperties.push_back(
2046 comphelper::makePropertyValue(getPropertyName(PROP_FRM_DIRECTION), *nDirection));
2049 if (bPreventOverlap)
2050 aFrameProperties.push_back(comphelper::makePropertyValue("AllowOverlap", uno::Any(false)));
2052 // If there is no fill, the Word default is 100% transparency.
2053 // Otherwise CellColorHandler has priority, and this setting
2054 // will be ignored.
2055 aFrameProperties.push_back(comphelper::makePropertyValue(
2056 getPropertyName(PROP_BACK_COLOR_TRANSPARENCY), sal_Int32(100)));
2058 uno::Sequence<beans::PropertyValue> aGrabBag(comphelper::InitPropertySequence(
2059 { { "ParaFrameProperties", uno::Any(true) } }));
2060 aFrameProperties.push_back(comphelper::makePropertyValue("FrameInteropGrabBag", aGrabBag));
2062 lcl_MoveBorderPropertiesToFrame(aFrameProperties,
2063 rAppendContext.pLastParagraphProperties->GetStartingRange(),
2064 rAppendContext.pLastParagraphProperties->GetEndingRange(),
2065 IsRTFImport());
2067 //frame conversion has to be executed after table conversion, not now
2068 RegisterFrameConversion(rAppendContext.pLastParagraphProperties->GetStartingRange(),
2069 rAppendContext.pLastParagraphProperties->GetEndingRange(),
2070 std::move(aFrameProperties));
2073 /// Check if the style or its parent has a list id, recursively.
2074 static sal_Int32 lcl_getListId(const StyleSheetEntryPtr& rEntry, const StyleSheetTablePtr& rStyleTable, bool & rNumberingFromBaseStyle)
2076 const StyleSheetPropertyMap* pEntryProperties = rEntry->m_pProperties.get();
2077 if (!pEntryProperties)
2078 return -1;
2080 sal_Int32 nListId = pEntryProperties->props().GetListId();
2081 // The style itself has a list id.
2082 if (nListId >= 0)
2083 return nListId;
2085 // The style has no parent.
2086 if (rEntry->m_sBaseStyleIdentifier.isEmpty())
2087 return -1;
2089 const StyleSheetEntryPtr pParent = rStyleTable->FindStyleSheetByISTD(rEntry->m_sBaseStyleIdentifier);
2090 // No such parent style or loop in the style hierarchy.
2091 if (!pParent || pParent == rEntry)
2092 return -1;
2094 rNumberingFromBaseStyle = true;
2096 return lcl_getListId(pParent, rStyleTable, rNumberingFromBaseStyle);
2099 /// Return the paragraph's list level (from styles, unless pParacontext is provided).
2100 /// -1 indicates the level is not set anywhere. [In that case, with a numId, use 0 (level 1)]
2101 /// 9 indicates that numbering should be at body level (aka disabled) - rarely used by MSWord.
2102 /// 0-8 are the nine valid numbering levels.
2103 sal_Int16 DomainMapper_Impl::GetListLevel(const StyleSheetEntryPtr& pEntry,
2104 const PropertyMapPtr& pParaContext)
2106 sal_Int16 nListLevel = -1;
2107 if (pParaContext)
2109 // Deliberately ignore inherited PROP_NUMBERING_LEVEL. Only trust StyleSheetEntry for that.
2110 std::optional<PropertyMap::Property> aLvl = pParaContext->getProperty(PROP_NUMBERING_LEVEL);
2111 if (aLvl)
2112 aLvl->second >>= nListLevel;
2114 if (nListLevel != -1)
2115 return nListLevel;
2118 if (!pEntry)
2119 return -1;
2121 const StyleSheetPropertyMap* pEntryProperties = pEntry->m_pProperties.get();
2122 if (!pEntryProperties)
2123 return -1;
2125 nListLevel = pEntryProperties->GetListLevel();
2126 // The style itself has a list level.
2127 if (nListLevel >= 0)
2128 return nListLevel;
2130 // The style has no parent.
2131 if (pEntry->m_sBaseStyleIdentifier.isEmpty())
2132 return -1;
2134 const StyleSheetEntryPtr pParent = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
2135 // No such parent style or loop in the style hierarchy.
2136 if (!pParent || pParent == pEntry)
2137 return -1;
2139 return GetListLevel(pParent);
2142 void DomainMapper_Impl::ValidateListLevel(const OUString& sStyleIdentifierD)
2144 StyleSheetEntryPtr pMyStyle = GetStyleSheetTable()->FindStyleSheetByISTD(sStyleIdentifierD);
2145 if (!pMyStyle)
2146 return;
2148 sal_Int8 nListLevel = GetListLevel(pMyStyle);
2149 if (nListLevel < 0 || nListLevel >= WW_OUTLINE_MAX)
2150 return;
2152 bool bDummy = false;
2153 sal_Int16 nListId = lcl_getListId(pMyStyle, GetStyleSheetTable(), bDummy);
2154 if (nListId < 1)
2155 return;
2157 auto const pList(GetListTable()->GetList(nListId));
2158 if (!pList)
2159 return;
2161 auto pLevel = pList->GetLevel(nListLevel);
2162 if (!pLevel && pList->GetAbstractDefinition())
2163 pLevel = pList->GetAbstractDefinition()->GetLevel(nListLevel);
2164 if (!pLevel)
2165 return;
2167 if (!pLevel->GetParaStyle())
2169 // First come, first served, and it hasn't been claimed yet, so claim it now.
2170 pLevel->SetParaStyle(pMyStyle);
2172 else if (pLevel->GetParaStyle() != pMyStyle)
2174 // This level is already used by another style, so prevent numbering via this style
2175 // by setting to body level (9).
2176 pMyStyle->m_pProperties->SetListLevel(WW_OUTLINE_MAX);
2177 // WARNING: PROP_NUMBERING_LEVEL is now out of sync with GetListLevel()
2181 void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove, const bool bNoNumbering )
2183 if (m_bDiscardHeaderFooter)
2184 return;
2186 if (!m_aFieldStack.empty())
2188 FieldContextPtr pFieldContext = m_aFieldStack.back();
2189 if (pFieldContext && !pFieldContext->IsCommandCompleted())
2191 std::vector<OUString> aCommandParts = pFieldContext->GetCommandParts();
2192 if (!aCommandParts.empty() && aCommandParts[0] == "IF")
2194 // Conditional text field conditions don't support linebreaks in Writer.
2195 return;
2199 if (pFieldContext && pFieldContext->IsCommandCompleted())
2201 if (pFieldContext->GetFieldId() == FIELD_IF)
2203 // Conditional text fields can't contain newlines, finish the paragraph later.
2204 FieldParagraph aFinish{pPropertyMap, bRemove};
2205 pFieldContext->GetParagraphsToFinish().push_back(aFinish);
2206 return;
2211 #ifdef DBG_UTIL
2212 TagLogger::getInstance().startElement("finishParagraph");
2213 #endif
2215 ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pPropertyMap.get() );
2216 if (m_aTextAppendStack.empty())
2217 return;
2218 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
2219 uno::Reference< text::XTextAppend > xTextAppend(rAppendContext.xTextAppend);
2220 #ifdef DBG_UTIL
2221 TagLogger::getInstance().attribute("isTextAppend", sal_uInt32(xTextAppend.is()));
2222 #endif
2224 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetCurrentParaStyleName() );
2225 SAL_WARN_IF(!pEntry, "writerfilter.dmapper", "no style sheet found");
2226 const StyleSheetPropertyMap* pStyleSheetProperties = pEntry ? pEntry->m_pProperties.get() : nullptr;
2227 sal_Int32 nListId = pParaContext ? pParaContext->props().GetListId() : -1;
2228 bool isNumberingViaStyle(false);
2229 bool isNumberingViaRule = nListId > -1;
2230 if ( !bRemove && pStyleSheetProperties && pParaContext )
2232 if (!pEntry || pEntry->m_nStyleTypeCode != StyleType::STYLE_TYPE_PARA) {
2233 // We could not resolve paragraph style or it is not a paragraph style
2234 // Remove this style reference, otherwise it will cause exceptions during further
2235 // processing and not all paragraph styles will be initialized.
2236 SAL_WARN("writerfilter.dmapper", "Paragraph style is incorrect. Ignored");
2237 pParaContext->Erase(PROP_PARA_STYLE_NAME);
2240 bool bNumberingFromBaseStyle = false;
2241 if (!isNumberingViaRule)
2242 nListId = lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle);
2244 //apply numbering level/style to paragraph if it was set at the style, but only if the paragraph itself
2245 //does not specify the numbering
2246 sal_Int16 nListLevel = GetListLevel(pEntry, pParaContext);
2247 // Undefined listLevel with a valid numId is treated as a first level numbering.
2248 if (nListLevel == -1 && nListId > (IsOOXMLImport() ? 0 : -1))
2249 nListLevel = 0;
2251 if (!bNoNumbering && nListLevel >= 0 && nListLevel < 9)
2252 pParaContext->Insert( PROP_NUMBERING_LEVEL, uno::Any(nListLevel), false );
2254 auto const pList(GetListTable()->GetList(nListId));
2255 if (pList && !pParaContext->isSet(PROP_NUMBERING_STYLE_NAME))
2257 // ListLevel 9 means Body Level/no numbering.
2258 if (bNoNumbering || nListLevel == 9)
2260 pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(OUString()), true);
2261 pParaContext->Erase(PROP_NUMBERING_LEVEL);
2263 else if ( !isNumberingViaRule )
2265 isNumberingViaStyle = true;
2266 // Since LO7.0/tdf#131321 fixed the loss of numbering in styles, this OUGHT to be obsolete,
2267 // but now other new/critical LO7.0 code expects it, and perhaps some corner cases still need it as well.
2268 pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true);
2270 else
2272 // we have direct numbering, as well as paragraph-style numbering.
2273 // Apply the style if it uses the same list as the direct numbering,
2274 // otherwise the directly-applied-to-paragraph status will be lost,
2275 // and the priority of the numbering-style-indents will be lowered. tdf#133000
2276 bool bDummy;
2277 if (nListId == lcl_getListId(pEntry, GetStyleSheetTable(), bDummy))
2278 pParaContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true );
2282 if ( isNumberingViaStyle )
2284 // When numbering is defined by the paragraph style, then the para-style indents have priority.
2285 // But since import has just copied para-style's PROP_NUMBERING_STYLE_NAME directly onto the paragraph,
2286 // the numbering indents now have the priority.
2287 // So now import must also copy the para-style indents directly onto the paragraph to compensate.
2288 std::optional<PropertyMap::Property> oProperty;
2289 const StyleSheetEntryPtr pParent = (!pEntry->m_sBaseStyleIdentifier.isEmpty()) ? GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier) : nullptr;
2290 const StyleSheetPropertyMap* pParentProperties = pParent ? pParent->m_pProperties.get() : nullptr;
2291 if (!pEntry->m_sBaseStyleIdentifier.isEmpty())
2293 oProperty = pStyleSheetProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT);
2294 if ( oProperty
2295 // If the numbering comes from a base style, indent of the base style has also priority.
2296 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))) )
2297 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false);
2299 oProperty = pStyleSheetProperties->getProperty(PROP_PARA_LEFT_MARGIN);
2300 if ( oProperty
2301 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_LEFT_MARGIN))) )
2302 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, oProperty->second, /*bOverwrite=*/false);
2304 // We're inheriting properties from a numbering style. Make sure a possible right margin is inherited from the base style.
2305 sal_Int32 nParaRightMargin;
2306 if ( pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_RIGHT_MARGIN)) && (nParaRightMargin = oProperty->second.get<sal_Int32>()) != 0 )
2308 // If we're setting the right margin, we should set the first / left margin as well from the numbering style.
2309 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent");
2310 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt");
2311 if (nFirstLineIndent != 0)
2312 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false);
2313 if (nParaLeftMargin != 0)
2314 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false);
2316 // Override right margin value with value from current style, if any
2317 if (pStyleSheetProperties && pStyleSheetProperties->isSet(PROP_PARA_RIGHT_MARGIN))
2318 nParaRightMargin = pStyleSheetProperties->getProperty(PROP_PARA_RIGHT_MARGIN)->second.get<sal_Int32>();
2320 pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, uno::Any(nParaRightMargin), /*bOverwrite=*/false);
2323 // Paragraph style based right paragraph indentation affects not paragraph style based lists in DOCX.
2324 // Apply it as direct formatting, also left and first line indentation of numbering to keep them.
2325 else if (isNumberingViaRule)
2327 uno::Any aRightMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
2328 if ( aRightMargin != uno::Any() )
2330 pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, aRightMargin, /*bOverwrite=*/false);
2332 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent");
2333 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt");
2334 if (nFirstLineIndent != 0)
2335 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false);
2336 if (nParaLeftMargin != 0)
2337 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false);
2341 if (nListId == 0 && !pList)
2343 // listid = 0 and no list definition is used in DOCX to stop numbering
2344 // defined somewhere in parent styles
2345 // And here we should explicitly set left margin and first-line margin.
2346 // They can be taken from referred style, but not from styles with listid!
2347 uno::Any aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_FIRST_LINE_INDENT, pEntry, m_pStyleSheetTable);
2348 if (aProp.hasValue())
2349 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, aProp, false);
2350 else
2351 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)), false);
2353 aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_LEFT_MARGIN, pEntry, m_pStyleSheetTable);
2354 if (aProp.hasValue())
2355 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, aProp, false);
2356 else
2357 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(sal_uInt32(0)), false);
2361 // apply AutoSpacing: it has priority over all other margin settings
2362 // (note that numbering with autoSpacing is handled separately later on)
2363 const bool bAllowAdjustments = !GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing();
2364 sal_Int32 nBeforeAutospacing = -1;
2365 bool bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
2366 const bool bNoTopmargin = pParaContext && !pParaContext->isSet(PROP_PARA_TOP_MARGIN);
2367 // apply INHERITED autospacing only if top margin is not set
2368 if ( bIsAutoSet || bNoTopmargin )
2370 GetAnyProperty(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, pPropertyMap) >>= nBeforeAutospacing;
2371 // tdf#137655 only w:beforeAutospacing=0 was specified, but not PARA_TOP_MARGIN
2372 // (see default_spacing = -1 in processing of LN_CT_Spacing_beforeAutospacing)
2373 if ( bNoTopmargin && nBeforeAutospacing == ConversionHelper::convertTwipToMM100(-1) )
2375 sal_Int32 nStyleAuto = -1;
2376 GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING) >>= nStyleAuto;
2377 if (nStyleAuto > 0)
2378 nBeforeAutospacing = 0;
2381 if ( nBeforeAutospacing > -1 && pParaContext )
2383 if (bAllowAdjustments)
2385 if ( GetIsFirstParagraphInShape() ||
2386 (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) ||
2387 (m_StreamStateStack.top().bFirstParagraphInCell
2388 && 0 < m_StreamStateStack.top().nTableDepth
2389 && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth))
2391 // export requires grabbag to match top_margin, so keep them in sync
2392 if (nBeforeAutospacing && bIsAutoSet)
2393 pParaContext->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::Any( sal_Int32(0) ),true, PARA_GRAB_BAG );
2394 nBeforeAutospacing = 0;
2397 pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(nBeforeAutospacing));
2400 sal_Int32 nAfterAutospacing = -1;
2401 bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING);
2402 const bool bNoBottomMargin = pParaContext && !pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN);
2403 bool bAppliedBottomAutospacing = false;
2404 if (bIsAutoSet || bNoBottomMargin)
2406 GetAnyProperty(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, pPropertyMap) >>= nAfterAutospacing;
2407 if (bNoBottomMargin && nAfterAutospacing == ConversionHelper::convertTwipToMM100(-1))
2409 sal_Int32 nStyleAuto = -1;
2410 GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING) >>= nStyleAuto;
2411 if (nStyleAuto > 0)
2412 nAfterAutospacing = 0;
2415 if ( nAfterAutospacing > -1 && pParaContext )
2417 pParaContext->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(nAfterAutospacing));
2418 bAppliedBottomAutospacing = bAllowAdjustments;
2421 // tell TableManager to reset the bottom margin if it determines that this is the cell's last paragraph.
2422 if ( hasTableManager() && getTableManager().isInCell() )
2423 getTableManager().setCellLastParaAfterAutospacing(bAppliedBottomAutospacing);
2425 if (xTextAppend.is() && pParaContext && hasTableManager() && !getTableManager().isIgnore())
2429 /*the following combinations of previous and current frame settings can occur:
2430 (1) - no old frame and no current frame -> no special action
2431 (2) - no old frame and current DropCap -> save DropCap for later use, don't call finishParagraph
2432 remove character properties of the DropCap?
2433 (3) - no old frame and current Frame -> save Frame for later use
2434 (4) - old DropCap and no current frame -> add DropCap to the properties of the finished paragraph, delete previous setting
2435 (5) - old DropCap and current frame -> add DropCap to the properties of the finished paragraph, save current frame settings
2436 (6) - old Frame and new DropCap -> add old Frame, save DropCap for later use
2437 (7) - old Frame and new same Frame -> continue
2438 (8) - old Frame and new different Frame -> add old Frame, save new Frame for later use
2439 (9) - old Frame and no current frame -> add old Frame, delete previous settings
2441 old _and_ new DropCap must not occur
2444 // The paragraph style is vital to knowing all the frame properties.
2445 std::optional<PropertyMap::Property> aParaStyle
2446 = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME);
2447 if (aParaStyle)
2449 OUString sName;
2450 aParaStyle->second >>= sName;
2451 pParaContext->props().SetParaStyleName(sName);
2454 bool bIsDropCap =
2455 pParaContext->props().IsFrameMode() &&
2456 sal::static_int_cast<Id>(pParaContext->props().GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none;
2458 style::DropCapFormat aDrop;
2459 ParagraphPropertiesPtr pToBeSavedProperties;
2460 bool bKeepLastParagraphProperties = false;
2461 if( bIsDropCap )
2463 uno::Reference<text::XParagraphCursor> xParaCursor(
2464 xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
2465 //select paragraph
2466 xParaCursor->gotoStartOfParagraph( true );
2467 uno::Reference< beans::XPropertyState > xParaProperties( xParaCursor, uno::UNO_QUERY_THROW );
2468 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT));
2469 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT));
2470 //handles (2) and part of (6)
2471 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2472 sal_Int32 nCount = xParaCursor->getString().getLength();
2473 pToBeSavedProperties->SetDropCapLength(nCount > 0 && nCount < 255 ? static_cast<sal_Int8>(nCount) : 1);
2475 if( rAppendContext.pLastParagraphProperties )
2477 if( sal::static_int_cast<Id>(rAppendContext.pLastParagraphProperties->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none)
2479 //handles (4) and part of (5)
2480 //create a DropCap property, add it to the property sequence of finishParagraph
2481 sal_Int32 nLines = rAppendContext.pLastParagraphProperties->GetLines();
2482 aDrop.Lines = nLines > 0 && nLines < SAL_MAX_INT8 ? static_cast<sal_Int8>(nLines) : 2;
2483 aDrop.Count = rAppendContext.pLastParagraphProperties->GetDropCapLength();
2484 sal_Int32 nHSpace = rAppendContext.pLastParagraphProperties->GethSpace();
2485 aDrop.Distance = nHSpace > 0 && nHSpace < SAL_MAX_INT16 ? static_cast<sal_Int16>(nHSpace) : 0;
2486 //completes (5)
2487 if( pParaContext->props().IsFrameMode() )
2488 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2490 else
2492 const bool bIsFrameMode(pParaContext->props().IsFrameMode());
2493 std::vector<beans::PropertyValue> aCurrFrameProperties;
2494 std::vector<beans::PropertyValue> aPrevFrameProperties;
2495 if (bIsFrameMode)
2497 aCurrFrameProperties = MakeFrameProperties(pParaContext->props());
2498 aPrevFrameProperties
2499 = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
2502 if (bIsFrameMode && aPrevFrameProperties == aCurrFrameProperties)
2504 //handles (7)
2505 rAppendContext.pLastParagraphProperties->SetEndingRange(
2506 rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition
2507 : xTextAppend->getEnd());
2508 bKeepLastParagraphProperties = true;
2510 else
2512 // handles (8)(9) and completes (6)
2514 // RTF has an \overlap flag (which we ignore so far)
2515 // but DOCX has nothing like that for framePr
2516 // Always allow overlap in the RTF case - so there can be no regression.
2518 // In MSO UI, there is no setting for AllowOverlap for this kind of frame.
2519 // Although they CAN overlap with other anchored things,
2520 // they do not _easily_ overlap with other framePr's,
2521 // so when one frame follows another (8), don't let the first be overlapped.
2522 bool bPreventOverlap = !IsRTFImport() && bIsFrameMode && !bIsDropCap;
2524 // Preventing overlap is emulation - so deny overlap as little as possible.
2525 sal_Int16 nVertOrient = text::VertOrientation::NONE;
2526 sal_Int16 nVertOrientRelation = text::RelOrientation::FRAME;
2527 sal_Int32 nCurrVertPos = 0;
2528 sal_Int32 nPrevVertPos = 0;
2529 for (size_t i = 0; bPreventOverlap && i < aCurrFrameProperties.size(); ++i)
2531 if (aCurrFrameProperties[i].Name == "VertOrientRelation")
2533 aCurrFrameProperties[i].Value >>= nVertOrientRelation;
2534 if (nVertOrientRelation != text::RelOrientation::FRAME)
2535 bPreventOverlap = false;
2537 else if (aCurrFrameProperties[i].Name == "VertOrient")
2539 aCurrFrameProperties[i].Value >>= nVertOrient;
2540 if (nVertOrient != text::VertOrientation::NONE)
2541 bPreventOverlap = false;
2543 else if (aCurrFrameProperties[i].Name == "VertOrientPosition")
2545 aCurrFrameProperties[i].Value >>= nCurrVertPos;
2546 // arbitrary value. Assume it must be less than 1st line height
2547 if (nCurrVertPos > 20 || nCurrVertPos < -20)
2548 bPreventOverlap = false;
2551 for (size_t i = 0; bPreventOverlap && i < aPrevFrameProperties.size(); ++i)
2553 if (aPrevFrameProperties[i].Name == "VertOrientRelation")
2555 aPrevFrameProperties[i].Value >>= nVertOrientRelation;
2556 if (nVertOrientRelation != text::RelOrientation::FRAME)
2557 bPreventOverlap = false;
2559 else if (aPrevFrameProperties[i].Name == "VertOrient")
2561 aPrevFrameProperties[i].Value >>= nVertOrient;
2562 if (nVertOrient != text::VertOrientation::NONE)
2563 bPreventOverlap = false;
2565 else if (aPrevFrameProperties[i].Name == "VertOrientPosition")
2567 aPrevFrameProperties[i].Value >>= nPrevVertPos;
2568 if (nPrevVertPos != nCurrVertPos)
2569 bPreventOverlap = false;
2573 CheckUnregisteredFrameConversion(bPreventOverlap);
2575 // If different frame properties are set on this paragraph, keep them.
2576 if (!bIsDropCap && bIsFrameMode)
2578 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2579 lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
2584 else
2586 // (1) doesn't need handling
2588 if( !bIsDropCap && pParaContext->props().IsFrameMode() )
2590 pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
2591 lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
2594 applyToggleAttributes(pPropertyMap); // for paragraph marker formatting
2595 std::vector<beans::PropertyValue> aProperties;
2596 if (pPropertyMap)
2598 aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues());
2600 // tdf#64222 filter out the "paragraph marker" formatting and
2601 // set it as a separate paragraph property, not a empty hint at
2602 // end of paragraph
2603 std::vector<beans::NamedValue> charProperties;
2604 for (auto it = aProperties.begin(); it != aProperties.end(); )
2606 // this condition isn't ideal but as it happens all
2607 // RES_CHRATR_* have names that start with "Char"
2608 if (it->Name.startsWith("Char"))
2610 charProperties.emplace_back(it->Name, it->Value);
2611 // as testN793262 demonstrates, font size in rPr must
2612 // affect the paragraph size => also insert empty hint!
2613 // it = aProperties.erase(it);
2615 ++it;
2617 if (!charProperties.empty())
2619 aProperties.push_back(beans::PropertyValue("ListAutoFormat",
2620 0, uno::Any(comphelper::containerToSequence(charProperties)), beans::PropertyState_DIRECT_VALUE));
2623 if( !bIsDropCap )
2625 if( aDrop.Lines > 1 )
2627 beans::PropertyValue aValue;
2628 aValue.Name = getPropertyName(PROP_DROP_CAP_FORMAT);
2629 aValue.Value <<= aDrop;
2630 aProperties.push_back(aValue);
2632 uno::Reference< text::XTextRange > xTextRange;
2633 if (rAppendContext.xInsertPosition.is())
2635 xTextRange = xTextAppend->finishParagraphInsert( comphelper::containerToSequence(aProperties), rAppendContext.xInsertPosition );
2636 rAppendContext.xCursor->gotoNextParagraph(false);
2637 if (rAppendContext.pLastParagraphProperties)
2638 rAppendContext.pLastParagraphProperties->SetEndingRange(xTextRange->getEnd());
2640 else
2642 uno::Reference<text::XTextCursor> xCursor;
2643 if (m_StreamStateStack.top().bParaHadField
2644 && !IsInComments() && !m_xTOCMarkerCursor.is())
2646 // Workaround to make sure char props of the field are not lost.
2647 // Not relevant for editeng-based comments.
2648 // Not relevant for fields inside a TOC field.
2649 xCursor = xTextAppend->getText()->createTextCursor();
2650 if (xCursor.is())
2651 xCursor->gotoEnd(false);
2652 PropertyMapPtr pEmpty(new PropertyMap());
2653 appendTextPortion("X", pEmpty);
2656 // Check if top / bottom margin has to be updated, now that we know the numbering status of both the previous and
2657 // the current text node.
2658 auto itNumberingRules = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2660 return rValue.Name == "NumberingRules";
2663 assert( isNumberingViaRule == (itNumberingRules != aProperties.end()) );
2664 isNumberingViaRule = (itNumberingRules != aProperties.end());
2665 if (m_xPreviousParagraph.is() && (isNumberingViaRule || isNumberingViaStyle))
2667 // This textnode has numbering. Look up the numbering style name of the current and previous paragraph.
2668 OUString aCurrentNumberingName;
2669 OUString aPreviousNumberingName;
2670 if (isNumberingViaRule)
2672 assert(itNumberingRules != aProperties.end() && "by definition itNumberingRules is valid if isNumberingViaRule is true");
2673 uno::Reference<container::XNamed> xCurrentNumberingRules(itNumberingRules->Value, uno::UNO_QUERY);
2674 if (xCurrentNumberingRules.is())
2675 aCurrentNumberingName = xCurrentNumberingRules->getName();
2678 uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY_THROW);
2679 aPreviousNumberingName = xPreviousNumberingRules->getName();
2681 catch (const uno::Exception&)
2683 TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::finishParagraph NumberingRules");
2686 else if (m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName")
2687 // don't update before tables
2688 && (m_StreamStateStack.top().nTableDepth == 0
2689 || !m_StreamStateStack.top().bFirstParagraphInCell))
2691 aCurrentNumberingName = GetListStyleName(nListId);
2692 m_xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName;
2695 // tdf#133363: remove extra auto space even for mixed list styles
2696 if (!aPreviousNumberingName.isEmpty()
2697 && (aCurrentNumberingName == aPreviousNumberingName
2698 || !isNumberingViaRule))
2700 uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq;
2701 m_xPreviousParagraph->getPropertyValue("ParaInteropGrabBag") >>= aPrevPropertiesSeq;
2702 const auto & rPrevProperties = aPrevPropertiesSeq;
2703 bool bParaAutoBefore = m_bParaAutoBefore || std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue)
2705 return rValue.Name == "ParaTopMarginBeforeAutoSpacing";
2707 // if style based spacing was set to auto in the previous paragraph, style of the actual paragraph must be the same
2708 if (bParaAutoBefore && !m_bParaAutoBefore && m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ParaStyleName"))
2710 auto itParaStyle = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2712 return rValue.Name == "ParaStyleName";
2714 bParaAutoBefore = itParaStyle != aProperties.end() &&
2715 m_xPreviousParagraph->getPropertyValue("ParaStyleName") == itParaStyle->Value;
2717 // There was a previous textnode and it had the same numbering.
2718 if (bParaAutoBefore)
2720 // This before spacing is set to auto, set before space to 0.
2721 auto itParaTopMargin = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
2723 return rValue.Name == "ParaTopMargin";
2725 if (itParaTopMargin != aProperties.end())
2726 itParaTopMargin->Value <<= static_cast<sal_Int32>(0);
2727 else
2728 aProperties.push_back(comphelper::makePropertyValue("ParaTopMargin", static_cast<sal_Int32>(0)));
2731 bool bPrevParaAutoAfter = std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue)
2733 return rValue.Name == "ParaBottomMarginAfterAutoSpacing";
2735 if (bPrevParaAutoAfter)
2737 // Previous after spacing is set to auto, set previous after space to 0.
2738 m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0)));
2743 // apply redlines for inline images
2744 if (IsParaWithInlineObject())
2746 for (const auto& rAnchored : rAppendContext.m_aAnchoredObjects)
2748 // process only inline objects with redlining
2749 if (!rAnchored.m_xRedlineForInline)
2750 continue;
2752 // select the inline image and set its redline
2753 auto xAnchorRange = rAnchored.m_xAnchoredObject->getAnchor();
2754 uno::Reference< text::XTextCursor > xCursorOnImage =
2755 xAnchorRange->getText()->createTextCursorByRange(xAnchorRange);
2756 xCursorOnImage->goRight(1, true);
2757 CreateRedline( xCursorOnImage, rAnchored.m_xRedlineForInline );
2761 xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
2762 m_xPreviousParagraph.set(xTextRange, uno::UNO_QUERY);
2764 if (m_xPreviousParagraph.is() && // null for SvxUnoTextBase
2765 (isNumberingViaStyle || isNumberingViaRule))
2767 assert(pParaContext);
2768 if (ListDef::Pointer const& pList = m_pListTable->GetList(nListId))
2769 { // styles could refer to non-existing lists...
2770 AbstractListDef::Pointer const& pAbsList =
2771 pList->GetAbstractDefinition();
2772 if (pAbsList &&
2773 // SvxUnoTextRange doesn't have ListId
2774 m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ListId"))
2776 OUString paraId;
2777 m_xPreviousParagraph->getPropertyValue("ListId") >>= paraId;
2778 if (!paraId.isEmpty()) // must be on some list?
2780 OUString const listId = pAbsList->MapListId(paraId);
2781 if (listId != paraId)
2783 m_xPreviousParagraph->setPropertyValue("ListId", uno::Any(listId));
2788 sal_Int16 nCurrentLevel = GetListLevel(pEntry, pPropertyMap);
2789 if (nCurrentLevel == -1)
2790 nCurrentLevel = 0;
2792 const ListLevel::Pointer pListLevel = pList->GetLevel(nCurrentLevel);
2793 if (pListLevel)
2795 sal_Int16 nOverrideLevel = pListLevel->GetStartOverride();
2796 if (nOverrideLevel != -1 && m_aListOverrideApplied.find(nListId) == m_aListOverrideApplied.end())
2798 // Apply override: we have override instruction for this level
2799 // And this was not done for this list before: we can do this only once on first occurrence
2800 // of list with override
2801 // TODO: Not tested variant with different levels override in different lists.
2802 // Probably m_aListOverrideApplied as a set of overridden listids is not sufficient
2803 // and we need to register level overrides separately.
2804 m_xPreviousParagraph->setPropertyValue("ParaIsNumberingRestart", uno::Any(true));
2805 m_xPreviousParagraph->setPropertyValue("NumberingStartValue", uno::Any(nOverrideLevel));
2806 m_aListOverrideApplied.insert(nListId);
2812 if (!rAppendContext.m_aAnchoredObjects.empty() && !IsInHeaderFooter())
2814 // Remember what objects are anchored to this paragraph.
2815 // That list is only used for Word compat purposes, and
2816 // it is only relevant for body text.
2817 AnchoredObjectsInfo aInfo;
2818 aInfo.m_xParagraph = xTextRange;
2819 aInfo.m_aAnchoredObjects = rAppendContext.m_aAnchoredObjects;
2820 m_aAnchoredObjectAnchors.push_back(aInfo);
2821 rAppendContext.m_aAnchoredObjects.clear();
2824 if (xCursor.is())
2826 xCursor->goLeft(1, true);
2827 xCursor->setString(OUString());
2830 getTableManager( ).handle(xTextRange);
2831 m_aSmartTagHandler.handle(xTextRange);
2833 if (xTextRange.is())
2835 // Get the end of paragraph character inserted
2836 uno::Reference< text::XTextCursor > xCur = xTextRange->getText( )->createTextCursor( );
2837 if (rAppendContext.xInsertPosition.is())
2838 xCur->gotoRange( rAppendContext.xInsertPosition, false );
2839 else
2840 xCur->gotoEnd( false );
2842 // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode
2843 sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode();
2844 if (0 < m_StreamStateStack.top().nTableDepth && 0 < nMode && nMode <= 14)
2846 // skip new line
2847 xCur->goLeft(1, false);
2848 while ( xCur->goLeft(1, true) )
2850 OUString sChar = xCur->getString();
2851 if ( sChar == " " || sChar == "\t" || sChar == OUStringChar(u'\x00A0') )
2852 xCur->setString("");
2853 else
2854 break;
2857 if (rAppendContext.xInsertPosition.is())
2858 xCur->gotoRange(rAppendContext.xInsertPosition, false);
2859 else
2860 xCur->gotoEnd(false);
2863 xCur->goLeft( 1 , true );
2864 // Extend the redline ranges for empty paragraphs
2865 if (!m_StreamStateStack.top().bParaChanged && m_previousRedline)
2866 CreateRedline( xCur, m_previousRedline );
2867 CheckParaMarkerRedline( xCur );
2870 css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY);
2872 // table style precedence and not hidden shapes anchored to hidden empty table paragraphs
2873 if (xParaProps && !IsInComments()
2874 && (0 < m_StreamStateStack.top().nTableDepth
2875 || !m_aAnchoredObjectAnchors.empty()))
2877 // table style has got bigger precedence than docDefault style
2878 // collect these pending paragraph properties to process in endTable()
2879 uno::Reference<text::XTextCursor> xCur = xTextRange->getText( )->createTextCursor( );
2880 xCur->gotoEnd(false);
2881 xCur->goLeft(1, false);
2882 uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur);
2883 uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW);
2884 xParaCursor->gotoStartOfParagraph(false);
2885 if (0 < m_StreamStateStack.top().nTableDepth)
2887 TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps};
2888 getTableManager().getCurrentParagraphs()->push_back(aPending);
2891 // hidden empty paragraph with a not hidden shape, set as not hidden
2892 std::optional<PropertyMap::Property> pHidden;
2893 if ( !m_aAnchoredObjectAnchors.empty() && (pHidden = pParaContext->getProperty(PROP_CHAR_HIDDEN)) )
2895 bool bIsHidden = {}; // -Werror=maybe-uninitialized
2896 pHidden->second >>= bIsHidden;
2897 if (bIsHidden)
2899 bIsHidden = false;
2900 pHidden = GetTopContext()->getProperty(PROP_CHAR_HIDDEN);
2901 if (pHidden)
2902 pHidden->second >>= bIsHidden;
2903 if (!bIsHidden)
2905 uno::Reference<text::XTextCursor> xCur3 = xTextRange->getText()->createTextCursorByRange(xParaCursor);
2906 xCur3->goRight(1, true);
2907 if (xCur3->getString() == SAL_NEWLINE_STRING)
2909 uno::Reference< beans::XPropertySet > xProp( xCur3, uno::UNO_QUERY );
2910 xProp->setPropertyValue(getPropertyName(PROP_CHAR_HIDDEN), uno::Any(false));
2917 // tdf#118521 set paragraph top or bottom margin based on the paragraph style
2918 // if we already set the other margin with direct formatting
2919 if (xParaProps)
2921 const bool bTopSet = pParaContext->isSet(PROP_PARA_TOP_MARGIN);
2922 const bool bBottomSet = pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN);
2923 const bool bContextSet = pParaContext->isSet(PROP_PARA_CONTEXT_MARGIN);
2924 if ( bTopSet != bBottomSet || bBottomSet != bContextSet )
2927 if ( !bTopSet )
2929 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN);
2930 if ( aMargin != uno::Any() )
2931 xParaProps->setPropertyValue("ParaTopMargin", aMargin);
2933 if ( !bBottomSet )
2935 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN);
2936 if ( aMargin != uno::Any() )
2937 xParaProps->setPropertyValue("ParaBottomMargin", aMargin);
2939 if ( !bContextSet )
2941 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN);
2942 if ( aMargin != uno::Any() )
2943 xParaProps->setPropertyValue("ParaContextMargin", aMargin);
2948 // Left, Right, and Hanging settings are also grouped. Ensure that all or none are set.
2949 if (xParaProps)
2951 const bool bLeftSet = pParaContext->isSet(PROP_PARA_LEFT_MARGIN);
2952 const bool bRightSet = pParaContext->isSet(PROP_PARA_RIGHT_MARGIN);
2953 const bool bFirstSet = pParaContext->isSet(PROP_PARA_FIRST_LINE_INDENT);
2954 if (bLeftSet != bRightSet || bRightSet != bFirstSet)
2956 if ( !bLeftSet )
2958 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_LEFT_MARGIN);
2959 if ( aMargin != uno::Any() )
2960 xParaProps->setPropertyValue("ParaLeftMargin", aMargin);
2961 else if (isNumberingViaStyle)
2963 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "IndentAt");
2964 if (nParaLeftMargin != 0)
2965 xParaProps->setPropertyValue("ParaLeftMargin", uno::Any(nParaLeftMargin));
2968 if ( !bRightSet )
2970 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
2971 if ( aMargin != uno::Any() )
2972 xParaProps->setPropertyValue("ParaRightMargin", aMargin);
2974 if ( !bFirstSet )
2976 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT);
2977 if ( aMargin != uno::Any() )
2978 xParaProps->setPropertyValue("ParaFirstLineIndent", aMargin);
2979 else if (isNumberingViaStyle)
2981 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "FirstLineIndent");
2982 if (nFirstLineIndent != 0)
2983 xParaProps->setPropertyValue("ParaFirstLineIndent", uno::Any(nFirstLineIndent));
2989 if( !bKeepLastParagraphProperties )
2990 rAppendContext.pLastParagraphProperties = pToBeSavedProperties;
2992 catch(const lang::IllegalArgumentException&)
2994 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::finishParagraph" );
2996 catch(const uno::Exception&)
2998 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "finishParagraph()" );
3003 bool bIgnoreFrameState = IsInHeaderFooter();
3004 if( (!bIgnoreFrameState && pParaContext && pParaContext->props().IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) )
3005 SetIsPreviousParagraphFramed(true);
3006 else
3007 SetIsPreviousParagraphFramed(false);
3009 m_StreamStateStack.top().bRemoveThisParagraph = false;
3010 if( !IsInHeaderFooter() && !IsInShape()
3011 && (!pParaContext || !pParaContext->props().IsFrameMode()) )
3012 { // If the paragraph is in a frame, shape or header/footer, it's not a paragraph of the section itself.
3013 SetIsFirstParagraphInSection(false);
3014 // don't count an empty deleted paragraph as first paragraph in section to avoid of
3015 // the deletion of the next empty paragraph later, resulting loss of the associated page break
3016 if (!m_previousRedline || m_StreamStateStack.top().bParaChanged)
3018 SetIsFirstParagraphInSectionAfterRedline(false);
3019 SetIsLastParagraphInSection(false);
3022 m_previousRedline.clear();
3023 m_StreamStateStack.top().bParaChanged = false;
3025 if (IsInComments() && pParaContext)
3027 if (const OUString sParaId = pParaContext->props().GetParaId(); !sParaId.isEmpty())
3029 if (const auto& item = m_aCommentProps.find(sParaId); item != m_aCommentProps.end())
3031 m_bAnnotationResolved = item->second.bDone;
3032 m_sAnnotationParent = item->second.sParaIdParent;
3034 m_sAnnotationImportedParaId = sParaId;
3038 if (m_bIsFirstParaInShape)
3039 m_bIsFirstParaInShape = false;
3041 if (pParaContext)
3043 // Reset the frame properties for the next paragraph
3044 pParaContext->props().ResetFrameProperties();
3047 SetIsOutsideAParagraph(true);
3048 m_StreamStateStack.top().bParaHadField = false;
3050 // don't overwrite m_bFirstParagraphInCell in table separator nodes
3051 // and in text boxes anchored to the first paragraph of table cells
3052 if (0 < m_StreamStateStack.top().nTableDepth
3053 && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth
3054 && !IsInShape() && !IsInComments())
3056 m_StreamStateStack.top().bFirstParagraphInCell = false;
3059 m_bParaAutoBefore = false;
3060 m_StreamStateStack.top().bParaWithInlineObject = false;
3062 #ifdef DBG_UTIL
3063 TagLogger::getInstance().endElement();
3064 #endif
3068 // TODO this does not yet take table styles into account
3069 void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap)
3071 std::optional<PropertyMap::Property> charStyleProperty = pPropertyMap->getProperty(PROP_CHAR_STYLE_NAME);
3072 if (charStyleProperty.has_value())
3074 OUString sCharStyleName;
3075 charStyleProperty->second >>= sCharStyleName;
3076 float fCharStyleBold = css::awt::FontWeight::NORMAL;
3077 float fCharStyleBoldComplex = css::awt::FontWeight::NORMAL;
3078 css::awt::FontSlant eCharStylePosture = css::awt::FontSlant_NONE;
3079 css::awt::FontSlant eCharStylePostureComplex = css::awt::FontSlant_NONE;
3080 sal_Int16 nCharStyleCaseMap = css::style::CaseMap::NONE;
3081 sal_Int16 nCharStyleRelief = css::awt::FontRelief::NONE;
3082 bool bCharStyleContoured = false;//Outline;
3083 bool bCharStyleShadowed = false;
3084 sal_Int16 nCharStyleStrikeThrough = awt::FontStrikeout::NONE;
3085 bool bCharStyleHidden = false;
3087 uno::Reference<beans::XPropertySet> xCharStylePropertySet = GetCharacterStyles()->getByName(sCharStyleName).get<uno::Reference<beans::XPropertySet>>();
3088 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT)) >>= fCharStyleBold;
3089 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT_COMPLEX)) >>= fCharStyleBoldComplex;
3090 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE)) >>= eCharStylePosture;
3091 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE_COMPLEX)) >>= eCharStylePostureComplex;
3092 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CASE_MAP)) >>= nCharStyleCaseMap;
3093 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_RELIEF)) >>= nCharStyleRelief;
3094 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CONTOURED)) >>= bCharStyleContoured;
3095 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_SHADOWED)) >>= bCharStyleShadowed;
3096 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_STRIKEOUT)) >>= nCharStyleStrikeThrough;
3097 xCharStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_HIDDEN)) >>= bCharStyleHidden;
3098 if (fCharStyleBold > css::awt::FontWeight::NORMAL || eCharStylePosture != css::awt::FontSlant_NONE|| nCharStyleCaseMap != css::style::CaseMap::NONE ||
3099 nCharStyleRelief != css::awt::FontRelief::NONE || bCharStyleContoured || bCharStyleShadowed ||
3100 nCharStyleStrikeThrough == awt::FontStrikeout::SINGLE || bCharStyleHidden)
3102 uno::Reference<beans::XPropertySet> xParaStylePropertySet = GetParagraphStyles()->getByName(m_sCurrentParaStyleName).get<uno::Reference<beans::XPropertySet>>();
3103 float fParaStyleBold = css::awt::FontWeight::NORMAL;
3104 float fParaStyleBoldComplex = css::awt::FontWeight::NORMAL;
3105 css::awt::FontSlant eParaStylePosture = css::awt::FontSlant_NONE;
3106 css::awt::FontSlant eParaStylePostureComplex = css::awt::FontSlant_NONE;
3107 sal_Int16 nParaStyleCaseMap = css::style::CaseMap::NONE;
3108 sal_Int16 nParaStyleRelief = css::awt::FontRelief::NONE;
3109 bool bParaStyleContoured = false;
3110 bool bParaStyleShadowed = false;
3111 sal_Int16 nParaStyleStrikeThrough = awt::FontStrikeout::NONE;
3112 bool bParaStyleHidden = false;
3113 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT)) >>= fParaStyleBold;
3114 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_WEIGHT_COMPLEX)) >>= fParaStyleBoldComplex;
3115 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE)) >>= eParaStylePosture;
3116 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_POSTURE_COMPLEX)) >>= eParaStylePostureComplex;
3117 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CASE_MAP)) >>= nParaStyleCaseMap;
3118 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_RELIEF)) >>= nParaStyleRelief;
3119 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_SHADOWED)) >>= bParaStyleShadowed;
3120 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_CONTOURED)) >>= bParaStyleContoured;
3121 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_STRIKEOUT)) >>= nParaStyleStrikeThrough;
3122 xParaStylePropertySet->getPropertyValue(getPropertyName(PROP_CHAR_HIDDEN)) >>= bParaStyleHidden;
3123 if (fCharStyleBold > css::awt::FontWeight::NORMAL && fParaStyleBold > css::awt::FontWeight::NORMAL)
3125 std::optional<PropertyMap::Property> charBoldProperty = pPropertyMap->getProperty(PROP_CHAR_WEIGHT);
3126 if (!charBoldProperty.has_value())
3128 pPropertyMap->Insert(PROP_CHAR_WEIGHT, uno::Any(css::awt::FontWeight::NORMAL));
3131 if (fCharStyleBoldComplex > css::awt::FontWeight::NORMAL && fParaStyleBoldComplex > css::awt::FontWeight::NORMAL)
3133 std::optional<PropertyMap::Property> charBoldPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_WEIGHT_COMPLEX);
3134 if (!charBoldPropertyComplex.has_value())
3136 pPropertyMap->Insert(PROP_CHAR_WEIGHT_COMPLEX, uno::Any(css::awt::FontWeight::NORMAL));
3137 pPropertyMap->Insert(PROP_CHAR_WEIGHT_ASIAN, uno::Any(css::awt::FontWeight::NORMAL));
3140 if (eCharStylePosture != css::awt::FontSlant_NONE && eParaStylePosture != css::awt::FontSlant_NONE)
3142 std::optional<PropertyMap::Property> charItalicProperty = pPropertyMap->getProperty(PROP_CHAR_POSTURE);
3143 if (!charItalicProperty.has_value())
3145 pPropertyMap->Insert(PROP_CHAR_POSTURE, uno::Any(css::awt::FontSlant_NONE));
3148 if (eCharStylePostureComplex != css::awt::FontSlant_NONE && eParaStylePostureComplex != css::awt::FontSlant_NONE)
3150 std::optional<PropertyMap::Property> charItalicPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_POSTURE_COMPLEX);
3151 if (!charItalicPropertyComplex.has_value())
3153 pPropertyMap->Insert(PROP_CHAR_POSTURE_COMPLEX, uno::Any(css::awt::FontSlant_NONE));
3154 pPropertyMap->Insert(PROP_CHAR_POSTURE_ASIAN, uno::Any(css::awt::FontSlant_NONE));
3157 if (nCharStyleCaseMap == nParaStyleCaseMap && nCharStyleCaseMap != css::style::CaseMap::NONE)
3159 std::optional<PropertyMap::Property> charCaseMap = pPropertyMap->getProperty(PROP_CHAR_CASE_MAP);
3160 if (!charCaseMap.has_value())
3162 pPropertyMap->Insert(PROP_CHAR_CASE_MAP, uno::Any(css::style::CaseMap::NONE));
3165 if (nParaStyleRelief != css::awt::FontRelief::NONE && nCharStyleRelief == nParaStyleRelief)
3167 std::optional<PropertyMap::Property> charRelief = pPropertyMap->getProperty(PROP_CHAR_RELIEF);
3168 if (!charRelief.has_value())
3170 pPropertyMap->Insert(PROP_CHAR_RELIEF, uno::Any(css::awt::FontRelief::NONE));
3173 if (bParaStyleContoured && bCharStyleContoured)
3175 std::optional<PropertyMap::Property> charContoured = pPropertyMap->getProperty(PROP_CHAR_CONTOURED);
3176 if (!charContoured.has_value())
3178 pPropertyMap->Insert(PROP_CHAR_CONTOURED, uno::Any(false));
3181 if (bParaStyleShadowed && bCharStyleShadowed)
3183 std::optional<PropertyMap::Property> charShadow = pPropertyMap->getProperty(PROP_CHAR_SHADOWED);
3184 if (!charShadow.has_value())
3186 pPropertyMap->Insert(PROP_CHAR_SHADOWED, uno::Any(false));
3189 if (nParaStyleStrikeThrough == css::awt::FontStrikeout::SINGLE && nParaStyleStrikeThrough == nCharStyleStrikeThrough)
3191 std::optional<PropertyMap::Property> charStrikeThrough = pPropertyMap->getProperty(PROP_CHAR_STRIKEOUT);
3192 if (!charStrikeThrough.has_value())
3194 pPropertyMap->Insert(PROP_CHAR_STRIKEOUT, uno::Any(css::awt::FontStrikeout::NONE));
3197 if (bParaStyleHidden && bCharStyleHidden)
3199 std::optional<PropertyMap::Property> charHidden = pPropertyMap->getProperty(PROP_CHAR_HIDDEN);
3200 if (!charHidden.has_value())
3202 pPropertyMap->Insert(PROP_CHAR_HIDDEN, uno::Any(false));
3209 void DomainMapper_Impl::MergeAtContentImageRedlineWithNext(const css::uno::Reference<css::text::XTextAppend>& xTextAppend)
3211 // remove workaround for change tracked images, if they are part of a redline,
3212 // i.e. if the next run is a tracked change with the same type, author and date,
3213 // as in the change tracking of the image.
3214 if ( m_bRedlineImageInPreviousRun )
3216 auto pCurrentRedline = m_aRedlines.top().size() > 0
3217 ? m_aRedlines.top().back()
3218 : GetTopContextOfType(CONTEXT_CHARACTER) &&
3219 GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0
3220 ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back()
3221 : nullptr;
3222 if ( m_previousRedline && pCurrentRedline &&
3223 // same redline
3224 (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) &&
3225 m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor &&
3226 m_previousRedline->m_sDate == pCurrentRedline->m_sDate )
3228 uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( );
3229 assert(xCursor.is());
3230 xCursor->gotoEnd(false);
3231 xCursor->goLeft(2, true);
3232 if ( xCursor->getString() == u"​​" )
3234 xCursor->goRight(1, true);
3235 xCursor->setString("");
3236 xCursor->gotoEnd(false);
3237 xCursor->goLeft(1, true);
3238 xCursor->setString("");
3242 m_bRedlineImageInPreviousRun = false;
3246 void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap )
3248 if (m_bDiscardHeaderFooter)
3249 return;
3251 if (m_aTextAppendStack.empty())
3252 return;
3253 // Before placing call to processDeferredCharacterProperties(), TopContextType should be CONTEXT_CHARACTER
3254 // processDeferredCharacterProperties() invokes only if character inserted
3255 if( pPropertyMap == m_pTopContext && !m_deferredCharacterProperties.empty() && (GetTopContextType() == CONTEXT_CHARACTER) )
3256 processDeferredCharacterProperties();
3257 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
3258 if (!xTextAppend.is() || !hasTableManager() || getTableManager().isIgnore())
3259 return;
3263 applyToggleAttributes(pPropertyMap);
3264 // If we are in comments, then disable CharGrabBag, comment text doesn't support that.
3265 uno::Sequence<beans::PropertyValue> aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!IsInComments());
3267 if (IsInTOC() || m_bStartIndex || m_bStartBibliography)
3268 for( auto& rValue : asNonConstRange(aValues) )
3270 if (rValue.Name == "CharHidden")
3271 rValue.Value <<= false;
3274 MergeAtContentImageRedlineWithNext(xTextAppend);
3276 uno::Reference< text::XTextRange > xTextRange;
3277 if (m_aTextAppendStack.top().xInsertPosition.is())
3279 xTextRange = xTextAppend->insertTextPortion(rString, aValues, m_aTextAppendStack.top().xInsertPosition);
3280 m_aTextAppendStack.top().xCursor->gotoRange(xTextRange->getEnd(), true);
3282 else
3284 if (IsInTOC() || m_bStartIndex || m_bStartBibliography || m_nStartGenericField != 0)
3286 if (IsInHeaderFooter() && !m_bStartTOCHeaderFooter)
3288 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
3290 else
3292 m_bStartedTOC = true;
3293 uno::Reference< text::XTextCursor > xTOCTextCursor = xTextAppend->getEnd()->getText( )->createTextCursor( );
3294 assert(xTOCTextCursor.is());
3295 xTOCTextCursor->gotoEnd(false);
3296 if (m_nStartGenericField != 0)
3298 xTOCTextCursor->goLeft(1, false);
3300 if (IsInComments())
3301 xTextRange = xTextAppend->finishParagraphInsert(aValues, xTOCTextCursor);
3302 else
3303 xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor);
3304 SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed");
3305 if (!xTextRange.is())
3306 throw uno::Exception("insertTextPortion failed", nullptr);
3307 m_StreamStateStack.top().bTextInserted = true;
3308 xTOCTextCursor->gotoRange(xTextRange->getEnd(), true);
3309 if (m_nStartGenericField == 0)
3311 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
3315 else
3317 if (IsOpenField() && GetTopFieldContext()->GetFieldId() == FIELD_HYPERLINK)
3319 // It is content of hyperlink field. We need to create and remember
3320 // character style for later applying to hyperlink
3321 PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(GetTopContext()->GetPropertyValues());
3322 OUString sHyperlinkStyleName = GetStyleSheetTable()->getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
3323 GetTopFieldContext()->SetHyperlinkStyle(sHyperlinkStyleName);
3326 #if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed
3327 sal_Int32 nPos = 0;
3328 OUString sFontName;
3329 OUString sDoubleSpace(" ");
3330 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
3331 // tdf#123703 workaround for longer space sequences of the old or compatible RTF documents
3332 if (GetSettingsTable()->GetLongerSpaceSequence() && !IsOpenFieldCommand() && (nPos = rString.indexOf(sDoubleSpace)) != -1 &&
3333 // monospaced fonts have no longer space sequences, regardless of \fprq2 (not monospaced) font setting
3334 // fix for the base monospaced font Courier
3335 (!pContext || !pContext->isSet(PROP_CHAR_FONT_NAME) ||
3336 ((pContext->getProperty(PROP_CHAR_FONT_NAME)->second >>= sFontName) && sFontName.indexOf("Courier") == -1)))
3338 // an RTF space character is longer by an extra six-em-space in an old-style RTF space sequence,
3339 // insert them to keep RTF document layout formatted by consecutive spaces
3340 const sal_Unicode aExtraSpace[5] = { 0x2006, 0x20, 0x2006, 0x20, 0 };
3341 const sal_Unicode aExtraSpace2[4] = { 0x20, 0x2006, 0x20, 0 };
3342 xTextRange = xTextAppend->appendTextPortion(rString.replaceAll(sDoubleSpace, aExtraSpace, nPos)
3343 .replaceAll(sDoubleSpace, aExtraSpace2, nPos), aValues);
3345 else
3346 #endif
3347 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
3351 // reset moveFrom/moveTo data of non-terminating runs of the paragraph
3352 if ( m_pParaMarkerRedlineMove )
3354 m_pParaMarkerRedlineMove.clear();
3356 CheckRedline( xTextRange );
3357 m_StreamStateStack.top().bParaChanged = true;
3359 //getTableManager( ).handle(xTextRange);
3361 catch(const lang::IllegalArgumentException&)
3363 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" );
3365 catch(const uno::Exception&)
3367 TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" );
3371 void DomainMapper_Impl::appendTextContent(
3372 const uno::Reference< text::XTextContent >& xContent,
3373 const uno::Sequence< beans::PropertyValue >& xPropertyValues
3376 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text append stack");
3377 if (m_aTextAppendStack.empty())
3378 return;
3379 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY );
3380 OSL_ENSURE( xTextAppendAndConvert.is(), "trying to append a text content without XTextAppendAndConvert" );
3381 if (!xTextAppendAndConvert.is() || !hasTableManager() || getTableManager().isIgnore())
3382 return;
3386 if (m_aTextAppendStack.top().xInsertPosition.is())
3387 xTextAppendAndConvert->insertTextContentWithProperties( xContent, xPropertyValues, m_aTextAppendStack.top().xInsertPosition );
3388 else
3389 xTextAppendAndConvert->appendTextContent( xContent, xPropertyValues );
3391 catch(const lang::IllegalArgumentException&)
3394 catch(const uno::Exception&)
3399 void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOLEHandler )
3403 uno::Reference< text::XTextContent > xOLE( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
3404 uno::Reference< beans::XPropertySet > xOLEProperties(xOLE, uno::UNO_QUERY_THROW);
3406 OUString aCLSID = pOLEHandler->getCLSID();
3407 if (aCLSID.isEmpty())
3408 xOLEProperties->setPropertyValue(getPropertyName( PROP_STREAM_NAME ),
3409 uno::Any( rStreamName ));
3410 else
3411 xOLEProperties->setPropertyValue("CLSID", uno::Any(aCLSID));
3413 OUString aDrawAspect = pOLEHandler->GetDrawAspect();
3414 if(!aDrawAspect.isEmpty())
3415 xOLEProperties->setPropertyValue("DrawAspect", uno::Any(aDrawAspect));
3417 awt::Size aSize = pOLEHandler->getSize();
3418 if( !aSize.Width )
3419 aSize.Width = 1000;
3420 if( !aSize.Height )
3421 aSize.Height = 1000;
3422 xOLEProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
3423 uno::Any(aSize.Width));
3424 xOLEProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
3425 uno::Any(aSize.Height));
3427 OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth();
3428 if(!aVisAreaWidth.isEmpty())
3429 xOLEProperties->setPropertyValue("VisibleAreaWidth", uno::Any(aVisAreaWidth));
3431 OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight();
3432 if(!aVisAreaHeight.isEmpty())
3433 xOLEProperties->setPropertyValue("VisibleAreaHeight", uno::Any(aVisAreaHeight));
3435 uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement();
3436 xOLEProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC ),
3437 uno::Any(xGraphic));
3438 uno::Reference<beans::XPropertySet> xReplacementProperties(pOLEHandler->getShape(), uno::UNO_QUERY);
3439 if (xReplacementProperties.is())
3441 table::BorderLine2 aBorderProps;
3442 xReplacementProperties->getPropertyValue("LineColor") >>= aBorderProps.Color;
3443 xReplacementProperties->getPropertyValue("LineWidth") >>= aBorderProps.LineWidth;
3444 xReplacementProperties->getPropertyValue("LineStyle") >>= aBorderProps.LineStyle;
3446 if (aBorderProps.LineStyle) // Set line props only if LineStyle is set
3448 xOLEProperties->setPropertyValue("RightBorder", uno::Any(aBorderProps));
3449 xOLEProperties->setPropertyValue("TopBorder", uno::Any(aBorderProps));
3450 xOLEProperties->setPropertyValue("LeftBorder", uno::Any(aBorderProps));
3451 xOLEProperties->setPropertyValue("BottomBorder", uno::Any(aBorderProps));
3453 OUString pProperties[] = {
3454 "AnchorType",
3455 "Surround",
3456 "SurroundContour",
3457 "HoriOrient",
3458 "HoriOrientPosition",
3459 "VertOrient",
3460 "VertOrientPosition",
3461 "VertOrientRelation",
3462 "HoriOrientRelation",
3463 "LeftMargin",
3464 "RightMargin",
3465 "TopMargin",
3466 "BottomMargin"
3468 for (const OUString& s : pProperties)
3470 const uno::Any aVal = xReplacementProperties->getPropertyValue(s);
3471 xOLEProperties->setPropertyValue(s, aVal);
3474 if (xReplacementProperties->getPropertyValue("FillStyle").get<css::drawing::FillStyle>()
3475 != css::drawing::FillStyle::FillStyle_NONE) // Apply fill props if style is set
3477 xOLEProperties->setPropertyValue(
3478 "FillStyle", xReplacementProperties->getPropertyValue("FillStyle"));
3479 xOLEProperties->setPropertyValue(
3480 "FillColor", xReplacementProperties->getPropertyValue("FillColor"));
3481 xOLEProperties->setPropertyValue(
3482 "FillColor2", xReplacementProperties->getPropertyValue("FillColor2"));
3485 else
3486 // mimic the treatment of graphics here... it seems anchoring as character
3487 // gives a better ( visually ) result
3488 xOLEProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ), uno::Any( text::TextContentAnchorType_AS_CHARACTER ) );
3489 // remove ( if valid ) associated shape ( used for graphic replacement )
3490 SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
3491 if (!m_aAnchoredStack.empty())
3492 m_aAnchoredStack.top( ).bToRemove = true;
3493 RemoveLastParagraph();
3494 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack");
3495 if (!m_aTextAppendStack.empty())
3496 m_aTextAppendStack.pop();
3498 appendTextContent( xOLE, uno::Sequence< beans::PropertyValue >() );
3500 if (!aCLSID.isEmpty())
3501 pOLEHandler->importStream(m_xComponentContext, GetTextDocument(), xOLE);
3504 catch( const uno::Exception& )
3506 TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of OLE object" );
3511 void DomainMapper_Impl::appendStarMath( const Value& val )
3513 uno::Reference< embed::XEmbeddedObject > formula;
3514 val.getAny() >>= formula;
3515 if( !formula.is() )
3516 return;
3520 uno::Reference< text::XTextContent > xStarMath( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
3521 uno::Reference< beans::XPropertySet > xStarMathProperties(xStarMath, uno::UNO_QUERY_THROW);
3523 xStarMathProperties->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ),
3524 val.getAny());
3525 // tdf#66405: set zero margins for embedded object
3526 xStarMathProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
3527 uno::Any(sal_Int32(0)));
3528 xStarMathProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
3529 uno::Any(sal_Int32(0)));
3530 xStarMathProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
3531 uno::Any(sal_Int32(0)));
3532 xStarMathProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
3533 uno::Any(sal_Int32(0)));
3535 uno::Reference< uno::XInterface > xInterface( formula->getComponent(), uno::UNO_QUERY );
3536 // set zero margins for object's component
3537 uno::Reference< beans::XPropertySet > xComponentProperties( xInterface, uno::UNO_QUERY_THROW );
3538 xComponentProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
3539 uno::Any(sal_Int32(0)));
3540 xComponentProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
3541 uno::Any(sal_Int32(0)));
3542 xComponentProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
3543 uno::Any(sal_Int32(0)));
3544 xComponentProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
3545 uno::Any(sal_Int32(0)));
3546 Size size( 1000, 1000 );
3547 if( oox::FormulaImExportBase* formulaimport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
3548 size = formulaimport->getFormulaSize();
3549 xStarMathProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
3550 uno::Any( sal_Int32(size.Width())));
3551 xStarMathProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
3552 uno::Any( sal_Int32(size.Height())));
3553 xStarMathProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE),
3554 uno::Any(text::TextContentAnchorType_AS_CHARACTER));
3555 // mimic the treatment of graphics here... it seems anchoring as character
3556 // gives a better ( visually ) result
3557 appendTextContent(xStarMath, uno::Sequence<beans::PropertyValue>());
3559 catch( const uno::Exception& )
3561 TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of StarMath object" );
3565 void DomainMapper_Impl::adjustLastPara(sal_Int8 nAlign)
3567 PropertyMapPtr pLastPara = GetTopContextOfType(dmapper::CONTEXT_PARAGRAPH);
3568 pLastPara->Insert(PROP_PARA_ADJUST, uno::Any(nAlign), true);
3571 static void checkAndAddPropVal(const OUString& prop, const css::uno::Any& val,
3572 std::vector<OUString>& props, std::vector<css::uno::Any>& values)
3574 // Avoid well-known reasons for exceptions when setting property values
3575 if (!val.hasValue())
3576 return;
3577 if (prop == "CharStyleName" || prop == "DropCapCharStyleName")
3578 if (OUString val_string; (val >>= val_string) && val_string.isEmpty())
3579 return;
3581 props.push_back(prop);
3582 values.push_back(val);
3585 static uno::Reference<lang::XComponent>
3586 getParagraphOfRange(const css::uno::Reference<css::text::XTextRange>& xRange)
3588 uno::Reference<container::XEnumerationAccess> xEA{ xRange, uno::UNO_QUERY_THROW };
3589 return { xEA->createEnumeration()->nextElement(), uno::UNO_QUERY_THROW };
3592 static void copyAllProps(const css::uno::Reference<css::uno::XInterface>& from,
3593 const css::uno::Reference<css::uno::XInterface>& to)
3595 css::uno::Reference<css::beans::XPropertySet> xFromProps(from, css::uno::UNO_QUERY_THROW);
3596 css::uno::Reference<css::beans::XPropertySetInfo> xFromInfo(xFromProps->getPropertySetInfo(),
3597 css::uno::UNO_SET_THROW);
3598 css::uno::Sequence<css::beans::Property> rawProps(xFromInfo->getProperties());
3599 std::vector<OUString> props;
3600 props.reserve(rawProps.getLength());
3601 for (const auto& prop : rawProps)
3602 if ((prop.Attributes & css::beans::PropertyAttribute::READONLY) == 0)
3603 props.push_back(prop.Name);
3605 if (css::uno::Reference<css::beans::XPropertyState> xFromState{ from, css::uno::UNO_QUERY })
3607 const auto propsSeq = comphelper::containerToSequence(props);
3608 const auto statesSeq = xFromState->getPropertyStates(propsSeq);
3609 assert(propsSeq.getLength() == statesSeq.getLength());
3610 for (sal_Int32 i = 0; i < propsSeq.getLength(); ++i)
3611 if (statesSeq[i] != css::beans::PropertyState_DIRECT_VALUE)
3612 std::erase(props, propsSeq[i]);
3615 std::vector<css::uno::Any> values;
3616 values.reserve(props.size());
3617 if (css::uno::Reference<css::beans::XMultiPropertySet> xFromMulti{ xFromProps,
3618 css::uno::UNO_QUERY })
3620 const auto propsSeq = comphelper::containerToSequence(props);
3621 const auto valuesSeq = xFromMulti->getPropertyValues(propsSeq);
3622 assert(propsSeq.getLength() == valuesSeq.getLength());
3623 props.clear();
3624 for (size_t i = 0; i < propsSeq.size(); ++i)
3625 checkAndAddPropVal(propsSeq[i], valuesSeq[i], props, values);
3627 else
3629 std::vector<OUString> filtered_props;
3630 filtered_props.reserve(props.size());
3631 for (const auto& prop : props)
3632 checkAndAddPropVal(prop, xFromProps->getPropertyValue(prop), filtered_props, values);
3633 filtered_props.swap(props);
3635 assert(props.size() == values.size());
3637 css::uno::Reference<css::beans::XPropertySet> xToProps(to, css::uno::UNO_QUERY_THROW);
3638 if (css::uno::Reference<css::beans::XMultiPropertySet> xToMulti{ xToProps,
3639 css::uno::UNO_QUERY })
3643 xToMulti->setPropertyValues(comphelper::containerToSequence(props),
3644 comphelper::containerToSequence(values));
3645 return;
3647 catch (css::uno::Exception&)
3649 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3651 // Fallback to property-by-property iteration
3654 for (size_t i = 0; i < props.size(); ++i)
3658 xToProps->setPropertyValue(props[i], values[i]);
3660 catch (css::uno::Exception&)
3662 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3667 uno::Reference< beans::XPropertySet > DomainMapper_Impl::appendTextSectionAfter(
3668 uno::Reference< text::XTextRange > const & xBefore )
3670 uno::Reference< beans::XPropertySet > xRet;
3671 if (m_aTextAppendStack.empty())
3672 return xRet;
3673 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
3674 if(xTextAppend.is())
3678 uno::Reference< text::XParagraphCursor > xCursor(
3679 xTextAppend->createTextCursorByRange( xBefore ), uno::UNO_QUERY_THROW);
3680 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
3681 xCursor->gotoStartOfParagraph( false );
3682 if (m_aTextAppendStack.top().xInsertPosition.is())
3683 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
3684 else
3685 xCursor->gotoEnd( true );
3686 // The paragraph after this new section is already inserted. The previous node may be a
3687 // table; then trying to go left would skip the whole table. Split the trailing
3688 // paragraph; let the section span over the first of the two resulting paragraphs;
3689 // destroy the last section's paragraph afterwards.
3690 xTextAppend->insertControlCharacter(
3691 xCursor->getEnd(), css::text::ControlCharacter::PARAGRAPH_BREAK, false);
3692 auto xNewPara = getParagraphOfRange(xCursor->getEnd());
3693 xCursor->gotoPreviousParagraph(true);
3694 auto xEndPara = getParagraphOfRange(xCursor->getEnd());
3695 // xEndPara may already have properties (like page break); make sure to apply them
3696 // to the newly appended paragraph, which will be kept in the end.
3697 copyAllProps(xEndPara, xNewPara);
3699 uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY_THROW );
3700 xSection->attach(xCursor);
3702 // Remove the extra paragraph (last inside the section)
3703 xEndPara->dispose();
3705 xRet.set(xSection, uno::UNO_QUERY );
3707 catch(const uno::Exception&)
3713 return xRet;
3716 void DomainMapper_Impl::appendGlossaryEntry()
3718 appendTextSectionAfter(m_xGlossaryEntryStart);
3721 void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar)
3723 if (bSetAnchorToChar)
3724 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER));
3726 uno::Any aEmptyBorder{table::BorderLine2()};
3727 static const std::vector<PropertyIds> aBorderIds
3728 = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER };
3729 for (size_t i = 0; i < aBorderIds.size(); ++i)
3730 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder));
3732 static const std::vector<PropertyIds> aMarginIds
3733 = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE,
3734 PROP_LEFT_MARGIN, PROP_LEFT_BORDER_DISTANCE,
3735 PROP_RIGHT_MARGIN, PROP_RIGHT_BORDER_DISTANCE,
3736 PROP_TOP_MARGIN, PROP_TOP_BORDER_DISTANCE };
3737 for (size_t i = 0; i < aMarginIds.size(); ++i)
3738 rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0)));
3741 bool DomainMapper_Impl::IsInTOC() const
3743 if (IsInHeaderFooter())
3744 return m_bStartTOCHeaderFooter;
3745 else
3746 return m_bStartTOC;
3749 void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom)
3751 while (!m_aHeaderFooterTextAppendStack.empty())
3753 auto& [aTextAppendContext, ePagePartType] = m_aHeaderFooterTextAppendStack.top();
3754 if ((ePagePartType == PagePartType::Header && !bDynamicHeightTop) || (ePagePartType == PagePartType::Footer && !bDynamicHeightBottom))
3756 uno::Reference< text::XTextAppend > xTextAppend = aTextAppendContext.xTextAppend;
3757 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor();
3758 uno::Reference< text::XTextRange > xRangeStart, xRangeEnd;
3760 xRangeStart = xCursor->getStart();
3761 xCursor->gotoEnd(false);
3762 xRangeEnd = xCursor->getStart();
3764 std::vector<beans::PropertyValue> aFrameProperties
3766 comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH),
3767 comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT),
3768 comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false),
3769 comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN),
3770 comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN),
3771 // tdf#143384 If the header/footer started with a table, convertToTextFrame could not
3772 // convert the table, because it used createTextCursor() -which ignore tables-
3773 // to set the conversion range.
3774 // This dummy property is set to make convertToTextFrame to use another CreateTextCursor
3775 // method that can be parameterized to not ignore tables.
3776 comphelper::makePropertyValue(getPropertyName(PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF), true)
3779 fillEmptyFrameProperties(aFrameProperties, false);
3781 // If it is a footer, then orient the frame to the bottom
3782 if (ePagePartType == PagePartType::Footer)
3784 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM));
3786 uno::Reference<text::XTextAppendAndConvert> xBodyText(xRangeStart->getText(), uno::UNO_QUERY);
3787 xBodyText->convertToTextFrame(xTextAppend, xRangeEnd, comphelper::containerToSequence(aFrameProperties));
3789 m_aHeaderFooterTextAppendStack.pop();
3793 namespace
3795 // Determines if the XText content is empty (no text, no shapes, no tables)
3796 bool isContentEmpty(uno::Reference<text::XText> const& xText)
3798 if (!xText.is())
3799 return true; // no XText means it's empty
3801 uno::Reference<css::lang::XServiceInfo> xTextServiceInfo(xText, uno::UNO_QUERY);
3802 if (xTextServiceInfo && xTextServiceInfo->getImplementationName() == "SwXHeadFootText")
3803 return false;
3805 uno::Reference<container::XEnumerationAccess> xEnumAccess(xText->getText(), uno::UNO_QUERY);
3806 uno::Reference<container::XEnumeration> xEnum = xEnumAccess->createEnumeration();
3807 while (xEnum->hasMoreElements())
3809 auto xObject = xEnum->nextElement();
3810 uno::Reference<text::XTextTable> const xTextTable(xObject, uno::UNO_QUERY);
3811 if (xTextTable.is())
3812 return false;
3814 uno::Reference<text::XTextRange> const xParagraph(xObject, uno::UNO_QUERY);
3815 if (xParagraph.is() && !xParagraph->getString().isEmpty())
3816 return false;
3818 return true;
3821 } // end anonymous namespace
3823 void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageType eType)
3825 bool bHeader = ePagePartType == PagePartType::Header;
3827 const PropertyIds ePropIsOn = bHeader ? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON;
3828 const PropertyIds ePropShared = bHeader ? PROP_HEADER_IS_SHARED: PROP_FOOTER_IS_SHARED;
3829 const PropertyIds ePropTextLeft = bHeader ? PROP_HEADER_TEXT_LEFT: PROP_FOOTER_TEXT_LEFT;
3830 const PropertyIds ePropTextFirst = bHeader ? PROP_HEADER_TEXT_FIRST: PROP_FOOTER_TEXT_FIRST;
3831 const PropertyIds ePropTextRight = bHeader ? PROP_HEADER_TEXT: PROP_FOOTER_TEXT;
3833 m_bDiscardHeaderFooter = true;
3834 m_StreamStateStack.top().eSubstreamType = bHeader ? SubstreamType::Header : SubstreamType::Footer;
3836 //get the section context
3837 SectionPropertyMap* pSectionContext = GetSectionContext();;
3838 if (!pSectionContext)
3839 return;
3841 if (!m_bIsNewDoc)
3842 return; // TODO sw cannot Undo insert header/footer without crashing
3844 uno::Reference<beans::XPropertySet> xPageStyle = pSectionContext->GetPageStyle(*this);
3845 if (!xPageStyle.is())
3846 return;
3848 bool bEvenAndOdd = GetSettingsTable()->GetEvenAndOddHeaders();
3852 // Turn on the headers
3853 xPageStyle->setPropertyValue(getPropertyName(ePropIsOn), uno::Any(true));
3855 // Set both sharing left and first to off so we can import the content regardless tha what value
3856 // the "titlePage" or "evenAndOdd" flags are set (which decide what the sharing is set to in the document).
3857 xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false));
3858 xPageStyle->setPropertyValue(getPropertyName(PROP_FIRST_IS_SHARED), uno::Any(false));
3860 if (eType == PageType::LEFT)
3862 if (bHeader)
3864 pSectionContext->m_bLeftHeader = true;
3865 pSectionContext->m_bHadLeftHeader = true;
3867 else
3868 pSectionContext->m_bLeftFooter = true;
3870 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextLeft, bEvenAndOdd);
3872 else if (eType == PageType::FIRST)
3874 if (bHeader)
3876 pSectionContext->m_bFirstHeader = true;
3877 pSectionContext->m_bHadFirstHeader = true;
3879 else
3880 pSectionContext->m_bFirstFooter = true;
3882 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextFirst, true);
3884 else
3886 if (bHeader)
3888 pSectionContext->m_bRightHeader = true;
3889 pSectionContext->m_bHadRightHeader = true;
3891 else
3892 pSectionContext->m_bRightFooter = true;
3894 prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextRight, true);
3897 m_bDiscardHeaderFooter = false; // set only on success!
3899 catch( const uno::Exception& )
3901 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3905 /** Prepares the header/footer text content by first removing the existing
3906 * content and adding it to the text append stack. */
3907 void DomainMapper_Impl::prepareHeaderFooterContent(uno::Reference<beans::XPropertySet> const& xPageStyle,
3908 PagePartType ePagePartType, PropertyIds ePropertyID,
3909 bool bAppendToHeaderAndFooterTextStack)
3911 uno::Reference<text::XText> xText;
3912 xPageStyle->getPropertyValue(getPropertyName(ePropertyID)) >>= xText;
3914 //remove the existing content first
3915 SectionPropertyMap::removeXTextContent(xText);
3917 auto xTextCursor = m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xText->createTextCursorByRange(xText->getStart());
3918 uno::Reference<text::XTextAppend> xTextAppend(xText, uno::UNO_QUERY_THROW);
3919 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTextCursor));
3920 if (bAppendToHeaderAndFooterTextStack)
3921 m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(xTextAppend, xTextCursor), ePagePartType));
3924 bool DomainMapper_Impl::SeenHeaderFooter(PagePartType const partType, PageType const pageType) const
3926 return m_HeaderFooterSeen.find({partType, pageType}) != m_HeaderFooterSeen.end();
3929 /** Checks if the header and footer content on the text appended stack is empty.
3931 void DomainMapper_Impl::checkIfHeaderFooterIsEmpty(PagePartType ePagePartType, PageType eType)
3933 if (m_bDiscardHeaderFooter)
3934 return;
3936 if (m_aTextAppendStack.empty())
3937 return;
3939 SectionPropertyMap* pSectionContext = GetSectionContext();
3940 if (!pSectionContext)
3941 return;
3943 bool bHeader = ePagePartType == PagePartType::Header;
3945 uno::Reference<beans::XPropertySet> xPageStyle(pSectionContext->GetPageStyle(*this));
3947 if (!xPageStyle.is())
3948 return;
3950 bool bEmpty = isContentEmpty(m_aTextAppendStack.top().xTextAppend);
3952 if (eType == PageType::FIRST && bEmpty)
3954 if (bHeader)
3955 pSectionContext->m_bFirstHeader = false;
3956 else
3957 pSectionContext->m_bFirstFooter = false;
3959 else if (eType == PageType::LEFT && bEmpty)
3961 if (bHeader)
3962 pSectionContext->m_bLeftHeader = false;
3963 else
3964 pSectionContext->m_bLeftFooter = false;
3966 else if (eType == PageType::RIGHT && bEmpty)
3968 if (bHeader)
3969 pSectionContext->m_bRightHeader = false;
3970 else
3971 pSectionContext->m_bRightFooter = false;
3975 void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType eType)
3977 //header and footer always have an empty paragraph at the end
3978 //this has to be removed
3979 RemoveLastParagraph();
3981 checkIfHeaderFooterIsEmpty(ePagePartType, eType);
3983 // clear the "Link To Previous" flag so that the header/footer
3984 // content is not copied from the previous section
3985 SectionPropertyMap* pSectionContext = GetSectionContext();
3986 if (pSectionContext)
3988 pSectionContext->clearHeaderFooterLinkToPrevious(ePagePartType, eType);
3989 m_HeaderFooterSeen.emplace(ePagePartType, eType);
3992 if (!m_aTextAppendStack.empty())
3994 if (!m_bDiscardHeaderFooter)
3996 m_aTextAppendStack.pop();
3998 m_bDiscardHeaderFooter = false;
4002 void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote )
4004 SAL_WARN_IF(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote");
4005 m_StreamStateStack.top().eSubstreamType = bIsFootnote ? SubstreamType::Footnote : SubstreamType::Endnote;
4006 m_bCheckFirstFootnoteTab = true;
4009 // Redlines outside the footnote should not affect footnote content
4010 m_aRedlines.push(std::vector< RedlineParamsPtr >());
4012 // IMHO character styles from footnote labels should be ignored in the edit view of Writer.
4013 // This adds a hack on top of the following hack to save the style name in the context.
4014 PropertyMapPtr pTopContext = GetTopContext();
4015 OUString sFootnoteCharStyleName;
4016 std::optional< PropertyMap::Property > aProp = pTopContext->getProperty(PROP_CHAR_STYLE_NAME);
4017 if (aProp)
4018 aProp->second >>= sFootnoteCharStyleName;
4020 // Remove style reference, if any. This reference did appear here as a side effect of tdf#43017
4021 // Seems it is not required by LO, but causes side effects during editing. So remove it
4022 // for footnotes/endnotes to restore original LO behavior here.
4023 pTopContext->Erase(PROP_CHAR_STYLE_NAME);
4025 uno::Reference< text::XText > xFootnoteText;
4026 if (GetTextFactory().is())
4027 xFootnoteText.set( GetTextFactory()->createInstance(
4028 bIsFootnote ?
4029 OUString( "com.sun.star.text.Footnote" ) : OUString( "com.sun.star.text.Endnote" )),
4030 uno::UNO_QUERY_THROW );
4031 uno::Reference< text::XFootnote > xFootnote( xFootnoteText, uno::UNO_QUERY_THROW );
4032 pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName);
4033 uno::Sequence< beans::PropertyValue > aFontProperties;
4034 if (GetTopContextOfType(CONTEXT_CHARACTER))
4035 aFontProperties = GetTopContextOfType(CONTEXT_CHARACTER)->GetPropertyValues();
4036 appendTextContent( uno::Reference< text::XTextContent >( xFootnoteText, uno::UNO_QUERY_THROW ), aFontProperties );
4037 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xFootnoteText, uno::UNO_QUERY_THROW ),
4038 xFootnoteText->createTextCursorByRange(xFootnoteText->getStart())));
4040 // Redlines for the footnote anchor in the main text content
4041 std::vector< RedlineParamsPtr > aFootnoteRedline = std::move(m_aRedlines.top());
4042 m_aRedlines.pop();
4043 CheckRedline( xFootnote->getAnchor( ) );
4044 m_aRedlines.push( aFootnoteRedline );
4046 // Try scanning for custom footnote labels
4047 if (!sFootnoteCharStyleName.isEmpty())
4048 StartCustomFootnote(pTopContext);
4049 else
4050 EndCustomFootnote();
4052 catch( const uno::Exception& )
4054 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "PushFootOrEndnote");
4058 void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xRange,
4059 const RedlineParamsPtr& pRedline)
4061 if ( !pRedline )
4062 return;
4064 bool bRedlineMoved = false;
4067 OUString sType;
4068 switch ( pRedline->m_nToken & 0xffff )
4070 case XML_mod:
4071 sType = getPropertyName( PROP_FORMAT );
4072 break;
4073 case XML_moveTo:
4074 bRedlineMoved = true;
4075 m_pParaMarkerRedlineMove = pRedline.get();
4076 [[fallthrough]];
4077 case XML_ins:
4078 sType = getPropertyName( PROP_INSERT );
4079 break;
4080 case XML_moveFrom:
4081 bRedlineMoved = true;
4082 m_pParaMarkerRedlineMove = pRedline.get();
4083 [[fallthrough]];
4084 case XML_del:
4085 sType = getPropertyName( PROP_DELETE );
4086 break;
4087 case XML_ParagraphFormat:
4088 sType = getPropertyName( PROP_PARAGRAPH_FORMAT );
4089 break;
4090 default:
4091 throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0);
4093 beans::PropertyValues aRedlineProperties( 4 );
4094 beans::PropertyValue * pRedlineProperties = aRedlineProperties.getArray( );
4095 pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_AUTHOR );
4096 pRedlineProperties[0].Value <<= pRedline->m_sAuthor;
4097 pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_DATE_TIME );
4098 util::DateTime aDateTime = ConversionHelper::ConvertDateStringToDateTime( pRedline->m_sDate );
4099 // tdf#146171 import not specified w:date (or specified as zero date "0-00-00")
4100 // as Epoch time to avoid of losing change tracking data during ODF roundtrip
4101 if ( aDateTime.Year == 0 && aDateTime.Month == 0 && aDateTime.Day == 0 )
4103 aDateTime.Year = 1970;
4104 aDateTime.Month = 1;
4105 aDateTime.Day = 1;
4107 pRedlineProperties[1].Value <<= aDateTime;
4108 pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES );
4109 pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties;
4111 sal_uInt32 nRedlineMovedID = 0;
4112 if (bRedlineMoved)
4114 if (!m_sCurrentBkmkId.isEmpty())
4116 nRedlineMovedID = 1;
4117 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find(m_sCurrentBkmkId);
4118 if (aBookmarkIter != m_aBookmarkMap.end())
4120 OUString sMoveID = aBookmarkIter->second.m_sBookmarkName;
4121 auto aIter = m_aRedlineMoveIDs.end();
4123 if (sMoveID.indexOf("__RefMoveFrom__") >= 0)
4125 aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
4126 sMoveID.subView(15));
4128 else if (sMoveID.indexOf("__RefMoveTo__") >= 0)
4130 aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
4131 sMoveID.subView(13));
4134 if (aIter != m_aRedlineMoveIDs.end())
4136 nRedlineMovedID = aIter - m_aRedlineMoveIDs.begin() + 2;
4137 m_nLastRedlineMovedID = nRedlineMovedID;
4141 else
4142 nRedlineMovedID = m_nLastRedlineMovedID;
4144 pRedlineProperties[3].Name = "RedlineMoved";
4145 pRedlineProperties[3].Value <<= nRedlineMovedID;
4147 if (!m_bIsActualParagraphFramed)
4149 uno::Reference < text::XRedline > xRedline( xRange, uno::UNO_QUERY_THROW );
4150 xRedline->makeRedline( sType, aRedlineProperties );
4152 // store frame and (possible floating) table redline data for restoring them after frame conversion
4153 enum StoredRedlines eType;
4154 if (m_bIsActualParagraphFramed || 0 < m_StreamStateStack.top().nTableDepth)
4155 eType = StoredRedlines::FRAME;
4156 else if (IsInFootOrEndnote())
4157 eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
4158 else
4159 eType = StoredRedlines::NONE;
4161 if (eType != StoredRedlines::NONE)
4163 m_aStoredRedlines[eType].push_back( uno::Any(xRange) );
4164 m_aStoredRedlines[eType].push_back( uno::Any(sType) );
4165 m_aStoredRedlines[eType].push_back( uno::Any(aRedlineProperties) );
4168 catch( const uno::Exception & )
4170 TOOLS_WARN_EXCEPTION( "writerfilter", "in makeRedline" );
4174 void DomainMapper_Impl::CheckParaMarkerRedline( uno::Reference< text::XTextRange > const& xRange )
4176 if ( m_pParaMarkerRedline )
4178 CreateRedline( xRange, m_pParaMarkerRedline );
4179 if ( m_pParaMarkerRedline )
4181 m_pParaMarkerRedline.clear();
4182 m_currentRedline.clear();
4185 else if ( m_pParaMarkerRedlineMove && m_bIsParaMarkerMove )
4187 // terminating moveFrom/moveTo redline removes also the paragraph mark
4188 CreateRedline( xRange, m_pParaMarkerRedlineMove );
4190 if ( m_pParaMarkerRedlineMove )
4192 m_pParaMarkerRedlineMove.clear();
4193 EndParaMarkerMove();
4197 void DomainMapper_Impl::CheckRedline( uno::Reference< text::XTextRange > const& xRange )
4199 // Writer core "officially" does not like overlapping redlines, and its UNO interface is stupid enough
4200 // to not prevent that. However, in practice in fact everything appears to work fine (except for the debug warnings
4201 // about redline table corruption, which may possibly be harmless in reality). So leave this as it is, since this
4202 // is a better representation of how the changes happened. If this will ever become a problem, overlapping redlines
4203 // will need to be merged into one, just like doing the changes in the UI does, which will lose some information
4204 // (and so if that happens, it may be better to fix Writer).
4205 // Create the redlines here from lowest (formats) to highest (inserts/removals) priority, since the last one is
4206 // what Writer presents graphically, so this will show deletes as deleted text and not as just formatted text being there.
4207 bool bUsedRange = m_aRedlines.top().size() > 0 || (GetTopContextOfType(CONTEXT_CHARACTER) &&
4208 GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0);
4210 // only export ParagraphFormat, when there is no other redline in the same text portion to avoid missing redline compression,
4211 // but always export the first ParagraphFormat redline in a paragraph to keep the paragraph style change data for rejection
4212 if ((!bUsedRange || !m_StreamStateStack.top().bParaChanged)
4213 && GetTopContextOfType(CONTEXT_PARAGRAPH))
4215 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_PARAGRAPH)->Redlines();
4216 for( const auto& rRedline : avRedLines )
4217 CreateRedline( xRange, rRedline );
4219 if( GetTopContextOfType(CONTEXT_CHARACTER) )
4221 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_CHARACTER)->Redlines();
4222 for( const auto& rRedline : avRedLines )
4223 CreateRedline( xRange, rRedline );
4225 for (const auto& rRedline : m_aRedlines.top() )
4226 CreateRedline( xRange, rRedline );
4229 void DomainMapper_Impl::StartParaMarkerChange( )
4231 m_bIsParaMarkerChange = true;
4234 void DomainMapper_Impl::EndParaMarkerChange( )
4236 m_bIsParaMarkerChange = false;
4237 m_previousRedline = m_currentRedline;
4238 m_currentRedline.clear();
4241 void DomainMapper_Impl::StartParaMarkerMove( )
4243 m_bIsParaMarkerMove = true;
4246 void DomainMapper_Impl::EndParaMarkerMove( )
4248 m_bIsParaMarkerMove = false;
4251 void DomainMapper_Impl::StartCustomFootnote(const PropertyMapPtr pContext)
4253 if (pContext == m_pFootnoteContext)
4254 return;
4256 assert(pContext->GetFootnote().is());
4257 m_bHasFootnoteStyle = true;
4258 m_bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty();
4259 m_pFootnoteContext = pContext;
4262 void DomainMapper_Impl::EndCustomFootnote()
4264 m_bHasFootnoteStyle = false;
4265 m_bCheckFootnoteStyle = false;
4268 void DomainMapper_Impl::PushAnnotation()
4272 m_StreamStateStack.top().eSubstreamType = SubstreamType::Annotation;
4273 if (!GetTextFactory().is())
4274 return;
4275 m_xAnnotationField.set( GetTextFactory()->createInstance( "com.sun.star.text.TextField.Annotation" ),
4276 uno::UNO_QUERY_THROW );
4277 uno::Reference< text::XText > xAnnotationText;
4278 m_xAnnotationField->getPropertyValue("TextRange") >>= xAnnotationText;
4279 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xAnnotationText, uno::UNO_QUERY_THROW ),
4280 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xAnnotationText->createTextCursorByRange(xAnnotationText->getStart())));
4282 catch( const uno::Exception&)
4284 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
4288 static void lcl_CopyRedlines(
4289 uno::Reference< text::XText > const& xSrc,
4290 std::deque<css::uno::Any>& rRedlines,
4291 std::vector<sal_Int32>& redPos,
4292 std::vector<sal_Int32>& redLen,
4293 sal_Int32& redIdx)
4295 redIdx = -1;
4296 for( size_t i = 0; i < rRedlines.size(); i+=3)
4298 uno::Reference< text::XTextRange > xRange;
4299 rRedlines[i] >>= xRange;
4301 // is this a redline of the temporary footnote?
4302 uno::Reference<text::XTextCursor> xRangeCursor;
4305 xRangeCursor = xSrc->createTextCursorByRange( xRange );
4307 catch( const uno::Exception& )
4310 if (xRangeCursor.is())
4312 redIdx = i;
4313 sal_Int32 nLen = xRange->getString().getLength();
4314 redLen.push_back(nLen);
4315 xRangeCursor->gotoRange(xSrc->getStart(), true);
4316 redPos.push_back(xRangeCursor->getString().getLength() - nLen);
4318 else
4320 // we have already found all redlines of the footnote,
4321 // skip checking the redlines of the other footnotes
4322 if (redIdx > -1)
4323 break;
4324 // failed createTextCursorByRange(), for example, table inside the frame
4325 redLen.push_back(-1);
4326 redPos.push_back(-1);
4331 static void lcl_PasteRedlines(
4332 uno::Reference< text::XText > const& xDest,
4333 std::deque<css::uno::Any>& rRedlines,
4334 std::vector<sal_Int32>& redPos,
4335 std::vector<sal_Int32>& redLen,
4336 sal_Int32 redIdx)
4338 // create redlines in the copied footnote
4339 for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx); i+=3)
4341 OUString sType;
4342 beans::PropertyValues aRedlineProperties( 3 );
4343 // skip failed createTextCursorByRange()
4344 if (redPos[i/3] == -1)
4345 continue;
4346 rRedlines[i+1] >>= sType;
4347 rRedlines[i+2] >>= aRedlineProperties;
4348 uno::Reference< text::XTextCursor > xCrsr = xDest->getText()->createTextCursor();
4349 xCrsr->goRight(redPos[i/3], false);
4350 xCrsr->goRight(redLen[i/3], true);
4351 uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW );
4352 try {
4353 xRedline->makeRedline( sType, aRedlineProperties );
4355 catch(const uno::Exception&)
4357 // ignore (footnotes of tracked deletions)
4362 bool DomainMapper_Impl::CopyTemporaryNotes(
4363 uno::Reference< text::XFootnote > xNoteSrc,
4364 uno::Reference< text::XFootnote > xNoteDest )
4366 if (!m_bSaxError && xNoteSrc != xNoteDest)
4368 uno::Reference< text::XText > xSrc( xNoteSrc, uno::UNO_QUERY_THROW );
4369 uno::Reference< text::XText > xDest( xNoteDest, uno::UNO_QUERY_THROW );
4370 uno::Reference< text::XTextCopy > xTxt, xTxt2;
4371 xTxt.set( xSrc, uno::UNO_QUERY_THROW );
4372 xTxt2.set( xDest, uno::UNO_QUERY_THROW );
4373 xTxt2->copyText( xTxt );
4375 // copy its redlines
4376 std::vector<sal_Int32> redPos, redLen;
4377 sal_Int32 redIdx;
4378 enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
4379 lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx);
4380 lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx);
4382 // remove processed redlines
4383 for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++)
4384 m_aStoredRedlines[eType].pop_front();
4386 return true;
4389 return false;
4392 void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes()
4394 uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
4395 uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
4396 uno::Reference< text::XFootnote > xNote;
4397 if (GetFootnoteCount() > 0)
4399 auto xFootnotes = xFootnotesSupplier->getFootnotes();
4400 if ( m_nFirstFootnoteIndex > 0 )
4402 uno::Reference< text::XFootnote > xFirstNote;
4403 xFootnotes->getByIndex(0) >>= xFirstNote;
4404 uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW );
4405 xText->setString("");
4406 xFootnotes->getByIndex(m_nFirstFootnoteIndex) >>= xNote;
4407 CopyTemporaryNotes(xNote, xFirstNote);
4409 for (sal_Int32 i = GetFootnoteCount(); i > 0; --i)
4411 xFootnotes->getByIndex(i) >>= xNote;
4412 xNote->getAnchor()->setString("");
4415 if (GetEndnoteCount() > 0)
4417 auto xEndnotes = xEndnotesSupplier->getEndnotes();
4418 if ( m_nFirstEndnoteIndex > 0 )
4420 uno::Reference< text::XFootnote > xFirstNote;
4421 xEndnotes->getByIndex(0) >>= xFirstNote;
4422 uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW );
4423 xText->setString("");
4424 xEndnotes->getByIndex(m_nFirstEndnoteIndex) >>= xNote;
4425 CopyTemporaryNotes(xNote, xFirstNote);
4427 for (sal_Int32 i = GetEndnoteCount(); i > 0; --i)
4429 xEndnotes->getByIndex(i) >>= xNote;
4430 xNote->getAnchor()->setString("");
4435 static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds, sal_Int32& rFirstNoteIndex)
4437 // rNoteIds contains XML footnote identifiers in the loaded order of the footnotes
4438 // (the same order as in footnotes.xml), i.e. it maps temporary footnote positions to the
4439 // identifiers. For example: Ids[0] = 100; Ids[1] = -1, Ids[2] = 5.
4440 // To copy the footnotes in their final place, create an array, which map the (normalized)
4441 // footnote identifiers to the temporary footnote positions. Using the previous example,
4442 // Pos[0] = 1; Pos[1] = 2; Pos[2] = 0 (where [0], [1], [2] are the normalized
4443 // -1, 5 and 100 identifiers).
4444 std::deque<sal_Int32> aSortedIds = rNoteIds;
4445 std::sort(aSortedIds.begin(), aSortedIds.end());
4446 std::map<sal_Int32, size_t> aMapIds;
4447 // normalize footnote identifiers to 0, 1, 2 ...
4448 for (size_t i = 0; i < aSortedIds.size(); ++i)
4449 aMapIds[aSortedIds[i]] = i;
4450 // reusing rNoteIds, create the Pos array to map normalized identifiers to the loaded positions
4451 std::deque<sal_Int32> aOrigNoteIds = rNoteIds;
4452 for (size_t i = 0; i < rNoteIds.size(); ++i)
4453 rNoteIds[aMapIds[aOrigNoteIds[i]]] = i;
4454 rFirstNoteIndex = rNoteIds.front();
4455 rNoteIds.pop_front();
4458 void DomainMapper_Impl::PopFootOrEndnote()
4460 // content of the footnotes were inserted after the first footnote in temporary footnotes,
4461 // restore the content of the actual footnote by copying its content from the first
4462 // (remaining) temporary footnote and remove the temporary footnote.
4463 uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
4464 uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY );
4465 bool bCopied = false;
4466 if ( IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 && xFootnotesSupplier.is() ) ||
4467 ( !IsInFootnote() && GetEndnoteCount() > -1 && xEndnotesSupplier.is() ) ) )
4469 uno::Reference< text::XFootnote > xNoteFirst, xNoteLast;
4470 auto xFootnotes = xFootnotesSupplier->getFootnotes();
4471 auto xEndnotes = xEndnotesSupplier->getEndnotes();
4472 if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 &&
4473 ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xNoteLast ) ) ||
4474 ( !IsInFootnote() && xEndnotes->getCount() > 1 &&
4475 ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xNoteLast ) )
4476 ) && xNoteLast->getLabel().isEmpty() )
4478 // copy content of the next temporary footnote
4481 if ( IsInFootnote() && !m_aFootnoteIds.empty() )
4483 if ( m_nFirstFootnoteIndex == -1 )
4484 lcl_convertToNoteIndices(m_aFootnoteIds, m_nFirstFootnoteIndex);
4485 if (m_aFootnoteIds.empty()) // lcl_convertToNoteIndices pops m_aFootnoteIds
4486 m_bSaxError = true;
4487 else
4489 xFootnotes->getByIndex(m_aFootnoteIds.front()) >>= xNoteFirst;
4490 m_aFootnoteIds.pop_front();
4493 else if ( !IsInFootnote() && !m_aEndnoteIds.empty() )
4495 if ( m_nFirstEndnoteIndex == -1 )
4496 lcl_convertToNoteIndices(m_aEndnoteIds, m_nFirstEndnoteIndex);
4497 if (m_aEndnoteIds.empty()) // lcl_convertToNoteIndices pops m_aEndnoteIds
4498 m_bSaxError = true;
4499 else
4501 xEndnotes->getByIndex(m_aEndnoteIds.front()) >>= xNoteFirst;
4502 m_aEndnoteIds.pop_front();
4505 else
4506 m_bSaxError = true;
4508 catch (uno::Exception const&)
4510 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote");
4511 m_bSaxError = true;
4514 bCopied = CopyTemporaryNotes(xNoteFirst, xNoteLast);
4518 if (!IsRTFImport() && !bCopied)
4519 RemoveLastParagraph();
4521 // In case the foot or endnote did not contain a tab.
4522 m_bIgnoreNextTab = false;
4524 if (!m_aTextAppendStack.empty())
4525 m_aTextAppendStack.pop();
4527 if (m_aRedlines.size() == 1)
4529 SAL_WARN("writerfilter.dmapper", "PopFootOrEndnote() is called without PushFootOrEndnote()?");
4530 return;
4532 m_aRedlines.pop();
4533 m_eSkipFootnoteState = SkipFootnoteSeparator::OFF;
4534 m_pFootnoteContext = nullptr;
4537 void DomainMapper_Impl::PopAnnotation()
4539 RemoveLastParagraph();
4541 m_aTextAppendStack.pop();
4545 if (m_bAnnotationResolved)
4546 m_xAnnotationField->setPropertyValue("Resolved", uno::Any(true));
4548 m_xAnnotationField->setPropertyValue("ParaIdParent", uno::Any(m_sAnnotationParent));
4549 m_xAnnotationField->setPropertyValue("ParaId", uno::Any(m_sAnnotationImportedParaId));
4551 // See if the annotation will be a single position or a range.
4552 if (m_nAnnotationId == -1 || !m_aAnnotationPositions[m_nAnnotationId].m_xStart.is() || !m_aAnnotationPositions[m_nAnnotationId].m_xEnd.is())
4554 uno::Sequence< beans::PropertyValue > aEmptyProperties;
4555 uno::Reference< text::XTextContent > xContent( m_xAnnotationField, uno::UNO_QUERY_THROW );
4556 appendTextContent( xContent, aEmptyProperties );
4557 CheckRedline( xContent->getAnchor( ) );
4559 else
4561 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[m_nAnnotationId];
4562 // Create a range that points to the annotation start/end.
4563 uno::Reference<text::XText> const xText = aAnnotationPosition.m_xStart->getText();
4564 uno::Reference<text::XTextCursor> const xCursor = xText->createTextCursorByRange(aAnnotationPosition.m_xStart);
4566 bool bMarker = false;
4567 uno::Reference<text::XTextRangeCompare> xTextRangeCompare(xText, uno::UNO_QUERY);
4568 if (xTextRangeCompare->compareRegionStarts(aAnnotationPosition.m_xStart, aAnnotationPosition.m_xEnd) == 0)
4570 // Insert a marker so that comment around an anchored image is not collapsed during
4571 // insertion.
4572 xText->insertString(xCursor, "x", false);
4573 bMarker = true;
4576 xCursor->gotoRange(aAnnotationPosition.m_xEnd, true);
4577 uno::Reference<text::XTextRange> const xTextRange(xCursor, uno::UNO_QUERY_THROW);
4579 // Attach the annotation to the range.
4580 uno::Reference<text::XTextAppend> const xTextAppend = m_aTextAppendStack.top().xTextAppend;
4581 xTextAppend->insertTextContent(xTextRange, uno::Reference<text::XTextContent>(m_xAnnotationField, uno::UNO_QUERY_THROW), !xCursor->isCollapsed());
4583 if (bMarker)
4585 // Remove the marker.
4586 xCursor->goLeft(1, true);
4587 xCursor->setString(OUString());
4590 m_aAnnotationPositions.erase( m_nAnnotationId );
4592 catch (uno::Exception const&)
4594 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert annotation field");
4597 m_xAnnotationField.clear();
4598 m_sAnnotationParent.clear();
4599 m_sAnnotationImportedParaId.clear();
4600 m_nAnnotationId = -1;
4601 m_bAnnotationResolved = false;
4604 void DomainMapper_Impl::PushPendingShape( const uno::Reference< drawing::XShape > & xShape )
4606 m_aPendingShapes.push_back(xShape);
4609 uno::Reference<drawing::XShape> DomainMapper_Impl::PopPendingShape()
4611 uno::Reference<drawing::XShape> xRet;
4612 if (!m_aPendingShapes.empty())
4614 xRet = m_aPendingShapes.front();
4615 m_aPendingShapes.pop_front();
4617 return xRet;
4620 void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape > & xShape )
4622 // Append these early, so the context and the table manager stack will be
4623 // in sync, even if the text append stack is empty.
4624 appendTableManager();
4625 appendTableHandler();
4626 getTableManager().startLevel();
4628 if (m_aTextAppendStack.empty())
4629 return;
4630 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
4634 uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW );
4635 if (xSInfo->supportsService("com.sun.star.drawing.GroupShape"))
4637 // Textboxes in shapes do not support styles, so check saved style information and apply properties directly to the child shapes.
4638 const uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY);
4639 const sal_uInt32 nShapeCount = xShapes.is() ? xShapes->getCount() : 0;
4640 for ( sal_uInt32 i = 0; i < nShapeCount; ++i )
4644 uno::Reference<text::XTextRange> xFrame(xShapes->getByIndex(i), uno::UNO_QUERY);
4645 uno::Reference<beans::XPropertySet> xFramePropertySet;
4646 if (xFrame)
4647 xFramePropertySet.set(xFrame, uno::UNO_QUERY_THROW);
4648 uno::Reference<beans::XPropertySet> xShapePropertySet(xShapes->getByIndex(i), uno::UNO_QUERY_THROW);
4650 comphelper::SequenceAsHashMap aGrabBag( xShapePropertySet->getPropertyValue("CharInteropGrabBag") );
4652 // only VML import has checked for style. Don't apply default parastyle properties to other imported shapes
4653 // - except for fontsize - to maintain compatibility with previous versions of LibreOffice.
4654 const bool bOnlyApplyCharHeight = !aGrabBag["mso-pStyle"].hasValue();
4656 OUString sStyleName;
4657 aGrabBag["mso-pStyle"] >>= sStyleName;
4658 StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByISTD( sStyleName );
4659 if ( !pEntry )
4661 // Use default style even in ambiguous cases (where multiple styles were defined) since MOST styles inherit
4662 // MOST of their properties from the default style. In the ambiguous case, we have to accept some kind of compromise
4663 // and the default paragraph style ought to be the safest one... (compared to DocDefaults or program defaults)
4664 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetDefaultParaStyleName() );
4666 if ( pEntry )
4668 // The Ids here come from oox/source/vml/vmltextbox.cxx.
4669 // It probably could safely expand to all Ids that shapes support.
4670 const PropertyIds eIds[] = {
4671 PROP_CHAR_HEIGHT,
4672 PROP_CHAR_FONT_NAME,
4673 PROP_CHAR_WEIGHT,
4674 PROP_CHAR_CHAR_KERNING,
4675 PROP_CHAR_COLOR,
4676 PROP_PARA_ADJUST
4678 const uno::Reference<beans::XPropertyState> xShapePropertyState(xShapePropertySet, uno::UNO_QUERY_THROW);
4679 for ( const auto& eId : eIds )
4683 if ( bOnlyApplyCharHeight && eId != PROP_CHAR_HEIGHT )
4684 continue;
4686 const OUString & sPropName = getPropertyName(eId);
4687 if ( beans::PropertyState_DEFAULT_VALUE == xShapePropertyState->getPropertyState(sPropName) )
4689 const uno::Any aProp = GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
4690 if (aProp.hasValue())
4692 if (xFrame)
4693 xFramePropertySet->setPropertyValue(sPropName, aProp);
4694 else
4695 xShapePropertySet->setPropertyValue(sPropName, aProp);
4699 catch (const uno::Exception&)
4701 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext() text stylesheet property exception" );
4706 catch (const uno::Exception&)
4708 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext()" );
4712 // A GroupShape doesn't implement text::XTextRange, but appending
4713 // an empty reference to the stacks still makes sense, because this
4714 // way bToRemove can be set, and we won't end up with duplicated
4715 // shapes for OLE objects.
4716 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
4717 uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY);
4718 m_aAnchoredStack.push(AnchoredContext(xTxtContent));
4720 else if (xSInfo->supportsService("com.sun.star.drawing.OLE2Shape"))
4722 // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw.
4723 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
4724 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
4725 m_aAnchoredStack.push(AnchoredContext(xTextContent));
4726 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4728 m_xEmbedded.set(m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW);
4729 uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW);
4730 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT)));
4731 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER));
4732 // So that the original bitmap-only shape will be replaced by the embedded object.
4733 m_aAnchoredStack.top().bToRemove = true;
4734 m_aTextAppendStack.pop();
4735 appendTextContent(m_xEmbedded, uno::Sequence<beans::PropertyValue>());
4737 else
4739 uno::Reference<text::XTextRange> xShapeTextRange(xShape, uno::UNO_QUERY_THROW);
4740 // Add the shape to the text append stack
4741 uno::Reference<text::XTextAppend> xShapeTextAppend(xShape, uno::UNO_QUERY_THROW);
4742 uno::Reference<text::XTextCursor> xTextCursor;
4743 if (!m_bIsNewDoc)
4745 xTextCursor = xShapeTextRange->getText()->createTextCursorByRange(
4746 xShapeTextRange->getStart());
4748 TextAppendContext aContext(xShapeTextAppend, xTextCursor);
4749 m_aTextAppendStack.push(aContext);
4751 // Add the shape to the anchored objects stack
4752 uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW );
4753 m_aAnchoredStack.push( AnchoredContext(xTxtContent) );
4755 uno::Reference< beans::XPropertySet > xProps( xShape, uno::UNO_QUERY_THROW );
4756 #ifdef DBG_UTIL
4757 TagLogger::getInstance().unoPropertySet(xProps);
4758 #endif
4759 text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH);
4760 xProps->getPropertyValue(getPropertyName( PROP_ANCHOR_TYPE )) >>= nAnchorType;
4761 bool checkZOrderStatus = false;
4762 if (xSInfo->supportsService("com.sun.star.text.TextFrame"))
4764 SetIsTextFrameInserted(true);
4765 // Extract the special "btLr text frame" mode, requested by oox, if needed.
4766 // Extract vml ZOrder from FrameInteropGrabBag
4767 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4768 uno::Sequence<beans::PropertyValue> aGrabBag;
4769 xShapePropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
4771 for (const auto& rProp : std::as_const(aGrabBag))
4773 if (rProp.Name == "VML-Z-ORDER")
4775 GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper();
4776 sal_Int32 zOrder(0);
4777 rProp.Value >>= zOrder;
4778 xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder)));
4779 pZOrderHelper->addItem(xShapePropertySet, zOrder);
4780 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) );
4781 checkZOrderStatus = true;
4783 else if ( rProp.Name == "TxbxHasLink" )
4785 //Chaining of textboxes will happen in ~DomainMapper_Impl
4786 //i.e when all the textboxes are read and all its attributes
4787 //have been set ( basically the Name/LinkedDisplayName )
4788 //which is set in Graphic Import.
4789 m_vTextFramesForChaining.push_back(xShape);
4793 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY_THROW);
4794 uno::Reference<text::XTextRange> xTextRange(xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
4795 xTextAppend->insertTextContent(xTextRange, xTextContent, false);
4797 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
4798 // we need to re-set this value to xTextContent, then only values are preserved.
4799 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aGrabBag));
4801 else if (nAnchorType == text::TextContentAnchorType_AS_CHARACTER)
4803 // Fix spacing for as-character objects. If the paragraph has CT_Spacing_after set,
4804 // it needs to be set on the object too, as that's what object placement code uses.
4805 PropertyMapPtr paragraphContext = GetTopContextOfType( CONTEXT_PARAGRAPH );
4806 std::optional<PropertyMap::Property> aPropMargin = paragraphContext->getProperty(PROP_PARA_BOTTOM_MARGIN);
4807 if(aPropMargin)
4808 xProps->setPropertyValue( getPropertyName( PROP_BOTTOM_MARGIN ), aPropMargin->second );
4810 else
4812 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
4813 uno::Sequence<beans::PropertyValue> aGrabBag;
4814 xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
4815 for (const auto& rProp : std::as_const(aGrabBag))
4817 if (rProp.Name == "VML-Z-ORDER")
4819 GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper();
4820 sal_Int32 zOrder(0);
4821 rProp.Value >>= zOrder;
4822 xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder)));
4823 pZOrderHelper->addItem(xShapePropertySet, zOrder);
4824 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) );
4825 checkZOrderStatus = true;
4827 else if ( rProp.Name == "TxbxHasLink" )
4829 //Chaining of textboxes will happen in ~DomainMapper_Impl
4830 //i.e when all the textboxes are read and all its attributes
4831 //have been set ( basically the Name/LinkedDisplayName )
4832 //which is set in Graphic Import.
4833 m_vTextFramesForChaining.push_back(xShape);
4837 if(IsSdtEndBefore())
4839 uno::Reference< beans::XPropertySetInfo > xPropSetInfo;
4840 if(xShapePropertySet.is())
4842 xPropSetInfo = xShapePropertySet->getPropertySetInfo();
4843 if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
4845 uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({
4846 { "SdtEndBefore", uno::Any(true) }
4847 }));
4848 xShapePropertySet->setPropertyValue("InteropGrabBag",uno::Any(aShapeGrabBag));
4853 if (!IsInHeaderFooter() && !checkZOrderStatus)
4854 xProps->setPropertyValue(
4855 getPropertyName( PROP_OPAQUE ),
4856 uno::Any( true ) );
4858 m_StreamStateStack.top().bParaChanged = true;
4859 getTableManager().setIsInShape(true);
4861 catch ( const uno::Exception& )
4863 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Exception when adding shape");
4867 * Updating chart height and width after reading the actual values from wp:extent
4869 void DomainMapper_Impl::UpdateEmbeddedShapeProps(const uno::Reference< drawing::XShape > & xShape)
4871 if (!xShape.is())
4872 return;
4874 uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW);
4875 awt::Size aSize = xShape->getSize( );
4876 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_WIDTH), uno::Any(sal_Int32(aSize.Width)));
4877 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::Any(sal_Int32(aSize.Height)));
4878 uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY);
4879 // tdf#130782 copy a11y related properties
4880 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_DESCRIPTION),
4881 xShapeProps->getPropertyValue(getPropertyName(PROP_DESCRIPTION)));
4882 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_TITLE),
4883 xShapeProps->getPropertyValue(getPropertyName(PROP_TITLE)));
4884 uno::Reference<container::XNamed> const xEmbedName(m_xEmbedded, uno::UNO_QUERY);
4885 uno::Reference<container::XNamed> const xShapeName(xShape, uno::UNO_QUERY);
4886 OUString const name(xShapeName->getName());
4887 if (!name.isEmpty()) // setting empty name will throw
4891 xEmbedName->setName(name);
4893 catch (uno::RuntimeException const&)
4895 // ignore - document may contain duplicates (testchartoleobjectembeddings.docx)
4900 void DomainMapper_Impl::PopShapeContext()
4902 if (hasTableManager())
4904 getTableManager().endLevel();
4905 popTableManager();
4907 if ( m_aAnchoredStack.empty() )
4908 return;
4910 // For OLE object replacement shape, the text append context was already removed
4911 // or the OLE object couldn't be inserted.
4912 if ( !m_aAnchoredStack.top().bToRemove )
4914 RemoveLastParagraph();
4915 if (!m_aTextAppendStack.empty())
4916 m_aTextAppendStack.pop();
4919 uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent;
4922 appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() );
4924 catch ( const uno::RuntimeException& )
4926 // this is normal: the shape is already attached
4929 const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW );
4930 // Remove the shape if required (most likely replacement shape for OLE object)
4931 // or anchored to a discarded header or footer
4932 if ( m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter )
4936 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(m_xTextDocument, uno::UNO_QUERY_THROW);
4937 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
4938 if ( xDrawPage.is() )
4939 xDrawPage->remove( xShape );
4941 catch( const uno::Exception& )
4946 // Relative width calculations deferred until section's margins are defined.
4947 // Being cautious: only deferring undefined/minimum-width shapes in order to avoid causing potential regressions
4948 css::awt::Size aShapeSize;
4951 aShapeSize = xShape->getSize();
4953 catch (const css::uno::RuntimeException& e)
4955 // May happen e.g. when text frame has no frame format
4956 // See sw/qa/extras/ooxmlimport/data/n779627.docx
4957 SAL_WARN("writerfilter.dmapper", "getSize failed. " << e.Message);
4959 if( aShapeSize.Width <= 2 )
4961 const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY );
4962 SectionPropertyMap* pSectionContext = GetSectionContext();
4963 if ( pSectionContext && (!hasTableManager() || !getTableManager().isInTable()) &&
4964 xShapePropertySet->getPropertySetInfo()->hasPropertyByName(getPropertyName(PROP_RELATIVE_WIDTH)) )
4966 pSectionContext->addRelativeWidthShape(xShape);
4970 m_aAnchoredStack.pop();
4973 bool DomainMapper_Impl::IsSdtEndBefore()
4975 bool bIsSdtEndBefore = false;
4976 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
4977 if(pContext)
4979 const uno::Sequence< beans::PropertyValue > currentCharProps = pContext->GetPropertyValues();
4980 for (const auto& rCurrentCharProp : currentCharProps)
4982 if (rCurrentCharProp.Name == "CharInteropGrabBag")
4984 uno::Sequence<beans::PropertyValue> aCharGrabBag;
4985 rCurrentCharProp.Value >>= aCharGrabBag;
4986 for (const auto& rProp : std::as_const(aCharGrabBag))
4988 if(rProp.Name == "SdtEndBefore")
4990 rProp.Value >>= bIsSdtEndBefore;
4996 return bIsSdtEndBefore;
4999 bool DomainMapper_Impl::IsDiscardHeaderFooter() const
5001 return m_bDiscardHeaderFooter;
5004 // called from TableManager::closeCell()
5005 void DomainMapper_Impl::ClearPreviousParagraph()
5007 // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering
5008 if ((m_StreamStateStack.top().nTableDepth == (m_nTableCellDepth + 1))
5009 && m_xPreviousParagraph.is()
5010 && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing())
5012 uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
5013 if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() )
5014 m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0)));
5017 m_xPreviousParagraph.clear();
5019 // next table paragraph will be first paragraph in a cell
5020 m_StreamStateStack.top().bFirstParagraphInCell = true;
5023 void DomainMapper_Impl::HandleAltChunk(const OUString& rStreamName)
5027 // Create the import filter.
5028 uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(
5029 comphelper::getProcessServiceFactory());
5030 uno::Reference<uno::XInterface> xDocxFilter
5031 = xMultiServiceFactory->createInstance("com.sun.star.comp.Writer.WriterFilter");
5033 // Set the target document.
5034 uno::Reference<document::XImporter> xImporter(xDocxFilter, uno::UNO_QUERY);
5035 xImporter->setTargetDocument(m_xTextDocument);
5037 // Set the import parameters.
5038 uno::Reference<embed::XHierarchicalStorageAccess> xStorageAccess(m_xDocumentStorage,
5039 uno::UNO_QUERY);
5040 if (!xStorageAccess.is())
5042 return;
5044 // Turn the ZIP stream into a seekable one, as the importer only works with such streams.
5045 uno::Reference<io::XStream> xStream = xStorageAccess->openStreamElementByHierarchicalName(
5046 rStreamName, embed::ElementModes::READ);
5047 std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(xStream, true);
5048 SvMemoryStream aMemory;
5049 aMemory.WriteStream(*pStream);
5050 uno::Reference<io::XStream> xInputStream = new utl::OStreamWrapper(aMemory);
5051 // Not handling AltChunk during paste for now.
5052 uno::Reference<text::XTextRange> xInsertTextRange = GetCurrentXText()->getEnd();
5053 uno::Reference<text::XTextRange> xSectionStartingRange;
5054 SectionPropertyMap* pSectionContext = GetSectionContext();
5055 if (pSectionContext)
5057 xSectionStartingRange = pSectionContext->GetStartingRange();
5059 uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({
5060 { "InputStream", uno::Any(xInputStream) },
5061 { "InsertMode", uno::Any(true) },
5062 { "TextInsertModeRange", uno::Any(xInsertTextRange) },
5063 { "AltChunkMode", uno::Any(true) },
5064 { "AltChunkStartingRange", uno::Any(xSectionStartingRange) },
5065 }));
5067 // Do the actual import.
5068 uno::Reference<document::XFilter> xFilter(xDocxFilter, uno::UNO_QUERY);
5069 xFilter->filter(aDescriptor);
5071 catch (const uno::Exception& rException)
5073 SAL_WARN("writerfilter", "DomainMapper_Impl::HandleAltChunk: failed to handle alt chunk: "
5074 << rException.Message);
5078 void DomainMapper_Impl::HandlePTab(sal_Int32 nAlignment)
5080 // We only handle the case when the line already has content, so the left-aligned ptab is
5081 // equivalent to a line break.
5082 if (nAlignment != NS_ooxml::LN_Value_ST_PTabAlignment_left)
5084 return;
5087 if (m_aTextAppendStack.empty())
5089 return;
5092 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
5093 if (!xTextAppend.is())
5095 return;
5098 uno::Reference<css::text::XTextRange> xInsertPosition
5099 = m_aTextAppendStack.top().xInsertPosition;
5100 if (!xInsertPosition.is())
5102 xInsertPosition = xTextAppend->getEnd();
5104 uno::Reference<text::XTextCursor> xCursor
5105 = xTextAppend->createTextCursorByRange(xInsertPosition);
5107 // Assume that we just inserted a tab character.
5108 xCursor->goLeft(1, true);
5109 if (xCursor->getString() != "\t")
5111 return;
5114 // Assume that there is some content before the tab character.
5115 uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY);
5116 if (!xParagraphCursor.is())
5118 return;
5121 xCursor->collapseToStart();
5122 xParagraphCursor->gotoStartOfParagraph(true);
5123 if (xCursor->isCollapsed())
5125 return;
5128 // Then select the tab again and replace with a line break.
5129 xCursor->collapseToEnd();
5130 xCursor->goRight(1, true);
5131 xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::LINE_BREAK, true);
5134 void DomainMapper_Impl::HandleLineBreakClear(sal_Int32 nClear)
5136 switch (nClear)
5138 case NS_ooxml::LN_Value_ST_BrClear_left:
5139 // SwLineBreakClear::LEFT
5140 m_oLineBreakClear = 1;
5141 break;
5142 case NS_ooxml::LN_Value_ST_BrClear_right:
5143 // SwLineBreakClear::RIGHT
5144 m_oLineBreakClear = 2;
5145 break;
5146 case NS_ooxml::LN_Value_ST_BrClear_all:
5147 // SwLineBreakClear::ALL
5148 m_oLineBreakClear = 3;
5149 break;
5153 void DomainMapper_Impl::HandleLineBreak(const PropertyMapPtr& pPropertyMap)
5155 if (!m_oLineBreakClear.has_value())
5157 appendTextPortion("\n", pPropertyMap);
5158 return;
5161 if (GetTextFactory().is())
5163 uno::Reference<text::XTextContent> xLineBreak(
5164 GetTextFactory()->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
5165 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
5166 xLineBreakProps->setPropertyValue("Clear", uno::Any(*m_oLineBreakClear));
5167 appendTextContent(xLineBreak, pPropertyMap->GetPropertyValues());
5169 m_oLineBreakClear.reset();
5172 static sal_Int16 lcl_ParseNumberingType( std::u16string_view rCommand )
5174 sal_Int16 nRet = style::NumberingType::PAGE_DESCRIPTOR;
5176 // The command looks like: " PAGE \* Arabic "
5177 // tdf#132185: but may as well be "PAGE \* Arabic"
5178 OUString sNumber;
5179 constexpr OUString rSeparator(u"\\* "_ustr);
5180 if (size_t nStartIndex = rCommand.find(rSeparator); nStartIndex != std::u16string_view::npos)
5182 sal_Int32 nStartIndex2 = nStartIndex + rSeparator.getLength();
5183 sNumber = o3tl::getToken(rCommand, 0, ' ', nStartIndex2);
5186 if( !sNumber.isEmpty() )
5188 //todo: might make sense to hash this list, too
5189 struct NumberingPairs
5191 const char* cWordName;
5192 sal_Int16 nType;
5194 static const NumberingPairs aNumberingPairs[] =
5196 {"Arabic", style::NumberingType::ARABIC}
5197 ,{"ROMAN", style::NumberingType::ROMAN_UPPER}
5198 ,{"roman", style::NumberingType::ROMAN_LOWER}
5199 ,{"ALPHABETIC", style::NumberingType::CHARS_UPPER_LETTER}
5200 ,{"alphabetic", style::NumberingType::CHARS_LOWER_LETTER}
5201 ,{"CircleNum", style::NumberingType::CIRCLE_NUMBER}
5202 ,{"ThaiArabic", style::NumberingType::CHARS_THAI}
5203 ,{"ThaiCardText", style::NumberingType::CHARS_THAI}
5204 ,{"ThaiLetter", style::NumberingType::CHARS_THAI}
5205 // ,{"SBCHAR", style::NumberingType::}
5206 // ,{"DBCHAR", style::NumberingType::}
5207 // ,{"DBNUM1", style::NumberingType::}
5208 // ,{"DBNUM2", style::NumberingType::}
5209 // ,{"DBNUM3", style::NumberingType::}
5210 // ,{"DBNUM4", style::NumberingType::}
5211 ,{"Aiueo", style::NumberingType::AIU_FULLWIDTH_JA}
5212 ,{"Iroha", style::NumberingType::IROHA_FULLWIDTH_JA}
5213 // ,{"ZODIAC1", style::NumberingType::}
5214 // ,{"ZODIAC2", style::NumberingType::}
5215 // ,{"ZODIAC3", style::NumberingType::}
5216 // ,{"CHINESENUM1", style::NumberingType::}
5217 // ,{"CHINESENUM2", style::NumberingType::}
5218 // ,{"CHINESENUM3", style::NumberingType::}
5219 ,{"ArabicAlpha", style::NumberingType::CHARS_ARABIC}
5220 ,{"ArabicAbjad", style::NumberingType::FULLWIDTH_ARABIC}
5221 ,{"Ganada", style::NumberingType::HANGUL_JAMO_KO}
5222 ,{"Chosung", style::NumberingType::HANGUL_SYLLABLE_KO}
5223 ,{"KoreanCounting", style::NumberingType::NUMBER_HANGUL_KO}
5224 ,{"KoreanLegal", style::NumberingType::NUMBER_LEGAL_KO}
5225 ,{"KoreanDigital", style::NumberingType::NUMBER_DIGITAL_KO}
5226 ,{"KoreanDigital2", style::NumberingType::NUMBER_DIGITAL2_KO}
5227 /* possible values:
5228 style::NumberingType::
5230 CHARS_UPPER_LETTER_N
5231 CHARS_LOWER_LETTER_N
5232 TRANSLITERATION
5233 NATIVE_NUMBERING
5234 CIRCLE_NUMBER
5235 NUMBER_LOWER_ZH
5236 NUMBER_UPPER_ZH
5237 NUMBER_UPPER_ZH_TW
5238 TIAN_GAN_ZH
5239 DI_ZI_ZH
5240 NUMBER_TRADITIONAL_JA
5241 AIU_HALFWIDTH_JA
5242 IROHA_HALFWIDTH_JA
5243 NUMBER_UPPER_KO
5244 NUMBER_HANGUL_KO
5245 HANGUL_JAMO_KO
5246 HANGUL_SYLLABLE_KO
5247 HANGUL_CIRCLED_JAMO_KO
5248 HANGUL_CIRCLED_SYLLABLE_KO
5249 CHARS_HEBREW
5250 CHARS_NEPALI
5251 CHARS_KHMER
5252 CHARS_LAO
5253 CHARS_TIBETAN
5254 CHARS_CYRILLIC_UPPER_LETTER_BG
5255 CHARS_CYRILLIC_LOWER_LETTER_BG
5256 CHARS_CYRILLIC_UPPER_LETTER_N_BG
5257 CHARS_CYRILLIC_LOWER_LETTER_N_BG
5258 CHARS_CYRILLIC_UPPER_LETTER_RU
5259 CHARS_CYRILLIC_LOWER_LETTER_RU
5260 CHARS_CYRILLIC_UPPER_LETTER_N_RU
5261 CHARS_CYRILLIC_LOWER_LETTER_N_RU
5262 CHARS_CYRILLIC_UPPER_LETTER_SR
5263 CHARS_CYRILLIC_LOWER_LETTER_SR
5264 CHARS_CYRILLIC_UPPER_LETTER_N_SR
5265 CHARS_CYRILLIC_LOWER_LETTER_N_SR
5266 CHARS_CYRILLIC_UPPER_LETTER_UK
5267 CHARS_CYRILLIC_LOWER_LETTER_UK
5268 CHARS_CYRILLIC_UPPER_LETTER_N_UK
5269 CHARS_CYRILLIC_LOWER_LETTER_N_UK*/
5272 for(const NumberingPairs& rNumberingPair : aNumberingPairs)
5274 if( /*sCommand*/sNumber.equalsAscii(rNumberingPair.cWordName ))
5276 nRet = rNumberingPair.nType;
5277 break;
5282 return nRet;
5286 static OUString lcl_ParseFormat( const OUString& rCommand )
5288 // The command looks like: " DATE \@"dd MMMM yyyy" or "09/02/2014"
5289 OUString command;
5290 sal_Int32 delimPos = rCommand.indexOf("\\@");
5291 if (delimPos != -1)
5293 // Remove whitespace permitted by standard between \@ and "
5294 const sal_Int32 nQuoteIndex = rCommand.indexOf('\"');
5295 if (nQuoteIndex != -1)
5297 sal_Int32 wsChars = nQuoteIndex - delimPos - 2;
5298 command = rCommand.replaceAt(delimPos+2, wsChars, u"");
5300 else
5302 // turn date \@ MM into date \@"MM"
5303 command = OUString::Concat(rCommand.subView(0, delimPos + 2)) + "\"" + o3tl::trim(rCommand.subView(delimPos + 2)) + "\"";
5305 return OUString(msfilter::util::findQuotedText(command, u"\\@\"", '\"'));
5308 return OUString();
5310 /*-------------------------------------------------------------------------
5311 extract a parameter (with or without quotes) between the command and the following backslash
5312 -----------------------------------------------------------------------*/
5313 static OUString lcl_ExtractToken(std::u16string_view rCommand,
5314 size_t & rIndex, bool & rHaveToken, bool & rIsSwitch)
5316 rHaveToken = false;
5317 rIsSwitch = false;
5319 OUStringBuffer token;
5320 bool bQuoted(false);
5321 for (; rIndex < rCommand.size(); ++rIndex)
5323 sal_Unicode const currentChar(rCommand[rIndex]);
5324 switch (currentChar)
5326 case '\\':
5328 if (rIndex == rCommand.size() - 1)
5330 SAL_INFO("writerfilter.dmapper", "field: trailing escape");
5331 ++rIndex;
5332 return OUString();
5334 sal_Unicode const nextChar(rCommand[rIndex+1]);
5335 if (bQuoted || '\\' == nextChar)
5337 ++rIndex; // read 2 chars
5338 token.append(nextChar);
5340 else // field switch (case insensitive)
5342 rHaveToken = true;
5343 if (token.isEmpty())
5345 rIsSwitch = true;
5346 rIndex += 2; // read 2 chars
5347 return OUString(rCommand.substr(rIndex - 2, 2)).toAsciiUpperCase();
5349 else
5350 { // leave rIndex, read it again next time
5351 return token.makeStringAndClear();
5355 break;
5356 case '\"':
5357 if (bQuoted || !token.isEmpty())
5359 rHaveToken = true;
5360 if (bQuoted)
5362 ++rIndex;
5364 return token.makeStringAndClear();
5366 else
5368 bQuoted = true;
5370 break;
5371 case ' ':
5372 if (bQuoted)
5374 token.append(' ');
5376 else
5378 if (!token.isEmpty())
5380 rHaveToken = true;
5381 ++rIndex;
5382 return token.makeStringAndClear();
5385 break;
5386 case '=':
5387 if (token.isEmpty())
5389 rHaveToken = true;
5390 ++rIndex;
5391 return "FORMULA";
5393 else
5394 token.append('=');
5395 break;
5396 default:
5397 token.append(currentChar);
5398 break;
5401 assert(rIndex == rCommand.size());
5402 if (bQuoted)
5404 // MS Word allows this, so just emit a debug message
5405 SAL_INFO("writerfilter.dmapper",
5406 "field argument with unterminated quote");
5408 rHaveToken = !token.isEmpty();
5409 return token.makeStringAndClear();
5412 std::tuple<OUString, std::vector<OUString>, std::vector<OUString> > splitFieldCommand(std::u16string_view rCommand)
5414 OUString sType;
5415 std::vector<OUString> arguments;
5416 std::vector<OUString> switches;
5417 size_t nStartIndex(0);
5418 // tdf#54584: Field may be prepended by a backslash
5419 // This is not an escapement, but already escaped literal "\"
5420 // MS Word allows this, so just skip it
5421 if ((rCommand.size() >= nStartIndex + 2) &&
5422 (rCommand[nStartIndex] == L'\\') &&
5423 (rCommand[nStartIndex + 1] != L'\\') &&
5424 (rCommand[nStartIndex + 1] != L' '))
5426 ++nStartIndex;
5431 bool bHaveToken;
5432 bool bIsSwitch;
5433 OUString const token =
5434 lcl_ExtractToken(rCommand, nStartIndex, bHaveToken, bIsSwitch);
5435 assert(nStartIndex <= rCommand.size());
5436 static std::map<OUString, std::set<OUString>> const noArgumentSwitches = {
5437 { u"STYLEREF"_ustr,
5438 { u"\\l"_ustr, u"\\n"_ustr, u"\\p"_ustr, u"\\r"_ustr, u"\\t"_ustr, u"\\w"_ustr } }
5440 if (bHaveToken)
5442 if (sType.isEmpty())
5444 sType = token.toAsciiUpperCase();
5446 else if (bIsSwitch)
5448 switches.push_back(token);
5450 // evidently Word evaluates 'STYLEREF \t "Heading 1" \* MERGEFORMAT'
5451 // despite the grammar specifying that the style name must
5452 // precede switches like '\t'; try to approximate that here
5453 // by checking for known switches that don't expect arguments
5454 else if (auto const it = noArgumentSwitches.find(sType);
5455 !switches.empty() && (it == noArgumentSwitches.end()
5456 || it->second.find(switches.back().toAsciiLowerCase()) == it->second.end()))
5458 switches.push_back(token);
5460 else
5462 arguments.push_back(token);
5465 } while (nStartIndex < rCommand.size());
5467 return std::make_tuple(sType, arguments, switches);
5470 static OUString lcl_ExtractVariableAndHint( std::u16string_view rCommand, OUString& rHint )
5472 // the first word after "ASK " is the variable
5473 // the text after the variable and before a '\' is the hint
5474 // if no hint is set the variable is used as hint
5475 // the quotes of the hint have to be removed
5476 size_t nIndex = rCommand.find( ' ', 2); //find last space after 'ASK'
5477 if (nIndex == std::u16string_view::npos)
5478 return OUString();
5479 while (nIndex < rCommand.size() && rCommand[nIndex] == ' ')
5480 ++nIndex;
5481 std::u16string_view sShortCommand( rCommand.substr( nIndex ) ); //cut off the " ASK "
5483 sShortCommand = o3tl::getToken(sShortCommand, 0, '\\');
5484 sal_Int32 nIndex2 = 0;
5485 std::u16string_view sRet = o3tl::getToken(sShortCommand, 0, ' ', nIndex2);
5486 if( nIndex2 > 0)
5487 rHint = sShortCommand.substr( nIndex2 );
5488 if( rHint.isEmpty() )
5489 rHint = sRet;
5490 return OUString(sRet);
5493 static size_t nextCode(std::u16string_view rCommand, size_t pos)
5495 bool inQuotes = false;
5496 for (; pos < rCommand.size(); ++pos)
5498 switch (rCommand[pos])
5500 case '"':
5501 inQuotes = !inQuotes;
5502 break;
5503 case '\\':
5504 ++pos;
5505 if (!inQuotes)
5506 return pos;
5507 break;
5510 return std::u16string_view::npos;
5513 // Returns the position of the field code
5514 static size_t findCode(std::u16string_view rCommand, sal_Unicode cSwitch)
5516 for (size_t i = nextCode(rCommand, 0); i < rCommand.size(); i = nextCode(rCommand, i))
5517 if (rCommand[i] == cSwitch)
5518 return i;
5520 return std::u16string_view::npos;
5523 static bool lcl_FindInCommand(
5524 std::u16string_view rCommand,
5525 sal_Unicode cSwitch,
5526 OUString& rValue )
5528 if (size_t i = findCode(rCommand, cSwitch); i < rCommand.size())
5530 ++i;
5531 size_t next = nextCode(rCommand, i);
5532 if (next < rCommand.size())
5533 --next; // get back before the next '\\'
5534 rValue = o3tl::trim(rCommand.substr(i, next - i));
5535 return true;
5538 return false;
5541 static OUString lcl_trim(std::u16string_view sValue)
5543 // it seems, all kind of quotation marks are allowed around index type identifiers
5544 // TODO apply this on bookmarks, too, if needed
5545 return OUString(o3tl::trim(sValue)).replaceAll("\"","").replaceAll(u"“", "").replaceAll(u"”", "");
5548 /*-------------------------------------------------------------------------
5549 extract the number format from the command and apply the resulting number
5550 format to the XPropertySet
5551 -----------------------------------------------------------------------*/
5552 void DomainMapper_Impl::SetNumberFormat( const OUString& rCommand,
5553 uno::Reference< beans::XPropertySet > const& xPropertySet,
5554 bool const bDetectFormat)
5556 OUString sFormatString = lcl_ParseFormat( rCommand );
5557 // find \h - hijri/luna calendar todo: what about saka/era calendar?
5558 bool bHijri = 0 < rCommand.indexOf("\\h ");
5559 lang::Locale aUSLocale;
5560 aUSLocale.Language = "en";
5561 aUSLocale.Country = "US";
5563 lang::Locale aCurrentLocale;
5564 GetAnyProperty(PROP_CHAR_LOCALE, GetTopContext()) >>= aCurrentLocale;
5566 if (sFormatString.isEmpty())
5568 // No format specified. MS Word uses different formats depending on w:lang,
5569 // "M/d/yyyy h:mm:ss AM/PM" for en-US, and "dd/MM/yyyy hh:mm:ss AM/PM" for en-GB.
5570 // ALSO SEE: ww8par5's GetWordDefaultDateStringAsUS.
5571 sal_Int32 nPos = rCommand.indexOf(" \\");
5572 OUString sCommand = nPos == -1 ? rCommand.trim()
5573 : OUString(o3tl::trim(rCommand.subView(0, nPos)));
5574 if (sCommand == "CREATEDATE" || sCommand == "PRINTDATE" || sCommand == "SAVEDATE")
5578 css::uno::Reference<css::i18n::XNumberFormatCode> const& xNumberFormatCode =
5579 i18n::NumberFormatMapper::create(m_xComponentContext);
5580 sFormatString = xNumberFormatCode->getFormatCode(
5581 css::i18n::NumberFormatIndex::DATE_SYSTEM_SHORT, aCurrentLocale).Code;
5582 nPos = sFormatString.indexOf("YYYY");
5583 if (nPos == -1)
5584 sFormatString = sFormatString.replaceFirst("YY", "YYYY");
5585 if (aCurrentLocale == aUSLocale)
5586 sFormatString += " h:mm:ss AM/PM";
5587 else
5588 sFormatString += " hh:mm:ss AM/PM";
5590 catch(const uno::Exception&)
5592 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
5596 OUString sFormat = ConversionHelper::ConvertMSFormatStringToSO( sFormatString, aCurrentLocale, bHijri);
5597 //get the number formatter and convert the string to a format value
5600 sal_Int32 nKey = 0;
5601 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW );
5602 if( bDetectFormat )
5604 uno::Reference< util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
5605 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
5606 nKey = xFormatter->detectNumberFormat( 0, rCommand );
5608 else
5610 nKey = xNumberSupplier->getNumberFormats()->addNewConverted( sFormat, aUSLocale, aCurrentLocale );
5612 xPropertySet->setPropertyValue(
5613 getPropertyName(PROP_NUMBER_FORMAT),
5614 uno::Any( nKey ));
5616 catch(const uno::Exception&)
5621 static uno::Any lcl_getGrabBagValue( const uno::Sequence<beans::PropertyValue>& grabBag, OUString const & name )
5623 auto pProp = std::find_if(grabBag.begin(), grabBag.end(),
5624 [&name](const beans::PropertyValue& rProp) { return rProp.Name == name; });
5625 if (pProp != grabBag.end())
5626 return pProp->Value;
5627 return uno::Any();
5630 //Link the text frames.
5631 void DomainMapper_Impl::ChainTextFrames()
5633 //can't link textboxes if there are not even two of them...
5634 if( 2 > m_vTextFramesForChaining.size() )
5635 return ;
5637 struct TextFramesForChaining {
5638 css::uno::Reference< css::drawing::XShape > xShape;
5639 sal_Int32 nId;
5640 sal_Int32 nSeq;
5641 OUString s_mso_next_textbox;
5642 OUString shapeName;
5643 TextFramesForChaining() : nId(0), nSeq(0) {}
5645 typedef std::map <OUString, TextFramesForChaining> ChainMap;
5649 ChainMap aTextFramesForChainingHelper;
5650 ::std::vector<TextFramesForChaining> chainingWPS;
5651 OUString sChainNextName("ChainNextName");
5653 //learn about ALL of the textboxes and their chaining values first - because frames are processed in no specific order.
5654 for( const auto& rTextFrame : m_vTextFramesForChaining )
5656 uno::Reference<text::XTextContent> xTextContent(rTextFrame, uno::UNO_QUERY_THROW);
5657 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5658 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo;
5659 if( xPropertySet.is() )
5660 xPropertySetInfo = xPropertySet->getPropertySetInfo();
5661 uno::Sequence<beans::PropertyValue> aGrabBag;
5662 uno::Reference<lang::XServiceInfo> xServiceInfo(xPropertySet, uno::UNO_QUERY);
5664 TextFramesForChaining aChainStruct;
5665 OUString sShapeName;
5666 OUString sLinkChainName;
5668 //The chaining name and the shape name CAN be different in .docx.
5669 //MUST use LinkDisplayName/ChainName as the shape name for establishing links.
5670 if ( xServiceInfo->supportsService("com.sun.star.text.TextFrame") )
5672 xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
5673 xPropertySet->getPropertyValue("LinkDisplayName") >>= sShapeName;
5675 else
5677 xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
5678 xPropertySet->getPropertyValue("ChainName") >>= sShapeName;
5681 lcl_getGrabBagValue( aGrabBag, "Txbx-Id") >>= aChainStruct.nId;
5682 lcl_getGrabBagValue( aGrabBag, "Txbx-Seq") >>= aChainStruct.nSeq;
5683 lcl_getGrabBagValue( aGrabBag, "LinkChainName") >>= sLinkChainName;
5684 lcl_getGrabBagValue( aGrabBag, "mso-next-textbox") >>= aChainStruct.s_mso_next_textbox;
5686 //Sometimes the shape names have not been imported. If not, we may have a fallback name.
5687 //Set name later, only if required for linking.
5688 aChainStruct.shapeName = sShapeName;
5690 if (!sLinkChainName.isEmpty())
5692 aChainStruct.xShape = rTextFrame;
5693 aTextFramesForChainingHelper[sLinkChainName] = aChainStruct;
5695 if (aChainStruct.s_mso_next_textbox.isEmpty())
5696 { // no VML chaining => try to chain DrawingML via IDs
5697 aChainStruct.xShape = rTextFrame;
5698 if (!sLinkChainName.isEmpty())
5699 { // for member of group shapes, TestTdf73499
5700 aChainStruct.shapeName = sLinkChainName;
5702 chainingWPS.emplace_back(aChainStruct);
5706 //if mso-next-textbox tags are provided, create those vml-style links first. Afterwards we will make dml-style id/seq links.
5707 for (auto& msoItem : aTextFramesForChainingHelper)
5709 //if no mso-next-textbox, we are done.
5710 //if it points to itself, we are done.
5711 if( !msoItem.second.s_mso_next_textbox.isEmpty()
5712 && msoItem.second.s_mso_next_textbox != msoItem.first )
5714 ChainMap::iterator nextFinder=aTextFramesForChainingHelper.find(msoItem.second.s_mso_next_textbox);
5715 if( nextFinder != aTextFramesForChainingHelper.end() )
5717 //if the frames have no name yet, then set them. LinkDisplayName / ChainName are read-only.
5718 if (msoItem.second.shapeName.isEmpty())
5720 uno::Reference< container::XNamed > xNamed( msoItem.second.xShape, uno::UNO_QUERY );
5721 if ( xNamed.is() )
5723 xNamed->setName( msoItem.first );
5724 msoItem.second.shapeName = msoItem.first;
5727 if (nextFinder->second.shapeName.isEmpty())
5729 uno::Reference< container::XNamed > xNamed( nextFinder->second.xShape, uno::UNO_QUERY );
5730 if ( xNamed.is() )
5732 xNamed->setName( nextFinder->first );
5733 nextFinder->second.shapeName = msoItem.first;
5737 uno::Reference<text::XTextContent> xTextContent(msoItem.second.xShape, uno::UNO_QUERY_THROW);
5738 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5740 //The reverse chaining happens automatically, so only one direction needs to be set
5741 xPropertySet->setPropertyValue(sChainNextName, uno::Any(nextFinder->second.shapeName));
5743 //the last item in an mso-next-textbox chain is indistinguishable from id/seq items. Now that it is handled, remove it.
5744 if( nextFinder->second.s_mso_next_textbox.isEmpty() )
5745 aTextFramesForChainingHelper.erase(nextFinder->first);
5750 //TODO: Perhaps allow reverse sequences when mso-layout-flow-alt = "bottom-to-top"
5751 const sal_Int32 nDirection = 1;
5753 //Finally - go through and attach the chains based on matching ID and incremented sequence number (dml-style).
5754 for (const auto& rOuter : chainingWPS)
5756 for (const auto& rInner : chainingWPS)
5758 if (rInner.nId == rOuter.nId)
5760 if (rInner.nSeq == (rOuter.nSeq + nDirection))
5762 uno::Reference<text::XTextContent> const xTextContent(rOuter.xShape, uno::UNO_QUERY_THROW);
5763 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
5765 //The reverse chaining happens automatically, so only one direction needs to be set
5766 xPropertySet->setPropertyValue(sChainNextName, uno::Any(rInner.shapeName));
5767 break ; //there cannot be more than one next frame
5772 m_vTextFramesForChaining.clear(); //clear the vector
5774 catch (const uno::Exception&)
5776 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
5780 void DomainMapper_Impl::PushTextBoxContent()
5782 if (m_bIsInTextBox)
5783 return;
5787 uno::Reference<text::XTextFrame> xTBoxFrame(
5788 m_xTextFactory->createInstance("com.sun.star.text.TextFrame"), uno::UNO_QUERY_THROW);
5789 uno::Reference<container::XNamed>(xTBoxFrame, uno::UNO_QUERY_THROW)
5790 ->setName("textbox" + OUString::number(m_xPendingTextBoxFrames.size() + 1));
5791 uno::Reference<text::XTextAppendAndConvert>(m_aTextAppendStack.top().xTextAppend,
5792 uno::UNO_QUERY_THROW)
5793 ->appendTextContent(xTBoxFrame, beans::PropertyValues());
5794 m_xPendingTextBoxFrames.push(xTBoxFrame);
5796 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xTBoxFrame, uno::UNO_QUERY_THROW), {}));
5797 m_bIsInTextBox = true;
5799 appendTableManager();
5800 appendTableHandler();
5801 getTableManager().startLevel();
5803 catch (uno::Exception& e)
5805 SAL_WARN("writerfilter.dmapper", "Exception during creating textbox (" + e.Message + ")!");
5809 void DomainMapper_Impl::PopTextBoxContent()
5811 if (!m_bIsInTextBox || m_xPendingTextBoxFrames.empty())
5812 return;
5814 if (uno::Reference<text::XTextFrame>(m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY).is())
5816 if (hasTableManager())
5818 getTableManager().endLevel();
5819 popTableManager();
5821 RemoveLastParagraph();
5823 m_aTextAppendStack.pop();
5824 m_bIsInTextBox = false;
5828 void DomainMapper_Impl::AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape)
5830 // Without textbox or shape pointless to continue
5831 if (m_xPendingTextBoxFrames.empty() || !xShape)
5832 return;
5834 uno::Reference< drawing::XShapes >xGroup(xShape, uno::UNO_QUERY);
5835 uno::Reference< beans::XPropertySet >xProps(xShape, uno::UNO_QUERY);
5837 // If this is a group go inside
5838 if (xGroup)
5839 for (sal_Int32 i = 0; i < xGroup->getCount(); ++i)
5840 AttachTextBoxContentToShape(
5841 uno::Reference<drawing::XShape>(xGroup->getByIndex(i), uno::UNO_QUERY));
5843 // if this shape has to be a textbox, attach the frame
5844 if (!xProps->getPropertyValue("TextBox").get<bool>())
5845 return;
5847 // if this is a textbox there must be a waiting frame
5848 auto xTextBox = m_xPendingTextBoxFrames.front();
5849 if (!xTextBox)
5850 return;
5852 // Pop the pending frames
5853 m_xPendingTextBoxFrames.pop();
5855 // Attach the textbox to the shape
5858 xProps->setPropertyValue("TextBoxContent", uno::Any(xTextBox));
5860 catch (...)
5862 SAL_WARN("writerfilter.dmapper", "Exception while trying to attach textboxes!");
5863 return;
5866 // If attaching is successful, then do the linking
5869 // Get the name of the textbox
5870 OUString sTextBoxName;
5871 uno::Reference<container::XNamed> xName(xTextBox, uno::UNO_QUERY);
5872 if (xName && !xName->getName().isEmpty())
5873 sTextBoxName = xName->getName();
5875 // Try to get the grabbag
5876 uno::Sequence<beans::PropertyValue> aOldGrabBagSeq;
5877 if (xProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
5878 xProps->getPropertyValue("InteropGrabBag") >>= aOldGrabBagSeq;
5880 // If the grabbag successfully get...
5881 if (!aOldGrabBagSeq.hasElements())
5882 return;
5884 // Check for the existing linking information
5885 bool bSuccess = false;
5886 beans::PropertyValues aNewGrabBagSeq;
5887 const auto& aHasLink = lcl_getGrabBagValue(aOldGrabBagSeq, "TxbxHasLink");
5889 // If there must be a link, do it
5890 if (aHasLink.hasValue() && aHasLink.get<bool>())
5892 auto aLinkProp = comphelper::makePropertyValue("LinkChainName", sTextBoxName);
5893 for (sal_uInt32 i = 0; i < aOldGrabBagSeq.size(); ++i)
5895 aNewGrabBagSeq.realloc(i + 1);
5896 // If this is the link name replace it
5897 if (!aOldGrabBagSeq[i].Name.isEmpty() && !aLinkProp.Name.isEmpty()
5898 && (aOldGrabBagSeq[i].Name == aLinkProp.Name))
5900 aNewGrabBagSeq.getArray()[i] = aLinkProp;
5901 bSuccess = true;
5903 // else copy
5904 else
5905 aNewGrabBagSeq.getArray()[i] = aOldGrabBagSeq[i];
5908 // If there was no replacement, append the linking data
5909 if (!bSuccess)
5911 aNewGrabBagSeq.realloc(aNewGrabBagSeq.size() + 1);
5912 aNewGrabBagSeq.getArray()[aNewGrabBagSeq.size() - 1] = aLinkProp;
5913 bSuccess = true;
5917 // If the linking changed the grabbag, apply the modifications
5918 if (aNewGrabBagSeq.hasElements() && bSuccess)
5920 xProps->setPropertyValue("InteropGrabBag", uno::Any(aNewGrabBagSeq));
5921 m_vTextFramesForChaining.push_back(xShape);
5924 catch (...)
5926 SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!");
5930 uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName)
5932 // query master, create if not available
5933 uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY_THROW );
5934 uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters();
5935 uno::Reference< beans::XPropertySet > xMaster;
5936 OUString sFieldMasterService( OUString::createFromAscii(pFieldMasterService) );
5937 OUStringBuffer aFieldMasterName;
5938 OUString sDatabaseDataSourceName = GetSettingsTable()->GetCurrentDatabaseDataSource();
5939 bool bIsMergeField = sFieldMasterService.endsWith("Database");
5940 aFieldMasterName.appendAscii( pFieldMasterService );
5941 aFieldMasterName.append('.');
5942 if ( bIsMergeField && !sDatabaseDataSourceName.isEmpty() )
5944 aFieldMasterName.append(sDatabaseDataSourceName + ".");
5946 aFieldMasterName.append(rFieldMasterName);
5947 OUString sFieldMasterName = aFieldMasterName.makeStringAndClear();
5948 if(xFieldMasterAccess->hasByName(sFieldMasterName))
5950 //get the master
5951 xMaster.set(xFieldMasterAccess->getByName(sFieldMasterName), uno::UNO_QUERY_THROW);
5953 else if( m_xTextFactory.is() )
5955 //create the master
5956 xMaster.set( m_xTextFactory->createInstance(sFieldMasterService), uno::UNO_QUERY_THROW);
5957 if ( !bIsMergeField || sDatabaseDataSourceName.isEmpty() )
5959 //set the master's name
5960 xMaster->setPropertyValue(
5961 getPropertyName(PROP_NAME),
5962 uno::Any(rFieldMasterName));
5963 } else {
5964 // set database data, based on the "databasename.tablename" of sDatabaseDataSourceName
5965 xMaster->setPropertyValue(
5966 getPropertyName(PROP_DATABASE_NAME),
5967 uno::Any(sDatabaseDataSourceName.copy(0, sDatabaseDataSourceName.indexOf('.'))));
5968 xMaster->setPropertyValue(
5969 getPropertyName(PROP_COMMAND_TYPE),
5970 uno::Any(sal_Int32(0)));
5971 xMaster->setPropertyValue(
5972 getPropertyName(PROP_DATATABLE_NAME),
5973 uno::Any(sDatabaseDataSourceName.copy(sDatabaseDataSourceName.indexOf('.') + 1)));
5974 xMaster->setPropertyValue(
5975 getPropertyName(PROP_DATACOLUMN_NAME),
5976 uno::Any(rFieldMasterName));
5979 return xMaster;
5982 void DomainMapper_Impl::PushFieldContext()
5984 m_StreamStateStack.top().bParaHadField = true;
5985 if(m_bDiscardHeaderFooter)
5986 return;
5987 #ifdef DBG_UTIL
5988 TagLogger::getInstance().element("pushFieldContext");
5989 #endif
5991 uno::Reference<text::XTextCursor> xCrsr;
5992 if (!m_aTextAppendStack.empty())
5994 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
5995 if (xTextAppend.is())
5996 xCrsr = xTextAppend->createTextCursorByRange(
5997 m_aTextAppendStack.top().xInsertPosition.is()
5998 ? m_aTextAppendStack.top().xInsertPosition
5999 : xTextAppend->getEnd());
6002 uno::Reference< text::XTextRange > xStart;
6003 if (xCrsr.is())
6004 xStart = xCrsr->getStart();
6005 m_aFieldStack.push_back(new FieldContext(xStart));
6007 /*-------------------------------------------------------------------------
6008 //the current field context waits for the completion of the command
6009 -----------------------------------------------------------------------*/
6010 bool DomainMapper_Impl::IsOpenFieldCommand() const
6012 return !m_aFieldStack.empty() && !m_aFieldStack.back()->IsCommandCompleted();
6014 /*-------------------------------------------------------------------------
6015 //the current field context waits for the completion of the command
6016 -----------------------------------------------------------------------*/
6017 bool DomainMapper_Impl::IsOpenField() const
6019 return !m_aFieldStack.empty();
6022 // Mark top field context as containing a fixed field
6023 void DomainMapper_Impl::SetFieldLocked()
6025 if (IsOpenField())
6026 m_aFieldStack.back()->SetFieldLocked();
6030 FieldContext::FieldContext(uno::Reference< text::XTextRange > xStart)
6031 : m_bFieldCommandCompleted(false)
6032 , m_xStartRange(std::move( xStart ))
6033 , m_bFieldLocked( false )
6034 , m_bCommandType(false)
6036 m_pProperties = new PropertyMap();
6040 FieldContext::~FieldContext()
6044 void FieldContext::SetTextField(uno::Reference<text::XTextField> const& xTextField)
6046 #ifndef NDEBUG
6047 if (xTextField.is())
6049 uno::Reference<lang::XServiceInfo> const xServiceInfo(xTextField, uno::UNO_QUERY);
6050 assert(xServiceInfo.is());
6051 // those must be set by SetFormField()
6052 assert(!xServiceInfo->supportsService("com.sun.star.text.Fieldmark")
6053 && !xServiceInfo->supportsService("com.sun.star.text.FormFieldmark"));
6055 #endif
6056 m_xTextField = xTextField;
6059 void FieldContext::CacheVariableValue(const uno::Any& rAny)
6061 rAny >>= m_sVariableValue;
6064 void FieldContext::AppendCommand(std::u16string_view rPart)
6066 m_sCommand[m_bCommandType] += rPart;
6069 ::std::vector<OUString> FieldContext::GetCommandParts() const
6071 ::std::vector<OUString> aResult;
6072 sal_Int32 nIndex = 0;
6073 bool bInString = false;
6074 OUString sPart;
6075 while (nIndex != -1)
6077 OUString sToken = GetCommand().getToken(0, ' ', nIndex);
6078 bool bInStringNext = bInString;
6080 if (sToken.isEmpty())
6081 continue;
6083 if (sToken[0] == '"')
6085 bInStringNext = true;
6086 sToken = sToken.copy(1);
6088 if (sToken.endsWith("\""))
6090 bInStringNext = false;
6091 sToken = sToken.copy(0, sToken.getLength() - 1);
6094 if (bInString)
6096 sPart += " " + sToken;
6097 if (!bInStringNext)
6099 aResult.push_back(sPart);
6102 else
6104 if (bInStringNext)
6106 sPart = sToken;
6108 else
6110 aResult.push_back(sToken);
6114 bInString = bInStringNext;
6117 return aResult;
6120 /*-------------------------------------------------------------------------
6121 //collect the pieces of the command
6122 -----------------------------------------------------------------------*/
6123 void DomainMapper_Impl::AppendFieldCommand(OUString const & rPartOfCommand)
6125 #ifdef DBG_UTIL
6126 TagLogger::getInstance().startElement("appendFieldCommand");
6127 TagLogger::getInstance().chars(rPartOfCommand);
6128 TagLogger::getInstance().endElement();
6129 #endif
6131 FieldContextPtr pContext = m_aFieldStack.back();
6132 OSL_ENSURE( pContext, "no field context available");
6133 if( pContext )
6135 // Set command line type: normal or deleted
6136 pContext->SetCommandType(m_bTextDeleted);
6137 pContext->AppendCommand( rPartOfCommand );
6142 typedef std::multimap < sal_Int32, OUString > TOCStyleMap;
6145 static ww::eField GetWW8FieldId(OUString const& rType)
6147 std::unordered_map<OUString, ww::eField> mapID
6149 {"ADDRESSBLOCK", ww::eADDRESSBLOCK},
6150 {"ADVANCE", ww::eADVANCE},
6151 {"ASK", ww::eASK},
6152 {"AUTONUM", ww::eAUTONUM},
6153 {"AUTONUMLGL", ww::eAUTONUMLGL},
6154 {"AUTONUMOUT", ww::eAUTONUMOUT},
6155 {"AUTOTEXT", ww::eAUTOTEXT},
6156 {"AUTOTEXTLIST", ww::eAUTOTEXTLIST},
6157 {"AUTHOR", ww::eAUTHOR},
6158 {"BARCODE", ww::eBARCODE},
6159 {"BIDIOUTLINE", ww::eBIDIOUTLINE},
6160 {"DATE", ww::eDATE},
6161 {"COMMENTS", ww::eCOMMENTS},
6162 {"COMPARE", ww::eCOMPARE},
6163 {"CONTROL", ww::eCONTROL},
6164 {"CREATEDATE", ww::eCREATEDATE},
6165 {"DATABASE", ww::eDATABASE},
6166 {"DDEAUTOREF", ww::eDDEAUTOREF},
6167 {"DDEREF", ww::eDDEREF},
6168 {"DOCPROPERTY", ww::eDOCPROPERTY},
6169 {"DOCVARIABLE", ww::eDOCVARIABLE},
6170 {"EDITTIME", ww::eEDITTIME},
6171 {"EMBED", ww::eEMBED},
6172 {"EQ", ww::eEQ},
6173 {"FILLIN", ww::eFILLIN},
6174 {"FILENAME", ww::eFILENAME},
6175 {"FILESIZE", ww::eFILESIZE},
6176 {"FOOTREF", ww::eFOOTREF},
6177 // {"FORMULA", ww::},
6178 {"FORMCHECKBOX", ww::eFORMCHECKBOX},
6179 {"FORMDROPDOWN", ww::eFORMDROPDOWN},
6180 {"FORMTEXT", ww::eFORMTEXT},
6181 {"GLOSSREF", ww::eGLOSSREF},
6182 {"GOTOBUTTON", ww::eGOTOBUTTON},
6183 {"GREETINGLINE", ww::eGREETINGLINE},
6184 {"HTMLCONTROL", ww::eHTMLCONTROL},
6185 {"HYPERLINK", ww::eHYPERLINK},
6186 {"IF", ww::eIF},
6187 {"INFO", ww::eINFO},
6188 {"INCLUDEPICTURE", ww::eINCLUDEPICTURE},
6189 {"INCLUDETEXT", ww::eINCLUDETEXT},
6190 {"INCLUDETIFF", ww::eINCLUDETIFF},
6191 {"KEYWORDS", ww::eKEYWORDS},
6192 {"LASTSAVEDBY", ww::eLASTSAVEDBY},
6193 {"LINK", ww::eLINK},
6194 {"LISTNUM", ww::eLISTNUM},
6195 {"MACRO", ww::eMACRO},
6196 {"MACROBUTTON", ww::eMACROBUTTON},
6197 {"MERGEDATA", ww::eMERGEDATA},
6198 {"MERGEFIELD", ww::eMERGEFIELD},
6199 {"MERGEINC", ww::eMERGEINC},
6200 {"MERGEREC", ww::eMERGEREC},
6201 {"MERGESEQ", ww::eMERGESEQ},
6202 {"NEXT", ww::eNEXT},
6203 {"NEXTIF", ww::eNEXTIF},
6204 {"NOTEREF", ww::eNOTEREF},
6205 {"PAGE", ww::ePAGE},
6206 {"PAGEREF", ww::ePAGEREF},
6207 {"PLUGIN", ww::ePLUGIN},
6208 {"PRINT", ww::ePRINT},
6209 {"PRINTDATE", ww::ePRINTDATE},
6210 {"PRIVATE", ww::ePRIVATE},
6211 {"QUOTE", ww::eQUOTE},
6212 {"RD", ww::eRD},
6213 {"REF", ww::eREF},
6214 {"REVNUM", ww::eREVNUM},
6215 {"SAVEDATE", ww::eSAVEDATE},
6216 {"SECTION", ww::eSECTION},
6217 {"SECTIONPAGES", ww::eSECTIONPAGES},
6218 {"SEQ", ww::eSEQ},
6219 {"SET", ww::eSET},
6220 {"SKIPIF", ww::eSKIPIF},
6221 {"STYLEREF", ww::eSTYLEREF},
6222 {"SUBSCRIBER", ww::eSUBSCRIBER},
6223 {"SUBJECT", ww::eSUBJECT},
6224 {"SYMBOL", ww::eSYMBOL},
6225 {"TA", ww::eTA},
6226 {"TEMPLATE", ww::eTEMPLATE},
6227 {"TIME", ww::eTIME},
6228 {"TITLE", ww::eTITLE},
6229 {"TOA", ww::eTOA},
6230 {"USERINITIALS", ww::eUSERINITIALS},
6231 {"USERADDRESS", ww::eUSERADDRESS},
6232 {"USERNAME", ww::eUSERNAME},
6234 {"TOC", ww::eTOC},
6235 {"TC", ww::eTC},
6236 {"NUMCHARS", ww::eNUMCHARS},
6237 {"NUMWORDS", ww::eNUMWORDS},
6238 {"NUMPAGES", ww::eNUMPAGES},
6239 {"INDEX", ww::eINDEX},
6240 {"XE", ww::eXE},
6241 {"BIBLIOGRAPHY", ww::eBIBLIOGRAPHY},
6242 {"CITATION", ww::eCITATION},
6244 auto const it = mapID.find(rType);
6245 return (it == mapID.end()) ? ww::eNONE : it->second;
6248 static const FieldConversionMap_t & lcl_GetFieldConversion()
6250 static const FieldConversionMap_t aFieldConversionMap
6252 // {"ADDRESSBLOCK", {"", FIELD_ADDRESSBLOCK }},
6253 // {"ADVANCE", {"", FIELD_ADVANCE }},
6254 {"ASK", {"SetExpression", FIELD_ASK }},
6255 {"AUTONUM", {"SetExpression", FIELD_AUTONUM }},
6256 {"AUTONUMLGL", {"SetExpression", FIELD_AUTONUMLGL }},
6257 {"AUTONUMOUT", {"SetExpression", FIELD_AUTONUMOUT }},
6258 {"AUTHOR", {"DocInfo.CreateAuthor", FIELD_AUTHOR }},
6259 {"DATE", {"DateTime", FIELD_DATE }},
6260 {"COMMENTS", {"DocInfo.Description", FIELD_COMMENTS }},
6261 {"CREATEDATE", {"DocInfo.CreateDateTime", FIELD_CREATEDATE }},
6262 {"DOCPROPERTY", {"", FIELD_DOCPROPERTY }},
6263 {"DOCVARIABLE", {"User", FIELD_DOCVARIABLE }},
6264 {"EDITTIME", {"DocInfo.EditTime", FIELD_EDITTIME }},
6265 {"EQ", {"", FIELD_EQ }},
6266 {"FILLIN", {"Input", FIELD_FILLIN }},
6267 {"FILENAME", {"FileName", FIELD_FILENAME }},
6268 // {"FILESIZE", {"", FIELD_FILESIZE }},
6269 {"FORMULA", {"TableFormula", FIELD_FORMULA }},
6270 {"FORMCHECKBOX", {"", FIELD_FORMCHECKBOX }},
6271 {"FORMDROPDOWN", {"DropDown", FIELD_FORMDROPDOWN }},
6272 {"FORMTEXT", {"Input", FIELD_FORMTEXT }},
6273 {"GOTOBUTTON", {"", FIELD_GOTOBUTTON }},
6274 {"HYPERLINK", {"", FIELD_HYPERLINK }},
6275 {"IF", {"ConditionalText", FIELD_IF }},
6276 // {"INFO", {"", FIELD_INFO }},
6277 {"INCLUDEPICTURE", {"", FIELD_INCLUDEPICTURE}},
6278 {"KEYWORDS", {"DocInfo.KeyWords", FIELD_KEYWORDS }},
6279 {"LASTSAVEDBY", {"DocInfo.ChangeAuthor", FIELD_LASTSAVEDBY }},
6280 {"MACROBUTTON", {"Macro", FIELD_MACROBUTTON }},
6281 {"MERGEFIELD", {"Database", FIELD_MERGEFIELD }},
6282 {"MERGEREC", {"DatabaseNumberOfSet", FIELD_MERGEREC }},
6283 // {"MERGESEQ", {"", FIELD_MERGESEQ }},
6284 {"NEXT", {"DatabaseNextSet", FIELD_NEXT }},
6285 {"NEXTIF", {"DatabaseNextSet", FIELD_NEXTIF }},
6286 {"PAGE", {"PageNumber", FIELD_PAGE }},
6287 {"PAGEREF", {"GetReference", FIELD_PAGEREF }},
6288 {"PRINTDATE", {"DocInfo.PrintDateTime", FIELD_PRINTDATE }},
6289 {"REF", {"GetReference", FIELD_REF }},
6290 {"REVNUM", {"DocInfo.Revision", FIELD_REVNUM }},
6291 {"SAVEDATE", {"DocInfo.ChangeDateTime", FIELD_SAVEDATE }},
6292 // {"SECTION", {"", FIELD_SECTION }},
6293 // {"SECTIONPAGES", {"", FIELD_SECTIONPAGES }},
6294 {"SEQ", {"SetExpression", FIELD_SEQ }},
6295 {"SET", {"SetExpression", FIELD_SET }},
6296 // {"SKIPIF", {"", FIELD_SKIPIF }},
6297 {"STYLEREF", {"GetReference", FIELD_STYLEREF }},
6298 {"SUBJECT", {"DocInfo.Subject", FIELD_SUBJECT }},
6299 {"SYMBOL", {"", FIELD_SYMBOL }},
6300 {"TEMPLATE", {"TemplateName", FIELD_TEMPLATE }},
6301 {"TIME", {"DateTime", FIELD_TIME }},
6302 {"TITLE", {"DocInfo.Title", FIELD_TITLE }},
6303 {"USERINITIALS", {"Author", FIELD_USERINITIALS }},
6304 // {"USERADDRESS", {"", FIELD_USERADDRESS }},
6305 {"USERNAME", {"Author", FIELD_USERNAME }},
6308 {"TOC", {"com.sun.star.text.ContentIndex", FIELD_TOC }},
6309 {"TC", {"com.sun.star.text.ContentIndexMark", FIELD_TC }},
6310 {"NUMCHARS", {"CharacterCount", FIELD_NUMCHARS }},
6311 {"NUMWORDS", {"WordCount", FIELD_NUMWORDS }},
6312 {"NUMPAGES", {"PageCount", FIELD_NUMPAGES }},
6313 {"INDEX", {"com.sun.star.text.DocumentIndex", FIELD_INDEX }},
6314 {"XE", {"com.sun.star.text.DocumentIndexMark", FIELD_XE }},
6315 {"BIBLIOGRAPHY",{"com.sun.star.text.Bibliography", FIELD_BIBLIOGRAPHY }},
6316 {"CITATION", {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION }},
6319 return aFieldConversionMap;
6322 static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion()
6324 static const FieldConversionMap_t aEnhancedFieldConversionMap =
6326 {"FORMCHECKBOX", {"FormFieldmark", FIELD_FORMCHECKBOX}},
6327 {"FORMDROPDOWN", {"FormFieldmark", FIELD_FORMDROPDOWN}},
6328 {"FORMTEXT", {"Fieldmark", FIELD_FORMTEXT}},
6331 return aEnhancedFieldConversionMap;
6334 void DomainMapper_Impl::handleFieldSet
6335 (const FieldContextPtr& pContext,
6336 uno::Reference< uno::XInterface > const & xFieldInterface,
6337 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6339 OUString sVariable, sHint;
6341 sVariable = lcl_ExtractVariableAndHint(pContext->GetCommand(), sHint);
6343 // remove surrounding "" if exists
6344 if(sHint.getLength() >= 2)
6346 std::u16string_view sTmp = o3tl::trim(sHint);
6347 if (o3tl::starts_with(sTmp, u"\"") && o3tl::ends_with(sTmp, u"\""))
6349 sHint = sTmp.substr(1, sTmp.size() - 2);
6353 // determine field master name
6354 uno::Reference< beans::XPropertySet > xMaster =
6355 FindOrCreateFieldMaster
6356 ("com.sun.star.text.FieldMaster.SetExpression", sVariable);
6358 // a set field is a string
6359 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6361 // attach the master to the field
6362 uno::Reference< text::XDependentTextField > xDependentField
6363 ( xFieldInterface, uno::UNO_QUERY_THROW );
6364 xDependentField->attachTextFieldMaster( xMaster );
6366 uno::Any aAnyHint(sHint);
6367 xFieldProperties->setPropertyValue(getPropertyName(PROP_HINT), aAnyHint);
6368 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), aAnyHint);
6369 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6371 // Mimic MS Word behavior (hide the SET)
6372 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false));
6375 void DomainMapper_Impl::handleFieldAsk
6376 (const FieldContextPtr& pContext,
6377 uno::Reference< uno::XInterface > & xFieldInterface,
6378 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6380 //doesn the command contain a variable name?
6381 OUString sVariable, sHint;
6383 sVariable = lcl_ExtractVariableAndHint( pContext->GetCommand(),
6384 sHint );
6385 if(!sVariable.isEmpty())
6387 // determine field master name
6388 uno::Reference< beans::XPropertySet > xMaster =
6389 FindOrCreateFieldMaster
6390 ("com.sun.star.text.FieldMaster.SetExpression", sVariable );
6391 // An ASK field is always a string of characters
6392 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6394 // attach the master to the field
6395 uno::Reference< text::XDependentTextField > xDependentField
6396 ( xFieldInterface, uno::UNO_QUERY_THROW );
6397 xDependentField->attachTextFieldMaster( xMaster );
6399 // set input flag at the field
6400 xFieldProperties->setPropertyValue(
6401 getPropertyName(PROP_IS_INPUT), uno::Any( true ));
6402 // set the prompt
6403 xFieldProperties->setPropertyValue(
6404 getPropertyName(PROP_HINT),
6405 uno::Any( sHint ));
6406 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
6407 // The ASK has no field value to display
6408 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false));
6410 else
6412 //don't insert the field
6413 //todo: maybe import a 'normal' input field here?
6414 xFieldInterface = nullptr;
6419 * Converts a Microsoft Word field formula into LibreOffice syntax
6420 * @param input The Microsoft Word field formula, with no leading '=' sign
6421 * @return An equivalent LibreOffice field formula
6423 OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) {
6425 if (!m_pSettingsTable)
6427 return input;
6430 OUString listSeparator = m_pSettingsTable->GetListSeparator();
6432 /* Replace logical condition functions with LO equivalent operators */
6433 OUString changed = input.replaceAll(" <> ", " NEQ ");
6434 changed = changed.replaceAll(" <= ", " LEQ ");
6435 changed = changed.replaceAll(" >= ", " GEQ ");
6436 changed = changed.replaceAll(" = " , " EQ ");
6437 changed = changed.replaceAll(" < " , " L ");
6438 changed = changed.replaceAll(" > " , " G ");
6440 changed = changed.replaceAll("<>", " NEQ ");
6441 changed = changed.replaceAll("<=", " LEQ ");
6442 changed = changed.replaceAll(">=", " GEQ ");
6443 changed = changed.replaceAll("=" , " EQ ");
6444 changed = changed.replaceAll("<" , " L ");
6445 changed = changed.replaceAll(">" , " G ");
6447 /* Replace function calls with infix keywords for AND(), OR(), and ROUND(). Nothing needs to be
6448 * done for NOT(). This simple regex will work properly with most common cases. However, it may
6449 * not work correctly when the arguments are nested subcalls to other functions, like
6450 * ROUND(MIN(1,2),MAX(3,4)). See TDF#134765. */
6451 icu::ErrorCode status;
6452 icu::UnicodeString usInput(changed.getStr());
6453 const uint32_t rMatcherFlags = UREGEX_CASE_INSENSITIVE;
6454 OUString regex = "\\b(AND|OR|ROUND)\\s*\\(\\s*([^" + listSeparator + "]+)\\s*" + listSeparator + "\\s*([^)]+)\\s*\\)";
6455 icu::UnicodeString usRegex(regex.getStr());
6456 icu::RegexMatcher rmatch1(usRegex, usInput, rMatcherFlags, status);
6457 usInput = rmatch1.replaceAll(icu::UnicodeString("(($2) $1 ($3))"), status);
6459 /* Assumes any remaining list separators separate arguments to functions that accept lists
6460 * (SUM, MIN, MAX, MEAN, etc.) */
6461 usInput.findAndReplace(icu::UnicodeString(listSeparator.getStr()), "|");
6463 /* Surround single cell references with angle brackets.
6464 * If there is ever added a function name that ends with a digit, this regex will need to be revisited. */
6465 icu::RegexMatcher rmatch2("\\b([A-Z]{1,3}[0-9]+)\\b(?![(])", usInput, rMatcherFlags, status);
6466 usInput = rmatch2.replaceAll(icu::UnicodeString("<$1>"), status);
6468 /* Cell references must be upper case
6469 * TODO: convert reference to other tables, e.g. SUM(Table1 A1:B2), where "Table1" is a bookmark of the table,
6470 * TODO: also column range A:A */
6471 icu::RegexMatcher rmatch3("(<[a-z]{1,3}[0-9]+>|\\b(above|below|left|right)\\b)", usInput, rMatcherFlags, status);
6472 icu::UnicodeString replacedCellRefs;
6473 while (rmatch3.find(status) && status.isSuccess()) {
6474 rmatch3.appendReplacement(replacedCellRefs, rmatch3.group(status).toUpper(), status);
6476 rmatch3.appendTail(replacedCellRefs);
6478 /* Fix up cell ranges */
6479 icu::RegexMatcher rmatch4("<([A-Z]{1,3}[0-9]+)>:<([A-Z]{1,3}[0-9]+)>", replacedCellRefs, rMatcherFlags, status);
6480 usInput = rmatch4.replaceAll(icu::UnicodeString("<$1:$2>"), status);
6482 /* Fix up user defined names */
6483 icu::RegexMatcher rmatch5("\\bDEFINED\\s*\\(<([A-Z]+[0-9]+)>\\)", usInput, rMatcherFlags, status);
6484 usInput = rmatch5.replaceAll(icu::UnicodeString("DEFINED($1)"), status);
6486 /* Prepare replace of ABOVE/BELOW/LEFT/RIGHT by adding spaces around them */
6487 icu::RegexMatcher rmatch6("\\b(ABOVE|BELOW|LEFT|RIGHT)\\b", usInput, rMatcherFlags, status);
6488 usInput = rmatch6.replaceAll(icu::UnicodeString(" $1 "), status);
6490 /* DOCX allows to set decimal symbol independently from the locale of the document, so if
6491 * needed, convert decimal comma to get working formula in a document language (locale),
6492 * which doesn't use decimal comma */
6493 if ( m_pSettingsTable->GetDecimalSymbol() == "," && !m_bIsDecimalComma )
6495 icu::RegexMatcher rmatch7("\\b([0-9]+),([0-9]+([eE][-]?[0-9]+)?)\\b", usInput, rMatcherFlags, status);
6496 usInput = rmatch7.replaceAll(icu::UnicodeString("$1.$2"), status);
6499 return OUString(usInput.getTerminatedBuffer());
6502 void DomainMapper_Impl::handleFieldFormula
6503 (const FieldContextPtr& pContext,
6504 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6506 OUString command = pContext->GetCommand().trim();
6508 // Remove number formatting from \# to end of command
6509 // TODO: handle custom number formatting
6510 sal_Int32 delimPos = command.indexOf("\\#");
6511 if (delimPos != -1)
6513 command = command.replaceAt(delimPos, command.getLength() - delimPos, u"").trim();
6516 // command must contains = and at least another char
6517 if (command.getLength() < 2)
6518 return;
6520 // we don't copy the = symbol from the command
6521 OUString formula = convertFieldFormula(command.copy(1));
6523 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(formula));
6524 xFieldProperties->setPropertyValue(getPropertyName(PROP_NUMBER_FORMAT), uno::Any(sal_Int32(0)));
6525 xFieldProperties->setPropertyValue("IsShowFormula", uno::Any(false));
6527 // grab-bag the original and converted formula
6528 if (hasTableManager())
6530 TablePropertyMapPtr pPropMap(new TablePropertyMap());
6531 pPropMap->Insert(PROP_CELL_FORMULA, uno::Any(command.copy(1)), true, CELL_GRAB_BAG);
6532 pPropMap->Insert(PROP_CELL_FORMULA_CONVERTED, uno::Any(formula), true, CELL_GRAB_BAG);
6533 getTableManager().cellProps(pPropMap);
6537 void DomainMapper_Impl::handleRubyEQField( const FieldContextPtr& pContext)
6539 const OUString & rCommand(pContext->GetCommand());
6540 sal_Int32 nIndex = 0, nEnd = 0;
6541 RubyInfo aInfo ;
6542 nIndex = rCommand.indexOf("\\* jc" );
6543 if (nIndex != -1)
6545 nIndex += 5;
6546 sal_uInt32 nJc = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex));
6547 const sal_Int32 aRubyAlignValues[] =
6549 NS_ooxml::LN_Value_ST_RubyAlign_center,
6550 NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter,
6551 NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace,
6552 NS_ooxml::LN_Value_ST_RubyAlign_left,
6553 NS_ooxml::LN_Value_ST_RubyAlign_right,
6554 NS_ooxml::LN_Value_ST_RubyAlign_rightVertical,
6556 aInfo.nRubyAlign = aRubyAlignValues[(nJc<SAL_N_ELEMENTS(aRubyAlignValues))?nJc:0];
6559 // we don't parse or use the font field in rCommand
6561 nIndex = rCommand.indexOf("\\* hps" );
6562 if (nIndex != -1)
6564 nIndex += 6;
6565 aInfo.nHps = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex));
6568 nIndex = rCommand.indexOf("\\o");
6569 if (nIndex == -1)
6570 return;
6571 nIndex = rCommand.indexOf('(', nIndex);
6572 if (nIndex == -1)
6573 return;
6574 nEnd = rCommand.lastIndexOf(')');
6575 if (nEnd == -1)
6576 return;
6577 if (nEnd <= nIndex)
6578 return;
6580 std::u16string_view sRubyParts = rCommand.subView(nIndex+1,nEnd-nIndex-1);
6581 nIndex = 0;
6582 std::u16string_view sPart1 = o3tl::getToken(sRubyParts, 0, ',', nIndex);
6583 std::u16string_view sPart2 = o3tl::getToken(sRubyParts, 0, ',', nIndex);
6584 size_t nIndex2 = 0;
6585 size_t nEnd2 = 0;
6586 if ((nIndex2 = sPart1.find('(')) != std::u16string_view::npos && (nEnd2 = sPart1.rfind(')')) != std::u16string_view::npos && nEnd2 > nIndex2)
6588 aInfo.sRubyText = sPart1.substr(nIndex2+1,nEnd2-nIndex2-1);
6591 PropertyMapPtr pRubyContext(new PropertyMap());
6592 pRubyContext->InsertProps(GetTopContext());
6593 if (aInfo.nHps > 0)
6595 double fVal = double(aInfo.nHps) / 2.;
6596 uno::Any aVal( fVal );
6598 pRubyContext->Insert(PROP_CHAR_HEIGHT, aVal);
6599 pRubyContext->Insert(PROP_CHAR_HEIGHT_ASIAN, aVal);
6601 PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pRubyContext->GetPropertyValues());
6602 aInfo.sRubyStyle = m_rDMapper.getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
6603 PropertyMapPtr pCharContext(new PropertyMap());
6604 if (m_pLastCharacterContext)
6605 pCharContext->InsertProps(m_pLastCharacterContext);
6606 pCharContext->InsertProps(pContext->getProperties());
6607 pCharContext->Insert(PROP_RUBY_TEXT, uno::Any( aInfo.sRubyText ) );
6608 pCharContext->Insert(PROP_RUBY_ADJUST, uno::Any(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign))));
6609 if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical )
6610 pCharContext->Insert(PROP_RUBY_POSITION, uno::Any(css::text::RubyPosition::INTER_CHARACTER));
6611 pCharContext->Insert(PROP_RUBY_STYLE, uno::Any(aInfo.sRubyStyle));
6612 appendTextPortion(OUString(sPart2), pCharContext);
6616 void DomainMapper_Impl::handleAutoNum
6617 (const FieldContextPtr& pContext,
6618 uno::Reference< uno::XInterface > const & xFieldInterface,
6619 uno::Reference< beans::XPropertySet > const& xFieldProperties)
6621 //create a sequence field master "AutoNr"
6622 uno::Reference< beans::XPropertySet > xMaster =
6623 FindOrCreateFieldMaster
6624 ("com.sun.star.text.FieldMaster.SetExpression",
6625 "AutoNr");
6627 xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE),
6628 uno::Any(text::SetVariableType::SEQUENCE));
6630 //apply the numbering type
6631 xFieldProperties->setPropertyValue(
6632 getPropertyName(PROP_NUMBERING_TYPE),
6633 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
6634 // attach the master to the field
6635 uno::Reference< text::XDependentTextField > xDependentField
6636 ( xFieldInterface, uno::UNO_QUERY_THROW );
6637 xDependentField->attachTextFieldMaster( xMaster );
6640 void DomainMapper_Impl::handleAuthor
6641 (std::u16string_view,
6642 uno::Reference< beans::XPropertySet > const& xFieldProperties,
6643 FieldId eFieldId )
6645 if (eFieldId == FIELD_USERNAME)
6646 xFieldProperties->setPropertyValue
6647 ( getPropertyName(PROP_FULL_NAME), uno::Any( true ));
6649 // Always set as FIXED b/c MS Word only updates these fields via user intervention (F9)
6650 // AUTHOR of course never changes and USERNAME is easily mis-used as an original author field.
6651 // Additionally, this was forced as fixed if any special case-formatting was provided.
6653 xFieldProperties->setPropertyValue(
6654 getPropertyName( PROP_IS_FIXED ),
6655 uno::Any( true ));
6656 //PROP_CURRENT_PRESENTATION is set later anyway
6660 void DomainMapper_Impl::handleDocProperty
6661 (const FieldContextPtr& pContext,
6662 OUString const& rFirstParam,
6663 uno::Reference< uno::XInterface > & xFieldInterface)
6665 //some docproperties should be imported as document statistic fields, some as DocInfo fields
6666 //others should be user fields
6667 if (rFirstParam.isEmpty())
6668 return;
6670 constexpr sal_uInt8 SET_ARABIC = 0x01;
6671 constexpr sal_uInt8 SET_DATE = 0x04;
6672 struct DocPropertyMap
6674 const char* pDocPropertyName;
6675 const char* pServiceName;
6676 sal_uInt8 nFlags;
6678 static const DocPropertyMap aDocProperties[] =
6680 {"CreateTime", "DocInfo.CreateDateTime", SET_DATE},
6681 {"Characters", "CharacterCount", SET_ARABIC},
6682 {"Comments", "DocInfo.Description", 0},
6683 {"Keywords", "DocInfo.KeyWords", 0},
6684 {"LastPrinted", "DocInfo.PrintDateTime", 0},
6685 {"LastSavedBy", "DocInfo.ChangeAuthor", 0},
6686 {"LastSavedTime", "DocInfo.ChangeDateTime", SET_DATE},
6687 {"Paragraphs", "ParagraphCount", SET_ARABIC},
6688 {"RevisionNumber", "DocInfo.Revision", 0},
6689 {"Subject", "DocInfo.Subject", 0},
6690 {"Template", "TemplateName", 0},
6691 {"Title", "DocInfo.Title", 0},
6692 {"TotalEditingTime", "DocInfo.EditTime", 0},
6693 {"Words", "WordCount", SET_ARABIC}
6695 //other available DocProperties:
6696 //Bytes, Category, CharactersWithSpaces, Company
6697 //HyperlinkBase,
6698 //Lines, Manager, NameofApplication, ODMADocId, Pages,
6699 //Security,
6701 uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(m_xTextDocument, uno::UNO_QUERY);
6702 uno::Reference<document::XDocumentProperties> xDocumentProperties = xDocumentPropertiesSupplier->getDocumentProperties();
6703 uno::Reference<beans::XPropertySet> xUserDefinedProps(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
6704 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xUserDefinedProps->getPropertySetInfo();
6705 //search for a field mapping
6706 OUString sFieldServiceName;
6707 size_t nMap = 0;
6708 if (!xPropertySetInfo->hasPropertyByName(rFirstParam))
6710 for( ; nMap < SAL_N_ELEMENTS(aDocProperties); ++nMap )
6712 if (rFirstParam.equalsAscii(aDocProperties[nMap].pDocPropertyName))
6714 sFieldServiceName = OUString::createFromAscii(aDocProperties[nMap].pServiceName);
6715 break;
6719 else
6720 pContext->CacheVariableValue(xUserDefinedProps->getPropertyValue(rFirstParam));
6722 OUString sServiceName("com.sun.star.text.TextField.");
6723 bool bIsCustomField = false;
6724 if(sFieldServiceName.isEmpty())
6726 //create a custom property field
6727 sServiceName += "DocInfo.Custom";
6728 bIsCustomField = true;
6730 else
6732 sServiceName += sFieldServiceName;
6734 if (m_xTextFactory.is())
6735 xFieldInterface = m_xTextFactory->createInstance(sServiceName);
6736 uno::Reference<beans::XPropertySet> xFieldProperties( xFieldInterface, uno::UNO_QUERY_THROW);
6737 if( bIsCustomField )
6739 xFieldProperties->setPropertyValue(
6740 getPropertyName(PROP_NAME), uno::Any(rFirstParam));
6741 pContext->SetCustomField( xFieldProperties );
6743 else
6745 if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC))
6746 xFieldProperties->setPropertyValue(
6747 getPropertyName(PROP_NUMBERING_TYPE),
6748 uno::Any( style::NumberingType::ARABIC ));
6749 else if(0 != (aDocProperties[nMap].nFlags & SET_DATE))
6751 xFieldProperties->setPropertyValue(
6752 getPropertyName(PROP_IS_DATE),
6753 uno::Any( true ));
6754 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
6759 static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator,
6760 const uno::Sequence< beans::PropertyValues >& aLevel, const std::optional<style::TabStop> numtab)
6762 //create a copy of the level and add new entries
6764 std::vector<css::beans::PropertyValues> aNewLevel;
6765 aNewLevel.reserve(aLevel.getLength() + 5); // at most 5 added items
6767 static constexpr OUString tokType(u"TokenType"_ustr);
6768 static constexpr OUString tokHStart(u"TokenHyperlinkStart"_ustr);
6769 static constexpr OUString tokHEnd(u"TokenHyperlinkEnd"_ustr);
6770 static constexpr OUStringLiteral tokPNum(u"TokenPageNumber");
6771 static constexpr OUStringLiteral tokENum(u"TokenEntryNumber");
6773 if (bHyperlinks)
6774 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHStart) });
6776 for (const auto& item : aLevel)
6778 OUString tokenType;
6779 if (auto it = std::find_if(item.begin(), item.end(),
6780 [](const auto& p) { return p.Name == tokType; });
6781 it != item.end())
6782 it->Value >>= tokenType;
6784 if (bHyperlinks && (tokenType == tokHStart || tokenType == tokHEnd))
6785 continue; // We add hyperlink ourselves, so just skip existing hyperlink start / end
6787 if (!sChapterNoSeparator.isEmpty() && tokenType == tokPNum)
6789 // This is an existing page number token; insert the chapter and separator before it
6790 aNewLevel.push_back(
6791 { comphelper::makePropertyValue(tokType, OUString("TokenChapterInfo")),
6792 comphelper::makePropertyValue("ChapterFormat", text::ChapterFormat::NUMBER) });
6793 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, OUString("TokenText")),
6794 comphelper::makePropertyValue("Text", sChapterNoSeparator) });
6797 aNewLevel.push_back(item);
6799 if (numtab && tokenType == tokENum)
6801 // There is a fixed tab stop position needed in the level after the numbering
6802 aNewLevel.push_back(
6803 { comphelper::makePropertyValue(tokType, OUString("TokenTabStop")),
6804 comphelper::makePropertyValue("TabStopPosition", numtab->Position) });
6808 if (bHyperlinks)
6809 aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHEnd) });
6811 return comphelper::containerToSequence(aNewLevel);
6814 /// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame
6815 OUString DomainMapper_Impl::extractTocTitle()
6817 if (!m_xSdtEntryStart.is())
6818 return OUString();
6820 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
6821 if(!xTextAppend.is())
6822 return OUString();
6824 // try-catch was added in the same way as inside appendTextSectionAfter()
6827 uno::Reference<text::XParagraphCursor> xCursor(xTextAppend->createTextCursorByRange(m_xSdtEntryStart), uno::UNO_QUERY_THROW);
6828 if (!xCursor.is())
6829 return OUString();
6831 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
6832 xCursor->gotoStartOfParagraph( false );
6833 if (m_aTextAppendStack.top().xInsertPosition.is())
6834 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
6835 else
6836 xCursor->gotoEnd( true );
6838 // the paragraph after this new section might have been already inserted
6839 OUString sResult = xCursor->getString();
6840 if (sResult.endsWith(SAL_NEWLINE_STRING))
6841 sResult = sResult.copy(0, sResult.getLength() - SAL_N_ELEMENTS(SAL_NEWLINE_STRING) + 1);
6843 return sResult;
6845 catch(const uno::Exception&)
6849 return OUString();
6852 css::uno::Reference<css::beans::XPropertySet>
6853 DomainMapper_Impl::StartIndexSectionChecked(const OUString& sServiceName)
6855 if (m_StreamStateStack.top().bParaChanged)
6857 finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets bParaChanged
6858 PopProperties(CONTEXT_PARAGRAPH);
6859 PushProperties(CONTEXT_PARAGRAPH);
6860 SetIsFirstRun(true);
6861 // The first paragraph of the index that is continuation of just finished one needs to be
6862 // removed when finished (unless more content will arrive, which will set bParaChanged)
6863 m_StreamStateStack.top().bRemoveThisParagraph = true;
6865 const auto& xTextAppend = GetTopTextAppend();
6866 const auto xTextRange = xTextAppend->getEnd();
6867 const auto xRet = createSectionForRange(xTextRange, xTextRange, sServiceName, false);
6868 if (!m_aTextAppendStack.top().xInsertPosition)
6872 m_bStartedTOC = true;
6873 uno::Reference<text::XTextCursor> xTOCTextCursor
6874 = xTextRange->getText()->createTextCursor();
6875 assert(xTOCTextCursor.is());
6876 xTOCTextCursor->gotoEnd(false);
6877 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
6879 catch (const uno::Exception&)
6881 TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
6882 "DomainMapper_Impl::StartIndexSectionChecked:");
6885 return xRet;
6889 This is a heuristic to find Word's w:styleId value from localised style name.
6890 It's not clear how exactly it works, but apparently Word stores into
6891 w:styleId some filtered representation of the localised style name.
6892 Tragically there are references to the localised style name itself in TOC
6893 fields.
6894 Hopefully this works and a complete map of >100 built-in style names
6895 localised to all languages isn't needed.
6897 static auto FilterChars(std::u16string_view const& rStyleName) -> OUString
6899 return msfilter::util::CreateDOCXStyleId(rStyleName);
6902 static OUString UnquoteFieldText(std::u16string_view s)
6904 OUStringBuffer result(s.size());
6905 for (size_t i = 0; i < s.size(); ++i)
6907 switch (s[i])
6909 case '"':
6910 continue;
6911 case '\\':
6912 if (i < s.size() - 1)
6913 ++i;
6914 [[fallthrough]];
6915 default:
6916 result.append(s[i]);
6919 return result.makeStringAndClear();
6922 OUString DomainMapper_Impl::ConvertTOCStyleName(OUString const& rTOCStyleName)
6924 assert(!rTOCStyleName.isEmpty());
6925 if (auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(FilterChars(rTOCStyleName)))
6927 auto const [convertedStyleName, isBuiltIn] = StyleSheetTable::ConvertStyleName(pStyle->m_sStyleName);
6928 if (isBuiltIn && m_bIsNewDoc)
6929 { // practical case: Word wrote i18n name to TOC field, but it doesn't
6930 // exist in styles.xml; tdf#153083 clone it for best roundtrip
6931 assert(convertedStyleName == pStyle->m_sConvertedStyleName);
6932 return GetStyleSheetTable()->CloneTOCStyle(GetFontTable(), pStyle, rTOCStyleName);
6935 // theoretical case: what OOXML says
6936 return StyleSheetTable::ConvertStyleName(rTOCStyleName).first;
6939 void DomainMapper_Impl::handleToc
6940 (const FieldContextPtr& pContext,
6941 const OUString & sTOCServiceName)
6943 OUString sValue;
6944 if (IsInHeaderFooter())
6945 m_bStartTOCHeaderFooter = true;
6946 bool bTableOfFigures = false;
6947 bool bHyperlinks = false;
6948 bool bFromOutline = false;
6949 bool bFromEntries = false;
6950 bool bHideTabLeaderPageNumbers = false ;
6951 bool bIsTabEntry = false ;
6952 bool bNewLine = false ;
6953 bool bParagraphOutlineLevel = false;
6955 sal_Int16 nMaxLevel = 10;
6956 OUString sTemplate;
6957 OUString sChapterNoSeparator;
6958 OUString sFigureSequence;
6959 OUString aBookmarkName;
6961 // \a Builds a table of figures but does not include the captions's label and number
6962 if( lcl_FindInCommand( pContext->GetCommand(), 'a', sValue ))
6963 { //make it a table of figures
6964 bTableOfFigures = true;
6966 // \b Uses a bookmark to specify area of document from which to build table of contents
6967 if( lcl_FindInCommand( pContext->GetCommand(), 'b', sValue ))
6969 aBookmarkName = sValue.trim().replaceAll("\"","");
6971 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
6972 // \c Builds a table of figures of the given label
6974 //todo: sValue contains the label's name
6975 bTableOfFigures = true;
6976 sFigureSequence = sValue.trim();
6977 sFigureSequence = sFigureSequence.replaceAll("\"", "").replaceAll("'","");
6979 // \d Defines the separator between sequence and page numbers
6980 if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue ))
6982 //todo: insert the chapter number into each level and insert the separator additionally
6983 sChapterNoSeparator = UnquoteFieldText(sValue);
6985 // \f Builds a table of contents using TC entries instead of outline levels
6986 if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
6988 //todo: sValue can contain a TOC entry identifier - use unclear
6989 bFromEntries = true;
6991 // \h Hyperlinks the entries and page numbers within the table of contents
6992 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
6994 //todo: make all entries to hyperlinks
6995 bHyperlinks = true;
6997 // \l Defines the TC entries field level used to build a table of contents
6998 // if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
6999 // {
7000 //todo: entries can only be included completely
7001 // }
7002 // \n Builds a table of contents or a range of entries, such as 1-9 in a table of contents without page numbers
7003 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
7004 // {
7005 //todo: what does the description mean?
7006 // }
7007 // \o Builds a table of contents by using outline levels instead of TC entries
7008 if( lcl_FindInCommand( pContext->GetCommand(), 'o', sValue ))
7010 bFromOutline = true;
7011 if (sValue.isEmpty())
7012 nMaxLevel = WW_OUTLINE_MAX;
7013 else
7015 sal_Int32 nIndex = 0;
7016 o3tl::getToken(sValue, 0, '-', nIndex );
7017 nMaxLevel = static_cast<sal_Int16>(nIndex != -1 ? o3tl::toInt32(sValue.subView(nIndex)) : 0);
7020 // \p Defines the separator between the table entry and its page number
7021 // \s Builds a table of contents by using a sequence type
7022 // \t Builds a table of contents by using style names other than the standard outline styles
7023 if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
7025 OUString sToken = sValue.getToken(1, '"');
7026 sTemplate = sToken.isEmpty() ? sValue : sToken;
7028 // \u Builds a table of contents by using the applied paragraph outline level
7029 if( lcl_FindInCommand( pContext->GetCommand(), 'u', sValue ))
7031 bFromOutline = true;
7032 bParagraphOutlineLevel = true;
7033 //todo: what doesn 'the applied paragraph outline level' refer to?
7035 // \w Preserve tab characters within table entries
7036 if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
7038 bIsTabEntry = true ;
7040 // \x Preserve newline characters within table entries
7041 if( lcl_FindInCommand( pContext->GetCommand(), 'x', sValue ))
7043 bNewLine = true ;
7045 // \z Hides page numbers within the table of contents when shown in Web Layout View
7046 if( lcl_FindInCommand( pContext->GetCommand(), 'z', sValue ))
7048 bHideTabLeaderPageNumbers = true ;
7051 //if there's no option then it should be created from outline
7052 if( !bFromOutline && !bFromEntries && sTemplate.isEmpty() )
7053 bFromOutline = true;
7055 const OUString aTocTitle = extractTocTitle();
7057 uno::Reference<beans::XPropertySet> xTOC;
7059 if (m_xTextFactory.is() && ! m_aTextAppendStack.empty())
7061 const auto& xTextAppend = GetTopTextAppend();
7062 if (aTocTitle.isEmpty() || bTableOfFigures)
7064 // reset marker of the TOC title
7065 m_xSdtEntryStart.clear();
7067 // Create section before setting m_bStartTOC: finishing paragraph
7068 // inside StartIndexSectionChecked could do the wrong thing otherwise
7069 xTOC = StartIndexSectionChecked(bTableOfFigures ? "com.sun.star.text.IllustrationsIndex"
7070 : sTOCServiceName);
7072 const auto xTextCursor = xTextAppend->getText()->createTextCursor();
7073 if (xTextCursor)
7074 xTextCursor->gotoEnd(false);
7075 m_xTOCMarkerCursor = xTextCursor;
7077 else
7079 // create TOC section
7080 css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd();
7081 xTOC = createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false);
7083 // init [xTOCMarkerCursor]
7084 uno::Reference< text::XText > xText = xTextAppend->getText();
7085 m_xTOCMarkerCursor = xText->createTextCursor();
7087 // create header of the TOC with the TOC title inside
7088 createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, "com.sun.star.text.IndexHeaderSection", true);
7092 m_bStartTOC = true;
7093 pContext->SetTOC(xTOC);
7094 m_StreamStateStack.top().bParaHadField = false;
7096 if (!xTOC)
7097 return;
7099 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(aTocTitle));
7101 if (!aBookmarkName.isEmpty())
7102 xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::Any(aBookmarkName));
7103 if (!bTableOfFigures)
7105 xTOC->setPropertyValue( getPropertyName( PROP_LEVEL ), uno::Any( nMaxLevel ) );
7106 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_OUTLINE ), uno::Any( bFromOutline ));
7107 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_MARKS ), uno::Any( bFromEntries ));
7108 xTOC->setPropertyValue( getPropertyName( PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS ), uno::Any( bHideTabLeaderPageNumbers ));
7109 xTOC->setPropertyValue( getPropertyName( PROP_TAB_IN_TOC ), uno::Any( bIsTabEntry ));
7110 xTOC->setPropertyValue( getPropertyName( PROP_TOC_NEW_LINE ), uno::Any( bNewLine ));
7111 xTOC->setPropertyValue( getPropertyName( PROP_TOC_PARAGRAPH_OUTLINE_LEVEL ), uno::Any( bParagraphOutlineLevel ));
7112 if( !sTemplate.isEmpty() )
7114 //the string contains comma separated the names and related levels
7115 //like: "Heading 1,1,Heading 2,2"
7116 TOCStyleMap aMap;
7117 sal_Int32 nLevel;
7118 sal_Int32 nPosition = 0;
7119 auto const tsep(sTemplate.indexOf(',') != -1 ? ',' : ';');
7120 while( nPosition >= 0)
7122 OUString sStyleName = sTemplate.getToken(0, tsep, nPosition);
7123 //empty tokens should be skipped
7124 while( sStyleName.isEmpty() && nPosition > 0 )
7125 sStyleName = sTemplate.getToken(0, tsep, nPosition);
7126 nLevel = o3tl::toInt32(o3tl::getToken(sTemplate, 0, tsep, nPosition ));
7127 if( !nLevel )
7128 nLevel = 1;
7129 if( !sStyleName.isEmpty() )
7130 aMap.emplace(nLevel, sStyleName);
7132 uno::Reference< container::XIndexReplace> xParaStyles;
7133 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_PARAGRAPH_STYLES)) >>= xParaStyles;
7134 for( nLevel = 1; nLevel < 10; ++nLevel)
7136 sal_Int32 nLevelCount = aMap.count( nLevel );
7137 if( nLevelCount )
7139 TOCStyleMap::iterator aTOCStyleIter = aMap.find( nLevel );
7141 uno::Sequence< OUString> aStyles( nLevelCount );
7142 for ( auto& rStyle : asNonConstRange(aStyles) )
7144 // tdf#153083 must map w:styleId to w:name
7145 rStyle = ConvertTOCStyleName(aTOCStyleIter->second);
7146 ++aTOCStyleIter;
7148 xParaStyles->replaceByIndex(nLevel - 1, uno::Any(aStyles));
7151 xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true ));
7155 uno::Reference<container::XIndexAccess> xChapterNumberingRules;
7156 if (auto xSupplier = GetTextDocument().query<text::XChapterNumberingSupplier>())
7157 xChapterNumberingRules = xSupplier->getChapterNumberingRules();
7158 uno::Reference<container::XNameContainer> xStyles;
7159 if (auto xStylesSupplier = GetTextDocument().query<style::XStyleFamiliesSupplier>())
7161 auto xStyleFamilies = xStylesSupplier->getStyleFamilies();
7162 xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES)) >>= xStyles;
7165 uno::Reference< container::XIndexReplace> xLevelFormats;
7166 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
7167 sal_Int32 nLevelCount = xLevelFormats->getCount();
7168 //start with level 1, 0 is the header level
7169 for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel)
7171 uno::Sequence< beans::PropertyValues > aLevel;
7172 xLevelFormats->getByIndex( nLevel ) >>= aLevel;
7174 // Get the tab stops coming from the styles; store to the level definitions
7175 std::optional<style::TabStop> numTab;
7176 if (xChapterNumberingRules && xStyles)
7178 // This relies on the chapter numbering rules already defined
7179 // (see ListDef::CreateNumberingRules)
7180 uno::Sequence<beans::PropertyValue> props;
7181 xChapterNumberingRules->getByIndex(nLevel - 1) >>= props;
7182 bool bHasNumbering = false;
7183 bool bUseTabStop = false;
7184 for (const auto& propval : props)
7186 // We rely on PositionAndSpaceMode being always equal to LABEL_ALIGNMENT,
7187 // because ListDef::CreateNumberingRules doesn't create legacy lists
7188 if (propval.Name == "NumberingType")
7189 bHasNumbering = propval.Value != style::NumberingType::NUMBER_NONE;
7190 else if (propval.Name == "LabelFollowedBy")
7191 bUseTabStop = propval.Value == text::LabelFollow::LISTTAB;
7192 // Do not use FirstLineIndent property from the rules, because it is unreliable
7194 if (bHasNumbering && bUseTabStop)
7196 OUString style;
7197 xTOC->getPropertyValue("ParaStyleLevel" + OUString::number(nLevel)) >>= style;
7198 uno::Reference<beans::XPropertySet> xStyle;
7199 if (xStyles->getByName(style) >>= xStyle)
7201 if (uno::Reference<beans::XPropertyState> xPropState{ xStyle,
7202 uno::UNO_QUERY })
7204 if (xPropState->getPropertyState("ParaTabStops")
7205 == beans::PropertyState_DIRECT_VALUE)
7207 if (uno::Sequence<style::TabStop> tabStops;
7208 xStyle->getPropertyValue("ParaTabStops") >>= tabStops)
7210 // If the style only has one tab stop, Word uses it for
7211 // page number, and generates the other from defaults
7212 if (tabStops.getLength() > 1)
7213 numTab = tabStops[0];
7218 if (!numTab)
7220 // Generate the default position.
7221 // Word uses multiples of 440 twips for default chapter number tab stops
7222 numTab.emplace();
7223 numTab->Position
7224 = o3tl::convert(440 * nLevel, o3tl::Length::twip, o3tl::Length::mm100);
7229 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
7230 bHyperlinks, sChapterNoSeparator,
7231 aLevel, numTab);
7232 xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) );
7235 else // if (bTableOfFigures)
7237 if (!sFigureSequence.isEmpty())
7238 xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY),
7239 uno::Any(sFigureSequence));
7241 if (!sTemplate.isEmpty())
7243 OUString const sConvertedStyleName(ConvertTOCStyleName(sTemplate));
7244 xTOC->setPropertyValue("CreateFromParagraphStyle", uno::Any(sConvertedStyleName));
7247 if ( bHyperlinks )
7249 uno::Reference< container::XIndexReplace> xLevelFormats;
7250 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
7251 uno::Sequence< beans::PropertyValues > aLevel;
7252 xLevelFormats->getByIndex( 1 ) >>= aLevel;
7254 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
7255 bHyperlinks, sChapterNoSeparator,
7256 aLevel, {});
7257 xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) );
7262 uno::Reference<beans::XPropertySet> DomainMapper_Impl::createSectionForRange(
7263 uno::Reference< css::text::XTextRange > xStart,
7264 uno::Reference< css::text::XTextRange > xEnd,
7265 const OUString & sObjectType,
7266 bool stepLeft)
7268 if (!xStart.is())
7269 return uno::Reference<beans::XPropertySet>();
7270 if (!xEnd.is())
7271 return uno::Reference<beans::XPropertySet>();
7273 uno::Reference< beans::XPropertySet > xRet;
7274 if (m_aTextAppendStack.empty())
7275 return xRet;
7276 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
7277 if(xTextAppend.is())
7281 uno::Reference< text::XParagraphCursor > xCursor(
7282 xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY_THROW);
7283 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
7284 xCursor->gotoStartOfParagraph( false );
7285 xCursor->gotoRange( xEnd, true );
7286 //the paragraph after this new section is already inserted
7287 if (stepLeft)
7288 xCursor->goLeft(1, true);
7289 uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance(sObjectType), uno::UNO_QUERY_THROW );
7292 xSection->attach( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW) );
7294 catch(const uno::Exception&)
7297 xRet.set(xSection, uno::UNO_QUERY );
7299 catch(const uno::Exception&)
7304 return xRet;
7307 void DomainMapper_Impl::handleBibliography
7308 (const FieldContextPtr& pContext,
7309 const OUString & sTOCServiceName)
7311 if (m_aTextAppendStack.empty())
7313 // tdf#130214: a workaround to avoid crash on import errors
7314 SAL_WARN("writerfilter.dmapper", "no text append stack");
7315 return;
7317 // Create section before setting m_bStartTOC and m_bStartBibliography: finishing paragraph
7318 // inside StartIndexSectionChecked could do the wrong thing otherwise
7319 const auto xTOC = StartIndexSectionChecked(sTOCServiceName);
7320 m_bStartTOC = true;
7321 m_bStartBibliography = true;
7323 if (xTOC.is())
7324 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString()));
7326 pContext->SetTOC( xTOC );
7327 m_StreamStateStack.top().bParaHadField = false;
7329 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
7330 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
7333 void DomainMapper_Impl::handleIndex
7334 (const FieldContextPtr& pContext,
7335 const OUString & sTOCServiceName)
7337 // only UserIndex can handle user index defined by \f
7338 // e.g. INDEX \f "user-index-id"
7339 OUString sUserIndex;
7340 if ( lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) )
7341 sUserIndex = lcl_trim(sUserIndex);
7343 // Create section before setting m_bStartTOC and m_bStartIndex: finishing paragraph
7344 // inside StartIndexSectionChecked could do the wrong thing otherwise
7345 const auto xTOC = StartIndexSectionChecked( sUserIndex.isEmpty()
7346 ? sTOCServiceName
7347 : "com.sun.star.text.UserIndex");
7349 m_bStartTOC = true;
7350 m_bStartIndex = true;
7351 OUString sValue;
7352 if (xTOC.is())
7354 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString()));
7356 if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
7358 xTOC->setPropertyValue("IsCommaSeparated", uno::Any(true));
7360 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
7362 xTOC->setPropertyValue("UseAlphabeticalSeparators", uno::Any(true));
7364 if( !sUserIndex.isEmpty() )
7366 xTOC->setPropertyValue("UserIndexName", uno::Any(sUserIndex));
7369 pContext->SetTOC( xTOC );
7370 m_StreamStateStack.top().bParaHadField = false;
7372 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
7373 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
7375 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
7377 sValue = sValue.replaceAll("\"", "");
7378 uno::Reference<text::XTextColumns> xTextColumns;
7379 if (xTOC.is())
7381 xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns;
7383 if (xTextColumns.is())
7385 xTextColumns->setColumnCount( sValue.toInt32() );
7386 xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xTextColumns ) );
7391 static auto InsertFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
7392 uno::Reference<text::XFormField> const& xFormField,
7393 uno::Reference<text::XTextRange> const& xStartRange,
7394 std::optional<FieldId> const oFieldId) -> void
7396 uno::Reference<text::XTextContent> const xTextContent(xFormField, uno::UNO_QUERY_THROW);
7397 uno::Reference<text::XTextAppend> const& xTextAppend(rTextAppendStack.top().xTextAppend);
7398 uno::Reference<text::XTextCursor> const xCursor =
7399 xTextAppend->createTextCursorByRange(xStartRange);
7400 if (rTextAppendStack.top().xInsertPosition.is())
7402 uno::Reference<text::XTextRangeCompare> const xCompare(
7403 rTextAppendStack.top().xTextAppend,
7404 uno::UNO_QUERY);
7405 if (xCompare->compareRegionStarts(xStartRange, rTextAppendStack.top().xInsertPosition) < 0)
7407 SAL_WARN("writerfilter.dmapper", "invalid field mark positions");
7408 assert(false);
7410 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, true);
7412 else
7414 xCursor->gotoEnd(true);
7416 xTextAppend->insertTextContent(xCursor, xTextContent, true);
7417 if (oFieldId
7418 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
7420 return; // only a single CH_TXT_ATR_FORMELEMENT!
7422 // problem: the fieldmark must be inserted in CloseFieldCommand(), because
7423 // attach() takes 2 positions, not 3!
7424 // FAIL: AppendTextNode() ignores the content index!
7425 // plan B: insert a spurious paragraph break now and join
7426 // it in PopFieldContext()!
7427 xCursor->gotoRange(xTextContent->getAnchor()->getEnd(), false);
7428 xCursor->goLeft(1, false); // skip CH_TXT_ATR_FIELDEND
7429 xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::PARAGRAPH_BREAK, false);
7430 xCursor->goLeft(1, false); // back to previous paragraph
7431 rTextAppendStack.push(TextAppendContext(xTextAppend, xCursor));
7434 static auto PopFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
7435 uno::Reference<text::XTextCursor> const& xCursor,
7436 std::optional<FieldId> const oFieldId) -> void
7438 if (oFieldId
7439 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
7441 return; // only a single CH_TXT_ATR_FORMELEMENT!
7443 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, false);
7444 xCursor->goRight(1, true);
7445 xCursor->setString(OUString()); // undo SplitNode from CloseFieldCommand()
7446 // note: paragraph properties will be overwritten
7447 // by finishParagraph() anyway so ignore here
7448 rTextAppendStack.pop();
7451 void DomainMapper_Impl::CloseFieldCommand()
7453 if(m_bDiscardHeaderFooter)
7454 return;
7455 #ifdef DBG_UTIL
7456 TagLogger::getInstance().element("closeFieldCommand");
7457 #endif
7459 FieldContextPtr pContext;
7460 if(!m_aFieldStack.empty())
7461 pContext = m_aFieldStack.back();
7462 OSL_ENSURE( pContext, "no field context available");
7463 if( !pContext )
7464 return;
7466 m_bSetUserFieldContent = false;
7467 m_bSetCitation = false;
7468 m_bSetDateValue = false;
7469 // tdf#124472: If the normal command line is not empty, use it,
7470 // otherwise, the last active row is evaluated.
7471 if (!pContext->GetCommandIsEmpty(false))
7472 pContext->SetCommandType(false);
7474 const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion();
7478 uno::Reference< uno::XInterface > xFieldInterface;
7480 const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) };
7481 (void)vSwitches;
7482 OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front());
7484 // apply character properties to the form control
7485 if (!m_aTextAppendStack.empty() && m_pLastCharacterContext)
7487 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
7488 if (xTextAppend.is())
7490 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
7491 if (xCrsr.is())
7493 xCrsr->gotoEnd(false);
7494 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
7495 for (auto& rPropValue : m_pLastCharacterContext->GetPropertyValues(false))
7499 xProp->setPropertyValue(rPropValue.Name, rPropValue.Value);
7501 catch(uno::Exception&)
7503 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Unknown Field PropVal");
7510 FieldConversionMap_t::const_iterator const aIt = aFieldConversionMap.find(sType);
7511 if (aIt != aFieldConversionMap.end()
7512 && (!m_bForceGenericFields
7513 // these need to convert ffData to properties...
7514 || (aIt->second.eFieldId == FIELD_FORMCHECKBOX)
7515 || (aIt->second.eFieldId == FIELD_FORMDROPDOWN)
7516 || (aIt->second.eFieldId == FIELD_FORMTEXT)))
7518 pContext->SetFieldId(aIt->second.eFieldId);
7519 bool bCreateEnhancedField = false;
7520 uno::Reference< beans::XPropertySet > xFieldProperties;
7521 bool bCreateField = true;
7522 switch (aIt->second.eFieldId)
7524 case FIELD_HYPERLINK:
7525 case FIELD_DOCPROPERTY:
7526 case FIELD_TOC:
7527 case FIELD_INDEX:
7528 case FIELD_XE:
7529 case FIELD_BIBLIOGRAPHY:
7530 case FIELD_CITATION:
7531 case FIELD_TC:
7532 case FIELD_EQ:
7533 case FIELD_INCLUDEPICTURE:
7534 case FIELD_SYMBOL:
7535 case FIELD_GOTOBUTTON:
7536 bCreateField = false;
7537 break;
7538 case FIELD_FORMCHECKBOX :
7539 case FIELD_FORMTEXT :
7540 case FIELD_FORMDROPDOWN :
7542 // If we use 'enhanced' fields then FIELD_FORMCHECKBOX,
7543 // FIELD_FORMTEXT & FIELD_FORMDROPDOWN are treated specially
7544 if ( m_bUsingEnhancedFields )
7546 bCreateField = false;
7547 bCreateEnhancedField = true;
7549 // for non enhanced fields checkboxes are displayed
7550 // as an awt control not a field
7551 else if ( aIt->second.eFieldId == FIELD_FORMCHECKBOX )
7552 bCreateField = false;
7553 break;
7555 default:
7557 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
7558 if (pOuter)
7560 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
7562 // Parent field can't host this child field: don't create a child field
7563 // in this case.
7564 bCreateField = false;
7567 break;
7570 if (IsInTOC() && (aIt->second.eFieldId == FIELD_PAGEREF))
7572 bCreateField = false;
7575 if( bCreateField || bCreateEnhancedField )
7577 //add the service prefix
7578 OUString sServiceName("com.sun.star.text.");
7579 if ( bCreateEnhancedField )
7581 const FieldConversionMap_t& aEnhancedFieldConversionMap = lcl_GetEnhancedFieldConversion();
7582 FieldConversionMap_t::const_iterator aEnhancedIt =
7583 aEnhancedFieldConversionMap.find(sType);
7584 if ( aEnhancedIt != aEnhancedFieldConversionMap.end())
7585 sServiceName += OUString::createFromAscii(aEnhancedIt->second.cFieldServiceName );
7587 else
7589 sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName );
7592 #ifdef DBG_UTIL
7593 TagLogger::getInstance().startElement("fieldService");
7594 TagLogger::getInstance().chars(sServiceName);
7595 TagLogger::getInstance().endElement();
7596 #endif
7598 if (m_xTextFactory.is())
7600 xFieldInterface = m_xTextFactory->createInstance(sServiceName);
7601 xFieldProperties.set( xFieldInterface, uno::UNO_QUERY_THROW);
7604 switch( aIt->second.eFieldId )
7606 case FIELD_ADDRESSBLOCK: break;
7607 case FIELD_ADVANCE : break;
7608 case FIELD_ASK :
7609 handleFieldAsk(pContext, xFieldInterface, xFieldProperties);
7610 break;
7611 case FIELD_AUTONUM :
7612 case FIELD_AUTONUMLGL :
7613 case FIELD_AUTONUMOUT :
7614 handleAutoNum(pContext, xFieldInterface, xFieldProperties);
7615 break;
7616 case FIELD_AUTHOR :
7617 case FIELD_USERNAME :
7618 case FIELD_USERINITIALS :
7619 handleAuthor(sFirstParam,
7620 xFieldProperties,
7621 aIt->second.eFieldId);
7622 break;
7623 case FIELD_DATE:
7624 if (xFieldProperties.is())
7626 // Get field fixed property from the context handler
7627 if (pContext->IsFieldLocked())
7629 xFieldProperties->setPropertyValue(
7630 getPropertyName(PROP_IS_FIXED),
7631 uno::Any( true ));
7632 m_bSetDateValue = true;
7634 else
7635 xFieldProperties->setPropertyValue(
7636 getPropertyName(PROP_IS_FIXED),
7637 uno::Any( false ));
7639 xFieldProperties->setPropertyValue(
7640 getPropertyName(PROP_IS_DATE),
7641 uno::Any( true ));
7642 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7644 break;
7645 case FIELD_COMMENTS :
7647 // OUString sParam = lcl_ExtractParameter(pContext->GetCommand(), sizeof(" COMMENTS") );
7648 // A parameter with COMMENTS shouldn't set fixed
7649 // ( or at least the binary filter doesn't )
7650 // If we set fixed then we won't export a field cmd.
7651 // Additionally the para in COMMENTS is more like an
7652 // instruction to set the document property comments
7653 // with the param ( e.g. each COMMENT with a param will
7654 // overwrite the Comments document property
7655 // #TODO implement the above too
7656 xFieldProperties->setPropertyValue(
7657 getPropertyName( PROP_IS_FIXED ), uno::Any( false ));
7658 //PROP_CURRENT_PRESENTATION is set later anyway
7660 break;
7661 case FIELD_CREATEDATE :
7662 case FIELD_PRINTDATE:
7663 case FIELD_SAVEDATE:
7665 if (pContext->IsFieldLocked())
7667 xFieldProperties->setPropertyValue(
7668 getPropertyName(PROP_IS_FIXED), uno::Any( true ));
7670 xFieldProperties->setPropertyValue(
7671 getPropertyName( PROP_IS_DATE ), uno::Any( true ));
7672 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7674 break;
7675 case FIELD_DOCPROPERTY :
7676 handleDocProperty(pContext, sFirstParam,
7677 xFieldInterface);
7678 break;
7679 case FIELD_DOCVARIABLE :
7681 if (bCreateField)
7683 //create a user field and type
7684 uno::Reference<beans::XPropertySet> xMaster = FindOrCreateFieldMaster(
7685 "com.sun.star.text.FieldMaster.User", sFirstParam);
7686 uno::Reference<text::XDependentTextField> xDependentField(
7687 xFieldInterface, uno::UNO_QUERY_THROW);
7688 xDependentField->attachTextFieldMaster(xMaster);
7689 m_bSetUserFieldContent = true;
7692 break;
7693 case FIELD_EDITTIME :
7694 //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldProperties );
7695 break;
7696 case FIELD_EQ:
7698 OUString aCommand = pContext->GetCommand().trim();
7700 msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand));
7701 if (!aResult.sType.isEmpty() && m_xTextFactory.is())
7703 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField." + aResult.sType);
7704 xFieldProperties =
7705 uno::Reference< beans::XPropertySet >( xFieldInterface,
7706 uno::UNO_QUERY_THROW);
7707 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(aResult.sResult));
7709 else
7711 //merge Read_SubF_Ruby into filter/.../util.cxx and reuse that ?
7712 sal_Int32 nSpaceIndex = aCommand.indexOf(' ');
7713 if(nSpaceIndex > 0)
7714 aCommand = o3tl::trim(aCommand.subView(nSpaceIndex));
7715 if (aCommand.startsWith("\\s"))
7717 aCommand = aCommand.copy(2);
7718 if (aCommand.startsWith("\\do"))
7720 aCommand = aCommand.copy(3);
7721 sal_Int32 nStartIndex = aCommand.indexOf('(');
7722 sal_Int32 nEndIndex = aCommand.indexOf(')');
7723 if (nStartIndex > 0 && nEndIndex > 0)
7725 // nDown is the requested "lower by" value in points.
7726 sal_Int32 nDown = o3tl::toInt32(aCommand.subView(0, nStartIndex));
7727 OUString aContent = aCommand.copy(nStartIndex + 1, nEndIndex - nStartIndex - 1);
7728 PropertyMapPtr pCharContext = GetTopContext();
7729 // dHeight is the font size of the current style.
7730 double dHeight = 0;
7731 if ((GetPropertyFromParaStyleSheet(PROP_CHAR_HEIGHT) >>= dHeight) && dHeight != 0)
7732 // Character escapement should be given in negative percents for subscripts.
7733 pCharContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( sal_Int16(- 100 * nDown / dHeight) ) );
7734 appendTextPortion(aContent, pCharContext);
7738 else if (aCommand.startsWith("\\* jc"))
7740 handleRubyEQField(pContext);
7744 break;
7745 case FIELD_FILLIN :
7746 if (xFieldProperties.is())
7747 xFieldProperties->setPropertyValue(
7748 getPropertyName(PROP_HINT), uno::Any( pContext->GetCommand().getToken(1, '\"')));
7749 break;
7750 case FIELD_FILENAME:
7752 sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p");
7753 if (xFieldProperties.is())
7754 xFieldProperties->setPropertyValue(
7755 getPropertyName(PROP_FILE_FORMAT),
7756 uno::Any( nNumberingTypeIndex > 0 ? text::FilenameDisplayFormat::FULL : text::FilenameDisplayFormat::NAME_AND_EXT ));
7758 break;
7759 case FIELD_FILESIZE : break;
7760 case FIELD_FORMULA :
7761 if (bCreateField)
7763 handleFieldFormula(pContext, xFieldProperties);
7765 break;
7766 case FIELD_FORMCHECKBOX :
7767 case FIELD_FORMDROPDOWN :
7768 case FIELD_FORMTEXT :
7770 if (bCreateEnhancedField)
7772 FFDataHandler::Pointer_t
7773 pFFDataHandler(pContext->getFFDataHandler());
7774 FormControlHelper::Pointer_t
7775 pFormControlHelper(new FormControlHelper
7776 (m_bUsingEnhancedFields ? aIt->second.eFieldId : FIELD_FORMCHECKBOX,
7778 m_xTextDocument, pFFDataHandler));
7779 pContext->setFormControlHelper(pFormControlHelper);
7780 uno::Reference< text::XFormField > xFormField( xFieldInterface, uno::UNO_QUERY );
7781 uno::Reference< container::XNamed > xNamed( xFormField, uno::UNO_QUERY );
7782 if ( xNamed.is() )
7784 if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() )
7785 xNamed->setName( pFFDataHandler->getName() );
7786 pContext->SetFormField( xFormField );
7788 InsertFieldmark(m_aTextAppendStack,
7789 xFormField, pContext->GetStartRange(),
7790 pContext->GetFieldId());
7792 else
7794 if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN )
7795 lcl_handleDropdownField( xFieldProperties, pContext->getFFDataHandler() );
7796 else
7797 lcl_handleTextField( xFieldProperties, pContext->getFFDataHandler() );
7800 break;
7801 case FIELD_GOTOBUTTON : break;
7802 case FIELD_HYPERLINK:
7804 ::std::vector<OUString> aParts = pContext->GetCommandParts();
7806 // Syntax is either:
7807 // HYPERLINK "" \l "link"
7808 // or
7809 // HYPERLINK \l "link"
7810 // Make sure "HYPERLINK" doesn't end up as part of link in the second case.
7811 if (!aParts.empty() && aParts[0] == "HYPERLINK")
7812 aParts.erase(aParts.begin());
7814 ::std::vector<OUString>::const_iterator aItEnd = aParts.end();
7815 ::std::vector<OUString>::const_iterator aPartIt = aParts.begin();
7817 OUString sURL;
7818 OUString sTarget;
7820 while (aPartIt != aItEnd)
7822 if ( *aPartIt == "\\l" )
7824 ++aPartIt;
7826 if (aPartIt == aItEnd)
7827 break;
7829 sURL += "#" + *aPartIt;
7831 else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h")
7834 else if ( *aPartIt == "\\o" || *aPartIt == "\\t" )
7836 ++aPartIt;
7838 if (aPartIt == aItEnd)
7839 break;
7841 sTarget = *aPartIt;
7843 else
7845 sURL = *aPartIt;
7848 ++aPartIt;
7851 if (!sURL.isEmpty())
7853 if (sURL.startsWith("file:///"))
7855 // file:///absolute\\path\\to\\file => invalid file URI (Writer cannot open)
7856 // convert all double backslashes to slashes:
7857 sURL = sURL.replaceAll("\\\\", "/");
7859 // file:///absolute\path\to\file => invalid file URI (Writer cannot open)
7860 // convert all backslashes to slashes:
7861 sURL = sURL.replace('\\', '/');
7863 // Try to make absolute any relative URLs, except
7864 // for relative same-document URLs that only contain
7865 // a fragment part:
7866 else if (!sURL.startsWith("#")) {
7867 try {
7868 sURL = rtl::Uri::convertRelToAbs(
7869 m_aBaseUrl, sURL);
7870 } catch (rtl::MalformedUriException & e) {
7871 SAL_WARN(
7872 "writerfilter.dmapper",
7873 "MalformedUriException "
7874 << e.getMessage());
7877 pContext->SetHyperlinkURL(sURL);
7880 if (!sTarget.isEmpty())
7881 pContext->SetHyperlinkTarget(sTarget);
7883 break;
7884 case FIELD_IF:
7886 if (vArguments.size() < 3)
7888 SAL_WARN("writerfilter.dmapper", "IF field requires at least 3 parameters!");
7889 break;
7892 if (xFieldProperties.is())
7894 // Following code assumes that last argument in field is false value
7895 // before it - true value and everything before them is a condition
7896 OUString sCondition;
7897 size_t i = 0;
7898 while (i < vArguments.size() - 2) {
7899 if (!sCondition.isEmpty())
7900 sCondition += " ";
7901 sCondition += vArguments[i++];
7904 xFieldProperties->setPropertyValue(
7905 "TrueContent", uno::Any(vArguments[vArguments.size() - 2]));
7906 xFieldProperties->setPropertyValue(
7907 "FalseContent", uno::Any(vArguments[vArguments.size() - 1]));
7908 xFieldProperties->setPropertyValue(
7909 "Condition", uno::Any(sCondition));
7912 break;
7913 case FIELD_INFO : break;
7914 case FIELD_INCLUDEPICTURE: break;
7915 case FIELD_KEYWORDS :
7917 if (!sFirstParam.isEmpty())
7919 xFieldProperties->setPropertyValue(
7920 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
7921 //PROP_CURRENT_PRESENTATION is set later anyway
7924 break;
7925 case FIELD_LASTSAVEDBY :
7926 xFieldProperties->setPropertyValue(
7927 getPropertyName(PROP_IS_FIXED), uno::Any(true));
7928 break;
7929 case FIELD_MACROBUTTON:
7931 if (xFieldProperties.is())
7933 sal_Int32 nIndex = sizeof(" MACROBUTTON ");
7934 OUString sCommand = pContext->GetCommand();
7936 //extract macro name
7937 if (sCommand.getLength() >= nIndex)
7939 OUString sMacro = sCommand.getToken(0, ' ', nIndex);
7940 xFieldProperties->setPropertyValue(
7941 getPropertyName(PROP_MACRO_NAME), uno::Any( sMacro ));
7944 //extract quick help text
7945 if (sCommand.getLength() > nIndex + 1)
7947 xFieldProperties->setPropertyValue(
7948 getPropertyName(PROP_HINT),
7949 uno::Any( sCommand.copy( nIndex )));
7953 break;
7954 case FIELD_MERGEFIELD :
7956 //todo: create a database field and fieldmaster pointing to a column, only
7957 //create a user field and type
7958 uno::Reference< beans::XPropertySet > xMaster =
7959 FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam);
7961 // xFieldProperties->setPropertyValue(
7962 // "FieldCode",
7963 // uno::makeAny( pContext->GetCommand().copy( nIndex + 1 )));
7964 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
7965 xDependentField->attachTextFieldMaster( xMaster );
7967 break;
7968 case FIELD_MERGEREC : break;
7969 case FIELD_MERGESEQ : break;
7970 case FIELD_NEXT : break;
7971 case FIELD_NEXTIF : break;
7972 case FIELD_PAGE :
7973 if (xFieldProperties.is())
7975 xFieldProperties->setPropertyValue(
7976 getPropertyName(PROP_NUMBERING_TYPE),
7977 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
7978 xFieldProperties->setPropertyValue(
7979 getPropertyName(PROP_SUB_TYPE),
7980 uno::Any( text::PageNumberType_CURRENT ));
7983 break;
7984 case FIELD_PAGEREF:
7985 case FIELD_REF:
7986 case FIELD_STYLEREF:
7987 if (xFieldProperties.is() && !IsInTOC())
7989 bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF;
7990 bool bStyleRef = aIt->second.eFieldId == FIELD_STYLEREF;
7992 // Do we need a GetReference (default) or a GetExpression field?
7993 uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY );
7994 uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters();
7996 if (!xFieldMasterAccess->hasByName(
7997 "com.sun.star.text.FieldMaster.SetExpression."
7998 + sFirstParam))
8000 if (bStyleRef)
8002 xFieldProperties->setPropertyValue(
8003 getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
8004 uno::Any(sal_Int16(text::ReferenceFieldSource::STYLE)));
8006 uno::Any aStyleDisplayName;
8007 aStyleDisplayName <<= ConvertTOCStyleName(sFirstParam);
8009 xFieldProperties->setPropertyValue(
8010 getPropertyName(PROP_SOURCE_NAME), aStyleDisplayName);
8012 sal_uInt16 nFlags = 0;
8013 OUString sValue;
8014 if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
8016 //search-below-first
8017 nFlags |= REFFLDFLAG_STYLE_FROM_BOTTOM;
8019 if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
8021 //suppress-nondelimiter
8022 nFlags |= REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL;
8024 xFieldProperties->setPropertyValue(
8025 getPropertyName( PROP_REFERENCE_FIELD_FLAGS ), uno::Any(nFlags) );
8027 else
8029 xFieldProperties->setPropertyValue(
8030 getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
8031 uno::Any( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) );
8033 xFieldProperties->setPropertyValue(
8034 getPropertyName(PROP_SOURCE_NAME),
8035 uno::Any(sFirstParam));
8038 sal_Int16 nFieldPart = (bPageRef ? text::ReferenceFieldPart::PAGE : text::ReferenceFieldPart::TEXT);
8039 OUString sValue;
8040 if( lcl_FindInCommand( pContext->GetCommand(), 'p', sValue ))
8042 //above-below
8043 nFieldPart = text::ReferenceFieldPart::UP_DOWN;
8045 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
8047 //number
8048 nFieldPart = text::ReferenceFieldPart::NUMBER;
8050 else if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
8052 //number-no-context
8053 nFieldPart = text::ReferenceFieldPart::NUMBER_NO_CONTEXT;
8055 else if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
8057 //number-full-context
8058 nFieldPart = text::ReferenceFieldPart::NUMBER_FULL_CONTEXT;
8060 xFieldProperties->setPropertyValue(
8061 getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::Any( nFieldPart ));
8063 else if( m_xTextFactory.is() )
8065 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField.GetExpression");
8066 xFieldProperties.set(xFieldInterface, uno::UNO_QUERY);
8067 xFieldProperties->setPropertyValue(
8068 getPropertyName(PROP_CONTENT),
8069 uno::Any(sFirstParam));
8070 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
8073 break;
8074 case FIELD_REVNUM : break;
8075 case FIELD_SECTION : break;
8076 case FIELD_SECTIONPAGES : break;
8077 case FIELD_SEQ :
8079 // command looks like: " SEQ Table \* ARABIC "
8080 OUString sCmd(pContext->GetCommand());
8081 // find the sequence name, e.g. "SEQ"
8082 std::u16string_view sSeqName = msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\');
8083 sSeqName = o3tl::trim(sSeqName);
8085 // create a sequence field master using the sequence name
8086 uno::Reference< beans::XPropertySet > xMaster = FindOrCreateFieldMaster(
8087 "com.sun.star.text.FieldMaster.SetExpression",
8088 OUString(sSeqName));
8090 xMaster->setPropertyValue(
8091 getPropertyName(PROP_SUB_TYPE),
8092 uno::Any(text::SetVariableType::SEQUENCE));
8094 // apply the numbering type
8095 xFieldProperties->setPropertyValue(
8096 getPropertyName(PROP_NUMBERING_TYPE),
8097 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
8099 // attach the master to the field
8100 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
8101 xDependentField->attachTextFieldMaster( xMaster );
8103 OUString sFormula = OUString::Concat(sSeqName) + "+1";
8104 OUString sValue;
8105 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
8107 sFormula = sSeqName;
8109 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
8111 sFormula = sValue;
8113 // TODO \s isn't handled, but the spec isn't easy to understand without
8114 // an example for this one.
8115 xFieldProperties->setPropertyValue(
8116 getPropertyName(PROP_CONTENT),
8117 uno::Any(sFormula));
8119 // Take care of the numeric formatting definition, default is Arabic
8120 sal_Int16 nNumberingType = lcl_ParseNumberingType(pContext->GetCommand());
8121 if (nNumberingType == style::NumberingType::PAGE_DESCRIPTOR)
8122 nNumberingType = style::NumberingType::ARABIC;
8123 xFieldProperties->setPropertyValue(
8124 getPropertyName(PROP_NUMBERING_TYPE),
8125 uno::Any(nNumberingType));
8127 break;
8128 case FIELD_SET :
8129 handleFieldSet(pContext, xFieldInterface, xFieldProperties);
8130 break;
8131 case FIELD_SKIPIF : break;
8132 case FIELD_SUBJECT :
8134 if (!sFirstParam.isEmpty())
8136 xFieldProperties->setPropertyValue(
8137 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
8138 //PROP_CURRENT_PRESENTATION is set later anyway
8141 break;
8142 case FIELD_SYMBOL:
8144 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8145 OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ? o3tl::toUInt32(sFirstParam.subView(2),16) : sFirstParam.toUInt32() ) );
8146 OUString sFont;
8147 bool bHasFont = lcl_FindInCommand( pContext->GetCommand(), 'f', sFont);
8148 if ( bHasFont )
8150 sFont = sFont.trim();
8151 if (sFont.startsWith("\""))
8152 sFont = sFont.copy(1);
8153 if (sFont.endsWith("\""))
8154 sFont = sFont.copy(0,sFont.getLength()-1);
8159 if (xTextAppend.is())
8161 uno::Reference< text::XText > xText = xTextAppend->getText();
8162 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
8163 if (xCrsr.is())
8165 xCrsr->gotoEnd(false);
8166 xText->insertString(xCrsr, sSymbol, true);
8167 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
8168 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::Any(awt::CharSet::SYMBOL));
8169 if(bHasFont)
8171 uno::Any aVal( sFont );
8172 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal);
8173 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal);
8174 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal);
8180 break;
8181 case FIELD_TEMPLATE: break;
8182 case FIELD_TIME :
8184 if (pContext->IsFieldLocked())
8186 xFieldProperties->setPropertyValue(
8187 getPropertyName(PROP_IS_FIXED),
8188 uno::Any( true ));
8189 m_bSetDateValue = true;
8191 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
8193 break;
8194 case FIELD_TITLE :
8196 if (!sFirstParam.isEmpty())
8198 xFieldProperties->setPropertyValue(
8199 getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
8200 //PROP_CURRENT_PRESENTATION is set later anyway
8203 break;
8204 case FIELD_USERADDRESS : //todo: user address collects street, city ...
8205 break;
8206 case FIELD_INDEX:
8207 handleIndex(pContext,
8208 OUString::createFromAscii(aIt->second.cFieldServiceName));
8209 break;
8210 case FIELD_BIBLIOGRAPHY:
8211 handleBibliography(pContext,
8212 OUString::createFromAscii(aIt->second.cFieldServiceName));
8213 break;
8214 case FIELD_TOC:
8215 handleToc(pContext,
8216 OUString::createFromAscii(aIt->second.cFieldServiceName));
8217 break;
8218 case FIELD_XE:
8220 if( !m_xTextFactory.is() )
8221 break;
8223 // only UserIndexMark can handle user index types defined by \f
8224 // e.g. XE "text" \f "user-index-id"
8225 OUString sUserIndex;
8226 OUString sFieldServiceName =
8227 lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex )
8228 ? "com.sun.star.text.UserIndexMark"
8229 : OUString::createFromAscii(aIt->second.cFieldServiceName);
8230 uno::Reference< beans::XPropertySet > xTC(
8231 m_xTextFactory->createInstance(sFieldServiceName),
8232 uno::UNO_QUERY_THROW);
8234 if (!sFirstParam.isEmpty())
8236 xTC->setPropertyValue(sUserIndex.isEmpty()
8237 ? OUString("PrimaryKey")
8238 : OUString("AlternativeText"),
8239 uno::Any(sFirstParam));
8242 sUserIndex = lcl_trim(sUserIndex);
8243 if (!sUserIndex.isEmpty())
8245 xTC->setPropertyValue("UserIndexName",
8246 uno::Any(sUserIndex));
8248 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
8249 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8250 if (xTextAppend.is())
8252 uno::Reference< text::XText > xText = xTextAppend->getText();
8253 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
8254 if (xCrsr.is())
8256 xCrsr->gotoEnd(false);
8257 xText->insertTextContent(uno::Reference< text::XTextRange >( xCrsr, uno::UNO_QUERY_THROW ), xToInsert, false);
8261 break;
8262 case FIELD_CITATION:
8264 if( !m_xTextFactory.is() )
8265 break;
8267 xFieldInterface = m_xTextFactory->createInstance(
8268 OUString::createFromAscii(aIt->second.cFieldServiceName));
8269 uno::Reference< beans::XPropertySet > xTC(xFieldInterface,
8270 uno::UNO_QUERY_THROW);
8271 OUString sCmd(pContext->GetCommand());//sCmd is the entire instrText including the index e.g. CITATION Kra06 \l 1033
8272 if( !sCmd.isEmpty()){
8273 uno::Sequence<beans::PropertyValue> aValues( comphelper::InitPropertySequence({
8274 { "Identifier", uno::Any(sCmd) }
8275 }));
8276 xTC->setPropertyValue("Fields", uno::Any(aValues));
8278 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
8280 uno::Sequence<beans::PropertyValue> aValues
8281 = m_aFieldStack.back()->getProperties()->GetPropertyValues();
8282 appendTextContent(xToInsert, aValues);
8283 m_bSetCitation = true;
8285 break;
8287 case FIELD_TC :
8289 if( !m_xTextFactory.is() )
8290 break;
8292 uno::Reference< beans::XPropertySet > xTC(
8293 m_xTextFactory->createInstance(
8294 OUString::createFromAscii(aIt->second.cFieldServiceName)),
8295 uno::UNO_QUERY_THROW);
8296 if (!sFirstParam.isEmpty())
8298 xTC->setPropertyValue(getPropertyName(PROP_ALTERNATIVE_TEXT),
8299 uno::Any(sFirstParam));
8301 OUString sValue;
8302 // \f TC entry in doc with multiple tables
8303 // if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
8304 // {
8305 // todo: unsupported
8306 // }
8307 if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
8308 // \l Outline Level
8310 sal_Int32 nLevel = sValue.toInt32();
8311 if( !sValue.isEmpty() && nLevel >= 0 && nLevel <= 10 )
8312 xTC->setPropertyValue(getPropertyName(PROP_LEVEL), uno::Any( static_cast<sal_Int16>(nLevel) ));
8314 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
8315 // \n Suppress page numbers
8316 // {
8317 //todo: unsupported feature
8318 // }
8319 pContext->SetTC( xTC );
8321 break;
8322 case FIELD_NUMCHARS:
8323 case FIELD_NUMWORDS:
8324 case FIELD_NUMPAGES:
8325 if (xFieldProperties.is())
8326 xFieldProperties->setPropertyValue(
8327 getPropertyName(PROP_NUMBERING_TYPE),
8328 uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
8329 break;
8332 if (!bCreateEnhancedField)
8334 pContext->SetTextField( uno::Reference<text::XTextField>(xFieldInterface, uno::UNO_QUERY) );
8337 else
8339 /* Unsupported fields will be handled here for docx file.
8340 * To handle unsupported fields used fieldmark API.
8342 OUString aCode( pContext->GetCommand().trim() );
8343 // Don't waste resources on wrapping shapes inside a fieldmark.
8344 if (sType != "SHAPE" && m_xTextFactory.is() && !m_aTextAppendStack.empty())
8346 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.Fieldmark");
8348 uno::Reference<text::XFormField> const xFormField(xFieldInterface, uno::UNO_QUERY);
8349 InsertFieldmark(m_aTextAppendStack, xFormField, pContext->GetStartRange(),
8350 pContext->GetFieldId());
8351 xFormField->setFieldType(ODF_UNHANDLED);
8352 ++m_nStartGenericField;
8353 pContext->SetFormField( xFormField );
8354 uno::Reference<container::XNameContainer> const xNameCont(xFormField->getParameters());
8355 // note: setting the code to empty string is *required* in
8356 // m_bForceGenericFields mode, or the export will write
8357 // the ODF_UNHANDLED string!
8358 assert(!m_bForceGenericFields || aCode.isEmpty());
8359 xNameCont->insertByName(ODF_CODE_PARAM, uno::Any(aCode));
8360 ww::eField const id(GetWW8FieldId(sType));
8361 if (id != ww::eNONE)
8362 { // tdf#129247 tdf#134264 set WW8 id for WW8 export
8363 xNameCont->insertByName(ODF_ID_PARAM, uno::Any(OUString::number(id)));
8366 else
8367 m_StreamStateStack.top().bParaHadField = false;
8370 catch( const uno::Exception& )
8372 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Exception in CloseFieldCommand()" );
8374 pContext->SetCommandCompleted();
8376 /*-------------------------------------------------------------------------
8377 //the _current_ fields require a string type result while TOCs accept richt results
8378 -----------------------------------------------------------------------*/
8379 bool DomainMapper_Impl::IsFieldResultAsString()
8381 bool bRet = false;
8382 OSL_ENSURE( !m_aFieldStack.empty(), "field stack empty?");
8383 FieldContextPtr pContext = m_aFieldStack.back();
8384 OSL_ENSURE( pContext, "no field context available");
8385 if( pContext )
8387 bRet = pContext->GetTextField().is()
8388 || pContext->GetFieldId() == FIELD_FORMDROPDOWN
8389 || pContext->GetFieldId() == FIELD_FILLIN;
8392 if (!bRet)
8394 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
8395 if (pOuter)
8397 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
8399 // If nesting is not allowed, then the result can only be a string.
8400 bRet = true;
8404 return bRet;
8407 void DomainMapper_Impl::AppendFieldResult(std::u16string_view rString)
8409 assert(!m_aFieldStack.empty());
8410 FieldContextPtr pContext = m_aFieldStack.back();
8411 SAL_WARN_IF(!pContext, "writerfilter.dmapper", "no field context");
8412 if (!pContext)
8413 return;
8415 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
8416 if (pOuter)
8418 if (!IsFieldNestingAllowed(pOuter, pContext))
8420 if (pOuter->IsCommandCompleted())
8422 // Child can't host the field result, forward to parent's result.
8423 pOuter->AppendResult(rString);
8425 return;
8429 pContext->AppendResult(rString);
8432 // Calculates css::DateTime based on ddddd.sssss since 1899-12-30
8433 static util::DateTime lcl_dateTimeFromSerial(const double& dSerial)
8435 DateTime d(Date(30, 12, 1899));
8436 d.AddTime(dSerial);
8437 return d.GetUNODateTime();
8440 void DomainMapper_Impl::SetFieldResult(OUString const& rResult)
8442 #ifdef DBG_UTIL
8443 TagLogger::getInstance().startElement("setFieldResult");
8444 TagLogger::getInstance().chars(rResult);
8445 #endif
8447 FieldContextPtr pContext = m_aFieldStack.back();
8448 OSL_ENSURE( pContext, "no field context available");
8450 if (m_aFieldStack.size() > 1)
8452 // This is a nested field. See if the parent supports nesting on the Writer side.
8453 FieldContextPtr pParentContext = m_aFieldStack[m_aFieldStack.size() - 2];
8454 if (pParentContext)
8456 std::vector<OUString> aParentParts = pParentContext->GetCommandParts();
8457 // Conditional text fields don't support nesting in Writer.
8458 if (!aParentParts.empty() && aParentParts[0] == "IF")
8460 return;
8465 if( !pContext )
8466 return;
8468 uno::Reference<text::XTextField> xTextField = pContext->GetTextField();
8471 OSL_ENSURE( xTextField.is()
8472 //||m_xTOC.is() ||m_xTC.is()
8473 //||m_sHyperlinkURL.getLength()
8474 , "DomainMapper_Impl::SetFieldResult: field not created" );
8475 if(xTextField.is())
8479 if( m_bSetUserFieldContent )
8481 // user field content has to be set at the field master
8482 uno::Reference< text::XDependentTextField > xDependentField( xTextField, uno::UNO_QUERY_THROW );
8483 xDependentField->getTextFieldMaster()->setPropertyValue(
8484 getPropertyName(PROP_CONTENT),
8485 uno::Any( rResult ));
8487 else if ( m_bSetCitation )
8490 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8491 // In case of SetExpression, the field result contains the content of the variable.
8492 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
8494 bool bIsSetbiblio = xServiceInfo->supportsService("com.sun.star.text.TextField.Bibliography");
8495 if( bIsSetbiblio )
8497 uno::Any aProperty = xFieldProperties->getPropertyValue("Fields");
8498 uno::Sequence<beans::PropertyValue> aValues ;
8499 aProperty >>= aValues;
8500 beans::PropertyValue propertyVal;
8501 sal_Int32 nTitleFoundIndex = -1;
8502 for (sal_Int32 i = 0; i < aValues.getLength(); ++i)
8504 propertyVal = aValues[i];
8505 if (propertyVal.Name == "Title")
8507 nTitleFoundIndex = i;
8508 break;
8511 if (nTitleFoundIndex != -1)
8513 OUString titleStr;
8514 uno::Any aValue(propertyVal.Value);
8515 aValue >>= titleStr;
8516 titleStr += rResult;
8517 propertyVal.Value <<= titleStr;
8518 aValues.getArray()[nTitleFoundIndex] = propertyVal;
8520 else
8522 aValues.realloc(aValues.getLength() + 1);
8523 propertyVal.Name = "Title";
8524 propertyVal.Value <<= rResult;
8525 aValues.getArray()[aValues.getLength() - 1] = propertyVal;
8527 xFieldProperties->setPropertyValue("Fields",
8528 uno::Any(aValues));
8531 else if ( m_bSetDateValue )
8533 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW );
8535 uno::Reference<util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
8536 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
8537 sal_Int32 nKey = 0;
8539 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8541 xFieldProperties->getPropertyValue( "NumberFormat" ) >>= nKey;
8542 xFieldProperties->setPropertyValue(
8543 "DateTimeValue",
8544 uno::Any( lcl_dateTimeFromSerial( xFormatter->convertStringToNumber( nKey, rResult ) ) ) );
8546 else
8548 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
8549 // In case of SetExpression, and Input fields the field result contains the content of the variable.
8550 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
8551 // there are fields with a content property, which aren't working correctly with
8552 // a generalized try catch of the content, property, so just restrict content
8553 // handling to these explicit services.
8554 const bool bHasContent = xServiceInfo->supportsService("com.sun.star.text.TextField.SetExpression") ||
8555 xServiceInfo->supportsService("com.sun.star.text.TextField.Input");
8556 // If we already have content set, then use the current presentation
8557 OUString sValue;
8558 if (bHasContent)
8560 // this will throw for field types without Content
8561 uno::Any aValue(xFieldProperties->getPropertyValue(
8562 getPropertyName(PROP_CONTENT)));
8563 aValue >>= sValue;
8565 xFieldProperties->setPropertyValue(
8566 getPropertyName(bHasContent && sValue.isEmpty()? PROP_CONTENT : PROP_CURRENT_PRESENTATION),
8567 uno::Any( rResult ));
8569 // LO always automatically updates a DocInfo field from the File-Properties-Custom Prop
8570 // while MS Word requires the user to manually refresh the field (with F9).
8571 // In other words, Word lets the field to be out of sync with the controlling variable.
8572 // Marking as FIXEDFLD solves the automatic replacement problem, but of course prevents
8573 // Writer from making any changes, even on an F9 refresh.
8574 OUString sVariable = pContext->GetVariableValue();
8575 if (rResult.getLength() != sVariable.getLength())
8577 sal_Int32 nLen = sVariable.indexOf('\x0');
8578 if (nLen >= 0)
8579 sVariable = sVariable.copy(0, nLen);
8581 bool bCustomFixedField = rResult != sVariable &&
8582 xServiceInfo->supportsService("com.sun.star.text.TextField.DocInfo.Custom");
8584 if (bCustomFixedField || xServiceInfo->supportsService(
8585 "com.sun.star.text.TextField.DocInfo.CreateDateTime"))
8587 // Creation time is const, don't try to update it.
8588 xFieldProperties->setPropertyValue("IsFixed", uno::Any(true));
8592 catch( const beans::UnknownPropertyException& )
8594 //some fields don't have a CurrentPresentation (DateTime)
8598 catch (const uno::Exception&)
8600 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "DomainMapper_Impl::SetFieldResult");
8604 void DomainMapper_Impl::SetFieldFFData(const FFDataHandler::Pointer_t& pFFDataHandler)
8606 #ifdef DBG_UTIL
8607 TagLogger::getInstance().startElement("setFieldFFData");
8608 #endif
8610 if (!m_aFieldStack.empty())
8612 FieldContextPtr pContext = m_aFieldStack.back();
8613 if (pContext)
8615 pContext->setFFDataHandler(pFFDataHandler);
8619 #ifdef DBG_UTIL
8620 TagLogger::getInstance().endElement();
8621 #endif
8624 void DomainMapper_Impl::PopFieldContext()
8626 if(m_bDiscardHeaderFooter)
8627 return;
8628 #ifdef DBG_UTIL
8629 TagLogger::getInstance().element("popFieldContext");
8630 #endif
8632 if (m_aFieldStack.empty())
8633 return;
8635 FieldContextPtr pContext = m_aFieldStack.back();
8636 OSL_ENSURE( pContext, "no field context available");
8637 if( pContext )
8639 if( !pContext->IsCommandCompleted() )
8640 CloseFieldCommand();
8642 if (!pContext->GetResult().isEmpty())
8644 uno::Reference< beans::XPropertySet > xFieldProperties = pContext->GetCustomField();
8645 if(xFieldProperties.is())
8646 SetNumberFormat( pContext->GetResult(), xFieldProperties, true );
8647 SetFieldResult( pContext->GetResult() );
8650 //insert the field, TC or TOC
8651 uno::Reference< text::XTextAppend > xTextAppend;
8652 if (!m_aTextAppendStack.empty())
8653 xTextAppend = m_aTextAppendStack.top().xTextAppend;
8654 if(xTextAppend.is())
8658 uno::Reference< text::XTextContent > xToInsert( pContext->GetTOC(), uno::UNO_QUERY );
8659 if( xToInsert.is() )
8661 if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography)
8663 // inside SDT, last empty paragraph is also part of index
8664 if (!m_StreamStateStack.top().bParaChanged && !m_xSdtEntryStart)
8666 // End of index is the first item on a new paragraph - this paragraph
8667 // should not be part of index
8668 auto xCursor
8669 = xTextAppend->createTextCursorByRange(
8670 m_aTextAppendStack.top().xInsertPosition.is()
8671 ? m_aTextAppendStack.top().xInsertPosition
8672 : xTextAppend->getEnd());
8673 xCursor->goLeft(1, true);
8674 // delete
8675 xCursor->setString(OUString());
8676 // But a new paragraph should be started after the index instead
8677 if (m_bIsNewDoc) // this check - see testTdf129402
8678 { // where finishParagraph inserts between 2 EndNode
8679 xTextAppend->finishParagraph(css::beans::PropertyValues());
8681 else
8683 xTextAppend->finishParagraphInsert(css::beans::PropertyValues(),
8684 m_aTextAppendStack.top().xInsertPosition);
8687 m_bStartedTOC = false;
8688 m_aTextAppendStack.pop();
8689 m_StreamStateStack.top().bTextInserted = false;
8690 m_StreamStateStack.top().bParaChanged = true; // the paragraph must stay anyway
8692 m_bStartTOC = false;
8693 m_bStartIndex = false;
8694 m_bStartBibliography = false;
8695 if (IsInHeaderFooter() && m_bStartTOCHeaderFooter)
8696 m_bStartTOCHeaderFooter = false;
8698 else
8700 xToInsert.set(pContext->GetTC(), uno::UNO_QUERY);
8701 if (!xToInsert.is() && !IsInTOC() && !m_bStartIndex && !m_bStartBibliography)
8702 xToInsert = pContext->GetTextField();
8703 if (xToInsert.is() && !IsInTOC() && !m_bStartIndex && !m_bStartBibliography)
8705 PropertyMap aMap;
8706 // Character properties of the field show up here the
8707 // last (always empty) run. Inherit character
8708 // properties from there.
8709 // Also merge in the properties from the field context,
8710 // e.g. SdtEndBefore.
8711 if (m_pLastCharacterContext)
8712 aMap.InsertProps(m_pLastCharacterContext);
8713 aMap.InsertProps(m_aFieldStack.back()->getProperties());
8714 appendTextContent(xToInsert, aMap.GetPropertyValues());
8715 CheckRedline( xToInsert->getAnchor( ) );
8717 else
8719 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->createTextCursorByRange(pContext->GetStartRange());
8720 FormControlHelper::Pointer_t pFormControlHelper(pContext->getFormControlHelper());
8721 if (pFormControlHelper)
8723 // xCrsr may be empty e.g. when pContext->GetStartRange() is outside of
8724 // xTextAppend, like when a field started in a parent paragraph is being
8725 // closed inside an anchored text box. It could be possible to throw an
8726 // exception here, and abort import, but Word tolerates such invalid
8727 // input, so it makes sense to do the same (tdf#152200)
8728 if (xCrsr.is())
8730 uno::Reference< text::XFormField > xFormField(pContext->GetFormField());
8731 if (pFormControlHelper->hasFFDataHandler())
8733 xToInsert.set(xFormField, uno::UNO_QUERY);
8734 if (xFormField.is() && xToInsert.is())
8736 PopFieldmark(m_aTextAppendStack, xCrsr,
8737 pContext->GetFieldId());
8738 pFormControlHelper->processField(xFormField);
8740 else
8742 pFormControlHelper->insertControl(xCrsr);
8745 else
8747 PopFieldmark(m_aTextAppendStack, xCrsr,
8748 pContext->GetFieldId());
8749 uno::Reference<lang::XComponent>(xFormField, uno::UNO_QUERY_THROW)->dispose(); // presumably invalid?
8753 else if (!pContext->GetHyperlinkURL().isEmpty() && xCrsr.is())
8755 if (m_aTextAppendStack.top().xInsertPosition.is())
8757 xCrsr->gotoRange(m_aTextAppendStack.top().xInsertPosition, true);
8759 else
8761 xCrsr->gotoEnd(true);
8764 // Draw components (like comments) need hyperlinks set differently
8765 SvxUnoTextRangeBase* pDrawText = dynamic_cast<SvxUnoTextRangeBase*>(xCrsr.get());
8766 if ( pDrawText )
8767 pDrawText->attachField( std::make_unique<SvxURLField>(pContext->GetHyperlinkURL(), xCrsr->getString(), SvxURLFormat::AppDefault) );
8768 else
8770 uno::Reference< beans::XPropertySet > xCrsrProperties( xCrsr, uno::UNO_QUERY_THROW );
8771 xCrsrProperties->setPropertyValue(getPropertyName(PROP_HYPER_LINK_U_R_L), uno::
8772 Any(pContext->GetHyperlinkURL()));
8774 if (!pContext->GetHyperlinkTarget().isEmpty())
8775 xCrsrProperties->setPropertyValue("HyperLinkTarget", uno::Any(pContext->GetHyperlinkTarget()));
8777 if (IsInTOC())
8779 OUString sDisplayName("Index Link");
8780 xCrsrProperties->setPropertyValue("VisitedCharStyleName",uno::Any(sDisplayName));
8781 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName",uno::Any(sDisplayName));
8783 else if (!pContext->GetHyperlinkStyle().isEmpty())
8785 uno::Any aAny = xCrsrProperties->getPropertyValue("CharStyleName");
8786 OUString charStyle;
8787 if (css::uno::fromAny(aAny, &charStyle))
8789 if (!charStyle.isEmpty() && charStyle.equalsIgnoreAsciiCase("Internet Link"))
8791 xCrsrProperties->setPropertyValue("CharStyleName", uno::Any(OUString("Default Style")));
8793 else
8795 xCrsrProperties->setPropertyValue("VisitedCharStyleName", uno::Any(pContext->GetHyperlinkStyle()));
8796 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", uno::Any(pContext->GetHyperlinkStyle()));
8803 else if (m_nStartGenericField != 0)
8805 --m_nStartGenericField;
8806 PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId());
8807 if (m_StreamStateStack.top().bTextInserted)
8809 m_StreamStateStack.top().bTextInserted = false;
8815 catch(const lang::IllegalArgumentException&)
8817 TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" );
8819 catch(const uno::Exception&)
8821 TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" );
8825 //TOCs have to include all the imported content
8828 std::vector<FieldParagraph> aParagraphsToFinish;
8829 if (pContext)
8831 aParagraphsToFinish = pContext->GetParagraphsToFinish();
8834 //remove the field context
8835 m_aFieldStack.pop_back();
8837 // Finish the paragraph(s) now that the field is closed.
8838 for (const auto& rFinish : aParagraphsToFinish)
8840 finishParagraph(rFinish.m_pPropertyMap, rFinish.m_bRemove);
8845 void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName )
8847 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( m_sCurrentBkmkId );
8848 if( aBookmarkIter != m_aBookmarkMap.end() )
8850 // fields are internal bookmarks: consume redundant "normal" bookmark
8851 if ( IsOpenField() )
8853 FFDataHandler::Pointer_t pFFDataHandler(GetTopFieldContext()->getFFDataHandler());
8854 if (pFFDataHandler && pFFDataHandler->getName() == rBookmarkName)
8856 // HACK: At the END marker, StartOrEndBookmark will START
8857 // a bookmark which will eventually be abandoned, not created.
8858 m_aBookmarkMap.erase(aBookmarkIter);
8859 return;
8863 if ((m_sCurrentBkmkPrefix == "__RefMoveFrom__"
8864 || m_sCurrentBkmkPrefix == "__RefMoveTo__")
8865 && std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), rBookmarkName)
8866 == m_aRedlineMoveIDs.end())
8868 m_aRedlineMoveIDs.push_back(rBookmarkName);
8871 aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + rBookmarkName;
8872 m_sCurrentBkmkPrefix.clear();
8874 else
8876 m_sCurrentBkmkName = rBookmarkName;
8877 m_sCurrentBkmkPrefix.clear();
8881 // This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation.
8882 void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId )
8885 * Add the dummy paragraph to handle section properties
8886 * iff the first element in the section is a table. If the dummy para is not added yet, then add it;
8887 * So bookmark is not attached to the wrong paragraph.
8889 if (hasTableManager() && getTableManager().isInCell()
8890 && m_StreamStateStack.top().nTableDepth == 0
8891 && GetIsFirstParagraphInSection()
8892 && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted())
8894 AddDummyParaForTableInSection();
8897 bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
8898 if (m_aTextAppendStack.empty())
8899 return;
8900 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
8901 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( rId );
8902 //is the bookmark name already registered?
8905 if( aBookmarkIter != m_aBookmarkMap.end() )
8907 if (m_xTextFactory.is())
8909 uno::Reference< text::XTextContent > xBookmark( m_xTextFactory->createInstance( "com.sun.star.text.Bookmark" ), uno::UNO_QUERY_THROW );
8910 uno::Reference< text::XTextCursor > xCursor;
8911 uno::Reference< text::XText > xText = aBookmarkIter->second.m_xTextRange->getText();
8912 if( aBookmarkIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
8914 xCursor = xText->createTextCursorByRange( xText->getStart() );
8916 else
8918 xCursor = xText->createTextCursorByRange( aBookmarkIter->second.m_xTextRange );
8920 if (!aBookmarkIter->second.m_bIsStartOfText)
8922 xCursor->goRight( 1, false );
8925 xCursor->gotoRange( xTextAppend->getEnd(), true );
8926 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
8927 // then move the bookmark-End to the earlier paragraph
8928 if (IsOutsideAParagraph())
8930 // keep bookmark range, if it doesn't exceed cell boundary
8931 uno::Reference< text::XTextRange > xStart = xCursor->getStart();
8932 xCursor->goLeft( 1, false );
8933 if (m_StreamStateStack.top().nTableDepth == 0
8934 || !m_StreamStateStack.top().bFirstParagraphInCell)
8936 xCursor->gotoRange(xStart, true );
8939 uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW );
8940 SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark");
8941 //todo: make sure the name is not used already!
8942 xBkmNamed->setName( aBookmarkIter->second.m_sBookmarkName );
8943 xTextAppend->insertTextContent( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW), xBookmark, !xCursor->isCollapsed() );
8945 m_aBookmarkMap.erase( aBookmarkIter );
8946 m_sCurrentBkmkId.clear();
8948 else
8950 //otherwise insert a text range as marker
8951 bool bIsStart = true;
8952 uno::Reference< text::XTextRange > xCurrent;
8953 if (xTextAppend.is())
8955 uno::Reference<text::XTextCursor> const xCursor =
8956 xTextAppend->createTextCursorByRange(
8957 m_aTextAppendStack.top().xInsertPosition.is()
8958 ? m_aTextAppendStack.top().xInsertPosition
8959 : xTextAppend->getEnd() );
8961 if (!xCursor)
8962 return;
8964 if (!bIsAfterDummyPara)
8965 bIsStart = !xCursor->goLeft(1, false);
8966 xCurrent = xCursor->getStart();
8968 m_sCurrentBkmkId = rId;
8969 m_aBookmarkMap.emplace( rId, BookmarkInsertPosition( bIsStart, m_sCurrentBkmkName, xCurrent ) );
8970 m_sCurrentBkmkName.clear();
8973 catch( const uno::Exception& )
8975 //TODO: What happens to bookmarks where start and end are at different XText objects?
8979 void DomainMapper_Impl::SetMoveBookmark( bool bIsFrom )
8981 static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = u"__RefMoveFrom__";
8982 static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = u"__RefMoveTo__";
8983 if ( bIsFrom )
8984 m_sCurrentBkmkPrefix = MoveFrom_Bookmark_NamePrefix;
8985 else
8986 m_sCurrentBkmkPrefix = MoveTo_Bookmark_NamePrefix;
8989 void DomainMapper_Impl::setPermissionRangeEd(const OUString& user)
8991 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
8992 if (aPremIter != m_aPermMap.end())
8993 aPremIter->second.m_Ed = user;
8994 else
8995 m_sCurrentPermEd = user;
8998 void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group)
9000 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
9001 if (aPremIter != m_aPermMap.end())
9002 aPremIter->second.m_EdGrp = group;
9003 else
9004 m_sCurrentPermEdGrp = group;
9007 // This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark()
9008 void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId)
9011 * Add the dummy paragraph to handle section properties
9012 * if the first element in the section is a table. If the dummy para is not added yet, then add it;
9013 * So permission is not attached to the wrong paragraph.
9015 if (getTableManager().isInCell()
9016 && m_StreamStateStack.top().nTableDepth == 0 && GetIsFirstParagraphInSection()
9017 && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted())
9019 AddDummyParaForTableInSection();
9022 if (m_aTextAppendStack.empty())
9023 return;
9025 const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
9027 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
9028 PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId);
9030 //is the bookmark name already registered?
9033 if (aPermIter == m_aPermMap.end())
9035 //otherwise insert a text range as marker
9036 bool bIsStart = true;
9037 uno::Reference< text::XTextRange > xCurrent;
9038 if (xTextAppend.is())
9040 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
9042 if (!bIsAfterDummyPara)
9043 bIsStart = !xCursor->goLeft(1, false);
9044 xCurrent = xCursor->getStart();
9047 // register the start of the new permission
9048 m_sCurrentPermId = permissinId;
9049 m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent));
9051 // clean up
9052 m_sCurrentPermEd.clear();
9053 m_sCurrentPermEdGrp.clear();
9055 else
9057 if (m_xTextFactory.is())
9059 uno::Reference< text::XTextCursor > xCursor;
9060 uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText();
9061 if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
9063 xCursor = xText->createTextCursorByRange(xText->getStart());
9065 else
9067 xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange);
9069 if (!aPermIter->second.m_bIsStartOfText)
9071 xCursor->goRight(1, false);
9074 xCursor->gotoRange(xTextAppend->getEnd(), true);
9075 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
9076 // then move the bookmark-End to the earlier paragraph
9077 if (IsOutsideAParagraph())
9079 xCursor->goLeft(1, false);
9082 // create a new bookmark using specific bookmark name pattern for permissions
9083 uno::Reference< text::XTextContent > xPerm(m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), uno::UNO_QUERY_THROW);
9084 uno::Reference< container::XNamed > xPermNamed(xPerm, uno::UNO_QUERY_THROW);
9085 xPermNamed->setName(aPermIter->second.createBookmarkName());
9087 // add new bookmark
9088 const bool bAbsorb = !xCursor->isCollapsed();
9089 uno::Reference< text::XTextRange > xCurrent(xCursor, uno::UNO_QUERY_THROW);
9090 xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb);
9093 // remove processed permission
9094 m_aPermMap.erase(aPermIter);
9096 // clean up
9097 m_sCurrentPermId = 0;
9098 m_sCurrentPermEd.clear();
9099 m_sCurrentPermEdGrp.clear();
9102 catch (const uno::Exception&)
9104 //TODO: What happens to bookmarks where start and end are at different XText objects?
9108 void DomainMapper_Impl::AddAnnotationPosition(
9109 const bool bStart,
9110 const sal_Int32 nAnnotationId)
9112 if (m_aTextAppendStack.empty())
9113 return;
9115 // Create a cursor, pointing to the current position.
9116 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
9117 uno::Reference<text::XTextRange> xCurrent;
9118 if (xTextAppend.is())
9120 uno::Reference<text::XTextCursor> xCursor;
9121 if (m_bIsNewDoc)
9122 xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
9123 else
9124 xCursor = m_aTextAppendStack.top().xCursor;
9125 if (xCursor.is())
9126 xCurrent = xCursor->getStart();
9129 // And save it, to be used by PopAnnotation() later.
9130 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[ nAnnotationId ];
9131 if (bStart)
9133 aAnnotationPosition.m_xStart = xCurrent;
9135 else
9137 aAnnotationPosition.m_xEnd = xCurrent;
9139 m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition;
9142 GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport()
9144 if(!m_pGraphicImport)
9146 m_pGraphicImport = new GraphicImport(m_xComponentContext, m_xTextFactory, m_rDMapper, m_eGraphicImportType, m_aPositionOffsets, m_aAligns, m_aPositivePercentages);
9148 return m_pGraphicImport;
9150 /*-------------------------------------------------------------------------
9151 reset graphic import if the last import resulted in a shape, not a graphic
9152 -----------------------------------------------------------------------*/
9153 void DomainMapper_Impl::ResetGraphicImport()
9155 m_pGraphicImport.clear();
9159 void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference<Properties>::Pointer_t& ref)
9161 GetGraphicImport();
9162 if (m_eGraphicImportType != IMPORT_AS_DETECTED_INLINE && m_eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR)
9163 { // this appears impossible?
9164 //create the graphic
9165 ref->resolve( *m_pGraphicImport );
9168 //insert it into the document at the current cursor position
9170 uno::Reference<text::XTextContent> xTextContent
9171 (m_pGraphicImport->GetGraphicObject());
9173 // In case the SDT starts with the text portion of the graphic, then set the SDT properties here.
9174 bool bHasGrabBag = false;
9175 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
9176 if (xPropertySet.is())
9178 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
9179 bHasGrabBag = xPropertySetInfo->hasPropertyByName("FrameInteropGrabBag");
9180 // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one.
9181 if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph())
9183 comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue("FrameInteropGrabBag"));
9184 aFrameGrabBag["SdtPr"] <<= m_pSdtHelper->getInteropGrabBagAndClear();
9185 xPropertySet->setPropertyValue("FrameInteropGrabBag", uno::Any(aFrameGrabBag.getAsConstPropertyValueList()));
9189 /* Set "SdtEndBefore" property on Drawing.
9190 * It is required in a case when Drawing appears immediately after first run i.e.
9191 * there is no text/space/tab in between two runs.
9192 * In this case "SdtEndBefore" property needs to be set on Drawing.
9194 if(IsSdtEndBefore())
9196 if(xPropertySet.is() && bHasGrabBag)
9198 uno::Sequence<beans::PropertyValue> aFrameGrabBag( comphelper::InitPropertySequence({
9199 { "SdtEndBefore", uno::Any(true) }
9200 }));
9201 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aFrameGrabBag));
9206 // Update the shape properties if it is embedded object.
9207 if(m_xEmbedded.is()){
9208 if (m_pGraphicImport->GetXShapeObject())
9209 m_pGraphicImport->GetXShapeObject()->setPosition(
9210 m_pGraphicImport->GetGraphicObjectPosition());
9212 uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject();
9213 UpdateEmbeddedShapeProps(xShape);
9214 if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR)
9216 uno::Reference<beans::XPropertySet> xEmbeddedProps(m_xEmbedded, uno::UNO_QUERY);
9217 xEmbeddedProps->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER));
9218 xEmbeddedProps->setPropertyValue("IsFollowingTextFlow", uno::Any(m_pGraphicImport->GetLayoutInCell()));
9219 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
9220 xEmbeddedProps->setPropertyValue("HoriOrient", xShapeProps->getPropertyValue("HoriOrient"));
9221 xEmbeddedProps->setPropertyValue("HoriOrientPosition", xShapeProps->getPropertyValue("HoriOrientPosition"));
9222 xEmbeddedProps->setPropertyValue("HoriOrientRelation", xShapeProps->getPropertyValue("HoriOrientRelation"));
9223 xEmbeddedProps->setPropertyValue("VertOrient", xShapeProps->getPropertyValue("VertOrient"));
9224 xEmbeddedProps->setPropertyValue("VertOrientPosition", xShapeProps->getPropertyValue("VertOrientPosition"));
9225 xEmbeddedProps->setPropertyValue("VertOrientRelation", xShapeProps->getPropertyValue("VertOrientRelation"));
9226 //tdf123873 fix missing textwrap import
9227 xEmbeddedProps->setPropertyValue("TextWrap", xShapeProps->getPropertyValue("TextWrap"));
9229 // GraphicZOrderHelper::findZOrder() was called already, so can just copy it over.
9230 xEmbeddedProps->setPropertyValue("ZOrder", xShapeProps->getPropertyValue("ZOrder"));
9233 //insert it into the document at the current cursor position
9234 OSL_ENSURE( xTextContent.is(), "DomainMapper_Impl::ImportGraphic");
9235 if( xTextContent.is())
9237 bool bAppend = true;
9238 // workaround for images anchored to characters: add ZWSPs around the anchoring point
9239 if (m_eGraphicImportType != IMPORT_AS_DETECTED_INLINE && !m_aRedlines.top().empty())
9241 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
9242 if(xTextAppend.is())
9246 uno::Reference< text::XText > xText = xTextAppend->getText();
9247 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
9248 xCrsr->gotoEnd(false);
9249 PropertyMapPtr pEmpty(new PropertyMap());
9250 appendTextPortion(u"​"_ustr, pEmpty);
9251 appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
9252 bAppend = false;
9253 xCrsr->gotoEnd(false);
9254 appendTextPortion(u"​"_ustr, pEmpty);
9256 m_bRedlineImageInPreviousRun = true;
9257 m_previousRedline = m_currentRedline;
9259 catch( const uno::Exception& )
9265 if ( bAppend )
9266 appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
9268 if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty())
9270 // Remember this object is anchored to the current paragraph.
9271 AnchoredObjectInfo aInfo;
9272 aInfo.m_xAnchoredObject = xTextContent;
9273 if (m_pGraphicImport)
9275 // We still have the graphic import around, remember the original margin, so later
9276 // SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing() can use it.
9277 aInfo.m_nLeftMargin = m_pGraphicImport->GetLeftMarginOrig();
9279 m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo);
9281 else if (m_eGraphicImportType == IMPORT_AS_DETECTED_INLINE)
9283 m_StreamStateStack.top().bParaWithInlineObject = true;
9285 // store inline images with track changes, because the anchor point
9286 // to set redlining is not available yet
9287 if (!m_aTextAppendStack.empty() && !m_aRedlines.top().empty() )
9289 // Remember this object is anchored to the current paragraph.
9290 AnchoredObjectInfo aInfo;
9291 aInfo.m_xAnchoredObject = xTextContent;
9292 aInfo.m_xRedlineForInline = m_aRedlines.top().back();
9293 m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo);
9299 // Clear the reference, so in case the embedded object is inside a
9300 // TextFrame, we won't try to resize it (to match the size of the
9301 // TextFrame) here.
9302 m_xEmbedded.clear();
9303 m_pGraphicImport.clear();
9307 void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn )
9309 if( !m_bLineNumberingSet )
9313 uno::Reference< text::XLineNumberingProperties > xLineProperties( m_xTextDocument, uno::UNO_QUERY_THROW );
9314 uno::Reference< beans::XPropertySet > xProperties = xLineProperties->getLineNumberingProperties();
9315 uno::Any aTrue( uno::Any( true ));
9316 xProperties->setPropertyValue( getPropertyName( PROP_IS_ON ), aTrue);
9317 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_EMPTY_LINES ), aTrue );
9318 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_LINES_IN_FRAMES ), uno::Any( false ) );
9319 xProperties->setPropertyValue( getPropertyName( PROP_INTERVAL ), uno::Any( static_cast< sal_Int16 >( nLnnMod )));
9320 xProperties->setPropertyValue( getPropertyName( PROP_DISTANCE ), uno::Any( ConversionHelper::convertTwipToMM100(ndxaLnn) ));
9321 xProperties->setPropertyValue( getPropertyName( PROP_NUMBER_POSITION ), uno::Any( style::LineNumberPosition::LEFT));
9322 xProperties->setPropertyValue( getPropertyName( PROP_NUMBERING_TYPE ), uno::Any( style::NumberingType::ARABIC));
9323 xProperties->setPropertyValue( getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::Any( nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage ));
9325 catch( const uno::Exception& )
9328 m_bLineNumberingSet = true;
9329 uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( GetTextDocument(), uno::UNO_QUERY_THROW );
9330 uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
9331 uno::Reference<container::XNameContainer> xStyles;
9332 xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xStyles;
9333 lcl_linenumberingHeaderFooter( xStyles, "Header", this );
9334 lcl_linenumberingHeaderFooter( xStyles, "Footer", this );
9338 void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue )
9340 nValue = ConversionHelper::convertTwipToMM100(nValue);
9341 switch(eElement)
9343 case PAGE_MAR_TOP : m_aPageMargins.top = nValue; break;
9344 case PAGE_MAR_RIGHT : m_aPageMargins.right = nValue; break;
9345 case PAGE_MAR_BOTTOM : m_aPageMargins.bottom = nValue; break;
9346 case PAGE_MAR_LEFT : m_aPageMargins.left = nValue; break;
9347 case PAGE_MAR_HEADER : m_aPageMargins.header = nValue; break;
9348 case PAGE_MAR_FOOTER : m_aPageMargins.footer = nValue; break;
9349 case PAGE_MAR_GUTTER:
9350 m_aPageMargins.gutter = nValue;
9351 break;
9355 void DomainMapper_Impl::SetPaperSource(PaperSourceElement eElement, sal_Int32 nValue)
9357 if(eElement == PAPER_SOURCE_FIRST)
9358 m_aPaperSource.first = nValue;
9359 else
9360 m_aPaperSource.other = nValue;
9364 PageMar::PageMar()
9365 : top(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
9366 // This is strange, the RTF spec says it's 1800, but it's clearly 1440 in Word
9367 // OOXML seems not to specify a default value
9368 , right(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
9369 , bottom(top)
9370 , left(right)
9371 , header(ConversionHelper::convertTwipToMM100(sal_Int32(720)))
9372 , footer(header)
9373 , gutter(0)
9378 void DomainMapper_Impl::RegisterFrameConversion(
9379 uno::Reference< text::XTextRange > const& xFrameStartRange,
9380 uno::Reference< text::XTextRange > const& xFrameEndRange,
9381 std::vector<beans::PropertyValue>&& rFrameProperties
9384 OSL_ENSURE(
9385 m_aFrameProperties.empty() && !m_xFrameStartRange.is() && !m_xFrameEndRange.is(),
9386 "frame properties not removed");
9387 m_aFrameProperties = std::move(rFrameProperties);
9388 m_xFrameStartRange = xFrameStartRange;
9389 m_xFrameEndRange = xFrameEndRange;
9393 void DomainMapper_Impl::ExecuteFrameConversion()
9395 if( m_xFrameStartRange.is() && m_xFrameEndRange.is() && !m_bDiscardHeaderFooter )
9397 std::vector<sal_Int32> redPos, redLen;
9400 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( GetTopTextAppend(), uno::UNO_QUERY_THROW );
9401 // convert redline ranges to cursor movement and character length
9402 sal_Int32 redIdx;
9403 lcl_CopyRedlines(GetTopTextAppend(), m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx);
9405 const uno::Reference< text::XTextContent >& xTextContent = xTextAppendAndConvert->convertToTextFrame(
9406 m_xFrameStartRange,
9407 m_xFrameEndRange,
9408 comphelper::containerToSequence(m_aFrameProperties) );
9410 uno::Reference< text::XText > xDest( xTextContent, uno::UNO_QUERY_THROW );
9411 lcl_PasteRedlines(xDest, m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx);
9413 catch( const uno::Exception&)
9415 DBG_UNHANDLED_EXCEPTION( "writerfilter.dmapper", "Exception caught when converting to frame");
9418 m_bIsActualParagraphFramed = false;
9420 if (redPos.size() == m_aStoredRedlines[StoredRedlines::FRAME].size()/3)
9422 for( sal_Int32 i = m_aStoredRedlines[StoredRedlines::FRAME].size() - 1; i >= 0; --i)
9424 // keep redlines of floating tables to process them in CloseSectionGroup()
9425 if ( redPos[i/3] != -1 )
9427 m_aStoredRedlines[StoredRedlines::FRAME].erase(m_aStoredRedlines[StoredRedlines::FRAME].begin() + i);
9431 else
9432 m_aStoredRedlines[StoredRedlines::FRAME].clear();
9434 m_xFrameStartRange = nullptr;
9435 m_xFrameEndRange = nullptr;
9436 m_aFrameProperties.clear();
9439 void DomainMapper_Impl::AddNewRedline( sal_uInt32 sprmId )
9441 RedlineParamsPtr pNew( new RedlineParams );
9442 pNew->m_nToken = XML_mod;
9443 if ( !m_bIsParaMarkerChange )
9445 // <w:rPrChange> applies to the whole <w:r>, <w:pPrChange> applies to the whole <w:p>,
9446 // so keep those two in CONTEXT_CHARACTERS and CONTEXT_PARAGRAPH, which will take
9447 // care of their scope (i.e. when they should be used and discarded).
9448 // Let's keep the rest the same way they used to be handled (explicitly dropped
9449 // from a global stack by endtrackchange), but quite possibly they should not be handled
9450 // that way either (I don't know).
9451 if( sprmId == NS_ooxml::LN_EG_RPrContent_rPrChange )
9452 GetTopContextOfType( CONTEXT_CHARACTER )->Redlines().push_back( pNew );
9453 else if( sprmId == NS_ooxml::LN_CT_PPr_pPrChange )
9454 GetTopContextOfType( CONTEXT_PARAGRAPH )->Redlines().push_back( pNew );
9455 else if( sprmId != NS_ooxml::LN_CT_ParaRPr_rPrChange )
9456 m_aRedlines.top().push_back( pNew );
9458 else
9460 m_pParaMarkerRedline = pNew;
9462 // Newly read data will go into this redline.
9463 m_currentRedline = pNew;
9466 void DomainMapper_Impl::SetCurrentRedlineIsRead()
9468 m_currentRedline.clear();
9471 sal_Int32 DomainMapper_Impl::GetCurrentRedlineToken( ) const
9473 assert(m_currentRedline);
9474 return m_currentRedline->m_nToken;
9477 void DomainMapper_Impl::SetCurrentRedlineAuthor( const OUString& sAuthor )
9479 if (!m_xAnnotationField.is())
9481 if (m_currentRedline)
9482 m_currentRedline->m_sAuthor = sAuthor;
9483 else
9484 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
9486 else
9487 m_xAnnotationField->setPropertyValue("Author", uno::Any(sAuthor));
9490 void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials )
9492 if (m_xAnnotationField.is())
9493 m_xAnnotationField->setPropertyValue("Initials", uno::Any(sInitials));
9496 void DomainMapper_Impl::SetCurrentRedlineDate( const OUString& sDate )
9498 if (!m_xAnnotationField.is())
9500 if (m_currentRedline)
9501 m_currentRedline->m_sDate = sDate;
9502 else
9503 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
9505 else
9506 m_xAnnotationField->setPropertyValue("DateTimeValue", uno::Any(ConversionHelper::ConvertDateStringToDateTime(sDate)));
9509 void DomainMapper_Impl::SetCurrentRedlineId( sal_Int32 sId )
9511 if (m_xAnnotationField.is())
9513 m_nAnnotationId = sId;
9515 else
9517 // This should be an assert, but somebody had the smart idea to reuse this function also for comments and whatnot,
9518 // and in some cases the id is actually not handled, which may be in fact a bug.
9519 if( !m_currentRedline)
9520 SAL_INFO("writerfilter.dmapper", "no current redline");
9524 void DomainMapper_Impl::SetCurrentRedlineToken( sal_Int32 nToken )
9526 assert(m_currentRedline);
9527 m_currentRedline->m_nToken = nToken;
9530 void DomainMapper_Impl::SetCurrentRedlineRevertProperties( const uno::Sequence<beans::PropertyValue>& aProperties )
9532 assert(m_currentRedline);
9533 m_currentRedline->m_aRevertProperties = aProperties;
9537 // This removes only the last redline stored here, those stored in contexts are automatically removed when
9538 // the context is destroyed.
9539 void DomainMapper_Impl::RemoveTopRedline( )
9541 if (m_aRedlines.top().empty())
9543 if (GetFootnoteCount() > -1 || GetEndnoteCount() > -1)
9544 return;
9545 SAL_WARN("writerfilter.dmapper", "RemoveTopRedline called with empty stack");
9546 throw uno::Exception("RemoveTopRedline failed", nullptr);
9548 m_aRedlines.top().pop_back( );
9549 m_currentRedline.clear();
9552 void DomainMapper_Impl::ApplySettingsTable()
9554 if (!(m_pSettingsTable && m_xTextFactory.is()))
9555 return;
9559 uno::Reference< beans::XPropertySet > xTextDefaults(m_xTextFactory->createInstance("com.sun.star.text.Defaults"), uno::UNO_QUERY_THROW );
9560 sal_Int32 nDefTab = m_pSettingsTable->GetDefaultTabStop();
9561 xTextDefaults->setPropertyValue( getPropertyName( PROP_TAB_STOP_DISTANCE ), uno::Any(nDefTab) );
9562 if (m_pSettingsTable->GetLinkStyles())
9564 // If linked styles are enabled, set paragraph defaults from Word's default template
9565 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_BOTTOM_MARGIN), uno::Any(ConversionHelper::convertTwipToMM100(200)));
9566 style::LineSpacing aSpacing;
9567 aSpacing.Mode = style::LineSpacingMode::PROP;
9568 aSpacing.Height = sal_Int16(115);
9569 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_LINE_SPACING), uno::Any(aSpacing));
9572 if (m_pSettingsTable->GetZoomFactor() || m_pSettingsTable->GetView())
9574 std::vector<beans::PropertyValue> aViewProps;
9575 if (m_pSettingsTable->GetZoomFactor())
9577 aViewProps.emplace_back("ZoomFactor", -1, uno::Any(m_pSettingsTable->GetZoomFactor()), beans::PropertyState_DIRECT_VALUE);
9578 aViewProps.emplace_back("VisibleBottom", -1, uno::Any(sal_Int32(0)), beans::PropertyState_DIRECT_VALUE);
9579 aViewProps.emplace_back("ZoomType", -1,
9580 uno::Any(m_pSettingsTable->GetZoomType()),
9581 beans::PropertyState_DIRECT_VALUE);
9583 rtl::Reference< comphelper::IndexedPropertyValuesContainer > xBox = new comphelper::IndexedPropertyValuesContainer();
9584 xBox->insertByIndex(sal_Int32(0), uno::Any(comphelper::containerToSequence(aViewProps)));
9585 uno::Reference<document::XViewDataSupplier> xViewDataSupplier(m_xTextDocument, uno::UNO_QUERY);
9586 xViewDataSupplier->setViewData(xBox);
9589 uno::Reference< beans::XPropertySet > xSettings(m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY);
9591 if (m_pSettingsTable->GetDoNotExpandShiftReturn())
9592 xSettings->setPropertyValue( "DoNotJustifyLinesWithManualBreak", uno::Any(true) );
9593 // new paragraph justification has been introduced in version 15,
9594 // breaking text layout interoperability: new line shrinking needs less space
9595 // i.e. it typesets the same text with less lines and pages.
9596 if (m_pSettingsTable->GetWordCompatibilityMode() >= 15)
9597 xSettings->setPropertyValue("JustifyLinesWithShrinking", uno::Any( true ));
9598 if (m_pSettingsTable->GetUsePrinterMetrics())
9599 xSettings->setPropertyValue("PrinterIndependentLayout", uno::Any(document::PrinterIndependentLayout::DISABLED));
9600 if( m_pSettingsTable->GetEmbedTrueTypeFonts())
9601 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_FONTS ), uno::Any(true) );
9602 if( m_pSettingsTable->GetEmbedSystemFonts())
9603 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_SYSTEM_FONTS ), uno::Any(true) );
9604 xSettings->setPropertyValue("AddParaTableSpacing", uno::Any(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing()));
9605 if (m_pSettingsTable->GetNoLeading())
9607 xSettings->setPropertyValue("AddExternalLeading", uno::Any(!m_pSettingsTable->GetNoLeading()));
9609 if( m_pSettingsTable->GetProtectForm() )
9610 xSettings->setPropertyValue("ProtectForm", uno::Any( true ));
9611 if( m_pSettingsTable->GetReadOnly() )
9612 xSettings->setPropertyValue("LoadReadonly", uno::Any( true ));
9613 if (m_pSettingsTable->GetGutterAtTop())
9615 xSettings->setPropertyValue("GutterAtTop", uno::Any(true));
9617 uno::Sequence<beans::PropertyValue> aWriteProtection
9618 = m_pSettingsTable->GetWriteProtectionSettings();
9619 if (aWriteProtection.hasElements())
9620 xSettings->setPropertyValue("ModifyPasswordInfo", uno::Any(aWriteProtection));
9622 catch(const uno::Exception&)
9627 SectionPropertyMap * DomainMapper_Impl::GetSectionContext()
9629 SectionPropertyMap* pSectionContext = nullptr;
9630 //the section context is not available before the first call of startSectionGroup()
9631 if( !IsAnyTableImport() )
9633 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
9634 pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
9637 return pSectionContext;
9640 void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any& value)
9642 m_deferredCharacterProperties[ id ] = value;
9645 void DomainMapper_Impl::processDeferredCharacterProperties(bool bCharContext)
9647 // Actually process in DomainMapper, so that it's the same source file like normal processing.
9648 if( !m_deferredCharacterProperties.empty())
9650 m_rDMapper.processDeferredCharacterProperties(m_deferredCharacterProperties, bCharContext);
9651 m_deferredCharacterProperties.clear();
9655 sal_Int32 DomainMapper_Impl::getNumberingProperty(const sal_Int32 nListId, sal_Int32 nNumberingLevel, const OUString& aProp)
9657 sal_Int32 nRet = 0;
9658 if ( nListId < 0 )
9659 return nRet;
9663 if (nNumberingLevel < 0) // It seems it's valid to omit numbering level, and in that case it means zero.
9664 nNumberingLevel = 0;
9666 auto const pList(GetListTable()->GetList(nListId));
9667 assert(pList);
9668 const OUString aListName = pList->GetStyleName();
9669 const uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY_THROW);
9670 const uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
9671 uno::Reference<container::XNameAccess> xNumberingStyles;
9672 xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles;
9673 const uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY);
9674 const uno::Reference<container::XIndexAccess> xNumberingRules(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
9675 if (xNumberingRules.is())
9677 uno::Sequence<beans::PropertyValue> aProps;
9678 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
9679 auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
9680 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
9681 if (pProp != std::cend(aProps))
9682 pProp->Value >>= nRet;
9685 catch( const uno::Exception& )
9687 // This can happen when the doc contains some hand-crafted invalid list level.
9690 return nRet;
9693 sal_Int32 DomainMapper_Impl::getCurrentNumberingProperty(const OUString& aProp)
9695 sal_Int32 nRet = 0;
9697 std::optional<PropertyMap::Property> pProp = m_pTopContext->getProperty(PROP_NUMBERING_RULES);
9698 uno::Reference<container::XIndexAccess> xNumberingRules;
9699 if (pProp)
9700 xNumberingRules.set(pProp->second, uno::UNO_QUERY);
9701 pProp = m_pTopContext->getProperty(PROP_NUMBERING_LEVEL);
9702 // Default numbering level is the first one.
9703 sal_Int32 nNumberingLevel = 0;
9704 if (pProp)
9705 pProp->second >>= nNumberingLevel;
9706 if (xNumberingRules.is())
9708 uno::Sequence<beans::PropertyValue> aProps;
9709 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
9710 auto pPropVal = std::find_if(std::cbegin(aProps), std::cend(aProps),
9711 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
9712 if (pPropVal != std::cend(aProps))
9713 pPropVal->Value >>= nRet;
9716 return nRet;
9720 void DomainMapper_Impl::enableInteropGrabBag(const OUString& aName)
9722 m_aInteropGrabBagName = aName;
9725 void DomainMapper_Impl::disableInteropGrabBag()
9727 m_aInteropGrabBagName.clear();
9728 m_aInteropGrabBag.clear();
9729 m_aSubInteropGrabBag.clear();
9732 bool DomainMapper_Impl::isInteropGrabBagEnabled() const
9734 return !(m_aInteropGrabBagName.isEmpty());
9737 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue)
9739 if (m_aInteropGrabBagName.isEmpty())
9740 return;
9741 beans::PropertyValue aProperty;
9742 aProperty.Name = aKey;
9743 aProperty.Value <<= aValue;
9744 rInteropGrabBag.push_back(aProperty);
9747 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<beans::PropertyValue>& rValue)
9749 if (m_aInteropGrabBagName.isEmpty())
9750 return;
9751 beans::PropertyValue aProperty;
9752 aProperty.Name = aKey;
9753 aProperty.Value <<= comphelper::containerToSequence(rValue);
9754 rValue.clear();
9755 rInteropGrabBag.push_back(aProperty);
9758 void DomainMapper_Impl::substream(Id rName,
9759 ::writerfilter::Reference<Stream>::Pointer_t const& ref)
9761 #ifndef NDEBUG
9762 size_t contextSize(m_aContextStack.size());
9763 size_t propSize[NUMBER_OF_CONTEXTS];
9764 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
9765 propSize[i] = m_aPropertyStacks[i].size();
9767 #endif
9769 // Save "has footnote" state, which is specific to a section in the body
9770 // text, so state from substreams is not relevant.
9771 bool bHasFtn = m_bHasFtn;
9773 //finalize any waiting frames before starting alternate streams
9774 CheckUnregisteredFrameConversion();
9775 ExecuteFrameConversion();
9777 appendTableManager();
9778 // Appending a TableManager resets its TableHandler, so we need to append
9779 // that as well, or tables won't be imported properly in headers/footers.
9780 appendTableHandler();
9781 getTableManager().startLevel();
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 assert(!m_StreamStateStack.empty());
9861 m_StreamStateStack.pop();
9863 getTableManager().endLevel();
9864 popTableManager();
9865 m_bHasFtn = bHasFtn;
9867 switch(rName)
9869 case NS_ooxml::LN_footnote:
9870 case NS_ooxml::LN_endnote:
9871 m_bHasFtn = true;
9872 break;
9875 // check that stacks are the same as before substream
9876 assert(m_aContextStack.size() == contextSize);
9877 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
9878 assert(m_aPropertyStacks[i].size() == propSize[i]);
9882 void DomainMapper_Impl::commentProps(const OUString& sId, const CommentProperties& rProps)
9884 m_aCommentProps[sId] = rProps;
9888 bool DomainMapper_Impl::handlePreviousParagraphBorderInBetween() const
9890 if (!m_xPreviousParagraph.is())
9891 return false;
9893 // Connected borders ("ParaIsConnectBorder") are always on by default
9894 // and never changed by DomainMapper. Except one case when border in
9895 // between is used. So this is not the best, but easiest way to check
9896 // is previous paragraph has border in between.
9897 bool bConnectBorders = true;
9898 m_xPreviousParagraph->getPropertyValue(getPropertyName(PROP_PARA_CONNECT_BORDERS)) >>= bConnectBorders;
9900 if (bConnectBorders)
9901 return false;
9903 // Previous paragraph has border in between. Current one also has (since this
9904 // method is called). So current paragraph will get border above, but
9905 // also need to ensure, that no unexpected bottom border are remaining in previous
9906 // paragraph: since ParaIsConnectBorder=false it will be displayed in unexpected way.
9907 m_xPreviousParagraph->setPropertyValue(getPropertyName(PROP_BOTTOM_BORDER), uno::Any(table::BorderLine2()));
9909 return true;
9912 OUString DomainMapper_Impl::getFontNameForTheme(const Id id)
9914 auto const& pHandler = getThemeHandler();
9915 if (pHandler)
9916 return pHandler->getFontNameForTheme(id);
9917 return OUString();
9922 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */