diff options
Diffstat (limited to 'writerfilter/source/dmapper/DomainMapper_Impl.cxx')
-rw-r--r-- | writerfilter/source/dmapper/DomainMapper_Impl.cxx | 8792 |
1 files changed, 8792 insertions, 0 deletions
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx new file mode 100644 index 000000000..ae0a8a27d --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -0,0 +1,8792 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <ooxml/resourceids.hxx> +#include "DomainMapper_Impl.hxx" +#include "ConversionHelper.hxx" +#include "SdtHelper.hxx" +#include "DomainMapperTableHandler.hxx" +#include "TagLogger.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/document/PrinterIndependentLayout.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/i18n/NumberFormatMapper.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/style/LineNumberPosition.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/text/ChapterFormat.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/text/SetVariableType.hpp> +#include <com/sun/star/text/XDocumentIndex.hpp> +#include <com/sun/star/text/XDocumentIndexesSupplier.hpp> +#include <com/sun/star/text/XFootnote.hpp> +#include <com/sun/star/text/XEndnotesSupplier.hpp> +#include <com/sun/star/text/XFootnotesSupplier.hpp> +#include <com/sun/star/text/XLineNumberingProperties.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/text/PageNumberType.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/ReferenceFieldPart.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/ReferenceFieldSource.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XDependentTextField.hpp> +#include <com/sun/star/text/XParagraphCursor.hpp> +#include <com/sun/star/text/XRedline.hpp> +#include <com/sun/star/text/XTextFieldsSupplier.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/RubyPosition.hpp> +#include <com/sun/star/text/XTextRangeCompare.hpp> +#include <com/sun/star/style/DropCapFormat.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormatter.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/text/ControlCharacter.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/document/XImporter.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <comphelper/indexedpropertyvalues.hxx> +#include <editeng/flditem.hxx> +#include <editeng/unotext.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/temporary.hxx> +#include <oox/mathml/import.hxx> +#include <xmloff/odffields.hxx> +#include <rtl/uri.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> +#include <comphelper/string.hxx> + +#include <dmapper/GraphicZOrderHelper.hxx> + +#include <oox/token/tokens.hxx> + +#include <cmath> +#include <optional> +#include <map> +#include <tuple> +#include <unordered_map> +#include <regex> +#include <algorithm> + +#include <officecfg/Office/Common.hxx> +#include <filter/msfilter/util.hxx> +#include <filter/msfilter/ww8fields.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/propertysequence.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/mediadescriptor.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/drawing/FillStyle.hpp> + +#include <unicode/errorcode.h> +#include <unicode/regex.h> + +using namespace ::com::sun::star; +using namespace oox; +namespace writerfilter::dmapper{ + +//line numbering for header/footer +static void lcl_linenumberingHeaderFooter( const uno::Reference<container::XNameContainer>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper ) +{ + const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname ); + if (!pEntry) + return; + const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->pProperties.get(); + if ( !pStyleSheetProperties ) + return; + sal_Int32 nListId = pStyleSheetProperties->GetListId(); + if( xStyles.is() ) + { + if( xStyles->hasByName( rname ) ) + { + uno::Reference< style::XStyle > xStyle; + xStyles->getByName( rname ) >>= xStyle; + if( !xStyle.is() ) + return; + uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY ); + xPropertySet->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::Any( nListId >= 0 ) ); + } + } +} + +// Populate Dropdown Field properties from FFData structure +static void lcl_handleDropdownField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler ) +{ + if ( !rxFieldProps.is() ) + return; + + if ( !pFFDataHandler->getName().isEmpty() ) + rxFieldProps->setPropertyValue( "Name", uno::Any( pFFDataHandler->getName() ) ); + + const FFDataHandler::DropDownEntries_t& rEntries = pFFDataHandler->getDropDownEntries(); + uno::Sequence< OUString > sItems( rEntries.size() ); + ::std::copy( rEntries.begin(), rEntries.end(), sItems.getArray()); + if ( sItems.hasElements() ) + rxFieldProps->setPropertyValue( "Items", uno::Any( sItems ) ); + + sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32(); + if (nResult > 0 && o3tl::make_unsigned(nResult) < sItems.size()) + rxFieldProps->setPropertyValue( "SelectedItem", uno::Any( std::as_const(sItems)[ nResult ] ) ); + if ( !pFFDataHandler->getHelpText().isEmpty() ) + rxFieldProps->setPropertyValue( "Help", uno::Any( pFFDataHandler->getHelpText() ) ); +} + +static void lcl_handleTextField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler ) +{ + if ( rxFieldProps.is() && pFFDataHandler ) + { + rxFieldProps->setPropertyValue + (getPropertyName(PROP_HINT), + uno::Any(pFFDataHandler->getStatusText())); + rxFieldProps->setPropertyValue + (getPropertyName(PROP_HELP), + uno::Any(pFFDataHandler->getHelpText())); + rxFieldProps->setPropertyValue + (getPropertyName(PROP_CONTENT), + uno::Any(pFFDataHandler->getTextDefault())); + } +} + +/** + Very similar to DomainMapper_Impl::GetPropertyFromStyleSheet + It is focused on paragraph properties search in current & parent stylesheet entries. + But it will not take into account properties with listid: these are "list paragraph styles" and + not used in some cases. +*/ +static uno::Any lcl_GetPropertyFromParaStyleSheetNoNum(PropertyIds eId, StyleSheetEntryPtr pEntry, const StyleSheetTablePtr& rStyleSheet) +{ + while (pEntry) + { + if (pEntry->pProperties) + { + std::optional<PropertyMap::Property> aProperty = + pEntry->pProperties->getProperty(eId); + if (aProperty) + { + if (pEntry->pProperties->GetListId()) + // It is a paragraph style with list. Paragraph list styles are not taken into account + return uno::Any(); + else + return aProperty->second; + } + } + //search until the property is set or no parent is available + StyleSheetEntryPtr pNewEntry; + if (!pEntry->sBaseStyleIdentifier.isEmpty()) + pNewEntry = rStyleSheet->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + + SAL_WARN_IF(pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?"); + + if (pEntry == pNewEntry) //fdo#49587 + break; + + pEntry = pNewEntry; + } + return uno::Any(); +} + + +namespace { + +struct FieldConversion +{ + const char* cFieldServiceName; + FieldId eFieldId; +}; + +} + +typedef std::unordered_map<OUString, FieldConversion> FieldConversionMap_t; + +/// Gives access to the parent field context of the topmost one, if there is any. +static FieldContextPtr GetParentFieldContext(const std::deque<FieldContextPtr>& rFieldStack) +{ + if (rFieldStack.size() < 2) + { + return nullptr; + } + + return rFieldStack[rFieldStack.size() - 2]; +} + +/// Decides if the pInner field inside pOuter is allowed in Writer core, depending on their type. +static bool IsFieldNestingAllowed(const FieldContextPtr& pOuter, const FieldContextPtr& pInner) +{ + std::optional<FieldId> oOuterFieldId = pOuter->GetFieldId(); + OUString aCommand = pOuter->GetCommand(); + + // Ignore leading space before the field name, but don't accept IFF when we check for IF. + if (!aCommand.isEmpty() && aCommand[0] == ' ') + { + aCommand = aCommand.subView(1); + } + + if (!oOuterFieldId && aCommand.startsWith("IF ")) + { + // This will be FIELD_IF once the command is closed. + oOuterFieldId = FIELD_IF; + } + + if (!oOuterFieldId) + { + return true; + } + + if (!pInner->GetFieldId()) + { + return true; + } + + switch (*oOuterFieldId) + { + case FIELD_IF: + { + switch (*pInner->GetFieldId()) + { + case FIELD_DOCVARIABLE: + case FIELD_FORMULA: + case FIELD_IF: + case FIELD_MERGEFIELD: + { + return false; + } + default: + break; + } + break; + } + default: + break; + } + + return true; +} + +uno::Any FloatingTableInfo::getPropertyValue(std::u16string_view propertyName) +{ + for( beans::PropertyValue const & propVal : std::as_const(m_aFrameProperties) ) + if( propVal.Name == propertyName ) + return propVal.Value ; + return uno::Any() ; +} + +DomainMapper_Impl::DomainMapper_Impl( + DomainMapper& rDMapper, + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<lang::XComponent> const& xModel, + SourceDocumentType eDocumentType, + utl::MediaDescriptor const & rMediaDesc) : + m_eDocumentType( eDocumentType ), + m_rDMapper( rDMapper ), + m_pOOXMLDocument(nullptr), + m_xTextDocument( xModel, uno::UNO_QUERY ), + m_xTextFactory( xModel, uno::UNO_QUERY ), + m_xComponentContext( xContext ), + m_bForceGenericFields(!utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get()), + m_bIsDecimalComma( false ), + m_bSetUserFieldContent( false ), + m_bSetCitation( false ), + m_bSetDateValue( false ), + m_bIsFirstSection( true ), + m_bIsColumnBreakDeferred( false ), + m_bIsPageBreakDeferred( false ), + m_nLineBreaksDeferred( 0 ), + m_bSdtEndDeferred(false), + m_bParaSdtEndDeferred(false), + m_bStartTOC(false), + m_bStartTOCHeaderFooter(false), + m_bStartedTOC(false), + m_bStartIndex(false), + m_bStartBibliography(false), + m_nStartGenericField(0), + m_bTextInserted(false), + m_sCurrentPermId(0), + m_bFrameDirectionSet(false), + m_bInDocDefaultsImport(false), + m_bInStyleSheetImport( false ), + m_bInNumberingImport(false), + m_bInAnyTableImport( false ), + m_eInHeaderFooterImport( HeaderFooterImportState::none ), + m_bDiscardHeaderFooter( false ), + m_bInFootOrEndnote(false), + m_bInFootnote(false), + m_bHasFootnoteStyle(false), + m_bCheckFootnoteStyle(false), + m_eSkipFootnoteState(SkipFootnoteSeparator::OFF), + m_nFootnotes(-1), + m_nEndnotes(-1), + m_nFirstFootnoteIndex(-1), + m_nFirstEndnoteIndex(-1), + m_bLineNumberingSet( false ), + m_bIsInFootnoteProperties( false ), + m_bIsParaMarkerChange( false ), + m_bIsParaMarkerMove( false ), + m_bRedlineImageInPreviousRun( false ), + m_bParaChanged( false ), + m_bIsFirstParaInSection( true ), + m_bIsFirstParaInSectionAfterRedline( true ), + m_bDummyParaAddedForTableInSection( false ), + m_bDummyParaAddedForTableInSectionPage( false ), + m_bTextFrameInserted(false), + m_bIsPreviousParagraphFramed( false ), + m_bIsLastParaInSection( false ), + m_bIsLastSectionGroup( false ), + m_bIsInComments( false ), + m_bParaSectpr( false ), + m_bUsingEnhancedFields( false ), + m_bSdt(false), + m_bIsFirstRun(false), + m_bIsOutsideAParagraph(true), + m_nAnnotationId( -1 ), + m_aSmartTagHandler(m_xComponentContext, m_xTextDocument), + m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault("TextInsertModeRange", uno::Reference<text::XTextRange>())), + m_xAltChunkStartingRange(rMediaDesc.getUnpackedValueOrDefault("AltChunkStartingRange", uno::Reference<text::XTextRange>())), + m_bIsInTextBox(false), + m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)), + m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault("AltChunkMode", false)), + m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)), + m_nTableDepth(0), + m_nTableCellDepth(0), + m_nLastTableCellParagraphDepth(0), + m_bHasFtn(false), + m_bHasFtnSep(false), + m_bCheckFirstFootnoteTab(false), + m_bIgnoreNextTab(false), + m_bIsSplitPara(false), + m_bIsActualParagraphFramed( false ), + m_bParaHadField(false), + m_bSaveParaHadField(false), + m_bParaAutoBefore(false), + m_bFirstParagraphInCell(true), + m_bSaveFirstParagraphInCell(false), + m_bParaWithInlineObject(false), + m_bSaxError(false) +{ + m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString()); + if (m_aBaseUrl.isEmpty()) { + m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_URL, OUString()); + } + + appendTableManager( ); + GetBodyText(); + if (!m_bIsNewDoc && !m_xBodyText) + { + throw uno::Exception("failed to find body text of the insert position", nullptr); + } + + uno::Reference< text::XTextAppend > xBodyTextAppend( m_xBodyText, uno::UNO_QUERY ); + m_aTextAppendStack.push(TextAppendContext(xBodyTextAppend, + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(m_xInsertTextRange))); + + //todo: does it makes sense to set the body text as static text interface? + uno::Reference< text::XTextAppendAndConvert > xBodyTextAppendAndConvert( m_xBodyText, uno::UNO_QUERY ); + m_pTableHandler = new DomainMapperTableHandler(xBodyTextAppendAndConvert, *this); + getTableManager( ).setHandler(m_pTableHandler); + + getTableManager( ).startLevel(); + m_bUsingEnhancedFields = !utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get(); + + m_pSdtHelper = new SdtHelper(*this, m_xComponentContext); + + m_aRedlines.push(std::vector<RedlineParamsPtr>()); + + if (m_bIsAltChunk) + { + m_bIsFirstSection = false; + } +} + + +DomainMapper_Impl::~DomainMapper_Impl() +{ + ChainTextFrames(); + // Don't remove last paragraph when pasting, sw expects that empty paragraph. + if (m_bIsNewDoc) + RemoveLastParagraph(); + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } +} + +writerfilter::ooxml::OOXMLDocument* DomainMapper_Impl::getDocumentReference() const +{ + return m_pOOXMLDocument; +} + +uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetPageStyles() +{ + if(!m_xPageStyles1.is()) + { + uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY ); + if (xSupplier.is()) + xSupplier->getStyleFamilies()->getByName("PageStyles") >>= m_xPageStyles1; + } + return m_xPageStyles1; +} + +OUString DomainMapper_Impl::GetUnusedPageStyleName() +{ + static const char DEFAULT_STYLE[] = "Converted"; + if (!m_xNextUnusedPageStyleNo) + { + const uno::Sequence< OUString > aPageStyleNames = GetPageStyles()->getElementNames(); + sal_Int32 nMaxIndex = 0; + // find the highest number x in each style with the name "DEFAULT_STYLE+x" and + // return an incremented name + + for ( const auto& rStyleName : aPageStyleNames ) + { + if ( rStyleName.startsWith( DEFAULT_STYLE ) ) + { + sal_Int32 nIndex = o3tl::toInt32(rStyleName.subView( strlen( DEFAULT_STYLE ) )); + if ( nIndex > nMaxIndex ) + nMaxIndex = nIndex; + } + } + m_xNextUnusedPageStyleNo = nMaxIndex + 1; + } + + OUString sPageStyleName = DEFAULT_STYLE + OUString::number( *m_xNextUnusedPageStyleNo ); + *m_xNextUnusedPageStyleNo = *m_xNextUnusedPageStyleNo + 1; + return sPageStyleName; +} + +uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetCharacterStyles() +{ + if(!m_xCharacterStyles.is()) + { + uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY ); + if (xSupplier.is()) + xSupplier->getStyleFamilies()->getByName("CharacterStyles") >>= m_xCharacterStyles; + } + return m_xCharacterStyles; +} + +OUString DomainMapper_Impl::GetUnusedCharacterStyleName() +{ + static const char cListLabel[] = "ListLabel "; + if (!m_xNextUnusedCharacterStyleNo) + { + //search for all character styles with the name sListLabel + <index> + const uno::Sequence< OUString > aCharacterStyleNames = GetCharacterStyles()->getElementNames(); + sal_Int32 nMaxIndex = 0; + for ( const auto& rStyleName : aCharacterStyleNames ) + { + OUString sSuffix; + if ( rStyleName.startsWith( cListLabel, &sSuffix ) ) + { + sal_Int32 nSuffix = sSuffix.toInt32(); + if( nSuffix > 0 && nSuffix > nMaxIndex ) + nMaxIndex = nSuffix; + } + } + m_xNextUnusedCharacterStyleNo = nMaxIndex + 1; + } + + OUString sPageStyleName = cListLabel + OUString::number( *m_xNextUnusedCharacterStyleNo ); + *m_xNextUnusedCharacterStyleNo = *m_xNextUnusedCharacterStyleNo + 1; + return sPageStyleName; +} + +uno::Reference< text::XText > const & DomainMapper_Impl::GetBodyText() +{ + if(!m_xBodyText.is()) + { + if (m_xInsertTextRange.is()) + m_xBodyText = m_xInsertTextRange->getText(); + else if (m_xTextDocument.is()) + m_xBodyText = m_xTextDocument->getText(); + } + return m_xBodyText; +} + + +uno::Reference< beans::XPropertySet > const & DomainMapper_Impl::GetDocumentSettings() +{ + if( !m_xDocumentSettings.is() && m_xTextFactory.is()) + { + m_xDocumentSettings.set( m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY ); + } + return m_xDocumentSettings; +} + + +void DomainMapper_Impl::SetDocumentSettingsProperty( const OUString& rPropName, const uno::Any& rValue ) +{ + uno::Reference< beans::XPropertySet > xSettings = GetDocumentSettings(); + if( xSettings.is() ) + { + try + { + xSettings->setPropertyValue( rPropName, rValue ); + } + catch( const uno::Exception& ) + { + } + } +} +void DomainMapper_Impl::RemoveDummyParaForTableInSection() +{ + SetIsDummyParaAddedForTableInSection(false); + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION); + SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + if (!pSectionContext) + return; + + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + return; + + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(pSectionContext->GetStartingRange()); + + // Remove the extra NumPicBullets from the document, + // which get attached to the first paragraph in the + // document + ListsManager::Pointer pListTable = GetListTable(); + pListTable->DisposeNumPicBullets(); + + uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY); + if (xEnumerationAccess.is() && m_aTextAppendStack.size() == 1 ) + { + uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration(); + uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY); + xParagraph->dispose(); + } +} +void DomainMapper_Impl::AddDummyParaForTableInSection() +{ + // Shapes and textboxes can't have sections. + if (IsInShape() || m_bIsInTextBox) + return; + + if (!m_aTextAppendStack.empty()) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + xTextAppend->finishParagraph( uno::Sequence< beans::PropertyValue >() ); + SetIsDummyParaAddedForTableInSection(true); + } + } +} + + static OUString lcl_FindLastBookmark(const uno::Reference<text::XTextCursor>& xCursor) + { + OUString sName; + if (!xCursor.is()) + return sName; + + // Select 1 previous element + xCursor->goLeft(1, true); + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY); + if (!xParaEnumAccess.is()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through selection paragraphs + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + if (!xParaEnum->hasMoreElements()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through first para portions + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), + uno::UNO_QUERY_THROW); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xProps(xRunEnum->nextElement(), uno::UNO_QUERY_THROW); + uno::Any aType(xProps->getPropertyValue("TextPortionType")); + OUString sType; + aType >>= sType; + if (sType == "Bookmark") + { + uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue("Bookmark"), + uno::UNO_QUERY_THROW); + sName = xBookmark->getName(); + // Do not stop the scan here. Maybe there are 2 bookmarks? + } + } + + xCursor->goRight(1, true); + return sName; + } + +void DomainMapper_Impl::RemoveLastParagraph( ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + return; + try + { + uno::Reference< text::XTextCursor > xCursor; + if (m_bIsNewDoc) + { + xCursor = xTextAppend->createTextCursor(); + xCursor->gotoEnd(false); + } + else + xCursor = m_aTextAppendStack.top().xCursor; + uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY); + // Keep the character properties of the last but one paragraph, even if + // it's empty. This works for headers/footers, and maybe in other cases + // as well, but surely not in textboxes. + // fdo#58327: also do this at the end of the document: when pasting, + // a table before the cursor position would be deleted + // (but only for paste/insert, not load; otherwise it can happen that + // flys anchored at the disposed paragraph are deleted (fdo47036.rtf)) + bool const bEndOfDocument(m_aTextAppendStack.size() == 1); + + // Try to find and remember last bookmark in document: it potentially + // can be deleted by xCursor->setString() but not by xParagraph->dispose() + OUString sLastBookmarkName; + if (bEndOfDocument) + sLastBookmarkName = lcl_FindLastBookmark(xCursor); + + if ((IsInHeaderFooter() || (bEndOfDocument && !m_bIsNewDoc)) + && xEnumerationAccess.is()) + { + uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration(); + uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY); + xParagraph->dispose(); + } + else if (xCursor.is()) + { + xCursor->goLeft( 1, true ); + // If this is a text on a shape, possibly the text has the trailing + // newline removed already. + if (xCursor->getString() == SAL_NEWLINE_STRING || + // tdf#105444 comments need an exception, if SAL_NEWLINE_STRING defined as "\r\n" + (sizeof(SAL_NEWLINE_STRING)-1 == 2 && xCursor->getString() == "\n")) + { + uno::Reference<beans::XPropertySet> xDocProps(GetTextDocument(), uno::UNO_QUERY); + static const OUStringLiteral aRecordChanges(u"RecordChanges"); + uno::Any aPreviousValue(xDocProps->getPropertyValue(aRecordChanges)); + + // disable redlining for this operation, otherwise we might + // end up with an unwanted recorded deletion + xDocProps->setPropertyValue(aRecordChanges, uno::Any(false)); + + // delete + xCursor->setString(OUString()); + + // While removing paragraphs that contain section properties, reset list + // related attributes to prevent them leaking into the following section's lists + if (GetParaSectpr()) + { + uno::Reference<beans::XPropertySet> XCursorProps(xCursor, uno::UNO_QUERY); + XCursorProps->setPropertyValue("ResetParagraphListAttributes", uno::Any()); + } + + // call to xCursor->setString possibly did remove final bookmark + // from previous paragraph. We need to restore it, if there was any. + if (sLastBookmarkName.getLength()) + { + OUString sBookmarkNameAfterRemoval = lcl_FindLastBookmark(xCursor); + if (sBookmarkNameAfterRemoval.isEmpty()) + { + // Yes, it was removed. Restore + uno::Reference<text::XTextContent> xBookmark( + m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), + uno::UNO_QUERY_THROW); + + uno::Reference<container::XNamed> xBkmNamed(xBookmark, + uno::UNO_QUERY_THROW); + xBkmNamed->setName(sLastBookmarkName); + xTextAppend->insertTextContent( + uno::Reference<text::XTextRange>(xCursor, uno::UNO_QUERY_THROW), + xBookmark, !xCursor->isCollapsed()); + } + } + // restore redline options again + xDocProps->setPropertyValue(aRecordChanges, aPreviousValue); + } + } + } + catch( const uno::Exception& ) + { + } +} + + +void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast ) +{ + m_bIsLastSectionGroup = bIsLast; +} + +void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast ) +{ + m_bIsLastParaInSection = bIsLast; +} + + +void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst ) +{ + m_bIsFirstParaInSection = bIsFirst; +} + +void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline ) +{ + m_bIsFirstParaInSectionAfterRedline = bIsFirstAfterRedline; +} + +bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const +{ + // Anchored objects may include multiple paragraphs, + // and none of them should be considered the first para in section. + return ( bAfterRedline ? m_bIsFirstParaInSectionAfterRedline : m_bIsFirstParaInSection ) + && !IsInShape() + && !m_bIsInComments + && !IsInFootOrEndnote(); +} + +void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst) +{ + m_bIsFirstParaInShape = bIsFirst; +} + +void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded ) +{ + m_bDummyParaAddedForTableInSection = bIsAdded; + m_bDummyParaAddedForTableInSectionPage = bIsAdded; +} + +void DomainMapper_Impl::SetIsDummyParaAddedForTableInSectionPage( bool bIsAdded ) +{ + m_bDummyParaAddedForTableInSectionPage = bIsAdded; +} + + +void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted ) +{ + m_bTextFrameInserted = bIsInserted; +} + + +void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr) +{ + m_bParaSectpr = bParaSectpr; +} + + +void DomainMapper_Impl::SetSdt(bool bSdt) +{ + m_bSdt = bSdt; + + if (m_bSdt && !m_aTextAppendStack.empty()) + { + m_xSdtEntryStart = GetTopTextAppend()->getEnd(); + } + else + { + m_xSdtEntryStart.clear(); + } +} + +void DomainMapper_Impl::PushSdt() +{ + if (m_aTextAppendStack.empty()) + { + return; + } + + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + uno::Reference<text::XTextCursor> xCursor + = xTextAppend->getText()->createTextCursorByRange(xTextAppend->getEnd()); + // Offset so the cursor is not adjusted as we import the SDT's content. + bool bStart = !xCursor->goLeft(1, /*bExpand=*/false); + m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()}); +} + +const std::stack<BookmarkInsertPosition>& DomainMapper_Impl::GetSdtStarts() const +{ + return m_xSdtStarts; +} + +void DomainMapper_Impl::PopSdt() +{ + if (m_xSdtStarts.empty()) + { + return; + } + + BookmarkInsertPosition aPosition = m_xSdtStarts.top(); + m_xSdtStarts.pop(); + uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange; + uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd(); + uno::Reference<text::XText> xText = xEnd->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursorByRange(xStart); + if (!xCursor) + { + SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position"); + return; + } + + if (aPosition.m_bIsStartOfText) + { + // Go to the start of the end's paragraph. This helps in case + // DomainMapper_Impl::AddDummyParaForTableInSection() would make our range multi-paragraph, + // while the intention is to keep start/end inside the same paragraph for run SDTs. + uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY); + if (xParagraphCursor.is()) + { + xCursor->gotoRange(xEnd, /*bExpand=*/false); + xParagraphCursor->gotoStartOfParagraph(/*bExpand=*/false); + } + } + else + { + // Undo the goLeft() in DomainMapper_Impl::PushSdt(); + xCursor->goRight(1, /*bExpand=*/false); + } + xCursor->gotoRange(xEnd, /*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + if (m_pSdtHelper->GetShowingPlcHdr()) + { + xContentControlProps->setPropertyValue("ShowingPlaceHolder", + uno::Any(m_pSdtHelper->GetShowingPlcHdr())); + } + + if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty()) + { + xContentControlProps->setPropertyValue("PlaceholderDocPart", + uno::Any(m_pSdtHelper->GetPlaceholderDocPart())); + } + + if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingPrefixMappings", + uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping())); + } + if (!m_pSdtHelper->GetDataBindingXPath().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingXpath", + uno::Any(m_pSdtHelper->GetDataBindingXPath())); + } + if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingStoreItemID", + uno::Any(m_pSdtHelper->GetDataBindingStoreItemID())); + } + + if (!m_pSdtHelper->GetColor().isEmpty()) + { + xContentControlProps->setPropertyValue("Color", + uno::Any(m_pSdtHelper->GetColor())); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::checkBox) + { + xContentControlProps->setPropertyValue("Checkbox", uno::Any(true)); + + xContentControlProps->setPropertyValue("Checked", uno::Any(m_pSdtHelper->GetChecked())); + + xContentControlProps->setPropertyValue("CheckedState", + uno::Any(m_pSdtHelper->GetCheckedState())); + + xContentControlProps->setPropertyValue("UncheckedState", + uno::Any(m_pSdtHelper->GetUncheckedState())); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::dropDown) + { + std::vector<OUString>& rDisplayTexts = m_pSdtHelper->getDropDownDisplayTexts(); + std::vector<OUString>& rValues = m_pSdtHelper->getDropDownItems(); + if (rDisplayTexts.size() == rValues.size()) + { + uno::Sequence<beans::PropertyValues> aItems(rValues.size()); + beans::PropertyValues* pItems = aItems.getArray(); + for (size_t i = 0; i < rValues.size(); ++i) + { + uno::Sequence<beans::PropertyValue> aItem = { + comphelper::makePropertyValue("DisplayText", rDisplayTexts[i]), + comphelper::makePropertyValue("Value", rValues[i]), + }; + pItems[i] = aItem; + } + xContentControlProps->setPropertyValue("ListItems", uno::Any(aItems)); + } + } + + if (m_pSdtHelper->getControlType() == SdtControlType::picture) + { + xContentControlProps->setPropertyValue("Picture", uno::Any(true)); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::datePicker) + { + xContentControlProps->setPropertyValue("Date", uno::Any(true)); + OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear(); + xContentControlProps->setPropertyValue("DateFormat", + uno::Any(aDateFormat.replaceAll("'", "\""))); + xContentControlProps->setPropertyValue("DateLanguage", + uno::Any(m_pSdtHelper->getLocale().makeStringAndClear())); + xContentControlProps->setPropertyValue("CurrentDate", + uno::Any(m_pSdtHelper->getDate().makeStringAndClear())); + } + + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + + m_pSdtHelper->clear(); +} + +void DomainMapper_Impl::PushProperties(ContextType eId) +{ + PropertyMapPtr pInsert(eId == CONTEXT_SECTION ? + (new SectionPropertyMap( m_bIsFirstSection )) : + eId == CONTEXT_PARAGRAPH ? new ParagraphPropertyMap : new PropertyMap); + if(eId == CONTEXT_SECTION) + { + if( m_bIsFirstSection ) + m_bIsFirstSection = false; + // beginning with the second section group a section has to be inserted + // into the document + SectionPropertyMap* pSectionContext_ = dynamic_cast< SectionPropertyMap* >( pInsert.get() ); + if (!m_aTextAppendStack.empty()) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is() && pSectionContext_) + pSectionContext_->SetStart( xTextAppend->getEnd() ); + } + } + if(eId == CONTEXT_PARAGRAPH && m_bIsSplitPara) + { + m_aPropertyStacks[eId].push( GetTopContextOfType(eId)); + m_bIsSplitPara = false; + } + else + { + m_aPropertyStacks[eId].push( pInsert ); + } + m_aContextStack.push(eId); + + m_pTopContext = m_aPropertyStacks[eId].top(); +} + + +void DomainMapper_Impl::PushStyleProperties( const PropertyMapPtr& pStyleProperties ) +{ + m_aPropertyStacks[CONTEXT_STYLESHEET].push( pStyleProperties ); + m_aContextStack.push(CONTEXT_STYLESHEET); + + m_pTopContext = m_aPropertyStacks[CONTEXT_STYLESHEET].top(); +} + + +void DomainMapper_Impl::PushListProperties(const PropertyMapPtr& pListProperties) +{ + m_aPropertyStacks[CONTEXT_LIST].push( pListProperties ); + m_aContextStack.push(CONTEXT_LIST); + m_pTopContext = m_aPropertyStacks[CONTEXT_LIST].top(); +} + + +void DomainMapper_Impl::PopProperties(ContextType eId) +{ + OSL_ENSURE(!m_aPropertyStacks[eId].empty(), "section stack already empty"); + if ( m_aPropertyStacks[eId].empty() ) + return; + + if ( eId == CONTEXT_SECTION ) + { + if (m_aPropertyStacks[eId].size() == 1) // tdf#112202 only top level !!! + { + m_pLastSectionContext = m_aPropertyStacks[eId].top(); + } + } + else if (eId == CONTEXT_CHARACTER) + { + m_pLastCharacterContext = m_aPropertyStacks[eId].top(); + // Sadly an assert about deferredCharacterProperties being empty is not possible + // here, because appendTextPortion() may not be called for every character section. + deferredCharacterProperties.clear(); + } + + if (!IsInFootOrEndnote() && IsInCustomFootnote() && !m_aPropertyStacks[eId].empty()) + { + PropertyMapPtr pRet = m_aPropertyStacks[eId].top(); + if (pRet->GetFootnote().is() && m_pFootnoteContext.is()) + EndCustomFootnote(); + } + + m_aPropertyStacks[eId].pop(); + m_aContextStack.pop(); + if(!m_aContextStack.empty() && !m_aPropertyStacks[m_aContextStack.top()].empty()) + + m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top(); + else + { + // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section context end"); + m_pTopContext.clear(); + } +} + + +PropertyMapPtr DomainMapper_Impl::GetTopContextOfType(ContextType eId) +{ + PropertyMapPtr pRet; + if(!m_aPropertyStacks[eId].empty()) + pRet = m_aPropertyStacks[eId].top(); + return pRet; +} + +bool DomainMapper_Impl::HasTopText() const +{ + return !m_aTextAppendStack.empty(); +} + +uno::Reference< text::XTextAppend > const & DomainMapper_Impl::GetTopTextAppend() +{ + OSL_ENSURE(!m_aTextAppendStack.empty(), "text append stack is empty" ); + return m_aTextAppendStack.top().xTextAppend; +} + +FieldContextPtr const & DomainMapper_Impl::GetTopFieldContext() +{ + SAL_WARN_IF(m_aFieldStack.empty(), "writerfilter.dmapper", "Field stack is empty"); + return m_aFieldStack.back(); +} + +bool DomainMapper_Impl::HasTopAnchoredObjects() const +{ + return !m_aTextAppendStack.empty() && !m_aTextAppendStack.top().m_aAnchoredObjects.empty(); +} + +void DomainMapper_Impl::InitTabStopFromStyle( const uno::Sequence< style::TabStop >& rInitTabStops ) +{ + OSL_ENSURE(m_aCurrentTabStops.empty(), "tab stops already initialized"); + for( const auto& rTabStop : rInitTabStops) + { + m_aCurrentTabStops.emplace_back(rTabStop); + } +} + +void DomainMapper_Impl::IncorporateTabStop( const DeletableTabStop & rTabStop ) +{ + sal_Int32 nConverted = rTabStop.Position; + auto aIt = std::find_if(m_aCurrentTabStops.begin(), m_aCurrentTabStops.end(), + [&nConverted](const DeletableTabStop& rCurrentTabStop) { return rCurrentTabStop.Position == nConverted; }); + if( aIt != m_aCurrentTabStops.end() ) + { + if( rTabStop.bDeleted ) + m_aCurrentTabStops.erase( aIt ); + else + *aIt = rTabStop; + } + else + m_aCurrentTabStops.push_back( rTabStop ); +} + + +uno::Sequence< style::TabStop > DomainMapper_Impl::GetCurrentTabStopAndClear() +{ + std::vector<style::TabStop> aRet; + for (const DeletableTabStop& rStop : m_aCurrentTabStops) + { + if (!rStop.bDeleted) + aRet.push_back(rStop); + } + m_aCurrentTabStops.clear(); + return comphelper::containerToSequence(aRet); +} + +OUString DomainMapper_Impl::GetCurrentParaStyleName() +{ + OUString sName; + // use saved currParaStyleName as a fallback, in case no particular para style name applied. + // tdf#134784 except in the case of first paragraph of shapes to avoid bad fallback. + // TODO fix this "highly inaccurate" m_sCurrentParaStyleName + if ( !IsInShape() ) + sName = m_sCurrentParaStyleName; + + PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH); + if ( pParaContext && pParaContext->isSet(PROP_PARA_STYLE_NAME) ) + pParaContext->getProperty(PROP_PARA_STYLE_NAME)->second >>= sName; + + // In rare situations the name might still be blank, so use the default style, + // despite documentation that states, "If this attribute is not specified for any style, + // then no properties shall be applied to objects of the specified type." + // Word, however, assigns "Normal" style even in these situations. + if ( !m_bInStyleSheetImport && sName.isEmpty() ) + sName = GetDefaultParaStyleName(); + + return sName; +} + +OUString DomainMapper_Impl::GetDefaultParaStyleName() +{ + // After import the default style won't change and is frequently requested: cache the LO style name. + // TODO assert !InStyleSheetImport? This function really only makes sense once import is finished anyway. + if ( m_sDefaultParaStyleName.isEmpty() ) + { + const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindDefaultParaStyle(); + if ( pEntry && !pEntry->sConvertedStyleName.isEmpty() ) + { + if ( !m_bInStyleSheetImport ) + m_sDefaultParaStyleName = pEntry->sConvertedStyleName; + return pEntry->sConvertedStyleName; + } + else + return "Standard"; + } + return m_sDefaultParaStyleName; +} + +uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault) +{ + while(pEntry) + { + if(pEntry->pProperties) + { + std::optional<PropertyMap::Property> aProperty = + pEntry->pProperties->getProperty(eId); + if( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = pEntry->pProperties->isDocDefault(eId); + + return aProperty->second; + } + } + //search until the property is set or no parent is available + StyleSheetEntryPtr pNewEntry; + if ( !pEntry->sBaseStyleIdentifier.isEmpty() ) + pNewEntry = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + + SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?"); + + if (pEntry == pNewEntry) //fdo#49587 + break; + + pEntry = pNewEntry; + } + // not found in style, try the document's DocDefault properties + if ( bDocDefaults && bPara ) + { + const PropertyMapPtr& pDefaultParaProps = GetStyleSheetTable()->GetDefaultParaProps(); + if ( pDefaultParaProps ) + { + std::optional<PropertyMap::Property> aProperty = pDefaultParaProps->getProperty(eId); + if ( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = true; + + return aProperty->second; + } + } + } + if ( bDocDefaults && isCharacterProperty(eId) ) + { + const PropertyMapPtr& pDefaultCharProps = GetStyleSheetTable()->GetDefaultCharProps(); + if ( pDefaultCharProps ) + { + std::optional<PropertyMap::Property> aProperty = pDefaultCharProps->getProperty(eId); + if ( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = true; + + return aProperty->second; + } + } + } + + if (pIsDocDefault) + *pIsDocDefault = false; + + return uno::Any(); +} + +uno::Any DomainMapper_Impl::GetPropertyFromParaStyleSheet(PropertyIds eId) +{ + StyleSheetEntryPtr pEntry; + if ( m_bInStyleSheetImport ) + pEntry = GetStyleSheetTable()->GetCurrentEntry(); + else + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName()); + return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true); +} + +uno::Any DomainMapper_Impl::GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext) +{ + if ( m_bInStyleSheetImport || eId == PROP_CHAR_STYLE_NAME || !isCharacterProperty(eId) ) + return uno::Any(); + + StyleSheetEntryPtr pEntry; + OUString sCharStyleName; + if ( GetAnyProperty(PROP_CHAR_STYLE_NAME, rContext) >>= sCharStyleName ) + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sCharStyleName); + return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/false, /*bPara=*/false); +} + +uno::Any DomainMapper_Impl::GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext) +{ + // first look in directly applied attributes + if ( rContext ) + { + std::optional<PropertyMap::Property> aProperty = rContext->getProperty(eId); + if ( aProperty ) + return aProperty->second; + } + + // then look whether it was directly applied as a paragraph property + PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH); + if (pParaContext && rContext != pParaContext) + { + std::optional<PropertyMap::Property> aProperty = pParaContext->getProperty(eId); + if (aProperty) + return aProperty->second; + } + + // then look whether it was inherited from a directly applied character style + if ( eId != PROP_CHAR_STYLE_NAME && isCharacterProperty(eId) ) + { + uno::Any aRet = GetPropertyFromCharStyleSheet(eId, rContext); + if ( aRet.hasValue() ) + return aRet; + } + + // then look in current paragraph style, and docDefaults + return GetPropertyFromParaStyleSheet(eId); +} + +OUString DomainMapper_Impl::GetListStyleName(sal_Int32 nListId) +{ + auto const pList(GetListTable()->GetList( nListId )); + return pList ? pList->GetStyleName() : OUString(); +} + +ListsManager::Pointer const & DomainMapper_Impl::GetListTable() +{ + if(!m_pListTable) + m_pListTable = + new ListsManager( m_rDMapper, m_xTextFactory ); + return m_pListTable; +} + + +void DomainMapper_Impl::deferBreak( BreakType deferredBreakType) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + m_nLineBreaksDeferred++; + break; + case COLUMN_BREAK: + m_bIsColumnBreakDeferred = true; + break; + case PAGE_BREAK: + // See SwWW8ImplReader::HandlePageBreakChar(), page break should be + // ignored inside tables. + if (m_nTableDepth > 0) + return; + + m_bIsPageBreakDeferred = true; + break; + default: + return; + } +} + +bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType ) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + return m_nLineBreaksDeferred > 0; + case COLUMN_BREAK: + return m_bIsColumnBreakDeferred; + case PAGE_BREAK: + return m_bIsPageBreakDeferred; + default: + return false; + } +} + +void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + assert(m_nLineBreaksDeferred > 0); + m_nLineBreaksDeferred--; + break; + case COLUMN_BREAK: + m_bIsColumnBreakDeferred = false; + break; + case PAGE_BREAK: + m_bIsPageBreakDeferred = false; + break; + default: + break; + } +} + +void DomainMapper_Impl::clearDeferredBreaks() +{ + m_nLineBreaksDeferred = 0; + m_bIsColumnBreakDeferred = false; + m_bIsPageBreakDeferred = false; +} + +void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred) +{ + m_bSdtEndDeferred = bSdtEndDeferred; +} + +bool DomainMapper_Impl::isSdtEndDeferred() const +{ + return m_bSdtEndDeferred; +} + +void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred) +{ + m_bParaSdtEndDeferred = bParaSdtEndDeferred; +} + +bool DomainMapper_Impl::isParaSdtEndDeferred() const +{ + return m_bParaSdtEndDeferred; +} + +static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties, + uno::Reference<text::XTextRange> const& xStartTextRange, + uno::Reference<text::XTextRange> const& xEndTextRange ) +{ + try + { + if (!xStartTextRange.is()) //rhbz#1077780 + return; + uno::Reference<text::XTextCursor> xRangeCursor = xStartTextRange->getText()->createTextCursorByRange( xStartTextRange ); + xRangeCursor->gotoRange( xEndTextRange, true ); + + uno::Reference<beans::XPropertySet> xTextRangeProperties(xRangeCursor, uno::UNO_QUERY); + if(!xTextRangeProperties.is()) + return ; + + static PropertyIds const aBorderProperties[] = + { + PROP_LEFT_BORDER, + PROP_RIGHT_BORDER, + PROP_TOP_BORDER, + PROP_BOTTOM_BORDER, + PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_BORDER_DISTANCE, + PROP_BOTTOM_BORDER_DISTANCE + }; + + for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty) + { + OUString sPropertyName = getPropertyName(aBorderProperties[nProperty]); + beans::PropertyValue aValue; + aValue.Name = sPropertyName; + aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName); + rFrameProperties.push_back(aValue); + if( nProperty < 4 ) + xTextRangeProperties->setPropertyValue( sPropertyName, uno::Any(table::BorderLine2())); + } + } + catch( const uno::Exception& ) + { + } +} + + +static void lcl_AddRangeAndStyle( + ParagraphPropertiesPtr const & pToBeSavedProperties, + uno::Reference< text::XTextAppend > const& xTextAppend, + const PropertyMapPtr& pPropertyMap, + TextAppendContext const & rAppendContext) +{ + uno::Reference<text::XParagraphCursor> xParaCursor( + xTextAppend->createTextCursorByRange( rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()), uno::UNO_QUERY_THROW ); + pToBeSavedProperties->SetEndingRange(xParaCursor->getStart()); + xParaCursor->gotoStartOfParagraph( false ); + + pToBeSavedProperties->SetStartingRange(xParaCursor->getStart()); + if(pPropertyMap) + { + std::optional<PropertyMap::Property> aParaStyle = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME); + if( aParaStyle ) + { + OUString sName; + aParaStyle->second >>= sName; + pToBeSavedProperties->SetParaStyleName(sName); + } + } +} + + +//define some default frame width - 0cm ATM: this allow the frame to be wrapped around the text +constexpr sal_Int32 DEFAULT_FRAME_MIN_WIDTH = 0; +constexpr sal_Int32 DEFAULT_FRAME_MIN_HEIGHT = 0; +constexpr sal_Int32 DEFAULT_VALUE = 0; + +void DomainMapper_Impl::CheckUnregisteredFrameConversion( ) +{ + if (m_aTextAppendStack.empty()) + return; + TextAppendContext& rAppendContext = m_aTextAppendStack.top(); + // n#779642: ignore fly frame inside table as it could lead to messy situations + if (!rAppendContext.pLastParagraphProperties) + return; + if (!rAppendContext.pLastParagraphProperties->IsFrameMode()) + return; + if (!hasTableManager()) + return; + if (getTableManager().isInTable()) + return; + try + { + StyleSheetEntryPtr pParaStyle = + GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rAppendContext.pLastParagraphProperties->GetParaStyleName()); + + std::vector<beans::PropertyValue> aFrameProperties; + + if ( pParaStyle ) + { + const StyleSheetPropertyMap* pStyleProperties = pParaStyle->pProperties.get(); + if (!pStyleProperties) + return; + sal_Int32 nWidth = + rAppendContext.pLastParagraphProperties->Getw() > 0 ? + rAppendContext.pLastParagraphProperties->Getw() : + pStyleProperties->Getw(); + bool bAutoWidth = nWidth < 1; + if( bAutoWidth ) + nWidth = DEFAULT_FRAME_MIN_WIDTH; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), + rAppendContext.pLastParagraphProperties->Geth() > 0 ? + rAppendContext.pLastParagraphProperties->Geth() : + pStyleProperties->Geth() > 0 ? pStyleProperties->Geth() : DEFAULT_FRAME_MIN_HEIGHT)); + + sal_Int16 nhRule = sal_Int16( + rAppendContext.pLastParagraphProperties->GethRule() >= 0 ? + rAppendContext.pLastParagraphProperties->GethRule() : + pStyleProperties->GethRule()); + if ( nhRule < 0 ) + { + if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 || + pStyleProperties->GethRule() >= 0 ) + { + // [MS-OE376] Word uses a default value of "atLeast" for + // this attribute when the value of the h attribute is not 0. + nhRule = text::SizeType::MIN; + } + else + { + nhRule = text::SizeType::VARIABLE; + } + } + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX)); + + if (const std::optional<sal_Int16> nDirection = PopFrameDirection()) + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_FRM_DIRECTION), *nDirection)); + } + + sal_Int16 nHoriOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetxAlign() : + pStyleProperties->GetxAlign() >= 0 ? pStyleProperties->GetxAlign() : text::HoriOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient)); + + //set a non negative default value + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), + rAppendContext.pLastParagraphProperties->IsxValid() ? + rAppendContext.pLastParagraphProperties->Getx() : + pStyleProperties->IsxValid() ? pStyleProperties->Getx() : DEFAULT_VALUE)); + + //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11 + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GethAnchor() : + pStyleProperties->GethAnchor() >=0 ? pStyleProperties->GethAnchor() : text::RelOrientation::FRAME ))); + + sal_Int16 nVertOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetyAlign() : + pStyleProperties->GetyAlign() >= 0 ? pStyleProperties->GetyAlign() : text::VertOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient)); + + //set a non negative default value + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), + rAppendContext.pLastParagraphProperties->IsyValid() ? + rAppendContext.pLastParagraphProperties->Gety() : + pStyleProperties->IsyValid() ? pStyleProperties->Gety() : DEFAULT_VALUE)); + + //Default the anchor in case FramePr_vAnchor is missing ECMA 17.3.1.11 + if (rAppendContext.pLastParagraphProperties->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE && + pStyleProperties->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE) + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvAnchor() : + pStyleProperties->GetvAnchor() >= 0 ? pStyleProperties->GetvAnchor() : text::RelOrientation::FRAME))); + } + else + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvAnchor() : + pStyleProperties->GetvAnchor() >= 0 ? pStyleProperties->GetvAnchor() : text::RelOrientation::PAGE_PRINT_AREA))); + } + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SURROUND), + rAppendContext.pLastParagraphProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE + ? rAppendContext.pLastParagraphProperties->GetWrap() + : pStyleProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE + ? pStyleProperties->GetWrap() + : text::WrapTextMode_NONE )); + + /** FDO#73546 : distL & distR should be unsigned integers <Ecma 20.4.3.6> + Swapped the array elements 11,12 & 13,14 since 11 & 12 are + LEFT & RIGHT margins and 13,14 are TOP and BOTTOM margins respectively. + */ + sal_Int32 nRightDist; + sal_Int32 nLeftDist = nRightDist = + rAppendContext.pLastParagraphProperties->GethSpace() >= 0 ? + rAppendContext.pLastParagraphProperties->GethSpace() : + pStyleProperties->GethSpace() >= 0 ? pStyleProperties->GethSpace() : 0; + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nLeftDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nRightDist)); + + sal_Int32 nBottomDist; + sal_Int32 nTopDist = nBottomDist = + rAppendContext.pLastParagraphProperties->GetvSpace() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvSpace() : + pStyleProperties->GetvSpace() >= 0 ? pStyleProperties->GetvSpace() : 0; + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nTopDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nBottomDist)); + // If there is no fill, the Word default is 100% transparency. + // Otherwise CellColorHandler has priority, and this setting + // will be ignored. + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BACK_COLOR_TRANSPARENCY), sal_Int32(100))); + + uno::Sequence<beans::PropertyValue> aGrabBag( comphelper::InitPropertySequence({ + { "ParaFrameProperties", uno::Any(rAppendContext.pLastParagraphProperties->IsFrameMode()) } + })); + aFrameProperties.push_back(comphelper::makePropertyValue("FrameInteropGrabBag", aGrabBag)); + + lcl_MoveBorderPropertiesToFrame(aFrameProperties, + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange()); + } + else + { + sal_Int32 nWidth = rAppendContext.pLastParagraphProperties->Getw(); + bool bAutoWidth = nWidth < 1; + if( bAutoWidth ) + nWidth = DEFAULT_FRAME_MIN_WIDTH; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth)); + + sal_Int16 nhRule = sal_Int16(rAppendContext.pLastParagraphProperties->GethRule()); + if ( nhRule < 0 ) + { + if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 ) + { + // [MS-OE376] Word uses a default value of atLeast for + // this attribute when the value of the h attribute is not 0. + nhRule = text::SizeType::MIN; + } + else + { + nhRule = text::SizeType::VARIABLE; + } + } + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX)); + + sal_Int16 nHoriOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetxAlign() : + text::HoriOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient)); + + sal_Int16 nVertOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetyAlign() : + text::VertOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient)); + + sal_Int32 nVertDist = rAppendContext.pLastParagraphProperties->GethSpace(); + if( nVertDist < 0 ) + nVertDist = 0; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nVertDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nVertDist)); + + sal_Int32 nHoriDist = rAppendContext.pLastParagraphProperties->GetvSpace(); + if( nHoriDist < 0 ) + nHoriDist = 0; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nHoriDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nHoriDist)); + + if( rAppendContext.pLastParagraphProperties->Geth() > 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), rAppendContext.pLastParagraphProperties->Geth())); + + if( rAppendContext.pLastParagraphProperties->IsxValid() ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Getx())); + + if( rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue("HoriOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GethAnchor()))); + + if( rAppendContext.pLastParagraphProperties->IsyValid() ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Gety())); + + if( rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue("VertOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GetvAnchor()))); + + if( rAppendContext.pLastParagraphProperties->GetWrap() >= text::WrapTextMode_NONE ) + aFrameProperties.push_back(comphelper::makePropertyValue("Surround", rAppendContext.pLastParagraphProperties->GetWrap())); + + lcl_MoveBorderPropertiesToFrame(aFrameProperties, + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange()); + } + + //frame conversion has to be executed after table conversion + RegisterFrameConversion( + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange(), + std::move(aFrameProperties) ); + } + catch( const uno::Exception& ) + { + } +} + +/// Check if the style or its parent has a list id, recursively. +static sal_Int32 lcl_getListId(const StyleSheetEntryPtr& rEntry, const StyleSheetTablePtr& rStyleTable, bool & rNumberingFromBaseStyle) +{ + const StyleSheetPropertyMap* pEntryProperties = rEntry->pProperties.get(); + if (!pEntryProperties) + return -1; + + sal_Int32 nListId = pEntryProperties->GetListId(); + // The style itself has a list id. + if (nListId >= 0) + return nListId; + + // The style has no parent. + if (rEntry->sBaseStyleIdentifier.isEmpty()) + return -1; + + const StyleSheetEntryPtr pParent = rStyleTable->FindStyleSheetByISTD(rEntry->sBaseStyleIdentifier); + // No such parent style or loop in the style hierarchy. + if (!pParent || pParent == rEntry) + return -1; + + rNumberingFromBaseStyle = true; + + return lcl_getListId(pParent, rStyleTable, rNumberingFromBaseStyle); +} + +/// Return the paragraph's list level (from styles, unless pParacontext is provided). +/// -1 indicates the level is not set anywhere. [In that case, with a numId, use 0 (level 1)] +/// 9 indicates that numbering should be at body level (aka disabled) - rarely used by MSWord. +/// 0-8 are the nine valid numbering levels. +sal_Int16 DomainMapper_Impl::GetListLevel(const StyleSheetEntryPtr& pEntry, + const PropertyMapPtr& pParaContext) +{ + sal_Int16 nListLevel = -1; + if (pParaContext) + { + // Deliberately ignore inherited PROP_NUMBERING_LEVEL. Only trust StyleSheetEntry for that. + std::optional<PropertyMap::Property> aLvl = pParaContext->getProperty(PROP_NUMBERING_LEVEL); + if (aLvl) + aLvl->second >>= nListLevel; + + if (nListLevel != -1) + return nListLevel; + } + + if (!pEntry) + return -1; + + const StyleSheetPropertyMap* pEntryProperties = pEntry->pProperties.get(); + if (!pEntryProperties) + return -1; + + nListLevel = pEntryProperties->GetListLevel(); + // The style itself has a list level. + if (nListLevel >= 0) + return nListLevel; + + // The style has no parent. + if (pEntry->sBaseStyleIdentifier.isEmpty()) + return -1; + + const StyleSheetEntryPtr pParent = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + // No such parent style or loop in the style hierarchy. + if (!pParent || pParent == pEntry) + return -1; + + return GetListLevel(pParent); +} + +void DomainMapper_Impl::ValidateListLevel(const OUString& sStyleIdentifierD) +{ + StyleSheetEntryPtr pMyStyle = GetStyleSheetTable()->FindStyleSheetByISTD(sStyleIdentifierD); + if (!pMyStyle) + return; + + sal_Int8 nListLevel = GetListLevel(pMyStyle); + if (nListLevel < 0 || nListLevel >= WW_OUTLINE_MAX) + return; + + bool bDummy = false; + sal_Int16 nListId = lcl_getListId(pMyStyle, GetStyleSheetTable(), bDummy); + if (nListId < 1) + return; + + auto const pList(GetListTable()->GetList(nListId)); + if (!pList) + return; + + auto pLevel = pList->GetLevel(nListLevel); + if (!pLevel && pList->GetAbstractDefinition()) + pLevel = pList->GetAbstractDefinition()->GetLevel(nListLevel); + if (!pLevel) + return; + + if (!pLevel->GetParaStyle()) + { + // First come, first served, and it hasn't been claimed yet, so claim it now. + pLevel->SetParaStyle(pMyStyle); + } + else if (pLevel->GetParaStyle() != pMyStyle) + { + // This level is already used by another style, so prevent numbering via this style + // by setting to body level (9). + pMyStyle->pProperties->SetListLevel(WW_OUTLINE_MAX); + // WARNING: PROP_NUMBERING_LEVEL is now out of sync with GetListLevel() + } +} + +void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove, const bool bNoNumbering ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (!m_aFieldStack.empty()) + { + FieldContextPtr pFieldContext = m_aFieldStack.back(); + if (pFieldContext && !pFieldContext->IsCommandCompleted()) + { + std::vector<OUString> aCommandParts = pFieldContext->GetCommandParts(); + if (!aCommandParts.empty() && aCommandParts[0] == "IF") + { + // Conditional text field conditions don't support linebreaks in Writer. + return; + } + } + + if (pFieldContext && pFieldContext->IsCommandCompleted()) + { + if (pFieldContext->GetFieldId() == FIELD_IF) + { + // Conditional text fields can't contain newlines, finish the paragraph later. + FieldParagraph aFinish{pPropertyMap, bRemove}; + pFieldContext->GetParagraphsToFinish().push_back(aFinish); + return; + } + } + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("finishParagraph"); +#endif + + m_nLastTableCellParagraphDepth = m_nTableCellDepth; + ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pPropertyMap.get() ); + if (m_aTextAppendStack.empty()) + return; + TextAppendContext& rAppendContext = m_aTextAppendStack.top(); + uno::Reference< text::XTextAppend > xTextAppend(rAppendContext.xTextAppend); +#ifdef DBG_UTIL + TagLogger::getInstance().attribute("isTextAppend", sal_uInt32(xTextAppend.is())); +#endif + + const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetCurrentParaStyleName() ); + OSL_ENSURE( pEntry, "no style sheet found" ); + const StyleSheetPropertyMap* pStyleSheetProperties = pEntry ? pEntry->pProperties.get() : nullptr; + sal_Int32 nListId = pParaContext ? pParaContext->GetListId() : -1; + bool isNumberingViaStyle(false); + bool isNumberingViaRule = nListId > -1; + if ( !bRemove && pStyleSheetProperties && pParaContext ) + { + bool bNumberingFromBaseStyle = false; + if (!isNumberingViaRule) + nListId = lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle); + + //apply numbering level/style to paragraph if it was set at the style, but only if the paragraph itself + //does not specify the numbering + sal_Int16 nListLevel = GetListLevel(pEntry, pParaContext); + // Undefined listLevel with a valid numId is treated as a first level numbering. + if (nListLevel == -1 && nListId > (IsOOXMLImport() ? 0 : -1)) + nListLevel = 0; + + if (!bNoNumbering && nListLevel >= 0 && nListLevel < 9) + pParaContext->Insert( PROP_NUMBERING_LEVEL, uno::Any(nListLevel), false ); + + auto const pList(GetListTable()->GetList(nListId)); + if (pList && !pParaContext->isSet(PROP_NUMBERING_STYLE_NAME)) + { + // ListLevel 9 means Body Level/no numbering. + if (bNoNumbering || nListLevel == 9) + { + pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(OUString()), true); + pParaContext->Erase(PROP_NUMBERING_LEVEL); + } + else if ( !isNumberingViaRule ) + { + isNumberingViaStyle = true; + // Since LO7.0/tdf#131321 fixed the loss of numbering in styles, this OUGHT to be obsolete, + // but now other new/critical LO7.0 code expects it, and perhaps some corner cases still need it as well. + pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true); + } + else + { + // we have direct numbering, as well as paragraph-style numbering. + // Apply the style if it uses the same list as the direct numbering, + // otherwise the directly-applied-to-paragraph status will be lost, + // and the priority of the numbering-style-indents will be lowered. tdf#133000 + bool bDummy; + if (nListId == lcl_getListId(pEntry, GetStyleSheetTable(), bDummy)) + pParaContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true ); + } + } + + if ( isNumberingViaStyle ) + { + // When numbering is defined by the paragraph style, then the para-style indents have priority. + // But since import has just copied para-style's PROP_NUMBERING_STYLE_NAME directly onto the paragraph, + // the numbering indents now have the priority. + // So now import must also copy the para-style indents directly onto the paragraph to compensate. + std::optional<PropertyMap::Property> oProperty; + const StyleSheetEntryPtr pParent = (!pEntry->sBaseStyleIdentifier.isEmpty()) ? GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier) : nullptr; + const StyleSheetPropertyMap* pParentProperties = pParent ? pParent->pProperties.get() : nullptr; + if (!pEntry->sBaseStyleIdentifier.isEmpty()) + { + oProperty = pStyleSheetProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT); + if ( oProperty + // If the numbering comes from a base style, indent of the base style has also priority. + || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))) ) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false); + } + oProperty = pStyleSheetProperties->getProperty(PROP_PARA_LEFT_MARGIN); + if ( oProperty + || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_LEFT_MARGIN))) ) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, oProperty->second, /*bOverwrite=*/false); + + // We're inheriting properties from a numbering style. Make sure a possible right margin is inherited from the base style. + sal_Int32 nParaRightMargin; + if ( pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_RIGHT_MARGIN)) && (nParaRightMargin = oProperty->second.get<sal_Int32>()) != 0 ) + { + // If we're setting the right margin, we should set the first / left margin as well from the numbering style. + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent"); + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt"); + if (nFirstLineIndent != 0) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + if (nParaLeftMargin != 0) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + + // Override right margin value with value from current style, if any + if (pStyleSheetProperties && pStyleSheetProperties->isSet(PROP_PARA_RIGHT_MARGIN)) + nParaRightMargin = pStyleSheetProperties->getProperty(PROP_PARA_RIGHT_MARGIN)->second.get<sal_Int32>(); + + pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, uno::Any(nParaRightMargin), /*bOverwrite=*/false); + } + } + // Paragraph style based right paragraph indentation affects not paragraph style based lists in DOCX. + // Apply it as direct formatting, also left and first line indentation of numbering to keep them. + else if (isNumberingViaRule) + { + uno::Any aRightMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN); + if ( aRightMargin != uno::Any() ) + { + pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, aRightMargin, /*bOverwrite=*/false); + + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent"); + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt"); + if (nFirstLineIndent != 0) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + if (nParaLeftMargin != 0) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + } + } + + if (nListId == 0 && !pList) + { + // listid = 0 and no list definition is used in DOCX to stop numbering + // defined somewhere in parent styles + // And here we should explicitly set left margin and first-line margin. + // They can be taken from referred style, but not from styles with listid! + uno::Any aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_FIRST_LINE_INDENT, pEntry, m_pStyleSheetTable); + if (aProp.hasValue()) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, aProp, false); + else + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)), false); + + aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_LEFT_MARGIN, pEntry, m_pStyleSheetTable); + if (aProp.hasValue()) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, aProp, false); + else + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(sal_uInt32(0)), false); + } + } + + // apply AutoSpacing: it has priority over all other margin settings + // (note that numbering with autoSpacing is handled separately later on) + const bool bAllowAdjustments = !GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing(); + sal_Int32 nBeforeAutospacing = -1; + bool bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING); + const bool bNoTopmargin = pParaContext && !pParaContext->isSet(PROP_PARA_TOP_MARGIN); + // apply INHERITED autospacing only if top margin is not set + if ( bIsAutoSet || bNoTopmargin ) + { + GetAnyProperty(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, pPropertyMap) >>= nBeforeAutospacing; + // tdf#137655 only w:beforeAutospacing=0 was specified, but not PARA_TOP_MARGIN + // (see default_spacing = -1 in processing of LN_CT_Spacing_beforeAutospacing) + if ( bNoTopmargin && nBeforeAutospacing == ConversionHelper::convertTwipToMM100(-1) ) + { + sal_Int32 nStyleAuto = -1; + GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING) >>= nStyleAuto; + if (nStyleAuto > 0) + nBeforeAutospacing = 0; + } + } + if ( nBeforeAutospacing > -1 && pParaContext ) + { + if (bAllowAdjustments) + { + if ( GetIsFirstParagraphInShape() || + (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) || + (m_bFirstParagraphInCell && m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth) ) + { + // export requires grabbag to match top_margin, so keep them in sync + if (nBeforeAutospacing && bIsAutoSet) + pParaContext->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::Any( sal_Int32(0) ),true, PARA_GRAB_BAG ); + nBeforeAutospacing = 0; + } + } + pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(nBeforeAutospacing)); + } + + sal_Int32 nAfterAutospacing = -1; + bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING); + const bool bNoBottomMargin = pParaContext && !pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN); + bool bAppliedBottomAutospacing = false; + if (bIsAutoSet || bNoBottomMargin) + { + GetAnyProperty(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, pPropertyMap) >>= nAfterAutospacing; + if (bNoBottomMargin && nAfterAutospacing == ConversionHelper::convertTwipToMM100(-1)) + { + sal_Int32 nStyleAuto = -1; + GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING) >>= nStyleAuto; + if (nStyleAuto > 0) + nAfterAutospacing = 0; + } + } + if ( nAfterAutospacing > -1 && pParaContext ) + { + pParaContext->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(nAfterAutospacing)); + bAppliedBottomAutospacing = bAllowAdjustments; + } + + // tell TableManager to reset the bottom margin if it determines that this is the cell's last paragraph. + if ( hasTableManager() && getTableManager().isInCell() ) + getTableManager().setCellLastParaAfterAutospacing(bAppliedBottomAutospacing); + + if (xTextAppend.is() && pParaContext && hasTableManager() && !getTableManager().isIgnore()) + { + try + { + /*the following combinations of previous and current frame settings can occur: + (1) - no old frame and no current frame -> no special action + (2) - no old frame and current DropCap -> save DropCap for later use, don't call finishParagraph + remove character properties of the DropCap? + (3) - no old frame and current Frame -> save Frame for later use + (4) - old DropCap and no current frame -> add DropCap to the properties of the finished paragraph, delete previous setting + (5) - old DropCap and current frame -> add DropCap to the properties of the finished paragraph, save current frame settings + (6) - old Frame and new DropCap -> add old Frame, save DropCap for later use + (7) - old Frame and new same Frame -> continue + (8) - old Frame and new different Frame -> add old Frame, save new Frame for later use + (9) - old Frame and no current frame -> add old Frame, delete previous settings + + old _and_ new DropCap must not occur + */ + + bool bIsDropCap = + pParaContext->IsFrameMode() && + sal::static_int_cast<Id>(pParaContext->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none; + + style::DropCapFormat aDrop; + ParagraphPropertiesPtr pToBeSavedProperties; + bool bKeepLastParagraphProperties = false; + if( bIsDropCap ) + { + uno::Reference<text::XParagraphCursor> xParaCursor( + xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW); + //select paragraph + xParaCursor->gotoStartOfParagraph( true ); + uno::Reference< beans::XPropertyState > xParaProperties( xParaCursor, uno::UNO_QUERY_THROW ); + xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT)); + xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT)); + //handles (2) and part of (6) + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + sal_Int32 nCount = xParaCursor->getString().getLength(); + pToBeSavedProperties->SetDropCapLength(nCount > 0 && nCount < 255 ? static_cast<sal_Int8>(nCount) : 1); + } + if( rAppendContext.pLastParagraphProperties ) + { + if( sal::static_int_cast<Id>(rAppendContext.pLastParagraphProperties->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none) + { + //handles (4) and part of (5) + //create a DropCap property, add it to the property sequence of finishParagraph + sal_Int32 nLines = rAppendContext.pLastParagraphProperties->GetLines(); + aDrop.Lines = nLines > 0 && nLines < SAL_MAX_INT8 ? static_cast<sal_Int8>(nLines) : 2; + aDrop.Count = rAppendContext.pLastParagraphProperties->GetDropCapLength(); + sal_Int32 nHSpace = rAppendContext.pLastParagraphProperties->GethSpace(); + aDrop.Distance = nHSpace > 0 && nHSpace < SAL_MAX_INT16 ? static_cast<sal_Int16>(nHSpace) : 0; + //completes (5) + if( pParaContext->IsFrameMode() ) + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + } + else if(*rAppendContext.pLastParagraphProperties == *pParaContext ) + { + //handles (7) + rAppendContext.pLastParagraphProperties->SetEndingRange(rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()); + bKeepLastParagraphProperties = true; + } + else + { + //handles (8)(9) and completes (6) + CheckUnregisteredFrameConversion( ); + + // If different frame properties are set on this paragraph, keep them. + if ( !bIsDropCap && pParaContext->IsFrameMode() ) + { + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext); + } + } + } + else + { + // (1) doesn't need handling + + if( !bIsDropCap && pParaContext->IsFrameMode() ) + { + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext); + } + } + std::vector<beans::PropertyValue> aProperties; + if (pPropertyMap) + { + aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues()); + } + if (pPropertyMap) + { + // tdf#64222 filter out the "paragraph marker" formatting and + // set it as a separate paragraph property, not a empty hint at + // end of paragraph + std::vector<beans::NamedValue> charProperties; + for (auto it = aProperties.begin(); it != aProperties.end(); ) + { + // this condition isn't ideal but as it happens all + // RES_CHRATR_* have names that start with "Char" + if (it->Name.startsWith("Char")) + { + charProperties.emplace_back(it->Name, it->Value); + // as testN793262 demonstrates, font size in rPr must + // affect the paragraph size => also insert empty hint! +// it = aProperties.erase(it); + } + ++it; + } + if (!charProperties.empty()) + { + aProperties.push_back(beans::PropertyValue("ListAutoFormat", + 0, uno::Any(comphelper::containerToSequence(charProperties)), beans::PropertyState_DIRECT_VALUE)); + } + } + if( !bIsDropCap ) + { + if( aDrop.Lines > 1 ) + { + beans::PropertyValue aValue; + aValue.Name = getPropertyName(PROP_DROP_CAP_FORMAT); + aValue.Value <<= aDrop; + aProperties.push_back(aValue); + } + uno::Reference< text::XTextRange > xTextRange; + if (rAppendContext.xInsertPosition.is()) + { + xTextRange = xTextAppend->finishParagraphInsert( comphelper::containerToSequence(aProperties), rAppendContext.xInsertPosition ); + rAppendContext.xCursor->gotoNextParagraph(false); + if (rAppendContext.pLastParagraphProperties) + rAppendContext.pLastParagraphProperties->SetEndingRange(xTextRange->getEnd()); + } + else + { + uno::Reference<text::XTextCursor> xCursor; + if (m_bParaHadField && !m_bIsInComments && !xTOCMarkerCursor.is()) + { + // Workaround to make sure char props of the field are not lost. + // Not relevant for editeng-based comments. + // Not relevant for fields inside a TOC field. + xCursor = xTextAppend->getText()->createTextCursor(); + if (xCursor.is()) + xCursor->gotoEnd(false); + PropertyMapPtr pEmpty(new PropertyMap()); + appendTextPortion("X", pEmpty); + } + + // Check if top / bottom margin has to be updated, now that we know the numbering status of both the previous and + // the current text node. + auto itNumberingRules = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "NumberingRules"; + }); + + assert( isNumberingViaRule == (itNumberingRules != aProperties.end()) ); + isNumberingViaRule = (itNumberingRules != aProperties.end()); + if (m_xPreviousParagraph.is() && (isNumberingViaRule || isNumberingViaStyle)) + { + // This textnode has numbering. Look up the numbering style name of the current and previous paragraph. + OUString aCurrentNumberingName; + OUString aPreviousNumberingName; + if (isNumberingViaRule) + { + assert(itNumberingRules != aProperties.end() && "by definition itNumberingRules is valid if isNumberingViaRule is true"); + uno::Reference<container::XNamed> xCurrentNumberingRules(itNumberingRules->Value, uno::UNO_QUERY); + if (xCurrentNumberingRules.is()) + aCurrentNumberingName = xCurrentNumberingRules->getName(); + if (m_xPreviousParagraph.is()) + { + uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if (xPreviousNumberingRules.is()) + aPreviousNumberingName = xPreviousNumberingRules->getName(); + } + } + else if ( m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName") && + // don't update before tables + (m_nTableDepth == 0 || !m_bFirstParagraphInCell)) + { + aCurrentNumberingName = GetListStyleName(nListId); + m_xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName; + } + + if (!aPreviousNumberingName.isEmpty() && aCurrentNumberingName == aPreviousNumberingName) + { + uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq; + m_xPreviousParagraph->getPropertyValue("ParaInteropGrabBag") >>= aPrevPropertiesSeq; + const auto & rPrevProperties = aPrevPropertiesSeq; + bool bParaAutoBefore = m_bParaAutoBefore || std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaTopMarginBeforeAutoSpacing"; + }); + // if style based spacing was set to auto in the previous paragraph, style of the actual paragraph must be the same + if (bParaAutoBefore && !m_bParaAutoBefore && m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ParaStyleName")) + { + auto itParaStyle = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaStyleName"; + }); + bParaAutoBefore = itParaStyle != aProperties.end() && + m_xPreviousParagraph->getPropertyValue("ParaStyleName") == itParaStyle->Value; + } + // There was a previous textnode and it had the same numbering. + if (bParaAutoBefore) + { + // This before spacing is set to auto, set before space to 0. + auto itParaTopMargin = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaTopMargin"; + }); + if (itParaTopMargin != aProperties.end()) + itParaTopMargin->Value <<= static_cast<sal_Int32>(0); + else + aProperties.push_back(comphelper::makePropertyValue("ParaTopMargin", static_cast<sal_Int32>(0))); + } + + bool bPrevParaAutoAfter = std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaBottomMarginAfterAutoSpacing"; + }); + if (bPrevParaAutoAfter) + { + // Previous after spacing is set to auto, set previous after space to 0. + m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0))); + } + } + } + + // apply redlines for inline images + if (IsParaWithInlineObject()) + { + for (const auto& rAnchored : rAppendContext.m_aAnchoredObjects) + { + // process only inline objects with redlining + if (!rAnchored.m_xRedlineForInline) + continue; + + // select the inline image and set its redline + auto xAnchorRange = rAnchored.m_xAnchoredObject->getAnchor(); + uno::Reference< text::XTextCursor > xCursorOnImage = + xAnchorRange->getText()->createTextCursorByRange(xAnchorRange); + xCursorOnImage->goRight(1, true); + CreateRedline( xCursorOnImage, rAnchored.m_xRedlineForInline ); + } + } + + xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) ); + m_xPreviousParagraph.set(xTextRange, uno::UNO_QUERY); + + if (m_xPreviousParagraph.is() && // null for SvxUnoTextBase + (isNumberingViaStyle || isNumberingViaRule)) + { + assert(pParaContext); + if (ListDef::Pointer const& pList = m_pListTable->GetList(nListId)) + { // styles could refer to non-existing lists... + AbstractListDef::Pointer const& pAbsList = + pList->GetAbstractDefinition(); + if (pAbsList && + // SvxUnoTextRange doesn't have ListId + m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ListId")) + { + OUString paraId; + m_xPreviousParagraph->getPropertyValue("ListId") >>= paraId; + if (!paraId.isEmpty()) // must be on some list? + { + OUString const listId = pAbsList->MapListId(paraId); + if (listId != paraId) + { + m_xPreviousParagraph->setPropertyValue("ListId", uno::Any(listId)); + } + } + } + + sal_Int16 nCurrentLevel = GetListLevel(pEntry, pPropertyMap); + if (nCurrentLevel == -1) + nCurrentLevel = 0; + + const ListLevel::Pointer pListLevel = pList->GetLevel(nCurrentLevel); + if (pListLevel) + { + sal_Int16 nOverrideLevel = pListLevel->GetStartOverride(); + if (nOverrideLevel != -1 && m_aListOverrideApplied.find(nListId) == m_aListOverrideApplied.end()) + { + // Apply override: we have override instruction for this level + // And this was not done for this list before: we can do this only once on first occurrence + // of list with override + // TODO: Not tested variant with different levels override in different lists. + // Probably m_aListOverrideApplied as a set of overridden listids is not sufficient + // and we need to register level overrides separately. + m_xPreviousParagraph->setPropertyValue("ParaIsNumberingRestart", uno::Any(true)); + m_xPreviousParagraph->setPropertyValue("NumberingStartValue", uno::Any(nOverrideLevel)); + m_aListOverrideApplied.insert(nListId); + } + } + } + } + + if (!rAppendContext.m_aAnchoredObjects.empty() && !IsInHeaderFooter()) + { + // Remember what objects are anchored to this paragraph. + // That list is only used for Word compat purposes, and + // it is only relevant for body text. + AnchoredObjectsInfo aInfo; + aInfo.m_xParagraph = xTextRange; + aInfo.m_aAnchoredObjects = rAppendContext.m_aAnchoredObjects; + m_aAnchoredObjectAnchors.push_back(aInfo); + rAppendContext.m_aAnchoredObjects.clear(); + } + + // We're no longer right after a table conversion. + m_bConvertedTable = false; + + if (xCursor.is()) + { + xCursor->goLeft(1, true); + xCursor->setString(OUString()); + } + } + getTableManager( ).handle(xTextRange); + m_aSmartTagHandler.handle(xTextRange); + + if (xTextRange.is()) + { + // Get the end of paragraph character inserted + uno::Reference< text::XTextCursor > xCur = xTextRange->getText( )->createTextCursor( ); + if (rAppendContext.xInsertPosition.is()) + xCur->gotoRange( rAppendContext.xInsertPosition, false ); + else + xCur->gotoEnd( false ); + + // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode + sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode(); + if ( m_nTableDepth > 0 && nMode > 0 && nMode <= 14 ) + { + // skip new line + xCur->goLeft(1, false); + while ( xCur->goLeft(1, true) ) + { + OUString sChar = xCur->getString(); + if ( sChar == " " || sChar == "\t" || sChar == OUStringChar(u'\x00A0') ) + xCur->setString(""); + else + break; + } + + if (rAppendContext.xInsertPosition.is()) + xCur->gotoRange(rAppendContext.xInsertPosition, false); + else + xCur->gotoEnd(false); + } + + xCur->goLeft( 1 , true ); + // Extend the redline ranges for empty paragraphs + if ( !m_bParaChanged && m_previousRedline ) + CreateRedline( xCur, m_previousRedline ); + CheckParaMarkerRedline( xCur ); + } + + css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY); + + // table style precedence and not hidden shapes anchored to hidden empty table paragraphs + if (xParaProps && (m_nTableDepth > 0 || !m_aAnchoredObjectAnchors.empty()) ) + { + // table style has got bigger precedence than docDefault style + // collect these pending paragraph properties to process in endTable() + uno::Reference<text::XTextCursor> xCur = xTextRange->getText( )->createTextCursor( ); + xCur->gotoEnd(false); + xCur->goLeft(1, false); + uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur); + uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW); + xParaCursor->gotoStartOfParagraph(false); + if (m_nTableDepth > 0) + { + TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps}; + getTableManager().getCurrentParagraphs()->push_back(aPending); + } + + // hidden empty paragraph with a not hidden shape, set as not hidden + std::optional<PropertyMap::Property> pHidden; + if ( !m_aAnchoredObjectAnchors.empty() && (pHidden = pParaContext->getProperty(PROP_CHAR_HIDDEN)) ) + { + bool bIsHidden = {}; // -Werror=maybe-uninitialized + pHidden->second >>= bIsHidden; + if (bIsHidden) + { + bIsHidden = false; + pHidden = GetTopContext()->getProperty(PROP_CHAR_HIDDEN); + if (pHidden) + pHidden->second >>= bIsHidden; + if (!bIsHidden) + { + uno::Reference<text::XTextCursor> xCur3 = xTextRange->getText()->createTextCursorByRange(xParaCursor); + xCur3->goRight(1, true); + if (xCur3->getString() == SAL_NEWLINE_STRING) + { + uno::Reference< beans::XPropertySet > xProp( xCur3, uno::UNO_QUERY ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HIDDEN), uno::Any(false)); + } + } + } + } + } + + // tdf#118521 set paragraph top or bottom margin based on the paragraph style + // if we already set the other margin with direct formatting + if (xParaProps) + { + const bool bTopSet = pParaContext->isSet(PROP_PARA_TOP_MARGIN); + const bool bBottomSet = pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN); + const bool bContextSet = pParaContext->isSet(PROP_PARA_CONTEXT_MARGIN); + if ( bTopSet != bBottomSet || bBottomSet != bContextSet ) + { + + if ( !bTopSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaTopMargin", aMargin); + } + if ( !bBottomSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaBottomMargin", aMargin); + } + if ( !bContextSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaContextMargin", aMargin); + } + } + } + + // Left, Right, and Hanging settings are also grouped. Ensure that all or none are set. + if (xParaProps) + { + const bool bLeftSet = pParaContext->isSet(PROP_PARA_LEFT_MARGIN); + const bool bRightSet = pParaContext->isSet(PROP_PARA_RIGHT_MARGIN); + const bool bFirstSet = pParaContext->isSet(PROP_PARA_FIRST_LINE_INDENT); + if (bLeftSet != bRightSet || bRightSet != bFirstSet) + { + if ( !bLeftSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_LEFT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaLeftMargin", aMargin); + else if (isNumberingViaStyle) + { + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "IndentAt"); + if (nParaLeftMargin != 0) + xParaProps->setPropertyValue("ParaLeftMargin", uno::Any(nParaLeftMargin)); + } + } + if ( !bRightSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaRightMargin", aMargin); + } + if ( !bFirstSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaFirstLineIndent", aMargin); + else if (isNumberingViaStyle) + { + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "FirstLineIndent"); + if (nFirstLineIndent != 0) + xParaProps->setPropertyValue("ParaFirstLineIndent", uno::Any(nFirstLineIndent)); + } + } + } + } + + // fix table paragraph properties + if ( xTextRange.is() && xParaProps && m_nTableDepth > 0 ) + { + // tdf#128959 table paragraphs haven't got window and orphan controls + uno::Any aAny(static_cast<sal_Int8>(0)); + xParaProps->setPropertyValue("ParaOrphans", aAny); + xParaProps->setPropertyValue("ParaWidows", aAny); + } + } + if( !bKeepLastParagraphProperties ) + rAppendContext.pLastParagraphProperties = pToBeSavedProperties; + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::finishParagraph" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "finishParagraph()" ); + } + + } + + bool bIgnoreFrameState = IsInHeaderFooter(); + if( (!bIgnoreFrameState && pParaContext && pParaContext->IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) ) + SetIsPreviousParagraphFramed(true); + else + SetIsPreviousParagraphFramed(false); + + m_bRemoveThisParagraph = false; + if( !IsInHeaderFooter() && !IsInShape() && (!pParaContext || !pParaContext->IsFrameMode()) ) + { // If the paragraph is in a frame, shape or header/footer, it's not a paragraph of the section itself. + SetIsFirstParagraphInSection(false); + // don't count an empty deleted paragraph as first paragraph in section to avoid of + // the deletion of the next empty paragraph later, resulting loss of the associated page break + if (!m_previousRedline || m_bParaChanged) + { + SetIsFirstParagraphInSectionAfterRedline(false); + SetIsLastParagraphInSection(false); + } + } + m_previousRedline.clear(); + m_bParaChanged = false; + + if (m_bIsInComments && pParaContext) + { + if (const OUString sParaId = pParaContext->GetParaId(); !sParaId.isEmpty()) + { + if (const auto& item = m_aCommentProps.find(sParaId); item != m_aCommentProps.end()) + { + m_bAnnotationResolved = item->second.bDone; + } + } + } + + if (m_bIsFirstParaInShape) + m_bIsFirstParaInShape = false; + + if (pParaContext) + { + // Reset the frame properties for the next paragraph + pParaContext->ResetFrameProperties(); + } + + SetIsOutsideAParagraph(true); + m_bParaHadField = false; + + // don't overwrite m_bFirstParagraphInCell in table separator nodes + // and in text boxes anchored to the first paragraph of table cells + if (m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth && !IsInShape()) + m_bFirstParagraphInCell = false; + + m_bParaAutoBefore = false; + m_bParaWithInlineObject = false; + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + +} + +void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (m_aTextAppendStack.empty()) + return; + // Before placing call to processDeferredCharacterProperties(), TopContextType should be CONTEXT_CHARACTER + // processDeferredCharacterProperties() invokes only if character inserted + if( pPropertyMap == m_pTopContext && !deferredCharacterProperties.empty() && (GetTopContextType() == CONTEXT_CHARACTER) ) + processDeferredCharacterProperties(); + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is() || !hasTableManager() || getTableManager().isIgnore()) + return; + + try + { + // If we are in comments, then disable CharGrabBag, comment text doesn't support that. + uno::Sequence< beans::PropertyValue > aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!m_bIsInComments); + + if (m_bStartTOC || m_bStartIndex || m_bStartBibliography) + for( auto& rValue : asNonConstRange(aValues) ) + { + if (rValue.Name == "CharHidden") + rValue.Value <<= false; + } + + // remove workaround for change tracked images, if they are part of a redline, + // i.e. if the next run is a tracked change with the same type, author and date, + // as in the change tracking of the image. + if ( m_bRedlineImageInPreviousRun ) + { + auto pCurrentRedline = m_aRedlines.top().size() > 0 + ? m_aRedlines.top().back() + : GetTopContextOfType(CONTEXT_CHARACTER) && + GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0 + ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back() + : nullptr; + if ( m_previousRedline && pCurrentRedline && + // same redline + (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) && + m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor && + m_previousRedline->m_sDate == pCurrentRedline->m_sDate ) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); + assert(xCursor.is()); + xCursor->gotoEnd(false); + xCursor->goLeft(2, true); + if ( xCursor->getString() == u"" ) + { + xCursor->goRight(1, true); + xCursor->setString(""); + xCursor->gotoEnd(false); + xCursor->goLeft(1, true); + xCursor->setString(""); + } + } + + m_bRedlineImageInPreviousRun = false; + } + + uno::Reference< text::XTextRange > xTextRange; + if (m_aTextAppendStack.top().xInsertPosition.is()) + { + xTextRange = xTextAppend->insertTextPortion(rString, aValues, m_aTextAppendStack.top().xInsertPosition); + m_aTextAppendStack.top().xCursor->gotoRange(xTextRange->getEnd(), true); + } + else + { + if (m_bStartTOC || m_bStartIndex || m_bStartBibliography || m_nStartGenericField != 0) + { + if (IsInHeaderFooter() && !m_bStartTOCHeaderFooter) + { + xTextRange = xTextAppend->appendTextPortion(rString, aValues); + } + else + { + m_bStartedTOC = true; + uno::Reference< text::XTextCursor > xTOCTextCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); + assert(xTOCTextCursor.is()); + xTOCTextCursor->gotoEnd(false); + if (m_nStartGenericField != 0) + { + xTOCTextCursor->goLeft(1, false); + } + xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor); + SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed"); + if (!xTextRange.is()) + throw uno::Exception("insertTextPortion failed", nullptr); + m_bTextInserted = true; + xTOCTextCursor->gotoRange(xTextRange->getEnd(), true); + if (m_nStartGenericField == 0) + { + m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor)); + } + } + } + else + { +#if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed + sal_Int32 nPos = 0; + OUString sFontName; + OUString sDoubleSpace(" "); + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER); + // tdf#123703 workaround for longer space sequences of the old or compatible RTF documents + if (GetSettingsTable()->GetLongerSpaceSequence() && !IsOpenFieldCommand() && (nPos = rString.indexOf(sDoubleSpace)) != -1 && + // monospaced fonts have no longer space sequences, regardless of \fprq2 (not monospaced) font setting + // fix for the base monospaced font Courier + (!pContext || !pContext->isSet(PROP_CHAR_FONT_NAME) || + ((pContext->getProperty(PROP_CHAR_FONT_NAME)->second >>= sFontName) && sFontName.indexOf("Courier") == -1))) + { + // an RTF space character is longer by an extra six-em-space in an old-style RTF space sequence, + // insert them to keep RTF document layout formatted by consecutive spaces + const sal_Unicode aExtraSpace[5] = { 0x2006, 0x20, 0x2006, 0x20, 0 }; + const sal_Unicode aExtraSpace2[4] = { 0x20, 0x2006, 0x20, 0 }; + xTextRange = xTextAppend->appendTextPortion(rString.replaceAll(sDoubleSpace, aExtraSpace, nPos) + .replaceAll(sDoubleSpace, aExtraSpace2, nPos), aValues); + } + else +#endif + xTextRange = xTextAppend->appendTextPortion(rString, aValues); + } + } + + // reset moveFrom/moveTo data of non-terminating runs of the paragraph + if ( m_pParaMarkerRedlineMove ) + { + m_pParaMarkerRedlineMove.clear(); + } + CheckRedline( xTextRange ); + m_bParaChanged = true; + + //getTableManager( ).handle(xTextRange); + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" ); + } +} + +void DomainMapper_Impl::appendTextContent( + const uno::Reference< text::XTextContent >& xContent, + const uno::Sequence< beans::PropertyValue >& xPropertyValues + ) +{ + SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text append stack"); + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY ); + OSL_ENSURE( xTextAppendAndConvert.is(), "trying to append a text content without XTextAppendAndConvert" ); + if (!xTextAppendAndConvert.is() || !hasTableManager() || getTableManager().isIgnore()) + return; + + try + { + if (m_aTextAppendStack.top().xInsertPosition.is()) + xTextAppendAndConvert->insertTextContentWithProperties( xContent, xPropertyValues, m_aTextAppendStack.top().xInsertPosition ); + else + xTextAppendAndConvert->appendTextContent( xContent, xPropertyValues ); + } + catch(const lang::IllegalArgumentException&) + { + } + catch(const uno::Exception&) + { + } +} + +void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOLEHandler ) +{ + try + { + uno::Reference< text::XTextContent > xOLE( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xOLEProperties(xOLE, uno::UNO_QUERY_THROW); + + OUString aCLSID = pOLEHandler->getCLSID(); + if (aCLSID.isEmpty()) + xOLEProperties->setPropertyValue(getPropertyName( PROP_STREAM_NAME ), + uno::Any( rStreamName )); + else + xOLEProperties->setPropertyValue("CLSID", uno::Any(aCLSID)); + + OUString aDrawAspect = pOLEHandler->GetDrawAspect(); + if(!aDrawAspect.isEmpty()) + xOLEProperties->setPropertyValue("DrawAspect", uno::Any(aDrawAspect)); + + awt::Size aSize = pOLEHandler->getSize(); + if( !aSize.Width ) + aSize.Width = 1000; + if( !aSize.Height ) + aSize.Height = 1000; + xOLEProperties->setPropertyValue(getPropertyName( PROP_WIDTH ), + uno::Any(aSize.Width)); + xOLEProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ), + uno::Any(aSize.Height)); + + OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth(); + if(!aVisAreaWidth.isEmpty()) + xOLEProperties->setPropertyValue("VisibleAreaWidth", uno::Any(aVisAreaWidth)); + + OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight(); + if(!aVisAreaHeight.isEmpty()) + xOLEProperties->setPropertyValue("VisibleAreaHeight", uno::Any(aVisAreaHeight)); + + uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement(); + xOLEProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC ), + uno::Any(xGraphic)); + uno::Reference<beans::XPropertySet> xReplacementProperties(pOLEHandler->getShape(), uno::UNO_QUERY); + if (xReplacementProperties.is()) + { + table::BorderLine2 aBorderProps; + xReplacementProperties->getPropertyValue("LineColor") >>= aBorderProps.Color; + xReplacementProperties->getPropertyValue("LineWidth") >>= aBorderProps.LineWidth; + xReplacementProperties->getPropertyValue("LineStyle") >>= aBorderProps.LineStyle; + + if (aBorderProps.LineStyle) // Set line props only if LineStyle is set + { + xOLEProperties->setPropertyValue("RightBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("TopBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("LeftBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("BottomBorder", uno::Any(aBorderProps)); + } + OUString pProperties[] = { + "AnchorType", + "Surround", + "SurroundContour", + "HoriOrient", + "HoriOrientPosition", + "VertOrient", + "VertOrientPosition", + "VertOrientRelation", + "HoriOrientRelation", + "LeftMargin", + "RightMargin", + "TopMargin", + "BottomMargin" + }; + for (const OUString& s : pProperties) + { + const uno::Any aVal = xReplacementProperties->getPropertyValue(s); + xOLEProperties->setPropertyValue(s, aVal); + } + + if (xReplacementProperties->getPropertyValue("FillStyle").get<css::drawing::FillStyle>() + != css::drawing::FillStyle::FillStyle_NONE) // Apply fill props if style is set + { + xOLEProperties->setPropertyValue( + "FillStyle", xReplacementProperties->getPropertyValue("FillStyle")); + xOLEProperties->setPropertyValue( + "FillColor", xReplacementProperties->getPropertyValue("FillColor")); + xOLEProperties->setPropertyValue( + "FillColor2", xReplacementProperties->getPropertyValue("FillColor2")); + } + } + else + // mimic the treatment of graphics here... it seems anchoring as character + // gives a better ( visually ) result + xOLEProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ), uno::Any( text::TextContentAnchorType_AS_CHARACTER ) ); + // remove ( if valid ) associated shape ( used for graphic replacement ) + SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack"); + if (!m_aAnchoredStack.empty()) + m_aAnchoredStack.top( ).bToRemove = true; + RemoveLastParagraph(); + SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack"); + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + + appendTextContent( xOLE, uno::Sequence< beans::PropertyValue >() ); + + if (!aCLSID.isEmpty()) + pOLEHandler->importStream(m_xComponentContext, GetTextDocument(), xOLE); + + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of OLE object" ); + } + +} + +void DomainMapper_Impl::appendStarMath( const Value& val ) +{ + uno::Reference< embed::XEmbeddedObject > formula; + val.getAny() >>= formula; + if( !formula.is() ) + return; + + try + { + uno::Reference< text::XTextContent > xStarMath( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xStarMathProperties(xStarMath, uno::UNO_QUERY_THROW); + + xStarMathProperties->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ), + val.getAny()); + // tdf#66405: set zero margins for embedded object + xStarMathProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ), + uno::Any(sal_Int32(0))); + + uno::Reference< uno::XInterface > xInterface( formula->getComponent(), uno::UNO_QUERY ); + // set zero margins for object's component + uno::Reference< beans::XPropertySet > xComponentProperties( xInterface, uno::UNO_QUERY_THROW ); + xComponentProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ), + uno::Any(sal_Int32(0))); + Size size( 1000, 1000 ); + if( oox::FormulaImportBase* formulaimport = dynamic_cast< oox::FormulaImportBase* >( xInterface.get())) + size = formulaimport->getFormulaSize(); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_WIDTH ), + uno::Any( sal_Int32(size.Width()))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ), + uno::Any( sal_Int32(size.Height()))); + xStarMathProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), + uno::Any(text::TextContentAnchorType_AS_CHARACTER)); + // mimic the treatment of graphics here... it seems anchoring as character + // gives a better ( visually ) result + appendTextContent(xStarMath, uno::Sequence<beans::PropertyValue>()); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of StarMath object" ); + } +} + +void DomainMapper_Impl::adjustLastPara(sal_Int8 nAlign) +{ + PropertyMapPtr pLastPara = GetTopContextOfType(dmapper::CONTEXT_PARAGRAPH); + pLastPara->Insert(PROP_PARA_ADJUST, uno::Any(nAlign), true); +} + +uno::Reference< beans::XPropertySet > DomainMapper_Impl::appendTextSectionAfter( + uno::Reference< text::XTextRange > const & xBefore ) +{ + uno::Reference< beans::XPropertySet > xRet; + if (m_aTextAppendStack.empty()) + return xRet; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XParagraphCursor > xCursor( + xTextAppend->createTextCursorByRange( xBefore ), uno::UNO_QUERY_THROW); + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + if (m_aTextAppendStack.top().xInsertPosition.is()) + xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true ); + else + xCursor->gotoEnd( true ); + //the paragraph after this new section is already inserted + xCursor->goLeft(1, true); + css::uno::Reference<css::text::XTextRange> xTextRange(xCursor, css::uno::UNO_QUERY_THROW); + + if (css::uno::Reference<css::text::XDocumentIndexesSupplier> xIndexSupplier{ + GetTextDocument(), css::uno::UNO_QUERY }) + { + css::uno::Reference<css::text::XTextRangeCompare> xCompare( + xTextAppend, css::uno::UNO_QUERY); + const auto xIndexAccess = xIndexSupplier->getDocumentIndexes(); + for (sal_Int32 i = xIndexAccess->getCount(); i > 0; --i) + { + if (css::uno::Reference<css::text::XDocumentIndex> xIndex{ + xIndexAccess->getByIndex(i - 1), css::uno::UNO_QUERY }) + { + const auto xIndexTextRange = xIndex->getAnchor(); + if (xCompare->compareRegionStarts(xTextRange, xIndexTextRange) == 0 + && xCompare->compareRegionEnds(xTextRange, xIndexTextRange) == 0) + { + // The boundaries coincide with an index: trying to attach a section + // to the range will insert the section inside the index. goRight will + // extend the range outside of the index, so that created section will + // be around it. Alternatively we could return index section itself + // instead : xRet.set(xIndex, uno::UNO_QUERY) - to set its properties, + // like columns/fill. + xCursor->goRight(1, true); + break; + } + } + } + } + + uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY_THROW ); + xSection->attach( xTextRange ); + xRet.set(xSection, uno::UNO_QUERY ); + } + catch(const uno::Exception&) + { + } + + } + + return xRet; +} + +void DomainMapper_Impl::appendGlossaryEntry() +{ + appendTextSectionAfter(m_xGlossaryEntryStart); +} + +void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar) +{ + if (bSetAnchorToChar) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER)); + + uno::Any aEmptyBorder{table::BorderLine2()}; + static const std::vector<PropertyIds> aBorderIds + = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER }; + for (size_t i = 0; i < aBorderIds.size(); ++i) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder)); + + static const std::vector<PropertyIds> aMarginIds + = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE, + PROP_LEFT_MARGIN, PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_MARGIN, PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_MARGIN, PROP_TOP_BORDER_DISTANCE }; + for (size_t i = 0; i < aMarginIds.size(); ++i) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0))); +} + +void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom) +{ + while (!m_aHeaderFooterTextAppendStack.empty()) + { + auto aFooterHeader = m_aHeaderFooterTextAppendStack.top(); + if ((aFooterHeader.second && !bDynamicHeightTop) || (!aFooterHeader.second && !bDynamicHeightBottom)) + { + uno::Reference< text::XTextAppend > xTextAppend = aFooterHeader.first.xTextAppend; + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor(); + uno::Reference< text::XTextRange > xRangeStart, xRangeEnd; + + xRangeStart = xCursor->getStart(); + xCursor->gotoEnd(false); + xRangeEnd = xCursor->getStart(); + + std::vector<beans::PropertyValue> aFrameProperties + { + comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH), + comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT), + comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false), + comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN), + comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN), + // tdf#143384 If the header/footer started with a table, convertToTextFrame could not + // convert the table, because it used createTextCursor() -which ignore tables- + // to set the conversion range. + // This dummy property is set to make convertToTextFrame to use another CreateTextCursor + // method that can be parameterized to not ignore tables. + comphelper::makePropertyValue(getPropertyName(PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF), true) + }; + + fillEmptyFrameProperties(aFrameProperties, false); + + // If it is a footer, then orient the frame to the bottom + if (!aFooterHeader.second) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM)); + + uno::Reference<text::XTextAppendAndConvert> xBodyText( + xRangeStart->getText(), uno::UNO_QUERY); + xBodyText->convertToTextFrame(xTextAppend, xRangeEnd, + comphelper::containerToSequence(aFrameProperties)); + } + m_aHeaderFooterTextAppendStack.pop(); + } +} + +void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType) +{ + m_bSaveParaHadField = m_bParaHadField; + m_aHeaderFooterStack.push(HeaderFooterContext(m_bTextInserted, m_nTableDepth)); + m_bTextInserted = false; + m_nTableDepth = 0; + + const PropertyIds ePropIsOn = bHeader? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON; + const PropertyIds ePropShared = bHeader? PROP_HEADER_IS_SHARED: PROP_FOOTER_IS_SHARED; + const PropertyIds ePropTextLeft = bHeader? PROP_HEADER_TEXT_LEFT: PROP_FOOTER_TEXT_LEFT; + const PropertyIds ePropText = bHeader? PROP_HEADER_TEXT: PROP_FOOTER_TEXT; + + m_bDiscardHeaderFooter = true; + m_eInHeaderFooterImport + = bHeader ? HeaderFooterImportState::header : HeaderFooterImportState::footer; + + //get the section context + PropertyMapPtr pContext = DomainMapper_Impl::GetTopContextOfType(CONTEXT_SECTION); + //ask for the header/footer name of the given type + SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + if(!pSectionContext) + return; + + // clear the "Link To Previous" flag so that the header/footer + // content is not copied from the previous section + pSectionContext->ClearHeaderFooterLinkToPrevious(bHeader, eType); + + if (!m_bIsNewDoc) + { + return; // TODO sw cannot Undo insert header/footer without crashing + } + + uno::Reference< beans::XPropertySet > xPageStyle = + pSectionContext->GetPageStyle( + *this, + eType == SectionPropertyMap::PAGE_FIRST ); + if (!xPageStyle.is()) + return; + try + { + bool bLeft = eType == SectionPropertyMap::PAGE_LEFT; + bool bFirst = eType == SectionPropertyMap::PAGE_FIRST; + if (!bLeft || GetSettingsTable()->GetEvenAndOddHeaders()) + { + //switch on header/footer use + xPageStyle->setPropertyValue( + getPropertyName(ePropIsOn), + uno::Any(true)); + + // If the 'Different Even & Odd Pages' flag is turned on - do not ignore it + // Even if the 'Even' header/footer is blank - the flag should be imported (so it would look in LO like in Word) + if (!bFirst && GetSettingsTable()->GetEvenAndOddHeaders()) + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false)); + + //set the interface + uno::Reference< text::XText > xText; + xPageStyle->getPropertyValue(getPropertyName(bLeft? ePropTextLeft: ePropText)) >>= xText; + + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc + ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart()))); + m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc + ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart())), + bHeader)); + } + // If we have *hidden* header footer + else + { + bool bIsShared = false; + // Turn on the headers + xPageStyle->setPropertyValue(getPropertyName(ePropIsOn), uno::Any(true)); + // Store the state of the previous state of shared prop + xPageStyle->getPropertyValue(getPropertyName(ePropShared)) >>= bIsShared; + // Turn on the shared prop in order to save the headers/footers in time + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false)); + // Add the content of the headers footers to the doc + uno::Reference<text::XText> xText; + xPageStyle->getPropertyValue(getPropertyName(bLeft ? ePropTextLeft : ePropText)) + >>= xText; + + m_aTextAppendStack.push( + TextAppendContext(uno::Reference<text::XTextAppend>(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart()))); + // Restore the original state of the shared prop after we stored the necessary values. + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(bIsShared)); + } + m_bDiscardHeaderFooter = false; // set only on success! + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +void DomainMapper_Impl::PushPageHeader(SectionPropertyMap::PageType eType) +{ + PushPageHeaderFooter(/* bHeader = */ true, eType); +} + +void DomainMapper_Impl::PushPageFooter(SectionPropertyMap::PageType eType) +{ + PushPageHeaderFooter(/* bHeader = */ false, eType); +} + +void DomainMapper_Impl::PopPageHeaderFooter() +{ + //header and footer always have an empty paragraph at the end + //this has to be removed + RemoveLastParagraph( ); + + if (!m_aTextAppendStack.empty()) + { + if (!m_bDiscardHeaderFooter) + { + m_aTextAppendStack.pop(); + } + m_bDiscardHeaderFooter = false; + } + m_eInHeaderFooterImport = HeaderFooterImportState::none; + + if (!m_aHeaderFooterStack.empty()) + { + m_bTextInserted = m_aHeaderFooterStack.top().getTextInserted(); + m_nTableDepth = m_aHeaderFooterStack.top().getTableDepth(); + m_aHeaderFooterStack.pop(); + } + + m_bParaHadField = m_bSaveParaHadField; +} + +void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote ) +{ + SAL_WARN_IF(m_bInFootOrEndnote, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote"); + m_bInFootOrEndnote = true; + m_bInFootnote = bIsFootnote; + m_bCheckFirstFootnoteTab = true; + m_bSaveFirstParagraphInCell = m_bFirstParagraphInCell; + try + { + // Redlines outside the footnote should not affect footnote content + m_aRedlines.push(std::vector< RedlineParamsPtr >()); + + // IMHO character styles from footnote labels should be ignored in the edit view of Writer. + // This adds a hack on top of the following hack to save the style name in the context. + PropertyMapPtr pTopContext = GetTopContext(); + OUString sFootnoteCharStyleName; + std::optional< PropertyMap::Property > aProp = pTopContext->getProperty(PROP_CHAR_STYLE_NAME); + if (aProp) + aProp->second >>= sFootnoteCharStyleName; + + // Remove style reference, if any. This reference did appear here as a side effect of tdf#43017 + // Seems it is not required by LO, but causes side effects during editing. So remove it + // for footnotes/endnotes to restore original LO behavior here. + pTopContext->Erase(PROP_CHAR_STYLE_NAME); + + uno::Reference< text::XText > xFootnoteText; + if (GetTextFactory().is()) + xFootnoteText.set( GetTextFactory()->createInstance( + bIsFootnote ? + OUString( "com.sun.star.text.Footnote" ) : OUString( "com.sun.star.text.Endnote" )), + uno::UNO_QUERY_THROW ); + uno::Reference< text::XFootnote > xFootnote( xFootnoteText, uno::UNO_QUERY_THROW ); + pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName); + uno::Sequence< beans::PropertyValue > aFontProperties; + if (GetTopContextOfType(CONTEXT_CHARACTER)) + aFontProperties = GetTopContextOfType(CONTEXT_CHARACTER)->GetPropertyValues(); + appendTextContent( uno::Reference< text::XTextContent >( xFootnoteText, uno::UNO_QUERY_THROW ), aFontProperties ); + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xFootnoteText, uno::UNO_QUERY_THROW ), + xFootnoteText->createTextCursorByRange(xFootnoteText->getStart()))); + + // Redlines for the footnote anchor in the main text content + std::vector< RedlineParamsPtr > aFootnoteRedline = std::move(m_aRedlines.top()); + m_aRedlines.pop(); + CheckRedline( xFootnote->getAnchor( ) ); + m_aRedlines.push( aFootnoteRedline ); + + // Try scanning for custom footnote labels + if (!sFootnoteCharStyleName.isEmpty()) + StartCustomFootnote(pTopContext); + else + EndCustomFootnote(); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "PushFootOrEndnote"); + } +} + +void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xRange, + const RedlineParamsPtr& pRedline) +{ + if ( !pRedline ) + return; + + bool bRedlineMoved = false; + try + { + OUString sType; + switch ( pRedline->m_nToken & 0xffff ) + { + case XML_mod: + sType = getPropertyName( PROP_FORMAT ); + break; + case XML_moveTo: + bRedlineMoved = true; + m_pParaMarkerRedlineMove = pRedline.get(); + [[fallthrough]]; + case XML_ins: + sType = getPropertyName( PROP_INSERT ); + break; + case XML_moveFrom: + bRedlineMoved = true; + m_pParaMarkerRedlineMove = pRedline.get(); + [[fallthrough]]; + case XML_del: + sType = getPropertyName( PROP_DELETE ); + break; + case XML_ParagraphFormat: + sType = getPropertyName( PROP_PARAGRAPH_FORMAT ); + break; + default: + throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0); + } + beans::PropertyValues aRedlineProperties( 4 ); + beans::PropertyValue * pRedlineProperties = aRedlineProperties.getArray( ); + pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_AUTHOR ); + pRedlineProperties[0].Value <<= pRedline->m_sAuthor; + pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_DATE_TIME ); + util::DateTime aDateTime = ConversionHelper::ConvertDateStringToDateTime( pRedline->m_sDate ); + // tdf#146171 import not specified w:date (or specified as zero date "0-00-00") + // as Epoch time to avoid of losing change tracking data during ODF roundtrip + if ( aDateTime.Year == 0 && aDateTime.Month == 0 && aDateTime.Day == 0 ) + { + aDateTime.Year = 1970; + aDateTime.Month = 1; + aDateTime.Day = 1; + } + pRedlineProperties[1].Value <<= aDateTime; + pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES ); + pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties; + pRedlineProperties[3].Name = "RedlineMoved"; + pRedlineProperties[3].Value <<= bRedlineMoved; + + if (!m_bIsActualParagraphFramed) + { + uno::Reference < text::XRedline > xRedline( xRange, uno::UNO_QUERY_THROW ); + xRedline->makeRedline( sType, aRedlineProperties ); + } + // store frame and (possible floating) table redline data for restoring them after frame conversion + enum StoredRedlines eType; + if (m_bIsActualParagraphFramed || m_nTableDepth > 0) + eType = StoredRedlines::FRAME; + else if (IsInFootOrEndnote()) + eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; + else + eType = StoredRedlines::NONE; + + if (eType != StoredRedlines::NONE) + { + m_aStoredRedlines[eType].push_back( uno::Any(xRange) ); + m_aStoredRedlines[eType].push_back( uno::Any(sType) ); + m_aStoredRedlines[eType].push_back( uno::Any(aRedlineProperties) ); + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in makeRedline" ); + } +} + +void DomainMapper_Impl::CheckParaMarkerRedline( uno::Reference< text::XTextRange > const& xRange ) +{ + if ( m_pParaMarkerRedline ) + { + CreateRedline( xRange, m_pParaMarkerRedline ); + if ( m_pParaMarkerRedline ) + { + m_pParaMarkerRedline.clear(); + m_currentRedline.clear(); + } + } + else if ( m_pParaMarkerRedlineMove && m_bIsParaMarkerMove ) + { + // terminating moveFrom/moveTo redline removes also the paragraph mark + CreateRedline( xRange, m_pParaMarkerRedlineMove ); + } + if ( m_pParaMarkerRedlineMove ) + { + m_pParaMarkerRedlineMove.clear(); + EndParaMarkerMove(); + } +} + +void DomainMapper_Impl::CheckRedline( uno::Reference< text::XTextRange > const& xRange ) +{ + // Writer core "officially" does not like overlapping redlines, and its UNO interface is stupid enough + // to not prevent that. However, in practice in fact everything appears to work fine (except for the debug warnings + // about redline table corruption, which may possibly be harmless in reality). So leave this as it is, since this + // is a better representation of how the changes happened. If this will ever become a problem, overlapping redlines + // will need to be merged into one, just like doing the changes in the UI does, which will lose some information + // (and so if that happens, it may be better to fix Writer). + // Create the redlines here from lowest (formats) to highest (inserts/removals) priority, since the last one is + // what Writer presents graphically, so this will show deletes as deleted text and not as just formatted text being there. + bool bUsedRange = m_aRedlines.top().size() > 0 || (GetTopContextOfType(CONTEXT_CHARACTER) && + GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0); + + // only export ParagraphFormat, when there is no other redline in the same text portion to avoid missing redline compression, + // but always export the first ParagraphFormat redline in a paragraph to keep the paragraph style change data for rejection + if( (!bUsedRange || !m_bParaChanged) && GetTopContextOfType(CONTEXT_PARAGRAPH) ) + { + std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_PARAGRAPH)->Redlines(); + for( const auto& rRedline : avRedLines ) + CreateRedline( xRange, rRedline ); + } + if( GetTopContextOfType(CONTEXT_CHARACTER) ) + { + std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_CHARACTER)->Redlines(); + for( const auto& rRedline : avRedLines ) + CreateRedline( xRange, rRedline ); + } + for (const auto& rRedline : m_aRedlines.top() ) + CreateRedline( xRange, rRedline ); +} + +void DomainMapper_Impl::StartParaMarkerChange( ) +{ + m_bIsParaMarkerChange = true; +} + +void DomainMapper_Impl::EndParaMarkerChange( ) +{ + m_bIsParaMarkerChange = false; + m_previousRedline = m_currentRedline; + m_currentRedline.clear(); +} + +void DomainMapper_Impl::StartParaMarkerMove( ) +{ + m_bIsParaMarkerMove = true; +} + +void DomainMapper_Impl::EndParaMarkerMove( ) +{ + m_bIsParaMarkerMove = false; +} + +void DomainMapper_Impl::StartCustomFootnote(const PropertyMapPtr pContext) +{ + if (pContext == m_pFootnoteContext) + return; + + assert(pContext->GetFootnote().is()); + m_bHasFootnoteStyle = true; + m_bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty(); + m_pFootnoteContext = pContext; +} + +void DomainMapper_Impl::EndCustomFootnote() +{ + m_bHasFootnoteStyle = false; + m_bCheckFootnoteStyle = false; +} + +void DomainMapper_Impl::PushAnnotation() +{ + try + { + m_bIsInComments = true; + if (!GetTextFactory().is()) + return; + m_xAnnotationField.set( GetTextFactory()->createInstance( "com.sun.star.text.TextField.Annotation" ), + uno::UNO_QUERY_THROW ); + uno::Reference< text::XText > xAnnotationText; + m_xAnnotationField->getPropertyValue("TextRange") >>= xAnnotationText; + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xAnnotationText, uno::UNO_QUERY_THROW ), + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xAnnotationText->createTextCursorByRange(xAnnotationText->getStart()))); + } + catch( const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +static void lcl_CopyRedlines( + uno::Reference< text::XText > const& xSrc, + std::deque<css::uno::Any>& rRedlines, + std::vector<sal_Int32>& redPos, + std::vector<sal_Int32>& redLen, + sal_Int32& redIdx) +{ + redIdx = -1; + for( size_t i = 0; i < rRedlines.size(); i+=3) + { + uno::Reference< text::XTextRange > xRange; + rRedlines[i] >>= xRange; + + // is this a redline of the temporary footnote? + uno::Reference<text::XTextCursor> xRangeCursor; + try + { + xRangeCursor = xSrc->createTextCursorByRange( xRange ); + } + catch( const uno::Exception& ) + { + } + if (xRangeCursor.is()) + { + redIdx = i; + sal_Int32 nLen = xRange->getString().getLength(); + redLen.push_back(nLen); + xRangeCursor->gotoRange(xSrc->getStart(), true); + redPos.push_back(xRangeCursor->getString().getLength() - nLen); + } + else + { + // we have already found all redlines of the footnote, + // skip checking the redlines of the other footnotes + if (redIdx > -1) + break; + // failed createTextCursorByRange(), for example, table inside the frame + redLen.push_back(-1); + redPos.push_back(-1); + } + } +} + +static void lcl_PasteRedlines( + uno::Reference< text::XText > const& xDest, + std::deque<css::uno::Any>& rRedlines, + std::vector<sal_Int32>& redPos, + std::vector<sal_Int32>& redLen, + sal_Int32 redIdx) +{ + // create redlines in the copied footnote + for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx); i+=3) + { + OUString sType; + beans::PropertyValues aRedlineProperties( 3 ); + // skip failed createTextCursorByRange() + if (redPos[i/3] == -1) + continue; + rRedlines[i+1] >>= sType; + rRedlines[i+2] >>= aRedlineProperties; + uno::Reference< text::XTextCursor > xCrsr = xDest->getText()->createTextCursor(); + xCrsr->goRight(redPos[i/3], false); + xCrsr->goRight(redLen[i/3], true); + uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW ); + try { + xRedline->makeRedline( sType, aRedlineProperties ); + } + catch(const uno::Exception&) + { + // ignore (footnotes of tracked deletions) + } + } +} + +bool DomainMapper_Impl::CopyTemporaryNotes( + uno::Reference< text::XFootnote > xNoteSrc, + uno::Reference< text::XFootnote > xNoteDest ) +{ + if (!m_bSaxError && xNoteSrc != xNoteDest) + { + uno::Reference< text::XText > xSrc( xNoteSrc, uno::UNO_QUERY_THROW ); + uno::Reference< text::XText > xDest( xNoteDest, uno::UNO_QUERY_THROW ); + uno::Reference< text::XTextCopy > xTxt, xTxt2; + xTxt.set( xSrc, uno::UNO_QUERY_THROW ); + xTxt2.set( xDest, uno::UNO_QUERY_THROW ); + xTxt2->copyText( xTxt ); + + // copy its redlines + std::vector<sal_Int32> redPos, redLen; + sal_Int32 redIdx; + enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; + lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx); + lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx); + + // remove processed redlines + for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++) + m_aStoredRedlines[eType].pop_front(); + + return true; + } + + return false; +} + +void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() +{ + uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XFootnote > xNote; + if (GetFootnoteCount() > 0) + { + auto xFootnotes = xFootnotesSupplier->getFootnotes(); + if ( m_nFirstFootnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xFootnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xFootnotes->getByIndex(m_nFirstFootnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } + for (sal_Int32 i = GetFootnoteCount(); i > 0; --i) + { + xFootnotes->getByIndex(i) >>= xNote; + xNote->getAnchor()->setString(""); + } + } + if (GetEndnoteCount() > 0) + { + auto xEndnotes = xEndnotesSupplier->getEndnotes(); + if ( m_nFirstEndnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xEndnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xEndnotes->getByIndex(m_nFirstEndnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } + for (sal_Int32 i = GetEndnoteCount(); i > 0; --i) + { + xEndnotes->getByIndex(i) >>= xNote; + xNote->getAnchor()->setString(""); + } + } +} + +static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds, sal_Int32& rFirstNoteIndex) +{ + // rNoteIds contains XML footnote identifiers in the loaded order of the footnotes + // (the same order as in footnotes.xml), i.e. it maps temporary footnote positions to the + // identifiers. For example: Ids[0] = 100; Ids[1] = -1, Ids[2] = 5. + // To copy the footnotes in their final place, create an array, which map the (normalized) + // footnote identifiers to the temporary footnote positions. Using the previous example, + // Pos[0] = 1; Pos[1] = 2; Pos[2] = 0 (where [0], [1], [2] are the normalized + // -1, 5 and 100 identifiers). + std::deque<sal_Int32> aSortedIds = rNoteIds; + std::sort(aSortedIds.begin(), aSortedIds.end()); + std::map<sal_Int32, size_t> aMapIds; + // normalize footnote identifiers to 0, 1, 2 ... + for (size_t i = 0; i < aSortedIds.size(); ++i) + aMapIds[aSortedIds[i]] = i; + // reusing rNoteIds, create the Pos array to map normalized identifiers to the loaded positions + std::deque<sal_Int32> aOrigNoteIds = rNoteIds; + for (size_t i = 0; i < rNoteIds.size(); ++i) + rNoteIds[aMapIds[aOrigNoteIds[i]]] = i; + rFirstNoteIndex = rNoteIds.front(); + rNoteIds.pop_front(); +} + +void DomainMapper_Impl::PopFootOrEndnote() +{ + // content of the footnotes were inserted after the first footnote in temporary footnotes, + // restore the content of the actual footnote by copying its content from the first + // (remaining) temporary footnote and remove the temporary footnote. + uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + bool bCopied = false; + if ( IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 && xFootnotesSupplier.is() ) || + ( !IsInFootnote() && GetEndnoteCount() > -1 && xEndnotesSupplier.is() ) ) ) + { + uno::Reference< text::XFootnote > xNoteFirst, xNoteLast; + auto xFootnotes = xFootnotesSupplier->getFootnotes(); + auto xEndnotes = xEndnotesSupplier->getEndnotes(); + if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 && + ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xNoteLast ) ) || + ( !IsInFootnote() && xEndnotes->getCount() > 1 && + ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xNoteLast ) ) + ) && xNoteLast->getLabel().isEmpty() ) + { + // copy content of the next temporary footnote + try + { + if ( IsInFootnote() && !m_aFootnoteIds.empty() ) + { + if ( m_nFirstFootnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aFootnoteIds, m_nFirstFootnoteIndex); + if (m_aFootnoteIds.empty()) // lcl_convertToNoteIndices pops m_aFootnoteIds + m_bSaxError = true; + else + { + xFootnotes->getByIndex(m_aFootnoteIds.front()) >>= xNoteFirst; + m_aFootnoteIds.pop_front(); + } + } + else if ( !IsInFootnote() && !m_aEndnoteIds.empty() ) + { + if ( m_nFirstEndnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aEndnoteIds, m_nFirstEndnoteIndex); + if (m_aEndnoteIds.empty()) // lcl_convertToNoteIndices pops m_aEndnoteIds + m_bSaxError = true; + else + { + xEndnotes->getByIndex(m_aEndnoteIds.front()) >>= xNoteFirst; + m_aEndnoteIds.pop_front(); + } + } + else + m_bSaxError = true; + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote"); + m_bSaxError = true; + } + + bCopied = CopyTemporaryNotes(xNoteFirst, xNoteLast); + } + } + + if (!IsRTFImport() && !bCopied) + RemoveLastParagraph(); + + // In case the foot or endnote did not contain a tab. + m_bIgnoreNextTab = false; + + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + + if (m_aRedlines.size() == 1) + { + SAL_WARN("writerfilter.dmapper", "PopFootOrEndnote() is called without PushFootOrEndnote()?"); + return; + } + m_aRedlines.pop(); + m_eSkipFootnoteState = SkipFootnoteSeparator::OFF; + m_bInFootOrEndnote = false; + m_pFootnoteContext = nullptr; + m_bFirstParagraphInCell = m_bSaveFirstParagraphInCell; +} + +void DomainMapper_Impl::PopAnnotation() +{ + RemoveLastParagraph(); + + m_bIsInComments = false; + m_aTextAppendStack.pop(); + + try + { + if (m_bAnnotationResolved) + m_xAnnotationField->setPropertyValue("Resolved", css::uno::Any(true)); + + // See if the annotation will be a single position or a range. + if (m_nAnnotationId == -1 || !m_aAnnotationPositions[m_nAnnotationId].m_xStart.is() || !m_aAnnotationPositions[m_nAnnotationId].m_xEnd.is()) + { + uno::Sequence< beans::PropertyValue > aEmptyProperties; + uno::Reference< text::XTextContent > xContent( m_xAnnotationField, uno::UNO_QUERY_THROW ); + appendTextContent( xContent, aEmptyProperties ); + CheckRedline( xContent->getAnchor( ) ); + } + else + { + AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[m_nAnnotationId]; + // Create a range that points to the annotation start/end. + uno::Reference<text::XText> const xText = aAnnotationPosition.m_xStart->getText(); + uno::Reference<text::XTextCursor> const xCursor = xText->createTextCursorByRange(aAnnotationPosition.m_xStart); + + bool bMarker = false; + uno::Reference<text::XTextRangeCompare> xTextRangeCompare(xText, uno::UNO_QUERY); + if (xTextRangeCompare->compareRegionStarts(aAnnotationPosition.m_xStart, aAnnotationPosition.m_xEnd) == 0) + { + // Insert a marker so that comment around an anchored image is not collapsed during + // insertion. + xText->insertString(xCursor, "x", false); + bMarker = true; + } + + xCursor->gotoRange(aAnnotationPosition.m_xEnd, true); + uno::Reference<text::XTextRange> const xTextRange(xCursor, uno::UNO_QUERY_THROW); + + // Attach the annotation to the range. + uno::Reference<text::XTextAppend> const xTextAppend = m_aTextAppendStack.top().xTextAppend; + xTextAppend->insertTextContent(xTextRange, uno::Reference<text::XTextContent>(m_xAnnotationField, uno::UNO_QUERY_THROW), !xCursor->isCollapsed()); + + if (bMarker) + { + // Remove the marker. + xCursor->goLeft(1, true); + xCursor->setString(OUString()); + } + } + m_aAnnotationPositions.erase( m_nAnnotationId ); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert annotation field"); + } + + m_xAnnotationField.clear(); + m_nAnnotationId = -1; + m_bAnnotationResolved = false; +} + +void DomainMapper_Impl::PushPendingShape( const uno::Reference< drawing::XShape > & xShape ) +{ + m_aPendingShapes.push_back(xShape); +} + +uno::Reference<drawing::XShape> DomainMapper_Impl::PopPendingShape() +{ + uno::Reference<drawing::XShape> xRet; + if (!m_aPendingShapes.empty()) + { + xRet = m_aPendingShapes.front(); + m_aPendingShapes.pop_front(); + } + return xRet; +} + +void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape > & xShape ) +{ + // Append these early, so the context and the table manager stack will be + // in sync, even if the text append stack is empty. + appendTableManager(); + appendTableHandler(); + getTableManager().startLevel(); + + if (m_aTextAppendStack.empty()) + return; + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + + try + { + uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW ); + if (xSInfo->supportsService("com.sun.star.drawing.GroupShape")) + { + // Textboxes in shapes do not support styles, so check saved style information and apply properties directly to the child shapes. + const uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY); + const sal_uInt32 nShapeCount = xShapes.is() ? xShapes->getCount() : 0; + for ( sal_uInt32 i = 0; i < nShapeCount; ++i ) + { + try + { + uno::Reference<text::XTextRange> xFrame(xShapes->getByIndex(i), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xFramePropertySet; + if (xFrame) + xFramePropertySet.set(xFrame, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShapes->getByIndex(i), uno::UNO_QUERY_THROW); + + comphelper::SequenceAsHashMap aGrabBag( xShapePropertySet->getPropertyValue("CharInteropGrabBag") ); + + // only VML import has checked for style. Don't apply default parastyle properties to other imported shapes + // - except for fontsize - to maintain compatibility with previous versions of LibreOffice. + const bool bOnlyApplyCharHeight = !aGrabBag["mso-pStyle"].hasValue(); + + OUString sStyleName; + aGrabBag["mso-pStyle"] >>= sStyleName; + StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByISTD( sStyleName ); + if ( !pEntry ) + { + // Use default style even in ambiguous cases (where multiple styles were defined) since MOST styles inherit + // MOST of their properties from the default style. In the ambiguous case, we have to accept some kind of compromise + // and the default paragraph style ought to be the safest one... (compared to DocDefaults or program defaults) + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetDefaultParaStyleName() ); + } + if ( pEntry ) + { + // The Ids here come from oox/source/vml/vmltextbox.cxx. + // It probably could safely expand to all Ids that shapes support. + const PropertyIds eIds[] = { + PROP_CHAR_HEIGHT, + PROP_CHAR_FONT_NAME, + PROP_CHAR_WEIGHT, + PROP_CHAR_CHAR_KERNING, + PROP_CHAR_COLOR, + PROP_PARA_ADJUST + }; + const uno::Reference<beans::XPropertyState> xShapePropertyState(xShapePropertySet, uno::UNO_QUERY_THROW); + for ( const auto& eId : eIds ) + { + try + { + if ( bOnlyApplyCharHeight && eId != PROP_CHAR_HEIGHT ) + continue; + + const OUString sPropName = getPropertyName(eId); + if ( beans::PropertyState_DEFAULT_VALUE == xShapePropertyState->getPropertyState(sPropName) ) + { + const uno::Any aProp = GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true); + if (aProp.hasValue()) + { + if (xFrame) + xFramePropertySet->setPropertyValue(sPropName, aProp); + else + xShapePropertySet->setPropertyValue(sPropName, aProp); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext() text stylesheet property exception" ); + } + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext()" ); + } + } + + // A GroupShape doesn't implement text::XTextRange, but appending + // an empty reference to the stacks still makes sense, because this + // way bToRemove can be set, and we won't end up with duplicated + // shapes for OLE objects. + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>())); + uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY); + m_aAnchoredStack.push(AnchoredContext(xTxtContent)); + } + else if (xSInfo->supportsService("com.sun.star.drawing.OLE2Shape")) + { + // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw. + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>())); + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY); + m_aAnchoredStack.push(AnchoredContext(xTextContent)); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + + m_xEmbedded.set(m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT))); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER)); + // So that the original bitmap-only shape will be replaced by the embedded object. + m_aAnchoredStack.top().bToRemove = true; + m_aTextAppendStack.pop(); + appendTextContent(m_xEmbedded, uno::Sequence<beans::PropertyValue>()); + } + else + { + uno::Reference<text::XTextRange> xShapeTextRange(xShape, uno::UNO_QUERY_THROW); + // Add the shape to the text append stack + uno::Reference<text::XTextAppend> xShapeTextAppend(xShape, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextCursor> xTextCursor; + if (!m_bIsNewDoc) + { + xTextCursor = xShapeTextRange->getText()->createTextCursorByRange( + xShapeTextRange->getStart()); + } + TextAppendContext aContext(xShapeTextAppend, xTextCursor); + m_aTextAppendStack.push(aContext); + + // Add the shape to the anchored objects stack + uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW ); + m_aAnchoredStack.push( AnchoredContext(xTxtContent) ); + + uno::Reference< beans::XPropertySet > xProps( xShape, uno::UNO_QUERY_THROW ); +#ifdef DBG_UTIL + TagLogger::getInstance().unoPropertySet(xProps); +#endif + text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH); + xProps->getPropertyValue(getPropertyName( PROP_ANCHOR_TYPE )) >>= nAnchorType; + bool checkZOrderStatus = false; + if (xSInfo->supportsService("com.sun.star.text.TextFrame")) + { + SetIsTextFrameInserted(true); + // Extract the special "btLr text frame" mode, requested by oox, if needed. + // Extract vml ZOrder from FrameInteropGrabBag + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aGrabBag; + xShapePropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag; + + for (const auto& rProp : std::as_const(aGrabBag)) + { + if (rProp.Name == "VML-Z-ORDER") + { + GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper(); + sal_Int32 zOrder(0); + rProp.Value >>= zOrder; + xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder))); + pZOrderHelper->addItem(xShapePropertySet, zOrder); + xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) ); + checkZOrderStatus = true; + } + else if ( rProp.Name == "TxbxHasLink" ) + { + //Chaining of textboxes will happen in ~DomainMapper_Impl + //i.e when all the textboxes are read and all its attributes + //have been set ( basically the Name/LinkedDisplayName ) + //which is set in Graphic Import. + m_vTextFramesForChaining.push_back(xShape); + } + } + + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextRange> xTextRange(xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW); + xTextAppend->insertTextContent(xTextRange, xTextContent, false); + + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + // we need to re-set this value to xTextContent, then only values are preserved. + xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aGrabBag)); + } + else if (nAnchorType == text::TextContentAnchorType_AS_CHARACTER) + { + // Fix spacing for as-character objects. If the paragraph has CT_Spacing_after set, + // it needs to be set on the object too, as that's what object placement code uses. + PropertyMapPtr paragraphContext = GetTopContextOfType( CONTEXT_PARAGRAPH ); + std::optional<PropertyMap::Property> aPropMargin = paragraphContext->getProperty(PROP_PARA_BOTTOM_MARGIN); + if(aPropMargin) + xProps->setPropertyValue( getPropertyName( PROP_BOTTOM_MARGIN ), aPropMargin->second ); + } + else + { + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aGrabBag; + xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + for (const auto& rProp : std::as_const(aGrabBag)) + { + if (rProp.Name == "VML-Z-ORDER") + { + GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper(); + sal_Int32 zOrder(0); + rProp.Value >>= zOrder; + xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder))); + pZOrderHelper->addItem(xShapePropertySet, zOrder); + xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) ); + checkZOrderStatus = true; + } + else if ( rProp.Name == "TxbxHasLink" ) + { + //Chaining of textboxes will happen in ~DomainMapper_Impl + //i.e when all the textboxes are read and all its attributes + //have been set ( basically the Name/LinkedDisplayName ) + //which is set in Graphic Import. + m_vTextFramesForChaining.push_back(xShape); + } + } + + if(IsSdtEndBefore()) + { + uno::Reference< beans::XPropertySetInfo > xPropSetInfo; + if(xShapePropertySet.is()) + { + xPropSetInfo = xShapePropertySet->getPropertySetInfo(); + if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag")) + { + uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({ + { "SdtEndBefore", uno::Any(true) } + })); + xShapePropertySet->setPropertyValue("InteropGrabBag",uno::Any(aShapeGrabBag)); + } + } + } + } + if (!IsInHeaderFooter() && !checkZOrderStatus) + xProps->setPropertyValue( + getPropertyName( PROP_OPAQUE ), + uno::Any( true ) ); + } + m_bParaChanged = true; + getTableManager().setIsInShape(true); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Exception when adding shape"); + } +} +/* + * Updating chart height and width after reading the actual values from wp:extent +*/ +void DomainMapper_Impl::UpdateEmbeddedShapeProps(const uno::Reference< drawing::XShape > & xShape) +{ + if (!xShape.is()) + return; + + uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW); + awt::Size aSize = xShape->getSize( ); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_WIDTH), uno::Any(sal_Int32(aSize.Width))); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::Any(sal_Int32(aSize.Height))); +} + + +void DomainMapper_Impl::PopShapeContext() +{ + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } + if ( m_aAnchoredStack.empty() ) + return; + + // For OLE object replacement shape, the text append context was already removed + // or the OLE object couldn't be inserted. + if ( !m_aAnchoredStack.top().bToRemove ) + { + RemoveLastParagraph(); + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + } + + uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent; + try + { + appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() ); + } + catch ( const uno::RuntimeException& ) + { + // this is normal: the shape is already attached + } + + const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW ); + // Remove the shape if required (most likely replacement shape for OLE object) + // or anchored to a discarded header or footer + if ( m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter ) + { + try + { + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(m_xTextDocument, uno::UNO_QUERY_THROW); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + if ( xDrawPage.is() ) + xDrawPage->remove( xShape ); + } + catch( const uno::Exception& ) + { + } + } + + // Relative width calculations deferred until section's margins are defined. + // Being cautious: only deferring undefined/minimum-width shapes in order to avoid causing potential regressions + css::awt::Size aShapeSize; + try + { + aShapeSize = xShape->getSize(); + } + catch (const css::uno::RuntimeException& e) + { + // May happen e.g. when text frame has no frame format + // See sw/qa/extras/ooxmlimport/data/n779627.docx + SAL_WARN("writerfilter.dmapper", "getSize failed. " << e.Message); + } + if( aShapeSize.Width <= 2 ) + { + const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY ); + SectionPropertyMap* pSectionContext = GetSectionContext(); + if ( pSectionContext && (!hasTableManager() || !getTableManager().isInTable()) && + xShapePropertySet->getPropertySetInfo()->hasPropertyByName(getPropertyName(PROP_RELATIVE_WIDTH)) ) + { + pSectionContext->addRelativeWidthShape(xShape); + } + } + + m_aAnchoredStack.pop(); +} + +bool DomainMapper_Impl::IsSdtEndBefore() +{ + bool bIsSdtEndBefore = false; + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER); + if(pContext) + { + const uno::Sequence< beans::PropertyValue > currentCharProps = pContext->GetPropertyValues(); + for (const auto& rCurrentCharProp : currentCharProps) + { + if (rCurrentCharProp.Name == "CharInteropGrabBag") + { + uno::Sequence<beans::PropertyValue> aCharGrabBag; + rCurrentCharProp.Value >>= aCharGrabBag; + for (const auto& rProp : std::as_const(aCharGrabBag)) + { + if(rProp.Name == "SdtEndBefore") + { + rProp.Value >>= bIsSdtEndBefore; + } + } + } + } + } + return bIsSdtEndBefore; +} + +bool DomainMapper_Impl::IsDiscardHeaderFooter() const +{ + return m_bDiscardHeaderFooter; +} + +// called from TableManager::closeCell() +void DomainMapper_Impl::ClearPreviousParagraph() +{ + // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering + if ((m_nTableDepth == (m_nTableCellDepth + 1)) + && m_xPreviousParagraph.is() + && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing()) + { + uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() ) + m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0))); + } + + m_xPreviousParagraph.clear(); + + // next table paragraph will be first paragraph in a cell + m_bFirstParagraphInCell = true; +} + +void DomainMapper_Impl::HandleAltChunk(const OUString& rStreamName) +{ + try + { + // Create the import filter. + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory( + comphelper::getProcessServiceFactory()); + uno::Reference<uno::XInterface> xDocxFilter + = xMultiServiceFactory->createInstance("com.sun.star.comp.Writer.WriterFilter"); + + // Set the target document. + uno::Reference<document::XImporter> xImporter(xDocxFilter, uno::UNO_QUERY); + xImporter->setTargetDocument(m_xTextDocument); + + // Set the import parameters. + uno::Reference<embed::XHierarchicalStorageAccess> xStorageAccess(m_xDocumentStorage, + uno::UNO_QUERY); + if (!xStorageAccess.is()) + { + return; + } + // Turn the ZIP stream into a seekable one, as the importer only works with such streams. + uno::Reference<io::XStream> xStream = xStorageAccess->openStreamElementByHierarchicalName( + rStreamName, embed::ElementModes::READ); + std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(xStream, true); + SvMemoryStream aMemory; + aMemory.WriteStream(*pStream); + uno::Reference<io::XStream> xInputStream = new utl::OStreamWrapper(aMemory); + // Not handling AltChunk during paste for now. + uno::Reference<text::XTextRange> xInsertTextRange = GetCurrentXText()->getEnd(); + uno::Reference<text::XTextRange> xSectionStartingRange; + SectionPropertyMap* pSectionContext = GetSectionContext(); + if (pSectionContext) + { + xSectionStartingRange = pSectionContext->GetStartingRange(); + } + uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({ + { "InputStream", uno::Any(xInputStream) }, + { "InsertMode", uno::Any(true) }, + { "TextInsertModeRange", uno::Any(xInsertTextRange) }, + { "AltChunkMode", uno::Any(true) }, + { "AltChunkStartingRange", uno::Any(xSectionStartingRange) }, + })); + + // Do the actual import. + uno::Reference<document::XFilter> xFilter(xDocxFilter, uno::UNO_QUERY); + xFilter->filter(aDescriptor); + } + catch (const uno::Exception& rException) + { + SAL_WARN("writerfilter", "DomainMapper_Impl::HandleAltChunk: failed to handle alt chunk: " + << rException.Message); + } +} + +void DomainMapper_Impl::HandlePTab(sal_Int32 nAlignment) +{ + // We only handle the case when the line already has content, so the left-aligned ptab is + // equivalent to a line break. + if (nAlignment != NS_ooxml::LN_Value_ST_PTabAlignment_left) + { + return; + } + + if (m_aTextAppendStack.empty()) + { + return; + } + + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + { + return; + } + + uno::Reference<css::text::XTextRange> xInsertPosition + = m_aTextAppendStack.top().xInsertPosition; + if (!xInsertPosition.is()) + { + xInsertPosition = xTextAppend->getEnd(); + } + uno::Reference<text::XTextCursor> xCursor + = xTextAppend->createTextCursorByRange(xInsertPosition); + + // Assume that we just inserted a tab character. + xCursor->goLeft(1, true); + if (xCursor->getString() != "\t") + { + return; + } + + // Assume that there is some content before the tab character. + uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY); + if (!xParagraphCursor.is()) + { + return; + } + + xCursor->collapseToStart(); + xParagraphCursor->gotoStartOfParagraph(true); + if (xCursor->isCollapsed()) + { + return; + } + + // Then select the tab again and replace with a line break. + xCursor->collapseToEnd(); + xCursor->goRight(1, true); + xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::LINE_BREAK, true); +} + +void DomainMapper_Impl::HandleLineBreakClear(sal_Int32 nClear) +{ + switch (nClear) + { + case NS_ooxml::LN_Value_ST_BrClear_left: + // SwLineBreakClear::LEFT + m_oLineBreakClear = 1; + break; + case NS_ooxml::LN_Value_ST_BrClear_right: + // SwLineBreakClear::RIGHT + m_oLineBreakClear = 2; + break; + case NS_ooxml::LN_Value_ST_BrClear_all: + // SwLineBreakClear::ALL + m_oLineBreakClear = 3; + break; + } +} + +void DomainMapper_Impl::HandleLineBreak(const PropertyMapPtr& pPropertyMap) +{ + if (!m_oLineBreakClear.has_value()) + { + appendTextPortion("\n", pPropertyMap); + return; + } + + if (GetTextFactory().is()) + { + uno::Reference<text::XTextContent> xLineBreak( + GetTextFactory()->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY); + xLineBreakProps->setPropertyValue("Clear", uno::Any(*m_oLineBreakClear)); + appendTextContent(xLineBreak, pPropertyMap->GetPropertyValues()); + } + m_oLineBreakClear.reset(); +} + +static sal_Int16 lcl_ParseNumberingType( std::u16string_view rCommand ) +{ + sal_Int16 nRet = style::NumberingType::PAGE_DESCRIPTOR; + + // The command looks like: " PAGE \* Arabic " + // tdf#132185: but may as well be "PAGE \* Arabic" + OUString sNumber; + constexpr OUStringLiteral rSeparator(u"\\* "); + if (size_t nStartIndex = rCommand.find(rSeparator); nStartIndex != std::u16string_view::npos) + { + sal_Int32 nStartIndex2 = nStartIndex + rSeparator.getLength(); + sNumber = o3tl::getToken(rCommand, 0, ' ', nStartIndex2); + } + + if( !sNumber.isEmpty() ) + { + //todo: might make sense to hash this list, too + struct NumberingPairs + { + const char* cWordName; + sal_Int16 nType; + }; + static const NumberingPairs aNumberingPairs[] = + { + {"Arabic", style::NumberingType::ARABIC} + ,{"ROMAN", style::NumberingType::ROMAN_UPPER} + ,{"roman", style::NumberingType::ROMAN_LOWER} + ,{"ALPHABETIC", style::NumberingType::CHARS_UPPER_LETTER} + ,{"alphabetic", style::NumberingType::CHARS_LOWER_LETTER} + ,{"CircleNum", style::NumberingType::CIRCLE_NUMBER} + ,{"ThaiArabic", style::NumberingType::CHARS_THAI} + ,{"ThaiCardText", style::NumberingType::CHARS_THAI} + ,{"ThaiLetter", style::NumberingType::CHARS_THAI} +// ,{"SBCHAR", style::NumberingType::} +// ,{"DBCHAR", style::NumberingType::} +// ,{"DBNUM1", style::NumberingType::} +// ,{"DBNUM2", style::NumberingType::} +// ,{"DBNUM3", style::NumberingType::} +// ,{"DBNUM4", style::NumberingType::} + ,{"Aiueo", style::NumberingType::AIU_FULLWIDTH_JA} + ,{"Iroha", style::NumberingType::IROHA_FULLWIDTH_JA} +// ,{"ZODIAC1", style::NumberingType::} +// ,{"ZODIAC2", style::NumberingType::} +// ,{"ZODIAC3", style::NumberingType::} +// ,{"CHINESENUM1", style::NumberingType::} +// ,{"CHINESENUM2", style::NumberingType::} +// ,{"CHINESENUM3", style::NumberingType::} + ,{"ArabicAlpha", style::NumberingType::CHARS_ARABIC} + ,{"ArabicAbjad", style::NumberingType::FULLWIDTH_ARABIC} + ,{"Ganada", style::NumberingType::HANGUL_JAMO_KO} + ,{"Chosung", style::NumberingType::HANGUL_SYLLABLE_KO} + ,{"KoreanCounting", style::NumberingType::NUMBER_HANGUL_KO} + ,{"KoreanLegal", style::NumberingType::NUMBER_LEGAL_KO} + ,{"KoreanDigital", style::NumberingType::NUMBER_DIGITAL_KO} + ,{"KoreanDigital2", style::NumberingType::NUMBER_DIGITAL2_KO} +/* possible values: +style::NumberingType:: + + CHARS_UPPER_LETTER_N + CHARS_LOWER_LETTER_N + TRANSLITERATION + NATIVE_NUMBERING + CIRCLE_NUMBER + NUMBER_LOWER_ZH + NUMBER_UPPER_ZH + NUMBER_UPPER_ZH_TW + TIAN_GAN_ZH + DI_ZI_ZH + NUMBER_TRADITIONAL_JA + AIU_HALFWIDTH_JA + IROHA_HALFWIDTH_JA + NUMBER_UPPER_KO + NUMBER_HANGUL_KO + HANGUL_JAMO_KO + HANGUL_SYLLABLE_KO + HANGUL_CIRCLED_JAMO_KO + HANGUL_CIRCLED_SYLLABLE_KO + CHARS_HEBREW + CHARS_NEPALI + CHARS_KHMER + CHARS_LAO + CHARS_TIBETAN + CHARS_CYRILLIC_UPPER_LETTER_BG + CHARS_CYRILLIC_LOWER_LETTER_BG + CHARS_CYRILLIC_UPPER_LETTER_N_BG + CHARS_CYRILLIC_LOWER_LETTER_N_BG + CHARS_CYRILLIC_UPPER_LETTER_RU + CHARS_CYRILLIC_LOWER_LETTER_RU + CHARS_CYRILLIC_UPPER_LETTER_N_RU + CHARS_CYRILLIC_LOWER_LETTER_N_RU + CHARS_CYRILLIC_UPPER_LETTER_SR + CHARS_CYRILLIC_LOWER_LETTER_SR + CHARS_CYRILLIC_UPPER_LETTER_N_SR + CHARS_CYRILLIC_LOWER_LETTER_N_SR*/ + + }; + for(const NumberingPairs& rNumberingPair : aNumberingPairs) + { + if( /*sCommand*/sNumber.equalsAscii(rNumberingPair.cWordName )) + { + nRet = rNumberingPair.nType; + break; + } + } + + } + return nRet; +} + + +static OUString lcl_ParseFormat( const OUString& rCommand ) +{ + // The command looks like: " DATE \@"dd MMMM yyyy" or "09/02/2014" + OUString command; + sal_Int32 delimPos = rCommand.indexOf("\\@"); + if (delimPos != -1) + { + // Remove whitespace permitted by standard between \@ and " + sal_Int32 wsChars = rCommand.indexOf('\"') - delimPos - 2; + command = rCommand.replaceAt(delimPos+2, wsChars, u""); + return OUString(msfilter::util::findQuotedText(command, "\\@\"", '\"')); + } + + return OUString(); +} +/*------------------------------------------------------------------------- +extract a parameter (with or without quotes) between the command and the following backslash + -----------------------------------------------------------------------*/ +static OUString lcl_ExtractToken(OUString const& rCommand, + sal_Int32 & rIndex, bool & rHaveToken, bool & rIsSwitch) +{ + rHaveToken = false; + rIsSwitch = false; + + OUStringBuffer token; + bool bQuoted(false); + for (; rIndex < rCommand.getLength(); ++rIndex) + { + sal_Unicode const currentChar(rCommand[rIndex]); + switch (currentChar) + { + case '\\': + { + if (rIndex == rCommand.getLength() - 1) + { + SAL_INFO("writerfilter.dmapper", "field: trailing escape"); + ++rIndex; + return OUString(); + } + sal_Unicode const nextChar(rCommand[rIndex+1]); + if (bQuoted || '\\' == nextChar) + { + ++rIndex; // read 2 chars + token.append(nextChar); + } + else // field switch (case insensitive) + { + rHaveToken = true; + if (token.isEmpty()) + { + rIsSwitch = true; + rIndex += 2; // read 2 chars + return rCommand.copy(rIndex - 2, 2).toAsciiUpperCase(); + } + else + { // leave rIndex, read it again next time + return token.makeStringAndClear(); + } + } + } + break; + case '\"': + if (bQuoted || !token.isEmpty()) + { + rHaveToken = true; + if (bQuoted) + { + ++rIndex; + } + return token.makeStringAndClear(); + } + else + { + bQuoted = true; + } + break; + case ' ': + if (bQuoted) + { + token.append(' '); + } + else + { + if (!token.isEmpty()) + { + rHaveToken = true; + ++rIndex; + return token.makeStringAndClear(); + } + } + break; + case '=': + if (token.isEmpty()) + { + rHaveToken = true; + ++rIndex; + return "FORMULA"; + } + else + token.append('='); + break; + default: + token.append(currentChar); + break; + } + } + assert(rIndex == rCommand.getLength()); + if (bQuoted) + { + // MS Word allows this, so just emit a debug message + SAL_INFO("writerfilter.dmapper", + "field argument with unterminated quote"); + } + rHaveToken = !token.isEmpty(); + return token.makeStringAndClear(); +} + +std::tuple<OUString, std::vector<OUString>, std::vector<OUString> > splitFieldCommand(const OUString& rCommand) +{ + OUString sType; + std::vector<OUString> arguments; + std::vector<OUString> switches; + sal_Int32 nStartIndex(0); + // tdf#54584: Field may be prepended by a backslash + // This is not an escapement, but already escaped literal "\" + // MS Word allows this, so just skip it + if ((rCommand.getLength() >= nStartIndex + 2) && + (rCommand[nStartIndex] == L'\\') && + (rCommand[nStartIndex + 1] != L'\\') && + (rCommand[nStartIndex + 1] != L' ')) + { + ++nStartIndex; + } + + do + { + bool bHaveToken; + bool bIsSwitch; + OUString const token = + lcl_ExtractToken(rCommand, nStartIndex, bHaveToken, bIsSwitch); + assert(nStartIndex <= rCommand.getLength()); + if (bHaveToken) + { + if (sType.isEmpty()) + { + sType = token.toAsciiUpperCase(); + } + else if (bIsSwitch || !switches.empty()) + { + switches.push_back(token); + } + else + { + arguments.push_back(token); + } + } + } while (nStartIndex < rCommand.getLength()); + + return std::make_tuple(sType, arguments, switches); +} + +static OUString lcl_ExtractVariableAndHint( std::u16string_view rCommand, OUString& rHint ) +{ + // the first word after "ASK " is the variable + // the text after the variable and before a '\' is the hint + // if no hint is set the variable is used as hint + // the quotes of the hint have to be removed + size_t nIndex = rCommand.find( ' ', 2); //find last space after 'ASK' + if (nIndex == std::u16string_view::npos) + return OUString(); + while (nIndex < rCommand.size() && rCommand[nIndex] == ' ') + ++nIndex; + std::u16string_view sShortCommand( rCommand.substr( nIndex ) ); //cut off the " ASK " + + sShortCommand = o3tl::getToken(sShortCommand, 0, '\\'); + sal_Int32 nIndex2 = 0; + std::u16string_view sRet = o3tl::getToken(sShortCommand, 0, ' ', nIndex2); + if( nIndex2 > 0) + rHint = sShortCommand.substr( nIndex2 ); + if( rHint.isEmpty() ) + rHint = sRet; + return OUString(sRet); +} + + +static bool lcl_FindInCommand( + const OUString& rCommand, + sal_Unicode cSwitch, + OUString& rValue ) +{ + bool bRet = false; + OUString sSearch = "\\" + OUStringChar( cSwitch ); + sal_Int32 nIndex = rCommand.indexOf( sSearch ); + if( nIndex >= 0 ) + { + bRet = true; + //find next '\' or end of string + sal_Int32 nEndIndex = rCommand.indexOf( '\\', nIndex + 1); + if( nEndIndex < 0 ) + nEndIndex = rCommand.getLength() ; + if( nEndIndex - nIndex > 3 ) + rValue = rCommand.copy( nIndex + 3, nEndIndex - nIndex - 3); + } + return bRet; +} + +static OUString lcl_trim(std::u16string_view sValue) +{ + // it seems, all kind of quotation marks are allowed around index type identifiers + // TODO apply this on bookmarks, too, if needed + return OUString(o3tl::trim(sValue)).replaceAll("\"","").replaceAll(u"“", "").replaceAll(u"”", ""); +} + +/*------------------------------------------------------------------------- + extract the number format from the command and apply the resulting number + format to the XPropertySet + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::SetNumberFormat( const OUString& rCommand, + uno::Reference< beans::XPropertySet > const& xPropertySet, + bool const bDetectFormat) +{ + OUString sFormatString = lcl_ParseFormat( rCommand ); + // find \h - hijri/luna calendar todo: what about saka/era calendar? + bool bHijri = 0 < rCommand.indexOf("\\h "); + lang::Locale aUSLocale; + aUSLocale.Language = "en"; + aUSLocale.Country = "US"; + + lang::Locale aCurrentLocale; + GetAnyProperty(PROP_CHAR_LOCALE, GetTopContext()) >>= aCurrentLocale; + + if (sFormatString.isEmpty()) + { + // No format specified. MS Word uses different formats depending on w:lang, + // "M/d/yyyy h:mm:ss AM/PM" for en-US, and "dd/MM/yyyy hh:mm:ss AM/PM" for en-GB. + // ALSO SEE: ww8par5's GetWordDefaultDateStringAsUS. + sal_Int32 nPos = rCommand.indexOf(" \\"); + OUString sCommand = nPos == -1 ? rCommand.trim() + : OUString(o3tl::trim(rCommand.subView(0, nPos))); + if (sCommand == "CREATEDATE" || sCommand == "PRINTDATE" || sCommand == "SAVEDATE") + { + try + { + css::uno::Reference<css::i18n::XNumberFormatCode> const& xNumberFormatCode = + i18n::NumberFormatMapper::create(m_xComponentContext); + sFormatString = xNumberFormatCode->getFormatCode( + css::i18n::NumberFormatIndex::DATE_SYSTEM_SHORT, aCurrentLocale).Code; + nPos = sFormatString.indexOf("YYYY"); + if (nPos == -1) + sFormatString = sFormatString.replaceFirst("YY", "YYYY"); + if (aCurrentLocale == aUSLocale) + sFormatString += " h:mm:ss AM/PM"; + else + sFormatString += " hh:mm:ss AM/PM"; + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } + } + } + OUString sFormat = ConversionHelper::ConvertMSFormatStringToSO( sFormatString, aCurrentLocale, bHijri); + //get the number formatter and convert the string to a format value + try + { + sal_Int32 nKey = 0; + uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW ); + if( bDetectFormat ) + { + uno::Reference< util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW); + xFormatter->attachNumberFormatsSupplier( xNumberSupplier ); + nKey = xFormatter->detectNumberFormat( 0, rCommand ); + } + else + { + nKey = xNumberSupplier->getNumberFormats()->addNewConverted( sFormat, aUSLocale, aCurrentLocale ); + } + xPropertySet->setPropertyValue( + getPropertyName(PROP_NUMBER_FORMAT), + uno::Any( nKey )); + } + catch(const uno::Exception&) + { + } +} + +static uno::Any lcl_getGrabBagValue( const uno::Sequence<beans::PropertyValue>& grabBag, OUString const & name ) +{ + auto pProp = std::find_if(grabBag.begin(), grabBag.end(), + [&name](const beans::PropertyValue& rProp) { return rProp.Name == name; }); + if (pProp != grabBag.end()) + return pProp->Value; + return uno::Any(); +} + +//Link the text frames. +void DomainMapper_Impl::ChainTextFrames() +{ + //can't link textboxes if there are not even two of them... + if( 2 > m_vTextFramesForChaining.size() ) + return ; + + struct TextFramesForChaining { + css::uno::Reference< css::drawing::XShape > xShape; + sal_Int32 nId; + sal_Int32 nSeq; + OUString s_mso_next_textbox; + OUString shapeName; + TextFramesForChaining() : nId(0), nSeq(0) {} + } ; + typedef std::map <OUString, TextFramesForChaining> ChainMap; + + try + { + ChainMap aTextFramesForChainingHelper; + ::std::vector<TextFramesForChaining> chainingWPS; + OUString sChainNextName("ChainNextName"); + + //learn about ALL of the textboxes and their chaining values first - because frames are processed in no specific order. + for( const auto& rTextFrame : m_vTextFramesForChaining ) + { + uno::Reference<text::XTextContent> xTextContent(rTextFrame, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo; + if( xPropertySet.is() ) + xPropertySetInfo = xPropertySet->getPropertySetInfo(); + uno::Sequence<beans::PropertyValue> aGrabBag; + uno::Reference<lang::XServiceInfo> xServiceInfo(xPropertySet, uno::UNO_QUERY); + + TextFramesForChaining aChainStruct; + OUString sShapeName; + OUString sLinkChainName; + + //The chaining name and the shape name CAN be different in .docx. + //MUST use LinkDisplayName/ChainName as the shape name for establishing links. + if ( xServiceInfo->supportsService("com.sun.star.text.TextFrame") ) + { + xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag; + xPropertySet->getPropertyValue("LinkDisplayName") >>= sShapeName; + } + else + { + xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + xPropertySet->getPropertyValue("ChainName") >>= sShapeName; + } + + lcl_getGrabBagValue( aGrabBag, "Txbx-Id") >>= aChainStruct.nId; + lcl_getGrabBagValue( aGrabBag, "Txbx-Seq") >>= aChainStruct.nSeq; + lcl_getGrabBagValue( aGrabBag, "LinkChainName") >>= sLinkChainName; + lcl_getGrabBagValue( aGrabBag, "mso-next-textbox") >>= aChainStruct.s_mso_next_textbox; + + //Sometimes the shape names have not been imported. If not, we may have a fallback name. + //Set name later, only if required for linking. + aChainStruct.shapeName = sShapeName; + + if (!sLinkChainName.isEmpty()) + { + aChainStruct.xShape = rTextFrame; + aTextFramesForChainingHelper[sLinkChainName] = aChainStruct; + } + if (aChainStruct.s_mso_next_textbox.isEmpty()) + { // no VML chaining => try to chain DrawingML via IDs + aChainStruct.xShape = rTextFrame; + if (!sLinkChainName.isEmpty()) + { // for member of group shapes, TestTdf73499 + aChainStruct.shapeName = sLinkChainName; + } + chainingWPS.emplace_back(aChainStruct); + } + } + + //if mso-next-textbox tags are provided, create those vml-style links first. Afterwards we will make dml-style id/seq links. + for (auto& msoItem : aTextFramesForChainingHelper) + { + //if no mso-next-textbox, we are done. + //if it points to itself, we are done. + if( !msoItem.second.s_mso_next_textbox.isEmpty() + && msoItem.second.s_mso_next_textbox != msoItem.first ) + { + ChainMap::iterator nextFinder=aTextFramesForChainingHelper.find(msoItem.second.s_mso_next_textbox); + if( nextFinder != aTextFramesForChainingHelper.end() ) + { + //if the frames have no name yet, then set them. LinkDisplayName / ChainName are read-only. + if (msoItem.second.shapeName.isEmpty()) + { + uno::Reference< container::XNamed > xNamed( msoItem.second.xShape, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + xNamed->setName( msoItem.first ); + msoItem.second.shapeName = msoItem.first; + } + } + if (nextFinder->second.shapeName.isEmpty()) + { + uno::Reference< container::XNamed > xNamed( nextFinder->second.xShape, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + xNamed->setName( nextFinder->first ); + nextFinder->second.shapeName = msoItem.first; + } + } + + uno::Reference<text::XTextContent> xTextContent(msoItem.second.xShape, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + + //The reverse chaining happens automatically, so only one direction needs to be set + xPropertySet->setPropertyValue(sChainNextName, uno::Any(nextFinder->second.shapeName)); + + //the last item in an mso-next-textbox chain is indistinguishable from id/seq items. Now that it is handled, remove it. + if( nextFinder->second.s_mso_next_textbox.isEmpty() ) + aTextFramesForChainingHelper.erase(nextFinder->first); + } + } + } + + //TODO: Perhaps allow reverse sequences when mso-layout-flow-alt = "bottom-to-top" + const sal_Int32 nDirection = 1; + + //Finally - go through and attach the chains based on matching ID and incremented sequence number (dml-style). + for (const auto& rOuter : chainingWPS) + { + for (const auto& rInner : chainingWPS) + { + if (rInner.nId == rOuter.nId) + { + if (rInner.nSeq == (rOuter.nSeq + nDirection)) + { + uno::Reference<text::XTextContent> const xTextContent(rOuter.xShape, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + + //The reverse chaining happens automatically, so only one direction needs to be set + xPropertySet->setPropertyValue(sChainNextName, uno::Any(rInner.shapeName)); + break ; //there cannot be more than one next frame + } + } + } + } + m_vTextFramesForChaining.clear(); //clear the vector + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +void DomainMapper_Impl::PushTextBoxContent() +{ + if (m_bIsInTextBox) + return; + + // tdf#154481: check for TOC creation with empty field stack, + // and close TOC, unless pages will lost. FIXME. + if (IsInTOC() && m_aFieldStack.size() == 0) + { + m_bStartTOC = false; + SAL_WARN("writerfilter.dmapper", + "broken TOC creation in textbox, but field stack is empty, so closing TOC!"); + } + + try + { + uno::Reference<text::XTextFrame> xTBoxFrame( + m_xTextFactory->createInstance("com.sun.star.text.TextFrame"), uno::UNO_QUERY_THROW); + uno::Reference<container::XNamed>(xTBoxFrame, uno::UNO_QUERY_THROW) + ->setName("textbox" + OUString::number(m_xPendingTextBoxFrames.size() + 1)); + uno::Reference<text::XTextAppendAndConvert>(m_aTextAppendStack.top().xTextAppend, + uno::UNO_QUERY_THROW) + ->appendTextContent(xTBoxFrame, beans::PropertyValues()); + m_xPendingTextBoxFrames.push(xTBoxFrame); + + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xTBoxFrame, uno::UNO_QUERY_THROW), {})); + m_bIsInTextBox = true; + + appendTableManager(); + appendTableHandler(); + getTableManager().startLevel(); + } + catch (uno::Exception& e) + { + SAL_WARN("writerfilter.dmapper", "Exception during creating textbox (" + e.Message + ")!"); + } +} + +void DomainMapper_Impl::PopTextBoxContent() +{ + if (!m_bIsInTextBox || m_xPendingTextBoxFrames.empty()) + return; + + if (uno::Reference<text::XTextFrame>(m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY).is()) + { + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } + RemoveLastParagraph(); + + m_aTextAppendStack.pop(); + m_bIsInTextBox = false; + } +} + +void DomainMapper_Impl::AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape) +{ + // Without textbox or shape pointless to continue + if (m_xPendingTextBoxFrames.empty() || !xShape) + return; + + uno::Reference< drawing::XShapes >xGroup(xShape, uno::UNO_QUERY); + uno::Reference< beans::XPropertySet >xProps(xShape, uno::UNO_QUERY); + + // If this is a group go inside + if (xGroup) + for (sal_Int32 i = 0; i < xGroup->getCount(); ++i) + AttachTextBoxContentToShape( + uno::Reference<drawing::XShape>(xGroup->getByIndex(i), uno::UNO_QUERY)); + + // if this shape has to be a textbox, attach the frame + if (!xProps->getPropertyValue("TextBox").get<bool>()) + return; + + // if this is a textbox there must be a waiting frame + auto xTextBox = m_xPendingTextBoxFrames.front(); + if (!xTextBox) + return; + + // Pop the pending frames + m_xPendingTextBoxFrames.pop(); + + // Attach the textbox to the shape + try + { + xProps->setPropertyValue("TextBoxContent", uno::Any(xTextBox)); + } + catch (...) + { + SAL_WARN("writerfilter.dmapper", "Exception while trying to attach textboxes!"); + return; + } + + // If attaching is successful, then do the linking + try + { + // Get the name of the textbox + OUString sTextBoxName; + uno::Reference<container::XNamed> xName(xTextBox, uno::UNO_QUERY); + if (xName && !xName->getName().isEmpty()) + sTextBoxName = xName->getName(); + + // Try to get the grabbag + uno::Sequence<beans::PropertyValue> aOldGrabBagSeq; + if (xProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag")) + xProps->getPropertyValue("InteropGrabBag") >>= aOldGrabBagSeq; + + // If the grabbag successfully get... + if (!aOldGrabBagSeq.hasElements()) + return; + + // Check for the existing linking information + bool bSuccess = false; + beans::PropertyValues aNewGrabBagSeq; + const auto& aHasLink = lcl_getGrabBagValue(aOldGrabBagSeq, "TxbxHasLink"); + + // If there must be a link, do it + if (aHasLink.hasValue() && aHasLink.get<bool>()) + { + auto aLinkProp = comphelper::makePropertyValue("LinkChainName", sTextBoxName); + for (sal_uInt32 i = 0; i < aOldGrabBagSeq.size(); ++i) + { + aNewGrabBagSeq.realloc(i + 1); + // If this is the link name replace it + if (!aOldGrabBagSeq[i].Name.isEmpty() && !aLinkProp.Name.isEmpty() + && (aOldGrabBagSeq[i].Name == aLinkProp.Name)) + { + aNewGrabBagSeq.getArray()[i] = aLinkProp; + bSuccess = true; + } + // else copy + else + aNewGrabBagSeq.getArray()[i] = aOldGrabBagSeq[i]; + } + + // If there was no replacement, append the linking data + if (!bSuccess) + { + aNewGrabBagSeq.realloc(aNewGrabBagSeq.size() + 1); + aNewGrabBagSeq.getArray()[aNewGrabBagSeq.size() - 1] = aLinkProp; + bSuccess = true; + } + } + + // If the linking changed the grabbag, apply the modifications + if (aNewGrabBagSeq.hasElements() && bSuccess) + { + xProps->setPropertyValue("InteropGrabBag", uno::Any(aNewGrabBagSeq)); + m_vTextFramesForChaining.push_back(xShape); + } + } + catch (...) + { + SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!"); + } +} + +uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName) +{ + // query master, create if not available + uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters(); + uno::Reference< beans::XPropertySet > xMaster; + OUString sFieldMasterService( OUString::createFromAscii(pFieldMasterService) ); + OUStringBuffer aFieldMasterName; + OUString sDatabaseDataSourceName = GetSettingsTable()->GetCurrentDatabaseDataSource(); + bool bIsMergeField = sFieldMasterService.endsWith("Database"); + aFieldMasterName.appendAscii( pFieldMasterService ); + aFieldMasterName.append('.'); + if ( bIsMergeField && !sDatabaseDataSourceName.isEmpty() ) + { + aFieldMasterName.append(sDatabaseDataSourceName); + aFieldMasterName.append('.'); + } + aFieldMasterName.append(rFieldMasterName); + OUString sFieldMasterName = aFieldMasterName.makeStringAndClear(); + if(xFieldMasterAccess->hasByName(sFieldMasterName)) + { + //get the master + xMaster.set(xFieldMasterAccess->getByName(sFieldMasterName), uno::UNO_QUERY_THROW); + } + else if( m_xTextFactory.is() ) + { + //create the master + xMaster.set( m_xTextFactory->createInstance(sFieldMasterService), uno::UNO_QUERY_THROW); + if ( !bIsMergeField || sDatabaseDataSourceName.isEmpty() ) + { + //set the master's name + xMaster->setPropertyValue( + getPropertyName(PROP_NAME), + uno::Any(rFieldMasterName)); + } else { + // set database data, based on the "databasename.tablename" of sDatabaseDataSourceName + xMaster->setPropertyValue( + getPropertyName(PROP_DATABASE_NAME), + uno::Any(sDatabaseDataSourceName.copy(0, sDatabaseDataSourceName.indexOf('.')))); + xMaster->setPropertyValue( + getPropertyName(PROP_COMMAND_TYPE), + uno::Any(sal_Int32(0))); + xMaster->setPropertyValue( + getPropertyName(PROP_DATATABLE_NAME), + uno::Any(sDatabaseDataSourceName.copy(sDatabaseDataSourceName.indexOf('.') + 1))); + xMaster->setPropertyValue( + getPropertyName(PROP_DATACOLUMN_NAME), + uno::Any(rFieldMasterName)); + } + } + return xMaster; +} + +void DomainMapper_Impl::PushFieldContext() +{ + m_bParaHadField = true; + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("pushFieldContext"); +#endif + + uno::Reference<text::XTextCursor> xCrsr; + if (!m_aTextAppendStack.empty()) + { + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + xCrsr = xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd()); + } + + uno::Reference< text::XTextRange > xStart; + if (xCrsr.is()) + xStart = xCrsr->getStart(); + m_aFieldStack.push_back(new FieldContext(xStart)); +} +/*------------------------------------------------------------------------- +//the current field context waits for the completion of the command + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsOpenFieldCommand() const +{ + return !m_aFieldStack.empty() && !m_aFieldStack.back()->IsCommandCompleted(); +} +/*------------------------------------------------------------------------- +//the current field context waits for the completion of the command + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsOpenField() const +{ + return !m_aFieldStack.empty(); +} + +// Mark top field context as containing a fixed field +void DomainMapper_Impl::SetFieldLocked() +{ + if (IsOpenField()) + m_aFieldStack.back()->SetFieldLocked(); +} + +HeaderFooterContext::HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth) + : m_bTextInserted(bTextInserted) + , m_nTableDepth(nTableDepth) +{ +} + +bool HeaderFooterContext::getTextInserted() const +{ + return m_bTextInserted; +} + +sal_Int32 HeaderFooterContext::getTableDepth() const { return m_nTableDepth; } + +FieldContext::FieldContext(uno::Reference< text::XTextRange > const& xStart) + : m_bFieldCommandCompleted(false) + , m_xStartRange( xStart ) + , m_bFieldLocked( false ) +{ + m_pProperties = new PropertyMap(); +} + + +FieldContext::~FieldContext() +{ +} + +void FieldContext::SetTextField(uno::Reference<text::XTextField> const& xTextField) +{ +#ifndef NDEBUG + if (xTextField.is()) + { + uno::Reference<lang::XServiceInfo> const xServiceInfo(xTextField, uno::UNO_QUERY); + assert(xServiceInfo.is()); + // those must be set by SetFormField() + assert(!xServiceInfo->supportsService("com.sun.star.text.Fieldmark") + && !xServiceInfo->supportsService("com.sun.star.text.FormFieldmark")); + } +#endif + m_xTextField = xTextField; +} + +void FieldContext::CacheVariableValue(const uno::Any& rAny) +{ + rAny >>= m_sVariableValue; +} + +void FieldContext::AppendCommand(std::u16string_view rPart) +{ + m_sCommand += rPart; +} + +::std::vector<OUString> FieldContext::GetCommandParts() const +{ + ::std::vector<OUString> aResult; + sal_Int32 nIndex = 0; + bool bInString = false; + OUString sPart; + while (nIndex != -1) + { + OUString sToken = GetCommand().getToken(0, ' ', nIndex); + bool bInStringNext = bInString; + + if (sToken.isEmpty()) + continue; + + if (sToken[0] == '"') + { + bInStringNext = true; + sToken = sToken.copy(1); + } + if (sToken.endsWith("\"")) + { + bInStringNext = false; + sToken = sToken.copy(0, sToken.getLength() - 1); + } + + if (bInString) + { + sPart += " " + sToken; + if (!bInStringNext) + { + aResult.push_back(sPart); + } + } + else + { + if (bInStringNext) + { + sPart = sToken; + } + else + { + aResult.push_back(sToken); + } + } + + bInString = bInStringNext; + } + + return aResult; +} + +/*------------------------------------------------------------------------- +//collect the pieces of the command + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::AppendFieldCommand(OUString const & rPartOfCommand) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("appendFieldCommand"); + TagLogger::getInstance().chars(rPartOfCommand); + TagLogger::getInstance().endElement(); +#endif + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + pContext->AppendCommand( rPartOfCommand ); + } +} + + +typedef std::multimap < sal_Int32, OUString > TOCStyleMap; + + +static ww::eField GetWW8FieldId(OUString const& rType) +{ + std::unordered_map<OUString, ww::eField> mapID + { + {"ADDRESSBLOCK", ww::eADDRESSBLOCK}, + {"ADVANCE", ww::eADVANCE}, + {"ASK", ww::eASK}, + {"AUTONUM", ww::eAUTONUM}, + {"AUTONUMLGL", ww::eAUTONUMLGL}, + {"AUTONUMOUT", ww::eAUTONUMOUT}, + {"AUTOTEXT", ww::eAUTOTEXT}, + {"AUTOTEXTLIST", ww::eAUTOTEXTLIST}, + {"AUTHOR", ww::eAUTHOR}, + {"BARCODE", ww::eBARCODE}, + {"BIDIOUTLINE", ww::eBIDIOUTLINE}, + {"DATE", ww::eDATE}, + {"COMMENTS", ww::eCOMMENTS}, + {"COMPARE", ww::eCOMPARE}, + {"CONTROL", ww::eCONTROL}, + {"CREATEDATE", ww::eCREATEDATE}, + {"DATABASE", ww::eDATABASE}, + {"DDEAUTOREF", ww::eDDEAUTOREF}, + {"DDEREF", ww::eDDEREF}, + {"DOCPROPERTY", ww::eDOCPROPERTY}, + {"DOCVARIABLE", ww::eDOCVARIABLE}, + {"EDITTIME", ww::eEDITTIME}, + {"EMBED", ww::eEMBED}, + {"EQ", ww::eEQ}, + {"FILLIN", ww::eFILLIN}, + {"FILENAME", ww::eFILENAME}, + {"FILESIZE", ww::eFILESIZE}, + {"FOOTREF", ww::eFOOTREF}, +// {"FORMULA", ww::}, + {"FORMCHECKBOX", ww::eFORMCHECKBOX}, + {"FORMDROPDOWN", ww::eFORMDROPDOWN}, + {"FORMTEXT", ww::eFORMTEXT}, + {"GLOSSREF", ww::eGLOSSREF}, + {"GOTOBUTTON", ww::eGOTOBUTTON}, + {"GREETINGLINE", ww::eGREETINGLINE}, + {"HTMLCONTROL", ww::eHTMLCONTROL}, + {"HYPERLINK", ww::eHYPERLINK}, + {"IF", ww::eIF}, + {"INFO", ww::eINFO}, + {"INCLUDEPICTURE", ww::eINCLUDEPICTURE}, + {"INCLUDETEXT", ww::eINCLUDETEXT}, + {"INCLUDETIFF", ww::eINCLUDETIFF}, + {"KEYWORDS", ww::eKEYWORDS}, + {"LASTSAVEDBY", ww::eLASTSAVEDBY}, + {"LINK", ww::eLINK}, + {"LISTNUM", ww::eLISTNUM}, + {"MACRO", ww::eMACRO}, + {"MACROBUTTON", ww::eMACROBUTTON}, + {"MERGEDATA", ww::eMERGEDATA}, + {"MERGEFIELD", ww::eMERGEFIELD}, + {"MERGEINC", ww::eMERGEINC}, + {"MERGEREC", ww::eMERGEREC}, + {"MERGESEQ", ww::eMERGESEQ}, + {"NEXT", ww::eNEXT}, + {"NEXTIF", ww::eNEXTIF}, + {"NOTEREF", ww::eNOTEREF}, + {"PAGE", ww::ePAGE}, + {"PAGEREF", ww::ePAGEREF}, + {"PLUGIN", ww::ePLUGIN}, + {"PRINT", ww::ePRINT}, + {"PRINTDATE", ww::ePRINTDATE}, + {"PRIVATE", ww::ePRIVATE}, + {"QUOTE", ww::eQUOTE}, + {"RD", ww::eRD}, + {"REF", ww::eREF}, + {"REVNUM", ww::eREVNUM}, + {"SAVEDATE", ww::eSAVEDATE}, + {"SECTION", ww::eSECTION}, + {"SECTIONPAGES", ww::eSECTIONPAGES}, + {"SEQ", ww::eSEQ}, + {"SET", ww::eSET}, + {"SKIPIF", ww::eSKIPIF}, + {"STYLEREF", ww::eSTYLEREF}, + {"SUBSCRIBER", ww::eSUBSCRIBER}, + {"SUBJECT", ww::eSUBJECT}, + {"SYMBOL", ww::eSYMBOL}, + {"TA", ww::eTA}, + {"TEMPLATE", ww::eTEMPLATE}, + {"TIME", ww::eTIME}, + {"TITLE", ww::eTITLE}, + {"TOA", ww::eTOA}, + {"USERINITIALS", ww::eUSERINITIALS}, + {"USERADDRESS", ww::eUSERADDRESS}, + {"USERNAME", ww::eUSERNAME}, + + {"TOC", ww::eTOC}, + {"TC", ww::eTC}, + {"NUMCHARS", ww::eNUMCHARS}, + {"NUMWORDS", ww::eNUMWORDS}, + {"NUMPAGES", ww::eNUMPAGES}, + {"INDEX", ww::eINDEX}, + {"XE", ww::eXE}, + {"BIBLIOGRAPHY", ww::eBIBLIOGRAPHY}, + {"CITATION", ww::eCITATION}, + }; + auto const it = mapID.find(rType); + return (it == mapID.end()) ? ww::eNONE : it->second; +} + +static const FieldConversionMap_t & lcl_GetFieldConversion() +{ + static const FieldConversionMap_t aFieldConversionMap + { +// {"ADDRESSBLOCK", {"", FIELD_ADDRESSBLOCK }}, +// {"ADVANCE", {"", FIELD_ADVANCE }}, + {"ASK", {"SetExpression", FIELD_ASK }}, + {"AUTONUM", {"SetExpression", FIELD_AUTONUM }}, + {"AUTONUMLGL", {"SetExpression", FIELD_AUTONUMLGL }}, + {"AUTONUMOUT", {"SetExpression", FIELD_AUTONUMOUT }}, + {"AUTHOR", {"DocInfo.CreateAuthor", FIELD_AUTHOR }}, + {"DATE", {"DateTime", FIELD_DATE }}, + {"COMMENTS", {"DocInfo.Description", FIELD_COMMENTS }}, + {"CREATEDATE", {"DocInfo.CreateDateTime", FIELD_CREATEDATE }}, + {"DOCPROPERTY", {"", FIELD_DOCPROPERTY }}, + {"DOCVARIABLE", {"User", FIELD_DOCVARIABLE }}, + {"EDITTIME", {"DocInfo.EditTime", FIELD_EDITTIME }}, + {"EQ", {"", FIELD_EQ }}, + {"FILLIN", {"Input", FIELD_FILLIN }}, + {"FILENAME", {"FileName", FIELD_FILENAME }}, +// {"FILESIZE", {"", FIELD_FILESIZE }}, + {"FORMULA", {"TableFormula", FIELD_FORMULA }}, + {"FORMCHECKBOX", {"", FIELD_FORMCHECKBOX }}, + {"FORMDROPDOWN", {"DropDown", FIELD_FORMDROPDOWN }}, + {"FORMTEXT", {"Input", FIELD_FORMTEXT }}, + {"GOTOBUTTON", {"", FIELD_GOTOBUTTON }}, + {"HYPERLINK", {"", FIELD_HYPERLINK }}, + {"IF", {"ConditionalText", FIELD_IF }}, +// {"INFO", {"", FIELD_INFO }}, + {"INCLUDEPICTURE", {"", FIELD_INCLUDEPICTURE}}, + {"KEYWORDS", {"DocInfo.KeyWords", FIELD_KEYWORDS }}, + {"LASTSAVEDBY", {"DocInfo.ChangeAuthor", FIELD_LASTSAVEDBY }}, + {"MACROBUTTON", {"Macro", FIELD_MACROBUTTON }}, + {"MERGEFIELD", {"Database", FIELD_MERGEFIELD }}, + {"MERGEREC", {"DatabaseNumberOfSet", FIELD_MERGEREC }}, +// {"MERGESEQ", {"", FIELD_MERGESEQ }}, + {"NEXT", {"DatabaseNextSet", FIELD_NEXT }}, + {"NEXTIF", {"DatabaseNextSet", FIELD_NEXTIF }}, + {"PAGE", {"PageNumber", FIELD_PAGE }}, + {"PAGEREF", {"GetReference", FIELD_PAGEREF }}, + {"PRINTDATE", {"DocInfo.PrintDateTime", FIELD_PRINTDATE }}, + {"REF", {"GetReference", FIELD_REF }}, + {"REVNUM", {"DocInfo.Revision", FIELD_REVNUM }}, + {"SAVEDATE", {"DocInfo.ChangeDateTime", FIELD_SAVEDATE }}, +// {"SECTION", {"", FIELD_SECTION }}, +// {"SECTIONPAGES", {"", FIELD_SECTIONPAGES }}, + {"SEQ", {"SetExpression", FIELD_SEQ }}, + {"SET", {"SetExpression", FIELD_SET }}, +// {"SKIPIF", {"", FIELD_SKIPIF }}, +// {"STYLEREF", {"", FIELD_STYLEREF }}, + {"SUBJECT", {"DocInfo.Subject", FIELD_SUBJECT }}, + {"SYMBOL", {"", FIELD_SYMBOL }}, + {"TEMPLATE", {"TemplateName", FIELD_TEMPLATE }}, + {"TIME", {"DateTime", FIELD_TIME }}, + {"TITLE", {"DocInfo.Title", FIELD_TITLE }}, + {"USERINITIALS", {"Author", FIELD_USERINITIALS }}, +// {"USERADDRESS", {"", FIELD_USERADDRESS }}, + {"USERNAME", {"Author", FIELD_USERNAME }}, + + + {"TOC", {"com.sun.star.text.ContentIndex", FIELD_TOC }}, + {"TC", {"com.sun.star.text.ContentIndexMark", FIELD_TC }}, + {"NUMCHARS", {"CharacterCount", FIELD_NUMCHARS }}, + {"NUMWORDS", {"WordCount", FIELD_NUMWORDS }}, + {"NUMPAGES", {"PageCount", FIELD_NUMPAGES }}, + {"INDEX", {"com.sun.star.text.DocumentIndex", FIELD_INDEX }}, + {"XE", {"com.sun.star.text.DocumentIndexMark", FIELD_XE }}, + {"BIBLIOGRAPHY",{"com.sun.star.text.Bibliography", FIELD_BIBLIOGRAPHY }}, + {"CITATION", {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION }}, + }; + + return aFieldConversionMap; +} + +static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion() +{ + static const FieldConversionMap_t aEnhancedFieldConversionMap = + { + {"FORMCHECKBOX", {"FormFieldmark", FIELD_FORMCHECKBOX}}, + {"FORMDROPDOWN", {"FormFieldmark", FIELD_FORMDROPDOWN}}, + {"FORMTEXT", {"Fieldmark", FIELD_FORMTEXT}}, + }; + + return aEnhancedFieldConversionMap; +} + +void DomainMapper_Impl::handleFieldSet + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > const & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + OUString sVariable, sHint; + + sVariable = lcl_ExtractVariableAndHint(pContext->GetCommand(), sHint); + + // remove surrounding "" if exists + if(sHint.getLength() >= 2) + { + std::u16string_view sTmp = o3tl::trim(sHint); + if (o3tl::starts_with(sTmp, u"\"") && o3tl::ends_with(sTmp, u"\"")) + { + sHint = sTmp.substr(1, sTmp.size() - 2); + } + } + + // determine field master name + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", sVariable); + + // a set field is a string + xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + uno::Any aAnyHint(sHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_HINT), aAnyHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), aAnyHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // Mimic MS Word behavior (hide the SET) + xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false)); +} + +void DomainMapper_Impl::handleFieldAsk + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + //doesn the command contain a variable name? + OUString sVariable, sHint; + + sVariable = lcl_ExtractVariableAndHint( pContext->GetCommand(), + sHint ); + if(!sVariable.isEmpty()) + { + // determine field master name + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", sVariable ); + // An ASK field is always a string of characters + xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + // set input flag at the field + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_INPUT), uno::Any( true )); + // set the prompt + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), + uno::Any( sHint )); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + // The ASK has no field value to display + xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false)); + } + else + { + //don't insert the field + //todo: maybe import a 'normal' input field here? + xFieldInterface = nullptr; + } +} + +/** + * Converts a Microsoft Word field formula into LibreOffice syntax + * @param input The Microsoft Word field formula, with no leading '=' sign + * @return An equivalent LibreOffice field formula + */ +OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) { + + if (!m_pSettingsTable) + { + return input; + } + + OUString listSeparator = m_pSettingsTable->GetListSeparator(); + + /* Replace logical condition functions with LO equivalent operators */ + OUString changed = input.replaceAll(" <> ", " NEQ "); + changed = changed.replaceAll(" <= ", " LEQ "); + changed = changed.replaceAll(" >= ", " GEQ "); + changed = changed.replaceAll(" = " , " EQ "); + changed = changed.replaceAll(" < " , " L "); + changed = changed.replaceAll(" > " , " G "); + + changed = changed.replaceAll("<>", " NEQ "); + changed = changed.replaceAll("<=", " LEQ "); + changed = changed.replaceAll(">=", " GEQ "); + changed = changed.replaceAll("=" , " EQ "); + changed = changed.replaceAll("<" , " L "); + changed = changed.replaceAll(">" , " G "); + + /* Replace function calls with infix keywords for AND(), OR(), and ROUND(). Nothing needs to be + * done for NOT(). This simple regex will work properly with most common cases. However, it may + * not work correctly when the arguments are nested subcalls to other functions, like + * ROUND(MIN(1,2),MAX(3,4)). See TDF#134765. */ + icu::ErrorCode status; + icu::UnicodeString usInput(changed.getStr()); + const uint32_t rMatcherFlags = UREGEX_CASE_INSENSITIVE; + OUString regex = "\\b(AND|OR|ROUND)\\s*\\(\\s*([^" + listSeparator + "]+)\\s*" + listSeparator + "\\s*([^)]+)\\s*\\)"; + icu::UnicodeString usRegex(regex.getStr()); + icu::RegexMatcher rmatch1(usRegex, usInput, rMatcherFlags, status); + usInput = rmatch1.replaceAll(icu::UnicodeString("(($2) $1 ($3))"), status); + + /* Assumes any remaining list separators separate arguments to functions that accept lists + * (SUM, MIN, MAX, MEAN, etc.) */ + usInput.findAndReplace(icu::UnicodeString(listSeparator.getStr()), "|"); + + /* Surround single cell references with angle brackets. + * If there is ever added a function name that ends with a digit, this regex will need to be revisited. */ + icu::RegexMatcher rmatch2("\\b([A-Z]{1,3}[0-9]+)\\b(?![(])", usInput, rMatcherFlags, status); + usInput = rmatch2.replaceAll(icu::UnicodeString("<$1>"), status); + + /* Cell references must be upper case + * TODO: convert reference to other tables, e.g. SUM(Table1 A1:B2), where "Table1" is a bookmark of the table, + * TODO: also column range A:A */ + icu::RegexMatcher rmatch3("(<[a-z]{1,3}[0-9]+>|\\b(above|below|left|right)\\b)", usInput, rMatcherFlags, status); + icu::UnicodeString replacedCellRefs; + while (rmatch3.find(status) && status.isSuccess()) { + rmatch3.appendReplacement(replacedCellRefs, rmatch3.group(status).toUpper(), status); + } + rmatch3.appendTail(replacedCellRefs); + + /* Fix up cell ranges */ + icu::RegexMatcher rmatch4("<([A-Z]{1,3}[0-9]+)>:<([A-Z]{1,3}[0-9]+)>", replacedCellRefs, rMatcherFlags, status); + usInput = rmatch4.replaceAll(icu::UnicodeString("<$1:$2>"), status); + + /* Fix up user defined names */ + icu::RegexMatcher rmatch5("\\bDEFINED\\s*\\(<([A-Z]+[0-9]+)>\\)", usInput, rMatcherFlags, status); + usInput = rmatch5.replaceAll(icu::UnicodeString("DEFINED($1)"), status); + + /* Prepare replace of ABOVE/BELOW/LEFT/RIGHT by adding spaces around them */ + icu::RegexMatcher rmatch6("\\b(ABOVE|BELOW|LEFT|RIGHT)\\b", usInput, rMatcherFlags, status); + usInput = rmatch6.replaceAll(icu::UnicodeString(" $1 "), status); + + /* DOCX allows to set decimal symbol independently from the locale of the document, so if + * needed, convert decimal comma to get working formula in a document language (locale), + * which doesn't use decimal comma */ + if ( m_pSettingsTable->GetDecimalSymbol() == "," && !m_bIsDecimalComma ) + { + icu::RegexMatcher rmatch7("\\b([0-9]+),([0-9]+([eE][-]?[0-9]+)?)\\b", usInput, rMatcherFlags, status); + usInput = rmatch7.replaceAll(icu::UnicodeString("$1.$2"), status); + } + + return OUString(usInput.getTerminatedBuffer()); +} + +void DomainMapper_Impl::handleFieldFormula + (const FieldContextPtr& pContext, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + OUString command = pContext->GetCommand().trim(); + + // Remove number formatting from \# to end of command + // TODO: handle custom number formatting + sal_Int32 delimPos = command.indexOf("\\#"); + if (delimPos != -1) + { + command = command.replaceAt(delimPos, command.getLength() - delimPos, u"").trim(); + } + + // command must contains = and at least another char + if (command.getLength() < 2) + return; + + // we don't copy the = symbol from the command + OUString formula = convertFieldFormula(command.copy(1)); + + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(formula)); + xFieldProperties->setPropertyValue(getPropertyName(PROP_NUMBER_FORMAT), uno::Any(sal_Int32(0))); + xFieldProperties->setPropertyValue("IsShowFormula", uno::Any(false)); + + // grab-bag the original and converted formula + if (hasTableManager()) + { + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_CELL_FORMULA, uno::Any(command.copy(1)), true, CELL_GRAB_BAG); + pPropMap->Insert(PROP_CELL_FORMULA_CONVERTED, uno::Any(formula), true, CELL_GRAB_BAG); + getTableManager().cellProps(pPropMap); + } +} + +void DomainMapper_Impl::handleRubyEQField( const FieldContextPtr& pContext) +{ + const OUString & rCommand(pContext->GetCommand()); + sal_Int32 nIndex = 0, nEnd = 0; + RubyInfo aInfo ; + nIndex = rCommand.indexOf("\\* jc" ); + if (nIndex != -1) + { + nIndex += 5; + sal_uInt32 nJc = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex)); + const sal_Int32 aRubyAlignValues[] = + { + NS_ooxml::LN_Value_ST_RubyAlign_center, + NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter, + NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace, + NS_ooxml::LN_Value_ST_RubyAlign_left, + NS_ooxml::LN_Value_ST_RubyAlign_right, + NS_ooxml::LN_Value_ST_RubyAlign_rightVertical, + }; + aInfo.nRubyAlign = aRubyAlignValues[(nJc<SAL_N_ELEMENTS(aRubyAlignValues))?nJc:0]; + } + + // we don't parse or use the font field in rCommand + + nIndex = rCommand.indexOf("\\* hps" ); + if (nIndex != -1) + { + nIndex += 6; + aInfo.nHps = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex)); + } + + nIndex = rCommand.indexOf("\\o"); + if (nIndex == -1) + return; + nIndex = rCommand.indexOf('(', nIndex); + if (nIndex == -1) + return; + nEnd = rCommand.lastIndexOf(')'); + if (nEnd == -1) + return; + if (nEnd <= nIndex) + return; + + std::u16string_view sRubyParts = rCommand.subView(nIndex+1,nEnd-nIndex-1); + nIndex = 0; + std::u16string_view sPart1 = o3tl::getToken(sRubyParts, 0, ',', nIndex); + std::u16string_view sPart2 = o3tl::getToken(sRubyParts, 0, ',', nIndex); + size_t nIndex2 = 0; + size_t nEnd2 = 0; + if ((nIndex2 = sPart1.find('(')) != std::u16string_view::npos && (nEnd2 = sPart1.rfind(')')) != std::u16string_view::npos && nEnd2 > nIndex2) + { + aInfo.sRubyText = sPart1.substr(nIndex2+1,nEnd2-nIndex2-1); + } + + PropertyMapPtr pRubyContext(new PropertyMap()); + pRubyContext->InsertProps(GetTopContext()); + if (aInfo.nHps > 0) + { + double fVal = double(aInfo.nHps) / 2.; + uno::Any aVal( fVal ); + + pRubyContext->Insert(PROP_CHAR_HEIGHT, aVal); + pRubyContext->Insert(PROP_CHAR_HEIGHT_ASIAN, aVal); + } + PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pRubyContext->GetPropertyValues()); + aInfo.sRubyStyle = m_rDMapper.getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false); + PropertyMapPtr pCharContext(new PropertyMap()); + if (m_pLastCharacterContext) + pCharContext->InsertProps(m_pLastCharacterContext); + pCharContext->InsertProps(pContext->getProperties()); + pCharContext->Insert(PROP_RUBY_TEXT, uno::Any( aInfo.sRubyText ) ); + pCharContext->Insert(PROP_RUBY_ADJUST, uno::Any(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign)))); + if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical ) + pCharContext->Insert(PROP_RUBY_POSITION, uno::Any(css::text::RubyPosition::INTER_CHARACTER)); + pCharContext->Insert(PROP_RUBY_STYLE, uno::Any(aInfo.sRubyStyle)); + appendTextPortion(OUString(sPart2), pCharContext); + +} + +void DomainMapper_Impl::handleAutoNum + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > const & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + //create a sequence field master "AutoNr" + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", + "AutoNr"); + + xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE), + uno::Any(text::SetVariableType::SEQUENCE)); + + //apply the numbering type + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); +} + +void DomainMapper_Impl::handleAuthor + (std::u16string_view, + uno::Reference< beans::XPropertySet > const& xFieldProperties, + FieldId eFieldId ) +{ + if (eFieldId == FIELD_USERNAME) + xFieldProperties->setPropertyValue + ( getPropertyName(PROP_FULL_NAME), uno::Any( true )); + + // Always set as FIXED b/c MS Word only updates these fields via user intervention (F9) + // AUTHOR of course never changes and USERNAME is easily mis-used as an original author field. + // Additionally, this was forced as fixed if any special case-formatting was provided. + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), + uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } +} + + void DomainMapper_Impl::handleDocProperty + (const FieldContextPtr& pContext, + OUString const& rFirstParam, + uno::Reference< uno::XInterface > & xFieldInterface) +{ + //some docproperties should be imported as document statistic fields, some as DocInfo fields + //others should be user fields + if (rFirstParam.isEmpty()) + return; + + constexpr sal_uInt8 SET_ARABIC = 0x01; + constexpr sal_uInt8 SET_DATE = 0x04; + struct DocPropertyMap + { + const char* pDocPropertyName; + const char* pServiceName; + sal_uInt8 nFlags; + }; + static const DocPropertyMap aDocProperties[] = + { + {"CreateTime", "DocInfo.CreateDateTime", SET_DATE}, + {"Characters", "CharacterCount", SET_ARABIC}, + {"Comments", "DocInfo.Description", 0}, + {"Keywords", "DocInfo.KeyWords", 0}, + {"LastPrinted", "DocInfo.PrintDateTime", 0}, + {"LastSavedBy", "DocInfo.ChangeAuthor", 0}, + {"LastSavedTime", "DocInfo.ChangeDateTime", SET_DATE}, + {"Paragraphs", "ParagraphCount", SET_ARABIC}, + {"RevisionNumber", "DocInfo.Revision", 0}, + {"Subject", "DocInfo.Subject", 0}, + {"Template", "TemplateName", 0}, + {"Title", "DocInfo.Title", 0}, + {"TotalEditingTime", "DocInfo.EditTime", 0}, + {"Words", "WordCount", SET_ARABIC} + + //other available DocProperties: + //Bytes, Category, CharactersWithSpaces, Company + //HyperlinkBase, + //Lines, Manager, NameofApplication, ODMADocId, Pages, + //Security, + }; + uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(m_xTextDocument, uno::UNO_QUERY); + uno::Reference<document::XDocumentProperties> xDocumentProperties = xDocumentPropertiesSupplier->getDocumentProperties(); + uno::Reference<beans::XPropertySet> xUserDefinedProps(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xUserDefinedProps->getPropertySetInfo(); + //search for a field mapping + OUString sFieldServiceName; + size_t nMap = 0; + if (!xPropertySetInfo->hasPropertyByName(rFirstParam)) + { + for( ; nMap < SAL_N_ELEMENTS(aDocProperties); ++nMap ) + { + if (rFirstParam.equalsAscii(aDocProperties[nMap].pDocPropertyName)) + { + sFieldServiceName = OUString::createFromAscii(aDocProperties[nMap].pServiceName); + break; + } + } + } + else + pContext->CacheVariableValue(xUserDefinedProps->getPropertyValue(rFirstParam)); + + OUString sServiceName("com.sun.star.text.TextField."); + bool bIsCustomField = false; + if(sFieldServiceName.isEmpty()) + { + //create a custom property field + sServiceName += "DocInfo.Custom"; + bIsCustomField = true; + } + else + { + sServiceName += sFieldServiceName; + } + if (m_xTextFactory.is()) + xFieldInterface = m_xTextFactory->createInstance(sServiceName); + uno::Reference<beans::XPropertySet> xFieldProperties( xFieldInterface, uno::UNO_QUERY_THROW); + if( bIsCustomField ) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NAME), uno::Any(rFirstParam)); + pContext->SetCustomField( xFieldProperties ); + } + else + { + if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC)) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( style::NumberingType::ARABIC )); + else if(0 != (aDocProperties[nMap].nFlags & SET_DATE)) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_DATE), + uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + } +} + +static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator, + const uno::Sequence< beans::PropertyValues >& aLevel ) +{ + //create a copy of the level and add two new entries - hyperlink start and end + bool bChapterNoSeparator = !sChapterNoSeparator.isEmpty(); + sal_Int32 nAdd = (bHyperlinks && bChapterNoSeparator) ? 4 : 2; + uno::Sequence< beans::PropertyValues > aNewLevel( aLevel.getLength() + nAdd); + beans::PropertyValues* pNewLevel = aNewLevel.getArray(); + if( bHyperlinks ) + { + beans::PropertyValues aHyperlink{ comphelper::makePropertyValue( + getPropertyName( PROP_TOKEN_TYPE ), getPropertyName( PROP_TOKEN_HYPERLINK_START )) }; + pNewLevel[0] = aHyperlink; + aHyperlink = { comphelper::makePropertyValue( + getPropertyName(PROP_TOKEN_TYPE), getPropertyName( PROP_TOKEN_HYPERLINK_END )) }; + pNewLevel[aNewLevel.getLength() -1] = aHyperlink; + } + if( bChapterNoSeparator ) + { + beans::PropertyValues aChapterNo{ + comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ), + getPropertyName( PROP_TOKEN_CHAPTER_INFO )), + comphelper::makePropertyValue(getPropertyName( PROP_CHAPTER_FORMAT ), + //todo: is ChapterFormat::Number correct? + sal_Int16(text::ChapterFormat::NUMBER)) + }; + pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 4 : 2) ] = aChapterNo; + + beans::PropertyValues aChapterSeparator{ + comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ), + getPropertyName( PROP_TOKEN_TEXT )), + comphelper::makePropertyValue(getPropertyName( PROP_TEXT ), sChapterNoSeparator) + }; + pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 3 : 1)] = aChapterSeparator; + } + //copy the 'old' entries except the last (page no) + std::copy(aLevel.begin(), std::prev(aLevel.end()), std::next(pNewLevel)); + //copy page no entry (last or last but one depending on bHyperlinks + sal_Int32 nPageNo = aNewLevel.getLength() - (bHyperlinks ? 2 : 3); + pNewLevel[nPageNo] = aLevel[aLevel.getLength() - 1]; + + return aNewLevel; +} + +/// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame +OUString DomainMapper_Impl::extractTocTitle() +{ + if (!m_xSdtEntryStart.is()) + return OUString(); + + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(!xTextAppend.is()) + return OUString(); + + // try-catch was added in the same way as inside appendTextSectionAfter() + try + { + uno::Reference<text::XParagraphCursor> xCursor(xTextAppend->createTextCursorByRange(m_xSdtEntryStart), uno::UNO_QUERY_THROW); + if (!xCursor.is()) + return OUString(); + + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + if (m_aTextAppendStack.top().xInsertPosition.is()) + xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true ); + else + xCursor->gotoEnd( true ); + + // the paragraph after this new section might have been already inserted + OUString sResult = xCursor->getString(); + if (sResult.endsWith(SAL_NEWLINE_STRING)) + sResult = sResult.copy(0, sResult.getLength() - SAL_N_ELEMENTS(SAL_NEWLINE_STRING) + 1); + + return sResult; + } + catch(const uno::Exception&) + { + } + + return OUString(); +} + +css::uno::Reference<css::beans::XPropertySet> +DomainMapper_Impl::StartIndexSectionChecked(const OUString& sServiceName) +{ + if (m_bParaChanged) + { + finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets m_bParaChanged + PopProperties(CONTEXT_PARAGRAPH); + PushProperties(CONTEXT_PARAGRAPH); + SetIsFirstRun(true); + // The first paragraph of the index that is continuation of just finished one needs to be + // removed when finished (unless more content will arrive, which will set m_bParaChanged) + m_bRemoveThisParagraph = true; + } + const auto& xTextAppend = GetTopTextAppend(); + const auto xTextRange = xTextAppend->getEnd(); + const auto xRet = createSectionForRange(xTextRange, xTextRange, sServiceName, false); + if (!m_aTextAppendStack.top().xInsertPosition) + { + try + { + m_bStartedTOC = true; + uno::Reference<text::XTextCursor> xTOCTextCursor + = xTextRange->getText()->createTextCursor(); + assert(xTOCTextCursor.is()); + xTOCTextCursor->gotoEnd(false); + m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor)); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", + "DomainMapper_Impl::StartIndexSectionChecked:"); + } + } + return xRet; +} + +void DomainMapper_Impl::handleToc + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + OUString sValue; + if (IsInHeaderFooter()) + m_bStartTOCHeaderFooter = true; + bool bTableOfFigures = false; + bool bHyperlinks = false; + bool bFromOutline = false; + bool bFromEntries = false; + bool bHideTabLeaderPageNumbers = false ; + bool bIsTabEntry = false ; + bool bNewLine = false ; + bool bParagraphOutlineLevel = false; + + sal_Int16 nMaxLevel = 10; + OUString sTemplate; + OUString sChapterNoSeparator; + OUString sFigureSequence; + uno::Reference< beans::XPropertySet > xTOC; + OUString aBookmarkName; + +// \a Builds a table of figures but does not include the captions's label and number + if( lcl_FindInCommand( pContext->GetCommand(), 'a', sValue )) + { //make it a table of figures + bTableOfFigures = true; + } +// \b Uses a bookmark to specify area of document from which to build table of contents + if( lcl_FindInCommand( pContext->GetCommand(), 'b', sValue )) + { + aBookmarkName = sValue.trim().replaceAll("\"",""); + } + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) +// \c Builds a table of figures of the given label + { + //todo: sValue contains the label's name + bTableOfFigures = true; + sFigureSequence = sValue.trim(); + sFigureSequence = sFigureSequence.replaceAll("\"", "").replaceAll("'",""); + } +// \d Defines the separator between sequence and page numbers + if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue )) + { + //todo: insert the chapter number into each level and insert the separator additionally + sChapterNoSeparator = sValue; + } +// \f Builds a table of contents using TC entries instead of outline levels + if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue )) + { + //todo: sValue can contain a TOC entry identifier - use unclear + bFromEntries = true; + } +// \h Hyperlinks the entries and page numbers within the table of contents + if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue )) + { + //todo: make all entries to hyperlinks + bHyperlinks = true; + } +// \l Defines the TC entries field level used to build a table of contents +// if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue )) +// { + //todo: entries can only be included completely +// } +// \n Builds a table of contents or a range of entries, such as 1-9 in a table of contents without page numbers +// if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) +// { + //todo: what does the description mean? +// } +// \o Builds a table of contents by using outline levels instead of TC entries + if( lcl_FindInCommand( pContext->GetCommand(), 'o', sValue )) + { + bFromOutline = true; + if (sValue.isEmpty()) + nMaxLevel = WW_OUTLINE_MAX; + else + { + sal_Int32 nIndex = 0; + o3tl::getToken(sValue, 0, '-', nIndex ); + nMaxLevel = static_cast<sal_Int16>(nIndex != -1 ? o3tl::toInt32(sValue.subView(nIndex)) : 0); + } + } +// \p Defines the separator between the table entry and its page number +// \s Builds a table of contents by using a sequence type +// \t Builds a table of contents by using style names other than the standard outline styles + if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue )) + { + OUString sToken = sValue.getToken(1, '"'); + sTemplate = sToken.isEmpty() ? sValue : sToken; + } +// \u Builds a table of contents by using the applied paragraph outline level + if( lcl_FindInCommand( pContext->GetCommand(), 'u', sValue )) + { + bFromOutline = true; + bParagraphOutlineLevel = true; + //todo: what doesn 'the applied paragraph outline level' refer to? + } +// \w Preserve tab characters within table entries + if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue )) + { + bIsTabEntry = true ; + } +// \x Preserve newline characters within table entries + if( lcl_FindInCommand( pContext->GetCommand(), 'x', sValue )) + { + bNewLine = true ; + } +// \z Hides page numbers within the table of contents when shown in Web Layout View + if( lcl_FindInCommand( pContext->GetCommand(), 'z', sValue )) + { + bHideTabLeaderPageNumbers = true ; + } + + //if there's no option then it should be created from outline + if( !bFromOutline && !bFromEntries && sTemplate.isEmpty() ) + bFromOutline = true; + + const OUString aTocTitle = extractTocTitle(); + + if (m_xTextFactory.is() && ! m_aTextAppendStack.empty()) + { + const auto& xTextAppend = GetTopTextAppend(); + if (aTocTitle.isEmpty() || bTableOfFigures) + { + // reset marker of the TOC title + m_xSdtEntryStart.clear(); + + // Create section before setting m_bStartTOC: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + xTOC = StartIndexSectionChecked(bTableOfFigures ? "com.sun.star.text.IllustrationsIndex" + : sTOCServiceName); + + const auto xTextCursor = xTextAppend->getText()->createTextCursor(); + if (xTextCursor) + xTextCursor->gotoEnd(false); + xTOCMarkerCursor = xTextCursor; + } + else + { + // create TOC section + css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd(); + xTOC = createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false); + + // init [xTOCMarkerCursor] + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + xTOCMarkerCursor = xCrsr; + + // create header of the TOC with the TOC title inside + createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, "com.sun.star.text.IndexHeaderSection", true); + } + } + + m_bStartTOC = true; + + if (xTOC.is()) + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(aTocTitle)); + + if (!aBookmarkName.isEmpty()) + xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::Any(aBookmarkName)); + if( !bTableOfFigures && xTOC.is() ) + { + xTOC->setPropertyValue( getPropertyName( PROP_LEVEL ), uno::Any( nMaxLevel ) ); + xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_OUTLINE ), uno::Any( bFromOutline )); + xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_MARKS ), uno::Any( bFromEntries )); + xTOC->setPropertyValue( getPropertyName( PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS ), uno::Any( bHideTabLeaderPageNumbers )); + xTOC->setPropertyValue( getPropertyName( PROP_TAB_IN_TOC ), uno::Any( bIsTabEntry )); + xTOC->setPropertyValue( getPropertyName( PROP_TOC_NEW_LINE ), uno::Any( bNewLine )); + xTOC->setPropertyValue( getPropertyName( PROP_TOC_PARAGRAPH_OUTLINE_LEVEL ), uno::Any( bParagraphOutlineLevel )); + if( !sTemplate.isEmpty() ) + { + //the string contains comma separated the names and related levels + //like: "Heading 1,1,Heading 2,2" + TOCStyleMap aMap; + sal_Int32 nLevel; + sal_Int32 nPosition = 0; + while( nPosition >= 0) + { + OUString sStyleName = sTemplate.getToken( 0, ',', nPosition ); + //empty tokens should be skipped + while( sStyleName.isEmpty() && nPosition > 0 ) + sStyleName = sTemplate.getToken( 0, ',', nPosition ); + nLevel = o3tl::toInt32(o3tl::getToken(sTemplate, 0, ',', nPosition )); + if( !nLevel ) + nLevel = 1; + if( !sStyleName.isEmpty() ) + aMap.emplace(nLevel, sStyleName); + } + uno::Reference< container::XIndexReplace> xParaStyles; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_PARAGRAPH_STYLES)) >>= xParaStyles; + for( nLevel = 1; nLevel < 10; ++nLevel) + { + sal_Int32 nLevelCount = aMap.count( nLevel ); + if( nLevelCount ) + { + TOCStyleMap::iterator aTOCStyleIter = aMap.find( nLevel ); + + uno::Sequence< OUString> aStyles( nLevelCount ); + for ( auto& rStyle : asNonConstRange(aStyles) ) + { + rStyle = (aTOCStyleIter++)->second; + } + xParaStyles->replaceByIndex(nLevel - 1, uno::Any(aStyles)); + } + } + xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true )); + + } + if(bHyperlinks || !sChapterNoSeparator.isEmpty()) + { + uno::Reference< container::XIndexReplace> xLevelFormats; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats; + sal_Int32 nLevelCount = xLevelFormats->getCount(); + //start with level 1, 0 is the header level + for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel) + { + uno::Sequence< beans::PropertyValues > aLevel; + xLevelFormats->getByIndex( nLevel ) >>= aLevel; + + uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks( + bHyperlinks, sChapterNoSeparator, + aLevel ); + xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) ); + } + } + } + else if (bTableOfFigures && xTOC.is()) + { + if (!sFigureSequence.isEmpty()) + xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY), + uno::Any(sFigureSequence)); + + if ( bHyperlinks ) + { + uno::Reference< container::XIndexReplace> xLevelFormats; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats; + uno::Sequence< beans::PropertyValues > aLevel; + xLevelFormats->getByIndex( 1 ) >>= aLevel; + + uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks( + bHyperlinks, sChapterNoSeparator, + aLevel ); + xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) ); + } + } + pContext->SetTOC( xTOC ); + m_bParaHadField = false; +} + +uno::Reference<beans::XPropertySet> DomainMapper_Impl::createSectionForRange( + uno::Reference< css::text::XTextRange > xStart, + uno::Reference< css::text::XTextRange > xEnd, + const OUString & sObjectType, + bool stepLeft) +{ + if (!xStart.is()) + return uno::Reference<beans::XPropertySet>(); + if (!xEnd.is()) + return uno::Reference<beans::XPropertySet>(); + + uno::Reference< beans::XPropertySet > xRet; + if (m_aTextAppendStack.empty()) + return xRet; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XParagraphCursor > xCursor( + xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY_THROW); + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + xCursor->gotoRange( xEnd, true ); + //the paragraph after this new section is already inserted + if (stepLeft) + xCursor->goLeft(1, true); + uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance(sObjectType), uno::UNO_QUERY_THROW ); + xSection->attach( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW) ); + xRet.set(xSection, uno::UNO_QUERY ); + } + catch(const uno::Exception&) + { + } + } + + return xRet; +} + +void DomainMapper_Impl::handleBibliography + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + if (m_aTextAppendStack.empty()) + { + // tdf#130214: a workaround to avoid crash on import errors + SAL_WARN("writerfilter.dmapper", "no text append stack"); + return; + } + // Create section before setting m_bStartTOC and m_bStartBibliography: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + const auto xTOC = StartIndexSectionChecked(sTOCServiceName); + m_bStartTOC = true; + m_bStartBibliography = true; + + if (xTOC.is()) + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString())); + + pContext->SetTOC( xTOC ); + m_bParaHadField = false; + + uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); + appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); +} + +void DomainMapper_Impl::handleIndex + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + // only UserIndex can handle user index defined by \f + // e.g. INDEX \f "user-index-id" + OUString sUserIndex; + if ( lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) ) + sUserIndex = lcl_trim(sUserIndex); + + // Create section before setting m_bStartTOC and m_bStartIndex: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + const auto xTOC = StartIndexSectionChecked( sUserIndex.isEmpty() + ? sTOCServiceName + : "com.sun.star.text.UserIndex"); + + m_bStartTOC = true; + m_bStartIndex = true; + OUString sValue; + if (xTOC.is()) + { + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString())); + + if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + xTOC->setPropertyValue("IsCommaSeparated", uno::Any(true)); + } + if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue )) + { + xTOC->setPropertyValue("UseAlphabeticalSeparators", uno::Any(true)); + } + if( !sUserIndex.isEmpty() ) + { + xTOC->setPropertyValue("UserIndexName", uno::Any(sUserIndex)); + } + } + pContext->SetTOC( xTOC ); + m_bParaHadField = false; + + uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); + appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); + + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) + { + sValue = sValue.replaceAll("\"", ""); + uno::Reference<text::XTextColumns> xTextColumns; + xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns; + if (xTextColumns.is()) + { + xTextColumns->setColumnCount( sValue.toInt32() ); + xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xTextColumns ) ); + } + } +} + +static auto InsertFieldmark(std::stack<TextAppendContext> & rTextAppendStack, + uno::Reference<text::XFormField> const& xFormField, + uno::Reference<text::XTextRange> const& xStartRange, + std::optional<FieldId> const oFieldId) -> void +{ + uno::Reference<text::XTextContent> const xTextContent(xFormField, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextAppend> const& xTextAppend(rTextAppendStack.top().xTextAppend); + uno::Reference<text::XTextCursor> const xCursor = + xTextAppend->createTextCursorByRange(xStartRange); + if (rTextAppendStack.top().xInsertPosition.is()) + { + uno::Reference<text::XTextRangeCompare> const xCompare( + rTextAppendStack.top().xTextAppend, + uno::UNO_QUERY); + if (xCompare->compareRegionStarts(xStartRange, rTextAppendStack.top().xInsertPosition) < 0) + { + SAL_WARN("writerfilter.dmapper", "invalid field mark positions"); + assert(false); + } + xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, true); + } + else + { + xCursor->gotoEnd(true); + } + xTextAppend->insertTextContent(xCursor, xTextContent, true); + if (oFieldId + && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN)) + { + return; // only a single CH_TXT_ATR_FORMELEMENT! + } + // problem: the fieldmark must be inserted in CloseFieldCommand(), because + // attach() takes 2 positions, not 3! + // FAIL: AppendTextNode() ignores the content index! + // plan B: insert a spurious paragraph break now and join + // it in PopFieldContext()! + xCursor->gotoRange(xTextContent->getAnchor()->getEnd(), false); + xCursor->goLeft(1, false); // skip CH_TXT_ATR_FIELDEND + xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::PARAGRAPH_BREAK, false); + xCursor->goLeft(1, false); // back to previous paragraph + rTextAppendStack.push(TextAppendContext(xTextAppend, xCursor)); +} + +static auto PopFieldmark(std::stack<TextAppendContext> & rTextAppendStack, + uno::Reference<text::XTextCursor> const& xCursor, + std::optional<FieldId> const oFieldId) -> void +{ + if (oFieldId + && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN)) + { + return; // only a single CH_TXT_ATR_FORMELEMENT! + } + xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, false); + xCursor->goRight(1, true); + xCursor->setString(OUString()); // undo SplitNode from CloseFieldCommand() + // note: paragraph properties will be overwritten + // by finishParagraph() anyway so ignore here + rTextAppendStack.pop(); +} + +void DomainMapper_Impl::CloseFieldCommand() +{ + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("closeFieldCommand"); +#endif + + FieldContextPtr pContext; + if(!m_aFieldStack.empty()) + pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( !pContext ) + return; + + m_bSetUserFieldContent = false; + m_bSetCitation = false; + m_bSetDateValue = false; + const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion(); + + try + { + uno::Reference< uno::XInterface > xFieldInterface; + + const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) }; + (void)vSwitches; + OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front()); + + // apply font size to the form control + if (!m_aTextAppendStack.empty() && m_pLastCharacterContext && ( m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT) || m_pLastCharacterContext->isSet(PROP_CHAR_FONT_NAME ))) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY ); + if (m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT)) + { + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT)->second); + if (m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT_COMPLEX)) + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT_COMPLEX), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT_COMPLEX)->second); + } + if (m_pLastCharacterContext->isSet(PROP_CHAR_FONT_NAME)) + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), m_pLastCharacterContext->getProperty(PROP_CHAR_FONT_NAME)->second); + } + } + } + + FieldConversionMap_t::const_iterator const aIt = aFieldConversionMap.find(sType); + if (aIt != aFieldConversionMap.end() + && (!m_bForceGenericFields + // these need to convert ffData to properties... + || (aIt->second.eFieldId == FIELD_FORMCHECKBOX) + || (aIt->second.eFieldId == FIELD_FORMDROPDOWN) + || (aIt->second.eFieldId == FIELD_FORMTEXT))) + { + pContext->SetFieldId(aIt->second.eFieldId); + bool bCreateEnhancedField = false; + uno::Reference< beans::XPropertySet > xFieldProperties; + bool bCreateField = true; + switch (aIt->second.eFieldId) + { + case FIELD_HYPERLINK: + case FIELD_DOCPROPERTY: + case FIELD_TOC: + case FIELD_INDEX: + case FIELD_XE: + case FIELD_BIBLIOGRAPHY: + case FIELD_CITATION: + case FIELD_TC: + case FIELD_EQ: + case FIELD_INCLUDEPICTURE: + case FIELD_SYMBOL: + case FIELD_GOTOBUTTON: + bCreateField = false; + break; + case FIELD_FORMCHECKBOX : + case FIELD_FORMTEXT : + case FIELD_FORMDROPDOWN : + { + // If we use 'enhanced' fields then FIELD_FORMCHECKBOX, + // FIELD_FORMTEXT & FIELD_FORMDROPDOWN are treated specially + if ( m_bUsingEnhancedFields ) + { + bCreateField = false; + bCreateEnhancedField = true; + } + // for non enhanced fields checkboxes are displayed + // as an awt control not a field + else if ( aIt->second.eFieldId == FIELD_FORMCHECKBOX ) + bCreateField = false; + break; + } + default: + { + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back())) + { + // Parent field can't host this child field: don't create a child field + // in this case. + bCreateField = false; + } + } + break; + } + } + if (m_bStartTOC && (aIt->second.eFieldId == FIELD_PAGEREF) ) + { + bCreateField = false; + } + + if( bCreateField || bCreateEnhancedField ) + { + //add the service prefix + OUString sServiceName("com.sun.star.text."); + if ( bCreateEnhancedField ) + { + const FieldConversionMap_t& aEnhancedFieldConversionMap = lcl_GetEnhancedFieldConversion(); + FieldConversionMap_t::const_iterator aEnhancedIt = + aEnhancedFieldConversionMap.find(sType); + if ( aEnhancedIt != aEnhancedFieldConversionMap.end()) + sServiceName += OUString::createFromAscii(aEnhancedIt->second.cFieldServiceName ); + } + else + { + sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName ); + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("fieldService"); + TagLogger::getInstance().chars(sServiceName); + TagLogger::getInstance().endElement(); +#endif + + if (m_xTextFactory.is()) + { + xFieldInterface = m_xTextFactory->createInstance(sServiceName); + xFieldProperties.set( xFieldInterface, uno::UNO_QUERY_THROW); + } + } + switch( aIt->second.eFieldId ) + { + case FIELD_ADDRESSBLOCK: break; + case FIELD_ADVANCE : break; + case FIELD_ASK : + handleFieldAsk(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_AUTONUM : + case FIELD_AUTONUMLGL : + case FIELD_AUTONUMOUT : + handleAutoNum(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_AUTHOR : + case FIELD_USERNAME : + case FIELD_USERINITIALS : + handleAuthor(sFirstParam, + xFieldProperties, + aIt->second.eFieldId); + break; + case FIELD_DATE: + if (xFieldProperties.is()) + { + // Get field fixed property from the context handler + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( true )); + m_bSetDateValue = true; + } + else + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( false )); + + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_DATE), + uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_COMMENTS : + { + // OUString sParam = lcl_ExtractParameter(pContext->GetCommand(), sizeof(" COMMENTS") ); + // A parameter with COMMENTS shouldn't set fixed + // ( or at least the binary filter doesn't ) + // If we set fixed then we won't export a field cmd. + // Additionally the para in COMMENTS is more like an + // instruction to set the document property comments + // with the param ( e.g. each COMMENT with a param will + // overwrite the Comments document property + // #TODO implement the above too + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( false )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + break; + case FIELD_CREATEDATE : + case FIELD_PRINTDATE: + case FIELD_SAVEDATE: + { + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), uno::Any( true )); + } + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_DATE ), uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_DOCPROPERTY : + handleDocProperty(pContext, sFirstParam, + xFieldInterface); + break; + case FIELD_DOCVARIABLE : + { + if (bCreateField) + { + //create a user field and type + uno::Reference<beans::XPropertySet> xMaster = FindOrCreateFieldMaster( + "com.sun.star.text.FieldMaster.User", sFirstParam); + uno::Reference<text::XDependentTextField> xDependentField( + xFieldInterface, uno::UNO_QUERY_THROW); + xDependentField->attachTextFieldMaster(xMaster); + m_bSetUserFieldContent = true; + } + } + break; + case FIELD_EDITTIME : + //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + break; + case FIELD_EQ: + { + OUString aCommand = pContext->GetCommand().trim(); + + msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand)); + if (!aResult.sType.isEmpty() && m_xTextFactory.is()) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField." + aResult.sType); + xFieldProperties = + uno::Reference< beans::XPropertySet >( xFieldInterface, + uno::UNO_QUERY_THROW); + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(aResult.sResult)); + } + else + { + //merge Read_SubF_Ruby into filter/.../util.cxx and reuse that ? + sal_Int32 nSpaceIndex = aCommand.indexOf(' '); + if(nSpaceIndex > 0) + aCommand = o3tl::trim(aCommand.subView(nSpaceIndex)); + if (aCommand.startsWith("\\s")) + { + aCommand = aCommand.copy(2); + if (aCommand.startsWith("\\do")) + { + aCommand = aCommand.copy(3); + sal_Int32 nStartIndex = aCommand.indexOf('('); + sal_Int32 nEndIndex = aCommand.indexOf(')'); + if (nStartIndex > 0 && nEndIndex > 0) + { + // nDown is the requested "lower by" value in points. + sal_Int32 nDown = o3tl::toInt32(aCommand.subView(0, nStartIndex)); + OUString aContent = aCommand.copy(nStartIndex + 1, nEndIndex - nStartIndex - 1); + PropertyMapPtr pCharContext = GetTopContext(); + // dHeight is the font size of the current style. + double dHeight = 0; + if ((GetPropertyFromParaStyleSheet(PROP_CHAR_HEIGHT) >>= dHeight) && dHeight != 0) + // Character escapement should be given in negative percents for subscripts. + pCharContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( sal_Int16(- 100 * nDown / dHeight) ) ); + appendTextPortion(aContent, pCharContext); + } + } + } + else if (aCommand.startsWith("\\* jc")) + { + handleRubyEQField(pContext); + } + } + } + break; + case FIELD_FILLIN : + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), uno::Any( pContext->GetCommand().getToken(1, '\"'))); + break; + case FIELD_FILENAME: + { + sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p"); + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_FILE_FORMAT), + uno::Any( nNumberingTypeIndex > 0 ? text::FilenameDisplayFormat::FULL : text::FilenameDisplayFormat::NAME_AND_EXT )); + } + break; + case FIELD_FILESIZE : break; + case FIELD_FORMULA : + if (bCreateField) + { + handleFieldFormula(pContext, xFieldProperties); + } + break; + case FIELD_FORMCHECKBOX : + case FIELD_FORMDROPDOWN : + case FIELD_FORMTEXT : + { + if (bCreateEnhancedField) + { + FFDataHandler::Pointer_t + pFFDataHandler(pContext->getFFDataHandler()); + FormControlHelper::Pointer_t + pFormControlHelper(new FormControlHelper + (m_bUsingEnhancedFields ? aIt->second.eFieldId : FIELD_FORMCHECKBOX, + + m_xTextDocument, pFFDataHandler)); + pContext->setFormControlHelper(pFormControlHelper); + uno::Reference< text::XFormField > xFormField( xFieldInterface, uno::UNO_QUERY ); + uno::Reference< container::XNamed > xNamed( xFormField, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() ) + xNamed->setName( pFFDataHandler->getName() ); + pContext->SetFormField( xFormField ); + } + InsertFieldmark(m_aTextAppendStack, + xFormField, pContext->GetStartRange(), + pContext->GetFieldId()); + } + else + { + if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN ) + lcl_handleDropdownField( xFieldProperties, pContext->getFFDataHandler() ); + else + lcl_handleTextField( xFieldProperties, pContext->getFFDataHandler() ); + } + } + break; + case FIELD_GOTOBUTTON : break; + case FIELD_HYPERLINK: + { + ::std::vector<OUString> aParts = pContext->GetCommandParts(); + + // Syntax is either: + // HYPERLINK "" \l "link" + // or + // HYPERLINK \l "link" + // Make sure "HYPERLINK" doesn't end up as part of link in the second case. + if (!aParts.empty() && aParts[0] == "HYPERLINK") + aParts.erase(aParts.begin()); + + ::std::vector<OUString>::const_iterator aItEnd = aParts.end(); + ::std::vector<OUString>::const_iterator aPartIt = aParts.begin(); + + OUString sURL; + OUString sTarget; + + while (aPartIt != aItEnd) + { + if ( *aPartIt == "\\l" ) + { + ++aPartIt; + + if (aPartIt == aItEnd) + break; + + sURL += "#" + *aPartIt; + } + else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h") + { + } + else if ( *aPartIt == "\\o" || *aPartIt == "\\t" ) + { + ++aPartIt; + + if (aPartIt == aItEnd) + break; + + sTarget = *aPartIt; + } + else + { + sURL = *aPartIt; + } + + ++aPartIt; + } + + if (!sURL.isEmpty()) + { + if (sURL.startsWith("file:///")) + { + // file:///absolute\\path\\to\\file => invalid file URI (Writer cannot open) + // convert all double backslashes to slashes: + sURL = sURL.replaceAll("\\\\", "/"); + + // file:///absolute\path\to\file => invalid file URI (Writer cannot open) + // convert all backslashes to slashes: + sURL = sURL.replace('\\', '/'); + } + // Try to make absolute any relative URLs, except + // for relative same-document URLs that only contain + // a fragment part: + else if (!sURL.startsWith("#")) { + try { + sURL = rtl::Uri::convertRelToAbs( + m_aBaseUrl, sURL); + } catch (rtl::MalformedUriException & e) { + SAL_WARN( + "writerfilter.dmapper", + "MalformedUriException " + << e.getMessage()); + } + } + pContext->SetHyperlinkURL(sURL); + } + + if (!sTarget.isEmpty()) + pContext->SetHyperlinkTarget(sTarget); + } + break; + case FIELD_IF : break; + case FIELD_INFO : break; + case FIELD_INCLUDEPICTURE: break; + case FIELD_KEYWORDS : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_LASTSAVEDBY : + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), uno::Any(true)); + break; + case FIELD_MACROBUTTON: + { + if (xFieldProperties.is()) + { + sal_Int32 nIndex = sizeof(" MACROBUTTON "); + OUString sCommand = pContext->GetCommand(); + + //extract macro name + if (sCommand.getLength() >= nIndex) + { + OUString sMacro = sCommand.getToken(0, ' ', nIndex); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_MACRO_NAME), uno::Any( sMacro )); + } + + //extract quick help text + if (sCommand.getLength() > nIndex + 1) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), + uno::Any( sCommand.copy( nIndex ))); + } + } + } + break; + case FIELD_MERGEFIELD : + { + //todo: create a database field and fieldmaster pointing to a column, only + //create a user field and type + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam); + +// xFieldProperties->setPropertyValue( +// "FieldCode", +// uno::makeAny( pContext->GetCommand().copy( nIndex + 1 ))); + uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + } + break; + case FIELD_MERGEREC : break; + case FIELD_MERGESEQ : break; + case FIELD_NEXT : break; + case FIELD_NEXTIF : break; + case FIELD_PAGE : + if (xFieldProperties.is()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_SUB_TYPE), + uno::Any( text::PageNumberType_CURRENT )); + } + + break; + case FIELD_PAGEREF: + case FIELD_REF: + if (xFieldProperties.is() && !m_bStartTOC) + { + bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF; + + // Do we need a GetReference (default) or a GetExpression field? + uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters(); + + if (!xFieldMasterAccess->hasByName( + "com.sun.star.text.FieldMaster.SetExpression." + + sFirstParam)) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_REFERENCE_FIELD_SOURCE), + uno::Any( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) ); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_SOURCE_NAME), + uno::Any(sFirstParam) ); + sal_Int16 nFieldPart = (bPageRef ? text::ReferenceFieldPart::PAGE : text::ReferenceFieldPart::TEXT); + OUString sValue; + if( lcl_FindInCommand( pContext->GetCommand(), 'p', sValue )) + { + //above-below + nFieldPart = text::ReferenceFieldPart::UP_DOWN; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + //number + nFieldPart = text::ReferenceFieldPart::NUMBER; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) + { + //number-no-context + nFieldPart = text::ReferenceFieldPart::NUMBER_NO_CONTEXT; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue )) + { + //number-full-context + nFieldPart = text::ReferenceFieldPart::NUMBER_FULL_CONTEXT; + } + xFieldProperties->setPropertyValue( + getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::Any( nFieldPart )); + } + else if( m_xTextFactory.is() ) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField.GetExpression"); + xFieldProperties.set(xFieldInterface, uno::UNO_QUERY); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any(sFirstParam)); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + } + } + break; + case FIELD_REVNUM : break; + case FIELD_SECTION : break; + case FIELD_SECTIONPAGES : break; + case FIELD_SEQ : + { + // command looks like: " SEQ Table \* ARABIC " + OUString sCmd(pContext->GetCommand()); + // find the sequence name, e.g. "SEQ" + std::u16string_view sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\'); + sSeqName = o3tl::trim(sSeqName); + + // create a sequence field master using the sequence name + uno::Reference< beans::XPropertySet > xMaster = FindOrCreateFieldMaster( + "com.sun.star.text.FieldMaster.SetExpression", + OUString(sSeqName)); + + xMaster->setPropertyValue( + getPropertyName(PROP_SUB_TYPE), + uno::Any(text::SetVariableType::SEQUENCE)); + + // apply the numbering type + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + OUString sFormula = OUString::Concat(sSeqName) + "+1"; + OUString sValue; + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) + { + sFormula = sSeqName; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + sFormula = sValue; + } + // TODO \s isn't handled, but the spec isn't easy to understand without + // an example for this one. + xFieldProperties->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any(sFormula)); + + // Take care of the numeric formatting definition, default is Arabic + sal_Int16 nNumberingType = lcl_ParseNumberingType(pContext->GetCommand()); + if (nNumberingType == style::NumberingType::PAGE_DESCRIPTOR) + nNumberingType = style::NumberingType::ARABIC; + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any(nNumberingType)); + } + break; + case FIELD_SET : + handleFieldSet(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_SKIPIF : break; + case FIELD_STYLEREF : break; + case FIELD_SUBJECT : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_SYMBOL: + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ? o3tl::toUInt32(sFirstParam.subView(2),16) : sFirstParam.toUInt32() ) ); + OUString sFont; + bool bHasFont = lcl_FindInCommand( pContext->GetCommand(), 'f', sFont); + if ( bHasFont ) + { + sFont = sFont.trim(); + if (sFont.startsWith("\"")) + sFont = sFont.copy(1); + if (sFont.endsWith("\"")) + sFont = sFont.copy(0,sFont.getLength()-1); + } + + + + if (xTextAppend.is()) + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + xText->insertString(xCrsr, sSymbol, true); + uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::Any(awt::CharSet::SYMBOL)); + if(bHasFont) + { + uno::Any aVal( sFont ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal); + + } + } + } + } + break; + case FIELD_TEMPLATE: break; + case FIELD_TIME : + { + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( true )); + m_bSetDateValue = true; + } + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_TITLE : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_USERADDRESS : //todo: user address collects street, city ... + break; + case FIELD_INDEX: + handleIndex(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_BIBLIOGRAPHY: + handleBibliography(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_TOC: + handleToc(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_XE: + { + if( !m_xTextFactory.is() ) + break; + + // only UserIndexMark can handle user index types defined by \f + // e.g. XE "text" \f "user-index-id" + OUString sUserIndex; + OUString sFieldServiceName = + lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) + ? "com.sun.star.text.UserIndexMark" + : OUString::createFromAscii(aIt->second.cFieldServiceName); + uno::Reference< beans::XPropertySet > xTC( + m_xTextFactory->createInstance(sFieldServiceName), + uno::UNO_QUERY_THROW); + + if (!sFirstParam.isEmpty()) + { + xTC->setPropertyValue(sUserIndex.isEmpty() + ? OUString("PrimaryKey") + : OUString("AlternativeText"), + uno::Any(sFirstParam)); + } + + sUserIndex = lcl_trim(sUserIndex); + if (!sUserIndex.isEmpty()) + { + xTC->setPropertyValue("UserIndexName", + uno::Any(sUserIndex)); + } + uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY ); + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + xText->insertTextContent(uno::Reference< text::XTextRange >( xCrsr, uno::UNO_QUERY_THROW ), xToInsert, false); + } + } + } + break; + case FIELD_CITATION: + { + if( !m_xTextFactory.is() ) + break; + + xFieldInterface = m_xTextFactory->createInstance( + OUString::createFromAscii(aIt->second.cFieldServiceName)); + uno::Reference< beans::XPropertySet > xTC(xFieldInterface, + uno::UNO_QUERY_THROW); + OUString sCmd(pContext->GetCommand());//sCmd is the entire instrText including the index e.g. CITATION Kra06 \l 1033 + if( !sCmd.isEmpty()){ + uno::Sequence<beans::PropertyValue> aValues( comphelper::InitPropertySequence({ + { "Identifier", uno::Any(sCmd) } + })); + xTC->setPropertyValue("Fields", uno::Any(aValues)); + } + uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY ); + + uno::Sequence<beans::PropertyValue> aValues + = m_aFieldStack.back()->getProperties()->GetPropertyValues(); + appendTextContent(xToInsert, aValues); + m_bSetCitation = true; + } + break; + + case FIELD_TC : + { + if( !m_xTextFactory.is() ) + break; + + uno::Reference< beans::XPropertySet > xTC( + m_xTextFactory->createInstance( + OUString::createFromAscii(aIt->second.cFieldServiceName)), + uno::UNO_QUERY_THROW); + if (!sFirstParam.isEmpty()) + { + xTC->setPropertyValue(getPropertyName(PROP_ALTERNATIVE_TEXT), + uno::Any(sFirstParam)); + } + OUString sValue; + // \f TC entry in doc with multiple tables +// if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue )) +// { + // todo: unsupported +// } + if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue )) + // \l Outline Level + { + sal_Int32 nLevel = sValue.toInt32(); + if( !sValue.isEmpty() && nLevel >= 0 && nLevel <= 10 ) + xTC->setPropertyValue(getPropertyName(PROP_LEVEL), uno::Any( static_cast<sal_Int16>(nLevel) )); + } +// if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) +// \n Suppress page numbers +// { + //todo: unsupported feature +// } + pContext->SetTC( xTC ); + } + break; + case FIELD_NUMCHARS: + case FIELD_NUMWORDS: + case FIELD_NUMPAGES: + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + break; + } + + if (!bCreateEnhancedField) + { + pContext->SetTextField( uno::Reference<text::XTextField>(xFieldInterface, uno::UNO_QUERY) ); + } + } + else + { + /* Unsupported fields will be handled here for docx file. + * To handle unsupported fields used fieldmark API. + */ + OUString aCode( pContext->GetCommand().trim() ); + // Don't waste resources on wrapping shapes inside a fieldmark. + if (sType != "SHAPE" && m_xTextFactory.is() && !m_aTextAppendStack.empty()) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.Fieldmark"); + + uno::Reference<text::XFormField> const xFormField(xFieldInterface, uno::UNO_QUERY); + InsertFieldmark(m_aTextAppendStack, xFormField, pContext->GetStartRange(), + pContext->GetFieldId()); + xFormField->setFieldType(ODF_UNHANDLED); + ++m_nStartGenericField; + pContext->SetFormField( xFormField ); + uno::Reference<container::XNameContainer> const xNameCont(xFormField->getParameters()); + // note: setting the code to empty string is *required* in + // m_bForceGenericFields mode, or the export will write + // the ODF_UNHANDLED string! + assert(!m_bForceGenericFields || aCode.isEmpty()); + xNameCont->insertByName(ODF_CODE_PARAM, uno::Any(aCode)); + ww::eField const id(GetWW8FieldId(sType)); + if (id != ww::eNONE) + { // tdf#129247 tdf#134264 set WW8 id for WW8 export + xNameCont->insertByName(ODF_ID_PARAM, uno::Any(OUString::number(id))); + } + } + else + m_bParaHadField = false; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Exception in CloseFieldCommand()" ); + } + pContext->SetCommandCompleted(); +} +/*------------------------------------------------------------------------- +//the _current_ fields require a string type result while TOCs accept richt results + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsFieldResultAsString() +{ + bool bRet = false; + OSL_ENSURE( !m_aFieldStack.empty(), "field stack empty?"); + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + bRet = pContext->GetTextField().is() + || pContext->GetFieldId() == FIELD_FORMDROPDOWN + || pContext->GetFieldId() == FIELD_FILLIN; + } + + if (!bRet) + { + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back())) + { + // If nesting is not allowed, then the result can only be a string. + bRet = true; + } + } + } + return bRet; +} + +void DomainMapper_Impl::AppendFieldResult(std::u16string_view rString) +{ + assert(!m_aFieldStack.empty()); + FieldContextPtr pContext = m_aFieldStack.back(); + SAL_WARN_IF(!pContext, "writerfilter.dmapper", "no field context"); + if (!pContext) + return; + + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, pContext)) + { + if (pOuter->IsCommandCompleted()) + { + // Child can't host the field result, forward to parent's result. + pOuter->AppendResult(rString); + } + return; + } + } + + pContext->AppendResult(rString); +} + +// Calculates css::DateTime based on ddddd.sssss since 1899-12-30 +static util::DateTime lcl_dateTimeFromSerial(const double& dSerial) +{ + DateTime d(Date(30, 12, 1899)); + d.AddTime(dSerial); + return d.GetUNODateTime(); +} + +void DomainMapper_Impl::SetFieldResult(OUString const& rResult) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("setFieldResult"); + TagLogger::getInstance().chars(rResult); +#endif + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + + if (m_aFieldStack.size() > 1) + { + // This is a nested field. See if the parent supports nesting on the Writer side. + FieldContextPtr pParentContext = m_aFieldStack[m_aFieldStack.size() - 2]; + if (pParentContext) + { + std::vector<OUString> aParentParts = pParentContext->GetCommandParts(); + // Conditional text fields don't support nesting in Writer. + if (!aParentParts.empty() && aParentParts[0] == "IF") + { + return; + } + } + } + + if( !pContext ) + return; + + uno::Reference<text::XTextField> xTextField = pContext->GetTextField(); + try + { + OSL_ENSURE( xTextField.is() + //||m_xTOC.is() ||m_xTC.is() + //||m_sHyperlinkURL.getLength() + , "DomainMapper_Impl::SetFieldResult: field not created" ); + if(xTextField.is()) + { + try + { + if( m_bSetUserFieldContent ) + { + // user field content has to be set at the field master + uno::Reference< text::XDependentTextField > xDependentField( xTextField, uno::UNO_QUERY_THROW ); + xDependentField->getTextFieldMaster()->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any( rResult )); + } + else if ( m_bSetCitation ) + { + + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + // In case of SetExpression, the field result contains the content of the variable. + uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY); + + bool bIsSetbiblio = xServiceInfo->supportsService("com.sun.star.text.TextField.Bibliography"); + if( bIsSetbiblio ) + { + uno::Any aProperty = xFieldProperties->getPropertyValue("Fields"); + uno::Sequence<beans::PropertyValue> aValues ; + aProperty >>= aValues; + beans::PropertyValue propertyVal; + sal_Int32 nTitleFoundIndex = -1; + for (sal_Int32 i = 0; i < aValues.getLength(); ++i) + { + propertyVal = aValues[i]; + if (propertyVal.Name == "Title") + { + nTitleFoundIndex = i; + break; + } + } + if (nTitleFoundIndex != -1) + { + OUString titleStr; + uno::Any aValue(propertyVal.Value); + aValue >>= titleStr; + titleStr += rResult; + propertyVal.Value <<= titleStr; + aValues.getArray()[nTitleFoundIndex] = propertyVal; + } + else + { + aValues.realloc(aValues.getLength() + 1); + propertyVal.Name = "Title"; + propertyVal.Value <<= rResult; + aValues.getArray()[aValues.getLength() - 1] = propertyVal; + } + xFieldProperties->setPropertyValue("Fields", + uno::Any(aValues)); + } + } + else if ( m_bSetDateValue ) + { + uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW ); + + uno::Reference<util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW); + xFormatter->attachNumberFormatsSupplier( xNumberSupplier ); + sal_Int32 nKey = 0; + + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + + xFieldProperties->getPropertyValue( "NumberFormat" ) >>= nKey; + xFieldProperties->setPropertyValue( + "DateTimeValue", + uno::Any( lcl_dateTimeFromSerial( xFormatter->convertStringToNumber( nKey, rResult ) ) ) ); + } + else + { + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + // In case of SetExpression, and Input fields the field result contains the content of the variable. + uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY); + // there are fields with a content property, which aren't working correctly with + // a generalized try catch of the content, property, so just restrict content + // handling to these explicit services. + const bool bHasContent = xServiceInfo->supportsService("com.sun.star.text.TextField.SetExpression") || + xServiceInfo->supportsService("com.sun.star.text.TextField.Input"); + // If we already have content set, then use the current presentation + OUString sValue; + if (bHasContent) + { + // this will throw for field types without Content + uno::Any aValue(xFieldProperties->getPropertyValue( + getPropertyName(PROP_CONTENT))); + aValue >>= sValue; + } + xFieldProperties->setPropertyValue( + getPropertyName(bHasContent && sValue.isEmpty()? PROP_CONTENT : PROP_CURRENT_PRESENTATION), + uno::Any( rResult )); + + // LO always automatically updates a DocInfo field from the File-Properties-Custom Prop + // while MS Word requires the user to manually refresh the field (with F9). + // In other words, Word lets the field to be out of sync with the controlling variable. + // Marking as FIXEDFLD solves the automatic replacement problem, but of course prevents + // Writer from making any changes, even on an F9 refresh. + OUString sVariable = pContext->GetVariableValue(); + if (rResult.getLength() != sVariable.getLength()) + { + sal_Int32 nLen = sVariable.indexOf('\x0'); + if (nLen >= 0) + sVariable = sVariable.copy(0, nLen); + } + bool bCustomFixedField = rResult != sVariable && + xServiceInfo->supportsService("com.sun.star.text.TextField.DocInfo.Custom"); + + if (bCustomFixedField || xServiceInfo->supportsService( + "com.sun.star.text.TextField.DocInfo.CreateDateTime")) + { + // Creation time is const, don't try to update it. + xFieldProperties->setPropertyValue("IsFixed", uno::Any(true)); + } + } + } + catch( const beans::UnknownPropertyException& ) + { + //some fields don't have a CurrentPresentation (DateTime) + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "DomainMapper_Impl::SetFieldResult"); + } +} + +void DomainMapper_Impl::SetFieldFFData(const FFDataHandler::Pointer_t& pFFDataHandler) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("setFieldFFData"); +#endif + + if (!m_aFieldStack.empty()) + { + FieldContextPtr pContext = m_aFieldStack.back(); + if (pContext) + { + pContext->setFFDataHandler(pFFDataHandler); + } + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void DomainMapper_Impl::PopFieldContext() +{ + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("popFieldContext"); +#endif + + if (m_aFieldStack.empty()) + return; + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + if( !pContext->IsCommandCompleted() ) + CloseFieldCommand(); + + if (!pContext->GetResult().isEmpty()) + { + uno::Reference< beans::XPropertySet > xFieldProperties = pContext->GetCustomField(); + if(xFieldProperties.is()) + SetNumberFormat( pContext->GetResult(), xFieldProperties, true ); + SetFieldResult( pContext->GetResult() ); + } + + //insert the field, TC or TOC + uno::Reference< text::XTextAppend > xTextAppend; + if (!m_aTextAppendStack.empty()) + xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XTextContent > xToInsert( pContext->GetTOC(), uno::UNO_QUERY ); + if( xToInsert.is() ) + { + if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography) + { + // inside SDT, last empty paragraph is also part of index + if (!m_bParaChanged && !m_xSdtEntryStart) + { + // End of index is the first item on a new paragraph - this paragraph + // should not be part of index + auto xCursor + = xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd()); + xCursor->goLeft(1, true); + // delete + xCursor->setString(OUString()); + // But a new paragraph should be started after the index instead + if (m_bIsNewDoc) // this check - see testTdf129402 + { // where finishParagraph inserts between 2 EndNode + xTextAppend->finishParagraph(css::beans::PropertyValues()); + } + else + { + xTextAppend->finishParagraphInsert(css::beans::PropertyValues(), + m_aTextAppendStack.top().xInsertPosition); + } + } + m_bStartedTOC = false; + m_aTextAppendStack.pop(); + m_bTextInserted = false; + m_bParaChanged = true; // the paragraph must stay anyway + } + m_bStartTOC = false; + m_bStartIndex = false; + m_bStartBibliography = false; + if (IsInHeaderFooter() && m_bStartTOCHeaderFooter) + m_bStartTOCHeaderFooter = false; + } + else + { + xToInsert.set(pContext->GetTC(), uno::UNO_QUERY); + if( !xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography ) + xToInsert = pContext->GetTextField(); + if( xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography) + { + PropertyMap aMap; + // Character properties of the field show up here the + // last (always empty) run. Inherit character + // properties from there. + // Also merge in the properties from the field context, + // e.g. SdtEndBefore. + if (m_pLastCharacterContext) + aMap.InsertProps(m_pLastCharacterContext); + aMap.InsertProps(m_aFieldStack.back()->getProperties()); + appendTextContent(xToInsert, aMap.GetPropertyValues()); + CheckRedline( xToInsert->getAnchor( ) ); + } + else + { + uno::Reference< text::XTextCursor > xCrsr = xTextAppend->createTextCursorByRange(pContext->GetStartRange()); + FormControlHelper::Pointer_t pFormControlHelper(pContext->getFormControlHelper()); + if (pFormControlHelper) + { + // xCrsr may be empty e.g. when pContext->GetStartRange() is outside of + // xTextAppend, like when a field started in a parent paragraph is being + // closed inside an anchored text box. It could be possible to throw an + // exception here, and abort import, but Word tolerates such invalid + // input, so it makes sense to do the same (tdf#152200) + if (xCrsr.is()) + { + uno::Reference< text::XFormField > xFormField(pContext->GetFormField()); + if (pFormControlHelper->hasFFDataHandler()) + { + xToInsert.set(xFormField, uno::UNO_QUERY); + if (xFormField.is() && xToInsert.is()) + { + PopFieldmark(m_aTextAppendStack, xCrsr, + pContext->GetFieldId()); + pFormControlHelper->processField(xFormField); + } + else + { + pFormControlHelper->insertControl(xCrsr); + } + } + else + { + PopFieldmark(m_aTextAppendStack, xCrsr, + pContext->GetFieldId()); + uno::Reference<lang::XComponent>(xFormField, uno::UNO_QUERY_THROW)->dispose(); // presumably invalid? + } + } + } + else if (!pContext->GetHyperlinkURL().isEmpty() && xCrsr.is()) + { + if (m_aTextAppendStack.top().xInsertPosition.is()) + { + xCrsr->gotoRange(m_aTextAppendStack.top().xInsertPosition, true); + } + else + { + xCrsr->gotoEnd(true); + } + + // Draw components (like comments) need hyperlinks set differently + SvxUnoTextRangeBase* pDrawText = dynamic_cast<SvxUnoTextRangeBase*>(xCrsr.get()); + if ( pDrawText ) + pDrawText->attachField( std::make_unique<SvxURLField>(pContext->GetHyperlinkURL(), xCrsr->getString(), SvxURLFormat::AppDefault) ); + else + { + uno::Reference< beans::XPropertySet > xCrsrProperties( xCrsr, uno::UNO_QUERY_THROW ); + xCrsrProperties->setPropertyValue(getPropertyName(PROP_HYPER_LINK_U_R_L), uno:: + Any(pContext->GetHyperlinkURL())); + + if (!pContext->GetHyperlinkTarget().isEmpty()) + xCrsrProperties->setPropertyValue("HyperLinkTarget", uno::Any(pContext->GetHyperlinkTarget())); + + if (m_bStartTOC) { + OUString sDisplayName("Index Link"); + xCrsrProperties->setPropertyValue("VisitedCharStyleName",uno::Any(sDisplayName)); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName",uno::Any(sDisplayName)); + } + else + { + uno::Any aAny = xCrsrProperties->getPropertyValue("CharStyleName"); + OUString charStyle; + if (css::uno::fromAny(aAny, &charStyle)) + { + if (charStyle.isEmpty()) + { + xCrsrProperties->setPropertyValue("VisitedCharStyleName", uno::Any(OUString("Default Style"))); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", uno::Any(OUString("Default Style"))); + } + else if (charStyle.equalsIgnoreAsciiCase("Internet Link")) + { + xCrsrProperties->setPropertyValue("CharStyleName", uno::Any(OUString("Default Style"))); + } + else + { + xCrsrProperties->setPropertyValue("VisitedCharStyleName", aAny); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", aAny); + } + } + } + } + } + else if (m_nStartGenericField != 0) + { + --m_nStartGenericField; + PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId()); + if(m_bTextInserted) + { + m_bTextInserted = false; + } + } + } + } + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" ); + } + } + + //TOCs have to include all the imported content + } + + std::vector<FieldParagraph> aParagraphsToFinish; + if (pContext) + { + aParagraphsToFinish = pContext->GetParagraphsToFinish(); + } + + //remove the field context + m_aFieldStack.pop_back(); + + // Finish the paragraph(s) now that the field is closed. + for (const auto& rFinish : aParagraphsToFinish) + { + finishParagraph(rFinish.m_pPropertyMap, rFinish.m_bRemove); + } +} + + +void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName ) +{ + BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( m_sCurrentBkmkId ); + if( aBookmarkIter != m_aBookmarkMap.end() ) + { + // fields are internal bookmarks: consume redundant "normal" bookmark + if ( IsOpenField() ) + { + FFDataHandler::Pointer_t pFFDataHandler(GetTopFieldContext()->getFFDataHandler()); + if (pFFDataHandler && pFFDataHandler->getName() == rBookmarkName) + { + // HACK: At the END marker, StartOrEndBookmark will START + // a bookmark which will eventually be abandoned, not created. + m_aBookmarkMap.erase(aBookmarkIter); + return; + } + } + + aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + rBookmarkName; + m_sCurrentBkmkPrefix.clear(); + } + else + { + m_sCurrentBkmkName = rBookmarkName; + m_sCurrentBkmkPrefix.clear(); + } +} + +// This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation. +void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) +{ + /* + * Add the dummy paragraph to handle section properties + * iff the first element in the section is a table. If the dummy para is not added yet, then add it; + * So bookmark is not attached to the wrong paragraph. + */ + if(hasTableManager() && getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() &&!GetIsTextFrameInserted()) + { + AddDummyParaForTableInSection(); + } + + bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection(); + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( rId ); + //is the bookmark name already registered? + try + { + if( aBookmarkIter != m_aBookmarkMap.end() ) + { + if (m_xTextFactory.is()) + { + uno::Reference< text::XTextContent > xBookmark( m_xTextFactory->createInstance( "com.sun.star.text.Bookmark" ), uno::UNO_QUERY_THROW ); + uno::Reference< text::XTextCursor > xCursor; + uno::Reference< text::XText > xText = aBookmarkIter->second.m_xTextRange->getText(); + if( aBookmarkIter->second.m_bIsStartOfText && !bIsAfterDummyPara) + { + xCursor = xText->createTextCursorByRange( xText->getStart() ); + } + else + { + xCursor = xText->createTextCursorByRange( aBookmarkIter->second.m_xTextRange ); + xCursor->goRight( 1, false ); + } + + xCursor->gotoRange( xTextAppend->getEnd(), true ); + // A Paragraph was recently finished, and a new Paragraph has not been started as yet + // then move the bookmark-End to the earlier paragraph + if (IsOutsideAParagraph()) + { + // keep bookmark range + uno::Reference< text::XTextRange > xStart = xCursor->getStart(); + xCursor->goLeft( 1, false ); + xCursor->gotoRange(xStart, true ); + } + uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW ); + SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark"); + //todo: make sure the name is not used already! + xBkmNamed->setName( aBookmarkIter->second.m_sBookmarkName ); + xTextAppend->insertTextContent( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW), xBookmark, !xCursor->isCollapsed() ); + } + m_aBookmarkMap.erase( aBookmarkIter ); + m_sCurrentBkmkId.clear(); + } + else + { + //otherwise insert a text range as marker + bool bIsStart = true; + uno::Reference< text::XTextRange > xCurrent; + if (xTextAppend.is()) + { + uno::Reference<text::XTextCursor> const xCursor = + xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd() ); + + if (!xCursor) + return; + + if (!bIsAfterDummyPara) + bIsStart = !xCursor->goLeft(1, false); + xCurrent = xCursor->getStart(); + } + m_sCurrentBkmkId = rId; + m_aBookmarkMap.emplace( rId, BookmarkInsertPosition( bIsStart, m_sCurrentBkmkName, xCurrent ) ); + m_sCurrentBkmkName.clear(); + } + } + catch( const uno::Exception& ) + { + //TODO: What happens to bookmarks where start and end are at different XText objects? + } +} + +void DomainMapper_Impl::SetMoveBookmark( bool bIsFrom ) +{ + static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = u"__RefMoveFrom__"; + static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = u"__RefMoveTo__"; + if ( bIsFrom ) + m_sCurrentBkmkPrefix = MoveFrom_Bookmark_NamePrefix; + else + m_sCurrentBkmkPrefix = MoveTo_Bookmark_NamePrefix; +} + +void DomainMapper_Impl::setPermissionRangeEd(const OUString& user) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_Ed = user; + else + m_sCurrentPermEd = user; +} + +void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_EdGrp = group; + else + m_sCurrentPermEdGrp = group; +} + +// This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark() +void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId) +{ + /* + * Add the dummy paragraph to handle section properties + * if the first element in the section is a table. If the dummy para is not added yet, then add it; + * So permission is not attached to the wrong paragraph. + */ + if (getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted()) + { + AddDummyParaForTableInSection(); + } + + if (m_aTextAppendStack.empty()) + return; + + const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection(); + + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId); + + //is the bookmark name already registered? + try + { + if (aPermIter == m_aPermMap.end()) + { + //otherwise insert a text range as marker + bool bIsStart = true; + uno::Reference< text::XTextRange > xCurrent; + if (xTextAppend.is()) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd()); + + if (!bIsAfterDummyPara) + bIsStart = !xCursor->goLeft(1, false); + xCurrent = xCursor->getStart(); + } + + // register the start of the new permission + m_sCurrentPermId = permissinId; + m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent)); + + // clean up + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + else + { + if (m_xTextFactory.is()) + { + uno::Reference< text::XTextCursor > xCursor; + uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText(); + if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara) + { + xCursor = xText->createTextCursorByRange(xText->getStart()); + } + else + { + xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange); + xCursor->goRight(1, false); + } + + xCursor->gotoRange(xTextAppend->getEnd(), true); + // A Paragraph was recently finished, and a new Paragraph has not been started as yet + // then move the bookmark-End to the earlier paragraph + if (IsOutsideAParagraph()) + { + xCursor->goLeft(1, false); + } + + // create a new bookmark using specific bookmark name pattern for permissions + uno::Reference< text::XTextContent > xPerm(m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), uno::UNO_QUERY_THROW); + uno::Reference< container::XNamed > xPermNamed(xPerm, uno::UNO_QUERY_THROW); + xPermNamed->setName(aPermIter->second.createBookmarkName()); + + // add new bookmark + const bool bAbsorb = !xCursor->isCollapsed(); + uno::Reference< text::XTextRange > xCurrent(xCursor, uno::UNO_QUERY_THROW); + xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb); + } + + // remove processed permission + m_aPermMap.erase(aPermIter); + + // clean up + m_sCurrentPermId = 0; + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + } + catch (const uno::Exception&) + { + //TODO: What happens to bookmarks where start and end are at different XText objects? + } +} + +void DomainMapper_Impl::AddAnnotationPosition( + const bool bStart, + const sal_Int32 nAnnotationId) +{ + if (m_aTextAppendStack.empty()) + return; + + // Create a cursor, pointing to the current position. + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + uno::Reference<text::XTextRange> xCurrent; + if (xTextAppend.is()) + { + uno::Reference<text::XTextCursor> xCursor; + if (m_bIsNewDoc) + xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd()); + else + xCursor = m_aTextAppendStack.top().xCursor; + if (xCursor.is()) + xCurrent = xCursor->getStart(); + } + + // And save it, to be used by PopAnnotation() later. + AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[ nAnnotationId ]; + if (bStart) + { + aAnnotationPosition.m_xStart = xCurrent; + } + else + { + aAnnotationPosition.m_xEnd = xCurrent; + } + m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition; +} + +GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport(GraphicImportType eGraphicImportType) +{ + if(!m_pGraphicImport) + m_pGraphicImport = new GraphicImport( m_xComponentContext, m_xTextFactory, m_rDMapper, eGraphicImportType, m_aPositionOffsets, m_aAligns, m_aPositivePercentages ); + return m_pGraphicImport; +} +/*------------------------------------------------------------------------- + reset graphic import if the last import resulted in a shape, not a graphic + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::ResetGraphicImport() +{ + m_pGraphicImport.clear(); +} + + +void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference< Properties >::Pointer_t& ref, GraphicImportType eGraphicImportType) +{ + GetGraphicImport(eGraphicImportType); + if( eGraphicImportType != IMPORT_AS_DETECTED_INLINE && eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR ) + { + //create the graphic + ref->resolve( *m_pGraphicImport ); + } + + //insert it into the document at the current cursor position + + uno::Reference<text::XTextContent> xTextContent + (m_pGraphicImport->GetGraphicObject()); + + // In case the SDT starts with the text portion of the graphic, then set the SDT properties here. + bool bHasGrabBag = false; + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + if (xPropertySet.is()) + { + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + bHasGrabBag = xPropertySetInfo->hasPropertyByName("FrameInteropGrabBag"); + // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one. + if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph()) + { + comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue("FrameInteropGrabBag")); + aFrameGrabBag["SdtPr"] <<= m_pSdtHelper->getInteropGrabBagAndClear(); + xPropertySet->setPropertyValue("FrameInteropGrabBag", uno::Any(aFrameGrabBag.getAsConstPropertyValueList())); + } + } + + /* Set "SdtEndBefore" property on Drawing. + * It is required in a case when Drawing appears immediately after first run i.e. + * there is no text/space/tab in between two runs. + * In this case "SdtEndBefore" property needs to be set on Drawing. + */ + if(IsSdtEndBefore()) + { + if(xPropertySet.is() && bHasGrabBag) + { + uno::Sequence<beans::PropertyValue> aFrameGrabBag( comphelper::InitPropertySequence({ + { "SdtEndBefore", uno::Any(true) } + })); + xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aFrameGrabBag)); + } + } + + + // Update the shape properties if it is embedded object. + if(m_xEmbedded.is()){ + if (m_pGraphicImport->GetXShapeObject()) + m_pGraphicImport->GetXShapeObject()->setPosition( + m_pGraphicImport->GetGraphicObjectPosition()); + + uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject(); + UpdateEmbeddedShapeProps(xShape); + if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR) + { + uno::Reference<beans::XPropertySet> xEmbeddedProps(m_xEmbedded, uno::UNO_QUERY); + xEmbeddedProps->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER)); + xEmbeddedProps->setPropertyValue("IsFollowingTextFlow", uno::Any(m_pGraphicImport->GetLayoutInCell())); + uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY); + xEmbeddedProps->setPropertyValue("HoriOrient", xShapeProps->getPropertyValue("HoriOrient")); + xEmbeddedProps->setPropertyValue("HoriOrientPosition", xShapeProps->getPropertyValue("HoriOrientPosition")); + xEmbeddedProps->setPropertyValue("HoriOrientRelation", xShapeProps->getPropertyValue("HoriOrientRelation")); + xEmbeddedProps->setPropertyValue("VertOrient", xShapeProps->getPropertyValue("VertOrient")); + xEmbeddedProps->setPropertyValue("VertOrientPosition", xShapeProps->getPropertyValue("VertOrientPosition")); + xEmbeddedProps->setPropertyValue("VertOrientRelation", xShapeProps->getPropertyValue("VertOrientRelation")); + //tdf123873 fix missing textwrap import + xEmbeddedProps->setPropertyValue("TextWrap", xShapeProps->getPropertyValue("TextWrap")); + + // GraphicZOrderHelper::findZOrder() was called already, so can just copy it over. + xEmbeddedProps->setPropertyValue("ZOrder", xShapeProps->getPropertyValue("ZOrder")); + } + } + //insert it into the document at the current cursor position + OSL_ENSURE( xTextContent.is(), "DomainMapper_Impl::ImportGraphic"); + if( xTextContent.is()) + { + bool bAppend = true; + // workaround for images anchored to characters: add ZWSPs around the anchoring point + if ( eGraphicImportType != IMPORT_AS_DETECTED_INLINE && !m_aRedlines.top().empty() ) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + xCrsr->gotoEnd(false); + PropertyMapPtr pEmpty(new PropertyMap()); + appendTextPortion(u"", pEmpty); + appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() ); + bAppend = false; + xCrsr->gotoEnd(false); + appendTextPortion(u"", pEmpty); + + m_bRedlineImageInPreviousRun = true; + m_previousRedline = m_currentRedline; + } + catch( const uno::Exception& ) + { + } + } + } + + if ( bAppend ) + appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() ); + + if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty()) + { + // Remember this object is anchored to the current paragraph. + AnchoredObjectInfo aInfo; + aInfo.m_xAnchoredObject = xTextContent; + if (m_pGraphicImport) + { + // We still have the graphic import around, remember the original margin, so later + // SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing() can use it. + aInfo.m_nLeftMargin = m_pGraphicImport->GetLeftMarginOrig(); + } + m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo); + } + else if (eGraphicImportType == IMPORT_AS_DETECTED_INLINE) + { + m_bParaWithInlineObject = true; + + // store inline images with track changes, because the anchor point + // to set redlining is not available yet + if (!m_aTextAppendStack.empty() && !m_aRedlines.top().empty() ) + { + // Remember this object is anchored to the current paragraph. + AnchoredObjectInfo aInfo; + aInfo.m_xAnchoredObject = xTextContent; + aInfo.m_xRedlineForInline = m_aRedlines.top().back(); + m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo); + } + + } + } + + // Clear the reference, so in case the embedded object is inside a + // TextFrame, we won't try to resize it (to match the size of the + // TextFrame) here. + m_xEmbedded.clear(); + m_pGraphicImport.clear(); +} + + +void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn ) +{ + if( !m_bLineNumberingSet ) + { + try + { + uno::Reference< text::XLineNumberingProperties > xLineProperties( m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProperties = xLineProperties->getLineNumberingProperties(); + uno::Any aTrue( uno::Any( true )); + xProperties->setPropertyValue( getPropertyName( PROP_IS_ON ), aTrue); + xProperties->setPropertyValue( getPropertyName( PROP_COUNT_EMPTY_LINES ), aTrue ); + xProperties->setPropertyValue( getPropertyName( PROP_COUNT_LINES_IN_FRAMES ), uno::Any( false ) ); + xProperties->setPropertyValue( getPropertyName( PROP_INTERVAL ), uno::Any( static_cast< sal_Int16 >( nLnnMod ))); + xProperties->setPropertyValue( getPropertyName( PROP_DISTANCE ), uno::Any( ConversionHelper::convertTwipToMM100(ndxaLnn) )); + xProperties->setPropertyValue( getPropertyName( PROP_NUMBER_POSITION ), uno::Any( style::LineNumberPosition::LEFT)); + xProperties->setPropertyValue( getPropertyName( PROP_NUMBERING_TYPE ), uno::Any( style::NumberingType::ARABIC)); + xProperties->setPropertyValue( getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::Any( nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage )); + } + catch( const uno::Exception& ) + {} + } + m_bLineNumberingSet = true; + uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( GetTextDocument(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xStyles; + xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xStyles; + lcl_linenumberingHeaderFooter( xStyles, "Header", this ); + lcl_linenumberingHeaderFooter( xStyles, "Footer", this ); +} + + +void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue ) +{ + nValue = ConversionHelper::convertTwipToMM100(nValue); + switch(eElement) + { + case PAGE_MAR_TOP : m_aPageMargins.top = nValue; break; + case PAGE_MAR_RIGHT : m_aPageMargins.right = nValue; break; + case PAGE_MAR_BOTTOM : m_aPageMargins.bottom = nValue; break; + case PAGE_MAR_LEFT : m_aPageMargins.left = nValue; break; + case PAGE_MAR_HEADER : m_aPageMargins.header = nValue; break; + case PAGE_MAR_FOOTER : m_aPageMargins.footer = nValue; break; + case PAGE_MAR_GUTTER: + m_aPageMargins.gutter = nValue; + break; + } +} + + +PageMar::PageMar() + : top(ConversionHelper::convertTwipToMM100( sal_Int32(1440))) + // This is strange, the RTF spec says it's 1800, but it's clearly 1440 in Word + // OOXML seems not to specify a default value + , right(ConversionHelper::convertTwipToMM100( sal_Int32(1440))) + , bottom(top) + , left(right) + , header(ConversionHelper::convertTwipToMM100(sal_Int32(720))) + , footer(header) + , gutter(0) +{ +} + + +void DomainMapper_Impl::RegisterFrameConversion( + uno::Reference< text::XTextRange > const& xFrameStartRange, + uno::Reference< text::XTextRange > const& xFrameEndRange, + std::vector<beans::PropertyValue>&& rFrameProperties + ) +{ + OSL_ENSURE( + m_aFrameProperties.empty() && !m_xFrameStartRange.is() && !m_xFrameEndRange.is(), + "frame properties not removed"); + m_aFrameProperties = std::move(rFrameProperties); + m_xFrameStartRange = xFrameStartRange; + m_xFrameEndRange = xFrameEndRange; +} + + +void DomainMapper_Impl::ExecuteFrameConversion() +{ + if( m_xFrameStartRange.is() && m_xFrameEndRange.is() && !m_bDiscardHeaderFooter ) + { + std::vector<sal_Int32> redPos, redLen; + try + { + uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( GetTopTextAppend(), uno::UNO_QUERY_THROW ); + // convert redline ranges to cursor movement and character length + sal_Int32 redIdx; + lcl_CopyRedlines(GetTopTextAppend(), m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx); + + const uno::Reference< text::XTextContent >& xTextContent = xTextAppendAndConvert->convertToTextFrame( + m_xFrameStartRange, + m_xFrameEndRange, + comphelper::containerToSequence(m_aFrameProperties) ); + + uno::Reference< text::XText > xDest( xTextContent, uno::UNO_QUERY_THROW ); + lcl_PasteRedlines(xDest, m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx); + } + catch( const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION( "writerfilter.dmapper", "Exception caught when converting to frame"); + } + + m_bIsActualParagraphFramed = false; + + if (redPos.size() == m_aStoredRedlines[StoredRedlines::FRAME].size()/3) + { + for( sal_Int32 i = m_aStoredRedlines[StoredRedlines::FRAME].size() - 1; i >= 0; --i) + { + // keep redlines of floating tables to process them in CloseSectionGroup() + if ( redPos[i/3] != -1 ) + { + m_aStoredRedlines[StoredRedlines::FRAME].erase(m_aStoredRedlines[StoredRedlines::FRAME].begin() + i); + } + } + } + else + m_aStoredRedlines[StoredRedlines::FRAME].clear(); + } + m_xFrameStartRange = nullptr; + m_xFrameEndRange = nullptr; + m_aFrameProperties.clear(); +} + +void DomainMapper_Impl::AddNewRedline( sal_uInt32 sprmId ) +{ + RedlineParamsPtr pNew( new RedlineParams ); + pNew->m_nToken = XML_mod; + if ( !m_bIsParaMarkerChange ) + { + // <w:rPrChange> applies to the whole <w:r>, <w:pPrChange> applies to the whole <w:p>, + // so keep those two in CONTEXT_CHARACTERS and CONTEXT_PARAGRAPH, which will take + // care of their scope (i.e. when they should be used and discarded). + // Let's keep the rest the same way they used to be handled (explicitly dropped + // from a global stack by endtrackchange), but quite possibly they should not be handled + // that way either (I don't know). + if( sprmId == NS_ooxml::LN_EG_RPrContent_rPrChange ) + GetTopContextOfType( CONTEXT_CHARACTER )->Redlines().push_back( pNew ); + else if( sprmId == NS_ooxml::LN_CT_PPr_pPrChange ) + GetTopContextOfType( CONTEXT_PARAGRAPH )->Redlines().push_back( pNew ); + else if( sprmId != NS_ooxml::LN_CT_ParaRPr_rPrChange ) + m_aRedlines.top().push_back( pNew ); + } + else + { + m_pParaMarkerRedline = pNew; + } + // Newly read data will go into this redline. + m_currentRedline = pNew; +} + +void DomainMapper_Impl::SetCurrentRedlineIsRead() +{ + m_currentRedline.clear(); +} + +sal_Int32 DomainMapper_Impl::GetCurrentRedlineToken( ) const +{ + assert(m_currentRedline); + return m_currentRedline->m_nToken; +} + +void DomainMapper_Impl::SetCurrentRedlineAuthor( const OUString& sAuthor ) +{ + if (!m_xAnnotationField.is()) + { + if (m_currentRedline) + m_currentRedline->m_sAuthor = sAuthor; + else + SAL_INFO("writerfilter.dmapper", "numberingChange not implemented"); + } + else + m_xAnnotationField->setPropertyValue("Author", uno::Any(sAuthor)); +} + +void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials ) +{ + if (m_xAnnotationField.is()) + m_xAnnotationField->setPropertyValue("Initials", uno::Any(sInitials)); +} + +void DomainMapper_Impl::SetCurrentRedlineDate( const OUString& sDate ) +{ + if (!m_xAnnotationField.is()) + { + if (m_currentRedline) + m_currentRedline->m_sDate = sDate; + else + SAL_INFO("writerfilter.dmapper", "numberingChange not implemented"); + } + else + m_xAnnotationField->setPropertyValue("DateTimeValue", uno::Any(ConversionHelper::ConvertDateStringToDateTime(sDate))); +} + +void DomainMapper_Impl::SetCurrentRedlineId( sal_Int32 sId ) +{ + if (m_xAnnotationField.is()) + { + m_nAnnotationId = sId; + } + else + { + // This should be an assert, but somebody had the smart idea to reuse this function also for comments and whatnot, + // and in some cases the id is actually not handled, which may be in fact a bug. + if( !m_currentRedline) + SAL_INFO("writerfilter.dmapper", "no current redline"); + } +} + +void DomainMapper_Impl::SetCurrentRedlineToken( sal_Int32 nToken ) +{ + assert(m_currentRedline); + m_currentRedline->m_nToken = nToken; +} + +void DomainMapper_Impl::SetCurrentRedlineRevertProperties( const uno::Sequence<beans::PropertyValue>& aProperties ) +{ + assert(m_currentRedline); + m_currentRedline->m_aRevertProperties = aProperties; +} + + +// This removes only the last redline stored here, those stored in contexts are automatically removed when +// the context is destroyed. +void DomainMapper_Impl::RemoveTopRedline( ) +{ + if (m_aRedlines.top().empty()) + { + if (GetFootnoteCount() > -1 || GetEndnoteCount() > -1) + return; + SAL_WARN("writerfilter.dmapper", "RemoveTopRedline called with empty stack"); + throw uno::Exception("RemoveTopRedline failed", nullptr); + } + m_aRedlines.top().pop_back( ); + m_currentRedline.clear(); +} + +void DomainMapper_Impl::ApplySettingsTable() +{ + if (!(m_pSettingsTable && m_xTextFactory.is())) + return; + + try + { + uno::Reference< beans::XPropertySet > xTextDefaults(m_xTextFactory->createInstance("com.sun.star.text.Defaults"), uno::UNO_QUERY_THROW ); + sal_Int32 nDefTab = m_pSettingsTable->GetDefaultTabStop(); + xTextDefaults->setPropertyValue( getPropertyName( PROP_TAB_STOP_DISTANCE ), uno::Any(nDefTab) ); + if (m_pSettingsTable->GetLinkStyles()) + { + // If linked styles are enabled, set paragraph defaults from Word's default template + xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_BOTTOM_MARGIN), uno::Any(ConversionHelper::convertTwipToMM100(200))); + style::LineSpacing aSpacing; + aSpacing.Mode = style::LineSpacingMode::PROP; + aSpacing.Height = sal_Int16(115); + xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_LINE_SPACING), uno::Any(aSpacing)); + } + + if (m_pSettingsTable->GetZoomFactor() || m_pSettingsTable->GetView()) + { + std::vector<beans::PropertyValue> aViewProps; + if (m_pSettingsTable->GetZoomFactor()) + { + aViewProps.emplace_back("ZoomFactor", -1, uno::Any(m_pSettingsTable->GetZoomFactor()), beans::PropertyState_DIRECT_VALUE); + aViewProps.emplace_back("VisibleBottom", -1, uno::Any(sal_Int32(0)), beans::PropertyState_DIRECT_VALUE); + aViewProps.emplace_back("ZoomType", -1, + uno::Any(m_pSettingsTable->GetZoomType()), + beans::PropertyState_DIRECT_VALUE); + } + rtl::Reference< comphelper::IndexedPropertyValuesContainer > xBox = new comphelper::IndexedPropertyValuesContainer(); + xBox->insertByIndex(sal_Int32(0), uno::Any(comphelper::containerToSequence(aViewProps))); + uno::Reference<document::XViewDataSupplier> xViewDataSupplier(m_xTextDocument, uno::UNO_QUERY); + xViewDataSupplier->setViewData(xBox); + } + + uno::Reference< beans::XPropertySet > xSettings(m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY); + + if (m_pSettingsTable->GetDoNotExpandShiftReturn()) + xSettings->setPropertyValue( "DoNotJustifyLinesWithManualBreak", uno::Any(true) ); + if (m_pSettingsTable->GetUsePrinterMetrics()) + xSettings->setPropertyValue("PrinterIndependentLayout", uno::Any(document::PrinterIndependentLayout::DISABLED)); + if( m_pSettingsTable->GetEmbedTrueTypeFonts()) + xSettings->setPropertyValue( getPropertyName( PROP_EMBED_FONTS ), uno::Any(true) ); + if( m_pSettingsTable->GetEmbedSystemFonts()) + xSettings->setPropertyValue( getPropertyName( PROP_EMBED_SYSTEM_FONTS ), uno::Any(true) ); + xSettings->setPropertyValue("AddParaTableSpacing", uno::Any(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing())); + if (m_pSettingsTable->GetNoLeading()) + { + xSettings->setPropertyValue("AddExternalLeading", uno::Any(!m_pSettingsTable->GetNoLeading())); + } + if( m_pSettingsTable->GetProtectForm() ) + xSettings->setPropertyValue("ProtectForm", uno::Any( true )); + if( m_pSettingsTable->GetReadOnly() ) + xSettings->setPropertyValue("LoadReadonly", uno::Any( true )); + if (m_pSettingsTable->GetGutterAtTop()) + { + xSettings->setPropertyValue("GutterAtTop", uno::Any(true)); + } + uno::Sequence<beans::PropertyValue> aWriteProtection + = m_pSettingsTable->GetWriteProtectionSettings(); + if (aWriteProtection.hasElements()) + xSettings->setPropertyValue("ModifyPasswordInfo", uno::Any(aWriteProtection)); + } + catch(const uno::Exception&) + { + } +} + +SectionPropertyMap * DomainMapper_Impl::GetSectionContext() +{ + SectionPropertyMap* pSectionContext = nullptr; + //the section context is not available before the first call of startSectionGroup() + if( !IsAnyTableImport() ) + { + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION); + pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + } + + return pSectionContext; +} + +void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any& value) +{ + deferredCharacterProperties[ id ] = value; +} + +void DomainMapper_Impl::processDeferredCharacterProperties() +{ + // Actually process in DomainMapper, so that it's the same source file like normal processing. + if( !deferredCharacterProperties.empty()) + { + m_rDMapper.processDeferredCharacterProperties( deferredCharacterProperties ); + deferredCharacterProperties.clear(); + } +} + +sal_Int32 DomainMapper_Impl::getNumberingProperty(const sal_Int32 nListId, sal_Int32 nNumberingLevel, const OUString& aProp) +{ + sal_Int32 nRet = 0; + if ( nListId < 0 ) + return nRet; + + try + { + if (nNumberingLevel < 0) // It seems it's valid to omit numbering level, and in that case it means zero. + nNumberingLevel = 0; + + auto const pList(GetListTable()->GetList(nListId)); + assert(pList); + const OUString aListName = pList->GetStyleName(); + const uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY_THROW); + const uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xNumberingStyles; + xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles; + const uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY); + const uno::Reference<container::XIndexAccess> xNumberingRules(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if (xNumberingRules.is()) + { + uno::Sequence<beans::PropertyValue> aProps; + xNumberingRules->getByIndex(nNumberingLevel) >>= aProps; + auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps), + [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; }); + if (pProp != std::cend(aProps)) + pProp->Value >>= nRet; + } + } + catch( const uno::Exception& ) + { + // This can happen when the doc contains some hand-crafted invalid list level. + } + + return nRet; +} + +sal_Int32 DomainMapper_Impl::getCurrentNumberingProperty(const OUString& aProp) +{ + sal_Int32 nRet = 0; + + std::optional<PropertyMap::Property> pProp = m_pTopContext->getProperty(PROP_NUMBERING_RULES); + uno::Reference<container::XIndexAccess> xNumberingRules; + if (pProp) + xNumberingRules.set(pProp->second, uno::UNO_QUERY); + pProp = m_pTopContext->getProperty(PROP_NUMBERING_LEVEL); + // Default numbering level is the first one. + sal_Int32 nNumberingLevel = 0; + if (pProp) + pProp->second >>= nNumberingLevel; + if (xNumberingRules.is()) + { + uno::Sequence<beans::PropertyValue> aProps; + xNumberingRules->getByIndex(nNumberingLevel) >>= aProps; + auto pPropVal = std::find_if(std::cbegin(aProps), std::cend(aProps), + [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; }); + if (pPropVal != std::cend(aProps)) + pPropVal->Value >>= nRet; + } + + return nRet; +} + + +void DomainMapper_Impl::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +void DomainMapper_Impl::disableInteropGrabBag() +{ + m_aInteropGrabBagName.clear(); + m_aInteropGrabBag.clear(); + m_aSubInteropGrabBag.clear(); +} + +bool DomainMapper_Impl::isInteropGrabBagEnabled() const +{ + return !(m_aInteropGrabBagName.isEmpty()); +} + +void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= aValue; + rInteropGrabBag.push_back(aProperty); +} + +void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<beans::PropertyValue>& rValue) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= comphelper::containerToSequence(rValue); + rValue.clear(); + rInteropGrabBag.push_back(aProperty); +} + +void DomainMapper_Impl::substream(Id rName, + ::writerfilter::Reference<Stream>::Pointer_t const& ref) +{ +#ifndef NDEBUG + size_t contextSize(m_aContextStack.size()); + size_t propSize[NUMBER_OF_CONTEXTS]; + for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) { + propSize[i] = m_aPropertyStacks[i].size(); + } +#endif + + // Save "has footnote" state, which is specific to a section in the body + // text, so state from substreams is not relevant. + bool bHasFtn = m_bHasFtn; + + //finalize any waiting frames before starting alternate streams + CheckUnregisteredFrameConversion(); + ExecuteFrameConversion(); + + appendTableManager(); + // Appending a TableManager resets its TableHandler, so we need to append + // that as well, or tables won't be imported properly in headers/footers. + appendTableHandler(); + getTableManager().startLevel(); + + //import of page header/footer + //Ensure that only one header/footer per section is pushed + + switch( rName ) + { + case NS_ooxml::LN_headerl: + PushPageHeader(SectionPropertyMap::PAGE_LEFT); + break; + case NS_ooxml::LN_headerr: + PushPageHeader(SectionPropertyMap::PAGE_RIGHT); + break; + case NS_ooxml::LN_headerf: + PushPageHeader(SectionPropertyMap::PAGE_FIRST); + break; + case NS_ooxml::LN_footerl: + PushPageFooter(SectionPropertyMap::PAGE_LEFT); + break; + case NS_ooxml::LN_footerr: + PushPageFooter(SectionPropertyMap::PAGE_RIGHT); + break; + case NS_ooxml::LN_footerf: + PushPageFooter(SectionPropertyMap::PAGE_FIRST); + break; + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + PushFootOrEndnote( NS_ooxml::LN_footnote == rName ); + break; + case NS_ooxml::LN_annotation : + PushAnnotation(); + break; + } + + try + { + ref->resolve(m_rDMapper); + } + catch (xml::sax::SAXException const&) + { + m_bSaxError = true; + throw; + } + + switch( rName ) + { + case NS_ooxml::LN_headerl: + case NS_ooxml::LN_headerr: + case NS_ooxml::LN_headerf: + case NS_ooxml::LN_footerl: + case NS_ooxml::LN_footerr: + case NS_ooxml::LN_footerf: + PopPageHeaderFooter(); + break; + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + PopFootOrEndnote(); + break; + case NS_ooxml::LN_annotation : + PopAnnotation(); + break; + } + + getTableManager().endLevel(); + popTableManager(); + m_bHasFtn = bHasFtn; + + switch(rName) + { + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + m_pTableHandler->setHadFootOrEndnote(true); + m_bHasFtn = true; + break; + } + + // check that stacks are the same as before substream + assert(m_aContextStack.size() == contextSize); + for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) { + assert(m_aPropertyStacks[i].size() == propSize[i]); + } +} + +void DomainMapper_Impl::commentProps(const OUString& sId, const CommentProperties& rProps) +{ + m_aCommentProps[sId] = rProps; +} + + +bool DomainMapper_Impl::handlePreviousParagraphBorderInBetween() const +{ + if (!m_xPreviousParagraph.is()) + return false; + + // Connected borders ("ParaIsConnectBorder") are always on by default + // and never changed by DomainMapper. Except one case when border in + // between is used. So this is not the best, but easiest way to check + // is previous paragraph has border in between. + bool bConnectBorders = true; + m_xPreviousParagraph->getPropertyValue(getPropertyName(PROP_PARA_CONNECT_BORDERS)) >>= bConnectBorders; + + if (bConnectBorders) + return false; + + // Previous paragraph has border in between. Current one also has (since this + // method is called). So current paragraph will get border above, but + // also need to ensure, that no unexpected bottom border are remaining in previous + // paragraph: since ParaIsConnectBorder=false it will be displayed in unexpected way. + m_xPreviousParagraph->setPropertyValue(getPropertyName(PROP_BOTTOM_BORDER), uno::Any(table::BorderLine2())); + + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |