/* -*- 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/PropertyAttribute.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/CaseMap.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/LabelFollow.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/XChapterNumberingSupplier.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/XTextTable.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/awt/FontRelief.hpp>
#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontWeight.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/deleter.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/temporary.hxx>
#include <oox/mathml/imexport.hxx>
#include <utility>
#include <xmloff/odffields.hxx>
#include <rtl/uri.hxx>
#include <tools/UnitConversion.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/streamwrap.hxx>
#include <comphelper/scopeguard.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/mediadescriptor.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
#include <com/sun/star/drawing/FillStyle.hpp>
 
#include <docufld.hxx>
#include <txtfld.hxx>
#include <ndtxt.hxx>
#include <unicode/errorcode.h>
#include <unicode/regex.h>
#include <unotextcursor.hxx>
#include <unotxdoc.hxx>
#include <SwXDocumentSettings.hxx>
#include <SwXTextDefaults.hxx>
#include <unobookmark.hxx>
#include <unosection.hxx>
#include <unofield.hxx>
#include <unofieldcoll.hxx>
#include <unolinebreak.hxx>
#include <unoframe.hxx>
#include <unoxstyle.hxx>
#include <unostyle.hxx>
#include <unocontentcontrol.hxx>
#include <unofootnote.hxx>
#include <unoport.hxx>
#include <unotextbodyhf.hxx>
#include <unosett.hxx>
#include <unodraw.hxx>
 
using namespace ::com::sun::star;
using namespace oox;
namespace writerfilter::dmapper{
 
//line numbering for header/footer
static void lcl_linenumberingHeaderFooter( const rtl::Reference<SwXStyleFamily>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper )
{
    const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname );
    if (!pEntry)
        return;
    const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->m_pProperties.get();
    if ( !pStyleSheetProperties )
        return;
    sal_Int32 nListId = pStyleSheetProperties->props().GetListId();
    if( xStyles.is() )
    {
        if( xStyles->hasByName( rname ) )
        {
            rtl::Reference< SwXBaseStyle > xStyle = xStyles->getStyleByName( rname );
            if( !xStyle.is() )
                return;
            xStyle->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( u"Name"_ustr, 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( u"Items"_ustr, uno::Any( sItems ) );
 
    sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32();
    if (nResult > 0 && o3tl::make_unsigned(nResult) < sItems.size())
        rxFieldProps->setPropertyValue(u"SelectedItem"_ustr, uno::Any(sItems[nResult]));
    if ( !pFFDataHandler->getHelpText().isEmpty() )
         rxFieldProps->setPropertyValue( u"Help"_ustr, 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()));
    }
}
 
static StyleSheetEntryPtr lcl_getParent(StyleSheetEntryPtr pEntry, StyleSheetTablePtr pStyleSheet)
{
    if (!pEntry->m_sBaseStyleIdentifier.isEmpty())
        return pStyleSheet->FindStyleSheetByISTD(pEntry->m_sBaseStyleIdentifier);
    return nullptr;
}
 
/**
 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->m_pProperties)
        {
            std::optional<PropertyMap::Property> aProperty =
                pEntry->m_pProperties->getProperty(eId);
            if (aProperty)
            {
                if (pEntry->m_pProperties->props().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 = lcl_getParent(pEntry, rStyleSheet);
        SAL_WARN_IF(pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
 
        if (pEntry == pNewEntry) //fdo#49587
            break;
 
        pEntry = std::move(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)
{
    if (!pInner->GetFieldId())
    {
        return true;
    }
 
    std::optional<FieldId> oOuterFieldId = pOuter->GetFieldId();
    if (!oOuterFieldId)
    {
        OUString aCommand = pOuter->GetCommand();
 
        // Ignore leading space before the field name, but don't accept IFF when we check for IF.
        while (aCommand.getLength() > 3 && aCommand[0] == ' ')
            aCommand = aCommand.subView(1);
 
        if (aCommand.startsWith("IF "))
        {
            // This will be FIELD_IF once the command is closed.
            oOuterFieldId = FIELD_IF;
        }
    }
 
    if (!oOuterFieldId)
    {
        return true;
    }
 
    switch (*oOuterFieldId)
    {
        case FIELD_IF:
        {
            switch (*pInner->GetFieldId())
            {
                case FIELD_DOCVARIABLE:
                case FIELD_DOCPROPERTY:
                case FIELD_FORMTEXT:
                case FIELD_FORMULA:
                case FIELD_IF:
                case FIELD_MERGEFIELD:
                case FIELD_REF:
                case FIELD_PAGE:
                case FIELD_NUMPAGES:
                case FIELD_SYMBOL:
                {
                    // LO does not currently know how to evaluate these as conditions or results
                    return false;
                }
                default:
                    // TODO: tdf#125038 suggests everything probably needs to return false
                    break;
            }
            break;
        }
        default:
            break;
    }
 
    return true;
}
 
DomainMapper_Impl::DomainMapper_Impl(
            DomainMapper& rDMapper,
            uno::Reference<uno::XComponentContext> xContext,
            rtl::Reference<SwXTextDocument> const& xModel,
            SourceDocumentType eDocumentType,
            utl::MediaDescriptor const & rMediaDesc) :
        m_eDocumentType( eDocumentType ),
        m_rDMapper( rDMapper ),
        m_pOOXMLDocument(nullptr),
        m_xTextDocument( xModel ),
        m_xComponentContext(std::move( xContext )),
        m_bForceGenericFields(officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get()),
        m_bIsDecimalComma( false ),
        m_bIsFirstSection( true ),
        m_bStartTOC(false),
        m_bStartTOCHeaderFooter(false),
        m_bStartedTOC(false),
        m_bStartIndex(false),
        m_bStartBibliography(false),
        m_nStartGenericField(0),
        m_bTextDeleted(false),
        m_nLastRedlineMovedID(1),
        m_sCurrentPermId(0),
        m_bFrameDirectionSet(false),
        m_bInDocDefaultsImport(false),
        m_bInStyleSheetImport( false ),
        m_bInNumberingImport(false),
        m_bInAnyTableImport( false ),
        m_bDiscardHeaderFooter( false ),
        m_eSkipFootnoteState(SkipFootnoteSeparator::OFF),
        m_nFootnotes(-1),
        m_nEndnotes(-1),
        m_nFirstFootnoteIndex(-1),
        m_nFirstEndnoteIndex(-1),
        m_bLineNumberingSet( false ),
        m_bIsParaMarkerChange( false ),
        m_bIsParaMarkerMove( false ),
        m_bRedlineImageInPreviousRun( false ),
        m_bIsLastSectionGroup( false ),
        m_bUsingEnhancedFields( false ),
        m_nAnnotationId( -1 ),
        m_aSmartTagHandler(m_xComponentContext, m_xTextDocument),
        m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault(u"TextInsertModeRange"_ustr, uno::Reference<text::XTextRange>())),
        m_xAltChunkStartingRange(rMediaDesc.getUnpackedValueOrDefault(u"AltChunkStartingRange"_ustr, uno::Reference<text::XTextRange>())),
        m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault(u"InsertMode"_ustr, false)),
        m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault(u"AltChunkMode"_ustr, false)),
        m_bReadOnly(rMediaDesc.getUnpackedValueOrDefault(u"ReadOnly"_ustr, false)),
        m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault(u"ReadGlossaries"_ustr, false)),
        m_bHasFtnSep(false),
        m_bIsSplitPara(false),
        m_bIsActualParagraphFramed( false ),
        m_bSaxError(false)
{
    m_StreamStateStack.emplace(); // add state for document body
    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(u"failed to find body text of the insert position"_ustr, nullptr);
    }
 
    m_aTextAppendStack.push(TextAppendContext(m_xBodyText,
                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?
    m_pTableHandler = new DomainMapperTableHandler(m_xBodyText, *this);
    getTableManager( ).setHandler(m_pTableHandler);
 
    getTableManager( ).startLevel();
    m_bUsingEnhancedFields = !comphelper::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()
{
    assert(!m_StreamStateStack.empty());
    ChainTextFrames();
    // Don't remove last paragraph when pasting, sw expects that empty paragraph.
    if (m_bIsNewDoc)
    {
        RemoveLastParagraph();
        suppress_fun_call_w_exception(GetStyleSheetTable()->ApplyClonedTOCStyles());
    }
    else if (m_pStyleSheetTable)
    {
        m_pStyleSheetTable->RemoveUnusedParagraphStyles();
    }
    if (hasTableManager())
    {
        getTableManager().endLevel();
        popTableManager();
    }
}
 
writerfilter::ooxml::OOXMLDocument* DomainMapper_Impl::getDocumentReference() const
{
    return m_pOOXMLDocument;
}
 
rtl::Reference< SwXStyleFamily > const &  DomainMapper_Impl::GetPageStyles()
{
    if(!m_xPageStyles.is() && m_xTextDocument)
    {
        m_xPageStyles = m_xTextDocument->getSwStyleFamilies()->GetPageStyles();
    }
    return m_xPageStyles;
}
 
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;
}
 
rtl::Reference< SwXStyleFamily > const &  DomainMapper_Impl::GetCharacterStyles()
{
    if(!m_xCharacterStyles.is() && m_xTextDocument)
    {
        m_xCharacterStyles = m_xTextDocument->getSwStyleFamilies()->GetCharacterStyles();
    }
    return m_xCharacterStyles;
}
 
rtl::Reference<SwXStyleFamily> const& DomainMapper_Impl::GetParagraphStyles()
{
    if (!m_xParagraphStyles.is() && m_xTextDocument)
    {
        m_xParagraphStyles = m_xTextDocument->getSwStyleFamilies()->GetParagraphStyles();
    }
    return m_xParagraphStyles;
}
 
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 )
        {
            std::u16string_view sSuffix;
            if ( rStyleName.startsWith( cListLabel, &sSuffix ) )
            {
                sal_Int32 nSuffix = o3tl::toInt32(sSuffix);
                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;
}
 
rtl::Reference< SwXText > const & DomainMapper_Impl::GetBodyText()
{
    if(!m_xBodyText.is())
    {
        if (m_xInsertTextRange.is())
        {
            uno::Reference<css::text::XText> xText = m_xInsertTextRange->getText();
            assert(!xText || dynamic_cast<SwXText*>(xText.get()));
            m_xBodyText = dynamic_cast<SwXText*>(m_xInsertTextRange->getText().get());
        }
        else if (m_xTextDocument.is())
            m_xBodyText = m_xTextDocument->getBodyText();
    }
    return m_xBodyText;
}
 
 
rtl::Reference<SwXDocumentSettings> const & DomainMapper_Impl::GetDocumentSettings()
{
    if( !m_xDocumentSettings.is() && m_xTextDocument.is())
    {
        m_xDocumentSettings = m_xTextDocument->createDocumentSettings();
    }
    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& )
        {
        }
    }
}
 
namespace
{
void CopyPageDescNameToNextParagraph(const uno::Reference<lang::XComponent>& xParagraph,
                                     const uno::Reference<text::XTextCursor>& xCursor)
{
    // First check if xParagraph has a non-empty page style name to copy from.
    uno::Reference<beans::XPropertySet> xParagraphProps(xParagraph, uno::UNO_QUERY);
    if (!xParagraphProps.is())
    {
        return;
    }
 
    uno::Any aPageDescName = xParagraphProps->getPropertyValue(u"PageDescName"_ustr);
    OUString sPageDescName;
    aPageDescName >>= sPageDescName;
    if (sPageDescName.isEmpty())
    {
        return;
    }
 
    // If so, search for the next paragraph.
    uno::Reference<text::XParagraphCursor> xParaCursor(xCursor, uno::UNO_QUERY);
    if (!xParaCursor.is())
    {
        return;
    }
 
    // Create a range till the next paragraph and then enumerate on the range.
    if (!xParaCursor->gotoNextParagraph(/*bExpand=*/true))
    {
        return;
    }
 
    uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xParaCursor, uno::UNO_QUERY);
    if (!xEnumerationAccess.is())
    {
        return;
    }
 
    uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();
    if (!xEnumeration.is())
    {
        return;
    }
 
    xEnumeration->nextElement();
    if (!xEnumeration->hasMoreElements())
    {
        return;
    }
 
    // We found a next item in the enumeration: it's usually a paragraph, but may be a table as
    // well.
    uno::Reference<beans::XPropertySet> xNextParagraph(xEnumeration->nextElement(), uno::UNO_QUERY);
    if (!xNextParagraph.is())
    {
        return;
    }
 
    // See if there is page style set already: if so, don't touch it.
    OUString sNextPageDescName;
    xNextParagraph->getPropertyValue(u"PageDescName"_ustr) >>= sNextPageDescName;
    if (!sNextPageDescName.isEmpty())
    {
        return;
    }
 
    // Finally copy it over, so it's not lost.
    xNextParagraph->setPropertyValue(u"PageDescName"_ustr, aPageDescName);
}
}
 
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);
        // Make sure no page breaks are lost.
        CopyPageDescNameToNextParagraph(xParagraph, xCursor);
        xParagraph->dispose();
    }
}
void DomainMapper_Impl::AddDummyParaForTableInSection()
{
    // Shapes and textboxes can't have sections.
    if (IsInShape() || m_StreamStateStack.top().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,
                                     bool bAlreadyExpanded)
 {
     OUString sName;
     if (!xCursor.is())
         return sName;
 
     // Select 1 previous element
     if (!bAlreadyExpanded)
         xCursor->goLeft(1, true);
     comphelper::ScopeGuard unselectGuard(
         [xCursor, bAlreadyExpanded]()
         {
             if (!bAlreadyExpanded)
                 xCursor->goRight(1, true);
         });
 
     uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY);
     if (!xParaEnumAccess.is())
         return sName;
 
     // Iterate through selection paragraphs
     uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration();
     if (!xParaEnum->hasMoreElements())
         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(u"TextPortionType"_ustr));
         OUString sType;
         aType >>= sType;
         if (sType == "Bookmark")
         {
             uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue(u"Bookmark"_ustr),
                                                         uno::UNO_QUERY_THROW);
             sName = xBookmark->getName();
             // Do not stop the scan here. Maybe there are 2 bookmarks?
         }
     }
 
     return sName;
 }
 
static void reanchorObjects(const uno::Reference<uno::XInterface>& xFrom,
                            const uno::Reference<text::XTextRange>& xTo,
                            const uno::Reference<drawing::XDrawPage>& xDrawPage)
{
    std::vector<uno::Reference<text::XTextContent>> aShapes;
    bool bFastPathDone = false;
    if (uno::Reference<beans::XPropertySet> xProps{ xFrom, uno::UNO_QUERY })
    {
        try
        {
            // See SwXParagraph::Impl::GetPropertyValues_Impl
            uno::Sequence<uno::Reference<text::XTextContent>> aSeq;
            xProps->getPropertyValue(u"OOXMLImport_AnchoredShapes"_ustr) >>= aSeq;
            aShapes.insert(aShapes.end(), aSeq.begin(), aSeq.end());
            bFastPathDone = true;
        }
        catch (const uno::Exception&)
        {
        }
    }
 
    if (!bFastPathDone)
    {
        // Can this happen? Fallback to slow DrawPage iteration and range comparison
        uno::Reference<text::XTextRange> xRange(xFrom, uno::UNO_QUERY_THROW);
        uno::Reference<text::XTextRangeCompare> xCompare(xRange->getText(), uno::UNO_QUERY_THROW);
 
        const sal_Int32 count = xDrawPage->getCount();
        for (sal_Int32 i = 0; i < count; ++i)
        {
            try
            {
                uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i),
                                                          uno::UNO_QUERY);
                if (!xShape)
                    continue;
                uno::Reference<text::XTextRange> xAnchor(xShape->getAnchor());
                if (!xAnchor)
                    continue;
                if (xCompare->compareRegionStarts(xAnchor, xRange) <= 0
                    && xCompare->compareRegionEnds(xAnchor, xRange) >= 0)
                {
                    aShapes.push_back(xShape);
                }
            }
            catch (const uno::Exception&)
            {
                // Can happen e.g. in compareRegion*, when the shape is in a header,
                // and paragraph in body
            }
        }
    }
 
    for (const auto& xShape : aShapes)
        xShape->attach(xTo);
}
 
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;
 
    if (hasTableManager() && getTableManager().getCurrentTablePosition().getLength() != 0)
    {
        // If we have an open floating table, then don't remove this paragraph, since that'll be the
        // anchor of the floating table. Otherwise we would lose the table.
        return;
    }
 
    try
    {
        uno::Reference< text::XTextCursor > xCursor;
        if (m_bIsNewDoc)
        {
            xCursor = xTextAppend->createTextCursor();
            xCursor->gotoEnd(false);
        }
        else
            xCursor.set(m_aTextAppendStack.top().xCursor, uno::UNO_SET_THROW);
 
        // 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
        bool const bEndOfDocument(m_aTextAppendStack.size() == 1);
 
        uno::Reference<lang::XComponent> xParagraph;
        if (IsInHeaderFooter() || bEndOfDocument)
        {
            if (uno::Reference<container::XEnumerationAccess> xEA{ xCursor, uno::UNO_QUERY })
            {
                xParagraph.set(xEA->createEnumeration()->nextElement(), uno::UNO_QUERY);
            }
        }
 
        xCursor->goLeft(1, true);
        // If this is a text on a shape, possibly the text has the trailing
        // newline removed already. RTF may also not have the trailing newline.
        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")))
            return;
 
        if (!m_xTextDocument)
            return;
 
        static constexpr OUString RecordChanges(u"RecordChanges"_ustr);
 
        comphelper::ScopeGuard redlineRestore(
            [this, aPreviousValue = m_xTextDocument->getPropertyValue(RecordChanges)]()
            { m_xTextDocument->setPropertyValue(RecordChanges, aPreviousValue); });
 
        // disable redlining, otherwise we might end up with an unwanted recorded operations
        m_xTextDocument->setPropertyValue(RecordChanges, uno::Any(false));
 
        if (xParagraph)
        {
            // move all anchored objects to the previous paragraph
            rtl::Reference<SwFmDrawPage> xDrawPage = m_xTextDocument->getSwDrawPage();
            if (xDrawPage && xDrawPage->hasElements())
            {
                // Cursor already spans two paragraphs
                uno::Reference<container::XEnumerationAccess> xEA(xCursor,
                                                                  uno::UNO_QUERY_THROW);
                auto xEnumeration = xEA->createEnumeration();
                uno::Reference<text::XTextRange> xPrevParagraph(xEnumeration->nextElement(),
                                                                uno::UNO_QUERY_THROW);
                reanchorObjects(xParagraph, xPrevParagraph, xDrawPage);
            }
 
            xParagraph->dispose();
        }
        else
        {
            // 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, true);
 
            // The cursor already selects across the paragraph break
            // delete
            xCursor->setString(OUString());
 
            // 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, false);
                if (sBookmarkNameAfterRemoval.isEmpty())
                {
                    // Yes, it was removed. Restore
                    rtl::Reference<SwXBookmark> xBookmark(m_xTextDocument->createBookmark());
                    xBookmark->setName(sLastBookmarkName);
                    xTextAppend->insertTextContent(xCursor, xBookmark, !xCursor->isCollapsed());
                }
            }
        }
    }
    catch( const uno::Exception& )
    {
    }
}
 
 
void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast )
{
    m_bIsLastSectionGroup = bIsLast;
}
 
void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast )
{
    m_StreamStateStack.top().bIsLastParaInSection = bIsLast;
}
 
 
void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst )
{
    m_StreamStateStack.top().bIsFirstParaInSection = bIsFirst;
}
 
void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline )
{
    m_StreamStateStack.top().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_StreamStateStack.top().bIsFirstParaInSectionAfterRedline
                    : m_StreamStateStack.top().bIsFirstParaInSection)
                && !IsInShape()
                && !IsInComments()
                && !IsInFootOrEndnote();
}
 
void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst)
{
    m_StreamStateStack.top().bIsFirstParaInShape = bIsFirst;
}
 
void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded )
{
    m_StreamStateStack.top().bDummyParaAddedForTableInSection = bIsAdded;
}
 
void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted )
{
    m_StreamStateStack.top().bTextFrameInserted = bIsInserted;
}
 
void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr)
{
    m_StreamStateStack.top().bParaSectpr = bParaSectpr;
}
 
void DomainMapper_Impl::SetSdt(bool bSdt)
{
    m_StreamStateStack.top().bSdt = bSdt;
 
    if (m_StreamStateStack.top().bSdt && !m_aTextAppendStack.empty())
    {
        m_StreamStateStack.top().xSdtEntryStart = GetTopTextAppend()->getEnd();
    }
    else
    {
        m_StreamStateStack.top().xSdtEntryStart.clear();
    }
}
 
void DomainMapper_Impl::PushSdt()
{
    if (m_aTextAppendStack.empty())
    {
        return;
    }
 
    uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
    if (!xTextAppend.is())
    {
        return;
    }
 
    // This may delete text, so call it early, before we would set our start position, which may be
    // invalidated by a delete.
    MergeAtContentImageRedlineWithNext(xTextAppend);
 
    uno::Reference<text::XText> xText = xTextAppend->getText();
    if (!xText.is())
    {
        return;
    }
 
    uno::Reference<text::XTextCursor> xCursor
        = xText->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;
    try
    {
        xCursor = xText->createTextCursorByRange(xStart);
    }
    catch (const uno::RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::DomainMapper_Impl::PopSdt: createTextCursorByRange() failed");
        // We redline form controls and that gets us confused when
        // we process the SDT around the placeholder. What seems to
        // happen is we lose the text-range when we pop the SDT position.
        // Here, we reset the text-range when we fail to create the
        // cursor from the top SDT position.
        if (m_aTextAppendStack.empty())
        {
            return;
        }
 
        uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend;
        if (!xTextAppend.is())
        {
            return;
        }
 
        uno::Reference<text::XText> xText2 = xTextAppend->getText();
        if (!xText2.is())
        {
            return;
        }
 
        // Reset to the start.
        xCursor = xText2->createTextCursorByRange(xTextAppend->getStart());
    }
 
    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()
            && m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent)
        {
            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);
 
    std::optional<OUString> oData = m_pSdtHelper->getValueFromDataBinding();
    if (oData.has_value())
    {
        // Data binding has a value for us, prefer that over the in-document value.
        xCursor->setString(*oData);
 
        // Such value is always a plain text string, remove the char style of the placeholder.
        uno::Reference<beans::XPropertyState> xPropertyState(xCursor, uno::UNO_QUERY);
        if (xPropertyState.is())
        {
            xPropertyState->setPropertyToDefault(u"CharStyleName"_ustr);
        }
    }
 
    rtl::Reference<SwXContentControl> xContentControl( m_xTextDocument->createContentControl());
    if (m_pSdtHelper->GetShowingPlcHdr())
    {
        xContentControl->setPropertyValue(u"ShowingPlaceHolder"_ustr,
                                               uno::Any(m_pSdtHelper->GetShowingPlcHdr()));
    }
 
    if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty())
    {
        xContentControl->setPropertyValue(u"PlaceholderDocPart"_ustr,
                                               uno::Any(m_pSdtHelper->GetPlaceholderDocPart()));
    }
 
    if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty())
    {
        xContentControl->setPropertyValue(u"DataBindingPrefixMappings"_ustr,
                                               uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping()));
    }
    if (!m_pSdtHelper->GetDataBindingXPath().isEmpty())
    {
        xContentControl->setPropertyValue(u"DataBindingXpath"_ustr,
                                               uno::Any(m_pSdtHelper->GetDataBindingXPath()));
    }
    if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty())
    {
        xContentControl->setPropertyValue(u"DataBindingStoreItemID"_ustr,
                                               uno::Any(m_pSdtHelper->GetDataBindingStoreItemID()));
    }
 
    if (!m_pSdtHelper->GetColor().isEmpty())
    {
        xContentControl->setPropertyValue(u"Color"_ustr,
                                               uno::Any(m_pSdtHelper->GetColor()));
    }
 
    if (!m_pSdtHelper->GetAppearance().isEmpty())
    {
        xContentControl->setPropertyValue(u"Appearance"_ustr,
                                               uno::Any(m_pSdtHelper->GetAppearance()));
    }
 
    if (!m_pSdtHelper->GetAlias().isEmpty())
    {
        xContentControl->setPropertyValue(u"Alias"_ustr,
                                               uno::Any(m_pSdtHelper->GetAlias()));
    }
 
    if (!m_pSdtHelper->GetTag().isEmpty())
    {
        xContentControl->setPropertyValue(u"Tag"_ustr,
                                               uno::Any(m_pSdtHelper->GetTag()));
    }
 
    if (m_pSdtHelper->GetId())
    {
        xContentControl->setPropertyValue(u"Id"_ustr, uno::Any(m_pSdtHelper->GetId()));
    }
 
    if (m_pSdtHelper->GetTabIndex())
    {
        xContentControl->setPropertyValue(u"TabIndex"_ustr, uno::Any(m_pSdtHelper->GetTabIndex()));
    }
 
    if (!m_pSdtHelper->GetLock().isEmpty())
    {
        xContentControl->setPropertyValue(u"Lock"_ustr, uno::Any(m_pSdtHelper->GetLock()));
    }
 
    if (m_pSdtHelper->getControlType() == SdtControlType::checkBox)
    {
        xContentControl->setPropertyValue(u"Checkbox"_ustr, uno::Any(true));
 
        xContentControl->setPropertyValue(u"Checked"_ustr, uno::Any(m_pSdtHelper->GetChecked()));
 
        xContentControl->setPropertyValue(u"CheckedState"_ustr,
                                               uno::Any(m_pSdtHelper->GetCheckedState()));
 
        xContentControl->setPropertyValue(u"UncheckedState"_ustr,
                                               uno::Any(m_pSdtHelper->GetUncheckedState()));
    }
 
    if (m_pSdtHelper->getControlType() == SdtControlType::dropDown
        || m_pSdtHelper->getControlType() == SdtControlType::comboBox)
    {
        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)
            {
                pItems[i] = {
                    comphelper::makePropertyValue(u"DisplayText"_ustr, rDisplayTexts[i]),
                    comphelper::makePropertyValue(u"Value"_ustr, rValues[i])
                };
            }
            xContentControl->setPropertyValue(u"ListItems"_ustr, uno::Any(aItems));
            if (m_pSdtHelper->getControlType() == SdtControlType::dropDown)
            {
                xContentControl->setPropertyValue(u"DropDown"_ustr, uno::Any(true));
            }
            else
            {
                xContentControl->setPropertyValue(u"ComboBox"_ustr, uno::Any(true));
            }
        }
    }
 
    if (m_pSdtHelper->getControlType() == SdtControlType::picture)
    {
        xContentControl->setPropertyValue(u"Picture"_ustr, uno::Any(true));
    }
 
    bool bDateFromDataBinding = false;
    if (m_pSdtHelper->getControlType() == SdtControlType::datePicker)
    {
        xContentControl->setPropertyValue(u"Date"_ustr, uno::Any(true));
        OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear();
        xContentControl->setPropertyValue(u"DateFormat"_ustr,
                                               uno::Any(aDateFormat.replaceAll("'", "\"")));
        xContentControl->setPropertyValue(u"DateLanguage"_ustr,
                                               uno::Any(m_pSdtHelper->getLocale().makeStringAndClear()));
        OUString aCurrentDate = m_pSdtHelper->getDate().makeStringAndClear();
        if (oData.has_value())
        {
            aCurrentDate = *oData;
            bDateFromDataBinding = true;
        }
        xContentControl->setPropertyValue(u"CurrentDate"_ustr,
                                               uno::Any(aCurrentDate));
    }
 
    if (m_pSdtHelper->getControlType() == SdtControlType::plainText)
    {
        xContentControl->setPropertyValue(u"PlainText"_ustr, uno::Any(true));
    }
 
    xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true);
 
    if (bDateFromDataBinding)
    {
        OUString aDateString;
        xContentControl->getPropertyValue(u"DateString"_ustr) >>= aDateString;
        xCursor->setString(aDateString);
    }
 
    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)
    {
        // Some paragraph properties only apply at the beginning of the paragraph - apply only once.
        if (!IsFirstRun())
        {
            auto pParaContext = static_cast<ParagraphPropertyMap*>(GetTopContextOfType(eId).get());
            pParaContext->props().SetListId(-1);
            pParaContext->Erase(PROP_NUMBERING_RULES); // only true with column, not page break
            pParaContext->Erase(PROP_NUMBERING_LEVEL);
            pParaContext->Erase(PROP_NUMBERING_TYPE);
            pParaContext->Erase(PROP_START_WITH);
 
            pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(sal_uInt32(0)));
            pParaContext->Erase(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING);
            pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)));
        }
 
        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 = dynamic_cast< SectionPropertyMap* >( m_aPropertyStacks[eId].top().get() );
            assert(m_pLastSectionContext);
        }
    }
    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.
        m_StreamStateStack.top().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_StreamStateStack.top().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->m_sConvertedStyleName.isEmpty() )
        {
            if ( !m_bInStyleSheetImport )
                m_sDefaultParaStyleName = pEntry->m_sConvertedStyleName;
            return pEntry->m_sConvertedStyleName;
        }
        else
            return u"Standard"_ustr;
    }
    return m_sDefaultParaStyleName;
}
 
uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, const StyleSheetEntryPtr& pInitEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault)
{
    StyleSheetEntryPtr pEntry = pInitEntry;
    while(pEntry)
    {
        if(pEntry->m_pProperties)
        {
            std::optional<PropertyMap::Property> aProperty =
                    pEntry->m_pProperties->getProperty(eId);
            if( aProperty )
            {
                if (pIsDocDefault)
                    *pIsDocDefault = pEntry->m_pProperties->isDocDefault(eId);
 
                return aProperty->second;
            }
        }
        //search until the property is set or no parent is available
        StyleSheetEntryPtr pNewEntry = lcl_getParent(pEntry, GetStyleSheetTable());
 
        SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?");
 
        if (pEntry == pNewEntry) //fdo#49587
            break;
 
        pEntry = std::move(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, std::move(pEntry), /*bDocDefaults=*/true, /*bPara=*/true);
}
 
uno::Any DomainMapper_Impl::GetInheritedParaProperty(PropertyIds eId)
{
    StyleSheetEntryPtr pEntry;
    if ( m_bInStyleSheetImport )
        pEntry = GetStyleSheetTable()->FindStyleSheetByISTD(
            GetStyleSheetTable()->GetCurrentEntry()->m_sBaseStyleIdentifier);
    else
        pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName());
 
    const bool bCheckDocDefaults = !IsDocDefaultsImport();
    return GetPropertyFromStyleSheet(eId, std::move(pEntry), bCheckDocDefaults, /*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, std::move(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_xTextDocument );
    return m_pListTable;
}
 
 
void DomainMapper_Impl::deferBreak( BreakType deferredBreakType)
{
    assert(!m_StreamStateStack.empty());
    switch (deferredBreakType)
    {
    case LINE_BREAK:
        m_StreamStateStack.top().nLineBreaksDeferred++;
        break;
    case COLUMN_BREAK:
        m_StreamStateStack.top().bIsColumnBreakDeferred = true;
    break;
    case PAGE_BREAK:
            // See SwWW8ImplReader::HandlePageBreakChar(), page break should be
            // ignored inside tables.
            if (0 < m_StreamStateStack.top().nTableDepth)
                return;
 
            m_StreamStateStack.top().bIsPageBreakDeferred = true;
        break;
    default:
        return;
    }
}
 
bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType )
{
    assert(!m_StreamStateStack.empty());
    switch (deferredBreakType)
    {
    case LINE_BREAK:
        return 0 < m_StreamStateStack.top().nLineBreaksDeferred;
    case COLUMN_BREAK:
        return m_StreamStateStack.top().bIsColumnBreakDeferred;
    case PAGE_BREAK:
        return m_StreamStateStack.top().bIsPageBreakDeferred;
    default:
        return false;
    }
}
 
void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType)
{
    assert(!m_StreamStateStack.empty());
    switch (deferredBreakType)
    {
    case LINE_BREAK:
        assert(0 < m_StreamStateStack.top().nLineBreaksDeferred);
        m_StreamStateStack.top().nLineBreaksDeferred--;
        break;
    case COLUMN_BREAK:
        m_StreamStateStack.top().bIsColumnBreakDeferred = false;
        break;
    case PAGE_BREAK:
        m_StreamStateStack.top().bIsPageBreakDeferred = false;
        break;
    default:
        break;
    }
}
 
void DomainMapper_Impl::clearDeferredBreaks()
{
    assert(!m_StreamStateStack.empty());
    m_StreamStateStack.top().nLineBreaksDeferred = 0;
    m_StreamStateStack.top().bIsColumnBreakDeferred = false;
    m_StreamStateStack.top().bIsPageBreakDeferred = false;
}
 
void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred)
{
    m_StreamStateStack.top().bSdtEndDeferred = bSdtEndDeferred;
}
 
bool DomainMapper_Impl::isSdtEndDeferred() const
{
    return m_StreamStateStack.top().bSdtEndDeferred;
}
 
void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred)
{
    m_StreamStateStack.top().bParaSdtEndDeferred = bParaSdtEndDeferred;
}
 
bool DomainMapper_Impl::isParaSdtEndDeferred() const
{
    return m_StreamStateStack.top().bParaSdtEndDeferred;
}
 
static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties,
    uno::Reference<text::XTextRange> const& xStartTextRange,
    uno::Reference<text::XTextRange> const& xEndTextRange,
    bool bIsRTFImport)
{
    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
        };
 
        // The frame width specified does not include border spacing,
        // so the frame needs to be increased by the left/right para border spacing amount
        sal_Int32 nWidth = 0;
        sal_Int32 nIndexOfWidthProperty = -1;
        sal_Int16 nType = text::SizeType::FIX;
        for (size_t i = 0; nType == text::SizeType::FIX && i < rFrameProperties.size(); ++i)
        {
            if (rFrameProperties[i].Name == "WidthType")
                rFrameProperties[i].Value >>= nType;
            else if (rFrameProperties[i].Name == "Width")
                nIndexOfWidthProperty = i;
        }
        if (nIndexOfWidthProperty > -1 && nType == text::SizeType::FIX)
            rFrameProperties[nIndexOfWidthProperty].Value >>= nWidth;
 
        for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty)
        {
            const OUString sPropertyName = getPropertyName(aBorderProperties[nProperty]);
            beans::PropertyValue aValue;
            aValue.Name = sPropertyName;
            aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName);
            if( nProperty < 4 )
            {
                xTextRangeProperties->setPropertyValue( sPropertyName, uno::Any(table::BorderLine2()));
                if (!aValue.Value.hasValue())
                    aValue.Value <<= table::BorderLine2();
            }
            else // border spacing
            {
                sal_Int32 nDistance = 0;
                aValue.Value >>= nDistance;
 
                // left4/right5 need to be duplicated because of INVERT_BORDER_SPACING (DOCX only)
                // Do not duplicate the top6/bottom7 border spacing.
                if (nProperty > 5 || bIsRTFImport)
                    aValue.Value <<= sal_Int32(0);
 
                // frames need to be increased by the left/right para border spacing amount
                // This is needed for RTF as well, but that requires other export/import fixes.
                if (!bIsRTFImport && nProperty < 6 && nWidth && nDistance)
                {
                    nWidth += nDistance;
                    rFrameProperties[nIndexOfWidthProperty].Value <<= nWidth;
                }
            }
            if (aValue.Value.hasValue())
                rFrameProperties.push_back(aValue);
        }
    }
    catch( const uno::Exception& )
    {
    }
}
 
 
static void lcl_AddRange(
    ParagraphPropertiesPtr const & pToBeSavedProperties,
    uno::Reference< text::XTextAppend > const& xTextAppend,
    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());
}
 
 
//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;
 
std::vector<css::beans::PropertyValue>
DomainMapper_Impl::MakeFrameProperties(const ParagraphProperties& rProps)
{
    std::vector<beans::PropertyValue> aFrameProperties;
 
    try
    {
        // A paragraph's properties come from direct formatting or somewhere in the style hierarchy
        std::vector<const ParagraphProperties*> vProps;
        vProps.emplace_back(&rProps);
        sal_Int8 nSafetyLimit = 16;
        StyleSheetEntryPtr pStyle
            = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rProps.GetParaStyleName());
        while (nSafetyLimit-- && pStyle && pStyle->m_pProperties)
        {
            vProps.emplace_back(&pStyle->m_pProperties->props());
            assert(pStyle->m_sBaseStyleIdentifier != pStyle->m_sStyleName);
            pStyle = lcl_getParent(pStyle, GetStyleSheetTable());
        }
        SAL_WARN_IF(!nSafetyLimit, "writerfilter.dmapper", "Inheritance loop likely: early exit");
 
 
        sal_Int32 nWidth = -1;
        for (const auto pProp : vProps)
        {
            if (pProp->Getw() < 0)
                continue;
            nWidth = pProp->Getw();
            break;
        }
        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_WIDTH_TYPE),
                                          bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX));
 
        bool bValidH = false;
        sal_Int32 nHeight = DEFAULT_FRAME_MIN_HEIGHT;
        for (const auto pProp : vProps)
        {
            if (pProp->Geth() < 0)
                continue;
            nHeight = pProp->Geth();
            bValidH = true;
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), nHeight));
 
        sal_Int16 nhRule = -1;
        for (const auto pProp : vProps)
        {
            if (pProp->GethRule() < 0)
                continue;
            nhRule = pProp->GethRule();
            break;
        }
        if (nhRule < 0)
        {
            if (bValidH && nHeight)
            {
                // [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));
 
        bool bValidX = false;
        sal_Int32 nX = DEFAULT_VALUE;
        for (const auto pProp : vProps)
        {
            bValidX = pProp->IsxValid();
            if (!bValidX)
                continue;
            nX = pProp->Getx();
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), nX));
 
        sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
        for (const auto pProp : vProps)
        {
            if (pProp->GetxAlign() < 0)
                continue;
            nHoriOrient = pProp->GetxAlign();
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient));
 
        //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11
        sal_Int16 nHAnchor = text::RelOrientation::FRAME; // 'text'
        for (const auto pProp : vProps)
        {
            if (pProp->GethAnchor() < 0)
                continue;
            nHAnchor = pProp->GethAnchor();
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), nHAnchor));
 
        bool bValidY = false;
        sal_Int32 nY = DEFAULT_VALUE;
        for (const auto pProp : vProps)
        {
            bValidY = pProp->IsyValid();
            if (!bValidY)
                continue;
            nY = pProp->Gety();
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), nY));
 
        sal_Int16 nVertOrient = text::VertOrientation::NONE;
        // Testing indicates that yAlign should be ignored if there is any specified w:y
        if (!bValidY)
        {
            for (const auto pProp : vProps)
            {
                if (pProp->GetyAlign() < 0)
                    continue;
                nVertOrient = pProp->GetyAlign();
                break;
            }
        }
 
        // Default the anchor in case FramePr_vAnchor is missing.
        // ECMA 17.3.1.11 says "page",
        // but errata documentation MS-OE376 2.1.48 Section 2.3.1.11 says "text"
        // while actual testing usually indicates "margin" tdf#157572 tdf#112287
        sal_Int16 nVAnchor = text::RelOrientation::PAGE_PRINT_AREA; // 'margin'
        if (!nY && (bValidY || nVertOrient == text::VertOrientation::NONE))
        {
            // special cases? "auto" position defaults to "paragraph" based on testing when w:y=0
            nVAnchor = text::RelOrientation::FRAME; // 'text'
        }
        for (const auto pProp : vProps)
        {
            if (pProp->GetvAnchor() < 0)
                continue;
            nVAnchor = pProp->GetvAnchor();
            // vAlign is ignored if vAnchor is set to 'text'
            if (nVAnchor == text::RelOrientation::FRAME)
                nVertOrient = text::VertOrientation::NONE;
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), nVAnchor));
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient));
 
        text::WrapTextMode nWrap = text::WrapTextMode_NONE;
        for (const auto pProp : vProps)
        {
            if (pProp->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
                continue;
            nWrap = pProp->GetWrap();
            break;
        }
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_SURROUND), nWrap));
 
        sal_Int32 nRightDist = 0;
        sal_Int32 nLeftDist = 0;
        for (const auto pProp : vProps)
        {
            if (pProp->GethSpace() < 0)
                continue;
            nLeftDist = nRightDist = pProp->GethSpace();
            break;
        }
        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 = 0;
        sal_Int32 nTopDist = 0;
        for (const auto pProp : vProps)
        {
            if (pProp->GetvSpace() < 0)
                continue;
            nTopDist = nBottomDist = pProp->GetvSpace();
            break;
        }
        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));
    }
    catch (const uno::Exception&)
    {
    }
 
    return aFrameProperties;
}
 
void DomainMapper_Impl::CheckUnregisteredFrameConversion(bool bPreventOverlap)
{
    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;
 
    std::vector<beans::PropertyValue> aFrameProperties
        = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
 
    if (const std::optional<sal_Int16> nDirection = PopFrameDirection())
    {
        aFrameProperties.push_back(
            comphelper::makePropertyValue(getPropertyName(PROP_FRM_DIRECTION), *nDirection));
    }
 
    if (bPreventOverlap)
        aFrameProperties.push_back(comphelper::makePropertyValue(u"AllowOverlap"_ustr, uno::Any(false)));
 
    // 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(true) } }));
    aFrameProperties.push_back(comphelper::makePropertyValue(u"FrameInteropGrabBag"_ustr, aGrabBag));
 
    lcl_MoveBorderPropertiesToFrame(aFrameProperties,
                                    rAppendContext.pLastParagraphProperties->GetStartingRange(),
                                    rAppendContext.pLastParagraphProperties->GetEndingRange(),
                                    IsRTFImport());
 
    //frame conversion has to be executed after table conversion, not now
    RegisterFrameConversion(rAppendContext.pLastParagraphProperties->GetStartingRange(),
                            rAppendContext.pLastParagraphProperties->GetEndingRange(),
                            std::move(aFrameProperties));
}
 
/// 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->m_pProperties.get();
    if (!pEntryProperties)
        return -1;
 
    sal_Int32 nListId = pEntryProperties->props().GetListId();
    // The style itself has a list id.
    if (nListId >= 0)
        return nListId;
 
    const StyleSheetEntryPtr pParent = lcl_getParent(rEntry, rStyleTable);
    // 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->m_pProperties.get();
    if (!pEntryProperties)
        return -1;
 
    nListLevel = pEntryProperties->GetListLevel();
    // The style itself has a list level.
    if (nListLevel >= 0)
        return nListLevel;
 
    const StyleSheetEntryPtr pParent = lcl_getParent(pEntry, GetStyleSheetTable());
    // 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->m_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
 
    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() );
    SAL_WARN_IF(!pEntry, "writerfilter.dmapper", "no style sheet found");
    sal_Int32 nListId = pParaContext ? pParaContext->props().GetListId() : -1;
    bool isNumberingViaStyle(false);
    bool isNumberingViaRule = nListId > -1;
    if (!bRemove && pEntry && pEntry->m_pProperties && pParaContext)
    {
        if (pEntry->m_nStyleTypeCode != StyleType::STYLE_TYPE_PARA) {
            // We could not resolve paragraph style or it is not a paragraph style
            // Remove this style reference, otherwise it will cause exceptions during further
            // processing and not all paragraph styles will be initialized.
            SAL_WARN("writerfilter.dmapper", "Paragraph style is incorrect. Ignored");
            pParaContext->Erase(PROP_PARA_STYLE_NAME);
        }
 
        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.
            auto getOptProperty
                = [&pEntry, parent = lcl_getParent(pEntry, GetStyleSheetTable())](PropertyIds id, bool useParent)
            {
                auto p = pEntry->m_pProperties->getProperty(id);
                if (!p && useParent && parent && parent->m_pProperties)
                    p = parent->m_pProperties->getProperty(id);
                return p;
            };
            if (auto oProperty = getOptProperty(PROP_PARA_FIRST_LINE_INDENT, bNumberingFromBaseStyle))
                pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false);
            if (auto oProperty = getOptProperty(PROP_PARA_LEFT_MARGIN, bNumberingFromBaseStyle))
                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.
            if (auto oProperty = getOptProperty(PROP_PARA_RIGHT_MARGIN, true);
                oProperty && 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, u"FirstLineIndent"_ustr);
                const sal_Int32 nParaLeftMargin  = getNumberingProperty(nListId, nListLevel, u"IndentAt"_ustr);
                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);
 
                pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, oProperty->second, /*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, u"FirstLineIndent"_ustr);
                const sal_Int32 nParaLeftMargin  = getNumberingProperty(nListId, nListLevel, u"IndentAt"_ustr);
                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 == 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_StreamStateStack.top().bFirstParagraphInCell
                 && 0 < m_StreamStateStack.top().nTableDepth
                 && m_StreamStateStack.top().nTableDepth == m_StreamStateStack.top().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 == 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
             */
 
            // The paragraph style is vital to knowing all the frame properties.
            std::optional<PropertyMap::Property> aParaStyle
                = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME);
            if (aParaStyle)
            {
                OUString sName;
                aParaStyle->second >>= sName;
                pParaContext->props().SetParaStyleName(sName);
            }
 
            bool bIsDropCap =
                pParaContext->props().IsFrameMode() &&
                sal::static_int_cast<Id>(pParaContext->props().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->props());
                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->props().IsFrameMode() )
                        pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
                }
                else
                {
                    const bool bIsFrameMode(pParaContext->props().IsFrameMode());
                    std::vector<beans::PropertyValue> aCurrFrameProperties;
                    std::vector<beans::PropertyValue> aPrevFrameProperties;
                    if (bIsFrameMode)
                    {
                        aCurrFrameProperties = MakeFrameProperties(pParaContext->props());
                        aPrevFrameProperties
                            = MakeFrameProperties(*rAppendContext.pLastParagraphProperties);
                    }
 
                    if (bIsFrameMode && aPrevFrameProperties == aCurrFrameProperties)
                    {
                        //handles (7)
                        rAppendContext.pLastParagraphProperties->SetEndingRange(
                            rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition
                                                                : xTextAppend->getEnd());
                        bKeepLastParagraphProperties = true;
                    }
                    else
                    {
                        // handles (8)(9) and completes (6)
 
                        // RTF has an \overlap flag (which we ignore so far)
                        // but DOCX has nothing like that for framePr
                        // Always allow overlap in the RTF case - so there can be no regression.
 
                        // In MSO UI, there is no setting for AllowOverlap for this kind of frame.
                        // Although they CAN overlap with other anchored things,
                        // they do not _easily_ overlap with other framePr's,
                        // so when one frame follows another (8), don't let the first be overlapped.
                        bool bPreventOverlap = !IsRTFImport() && bIsFrameMode && !bIsDropCap;
 
                        // Preventing overlap is emulation - so deny overlap as little as possible.
                        sal_Int16 nVertOrient = text::VertOrientation::NONE;
                        sal_Int16 nVertOrientRelation = text::RelOrientation::FRAME;
                        sal_Int32 nCurrVertPos = 0;
                        sal_Int32 nPrevVertPos = 0;
                        for (size_t i = 0; bPreventOverlap && i < aCurrFrameProperties.size(); ++i)
                        {
                            if (aCurrFrameProperties[i].Name == "VertOrientRelation")
                            {
                                aCurrFrameProperties[i].Value >>= nVertOrientRelation;
                                if (nVertOrientRelation != text::RelOrientation::FRAME)
                                    bPreventOverlap = false;
                            }
                            else if (aCurrFrameProperties[i].Name == "VertOrient")
                            {
                                aCurrFrameProperties[i].Value >>= nVertOrient;
                                if (nVertOrient != text::VertOrientation::NONE)
                                    bPreventOverlap = false;
                            }
                            else if (aCurrFrameProperties[i].Name == "VertOrientPosition")
                            {
                                aCurrFrameProperties[i].Value >>= nCurrVertPos;
                                // arbitrary value. Assume it must be less than 1st line height
                                if (nCurrVertPos > 20 || nCurrVertPos < -20)
                                    bPreventOverlap = false;
                            }
                        }
                        for (size_t i = 0; bPreventOverlap && i < aPrevFrameProperties.size(); ++i)
                        {
                            if (aPrevFrameProperties[i].Name == "VertOrientRelation")
                            {
                                aPrevFrameProperties[i].Value >>= nVertOrientRelation;
                                if (nVertOrientRelation != text::RelOrientation::FRAME)
                                    bPreventOverlap = false;
                            }
                            else if (aPrevFrameProperties[i].Name == "VertOrient")
                            {
                                aPrevFrameProperties[i].Value >>= nVertOrient;
                                if (nVertOrient != text::VertOrientation::NONE)
                                    bPreventOverlap = false;
                            }
                            else if (aPrevFrameProperties[i].Name == "VertOrientPosition")
                            {
                                aPrevFrameProperties[i].Value >>= nPrevVertPos;
                                if (nPrevVertPos != nCurrVertPos)
                                    bPreventOverlap = false;
                            }
                        }
 
                        CheckUnregisteredFrameConversion(bPreventOverlap);
 
                        // If different frame properties are set on this paragraph, keep them.
                        if (!bIsDropCap && bIsFrameMode)
                        {
                            pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
                            lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
                        }
                    }
                }
            }
            else
            {
                // (1) doesn't need handling
 
                if( !bIsDropCap && pParaContext->props().IsFrameMode() )
                {
                    pToBeSavedProperties = new ParagraphProperties(pParaContext->props());
                    lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext);
                }
            }
            applyToggleAttributes(pPropertyMap); // for paragraph marker formatting
            std::vector<beans::PropertyValue> aProperties;
            if (pPropertyMap)
            {
                aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues());
 
                // 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(u"ListAutoFormat"_ustr,
                        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_StreamStateStack.top().bParaHadField
                        && !IsInComments() && !m_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(u"X"_ustr, 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_StreamStateStack.top().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();
                            try
                            {
                                uno::Reference<container::XNamed> xPreviousNumberingRules(
                                    m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"NumberingRules"_ustr),
                                    uno::UNO_QUERY_THROW);
                                aPreviousNumberingName = xPreviousNumberingRules->getName();
                            }
                            catch (const uno::Exception&)
                            {
                                TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::finishParagraph NumberingRules");
                            }
                        }
                        else if (m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName(u"NumberingStyleName"_ustr)
                                // don't update before tables
                            && (m_StreamStateStack.top().nTableDepth == 0
                                || !m_StreamStateStack.top().bFirstParagraphInCell))
                        {
                            aCurrentNumberingName = GetListStyleName(nListId);
                            m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"NumberingStyleName"_ustr) >>= aPreviousNumberingName;
                        }
 
                        // tdf#133363: remove extra auto space even for mixed list styles
                        if (!aPreviousNumberingName.isEmpty()
                            && (aCurrentNumberingName == aPreviousNumberingName
                                || !isNumberingViaRule))
                        {
                            uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq;
                            m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"ParaInteropGrabBag"_ustr) >>= aPrevPropertiesSeq;
                            const auto & rPrevProperties = aPrevPropertiesSeq;
                            bool bParaAutoBefore = m_StreamStateStack.top().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_StreamStateStack.top().bParaAutoBefore
                                && m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName(u"ParaStyleName"_ustr))
                            {
                               auto itParaStyle = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
                               {
                                   return rValue.Name == "ParaStyleName";
                               });
                               bParaAutoBefore = itParaStyle != aProperties.end() &&
                                   m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"ParaStyleName"_ustr) == 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(u"ParaTopMargin"_ustr, 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_StreamStateStack.top().xPreviousParagraph->setPropertyValue(u"ParaBottomMargin"_ustr, 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 );
                        }
                    }
 
                    // tdf#131728 insert the paragraph before the style separator to
                    // a frame to get inline paragraphs, keeping also the table of content support
                    // FIXME: overwrite the non-character styles of the paragraph style
                    // with the settings of the next paragraph style, like MSO does
                    if ( m_StreamStateStack.top().bIsInlineParagraph && !m_StreamStateStack.top().bIsPreviousInlineParagraph )
                    {
                        uno::Reference<text::XParagraphCursor> xParaCursor(
                            xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW);
                        //select paragraph
                        xParaCursor->gotoStartOfParagraph( true );
 
                        xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
 
                        uno::Reference< text::XTextRange > xRangeStart, xRangeEnd;
                        xRangeStart = xParaCursor->getStart();
                        xRangeEnd = xParaCursor->getEnd();
 
                        comphelper::SequenceAsHashMap aFrameGrabBag;
                        aFrameGrabBag[u"FrameInlineHeading"_ustr] <<= true;
 
                        std::vector<beans::PropertyValue> aFrameProperties
                        {
                            comphelper::makePropertyValue(u"TextWrap"_ustr, css::text::WrapTextMode_RIGHT),
                            comphelper::makePropertyValue(getPropertyName(PROP_POSITION_PROTECTED), true),
                            comphelper::makePropertyValue(getPropertyName(PROP_SIZE_PROTECTED), true),
                            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),
                        };
 
                        fillEmptyFrameProperties(aFrameProperties, false);
 
                        uno::Reference<text::XTextAppendAndConvert> xBodyText(xRangeStart->getText(), uno::UNO_QUERY);
 
                        uno::Reference<text::XTextContent> xFrame = xBodyText->convertToTextFrame(xRangeStart, xRangeEnd, comphelper::containerToSequence(aFrameProperties));
                        uno::Reference<beans::XPropertySet> xFrameProps(xFrame, uno::UNO_QUERY);
                        // set frame style now to avoid losing text content (convertToTextFrame() doesn't work
                        // with frame styles anchored "as character")
                        xFrameProps->setPropertyValue(u"FrameStyleName"_ustr, uno::Any(u"Inline Heading"_ustr));
                        // set anchoring because setting frame style doesn't modify the anchoring to
                        // its default "anchored as character" setting
                        xFrameProps->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER));
 
                        m_StreamStateStack.top().bIsInlineParagraph = false;
                        m_StreamStateStack.top().bIsPreviousInlineParagraph = true;
                    }
                    else
                    {
                        xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) );
 
                        if ( m_StreamStateStack.top().bIsPreviousInlineParagraph )
                        {
                            m_StreamStateStack.top().bIsInlineParagraph = false;
                            m_StreamStateStack.top().bIsPreviousInlineParagraph = false;
                        }
                    }
 
                    m_StreamStateStack.top().xPreviousParagraph.set(xTextRange, uno::UNO_QUERY);
 
                    if (m_StreamStateStack.top().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_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName(u"ListId"_ustr))
                            {
                                OUString paraId;
                                m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"ListId"_ustr) >>= paraId;
                                if (!paraId.isEmpty()) // must be on some list?
                                {
                                    OUString const listId = pAbsList->MapListId(paraId);
                                    if (listId != paraId)
                                    {
                                        m_StreamStateStack.top().xPreviousParagraph->setPropertyValue(u"ListId"_ustr, 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_StreamStateStack.top().xPreviousParagraph->setPropertyValue(u"ParaIsNumberingRestart"_ustr, uno::Any(true));
                                    m_StreamStateStack.top().xPreviousParagraph->setPropertyValue(u"NumberingStartValue"_ustr, 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();
                    }
 
                    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 (0 < m_StreamStateStack.top().nTableDepth && 0 < nMode && 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(u""_ustr);
                            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_StreamStateStack.top().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 && !IsInComments()
                    && (0 < m_StreamStateStack.top().nTableDepth
                        || !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 (0 < m_StreamStateStack.top().nTableDepth)
                    {
                        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(u"ParaTopMargin"_ustr, aMargin);
                        }
                        if ( !bBottomSet )
                        {
                            uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN);
                            if ( aMargin != uno::Any() )
                                xParaProps->setPropertyValue(u"ParaBottomMargin"_ustr, aMargin);
                        }
                        if ( !bContextSet )
                        {
                            uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN);
                            if ( aMargin != uno::Any() )
                                xParaProps->setPropertyValue(u"ParaContextMargin"_ustr, 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(u"ParaLeftMargin"_ustr, aMargin);
                            else if (isNumberingViaStyle)
                            {
                                const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), u"IndentAt"_ustr);
                                if (nParaLeftMargin != 0)
                                    xParaProps->setPropertyValue(u"ParaLeftMargin"_ustr, uno::Any(nParaLeftMargin));
                            }
                        }
                        if ( !bRightSet )
                        {
                            uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN);
                            if ( aMargin != uno::Any() )
                                xParaProps->setPropertyValue(u"ParaRightMargin"_ustr, aMargin);
                        }
                        if ( !bFirstSet )
                        {
                            uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT);
                            if ( aMargin != uno::Any() )
                                xParaProps->setPropertyValue(u"ParaFirstLineIndent"_ustr, aMargin);
                            else if (isNumberingViaStyle)
                            {
                                const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), u"FirstLineIndent"_ustr);
                                if (nFirstLineIndent != 0)
                                    xParaProps->setPropertyValue(u"ParaFirstLineIndent"_ustr, uno::Any(nFirstLineIndent));
                            }
                        }
                    }
                }
            }
            if( !bKeepLastParagraphProperties )
                rAppendContext.pLastParagraphProperties = std::move(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->props().IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) )
        SetIsPreviousParagraphFramed(true);
    else
        SetIsPreviousParagraphFramed(false);
 
    m_StreamStateStack.top().bRemoveThisParagraph = false;
    if( !IsInHeaderFooter() && !IsInShape()
        && (!pParaContext || !pParaContext->props().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_StreamStateStack.top().bParaChanged)
        {
            SetIsFirstParagraphInSectionAfterRedline(false);
            SetIsLastParagraphInSection(false);
        }
    }
    m_previousRedline.clear();
    m_StreamStateStack.top().bParaChanged = false;
 
    if (IsInComments() && pParaContext)
    {
        if (const OUString sParaId = pParaContext->props().GetParaId(); !sParaId.isEmpty())
        {
            if (const auto item = m_aCommentProps.find(sParaId); item != m_aCommentProps.end())
            {
                m_bAnnotationResolved = item->second.bDone;
                m_sAnnotationParent = item->second.sParaIdParent;
            }
            m_sAnnotationImportedParaId = sParaId;
        }
    }
 
    if (m_StreamStateStack.top().bIsFirstParaInShape)
        m_StreamStateStack.top().bIsFirstParaInShape = false;
 
    if (pParaContext)
    {
        // Reset the frame properties for the next paragraph
        pParaContext->props().ResetFrameProperties();
    }
 
    SetIsOutsideAParagraph(true);
    m_StreamStateStack.top().bParaHadField = false;
 
    // don't overwrite m_bFirstParagraphInCell in table separator nodes
    // and in text boxes anchored to the first paragraph of table cells
    if (0 < m_StreamStateStack.top().nTableDepth
        && m_StreamStateStack.top().nTableDepth == m_StreamStateStack.top().nTableCellDepth
        && !IsInShape() && !IsInComments())
    {
        m_StreamStateStack.top().bFirstParagraphInCell = false;
    }
 
    m_StreamStateStack.top().bParaAutoBefore = false;
    m_StreamStateStack.top().bParaWithInlineObject = false;
 
#ifdef DBG_UTIL
    TagLogger::getInstance().endElement();
#endif
 
}
 
// TODO this does not yet take table styles into account
void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap)
{
    std::optional<PropertyMap::Property> charStyleProperty = pPropertyMap->getProperty(PROP_CHAR_STYLE_NAME);
    if (charStyleProperty.has_value())
    {
        OUString sCharStyleName;
        charStyleProperty->second >>= sCharStyleName;
        float fCharStyleBold = css::awt::FontWeight::NORMAL;
        float fCharStyleBoldComplex = css::awt::FontWeight::NORMAL;
        css::awt::FontSlant eCharStylePosture = css::awt::FontSlant_NONE;
        css::awt::FontSlant eCharStylePostureComplex = css::awt::FontSlant_NONE;
        sal_Int16 nCharStyleCaseMap = css::style::CaseMap::NONE;
        sal_Int16 nCharStyleRelief = css::awt::FontRelief::NONE;
        bool bCharStyleContoured = false;//Outline;
        bool bCharStyleShadowed = false;
        sal_Int16 nCharStyleStrikeThrough = awt::FontStrikeout::NONE;
        bool bCharStyleHidden = false;
 
        rtl::Reference<SwXStyle> xCharStylePropertySet = GetCharacterStyles()->getCharacterStyleByName(sCharStyleName);
        xCharStylePropertySet->getToggleAttributes(
                fCharStyleBold,
                fCharStyleBoldComplex,
                eCharStylePosture,
                eCharStylePostureComplex,
                nCharStyleCaseMap,
                nCharStyleRelief,
                bCharStyleContoured,
                bCharStyleShadowed,
                nCharStyleStrikeThrough,
                bCharStyleHidden);
 
        if (fCharStyleBold > css::awt::FontWeight::NORMAL || eCharStylePosture != css::awt::FontSlant_NONE|| nCharStyleCaseMap != css::style::CaseMap::NONE ||
            nCharStyleRelief != css::awt::FontRelief::NONE || bCharStyleContoured || bCharStyleShadowed ||
            nCharStyleStrikeThrough == awt::FontStrikeout::SINGLE || bCharStyleHidden)
        {
            rtl::Reference<SwXStyle> const xParaStylePropertySet =
                GetParagraphStyles()->getParagraphStyleByName(m_StreamStateStack.top().sCurrentParaStyleName);
            float fParaStyleBold = css::awt::FontWeight::NORMAL;
            float fParaStyleBoldComplex = css::awt::FontWeight::NORMAL;
            css::awt::FontSlant eParaStylePosture = css::awt::FontSlant_NONE;
            css::awt::FontSlant eParaStylePostureComplex = css::awt::FontSlant_NONE;
            sal_Int16 nParaStyleCaseMap = css::style::CaseMap::NONE;
            sal_Int16 nParaStyleRelief = css::awt::FontRelief::NONE;
            bool bParaStyleContoured = false;
            bool bParaStyleShadowed = false;
            sal_Int16 nParaStyleStrikeThrough = awt::FontStrikeout::NONE;
            bool bParaStyleHidden = false;
            xParaStylePropertySet->getToggleAttributes(
                    fParaStyleBold,
                    fParaStyleBoldComplex,
                    eParaStylePosture,
                    eParaStylePostureComplex,
                    nParaStyleCaseMap,
                    nParaStyleRelief,
                    bParaStyleContoured,
                    bParaStyleShadowed,
                    nParaStyleStrikeThrough,
                    bParaStyleHidden);
            if (fCharStyleBold > css::awt::FontWeight::NORMAL && fParaStyleBold > css::awt::FontWeight::NORMAL)
            {
                std::optional<PropertyMap::Property> charBoldProperty = pPropertyMap->getProperty(PROP_CHAR_WEIGHT);
                if (!charBoldProperty.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_WEIGHT, uno::Any(css::awt::FontWeight::NORMAL));
                }
            }
            if (fCharStyleBoldComplex > css::awt::FontWeight::NORMAL && fParaStyleBoldComplex > css::awt::FontWeight::NORMAL)
            {
                std::optional<PropertyMap::Property> charBoldPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_WEIGHT_COMPLEX);
                if (!charBoldPropertyComplex.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_WEIGHT_COMPLEX, uno::Any(css::awt::FontWeight::NORMAL));
                    pPropertyMap->Insert(PROP_CHAR_WEIGHT_ASIAN, uno::Any(css::awt::FontWeight::NORMAL));
                }
            }
            if (eCharStylePosture != css::awt::FontSlant_NONE && eParaStylePosture != css::awt::FontSlant_NONE)
            {
                std::optional<PropertyMap::Property> charItalicProperty = pPropertyMap->getProperty(PROP_CHAR_POSTURE);
                if (!charItalicProperty.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_POSTURE, uno::Any(css::awt::FontSlant_NONE));
                }
            }
            if (eCharStylePostureComplex != css::awt::FontSlant_NONE && eParaStylePostureComplex != css::awt::FontSlant_NONE)
            {
                std::optional<PropertyMap::Property> charItalicPropertyComplex = pPropertyMap->getProperty(PROP_CHAR_POSTURE_COMPLEX);
                if (!charItalicPropertyComplex.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_POSTURE_COMPLEX, uno::Any(css::awt::FontSlant_NONE));
                    pPropertyMap->Insert(PROP_CHAR_POSTURE_ASIAN, uno::Any(css::awt::FontSlant_NONE));
                }
            }
            if (nCharStyleCaseMap == nParaStyleCaseMap && nCharStyleCaseMap != css::style::CaseMap::NONE)
            {
                std::optional<PropertyMap::Property> charCaseMap = pPropertyMap->getProperty(PROP_CHAR_CASE_MAP);
                if (!charCaseMap.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_CASE_MAP, uno::Any(css::style::CaseMap::NONE));
                }
            }
            if (nParaStyleRelief != css::awt::FontRelief::NONE && nCharStyleRelief == nParaStyleRelief)
            {
                std::optional<PropertyMap::Property> charRelief = pPropertyMap->getProperty(PROP_CHAR_RELIEF);
                if (!charRelief.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_RELIEF, uno::Any(css::awt::FontRelief::NONE));
                }
            }
            if (bParaStyleContoured && bCharStyleContoured)
            {
                std::optional<PropertyMap::Property> charContoured = pPropertyMap->getProperty(PROP_CHAR_CONTOURED);
                if (!charContoured.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_CONTOURED, uno::Any(false));
                }
            }
            if (bParaStyleShadowed && bCharStyleShadowed)
            {
                std::optional<PropertyMap::Property> charShadow = pPropertyMap->getProperty(PROP_CHAR_SHADOWED);
                if (!charShadow.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_SHADOWED, uno::Any(false));
                }
            }
            if (nParaStyleStrikeThrough == css::awt::FontStrikeout::SINGLE && nParaStyleStrikeThrough == nCharStyleStrikeThrough)
            {
                std::optional<PropertyMap::Property> charStrikeThrough = pPropertyMap->getProperty(PROP_CHAR_STRIKEOUT);
                if (!charStrikeThrough.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_STRIKEOUT, uno::Any(css::awt::FontStrikeout::NONE));
                }
            }
            if (bParaStyleHidden && bCharStyleHidden)
            {
                std::optional<PropertyMap::Property> charHidden = pPropertyMap->getProperty(PROP_CHAR_HIDDEN);
                if (!charHidden.has_value())
                {
                    pPropertyMap->Insert(PROP_CHAR_HIDDEN, uno::Any(false));
                }
            }
        }
    }
}
 
void DomainMapper_Impl::MergeAtContentImageRedlineWithNext(const css::uno::Reference<css::text::XTextAppend>& xTextAppend)
{
    // 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(u""_ustr);
                xCursor->gotoEnd(false);
                xCursor->goLeft(1, true);
                xCursor->setString(u""_ustr);
            }
        }
 
        m_bRedlineImageInPreviousRun = false;
    }
}
 
    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
        && !m_StreamStateStack.top().deferredCharacterProperties.empty()
        && (GetTopContextType() == CONTEXT_CHARACTER))
    {
        processDeferredCharacterProperties();
    }
    uno::Reference< text::XTextAppend >  xTextAppend = m_aTextAppendStack.top().xTextAppend;
    if (!xTextAppend.is() || !hasTableManager() || getTableManager().isIgnore())
        return;
 
    try
    {
        applyToggleAttributes(pPropertyMap);
        // If we are in comments, then disable CharGrabBag, comment text doesn't support that.
        uno::Sequence<beans::PropertyValue> aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!IsInComments());
 
        if (IsInTOC() || m_bStartIndex || m_bStartBibliography)
            for( auto& rValue : asNonConstRange(aValues) )
            {
                if (rValue.Name == "CharHidden")
                    rValue.Value <<= false;
            }
 
        MergeAtContentImageRedlineWithNext(xTextAppend);
 
        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 (IsInTOC() || 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);
                    }
                    if (IsInComments())
                        xTextRange = xTextAppend->finishParagraphInsert(aValues, xTOCTextCursor);
                    else
                        xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor);
                    SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed");
                    if (!xTextRange.is())
                        throw uno::Exception(u"insertTextPortion failed"_ustr, nullptr);
                    m_StreamStateStack.top().bTextInserted = true;
                    xTOCTextCursor->gotoRange(xTextRange->getEnd(), true);
                    if (m_nStartGenericField == 0)
                    {
                        m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor));
                    }
                }
            }
            else
            {
                if (IsOpenField() && GetTopFieldContext()->GetFieldId() == FIELD_HYPERLINK)
                {
                    // It is content of hyperlink field. We need to create and remember
                    // character style for later applying to hyperlink
                    PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(GetTopContext()->GetPropertyValues());
                    OUString sHyperlinkStyleName = GetStyleSheetTable()->getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false);
                    GetTopFieldContext()->SetHyperlinkStyle(sHyperlinkStyleName);
                }
 
