tdf#131321 writerfilter: ApplyNumberingStyleNameToParaStyles()
[LibreOffice.git] / writerfilter / source / dmapper / DomainMapper_Impl.cxx
blob6428155d85cedc9e54b8431cfafff97c9295671a
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 <ooxml/resourceids.hxx>
24 #include "DomainMapper_Impl.hxx"
25 #include "ConversionHelper.hxx"
26 #include "SdtHelper.hxx"
27 #include "DomainMapperTableHandler.hxx"
28 #include "TagLogger.hxx"
29 #include <com/sun/star/uno/XComponentContext.hpp>
30 #include <com/sun/star/graphic/XGraphic.hpp>
31 #include <com/sun/star/beans/XPropertyState.hpp>
32 #include <com/sun/star/container/XNamed.hpp>
33 #include <com/sun/star/document/PrinterIndependentLayout.hpp>
34 #include <com/sun/star/document/IndexedPropertyValues.hpp>
35 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
36 #include <com/sun/star/embed/XEmbeddedObject.hpp>
37 #include <com/sun/star/lang/XServiceInfo.hpp>
38 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
39 #include <com/sun/star/style/LineNumberPosition.hpp>
40 #include <com/sun/star/style/LineSpacing.hpp>
41 #include <com/sun/star/style/LineSpacingMode.hpp>
42 #include <com/sun/star/text/ChapterFormat.hpp>
43 #include <com/sun/star/text/FilenameDisplayFormat.hpp>
44 #include <com/sun/star/text/SetVariableType.hpp>
45 #include <com/sun/star/text/XDocumentIndex.hpp>
46 #include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
47 #include <com/sun/star/text/XFootnote.hpp>
48 #include <com/sun/star/text/XLineNumberingProperties.hpp>
49 #include <com/sun/star/style/XStyle.hpp>
50 #include <com/sun/star/text/PageNumberType.hpp>
51 #include <com/sun/star/text/HoriOrientation.hpp>
52 #include <com/sun/star/text/VertOrientation.hpp>
53 #include <com/sun/star/text/ReferenceFieldPart.hpp>
54 #include <com/sun/star/text/RelOrientation.hpp>
55 #include <com/sun/star/text/ReferenceFieldSource.hpp>
56 #include <com/sun/star/text/SizeType.hpp>
57 #include <com/sun/star/text/TextContentAnchorType.hpp>
58 #include <com/sun/star/text/WrapTextMode.hpp>
59 #include <com/sun/star/text/XDependentTextField.hpp>
60 #include <com/sun/star/text/XParagraphCursor.hpp>
61 #include <com/sun/star/text/XRedline.hpp>
62 #include <com/sun/star/text/XTextFieldsSupplier.hpp>
63 #include <com/sun/star/text/XTextFrame.hpp>
64 #include <com/sun/star/text/RubyPosition.hpp>
65 #include <com/sun/star/text/XTextRangeCompare.hpp>
66 #include <com/sun/star/style/DropCapFormat.hpp>
67 #include <com/sun/star/util/NumberFormatter.hpp>
68 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
69 #include <com/sun/star/util/XNumberFormatter.hpp>
70 #include <com/sun/star/document/XViewDataSupplier.hpp>
71 #include <com/sun/star/container/XIndexContainer.hpp>
72 #include <com/sun/star/text/ControlCharacter.hpp>
73 #include <com/sun/star/text/XTextColumns.hpp>
74 #include <com/sun/star/awt/CharSet.hpp>
75 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
76 #include <editeng/flditem.hxx>
77 #include <editeng/unotext.hxx>
78 #include <o3tl/temporary.hxx>
79 #include <oox/mathml/import.hxx>
80 #include <xmloff/odffields.hxx>
81 #include <rtl/uri.hxx>
82 #include <dmapper/GraphicZOrderHelper.hxx>
84 #include <oox/token/tokens.hxx>
86 #include <cmath>
87 #include <optional>
88 #include <map>
89 #include <tuple>
90 #include <unordered_map>
92 #include <officecfg/Office/Common.hxx>
93 #include <filter/msfilter/util.hxx>
94 #include <comphelper/sequence.hxx>
95 #include <comphelper/propertyvalue.hxx>
96 #include <comphelper/propertysequence.hxx>
97 #include <unotools/configmgr.hxx>
98 #include <unotools/mediadescriptor.hxx>
99 #include <tools/diagnose_ex.h>
100 #include <sal/log.hxx>
103 using namespace ::com::sun::star;
104 using namespace oox;
105 namespace writerfilter::dmapper{
107 //line numbering for header/footer
108 static void lcl_linenumberingHeaderFooter( const uno::Reference<container::XNameContainer>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper )
110 const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname );
111 if (!pEntry)
112 return;
113 const StyleSheetPropertyMap* pStyleSheetProperties = dynamic_cast<const StyleSheetPropertyMap*>( pEntry->pProperties.get() );
114 if ( !pStyleSheetProperties )
115 return;
116 sal_Int32 nListId = pStyleSheetProperties->GetListId();
117 if( xStyles.is() )
119 if( xStyles->hasByName( rname ) )
121 uno::Reference< style::XStyle > xStyle;
122 xStyles->getByName( rname ) >>= xStyle;
123 if( !xStyle.is() )
124 return;
125 uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY );
126 xPropertySet->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::makeAny( nListId >= 0 ) );
131 // Populate Dropdown Field properties from FFData structure
132 static void lcl_handleDropdownField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
134 if ( rxFieldProps.is() )
136 if ( !pFFDataHandler->getName().isEmpty() )
137 rxFieldProps->setPropertyValue( "Name", uno::makeAny( pFFDataHandler->getName() ) );
139 const FFDataHandler::DropDownEntries_t& rEntries = pFFDataHandler->getDropDownEntries();
140 uno::Sequence< OUString > sItems( rEntries.size() );
141 ::std::copy( rEntries.begin(), rEntries.end(), sItems.begin());
142 if ( sItems.hasElements() )
143 rxFieldProps->setPropertyValue( "Items", uno::makeAny( sItems ) );
145 sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32();
146 if ( nResult )
147 rxFieldProps->setPropertyValue( "SelectedItem", uno::makeAny( sItems[ nResult ] ) );
148 if ( !pFFDataHandler->getHelpText().isEmpty() )
149 rxFieldProps->setPropertyValue( "Help", uno::makeAny( pFFDataHandler->getHelpText() ) );
153 static void lcl_handleTextField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler )
155 if ( rxFieldProps.is() && pFFDataHandler )
157 rxFieldProps->setPropertyValue
158 (getPropertyName(PROP_HINT),
159 uno::makeAny(pFFDataHandler->getStatusText()));
160 rxFieldProps->setPropertyValue
161 (getPropertyName(PROP_HELP),
162 uno::makeAny(pFFDataHandler->getHelpText()));
163 rxFieldProps->setPropertyValue
164 (getPropertyName(PROP_CONTENT),
165 uno::makeAny(pFFDataHandler->getTextDefault()));
169 namespace {
171 struct FieldConversion
173 const char* cFieldServiceName;
174 FieldId eFieldId;
179 typedef std::unordered_map<OUString, FieldConversion> FieldConversionMap_t;
181 /// Gives access to the parent field context of the topmost one, if there is any.
182 static FieldContextPtr GetParentFieldContext(const std::deque<FieldContextPtr>& rFieldStack)
184 if (rFieldStack.size() < 2)
186 return nullptr;
189 return rFieldStack[rFieldStack.size() - 2];
192 /// Decides if the pInner field inside pOuter is allowed in Writer core, depending on their type.
193 static bool IsFieldNestingAllowed(const FieldContextPtr& pOuter, const FieldContextPtr& pInner)
195 if (!pOuter->GetFieldId())
197 return true;
200 if (!pInner->GetFieldId())
202 return true;
205 switch (*pOuter->GetFieldId())
207 case FIELD_IF:
209 switch (*pInner->GetFieldId())
211 case FIELD_MERGEFIELD:
213 return false;
215 default:
216 break;
218 break;
220 default:
221 break;
224 return true;
227 uno::Any FloatingTableInfo::getPropertyValue(const OUString &propertyName)
229 for( beans::PropertyValue const & propVal : std::as_const(m_aFrameProperties) )
230 if( propVal.Name == propertyName )
231 return propVal.Value ;
232 return uno::Any() ;
235 DomainMapper_Impl::DomainMapper_Impl(
236 DomainMapper& rDMapper,
237 uno::Reference<uno::XComponentContext> const& xContext,
238 uno::Reference<lang::XComponent> const& xModel,
239 SourceDocumentType eDocumentType,
240 utl::MediaDescriptor const & rMediaDesc) :
241 m_eDocumentType( eDocumentType ),
242 m_rDMapper( rDMapper ),
243 m_xTextDocument( xModel, uno::UNO_QUERY ),
244 m_xTextFactory( xModel, uno::UNO_QUERY ),
245 m_xComponentContext( xContext ),
246 m_bForceGenericFields(!utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get(m_xComponentContext)),
247 m_bSetUserFieldContent( false ),
248 m_bSetCitation( false ),
249 m_bSetDateValue( false ),
250 m_bIsFirstSection( true ),
251 m_bIsColumnBreakDeferred( false ),
252 m_bIsPageBreakDeferred( false ),
253 m_bSdtEndDeferred(false),
254 m_bParaSdtEndDeferred(false),
255 m_bStartTOC(false),
256 m_bStartTOCHeaderFooter(false),
257 m_bStartedTOC(false),
258 m_bStartIndex(false),
259 m_bStartBibliography(false),
260 m_nStartGenericField(0),
261 m_bTextInserted(false),
262 m_sCurrentPermId(0),
263 m_pLastSectionContext( ),
264 m_pLastCharacterContext(),
265 m_sCurrentParaStyleName(),
266 m_sDefaultParaStyleName(),
267 m_bInStyleSheetImport( false ),
268 m_bInAnyTableImport( false ),
269 m_eInHeaderFooterImport( HeaderFooterImportState::none ),
270 m_bDiscardHeaderFooter( false ),
271 m_bInFootOrEndnote(false),
272 m_bHasFootnoteStyle(false),
273 m_bCheckFootnoteStyle(false),
274 m_eSkipFootnoteState(SkipFootnoteSeparator::OFF),
275 m_bLineNumberingSet( false ),
276 m_bIsInFootnoteProperties( false ),
277 m_bIsParaMarkerChange( false ),
278 m_bParaChanged( false ),
279 m_bIsFirstParaInSection( true ),
280 m_bIsFirstParaInSectionAfterRedline( true ),
281 m_bDummyParaAddedForTableInSection( false ),
282 m_bTextFrameInserted(false),
283 m_bIsPreviousParagraphFramed( false ),
284 m_bIsLastParaInSection( false ),
285 m_bIsLastSectionGroup( false ),
286 m_bIsInComments( false ),
287 m_bParaSectpr( false ),
288 m_bUsingEnhancedFields( false ),
289 m_bSdt(false),
290 m_bIsFirstRun(false),
291 m_bIsOutsideAParagraph(true),
292 m_xAnnotationField(),
293 m_nAnnotationId( -1 ),
294 m_aAnnotationPositions(),
295 m_aSmartTagHandler(m_xComponentContext, m_xTextDocument),
296 m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault("TextInsertModeRange", uno::Reference<text::XTextRange>())),
297 m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)),
298 m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)),
299 m_nTableDepth(0),
300 m_nTableCellDepth(0),
301 m_nLastTableCellParagraphDepth(0),
302 m_bHasFtn(false),
303 m_bHasFtnSep(false),
304 m_bCheckFirstFootnoteTab(false),
305 m_bIgnoreNextTab(false),
306 m_bIsSplitPara(false),
307 m_bIsActualParagraphFramed( false ),
308 m_vTextFramesForChaining(),
309 m_bParaHadField(false),
310 m_bParaAutoBefore(false),
311 m_bFirstParagraphInCell(true),
312 m_bSaveFirstParagraphInCell(false)
315 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
316 utl::MediaDescriptor::PROP_DOCUMENTBASEURL(), OUString());
317 if (m_aBaseUrl.isEmpty()) {
318 m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault(
319 utl::MediaDescriptor::PROP_URL(), OUString());
322 appendTableManager( );
323 GetBodyText();
324 uno::Reference< text::XTextAppend > xBodyTextAppend( m_xBodyText, uno::UNO_QUERY );
325 m_aTextAppendStack.push(TextAppendContext(xBodyTextAppend,
326 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(m_xInsertTextRange)));
328 //todo: does it makes sense to set the body text as static text interface?
329 uno::Reference< text::XTextAppendAndConvert > xBodyTextAppendAndConvert( m_xBodyText, uno::UNO_QUERY );
330 m_pTableHandler = new DomainMapperTableHandler(xBodyTextAppendAndConvert, *this);
331 getTableManager( ).setHandler(m_pTableHandler);
333 getTableManager( ).startLevel();
334 m_bUsingEnhancedFields = !utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get(m_xComponentContext);
336 m_pSdtHelper = new SdtHelper(*this);
338 m_aRedlines.push(std::vector<RedlineParamsPtr>());
342 DomainMapper_Impl::~DomainMapper_Impl()
344 ChainTextFrames();
345 // Don't remove last paragraph when pasting, sw expects that empty paragraph.
346 if (m_bIsNewDoc)
347 RemoveLastParagraph();
348 if (hasTableManager())
350 getTableManager().endLevel();
351 popTableManager();
355 uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetPageStyles()
357 if(!m_xPageStyles1.is())
359 uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY );
360 if (xSupplier.is())
361 xSupplier->getStyleFamilies()->getByName("PageStyles") >>= m_xPageStyles1;
363 return m_xPageStyles1;
366 OUString DomainMapper_Impl::GetUnusedPageStyleName()
368 static const char DEFAULT_STYLE[] = "Converted";
369 if (!m_xNextUnusedPageStyleNo)
371 const uno::Sequence< OUString > aPageStyleNames = GetPageStyles()->getElementNames();
372 sal_Int32 nMaxIndex = 0;
373 // find the highest number x in each style with the name "DEFAULT_STYLE+x" and
374 // return an incremented name
376 for ( const auto& rStyleName : aPageStyleNames )
378 if ( rStyleName.startsWith( DEFAULT_STYLE ) )
380 sal_Int32 nIndex = rStyleName.copy( strlen( DEFAULT_STYLE ) ).toInt32();
381 if ( nIndex > nMaxIndex )
382 nMaxIndex = nIndex;
385 m_xNextUnusedPageStyleNo = nMaxIndex + 1;
388 OUString sPageStyleName = DEFAULT_STYLE + OUString::number( *m_xNextUnusedPageStyleNo );
389 *m_xNextUnusedPageStyleNo = *m_xNextUnusedPageStyleNo + 1;
390 return sPageStyleName;
393 uno::Reference< text::XText > const & DomainMapper_Impl::GetBodyText()
395 if(!m_xBodyText.is())
397 if (m_xInsertTextRange.is())
398 m_xBodyText = m_xInsertTextRange->getText();
399 else if (m_xTextDocument.is())
400 m_xBodyText = m_xTextDocument->getText();
402 return m_xBodyText;
406 uno::Reference< beans::XPropertySet > const & DomainMapper_Impl::GetDocumentSettings()
408 if( !m_xDocumentSettings.is() && m_xTextFactory.is())
410 m_xDocumentSettings.set( m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY );
412 return m_xDocumentSettings;
416 void DomainMapper_Impl::SetDocumentSettingsProperty( const OUString& rPropName, const uno::Any& rValue )
418 uno::Reference< beans::XPropertySet > xSettings = GetDocumentSettings();
419 if( xSettings.is() )
423 xSettings->setPropertyValue( rPropName, rValue );
425 catch( const uno::Exception& )
430 void DomainMapper_Impl::RemoveDummyParaForTableInSection()
432 SetIsDummyParaAddedForTableInSection(false);
433 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
434 SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
435 if (!pSectionContext)
436 return;
438 if (m_aTextAppendStack.empty())
439 return;
440 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
441 if (!xTextAppend.is())
442 return;
444 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(pSectionContext->GetStartingRange());
446 // Remove the extra NumPicBullets from the document,
447 // which get attached to the first paragraph in the
448 // document
449 ListsManager::Pointer pListTable = GetListTable();
450 pListTable->DisposeNumPicBullets();
452 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY);
453 if (xEnumerationAccess.is() && m_aTextAppendStack.size() == 1 )
455 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
456 uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
457 xParagraph->dispose();
460 void DomainMapper_Impl::AddDummyParaForTableInSection()
462 // Shapes can't have sections.
463 if (IsInShape())
464 return;
466 if (!m_aTextAppendStack.empty())
468 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
469 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
470 uno::Reference< text::XText > xText = xTextAppend->getText();
471 if(xCrsr.is() && xText.is())
473 xTextAppend->finishParagraph( uno::Sequence< beans::PropertyValue >() );
474 SetIsDummyParaAddedForTableInSection(true);
479 void DomainMapper_Impl::RemoveLastParagraph( )
481 if (m_bDiscardHeaderFooter)
482 return;
484 if (m_aTextAppendStack.empty())
485 return;
486 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
487 if (!xTextAppend.is())
488 return;
491 uno::Reference< text::XTextCursor > xCursor;
492 if (m_bIsNewDoc)
494 xCursor = xTextAppend->createTextCursor();
495 xCursor->gotoEnd(false);
497 else
498 xCursor = m_aTextAppendStack.top().xCursor;
499 uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY);
500 // Keep the character properties of the last but one paragraph, even if
501 // it's empty. This works for headers/footers, and maybe in other cases
502 // as well, but surely not in textboxes.
503 // fdo#58327: also do this at the end of the document: when pasting,
504 // a table before the cursor position would be deleted
505 // (but only for paste/insert, not load; otherwise it can happen that
506 // flys anchored at the disposed paragraph are deleted (fdo47036.rtf))
507 bool const bEndOfDocument(m_aTextAppendStack.size() == 1);
508 if ((IsInHeaderFooter() || (bEndOfDocument && !m_bIsNewDoc))
509 && xEnumerationAccess.is())
511 uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
512 uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
513 xParagraph->dispose();
515 else if (xCursor.is())
517 xCursor->goLeft( 1, true );
518 // If this is a text on a shape, possibly the text has the trailing
519 // newline removed already.
520 if (xCursor->getString() == SAL_NEWLINE_STRING ||
521 // tdf#105444 comments need an exception, if SAL_NEWLINE_STRING defined as "\r\n"
522 (sizeof(SAL_NEWLINE_STRING)-1 == 2 && xCursor->getString() == "\n"))
524 uno::Reference<beans::XPropertySet> xDocProps(GetTextDocument(), uno::UNO_QUERY);
525 const OUString aRecordChanges("RecordChanges");
526 uno::Any aPreviousValue(xDocProps->getPropertyValue(aRecordChanges));
528 // disable redlining for this operation, otherwise we might
529 // end up with an unwanted recorded deletion
530 xDocProps->setPropertyValue(aRecordChanges, uno::Any(false));
532 // delete
533 xCursor->setString(OUString());
535 // restore again
536 xDocProps->setPropertyValue(aRecordChanges, aPreviousValue);
540 catch( const uno::Exception& )
546 void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast )
548 m_bIsLastSectionGroup = bIsLast;
551 void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast )
553 m_bIsLastParaInSection = bIsLast;
557 void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst )
559 m_bIsFirstParaInSection = bIsFirst;
562 void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline )
564 m_bIsFirstParaInSectionAfterRedline = bIsFirstAfterRedline;
567 bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const
569 // Anchored objects may include multiple paragraphs,
570 // and none of them should be considered the first para in section.
571 return ( bAfterRedline ? m_bIsFirstParaInSectionAfterRedline : m_bIsFirstParaInSection )
572 && !IsInShape()
573 && !m_bIsInComments
574 && !IsInFootOrEndnote();
577 void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst)
579 m_bIsFirstParaInShape = bIsFirst;
582 void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded )
584 m_bDummyParaAddedForTableInSection = bIsAdded;
588 void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted )
590 m_bTextFrameInserted = bIsInserted;
594 void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr)
596 m_bParaSectpr = bParaSectpr;
600 void DomainMapper_Impl::SetSdt(bool bSdt)
602 m_bSdt = bSdt;
604 if (m_bSdt && !m_aTextAppendStack.empty())
606 m_xSdtEntryStart = GetTopTextAppend()->getEnd();
608 else
610 m_xSdtEntryStart.clear();
615 void DomainMapper_Impl::PushProperties(ContextType eId)
617 PropertyMapPtr pInsert(eId == CONTEXT_SECTION ?
618 (new SectionPropertyMap( m_bIsFirstSection )) :
619 eId == CONTEXT_PARAGRAPH ? new ParagraphPropertyMap : new PropertyMap);
620 if(eId == CONTEXT_SECTION)
622 if( m_bIsFirstSection )
623 m_bIsFirstSection = false;
624 // beginning with the second section group a section has to be inserted
625 // into the document
626 SectionPropertyMap* pSectionContext_ = dynamic_cast< SectionPropertyMap* >( pInsert.get() );
627 if (!m_aTextAppendStack.empty())
629 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
630 if (xTextAppend.is() && pSectionContext_)
631 pSectionContext_->SetStart( xTextAppend->getEnd() );
634 if(eId == CONTEXT_PARAGRAPH && m_bIsSplitPara)
636 m_aPropertyStacks[eId].push( GetTopContextOfType(eId));
637 m_bIsSplitPara = false;
639 else
641 m_aPropertyStacks[eId].push( pInsert );
643 m_aContextStack.push(eId);
645 m_pTopContext = m_aPropertyStacks[eId].top();
649 void DomainMapper_Impl::PushStyleProperties( const PropertyMapPtr& pStyleProperties )
651 m_aPropertyStacks[CONTEXT_STYLESHEET].push( pStyleProperties );
652 m_aContextStack.push(CONTEXT_STYLESHEET);
654 m_pTopContext = m_aPropertyStacks[CONTEXT_STYLESHEET].top();
658 void DomainMapper_Impl::PushListProperties(const PropertyMapPtr& pListProperties)
660 m_aPropertyStacks[CONTEXT_LIST].push( pListProperties );
661 m_aContextStack.push(CONTEXT_LIST);
662 m_pTopContext = m_aPropertyStacks[CONTEXT_LIST].top();
666 void DomainMapper_Impl::PopProperties(ContextType eId)
668 OSL_ENSURE(!m_aPropertyStacks[eId].empty(), "section stack already empty");
669 if ( m_aPropertyStacks[eId].empty() )
670 return;
672 if ( eId == CONTEXT_SECTION )
674 if (m_aPropertyStacks[eId].size() == 1) // tdf#112202 only top level !!!
676 m_pLastSectionContext = m_aPropertyStacks[eId].top();
679 else if (eId == CONTEXT_CHARACTER)
681 m_pLastCharacterContext = m_aPropertyStacks[eId].top();
682 // Sadly an assert about deferredCharacterProperties being empty is not possible
683 // here, because appendTextPortion() may not be called for every character section.
684 deferredCharacterProperties.clear();
687 if (!IsInFootOrEndnote() && IsInCustomFootnote() && !m_aPropertyStacks[eId].empty())
689 PropertyMapPtr pRet = m_aPropertyStacks[eId].top();
690 if (pRet->GetFootnote().is() && m_pFootnoteContext.is())
691 EndCustomFootnote();
694 m_aPropertyStacks[eId].pop();
695 m_aContextStack.pop();
696 if(!m_aContextStack.empty() && !m_aPropertyStacks[m_aContextStack.top()].empty())
698 m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top();
699 else
701 // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section context end");
702 m_pTopContext.clear();
707 PropertyMapPtr DomainMapper_Impl::GetTopContextOfType(ContextType eId)
709 PropertyMapPtr pRet;
710 if(!m_aPropertyStacks[eId].empty())
711 pRet = m_aPropertyStacks[eId].top();
712 return pRet;
715 bool DomainMapper_Impl::HasTopText() const
717 return !m_aTextAppendStack.empty();
720 uno::Reference< text::XTextAppend > const & DomainMapper_Impl::GetTopTextAppend()
722 OSL_ENSURE(!m_aTextAppendStack.empty(), "text append stack is empty" );
723 return m_aTextAppendStack.top().xTextAppend;
726 FieldContextPtr const & DomainMapper_Impl::GetTopFieldContext()
728 SAL_WARN_IF(m_aFieldStack.empty(), "writerfilter.dmapper", "Field stack is empty");
729 return m_aFieldStack.back();
732 void DomainMapper_Impl::InitTabStopFromStyle( const uno::Sequence< style::TabStop >& rInitTabStops )
734 OSL_ENSURE(m_aCurrentTabStops.empty(), "tab stops already initialized");
735 for( const auto& rTabStop : rInitTabStops)
737 m_aCurrentTabStops.emplace_back(rTabStop);
741 void DomainMapper_Impl::IncorporateTabStop( const DeletableTabStop & rTabStop )
743 sal_Int32 nConverted = rTabStop.Position;
744 auto aIt = std::find_if(m_aCurrentTabStops.begin(), m_aCurrentTabStops.end(),
745 [&nConverted](const DeletableTabStop& rCurrentTabStop) { return rCurrentTabStop.Position == nConverted; });
746 if( aIt != m_aCurrentTabStops.end() )
748 if( rTabStop.bDeleted )
749 m_aCurrentTabStops.erase( aIt );
750 else
751 *aIt = rTabStop;
753 else
754 m_aCurrentTabStops.push_back( rTabStop );
758 uno::Sequence< style::TabStop > DomainMapper_Impl::GetCurrentTabStopAndClear()
760 std::vector<style::TabStop> aRet;
761 for (const DeletableTabStop& rStop : m_aCurrentTabStops)
763 if (!rStop.bDeleted)
764 aRet.push_back(rStop);
766 m_aCurrentTabStops.clear();
767 return comphelper::containerToSequence(aRet);
770 OUString DomainMapper_Impl::GetCurrentParaStyleName()
772 // use saved currParaStyleName as a fallback, in case no particular para style name applied.
773 OUString sName = m_sCurrentParaStyleName;
774 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
775 if ( pParaContext && pParaContext->isSet(PROP_PARA_STYLE_NAME) )
776 pParaContext->getProperty(PROP_PARA_STYLE_NAME)->second >>= sName;
778 // In rare situations the name might still be blank, so use the default style,
779 // despite documentation that states, "If this attribute is not specified for any style,
780 // then no properties shall be applied to objects of the specified type."
781 // Word, however, assigns "Normal" style even in these situations.
782 if ( !m_bInStyleSheetImport && sName.isEmpty() )
783 sName = GetDefaultParaStyleName();
785 return sName;
788 OUString DomainMapper_Impl::GetDefaultParaStyleName()
790 // After import the default style won't change and is frequently requested: cache the LO style name.
791 // TODO assert !InStyleSheetImport? This function really only makes sense once import is finished anyway.
792 if ( m_sDefaultParaStyleName.isEmpty() )
794 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindDefaultParaStyle();
795 if ( pEntry && !pEntry->sConvertedStyleName.isEmpty() )
797 if ( !m_bInStyleSheetImport )
798 m_sDefaultParaStyleName = pEntry->sConvertedStyleName;
799 return pEntry->sConvertedStyleName;
801 else
802 return "Standard";
804 return m_sDefaultParaStyleName;
807 uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault)
809 while(pEntry.get( ) )
811 if(pEntry->pProperties)
813 std::optional<PropertyMap::Property> aProperty =
814 pEntry->pProperties->getProperty(eId);
815 if( aProperty )
817 if (pIsDocDefault)
818 *pIsDocDefault = pEntry->pProperties->isDocDefault(eId);
820 return aProperty->second;
823 //search until the property is set or no parent is available
824 StyleSheetEntryPtr pNewEntry;
825 if ( !pEntry->sBaseStyleIdentifier.isEmpty() )
826 pNewEntry = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier);
828 SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
830 if (pEntry == pNewEntry) //fdo#49587
831 break;
833 pEntry = pNewEntry;
835 // not found in style, try the document's DocDefault properties
836 if ( bDocDefaults && bPara )
838 const PropertyMapPtr& pDefaultParaProps = GetStyleSheetTable()->GetDefaultParaProps();
839 if ( pDefaultParaProps )
841 std::optional<PropertyMap::Property> aProperty = pDefaultParaProps->getProperty(eId);
842 if ( aProperty )
844 if (pIsDocDefault)
845 *pIsDocDefault = true;
847 return aProperty->second;
851 if ( bDocDefaults && isCharacterProperty(eId) )
853 const PropertyMapPtr& pDefaultCharProps = GetStyleSheetTable()->GetDefaultCharProps();
854 if ( pDefaultCharProps )
856 std::optional<PropertyMap::Property> aProperty = pDefaultCharProps->getProperty(eId);
857 if ( aProperty )
859 if (pIsDocDefault)
860 *pIsDocDefault = true;
862 return aProperty->second;
867 if (pIsDocDefault)
868 *pIsDocDefault = false;
870 return uno::Any();
873 uno::Any DomainMapper_Impl::GetPropertyFromParaStyleSheet(PropertyIds eId)
875 StyleSheetEntryPtr pEntry;
876 if ( m_bInStyleSheetImport )
877 pEntry = GetStyleSheetTable()->GetCurrentEntry();
878 else
879 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName());
880 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
883 uno::Any DomainMapper_Impl::GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext)
885 if ( m_bInStyleSheetImport || eId == PROP_CHAR_STYLE_NAME || !isCharacterProperty(eId) )
886 return uno::Any();
888 StyleSheetEntryPtr pEntry;
889 OUString sCharStyleName;
890 if ( GetAnyProperty(PROP_CHAR_STYLE_NAME, rContext) >>= sCharStyleName )
891 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sCharStyleName);
892 return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/false, /*bPara=*/false);
895 uno::Any DomainMapper_Impl::GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext)
897 // first look in directly applied attributes
898 if ( rContext )
900 std::optional<PropertyMap::Property> aProperty = rContext->getProperty(eId);
901 if ( aProperty )
902 return aProperty->second;
905 // then look whether it was inherited from a directly applied character style
906 if ( eId != PROP_CHAR_STYLE_NAME && isCharacterProperty(eId) )
908 uno::Any aRet = GetPropertyFromCharStyleSheet(eId, rContext);
909 if ( aRet.hasValue() )
910 return aRet;
913 // then look in current paragraph style, and docDefaults
914 return GetPropertyFromParaStyleSheet(eId);
917 OUString DomainMapper_Impl::GetListStyleName(sal_Int32 nListId)
919 auto const pList(GetListTable()->GetList( nListId ));
920 return pList ? pList->GetStyleName() : OUString();
923 ListsManager::Pointer const & DomainMapper_Impl::GetListTable()
925 if(!m_pListTable)
926 m_pListTable =
927 new ListsManager( m_rDMapper, m_xTextFactory );
928 return m_pListTable;
932 void DomainMapper_Impl::deferBreak( BreakType deferredBreakType)
934 switch (deferredBreakType)
936 case COLUMN_BREAK:
937 m_bIsColumnBreakDeferred = true;
938 break;
939 case PAGE_BREAK:
940 // See SwWW8ImplReader::HandlePageBreakChar(), page break should be
941 // ignored inside tables.
942 if (m_nTableDepth > 0)
943 return;
945 m_bIsPageBreakDeferred = true;
946 break;
947 default:
948 return;
952 bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType )
954 switch (deferredBreakType)
956 case COLUMN_BREAK:
957 return m_bIsColumnBreakDeferred;
958 case PAGE_BREAK:
959 return m_bIsPageBreakDeferred;
960 default:
961 return false;
965 void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType)
967 switch (deferredBreakType)
969 case COLUMN_BREAK:
970 m_bIsColumnBreakDeferred = false;
971 break;
972 case PAGE_BREAK:
973 m_bIsPageBreakDeferred = false;
974 break;
975 default:
976 break;
980 void DomainMapper_Impl::clearDeferredBreaks()
982 m_bIsColumnBreakDeferred = false;
983 m_bIsPageBreakDeferred = false;
986 void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred)
988 m_bSdtEndDeferred = bSdtEndDeferred;
991 bool DomainMapper_Impl::isSdtEndDeferred() const
993 return m_bSdtEndDeferred;
996 void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred)
998 m_bParaSdtEndDeferred = bParaSdtEndDeferred;
1001 bool DomainMapper_Impl::isParaSdtEndDeferred() const
1003 return m_bParaSdtEndDeferred;
1006 static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties,
1007 uno::Reference<text::XTextRange> const& xStartTextRange,
1008 uno::Reference<text::XTextRange> const& xEndTextRange )
1012 if (!xStartTextRange.is()) //rhbz#1077780
1013 return;
1014 uno::Reference<text::XTextCursor> xRangeCursor = xStartTextRange->getText()->createTextCursorByRange( xStartTextRange );
1015 xRangeCursor->gotoRange( xEndTextRange, true );
1017 uno::Reference<beans::XPropertySet> xTextRangeProperties(xRangeCursor, uno::UNO_QUERY);
1018 if(!xTextRangeProperties.is())
1019 return ;
1021 static PropertyIds const aBorderProperties[] =
1023 PROP_LEFT_BORDER,
1024 PROP_RIGHT_BORDER,
1025 PROP_TOP_BORDER,
1026 PROP_BOTTOM_BORDER,
1027 PROP_LEFT_BORDER_DISTANCE,
1028 PROP_RIGHT_BORDER_DISTANCE,
1029 PROP_TOP_BORDER_DISTANCE,
1030 PROP_BOTTOM_BORDER_DISTANCE
1033 for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty)
1035 OUString sPropertyName = getPropertyName(aBorderProperties[nProperty]);
1036 beans::PropertyValue aValue;
1037 aValue.Name = sPropertyName;
1038 aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName);
1039 rFrameProperties.push_back(aValue);
1040 if( nProperty < 4 )
1041 xTextRangeProperties->setPropertyValue( sPropertyName, uno::makeAny(table::BorderLine2()));
1044 catch( const uno::Exception& )
1050 static void lcl_AddRangeAndStyle(
1051 ParagraphPropertiesPtr const & pToBeSavedProperties,
1052 uno::Reference< text::XTextAppend > const& xTextAppend,
1053 const PropertyMapPtr& pPropertyMap,
1054 TextAppendContext const & rAppendContext)
1056 uno::Reference<text::XParagraphCursor> xParaCursor(
1057 xTextAppend->createTextCursorByRange( rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()), uno::UNO_QUERY_THROW );
1058 pToBeSavedProperties->SetEndingRange(xParaCursor->getStart());
1059 xParaCursor->gotoStartOfParagraph( false );
1061 pToBeSavedProperties->SetStartingRange(xParaCursor->getStart());
1062 if(pPropertyMap)
1064 std::optional<PropertyMap::Property> aParaStyle = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME);
1065 if( aParaStyle )
1067 OUString sName;
1068 aParaStyle->second >>= sName;
1069 pToBeSavedProperties->SetParaStyleName(sName);
1075 //define some default frame width - 0cm ATM: this allow the frame to be wrapped around the text
1076 static constexpr sal_Int32 DEFAULT_FRAME_MIN_WIDTH = 0;
1077 static constexpr sal_Int32 DEFAULT_FRAME_MIN_HEIGHT = 0;
1078 static constexpr sal_Int32 DEFAULT_VALUE = 0;
1080 void DomainMapper_Impl::CheckUnregisteredFrameConversion( )
1082 if (m_aTextAppendStack.empty())
1083 return;
1084 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
1085 // n#779642: ignore fly frame inside table as it could lead to messy situations
1086 if (!rAppendContext.pLastParagraphProperties.get())
1087 return;
1088 if (!rAppendContext.pLastParagraphProperties->IsFrameMode())
1089 return;
1090 if (!hasTableManager())
1091 return;
1092 if (getTableManager().isInTable())
1093 return;
1096 StyleSheetEntryPtr pParaStyle =
1097 GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rAppendContext.pLastParagraphProperties->GetParaStyleName());
1099 std::vector<beans::PropertyValue> aFrameProperties;
1101 if ( pParaStyle.get( ) )
1103 const ParagraphProperties* pStyleProperties = dynamic_cast<const ParagraphProperties*>( pParaStyle->pProperties.get() );
1104 if (!pStyleProperties)
1105 return;
1106 sal_Int32 nWidth =
1107 rAppendContext.pLastParagraphProperties->Getw() > 0 ?
1108 rAppendContext.pLastParagraphProperties->Getw() :
1109 pStyleProperties->Getw();
1110 bool bAutoWidth = nWidth < 1;
1111 if( bAutoWidth )
1112 nWidth = DEFAULT_FRAME_MIN_WIDTH;
1113 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth));
1115 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT),
1116 rAppendContext.pLastParagraphProperties->Geth() > 0 ?
1117 rAppendContext.pLastParagraphProperties->Geth() :
1118 pStyleProperties->Geth() > 0 ? pStyleProperties->Geth() : DEFAULT_FRAME_MIN_HEIGHT));
1120 sal_Int16 nhRule = sal_Int16(
1121 rAppendContext.pLastParagraphProperties->GethRule() >= 0 ?
1122 rAppendContext.pLastParagraphProperties->GethRule() :
1123 pStyleProperties->GethRule());
1124 if ( nhRule < 0 )
1126 if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 ||
1127 pStyleProperties->GethRule() >= 0 )
1129 // [MS-OE376] Word uses a default value of "atLeast" for
1130 // this attribute when the value of the h attribute is not 0.
1131 nhRule = text::SizeType::MIN;
1133 else
1135 nhRule = text::SizeType::VARIABLE;
1138 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule));
1140 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX));
1142 sal_Int16 nHoriOrient = sal_Int16(
1143 rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ?
1144 rAppendContext.pLastParagraphProperties->GetxAlign() :
1145 pStyleProperties->GetxAlign() >= 0 ? pStyleProperties->GetxAlign() : text::HoriOrientation::NONE );
1146 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient));
1148 //set a non negative default value
1149 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION),
1150 rAppendContext.pLastParagraphProperties->IsxValid() ?
1151 rAppendContext.pLastParagraphProperties->Getx() :
1152 pStyleProperties->IsxValid() ? pStyleProperties->Getx() : DEFAULT_VALUE));
1154 //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11
1155 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), sal_Int16(
1156 rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 ?
1157 rAppendContext.pLastParagraphProperties->GethAnchor() :
1158 pStyleProperties->GethAnchor() >=0 ? pStyleProperties->GethAnchor() : text::RelOrientation::FRAME )));
1160 sal_Int16 nVertOrient = sal_Int16(
1161 rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ?
1162 rAppendContext.pLastParagraphProperties->GetyAlign() :
1163 pStyleProperties->GetyAlign() >= 0 ? pStyleProperties->GetyAlign() : text::VertOrientation::NONE );
1164 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient));
1166 //set a non negative default value
1167 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION),
1168 rAppendContext.pLastParagraphProperties->IsyValid() ?
1169 rAppendContext.pLastParagraphProperties->Gety() :
1170 pStyleProperties->IsyValid() ? pStyleProperties->Gety() : DEFAULT_VALUE));
1172 //Default the anchor in case FramePr_vAnchor is missing ECMA 17.3.1.11
1173 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), sal_Int16(
1174 rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ?
1175 rAppendContext.pLastParagraphProperties->GetvAnchor() :
1176 pStyleProperties->GetvAnchor() >= 0 ? pStyleProperties->GetvAnchor() : text::RelOrientation::FRAME )));
1178 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SURROUND),
1179 rAppendContext.pLastParagraphProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE
1180 ? rAppendContext.pLastParagraphProperties->GetWrap()
1181 : pStyleProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE
1182 ? pStyleProperties->GetWrap()
1183 : text::WrapTextMode_NONE ));
1185 /** FDO#73546 : distL & distR should be unsigned integers <Ecma 20.4.3.6>
1186 Swapped the array elements 11,12 & 13,14 since 11 & 12 are
1187 LEFT & RIGHT margins and 13,14 are TOP and BOTTOM margins respectively.
1189 sal_Int32 nRightDist;
1190 sal_Int32 nLeftDist = nRightDist =
1191 rAppendContext.pLastParagraphProperties->GethSpace() >= 0 ?
1192 rAppendContext.pLastParagraphProperties->GethSpace() :
1193 pStyleProperties->GethSpace() >= 0 ? pStyleProperties->GethSpace() : 0;
1195 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nLeftDist));
1196 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nRightDist));
1198 sal_Int32 nBottomDist;
1199 sal_Int32 nTopDist = nBottomDist =
1200 rAppendContext.pLastParagraphProperties->GetvSpace() >= 0 ?
1201 rAppendContext.pLastParagraphProperties->GetvSpace() :
1202 pStyleProperties->GetvSpace() >= 0 ? pStyleProperties->GetvSpace() : 0;
1204 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nTopDist));
1205 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nBottomDist));
1206 // If there is no fill, the Word default is 100% transparency.
1207 // Otherwise CellColorHandler has priority, and this setting
1208 // will be ignored.
1209 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BACK_COLOR_TRANSPARENCY), sal_Int32(100)));
1211 uno::Sequence<beans::PropertyValue> aGrabBag( comphelper::InitPropertySequence({
1212 { "ParaFrameProperties", uno::Any(rAppendContext.pLastParagraphProperties->IsFrameMode()) }
1213 }));
1214 aFrameProperties.push_back(comphelper::makePropertyValue("FrameInteropGrabBag", aGrabBag));
1216 lcl_MoveBorderPropertiesToFrame(aFrameProperties,
1217 rAppendContext.pLastParagraphProperties->GetStartingRange(),
1218 rAppendContext.pLastParagraphProperties->GetEndingRange());
1220 else
1222 sal_Int32 nWidth = rAppendContext.pLastParagraphProperties->Getw();
1223 bool bAutoWidth = nWidth < 1;
1224 if( bAutoWidth )
1225 nWidth = DEFAULT_FRAME_MIN_WIDTH;
1226 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth));
1228 sal_Int16 nhRule = sal_Int16(rAppendContext.pLastParagraphProperties->GethRule());
1229 if ( nhRule < 0 )
1231 if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 )
1233 // [MS-OE376] Word uses a default value of atLeast for
1234 // this attribute when the value of the h attribute is not 0.
1235 nhRule = text::SizeType::MIN;
1237 else
1239 nhRule = text::SizeType::VARIABLE;
1242 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule));
1244 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX));
1246 sal_Int16 nHoriOrient = sal_Int16(
1247 rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ?
1248 rAppendContext.pLastParagraphProperties->GetxAlign() :
1249 text::HoriOrientation::NONE );
1250 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient));
1252 sal_Int16 nVertOrient = sal_Int16(
1253 rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ?
1254 rAppendContext.pLastParagraphProperties->GetyAlign() :
1255 text::VertOrientation::NONE );
1256 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient));
1258 sal_Int32 nVertDist = rAppendContext.pLastParagraphProperties->GethSpace();
1259 if( nVertDist < 0 )
1260 nVertDist = 0;
1261 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nVertDist));
1262 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nVertDist));
1264 sal_Int32 nHoriDist = rAppendContext.pLastParagraphProperties->GetvSpace();
1265 if( nHoriDist < 0 )
1266 nHoriDist = 0;
1267 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nHoriDist));
1268 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nHoriDist));
1270 if( rAppendContext.pLastParagraphProperties->Geth() > 0 )
1271 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), rAppendContext.pLastParagraphProperties->Geth()));
1273 if( rAppendContext.pLastParagraphProperties->IsxValid() )
1274 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Getx()));
1276 if( rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 )
1277 aFrameProperties.push_back(comphelper::makePropertyValue("HoriOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GethAnchor())));
1279 if( rAppendContext.pLastParagraphProperties->IsyValid() )
1280 aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Gety()));
1282 if( rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 )
1283 aFrameProperties.push_back(comphelper::makePropertyValue("VertOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GetvAnchor())));
1285 if( rAppendContext.pLastParagraphProperties->GetWrap() >= text::WrapTextMode_NONE )
1286 aFrameProperties.push_back(comphelper::makePropertyValue("Surround", rAppendContext.pLastParagraphProperties->GetWrap()));
1288 lcl_MoveBorderPropertiesToFrame(aFrameProperties,
1289 rAppendContext.pLastParagraphProperties->GetStartingRange(),
1290 rAppendContext.pLastParagraphProperties->GetEndingRange());
1293 //frame conversion has to be executed after table conversion
1294 RegisterFrameConversion(
1295 rAppendContext.pLastParagraphProperties->GetStartingRange(),
1296 rAppendContext.pLastParagraphProperties->GetEndingRange(),
1297 aFrameProperties );
1299 catch( const uno::Exception& )
1304 /// Check if the style or its parent has a list id, recursively.
1305 static sal_Int32 lcl_getListId(const StyleSheetEntryPtr& rEntry, const StyleSheetTablePtr& rStyleTable, bool & rNumberingFromBaseStyle)
1307 const StyleSheetPropertyMap* pEntryProperties = dynamic_cast<const StyleSheetPropertyMap*>(rEntry->pProperties.get());
1308 if (!pEntryProperties)
1309 return -1;
1311 sal_Int32 nListId = pEntryProperties->GetListId();
1312 // The style itself has a list id.
1313 if (nListId >= 0)
1314 return nListId;
1316 // The style has no parent.
1317 if (rEntry->sBaseStyleIdentifier.isEmpty())
1318 return -1;
1320 const StyleSheetEntryPtr pParent = rStyleTable->FindStyleSheetByISTD(rEntry->sBaseStyleIdentifier);
1321 // No such parent style or loop in the style hierarchy.
1322 if (!pParent || pParent == rEntry)
1323 return -1;
1325 rNumberingFromBaseStyle = true;
1327 return lcl_getListId(pParent, rStyleTable, rNumberingFromBaseStyle);
1330 void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove )
1332 if (m_bDiscardHeaderFooter)
1333 return;
1335 if (!m_aFieldStack.empty())
1337 FieldContextPtr pFieldContext = m_aFieldStack.back();
1338 if (pFieldContext && !pFieldContext->IsCommandCompleted())
1340 std::vector<OUString> aCommandParts = pFieldContext->GetCommandParts();
1341 if (!aCommandParts.empty() && aCommandParts[0] == "IF")
1343 // Conditional text field conditions don't support linebreaks in Writer.
1344 return;
1348 if (pFieldContext && pFieldContext->IsCommandCompleted())
1350 if (pFieldContext->GetFieldId() == FIELD_IF)
1352 // Conditional text fields can't contain newlines, finish the paragraph later.
1353 FieldParagraph aFinish{pPropertyMap, bRemove};
1354 pFieldContext->GetParagraphsToFinish().push_back(aFinish);
1355 return;
1360 #ifdef DBG_UTIL
1361 TagLogger::getInstance().startElement("finishParagraph");
1362 #endif
1364 m_nLastTableCellParagraphDepth = m_nTableCellDepth;
1365 ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pPropertyMap.get() );
1366 if (m_aTextAppendStack.empty())
1367 return;
1368 TextAppendContext& rAppendContext = m_aTextAppendStack.top();
1369 uno::Reference< text::XTextAppend > xTextAppend(rAppendContext.xTextAppend);
1370 #ifdef DBG_UTIL
1371 TagLogger::getInstance().attribute("isTextAppend", sal_uInt32(xTextAppend.is()));
1372 #endif
1374 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetCurrentParaStyleName() );
1375 OSL_ENSURE( pEntry.get(), "no style sheet found" );
1376 const StyleSheetPropertyMap* pStyleSheetProperties = dynamic_cast<const StyleSheetPropertyMap*>(pEntry ? pEntry->pProperties.get() : nullptr);
1377 bool isNumberingViaStyle(false);
1378 //apply numbering to paragraph if it was set at the style, but only if the paragraph itself
1379 //does not specify the numbering
1380 if ( !bRemove && pStyleSheetProperties && pParaContext && !pParaContext->isSet(PROP_NUMBERING_RULES) )
1382 bool bNumberingFromBaseStyle = false;
1383 sal_Int32 nListId = pEntry ? lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle) : -1;
1384 auto const pList(GetListTable()->GetList(nListId));
1385 if (pList && nListId >= 0 && !pParaContext->isSet(PROP_NUMBERING_STYLE_NAME))
1387 pParaContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::makeAny( pList->GetStyleName() ), false);
1388 isNumberingViaStyle = true;
1390 // Indent properties from the paragraph style have priority
1391 // over the ones from the numbering styles in Word
1392 // but in Writer numbering styles have priority,
1393 // so insert directly into the paragraph properties to compensate.
1394 std::optional<PropertyMap::Property> oProperty;
1395 const StyleSheetEntryPtr pParent = (!pEntry->sBaseStyleIdentifier.isEmpty()) ? GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier) : nullptr;
1396 const StyleSheetPropertyMap* pParentProperties = dynamic_cast<const StyleSheetPropertyMap*>(pParent ? pParent->pProperties.get() : nullptr);
1397 if (!pEntry->sBaseStyleIdentifier.isEmpty())
1398 if ( (oProperty = pStyleSheetProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))
1399 // If the numbering comes from a base style, indent of the base style has also priority.
1400 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))) )
1401 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false);
1402 if ( (oProperty = pStyleSheetProperties->getProperty(PROP_PARA_LEFT_MARGIN))
1403 || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_LEFT_MARGIN))) )
1404 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, oProperty->second, /*bOverwrite=*/false);
1406 // We're inheriting properties from a numbering style. Make sure a possible right margin is inherited from the base style.
1407 sal_Int32 nParaRightMargin;
1408 if ( pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_RIGHT_MARGIN)) && (nParaRightMargin = oProperty->second.get<sal_Int32>()) != 0 )
1410 // If we're setting the right margin, we should set the first / left margin as well from the numbering style.
1411 const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, pStyleSheetProperties->GetListLevel(), "FirstLineIndent");
1412 const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, pStyleSheetProperties->GetListLevel(), "IndentAt");
1413 if (nFirstLineIndent != 0)
1414 pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::makeAny(nFirstLineIndent), /*bOverwrite=*/false);
1415 if (nParaLeftMargin != 0)
1416 pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::makeAny(nParaLeftMargin), /*bOverwrite=*/false);
1418 pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, uno::makeAny(nParaRightMargin), /*bOverwrite=*/false);
1422 if ( pStyleSheetProperties->GetListLevel() >= 0 )
1423 pParaContext->Insert( PROP_NUMBERING_LEVEL, uno::makeAny(pStyleSheetProperties->GetListLevel()), false);
1426 // apply AutoSpacing: it has priority over all other margin settings
1427 // (note that numbering with autoSpacing is handled separately later on)
1428 const bool bAllowAdjustments = !GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing();
1429 sal_Int32 nBeforeAutospacing = -1;
1430 bool bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
1431 // apply INHERITED autospacing only if top margin is not set
1432 if ( bIsAutoSet || (pParaContext && !pParaContext->isSet(PROP_PARA_TOP_MARGIN)) )
1433 GetAnyProperty(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, pPropertyMap) >>= nBeforeAutospacing;
1434 if ( nBeforeAutospacing > -1 && pParaContext )
1436 if ( bAllowAdjustments )
1438 if ( GetIsFirstParagraphInShape() ||
1439 (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) ||
1440 (m_bFirstParagraphInCell && m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth) )
1442 nBeforeAutospacing = 0;
1443 // export requires grabbag to match top_margin, so keep them in sync
1444 if ( bIsAutoSet )
1445 pParaContext->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::makeAny( sal_Int32(0) ),true, PARA_GRAB_BAG );
1448 pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::makeAny(nBeforeAutospacing));
1451 sal_Int32 nAfterAutospacing = -1;
1452 bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING);
1453 bool bApplyAutospacing = bIsAutoSet || (pParaContext && !pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN));
1454 if ( bApplyAutospacing )
1455 GetAnyProperty(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, pPropertyMap) >>= nAfterAutospacing;
1456 if ( nAfterAutospacing > -1 && pParaContext )
1458 pParaContext->Insert(PROP_PARA_BOTTOM_MARGIN, uno::makeAny(nAfterAutospacing));
1459 bApplyAutospacing = bAllowAdjustments;
1461 else
1462 bApplyAutospacing = false;
1464 // tell TableManager to reset the bottom margin if it determines that this is the cell's last paragraph.
1465 if ( hasTableManager() && getTableManager().isInCell() )
1466 getTableManager().setCellLastParaAfterAutospacing( bApplyAutospacing );
1469 if (xTextAppend.is() && pParaContext && hasTableManager() && !getTableManager().isIgnore())
1473 /*the following combinations of previous and current frame settings can occur:
1474 (1) - no old frame and no current frame -> no special action
1475 (2) - no old frame and current DropCap -> save DropCap for later use, don't call finishParagraph
1476 remove character properties of the DropCap?
1477 (3) - no old frame and current Frame -> save Frame for later use
1478 (4) - old DropCap and no current frame -> add DropCap to the properties of the finished paragraph, delete previous setting
1479 (5) - old DropCap and current frame -> add DropCap to the properties of the finished paragraph, save current frame settings
1480 (6) - old Frame and new DropCap -> add old Frame, save DropCap for later use
1481 (7) - old Frame and new same Frame -> continue
1482 (8) - old Frame and new different Frame -> add old Frame, save new Frame for later use
1483 (9) - old Frame and no current frame -> add old Frame, delete previous settings
1485 old _and_ new DropCap must not occur
1488 bool bIsDropCap =
1489 pParaContext->IsFrameMode() &&
1490 sal::static_int_cast<Id>(pParaContext->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none;
1492 style::DropCapFormat aDrop;
1493 ParagraphPropertiesPtr pToBeSavedProperties;
1494 bool bKeepLastParagraphProperties = false;
1495 if( bIsDropCap )
1497 uno::Reference<text::XParagraphCursor> xParaCursor(
1498 xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
1499 //select paragraph
1500 xParaCursor->gotoStartOfParagraph( true );
1501 uno::Reference< beans::XPropertyState > xParaProperties( xParaCursor, uno::UNO_QUERY_THROW );
1502 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT));
1503 xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT));
1504 //handles (2) and part of (6)
1505 pToBeSavedProperties = new ParagraphProperties(*pParaContext);
1506 sal_Int32 nCount = xParaCursor->getString().getLength();
1507 pToBeSavedProperties->SetDropCapLength(nCount > 0 && nCount < 255 ? static_cast<sal_Int8>(nCount) : 1);
1509 if( rAppendContext.pLastParagraphProperties.get() )
1511 if( sal::static_int_cast<Id>(rAppendContext.pLastParagraphProperties->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none)
1513 //handles (4) and part of (5)
1514 //create a DropCap property, add it to the property sequence of finishParagraph
1515 sal_Int32 nLines = rAppendContext.pLastParagraphProperties->GetLines();
1516 aDrop.Lines = nLines > 0 && nLines < SAL_MAX_INT8 ? static_cast<sal_Int8>(nLines) : 2;
1517 aDrop.Count = rAppendContext.pLastParagraphProperties->GetDropCapLength();
1518 sal_Int32 nHSpace = rAppendContext.pLastParagraphProperties->GethSpace();
1519 aDrop.Distance = nHSpace > 0 && nHSpace < SAL_MAX_INT16 ? static_cast<sal_Int16>(nHSpace) : 0;
1520 //completes (5)
1521 if( pParaContext->IsFrameMode() )
1522 pToBeSavedProperties = new ParagraphProperties(*pParaContext);
1524 else if(*rAppendContext.pLastParagraphProperties == *pParaContext )
1526 //handles (7)
1527 rAppendContext.pLastParagraphProperties->SetEndingRange(rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd());
1528 bKeepLastParagraphProperties = true;
1530 else
1532 //handles (8)(9) and completes (6)
1533 CheckUnregisteredFrameConversion( );
1535 // If different frame properties are set on this paragraph, keep them.
1536 if ( !bIsDropCap && pParaContext->IsFrameMode() )
1538 pToBeSavedProperties = new ParagraphProperties(*pParaContext);
1539 lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext);
1543 else
1545 // (1) doesn't need handling
1547 if( !bIsDropCap && pParaContext->IsFrameMode() )
1549 pToBeSavedProperties = new ParagraphProperties(*pParaContext);
1550 lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext);
1553 std::vector<beans::PropertyValue> aProperties;
1554 if (pPropertyMap.get())
1556 aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues());
1558 // TODO: this *should* work for RTF but there are test failures, maybe rtftok doesn't distinguish between formatting for the paragraph marker and for the paragraph as a whole; needs investigation
1559 if (pPropertyMap.get() && IsOOXMLImport())
1561 // tdf#64222 filter out the "paragraph marker" formatting and
1562 // set it as a separate paragraph property, not a empty hint at
1563 // end of paragraph
1564 std::vector<beans::NamedValue> charProperties;
1565 for (auto it = aProperties.begin(); it != aProperties.end(); )
1567 // this condition isn't ideal but as it happens all
1568 // RES_CHRATR_* have names that start with "Char"
1569 if (it->Name.startsWith("Char")
1570 // TODO testParagraphMark *wants* this but it's some effort to create a real SwFormatCharFormat...
1571 && !it->Name.startsWith("CharStyleName"))
1573 charProperties.emplace_back(it->Name, it->Value);
1574 // as testN793262 demonstrates, font size in rPr must
1575 // affect the paragraph size => also insert empty hint!
1576 // it = aProperties.erase(it);
1578 ++it;
1580 if (!charProperties.empty())
1582 aProperties.push_back(beans::PropertyValue("ListAutoFormat",
1583 0, uno::makeAny(comphelper::containerToSequence(charProperties)), beans::PropertyState_DIRECT_VALUE));
1586 if( !bIsDropCap )
1588 if( aDrop.Lines > 1 )
1590 beans::PropertyValue aValue;
1591 aValue.Name = getPropertyName(PROP_DROP_CAP_FORMAT);
1592 aValue.Value <<= aDrop;
1593 aProperties.push_back(aValue);
1595 uno::Reference< text::XTextRange > xTextRange;
1596 if (rAppendContext.xInsertPosition.is())
1598 xTextRange = xTextAppend->finishParagraphInsert( comphelper::containerToSequence(aProperties), rAppendContext.xInsertPosition );
1599 rAppendContext.xCursor->gotoNextParagraph(false);
1600 if (rAppendContext.pLastParagraphProperties.get())
1601 rAppendContext.pLastParagraphProperties->SetEndingRange(xTextRange->getEnd());
1603 else
1605 uno::Reference<text::XTextCursor> xCursor;
1606 if (m_bParaHadField && !m_bIsInComments && !xTOCMarkerCursor.is())
1608 // Workaround to make sure char props of the field are not lost.
1609 // Not relevant for editeng-based comments.
1610 // Not relevant for fields inside a TOC field.
1611 OUString const sMarker("X");
1612 xCursor = xTextAppend->getText()->createTextCursor();
1613 if (xCursor.is())
1614 xCursor->gotoEnd(false);
1615 PropertyMapPtr pEmpty(new PropertyMap());
1616 appendTextPortion(sMarker, pEmpty);
1619 // Check if top / bottom margin has to be updated, now that we know the numbering status of both the previous and
1620 // the current text node.
1621 auto itNumberingRules = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
1623 return rValue.Name == "NumberingRules";
1625 if (itNumberingRules != aProperties.end())
1627 // This textnode has numbering. Look up the numbering style name of the current and previous paragraph.
1628 OUString aCurrentNumberingRuleName;
1629 uno::Reference<container::XNamed> xCurrentNumberingRules(itNumberingRules->Value, uno::UNO_QUERY);
1630 if (xCurrentNumberingRules.is())
1631 aCurrentNumberingRuleName = xCurrentNumberingRules->getName();
1632 OUString aPreviousNumberingRuleName;
1633 if (m_xPreviousParagraph.is())
1635 uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
1636 if (xPreviousNumberingRules.is())
1637 aPreviousNumberingRuleName = xPreviousNumberingRules->getName();
1640 if (!aPreviousNumberingRuleName.isEmpty() && aCurrentNumberingRuleName == aPreviousNumberingRuleName)
1642 // There was a previous textnode and it had the same numbering.
1643 if (m_bParaAutoBefore)
1645 // This before spacing is set to auto, set before space to 0.
1646 auto itParaTopMargin = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
1648 return rValue.Name == "ParaTopMargin";
1650 if (itParaTopMargin != aProperties.end())
1651 itParaTopMargin->Value <<= static_cast<sal_Int32>(0);
1652 else
1653 aProperties.push_back(comphelper::makePropertyValue("ParaTopMargin", static_cast<sal_Int32>(0)));
1655 uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq;
1656 m_xPreviousParagraph->getPropertyValue("ParaInteropGrabBag") >>= aPrevPropertiesSeq;
1657 auto aPrevProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aPrevPropertiesSeq);
1658 bool bPrevParaAutoAfter = std::any_of(aPrevProperties.begin(), aPrevProperties.end(), [](const beans::PropertyValue& rValue)
1660 return rValue.Name == "ParaBottomMarginAfterAutoSpacing";
1662 if (bPrevParaAutoAfter)
1664 // Previous after spacing is set to auto, set previous after space to 0.
1665 m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::makeAny(static_cast<sal_Int32>(0)));
1670 xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
1671 m_xPreviousParagraph.set(xTextRange, uno::UNO_QUERY);
1673 if (m_xPreviousParagraph.is() && // null for SvxUnoTextBase
1674 (isNumberingViaStyle || itNumberingRules != aProperties.end()))
1676 assert(dynamic_cast<ParagraphPropertyMap*>(pPropertyMap.get()));
1677 // Use lcl_getListId(), so we find the list ID in parent styles as well.
1678 bool bNumberingFromBaseStyle = false;
1679 sal_Int32 const nListId( isNumberingViaStyle
1680 ? lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle)
1681 : static_cast<ParagraphPropertyMap*>(pPropertyMap.get())->GetListId());
1682 if (ListDef::Pointer const& pList = m_pListTable->GetList(nListId))
1683 { // styles could refer to non-existing lists...
1684 AbstractListDef::Pointer const& pAbsList =
1685 pList->GetAbstractDefinition();
1686 if (pAbsList &&
1687 // SvxUnoTextRange doesn't have ListId
1688 m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ListId"))
1690 OUString paraId;
1691 m_xPreviousParagraph->getPropertyValue("ListId") >>= paraId;
1692 assert(!paraId.isEmpty()); // must be on some list?
1693 OUString const listId = pAbsList->MapListId(paraId);
1694 if (listId != paraId)
1696 m_xPreviousParagraph->setPropertyValue("ListId", uno::makeAny(listId));
1702 if (!rAppendContext.m_aAnchoredObjects.empty() && !IsInHeaderFooter())
1704 // Remember what objects are anchored to this paragraph.
1705 // That list is only used for Word compat purposes, and
1706 // it is only relevant for body text.
1707 AnchoredObjectsInfo aInfo;
1708 aInfo.m_xParagraph = xTextRange;
1709 aInfo.m_aAnchoredObjects = rAppendContext.m_aAnchoredObjects;
1710 m_aAnchoredObjectAnchors.push_back(aInfo);
1711 rAppendContext.m_aAnchoredObjects.clear();
1714 // We're no longer right after a table conversion.
1715 m_bConvertedTable = false;
1717 if (xCursor.is())
1719 xCursor->goLeft(1, true);
1720 xCursor->setString(OUString());
1723 getTableManager( ).handle(xTextRange);
1724 m_aSmartTagHandler.handle(xTextRange);
1726 if (xTextRange.is())
1728 // Get the end of paragraph character inserted
1729 uno::Reference< text::XTextCursor > xCur = xTextRange->getText( )->createTextCursor( );
1730 if (rAppendContext.xInsertPosition.is())
1731 xCur->gotoRange( rAppendContext.xInsertPosition, false );
1732 else
1733 xCur->gotoEnd( false );
1735 // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode
1736 sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode();
1737 if ( m_nTableDepth > 0 && nMode > 0 && nMode <= 14 )
1739 // skip new line
1740 xCur->goLeft(1, false);
1741 while ( xCur->goLeft(1, true) )
1743 OUString sChar = xCur->getString();
1744 if ( sChar == " " || sChar == "\t" || sChar == OUStringChar(u'\x00A0') )
1745 xCur->setString("");
1746 else
1747 break;
1749 xCur->goRight(2, false);
1752 xCur->goLeft( 1 , true );
1753 // Extend the redline ranges for empty paragraphs
1754 if ( !m_bParaChanged && m_previousRedline.get() )
1755 CreateRedline( xCur, m_previousRedline );
1756 CheckParaMarkerRedline( xCur );
1759 css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY);
1761 // table style precedence and not hidden shapes anchored to hidden empty table paragraphs
1762 if (xParaProps && (m_nTableDepth > 0 || !m_aAnchoredObjectAnchors.empty()) )
1764 // table style has got bigger precedence than docDefault style
1765 // collect these pending paragraph properties to process in endTable()
1766 uno::Reference<text::XTextCursor> xCur = xTextRange->getText( )->createTextCursor( );
1767 xCur->gotoEnd(false);
1768 xCur->goLeft(1, false);
1769 uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur);
1770 uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW);
1771 xParaCursor->gotoStartOfParagraph(false);
1772 if (m_nTableDepth > 0)
1774 TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps};
1775 m_aParagraphsToEndTable.push_back(aPending);
1778 // hidden empty paragraph with a not hidden shape, set as not hidden
1779 std::optional<PropertyMap::Property> pHidden;
1780 if ( !m_aAnchoredObjectAnchors.empty() && (pHidden = pParaContext->getProperty(PROP_CHAR_HIDDEN)) )
1782 bool bIsHidden = {}; // -Werror=maybe-uninitialized
1783 pHidden->second >>= bIsHidden;
1784 if (bIsHidden)
1786 bIsHidden = false;
1787 pHidden = GetTopContext()->getProperty(PROP_CHAR_HIDDEN);
1788 if (pHidden)
1789 pHidden->second >>= bIsHidden;
1790 if (!bIsHidden)
1792 uno::Reference<text::XTextCursor> xCur3 = xTextRange->getText()->createTextCursorByRange(xParaCursor);
1793 xCur3->goRight(1, true);
1794 if (xCur3->getString() == SAL_NEWLINE_STRING)
1796 uno::Reference< beans::XPropertySet > xProp( xCur3, uno::UNO_QUERY );
1797 xProp->setPropertyValue(getPropertyName(PROP_CHAR_HIDDEN), uno::makeAny(false));
1804 // tdf#118521 set paragraph top or bottom margin based on the paragraph style
1805 // if we already set the other margin with direct formatting
1806 if (xParaProps)
1808 const bool bTopSet = pParaContext->isSet(PROP_PARA_TOP_MARGIN);
1809 const bool bBottomSet = pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN);
1810 const bool bContextSet = pParaContext->isSet(PROP_PARA_CONTEXT_MARGIN);
1811 if ( !(bTopSet == bBottomSet && bBottomSet == bContextSet) )
1814 if ( !bTopSet )
1816 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN);
1817 if ( aMargin != uno::Any() )
1818 xParaProps->setPropertyValue("ParaTopMargin", aMargin);
1820 if ( !bBottomSet )
1822 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN);
1823 if ( aMargin != uno::Any() )
1824 xParaProps->setPropertyValue("ParaBottomMargin", aMargin);
1826 if ( !bContextSet )
1828 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN);
1829 if ( aMargin != uno::Any() )
1830 xParaProps->setPropertyValue("ParaContextMargin", aMargin);
1835 // Left, Right, and Hanging settings are also grouped. Ensure that all or none are set.
1836 if (xParaProps)
1838 const bool bLeftSet = pParaContext->isSet(PROP_PARA_LEFT_MARGIN);
1839 const bool bRightSet = pParaContext->isSet(PROP_PARA_RIGHT_MARGIN);
1840 const bool bFirstSet = pParaContext->isSet(PROP_PARA_FIRST_LINE_INDENT);
1841 if ( !(bLeftSet == bRightSet && bRightSet == bFirstSet) )
1843 if ( !bLeftSet )
1845 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_LEFT_MARGIN);
1846 if ( aMargin != uno::Any() )
1847 xParaProps->setPropertyValue("ParaLeftMargin", aMargin);
1849 if ( !bRightSet )
1851 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
1852 if ( aMargin != uno::Any() )
1853 xParaProps->setPropertyValue("ParaRightMargin", aMargin);
1855 if ( !bFirstSet )
1857 uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT);
1858 if ( aMargin != uno::Any() )
1859 xParaProps->setPropertyValue("ParaFirstLineIndent", aMargin);
1864 // fix table paragraph properties
1865 if ( xTextRange.is() && xParaProps && m_nTableDepth > 0 )
1867 uno::Sequence< beans::PropertyValue > aParaProps = pParaContext->GetPropertyValues(false);
1868 uno::Reference<text::XTextCursor> xCur = xTextRange->getText()->createTextCursorByRange(xTextRange);
1869 uno::Reference< beans::XPropertyState > xRunProperties( xCur, uno::UNO_QUERY_THROW );
1870 // tdf#90069 in tables, apply paragraph level character style also on
1871 // paragraph level to support its copy during insertion of new table rows
1872 for( const auto& rParaProp : std::as_const(aParaProps) )
1874 if ( rParaProp.Name.startsWith("Char") && rParaProp.Name != "CharStyleName" && rParaProp.Name != "CharInteropGrabBag" &&
1875 // all text portions contain the same value, so next setPropertyValue() won't overwrite part of them
1876 xRunProperties->getPropertyState(rParaProp.Name) == css::beans::PropertyState_DIRECT_VALUE )
1878 uno::Reference<beans::XPropertySet> xRunPropertySet(xCur, uno::UNO_QUERY);
1879 xParaProps->setPropertyValue( rParaProp.Name, xRunPropertySet->getPropertyValue(rParaProp.Name) );
1883 // tdf#128959 table paragraphs haven't got window and orphan controls
1884 uno::Any aAny = uno::makeAny(static_cast<sal_Int8>(0));
1885 xParaProps->setPropertyValue("ParaOrphans", aAny);
1886 xParaProps->setPropertyValue("ParaWidows", aAny);
1889 if( !bKeepLastParagraphProperties )
1890 rAppendContext.pLastParagraphProperties = pToBeSavedProperties;
1892 catch(const lang::IllegalArgumentException&)
1894 OSL_FAIL( "IllegalArgumentException in DomainMapper_Impl::finishParagraph" );
1896 catch(const uno::Exception&)
1898 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "finishParagraph()" );
1903 bool bIgnoreFrameState = IsInHeaderFooter();
1904 if( (!bIgnoreFrameState && pParaContext && pParaContext->IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) )
1905 SetIsPreviousParagraphFramed(true);
1906 else
1907 SetIsPreviousParagraphFramed(false);
1909 m_bParaChanged = false;
1910 m_bRemoveThisParagraph = false;
1911 if( !IsInHeaderFooter() && !IsInShape() && (!pParaContext || !pParaContext->IsFrameMode()) )
1912 { // If the paragraph is in a frame, shape or header/footer, it's not a paragraph of the section itself.
1913 SetIsFirstParagraphInSection(false);
1914 // count first not deleted paragraph as first paragraph in section to avoid of
1915 // its deletion later, resulting loss of the associated page break
1916 if (!m_previousRedline.get())
1918 SetIsFirstParagraphInSectionAfterRedline(false);
1919 SetIsLastParagraphInSection(false);
1922 m_previousRedline.clear();
1924 if (m_bIsFirstParaInShape)
1925 m_bIsFirstParaInShape = false;
1927 if (pParaContext)
1929 // Reset the frame properties for the next paragraph
1930 pParaContext->ResetFrameProperties();
1933 SetIsOutsideAParagraph(true);
1934 m_bParaHadField = false;
1936 // don't overwrite m_bFirstParagraphInCell in table separator nodes
1937 if (m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth)
1938 m_bFirstParagraphInCell = false;
1940 m_bParaAutoBefore = false;
1942 #ifdef DBG_UTIL
1943 TagLogger::getInstance().endElement();
1944 #endif
1948 void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap )
1950 if (m_bDiscardHeaderFooter)
1951 return;
1953 if (m_aTextAppendStack.empty())
1954 return;
1955 // Before placing call to processDeferredCharacterProperties(), TopContextType should be CONTEXT_CHARACTER
1956 // processDeferredCharacterProperties() invokes only if character inserted
1957 if( pPropertyMap == m_pTopContext && !deferredCharacterProperties.empty() && (GetTopContextType() == CONTEXT_CHARACTER) )
1958 processDeferredCharacterProperties();
1959 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
1960 if (xTextAppend.is() && hasTableManager() && !getTableManager().isIgnore())
1964 // If we are in comments, then disable CharGrabBag, comment text doesn't support that.
1965 uno::Sequence< beans::PropertyValue > aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!m_bIsInComments);
1967 if (m_bStartTOC || m_bStartIndex || m_bStartBibliography)
1968 for( auto& rValue : aValues )
1970 if (rValue.Name == "CharHidden")
1971 rValue.Value <<= false;
1974 uno::Reference< text::XTextRange > xTextRange;
1975 if (m_aTextAppendStack.top().xInsertPosition.is())
1977 xTextRange = xTextAppend->insertTextPortion(rString, aValues, m_aTextAppendStack.top().xInsertPosition);
1978 m_aTextAppendStack.top().xCursor->gotoRange(xTextRange->getEnd(), true);
1980 else
1982 if (m_bStartTOC || m_bStartIndex || m_bStartBibliography || m_nStartGenericField != 0)
1984 if (IsInHeaderFooter() && !m_bStartTOCHeaderFooter)
1986 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
1988 else
1990 m_bStartedTOC = true;
1991 uno::Reference< text::XTextCursor > xTOCTextCursor = xTextAppend->getEnd()->getText( )->createTextCursor( );
1992 xTOCTextCursor->gotoEnd(false);
1993 if (m_nStartGenericField != 0)
1995 xTOCTextCursor->goLeft(1, false);
1997 xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor);
1998 SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed");
1999 if (!xTextRange.is())
2000 throw uno::Exception("insertTextPortion failed", nullptr);
2001 m_bTextInserted = true;
2002 xTOCTextCursor->gotoRange(xTextRange->getEnd(), true);
2003 if (m_nStartGenericField == 0)
2005 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
2009 else
2011 #if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed
2012 sal_Int32 nPos = 0;
2013 OUString sFontName;
2014 OUString sDoubleSpace(" ");
2015 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
2016 // tdf#123703 workaround for longer space sequences of the old or compatible RTF documents
2017 if (GetSettingsTable()->GetLongerSpaceSequence() && !IsOpenFieldCommand() && (nPos = rString.indexOf(sDoubleSpace)) != -1 &&
2018 // monospaced fonts have no longer space sequences, regardless of \fprq2 (not monospaced) font setting
2019 // fix for the base monospaced font Courier
2020 (!pContext || !pContext->isSet(PROP_CHAR_FONT_NAME) ||
2021 ((pContext->getProperty(PROP_CHAR_FONT_NAME)->second >>= sFontName) && sFontName.indexOf("Courier") == -1)))
2023 // an RTF space character is longer by an extra six-em-space in an old-style RTF space sequence,
2024 // insert them to keep RTF document layout formatted by consecutive spaces
2025 const sal_Unicode aExtraSpace[5] = { 0x2006, 0x20, 0x2006, 0x20, 0 };
2026 const sal_Unicode aExtraSpace2[4] = { 0x20, 0x2006, 0x20, 0 };
2027 xTextRange = xTextAppend->appendTextPortion(rString.replaceAll(sDoubleSpace, aExtraSpace, nPos)
2028 .replaceAll(sDoubleSpace, aExtraSpace2, nPos), aValues);
2030 else
2031 #endif
2032 xTextRange = xTextAppend->appendTextPortion(rString, aValues);
2036 // reset moveFrom data of non-terminating runs of the paragraph
2037 if ( m_pParaMarkerRedlineMoveFrom.get( ) )
2039 m_pParaMarkerRedlineMoveFrom.clear();
2041 CheckRedline( xTextRange );
2042 m_bParaChanged = true;
2044 //getTableManager( ).handle(xTextRange);
2046 catch(const lang::IllegalArgumentException&)
2048 OSL_FAIL( "IllegalArgumentException in DomainMapper_Impl::appendTextPortion" );
2050 catch(const uno::Exception&)
2052 OSL_FAIL( "Exception in DomainMapper_Impl::appendTextPortion" );
2057 void DomainMapper_Impl::appendTextContent(
2058 const uno::Reference< text::XTextContent >& xContent,
2059 const uno::Sequence< beans::PropertyValue >& xPropertyValues
2062 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text append stack");
2063 if (m_aTextAppendStack.empty())
2064 return;
2065 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY );
2066 OSL_ENSURE( xTextAppendAndConvert.is(), "trying to append a text content without XTextAppendAndConvert" );
2067 if (xTextAppendAndConvert.is() && hasTableManager() && !getTableManager().isIgnore())
2071 if (m_aTextAppendStack.top().xInsertPosition.is())
2072 xTextAppendAndConvert->insertTextContentWithProperties( xContent, xPropertyValues, m_aTextAppendStack.top().xInsertPosition );
2073 else
2074 xTextAppendAndConvert->appendTextContent( xContent, xPropertyValues );
2076 catch(const lang::IllegalArgumentException&)
2079 catch(const uno::Exception&)
2085 void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOLEHandler )
2089 uno::Reference< text::XTextContent > xOLE( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
2090 uno::Reference< beans::XPropertySet > xOLEProperties(xOLE, uno::UNO_QUERY_THROW);
2092 OUString aCLSID = pOLEHandler->getCLSID(m_xComponentContext);
2093 if (aCLSID.isEmpty())
2094 xOLEProperties->setPropertyValue(getPropertyName( PROP_STREAM_NAME ),
2095 uno::makeAny( rStreamName ));
2096 else
2097 xOLEProperties->setPropertyValue("CLSID", uno::makeAny(aCLSID));
2099 OUString aDrawAspect = pOLEHandler->GetDrawAspect();
2100 if(!aDrawAspect.isEmpty())
2101 xOLEProperties->setPropertyValue("DrawAspect", uno::makeAny(aDrawAspect));
2103 awt::Size aSize = pOLEHandler->getSize();
2104 if( !aSize.Width )
2105 aSize.Width = 1000;
2106 if( !aSize.Height )
2107 aSize.Height = 1000;
2108 xOLEProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
2109 uno::makeAny(aSize.Width));
2110 xOLEProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
2111 uno::makeAny(aSize.Height));
2113 OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth();
2114 if(!aVisAreaWidth.isEmpty())
2115 xOLEProperties->setPropertyValue("VisibleAreaWidth", uno::makeAny(aVisAreaWidth));
2117 OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight();
2118 if(!aVisAreaHeight.isEmpty())
2119 xOLEProperties->setPropertyValue("VisibleAreaHeight", uno::makeAny(aVisAreaHeight));
2121 uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement();
2122 xOLEProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC ),
2123 uno::makeAny(xGraphic));
2124 uno::Reference<beans::XPropertySet> xReplacementProperties(pOLEHandler->getShape(), uno::UNO_QUERY);
2125 if (xReplacementProperties.is())
2127 OUString pProperties[] = {
2128 OUString("AnchorType"),
2129 OUString("Surround"),
2130 OUString("HoriOrient"),
2131 OUString("HoriOrientPosition"),
2132 OUString("VertOrient"),
2133 OUString("VertOrientPosition")
2135 for (const OUString & s : pProperties)
2136 xOLEProperties->setPropertyValue(s, xReplacementProperties->getPropertyValue(s));
2138 else
2139 // mimic the treatment of graphics here... it seems anchoring as character
2140 // gives a better ( visually ) result
2141 xOLEProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ), uno::makeAny( text::TextContentAnchorType_AS_CHARACTER ) );
2142 // remove ( if valid ) associated shape ( used for graphic replacement )
2143 SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
2144 if (!m_aAnchoredStack.empty())
2145 m_aAnchoredStack.top( ).bToRemove = true;
2146 RemoveLastParagraph();
2147 SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack");
2148 if (!m_aTextAppendStack.empty())
2149 m_aTextAppendStack.pop();
2151 appendTextContent( xOLE, uno::Sequence< beans::PropertyValue >() );
2153 if (!aCLSID.isEmpty())
2154 pOLEHandler->importStream(m_xComponentContext, GetTextDocument(), xOLE);
2157 catch( const uno::Exception& )
2159 OSL_FAIL( "Exception in creation of OLE object" );
2164 void DomainMapper_Impl::appendStarMath( const Value& val )
2166 uno::Reference< embed::XEmbeddedObject > formula;
2167 val.getAny() >>= formula;
2168 if( formula.is() )
2172 uno::Reference< text::XTextContent > xStarMath( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW );
2173 uno::Reference< beans::XPropertySet > xStarMathProperties(xStarMath, uno::UNO_QUERY_THROW);
2175 xStarMathProperties->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ),
2176 val.getAny());
2177 // tdf#66405: set zero margins for embedded object
2178 xStarMathProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
2179 uno::makeAny(sal_Int32(0)));
2180 xStarMathProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
2181 uno::makeAny(sal_Int32(0)));
2182 xStarMathProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
2183 uno::makeAny(sal_Int32(0)));
2184 xStarMathProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
2185 uno::makeAny(sal_Int32(0)));
2187 uno::Reference< uno::XInterface > xInterface( formula->getComponent(), uno::UNO_QUERY );
2188 // set zero margins for object's component
2189 uno::Reference< beans::XPropertySet > xComponentProperties( xInterface, uno::UNO_QUERY_THROW );
2190 xComponentProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
2191 uno::makeAny(sal_Int32(0)));
2192 xComponentProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
2193 uno::makeAny(sal_Int32(0)));
2194 xComponentProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
2195 uno::makeAny(sal_Int32(0)));
2196 xComponentProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ),
2197 uno::makeAny(sal_Int32(0)));
2198 Size size( 1000, 1000 );
2199 if( oox::FormulaImportBase* formulaimport = dynamic_cast< oox::FormulaImportBase* >( xInterface.get()))
2200 size = formulaimport->getFormulaSize();
2201 xStarMathProperties->setPropertyValue(getPropertyName( PROP_WIDTH ),
2202 uno::makeAny( sal_Int32(size.Width())));
2203 xStarMathProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ),
2204 uno::makeAny( sal_Int32(size.Height())));
2205 // mimic the treatment of graphics here... it seems anchoring as character
2206 // gives a better ( visually ) result
2207 xStarMathProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ),
2208 uno::makeAny( text::TextContentAnchorType_AS_CHARACTER ) );
2209 appendTextContent( xStarMath, uno::Sequence< beans::PropertyValue >() );
2211 catch( const uno::Exception& )
2213 OSL_FAIL( "Exception in creation of StarMath object" );
2218 uno::Reference< beans::XPropertySet > DomainMapper_Impl::appendTextSectionAfter(
2219 uno::Reference< text::XTextRange > const & xBefore )
2221 uno::Reference< beans::XPropertySet > xRet;
2222 if (m_aTextAppendStack.empty())
2223 return xRet;
2224 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
2225 if(xTextAppend.is())
2229 uno::Reference< text::XParagraphCursor > xCursor(
2230 xTextAppend->createTextCursorByRange( xBefore ), uno::UNO_QUERY_THROW);
2231 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
2232 xCursor->gotoStartOfParagraph( false );
2233 if (m_aTextAppendStack.top().xInsertPosition.is())
2234 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
2235 else
2236 xCursor->gotoEnd( true );
2237 //the paragraph after this new section is already inserted
2238 xCursor->goLeft(1, true);
2239 css::uno::Reference<css::text::XTextRange> xTextRange(xCursor, css::uno::UNO_QUERY_THROW);
2241 if (css::uno::Reference<css::text::XDocumentIndexesSupplier> xIndexSupplier{
2242 GetTextDocument(), css::uno::UNO_QUERY })
2244 css::uno::Reference<css::text::XTextRangeCompare> xCompare(
2245 xTextAppend, css::uno::UNO_QUERY);
2246 const auto xIndexAccess = xIndexSupplier->getDocumentIndexes();
2247 for (sal_Int32 i = xIndexAccess->getCount(); i > 0; --i)
2249 if (css::uno::Reference<css::text::XDocumentIndex> xIndex{
2250 xIndexAccess->getByIndex(i - 1), css::uno::UNO_QUERY })
2252 const auto xIndexTextRange = xIndex->getAnchor();
2253 if (xCompare->compareRegionStarts(xTextRange, xIndexTextRange) == 0
2254 && xCompare->compareRegionEnds(xTextRange, xIndexTextRange) == 0)
2256 // The boundaries coincide with an index: trying to attach a section
2257 // to the range will insert the section inside the index. goRight will
2258 // extend the range outside of the index, so that created section will
2259 // be around it. Alternatively we could return index section itself
2260 // instead : xRet.set(xIndex, uno::UNO_QUERY) - to set its properties,
2261 // like columns/fill.
2262 xCursor->goRight(1, true);
2263 break;
2269 uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY_THROW );
2270 xSection->attach( xTextRange );
2271 xRet.set(xSection, uno::UNO_QUERY );
2273 catch(const uno::Exception&)
2279 return xRet;
2282 void DomainMapper_Impl::appendGlossaryEntry()
2284 appendTextSectionAfter(m_xGlossaryEntryStart);
2287 void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType)
2289 m_aHeaderFooterStack.push(HeaderFooterContext(m_bTextInserted, m_nTableDepth));
2290 m_bTextInserted = false;
2291 m_nTableDepth = 0;
2293 const PropertyIds ePropIsOn = bHeader? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON;
2294 const PropertyIds ePropShared = bHeader? PROP_HEADER_IS_SHARED: PROP_FOOTER_IS_SHARED;
2295 const PropertyIds ePropTextLeft = bHeader? PROP_HEADER_TEXT_LEFT: PROP_FOOTER_TEXT_LEFT;
2296 const PropertyIds ePropText = bHeader? PROP_HEADER_TEXT: PROP_FOOTER_TEXT;
2298 m_eInHeaderFooterImport
2299 = bHeader ? HeaderFooterImportState::header : HeaderFooterImportState::footer;
2301 //get the section context
2302 PropertyMapPtr pContext = DomainMapper_Impl::GetTopContextOfType(CONTEXT_SECTION);
2303 //ask for the header/footer name of the given type
2304 SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
2305 if(pSectionContext)
2307 // clear the "Link To Previous" flag so that the header/footer
2308 // content is not copied from the previous section
2309 pSectionContext->ClearHeaderFooterLinkToPrevious(bHeader, eType);
2311 uno::Reference< beans::XPropertySet > xPageStyle =
2312 pSectionContext->GetPageStyle(
2313 *this,
2314 eType == SectionPropertyMap::PAGE_FIRST );
2315 if (!xPageStyle.is())
2316 return;
2319 bool bLeft = eType == SectionPropertyMap::PAGE_LEFT;
2320 bool bFirst = eType == SectionPropertyMap::PAGE_FIRST;
2321 if ((!bLeft && !GetSettingsTable()->GetEvenAndOddHeaders()) || (GetSettingsTable()->GetEvenAndOddHeaders()))
2323 //switch on header/footer use
2324 xPageStyle->setPropertyValue(
2325 getPropertyName(ePropIsOn),
2326 uno::makeAny(true));
2328 // If the 'Different Even & Odd Pages' flag is turned on - do not ignore it
2329 // Even if the 'Even' header/footer is blank - the flag should be imported (so it would look in LO like in Word)
2330 if (!bFirst && GetSettingsTable()->GetEvenAndOddHeaders())
2331 xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::makeAny(false));
2333 //set the interface
2334 uno::Reference< text::XText > xText;
2335 xPageStyle->getPropertyValue(getPropertyName(bLeft? ePropTextLeft: ePropText)) >>= xText;
2337 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW),
2338 m_bIsNewDoc? uno::Reference<text::XTextCursor>(): m_xBodyText->createTextCursorByRange(xText->getStart())));
2340 else
2342 m_bDiscardHeaderFooter = true;
2345 catch( const uno::Exception& )
2351 void DomainMapper_Impl::PushPageHeader(SectionPropertyMap::PageType eType)
2353 PushPageHeaderFooter(/* bHeader = */ true, eType);
2356 void DomainMapper_Impl::PushPageFooter(SectionPropertyMap::PageType eType)
2358 PushPageHeaderFooter(/* bHeader = */ false, eType);
2361 void DomainMapper_Impl::PopPageHeaderFooter()
2363 //header and footer always have an empty paragraph at the end
2364 //this has to be removed
2365 RemoveLastParagraph( );
2367 if (!m_aTextAppendStack.empty())
2369 if (!m_bDiscardHeaderFooter)
2371 m_aTextAppendStack.pop();
2373 m_bDiscardHeaderFooter = false;
2375 m_eInHeaderFooterImport = HeaderFooterImportState::none;
2377 if (!m_aHeaderFooterStack.empty())
2379 m_bTextInserted = m_aHeaderFooterStack.top().getTextInserted();
2380 m_nTableDepth = m_aHeaderFooterStack.top().getTableDepth();
2381 m_aHeaderFooterStack.pop();
2385 void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote )
2387 SAL_WARN_IF(m_bInFootOrEndnote, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote");
2388 m_bInFootOrEndnote = true;
2389 m_bCheckFirstFootnoteTab = true;
2390 m_bSaveFirstParagraphInCell = m_bFirstParagraphInCell;
2393 // Redlines outside the footnote should not affect footnote content
2394 m_aRedlines.push(std::vector< RedlineParamsPtr >());
2396 // IMHO character styles from footnote labels should be ignored in the edit view of Writer.
2397 // This adds a hack on top of the following hack to save the style name in the context.
2398 PropertyMapPtr pTopContext = GetTopContext();
2399 OUString sFootnoteCharStyleName;
2400 std::optional< PropertyMap::Property > aProp = pTopContext->getProperty(PROP_CHAR_STYLE_NAME);
2401 if (aProp)
2402 aProp->second >>= sFootnoteCharStyleName;
2404 // Remove style reference, if any. This reference did appear here as a side effect of tdf#43017
2405 // Seems it is not required by LO, but causes side effects during editing. So remove it
2406 // for footnotes/endnotes to restore original LO behavior here.
2407 pTopContext->Erase(PROP_CHAR_STYLE_NAME);
2409 uno::Reference< text::XText > xFootnoteText;
2410 if (GetTextFactory().is())
2411 xFootnoteText.set( GetTextFactory()->createInstance(
2412 bIsFootnote ?
2413 OUString( "com.sun.star.text.Footnote" ) : OUString( "com.sun.star.text.Endnote" )),
2414 uno::UNO_QUERY_THROW );
2415 uno::Reference< text::XFootnote > xFootnote( xFootnoteText, uno::UNO_QUERY_THROW );
2416 pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName);
2417 uno::Sequence< beans::PropertyValue > aFontProperties = pTopContext->GetPropertyValues();
2418 appendTextContent( uno::Reference< text::XTextContent >( xFootnoteText, uno::UNO_QUERY_THROW ), aFontProperties );
2419 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xFootnoteText, uno::UNO_QUERY_THROW ),
2420 xFootnoteText->createTextCursorByRange(xFootnoteText->getStart())));
2422 // Redlines for the footnote anchor in the main text content
2423 std::vector< RedlineParamsPtr > aFootnoteRedline = std::move(m_aRedlines.top());
2424 m_aRedlines.pop();
2425 CheckRedline( xFootnote->getAnchor( ) );
2426 m_aRedlines.push( aFootnoteRedline );
2428 // Try scanning for custom footnote labels
2429 if (!sFootnoteCharStyleName.isEmpty())
2430 StartCustomFootnote(pTopContext);
2431 else
2432 EndCustomFootnote();
2434 catch( const uno::Exception& )
2436 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "PushFootOrEndnote");
2440 void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xRange,
2441 const RedlineParamsPtr& pRedline)
2443 if ( pRedline.get( ) )
2447 OUString sType;
2448 switch ( pRedline->m_nToken & 0xffff )
2450 case XML_mod:
2451 sType = getPropertyName( PROP_FORMAT );
2452 break;
2453 case XML_moveTo:
2454 case XML_ins:
2455 sType = getPropertyName( PROP_INSERT );
2456 break;
2457 case XML_moveFrom:
2458 m_pParaMarkerRedlineMoveFrom = pRedline.get();
2459 [[fallthrough]];
2460 case XML_del:
2461 sType = getPropertyName( PROP_DELETE );
2462 break;
2463 case XML_ParagraphFormat:
2464 sType = getPropertyName( PROP_PARAGRAPH_FORMAT );
2465 break;
2466 default:
2467 throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0);
2469 beans::PropertyValues aRedlineProperties( 3 );
2470 beans::PropertyValue * pRedlineProperties = aRedlineProperties.getArray( );
2471 pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_AUTHOR );
2472 pRedlineProperties[0].Value <<= pRedline->m_sAuthor;
2473 pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_DATE_TIME );
2474 pRedlineProperties[1].Value <<= ConversionHelper::ConvertDateStringToDateTime( pRedline->m_sDate );
2475 pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES );
2476 pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties;
2477 if (!m_bIsActualParagraphFramed)
2479 uno::Reference < text::XRedline > xRedline( xRange, uno::UNO_QUERY_THROW );
2480 xRedline->makeRedline( sType, aRedlineProperties );
2482 else
2484 aFramedRedlines.push_back( uno::makeAny(xRange) );
2485 aFramedRedlines.push_back( uno::makeAny(sType) );
2486 aFramedRedlines.push_back( uno::makeAny(aRedlineProperties) );
2489 catch( const uno::Exception & )
2491 OSL_FAIL( "Exception in makeRedline" );
2496 void DomainMapper_Impl::CheckParaMarkerRedline( uno::Reference< text::XTextRange > const& xRange )
2498 if ( m_pParaMarkerRedline.get( ) )
2500 CreateRedline( xRange, m_pParaMarkerRedline );
2501 if ( m_pParaMarkerRedline.get( ) )
2503 m_pParaMarkerRedline.clear();
2504 m_currentRedline.clear();
2507 else if ( m_pParaMarkerRedlineMoveFrom.get( ) )
2509 // terminating moveFrom redline removes also the paragraph mark
2510 m_pParaMarkerRedlineMoveFrom->m_nToken = XML_del;
2511 CreateRedline( xRange, m_pParaMarkerRedlineMoveFrom );
2513 if ( m_pParaMarkerRedlineMoveFrom.get( ) )
2515 m_pParaMarkerRedlineMoveFrom.clear();
2519 void DomainMapper_Impl::CheckRedline( uno::Reference< text::XTextRange > const& xRange )
2521 // Writer core "officially" does not like overlapping redlines, and its UNO interface is stupid enough
2522 // to not prevent that. However, in practice in fact everything appears to work fine (except for the debug warnings
2523 // about redline table corruption, which may possibly be harmless in reality). So leave this as it is, since this
2524 // is a better representation of how the changes happened. If this will ever become a problem, overlapping redlines
2525 // will need to be merged into one, just like doing the changes in the UI does, which will lose some information
2526 // (and so if that happens, it may be better to fix Writer).
2527 // Create the redlines here from lowest (formats) to highest (inserts/removals) priority, since the last one is
2528 // what Writer presents graphically, so this will show deletes as deleted text and not as just formatted text being there.
2529 bool bUsedRange = m_aRedlines.top().size() > 0 || (GetTopContextOfType(CONTEXT_CHARACTER) &&
2530 GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0);
2532 // only export ParagraphFormat, when there is no other redline in the same text portion to avoid missing redline compression,
2533 // but always export the first ParagraphFormat redline in a paragraph to keep the paragraph style change data for rejection
2534 if( (!bUsedRange || !m_bParaChanged) && GetTopContextOfType(CONTEXT_PARAGRAPH) )
2536 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_PARAGRAPH)->Redlines();
2537 for( const auto& rRedline : avRedLines )
2538 CreateRedline( xRange, rRedline );
2540 if( GetTopContextOfType(CONTEXT_CHARACTER) )
2542 std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_CHARACTER)->Redlines();
2543 for( const auto& rRedline : avRedLines )
2544 CreateRedline( xRange, rRedline );
2546 for (const auto& rRedline : m_aRedlines.top() )
2547 CreateRedline( xRange, rRedline );
2550 void DomainMapper_Impl::StartParaMarkerChange( )
2552 m_bIsParaMarkerChange = true;
2555 void DomainMapper_Impl::EndParaMarkerChange( )
2557 m_bIsParaMarkerChange = false;
2558 m_previousRedline = m_currentRedline;
2559 m_currentRedline.clear();
2562 void DomainMapper_Impl::StartCustomFootnote(const PropertyMapPtr pContext)
2564 if (pContext == m_pFootnoteContext)
2565 return;
2567 assert(pContext->GetFootnote().is());
2568 m_bHasFootnoteStyle = true;
2569 m_bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty();
2570 m_pFootnoteContext = pContext;
2573 void DomainMapper_Impl::EndCustomFootnote()
2575 m_bHasFootnoteStyle = false;
2576 m_bCheckFootnoteStyle = false;
2579 void DomainMapper_Impl::PushAnnotation()
2583 m_bIsInComments = true;
2584 if (!GetTextFactory().is())
2585 return;
2586 m_xAnnotationField.set( GetTextFactory()->createInstance( "com.sun.star.text.TextField.Annotation" ),
2587 uno::UNO_QUERY_THROW );
2588 uno::Reference< text::XText > xAnnotationText;
2589 m_xAnnotationField->getPropertyValue("TextRange") >>= xAnnotationText;
2590 m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xAnnotationText, uno::UNO_QUERY_THROW ),
2591 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xAnnotationText->createTextCursorByRange(xAnnotationText->getStart())));
2593 catch( const uno::Exception&)
2595 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
2600 void DomainMapper_Impl::PopFootOrEndnote()
2602 if (!IsRTFImport())
2603 RemoveLastParagraph();
2605 // In case the foot or endnote did not contain a tab.
2606 m_bIgnoreNextTab = false;
2608 if (!m_aTextAppendStack.empty())
2609 m_aTextAppendStack.pop();
2611 if (m_aRedlines.size() == 1)
2613 SAL_WARN("writerfilter.dmapper", "PopFootOrEndnote() is called without PushFootOrEndnote()?");
2614 return;
2616 m_aRedlines.pop();
2617 m_eSkipFootnoteState = SkipFootnoteSeparator::OFF;
2618 m_bInFootOrEndnote = false;
2619 m_pFootnoteContext = nullptr;
2620 m_bFirstParagraphInCell = m_bSaveFirstParagraphInCell;
2623 void DomainMapper_Impl::PopAnnotation()
2625 RemoveLastParagraph();
2627 m_bIsInComments = false;
2628 m_aTextAppendStack.pop();
2632 // See if the annotation will be a single position or a range.
2633 if (m_nAnnotationId == -1 || !m_aAnnotationPositions[m_nAnnotationId].m_xStart.is() || !m_aAnnotationPositions[m_nAnnotationId].m_xEnd.is())
2635 uno::Sequence< beans::PropertyValue > aEmptyProperties;
2636 uno::Reference< text::XTextContent > xContent( m_xAnnotationField, uno::UNO_QUERY_THROW );
2637 appendTextContent( xContent, aEmptyProperties );
2638 CheckRedline( xContent->getAnchor( ) );
2640 else
2642 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[m_nAnnotationId];
2643 // Create a range that points to the annotation start/end.
2644 uno::Reference<text::XText> const xText = aAnnotationPosition.m_xStart->getText();
2645 uno::Reference<text::XTextCursor> const xCursor = xText->createTextCursorByRange(aAnnotationPosition.m_xStart);
2647 bool bMarker = false;
2648 uno::Reference<text::XTextRangeCompare> xTextRangeCompare(xText, uno::UNO_QUERY);
2649 if (xTextRangeCompare->compareRegionStarts(aAnnotationPosition.m_xStart, aAnnotationPosition.m_xEnd) == 0)
2651 // Insert a marker so that comment around an anchored image is not collapsed during
2652 // insertion.
2653 xText->insertString(xCursor, "x", false);
2654 bMarker = true;
2657 xCursor->gotoRange(aAnnotationPosition.m_xEnd, true);
2658 uno::Reference<text::XTextRange> const xTextRange(xCursor, uno::UNO_QUERY_THROW);
2660 // Attach the annotation to the range.
2661 uno::Reference<text::XTextAppend> const xTextAppend = m_aTextAppendStack.top().xTextAppend;
2662 xTextAppend->insertTextContent(xTextRange, uno::Reference<text::XTextContent>(m_xAnnotationField, uno::UNO_QUERY_THROW), !xCursor->isCollapsed());
2664 if (bMarker)
2666 // Remove the marker.
2667 xCursor->goLeft(1, true);
2668 xCursor->setString(OUString());
2671 m_aAnnotationPositions.erase( m_nAnnotationId );
2673 catch (uno::Exception const&)
2675 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert annotation field");
2678 m_xAnnotationField.clear();
2679 m_nAnnotationId = -1;
2682 void DomainMapper_Impl::PushPendingShape( const uno::Reference< drawing::XShape > & xShape )
2684 m_aPendingShapes.push_back(xShape);
2687 uno::Reference<drawing::XShape> DomainMapper_Impl::PopPendingShape()
2689 uno::Reference<drawing::XShape> xRet;
2690 if (!m_aPendingShapes.empty())
2692 xRet = m_aPendingShapes.front();
2693 m_aPendingShapes.pop_front();
2695 return xRet;
2698 void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape > & xShape )
2700 // Append these early, so the context and the table manager stack will be
2701 // in sync, even if the text append stack is empty.
2702 appendTableManager();
2703 appendTableHandler();
2704 getTableManager().startLevel();
2706 if (m_aTextAppendStack.empty())
2707 return;
2708 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
2712 uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW );
2713 if (xSInfo->supportsService("com.sun.star.drawing.GroupShape"))
2715 // Textboxes in shapes do not support styles, so check saved style information and apply properties directly to the child shapes.
2716 const uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY);
2717 const sal_uInt32 nShapeCount = xShapes.is() ? xShapes->getCount() : 0;
2718 for ( sal_uInt32 i = 0; i < nShapeCount; ++i )
2722 uno::Reference<beans::XPropertySet> xSyncedPropertySet(xShapes->getByIndex(i), uno::UNO_QUERY_THROW);
2723 comphelper::SequenceAsHashMap aGrabBag( xSyncedPropertySet->getPropertyValue("CharInteropGrabBag") );
2725 // only VML import has checked for style. Don't apply default parastyle properties to other imported shapes
2726 // - except for fontsize - to maintain compatibility with previous versions of LibreOffice.
2727 const bool bOnlyApplyCharHeight = !aGrabBag["mso-pStyle"].hasValue();
2729 OUString sStyleName;
2730 aGrabBag["mso-pStyle"] >>= sStyleName;
2731 StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByISTD( sStyleName );
2732 if ( !pEntry )
2734 // Use default style even in ambiguous cases (where multiple styles were defined) since MOST styles inherit
2735 // MOST of their properties from the default style. In the ambiguous case, we have to accept some kind of compromise
2736 // and the default paragraph style ought to be the safest one... (compared to DocDefaults or program defaults)
2737 pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetDefaultParaStyleName() );
2739 if ( pEntry )
2741 // The Ids here come from oox/source/vml/vmltextbox.cxx.
2742 // It probably could safely expand to all Ids that shapes support.
2743 const PropertyIds eIds[] = {
2744 PROP_CHAR_HEIGHT,
2745 PROP_CHAR_FONT_NAME,
2746 PROP_CHAR_WEIGHT,
2747 PROP_CHAR_CHAR_KERNING,
2748 PROP_CHAR_COLOR,
2749 PROP_PARA_ADJUST
2751 const uno::Reference<beans::XPropertyState> xSyncedPropertyState(xSyncedPropertySet, uno::UNO_QUERY_THROW);
2752 for ( const auto& eId : eIds )
2756 if ( bOnlyApplyCharHeight && eId != PROP_CHAR_HEIGHT )
2757 continue;
2759 const OUString sPropName = getPropertyName(eId);
2760 if ( beans::PropertyState_DEFAULT_VALUE == xSyncedPropertyState->getPropertyState(sPropName) )
2762 const uno::Any aProp = GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true);
2763 if ( aProp.hasValue() )
2764 xSyncedPropertySet->setPropertyValue( sPropName, aProp );
2767 catch (const uno::Exception&)
2769 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext() text stylesheet property exception" );
2774 catch (const uno::Exception&)
2776 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext()" );
2780 // A GroupShape doesn't implement text::XTextRange, but appending
2781 // an empty reference to the stacks still makes sense, because this
2782 // way bToRemove can be set, and we won't end up with duplicated
2783 // shapes for OLE objects.
2784 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
2785 uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY);
2786 m_aAnchoredStack.push(AnchoredContext(xTxtContent));
2788 else if (xSInfo->supportsService("com.sun.star.drawing.OLE2Shape"))
2790 // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw.
2791 m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
2792 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
2793 m_aAnchoredStack.push(AnchoredContext(xTextContent));
2794 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
2796 m_xEmbedded.set(m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW);
2797 uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW);
2798 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT)));
2799 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::makeAny(text::TextContentAnchorType_AS_CHARACTER));
2800 // So that the original bitmap-only shape will be replaced by the embedded object.
2801 m_aAnchoredStack.top().bToRemove = true;
2802 m_aTextAppendStack.pop();
2803 appendTextContent(m_xEmbedded, uno::Sequence<beans::PropertyValue>());
2805 else
2807 uno::Reference< text::XTextRange > xShapeText( xShape, uno::UNO_QUERY_THROW);
2808 // Add the shape to the text append stack
2809 m_aTextAppendStack.push( TextAppendContext(uno::Reference< text::XTextAppend >( xShape, uno::UNO_QUERY_THROW ),
2810 m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(xShapeText->getStart() )));
2812 // Add the shape to the anchored objects stack
2813 uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW );
2814 m_aAnchoredStack.push( AnchoredContext(xTxtContent) );
2816 uno::Reference< beans::XPropertySet > xProps( xShape, uno::UNO_QUERY_THROW );
2817 #ifdef DBG_UTIL
2818 TagLogger::getInstance().unoPropertySet(xProps);
2819 #endif
2820 text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH);
2821 xProps->getPropertyValue(getPropertyName( PROP_ANCHOR_TYPE )) >>= nAnchorType;
2822 bool checkZOrderStatus = false;
2823 if (xSInfo->supportsService("com.sun.star.text.TextFrame"))
2825 SetIsTextFrameInserted(true);
2826 // Extract the special "btLr text frame" mode, requested by oox, if needed.
2827 // Extract vml ZOrder from FrameInteropGrabBag
2828 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
2829 uno::Sequence<beans::PropertyValue> aGrabBag;
2830 xShapePropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
2832 for (const auto& rProp : std::as_const(aGrabBag))
2834 if (rProp.Name == "VML-Z-ORDER")
2836 GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper();
2837 sal_Int32 zOrder(0);
2838 rProp.Value >>= zOrder;
2839 xShapePropertySet->setPropertyValue( "ZOrder", uno::makeAny(pZOrderHelper->findZOrder(zOrder)));
2840 pZOrderHelper->addItem(xShapePropertySet, zOrder);
2841 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::makeAny( zOrder >= 0 ) );
2842 checkZOrderStatus = true;
2844 else if ( rProp.Name == "TxbxHasLink" )
2846 //Chaining of textboxes will happen in ~DomainMapper_Impl
2847 //i.e when all the textboxes are read and all its attributes
2848 //have been set ( basically the Name/LinkedDisplayName )
2849 //which is set in Graphic Import.
2850 m_vTextFramesForChaining.push_back(xShape);
2854 uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY_THROW);
2855 uno::Reference<text::XTextRange> xTextRange(xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
2856 xTextAppend->insertTextContent(xTextRange, xTextContent, false);
2858 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
2859 // we need to re-set this value to xTextContent, then only values are preserved.
2860 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::makeAny(aGrabBag));
2862 else if (nAnchorType == text::TextContentAnchorType_AS_CHARACTER)
2864 // Fix spacing for as-character objects. If the paragraph has CT_Spacing_after set,
2865 // it needs to be set on the object too, as that's what object placement code uses.
2866 PropertyMapPtr paragraphContext = GetTopContextOfType( CONTEXT_PARAGRAPH );
2867 std::optional<PropertyMap::Property> aPropMargin = paragraphContext->getProperty(PROP_PARA_BOTTOM_MARGIN);
2868 if(aPropMargin)
2869 xProps->setPropertyValue( getPropertyName( PROP_BOTTOM_MARGIN ), aPropMargin->second );
2871 else
2873 uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
2874 uno::Sequence<beans::PropertyValue> aGrabBag;
2875 xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
2876 for (const auto& rProp : std::as_const(aGrabBag))
2878 if (rProp.Name == "VML-Z-ORDER")
2880 GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper();
2881 sal_Int32 zOrder(0);
2882 rProp.Value >>= zOrder;
2883 xShapePropertySet->setPropertyValue( "ZOrder", uno::makeAny(pZOrderHelper->findZOrder(zOrder)));
2884 pZOrderHelper->addItem(xShapePropertySet, zOrder);
2885 xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::makeAny( zOrder >= 0 ) );
2886 checkZOrderStatus = true;
2888 else if ( rProp.Name == "TxbxHasLink" )
2890 //Chaining of textboxes will happen in ~DomainMapper_Impl
2891 //i.e when all the textboxes are read and all its attributes
2892 //have been set ( basically the Name/LinkedDisplayName )
2893 //which is set in Graphic Import.
2894 m_vTextFramesForChaining.push_back(xShape);
2898 if(IsSdtEndBefore())
2900 uno::Reference< beans::XPropertySetInfo > xPropSetInfo;
2901 if(xShapePropertySet.is())
2903 xPropSetInfo = xShapePropertySet->getPropertySetInfo();
2904 if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag"))
2906 uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({
2907 { "SdtEndBefore", uno::Any(true) }
2908 }));
2909 xShapePropertySet->setPropertyValue("InteropGrabBag",uno::makeAny(aShapeGrabBag));
2914 if (!IsInHeaderFooter() && !checkZOrderStatus)
2915 xProps->setPropertyValue(
2916 getPropertyName( PROP_OPAQUE ),
2917 uno::makeAny( true ) );
2919 m_bParaChanged = true;
2920 getTableManager().setIsInShape(true);
2922 catch ( const uno::Exception& )
2924 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Exception when adding shape");
2928 * Updating chart height and width after reading the actual values from wp:extent
2930 void DomainMapper_Impl::UpdateEmbeddedShapeProps(const uno::Reference< drawing::XShape > & xShape)
2932 if (!xShape.is())
2933 return;
2935 uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW);
2936 awt::Size aSize = xShape->getSize( );
2937 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_WIDTH), uno::makeAny(sal_Int32(aSize.Width)));
2938 xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::makeAny(sal_Int32(aSize.Height)));
2942 void DomainMapper_Impl::PopShapeContext()
2944 if (hasTableManager())
2946 getTableManager().endLevel();
2947 popTableManager();
2949 if ( !m_aAnchoredStack.empty() )
2951 // For OLE object replacement shape, the text append context was already removed
2952 // or the OLE object couldn't be inserted.
2953 if ( !m_aAnchoredStack.top().bToRemove )
2955 RemoveLastParagraph();
2956 if (!m_aTextAppendStack.empty())
2957 m_aTextAppendStack.pop();
2960 uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent;
2963 appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() );
2965 catch ( const uno::RuntimeException& )
2967 // this is normal: the shape is already attached
2970 const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW );
2971 // Remove the shape if required (most likely replacement shape for OLE object)
2972 // or anchored to a discarded header or footer
2973 if ( m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter )
2977 uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(m_xTextDocument, uno::UNO_QUERY_THROW);
2978 uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
2979 if ( xDrawPage.is() )
2980 xDrawPage->remove( xShape );
2982 catch( const uno::Exception& )
2987 // Relative width calculations deferred until section's margins are defined.
2988 // Being cautious: only deferring undefined/minimum-width shapes in order to avoid causing potential regressions
2989 css::awt::Size aShapeSize;
2992 aShapeSize = xShape->getSize();
2994 catch (const css::uno::RuntimeException& e)
2996 // May happen e.g. when text frame has no frame format
2997 // See sw/qa/extras/ooxmlimport/data/n779627.docx
2998 SAL_WARN("writerfilter.dmapper", "getSize failed. " << e.Message);
3000 if( aShapeSize.Width <= 2 )
3002 const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY );
3003 SectionPropertyMap* pSectionContext = GetSectionContext();
3004 if ( pSectionContext && (!hasTableManager() || !getTableManager().isInTable()) &&
3005 xShapePropertySet->getPropertySetInfo()->hasPropertyByName(getPropertyName(PROP_RELATIVE_WIDTH)) )
3007 pSectionContext->addRelativeWidthShape(xShape);
3011 m_aAnchoredStack.pop();
3015 bool DomainMapper_Impl::IsSdtEndBefore()
3017 bool bIsSdtEndBefore = false;
3018 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER);
3019 if(pContext)
3021 const uno::Sequence< beans::PropertyValue > currentCharProps = pContext->GetPropertyValues();
3022 for (const auto& rCurrentCharProp : currentCharProps)
3024 if (rCurrentCharProp.Name == "CharInteropGrabBag")
3026 uno::Sequence<beans::PropertyValue> aCharGrabBag;
3027 rCurrentCharProp.Value >>= aCharGrabBag;
3028 for (const auto& rProp : std::as_const(aCharGrabBag))
3030 if(rProp.Name == "SdtEndBefore")
3032 rProp.Value >>= bIsSdtEndBefore;
3038 return bIsSdtEndBefore;
3041 bool DomainMapper_Impl::IsDiscardHeaderFooter() const
3043 return m_bDiscardHeaderFooter;
3046 // called from TableManager::closeCell()
3047 void DomainMapper_Impl::ClearPreviousParagraph()
3049 // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering
3050 if ((m_nTableDepth == (m_nTableCellDepth + 1))
3051 && m_xPreviousParagraph.is()
3052 && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing())
3054 uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
3055 if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() )
3056 m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::makeAny(static_cast<sal_Int32>(0)));
3059 m_xPreviousParagraph.clear();
3061 // next table paragraph will be first paragraph in a cell
3062 m_bFirstParagraphInCell = true;
3065 static sal_Int16 lcl_ParseNumberingType( const OUString& rCommand )
3067 sal_Int16 nRet = style::NumberingType::PAGE_DESCRIPTOR;
3069 // The command looks like: " PAGE \* Arabic "
3070 OUString sNumber = msfilter::util::findQuotedText(rCommand, "\\* ", ' ');
3072 if( !sNumber.isEmpty() )
3074 //todo: might make sense to hash this list, too
3075 struct NumberingPairs
3077 const sal_Char* cWordName;
3078 sal_Int16 nType;
3080 static const NumberingPairs aNumberingPairs[] =
3082 {"Arabic", style::NumberingType::ARABIC}
3083 ,{"ROMAN", style::NumberingType::ROMAN_UPPER}
3084 ,{"roman", style::NumberingType::ROMAN_LOWER}
3085 ,{"ALPHABETIC", style::NumberingType::CHARS_UPPER_LETTER}
3086 ,{"alphabetic", style::NumberingType::CHARS_LOWER_LETTER}
3087 ,{"CircleNum", style::NumberingType::CIRCLE_NUMBER}
3088 ,{"ThaiArabic", style::NumberingType::CHARS_THAI}
3089 ,{"ThaiCardText", style::NumberingType::CHARS_THAI}
3090 ,{"ThaiLetter", style::NumberingType::CHARS_THAI}
3091 // ,{"SBCHAR", style::NumberingType::}
3092 // ,{"DBCHAR", style::NumberingType::}
3093 // ,{"DBNUM1", style::NumberingType::}
3094 // ,{"DBNUM2", style::NumberingType::}
3095 // ,{"DBNUM3", style::NumberingType::}
3096 // ,{"DBNUM4", style::NumberingType::}
3097 ,{"Aiueo", style::NumberingType::AIU_FULLWIDTH_JA}
3098 ,{"Iroha", style::NumberingType::IROHA_FULLWIDTH_JA}
3099 // ,{"ZODIAC1", style::NumberingType::}
3100 // ,{"ZODIAC2", style::NumberingType::}
3101 // ,{"ZODIAC3", style::NumberingType::}
3102 // ,{"CHINESENUM1", style::NumberingType::}
3103 // ,{"CHINESENUM2", style::NumberingType::}
3104 // ,{"CHINESENUM3", style::NumberingType::}
3105 ,{"ArabicAlpha", style::NumberingType::CHARS_ARABIC}
3106 ,{"ArabicAbjad", style::NumberingType::FULLWIDTH_ARABIC}
3107 /* possible values:
3108 style::NumberingType::
3110 CHARS_UPPER_LETTER_N
3111 CHARS_LOWER_LETTER_N
3112 TRANSLITERATION
3113 NATIVE_NUMBERING
3114 CIRCLE_NUMBER
3115 NUMBER_LOWER_ZH
3116 NUMBER_UPPER_ZH
3117 NUMBER_UPPER_ZH_TW
3118 TIAN_GAN_ZH
3119 DI_ZI_ZH
3120 NUMBER_TRADITIONAL_JA
3121 AIU_HALFWIDTH_JA
3122 IROHA_HALFWIDTH_JA
3123 NUMBER_UPPER_KO
3124 NUMBER_HANGUL_KO
3125 HANGUL_JAMO_KO
3126 HANGUL_SYLLABLE_KO
3127 HANGUL_CIRCLED_JAMO_KO
3128 HANGUL_CIRCLED_SYLLABLE_KO
3129 CHARS_HEBREW
3130 CHARS_NEPALI
3131 CHARS_KHMER
3132 CHARS_LAO
3133 CHARS_TIBETAN
3134 CHARS_CYRILLIC_UPPER_LETTER_BG
3135 CHARS_CYRILLIC_LOWER_LETTER_BG
3136 CHARS_CYRILLIC_UPPER_LETTER_N_BG
3137 CHARS_CYRILLIC_LOWER_LETTER_N_BG
3138 CHARS_CYRILLIC_UPPER_LETTER_RU
3139 CHARS_CYRILLIC_LOWER_LETTER_RU
3140 CHARS_CYRILLIC_UPPER_LETTER_N_RU
3141 CHARS_CYRILLIC_LOWER_LETTER_N_RU
3142 CHARS_CYRILLIC_UPPER_LETTER_SR
3143 CHARS_CYRILLIC_LOWER_LETTER_SR
3144 CHARS_CYRILLIC_UPPER_LETTER_N_SR
3145 CHARS_CYRILLIC_LOWER_LETTER_N_SR*/
3148 for(const NumberingPairs& rNumberingPair : aNumberingPairs)
3150 if( /*sCommand*/sNumber.equalsAscii(rNumberingPair.cWordName ))
3152 nRet = rNumberingPair.nType;
3153 break;
3158 return nRet;
3162 static OUString lcl_ParseFormat( const OUString& rCommand )
3164 // The command looks like: " DATE \@"dd MMMM yyyy" or "09/02/2014"
3165 // Remove whitespace permitted by standard between \@ and "
3166 OUString command;
3167 sal_Int32 delimPos = rCommand.indexOf("\\@");
3168 if (delimPos != -1)
3170 sal_Int32 wsChars = rCommand.indexOf('\"') - delimPos - 2;
3171 command = rCommand.replaceAt(delimPos+2, wsChars, "");
3173 else
3174 command = rCommand;
3176 return msfilter::util::findQuotedText(command, "\\@\"", '\"');
3178 /*-------------------------------------------------------------------------
3179 extract a parameter (with or without quotes) between the command and the following backslash
3180 -----------------------------------------------------------------------*/
3181 static OUString lcl_ExtractToken(OUString const& rCommand,
3182 sal_Int32 & rIndex, bool & rHaveToken, bool & rIsSwitch)
3184 rHaveToken = false;
3185 rIsSwitch = false;
3187 OUStringBuffer token;
3188 bool bQuoted(false);
3189 for (; rIndex < rCommand.getLength(); ++rIndex)
3191 sal_Unicode const currentChar(rCommand[rIndex]);
3192 switch (currentChar)
3194 case '\\':
3196 if (rIndex == rCommand.getLength() - 1)
3198 SAL_INFO("writerfilter.dmapper", "field: trailing escape");
3199 ++rIndex;
3200 return OUString();
3202 sal_Unicode const nextChar(rCommand[rIndex+1]);
3203 if (bQuoted || '\\' == nextChar)
3205 ++rIndex; // read 2 chars
3206 token.append(nextChar);
3208 else // field switch (case insensitive)
3210 rHaveToken = true;
3211 if (token.isEmpty())
3213 rIsSwitch = true;
3214 rIndex += 2; // read 2 chars
3215 return rCommand.copy(rIndex - 2, 2).toAsciiUpperCase();
3217 else
3218 { // leave rIndex, read it again next time
3219 return token.makeStringAndClear();
3223 break;
3224 case '\"':
3225 if (bQuoted || !token.isEmpty())
3227 rHaveToken = true;
3228 if (bQuoted)
3230 ++rIndex;
3232 return token.makeStringAndClear();
3234 else
3236 bQuoted = true;
3238 break;
3239 case ' ':
3240 if (bQuoted)
3242 token.append(' ');
3244 else
3246 if (!token.isEmpty())
3248 rHaveToken = true;
3249 ++rIndex;
3250 return token.makeStringAndClear();
3253 break;
3254 case '=':
3255 if (token.isEmpty())
3257 rHaveToken = true;
3258 ++rIndex;
3259 return "FORMULA";
3261 break;
3262 default:
3263 token.append(currentChar);
3264 break;
3267 assert(rIndex == rCommand.getLength());
3268 if (bQuoted)
3270 // MS Word allows this, so just emit a debug message
3271 SAL_INFO("writerfilter.dmapper",
3272 "field argument with unterminated quote");
3274 rHaveToken = !token.isEmpty();
3275 return token.makeStringAndClear();
3278 std::tuple<OUString, std::vector<OUString>, std::vector<OUString> > splitFieldCommand(const OUString& rCommand)
3280 OUString sType;
3281 std::vector<OUString> arguments;
3282 std::vector<OUString> switches;
3283 sal_Int32 nStartIndex(0);
3284 // tdf#54584: Field may be prepended by a backslash
3285 // This is not an escapement, but already escaped literal "\"
3286 // MS Word allows this, so just skip it
3287 if ((rCommand.getLength() >= nStartIndex + 2) &&
3288 (rCommand[nStartIndex] == L'\\') &&
3289 (rCommand[nStartIndex + 1] != L'\\') &&
3290 (rCommand[nStartIndex + 1] != L' '))
3292 ++nStartIndex;
3297 bool bHaveToken;
3298 bool bIsSwitch;
3299 OUString const token =
3300 lcl_ExtractToken(rCommand, nStartIndex, bHaveToken, bIsSwitch);
3301 assert(nStartIndex <= rCommand.getLength());
3302 if (bHaveToken)
3304 if (sType.isEmpty())
3306 sType = token.toAsciiUpperCase();
3308 else if (bIsSwitch || !switches.empty())
3310 switches.push_back(token);
3312 else
3314 arguments.push_back(token);
3317 } while (nStartIndex < rCommand.getLength());
3319 return std::make_tuple(sType, arguments, switches);
3322 static OUString lcl_ExctractVariableAndHint( const OUString& rCommand, OUString& rHint )
3324 // the first word after "ASK " is the variable
3325 // the text after the variable and before a '\' is the hint
3326 // if no hint is set the variable is used as hint
3327 // the quotes of the hint have to be removed
3328 sal_Int32 nIndex = rCommand.indexOf( ' ', 2); //find last space after 'ASK'
3329 if (nIndex == -1)
3330 return OUString();
3331 while(rCommand[nIndex] == ' ')
3332 ++nIndex;
3333 OUString sShortCommand( rCommand.copy( nIndex ) ); //cut off the " ASK "
3335 sShortCommand = sShortCommand.getToken(0, '\\');
3336 nIndex = 0;
3337 OUString sRet = sShortCommand.getToken( 0, ' ', nIndex);
3338 if( nIndex > 0)
3339 rHint = sShortCommand.copy( nIndex );
3340 if( rHint.isEmpty() )
3341 rHint = sRet;
3342 return sRet;
3346 static bool lcl_FindInCommand(
3347 const OUString& rCommand,
3348 sal_Unicode cSwitch,
3349 OUString& rValue )
3351 bool bRet = false;
3352 OUString sSearch = "\\" + OUStringChar( cSwitch );
3353 sal_Int32 nIndex = rCommand.indexOf( sSearch );
3354 if( nIndex >= 0 )
3356 bRet = true;
3357 //find next '\' or end of string
3358 sal_Int32 nEndIndex = rCommand.indexOf( '\\', nIndex + 1);
3359 if( nEndIndex < 0 )
3360 nEndIndex = rCommand.getLength() ;
3361 if( nEndIndex - nIndex > 3 )
3362 rValue = rCommand.copy( nIndex + 3, nEndIndex - nIndex - 3);
3364 return bRet;
3368 void DomainMapper_Impl::GetCurrentLocale(lang::Locale& rLocale)
3370 PropertyMapPtr pTopContext = GetTopContext();
3371 std::optional<PropertyMap::Property> pLocale = pTopContext->getProperty(PROP_CHAR_LOCALE);
3372 if( pLocale )
3373 pLocale->second >>= rLocale;
3374 else
3376 PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
3377 pLocale = pParaContext->getProperty(PROP_CHAR_LOCALE);
3378 if( pLocale )
3380 pLocale->second >>= rLocale;
3385 /*-------------------------------------------------------------------------
3386 extract the number format from the command and apply the resulting number
3387 format to the XPropertySet
3388 -----------------------------------------------------------------------*/
3389 void DomainMapper_Impl::SetNumberFormat( const OUString& rCommand,
3390 uno::Reference< beans::XPropertySet > const& xPropertySet,
3391 bool const bDetectFormat)
3393 OUString sFormatString = lcl_ParseFormat( rCommand );
3394 // find \h - hijri/luna calendar todo: what about saka/era calendar?
3395 bool bHijri = 0 < rCommand.indexOf("\\h ");
3396 lang::Locale aUSLocale;
3397 aUSLocale.Language = "en";
3398 aUSLocale.Country = "US";
3400 //determine current locale - todo: is it necessary to initialize this locale?
3401 lang::Locale aCurrentLocale = aUSLocale;
3402 GetCurrentLocale( aCurrentLocale );
3403 OUString sFormat = ConversionHelper::ConvertMSFormatStringToSO( sFormatString, aCurrentLocale, bHijri);
3404 //get the number formatter and convert the string to a format value
3407 sal_Int32 nKey = 0;
3408 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW );
3409 if( bDetectFormat )
3411 uno::Reference< util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
3412 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
3413 nKey = xFormatter->detectNumberFormat( 0, rCommand );
3415 else
3417 nKey = xNumberSupplier->getNumberFormats()->addNewConverted( sFormat, aUSLocale, aCurrentLocale );
3419 xPropertySet->setPropertyValue(
3420 getPropertyName(PROP_NUMBER_FORMAT),
3421 uno::makeAny( nKey ));
3423 catch(const uno::Exception&)
3428 static uno::Any lcl_getGrabBagValue( const uno::Sequence<beans::PropertyValue>& grabBag, OUString const & name )
3430 auto pProp = std::find_if(grabBag.begin(), grabBag.end(),
3431 [&name](const beans::PropertyValue& rProp) { return rProp.Name == name; });
3432 if (pProp != grabBag.end())
3433 return pProp->Value;
3434 return uno::Any();
3437 //Link the text frames.
3438 void DomainMapper_Impl::ChainTextFrames()
3440 //can't link textboxes if there are not even two of them...
3441 if( 2 > m_vTextFramesForChaining.size() )
3442 return ;
3444 struct TextFramesForChaining {
3445 css::uno::Reference< css::drawing::XShape > xShape;
3446 sal_Int32 nId;
3447 sal_Int32 nSeq;
3448 OUString s_mso_next_textbox;
3449 bool bShapeNameSet;
3450 TextFramesForChaining(): nId(0), nSeq(0), bShapeNameSet(false) {}
3452 typedef std::map <OUString, TextFramesForChaining> ChainMap;
3456 ChainMap aTextFramesForChainingHelper;
3457 OUString sChainNextName("ChainNextName");
3459 //learn about ALL of the textboxes and their chaining values first - because frames are processed in no specific order.
3460 for( const auto& rTextFrame : m_vTextFramesForChaining )
3462 uno::Reference<text::XTextContent> xTextContent(rTextFrame, uno::UNO_QUERY_THROW);
3463 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
3464 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo;
3465 if( xPropertySet.is() )
3466 xPropertySetInfo = xPropertySet->getPropertySetInfo();
3467 uno::Sequence<beans::PropertyValue> aGrabBag;
3468 uno::Reference<lang::XServiceInfo> xServiceInfo(xPropertySet, uno::UNO_QUERY);
3470 TextFramesForChaining aChainStruct;
3471 OUString sShapeName;
3472 OUString sLinkChainName;
3474 //The chaining name and the shape name CAN be different in .docx.
3475 //MUST use LinkDisplayName/ChainName as the shape name for establishing links.
3476 if ( xServiceInfo->supportsService("com.sun.star.text.TextFrame") )
3478 xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag;
3479 xPropertySet->getPropertyValue("LinkDisplayName") >>= sShapeName;
3481 else
3483 xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3484 xPropertySet->getPropertyValue("ChainName") >>= sShapeName;
3487 lcl_getGrabBagValue( aGrabBag, "Txbx-Id") >>= aChainStruct.nId;
3488 lcl_getGrabBagValue( aGrabBag, "Txbx-Seq") >>= aChainStruct.nSeq;
3489 lcl_getGrabBagValue( aGrabBag, "LinkChainName") >>= sLinkChainName;
3490 lcl_getGrabBagValue( aGrabBag, "mso-next-textbox") >>= aChainStruct.s_mso_next_textbox;
3492 //Sometimes the shape names have not been imported. If not, we may have a fallback name.
3493 //Set name later, only if required for linking.
3494 if( sShapeName.isEmpty() )
3495 aChainStruct.bShapeNameSet = false;
3496 else
3498 aChainStruct.bShapeNameSet = true;
3499 sLinkChainName = sShapeName;
3502 if( !sLinkChainName.isEmpty() )
3504 aChainStruct.xShape = rTextFrame;
3505 aTextFramesForChainingHelper[sLinkChainName] = aChainStruct;
3509 //if mso-next-textbox tags are provided, create those vml-style links first. Afterwards we will make dml-style id/seq links.
3510 for (auto& msoItem : aTextFramesForChainingHelper)
3512 //if no mso-next-textbox, we are done.
3513 //if it points to itself, we are done.
3514 if( !msoItem.second.s_mso_next_textbox.isEmpty()
3515 && msoItem.second.s_mso_next_textbox != msoItem.first )
3517 ChainMap::iterator nextFinder=aTextFramesForChainingHelper.find(msoItem.second.s_mso_next_textbox);
3518 if( nextFinder != aTextFramesForChainingHelper.end() )
3520 //if the frames have no name yet, then set them. LinkDisplayName / ChainName are read-only.
3521 if( !msoItem.second.bShapeNameSet )
3523 uno::Reference< container::XNamed > xNamed( msoItem.second.xShape, uno::UNO_QUERY );
3524 if ( xNamed.is() )
3526 xNamed->setName( msoItem.first );
3527 msoItem.second.bShapeNameSet = true;
3530 if( !nextFinder->second.bShapeNameSet )
3532 uno::Reference< container::XNamed > xNamed( nextFinder->second.xShape, uno::UNO_QUERY );
3533 if ( xNamed.is() )
3535 xNamed->setName( nextFinder->first );
3536 nextFinder->second.bShapeNameSet = true;
3540 uno::Reference<text::XTextContent> xTextContent(msoItem.second.xShape, uno::UNO_QUERY_THROW);
3541 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
3543 //The reverse chaining happens automatically, so only one direction needs to be set
3544 xPropertySet->setPropertyValue(sChainNextName, uno::makeAny(nextFinder->first));
3546 //the last item in an mso-next-textbox chain is indistinguishable from id/seq items. Now that it is handled, remove it.
3547 if( nextFinder->second.s_mso_next_textbox.isEmpty() )
3548 aTextFramesForChainingHelper.erase(nextFinder->first);
3553 //TODO: Perhaps allow reverse sequences when mso-layout-flow-alt = "bottom-to-top"
3554 const sal_Int32 nDirection = 1;
3556 //Finally - go through and attach the chains based on matching ID and incremented sequence number (dml-style).
3557 for (const auto& rOuter : aTextFramesForChainingHelper)
3559 if( rOuter.second.s_mso_next_textbox.isEmpty() ) //non-empty ones already handled earlier - so skipping them now.
3561 for (const auto& rInner : aTextFramesForChainingHelper)
3563 if ( rInner.second.nId == rOuter.second.nId )
3565 if ( rInner.second.nSeq == ( rOuter.second.nSeq + nDirection ) )
3567 uno::Reference<text::XTextContent> xTextContent(rOuter.second.xShape, uno::UNO_QUERY_THROW);
3568 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
3570 //The reverse chaining happens automatically, so only one direction needs to be set
3571 xPropertySet->setPropertyValue(sChainNextName, uno::makeAny(rInner.first));
3572 break ; //there cannot be more than one next frame
3578 m_vTextFramesForChaining.clear(); //clear the vector
3580 catch (const uno::Exception&)
3582 DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
3586 uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName)
3588 // query master, create if not available
3589 uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY_THROW );
3590 uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters();
3591 uno::Reference< beans::XPropertySet > xMaster;
3592 OUString sFieldMasterService( OUString::createFromAscii(pFieldMasterService) );
3593 OUStringBuffer aFieldMasterName;
3594 OUString sDatabaseDataSourceName = GetSettingsTable()->GetCurrentDatabaseDataSource();
3595 bool bIsMergeField = sFieldMasterService.endsWith("Database");
3596 aFieldMasterName.appendAscii( pFieldMasterService );
3597 aFieldMasterName.append('.');
3598 if ( bIsMergeField && !sDatabaseDataSourceName.isEmpty() )
3600 aFieldMasterName.append(sDatabaseDataSourceName);
3601 aFieldMasterName.append('.');
3603 aFieldMasterName.append(rFieldMasterName);
3604 OUString sFieldMasterName = aFieldMasterName.makeStringAndClear();
3605 if(xFieldMasterAccess->hasByName(sFieldMasterName))
3607 //get the master
3608 xMaster.set(xFieldMasterAccess->getByName(sFieldMasterName), uno::UNO_QUERY_THROW);
3610 else if( m_xTextFactory.is() )
3612 //create the master
3613 xMaster.set( m_xTextFactory->createInstance(sFieldMasterService), uno::UNO_QUERY_THROW);
3614 if ( !bIsMergeField || sDatabaseDataSourceName.isEmpty() )
3616 //set the master's name
3617 xMaster->setPropertyValue(
3618 getPropertyName(PROP_NAME),
3619 uno::makeAny(rFieldMasterName));
3620 } else {
3621 // set database data, based on the "databasename.tablename" of sDatabaseDataSourceName
3622 xMaster->setPropertyValue(
3623 getPropertyName(PROP_DATABASE_NAME),
3624 uno::makeAny(sDatabaseDataSourceName.copy(0, sDatabaseDataSourceName.indexOf('.'))));
3625 xMaster->setPropertyValue(
3626 getPropertyName(PROP_COMMAND_TYPE),
3627 uno::makeAny(sal_Int32(0)));
3628 xMaster->setPropertyValue(
3629 getPropertyName(PROP_DATATABLE_NAME),
3630 uno::makeAny(sDatabaseDataSourceName.copy(sDatabaseDataSourceName.indexOf('.') + 1)));
3631 xMaster->setPropertyValue(
3632 getPropertyName(PROP_DATACOLUMN_NAME),
3633 uno::makeAny(rFieldMasterName));
3636 return xMaster;
3639 void DomainMapper_Impl::PushFieldContext()
3641 m_bParaHadField = true;
3642 if(m_bDiscardHeaderFooter)
3643 return;
3644 #ifdef DBG_UTIL
3645 TagLogger::getInstance().element("pushFieldContext");
3646 #endif
3648 uno::Reference<text::XTextCursor> xCrsr;
3649 if (!m_aTextAppendStack.empty())
3651 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
3652 if (xTextAppend.is())
3653 xCrsr = xTextAppend->createTextCursorByRange(
3654 m_aTextAppendStack.top().xInsertPosition.is()
3655 ? m_aTextAppendStack.top().xInsertPosition
3656 : xTextAppend->getEnd());
3659 uno::Reference< text::XTextRange > xStart;
3660 if (xCrsr.is())
3661 xStart = xCrsr->getStart();
3662 m_aFieldStack.push_back(new FieldContext(xStart));
3664 /*-------------------------------------------------------------------------
3665 //the current field context waits for the completion of the command
3666 -----------------------------------------------------------------------*/
3667 bool DomainMapper_Impl::IsOpenFieldCommand() const
3669 return !m_aFieldStack.empty() && !m_aFieldStack.back()->IsCommandCompleted();
3671 /*-------------------------------------------------------------------------
3672 //the current field context waits for the completion of the command
3673 -----------------------------------------------------------------------*/
3674 bool DomainMapper_Impl::IsOpenField() const
3676 return !m_aFieldStack.empty();
3679 // Mark top field context as containing a fixed field
3680 void DomainMapper_Impl::SetFieldLocked()
3682 if (IsOpenField())
3683 m_aFieldStack.back()->SetFieldLocked();
3686 HeaderFooterContext::HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth)
3687 : m_bTextInserted(bTextInserted)
3688 , m_nTableDepth(nTableDepth)
3692 bool HeaderFooterContext::getTextInserted() const
3694 return m_bTextInserted;
3697 sal_Int32 HeaderFooterContext::getTableDepth() const { return m_nTableDepth; }
3699 FieldContext::FieldContext(uno::Reference< text::XTextRange > const& xStart)
3700 : m_bFieldCommandCompleted(false)
3701 , m_xStartRange( xStart )
3702 , m_bFieldLocked( false )
3704 m_pProperties = new PropertyMap();
3708 FieldContext::~FieldContext()
3713 void FieldContext::AppendCommand(const OUString& rPart)
3715 m_sCommand += rPart;
3718 ::std::vector<OUString> FieldContext::GetCommandParts() const
3720 ::std::vector<OUString> aResult;
3721 sal_Int32 nIndex = 0;
3722 bool bInString = false;
3723 OUString sPart;
3724 while (nIndex != -1)
3726 OUString sToken = GetCommand().getToken(0, ' ', nIndex);
3727 bool bInStringNext = bInString;
3729 if (sToken.isEmpty())
3730 continue;
3732 if (sToken[0] == '"')
3734 bInStringNext = true;
3735 sToken = sToken.copy(1);
3737 if (sToken.endsWith("\""))
3739 bInStringNext = false;
3740 sToken = sToken.copy(0, sToken.getLength() - 1);
3743 if (bInString)
3745 sPart += " " + sToken;
3746 if (!bInStringNext)
3748 aResult.push_back(sPart);
3751 else
3753 if (bInStringNext)
3755 sPart = sToken;
3757 else
3759 aResult.push_back(sToken);
3763 bInString = bInStringNext;
3766 return aResult;
3769 /*-------------------------------------------------------------------------
3770 //collect the pieces of the command
3771 -----------------------------------------------------------------------*/
3772 void DomainMapper_Impl::AppendFieldCommand(OUString const & rPartOfCommand)
3774 #ifdef DBG_UTIL
3775 TagLogger::getInstance().startElement("appendFieldCommand");
3776 TagLogger::getInstance().chars(rPartOfCommand);
3777 TagLogger::getInstance().endElement();
3778 #endif
3780 FieldContextPtr pContext = m_aFieldStack.back();
3781 OSL_ENSURE( pContext.get(), "no field context available");
3782 if( pContext.get() )
3784 pContext->AppendCommand( rPartOfCommand );
3789 typedef std::multimap < sal_Int32, OUString > TOCStyleMap;
3791 static const FieldConversionMap_t & lcl_GetFieldConversion()
3793 static const FieldConversionMap_t aFieldConversionMap
3795 // {"ADDRESSBLOCK", {"", FIELD_ADDRESSBLOCK }},
3796 // {"ADVANCE", {"", FIELD_ADVANCE }},
3797 {"ASK", {"SetExpression", FIELD_ASK }},
3798 {"AUTONUM", {"SetExpression", FIELD_AUTONUM }},
3799 {"AUTONUMLGL", {"SetExpression", FIELD_AUTONUMLGL }},
3800 {"AUTONUMOUT", {"SetExpression", FIELD_AUTONUMOUT }},
3801 {"AUTHOR", {"DocInfo.CreateAuthor", FIELD_AUTHOR }},
3802 {"DATE", {"DateTime", FIELD_DATE }},
3803 {"COMMENTS", {"DocInfo.Description", FIELD_COMMENTS }},
3804 {"CREATEDATE", {"DocInfo.CreateDateTime", FIELD_CREATEDATE }},
3805 {"DOCPROPERTY", {"", FIELD_DOCPROPERTY }},
3806 {"DOCVARIABLE", {"User", FIELD_DOCVARIABLE }},
3807 {"EDITTIME", {"DocInfo.EditTime", FIELD_EDITTIME }},
3808 {"EQ", {"", FIELD_EQ }},
3809 {"FILLIN", {"Input", FIELD_FILLIN }},
3810 {"FILENAME", {"FileName", FIELD_FILENAME }},
3811 // {"FILESIZE", {"", FIELD_FILESIZE }},
3812 {"FORMULA", {"TableFormula", FIELD_FORMULA }},
3813 {"FORMCHECKBOX", {"", FIELD_FORMCHECKBOX }},
3814 {"FORMDROPDOWN", {"DropDown", FIELD_FORMDROPDOWN }},
3815 {"FORMTEXT", {"Input", FIELD_FORMTEXT }},
3816 {"GOTOBUTTON", {"", FIELD_GOTOBUTTON }},
3817 {"HYPERLINK", {"", FIELD_HYPERLINK }},
3818 {"IF", {"ConditionalText", FIELD_IF }},
3819 // {"INFO", {"", FIELD_INFO }},
3820 {"INCLUDEPICTURE", {"", FIELD_INCLUDEPICTURE}},
3821 {"KEYWORDS", {"DocInfo.KeyWords", FIELD_KEYWORDS }},
3822 {"LASTSAVEDBY", {"DocInfo.ChangeAuthor", FIELD_LASTSAVEDBY }},
3823 {"MACROBUTTON", {"Macro", FIELD_MACROBUTTON }},
3824 {"MERGEFIELD", {"Database", FIELD_MERGEFIELD }},
3825 {"MERGEREC", {"DatabaseNumberOfSet", FIELD_MERGEREC }},
3826 // {"MERGESEQ", {"", FIELD_MERGESEQ }},
3827 {"NEXT", {"DatabaseNextSet", FIELD_NEXT }},
3828 {"NEXTIF", {"DatabaseNextSet", FIELD_NEXTIF }},
3829 {"PAGE", {"PageNumber", FIELD_PAGE }},
3830 {"PAGEREF", {"GetReference", FIELD_PAGEREF }},
3831 {"REF", {"GetReference", FIELD_REF }},
3832 {"REVNUM", {"DocInfo.Revision", FIELD_REVNUM }},
3833 {"SAVEDATE", {"DocInfo.Change", FIELD_SAVEDATE }},
3834 // {"SECTION", {"", FIELD_SECTION }},
3835 // {"SECTIONPAGES", {"", FIELD_SECTIONPAGES }},
3836 {"SEQ", {"SetExpression", FIELD_SEQ }},
3837 {"SET", {"SetExpression", FIELD_SET }},
3838 // {"SKIPIF", {"", FIELD_SKIPIF }},
3839 // {"STYLEREF", {"", FIELD_STYLEREF }},
3840 {"SUBJECT", {"DocInfo.Subject", FIELD_SUBJECT }},
3841 {"SYMBOL", {"", FIELD_SYMBOL }},
3842 {"TEMPLATE", {"TemplateName", FIELD_TEMPLATE }},
3843 {"TIME", {"DateTime", FIELD_TIME }},
3844 {"TITLE", {"DocInfo.Title", FIELD_TITLE }},
3845 {"USERINITIALS", {"Author", FIELD_USERINITIALS }},
3846 // {"USERADDRESS", {"", FIELD_USERADDRESS }},
3847 {"USERNAME", {"Author", FIELD_USERNAME }},
3850 {"TOC", {"com.sun.star.text.ContentIndex", FIELD_TOC }},
3851 {"TC", {"com.sun.star.text.ContentIndexMark", FIELD_TC }},
3852 {"NUMCHARS", {"CharacterCount", FIELD_NUMCHARS }},
3853 {"NUMWORDS", {"WordCount", FIELD_NUMWORDS }},
3854 {"NUMPAGES", {"PageCount", FIELD_NUMPAGES }},
3855 {"INDEX", {"com.sun.star.text.DocumentIndex", FIELD_INDEX }},
3856 {"XE", {"com.sun.star.text.DocumentIndexMark", FIELD_XE }},
3857 {"BIBLIOGRAPHY",{"com.sun.star.text.Bibliography", FIELD_BIBLIOGRAPHY }},
3858 {"CITATION", {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION }},
3861 return aFieldConversionMap;
3864 static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion()
3866 static const FieldConversionMap_t aEnhancedFieldConversionMap =
3868 {"FORMCHECKBOX", {"FormFieldmark", FIELD_FORMCHECKBOX}},
3869 {"FORMDROPDOWN", {"FormFieldmark", FIELD_FORMDROPDOWN}},
3870 {"FORMTEXT", {"Fieldmark", FIELD_FORMTEXT}},
3873 return aEnhancedFieldConversionMap;
3876 void DomainMapper_Impl::handleFieldSet
3877 (const FieldContextPtr& pContext,
3878 uno::Reference< uno::XInterface > const & xFieldInterface,
3879 uno::Reference< beans::XPropertySet > const& xFieldProperties)
3881 OUString sVariable, sHint;
3883 sVariable = lcl_ExctractVariableAndHint(pContext->GetCommand(), sHint);
3885 // remove surrounding "" if exists
3886 if(sHint.getLength() >= 2)
3888 OUString sTmp = sHint.trim();
3889 if (sTmp.startsWith("\"") && sTmp.endsWith("\""))
3891 sHint = sTmp.copy(1, sTmp.getLength() - 2);
3895 // determine field master name
3896 uno::Reference< beans::XPropertySet > xMaster =
3897 FindOrCreateFieldMaster
3898 ("com.sun.star.text.FieldMaster.SetExpression", sVariable );
3900 // a set field is a string
3901 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::makeAny(text::SetVariableType::STRING));
3903 // attach the master to the field
3904 uno::Reference< text::XDependentTextField > xDependentField
3905 ( xFieldInterface, uno::UNO_QUERY_THROW );
3906 xDependentField->attachTextFieldMaster( xMaster );
3908 xFieldProperties->setPropertyValue(getPropertyName(PROP_HINT), uno::makeAny( sHint ));
3909 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::makeAny( sHint ));
3910 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::makeAny(text::SetVariableType::STRING));
3912 // Mimic MS Word behavior (hide the SET)
3913 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::makeAny(false));
3916 void DomainMapper_Impl::handleFieldAsk
3917 (const FieldContextPtr& pContext,
3918 uno::Reference< uno::XInterface > & xFieldInterface,
3919 uno::Reference< beans::XPropertySet > const& xFieldProperties)
3921 //doesn the command contain a variable name?
3922 OUString sVariable, sHint;
3924 sVariable = lcl_ExctractVariableAndHint( pContext->GetCommand(),
3925 sHint );
3926 if(!sVariable.isEmpty())
3928 // determine field master name
3929 uno::Reference< beans::XPropertySet > xMaster =
3930 FindOrCreateFieldMaster
3931 ("com.sun.star.text.FieldMaster.SetExpression", sVariable );
3932 // An ASK field is always a string of characters
3933 xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::makeAny(text::SetVariableType::STRING));
3935 // attach the master to the field
3936 uno::Reference< text::XDependentTextField > xDependentField
3937 ( xFieldInterface, uno::UNO_QUERY_THROW );
3938 xDependentField->attachTextFieldMaster( xMaster );
3940 // set input flag at the field
3941 xFieldProperties->setPropertyValue(
3942 getPropertyName(PROP_IS_INPUT), uno::makeAny( true ));
3943 // set the prompt
3944 xFieldProperties->setPropertyValue(
3945 getPropertyName(PROP_HINT),
3946 uno::makeAny( sHint ));
3947 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::makeAny(text::SetVariableType::STRING));
3948 // The ASK has no field value to display
3949 xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::makeAny(false));
3951 else
3953 //don't insert the field
3954 //todo: maybe import a 'normal' input field here?
3955 xFieldInterface = nullptr;
3959 void DomainMapper_Impl::handleFieldFormula
3960 (const FieldContextPtr& pContext,
3961 uno::Reference< beans::XPropertySet > const& xFieldProperties)
3963 OUString command = pContext->GetCommand().trim();
3965 // Remove number formatting from \# to end of command
3966 // TODO: handle custom number formatting
3967 sal_Int32 delimPos = command.indexOf("\\#");
3968 if (delimPos != -1)
3970 command = command.replaceAt(delimPos, command.getLength() - delimPos, "").trim();
3973 // command must contains = and at least another char
3974 if (command.getLength() < 2)
3975 return;
3977 // we don't copy the = symbol from the command
3978 OUString formula = command.copy(1);
3980 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::makeAny(formula));
3981 xFieldProperties->setPropertyValue(getPropertyName(PROP_NUMBER_FORMAT), uno::makeAny(sal_Int32(0)));
3982 xFieldProperties->setPropertyValue("IsShowFormula", uno::makeAny(false));
3985 void DomainMapper_Impl::handleRubyEQField( const FieldContextPtr& pContext)
3987 const OUString & rCommand(pContext->GetCommand());
3988 sal_Int32 nIndex = 0, nEnd = 0;
3989 RubyInfo aInfo ;
3990 nIndex = rCommand.indexOf("\\* jc" );
3991 if (nIndex != -1)
3993 nIndex += 5;
3994 sal_uInt32 nJc = rCommand.getToken(0, ' ',nIndex).toInt32();
3995 const sal_Int32 aRubyAlignValues[] =
3997 NS_ooxml::LN_Value_ST_RubyAlign_center,
3998 NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter,
3999 NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace,
4000 NS_ooxml::LN_Value_ST_RubyAlign_left,
4001 NS_ooxml::LN_Value_ST_RubyAlign_right,
4002 NS_ooxml::LN_Value_ST_RubyAlign_rightVertical,
4004 aInfo.nRubyAlign = aRubyAlignValues[(nJc<SAL_N_ELEMENTS(aRubyAlignValues))?nJc:0];
4007 // we don't parse or use the font field in rCommand
4009 nIndex = rCommand.indexOf("\\* hps" );
4010 if (nIndex != -1)
4012 nIndex += 6;
4013 aInfo.nHps = rCommand.getToken(0, ' ',nIndex).toInt32();
4016 nIndex = rCommand.indexOf("\\o");
4017 if (nIndex == -1 || (nIndex = rCommand.indexOf('(', nIndex)) == -1 || (nEnd = rCommand.lastIndexOf(')'))==-1 || nEnd <= nIndex)
4018 return;
4020 OUString sRubyParts = rCommand.copy(nIndex+1,nEnd-nIndex-1);
4021 nIndex = 0;
4022 OUString sPart1 = sRubyParts.getToken(0, ',', nIndex);
4023 OUString sPart2 = sRubyParts.getToken(0, ',', nIndex);
4024 if ((nIndex = sPart1.indexOf('(')) != -1 && (nEnd = sPart1.lastIndexOf(')'))!=-1 && nEnd > nIndex)
4026 aInfo.sRubyText = sPart1.copy(nIndex+1,nEnd-nIndex-1);
4029 PropertyMapPtr pRubyContext(new PropertyMap());
4030 pRubyContext->InsertProps(GetTopContext());
4031 if (aInfo.nHps > 0)
4033 double fVal = double(aInfo.nHps) / 2.;
4034 uno::Any aVal = uno::makeAny( fVal );
4036 pRubyContext->Insert(PROP_CHAR_HEIGHT, aVal);
4037 pRubyContext->Insert(PROP_CHAR_HEIGHT_ASIAN, aVal);
4039 PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pRubyContext->GetPropertyValues());
4040 aInfo.sRubyStyle = m_rDMapper.getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
4041 PropertyMapPtr pCharContext(new PropertyMap());
4042 if (m_pLastCharacterContext.get())
4043 pCharContext->InsertProps(m_pLastCharacterContext);
4044 pCharContext->InsertProps(pContext->getProperties());
4045 pCharContext->Insert(PROP_RUBY_TEXT, uno::makeAny( aInfo.sRubyText ) );
4046 pCharContext->Insert(PROP_RUBY_ADJUST, uno::makeAny(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign))));
4047 if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical )
4048 pCharContext->Insert(PROP_RUBY_POSITION, uno::makeAny(css::text::RubyPosition::INTER_CHARACTER));
4049 pCharContext->Insert(PROP_RUBY_STYLE, uno::makeAny(aInfo.sRubyStyle));
4050 appendTextPortion(sPart2, pCharContext);
4054 void DomainMapper_Impl::handleAutoNum
4055 (const FieldContextPtr& pContext,
4056 uno::Reference< uno::XInterface > const & xFieldInterface,
4057 uno::Reference< beans::XPropertySet > const& xFieldProperties)
4059 //create a sequence field master "AutoNr"
4060 uno::Reference< beans::XPropertySet > xMaster =
4061 FindOrCreateFieldMaster
4062 ("com.sun.star.text.FieldMaster.SetExpression",
4063 "AutoNr");
4065 xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE),
4066 uno::makeAny(text::SetVariableType::SEQUENCE));
4068 //apply the numbering type
4069 xFieldProperties->setPropertyValue(
4070 getPropertyName(PROP_NUMBERING_TYPE),
4071 uno::makeAny( lcl_ParseNumberingType(pContext->GetCommand()) ));
4072 // attach the master to the field
4073 uno::Reference< text::XDependentTextField > xDependentField
4074 ( xFieldInterface, uno::UNO_QUERY_THROW );
4075 xDependentField->attachTextFieldMaster( xMaster );
4078 void DomainMapper_Impl::handleAuthor
4079 (OUString const& rFirstParam,
4080 uno::Reference< beans::XPropertySet > const& xFieldProperties,
4081 FieldId eFieldId )
4083 if ( eFieldId != FIELD_USERINITIALS )
4084 xFieldProperties->setPropertyValue
4085 ( getPropertyName(PROP_FULL_NAME), uno::makeAny( true ));
4087 if (!rFirstParam.isEmpty())
4089 xFieldProperties->setPropertyValue(
4090 getPropertyName( PROP_IS_FIXED ),
4091 uno::makeAny( true ));
4092 //PROP_CURRENT_PRESENTATION is set later anyway
4096 void DomainMapper_Impl::handleDocProperty
4097 (const FieldContextPtr& pContext,
4098 OUString const& rFirstParam,
4099 uno::Reference< uno::XInterface > & xFieldInterface)
4101 //some docproperties should be imported as document statistic fields, some as DocInfo fields
4102 //others should be user fields
4103 if (rFirstParam.isEmpty())
4104 return;
4106 constexpr sal_uInt8 SET_ARABIC = 0x01;
4107 constexpr sal_uInt8 SET_DATE = 0x04;
4108 struct DocPropertyMap
4110 const sal_Char* pDocPropertyName;
4111 const sal_Char* pServiceName;
4112 sal_uInt8 nFlags;
4114 static const DocPropertyMap aDocProperties[] =
4116 {"CreateTime", "DocInfo.CreateDateTime", SET_DATE},
4117 {"Characters", "CharacterCount", SET_ARABIC},
4118 {"Comments", "DocInfo.Description", 0},
4119 {"Keywords", "DocInfo.KeyWords", 0},
4120 {"LastPrinted", "DocInfo.PrintDateTime", 0},
4121 {"LastSavedBy", "DocInfo.ChangeAuthor", 0},
4122 {"LastSavedTime", "DocInfo.ChangeDateTime", SET_DATE},
4123 {"Paragraphs", "ParagraphCount", SET_ARABIC},
4124 {"RevisionNumber", "DocInfo.Revision", 0},
4125 {"Subject", "DocInfo.Subject", 0},
4126 {"Template", "TemplateName", 0},
4127 {"Title", "DocInfo.Title", 0},
4128 {"TotalEditingTime", "DocInfo.EditTime", 0},
4129 {"Words", "WordCount", SET_ARABIC}
4131 //other available DocProperties:
4132 //Bytes, Category, CharactersWithSpaces, Company
4133 //HyperlinkBase,
4134 //Lines, Manager, NameofApplication, ODMADocId, Pages,
4135 //Security,
4137 uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(m_xTextDocument, uno::UNO_QUERY);
4138 uno::Reference<document::XDocumentProperties> xDocumentProperties = xDocumentPropertiesSupplier->getDocumentProperties();
4139 uno::Reference<beans::XPropertySet> xUserDefinedProps(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
4140 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xUserDefinedProps->getPropertySetInfo();
4141 //search for a field mapping
4142 OUString sFieldServiceName;
4143 size_t nMap = 0;
4144 for( ; nMap < SAL_N_ELEMENTS(aDocProperties); ++nMap )
4146 if ((rFirstParam.equalsAscii(aDocProperties[nMap].pDocPropertyName)) && (!xPropertySetInfo->hasPropertyByName(rFirstParam)))
4148 sFieldServiceName =
4149 OUString::createFromAscii
4150 (aDocProperties[nMap].pServiceName);
4151 break;
4154 OUString sServiceName("com.sun.star.text.TextField.");
4155 bool bIsCustomField = false;
4156 if(sFieldServiceName.isEmpty())
4158 //create a custom property field
4159 sServiceName += "DocInfo.Custom";
4160 bIsCustomField = true;
4162 else
4164 sServiceName += sFieldServiceName;
4166 if (m_xTextFactory.is())
4167 xFieldInterface = m_xTextFactory->createInstance(sServiceName);
4168 uno::Reference<beans::XPropertySet> xFieldProperties( xFieldInterface, uno::UNO_QUERY_THROW);
4169 if( bIsCustomField )
4171 xFieldProperties->setPropertyValue(
4172 getPropertyName(PROP_NAME), uno::makeAny(rFirstParam));
4173 pContext->SetCustomField( xFieldProperties );
4175 else
4177 if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC))
4178 xFieldProperties->setPropertyValue(
4179 getPropertyName(PROP_NUMBERING_TYPE),
4180 uno::makeAny( style::NumberingType::ARABIC ));
4181 else if(0 != (aDocProperties[nMap].nFlags & SET_DATE))
4183 xFieldProperties->setPropertyValue(
4184 getPropertyName(PROP_IS_DATE),
4185 uno::makeAny( true ));
4186 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
4191 static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator,
4192 const uno::Sequence< beans::PropertyValues >& aLevel )
4194 //create a copy of the level and add two new entries - hyperlink start and end
4195 bool bChapterNoSeparator = !sChapterNoSeparator.isEmpty();
4196 sal_Int32 nAdd = (bHyperlinks && bChapterNoSeparator) ? 4 : 2;
4197 uno::Sequence< beans::PropertyValues > aNewLevel( aLevel.getLength() + nAdd);
4198 beans::PropertyValues* pNewLevel = aNewLevel.getArray();
4199 if( bHyperlinks )
4201 beans::PropertyValues aHyperlink(1);
4202 aHyperlink[0].Name = getPropertyName( PROP_TOKEN_TYPE );
4203 aHyperlink[0].Value <<= getPropertyName( PROP_TOKEN_HYPERLINK_START );
4204 pNewLevel[0] = aHyperlink;
4205 aHyperlink[0].Value <<= getPropertyName( PROP_TOKEN_HYPERLINK_END );
4206 pNewLevel[aNewLevel.getLength() -1] = aHyperlink;
4208 if( bChapterNoSeparator )
4210 beans::PropertyValues aChapterNo(2);
4211 aChapterNo[0].Name = getPropertyName( PROP_TOKEN_TYPE );
4212 aChapterNo[0].Value <<= getPropertyName( PROP_TOKEN_CHAPTER_INFO );
4213 aChapterNo[1].Name = getPropertyName( PROP_CHAPTER_FORMAT );
4214 //todo: is ChapterFormat::Number correct?
4215 aChapterNo[1].Value <<= sal_Int16(text::ChapterFormat::NUMBER);
4216 pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 4 : 2) ] = aChapterNo;
4218 beans::PropertyValues aChapterSeparator(2);
4219 aChapterSeparator[0].Name = getPropertyName( PROP_TOKEN_TYPE );
4220 aChapterSeparator[0].Value <<= getPropertyName( PROP_TOKEN_TEXT );
4221 aChapterSeparator[1].Name = getPropertyName( PROP_TEXT );
4222 aChapterSeparator[1].Value <<= sChapterNoSeparator;
4223 pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 3 : 1)] = aChapterSeparator;
4225 //copy the 'old' entries except the last (page no)
4226 std::copy(aLevel.begin(), std::prev(aLevel.end()), std::next(aNewLevel.begin()));
4227 //copy page no entry (last or last but one depending on bHyperlinks
4228 sal_Int32 nPageNo = aNewLevel.getLength() - (bHyperlinks ? 2 : 3);
4229 pNewLevel[nPageNo] = aLevel[aLevel.getLength() - 1];
4231 return aNewLevel;
4234 /// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame
4235 OUString DomainMapper_Impl::extractTocTitle()
4237 if (!m_xSdtEntryStart.is())
4238 return OUString();
4240 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
4241 if(!xTextAppend.is())
4242 return OUString();
4244 // try-catch was added in the same way as inside appendTextSectionAfter()
4247 uno::Reference<text::XParagraphCursor> xCursor(xTextAppend->createTextCursorByRange(m_xSdtEntryStart), uno::UNO_QUERY_THROW);
4248 if (!xCursor.is())
4249 return OUString();
4251 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
4252 xCursor->gotoStartOfParagraph( false );
4253 if (m_aTextAppendStack.top().xInsertPosition.is())
4254 xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true );
4255 else
4256 xCursor->gotoEnd( true );
4258 // the paragraph after this new section might have been already inserted
4259 OUString sResult = xCursor->getString();
4260 if (sResult.endsWith(SAL_NEWLINE_STRING))
4261 sResult = sResult.copy(0, sResult.getLength() - SAL_N_ELEMENTS(SAL_NEWLINE_STRING) + 1);
4263 return sResult;
4265 catch(const uno::Exception&)
4269 return OUString();
4272 css::uno::Reference<css::beans::XPropertySet>
4273 DomainMapper_Impl::StartIndexSectionChecked(const OUString& sServiceName)
4275 if (m_bParaChanged)
4277 finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets m_bParaChanged
4278 PopProperties(CONTEXT_PARAGRAPH);
4279 PushProperties(CONTEXT_PARAGRAPH);
4280 SetIsFirstRun(true);
4281 // The first paragraph of the index that is continuation of just finished one needs to be
4282 // removed when finished (unless more content will arrive, which will set m_bParaChanged)
4283 m_bRemoveThisParagraph = true;
4285 const auto& xTextAppend = GetTopTextAppend();
4286 const auto xTextRange = xTextAppend->getEnd();
4287 const auto xRet = createSectionForRange(xTextRange, xTextRange, sServiceName, false);
4288 if (!m_aTextAppendStack.top().xInsertPosition)
4292 m_bStartedTOC = true;
4293 uno::Reference<text::XTextCursor> xTOCTextCursor
4294 = xTextRange->getText()->createTextCursor();
4295 xTOCTextCursor->gotoEnd(false);
4296 m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
4298 catch (const uno::Exception&)
4300 TOOLS_WARN_EXCEPTION("writerfilter.dmapper",
4301 "DomainMapper_Impl::StartIndexSectionChecked:");
4304 return xRet;
4307 void DomainMapper_Impl::handleToc
4308 (const FieldContextPtr& pContext,
4309 const OUString & sTOCServiceName)
4311 OUString sValue;
4312 if (IsInHeaderFooter())
4313 m_bStartTOCHeaderFooter = true;
4314 bool bTableOfFigures = false;
4315 bool bHyperlinks = false;
4316 bool bFromOutline = false;
4317 bool bFromEntries = false;
4318 bool bHideTabLeaderPageNumbers = false ;
4319 bool bIsTabEntry = false ;
4320 bool bNewLine = false ;
4321 bool bParagraphOutlineLevel = false;
4323 sal_Int16 nMaxLevel = 10;
4324 OUString sTemplate;
4325 OUString sChapterNoSeparator;
4326 OUString sFigureSequence;
4327 uno::Reference< beans::XPropertySet > xTOC;
4328 OUString aBookmarkName;
4330 // \a Builds a table of figures but does not include the captions's label and number
4331 if( lcl_FindInCommand( pContext->GetCommand(), 'a', sValue ))
4332 { //make it a table of figures
4333 bTableOfFigures = true;
4335 // \b Uses a bookmark to specify area of document from which to build table of contents
4336 if( lcl_FindInCommand( pContext->GetCommand(), 'b', sValue ))
4338 aBookmarkName = sValue.trim().replaceAll("\"","");
4340 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
4341 // \c Builds a table of figures of the given label
4343 //todo: sValue contains the label's name
4344 bTableOfFigures = true;
4345 sFigureSequence = sValue.trim();
4346 sFigureSequence = sFigureSequence.replaceAll("\"", "").replaceAll("'","");
4348 // \d Defines the separator between sequence and page numbers
4349 if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue ))
4351 //todo: insert the chapter number into each level and insert the separator additionally
4352 sChapterNoSeparator = sValue;
4354 // \f Builds a table of contents using TC entries instead of outline levels
4355 if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
4357 //todo: sValue can contain a TOC entry identifier - use unclear
4358 bFromEntries = true;
4360 // \h Hyperlinks the entries and page numbers within the table of contents
4361 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
4363 //todo: make all entries to hyperlinks
4364 bHyperlinks = true;
4366 // \l Defines the TC entries field level used to build a table of contents
4367 // if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
4368 // {
4369 //todo: entries can only be included completely
4370 // }
4371 // \n Builds a table of contents or a range of entries, such as 1-9 in a table of contents without page numbers
4372 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
4373 // {
4374 //todo: what does the description mean?
4375 // }
4376 // \o Builds a table of contents by using outline levels instead of TC entries
4377 if( lcl_FindInCommand( pContext->GetCommand(), 'o', sValue ))
4379 bFromOutline = true;
4380 if (sValue.isEmpty())
4381 nMaxLevel = WW_OUTLINE_MAX;
4382 else
4384 sal_Int32 nIndex = 0;
4385 sValue.getToken( 0, '-', nIndex );
4386 nMaxLevel = static_cast<sal_Int16>(nIndex != -1 ? sValue.copy(nIndex).toInt32() : 0);
4389 // \p Defines the separator between the table entry and its page number
4390 // \s Builds a table of contents by using a sequence type
4391 // \t Builds a table of contents by using style names other than the standard outline styles
4392 if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
4394 OUString sToken = sValue.getToken(1, '"');
4395 sTemplate = sToken.isEmpty() ? sValue : sToken;
4397 // \u Builds a table of contents by using the applied paragraph outline level
4398 if( lcl_FindInCommand( pContext->GetCommand(), 'u', sValue ))
4400 bFromOutline = true;
4401 bParagraphOutlineLevel = true;
4402 //todo: what doesn 'the applied paragraph outline level' refer to?
4404 // \w Preserve tab characters within table entries
4405 if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
4407 bIsTabEntry = true ;
4409 // \x Preserve newline characters within table entries
4410 if( lcl_FindInCommand( pContext->GetCommand(), 'x', sValue ))
4412 bNewLine = true ;
4414 // \z Hides page numbers within the table of contents when shown in Web Layout View
4415 if( lcl_FindInCommand( pContext->GetCommand(), 'z', sValue ))
4417 bHideTabLeaderPageNumbers = true ;
4420 //if there's no option then it should be created from outline
4421 if( !bFromOutline && !bFromEntries && sTemplate.isEmpty() )
4422 bFromOutline = true;
4424 const OUString aTocTitle = extractTocTitle();
4426 if (m_xTextFactory.is() && ! m_aTextAppendStack.empty())
4428 const auto& xTextAppend = GetTopTextAppend();
4429 if (aTocTitle.isEmpty() || bTableOfFigures)
4431 // reset marker of the TOC title
4432 m_xSdtEntryStart.clear();
4434 // Create section before setting m_bStartTOC: finishing paragraph
4435 // inside StartIndexSectionChecked could do the wrong thing otherwise
4436 xTOC = StartIndexSectionChecked(bTableOfFigures ? "com.sun.star.text.IllustrationsIndex"
4437 : sTOCServiceName);
4439 const auto xTextCursor = xTextAppend->getText()->createTextCursor();
4440 if (xTextCursor)
4441 xTextCursor->gotoEnd(false);
4442 xTOCMarkerCursor = xTextCursor;
4444 else
4446 // create TOC section
4447 css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd();
4448 xTOC = createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false);
4450 // init [xTOCMarkerCursor]
4451 uno::Reference< text::XText > xText = xTextAppend->getText();
4452 uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor();
4453 xTOCMarkerCursor = xCrsr;
4455 // create header of the TOC with the TOC title inside
4456 const OUString aObjectType("com.sun.star.text.IndexHeaderSection");
4457 createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, aObjectType, true);
4461 m_bStartTOC = true;
4463 if (xTOC.is())
4464 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::makeAny(aTocTitle));
4466 if (!aBookmarkName.isEmpty())
4467 xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::makeAny(aBookmarkName));
4468 if( !bTableOfFigures && xTOC.is() )
4470 xTOC->setPropertyValue( getPropertyName( PROP_LEVEL ), uno::makeAny( nMaxLevel ) );
4471 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_OUTLINE ), uno::makeAny( bFromOutline ));
4472 xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_MARKS ), uno::makeAny( bFromEntries ));
4473 xTOC->setPropertyValue( getPropertyName( PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS ), uno::makeAny( bHideTabLeaderPageNumbers ));
4474 xTOC->setPropertyValue( getPropertyName( PROP_TAB_IN_TOC ), uno::makeAny( bIsTabEntry ));
4475 xTOC->setPropertyValue( getPropertyName( PROP_TOC_NEW_LINE ), uno::makeAny( bNewLine ));
4476 xTOC->setPropertyValue( getPropertyName( PROP_TOC_PARAGRAPH_OUTLINE_LEVEL ), uno::makeAny( bParagraphOutlineLevel ));
4477 if( !sTemplate.isEmpty() )
4479 //the string contains comma separated the names and related levels
4480 //like: "Heading 1,1,Heading 2,2"
4481 TOCStyleMap aMap;
4482 sal_Int32 nLevel;
4483 sal_Int32 nPosition = 0;
4484 while( nPosition >= 0)
4486 OUString sStyleName = sTemplate.getToken( 0, ',', nPosition );
4487 //empty tokens should be skipped
4488 while( sStyleName.isEmpty() && nPosition > 0 )
4489 sStyleName = sTemplate.getToken( 0, ',', nPosition );
4490 nLevel = sTemplate.getToken( 0, ',', nPosition ).toInt32();
4491 if( !nLevel )
4492 nLevel = 1;
4493 if( !sStyleName.isEmpty() )
4494 aMap.emplace(nLevel, sStyleName);
4496 uno::Reference< container::XIndexReplace> xParaStyles;
4497 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_PARAGRAPH_STYLES)) >>= xParaStyles;
4498 for( nLevel = 1; nLevel < 10; ++nLevel)
4500 sal_Int32 nLevelCount = aMap.count( nLevel );
4501 if( nLevelCount )
4503 TOCStyleMap::iterator aTOCStyleIter = aMap.find( nLevel );
4505 uno::Sequence< OUString> aStyles( nLevelCount );
4506 for ( sal_Int32 nStyle = 0; nStyle < nLevelCount; ++nStyle, ++aTOCStyleIter )
4508 aStyles[nStyle] = aTOCStyleIter->second;
4510 xParaStyles->replaceByIndex(nLevel - 1, uno::makeAny(aStyles));
4513 xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::makeAny( true ));
4516 if(bHyperlinks || !sChapterNoSeparator.isEmpty())
4518 uno::Reference< container::XIndexReplace> xLevelFormats;
4519 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
4520 sal_Int32 nLevelCount = xLevelFormats->getCount();
4521 //start with level 1, 0 is the header level
4522 for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel)
4524 uno::Sequence< beans::PropertyValues > aLevel;
4525 xLevelFormats->getByIndex( nLevel ) >>= aLevel;
4527 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
4528 bHyperlinks, sChapterNoSeparator,
4529 aLevel );
4530 xLevelFormats->replaceByIndex( nLevel, uno::makeAny( aNewLevel ) );
4534 else if (bTableOfFigures && xTOC.is())
4536 if (!sFigureSequence.isEmpty())
4537 xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY),
4538 uno::makeAny(sFigureSequence));
4540 if ( bHyperlinks )
4542 uno::Reference< container::XIndexReplace> xLevelFormats;
4543 xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats;
4544 uno::Sequence< beans::PropertyValues > aLevel;
4545 xLevelFormats->getByIndex( 1 ) >>= aLevel;
4547 uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
4548 bHyperlinks, sChapterNoSeparator,
4549 aLevel );
4550 xLevelFormats->replaceByIndex( 1, uno::makeAny( aNewLevel ) );
4553 pContext->SetTOC( xTOC );
4554 m_bParaHadField = false;
4557 uno::Reference<beans::XPropertySet> DomainMapper_Impl::createSectionForRange(
4558 uno::Reference< css::text::XTextRange > xStart,
4559 uno::Reference< css::text::XTextRange > xEnd,
4560 const OUString & sObjectType,
4561 bool stepLeft)
4563 if (!xStart.is())
4564 return uno::Reference<beans::XPropertySet>();
4565 if (!xEnd.is())
4566 return uno::Reference<beans::XPropertySet>();
4568 uno::Reference< beans::XPropertySet > xRet;
4569 if (m_aTextAppendStack.empty())
4570 return xRet;
4571 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
4572 if(xTextAppend.is())
4576 uno::Reference< text::XParagraphCursor > xCursor(
4577 xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY_THROW);
4578 //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls
4579 xCursor->gotoStartOfParagraph( false );
4580 xCursor->gotoRange( xEnd, true );
4581 //the paragraph after this new section is already inserted
4582 if (stepLeft)
4583 xCursor->goLeft(1, true);
4584 uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance(sObjectType), uno::UNO_QUERY_THROW );
4585 xSection->attach( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW) );
4586 xRet.set(xSection, uno::UNO_QUERY );
4588 catch(const uno::Exception&)
4593 return xRet;
4596 void DomainMapper_Impl::handleBibliography
4597 (const FieldContextPtr& pContext,
4598 const OUString & sTOCServiceName)
4600 if (m_aTextAppendStack.empty())
4602 // tdf#130214: a workaround to avoid crash on import errors
4603 SAL_WARN("writerfilter.dmapper", "no text append stack");
4604 return;
4606 // Create section before setting m_bStartTOC and m_bStartBibliography: finishing paragraph
4607 // inside StartIndexSectionChecked could do the wrong thing otherwise
4608 const auto xTOC = StartIndexSectionChecked(sTOCServiceName);
4609 m_bStartTOC = true;
4610 m_bStartBibliography = true;
4612 if (xTOC.is())
4613 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::makeAny(OUString()));
4615 pContext->SetTOC( xTOC );
4616 m_bParaHadField = false;
4618 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
4619 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
4622 void DomainMapper_Impl::handleIndex
4623 (const FieldContextPtr& pContext,
4624 const OUString & sTOCServiceName)
4626 // Create section before setting m_bStartTOC and m_bStartIndex: finishing paragraph
4627 // inside StartIndexSectionChecked could do the wrong thing otherwise
4628 const auto xTOC = StartIndexSectionChecked(sTOCServiceName);
4630 m_bStartTOC = true;
4631 m_bStartIndex = true;
4632 OUString sValue;
4633 OUString sIndexEntryType = "I"; // Default value for field flag '\f' is 'I'.
4635 if (xTOC.is())
4637 xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::makeAny(OUString()));
4639 if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
4641 xTOC->setPropertyValue("IsCommaSeparated", uno::makeAny(true));
4643 if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
4645 xTOC->setPropertyValue("UseAlphabeticalSeparators", uno::makeAny(true));
4647 if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
4649 if(!sValue.isEmpty())
4650 sIndexEntryType = sValue ;
4651 xTOC->setPropertyValue(getPropertyName( PROP_INDEX_ENTRY_TYPE ), uno::makeAny(sIndexEntryType));
4654 pContext->SetTOC( xTOC );
4655 m_bParaHadField = false;
4657 uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY );
4658 appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() );
4660 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
4662 sValue = sValue.replaceAll("\"", "");
4663 uno::Reference<text::XTextColumns> xTextColumns;
4664 xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns;
4665 if (xTextColumns.is())
4667 xTextColumns->setColumnCount( sValue.toInt32() );
4668 xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::makeAny( xTextColumns ) );
4673 static auto InsertFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
4674 uno::Reference<text::XFormField> const& xFormField,
4675 uno::Reference<text::XTextRange> const& xStartRange,
4676 std::optional<FieldId> const oFieldId) -> void
4678 uno::Reference<text::XTextContent> const xTextContent(xFormField, uno::UNO_QUERY_THROW);
4679 uno::Reference<text::XTextAppend> const& xTextAppend(rTextAppendStack.top().xTextAppend);
4680 uno::Reference<text::XTextCursor> const xCursor =
4681 xTextAppend->createTextCursorByRange(xStartRange);
4682 if (rTextAppendStack.top().xInsertPosition.is())
4684 uno::Reference<text::XTextRangeCompare> const xCompare(
4685 rTextAppendStack.top().xTextAppend,
4686 uno::UNO_QUERY);
4687 if (xCompare->compareRegionStarts(xStartRange, rTextAppendStack.top().xInsertPosition) < 0)
4689 SAL_WARN("writerfilter.dmapper", "invalid field mark positions");
4690 assert(false);
4692 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, true);
4694 else
4696 xCursor->gotoEnd(true);
4698 xTextAppend->insertTextContent(xCursor, xTextContent, true);
4699 if (oFieldId
4700 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
4702 return; // only a single CH_TXT_ATR_FORMELEMENT!
4704 // problem: the fieldmark must be inserted in CloseFieldCommand(), because
4705 // attach() takes 2 positions, not 3!
4706 // FAIL: AppendTextNode() ignores the content index!
4707 // plan B: insert a spurious paragraph break now and join
4708 // it in PopFieldContext()!
4709 xCursor->gotoRange(xTextContent->getAnchor()->getEnd(), false);
4710 xCursor->goLeft(1, false); // skip CH_TXT_ATR_FIELDEND
4711 xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::PARAGRAPH_BREAK, false);
4712 xCursor->goLeft(1, false); // back to previous paragraph
4713 rTextAppendStack.push(TextAppendContext(xTextAppend, xCursor));
4716 static auto PopFieldmark(std::stack<TextAppendContext> & rTextAppendStack,
4717 uno::Reference<text::XTextCursor> const& xCursor,
4718 std::optional<FieldId> const oFieldId) -> void
4720 if (oFieldId
4721 && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN))
4723 return; // only a single CH_TXT_ATR_FORMELEMENT!
4725 xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, false);
4726 xCursor->goRight(1, true);
4727 xCursor->setString(OUString()); // undo SplitNode from CloseFieldCommand()
4728 // note: paragraph properties will be overwritten
4729 // by finishParagraph() anyway so ignore here
4730 rTextAppendStack.pop();
4733 void DomainMapper_Impl::CloseFieldCommand()
4735 if(m_bDiscardHeaderFooter)
4736 return;
4737 #ifdef DBG_UTIL
4738 TagLogger::getInstance().element("closeFieldCommand");
4739 #endif
4741 FieldContextPtr pContext;
4742 if(!m_aFieldStack.empty())
4743 pContext = m_aFieldStack.back();
4744 OSL_ENSURE( pContext.get(), "no field context available");
4745 if( pContext.get() )
4747 m_bSetUserFieldContent = false;
4748 m_bSetCitation = false;
4749 m_bSetDateValue = false;
4750 const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion();
4754 uno::Reference< uno::XInterface > xFieldInterface;
4756 const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) };
4757 (void)vSwitches;
4758 OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front());
4760 // apply font size to the form control
4761 if ( m_pLastCharacterContext.get() && m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT) )
4763 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
4764 if (xTextAppend.is())
4766 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
4767 uno::Reference< text::XText > xText = xTextAppend->getText();
4768 if(xCrsr.is() && xText.is())
4770 xCrsr->gotoEnd(false);
4771 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
4772 xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT)->second);
4773 if ( m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT_COMPLEX) )
4774 xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT_COMPLEX), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT_COMPLEX)->second);
4779 FieldConversionMap_t::const_iterator const aIt = aFieldConversionMap.find(sType);
4780 if (aIt != aFieldConversionMap.end()
4781 && (!m_bForceGenericFields
4782 // these need to convert ffData to properties...
4783 || (aIt->second.eFieldId == FIELD_FORMCHECKBOX)
4784 || (aIt->second.eFieldId == FIELD_FORMDROPDOWN)
4785 || (aIt->second.eFieldId == FIELD_FORMTEXT)))
4787 pContext->SetFieldId(aIt->second.eFieldId);
4788 bool bCreateEnhancedField = false;
4789 uno::Reference< beans::XPropertySet > xFieldProperties;
4790 bool bCreateField = true;
4791 switch (aIt->second.eFieldId)
4793 case FIELD_HYPERLINK:
4794 case FIELD_DOCPROPERTY:
4795 case FIELD_TOC:
4796 case FIELD_INDEX:
4797 case FIELD_XE:
4798 case FIELD_BIBLIOGRAPHY:
4799 case FIELD_CITATION:
4800 case FIELD_TC:
4801 case FIELD_EQ:
4802 case FIELD_INCLUDEPICTURE:
4803 case FIELD_SYMBOL:
4804 case FIELD_GOTOBUTTON:
4805 bCreateField = false;
4806 break;
4807 case FIELD_FORMCHECKBOX :
4808 case FIELD_FORMTEXT :
4809 case FIELD_FORMDROPDOWN :
4811 // If we use 'enhanced' fields then FIELD_FORMCHECKBOX,
4812 // FIELD_FORMTEXT & FIELD_FORMDROPDOWN are treated specially
4813 if ( m_bUsingEnhancedFields )
4815 bCreateField = false;
4816 bCreateEnhancedField = true;
4818 // for non enhanced fields checkboxes are displayed
4819 // as an awt control not a field
4820 else if ( aIt->second.eFieldId == FIELD_FORMCHECKBOX )
4821 bCreateField = false;
4822 break;
4824 default:
4826 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
4827 if (pOuter)
4829 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
4831 // Parent field can't host this child field: don't create a child field
4832 // in this case.
4833 bCreateField = false;
4836 break;
4839 if (m_bStartTOC && (aIt->second.eFieldId == FIELD_PAGEREF) )
4841 bCreateField = false;
4844 if( bCreateField || bCreateEnhancedField )
4846 //add the service prefix
4847 OUString sServiceName("com.sun.star.text.");
4848 if ( bCreateEnhancedField )
4850 const FieldConversionMap_t& aEnhancedFieldConversionMap = lcl_GetEnhancedFieldConversion();
4851 FieldConversionMap_t::const_iterator aEnhancedIt =
4852 aEnhancedFieldConversionMap.find(sType);
4853 if ( aEnhancedIt != aEnhancedFieldConversionMap.end())
4854 sServiceName += OUString::createFromAscii(aEnhancedIt->second.cFieldServiceName );
4856 else
4858 sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName );
4861 #ifdef DBG_UTIL
4862 TagLogger::getInstance().startElement("fieldService");
4863 TagLogger::getInstance().chars(sServiceName);
4864 TagLogger::getInstance().endElement();
4865 #endif
4867 if (m_xTextFactory.is())
4869 xFieldInterface = m_xTextFactory->createInstance(sServiceName);
4870 xFieldProperties.set( xFieldInterface, uno::UNO_QUERY_THROW);
4873 switch( aIt->second.eFieldId )
4875 case FIELD_ADDRESSBLOCK: break;
4876 case FIELD_ADVANCE : break;
4877 case FIELD_ASK :
4878 handleFieldAsk(pContext, xFieldInterface, xFieldProperties);
4879 break;
4880 case FIELD_AUTONUM :
4881 case FIELD_AUTONUMLGL :
4882 case FIELD_AUTONUMOUT :
4883 handleAutoNum(pContext, xFieldInterface, xFieldProperties);
4884 break;
4885 case FIELD_AUTHOR :
4886 case FIELD_USERNAME :
4887 case FIELD_USERINITIALS :
4888 handleAuthor(sFirstParam,
4889 xFieldProperties,
4890 aIt->second.eFieldId);
4891 break;
4892 case FIELD_DATE:
4893 if (xFieldProperties.is())
4895 // Get field fixed property from the context handler
4896 if (pContext->IsFieldLocked())
4898 xFieldProperties->setPropertyValue(
4899 getPropertyName(PROP_IS_FIXED),
4900 uno::makeAny( true ));
4901 m_bSetDateValue = true;
4903 else
4904 xFieldProperties->setPropertyValue(
4905 getPropertyName(PROP_IS_FIXED),
4906 uno::makeAny( false ));
4908 xFieldProperties->setPropertyValue(
4909 getPropertyName(PROP_IS_DATE),
4910 uno::makeAny( true ));
4911 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
4913 break;
4914 case FIELD_COMMENTS :
4916 // OUString sParam = lcl_ExtractParameter(pContext->GetCommand(), sizeof(" COMMENTS") );
4917 // A parameter with COMMENTS shouldn't set fixed
4918 // ( or at least the binary filter doesn't )
4919 // If we set fixed then we won't export a field cmd.
4920 // Additionally the para in COMMENTS is more like an
4921 // instruction to set the document property comments
4922 // with the param ( e.g. each COMMENT with a param will
4923 // overwrite the Comments document property
4924 // #TODO implement the above too
4925 xFieldProperties->setPropertyValue(
4926 getPropertyName( PROP_IS_FIXED ), uno::makeAny( false ));
4927 //PROP_CURRENT_PRESENTATION is set later anyway
4929 break;
4930 case FIELD_CREATEDATE :
4932 xFieldProperties->setPropertyValue(
4933 getPropertyName( PROP_IS_DATE ), uno::makeAny( true ));
4934 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
4936 break;
4937 case FIELD_DOCPROPERTY :
4938 handleDocProperty(pContext, sFirstParam,
4939 xFieldInterface);
4940 break;
4941 case FIELD_DOCVARIABLE :
4943 //create a user field and type
4944 uno::Reference< beans::XPropertySet > xMaster =
4945 FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.User", sFirstParam);
4946 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
4947 xDependentField->attachTextFieldMaster( xMaster );
4948 m_bSetUserFieldContent = true;
4950 break;
4951 case FIELD_EDITTIME :
4952 //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldProperties );
4953 break;
4954 case FIELD_EQ:
4956 OUString aCommand = pContext->GetCommand().trim();
4958 msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand));
4959 if (!aResult.sType.isEmpty() && m_xTextFactory.is())
4961 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField." + aResult.sType);
4962 xFieldProperties =
4963 uno::Reference< beans::XPropertySet >( xFieldInterface,
4964 uno::UNO_QUERY_THROW);
4965 xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::makeAny(aResult.sResult));
4967 else
4969 //merge Read_SubF_Ruby into filter/.../util.cxx and reuse that ?
4970 sal_Int32 nSpaceIndex = aCommand.indexOf(' ');
4971 if(nSpaceIndex > 0)
4972 aCommand = aCommand.copy(nSpaceIndex).trim();
4973 if (aCommand.startsWith("\\s"))
4975 aCommand = aCommand.copy(2);
4976 if (aCommand.startsWith("\\do"))
4978 aCommand = aCommand.copy(3);
4979 sal_Int32 nStartIndex = aCommand.indexOf('(');
4980 sal_Int32 nEndIndex = aCommand.indexOf(')');
4981 if (nStartIndex > 0 && nEndIndex > 0)
4983 // nDown is the requested "lower by" value in points.
4984 sal_Int32 nDown = aCommand.copy(0, nStartIndex).toInt32();
4985 OUString aContent = aCommand.copy(nStartIndex + 1, nEndIndex - nStartIndex - 1);
4986 PropertyMapPtr pCharContext = GetTopContext();
4987 // dHeight is the font size of the current style.
4988 double dHeight = 0;
4989 if ((GetPropertyFromParaStyleSheet(PROP_CHAR_HEIGHT) >>= dHeight) && dHeight != 0)
4990 // Character escapement should be given in negative percents for subscripts.
4991 pCharContext->Insert(PROP_CHAR_ESCAPEMENT, uno::makeAny( sal_Int16(- 100 * nDown / dHeight) ) );
4992 appendTextPortion(aContent, pCharContext);
4996 else if (aCommand.startsWith("\\* jc"))
4998 handleRubyEQField(pContext);
5002 break;
5003 case FIELD_FILLIN :
5004 if (xFieldProperties.is())
5005 xFieldProperties->setPropertyValue(
5006 getPropertyName(PROP_HINT), uno::makeAny( pContext->GetCommand().getToken(1, '\"')));
5007 break;
5008 case FIELD_FILENAME:
5010 sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p");
5011 if (xFieldProperties.is())
5012 xFieldProperties->setPropertyValue(
5013 getPropertyName(PROP_FILE_FORMAT),
5014 uno::makeAny( nNumberingTypeIndex > 0 ? text::FilenameDisplayFormat::FULL : text::FilenameDisplayFormat::NAME_AND_EXT ));
5016 break;
5017 case FIELD_FILESIZE : break;
5018 case FIELD_FORMULA :
5019 handleFieldFormula(pContext, xFieldProperties);
5020 break;
5021 case FIELD_FORMCHECKBOX :
5022 case FIELD_FORMDROPDOWN :
5023 case FIELD_FORMTEXT :
5025 uno::Reference< text::XTextField > xTextField( xFieldInterface, uno::UNO_QUERY );
5026 if ( !xTextField.is() )
5028 FFDataHandler::Pointer_t
5029 pFFDataHandler(pContext->getFFDataHandler());
5030 FormControlHelper::Pointer_t
5031 pFormControlHelper(new FormControlHelper
5032 (m_bUsingEnhancedFields ? aIt->second.eFieldId : FIELD_FORMCHECKBOX,
5034 m_xTextDocument, pFFDataHandler));
5035 pContext->setFormControlHelper(pFormControlHelper);
5036 uno::Reference< text::XFormField > xFormField( xFieldInterface, uno::UNO_QUERY );
5037 uno::Reference< container::XNamed > xNamed( xFormField, uno::UNO_QUERY );
5038 if ( xNamed.is() )
5040 if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() )
5041 xNamed->setName( pFFDataHandler->getName() );
5042 pContext->SetFormField( xFormField );
5044 InsertFieldmark(m_aTextAppendStack,
5045 xFormField, pContext->GetStartRange(),
5046 pContext->GetFieldId());
5048 else
5050 if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN )
5051 lcl_handleDropdownField( xFieldProperties, pContext->getFFDataHandler() );
5052 else
5053 lcl_handleTextField( xFieldProperties, pContext->getFFDataHandler() );
5056 break;
5057 case FIELD_GOTOBUTTON : break;
5058 case FIELD_HYPERLINK:
5060 ::std::vector<OUString> aParts = pContext->GetCommandParts();
5062 // Syntax is either:
5063 // HYPERLINK "" \l "link"
5064 // or
5065 // HYPERLINK \l "link"
5066 // Make sure "HYPERLINK" doesn't end up as part of link in the second case.
5067 if (!aParts.empty() && aParts[0] == "HYPERLINK")
5068 aParts.erase(aParts.begin());
5070 ::std::vector<OUString>::const_iterator aItEnd = aParts.end();
5071 ::std::vector<OUString>::const_iterator aPartIt = aParts.begin();
5073 OUString sURL;
5074 OUString sTarget;
5076 while (aPartIt != aItEnd)
5078 if ( *aPartIt == "\\l" )
5080 ++aPartIt;
5082 if (aPartIt == aItEnd)
5083 break;
5085 sURL += "#" + *aPartIt;
5087 else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h")
5090 else if ( *aPartIt == "\\o" || *aPartIt == "\\t" )
5092 ++aPartIt;
5094 if (aPartIt == aItEnd)
5095 break;
5097 sTarget = *aPartIt;
5099 else
5101 sURL = *aPartIt;
5104 ++aPartIt;
5107 if (!sURL.isEmpty())
5109 if (sURL.startsWith("file:///"))
5111 // file:///absolute\\path\\to\\file => invalid file URI (Writer cannot open)
5112 // convert all double backslashes to slashes:
5113 sURL = sURL.replaceAll("\\\\", "/");
5115 // file:///absolute\path\to\file => invalid file URI (Writer cannot open)
5116 // convert all backslashes to slashes:
5117 sURL = sURL.replace('\\', '/');
5119 // Try to make absolute any relative URLs, except
5120 // for relative same-document URLs that only contain
5121 // a fragment part:
5122 else if (!sURL.startsWith("#")) {
5123 try {
5124 sURL = rtl::Uri::convertRelToAbs(
5125 m_aBaseUrl, sURL);
5126 } catch (rtl::MalformedUriException & e) {
5127 SAL_WARN(
5128 "writerfilter.dmapper",
5129 "MalformedUriException "
5130 << e.getMessage());
5133 pContext->SetHyperlinkURL(sURL);
5136 if (!sTarget.isEmpty())
5137 pContext->SetHyperlinkTarget(sTarget);
5139 break;
5140 case FIELD_IF : break;
5141 case FIELD_INFO : break;
5142 case FIELD_INCLUDEPICTURE: break;
5143 case FIELD_KEYWORDS :
5145 if (!sFirstParam.isEmpty())
5147 xFieldProperties->setPropertyValue(
5148 getPropertyName( PROP_IS_FIXED ), uno::makeAny( true ));
5149 //PROP_CURRENT_PRESENTATION is set later anyway
5152 break;
5153 case FIELD_LASTSAVEDBY : break;
5154 case FIELD_MACROBUTTON:
5156 //extract macro name
5157 sal_Int32 nIndex = sizeof(" MACROBUTTON ");
5158 OUString sMacro = pContext->GetCommand().getToken( 0, ' ', nIndex);
5159 if (xFieldProperties.is())
5160 xFieldProperties->setPropertyValue(
5161 getPropertyName(PROP_MACRO_NAME), uno::makeAny( sMacro ));
5163 //extract quick help text
5164 if(xFieldProperties.is() && pContext->GetCommand().getLength() > nIndex + 1)
5166 xFieldProperties->setPropertyValue(
5167 getPropertyName(PROP_HINT),
5168 uno::makeAny( pContext->GetCommand().copy( nIndex )));
5171 break;
5172 case FIELD_MERGEFIELD :
5174 //todo: create a database field and fieldmaster pointing to a column, only
5175 //create a user field and type
5176 uno::Reference< beans::XPropertySet > xMaster =
5177 FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam);
5179 // xFieldProperties->setPropertyValue(
5180 // "FieldCode",
5181 // uno::makeAny( pContext->GetCommand().copy( nIndex + 1 )));
5182 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
5183 xDependentField->attachTextFieldMaster( xMaster );
5185 break;
5186 case FIELD_MERGEREC : break;
5187 case FIELD_MERGESEQ : break;
5188 case FIELD_NEXT : break;
5189 case FIELD_NEXTIF : break;
5190 case FIELD_PAGE :
5191 if (xFieldProperties.is())
5193 xFieldProperties->setPropertyValue(
5194 getPropertyName(PROP_NUMBERING_TYPE),
5195 uno::makeAny( lcl_ParseNumberingType(pContext->GetCommand()) ));
5196 xFieldProperties->setPropertyValue(
5197 getPropertyName(PROP_SUB_TYPE),
5198 uno::makeAny( text::PageNumberType_CURRENT ));
5201 break;
5202 case FIELD_PAGEREF:
5203 case FIELD_REF:
5204 if (xFieldProperties.is() && !m_bStartTOC)
5206 bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF;
5208 // Do we need a GetReference (default) or a GetExpression field?
5209 uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY );
5210 uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters();
5212 if (!xFieldMasterAccess->hasByName(
5213 "com.sun.star.text.FieldMaster.SetExpression."
5214 + sFirstParam))
5216 xFieldProperties->setPropertyValue(
5217 getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
5218 uno::makeAny( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) );
5219 xFieldProperties->setPropertyValue(
5220 getPropertyName(PROP_SOURCE_NAME),
5221 uno::makeAny(sFirstParam) );
5222 sal_Int16 nFieldPart = (bPageRef ? text::ReferenceFieldPart::PAGE : text::ReferenceFieldPart::TEXT);
5223 OUString sValue;
5224 if( lcl_FindInCommand( pContext->GetCommand(), 'p', sValue ))
5226 //above-below
5227 nFieldPart = text::ReferenceFieldPart::UP_DOWN;
5229 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
5231 //number
5232 nFieldPart = text::ReferenceFieldPart::NUMBER;
5234 else if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
5236 //number-no-context
5237 nFieldPart = text::ReferenceFieldPart::NUMBER_NO_CONTEXT;
5239 else if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue ))
5241 //number-full-context
5242 nFieldPart = text::ReferenceFieldPart::NUMBER_FULL_CONTEXT;
5244 xFieldProperties->setPropertyValue(
5245 getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::makeAny( nFieldPart ));
5247 else if( m_xTextFactory.is() )
5249 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField.GetExpression");
5250 xFieldProperties.set(xFieldInterface, uno::UNO_QUERY);
5251 xFieldProperties->setPropertyValue(
5252 getPropertyName(PROP_CONTENT),
5253 uno::makeAny(sFirstParam));
5254 xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::makeAny(text::SetVariableType::STRING));
5257 break;
5258 case FIELD_REVNUM : break;
5259 case FIELD_SAVEDATE :
5260 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
5261 break;
5262 case FIELD_SECTION : break;
5263 case FIELD_SECTIONPAGES : break;
5264 case FIELD_SEQ :
5266 // command looks like: " SEQ Table \* ARABIC "
5267 OUString sCmd(pContext->GetCommand());
5268 // find the sequence name, e.g. "SEQ"
5269 OUString sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\');
5270 sSeqName = sSeqName.trim();
5272 // create a sequence field master using the sequence name
5273 uno::Reference< beans::XPropertySet > xMaster = FindOrCreateFieldMaster(
5274 "com.sun.star.text.FieldMaster.SetExpression",
5275 sSeqName);
5277 xMaster->setPropertyValue(
5278 getPropertyName(PROP_SUB_TYPE),
5279 uno::makeAny(text::SetVariableType::SEQUENCE));
5281 // apply the numbering type
5282 xFieldProperties->setPropertyValue(
5283 getPropertyName(PROP_NUMBERING_TYPE),
5284 uno::makeAny( lcl_ParseNumberingType(pContext->GetCommand()) ));
5286 // attach the master to the field
5287 uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW );
5288 xDependentField->attachTextFieldMaster( xMaster );
5290 OUString sFormula = sSeqName + "+1";
5291 OUString sValue;
5292 if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
5294 sFormula = sSeqName;
5296 else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue ))
5298 sFormula = sValue;
5300 // TODO \s isn't handled, but the spec isn't easy to understand without
5301 // an example for this one.
5302 xFieldProperties->setPropertyValue(
5303 getPropertyName(PROP_CONTENT),
5304 uno::makeAny(sFormula));
5306 // Take care of the numeric formatting definition, default is Arabic
5307 sal_Int16 nNumberingType = lcl_ParseNumberingType(pContext->GetCommand());
5308 if (nNumberingType == style::NumberingType::PAGE_DESCRIPTOR)
5309 nNumberingType = style::NumberingType::ARABIC;
5310 xFieldProperties->setPropertyValue(
5311 getPropertyName(PROP_NUMBERING_TYPE),
5312 uno::makeAny(nNumberingType));
5314 break;
5315 case FIELD_SET :
5316 handleFieldSet(pContext, xFieldInterface, xFieldProperties);
5317 break;
5318 case FIELD_SKIPIF : break;
5319 case FIELD_STYLEREF : break;
5320 case FIELD_SUBJECT :
5322 if (!sFirstParam.isEmpty())
5324 xFieldProperties->setPropertyValue(
5325 getPropertyName( PROP_IS_FIXED ), uno::makeAny( true ));
5326 //PROP_CURRENT_PRESENTATION is set later anyway
5329 break;
5330 case FIELD_SYMBOL:
5332 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
5333 OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ? sFirstParam.copy(2).toUInt32(16) : sFirstParam.toUInt32() ) );
5334 OUString sFont;
5335 bool bHasFont = lcl_FindInCommand( pContext->GetCommand(), 'f', sFont);
5336 if ( bHasFont )
5338 sFont = sFont.trim();
5339 if (sFont.startsWith("\""))
5340 sFont = sFont.copy(1);
5341 if (sFont.endsWith("\""))
5342 sFont = sFont.copy(0,sFont.getLength()-1);
5347 if (xTextAppend.is())
5349 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
5350 uno::Reference< text::XText > xText = xTextAppend->getText();
5351 if(xCrsr.is() && xText.is())
5353 xCrsr->gotoEnd(false);
5354 xText->insertString(xCrsr, sSymbol, true);
5355 uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY );
5356 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::makeAny(awt::CharSet::SYMBOL));
5357 if(bHasFont)
5359 uno::Any aVal = uno::makeAny( sFont );
5360 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal);
5361 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal);
5362 xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal);
5368 break;
5369 case FIELD_TEMPLATE: break;
5370 case FIELD_TIME :
5372 if (pContext->IsFieldLocked())
5374 xFieldProperties->setPropertyValue(
5375 getPropertyName(PROP_IS_FIXED),
5376 uno::makeAny( true ));
5377 m_bSetDateValue = true;
5379 SetNumberFormat( pContext->GetCommand(), xFieldProperties );
5381 break;
5382 case FIELD_TITLE :
5384 if (!sFirstParam.isEmpty())
5386 xFieldProperties->setPropertyValue(
5387 getPropertyName( PROP_IS_FIXED ), uno::makeAny( true ));
5388 //PROP_CURRENT_PRESENTATION is set later anyway
5391 break;
5392 case FIELD_USERADDRESS : //todo: user address collects street, city ...
5393 break;
5394 case FIELD_INDEX:
5395 handleIndex(pContext,
5396 OUString::createFromAscii(aIt->second.cFieldServiceName));
5397 break;
5398 case FIELD_BIBLIOGRAPHY:
5399 handleBibliography(pContext,
5400 OUString::createFromAscii(aIt->second.cFieldServiceName));
5401 break;
5402 case FIELD_TOC:
5403 handleToc(pContext,
5404 OUString::createFromAscii(aIt->second.cFieldServiceName));
5405 break;
5406 case FIELD_XE:
5408 if( !m_xTextFactory.is() )
5409 break;
5411 uno::Reference< beans::XPropertySet > xTC(
5412 m_xTextFactory->createInstance(
5413 OUString::createFromAscii(aIt->second.cFieldServiceName)),
5414 uno::UNO_QUERY_THROW);
5415 if (!sFirstParam.isEmpty())
5417 xTC->setPropertyValue("PrimaryKey",
5418 uno::makeAny(sFirstParam));
5420 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
5421 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
5422 if (xTextAppend.is())
5424 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor();
5426 uno::Reference< text::XText > xText = xTextAppend->getText();
5427 if(xCrsr.is() && xText.is())
5429 xCrsr->gotoEnd(false);
5430 xText->insertTextContent(uno::Reference< text::XTextRange >( xCrsr, uno::UNO_QUERY_THROW ), xToInsert, false);
5434 break;
5435 case FIELD_CITATION:
5437 if( !m_xTextFactory.is() )
5438 break;
5440 xFieldInterface = m_xTextFactory->createInstance(
5441 OUString::createFromAscii(aIt->second.cFieldServiceName));
5442 uno::Reference< beans::XPropertySet > xTC(xFieldInterface,
5443 uno::UNO_QUERY_THROW);
5444 OUString sCmd(pContext->GetCommand());//sCmd is the entire instrText including the index e.g. CITATION Kra06 \l 1033
5445 if( !sCmd.isEmpty()){
5446 uno::Sequence<beans::PropertyValue> aValues( comphelper::InitPropertySequence({
5447 { "Identifier", uno::Any(sCmd) }
5448 }));
5449 xTC->setPropertyValue("Fields", uno::makeAny(aValues));
5451 uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY );
5453 uno::Sequence<beans::PropertyValue> aValues
5454 = m_aFieldStack.back()->getProperties()->GetPropertyValues();
5455 appendTextContent(xToInsert, aValues);
5456 m_bSetCitation = true;
5458 break;
5460 case FIELD_TC :
5462 if( !m_xTextFactory.is() )
5463 break;
5465 uno::Reference< beans::XPropertySet > xTC(
5466 m_xTextFactory->createInstance(
5467 OUString::createFromAscii(aIt->second.cFieldServiceName)),
5468 uno::UNO_QUERY_THROW);
5469 if (!sFirstParam.isEmpty())
5471 xTC->setPropertyValue(getPropertyName(PROP_ALTERNATIVE_TEXT),
5472 uno::makeAny(sFirstParam));
5474 OUString sValue;
5475 // \f TC entry in doc with multiple tables
5476 // if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue ))
5477 // {
5478 // todo: unsupported
5479 // }
5480 if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
5481 // \l Outline Level
5483 sal_Int32 nLevel = sValue.toInt32();
5484 if( !sValue.isEmpty() && nLevel >= 0 && nLevel <= 10 )
5485 xTC->setPropertyValue(getPropertyName(PROP_LEVEL), uno::makeAny( static_cast<sal_Int16>(nLevel) ));
5487 // if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue ))
5488 // \n Suppress page numbers
5489 // {
5490 //todo: unsupported feature
5491 // }
5492 pContext->SetTC( xTC );
5494 break;
5495 case FIELD_NUMCHARS:
5496 case FIELD_NUMWORDS:
5497 case FIELD_NUMPAGES:
5498 if (xFieldProperties.is())
5499 xFieldProperties->setPropertyValue(
5500 getPropertyName(PROP_NUMBERING_TYPE),
5501 uno::makeAny( lcl_ParseNumberingType(pContext->GetCommand()) ));
5502 break;
5505 else
5507 /* Unsupported fields will be handled here for docx file.
5508 * To handle unsupported fields used fieldmark API.
5510 OUString aCode( pContext->GetCommand().trim() );
5511 // Don't waste resources on wrapping shapes inside a fieldmark.
5512 if (sType != "SHAPE" && m_xTextFactory.is() && !m_aTextAppendStack.empty())
5514 xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.Fieldmark");
5516 uno::Reference<text::XFormField> const xFormField(xFieldInterface, uno::UNO_QUERY);
5517 InsertFieldmark(m_aTextAppendStack, xFormField, pContext->GetStartRange(),
5518 pContext->GetFieldId());
5519 xFormField->setFieldType(ODF_UNHANDLED);
5520 ++m_nStartGenericField;
5521 pContext->SetFormField( xFormField );
5522 uno::Reference<container::XNameContainer> const xNameCont(xFormField->getParameters());
5523 // note: setting the code to empty string is *required* in
5524 // m_bForceGenericFields mode, or the export will write
5525 // the ODF_UNHANDLED string!
5526 assert(!m_bForceGenericFields || aCode.isEmpty());
5527 xNameCont->insertByName(ODF_CODE_PARAM, uno::makeAny(aCode));
5528 if (sType == "CONTROL")
5529 { // tdf#129247 HACK probably this should be imported as something else, like in ww8?
5530 xNameCont->insertByName(ODF_ID_PARAM, uno::makeAny(OUString::number(87))); // ww8::eCONTROL
5533 else
5534 m_bParaHadField = false;
5536 //set the text field if there is any
5537 pContext->SetTextField( uno::Reference< text::XTextField >( xFieldInterface, uno::UNO_QUERY ) );
5539 catch( const uno::Exception& )
5541 TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Exception in CloseFieldCommand()" );
5543 pContext->SetCommandCompleted();
5546 /*-------------------------------------------------------------------------
5547 //the _current_ fields require a string type result while TOCs accept richt results
5548 -----------------------------------------------------------------------*/
5549 bool DomainMapper_Impl::IsFieldResultAsString()
5551 bool bRet = false;
5552 OSL_ENSURE( !m_aFieldStack.empty(), "field stack empty?");
5553 FieldContextPtr pContext = m_aFieldStack.back();
5554 OSL_ENSURE( pContext.get(), "no field context available");
5555 if( pContext.get() )
5557 bRet = pContext->GetTextField().is()
5558 || pContext->GetFieldId() == FIELD_FORMDROPDOWN
5559 || pContext->GetFieldId() == FIELD_FILLIN;
5562 if (!bRet)
5564 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
5565 if (pOuter)
5567 if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
5569 // Child field has no backing SwField, but the parent has: append is still possible.
5570 bRet = pOuter->GetTextField().is();
5574 return bRet;
5577 void DomainMapper_Impl::AppendFieldResult(OUString const& rString)
5579 assert(!m_aFieldStack.empty());
5580 FieldContextPtr pContext = m_aFieldStack.back();
5581 SAL_WARN_IF(!pContext.get(), "writerfilter.dmapper", "no field context");
5582 if (pContext.get())
5584 FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
5585 if (pOuter)
5587 if (!IsFieldNestingAllowed(pOuter, pContext))
5589 // Child can't host the field result, forward to parent.
5590 pOuter->AppendResult(rString);
5591 return;
5595 pContext->AppendResult(rString);
5599 // Calculates css::DateTime based on ddddd.sssss since 1900-1-0
5600 static util::DateTime lcl_dateTimeFromSerial(const double& dSerial)
5602 const sal_uInt32 secondsPerDay = 86400;
5603 const sal_uInt16 secondsPerHour = 3600;
5605 DateTime d(Date(30, 12, 1899));
5606 d.AddDays( static_cast<sal_Int32>(dSerial) );
5608 double frac = std::modf(dSerial, &o3tl::temporary(double()));
5609 sal_uInt32 seconds = frac * secondsPerDay;
5611 util::DateTime date;
5612 date.Year = d.GetYear();
5613 date.Month = d.GetMonth();
5614 date.Day = d.GetDay();
5615 date.Hours = seconds / secondsPerHour;
5616 date.Minutes = (seconds % secondsPerHour) / 60;
5617 date.Seconds = seconds % 60;
5619 return date;
5622 void DomainMapper_Impl::SetFieldResult(OUString const& rResult)
5624 #ifdef DBG_UTIL
5625 TagLogger::getInstance().startElement("setFieldResult");
5626 TagLogger::getInstance().chars(rResult);
5627 #endif
5629 FieldContextPtr pContext = m_aFieldStack.back();
5630 OSL_ENSURE( pContext.get(), "no field context available");
5632 if (m_aFieldStack.size() > 1)
5634 // This is a nested field. See if the parent supports nesting on the Writer side.
5635 FieldContextPtr pParentContext = m_aFieldStack[m_aFieldStack.size() - 2];
5636 if (pParentContext)
5638 std::vector<OUString> aParentParts = pParentContext->GetCommandParts();
5639 // Conditional text fields don't support nesting in Writer.
5640 if (!aParentParts.empty() && aParentParts[0] == "IF")
5642 return;
5647 if( pContext.get() )
5649 uno::Reference<text::XTextField> xTextField = pContext->GetTextField();
5652 OSL_ENSURE( xTextField.is()
5653 //||m_xTOC.is() ||m_xTC.is()
5654 //||m_sHyperlinkURL.getLength()
5655 , "DomainMapper_Impl::SetFieldResult: field not created" );
5656 if(xTextField.is())
5660 if( m_bSetUserFieldContent )
5662 // user field content has to be set at the field master
5663 uno::Reference< text::XDependentTextField > xDependentField( xTextField, uno::UNO_QUERY_THROW );
5664 xDependentField->getTextFieldMaster()->setPropertyValue(
5665 getPropertyName(PROP_CONTENT),
5666 uno::makeAny( rResult ));
5668 else if ( m_bSetCitation )
5671 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
5672 // In case of SetExpression, the field result contains the content of the variable.
5673 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
5675 bool bIsSetbiblio = xServiceInfo->supportsService("com.sun.star.text.TextField.Bibliography");
5676 if( bIsSetbiblio )
5678 uno::Any aProperty = xFieldProperties->getPropertyValue("Fields");
5679 uno::Sequence<beans::PropertyValue> aValues ;
5680 aProperty >>= aValues;
5681 beans::PropertyValue propertyVal;
5682 sal_Int32 nTitleFoundIndex = -1;
5683 for (sal_Int32 i = 0; i < aValues.getLength(); ++i)
5685 propertyVal = aValues[i];
5686 if (propertyVal.Name == "Title")
5688 nTitleFoundIndex = i;
5689 break;
5692 if (nTitleFoundIndex != -1)
5694 OUString titleStr;
5695 uno::Any aValue(propertyVal.Value);
5696 aValue >>= titleStr;
5697 titleStr += rResult;
5698 propertyVal.Value <<= titleStr;
5699 aValues[nTitleFoundIndex] = propertyVal;
5701 else
5703 aValues.realloc(aValues.getLength() + 1);
5704 propertyVal.Name = "Title";
5705 propertyVal.Value <<= rResult;
5706 aValues[aValues.getLength() - 1] = propertyVal;
5708 xFieldProperties->setPropertyValue("Fields",
5709 uno::makeAny(aValues));
5712 else if ( m_bSetDateValue )
5714 uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW );
5716 uno::Reference<util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW);
5717 xFormatter->attachNumberFormatsSupplier( xNumberSupplier );
5718 sal_Int32 nKey = 0;
5720 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
5722 xFieldProperties->getPropertyValue( "NumberFormat" ) >>= nKey;
5723 xFieldProperties->setPropertyValue(
5724 "DateTimeValue",
5725 uno::makeAny( lcl_dateTimeFromSerial( xFormatter->convertStringToNumber( nKey, rResult ) ) ) );
5727 else
5729 uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW);
5730 // In case of SetExpression, and Input fields the field result contains the content of the variable.
5731 uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY);
5732 // there are fields with a content property, which aren't working correctly with
5733 // a generalized try catch of the content, property, so just restrict content
5734 // handling to these explicit services.
5735 const bool bHasContent = xServiceInfo->supportsService("com.sun.star.text.TextField.SetExpression") ||
5736 xServiceInfo->supportsService("com.sun.star.text.TextField.Input");
5737 // If we already have content set, then use the current presentation
5738 OUString sValue;
5739 if (bHasContent)
5741 // this will throw for field types without Content
5742 uno::Any aValue(xFieldProperties->getPropertyValue(
5743 getPropertyName(PROP_CONTENT)));
5744 aValue >>= sValue;
5746 xFieldProperties->setPropertyValue(
5747 getPropertyName(bHasContent && sValue.isEmpty()? PROP_CONTENT : PROP_CURRENT_PRESENTATION),
5748 uno::makeAny( rResult ));
5751 catch( const beans::UnknownPropertyException& )
5753 //some fields don't have a CurrentPresentation (DateTime)
5757 catch (const uno::Exception&)
5759 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "DomainMapper_Impl::SetFieldResult");
5764 void DomainMapper_Impl::SetFieldFFData(const FFDataHandler::Pointer_t& pFFDataHandler)
5766 #ifdef DBG_UTIL
5767 TagLogger::getInstance().startElement("setFieldFFData");
5768 #endif
5770 if (!m_aFieldStack.empty())
5772 FieldContextPtr pContext = m_aFieldStack.back();
5773 if (pContext.get())
5775 pContext->setFFDataHandler(pFFDataHandler);
5779 #ifdef DBG_UTIL
5780 TagLogger::getInstance().endElement();
5781 #endif
5784 void DomainMapper_Impl::PopFieldContext()
5786 if(m_bDiscardHeaderFooter)
5787 return;
5788 #ifdef DBG_UTIL
5789 TagLogger::getInstance().element("popFieldContext");
5790 #endif
5792 if (m_aFieldStack.empty())
5793 return;
5795 FieldContextPtr pContext = m_aFieldStack.back();
5796 OSL_ENSURE( pContext.get(), "no field context available");
5797 if( pContext.get() )
5799 if( !pContext->IsCommandCompleted() )
5800 CloseFieldCommand();
5802 if (!pContext->GetResult().isEmpty())
5804 uno::Reference< beans::XPropertySet > xFieldProperties = pContext->GetCustomField();
5805 if(xFieldProperties.is())
5806 SetNumberFormat( pContext->GetResult(), xFieldProperties, true );
5807 SetFieldResult( pContext->GetResult() );
5810 //insert the field, TC or TOC
5811 uno::Reference< text::XTextAppend > xTextAppend;
5812 if (!m_aTextAppendStack.empty())
5813 xTextAppend = m_aTextAppendStack.top().xTextAppend;
5814 if(xTextAppend.is())
5818 uno::Reference< text::XTextCursor > xCrsr = xTextAppend->createTextCursorByRange(pContext->GetStartRange());
5819 uno::Reference< text::XTextContent > xToInsert( pContext->GetTOC(), uno::UNO_QUERY );
5820 if( xToInsert.is() )
5822 if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography)
5824 // inside SDT, last empty paragraph is also part of index
5825 if (!m_bParaChanged && !m_xSdtEntryStart)
5827 // End of index is the first item on a new paragraph - this paragraph
5828 // should not be part of index
5829 auto xCursor
5830 = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
5831 xCursor->gotoEnd(false);
5832 xCursor->goLeft(1, true);
5833 // delete
5834 xCursor->setString(OUString());
5835 // But a new paragraph should be started after the index instead
5836 xTextAppend->finishParagraph(css::beans::PropertyValues());
5838 m_bStartedTOC = false;
5839 m_aTextAppendStack.pop();
5840 m_bTextInserted = false;
5841 m_bParaChanged = true; // the paragraph must stay anyway
5843 m_bStartTOC = false;
5844 m_bStartIndex = false;
5845 m_bStartBibliography = false;
5846 if (IsInHeaderFooter() && m_bStartTOCHeaderFooter)
5847 m_bStartTOCHeaderFooter = false;
5849 else
5851 xToInsert.set(pContext->GetTC(), uno::UNO_QUERY);
5852 if( !xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography )
5853 xToInsert = pContext->GetTextField();
5854 if( xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography)
5856 PropertyMap aMap;
5857 // Character properties of the field show up here the
5858 // last (always empty) run. Inherit character
5859 // properties from there.
5860 // Also merge in the properties from the field context,
5861 // e.g. SdtEndBefore.
5862 if (m_pLastCharacterContext.get())
5863 aMap.InsertProps(m_pLastCharacterContext);
5864 aMap.InsertProps(m_aFieldStack.back()->getProperties());
5865 appendTextContent(xToInsert, aMap.GetPropertyValues());
5866 CheckRedline( xToInsert->getAnchor( ) );
5868 else
5870 FormControlHelper::Pointer_t pFormControlHelper(pContext->getFormControlHelper());
5871 if (pFormControlHelper.get() != nullptr)
5873 uno::Reference< text::XFormField > xFormField( pContext->GetFormField() );
5874 assert(xCrsr.is());
5875 if (pFormControlHelper->hasFFDataHandler())
5877 xToInsert.set(xFormField, uno::UNO_QUERY);
5878 if (xFormField.is() && xToInsert.is())
5880 PopFieldmark(m_aTextAppendStack, xCrsr,
5881 pContext->GetFieldId());
5882 pFormControlHelper->processField( xFormField );
5884 else
5886 pFormControlHelper->insertControl(xCrsr);
5889 else
5891 PopFieldmark(m_aTextAppendStack, xCrsr,
5892 pContext->GetFieldId());
5893 uno::Reference<lang::XComponent>(xFormField, uno::UNO_QUERY_THROW)->dispose(); // presumably invalid?
5896 else if (!pContext->GetHyperlinkURL().isEmpty() && xCrsr.is())
5898 xCrsr->gotoEnd( true );
5900 // Draw components (like comments) need hyperlinks set differently
5901 SvxUnoTextRangeBase* pDrawText = dynamic_cast<SvxUnoTextRangeBase*>(xCrsr.get());
5902 if ( pDrawText )
5903 pDrawText->attachField( std::make_unique<SvxURLField>(pContext->GetHyperlinkURL(), xCrsr->getString(), SvxURLFormat::AppDefault) );
5904 else
5906 uno::Reference< beans::XPropertySet > xCrsrProperties( xCrsr, uno::UNO_QUERY_THROW );
5907 xCrsrProperties->setPropertyValue(getPropertyName(PROP_HYPER_LINK_U_R_L), uno::
5908 makeAny(pContext->GetHyperlinkURL()));
5910 if (!pContext->GetHyperlinkTarget().isEmpty())
5911 xCrsrProperties->setPropertyValue("HyperLinkTarget", uno::makeAny(pContext->GetHyperlinkTarget()));
5913 if (m_bStartTOC) {
5914 OUString sDisplayName("Index Link");
5915 xCrsrProperties->setPropertyValue("VisitedCharStyleName",uno::makeAny(sDisplayName));
5916 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName",uno::makeAny(sDisplayName));
5918 else
5920 uno::Any aAny = xCrsrProperties->getPropertyValue("CharStyleName");
5921 OUString charStyle;
5922 if (css::uno::fromAny(aAny, &charStyle))
5924 if (charStyle.isEmpty())
5926 xCrsrProperties->setPropertyValue("VisitedCharStyleName", uno::makeAny(OUString("Default Style")));
5927 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", uno::makeAny(OUString("Default Style")));
5929 else if (charStyle.equalsIgnoreAsciiCase("Internet Link"))
5931 xCrsrProperties->setPropertyValue("CharStyleName", uno::makeAny(OUString("Default Style")));
5933 else
5935 xCrsrProperties->setPropertyValue("VisitedCharStyleName", aAny);
5936 xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", aAny);
5942 else if (m_nStartGenericField != 0)
5944 --m_nStartGenericField;
5945 PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId());
5946 if(m_bTextInserted)
5948 m_bTextInserted = false;
5954 catch(const lang::IllegalArgumentException&)
5956 OSL_FAIL( "IllegalArgumentException in PopFieldContext()" );
5958 catch(const uno::Exception&)
5960 OSL_FAIL( "exception in PopFieldContext()" );
5964 //TOCs have to include all the imported content
5967 std::vector<FieldParagraph> aParagraphsToFinish;
5968 if (pContext)
5970 aParagraphsToFinish = pContext->GetParagraphsToFinish();
5973 //remove the field context
5974 m_aFieldStack.pop_back();
5976 // Finish the paragraph(s) now that the field is closed.
5977 for (const auto& rFinish : aParagraphsToFinish)
5979 finishParagraph(rFinish.m_pPropertyMap, rFinish.m_bRemove);
5984 void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName )
5986 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( m_sCurrentBkmkId );
5987 if( aBookmarkIter != m_aBookmarkMap.end() )
5989 // fields are internal bookmarks: consume redundant "normal" bookmark
5990 if ( IsOpenField() )
5992 FFDataHandler::Pointer_t pFFDataHandler(GetTopFieldContext()->getFFDataHandler());
5993 if (pFFDataHandler && pFFDataHandler->getName() == rBookmarkName)
5995 // HACK: At the END marker, StartOrEndBookmark will START
5996 // a bookmark which will eventually be abandoned, not created.
5997 m_aBookmarkMap.erase(aBookmarkIter);
5998 return;
6002 aBookmarkIter->second.m_sBookmarkName = rBookmarkName;
6004 else
6005 m_sCurrentBkmkName = rBookmarkName;
6008 // This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation.
6009 void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId )
6012 * Add the dummy paragraph to handle section properties
6013 * iff the first element in the section is a table. If the dummy para is not added yet, then add it;
6014 * So bookmark is not attached to the wrong paragraph.
6016 if(hasTableManager() && getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection()
6017 && !GetIsDummyParaAddedForTableInSection() &&!GetIsTextFrameInserted())
6019 AddDummyParaForTableInSection();
6022 bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
6023 if (m_aTextAppendStack.empty())
6024 return;
6025 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
6026 BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( rId );
6027 //is the bookmark name already registered?
6030 if( aBookmarkIter != m_aBookmarkMap.end() )
6032 if (m_xTextFactory.is())
6034 uno::Reference< text::XTextContent > xBookmark( m_xTextFactory->createInstance( "com.sun.star.text.Bookmark" ), uno::UNO_QUERY_THROW );
6035 uno::Reference< text::XTextCursor > xCursor;
6036 uno::Reference< text::XText > xText = aBookmarkIter->second.m_xTextRange->getText();
6037 if( aBookmarkIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
6039 xCursor = xText->createTextCursorByRange( xText->getStart() );
6041 else
6043 xCursor = xText->createTextCursorByRange( aBookmarkIter->second.m_xTextRange );
6044 xCursor->goRight( 1, false );
6047 xCursor->gotoRange( xTextAppend->getEnd(), true );
6048 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
6049 // then move the bookmark-End to the earlier paragraph
6050 if (IsOutsideAParagraph())
6052 xCursor->goLeft( 1, false );
6054 uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW );
6055 assert(!aBookmarkIter->second.m_sBookmarkName.isEmpty());
6056 //todo: make sure the name is not used already!
6057 xBkmNamed->setName( aBookmarkIter->second.m_sBookmarkName );
6058 xTextAppend->insertTextContent( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW), xBookmark, !xCursor->isCollapsed() );
6060 m_aBookmarkMap.erase( aBookmarkIter );
6061 m_sCurrentBkmkId.clear();
6063 else
6065 //otherwise insert a text range as marker
6066 bool bIsStart = true;
6067 uno::Reference< text::XTextRange > xCurrent;
6068 if (xTextAppend.is())
6070 uno::Reference<text::XTextCursor> const xCursor =
6071 xTextAppend->createTextCursorByRange(
6072 m_aTextAppendStack.top().xInsertPosition.is()
6073 ? m_aTextAppendStack.top().xInsertPosition
6074 : xTextAppend->getEnd() );
6076 if (!xCursor)
6077 return;
6079 if (!bIsAfterDummyPara)
6080 bIsStart = !xCursor->goLeft(1, false);
6081 xCurrent = xCursor->getStart();
6083 m_sCurrentBkmkId = rId;
6084 m_aBookmarkMap.emplace( rId, BookmarkInsertPosition( bIsStart, m_sCurrentBkmkName, xCurrent ) );
6085 m_sCurrentBkmkName.clear();
6088 catch( const uno::Exception& )
6090 //TODO: What happens to bookmarks where start and end are at different XText objects?
6094 void DomainMapper_Impl::setPermissionRangeEd(const OUString& user)
6096 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
6097 if (aPremIter != m_aPermMap.end())
6098 aPremIter->second.m_Ed = user;
6099 else
6100 m_sCurrentPermEd = user;
6103 void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group)
6105 PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId);
6106 if (aPremIter != m_aPermMap.end())
6107 aPremIter->second.m_EdGrp = group;
6108 else
6109 m_sCurrentPermEdGrp = group;
6112 // This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark()
6113 void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId)
6116 * Add the dummy paragraph to handle section properties
6117 * if the first element in the section is a table. If the dummy para is not added yet, then add it;
6118 * So permission is not attached to the wrong paragraph.
6120 if (getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection()
6121 && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted())
6123 AddDummyParaForTableInSection();
6126 if (m_aTextAppendStack.empty())
6127 return;
6129 const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection();
6131 uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
6132 PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId);
6134 //is the bookmark name already registered?
6137 if (aPermIter == m_aPermMap.end())
6139 //otherwise insert a text range as marker
6140 bool bIsStart = true;
6141 uno::Reference< text::XTextRange > xCurrent;
6142 if (xTextAppend.is())
6144 uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
6146 if (!bIsAfterDummyPara)
6147 bIsStart = !xCursor->goLeft(1, false);
6148 xCurrent = xCursor->getStart();
6151 // register the start of the new permission
6152 m_sCurrentPermId = permissinId;
6153 m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent));
6155 // clean up
6156 m_sCurrentPermEd.clear();
6157 m_sCurrentPermEdGrp.clear();
6159 else
6161 if (m_xTextFactory.is())
6163 uno::Reference< text::XTextCursor > xCursor;
6164 uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText();
6165 if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara)
6167 xCursor = xText->createTextCursorByRange(xText->getStart());
6169 else
6171 xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange);
6172 xCursor->goRight(1, false);
6175 xCursor->gotoRange(xTextAppend->getEnd(), true);
6176 // A Paragraph was recently finished, and a new Paragraph has not been started as yet
6177 // then move the bookmark-End to the earlier paragraph
6178 if (IsOutsideAParagraph())
6180 xCursor->goLeft(1, false);
6183 // create a new bookmark using specific bookmark name pattern for permissions
6184 uno::Reference< text::XTextContent > xPerm(m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), uno::UNO_QUERY_THROW);
6185 uno::Reference< container::XNamed > xPermNamed(xPerm, uno::UNO_QUERY_THROW);
6186 xPermNamed->setName(aPermIter->second.createBookmarkName());
6188 // add new bookmark
6189 const bool bAbsorb = !xCursor->isCollapsed();
6190 uno::Reference< text::XTextRange > xCurrent(xCursor, uno::UNO_QUERY_THROW);
6191 xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb);
6194 // remove processed permission
6195 m_aPermMap.erase(aPermIter);
6197 // clean up
6198 m_sCurrentPermId = 0;
6199 m_sCurrentPermEd.clear();
6200 m_sCurrentPermEdGrp.clear();
6203 catch (const uno::Exception&)
6205 //TODO: What happens to bookmarks where start and end are at different XText objects?
6209 void DomainMapper_Impl::AddAnnotationPosition(
6210 const bool bStart,
6211 const sal_Int32 nAnnotationId)
6213 if (m_aTextAppendStack.empty())
6214 return;
6216 // Create a cursor, pointing to the current position.
6217 uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
6218 uno::Reference<text::XTextRange> xCurrent;
6219 if (xTextAppend.is())
6221 uno::Reference<text::XTextCursor> xCursor;
6222 if (m_bIsNewDoc)
6223 xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd());
6224 else
6225 xCursor = m_aTextAppendStack.top().xCursor;
6226 if (xCursor.is())
6227 xCurrent = xCursor->getStart();
6230 // And save it, to be used by PopAnnotation() later.
6231 AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[ nAnnotationId ];
6232 if (bStart)
6234 aAnnotationPosition.m_xStart = xCurrent;
6236 else
6238 aAnnotationPosition.m_xEnd = xCurrent;
6240 m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition;
6243 GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport(GraphicImportType eGraphicImportType)
6245 if(!m_pGraphicImport)
6246 m_pGraphicImport = new GraphicImport( m_xComponentContext, m_xTextFactory, m_rDMapper, eGraphicImportType, m_aPositionOffsets, m_aAligns, m_aPositivePercentages );
6247 return m_pGraphicImport;
6249 /*-------------------------------------------------------------------------
6250 reset graphic import if the last import resulted in a shape, not a graphic
6251 -----------------------------------------------------------------------*/
6252 void DomainMapper_Impl::ResetGraphicImport()
6254 m_pGraphicImport.clear();
6258 void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference< Properties >::Pointer_t& ref, GraphicImportType eGraphicImportType)
6260 GetGraphicImport(eGraphicImportType);
6261 if( eGraphicImportType != IMPORT_AS_DETECTED_INLINE && eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR )
6263 //create the graphic
6264 ref->resolve( *m_pGraphicImport );
6267 //insert it into the document at the current cursor position
6269 uno::Reference<text::XTextContent> xTextContent
6270 (m_pGraphicImport->GetGraphicObject());
6272 // In case the SDT starts with the text portion of the graphic, then set the SDT properties here.
6273 bool bHasGrabBag = false;
6274 uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY);
6275 if (xPropertySet.is())
6277 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
6278 bHasGrabBag = xPropertySetInfo->hasPropertyByName("FrameInteropGrabBag");
6279 // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one.
6280 if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph())
6282 comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue("FrameInteropGrabBag"));
6283 aFrameGrabBag["SdtPr"] <<= m_pSdtHelper->getInteropGrabBagAndClear();
6284 xPropertySet->setPropertyValue("FrameInteropGrabBag", uno::makeAny(aFrameGrabBag.getAsConstPropertyValueList()));
6288 /* Set "SdtEndBefore" property on Drawing.
6289 * It is required in a case when Drawing appears immediately after first run i.e.
6290 * there is no text/space/tab in between two runs.
6291 * In this case "SdtEndBefore" property needs to be set on Drawing.
6293 if(IsSdtEndBefore())
6295 if(xPropertySet.is() && bHasGrabBag)
6297 uno::Sequence<beans::PropertyValue> aFrameGrabBag( comphelper::InitPropertySequence({
6298 { "SdtEndBefore", uno::Any(true) }
6299 }));
6300 xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::makeAny(aFrameGrabBag));
6305 // Update the shape properties if it is embedded object.
6306 if(m_xEmbedded.is()){
6307 if (m_pGraphicImport->GetXShapeObject())
6308 m_pGraphicImport->GetXShapeObject()->setPosition(
6309 m_pGraphicImport->GetGraphicObjectPosition());
6311 uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject();
6312 UpdateEmbeddedShapeProps(xShape);
6313 if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR)
6315 uno::Reference<beans::XPropertySet> xEmbeddedProps(m_xEmbedded, uno::UNO_QUERY);
6316 xEmbeddedProps->setPropertyValue("AnchorType", uno::makeAny(text::TextContentAnchorType_AT_CHARACTER));
6317 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
6318 xEmbeddedProps->setPropertyValue("HoriOrient", xShapeProps->getPropertyValue("HoriOrient"));
6319 xEmbeddedProps->setPropertyValue("HoriOrientPosition", xShapeProps->getPropertyValue("HoriOrientPosition"));
6320 xEmbeddedProps->setPropertyValue("HoriOrientRelation", xShapeProps->getPropertyValue("HoriOrientRelation"));
6321 xEmbeddedProps->setPropertyValue("VertOrient", xShapeProps->getPropertyValue("VertOrient"));
6322 xEmbeddedProps->setPropertyValue("VertOrientPosition", xShapeProps->getPropertyValue("VertOrientPosition"));
6323 xEmbeddedProps->setPropertyValue("VertOrientRelation", xShapeProps->getPropertyValue("VertOrientRelation"));
6324 //tdf123873 fix missing textwrap import
6325 xEmbeddedProps->setPropertyValue("TextWrap", xShapeProps->getPropertyValue("TextWrap"));
6328 //insert it into the document at the current cursor position
6329 OSL_ENSURE( xTextContent.is(), "DomainMapper_Impl::ImportGraphic");
6330 if( xTextContent.is())
6332 appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
6334 if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty())
6336 // Remember this object is anchored to the current paragraph.
6337 AnchoredObjectInfo aInfo;
6338 aInfo.m_xAnchoredObject = xTextContent;
6339 if (m_pGraphicImport)
6341 // We still have the graphic import around, remember the original margin, so later
6342 // SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing() can use it.
6343 aInfo.m_nLeftMargin = m_pGraphicImport->GetLeftMarginOrig();
6345 m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo);
6349 // Clear the reference, so in case the embedded object is inside a
6350 // TextFrame, we won't try to resize it (to match the size of the
6351 // TextFrame) here.
6352 m_xEmbedded.clear();
6353 m_pGraphicImport.clear();
6357 void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn )
6359 if( !m_bLineNumberingSet )
6363 uno::Reference< text::XLineNumberingProperties > xLineProperties( m_xTextDocument, uno::UNO_QUERY_THROW );
6364 uno::Reference< beans::XPropertySet > xProperties = xLineProperties->getLineNumberingProperties();
6365 uno::Any aTrue( uno::makeAny( true ));
6366 xProperties->setPropertyValue( getPropertyName( PROP_IS_ON ), aTrue);
6367 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_EMPTY_LINES ), aTrue );
6368 xProperties->setPropertyValue( getPropertyName( PROP_COUNT_LINES_IN_FRAMES ), uno::makeAny( false ) );
6369 xProperties->setPropertyValue( getPropertyName( PROP_INTERVAL ), uno::makeAny( static_cast< sal_Int16 >( nLnnMod )));
6370 xProperties->setPropertyValue( getPropertyName( PROP_DISTANCE ), uno::makeAny( ConversionHelper::convertTwipToMM100(ndxaLnn) ));
6371 xProperties->setPropertyValue( getPropertyName( PROP_NUMBER_POSITION ), uno::makeAny( style::LineNumberPosition::LEFT));
6372 xProperties->setPropertyValue( getPropertyName( PROP_NUMBERING_TYPE ), uno::makeAny( style::NumberingType::ARABIC));
6373 xProperties->setPropertyValue( getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::makeAny( nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage ));
6375 catch( const uno::Exception& )
6378 m_bLineNumberingSet = true;
6379 uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( GetTextDocument(), uno::UNO_QUERY_THROW );
6380 uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
6381 uno::Reference<container::XNameContainer> xStyles;
6382 xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xStyles;
6383 lcl_linenumberingHeaderFooter( xStyles, "Header", this );
6384 lcl_linenumberingHeaderFooter( xStyles, "Footer", this );
6388 void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue )
6390 nValue = ConversionHelper::convertTwipToMM100(nValue);
6391 switch(eElement)
6393 case PAGE_MAR_TOP : m_aPageMargins.top = nValue; break;
6394 case PAGE_MAR_RIGHT : m_aPageMargins.right = nValue; break;
6395 case PAGE_MAR_BOTTOM : m_aPageMargins.bottom = nValue; break;
6396 case PAGE_MAR_LEFT : m_aPageMargins.left = nValue; break;
6397 case PAGE_MAR_HEADER : m_aPageMargins.header = nValue; break;
6398 case PAGE_MAR_FOOTER : m_aPageMargins.footer = nValue; break;
6399 case PAGE_MAR_GUTTER : break;
6404 PageMar::PageMar()
6405 : top(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
6406 // This is strange, the RTF spec says it's 1800, but it's clearly 1440 in Word
6407 // OOXML seems not to specify a default value
6408 , right(ConversionHelper::convertTwipToMM100( sal_Int32(1440)))
6409 , bottom(top)
6410 , left(right)
6411 , header(ConversionHelper::convertTwipToMM100(sal_Int32(720)))
6412 , footer(header)
6417 void DomainMapper_Impl::RegisterFrameConversion(
6418 uno::Reference< text::XTextRange > const& xFrameStartRange,
6419 uno::Reference< text::XTextRange > const& xFrameEndRange,
6420 const std::vector<beans::PropertyValue>& rFrameProperties
6423 OSL_ENSURE(
6424 m_aFrameProperties.empty() && !m_xFrameStartRange.is() && !m_xFrameEndRange.is(),
6425 "frame properties not removed");
6426 m_aFrameProperties = rFrameProperties;
6427 m_xFrameStartRange = xFrameStartRange;
6428 m_xFrameEndRange = xFrameEndRange;
6432 void DomainMapper_Impl::ExecuteFrameConversion()
6434 if( m_xFrameStartRange.is() && m_xFrameEndRange.is() && !m_bDiscardHeaderFooter )
6438 uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( GetTopTextAppend(), uno::UNO_QUERY_THROW );
6439 // convert redline ranges to cursor movement and character length
6440 std::vector<sal_Int32> redPos, redLen;
6441 for( size_t i = 0; i < aFramedRedlines.size(); i+=3)
6443 uno::Reference< text::XTextRange > xRange;
6444 aFramedRedlines[i] >>= xRange;
6445 uno::Reference<text::XTextCursor> xRangeCursor = GetTopTextAppend()->createTextCursorByRange( xRange );
6446 if (xRangeCursor.is())
6448 sal_Int32 nLen = xRange->getString().getLength();
6449 redLen.push_back(nLen);
6450 xRangeCursor->gotoRange(m_xFrameStartRange, true);
6451 redPos.push_back(xRangeCursor->getString().getLength() - nLen);
6453 else
6455 // failed createTextCursorByRange(), for example, table inside the frame
6456 redLen.push_back(-1);
6457 redPos.push_back(-1);
6461 const uno::Reference< text::XTextContent >& xTextContent = xTextAppendAndConvert->convertToTextFrame(
6462 m_xFrameStartRange,
6463 m_xFrameEndRange,
6464 comphelper::containerToSequence(m_aFrameProperties) );
6466 // create redlines in the previous frame
6467 for( size_t i = 0; i < aFramedRedlines.size(); i+=3)
6469 OUString sType;
6470 beans::PropertyValues aRedlineProperties( 3 );
6471 // skip failed createTextCursorByRange()
6472 if (redPos[i/3] == -1)
6473 continue;
6474 aFramedRedlines[i+1] >>= sType;
6475 aFramedRedlines[i+2] >>= aRedlineProperties;
6476 uno::Reference< text::XTextFrame > xFrame( xTextContent, uno::UNO_QUERY_THROW );
6477 uno::Reference< text::XTextCursor > xCrsr = xFrame->getText()->createTextCursor();
6478 xCrsr->goRight(redPos[i/3], false);
6479 xCrsr->goRight(redLen[i/3], true);
6480 uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW );
6481 xRedline->makeRedline( sType, aRedlineProperties );
6484 catch( const uno::Exception&)
6486 DBG_UNHANDLED_EXCEPTION( "writerfilter.dmapper", "Exception caught when converting to frame");
6489 m_bIsActualParagraphFramed = false;
6490 aFramedRedlines.clear();
6492 m_xFrameStartRange = nullptr;
6493 m_xFrameEndRange = nullptr;
6494 m_aFrameProperties.clear();
6497 void DomainMapper_Impl::AddNewRedline( sal_uInt32 sprmId )
6499 RedlineParamsPtr pNew( new RedlineParams );
6500 pNew->m_nToken = XML_mod;
6501 if ( !m_bIsParaMarkerChange )
6503 // <w:rPrChange> applies to the whole <w:r>, <w:pPrChange> applies to the whole <w:p>,
6504 // so keep those two in CONTEXT_CHARACTERS and CONTEXT_PARAGRAPH, which will take
6505 // care of their scope (i.e. when they should be used and discarded).
6506 // Let's keep the rest the same way they used to be handled (explicitly dropped
6507 // from a global stack by endtrackchange), but quite possibly they should not be handled
6508 // that way either (I don't know).
6509 if( sprmId == NS_ooxml::LN_EG_RPrContent_rPrChange )
6510 GetTopContextOfType( CONTEXT_CHARACTER )->Redlines().push_back( pNew );
6511 else if( sprmId == NS_ooxml::LN_CT_PPr_pPrChange )
6512 GetTopContextOfType( CONTEXT_PARAGRAPH )->Redlines().push_back( pNew );
6513 else if( sprmId != NS_ooxml::LN_CT_ParaRPr_rPrChange )
6514 m_aRedlines.top().push_back( pNew );
6516 else
6518 m_pParaMarkerRedline = pNew;
6520 // Newly read data will go into this redline.
6521 m_currentRedline = pNew;
6524 void DomainMapper_Impl::SetCurrentRedlineIsRead()
6526 m_currentRedline.clear();
6529 sal_Int32 DomainMapper_Impl::GetCurrentRedlineToken( ) const
6531 return m_currentRedline->m_nToken;
6534 void DomainMapper_Impl::SetCurrentRedlineAuthor( const OUString& sAuthor )
6536 if (!m_xAnnotationField.is())
6538 if (m_currentRedline.get())
6539 m_currentRedline->m_sAuthor = sAuthor;
6540 else
6541 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
6543 else
6544 m_xAnnotationField->setPropertyValue("Author", uno::makeAny(sAuthor));
6547 void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials )
6549 if (m_xAnnotationField.is())
6550 m_xAnnotationField->setPropertyValue("Initials", uno::makeAny(sInitials));
6553 void DomainMapper_Impl::SetCurrentRedlineDate( const OUString& sDate )
6555 if (!m_xAnnotationField.is())
6557 if (m_currentRedline.get())
6558 m_currentRedline->m_sDate = sDate;
6559 else
6560 SAL_INFO("writerfilter.dmapper", "numberingChange not implemented");
6562 else
6563 m_xAnnotationField->setPropertyValue("DateTimeValue", uno::makeAny(ConversionHelper::ConvertDateStringToDateTime(sDate)));
6566 void DomainMapper_Impl::SetCurrentRedlineId( sal_Int32 sId )
6568 if (m_xAnnotationField.is())
6570 m_nAnnotationId = sId;
6572 else
6574 // This should be an assert, but somebody had the smart idea to reuse this function also for comments and whatnot,
6575 // and in some cases the id is actually not handled, which may be in fact a bug.
6576 if( !m_currentRedline.get())
6577 SAL_INFO("writerfilter.dmapper", "no current redline");
6581 void DomainMapper_Impl::SetCurrentRedlineToken( sal_Int32 nToken )
6583 m_currentRedline->m_nToken = nToken;
6586 void DomainMapper_Impl::SetCurrentRedlineRevertProperties( const uno::Sequence<beans::PropertyValue>& aProperties )
6588 m_currentRedline->m_aRevertProperties = aProperties;
6592 // This removes only the last redline stored here, those stored in contexts are automatically removed when
6593 // the context is destroyed.
6594 void DomainMapper_Impl::RemoveTopRedline( )
6596 if (m_aRedlines.top().empty())
6598 SAL_WARN("writerfilter.dmapper", "RemoveTopRedline called with empty stack");
6599 throw uno::Exception("RemoveTopRedline failed", nullptr);
6601 m_aRedlines.top().pop_back( );
6602 m_currentRedline.clear();
6605 void DomainMapper_Impl::ApplySettingsTable()
6607 if (m_pSettingsTable && m_xTextFactory.is())
6611 uno::Reference< beans::XPropertySet > xTextDefaults(m_xTextFactory->createInstance("com.sun.star.text.Defaults"), uno::UNO_QUERY_THROW );
6612 sal_Int32 nDefTab = m_pSettingsTable->GetDefaultTabStop();
6613 xTextDefaults->setPropertyValue( getPropertyName( PROP_TAB_STOP_DISTANCE ), uno::makeAny(nDefTab) );
6614 if (m_pSettingsTable->GetLinkStyles())
6616 // If linked styles are enabled, set paragraph defaults from Word's default template
6617 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_BOTTOM_MARGIN), uno::makeAny(ConversionHelper::convertTwipToMM100(200)));
6618 style::LineSpacing aSpacing;
6619 aSpacing.Mode = style::LineSpacingMode::PROP;
6620 aSpacing.Height = sal_Int16(115);
6621 xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_LINE_SPACING), uno::makeAny(aSpacing));
6624 if (m_pSettingsTable->GetZoomFactor() || m_pSettingsTable->GetView())
6626 std::vector<beans::PropertyValue> aViewProps;
6627 if (m_pSettingsTable->GetZoomFactor())
6629 aViewProps.emplace_back("ZoomFactor", -1, uno::makeAny(m_pSettingsTable->GetZoomFactor()), beans::PropertyState_DIRECT_VALUE);
6630 aViewProps.emplace_back("VisibleBottom", -1, uno::makeAny(sal_Int32(0)), beans::PropertyState_DIRECT_VALUE);
6631 aViewProps.emplace_back("ZoomType", -1,
6632 uno::makeAny(m_pSettingsTable->GetZoomType()),
6633 beans::PropertyState_DIRECT_VALUE);
6635 uno::Reference<container::XIndexContainer> xBox = document::IndexedPropertyValues::create(m_xComponentContext);
6636 xBox->insertByIndex(sal_Int32(0), uno::makeAny(comphelper::containerToSequence(aViewProps)));
6637 uno::Reference<document::XViewDataSupplier> xViewDataSupplier(m_xTextDocument, uno::UNO_QUERY);
6638 xViewDataSupplier->setViewData(xBox);
6641 uno::Reference< beans::XPropertySet > xSettings(m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY);
6643 if (m_pSettingsTable->GetDoNotExpandShiftReturn())
6644 xSettings->setPropertyValue( "DoNotJustifyLinesWithManualBreak", uno::makeAny(true) );
6645 if (m_pSettingsTable->GetUsePrinterMetrics())
6646 xSettings->setPropertyValue("PrinterIndependentLayout", uno::makeAny(document::PrinterIndependentLayout::DISABLED));
6647 if( m_pSettingsTable->GetEmbedTrueTypeFonts())
6648 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_FONTS ), uno::makeAny(true) );
6649 if( m_pSettingsTable->GetEmbedSystemFonts())
6650 xSettings->setPropertyValue( getPropertyName( PROP_EMBED_SYSTEM_FONTS ), uno::makeAny(true) );
6651 xSettings->setPropertyValue("AddParaTableSpacing", uno::makeAny(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing()));
6652 if( m_pSettingsTable->GetProtectForm() )
6653 xSettings->setPropertyValue("ProtectForm", uno::makeAny( true ));
6654 if( m_pSettingsTable->GetReadOnly() )
6655 xSettings->setPropertyValue("LoadReadonly", uno::makeAny( true ));
6657 catch(const uno::Exception&)
6663 uno::Reference<container::XIndexAccess> DomainMapper_Impl::GetCurrentNumberingRules(sal_Int32* pListLevel)
6665 uno::Reference<container::XIndexAccess> xRet;
6668 OUString aStyle = GetCurrentParaStyleName();
6669 if (aStyle.isEmpty())
6670 return xRet;
6671 const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(aStyle);
6672 if (!pEntry)
6673 return xRet;
6674 const StyleSheetPropertyMap* pStyleSheetProperties = dynamic_cast<const StyleSheetPropertyMap*>(pEntry->pProperties.get());
6675 if (!pStyleSheetProperties)
6676 return xRet;
6677 sal_Int32 nListId = pStyleSheetProperties->GetListId();
6678 if (nListId < 0)
6679 return xRet;
6680 if (pListLevel)
6681 *pListLevel = pStyleSheetProperties->GetListLevel();
6683 // So we are in a paragraph style and it has numbering. Look up the relevant numbering rules.
6684 auto const pList(GetListTable()->GetList(nListId));
6685 OUString aListName;
6686 if (pList)
6688 aListName = pList->GetStyleName();
6690 uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY_THROW);
6691 uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
6692 uno::Reference<container::XNameAccess> xNumberingStyles;
6693 xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles;
6694 uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY);
6695 xRet.set(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
6697 catch (const uno::Exception&)
6699 TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "GetCurrentNumberingRules: exception caught");
6701 return xRet;
6704 uno::Reference<beans::XPropertySet> DomainMapper_Impl::GetCurrentNumberingCharStyle()
6706 uno::Reference<beans::XPropertySet> xRet;
6709 sal_Int32 nListLevel = -1;
6710 uno::Reference<container::XIndexAccess> xLevels;
6711 if ( GetTopContextType() == CONTEXT_PARAGRAPH )
6712 xLevels = GetCurrentNumberingRules(&nListLevel);
6713 if (!xLevels.is())
6715 if (IsOOXMLImport())
6716 return xRet;
6718 PropertyMapPtr pContext = m_pTopContext;
6719 if (IsRTFImport() && !IsOpenField())
6721 // Looking up the paragraph context explicitly (and not just taking
6722 // the top context) is necessary for RTF, where formatting of a run
6723 // and of the paragraph mark is not separated.
6724 // We know that the formatting inside a field won't affect the
6725 // paragraph marker formatting, though.
6726 pContext = GetTopContextOfType(CONTEXT_PARAGRAPH);
6727 if (!pContext)
6728 return xRet;
6731 // In case numbering rules is not found via a style, try the direct formatting instead.
6732 std::optional<PropertyMap::Property> oProp = pContext->getProperty(PROP_NUMBERING_RULES);
6733 if (oProp)
6735 xLevels.set(oProp->second, uno::UNO_QUERY);
6736 // Found the rules, then also try to look up our numbering level.
6737 oProp = pContext->getProperty(PROP_NUMBERING_LEVEL);
6738 if (oProp)
6739 oProp->second >>= nListLevel;
6740 else
6741 nListLevel = 0;
6744 if (!xLevels.is())
6745 return xRet;
6747 uno::Sequence<beans::PropertyValue> aProps;
6748 xLevels->getByIndex(nListLevel) >>= aProps;
6749 auto pProp = std::find_if(aProps.begin(), aProps.end(),
6750 [](const beans::PropertyValue& rProp) { return rProp.Name == "CharStyleName"; });
6751 if (pProp != aProps.end())
6753 OUString aCharStyle;
6754 pProp->Value >>= aCharStyle;
6755 uno::Reference<container::XNameAccess> xCharacterStyles;
6756 uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY);
6757 uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
6758 xStyleFamilies->getByName("CharacterStyles") >>= xCharacterStyles;
6759 xRet.set(xCharacterStyles->getByName(aCharStyle), uno::UNO_QUERY_THROW);
6762 catch( const uno::Exception& )
6765 return xRet;
6768 SectionPropertyMap * DomainMapper_Impl::GetSectionContext()
6770 SectionPropertyMap* pSectionContext = nullptr;
6771 //the section context is not available before the first call of startSectionGroup()
6772 if( !IsAnyTableImport() )
6774 PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION);
6775 pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() );
6778 return pSectionContext;
6781 void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any& value)
6783 deferredCharacterProperties[ id ] = value;
6786 void DomainMapper_Impl::processDeferredCharacterProperties()
6788 // Actually process in DomainMapper, so that it's the same source file like normal processing.
6789 if( !deferredCharacterProperties.empty())
6791 m_rDMapper.processDeferredCharacterProperties( deferredCharacterProperties );
6792 deferredCharacterProperties.clear();
6796 sal_Int32 DomainMapper_Impl::getNumberingProperty(const sal_Int32 nListId, sal_Int32 nNumberingLevel, const OUString& aProp)
6798 sal_Int32 nRet = 0;
6799 if ( nListId < 0 )
6800 return nRet;
6804 if (nNumberingLevel < 0) // It seems it's valid to omit numbering level, and in that case it means zero.
6805 nNumberingLevel = 0;
6807 auto const pList(GetListTable()->GetList(nListId));
6808 const OUString aListName = pList->GetStyleName();
6809 const uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY_THROW);
6810 const uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies();
6811 uno::Reference<container::XNameAccess> xNumberingStyles;
6812 xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles;
6813 const uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY);
6814 const uno::Reference<container::XIndexAccess> xNumberingRules(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
6815 if (xNumberingRules.is())
6817 uno::Sequence<beans::PropertyValue> aProps;
6818 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
6819 auto pProp = std::find_if(aProps.begin(), aProps.end(),
6820 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
6821 if (pProp != aProps.end())
6822 pProp->Value >>= nRet;
6825 catch( const uno::Exception& )
6827 // This can happen when the doc contains some hand-crafted invalid list level.
6830 return nRet;
6833 sal_Int32 DomainMapper_Impl::getCurrentNumberingProperty(const OUString& aProp)
6835 sal_Int32 nRet = 0;
6837 std::optional<PropertyMap::Property> pProp = m_pTopContext->getProperty(PROP_NUMBERING_RULES);
6838 uno::Reference<container::XIndexAccess> xNumberingRules;
6839 if (pProp)
6840 xNumberingRules.set(pProp->second, uno::UNO_QUERY);
6841 pProp = m_pTopContext->getProperty(PROP_NUMBERING_LEVEL);
6842 // Default numbering level is the first one.
6843 sal_Int32 nNumberingLevel = 0;
6844 if (pProp)
6845 pProp->second >>= nNumberingLevel;
6846 if (xNumberingRules.is())
6848 uno::Sequence<beans::PropertyValue> aProps;
6849 xNumberingRules->getByIndex(nNumberingLevel) >>= aProps;
6850 auto pPropVal = std::find_if(aProps.begin(), aProps.end(),
6851 [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; });
6852 if (pPropVal != aProps.end())
6853 pPropVal->Value >>= nRet;
6856 return nRet;
6860 void DomainMapper_Impl::enableInteropGrabBag(const OUString& aName)
6862 m_aInteropGrabBagName = aName;
6865 void DomainMapper_Impl::disableInteropGrabBag()
6867 m_aInteropGrabBagName.clear();
6868 m_aInteropGrabBag.clear();
6869 m_aSubInteropGrabBag.clear();
6872 bool DomainMapper_Impl::isInteropGrabBagEnabled() const
6874 return !(m_aInteropGrabBagName.isEmpty());
6877 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue)
6879 if (m_aInteropGrabBagName.isEmpty())
6880 return;
6881 beans::PropertyValue aProperty;
6882 aProperty.Name = aKey;
6883 aProperty.Value <<= aValue;
6884 rInteropGrabBag.push_back(aProperty);
6887 void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<beans::PropertyValue>& rValue)
6889 if (m_aInteropGrabBagName.isEmpty())
6890 return;
6891 beans::PropertyValue aProperty;
6892 aProperty.Name = aKey;
6893 aProperty.Value <<= comphelper::containerToSequence(rValue);
6894 rValue.clear();
6895 rInteropGrabBag.push_back(aProperty);
6898 void DomainMapper_Impl::substream(Id rName,
6899 ::writerfilter::Reference<Stream>::Pointer_t const& ref)
6901 #ifndef NDEBUG
6902 size_t contextSize(m_aContextStack.size());
6903 size_t propSize[NUMBER_OF_CONTEXTS];
6904 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
6905 propSize[i] = m_aPropertyStacks[i].size();
6907 #endif
6909 // Save "has footnote" state, which is specific to a section in the body
6910 // text, so state from substreams is not relevant.
6911 bool bHasFtn = m_bHasFtn;
6913 //finalize any waiting frames before starting alternate streams
6914 CheckUnregisteredFrameConversion();
6915 ExecuteFrameConversion();
6917 appendTableManager();
6918 // Appending a TableManager resets its TableHandler, so we need to append
6919 // that as well, or tables won't be imported properly in headers/footers.
6920 appendTableHandler();
6921 getTableManager().startLevel();
6923 //import of page header/footer
6924 //Ensure that only one header/footer per section is pushed
6926 switch( rName )
6928 case NS_ooxml::LN_headerl:
6929 PushPageHeader(SectionPropertyMap::PAGE_LEFT);
6930 break;
6931 case NS_ooxml::LN_headerr:
6932 PushPageHeader(SectionPropertyMap::PAGE_RIGHT);
6933 break;
6934 case NS_ooxml::LN_headerf:
6935 PushPageHeader(SectionPropertyMap::PAGE_FIRST);
6936 break;
6937 case NS_ooxml::LN_footerl:
6938 PushPageFooter(SectionPropertyMap::PAGE_LEFT);
6939 break;
6940 case NS_ooxml::LN_footerr:
6941 PushPageFooter(SectionPropertyMap::PAGE_RIGHT);
6942 break;
6943 case NS_ooxml::LN_footerf:
6944 PushPageFooter(SectionPropertyMap::PAGE_FIRST);
6945 break;
6946 case NS_ooxml::LN_footnote:
6947 case NS_ooxml::LN_endnote:
6948 PushFootOrEndnote( NS_ooxml::LN_footnote == rName );
6949 break;
6950 case NS_ooxml::LN_annotation :
6951 PushAnnotation();
6952 break;
6954 ref->resolve(m_rDMapper);
6956 switch( rName )
6958 case NS_ooxml::LN_headerl:
6959 case NS_ooxml::LN_headerr:
6960 case NS_ooxml::LN_headerf:
6961 case NS_ooxml::LN_footerl:
6962 case NS_ooxml::LN_footerr:
6963 case NS_ooxml::LN_footerf:
6964 PopPageHeaderFooter();
6965 break;
6966 case NS_ooxml::LN_footnote:
6967 case NS_ooxml::LN_endnote:
6968 PopFootOrEndnote();
6969 break;
6970 case NS_ooxml::LN_annotation :
6971 PopAnnotation();
6972 break;
6975 getTableManager().endLevel();
6976 popTableManager();
6977 m_bHasFtn = bHasFtn;
6979 switch(rName)
6981 case NS_ooxml::LN_footnote:
6982 case NS_ooxml::LN_endnote:
6983 m_pTableHandler->setHadFootOrEndnote(true);
6984 m_bHasFtn = true;
6985 break;
6988 // check that stacks are the same as before substream
6989 assert(m_aContextStack.size() == contextSize);
6990 for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) {
6991 assert(m_aPropertyStacks[i].size() == propSize[i]);
6996 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */