/* -*- 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.