#if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed
                sal_Int32 nPos = 0;
                OUString sFontName;
                OUString sDoubleSpace(u"  "_ustr);
                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_StreamStateStack.top().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
    {
        rtl::Reference<SwXTextEmbeddedObject> xOLE = m_xTextDocument->createTextEmbeddedObject();
 
        OUString aCLSID = pOLEHandler->getCLSID();
        if (aCLSID.isEmpty())
            xOLE->setPropertyValue(getPropertyName( PROP_STREAM_NAME ),
                            uno::Any( rStreamName ));
        else
            xOLE->setPropertyValue(u"CLSID"_ustr, uno::Any(aCLSID));
 
        OUString aDrawAspect = pOLEHandler->GetDrawAspect();
        if(!aDrawAspect.isEmpty())
            xOLE->setPropertyValue(u"DrawAspect"_ustr, uno::Any(aDrawAspect));
 
        awt::Size aSize = pOLEHandler->getSize();
        if( !aSize.Width )
            aSize.Width = 1000;
        if( !aSize.Height )
            aSize.Height = 1000;
        xOLE->setPropertyValue(getPropertyName( PROP_WIDTH ),
                        uno::Any(aSize.Width));
        xOLE->setPropertyValue(getPropertyName( PROP_HEIGHT ),
                        uno::Any(aSize.Height));
 
        OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth();
        if(!aVisAreaWidth.isEmpty())
            xOLE->setPropertyValue(u"VisibleAreaWidth"_ustr, uno::Any(aVisAreaWidth));
 
        OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight();
        if(!aVisAreaHeight.isEmpty())
            xOLE->setPropertyValue(u"VisibleAreaHeight"_ustr, uno::Any(aVisAreaHeight));
 
        uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement();
        xOLE->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(u"LineColor"_ustr) >>= aBorderProps.Color;
            xReplacementProperties->getPropertyValue(u"LineWidth"_ustr) >>= aBorderProps.LineWidth;
            xReplacementProperties->getPropertyValue(u"LineStyle"_ustr) >>= aBorderProps.LineStyle;
 
            if (aBorderProps.LineStyle) // Set line props only if LineStyle is set
            {
                xOLE->setPropertyValue(u"RightBorder"_ustr, uno::Any(aBorderProps));
                xOLE->setPropertyValue(u"TopBorder"_ustr, uno::Any(aBorderProps));
                xOLE->setPropertyValue(u"LeftBorder"_ustr, uno::Any(aBorderProps));
                xOLE->setPropertyValue(u"BottomBorder"_ustr, uno::Any(aBorderProps));
            }
            static constexpr OUString pProperties[] = {
                u"AnchorType"_ustr,
                u"Surround"_ustr,
                u"SurroundContour"_ustr,
                u"HoriOrient"_ustr,
                u"HoriOrientPosition"_ustr,
                u"VertOrient"_ustr,
                u"VertOrientPosition"_ustr,
                u"VertOrientRelation"_ustr,
                u"HoriOrientRelation"_ustr,
                u"LeftMargin"_ustr,
                u"RightMargin"_ustr,
                u"TopMargin"_ustr,
                u"BottomMargin"_ustr
            };
            for (const OUString& s : pProperties)
            {
                const uno::Any aVal = xReplacementProperties->getPropertyValue(s);
                xOLE->setPropertyValue(s, aVal);
            }
 
            if (xReplacementProperties->getPropertyValue(u"FillStyle"_ustr).get<css::drawing::FillStyle>()
                != css::drawing::FillStyle::FillStyle_NONE) // Apply fill props if style is set
            {
                xOLE->setPropertyValue(
                    u"FillStyle"_ustr, xReplacementProperties->getPropertyValue(u"FillStyle"_ustr));
                xOLE->setPropertyValue(
                    u"FillColor"_ustr, xReplacementProperties->getPropertyValue(u"FillColor"_ustr));
                xOLE->setPropertyValue(
                    u"FillColor2"_ustr, xReplacementProperties->getPropertyValue(u"FillColor2"_ustr));
            }
        }
        else
            // mimic the treatment of graphics here... it seems anchoring as character
            // gives a better ( visually ) result
            xOLE->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ),  uno::Any( text::TextContentAnchorType_AS_CHARACTER ) );
        // remove ( if valid ) associated shape ( used for graphic replacement )
        SAL_WARN_IF(m_vAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
        if (!m_vAnchoredStack.empty())
            m_vAnchoredStack.back().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
    {
        rtl::Reference<SwXTextEmbeddedObject> xStarMath = m_xTextDocument->createTextEmbeddedObject();
 
        xStarMath->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ),
            val.getAny());
        // tdf#66405: set zero margins for embedded object
        xStarMath->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ),
            uno::Any(sal_Int32(0)));
        xStarMath->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ),
            uno::Any(sal_Int32(0)));
        xStarMath->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ),
            uno::Any(sal_Int32(0)));
        xStarMath->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::FormulaImExportBase* formulaimport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
            size = formulaimport->getFormulaSize();
        xStarMath->setPropertyValue(getPropertyName( PROP_WIDTH ),
            uno::Any( sal_Int32(size.Width())));
        xStarMath->setPropertyValue(getPropertyName( PROP_HEIGHT ),
            uno::Any( sal_Int32(size.Height())));
        xStarMath->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);
}
 
static void checkAndAddPropVal(const OUString& prop, const css::uno::Any& val,
                               std::vector<OUString>& props, std::vector<css::uno::Any>& values)
{
    // Avoid well-known reasons for exceptions when setting property values
    if (!val.hasValue())
        return;
    if (prop == "CharStyleName" || prop == "DropCapCharStyleName")
        if (OUString val_string; (val >>= val_string) && val_string.isEmpty())
            return;
 
    props.push_back(prop);
    values.push_back(val);
}
 
static uno::Reference<lang::XComponent>
getParagraphOfRange(const css::uno::Reference<css::text::XTextRange>& xRange)
{
    uno::Reference<container::XEnumerationAccess> xEA{ xRange, uno::UNO_QUERY_THROW };
    return { xEA->createEnumeration()->nextElement(), uno::UNO_QUERY_THROW };
}
 
static void copyAllProps(const css::uno::Reference<css::uno::XInterface>& from,
                         const css::uno::Reference<css::uno::XInterface>& to)
{
    css::uno::Reference<css::beans::XPropertySet> xFromProps(from, css::uno::UNO_QUERY_THROW);
    css::uno::Reference<css::beans::XPropertySetInfo> xFromInfo(xFromProps->getPropertySetInfo(),
                                                                css::uno::UNO_SET_THROW);
    css::uno::Sequence<css::beans::Property> rawProps(xFromInfo->getProperties());
    std::vector<OUString> props;
    props.reserve(rawProps.getLength());
    for (const auto& prop : rawProps)
        if ((prop.Attributes & css::beans::PropertyAttribute::READONLY) == 0)
            props.push_back(prop.Name);
 
    if (css::uno::Reference<css::beans::XPropertyState> xFromState{ from, css::uno::UNO_QUERY })
    {
        const auto propsSeq = comphelper::containerToSequence(props);
        const auto statesSeq = xFromState->getPropertyStates(propsSeq);
        assert(propsSeq.getLength() == statesSeq.getLength());
        for (sal_Int32 i = 0; i < propsSeq.getLength(); ++i)
            if (statesSeq[i] != css::beans::PropertyState_DIRECT_VALUE)
                std::erase(props, propsSeq[i]);
    }
 
    std::vector<css::uno::Any> values;
    values.reserve(props.size());
    if (css::uno::Reference<css::beans::XMultiPropertySet> xFromMulti{ xFromProps,
                                                                       css::uno::UNO_QUERY })
    {
        const auto propsSeq = comphelper::containerToSequence(props);
        const auto valuesSeq = xFromMulti->getPropertyValues(propsSeq);
        assert(propsSeq.getLength() == valuesSeq.getLength());
        props.clear();
        for (size_t i = 0; i < propsSeq.size(); ++i)
            checkAndAddPropVal(propsSeq[i], valuesSeq[i], props, values);
    }
    else
    {
        std::vector<OUString> filtered_props;
        filtered_props.reserve(props.size());
        for (const auto& prop : props)
            checkAndAddPropVal(prop, xFromProps->getPropertyValue(prop), filtered_props, values);
        filtered_props.swap(props);
    }
    assert(props.size() == values.size());
 
    css::uno::Reference<css::beans::XPropertySet> xToProps(to, css::uno::UNO_QUERY_THROW);
    if (css::uno::Reference<css::beans::XMultiPropertySet> xToMulti{ xToProps,
                                                                     css::uno::UNO_QUERY })
    {
        try
        {
            xToMulti->setPropertyValues(comphelper::containerToSequence(props),
                                        comphelper::containerToSequence(values));
            return;
        }
        catch (css::uno::Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
        }
        // Fallback to property-by-property iteration
    }
 
    for (size_t i = 0; i < props.size(); ++i)
    {
        try
        {
            xToProps->setPropertyValue(props[i], values[i]);
        }
        catch (css::uno::Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
        }
    }
}
 
rtl::Reference< SwXTextSection > DomainMapper_Impl::appendTextSectionAfter(
                                    uno::Reference< text::XTextRange > const & xBefore )
{
    rtl::Reference< SwXTextSection > xSection;
    if (m_aTextAppendStack.empty())
        return xSection;
    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. The previous node may be a
            // table; then trying to go left would skip the whole table. Split the trailing
            // paragraph; let the section span over the first of the two resulting paragraphs;
            // destroy the last section's paragraph afterwards.
            xTextAppend->insertControlCharacter(
                xCursor->getEnd(), css::text::ControlCharacter::PARAGRAPH_BREAK, false);
            auto xNewPara = getParagraphOfRange(xCursor->getEnd());
            xCursor->gotoPreviousParagraph(true);
            auto xEndPara = getParagraphOfRange(xCursor->getEnd());
            // xEndPara may already have properties (like page break); make sure to apply them
            // to the newly appended paragraph, which will be kept in the end.
            copyAllProps(xEndPara, xNewPara);
 
            xSection = m_xTextDocument->createTextSection();
            xSection->attach(xCursor);
 
            // Remove the extra paragraph (last inside the section)
            xEndPara->dispose();
        }
        catch(const uno::Exception&)
        {
        }
 
    }
 
    return xSection;
}
 
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)));
}
 
bool DomainMapper_Impl::IsInTOC() const
{
    if (IsInHeaderFooter())
        return m_bStartTOCHeaderFooter;
    else
        return m_bStartTOC;
}
 
void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom)
{
    while (!m_aHeaderFooterTextAppendStack.empty())
    {
        auto& [aTextAppendContext, ePagePartType] = m_aHeaderFooterTextAppendStack.top();
        if ((ePagePartType == PagePartType::Header && !bDynamicHeightTop) || (ePagePartType == PagePartType::Footer && !bDynamicHeightBottom))
        {
            uno::Reference< text::XTextAppend > xTextAppend = aTextAppendContext.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(u"TextWrap"_ustr, 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 (ePagePartType == PagePartType::Footer)
            {
                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();
    }
}
 
namespace
{
// Determines if the XText content is empty (no text, no shapes, no tables)
bool isContentEmpty(uno::Reference<text::XText> const& xText)
{
    if (!xText.is())
        return true; // no XText means it's empty
 
    uno::Reference<beans::XPropertySet> xTextProperties(xText, uno::UNO_QUERY);
    if (!xTextProperties.is())
    {
        return true;
    }
 
    bool bContentEmpty{};
    xTextProperties->getPropertyValue("IsContentEmpty") >>= bContentEmpty;
    return bContentEmpty;
}
 
} // end anonymous namespace
 
void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageType eType)
{
    bool bHeader = ePagePartType == PagePartType::Header;
 
    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 ePropTextFirst = bHeader ? PROP_HEADER_TEXT_FIRST: PROP_FOOTER_TEXT_FIRST;
    const PropertyIds ePropTextRight = bHeader ? PROP_HEADER_TEXT: PROP_FOOTER_TEXT;
 
    m_bDiscardHeaderFooter = true;
    m_StreamStateStack.top().eSubstreamType = bHeader ? SubstreamType::Header : SubstreamType::Footer;
 
    //get the section context
    SectionPropertyMap* pSectionContext = GetSectionContext();;
    if (!pSectionContext)
        return;
 
    if (!m_bIsNewDoc)
        return; // TODO sw cannot Undo insert header/footer without crashing
 
    rtl::Reference<SwXPageStyle> xPageStyle = pSectionContext->GetPageStyle(*this);
    if (!xPageStyle.is())
        return;
 
    bool bEvenAndOdd = GetSettingsTable()->GetEvenAndOddHeaders();
 
    try
    {
        // Note that the header property calls are very expensive, hence the need to check if the property needs
        // setting before calling setPropertyValue.
 
        // Turn on the headers
        bool bPropIsOn = false;
        xPageStyle->getPropertyValue(getPropertyName(ePropIsOn)) >>= bPropIsOn;
        if (!bPropIsOn)
            xPageStyle->setPropertyValue(getPropertyName(ePropIsOn), uno::Any(true));
 
        // Set both sharing left and first to off so we can import the content regardless of what value
        // the "titlePage" or "evenAndOdd" flags are set (which decide what the sharing is set to in the document).
        bool bPropShared = false;
        xPageStyle->getPropertyValue(getPropertyName(ePropShared)) >>= bPropShared;
        if (bPropShared)
            xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false));
        bool bFirstShared = false;
        xPageStyle->getPropertyValue(getPropertyName(PROP_FIRST_IS_SHARED)) >>= bFirstShared;
        if (bFirstShared)
            xPageStyle->setPropertyValue(getPropertyName(PROP_FIRST_IS_SHARED), uno::Any(false));
 
        if (eType == PageType::LEFT)
        {
            if (bHeader)
            {
                pSectionContext->m_bLeftHeader = true;
                pSectionContext->m_bHadLeftHeader = true;
            }
            else
                pSectionContext->m_bLeftFooter = true;
 
            prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextLeft, bEvenAndOdd);
        }
        else if (eType == PageType::FIRST)
        {
            if (bHeader)
            {
                pSectionContext->m_bFirstHeader = true;
                pSectionContext->m_bHadFirstHeader = true;
            }
            else
                pSectionContext->m_bFirstFooter = true;
 
            prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextFirst, true);
        }
        else
        {
            if (bHeader)
            {
                pSectionContext->m_bRightHeader = true;
                pSectionContext->m_bHadRightHeader = true;
            }
            else
                pSectionContext->m_bRightFooter = true;
 
            prepareHeaderFooterContent(xPageStyle, ePagePartType, ePropTextRight, true);
        }
 
        m_bDiscardHeaderFooter = false; // set only on success!
    }
    catch( const uno::Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper");
    }
}
 
/** Prepares the header/footer text content by first removing the existing
 *  content and adding it to the text append stack. */
void DomainMapper_Impl::prepareHeaderFooterContent(rtl::Reference<SwXPageStyle> const& xPageStyle,
                                                   PagePartType ePagePartType, PropertyIds ePropertyID,
                                                   bool bAppendToHeaderAndFooterTextStack)
{
    uno::Reference<text::XText> xText;
    xPageStyle->getPropertyValue(getPropertyName(ePropertyID)) >>= xText;
 
    //remove the existing content first
    SectionPropertyMap::removeXTextContent(xText);
 
    auto xTextCursor = m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xText->createTextCursorByRange(xText->getStart());
    uno::Reference<text::XTextAppend> xTextAppend(xText, uno::UNO_QUERY_THROW);
    m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTextCursor));
    if (bAppendToHeaderAndFooterTextStack)
        m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(xTextAppend, xTextCursor), ePagePartType));
}
 
bool DomainMapper_Impl::SeenHeaderFooter(PagePartType const partType, PageType const pageType) const
{
    return m_HeaderFooterSeen.find({partType, pageType}) != m_HeaderFooterSeen.end();
}
 
/** Checks if the header and footer content on the text appended stack is empty.
 */
void DomainMapper_Impl::checkIfHeaderFooterIsEmpty(PagePartType ePagePartType, PageType eType)
{
    if (m_bDiscardHeaderFooter)
        return;
 
    if (m_aTextAppendStack.empty())
        return;
 
    SectionPropertyMap* pSectionContext = GetSectionContext();
    if (!pSectionContext)
        return;
 
    bool bHeader = ePagePartType == PagePartType::Header;
 
    rtl::Reference<SwXPageStyle> xPageStyle(pSectionContext->GetPageStyle(*this));
 
    if (!xPageStyle.is())
        return;
 
    bool bEmpty = isContentEmpty(m_aTextAppendStack.top().xTextAppend);
 
    if (eType == PageType::FIRST && bEmpty)
    {
        if (bHeader)
            pSectionContext->m_bFirstHeader = false;
        else
            pSectionContext->m_bFirstFooter = false;
    }
    else if (eType == PageType::LEFT && bEmpty)
    {
        if (bHeader)
            pSectionContext->m_bLeftHeader = false;
        else
            pSectionContext->m_bLeftFooter = false;
    }
    else if (eType == PageType::RIGHT && bEmpty)
    {
        if (bHeader)
            pSectionContext->m_bRightHeader = false;
        else
            pSectionContext->m_bRightFooter = false;
    }
}
 
void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType eType)
{
    //header and footer always have an empty paragraph at the end
    //this has to be removed
    RemoveLastParagraph();
 
    checkIfHeaderFooterIsEmpty(ePagePartType, eType);
 
    // clear the "Link To Previous" flag so that the header/footer
    // content is not copied from the previous section
    SectionPropertyMap* pSectionContext = GetSectionContext();
    if (pSectionContext)
    {
        pSectionContext->clearHeaderFooterLinkToPrevious(ePagePartType, eType);
        m_HeaderFooterSeen.emplace(ePagePartType, eType);
    }
 
    if (!m_aTextAppendStack.empty())
    {
        if (!m_bDiscardHeaderFooter)
        {
            m_aTextAppendStack.pop();
        }
        m_bDiscardHeaderFooter = false;
    }
}
 
void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote )
{
    SAL_WARN_IF(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote");
    m_StreamStateStack.top().eSubstreamType = bIsFootnote ? SubstreamType::Footnote : SubstreamType::Endnote;
    m_StreamStateStack.top().bCheckFirstFootnoteTab = true;
    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);
 
        rtl::Reference< SwXFootnote > xFootnote;
        if (m_xTextDocument)
        {
            if (bIsFootnote)
                xFootnote = m_xTextDocument->createFootnote();
            else
                xFootnote = m_xTextDocument->createEndnote();
        }
        pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName);
        uno::Sequence< beans::PropertyValue > aFontProperties;
        if (GetTopContextOfType(CONTEXT_CHARACTER))
            aFontProperties = GetTopContextOfType(CONTEXT_CHARACTER)->GetPropertyValues();
        appendTextContent( xFootnote, aFontProperties );
        m_aTextAppendStack.push(TextAppendContext(xFootnote,
                    xFootnote->createTextCursorByRange(xFootnote->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(std::move(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(u"illegal redline token type"_ustr, 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;
 
        sal_uInt32 nRedlineMovedID = 0;
        if (bRedlineMoved)
        {
            if (!m_sCurrentBkmkId.isEmpty())
            {
                nRedlineMovedID = 1;
                BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find(m_sCurrentBkmkId);
                if (aBookmarkIter != m_aBookmarkMap.end())
                {
                    OUString sMoveID = aBookmarkIter->second.m_sBookmarkName;
                    auto aIter = m_aRedlineMoveIDs.end();
 
                    if (sMoveID.indexOf("__RefMoveFrom__") >= 0)
                    {
                        aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
                                          sMoveID.subView(15));
                    }
                    else if (sMoveID.indexOf("__RefMoveTo__") >= 0)
                    {
                        aIter = std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(),
                                          sMoveID.subView(13));
                    };
 
                    if (aIter != m_aRedlineMoveIDs.end())
                    {
                        nRedlineMovedID = aIter - m_aRedlineMoveIDs.begin() + 2;
                        m_nLastRedlineMovedID = nRedlineMovedID;
                    }
                }
            }
            else
                nRedlineMovedID = m_nLastRedlineMovedID;
        }
        pRedlineProperties[3].Name = "RedlineMoved";
        pRedlineProperties[3].Value <<= nRedlineMovedID;
 
        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 || 0 < m_StreamStateStack.top().nTableDepth)
            eType = StoredRedlines::FRAME;
        else if (IsInFootOrEndnote())
            eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
        else
            eType = StoredRedlines::NONE;
 
        if (eType != StoredRedlines::NONE)
        {
            m_aStoredRedlines[eType].emplace_back(StoredRedline{xRange, sType, 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_StreamStateStack.top().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_StreamStateStack.top().bHasFootnoteStyle = true;
    m_StreamStateStack.top().bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty();
    m_pFootnoteContext = pContext;
}
 
void DomainMapper_Impl::EndCustomFootnote()
{
    m_StreamStateStack.top().bHasFootnoteStyle = false;
    m_StreamStateStack.top().bCheckFootnoteStyle = false;
}
 
void DomainMapper_Impl::PushAnnotation()
{
    try
    {
        m_StreamStateStack.top().eSubstreamType = SubstreamType::Annotation;
        if (!m_xTextDocument)
            return;
        m_xAnnotationField = m_xTextDocument->createFieldAnnotation();
        uno::Reference< text::XText > xAnnotationText;
        m_xAnnotationField->getPropertyValue(u"TextRange"_ustr) >>= 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,
                const std::deque<StoredRedline>& 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++)
    {
        uno::Reference< text::XTextRange > xRange = rRedlines[i].mxRange;
 
        // 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,
                const std::deque<StoredRedline>& 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++)
    {
        // skip failed createTextCursorByRange()
        if (redPos[i] == -1)
            continue;
        uno::Reference< text::XTextCursor > xCrsr = xDest->getText()->createTextCursor();
        xCrsr->goRight(redPos[i], false);
        xCrsr->goRight(redLen[i], true);
        uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW );
        try {
            xRedline->makeRedline( rRedlines[i].msType, rRedlines[i].maRedlineProperties );
        }
        catch(const uno::Exception&)
        {
            // ignore (footnotes of tracked deletions)
        }
    }
}
 
bool DomainMapper_Impl::CopyTemporaryNotes(
        rtl::Reference< SwXFootnote > xNoteSrc,
        rtl::Reference< SwXFootnote > 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 );
        xNoteDest->copyText( xNoteSrc );
 
        // copy its redlines
        std::vector<sal_Int32> redPos, redLen;
        sal_Int32 redIdx;
        enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE;
        lcl_CopyRedlines(xNoteSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx);
        lcl_PasteRedlines(xNoteDest, m_aStoredRedlines[eType], redPos, redLen, redIdx);
 
        // remove processed redlines
        for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx); i++)
            m_aStoredRedlines[eType].pop_front();
 
        return true;
    }
 
    return false;
}
 
void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes()
{
    rtl::Reference< SwXTextDocument> xTextDoc( GetTextDocument() );
    rtl::Reference< SwXFootnote > xNote;
    if  (GetFootnoteCount() > 0)
    {
        rtl::Reference<SwXFootnotes> xFootnotes = xTextDoc->getSwXFootnotes();
        if ( m_nFirstFootnoteIndex > 0 )
        {
            rtl::Reference< SwXFootnote > xFirstNote = xFootnotes->getFootnoteByIndex(0);
            xFirstNote->setString(u""_ustr);
            xNote = xFootnotes->getFootnoteByIndex(m_nFirstFootnoteIndex);
            CopyTemporaryNotes(xNote, xFirstNote);
        }
        for (sal_Int32 i = GetFootnoteCount(); i > 0; --i)
        {
            xNote = xFootnotes->getFootnoteByIndex(i);
            xNote->getAnchor()->setString(u""_ustr);
        }
    }
    if  (GetEndnoteCount() > 0)
    {
        rtl::Reference<SwXFootnotes> xEndnotes = xTextDoc->getSwXEndnotes();
        if ( m_nFirstEndnoteIndex > 0 )
        {
            rtl::Reference< SwXFootnote > xFirstNote = xEndnotes->getFootnoteByIndex(0);
            xFirstNote->setString(u""_ustr);
            xNote = xEndnotes->getFootnoteByIndex(m_nFirstEndnoteIndex);
            CopyTemporaryNotes(xNote, xFirstNote);
        }
        for (sal_Int32 i = GetEndnoteCount(); i > 0; --i)
        {
            xNote = xEndnotes->getFootnoteByIndex(i);
            xNote->getAnchor()->setString(u""_ustr);
        }
    }
}
 
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.
    bool bCopied = false;
    if ( m_xTextDocument && IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 ) ||
         ( !IsInFootnote() && GetEndnoteCount() > -1 ) ) )
    {
        rtl::Reference< SwXFootnote > xNoteFirst, xNoteLast;
        rtl::Reference<SwXFootnotes> xFootnotes = m_xTextDocument->getSwXFootnotes();
        rtl::Reference<SwXFootnotes> xEndnotes = m_xTextDocument->getSwXEndnotes();
        if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 &&
                       ( xNoteLast = xFootnotes->getFootnoteByIndex(xFootnotes->getCount()-1) ) ) ||
               ( !IsInFootnote() && xEndnotes->getCount() > 1 &&
                       ( xNoteLast = xEndnotes->getFootnoteByIndex(xEndnotes->getCount()-1) ) )
             ) && 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
                    {
                        xNoteFirst = xFootnotes->getFootnoteByIndex(m_aFootnoteIds.front());
                        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
                    {
                        xNoteFirst = xEndnotes->getFootnoteByIndex(m_aEndnoteIds.front());
                        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();
 
    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_pFootnoteContext = nullptr;
}
 
void DomainMapper_Impl::PopAnnotation()
{
    RemoveLastParagraph();
 
    m_aTextAppendStack.pop();
 
    try
    {
        if (m_bAnnotationResolved)
            m_xAnnotationField->setPropertyValue(u"Resolved"_ustr, uno::Any(true));
 
        m_xAnnotationField->setPropertyValue(u"ParaIdParent"_ustr, uno::Any(m_sAnnotationParent));
        m_xAnnotationField->setPropertyValue(u"ParaId"_ustr, uno::Any(m_sAnnotationImportedParaId));
 
        // 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;
            appendTextContent( m_xAnnotationField, aEmptyProperties );
            CheckRedline( m_xAnnotationField->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, u"x"_ustr, false);
                bMarker = true;
            }
 
            xCursor->gotoRange(aAnnotationPosition.m_xEnd, true);
 
            // MS Word can anchor a comment anywhere in the document, but LO always anchors it
            // immediately at the end of the range it spans.
            // If multiple comments have the same end-point, we need to expand the range so that
            // this later comment doesn't insert itself before any earlier ones.
            auto pSwCursor = dynamic_cast<const SwXTextCursor*>(xCursor.get());
            const SwPaM* pPaM = pSwCursor ? pSwCursor->GetPaM() : nullptr;
            SwTextNode* pTNd = pPaM ? pPaM->GetPointNode().GetTextNode() : nullptr;
            if (pTNd)
            {
                const sal_Int32 nPos = pPaM->End()->GetContentIndex();
                sal_Int32 nShift = 0;
                do
                {
                    const SwTextField* pAttr = pTNd->GetFieldTextAttrAt(
                        nPos + nShift, ::sw::GetTextAttrMode::Default);
                    auto pPostit = dynamic_cast<const SwPostItField*>(
                        pAttr ? pAttr->GetFormatField().GetField() : nullptr);
                    if (pPostit)
                        ++nShift;
                    else
                        break;
                }
                while (true);
 
                if (nShift)
                    xCursor->goRight(nShift, /*Select=*/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, m_xAnnotationField, !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_sAnnotationParent.clear();
    m_sAnnotationImportedParaId.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(u"com.sun.star.drawing.GroupShape"_ustr))
        {
            // 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(u"CharInteropGrabBag"_ustr) );
 
                    // 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[u"mso-pStyle"_ustr].hasValue();
 
                    OUString sStyleName;
                    aGrabBag[u"mso-pStyle"_ustr] >>= 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()" );
                }
            }
 
            uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
            uno::Sequence<beans::PropertyValue> aGrabBag;
            xShapePropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
 
            for (const auto& rProp : aGrabBag)
            {
                if (rProp.Name == "VML-Z-ORDER")
                {
                    sal_Int64 zOrder(0);
                    rProp.Value >>= zOrder;
 
                    text::TextContentAnchorType nAnchorType
                        = text::TextContentAnchorType_AT_PARAGRAPH;
                    xShapePropertySet->getPropertyValue(getPropertyName(PROP_ANCHOR_TYPE))
                        >>= nAnchorType;
 
                    const uno::Any aOpaque(nAnchorType == text::TextContentAnchorType_AS_CHARACTER
                                           || (zOrder >= 0 && !IsInHeaderFooter()));
                    xShapePropertySet->setPropertyValue(getPropertyName(PROP_OPAQUE), aOpaque);
                }
            }
            // 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_vAnchoredStack.push_back(AnchoredContext(xTxtContent));
        }
        else if (xSInfo->supportsService(u"com.sun.star.drawing.OLE2Shape"_ustr))
        {
            // 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_vAnchoredStack.push_back(AnchoredContext(xTextContent));
            uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
 
            rtl::Reference<SwXTextEmbeddedObject> xEmbedded = m_xTextDocument->createTextEmbeddedObject();
            m_StreamStateStack.top().xEmbedded = xEmbedded;
            xEmbedded->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT)));
            xEmbedded->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_vAnchoredStack.back().bToRemove = true;
            m_aTextAppendStack.pop();
            appendTextContent(m_StreamStateStack.top().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_vAnchoredStack.push_back(AnchoredContext(xTxtContent));
 
            uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY_THROW);
#ifdef DBG_UTIL
            TagLogger::getInstance().unoPropertySet(xShapePropertySet);
#endif
            text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH);
            xShapePropertySet->getPropertyValue(getPropertyName(PROP_ANCHOR_TYPE)) >>= nAnchorType;
            bool checkZOrderStatus = false;
            GraphicZOrderHelper& rZOrderHelper = m_rDMapper.graphicZOrderHelper();
            if (xSInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr))
            {
                SetIsTextFrameInserted(true);
                // Extract the special "btLr text frame" mode, requested by oox, if needed.
                // Extract vml ZOrder from FrameInteropGrabBag
                uno::Sequence<beans::PropertyValue> aGrabBag;
                xShapePropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag;
 
                for (const auto& rProp : aGrabBag)
                {
                    if (rProp.Name == "VML-Z-ORDER")
                    {
                        sal_Int64 zOrder(0);
                        rProp.Value >>= zOrder;
                        GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
                                                                  zOrder < 0, IsInHeaderFooter());
                        xShapePropertySet->setPropertyValue(u"ZOrder"_ustr,
                            uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
                        rZOrderHelper.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(u"FrameInteropGrabBag"_ustr,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)
                    xShapePropertySet->setPropertyValue(getPropertyName(PROP_BOTTOM_MARGIN),
                                                        aPropMargin->second);
            }
            else
            {
                uno::Sequence<beans::PropertyValue> aGrabBag;
                xShapePropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
                for (const auto& rProp : aGrabBag)
                {
                    if (rProp.Name == "VML-Z-ORDER")
                    {
                        sal_Int64 zOrder(0);
                        rProp.Value >>= zOrder;
                        GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
                                                                  zOrder < 0, IsInHeaderFooter());
                        xShapePropertySet->setPropertyValue(u"ZOrder"_ustr,
                            uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
                        rZOrderHelper.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(u"InteropGrabBag"_ustr))
                        {
                            uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({
                                { "SdtEndBefore", uno::Any(true) }
                            }));
                            xShapePropertySet->setPropertyValue(u"InteropGrabBag"_ustr,uno::Any(aShapeGrabBag));
                        }
                    }
                }
            }
            if (!IsInHeaderFooter() && !checkZOrderStatus)
                xShapePropertySet->setPropertyValue(getPropertyName(PROP_OPAQUE), uno::Any(true));
        }
        m_StreamStateStack.top().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;
 
    rtl::Reference<SwXTextEmbeddedObject> const xEmbedded(m_StreamStateStack.top().xEmbedded);
    awt::Size aSize = xShape->getSize( );
    xEmbedded->setPropertyValue(getPropertyName(PROP_WIDTH), uno::Any(sal_Int32(aSize.Width)));
    xEmbedded->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::Any(sal_Int32(aSize.Height)));
    uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY);
    // tdf#130782 copy a11y related properties
    xEmbedded->setPropertyValue(getPropertyName(PROP_DESCRIPTION),
        xShapeProps->getPropertyValue(getPropertyName(PROP_DESCRIPTION)));
    xEmbedded->setPropertyValue(getPropertyName(PROP_TITLE),
        xShapeProps->getPropertyValue(getPropertyName(PROP_TITLE)));
    uno::Reference<container::XNamed> const xShapeName(xShape, uno::UNO_QUERY);
    OUString const name(xShapeName->getName());
    if (!name.isEmpty()) // setting empty name will throw
    {
        try
        {
            xEmbedded->setName(name);
        }
        catch (uno::RuntimeException const&)
        {
            // ignore - document may contain duplicates (testchartoleobjectembeddings.docx)
        }
    }
}
 
void DomainMapper_Impl::PopShapeContext()
{
    if (hasTableManager())
    {
        getTableManager().endLevel();
        popTableManager();
    }
    if (m_vAnchoredStack.empty())
        return;
 
    // For OLE object replacement shape, the text append context was already removed
    // or the OLE object couldn't be inserted.
    if (!m_vAnchoredStack.back().bToRemove)
    {
        RemoveLastParagraph();
        if (!m_aTextAppendStack.empty())
            m_aTextAppendStack.pop();
    }
 
    uno::Reference<text::XTextContent> xObj = m_vAnchoredStack.back().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_xTextDocument && (m_vAnchoredStack.back().bToRemove || m_bDiscardHeaderFooter))
    {
        try
        {
            rtl::Reference<SwFmDrawPage> xDrawPage = m_xTextDocument->getSwDrawPage();
            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_vAnchoredStack.pop_back();
}
 
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 : aCharGrabBag)
                {
                    if(rProp.Name == "SdtEndBefore")
                    {
                        rProp.Value >>= bIsSdtEndBefore;
                    }
                }
            }
        }
    }
    return bIsSdtEndBefore;
}
 
bool DomainMapper_Impl::IsDiscardHeaderFooter() const
{
    return m_bDiscardHeaderFooter;
}
 
void DomainMapper_Impl::SetLineSpacing(const Id nName, sal_Int32 nIntValue, bool bNegativeFlip)
{
    static const int nSingleLineSpacing = 240;
 
    style::LineSpacing aSpacing;
    const PropertyMapPtr pTopContext = GetTopContext();
    std::optional<PropertyMap::Property> aLineSpacingVal;
    if (pTopContext && (aLineSpacingVal = pTopContext->getProperty(PROP_PARA_LINE_SPACING)))
        aLineSpacingVal->second >>= aSpacing;
    else
    {
        // default to exact 12pt spacing
        aSpacing.Mode = style::LineSpacingMode::FIX;
        aSpacing.Height = convertTwipToMm100(nSingleLineSpacing);
    }
    if (nName == NS_ooxml::LN_CT_Spacing_line)
    {
        appendGrabBag(m_aSubInteropGrabBag, u"line"_ustr, OUString::number(nIntValue));
        // now set the value depending on the Mode
        if (aSpacing.Mode == style::LineSpacingMode::PROP)
            aSpacing.Height = sal_Int16(round(nIntValue * 100.0 / nSingleLineSpacing));
        else
            aSpacing.Height = sal_Int16(ConversionHelper::convertTwipToMm100_Limited(nIntValue));
    }
    else // NS_ooxml::LN_CT_Spacing_lineRule:
    {
        // exactly, atLeast, auto
        if (sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_LineSpacingRule_auto)
        {
            appendGrabBag(m_aSubInteropGrabBag, u"lineRule"_ustr, u"auto"_ustr);
            if (aSpacing.Mode != style::LineSpacingMode::PROP)
            {
                if (aSpacing.Height >= 0)
                {
                    aSpacing.Mode = style::LineSpacingMode::PROP;
                    // reinterpret the already set value
                    aSpacing.Height = sal_Int16(
                        round(aSpacing.Height * 100.0
                            / ConversionHelper::convertTwipToMm100_Limited(nSingleLineSpacing)));
                }
                else
                {
                    // Negative value still means a positive height,
                    // just the mode is "exact".
                    aSpacing.Mode = style::LineSpacingMode::FIX;
                    aSpacing.Height *= -1;
                }
            }
        }
        else if (sal::static_int_cast<Id>(nIntValue)
                 == NS_ooxml::LN_Value_doc_ST_LineSpacingRule_atLeast)
        {
            appendGrabBag(m_aSubInteropGrabBag, u"lineRule"_ustr, u"atLeast"_ustr);
            if (aSpacing.Height < 0)
            {
                aSpacing.Mode = style::LineSpacingMode::FIX;
                aSpacing.Height *= -1;
            }
            else
                aSpacing.Mode = style::LineSpacingMode::MINIMUM;
        }
        else // NS_ooxml::LN_Value_doc_ST_LineSpacingRule_exact
        {
            appendGrabBag(m_aSubInteropGrabBag, u"lineRule"_ustr, u"exact"_ustr);
            if (aSpacing.Height < 0)
            {
                if (bNegativeFlip)
                    aSpacing.Mode = style::LineSpacingMode::MINIMUM;
                aSpacing.Height *= -1;
            }
            else
                aSpacing.Mode = style::LineSpacingMode::FIX;
        }
    }
    if (pTopContext)
        pTopContext->Insert(PROP_PARA_LINE_SPACING, uno::Any(aSpacing));
}
 
// 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_StreamStateStack.top().nTableDepth == (m_StreamStateStack.top().nTableCellDepth + 1))
        && m_StreamStateStack.top().xPreviousParagraph.is()
        && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing())
    {
        uno::Reference<container::XNamed> xPreviousNumberingRules(m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(u"NumberingRules"_ustr), uno::UNO_QUERY);
        if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() )
            m_StreamStateStack.top().xPreviousParagraph->setPropertyValue(u"ParaBottomMargin"_ustr, uno::Any(static_cast<sal_Int32>(0)));
    }
 
    m_StreamStateStack.top().xPreviousParagraph.clear();
 
    // next table paragraph will be first paragraph in a cell
    m_StreamStateStack.top().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(u"com.sun.star.comp.Writer.WriterFilter"_ustr);
 
        // Set the target document.
        uno::Reference<document::XImporter> xImporter(xDocxFilter, uno::UNO_QUERY);
        xImporter->setTargetDocument(static_cast<SfxBaseModel*>(m_xTextDocument.get()));
 
        // 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_StreamStateStack.top().oLineBreakClear = 1;
            break;
        case NS_ooxml::LN_Value_ST_BrClear_right:
            // SwLineBreakClear::RIGHT
            m_StreamStateStack.top().oLineBreakClear = 2;
            break;
        case NS_ooxml::LN_Value_ST_BrClear_all:
            // SwLineBreakClear::ALL
            m_StreamStateStack.top().oLineBreakClear = 3;
            break;
    }
}
 
bool DomainMapper_Impl::HasLineBreakClear() const
{
    return m_StreamStateStack.top().oLineBreakClear.has_value();
}
 
void DomainMapper_Impl::HandleLineBreak(const PropertyMapPtr& pPropertyMap)
{
    if (!m_StreamStateStack.top().oLineBreakClear.has_value())
    {
        appendTextPortion(u"\n"_ustr, pPropertyMap);
        return;
    }
 
    if (m_xTextDocument)
    {
        rtl::Reference<SwXLineBreak> xLineBreak = m_xTextDocument->createLineBreak();
        xLineBreak->setPropertyValue(u"Clear"_ustr, uno::Any(*m_StreamStateStack.top().oLineBreakClear));
        appendTextContent(xLineBreak, pPropertyMap->GetPropertyValues());
    }
    m_StreamStateStack.top().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;
    static constexpr OUString rSeparator(u"\\* "_ustr);
    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
    CHARS_CYRILLIC_UPPER_LETTER_UK
    CHARS_CYRILLIC_LOWER_LETTER_UK
    CHARS_CYRILLIC_UPPER_LETTER_N_UK
    CHARS_CYRILLIC_LOWER_LETTER_N_UK*/
 
        };
        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 "
        const sal_Int32 nQuoteIndex = rCommand.indexOf('\"');
        if (nQuoteIndex != -1)
        {
            sal_Int32 wsChars = nQuoteIndex - delimPos - 2;
            command = rCommand.replaceAt(delimPos+2, wsChars, u"");
        }
        else
        {
            // turn date \@ MM into date \@"MM"
            command = OUString::Concat(rCommand.subView(0, delimPos + 2)) + "\"" + o3tl::trim(rCommand.subView(delimPos + 2)) + "\"";
        }
        return OUString(msfilter::util::findQuotedText(command, u"\\@\"", '\"'));
    }
 
    return OUString();
}
/*-------------------------------------------------------------------------
extract a parameter (with or without quotes) between the command and the following backslash
  -----------------------------------------------------------------------*/
static OUString lcl_ExtractToken(std::u16string_view rCommand,
        size_t & rIndex, bool & rHaveToken, bool & rIsSwitch)
{
    rHaveToken = false;
    rIsSwitch = false;
 
    OUStringBuffer token;
    bool bQuoted(false);
    for (; rIndex < rCommand.size(); ++rIndex)
    {
        sal_Unicode const currentChar(rCommand[rIndex]);
        switch (currentChar)
        {
            case '\\':
            {
                if (rIndex == rCommand.size() - 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 OUString(rCommand.substr(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 u"FORMULA"_ustr;
                }
                else
                    token.append('=');
            break;
            default:
                token.append(currentChar);
            break;
        }
    }
    assert(rIndex == rCommand.size());
    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(std::u16string_view rCommand)
{
    OUString sType;
    std::vector<OUString> arguments;
    std::vector<OUString> switches;
    size_t 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.size() >= 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.size());
        static std::map<OUString, std::set<OUString>> const noArgumentSwitches = {
            { u"STYLEREF"_ustr,
              { u"\\l"_ustr, u"\\n"_ustr, u"\\p"_ustr, u"\\r"_ustr, u"\\t"_ustr, u"\\w"_ustr } }
        };
        if (bHaveToken)
        {
            if (sType.isEmpty())
            {
                sType = token.toAsciiUpperCase();
            }
            else if (bIsSwitch)
            {
                switches.push_back(token);
            }
            // evidently Word evaluates 'STYLEREF \t "Heading 1" \* MERGEFORMAT'
            // despite the grammar specifying that the style name must
            // precede switches like '\t'; try to approximate that here
            // by checking for known switches that don't expect arguments
            else if (auto const it = noArgumentSwitches.find(sType);
                !switches.empty() && (it == noArgumentSwitches.end()
                                    || it->second.find(switches.back().toAsciiLowerCase()) == it->second.end()))
            {
                switches.push_back(token);
            }
            else
            {
                arguments.push_back(token);
            }
        }
    } while (nStartIndex < rCommand.size());
 
    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 size_t nextCode(std::u16string_view rCommand, size_t pos)
{
    bool inQuotes = false;
    for (; pos < rCommand.size(); ++pos)
    {
        switch (rCommand[pos])
        {
        case '"':
            inQuotes = !inQuotes;
            break;
        case '\\':
            ++pos;
            if (!inQuotes)
                return pos;
            break;
        }
    }
    return std::u16string_view::npos;
}
 
// Returns the position of the field code
static size_t findCode(std::u16string_view rCommand, sal_Unicode cSwitch)
{
    for (size_t i = nextCode(rCommand, 0); i < rCommand.size(); i = nextCode(rCommand, i))
        if (rCommand[i] == cSwitch)
            return i;
 
    return std::u16string_view::npos;
}
 
static bool lcl_FindInCommand(
    std::u16string_view rCommand,
    sal_Unicode cSwitch,
    OUString& rValue )
{
    if (size_t i = findCode(rCommand, cSwitch); i < rCommand.size())
    {
        ++i;
        size_t next = nextCode(rCommand, i);
        if (next < rCommand.size())
            --next; // get back before the next '\\'
        rValue = o3tl::trim(rCommand.substr(i, next - i));
        return true;
    }
 
    return false;
}
 
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( static_cast<cppu::OWeakObject*>(m_xTextDocument.get()), uno::UNO_QUERY );
        if (!xNumberSupplier)
            return;
        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(u"ChainNextName"_ustr);
 
        //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(u"com.sun.star.text.TextFrame"_ustr) )
            {
                xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag;
                xPropertySet->getPropertyValue(u"LinkDisplayName"_ustr) >>= sShapeName;
            }
            else
            {
                xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
                xPropertySet->getPropertyValue(u"ChainName"_ustr) >>= sShapeName;
            }
 
            lcl_getGrabBagValue( aGrabBag, u"Txbx-Id"_ustr)  >>= aChainStruct.nId;
            lcl_getGrabBagValue( aGrabBag, u"Txbx-Seq"_ustr) >>= aChainStruct.nSeq;
            lcl_getGrabBagValue( aGrabBag, u"LinkChainName"_ustr) >>= sLinkChainName;
            lcl_getGrabBagValue( aGrabBag, u"mso-next-textbox"_ustr) >>= 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_StreamStateStack.top().bIsInTextBox)
        return;
 
    try
    {
        rtl::Reference<SwXTextFrame> xTBoxFrame(m_xTextDocument->createTextFrame());
        xTBoxFrame->setName("textbox" + OUString::number(m_xPendingTextBoxFrames.size() + 1));
        uno::Reference<text::XTextAppendAndConvert>(m_aTextAppendStack.top().xTextAppend,
            uno::UNO_QUERY_THROW)
            ->appendTextContent(static_cast<SwXFrame*>(xTBoxFrame.get()), beans::PropertyValues());
        m_xPendingTextBoxFrames.push(xTBoxFrame);
 
        m_aTextAppendStack.push(TextAppendContext(xTBoxFrame, {}));
        m_StreamStateStack.top().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_StreamStateStack.top().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_StreamStateStack.top().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(u"TextBox"_ustr).get<bool>())
        return;
 
    // if this is a textbox there must be a waiting frame
    rtl::Reference<SwXTextFrame> xTextBox = m_xPendingTextBoxFrames.front();
    if (!xTextBox)
        return;
 
    // Pop the pending frames
    m_xPendingTextBoxFrames.pop();
 
    // Attach the textbox to the shape
    try
    {
        xProps->setPropertyValue(u"TextBoxContent"_ustr, uno::Any(uno::Reference< text::XTextFrame >(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;
        if (!xTextBox->getName().isEmpty())
            sTextBoxName = xTextBox->getName();
 
        // Try to get the grabbag
        uno::Sequence<beans::PropertyValue> aOldGrabBagSeq;
        if (xProps->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
            xProps->getPropertyValue(u"InteropGrabBag"_ustr) >>= 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, u"TxbxHasLink"_ustr);
 
        // If there must be a link, do it
        if (aHasLink.hasValue() && aHasLink.get<bool>())
        {
            auto aLinkProp = comphelper::makePropertyValue(u"LinkChainName"_ustr, 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] = std::move(aLinkProp);
                bSuccess = true;
            }
        }
 
        // If the linking changed the grabbag, apply the modifications
        if (aNewGrabBagSeq.hasElements() && bSuccess)
        {
            xProps->setPropertyValue(u"InteropGrabBag"_ustr, uno::Any(aNewGrabBagSeq));
            m_vTextFramesForChaining.push_back(xShape);
        }
    }
    catch (...)
    {
        SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!");
    }
}
 
rtl::Reference<SwXFieldMaster> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName)
{
    // query master, create if not available
    if (!m_xTextDocument)
        throw uno::RuntimeException();
    rtl::Reference< SwXTextFieldMasters > xFieldMasterAccess = m_xTextDocument->getSwXTextFieldMasters();
    rtl::Reference< SwXFieldMaster > 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(rFieldMasterName);
    OUString sFieldMasterName = aFieldMasterName.makeStringAndClear();
    if(xFieldMasterAccess->hasByName(sFieldMasterName))
    {
        //get the master
        xMaster = xFieldMasterAccess->getFieldMasterByName(sFieldMasterName);
    }
    else if( m_xTextDocument )
    {
        //create the master
        xMaster = m_xTextDocument->createFieldMaster(sFieldMasterService);
        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_StreamStateStack.top().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();
}
 
 
FieldContext::FieldContext(uno::Reference< text::XTextRange > xStart)
    : m_bFieldCommandCompleted(false)
    , m_xStartRange(std::move( xStart ))
    , m_bFieldLocked( false )
    , m_bCommandType(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(u"com.sun.star.text.Fieldmark"_ustr)
            && !xServiceInfo->supportsService(u"com.sun.star.text.FormFieldmark"_ustr));
    }
#endif
    m_xTextField = xTextField;
}
 
void FieldContext::CacheVariableValue(const uno::Any& rAny)
{
        rAny >>= m_sVariableValue;
}
 
void FieldContext::AppendCommand(std::u16string_view rPart)
{
    m_sCommand[m_bCommandType] += rPart;
}
 
::std::vector<OUString> FieldContext::GetCommandParts() const
{
    ::std::vector<OUString> aResult;
    sal_Int32 nIndex = 0;
    bool bInString = false;
    bool bScreenTip = false;
    OUString sPart;
    while (nIndex != -1)
    {
        OUString sToken = GetCommand().getToken(0, ' ', nIndex);
        bool bInStringNext = bInString;
 
        if (sToken.isEmpty())
            continue;
 
        if (bScreenTip)
        {
            bool bRemoveQuotation = true;
            bInStringNext = (nIndex != -1) ? true : false;
 
            if (sToken[0] == '"' && !bInString)
                sToken = sToken.copy(1);
 
            if (!sToken.isEmpty())
            {
                if (sToken[0] == '\\')
                {
                    bRemoveQuotation = false;
                    OUStringBuffer sBuffer;
                    for (sal_Int32 i = 0; i < sToken.getLength(); ++i)
                    {
                        if (sToken[i] != '\\')
                        {
                            sBuffer.append(sToken[i]);
                        }
                    }
                    sToken = sBuffer.makeStringAndClear();
                }
            }
 
            if (!bInStringNext && bRemoveQuotation)
                sToken = sToken.copy(0, sToken.getLength() - 1);
        }
        else
        {
            if (sToken[0] == '"')
            {
                bInStringNext = true;
                sToken = sToken.copy(1);
            }
            if (sToken.endsWith("\""))
            {
                bInStringNext = false;
                sToken = sToken.copy(0, sToken.getLength() - 1);
            }
        }
 
        if (sToken == "\\o")
            bScreenTip = true;
 
        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 )
    {
        // Set command line type: normal or deleted
        pContext->SetCommandType(m_bTextDeleted);
        pContext->AppendCommand( rPartOfCommand );
    }
}
 
 
typedef std::multimap < sal_Int32, OUString > TOCStyleMap;
 
 
static ww::eField GetWW8FieldId(OUString const& rType)
{
    static const 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       }},
        {u"ASK"_ustr,             {"SetExpression",           FIELD_ASK           }},
        {u"AUTONUM"_ustr,         {"SetExpression",           FIELD_AUTONUM       }},
        {u"AUTONUMLGL"_ustr,      {"SetExpression",           FIELD_AUTONUMLGL    }},
        {u"AUTONUMOUT"_ustr,      {"SetExpression",           FIELD_AUTONUMOUT    }},
        {u"AUTHOR"_ustr,          {"DocInfo.CreateAuthor",    FIELD_AUTHOR        }},
        {u"DATE"_ustr,            {"DateTime",                FIELD_DATE          }},
        {u"COMMENTS"_ustr,        {"DocInfo.Description",     FIELD_COMMENTS      }},
        {u"CREATEDATE"_ustr,      {"DocInfo.CreateDateTime",  FIELD_CREATEDATE    }},
        {u"DOCPROPERTY"_ustr,     {"",                        FIELD_DOCPROPERTY   }},
        {u"DOCVARIABLE"_ustr,     {"User",                    FIELD_DOCVARIABLE   }},
        {u"EDITTIME"_ustr,        {"DocInfo.EditTime",        FIELD_EDITTIME      }},
        {u"EQ"_ustr,              {"",                        FIELD_EQ            }},
        {u"FILLIN"_ustr,          {"Input",                   FIELD_FILLIN        }},
        {u"FILENAME"_ustr,        {"FileName",                FIELD_FILENAME      }},
//      {"FILESIZE",        {"",                        FIELD_FILESIZE      }},
        {u"FORMULA"_ustr,         {"TableFormula",            FIELD_FORMULA       }},
        {u"FORMCHECKBOX"_ustr,    {"",                        FIELD_FORMCHECKBOX  }},
        {u"FORMDROPDOWN"_ustr,    {"DropDown",                FIELD_FORMDROPDOWN  }},
        {u"FORMTEXT"_ustr,        {"Input",                   FIELD_FORMTEXT      }},
        {u"GOTOBUTTON"_ustr,      {"",                        FIELD_GOTOBUTTON    }},
        {u"HYPERLINK"_ustr,       {"",                        FIELD_HYPERLINK     }},
        {u"IF"_ustr,              {"ConditionalText",         FIELD_IF            }},
//      {"INFO",            {"",                        FIELD_INFO          }},
        {u"INCLUDEPICTURE"_ustr,  {"",                        FIELD_INCLUDEPICTURE}},
        {u"KEYWORDS"_ustr,        {"DocInfo.KeyWords",        FIELD_KEYWORDS      }},
        {u"LASTSAVEDBY"_ustr,     {"DocInfo.ChangeAuthor",    FIELD_LASTSAVEDBY   }},
        {u"MACROBUTTON"_ustr,     {"Macro",                   FIELD_MACROBUTTON   }},
        {u"MERGEFIELD"_ustr,      {"Database",                FIELD_MERGEFIELD    }},
        {u"MERGEREC"_ustr,        {"DatabaseNumberOfSet",     FIELD_MERGEREC      }},
//      {"MERGESEQ",        {"",                        FIELD_MERGESEQ      }},
        {u"NEXT"_ustr,            {"DatabaseNextSet",         FIELD_NEXT          }},
        {u"NEXTIF"_ustr,          {"DatabaseNextSet",         FIELD_NEXTIF        }},
        {u"PAGE"_ustr,            {"PageNumber",              FIELD_PAGE          }},
        {u"PAGEREF"_ustr,         {"GetReference",            FIELD_PAGEREF       }},
        {u"PRINTDATE"_ustr,       {"DocInfo.PrintDateTime",   FIELD_PRINTDATE     }},
        {u"REF"_ustr,             {"GetReference",            FIELD_REF           }},
        {u"REVNUM"_ustr,          {"DocInfo.Revision",        FIELD_REVNUM        }},
        {u"SAVEDATE"_ustr,        {"DocInfo.ChangeDateTime",  FIELD_SAVEDATE      }},
//      {"SECTION",         {"",                        FIELD_SECTION       }},
//      {"SECTIONPAGES",    {"",                        FIELD_SECTIONPAGES  }},
        {u"SEQ"_ustr,             {"SetExpression",           FIELD_SEQ           }},
        {u"SET"_ustr,             {"SetExpression",           FIELD_SET           }},
//      {"SKIPIF",          {"",                        FIELD_SKIPIF        }},
        {u"STYLEREF"_ustr,        {"GetReference",            FIELD_STYLEREF      }},
        {u"SUBJECT"_ustr,         {"DocInfo.Subject",         FIELD_SUBJECT       }},
        {u"SYMBOL"_ustr,          {"",                        FIELD_SYMBOL        }},
        {u"TEMPLATE"_ustr,        {"TemplateName",            FIELD_TEMPLATE      }},
        {u"TIME"_ustr,            {"DateTime",                FIELD_TIME          }},
        {u"TITLE"_ustr,           {"DocInfo.Title",           FIELD_TITLE         }},
        {u"USERINITIALS"_ustr,    {"Author",                  FIELD_USERINITIALS  }},
//      {"USERADDRESS",     {"",                        FIELD_USERADDRESS   }},
        {u"USERNAME"_ustr,        {"Author",                  FIELD_USERNAME      }},
 
 
        {u"TOC"_ustr,         {"com.sun.star.text.ContentIndex",          FIELD_TOC           }},
        {u"TC"_ustr,          {"com.sun.star.text.ContentIndexMark",      FIELD_TC            }},
        {u"NUMCHARS"_ustr,    {"CharacterCount",                          FIELD_NUMCHARS      }},
        {u"NUMWORDS"_ustr,    {"WordCount",                               FIELD_NUMWORDS      }},
        {u"NUMPAGES"_ustr,    {"PageCount",                               FIELD_NUMPAGES      }},
        {u"INDEX"_ustr,       {"com.sun.star.text.DocumentIndex",         FIELD_INDEX         }},
        {u"XE"_ustr,          {"com.sun.star.text.DocumentIndexMark",     FIELD_XE            }},
        {u"BIBLIOGRAPHY"_ustr,{"com.sun.star.text.Bibliography",          FIELD_BIBLIOGRAPHY  }},
        {u"CITATION"_ustr,    {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION      }},
    };
 
    return aFieldConversionMap;
}
 
static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion()
{
    static const FieldConversionMap_t aEnhancedFieldConversionMap =
    {
        {u"FORMCHECKBOX"_ustr, {"FormFieldmark", FIELD_FORMCHECKBOX}},
        {u"FORMDROPDOWN"_ustr, {"FormFieldmark", FIELD_FORMDROPDOWN}},
        {u"FORMTEXT"_ustr,     {"Fieldmark",     FIELD_FORMTEXT}},
    };
 
    return aEnhancedFieldConversionMap;
}
 
void DomainMapper_Impl::handleFieldSet
    (const FieldContextPtr& pContext,
     rtl::Reference< SwXTextField > const & xFieldInterface)
{
    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
    rtl::Reference<SwXFieldMaster> 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
    xFieldInterface->attachTextFieldMaster( xMaster );
 
    uno::Any aAnyHint(sHint);
    xFieldInterface->setPropertyValue(getPropertyName(PROP_HINT), aAnyHint);
    xFieldInterface->setPropertyValue(getPropertyName(PROP_CONTENT), aAnyHint);
    xFieldInterface->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
 
    // Mimic MS Word behavior (hide the SET)
    xFieldInterface->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false));
}
 
void DomainMapper_Impl::handleFieldAsk
    (const FieldContextPtr& pContext,
     rtl::Reference< SwXTextField > & xFieldInterface)
{
    //doesn the command contain a variable name?
    OUString sVariable, sHint;
 
    sVariable = lcl_ExtractVariableAndHint( pContext->GetCommand(),
        sHint );
    if(!sVariable.isEmpty())
    {
        // determine field master name
        rtl::Reference<SwXFieldMaster> 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
        xFieldInterface->attachTextFieldMaster( xMaster );
 
        // set input flag at the field
        xFieldInterface->setPropertyValue(
            getPropertyName(PROP_IS_INPUT), uno::Any( true ));
        // set the prompt
        xFieldInterface->setPropertyValue(
            getPropertyName(PROP_HINT),
            uno::Any( sHint ));
        xFieldInterface->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING));
        // The ASK has no field value to display
        xFieldInterface->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(u"IsShowFormula"_ustr, 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,
    rtl::Reference< SwXTextField > const & xFieldInterface)
{
    //create a sequence field master "AutoNr"
    rtl::Reference<SwXFieldMaster> xMaster = FindOrCreateFieldMaster(
        "com.sun.star.text.FieldMaster.SetExpression",
        u"AutoNr"_ustr);
 
    xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE),
        uno::Any(text::SetVariableType::SEQUENCE));
 
    //apply the numbering type
    xFieldInterface->setPropertyValue(
        getPropertyName(PROP_NUMBERING_TYPE),
        uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
        // attach the master to the field
    xFieldInterface->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,
    rtl::Reference< SwXTextField > & 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::XDocumentProperties> xDocumentProperties = m_xTextDocument->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(u"com.sun.star.text.TextField."_ustr);
    bool bIsCustomField = false;
    if(sFieldServiceName.isEmpty())
    {
        //create a custom property field
        sServiceName += "DocInfo.Custom";
        bIsCustomField = true;
    }
    else
    {
        sServiceName += sFieldServiceName;
    }
    if (m_xTextDocument)
        xFieldInterface = m_xTextDocument->createTextField(sServiceName);
    if( bIsCustomField )
    {
        xFieldInterface->setPropertyValue(
            getPropertyName(PROP_NAME), uno::Any(rFirstParam));
        pContext->SetCustomField( xFieldInterface );
    }
    else
    {
        if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC))
            xFieldInterface->setPropertyValue(
                getPropertyName(PROP_NUMBERING_TYPE),
                uno::Any( style::NumberingType::ARABIC ));
        else if(0 != (aDocProperties[nMap].nFlags & SET_DATE))
        {
            xFieldInterface->setPropertyValue(
                getPropertyName(PROP_IS_DATE),
                    uno::Any( true ));
            SetNumberFormat( pContext->GetCommand(), xFieldInterface );
        }
    }
}
 
static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator,
                                   const uno::Sequence< beans::PropertyValues >& aLevel, const std::optional<style::TabStop> numtab,
                                   bool bSkipPageNumberAndTab)
{
    //create a copy of the level and add new entries
 
    std::vector<css::beans::PropertyValues> aNewLevel;
    aNewLevel.reserve(aLevel.getLength() + 5); // at most 5 added items
 
    static constexpr OUString tokType(u"TokenType"_ustr);
    static constexpr OUString tokHStart(u"TokenHyperlinkStart"_ustr);
    static constexpr OUString tokHEnd(u"TokenHyperlinkEnd"_ustr);
    static constexpr OUString tokPNum(u"TokenPageNumber"_ustr);
    static constexpr OUString tokENum(u"TokenEntryNumber"_ustr);
 
    if (bHyperlinks)
        aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHStart) });
 
    for (const auto& item : aLevel)
    {
        OUString tokenType;
        if (auto it = std::find_if(item.begin(), item.end(),
                                   [](const auto& p) { return p.Name == tokType; });
            it != item.end())
            it->Value >>= tokenType;
 
        if (bHyperlinks && (tokenType == tokHStart || tokenType == tokHEnd))
            continue; // We add hyperlink ourselves, so just skip existing hyperlink start / end
 
        if (!sChapterNoSeparator.isEmpty() && tokenType == tokPNum)
        {
            // This is an existing page number token; insert the chapter and separator before it
            aNewLevel.push_back(
                { comphelper::makePropertyValue(tokType, u"TokenChapterInfo"_ustr),
                  comphelper::makePropertyValue(u"ChapterFormat"_ustr, text::ChapterFormat::NUMBER) });
            aNewLevel.push_back({ comphelper::makePropertyValue(tokType, u"TokenText"_ustr),
                                  comphelper::makePropertyValue(u"Text"_ustr, sChapterNoSeparator) });
        }
 
        if (bSkipPageNumberAndTab && tokenType == tokPNum)
        {
            // also skip the preceding tabstop
            if (aNewLevel.size())
            {
                OUString aPrevTokenType;
                const auto& rPrevLevel = aNewLevel.back();
                auto it = std::find_if(rPrevLevel.begin(), rPrevLevel.end(),
                              [](const auto& p) { return p.Name == tokType; });
                if (it != rPrevLevel.end())
                    it->Value >>= aPrevTokenType;
                if (aPrevTokenType == u"TokenTabStop"_ustr)
                    aNewLevel.pop_back();
            }
        }
        else
            aNewLevel.push_back(item);
 
        if (numtab && tokenType == tokENum)
        {
            // There is a fixed tab stop position needed in the level after the numbering
            aNewLevel.push_back(
                { comphelper::makePropertyValue(tokType, u"TokenTabStop"_ustr),
                  comphelper::makePropertyValue(u"TabStopPosition"_ustr, numtab->Position) });
        }
    }
 
    if (bHyperlinks)
        aNewLevel.push_back({ comphelper::makePropertyValue(tokType, tokHEnd) });
 
    return comphelper::containerToSequence(aNewLevel);
}
 
/// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame
OUString DomainMapper_Impl::extractTocTitle()
{
    if (!m_StreamStateStack.top().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> const xCursor(
            xTextAppend->createTextCursorByRange(m_StreamStateStack.top().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();
}
 
rtl::Reference<SwXSection>
DomainMapper_Impl::StartIndexSectionChecked(std::u16string_view sServiceName)
{
    if (m_StreamStateStack.top().bParaChanged)
    {
        finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets 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 bParaChanged)
        m_StreamStateStack.top().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;
}
 
/**
 This is a heuristic to find Word's w:styleId value from localised style name.
 It's not clear how exactly it works, but apparently Word stores into
 w:styleId some filtered representation of the localised style name.
 Tragically there are references to the localised style name itself in TOC
 fields.
 Hopefully this works and a complete map of >100 built-in style names
 localised to all languages isn't needed.
*/
static auto FilterChars(std::u16string_view const& rStyleName) -> OUString
{
    return msfilter::util::CreateDOCXStyleId(rStyleName);
}
 
static OUString UnquoteFieldText(std::u16string_view s)
{
    OUStringBuffer result(s.size());
    for (size_t i = 0; i < s.size(); ++i)
    {
        switch (s[i])
        {
        case '"':
            continue;
        case '\\':
            if (i < s.size() - 1)
                ++i;
            [[fallthrough]];
        default:
            result.append(s[i]);
        }
    }
    return result.makeStringAndClear();
}
 
OUString DomainMapper_Impl::ConvertTOCStyleName(OUString const& rTOCStyleName)
{
    assert(!rTOCStyleName.isEmpty());
    if (auto const pStyle = GetStyleSheetTable()->FindStyleSheetByISTD(FilterChars(rTOCStyleName)))
    {
        auto const [convertedStyleName, isBuiltIn] = StyleSheetTable::ConvertStyleName(pStyle->m_sStyleName);
        if (isBuiltIn && m_bIsNewDoc)
        {   // practical case: Word wrote i18n name to TOC field, but it doesn't
            // exist in styles.xml; tdf#153083 clone it for best roundtrip
            assert(convertedStyleName == pStyle->m_sConvertedStyleName);
            if (rTOCStyleName != pStyle->m_sStyleName)
            {
                // rTOCStyleName is localized, pStyle->m_sStyleName is not. They don't match, so
                // make sense to clone the style.
                return GetStyleSheetTable()->CloneTOCStyle(GetFontTable(), pStyle, rTOCStyleName);
            }
        }
    }
    // theoretical case: what OOXML says
    return StyleSheetTable::ConvertStyleName(rTOCStyleName).first;
}
 
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;
    // some levels (optionally specified via a single range) might not display the page number
    sal_uInt8 nStartNoPageNumber = 0;
    sal_uInt8 nEndNoPageNumber = 0;
 
 
    sal_Int16 nMaxLevel = 10;
    OUString sTemplate;
    OUString sChapterNoSeparator;
    OUString sFigureSequence;
    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 = UnquoteFieldText(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))
    {
        // skip the tabstop and page-number on the specified levels
        sValue = sValue.replaceAll("\"", "").trim();
        if (sValue.isEmpty())
        {
            nStartNoPageNumber = 1;
            nEndNoPageNumber = WW_OUTLINE_MAX;
        }
        else
        {
            // valid command format is fairly strict, requiring # <dash> #: TOC \n "2-3"
            sal_Int32 nIndex = 0;
            o3tl::getToken(sValue, 0, '-', nIndex);
            if (nIndex > 1)
            {
                const sal_Int32 nStartLevel = o3tl::toInt32(sValue.subView(0, nIndex - 1));
                const sal_Int32 nEndLevel = o3tl::toInt32(sValue.subView(nIndex));
                if (nStartLevel > 0 && nStartLevel <= nEndLevel && nEndLevel <= WW_OUTLINE_MAX)
                {
                    nStartNoPageNumber = static_cast<sal_uInt8>(nStartLevel);
                    nEndNoPageNumber = static_cast<sal_uInt8>(nEndLevel);
                }
            }
        }
    }
//                  \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();
 
    rtl::Reference<SwXSection> xTOC;
 
    if (m_xTextDocument && ! m_aTextAppendStack.empty())
    {
        const auto& xTextAppend = GetTopTextAppend();
        if (aTocTitle.isEmpty() || bTableOfFigures)
        {
            // reset marker of the TOC title
            m_StreamStateStack.top().xSdtEntryStart.clear();
 
            // Create section before setting m_bStartTOC: finishing paragraph
            // inside StartIndexSectionChecked could do the wrong thing otherwise
            xTOC = StartIndexSectionChecked(bTableOfFigures ? u"com.sun.star.text.IllustrationsIndex"_ustr
                                                            : sTOCServiceName);
 
            const auto xTextCursor = xTextAppend->getText()->createTextCursor();
            if (xTextCursor)
                xTextCursor->gotoEnd(false);
            m_xTOCMarkerCursor = xTextCursor;
        }
        else
        {
            // create TOC section
            css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd();
            xTOC = createSectionForRange(m_StreamStateStack.top().xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false);
 
            // init [xTOCMarkerCursor]
            uno::Reference< text::XText > xText = xTextAppend->getText();
            m_xTOCMarkerCursor = xText->createTextCursor();
 
            // create header of the TOC with the TOC title inside
            createSectionForRange(m_StreamStateStack.top().xSdtEntryStart, xTextRangeEndOfTocHeader, u"com.sun.star.text.IndexHeaderSection", true);
        }
    }
 
    m_bStartTOC = true;
    pContext->SetTOC(xTOC);
    m_StreamStateStack.top().bParaHadField = false;
 
    if (!xTOC)
        return;
 
    xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(aTocTitle));
 
    if (!aBookmarkName.isEmpty())
        xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::Any(aBookmarkName));
    if (!bTableOfFigures)
    {
        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;
            auto const tsep(sTemplate.indexOf(',') != -1 ? ',' : ';');
            while( nPosition >= 0)
            {
                OUString sStyleName = sTemplate.getToken(0, tsep, nPosition);
                                //empty tokens should be skipped
                while( sStyleName.isEmpty() && nPosition > 0 )
                    sStyleName = sTemplate.getToken(0, tsep, nPosition);
                nLevel = o3tl::toInt32(o3tl::getToken(sTemplate, 0, tsep, nPosition ));
                if( !nLevel )
                    nLevel = 1;
 
                // The separator can be ',' or ', ': make sure the leading space doesn't end up in
                // the style name.
                sStyleName = sStyleName.trim();
 
                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) )
                    {
                        // tdf#153083 must map w:styleId to w:name
                        rStyle = ConvertTOCStyleName(aTOCStyleIter->second);
                        ++aTOCStyleIter;
                    }
                    xParaStyles->replaceByIndex(nLevel - 1, uno::Any(aStyles));
                }
            }
            xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true ));
 
        }
 
        uno::Reference<container::XIndexAccess> xChapterNumberingRules;
        if (m_xTextDocument)
            xChapterNumberingRules = m_xTextDocument->getChapterNumberingRules();
        rtl::Reference<SwXStyleFamily> xStyles;
        if (m_xTextDocument)
        {
            rtl::Reference<SwXStyleFamilies> xStyleFamilies = m_xTextDocument->getSwStyleFamilies();
            xStyles = xStyleFamilies->GetParagraphStyles();
        }
 
        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;
 
            // Get the tab stops coming from the styles; store to the level definitions
            std::optional<style::TabStop> numTab;
            if (xChapterNumberingRules && xStyles)
            {
                // This relies on the chapter numbering rules already defined
                // (see ListDef::CreateNumberingRules)
                uno::Sequence<beans::PropertyValue> props;
                xChapterNumberingRules->getByIndex(nLevel - 1) >>= props;
                bool bHasNumbering = false;
                bool bUseTabStop = false;
                for (const auto& propval : props)
                {
                    // We rely on PositionAndSpaceMode being always equal to LABEL_ALIGNMENT,
                    // because ListDef::CreateNumberingRules doesn't create legacy lists
                    if (propval.Name == "NumberingType")
                        bHasNumbering = propval.Value != style::NumberingType::NUMBER_NONE;
                    else if (propval.Name == "LabelFollowedBy")
                        bUseTabStop = propval.Value == text::LabelFollow::LISTTAB;
                    // Do not use FirstLineIndent property from the rules, because it is unreliable
                }
                if (bHasNumbering && bUseTabStop)
                {
                    OUString style;
                    xTOC->getPropertyValue("ParaStyleLevel" + OUString::number(nLevel)) >>= style;
                    rtl::Reference<SwXBaseStyle> xStyle = xStyles->getStyleByName(style);
                    if (xStyle)
                    {
                        if (uno::Reference<beans::XPropertyState> xPropState{ static_cast<cppu::OWeakObject*>(xStyle.get()),
                                                                              uno::UNO_QUERY })
                        {
                            if (xPropState->getPropertyState(u"ParaTabStops"_ustr)
                                == beans::PropertyState_DIRECT_VALUE)
                            {
                                if (uno::Sequence<style::TabStop> tabStops;
                                    xStyle->getPropertyValue(u"ParaTabStops"_ustr) >>= tabStops)
                                {
                                    // If the style only has one tab stop, Word uses it for
                                    // page number, and generates the other from defaults
                                    if (tabStops.getLength() > 1)
                                        numTab = tabStops[0];
                                }
                            }
                        }
                    }
                    if (!numTab)
                    {
                        // Generate the default position.
                        // Word uses multiples of 440 twips for default chapter number tab stops
                        numTab.emplace();
                        numTab->Position
                            = o3tl::convert(440 * nLevel, o3tl::Length::twip, o3tl::Length::mm100);
                    }
                }
            }
            bool bSkipPageNumberAndTab = nLevel >= nStartNoPageNumber && nLevel <= nEndNoPageNumber;
            uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks(
                                                bHyperlinks, sChapterNoSeparator,
                                                aLevel, numTab, bSkipPageNumberAndTab);
            xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) );
        }
    }
    else // if (bTableOfFigures)
    {
        if (!sFigureSequence.isEmpty())
            xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY),
                                   uno::Any(sFigureSequence));
 
        if (!sTemplate.isEmpty())
        {
            OUString const sConvertedStyleName(ConvertTOCStyleName(sTemplate));
            xTOC->setPropertyValue(u"CreateFromParagraphStyle"_ustr, uno::Any(sConvertedStyleName));
        }
 
        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, {}, /*SkipPageNumberAndTab=*/false);
            xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) );
        }
    }
}
 
rtl::Reference<SwXSection> DomainMapper_Impl::createSectionForRange(
    uno::Reference< css::text::XTextRange > xStart,
    uno::Reference< css::text::XTextRange > xEnd,
    std::u16string_view sObjectType,
    bool stepLeft)
{
    if (!xStart.is())
        return {};
    if (!xEnd.is())
        return {};
 
    if (m_aTextAppendStack.empty())
        return {};
    uno::Reference< text::XTextAppend >  xTextAppend = m_aTextAppendStack.top().xTextAppend;
    if(!xTextAppend)
        return {};
    rtl::Reference< SwXSection > xSection;
    try
    {
        uno::Reference< text::XParagraphCursor > xCursor(
            xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY );
        if(!xCursor)
            return {};
        //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);
        xSection = m_xTextDocument->createSection(sObjectType);
        if (!xSection)
            return {};
        xSection->attach( xCursor );
    }
    catch(const uno::Exception&)
    {
    }
    return xSection;
}
 
void DomainMapper_Impl::handleBibliography
    (const FieldContextPtr& pContext,
    std::u16string_view 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_StreamStateStack.top().bParaHadField = false;
 
    appendTextContent(xTOC, 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
            : u"com.sun.star.text.UserIndex"_ustr);
 
    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(u"IsCommaSeparated"_ustr, uno::Any(true));
        }
        if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue ))
        {
            xTOC->setPropertyValue(u"UseAlphabeticalSeparators"_ustr, uno::Any(true));
        }
        if( !sUserIndex.isEmpty() )
        {
            xTOC->setPropertyValue(u"UserIndexName"_ustr, uno::Any(sUserIndex));
        }
    }
    pContext->SetTOC( xTOC );
    m_StreamStateStack.top().bParaHadField = false;
 
    appendTextContent(xTOC, uno::Sequence< beans::PropertyValue >() );
 
    if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue ))
    {
        sValue = sValue.replaceAll("\"", "");
        uno::Reference<text::XTextColumns> xTextColumns;
        if (xTOC.is())
        {
            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,
        rtl::Reference<SwXFieldmark> const& xFormField,
        uno::Reference<text::XTextRange> const& xStartRange,
        std::optional<FieldId> const oFieldId) -> void
{
    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, static_cast<SwXBookmark*>(xFormField.get()), 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(xFormField->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;
 
    pContext->m_bSetUserFieldContent = false;
    pContext->m_bSetCitation = false;
    pContext->m_bSetDateValue = false;
    // tdf#124472: If the normal command line is not empty, use it,
    // otherwise, the last active row is evaluated.
    if (!pContext->GetCommandIsEmpty(false))
        pContext->SetCommandType(false);
 
    const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion();
 
    try
    {
        const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) };
        (void)vSwitches;
        OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front());
 
        // apply character properties to the form control
        if (!m_aTextAppendStack.empty() && m_pLastCharacterContext)
        {
            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 );
                    for (auto& rPropValue : m_pLastCharacterContext->GetPropertyValues(false))
                    {
                        try
                        {
                            xProp->setPropertyValue(rPropValue.Name, rPropValue.Value);
                        }
                        catch(uno::Exception&)
                        {
                            TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Unknown Field PropVal");
                        }
                    }
                }
            }
        }
 
        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;
            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 (IsInTOC() && (aIt->second.eFieldId == FIELD_PAGEREF))
            {
                bCreateField = false;
            }
 
            rtl::Reference< SwXTextField > xFieldInterface;
            rtl::Reference< SwXFieldmark > xFieldmark;
            if( bCreateField || bCreateEnhancedField )
            {
                //add the service prefix
                OUString sServiceName(u"com.sun.star.text."_ustr);
                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 );
                    if (m_xTextDocument)
                        xFieldmark = m_xTextDocument->createFieldmark(sServiceName);
                }
                else
                {
                    sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName );
                    if (m_xTextDocument)
                        xFieldInterface = m_xTextDocument->createTextField(sServiceName);
                }
 
#ifdef DBG_UTIL
                TagLogger::getInstance().startElement("fieldService");
                TagLogger::getInstance().chars(sServiceName);
                TagLogger::getInstance().endElement();
#endif
 
            }
            switch( aIt->second.eFieldId )
            {
                case FIELD_ADDRESSBLOCK: break;
                case FIELD_ADVANCE     : break;
                case FIELD_ASK         :
                    handleFieldAsk(pContext, xFieldInterface);
                break;
                case FIELD_AUTONUM    :
                case FIELD_AUTONUMLGL :
                case FIELD_AUTONUMOUT :
                    handleAutoNum(pContext, xFieldInterface);
                break;
                case FIELD_AUTHOR       :
                case FIELD_USERNAME     :
                case FIELD_USERINITIALS :
                    handleAuthor(sFirstParam,
                        xFieldInterface,
                        aIt->second.eFieldId);
                break;
                case FIELD_DATE:
                if (xFieldInterface.is())
                {
                    // Get field fixed property from the context handler
                    if (pContext->IsFieldLocked())
                    {
                        xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_IS_FIXED),
                            uno::Any( true ));
                        pContext->m_bSetDateValue = true;
                    }
                    else
                        xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_IS_FIXED),
                            uno::Any( false ));
 
                    xFieldInterface->setPropertyValue(
                        getPropertyName(PROP_IS_DATE),
                        uno::Any( true ));
                    SetNumberFormat( pContext->GetCommand(), xFieldInterface );
                }
                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
                    xFieldInterface->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())
                    {
                        xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_IS_FIXED), uno::Any( true ));
                    }
                    xFieldInterface->setPropertyValue(
                        getPropertyName( PROP_IS_DATE ), uno::Any( true ));
                    SetNumberFormat( pContext->GetCommand(), xFieldInterface );
                }
                break;
                case FIELD_DOCPROPERTY :
                {
                    FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
                    if (!pOuter || IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
                        handleDocProperty(pContext, sFirstParam, xFieldInterface);
                }
                break;
                case FIELD_DOCVARIABLE  :
                {
                    if (bCreateField)
                    {
                        //create a user field and type
                        rtl::Reference<SwXFieldMaster> xMaster = FindOrCreateFieldMaster(
                            "com.sun.star.text.FieldMaster.User", sFirstParam);
                        xFieldInterface->attachTextFieldMaster(xMaster);
                        pContext->m_bSetUserFieldContent = true;
                    }
                }
                break;
                case FIELD_EDITTIME     :
                    //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldInterface );
                break;
                case FIELD_EQ:
                {
                    OUString aCommand = pContext->GetCommand().trim();
 
                    msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand));
                    if (!aResult.sType.isEmpty() && m_xTextDocument)
                    {
                        xFieldInterface = m_xTextDocument->createTextField(Concat2View("com.sun.star.text.TextField." + aResult.sType));
                        xFieldInterface->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 (xFieldInterface.is())
                        xFieldInterface->setPropertyValue(
                                getPropertyName(PROP_HINT), uno::Any( pContext->GetCommand().getToken(1, '\"')));
                break;
                case FIELD_FILENAME:
                {
                    sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p");
                    if (xFieldInterface.is())
                        xFieldInterface->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, xFieldInterface);
                    }
                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);
                            if ( xFieldmark.is() )
                            {
                                if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() )
                                    xFieldmark->setName(  pFFDataHandler->getName() );
                                pContext->SetFormField( xFieldmark );
                            }
                            InsertFieldmark(m_aTextAppendStack,
                                xFieldmark, pContext->GetStartRange(),
                                pContext->GetFieldId());
                        }
                        else
                        {
                            if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN )
                                lcl_handleDropdownField( xFieldInterface, pContext->getFFDataHandler() );
                            else
                                lcl_handleTextField( xFieldInterface, 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;
                    OUString sName;
 
                    while (aPartIt != aItEnd)
                    {
                        if ( *aPartIt == "\\l" )
                        {
                            ++aPartIt;
 
                            if (aPartIt == aItEnd)
                                break;
 
                            sURL += "#" + *aPartIt;
                        }
                        else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h")
                        {
                        }
                        else if (*aPartIt == "\\t")
                        {
                            ++aPartIt;
 
                            if (aPartIt == aItEnd)
                                break;
 
                            sTarget = *aPartIt;
                        }
                        else if (*aPartIt == "\\o")
                        {
                            ++aPartIt;
 
                            if (aPartIt == aItEnd)
                                break;
 
                            sName = *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);
                    if (!sName.isEmpty())
                        pContext->SetHyperlinkName(sName);
                }
                break;
                case FIELD_IF:
                {
                    if (vArguments.size() < 3)
                    {
                        SAL_WARN("writerfilter.dmapper", "IF field requires at least 3 parameters!");
                        break;
                    }
 
                    if (xFieldInterface.is())
                    {
                        // Following code assumes that last argument in field is false value
                        // before it - true value and everything before them is a condition
                        OUString sCondition;
                        size_t i = 0;
                        while (i < vArguments.size() - 2) {
                            if (!sCondition.isEmpty())
                                sCondition += " ";
                            sCondition += vArguments[i++];
                        }
 
                        xFieldInterface->setPropertyValue(
                            u"TrueContent"_ustr, uno::Any(vArguments[vArguments.size() - 2]));
                        xFieldInterface->setPropertyValue(
                            u"FalseContent"_ustr, uno::Any(vArguments[vArguments.size() - 1]));
                        xFieldInterface->setPropertyValue(
                            u"Condition"_ustr, uno::Any(sCondition));
                    }
                }
                break;
                case FIELD_INFO         : break;
                case FIELD_INCLUDEPICTURE: break;
                case FIELD_KEYWORDS     :
                case FIELD_SUBJECT      :
                case FIELD_TITLE        :
                {
                    if (!sFirstParam.isEmpty())
                    {
                        xFieldInterface->setPropertyValue(
                                getPropertyName( PROP_IS_FIXED ), uno::Any( true ));
                        //PROP_CURRENT_PRESENTATION is set later anyway
                    }
                }
                break;
                case FIELD_LASTSAVEDBY :
                    xFieldInterface->setPropertyValue(
                        getPropertyName(PROP_IS_FIXED), uno::Any(true));
                    break;
                case FIELD_MACROBUTTON:
                {
                    if (xFieldInterface.is())
                    {
                        sal_Int32 nIndex = sizeof(" MACROBUTTON ");
                        OUString sCommand = pContext->GetCommand();
 
                        //extract macro name
                        if (sCommand.getLength() >= nIndex)
                        {
                            OUString sMacro = sCommand.getToken(0, ' ', nIndex);
                            xFieldInterface->setPropertyValue(
                                    getPropertyName(PROP_MACRO_NAME), uno::Any( sMacro ));
                        }
 
                        //extract quick help text
                        if (sCommand.getLength() > nIndex + 1)
                        {
                            xFieldInterface->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
                    rtl::Reference<SwXFieldMaster> xMaster =
                        FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam);
 
//                    xFieldInterface->setPropertyValue(
//                             "FieldCode",
//                             uno::makeAny( pContext->GetCommand().copy( nIndex + 1 )));
                    if (!xFieldInterface)
                        throw uno::Exception();
                    xFieldInterface->attachTextFieldMaster( xMaster );
                }
                break;
                case FIELD_MERGEREC     : break;
                case FIELD_MERGESEQ     : break;
                case FIELD_NEXT         : break;
                case FIELD_NEXTIF       : break;
                case FIELD_PAGE        :
                    if (xFieldInterface.is())
                    {
                        xFieldInterface->setPropertyValue(
                                getPropertyName(PROP_NUMBERING_TYPE),
                                uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
                        xFieldInterface->setPropertyValue(
                                getPropertyName(PROP_SUB_TYPE),
                                uno::Any( text::PageNumberType_CURRENT ));
                    }
 
                break;
                case FIELD_PAGEREF:
                case FIELD_REF:
                case FIELD_STYLEREF:
                if (xFieldInterface.is() && !IsInTOC())
                {
                    bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF;
                    bool bStyleRef = aIt->second.eFieldId == FIELD_STYLEREF;
 
                    // Do we need a GetReference (default) or a GetExpression field?
                    uno::Reference< container::XNameAccess > xFieldMasterAccess = GetTextDocument()->getTextFieldMasters();
 
                    if (!xFieldMasterAccess->hasByName(
                            "com.sun.star.text.FieldMaster.SetExpression."
                            + sFirstParam))
                    {
                        if (bStyleRef)
                        {
                            xFieldInterface->setPropertyValue(
                                getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
                                uno::Any(sal_Int16(text::ReferenceFieldSource::STYLE)));
 
                            OUString styleName(sFirstParam);
                            if (styleName.isEmpty())
                            {
                                for (auto const& rSwitch : vSwitches)
                                {
                                    // undocumented Word feature: \1 = "Heading 1" etc.
                                    if (rSwitch.getLength() == 2 && rSwitch[0] == '\\'
                                        && '1' <= rSwitch[1] && rSwitch[1] <= '9')
                                    {
                                        styleName = OUString(rSwitch[1]);
                                        break;
                                    }
                                }
                            }
 
                            if (!styleName.isEmpty())
                            {
                                uno::Any aStyleDisplayName;
                                aStyleDisplayName <<= ConvertTOCStyleName(styleName);
 
                                xFieldInterface->setPropertyValue(
                                    getPropertyName(PROP_SOURCE_NAME), aStyleDisplayName);
                            }
 
                            sal_uInt16 nFlags = 0;
                            OUString sValue;
                            if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue ))
                            {
                                //search-below-first
                                nFlags |= REFFLDFLAG_STYLE_FROM_BOTTOM;
                            }
                            if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue ))
                            {
                                //suppress-nondelimiter
                                nFlags |= REFFLDFLAG_STYLE_HIDE_NON_NUMERICAL;
                            }
                            xFieldInterface->setPropertyValue(
                                    getPropertyName( PROP_REFERENCE_FIELD_FLAGS ), uno::Any(nFlags) );
                        }
                        else
                        {
                            xFieldInterface->setPropertyValue(
                                getPropertyName(PROP_REFERENCE_FIELD_SOURCE),
                                uno::Any( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) );
 
                            xFieldInterface->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;
                        }
                        xFieldInterface->setPropertyValue(
                                getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::Any( nFieldPart ));
                    }
                    else if( m_xTextDocument )
                    {
                        xFieldInterface = m_xTextDocument->createTextField(u"com.sun.star.text.TextField.GetExpression");
                        xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_CONTENT),
                            uno::Any(sFirstParam));
                        xFieldInterface->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, u"SEQ ", '\\');
                    sSeqName = o3tl::trim(sSeqName);
 
                    // create a sequence field master using the sequence name
                    rtl::Reference<SwXFieldMaster> 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
                    xFieldInterface->setPropertyValue(
                        getPropertyName(PROP_NUMBERING_TYPE),
                        uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
 
                    // attach the master to the field
                    xFieldInterface->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.
                    xFieldInterface->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;
                    xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_NUMBERING_TYPE),
                            uno::Any(nNumberingType));
                }
                break;
                case FIELD_SET          :
                    handleFieldSet(pContext, xFieldInterface);
                break;
                case FIELD_SKIPIF       : break;
                case FIELD_SYMBOL:
                {
                    FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack);
                    OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ?  o3tl::toUInt32(sFirstParam.subView(2),16) : sFirstParam.toUInt32() ) );
                    if (!pOuter || IsFieldNestingAllowed(pOuter, m_aFieldStack.back()))
                    {
                        uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend;
                        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);
 
                                }
                                PropertyMapPtr pCharTopContext = GetTopContextOfType(CONTEXT_CHARACTER);
                                if (pCharTopContext.is())
                                {
                                    uno::Sequence<beans::PropertyValue> aValues
                                        = pCharTopContext->GetPropertyValues(
                                            /*bCharGrabBag=*/!IsInComments());
                                    OUString sFontName = getPropertyName(PROP_CHAR_FONT_NAME);
                                    for (const beans::PropertyValue& rProperty : aValues)
                                    {
                                        if (!bHasFont || !rProperty.Name.startsWith(sFontName))
                                            xProp->setPropertyValue(rProperty.Name, rProperty.Value);
                                    }
 
                                }
 
                            }
                        }
                    }
                }
                break;
                case FIELD_TEMPLATE: break;
                case FIELD_TIME         :
                {
                    if (pContext->IsFieldLocked())
                    {
                        xFieldInterface->setPropertyValue(
                            getPropertyName(PROP_IS_FIXED),
                            uno::Any( true ));
                        pContext->m_bSetDateValue = true;
                    }
                    SetNumberFormat( pContext->GetCommand(), xFieldInterface );
                }
                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_xTextDocument )
                        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 )
                            ? u"com.sun.star.text.UserIndexMark"_ustr
                            : OUString::createFromAscii(aIt->second.cFieldServiceName);
                    uno::Reference< beans::XPropertySet > xTC(
                            m_xTextDocument->createInstance(sFieldServiceName),
                                    uno::UNO_QUERY_THROW);
 
                    if (!sFirstParam.isEmpty())
                    {
                        xTC->setPropertyValue(sUserIndex.isEmpty()
                                    ? u"PrimaryKey"_ustr
                                    : u"AlternativeText"_ustr,
                                uno::Any(sFirstParam));
                    }
 
                    sUserIndex = lcl_trim(sUserIndex);
                    if (!sUserIndex.isEmpty())
                    {
                        xTC->setPropertyValue(u"UserIndexName"_ustr,
                                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_xTextDocument )
                        break;
 
                    xFieldInterface = m_xTextDocument->createTextField(
                              OUString::createFromAscii(aIt->second.cFieldServiceName));
                    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) }
                        }));
                        xFieldInterface->setPropertyValue(u"Fields"_ustr, uno::Any(aValues));
                    }
 
                    uno::Sequence<beans::PropertyValue> aValues
                        = m_aFieldStack.back()->getProperties()->GetPropertyValues();
                    appendTextContent(xFieldInterface, aValues);
                    pContext->m_bSetCitation = true;
                }
                break;
 
                case FIELD_TC :
                {
                    if( !m_xTextDocument )
                        break;
 
                    uno::Reference< beans::XPropertySet > xTC(
                        m_xTextDocument->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 (xFieldInterface.is())
                    xFieldInterface->setPropertyValue(
                        getPropertyName(PROP_NUMBERING_TYPE),
                        uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) ));
                break;
            }
 
            if (!bCreateEnhancedField)
            {
                pContext->SetTextField( xFieldInterface );
            }
        }
        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_xTextDocument && !m_aTextAppendStack.empty())
            {
                rtl::Reference<SwXBookmark> xFieldInterface = m_xTextDocument->createFieldmark();
 
                rtl::Reference<SwXFieldmark> xFormField = dynamic_cast<SwXFieldmark*>(xFieldInterface.get());
                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_StreamStateStack.top().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 (pContext->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 (pContext->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(u"com.sun.star.text.TextField.Bibliography"_ustr);
                    if( bIsSetbiblio )
                    {
                        uno::Any aProperty  = xFieldProperties->getPropertyValue(u"Fields"_ustr);
                        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] = std::move(propertyVal);
                        }
                        else
                        {
                            aValues.realloc(aValues.getLength() + 1);
                            propertyVal.Name = "Title";
                            propertyVal.Value <<= rResult;
                            aValues.getArray()[aValues.getLength() - 1] = std::move(propertyVal);
                        }
                        xFieldProperties->setPropertyValue(u"Fields"_ustr,
                                uno::Any(aValues));
                    }
                }
                else if (pContext->m_bSetDateValue)
                {
                    uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( static_cast<cppu::OWeakObject*>(m_xTextDocument.get()), 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( u"NumberFormat"_ustr ) >>= nKey;
                    xFieldProperties->setPropertyValue(
                        u"DateTimeValue"_ustr,
                        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(u"com.sun.star.text.TextField.SetExpression"_ustr) ||
                        xServiceInfo->supportsService(u"com.sun.star.text.TextField.Input"_ustr);
                    // 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(u"com.sun.star.text.TextField.DocInfo.Custom"_ustr);
 
                    if (bCustomFixedField || xServiceInfo->supportsService(
                            u"com.sun.star.text.TextField.DocInfo.CreateDateTime"_ustr))
                    {
                        // Creation time is const, don't try to update it.
                        xFieldProperties->setPropertyValue(u"IsFixed"_ustr, 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
            {
                if( pContext->GetTOC().is() )
                {
                    if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography)
                    {
                        // inside SDT, last empty paragraph is also part of index
                        if (!m_StreamStateStack.top().bParaChanged && !m_StreamStateStack.top().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_StreamStateStack.top().bTextInserted = false;
                        m_StreamStateStack.top().bParaChanged = true; // the paragraph must stay anyway
                    }
                    m_bStartTOC = false;
                    m_bStartIndex = false;
                    m_bStartBibliography = false;
                    if (IsInHeaderFooter() && m_bStartTOCHeaderFooter)
                        m_bStartTOCHeaderFooter = false;
                }
                else
                {
                    uno::Reference< text::XTextContent > xToInsert(pContext->GetTC(), uno::UNO_QUERY);
                    if (!xToInsert.is() && !IsInTOC() && !m_bStartIndex && !m_bStartBibliography)
                        xToInsert = pContext->GetTextField();
                    if (xToInsert.is() && !IsInTOC() && !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())
                            {
                                rtl::Reference< SwXFieldmark > xFormField(pContext->GetFormField());
                                if (pFormControlHelper->hasFFDataHandler())
                                {
                                    xToInsert = static_cast<SwXBookmark*>(xFormField.get());
                                    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());
                                    xFormField->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(u"HyperLinkTarget"_ustr, uno::Any(pContext->GetHyperlinkTarget()));
                                if (!pContext->GetHyperlinkName().isEmpty())
                                    xCrsrProperties->setPropertyValue(u"HyperLinkName"_ustr, uno::Any(pContext->GetHyperlinkName()));
 
                                if (IsInTOC())
                                {
                                    OUString sDisplayName(u"Index Link"_ustr);
                                    xCrsrProperties->setPropertyValue(u"VisitedCharStyleName"_ustr,uno::Any(sDisplayName));
                                    xCrsrProperties->setPropertyValue(u"UnvisitedCharStyleName"_ustr,uno::Any(sDisplayName));
                                }
                                else if (!pContext->GetHyperlinkStyle().isEmpty())
                                {
                                    uno::Any aAny = xCrsrProperties->getPropertyValue(u"CharStyleName"_ustr);
                                    OUString charStyle;
                                    if (css::uno::fromAny(aAny, &charStyle))
                                    {
                                        if (!charStyle.isEmpty() && charStyle.equalsIgnoreAsciiCase("Internet Link"))
                                        {
                                            xCrsrProperties->setPropertyValue(u"CharStyleName"_ustr, uno::Any(u"Default Style"_ustr));
                                        }
                                        else
                                        {
                                            xCrsrProperties->setPropertyValue(u"VisitedCharStyleName"_ustr, uno::Any(pContext->GetHyperlinkStyle()));
                                            xCrsrProperties->setPropertyValue(u"UnvisitedCharStyleName"_ustr, uno::Any(pContext->GetHyperlinkStyle()));
 
                                        }
                                    }
                                }
                            }
                        }
                        else if (m_nStartGenericField != 0)
                        {
                            --m_nStartGenericField;
                            PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId());
                            if (m_StreamStateStack.top().bTextInserted)
                            {
                                m_StreamStateStack.top().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() )
    {
        if ((m_sCurrentBkmkPrefix == "__RefMoveFrom__"
             || m_sCurrentBkmkPrefix == "__RefMoveTo__")
            && std::find(m_aRedlineMoveIDs.begin(), m_aRedlineMoveIDs.end(), rBookmarkName)
                   == m_aRedlineMoveIDs.end())
        {
            m_aRedlineMoveIDs.push_back(rBookmarkName);
        }
 
        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_StreamStateStack.top().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_xTextDocument)
            {
                rtl::Reference<SwXBookmark> xBookmark( m_xTextDocument->createBookmark() );
                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 );
                }
                if (!aBookmarkIter->second.m_bIsStartOfText)
                {
                    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, if it doesn't exceed cell boundary
                    uno::Reference< text::XTextRange > xStart = xCursor->getStart();
                    xCursor->goLeft( 1, false );
                    if (m_StreamStateStack.top().nTableDepth == 0
                        || !m_StreamStateStack.top().bFirstParagraphInCell)
                    {
                        xCursor->gotoRange(xStart, true );
                    }
                }
                SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark");
                //todo: make sure the name is not used already!
                xBookmark->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_StreamStateStack.top().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_xTextDocument)
            {
                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);
                }
                if (!aPermIter->second.m_bIsStartOfText)
                {
                    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
                rtl::Reference< SwXBookmark > xPerm(m_xTextDocument->createBookmark());
                xPerm->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 = std::move(xCurrent);
    }
    else
    {
        aAnnotationPosition.m_xEnd = std::move(xCurrent);
    }
    m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition;
}
 
GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport()
{
    if(!m_pGraphicImport)
    {
        m_pGraphicImport = new GraphicImport(m_xComponentContext, m_xTextDocument, m_rDMapper, m_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)
{
    GetGraphicImport();
    if (m_eGraphicImportType != IMPORT_AS_DETECTED_INLINE && m_eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR)
    {   // this appears impossible?
        //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(u"FrameInteropGrabBag"_ustr);
        // 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(u"FrameInteropGrabBag"_ustr));
            aFrameGrabBag[u"SdtPr"_ustr] <<= m_pSdtHelper->getInteropGrabBagAndClear();
            xPropertySet->setPropertyValue(u"FrameInteropGrabBag"_ustr, 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(u"FrameInteropGrabBag"_ustr,uno::Any(aFrameGrabBag));
        }
    }
 
 
    // Update the shape properties if it is embedded object.
    if (m_StreamStateStack.top().xEmbedded.is())
    {
        if (m_pGraphicImport->GetXShapeObject())
                m_pGraphicImport->GetXShapeObject()->setPosition(
                    m_pGraphicImport->GetGraphicObjectPosition());
 
        uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject();
        UpdateEmbeddedShapeProps(xShape);
        if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR)
        {
            rtl::Reference<SwXTextEmbeddedObject> const xEmbedded(m_StreamStateStack.top().xEmbedded);
            xEmbedded->setPropertyValue(u"AnchorType"_ustr, uno::Any(text::TextContentAnchorType_AT_CHARACTER));
            xEmbedded->setPropertyValue(u"IsFollowingTextFlow"_ustr, uno::Any(m_pGraphicImport->GetLayoutInCell()));
            uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
            xEmbedded->setPropertyValue(u"HoriOrient"_ustr, xShapeProps->getPropertyValue(u"HoriOrient"_ustr));
            xEmbedded->setPropertyValue(u"HoriOrientPosition"_ustr, xShapeProps->getPropertyValue(u"HoriOrientPosition"_ustr));
            xEmbedded->setPropertyValue(u"HoriOrientRelation"_ustr, xShapeProps->getPropertyValue(u"HoriOrientRelation"_ustr));
            xEmbedded->setPropertyValue(u"VertOrient"_ustr, xShapeProps->getPropertyValue(u"VertOrient"_ustr));
            xEmbedded->setPropertyValue(u"VertOrientPosition"_ustr, xShapeProps->getPropertyValue(u"VertOrientPosition"_ustr));
            xEmbedded->setPropertyValue(u"VertOrientRelation"_ustr, xShapeProps->getPropertyValue(u"VertOrientRelation"_ustr));
            //tdf123873 fix missing textwrap import
            xEmbedded->setPropertyValue(u"TextWrap"_ustr, xShapeProps->getPropertyValue(u"TextWrap"_ustr));
 
            // GraphicZOrderHelper::findZOrder() was called already, so can just copy it over.
            xEmbedded->setPropertyValue(u"ZOrder"_ustr, xShapeProps->getPropertyValue(u"ZOrder"_ustr));
        }
    }
    //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 (m_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"​"_ustr, pEmpty);
                    appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
                    bAppend = false;
                    xCrsr->gotoEnd(false);
                    appendTextPortion(u"​"_ustr, pEmpty);
 
                    m_bRedlineImageInPreviousRun = true;
                    m_previousRedline = m_currentRedline;
                }
                catch( const uno::Exception& )
                {
                }
            }
        }
 
        if ( bAppend )
            appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() );
 
        if (m_eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty())
        {
            // Remember this object is anchored to the current paragraph.
            AnchoredObjectInfo aInfo;
            aInfo.m_xAnchoredObject = std::move(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 (m_eGraphicImportType == IMPORT_AS_DETECTED_INLINE)
        {
            m_StreamStateStack.top().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 = std::move(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_StreamStateStack.top().xEmbedded.clear();
    m_pGraphicImport.clear();
}
 
 
void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn )
{
    if (!m_xTextDocument)
        throw uno::RuntimeException();
    if( !m_bLineNumberingSet )
    {
        try
        {
            uno::Reference< beans::XPropertySet > xProperties = m_xTextDocument->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_Limited(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;
    rtl::Reference<SwXStyleFamilies> xStyleFamilies = m_xTextDocument->getSwStyleFamilies();
    rtl::Reference<SwXStyleFamily> xStyles = xStyleFamilies->GetParagraphStyles();
    lcl_linenumberingHeaderFooter( xStyles, u"Header"_ustr, this );
    lcl_linenumberingHeaderFooter( xStyles, u"Footer"_ustr, this );
}
 
 
void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue )
{
    nValue = ConversionHelper::convertTwipToMm100_Limited(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;
    }
}
 
void DomainMapper_Impl::SetPaperSource(PaperSourceElement eElement, sal_Int32 nValue)
{
    if(eElement == PAPER_SOURCE_FIRST)
        m_aPaperSource.first = nValue;
    else
        m_aPaperSource.other = nValue;
}
 
 
PageMar::PageMar()
    : top(convertTwipToMm100(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(convertTwipToMm100(1440))
    , bottom(top)
    , left(right)
    , header(convertTwipToMm100(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())
        {
            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] != -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 = std::move(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(u"Author"_ustr, uno::Any(sAuthor));
}
 
void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials )
{
    if (m_xAnnotationField.is())
        m_xAnnotationField->setPropertyValue(u"Initials"_ustr, 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(u"DateTimeValue"_ustr, 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(u"RemoveTopRedline failed"_ustr, nullptr);
    }
    m_aRedlines.top().pop_back( );
    m_currentRedline.clear();
}
 
void DomainMapper_Impl::ApplySettingsTable()
{
    if (!(m_pSettingsTable && m_xTextDocument))
        return;
 
    try
    {
        rtl::Reference< SwXTextDefaults > xTextDefaults(m_xTextDocument->createTextDefaults());
        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(sal_Int32(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)));
            m_xTextDocument->setViewData(xBox);
        }
 
        rtl::Reference<SwXDocumentSettings> xSettings(m_xTextDocument->createDocumentSettings());
 
        if (m_pSettingsTable->GetDoNotExpandShiftReturn())
            xSettings->setPropertyValue( u"DoNotJustifyLinesWithManualBreak"_ustr, uno::Any(true) );
        // new paragraph justification has been introduced in version 15,
        // breaking text layout interoperability: new line shrinking needs less space
        // i.e. it typesets the same text with less lines and pages.
        if (m_pSettingsTable->GetWordCompatibilityMode() >= 15)
            xSettings->setPropertyValue(u"JustifyLinesWithShrinking"_ustr, uno::Any( true ));
        if (m_pSettingsTable->GetUsePrinterMetrics())
            xSettings->setPropertyValue(u"PrinterIndependentLayout"_ustr, 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(u"AddParaTableSpacing"_ustr, uno::Any(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing()));
        if (m_pSettingsTable->GetNoLeading())
        {
            xSettings->setPropertyValue(u"AddExternalLeading"_ustr, uno::Any(!m_pSettingsTable->GetNoLeading()));
        }
        if( m_pSettingsTable->GetProtectForm() )
            xSettings->setPropertyValue(u"ProtectForm"_ustr, uno::Any( true ));
        if( m_pSettingsTable->GetReadOnly() )
            xSettings->setPropertyValue(u"LoadReadonly"_ustr, uno::Any( true ));
        if (m_pSettingsTable->GetGutterAtTop())
        {
            xSettings->setPropertyValue(u"GutterAtTop"_ustr, uno::Any(true));
        }
        uno::Sequence<beans::PropertyValue> aWriteProtection
                = m_pSettingsTable->GetWriteProtectionSettings();
        if (aWriteProtection.hasElements())
                xSettings->setPropertyValue(u"ModifyPasswordInfo"_ustr, 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)
{
    m_StreamStateStack.top().deferredCharacterProperties[ id ] = value;
}
 
void DomainMapper_Impl::processDeferredCharacterProperties(bool bCharContext)
{
    // Actually process in DomainMapper, so that it's the same source file like normal processing.
    if (!m_StreamStateStack.top().deferredCharacterProperties.empty())
    {
        m_rDMapper.processDeferredCharacterProperties(m_StreamStateStack.top().deferredCharacterProperties, bCharContext);
        m_StreamStateStack.top().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;
    if ( !m_xTextDocument )
        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 rtl::Reference< SwXStyleFamilies > xStyleFamilies = m_xTextDocument->getSwStyleFamilies();
        rtl::Reference<SwXStyleFamily> xNumberingStyles = xStyleFamilies->GetNumberingStyles();
        const rtl::Reference<SwXStyle> xStyle = dynamic_cast<SwXStyle*>(xNumberingStyles->getStyleByName(aListName).get());
        const rtl::Reference<SwXNumberingRules> xNumberingRules = xStyle->getNumberingRules();
        if (xNumberingRules.is())
        {
            xNumberingRules->getPropertyByIndex(nNumberingLevel, aProp) >>= 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
 
    //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();
 
    // Save "has footnote" state, which is specific to a section in the body
    // text, so state from substreams is not relevant.
    m_StreamStateStack.emplace();
 
    //import of page header/footer
    //Ensure that only one header/footer per section is pushed
 
    switch( rName )
    {
    case NS_ooxml::LN_headerl:
            PushPageHeaderFooter(PagePartType::Header, PageType::LEFT);
        break;
    case NS_ooxml::LN_headerr:
            PushPageHeaderFooter(PagePartType::Header, PageType::RIGHT);
        break;
    case NS_ooxml::LN_headerf:
            PushPageHeaderFooter(PagePartType::Header, PageType::FIRST);
        break;
    case NS_ooxml::LN_footerl:
            PushPageHeaderFooter(PagePartType::Footer, PageType::LEFT);
        break;
    case NS_ooxml::LN_footerr:
            PushPageHeaderFooter(PagePartType::Footer, PageType::RIGHT);
        break;
    case NS_ooxml::LN_footerf:
            PushPageHeaderFooter(PagePartType::Footer, PageType::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;
    default:
        assert(false); // unexpected?
    }
 
    assert(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body);
 
    try
    {
        ref->resolve(m_rDMapper);
    }
    catch (xml::sax::SAXException const&)
    {
        m_bSaxError = true;
        throw;
    }
 
    switch( rName )
    {
    case NS_ooxml::LN_headerl:
        PopPageHeaderFooter(PagePartType::Header, PageType::LEFT);
    break;
    case NS_ooxml::LN_footerl:
        PopPageHeaderFooter(PagePartType::Footer, PageType::LEFT);
    break;
    case NS_ooxml::LN_headerr:
        PopPageHeaderFooter(PagePartType::Header, PageType::RIGHT);
    break;
    case NS_ooxml::LN_footerr:
        PopPageHeaderFooter(PagePartType::Footer, PageType::RIGHT);
    break;
    case NS_ooxml::LN_headerf:
        PopPageHeaderFooter(PagePartType::Header, PageType::FIRST);
    break;
    case NS_ooxml::LN_footerf:
        PopPageHeaderFooter(PagePartType::Footer, PageType::FIRST);
    break;
    case NS_ooxml::LN_footnote:
    case NS_ooxml::LN_endnote:
        PopFootOrEndnote();
    break;
    case NS_ooxml::LN_annotation :
        PopAnnotation();
    break;
    }
 
    m_StreamStateStack.pop();
    assert(!m_StreamStateStack.empty());
 
    getTableManager().endLevel();
    popTableManager();
 
    switch(rName)
    {
    case NS_ooxml::LN_footnote:
    case NS_ooxml::LN_endnote:
        m_StreamStateStack.top().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_StreamStateStack.top().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_StreamStateStack.top().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_StreamStateStack.top().xPreviousParagraph->setPropertyValue(getPropertyName(PROP_BOTTOM_BORDER), uno::Any(table::BorderLine2()));
 
    return true;
}
 
OUString DomainMapper_Impl::getFontNameForTheme(const Id id)
{
    auto const& pHandler = getThemeHandler();
    if (pHandler)
        return pHandler->getFontNameForTheme(id);
    return OUString();
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'appendAscii' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V547 Expression 'nListLevel != - 1' is always false.

V547 Expression 'nStyleAuto > 0' is always false.

V547 Expression 'nStyleAuto > 0' is always false.

V547 Expression is always false.

V547 Expression is always false.

V547 Expression is always false.

V547 Expression is always false.

V547 Expression 'nPrevVertPos != nCurrVertPos' is always false.

V547 Expression 'zOrder < 0' is always false.

V547 Expression is always false.

V547 Expression 'zOrder < 0' is always false.

V1076 Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.

V1076 Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.

V1076 Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.

V547 Expression 'bIsHidden' is always false.

V547 Expression '!bPropIsOn' is always true.

V547 Expression 'bPropShared' is always false.

V547 Expression 'bFirstShared' is always false.

V547 Expression 'bConnectBorders' is always true.

V560 A part of conditional expression is always true: nType == text::SizeType::FIX.

V560 A part of conditional expression is always true: nType == text::SizeType::FIX.

V560 A part of conditional expression is always false: nBeforeAutospacing > - 1.

V560 A part of conditional expression is always false: nAfterAutospacing > - 1.

V560 A part of conditional expression is always false: nCurrVertPos < - 20.

V560 A part of conditional expression is always false: nCurrVertPos > 20.

V560 A part of conditional expression is always false.

V560 A part of conditional expression is always true: zOrder >= 0.

V560 A part of conditional expression is always false.

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 2974, 3004.

V1019 Compound assignment expression is used inside condition.