diff options
Diffstat (limited to 'writerfilter/source/dmapper')
85 files changed, 39837 insertions, 0 deletions
diff --git a/writerfilter/source/dmapper/BorderHandler.cxx b/writerfilter/source/dmapper/BorderHandler.cxx new file mode 100644 index 000000000..cb8e38c75 --- /dev/null +++ b/writerfilter/source/dmapper/BorderHandler.cxx @@ -0,0 +1,213 @@ +/* -*- 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 "BorderHandler.hxx" +#include "TDefTableHandler.hxx" +#include "PropertyMap.hxx" +#include "ConversionHelper.hxx" +#include <com/sun/star/table/BorderLine2.hpp> +#include <o3tl/enumarray.hxx> +#include <o3tl/enumrange.hxx> +#include <ooxml/resourceids.hxx> +#include <filter/msfilter/util.hxx> +#include <comphelper/sequence.hxx> +#include <tools/color.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + + +BorderHandler::BorderHandler( bool bOOXML ) : +LoggedProperties("BorderHandler"), +m_nLineWidth(15), // Word default, in twips +m_nLineType(0), +m_nLineColor(0), +m_nLineDistance(0), +m_bShadow(false), +m_bOOXML( bOOXML ) +{ + m_aFilledLines.fill(false); + m_aBorderLines.fill(table::BorderLine2()); +} + +BorderHandler::~BorderHandler() +{ +} + +void BorderHandler::lcl_attribute(Id rName, Value & rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch( rName ) + { + case NS_ooxml::LN_CT_Border_sz: + // width of a single line in 1/8 pt, max of 32 pt -> twip * 5 / 2. + m_nLineWidth = nIntValue * 5 / 2; + appendGrabBag("sz", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_val: + m_nLineType = nIntValue; + appendGrabBag("val", TDefTableHandler::getBorderTypeString(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_color: + m_nLineColor = nIntValue; + appendGrabBag("color", msfilter::util::ConvertColorOU(Color(ColorTransparency, nIntValue))); + break; + case NS_ooxml::LN_CT_Border_space: // border distance in points + m_nLineDistance = ConversionHelper::convertTwipToMM100( nIntValue * 20 ); + appendGrabBag("space", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_shadow: + m_bShadow = nIntValue; + break; + case NS_ooxml::LN_CT_Border_frame: + case NS_ooxml::LN_CT_Border_themeTint: + appendGrabBag("themeTint", OUString::number(nIntValue, 16)); + break; + case NS_ooxml::LN_CT_Border_themeColor: + appendGrabBag("themeColor", TDefTableHandler::getThemeColorTypeString(nIntValue)); + break; + default: + OSL_FAIL( "unknown attribute"); + } +} + +void BorderHandler::lcl_sprm(Sprm & rSprm) +{ + BorderPosition pos; + const bool rtl = false; // TODO detect + OUString aBorderPos; + switch( rSprm.getId()) + { + case NS_ooxml::LN_CT_TblBorders_top: + pos = BorderPosition::Top; + aBorderPos = "top"; + break; + case NS_ooxml::LN_CT_TblBorders_start: + pos = rtl ? BorderPosition::Right : BorderPosition::Left; + aBorderPos = "start"; + break; + case NS_ooxml::LN_CT_TblBorders_left: + pos = BorderPosition::Left; + aBorderPos = "left"; + break; + case NS_ooxml::LN_CT_TblBorders_bottom: + pos = BorderPosition::Bottom; + aBorderPos = "bottom"; + break; + case NS_ooxml::LN_CT_TblBorders_end: + pos = rtl ? BorderPosition::Left : BorderPosition::Right; + aBorderPos = "end"; + break; + case NS_ooxml::LN_CT_TblBorders_right: + pos = BorderPosition::Right; + aBorderPos = "right"; + break; + case NS_ooxml::LN_CT_TblBorders_insideH: + pos = BorderPosition::Horizontal; + aBorderPos = "insideH"; + break; + case NS_ooxml::LN_CT_TblBorders_insideV: + pos = BorderPosition::Vertical; + aBorderPos = "insideV"; + break; + default: + return; + } + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties) + { + std::vector<beans::PropertyValue> aSavedGrabBag; + if (!m_aInteropGrabBagName.isEmpty()) + { + aSavedGrabBag = m_aInteropGrabBag; + m_aInteropGrabBag.clear(); + } + pProperties->resolve(*this); + if (!m_aInteropGrabBagName.isEmpty()) + { + aSavedGrabBag.push_back(getInteropGrabBag(aBorderPos)); + m_aInteropGrabBag = aSavedGrabBag; + } + } + ConversionHelper::MakeBorderLine( m_nLineWidth, m_nLineType, m_nLineColor, + m_aBorderLines[ pos ], m_bOOXML ); + m_aFilledLines[ pos ] = true; +} + +PropertyMapPtr BorderHandler::getProperties() +{ + static const o3tl::enumarray<BorderPosition, PropertyIds> aPropNames = + { + PROP_TOP_BORDER, + PROP_LEFT_BORDER, + PROP_BOTTOM_BORDER, + PROP_RIGHT_BORDER, + META_PROP_HORIZONTAL_BORDER, + META_PROP_VERTICAL_BORDER + }; + PropertyMapPtr pPropertyMap(new PropertyMap); + // don't fill in default properties + if( m_bOOXML ) + { + for( auto nProp: o3tl::enumrange<BorderPosition>()) + { + if ( m_aFilledLines[nProp] ) { + pPropertyMap->Insert( aPropNames[nProp], uno::Any( m_aBorderLines[nProp] ) ); + } + } + } + return pPropertyMap; +} + +table::BorderLine2 BorderHandler::getBorderLine() +{ + table::BorderLine2 aBorderLine; + ConversionHelper::MakeBorderLine( m_nLineWidth, m_nLineType, m_nLineColor, aBorderLine, m_bOOXML ); + return aBorderLine; +} + + +void BorderHandler::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +beans::PropertyValue BorderHandler::getInteropGrabBag(const OUString& aName) +{ + beans::PropertyValue aRet; + if (aName.isEmpty()) + aRet.Name = m_aInteropGrabBagName; + else + aRet.Name = aName; + + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + return aRet; +} + +void BorderHandler::appendGrabBag(const OUString& aKey, const OUString& aValue) +{ + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= aValue; + m_aInteropGrabBag.push_back(aProperty); +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/BorderHandler.hxx b/writerfilter/source/dmapper/BorderHandler.hxx new file mode 100644 index 000000000..6c607ca5a --- /dev/null +++ b/writerfilter/source/dmapper/BorderHandler.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ +#pragma once + +#include <vector> +#include "LoggedResources.hxx" +#include "PropertyMap.hxx" +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <o3tl/enumarray.hxx> + +namespace writerfilter::dmapper +{ +class PropertyMap; +class BorderHandler : public LoggedProperties +{ +private: + //todo: order is a guess + enum class BorderPosition + { + Top, + Left, + Bottom, + Right, + Horizontal, + Vertical, + LAST = Vertical + }; + + //values of the current border + sal_Int32 m_nLineWidth; + sal_Int32 m_nLineType; + sal_Int32 m_nLineColor; + sal_Int32 m_nLineDistance; + bool m_bShadow; + bool m_bOOXML; + + o3tl::enumarray<BorderPosition, bool> m_aFilledLines; + o3tl::enumarray<BorderPosition, css::table::BorderLine2> m_aBorderLines; + OUString m_aInteropGrabBagName; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + void appendGrabBag(const OUString& aKey, const OUString& aValue); + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + +public: + explicit BorderHandler( bool bOOXML ); + virtual ~BorderHandler() override; + + PropertyMapPtr getProperties(); + css::table::BorderLine2 getBorderLine(); + sal_Int32 getLineDistance() const { return m_nLineDistance;} + sal_Int32 getLineType() const { return m_nLineType;} + bool getShadow() const { return m_bShadow;} + void enableInteropGrabBag(const OUString& aName); + css::beans::PropertyValue getInteropGrabBag(const OUString& aName = OUString()); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/CellColorHandler.cxx b/writerfilter/source/dmapper/CellColorHandler.cxx new file mode 100644 index 000000000..439806e20 --- /dev/null +++ b/writerfilter/source/dmapper/CellColorHandler.cxx @@ -0,0 +1,331 @@ +/* -*- 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 "CellColorHandler.hxx" +#include "PropertyMap.hxx" +#include "TDefTableHandler.hxx" +#include <ooxml/resourceids.hxx> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/ShadingPattern.hpp> +#include <filter/msfilter/util.hxx> +#include <comphelper/sequence.hxx> +#include <tools/color.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + +CellColorHandler::CellColorHandler() : +LoggedProperties("CellColorHandler"), +m_nShadingPattern( drawing::ShadingPattern::CLEAR ), +m_nColor( 0xffffffff ), +m_nFillColor( 0xffffffff ), +m_bAutoFillColor( true ), +m_bFillSpecified( false ), + m_OutputFormat( Form ) +{ +} + +CellColorHandler::~CellColorHandler() +{ +} + +// ST_Shd strings are converted to integers by the tokenizer, store strings in +// the InteropGrabBag +static uno::Any lcl_ConvertShd(sal_Int32 nIntValue) +{ + OUString aRet; + // This should be in sync with the ST_Shd list in ooxml's model.xml. + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_Shd_clear: aRet = "clear"; break; + case NS_ooxml::LN_Value_ST_Shd_solid: aRet = "solid"; break; + case NS_ooxml::LN_Value_ST_Shd_pct5: aRet = "pct5"; break; + case NS_ooxml::LN_Value_ST_Shd_pct10: aRet = "pct10"; break; + case NS_ooxml::LN_Value_ST_Shd_pct20: aRet = "pct20"; break; + case NS_ooxml::LN_Value_ST_Shd_pct25: aRet = "pct25"; break; + case NS_ooxml::LN_Value_ST_Shd_pct30: aRet = "pct30"; break; + case NS_ooxml::LN_Value_ST_Shd_pct40: aRet = "pct40"; break; + case NS_ooxml::LN_Value_ST_Shd_pct50: aRet = "pct50"; break; + case NS_ooxml::LN_Value_ST_Shd_pct60: aRet = "pct60"; break; + case NS_ooxml::LN_Value_ST_Shd_pct70: aRet = "pct70"; break; + case NS_ooxml::LN_Value_ST_Shd_pct75: aRet = "pct75"; break; + case NS_ooxml::LN_Value_ST_Shd_pct80: aRet = "pct80"; break; + case NS_ooxml::LN_Value_ST_Shd_pct90: aRet = "pct90"; break; + case NS_ooxml::LN_Value_ST_Shd_horzStripe: aRet = "horzStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_vertStripe: aRet = "vertStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_reverseDiagStripe: aRet = "reverseDiagStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_diagStripe: aRet = "diagStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_horzCross: aRet = "horzCross"; break; + case NS_ooxml::LN_Value_ST_Shd_diagCross: aRet = "diagCross"; break; + case NS_ooxml::LN_Value_ST_Shd_thinHorzStripe: aRet = "thinHorzStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_thinVertStripe: aRet = "thinVertStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_thinReverseDiagStripe: aRet = "thinReverseDiagStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_thinDiagStripe: aRet = "thinDiagStripe"; break; + case NS_ooxml::LN_Value_ST_Shd_thinHorzCross: aRet = "thinHorzCross"; break; + case NS_ooxml::LN_Value_ST_Shd_thinDiagCross: aRet = "thinDiagCross"; break; + case NS_ooxml::LN_Value_ST_Shd_pct12: aRet = "pct12"; break; + case NS_ooxml::LN_Value_ST_Shd_pct15: aRet = "pct15"; break; + case NS_ooxml::LN_Value_ST_Shd_pct35: aRet = "pct35"; break; + case NS_ooxml::LN_Value_ST_Shd_pct37: aRet = "pct37"; break; + case NS_ooxml::LN_Value_ST_Shd_pct45: aRet = "pct45"; break; + case NS_ooxml::LN_Value_ST_Shd_pct55: aRet = "pct55"; break; + case NS_ooxml::LN_Value_ST_Shd_pct62: aRet = "pct62"; break; + case NS_ooxml::LN_Value_ST_Shd_pct65: aRet = "pct65"; break; + case NS_ooxml::LN_Value_ST_Shd_pct85: aRet = "pct85"; break; + case NS_ooxml::LN_Value_ST_Shd_pct87: aRet = "pct87"; break; + case NS_ooxml::LN_Value_ST_Shd_pct95: aRet = "pct95"; break; + case NS_ooxml::LN_Value_ST_Shd_nil: aRet = "nil"; break; + } + return uno::Any(aRet); +} + +void CellColorHandler::lcl_attribute(Id rName, Value & rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch( rName ) + { + case NS_ooxml::LN_CT_Shd_val: + { + createGrabBag("val", lcl_ConvertShd(nIntValue)); + m_nShadingPattern = nIntValue; + } + break; + case NS_ooxml::LN_CT_Shd_fill: + createGrabBag("fill", uno::Any(msfilter::util::ConvertColorOU(Color(ColorTransparency, nIntValue)))); + if( nIntValue == sal_Int32(COL_AUTO) ) + nIntValue = 0xffffff; //fill color auto means white + else + m_bAutoFillColor = false; + + m_nFillColor = nIntValue; + m_bFillSpecified = true; + break; + case NS_ooxml::LN_CT_Shd_color: + createGrabBag("color", uno::Any(msfilter::util::ConvertColorOU(Color(ColorTransparency, nIntValue)))); + if( nIntValue == sal_Int32(COL_AUTO) ) + nIntValue = 0; //shading color auto means black + //color of the shading + m_nColor = nIntValue; + break; + case NS_ooxml::LN_CT_Shd_themeFill: + createGrabBag("themeFill", uno::Any(TDefTableHandler::getThemeColorTypeString(nIntValue))); + break; + case NS_ooxml::LN_CT_Shd_themeFillShade: + createGrabBag("themeFillShade", uno::Any(OUString::number(nIntValue, 16))); + break; + case NS_ooxml::LN_CT_Shd_themeFillTint: + createGrabBag("themeFillTint", uno::Any(OUString::number(nIntValue, 16))); + break; + case NS_ooxml::LN_CT_Shd_themeColor: + createGrabBag("themeColor", uno::Any(TDefTableHandler::getThemeColorTypeString(nIntValue))); + break; + case NS_ooxml::LN_CT_Shd_themeShade: + createGrabBag("themeShade", uno::Any(OUString::number(nIntValue, 16))); + break; + case NS_ooxml::LN_CT_Shd_themeTint: + createGrabBag("themeTint", uno::Any(OUString::number(nIntValue, 16))); + break; + default: + OSL_FAIL( "unknown attribute"); + } +} + +void CellColorHandler::lcl_sprm(Sprm &) {} + +TablePropertyMapPtr CellColorHandler::getProperties() +{ + TablePropertyMapPtr pPropertyMap(new TablePropertyMap); + + // Code from binary word filter (the values are out of 1000) + sal_Int32 nWW8BrushStyle = 0; + switch (m_nShadingPattern) + { + // Clear-Brush + case NS_ooxml::LN_Value_ST_Shd_clear: nWW8BrushStyle = 0; break; + // Solid-Brush + case NS_ooxml::LN_Value_ST_Shd_solid: nWW8BrushStyle = 1000; break; + // Percent values + case NS_ooxml::LN_Value_ST_Shd_pct5: nWW8BrushStyle = 50; break; + case NS_ooxml::LN_Value_ST_Shd_pct10: nWW8BrushStyle = 100; break; + case NS_ooxml::LN_Value_ST_Shd_pct20: nWW8BrushStyle = 200; break; + case NS_ooxml::LN_Value_ST_Shd_pct25: nWW8BrushStyle = 250; break; + case NS_ooxml::LN_Value_ST_Shd_pct30: nWW8BrushStyle = 300; break; + case NS_ooxml::LN_Value_ST_Shd_pct40: nWW8BrushStyle = 400; break; + case NS_ooxml::LN_Value_ST_Shd_pct50: nWW8BrushStyle = 500; break; + case NS_ooxml::LN_Value_ST_Shd_pct60: nWW8BrushStyle = 600; break; + case NS_ooxml::LN_Value_ST_Shd_pct70: nWW8BrushStyle = 700; break; + case NS_ooxml::LN_Value_ST_Shd_pct75: nWW8BrushStyle = 750; break; + case NS_ooxml::LN_Value_ST_Shd_pct80: nWW8BrushStyle = 800; break; + case NS_ooxml::LN_Value_ST_Shd_pct90: nWW8BrushStyle = 900; break; + // Special cases + case NS_ooxml::LN_Value_ST_Shd_horzStripe: nWW8BrushStyle = 333; break; // Dark Horizontal + case NS_ooxml::LN_Value_ST_Shd_vertStripe: nWW8BrushStyle = 333; break; // Dark Vertical + case NS_ooxml::LN_Value_ST_Shd_reverseDiagStripe: nWW8BrushStyle = 333; break; // Dark Forward Diagonal + case NS_ooxml::LN_Value_ST_Shd_diagStripe: nWW8BrushStyle = 333; break; // Dark Backward Diagonal + case NS_ooxml::LN_Value_ST_Shd_horzCross: nWW8BrushStyle = 333; break; // Dark Cross + case NS_ooxml::LN_Value_ST_Shd_diagCross: nWW8BrushStyle = 333; break; // Dark Diagonal Cross + case NS_ooxml::LN_Value_ST_Shd_thinHorzStripe: nWW8BrushStyle = 333; break; // Horizontal + case NS_ooxml::LN_Value_ST_Shd_thinVertStripe: nWW8BrushStyle = 333; break; // Vertical + case NS_ooxml::LN_Value_ST_Shd_thinReverseDiagStripe: nWW8BrushStyle = 333; break; // Forward Diagonal + case NS_ooxml::LN_Value_ST_Shd_thinDiagStripe: nWW8BrushStyle = 333; break; // Backward Diagonal + case NS_ooxml::LN_Value_ST_Shd_thinHorzCross: nWW8BrushStyle = 333; break; // Cross + case NS_ooxml::LN_Value_ST_Shd_thinDiagCross: nWW8BrushStyle = 333; break; // 25 Diagonal Cross + // Different shading types + case NS_ooxml::LN_Value_ST_Shd_pct12: nWW8BrushStyle = 125; break; + case NS_ooxml::LN_Value_ST_Shd_pct15: nWW8BrushStyle = 150; break; + case NS_ooxml::LN_Value_ST_Shd_pct35: nWW8BrushStyle = 350; break; + case NS_ooxml::LN_Value_ST_Shd_pct37: nWW8BrushStyle = 375; break; + case NS_ooxml::LN_Value_ST_Shd_pct45: nWW8BrushStyle = 450; break; + case NS_ooxml::LN_Value_ST_Shd_pct55: nWW8BrushStyle = 550; break; + case NS_ooxml::LN_Value_ST_Shd_pct62: nWW8BrushStyle = 625; break; + case NS_ooxml::LN_Value_ST_Shd_pct65: nWW8BrushStyle = 650; break; + case NS_ooxml::LN_Value_ST_Shd_pct85: nWW8BrushStyle = 850; break; + case NS_ooxml::LN_Value_ST_Shd_pct87: nWW8BrushStyle = 875; break; + case NS_ooxml::LN_Value_ST_Shd_pct95: nWW8BrushStyle = 950; break; + } + + sal_Int32 nApplyColor = 0; + if( !nWW8BrushStyle ) + { + // Clear-Brush + if ( m_bFillSpecified && m_bAutoFillColor ) + nApplyColor = sal_Int32(COL_AUTO); + else + nApplyColor = m_nFillColor; + } + else + { + sal_Int32 nFore = m_nColor; + sal_Int32 nBack = m_nFillColor; + + sal_uInt32 nRed = ((nFore & 0xff0000)>>0x10) * nWW8BrushStyle; + sal_uInt32 nGreen = ((nFore & 0xff00)>>0x8) * nWW8BrushStyle; + sal_uInt32 nBlue = (nFore & 0xff) * nWW8BrushStyle; + nRed += ((nBack & 0xff0000)>>0x10) * (1000L - nWW8BrushStyle); + nGreen += ((nBack & 0xff00)>>0x8)* (1000L - nWW8BrushStyle); + nBlue += (nBack & 0xff) * (1000L - nWW8BrushStyle); + + nApplyColor = ( (nRed/1000) << 0x10 ) + ((nGreen/1000) << 8) + nBlue/1000; + } + + // Check if it is a 'Character' + if (m_OutputFormat == Character) + { + sal_Int32 nShadingPattern = drawing::ShadingPattern::CLEAR; + switch (m_nShadingPattern) + { + case NS_ooxml::LN_Value_ST_Shd_clear: nShadingPattern = drawing::ShadingPattern::CLEAR; break; + case NS_ooxml::LN_Value_ST_Shd_solid: nShadingPattern = drawing::ShadingPattern::SOLID; break; + case NS_ooxml::LN_Value_ST_Shd_pct5: nShadingPattern = drawing::ShadingPattern::PCT5; break; + case NS_ooxml::LN_Value_ST_Shd_pct10: nShadingPattern = drawing::ShadingPattern::PCT10; break; + case NS_ooxml::LN_Value_ST_Shd_pct20: nShadingPattern = drawing::ShadingPattern::PCT20; break; + case NS_ooxml::LN_Value_ST_Shd_pct25: nShadingPattern = drawing::ShadingPattern::PCT25; break; + case NS_ooxml::LN_Value_ST_Shd_pct30: nShadingPattern = drawing::ShadingPattern::PCT30; break; + case NS_ooxml::LN_Value_ST_Shd_pct40: nShadingPattern = drawing::ShadingPattern::PCT40; break; + case NS_ooxml::LN_Value_ST_Shd_pct50: nShadingPattern = drawing::ShadingPattern::PCT50; break; + case NS_ooxml::LN_Value_ST_Shd_pct60: nShadingPattern = drawing::ShadingPattern::PCT60; break; + case NS_ooxml::LN_Value_ST_Shd_pct70: nShadingPattern = drawing::ShadingPattern::PCT70; break; + case NS_ooxml::LN_Value_ST_Shd_pct75: nShadingPattern = drawing::ShadingPattern::PCT75; break; + case NS_ooxml::LN_Value_ST_Shd_pct80: nShadingPattern = drawing::ShadingPattern::PCT80; break; + case NS_ooxml::LN_Value_ST_Shd_pct90: nShadingPattern = drawing::ShadingPattern::PCT90; break; + case NS_ooxml::LN_Value_ST_Shd_horzStripe: nShadingPattern = drawing::ShadingPattern::HORZ_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_vertStripe: nShadingPattern = drawing::ShadingPattern::VERT_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_reverseDiagStripe: nShadingPattern = drawing::ShadingPattern::REVERSE_DIAG_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_diagStripe: nShadingPattern = drawing::ShadingPattern::DIAG_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_horzCross: nShadingPattern = drawing::ShadingPattern::HORZ_CROSS; break; + case NS_ooxml::LN_Value_ST_Shd_diagCross: nShadingPattern = drawing::ShadingPattern::DIAG_CROSS; break; + case NS_ooxml::LN_Value_ST_Shd_thinHorzStripe: nShadingPattern = drawing::ShadingPattern::THIN_HORZ_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_thinVertStripe: nShadingPattern = drawing::ShadingPattern::THIN_VERT_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_thinReverseDiagStripe: nShadingPattern = drawing::ShadingPattern::THIN_REVERSE_DIAG_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_thinDiagStripe: nShadingPattern = drawing::ShadingPattern::THIN_DIAG_STRIPE; break; + case NS_ooxml::LN_Value_ST_Shd_thinHorzCross: nShadingPattern = drawing::ShadingPattern::THIN_HORZ_CROSS; break; + case NS_ooxml::LN_Value_ST_Shd_thinDiagCross: nShadingPattern = drawing::ShadingPattern::THIN_DIAG_CROSS; break; + case NS_ooxml::LN_Value_ST_Shd_pct12: nShadingPattern = drawing::ShadingPattern::PCT12; break; + case NS_ooxml::LN_Value_ST_Shd_pct15: nShadingPattern = drawing::ShadingPattern::PCT15; break; + case NS_ooxml::LN_Value_ST_Shd_pct35: nShadingPattern = drawing::ShadingPattern::PCT35; break; + case NS_ooxml::LN_Value_ST_Shd_pct37: nShadingPattern = drawing::ShadingPattern::PCT37; break; + case NS_ooxml::LN_Value_ST_Shd_pct45: nShadingPattern = drawing::ShadingPattern::PCT45; break; + case NS_ooxml::LN_Value_ST_Shd_pct55: nShadingPattern = drawing::ShadingPattern::PCT55; break; + case NS_ooxml::LN_Value_ST_Shd_pct62: nShadingPattern = drawing::ShadingPattern::PCT62; break; + case NS_ooxml::LN_Value_ST_Shd_pct65: nShadingPattern = drawing::ShadingPattern::PCT65; break; + case NS_ooxml::LN_Value_ST_Shd_pct85: nShadingPattern = drawing::ShadingPattern::PCT85; break; + case NS_ooxml::LN_Value_ST_Shd_pct87: nShadingPattern = drawing::ShadingPattern::PCT87; break; + case NS_ooxml::LN_Value_ST_Shd_pct95: nShadingPattern = drawing::ShadingPattern::PCT95; break; + } + + // Write the shading pattern property + pPropertyMap->Insert(PROP_CHAR_SHADING_VALUE, uno::Any( nShadingPattern )); + } + + if (m_OutputFormat == Paragraph && m_nShadingPattern != NS_ooxml::LN_Value_ST_Shd_nil) + { + if (nWW8BrushStyle || !m_bAutoFillColor) + pPropertyMap->Insert(PROP_FILL_STYLE, uno::Any(drawing::FillStyle_SOLID)); + else if (m_bFillSpecified) // m_bAutoFillColor == true + pPropertyMap->Insert(PROP_FILL_STYLE, uno::Any(drawing::FillStyle_NONE)); + + pPropertyMap->Insert(PROP_FILL_COLOR, uno::Any(nApplyColor)); + } + else if ( nWW8BrushStyle || !m_bAutoFillColor || m_bFillSpecified ) + pPropertyMap->Insert( m_OutputFormat == Form ? PROP_BACK_COLOR + : PROP_CHAR_BACK_COLOR, uno::Any( nApplyColor )); + + createGrabBag("originalColor", uno::Any(msfilter::util::ConvertColorOU(Color(ColorTransparency, nApplyColor)))); + + return pPropertyMap; +} + +void CellColorHandler::createGrabBag(const OUString& aName, const uno::Any& rAny) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + + beans::PropertyValue aValue; + aValue.Name = aName; + aValue.Value = rAny; + m_aInteropGrabBag.push_back(aValue); +} + +void CellColorHandler::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +beans::PropertyValue CellColorHandler::getInteropGrabBag() +{ + beans::PropertyValue aRet; + aRet.Name = m_aInteropGrabBagName; + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + return aRet; +} + +void CellColorHandler::disableInteropGrabBag() +{ + m_aInteropGrabBagName.clear(); + m_aInteropGrabBag.clear(); +} + +bool CellColorHandler::isInteropGrabBagEnabled() const +{ + return !(m_aInteropGrabBagName.isEmpty()); +} + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/CellColorHandler.hxx b/writerfilter/source/dmapper/CellColorHandler.hxx new file mode 100644 index 000000000..a011cd9fd --- /dev/null +++ b/writerfilter/source/dmapper/CellColorHandler.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include "PropertyMap.hxx" +#include <vector> + +#include <com/sun/star/beans/PropertyValue.hpp> + +namespace writerfilter::dmapper +{ +class TablePropertyMap; +class CellColorHandler : public LoggedProperties +{ +public: + enum OutputFormat { Form, Paragraph, Character }; // for what part of the document +private: + sal_Int32 m_nShadingPattern; + sal_Int32 m_nColor; + sal_Int32 m_nFillColor; + bool m_bAutoFillColor; + bool m_bFillSpecified; + OutputFormat m_OutputFormat; + + OUString m_aInteropGrabBagName; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + + void createGrabBag(const OUString& aName, const css::uno::Any& rValue); + +public: + CellColorHandler( ); + virtual ~CellColorHandler() override; + + TablePropertyMapPtr getProperties(); + + void setOutputFormat( OutputFormat format ) { m_OutputFormat = format; } + + void enableInteropGrabBag(const OUString& aName); + css::beans::PropertyValue getInteropGrabBag(); + void disableInteropGrabBag(); + bool isInteropGrabBagEnabled() const; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/CellMarginHandler.cxx b/writerfilter/source/dmapper/CellMarginHandler.cxx new file mode 100644 index 000000000..8b7b5fa77 --- /dev/null +++ b/writerfilter/source/dmapper/CellMarginHandler.cxx @@ -0,0 +1,177 @@ +/* -*- 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 "CellMarginHandler.hxx" +#include "ConversionHelper.hxx" +#include <ooxml/resourceids.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; +using namespace ::writerfilter; + +CellMarginHandler::CellMarginHandler() : +LoggedProperties("CellMarginHandler"), +m_nValue( 0 ), +m_nWidth( 0 ), +m_nType( 0 ), +m_nLeftMargin( 0 ), +m_bLeftMarginValid( false ), +m_nRightMargin( 0 ), +m_bRightMarginValid( false ), +m_nTopMargin( 0 ), +m_bTopMarginValid( false ), +m_nBottomMargin( 0 ), +m_bBottomMarginValid( false ) +{ +} + +CellMarginHandler::~CellMarginHandler() +{ +} + +void CellMarginHandler::lcl_attribute(Id rName, Value & rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch( rName ) + { + case NS_ooxml::LN_CT_TblWidth_w: + m_nWidth = nIntValue; + m_nValue = ConversionHelper::convertTwipToMM100Unsigned( nIntValue ); + break; + case NS_ooxml::LN_CT_TblWidth_type: + SAL_WARN_IF(NS_ooxml::LN_Value_ST_TblWidth_dxa != sal::static_int_cast<Id>(nIntValue), "writerfilter", "CellMarginHandler: cell margins work for absolute values only"); + m_nType = nIntValue; + break; + default: + SAL_WARN("writerfilter", "CellMarginHandler::lcl_attribute: unknown attribute"); + } +} + +void CellMarginHandler::createGrabBag(const OUString& aName) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + + beans::PropertyValue aRet; + aRet.Name = aName; + + OUString sType; + switch (m_nType) + { + case NS_ooxml::LN_Value_ST_TblWidth_nil: sType = "nil"; break; + case NS_ooxml::LN_Value_ST_TblWidth_pct: sType = "pct"; break; + case NS_ooxml::LN_Value_ST_TblWidth_dxa: sType = "dxa"; break; + case NS_ooxml::LN_Value_ST_TblWidth_auto: sType = "auto"; break; + } + uno::Sequence<beans::PropertyValue> aSeq( comphelper::InitPropertySequence({ + { "w", uno::Any(m_nWidth) }, + { "type", uno::Any(sType) } + })); + + aRet.Value <<= aSeq; + m_aInteropGrabBag.push_back(aRet); +} + +void CellMarginHandler::lcl_sprm(Sprm & rSprm) +{ + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties) + { + pProperties->resolve( *this ); + const bool rtl = false; // TODO + switch( rSprm.getId() ) + { + case NS_ooxml::LN_CT_TblCellMar_top: + case NS_ooxml::LN_CT_TcMar_top: + m_nTopMargin = m_nValue; + m_bTopMarginValid = true; + createGrabBag("top"); + break; + case NS_ooxml::LN_CT_TblCellMar_start: + case NS_ooxml::LN_CT_TcMar_start: + if( rtl ) + { + m_nRightMargin = m_nValue; + m_bRightMarginValid = true; + } + else + { + m_nLeftMargin = m_nValue; + m_bLeftMarginValid = true; + } + createGrabBag("start"); + break; + case NS_ooxml::LN_CT_TblCellMar_left: + case NS_ooxml::LN_CT_TcMar_left: + m_nLeftMargin = m_nValue; + m_bLeftMarginValid = true; + createGrabBag("left"); + break; + case NS_ooxml::LN_CT_TblCellMar_bottom: + case NS_ooxml::LN_CT_TcMar_bottom: + m_nBottomMargin = m_nValue; + m_bBottomMarginValid = true; + createGrabBag("bottom"); + break; + case NS_ooxml::LN_CT_TblCellMar_end: + case NS_ooxml::LN_CT_TcMar_end: + if( rtl ) + { + m_nLeftMargin = m_nValue; + m_bLeftMarginValid = true; + } + else + { + m_nRightMargin = m_nValue; + m_bRightMarginValid = true; + } + createGrabBag("end"); + break; + case NS_ooxml::LN_CT_TblCellMar_right: + case NS_ooxml::LN_CT_TcMar_right: + m_nRightMargin = m_nValue; + m_bRightMarginValid = true; + createGrabBag("right"); + break; + default: + SAL_WARN("writerfilter", "CellMarginHandler::lcl_sprm: unknown sprm"); + } + } + m_nValue = 0; +} + +void CellMarginHandler::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +beans::PropertyValue CellMarginHandler::getInteropGrabBag() +{ + beans::PropertyValue aRet; + aRet.Name = m_aInteropGrabBagName; + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + return aRet; +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/CellMarginHandler.hxx b/writerfilter/source/dmapper/CellMarginHandler.hxx new file mode 100644 index 000000000..8dcbf2dc5 --- /dev/null +++ b/writerfilter/source/dmapper/CellMarginHandler.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include <vector> +#include <com/sun/star/beans/PropertyValue.hpp> + +namespace writerfilter::dmapper +{ +class TablePropertyMap; +class CellMarginHandler : public LoggedProperties +{ +private: + sal_Int32 m_nValue; ///< Converted value. + sal_Int32 m_nWidth; ///< Original value. + sal_Int32 m_nType; ///< Unit of the value (dxa, etc). + + OUString m_aInteropGrabBagName; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + + void createGrabBag(const OUString& aName); + +public: + sal_Int32 m_nLeftMargin; + bool m_bLeftMarginValid; + sal_Int32 m_nRightMargin; + bool m_bRightMarginValid; + sal_Int32 m_nTopMargin; + bool m_bTopMarginValid; + sal_Int32 m_nBottomMargin; + bool m_bBottomMarginValid; + +public: + CellMarginHandler( ); + virtual ~CellMarginHandler() override; + + void enableInteropGrabBag(const OUString& aName); + css::beans::PropertyValue getInteropGrabBag(); + +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ConversionHelper.cxx b/writerfilter/source/dmapper/ConversionHelper.cxx new file mode 100644 index 000000000..5c3b0831f --- /dev/null +++ b/writerfilter/source/dmapper/ConversionHelper.cxx @@ -0,0 +1,691 @@ +/* -*- 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 "ConversionHelper.hxx" +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <editeng/borderline.hxx> +#include <ooxml/resourceids.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/color.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper::ConversionHelper{ + +/// Convert OOXML border style to WW8 that editeng can handle. +static sal_Int32 lcl_convertBorderStyleFromToken(sal_Int32 nOOXMLType) +{ + switch (nOOXMLType) + { + case NS_ooxml::LN_Value_ST_Border_nil: return 255; + case NS_ooxml::LN_Value_ST_Border_none: return 0; + case NS_ooxml::LN_Value_ST_Border_single: return 1; + case NS_ooxml::LN_Value_ST_Border_thick: return 2; + case NS_ooxml::LN_Value_ST_Border_double: return 3; + case NS_ooxml::LN_Value_ST_Border_dotted: return 6; + case NS_ooxml::LN_Value_ST_Border_dashed: return 7; + case NS_ooxml::LN_Value_ST_Border_dotDash: return 8; + case NS_ooxml::LN_Value_ST_Border_dotDotDash: return 9; + case NS_ooxml::LN_Value_ST_Border_triple: return 10; + case NS_ooxml::LN_Value_ST_Border_thinThickSmallGap: return 11; + case NS_ooxml::LN_Value_ST_Border_thickThinSmallGap: return 12; + case NS_ooxml::LN_Value_ST_Border_thinThickThinSmallGap: return 13; + case NS_ooxml::LN_Value_ST_Border_thinThickMediumGap: return 14; + case NS_ooxml::LN_Value_ST_Border_thickThinMediumGap: return 15; + case NS_ooxml::LN_Value_ST_Border_thinThickThinMediumGap: return 16; + case NS_ooxml::LN_Value_ST_Border_thinThickLargeGap: return 17; + case NS_ooxml::LN_Value_ST_Border_thickThinLargeGap: return 18; + case NS_ooxml::LN_Value_ST_Border_thinThickThinLargeGap: return 19; + case NS_ooxml::LN_Value_ST_Border_wave: return 20; + case NS_ooxml::LN_Value_ST_Border_doubleWave: return 21; + case NS_ooxml::LN_Value_ST_Border_dashSmallGap: return 22; + case NS_ooxml::LN_Value_ST_Border_dashDotStroked: return 23; + case NS_ooxml::LN_Value_ST_Border_threeDEmboss: return 24; + case NS_ooxml::LN_Value_ST_Border_threeDEngrave: return 25; + case NS_ooxml::LN_Value_ST_Border_outset: return 26; + case NS_ooxml::LN_Value_ST_Border_inset: return 27; + case NS_ooxml::LN_Value_ST_Border_apples: return 64; + case NS_ooxml::LN_Value_ST_Border_archedScallops: return 65; + case NS_ooxml::LN_Value_ST_Border_babyPacifier: return 66; + case NS_ooxml::LN_Value_ST_Border_babyRattle: return 67; + case NS_ooxml::LN_Value_ST_Border_balloons3Colors: return 68; + case NS_ooxml::LN_Value_ST_Border_balloonsHotAir: return 69; + case NS_ooxml::LN_Value_ST_Border_basicBlackDashes: return 70; + case NS_ooxml::LN_Value_ST_Border_basicBlackDots: return 71; + case NS_ooxml::LN_Value_ST_Border_basicBlackSquares: return 72; + case NS_ooxml::LN_Value_ST_Border_basicThinLines: return 73; + case NS_ooxml::LN_Value_ST_Border_basicWhiteDashes: return 74; + case NS_ooxml::LN_Value_ST_Border_basicWhiteDots: return 75; + case NS_ooxml::LN_Value_ST_Border_basicWhiteSquares: return 76; + case NS_ooxml::LN_Value_ST_Border_basicWideInline: return 77; + case NS_ooxml::LN_Value_ST_Border_basicWideMidline: return 78; + case NS_ooxml::LN_Value_ST_Border_basicWideOutline: return 79; + case NS_ooxml::LN_Value_ST_Border_bats: return 80; + case NS_ooxml::LN_Value_ST_Border_birds: return 81; + case NS_ooxml::LN_Value_ST_Border_birdsFlight: return 82; + case NS_ooxml::LN_Value_ST_Border_cabins: return 83; + case NS_ooxml::LN_Value_ST_Border_cakeSlice: return 84; + case NS_ooxml::LN_Value_ST_Border_candyCorn: return 85; + case NS_ooxml::LN_Value_ST_Border_celticKnotwork: return 86; + case NS_ooxml::LN_Value_ST_Border_certificateBanner: return 87; + case NS_ooxml::LN_Value_ST_Border_chainLink: return 88; + case NS_ooxml::LN_Value_ST_Border_champagneBottle: return 89; + case NS_ooxml::LN_Value_ST_Border_checkedBarBlack: return 90; + case NS_ooxml::LN_Value_ST_Border_checkedBarColor: return 91; + case NS_ooxml::LN_Value_ST_Border_checkered: return 92; + case NS_ooxml::LN_Value_ST_Border_christmasTree: return 93; + case NS_ooxml::LN_Value_ST_Border_circlesLines: return 94; + case NS_ooxml::LN_Value_ST_Border_circlesRectangles: return 95; + case NS_ooxml::LN_Value_ST_Border_classicalWave: return 96; + case NS_ooxml::LN_Value_ST_Border_clocks: return 97; + case NS_ooxml::LN_Value_ST_Border_compass: return 98; + case NS_ooxml::LN_Value_ST_Border_confetti: return 99; + case NS_ooxml::LN_Value_ST_Border_confettiGrays: return 100; + case NS_ooxml::LN_Value_ST_Border_confettiOutline: return 101; + case NS_ooxml::LN_Value_ST_Border_confettiStreamers: return 102; + case NS_ooxml::LN_Value_ST_Border_confettiWhite: return 103; + case NS_ooxml::LN_Value_ST_Border_cornerTriangles: return 104; + case NS_ooxml::LN_Value_ST_Border_couponCutoutDashes: return 105; + case NS_ooxml::LN_Value_ST_Border_couponCutoutDots: return 106; + case NS_ooxml::LN_Value_ST_Border_crazyMaze: return 107; + case NS_ooxml::LN_Value_ST_Border_creaturesButterfly: return 108; + case NS_ooxml::LN_Value_ST_Border_creaturesFish: return 109; + case NS_ooxml::LN_Value_ST_Border_creaturesInsects: return 110; + case NS_ooxml::LN_Value_ST_Border_creaturesLadyBug: return 111; + case NS_ooxml::LN_Value_ST_Border_crossStitch: return 112; + case NS_ooxml::LN_Value_ST_Border_cup: return 113; + case NS_ooxml::LN_Value_ST_Border_decoArch: return 114; + case NS_ooxml::LN_Value_ST_Border_decoArchColor: return 115; + case NS_ooxml::LN_Value_ST_Border_decoBlocks: return 116; + case NS_ooxml::LN_Value_ST_Border_diamondsGray: return 117; + case NS_ooxml::LN_Value_ST_Border_doubleD: return 118; + case NS_ooxml::LN_Value_ST_Border_doubleDiamonds: return 119; + case NS_ooxml::LN_Value_ST_Border_earth1: return 120; + case NS_ooxml::LN_Value_ST_Border_earth2: return 121; + case NS_ooxml::LN_Value_ST_Border_eclipsingSquares1: return 122; + case NS_ooxml::LN_Value_ST_Border_eclipsingSquares2: return 123; + case NS_ooxml::LN_Value_ST_Border_eggsBlack: return 124; + case NS_ooxml::LN_Value_ST_Border_fans: return 125; + case NS_ooxml::LN_Value_ST_Border_film: return 126; + case NS_ooxml::LN_Value_ST_Border_firecrackers: return 127; + case NS_ooxml::LN_Value_ST_Border_flowersBlockPrint: return 128; + case NS_ooxml::LN_Value_ST_Border_flowersDaisies: return 129; + case NS_ooxml::LN_Value_ST_Border_flowersModern1: return 130; + case NS_ooxml::LN_Value_ST_Border_flowersModern2: return 131; + case NS_ooxml::LN_Value_ST_Border_flowersPansy: return 132; + case NS_ooxml::LN_Value_ST_Border_flowersRedRose: return 133; + case NS_ooxml::LN_Value_ST_Border_flowersRoses: return 134; + case NS_ooxml::LN_Value_ST_Border_flowersTeacup: return 135; + case NS_ooxml::LN_Value_ST_Border_flowersTiny: return 136; + case NS_ooxml::LN_Value_ST_Border_gems: return 137; + case NS_ooxml::LN_Value_ST_Border_gingerbreadMan: return 138; + case NS_ooxml::LN_Value_ST_Border_gradient: return 139; + case NS_ooxml::LN_Value_ST_Border_handmade1: return 140; + case NS_ooxml::LN_Value_ST_Border_handmade2: return 141; + case NS_ooxml::LN_Value_ST_Border_heartBalloon: return 142; + case NS_ooxml::LN_Value_ST_Border_heartGray: return 143; + case NS_ooxml::LN_Value_ST_Border_hearts: return 144; + case NS_ooxml::LN_Value_ST_Border_heebieJeebies: return 145; + case NS_ooxml::LN_Value_ST_Border_holly: return 146; + case NS_ooxml::LN_Value_ST_Border_houseFunky: return 147; + case NS_ooxml::LN_Value_ST_Border_hypnotic: return 148; + case NS_ooxml::LN_Value_ST_Border_iceCreamCones: return 149; + case NS_ooxml::LN_Value_ST_Border_lightBulb: return 150; + case NS_ooxml::LN_Value_ST_Border_lightning1: return 151; + case NS_ooxml::LN_Value_ST_Border_lightning2: return 152; + case NS_ooxml::LN_Value_ST_Border_mapPins: return 153; + case NS_ooxml::LN_Value_ST_Border_mapleLeaf: return 154; + case NS_ooxml::LN_Value_ST_Border_mapleMuffins: return 155; + case NS_ooxml::LN_Value_ST_Border_marquee: return 156; + case NS_ooxml::LN_Value_ST_Border_marqueeToothed: return 157; + case NS_ooxml::LN_Value_ST_Border_moons: return 158; + case NS_ooxml::LN_Value_ST_Border_mosaic: return 159; + case NS_ooxml::LN_Value_ST_Border_musicNotes: return 160; + case NS_ooxml::LN_Value_ST_Border_northwest: return 161; + case NS_ooxml::LN_Value_ST_Border_ovals: return 162; + case NS_ooxml::LN_Value_ST_Border_packages: return 163; + case NS_ooxml::LN_Value_ST_Border_palmsBlack: return 164; + case NS_ooxml::LN_Value_ST_Border_palmsColor: return 165; + case NS_ooxml::LN_Value_ST_Border_paperClips: return 166; + case NS_ooxml::LN_Value_ST_Border_papyrus: return 167; + case NS_ooxml::LN_Value_ST_Border_partyFavor: return 168; + case NS_ooxml::LN_Value_ST_Border_partyGlass: return 169; + case NS_ooxml::LN_Value_ST_Border_pencils: return 170; + case NS_ooxml::LN_Value_ST_Border_people: return 171; + case NS_ooxml::LN_Value_ST_Border_peopleWaving: return 172; + case NS_ooxml::LN_Value_ST_Border_peopleHats: return 173; + case NS_ooxml::LN_Value_ST_Border_poinsettias: return 174; + case NS_ooxml::LN_Value_ST_Border_postageStamp: return 175; + case NS_ooxml::LN_Value_ST_Border_pumpkin1: return 176; + case NS_ooxml::LN_Value_ST_Border_pushPinNote2: return 177; + case NS_ooxml::LN_Value_ST_Border_pushPinNote1: return 178; + case NS_ooxml::LN_Value_ST_Border_pyramids: return 179; + case NS_ooxml::LN_Value_ST_Border_pyramidsAbove: return 180; + case NS_ooxml::LN_Value_ST_Border_quadrants: return 181; + case NS_ooxml::LN_Value_ST_Border_rings: return 182; + case NS_ooxml::LN_Value_ST_Border_safari: return 183; + case NS_ooxml::LN_Value_ST_Border_sawtooth: return 184; + case NS_ooxml::LN_Value_ST_Border_sawtoothGray: return 185; + case NS_ooxml::LN_Value_ST_Border_scaredCat: return 186; + case NS_ooxml::LN_Value_ST_Border_seattle: return 187; + case NS_ooxml::LN_Value_ST_Border_shadowedSquares: return 188; + case NS_ooxml::LN_Value_ST_Border_sharksTeeth: return 189; + case NS_ooxml::LN_Value_ST_Border_shorebirdTracks: return 190; + case NS_ooxml::LN_Value_ST_Border_skyrocket: return 191; + case NS_ooxml::LN_Value_ST_Border_snowflakeFancy: return 192; + case NS_ooxml::LN_Value_ST_Border_snowflakes: return 193; + case NS_ooxml::LN_Value_ST_Border_sombrero: return 194; + case NS_ooxml::LN_Value_ST_Border_southwest: return 195; + case NS_ooxml::LN_Value_ST_Border_stars: return 196; + case NS_ooxml::LN_Value_ST_Border_starsTop: return 197; + case NS_ooxml::LN_Value_ST_Border_stars3d: return 198; + case NS_ooxml::LN_Value_ST_Border_starsBlack: return 199; + case NS_ooxml::LN_Value_ST_Border_starsShadowed: return 200; + case NS_ooxml::LN_Value_ST_Border_sun: return 201; + case NS_ooxml::LN_Value_ST_Border_swirligig: return 202; + case NS_ooxml::LN_Value_ST_Border_tornPaper: return 203; + case NS_ooxml::LN_Value_ST_Border_tornPaperBlack: return 204; + case NS_ooxml::LN_Value_ST_Border_trees: return 205; + case NS_ooxml::LN_Value_ST_Border_triangleParty: return 206; + case NS_ooxml::LN_Value_ST_Border_triangles: return 207; + case NS_ooxml::LN_Value_ST_Border_tribal1: return 208; + case NS_ooxml::LN_Value_ST_Border_tribal2: return 209; + case NS_ooxml::LN_Value_ST_Border_tribal3: return 210; + case NS_ooxml::LN_Value_ST_Border_tribal4: return 211; + case NS_ooxml::LN_Value_ST_Border_tribal5: return 212; + case NS_ooxml::LN_Value_ST_Border_tribal6: return 213; + case NS_ooxml::LN_Value_ST_Border_twistedLines1: return 214; + case NS_ooxml::LN_Value_ST_Border_twistedLines2: return 215; + case NS_ooxml::LN_Value_ST_Border_vine: return 216; + case NS_ooxml::LN_Value_ST_Border_waveline: return 217; + case NS_ooxml::LN_Value_ST_Border_weavingAngles: return 218; + case NS_ooxml::LN_Value_ST_Border_weavingBraid: return 219; + case NS_ooxml::LN_Value_ST_Border_weavingRibbon: return 220; + case NS_ooxml::LN_Value_ST_Border_weavingStrips: return 221; + case NS_ooxml::LN_Value_ST_Border_whiteFlowers: return 222; + case NS_ooxml::LN_Value_ST_Border_woodwork: return 223; + case NS_ooxml::LN_Value_ST_Border_xIllusions: return 224; + case NS_ooxml::LN_Value_ST_Border_zanyTriangles: return 225; + case NS_ooxml::LN_Value_ST_Border_zigZag: return 226; + case NS_ooxml::LN_Value_ST_Border_zigZagStitch: return 227; + default: break; + } + return 0; +} + +void MakeBorderLine( sal_Int32 nLineThickness, sal_Int32 nLineToken, + sal_Int32 nLineColor, + table::BorderLine2& rToFill, bool bIsOOXML ) +{ + static const Color aBorderDefColor[] = + { + // The first item means automatic color (COL_AUTO), but we + // do not use it anyway (see the next statement) .-) + // See also GetLineIndex in sw/source/filter/ww8/ww8par6.cxx + COL_AUTO, COL_BLACK, COL_LIGHTBLUE, COL_LIGHTCYAN, COL_LIGHTGREEN, + COL_LIGHTMAGENTA, COL_LIGHTRED, COL_YELLOW, COL_WHITE, COL_BLUE, + COL_CYAN, COL_GREEN, COL_MAGENTA, COL_RED, COL_BROWN, COL_GRAY, + COL_LIGHTGRAY + }; + if(!bIsOOXML && sal::static_int_cast<sal_uInt32>(nLineColor) < SAL_N_ELEMENTS(aBorderDefColor)) + nLineColor = sal_Int32(aBorderDefColor[nLineColor]); + //no auto color for borders + if (nLineColor == sal_Int32(COL_AUTO)) + nLineColor = sal_Int32(COL_BLACK); + + sal_Int32 nLineType = lcl_convertBorderStyleFromToken(nLineToken); + + // Map to our border types, we should use of one equal line + // thickness, or one of smaller thickness. If too small we + // can make the deficit up in additional white space or + // object size + SvxBorderLineStyle const nLineStyle( + ::editeng::ConvertBorderStyleFromWord(nLineType)); + rToFill.LineStyle = static_cast<sal_Int16>(nLineStyle); + double const fConverted( (SvxBorderLineStyle::NONE == nLineStyle) ? 0.0 : + ::editeng::ConvertBorderWidthFromWord(nLineStyle, nLineThickness, + nLineType)); + rToFill.LineWidth = convertTwipToMM100(fConverted); + rToFill.Color = nLineColor; +} + +namespace { +void lcl_SwapQuotesInField(OUString &rFmt) +{ + //Swap unescaped " and ' with ' and " + sal_Int32 nLen = rFmt.getLength(); + OUStringBuffer aBuffer( rFmt ); + const sal_Unicode* pFmt = rFmt.getStr(); + for (sal_Int32 nI = 0; nI < nLen; ++nI) + { + if ((pFmt[nI] == '\"') && (!nI || pFmt[nI-1] != '\\')) + aBuffer[nI] = '\''; + else if ((pFmt[nI] == '\'') && (!nI || pFmt[nI-1] != '\\')) + aBuffer[nI] = '\"'; + } + rFmt = aBuffer.makeStringAndClear(); +} +bool lcl_IsNotAM(OUString const & rFmt, sal_Int32 nPos) +{ + return ( + (nPos == rFmt.getLength() - 1) || + ( + (rFmt[nPos+1] != 'M') && + (rFmt[nPos+1] != 'm') + ) + ); +} +bool IsPreviousAM(std::u16string_view rParams, sal_Int32 nPos) +{ + return nPos >= 2 && o3tl::matchIgnoreAsciiCase(rParams, u"am", nPos - 2); +} +bool IsNextPM(std::u16string_view rParams, sal_Int32 nPos) +{ + return o3tl::make_unsigned(nPos + 2) < rParams.size() && o3tl::matchIgnoreAsciiCase(rParams, u"pm", nPos + 1); +} +} + +// See also sw::ms::MSDateTimeFormatToSwFormat +OUString ConvertMSFormatStringToSO( + const OUString& rFormat, lang::Locale& rLocale, bool bHijri) +{ + OUString sFormat(rFormat); + lcl_SwapQuotesInField(sFormat); + + //#102782#, #102815#, #108341# & #111944# have to work at the same time :-) + bool bForceJapanese(false); + bool bForceNatNum(false); + const sal_Int32 nLen = sFormat.getLength(); + sal_Int32 nI = 0; + sal_Int32 nAddedChars = 0; +// const sal_Unicode* pFormat = sFormat.getStr(); + OUStringBuffer aNewFormat( sFormat ); + while (nI < nLen) + { + if (sFormat[nI] == '\\') + ++nI; + else if (sFormat[nI] == '\"') + { + ++nI; + //While not at the end and not at an unescaped end quote + while ((nI < nLen) && ((sFormat[nI] != '\"') && (sFormat[nI-1] != '\\'))) + ++nI; + } + else //normal unquoted section + { + sal_Unicode nChar = sFormat[nI]; + if (nChar == 'O') + { + aNewFormat[nI + nAddedChars] = 'M'; + bForceNatNum = true; + } + else if (nChar == 'o') + { + aNewFormat[nI + nAddedChars] = 'm'; + bForceNatNum = true; + } + else if ((nChar == 'A') && lcl_IsNotAM(sFormat, nI)) + { + aNewFormat[nI + nAddedChars] = 'D'; + bForceNatNum = true; + } + else if ((nChar == 'g') || (nChar == 'G')) + bForceJapanese = true; + else if ((nChar == 'a') && lcl_IsNotAM(sFormat, nI)) + bForceJapanese = true; + else if (nChar == 'E') + { + if ((nI != nLen-1) && (sFormat[nI+1] == 'E')) + { + //todo: this cannot be the right way to replace a part of the string! + aNewFormat[nI + nAddedChars] = 'Y'; + aNewFormat[nI + nAddedChars + 1] = 'Y'; + aNewFormat.insert(nI + nAddedChars + 2, "YY"); + nAddedChars += 2; + ++nI; + } + bForceJapanese = true; + } + else if (nChar == 'e') + { + if ((nI != nLen-1) && (sFormat[nI+1] == 'e')) + { + //todo: this cannot be the right way to replace a part of the string! + aNewFormat[nI + nAddedChars] = 'y'; + aNewFormat[nI + nAddedChars + 1] = 'y'; + aNewFormat.insert(nI + nAddedChars + 2, "yy"); + nAddedChars += 2; + ++nI; + } + bForceJapanese = true; + } + else if (nChar == '/' && !(IsPreviousAM(sFormat, nI) && IsNextPM(sFormat, nI))) + { + // MM We have to escape '/' in case it's used as a char + //todo: this cannot be the right way to replace a part of the string! + aNewFormat[nI + nAddedChars] = '\\'; + aNewFormat.insert(nI + nAddedChars + 1, "/"); + ++nAddedChars; + ++nI; + } + } + ++nI; + } + + if (bForceNatNum) + bForceJapanese = true; + + if (bForceJapanese) + { + rLocale.Language = "ja"; + rLocale.Country = "JP"; + } + + if (bForceNatNum) + { + aNewFormat.insert( 0, "[NatNum1][$-411]"); + } + + if (bHijri) + { + aNewFormat.insert( 0, "[~hijri]"); + } + return aNewFormat.makeStringAndClear(); + +} + +sal_Int32 convertTwipToMM100(sal_Int32 _t) +{ + // It appears that MSO handles large twip values specially, probably legacy 16bit handling, + // anything that's bigger than 32767 appears to be simply ignored. + if( _t >= 0x8000 ) + return 0; + return ::convertTwipToMm100( _t ); +} + +sal_Int32 convertTwipToMM100WithoutLimit(sal_Int32 _t) +{ + return ::convertTwipToMm100(_t); +} + +double convertTwipToMM100Double(sal_Int32 _t) +{ + // It appears that MSO handles large twip values specially, probably legacy 16bit handling, + // anything that's bigger than 32767 appears to be simply ignored. + if( _t >= 0x8000 ) + return 0.0; + return o3tl::convert<double>(_t, o3tl::Length::twip, o3tl::Length::mm100); +} + +sal_uInt32 convertTwipToMM100Unsigned(sal_Int32 _t) +{ + if( _t < 0 ) + return 0; + return convertTwipToMM100( _t ); +} + +text::RubyAdjust convertRubyAlign( sal_Int32 nIntValue ) +{ + text::RubyAdjust rubyAdjust = text::RubyAdjust_LEFT; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_RubyAlign_center: + case NS_ooxml::LN_Value_ST_RubyAlign_rightVertical: + rubyAdjust = text::RubyAdjust_CENTER; + break; + case NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter: + rubyAdjust = text::RubyAdjust_BLOCK; + break; + case NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace: + rubyAdjust = text::RubyAdjust_INDENT_BLOCK; + break; + case NS_ooxml::LN_Value_ST_RubyAlign_left: + rubyAdjust = text::RubyAdjust_LEFT; + break; + case NS_ooxml::LN_Value_ST_RubyAlign_right: + rubyAdjust = text::RubyAdjust_RIGHT; + break; + } + return rubyAdjust; +} + +sal_Int16 convertTableJustification( sal_Int32 nIntValue ) +{ + sal_Int16 nOrient = text::HoriOrientation::LEFT_AND_WIDTH; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_Jc_center: + nOrient = text::HoriOrientation::CENTER; + break; + case NS_ooxml::LN_Value_ST_Jc_right: + case NS_ooxml::LN_Value_ST_Jc_end: + nOrient = text::HoriOrientation::RIGHT; + break; + case NS_ooxml::LN_Value_ST_Jc_left: + case NS_ooxml::LN_Value_ST_Jc_start: + //no break + default:; + + } + return nOrient; +} + +// Return the suggested default if the given format has no known conversion +sal_Int16 ConvertNumberingType(const sal_Int32 nFmt, const sal_Int16 nDefault) +{ + sal_Int16 nRet; + switch(nFmt) + { + case NS_ooxml::LN_Value_ST_NumberFormat_decimal: + nRet = style::NumberingType::ARABIC; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_upperRoman: + nRet = style::NumberingType::ROMAN_UPPER; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_lowerRoman: + nRet = style::NumberingType::ROMAN_LOWER; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ordinal: + nRet = style::NumberingType::TEXT_NUMBER; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_bullet: + nRet = style::NumberingType::CHAR_SPECIAL; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_none: + nRet = style::NumberingType::NUMBER_NONE; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_upperLetter: + nRet = style::NumberingType::CHARS_UPPER_LETTER_N; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_lowerLetter: + nRet = style::NumberingType::CHARS_LOWER_LETTER_N; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_iroha: + nRet = style::NumberingType::IROHA_HALFWIDTH_JA; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_irohaFullWidth: + nRet = style::NumberingType::IROHA_FULLWIDTH_JA; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_aiueo: + nRet = style::NumberingType::AIU_HALFWIDTH_JA; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_aiueoFullWidth: + nRet = style::NumberingType::AIU_FULLWIDTH_JA; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_hebrew2: + nRet = style::NumberingType::CHARS_HEBREW; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_thaiLetters: + nRet = style::NumberingType::CHARS_THAI; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_russianLower: + nRet = style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_russianUpper: + nRet = style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_decimalEnclosedCircleChinese: + case NS_ooxml::LN_Value_ST_NumberFormat_decimalEnclosedCircle: + case NS_ooxml::LN_Value_ST_NumberFormat_ideographEnclosedCircle: + nRet = style::NumberingType::CIRCLE_NUMBER; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ideographTraditional: + nRet = style::NumberingType::TIAN_GAN_ZH; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ideographZodiac: + nRet = style::NumberingType::DI_ZI_ZH; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ganada: + nRet = style::NumberingType::HANGUL_SYLLABLE_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_chosung: + nRet = style::NumberingType::HANGUL_JAMO_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_koreanLegal: + nRet = style::NumberingType::NUMBER_LEGAL_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_koreanDigital: + nRet = style::NumberingType::NUMBER_DIGITAL_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_koreanCounting: + nRet = style::NumberingType::NUMBER_HANGUL_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_koreanDigital2: + nRet = style::NumberingType::NUMBER_DIGITAL2_KO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ideographLegalTraditional: + nRet = style::NumberingType::NUMBER_UPPER_ZH_TW; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_arabicAlpha: + nRet = style::NumberingType::CHARS_ARABIC; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_arabicAbjad: + nRet = style::NumberingType::CHARS_ARABIC_ABJAD; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_hindiVowels: + nRet = style::NumberingType::CHARS_NEPALI; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_japaneseLegal: + nRet = style::NumberingType::NUMBER_TRADITIONAL_JA; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_chineseCounting: + case NS_ooxml::LN_Value_ST_NumberFormat_japaneseCounting: + case NS_ooxml::LN_Value_ST_NumberFormat_taiwaneseCounting: + case NS_ooxml::LN_Value_ST_NumberFormat_taiwaneseCountingThousand: + case NS_ooxml::LN_Value_ST_NumberFormat_ideographDigital: + case NS_ooxml::LN_Value_ST_NumberFormat_chineseCountingThousand: + nRet = style::NumberingType::NUMBER_LOWER_ZH; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_chineseLegalSimplified: + nRet = style::NumberingType::NUMBER_UPPER_ZH; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_hebrew1: + //91726 + nRet = style::NumberingType::NUMBER_HEBREW; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_decimalFullWidth: + case NS_ooxml::LN_Value_ST_NumberFormat_decimalFullWidth2: + nRet = style::NumberingType::FULLWIDTH_ARABIC; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_cardinalText: + nRet = style::NumberingType::TEXT_CARDINAL; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_ordinalText: + nRet = style::NumberingType::TEXT_ORDINAL; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_chicago: + nRet = style::NumberingType::SYMBOL_CHICAGO; + break; + case NS_ooxml::LN_Value_ST_NumberFormat_decimalZero: + nRet = style::NumberingType::ARABIC_ZERO; + break; + default: nRet = nDefault; + } +/* TODO: Lots of additional values are available - some are supported in the I18 framework + NS_ooxml::LN_Value_ST_NumberFormat_hex = 91685; + NS_ooxml::LN_Value_ST_NumberFormat_decimalHalfWidth = 91692; + NS_ooxml::LN_Value_ST_NumberFormat_japaneseDigitalTenThousand = 91694; + NS_ooxml::LN_Value_ST_NumberFormat_decimalEnclosedFullstop = 91703; + NS_ooxml::LN_Value_ST_NumberFormat_decimalEnclosedParen = 91704; + NS_ooxml::LN_Value_ST_NumberFormat_ideographZodiacTraditional = 91709; + NS_ooxml::LN_Value_ST_NumberFormat_taiwaneseDigital = 91713; + NS_ooxml::LN_Value_ST_NumberFormat_vietnameseCounting = 91721; + NS_ooxml::LN_Value_ST_NumberFormat_numberInDash = 91725; + NS_ooxml::LN_Value_ST_NumberFormat_hindiConsonants = 91731; + NS_ooxml::LN_Value_ST_NumberFormat_hindiNumbers = 91732; + NS_ooxml::LN_Value_ST_NumberFormat_hindiCounting = 91733; + NS_ooxml::LN_Value_ST_NumberFormat_thaiNumbers = 91735; + NS_ooxml::LN_Value_ST_NumberFormat_thaiCounting = 91736;*/ + return nRet; +} + +sal_Int16 ConvertCustomNumberFormat(std::u16string_view rFormat) +{ + sal_Int16 nRet = -1; + + if (rFormat == u"001, 002, 003, ...") + { + nRet = style::NumberingType::ARABIC_ZERO3; + } + else if (rFormat == u"0001, 0002, 0003, ...") + { + nRet = style::NumberingType::ARABIC_ZERO4; + } + else if (rFormat == u"00001, 00002, 00003, ...") + { + nRet = style::NumberingType::ARABIC_ZERO5; + } + + return nRet; +} + +util::DateTime ConvertDateStringToDateTime( std::u16string_view rDateTime ) +{ + util::DateTime aDateTime; + //xsd::DateTime in the format [-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] example: 2008-01-21T10:42:00Z + //OUString getToken( sal_Int32 token, sal_Unicode cTok, sal_Int32& index ) const + sal_Int32 nIndex = 0; + std::u16string_view sDate = o3tl::getToken(rDateTime, 0, 'T', nIndex ); + // HACK: this is broken according to the spec, but MSOffice always treats the time as local, + // and writes it as Z (=UTC+0) + std::u16string_view sTime = o3tl::getToken(rDateTime, 0, 'Z', nIndex ); + nIndex = 0; + aDateTime.Year = sal_uInt16( o3tl::toInt32(o3tl::getToken(sDate, 0, '-', nIndex )) ); + aDateTime.Month = sal_uInt16( o3tl::toInt32(o3tl::getToken(sDate, 0, '-', nIndex )) ); + if (nIndex != -1) + aDateTime.Day = sal_uInt16( o3tl::toInt32(sDate.substr( nIndex )) ); + + nIndex = 0; + aDateTime.Hours = sal_uInt16( o3tl::toInt32(o3tl::getToken(sTime, 0, ':', nIndex )) ); + aDateTime.Minutes = sal_uInt16( o3tl::toInt32(o3tl::getToken(sTime, 0, ':', nIndex )) ); + if (nIndex != -1) + aDateTime.Seconds = sal_uInt16( o3tl::toInt32(sTime.substr( nIndex )) ); + + return aDateTime; +} + + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ConversionHelper.hxx b/writerfilter/source/dmapper/ConversionHelper.hxx new file mode 100644 index 000000000..c14c8033e --- /dev/null +++ b/writerfilter/source/dmapper/ConversionHelper.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/RubyAdjust.hpp> + +namespace com::sun::star{ + namespace lang{ + struct Locale; + } + namespace table{ + struct BorderLine2; + } +} + +namespace writerfilter::dmapper::ConversionHelper{ + + // create a border line and return the distance value + void MakeBorderLine(sal_Int32 nLineThickness, + sal_Int32 nLineType, + sal_Int32 nLineColor, + css::table::BorderLine2& rToFill, + bool bIsOOXML); + //convert the number format string form MS format to SO format + OUString ConvertMSFormatStringToSO(const OUString& rFormat, css::lang::Locale& rLocale, bool bHijri); + // export just for test + SAL_DLLPUBLIC_EXPORT sal_Int32 convertTwipToMM100(sal_Int32 _t); + sal_Int32 convertTwipToMM100WithoutLimit(sal_Int32 _t); + double convertTwipToMM100Double(sal_Int32 _t); + SAL_DLLPUBLIC_EXPORT sal_uInt32 convertTwipToMM100Unsigned(sal_Int32 _t); + sal_Int16 convertTableJustification( sal_Int32 nIntValue ); + css::text::RubyAdjust convertRubyAlign( sal_Int32 nIntValue ); + sal_Int16 ConvertNumberingType(const sal_Int32 nFmt, const sal_Int16 nDefault = css::style::NumberingType::ARABIC); + sal_Int16 ConvertCustomNumberFormat(std::u16string_view rFormat); + + css::util::DateTime ConvertDateStringToDateTime(std::u16string_view rDateTime); +} // namespace writerfilter::dmapper::ConversionHelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DocumentProtection.cxx b/writerfilter/source/dmapper/DocumentProtection.cxx new file mode 100644 index 000000000..dddf964c4 --- /dev/null +++ b/writerfilter/source/dmapper/DocumentProtection.cxx @@ -0,0 +1,239 @@ +/* -*- 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 "DocumentProtection.hxx" +#include "TagLogger.hxx" +#include <vector> +#include <comphelper/sequence.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper +{ +DocumentProtection::DocumentProtection() + : LoggedProperties("DocumentProtection") + , m_nEdit( + NS_ooxml:: + LN_Value_doc_ST_DocProtect_none) // Specifies that no editing restrictions have been applied to the document + , m_bProtectForm(false) + , m_bRedlineProtection(false) + , m_bReadOnly(false) + , m_bEnforcement(false) + , m_bFormatting(false) + , m_nCryptProviderType(NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES) + , m_sCryptAlgorithmClass("hash") + , m_sCryptAlgorithmType("typeAny") + , m_CryptSpinCount(0) +{ +} + +DocumentProtection::~DocumentProtection() {} + +void DocumentProtection::lcl_attribute(Id nName, Value& val) +{ + int nIntValue = val.getInt(); + OUString sStringValue = val.getString(); + + switch (nName) + { + case NS_ooxml::LN_CT_DocProtect_edit: // 92037 + m_nEdit = nIntValue; + // multiple DocProtect_edits should not exist. If they do, last one wins + m_bRedlineProtection = false; + m_bProtectForm = false; + m_bReadOnly = false; + switch (nIntValue) + { + case NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges: + { + m_bRedlineProtection = true; + m_sRedlineProtectionKey = m_sHash; + break; + } + case NS_ooxml::LN_Value_doc_ST_DocProtect_forms: + m_bProtectForm = true; + break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly: + m_bReadOnly = true; + break; + } + break; + case NS_ooxml::LN_CT_DocProtect_enforcement: // 92039 + m_bEnforcement = (nIntValue != 0); + break; + case NS_ooxml::LN_CT_DocProtect_formatting: // 92038 + m_bFormatting = (nIntValue != 0); + break; + case NS_ooxml::LN_AG_Password_cryptProviderType: // 92025 + m_nCryptProviderType = nIntValue; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmClass: // 92026 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgClass_hash) // 92023 + m_sCryptAlgorithmClass = "hash"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmType: // 92027 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgType_typeAny) // 92024 + m_sCryptAlgorithmType = "typeAny"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmSid: // 92028 + m_sCryptAlgorithmSid = sStringValue; + break; + case NS_ooxml::LN_AG_Password_cryptSpinCount: // 92029 + m_CryptSpinCount = nIntValue; + break; + case NS_ooxml::LN_AG_Password_hash: // 92035 + m_sHash = sStringValue; + break; + case NS_ooxml::LN_AG_Password_salt: // 92036 + m_sSalt = sStringValue; + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } +} + +void DocumentProtection::lcl_sprm(Sprm& /*rSprm*/) {} + +uno::Sequence<beans::PropertyValue> DocumentProtection::toSequence() const +{ + std::vector<beans::PropertyValue> documentProtection; + + if (enabled()) + { + // w:edit + { + beans::PropertyValue aValue; + aValue.Name = "edit"; + + switch (m_nEdit) + { + case NS_ooxml::LN_Value_doc_ST_DocProtect_none: + aValue.Value <<= OUString("none"); + break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly: + aValue.Value <<= OUString("readOnly"); + break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_comments: + aValue.Value <<= OUString("comments"); + break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges: + aValue.Value <<= OUString("trackedChanges"); + break; + case NS_ooxml::LN_Value_doc_ST_DocProtect_forms: + aValue.Value <<= OUString("forms"); + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } + + documentProtection.push_back(aValue); + } + + // w:enforcement + if (m_bEnforcement) + { + beans::PropertyValue aValue; + aValue.Name = "enforcement"; + aValue.Value <<= OUString("1"); + documentProtection.push_back(aValue); + } + + // w:formatting + if (m_bFormatting) + { + beans::PropertyValue aValue; + aValue.Name = "formatting"; + aValue.Value <<= OUString("1"); + documentProtection.push_back(aValue); + } + + // w:cryptProviderType + { + beans::PropertyValue aValue; + aValue.Name = "cryptProviderType"; + if (m_nCryptProviderType == NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES) + aValue.Value <<= OUString("rsaAES"); + else if (m_nCryptProviderType == NS_ooxml::LN_Value_doc_ST_CryptProv_rsaFull) + aValue.Value <<= OUString("rsaFull"); + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmClass + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmClass"; + aValue.Value <<= m_sCryptAlgorithmClass; + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmType + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmType"; + aValue.Value <<= m_sCryptAlgorithmType; + documentProtection.push_back(aValue); + } + + // w:cryptAlgorithmSid + { + beans::PropertyValue aValue; + aValue.Name = "cryptAlgorithmSid"; + aValue.Value <<= m_sCryptAlgorithmSid; + documentProtection.push_back(aValue); + } + + // w:cryptSpinCount + { + beans::PropertyValue aValue; + aValue.Name = "cryptSpinCount"; + aValue.Value <<= OUString::number(m_CryptSpinCount); + documentProtection.push_back(aValue); + } + + // w:hash + { + beans::PropertyValue aValue; + aValue.Name = "hash"; + aValue.Value <<= m_sHash; + documentProtection.push_back(aValue); + } + + // w:salt + { + beans::PropertyValue aValue; + aValue.Name = "salt"; + aValue.Value <<= m_sSalt; + documentProtection.push_back(aValue); + } + } + + return comphelper::containerToSequence(documentProtection); +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DocumentProtection.hxx b/writerfilter/source/dmapper/DocumentProtection.hxx new file mode 100644 index 000000000..2ec0f3f21 --- /dev/null +++ b/writerfilter/source/dmapper/DocumentProtection.hxx @@ -0,0 +1,88 @@ +/* -*- 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 . + */ + +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> +#include <ooxml/resourceids.hxx> + +namespace writerfilter::dmapper +{ +/** Document protection restrictions + * + * This element specifies the set of document protection restrictions which have been applied to the contents of a + * WordprocessingML document.These restrictions should be enforced by applications editing this document + * when the enforcement attribute is turned on, and ignored(but persisted) otherwise.Document protection is a + * set of restrictions used to prevent unintentional changes to all or part of a WordprocessingML document. + */ +class DocumentProtection : public LoggedProperties +{ +private: + /** Document Editing Restrictions + * + * Possible values: + * - NS_ooxml::LN_Value_doc_ST_DocProtect_none + * - NS_ooxml::LN_Value_doc_ST_DocProtect_readOnly + * - NS_ooxml::LN_Value_doc_ST_DocProtect_comments + * - NS_ooxml::LN_Value_doc_ST_DocProtect_trackedChanges + * - NS_ooxml::LN_Value_doc_ST_DocProtect_forms + */ + sal_Int32 m_nEdit; + bool m_bProtectForm; + bool m_bRedlineProtection; + OUString m_sRedlineProtectionKey; + bool m_bReadOnly; + bool m_bEnforcement; + bool m_bFormatting; + + /** Provider type + * + * Possible values: + * "rsaAES" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES + * "rsaFull" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaFull + */ + sal_Int32 m_nCryptProviderType; + OUString m_sCryptAlgorithmClass; + OUString m_sCryptAlgorithmType; + OUString m_sCryptAlgorithmSid; + sal_Int32 m_CryptSpinCount; + OUString m_sHash; + OUString m_sSalt; + + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + + bool enabled() const { return !isNone(); } + bool isNone() const { return m_nEdit == NS_ooxml::LN_Value_doc_ST_DocProtect_none; }; + +public: + DocumentProtection(); + virtual ~DocumentProtection() override; + + css::uno::Sequence<css::beans::PropertyValue> toSequence() const; + + bool getProtectForm() const { return m_bProtectForm; } + bool getRedlineProtection() const { return m_bRedlineProtection; } + bool getReadOnly() const { return m_bReadOnly; } + bool getEnforcement() const { return m_bEnforcement; } +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx new file mode 100644 index 000000000..e4b507957 --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -0,0 +1,4510 @@ +/* -*- 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 "BorderHandler.hxx" +#include "PageBordersHandler.hxx" + +#include "util.hxx" +#include "SdtHelper.hxx" +#include "TagLogger.hxx" +#include "TDefTableHandler.hxx" +#include "DomainMapper_Impl.hxx" +#include "ConversionHelper.hxx" +#include "ModelEventListener.hxx" +#include "MeasureHandler.hxx" +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/paper.hxx> +#include <ooxml/resourceids.hxx> +#include <oox/token/tokens.hxx> +#include <oox/drawingml/drawingmltypes.hxx> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/SizeType.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/awt/FontRelief.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/document/XEventBroadcaster.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/text/FootnoteNumbering.hpp> +#include <com/sun/star/text/TextGridMode.hpp> +#include <com/sun/star/text/XDocumentIndexesSupplier.hpp> +#include <com/sun/star/text/XTextFieldsSupplier.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XFootnote.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/RubyPosition.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/text/FontEmphasis.hpp> +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <comphelper/types.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/sequence.hxx> +#include <editeng/escapementitem.hxx> +#include <filter/msfilter/util.hxx> +#include <sfx2/DocumentMetadataAccess.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/mediadescriptor.hxx> + +#include "TextEffectsHandler.hxx" +#include "CellColorHandler.hxx" +#include "SectionColumnHandler.hxx" +#include "GraphicHelpers.hxx" +#include <dmapper/GraphicZOrderHelper.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <tools/UnitConversion.hxx> + +using namespace ::com::sun::star; +using namespace oox; + +namespace writerfilter::dmapper{ + +struct +{ + sal_Int32 h; + bool orient; + sal_Int32 w; +} CT_PageSz; + + +DomainMapper::DomainMapper( const uno::Reference< uno::XComponentContext >& xContext, + uno::Reference<io::XInputStream> const& xInputStream, + uno::Reference<lang::XComponent> const& xModel, + bool bRepairStorage, + SourceDocumentType eDocumentType, + utl::MediaDescriptor const & rMediaDesc) : + LoggedProperties("DomainMapper"), + LoggedTable("DomainMapper"), + LoggedStream("DomainMapper"), + m_pImpl(new DomainMapper_Impl(*this, xContext, xModel, eDocumentType, rMediaDesc)), + mbIsSplitPara(false), + mbHasControls(false), + mbWasShapeInPara(false) +{ + // #i24363# tab stops relative to indent + m_pImpl->SetDocumentSettingsProperty( + getPropertyName( PROP_TABS_RELATIVE_TO_INDENT ), + uno::Any( false ) ); + m_pImpl->SetDocumentSettingsProperty( + getPropertyName( PROP_SURROUND_TEXT_WRAP_SMALL ), + uno::Any( true ) ); + m_pImpl->SetDocumentSettingsProperty( + getPropertyName( PROP_APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ), + uno::Any( true ) ); + + // Don't load the default style definitions to avoid weird mix + m_pImpl->SetDocumentSettingsProperty("StylesNoDefault", uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("MsWordCompTrailingBlanks", uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("HeaderSpacingBelowLastPara", + uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("FrameAutowidthWithMorePara", uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("FootnoteInColumnToPageEnd", uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("TabAtLeftIndentForParagraphsInList", uno::Any(true)); + m_pImpl->SetDocumentSettingsProperty("NoNumberingShowFollowBy", uno::Any(true)); + + // Initialize RDF metadata, to be able to add statements during the import. + try + { + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY_THROW); + uno::Reference<embed::XStorage> xStorage = comphelper::OStorageHelper::GetTemporaryStorage(); + OUString aBaseURL = rMediaDesc.getUnpackedValueOrDefault("URL", OUString()); + const uno::Reference<frame::XModel> xModel_(xModel, + uno::UNO_QUERY_THROW); + const uno::Reference<rdf::XURI> xBaseURI(sfx2::createBaseURI(xContext, xModel_, aBaseURL, u"")); + const uno::Reference<task::XInteractionHandler> xHandler; + xDocumentMetadataAccess->loadMetadataFromStorage(xStorage, xBaseURI, xHandler); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "failed to initialize RDF metadata"); + } + + if (eDocumentType == SourceDocumentType::OOXML) { + // tdf#108350 + // In Word since version 2007, the default document font is Calibri 11 pt. + // If a DOCX document doesn't contain font information, we should assume + // the intended font to provide best layout match. + try + { + uno::Reference< beans::XPropertySet > xDefProps(GetTextFactory()->createInstance("com.sun.star.text.Defaults"), + uno::UNO_QUERY_THROW); + xDefProps->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), css::uno::Any(OUString("Calibri"))); + xDefProps->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT), css::uno::Any(double(11))); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "failed to initialize default font"); + } + } + + //import document properties + try + { + m_pImpl->m_xDocumentStorage = comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( + OFOPXML_STORAGE_FORMAT_STRING, xInputStream, xContext, bRepairStorage); + + uno::Reference< uno::XInterface > xTemp = xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.OOXMLDocumentPropertiesImporter", + xContext); + + uno::Reference< document::XOOXMLDocumentPropertiesImporter > xImporter( xTemp, uno::UNO_QUERY_THROW ); + uno::Reference< document::XDocumentPropertiesSupplier > xPropSupplier( xModel, uno::UNO_QUERY_THROW); + xImporter->importProperties(m_pImpl->m_xDocumentStorage, + xPropSupplier->getDocumentProperties()); + } + catch( const uno::Exception& ) {} +} + +void DomainMapper::setDocumentReference(writerfilter::ooxml::OOXMLDocument* pDocument) +{ + m_pImpl->setDocumentReference(pDocument); +} + +DomainMapper::~DomainMapper() +{ + try + { + // Remove temporary footnotes and endnotes + m_pImpl->RemoveTemporaryFootOrEndnotes(); + + uno::Reference< text::XDocumentIndexesSupplier> xIndexesSupplier( m_pImpl->GetTextDocument(), uno::UNO_QUERY ); + sal_Int32 nIndexes = 0; + if( xIndexesSupplier.is() ) + { + uno::Reference< container::XIndexAccess > xIndexes = xIndexesSupplier->getDocumentIndexes(); + nIndexes = xIndexes->getCount(); + } + // If we have page references, those need updating as well, similar to the indexes. + uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(m_pImpl->GetTextDocument(), uno::UNO_QUERY); + if(xTextFieldsSupplier.is()) + { + uno::Reference<container::XEnumeration> xEnumeration = xTextFieldsSupplier->getTextFields()->createEnumeration(); + while(xEnumeration->hasMoreElements()) + { + ++nIndexes; + xEnumeration->nextElement(); + } + } + + mbHasControls |= m_pImpl->m_pSdtHelper->hasElements(); + if ( nIndexes || mbHasControls ) + { + //index update has to wait until first view is created + uno::Reference< document::XEventBroadcaster > xBroadcaster(xIndexesSupplier, uno::UNO_QUERY); + if (xBroadcaster.is()) + xBroadcaster->addEventListener(uno::Reference< document::XEventListener >(new ModelEventListener(nIndexes, mbHasControls))); + } + + + // Apply the document settings for both DOCX and RTF after everything else + m_pImpl->GetSettingsTable()->ApplyProperties( m_pImpl->GetTextDocument( ) ); + + // now that importing is finished, re-enable default styles for any that were never defined/imported. + m_pImpl->SetDocumentSettingsProperty("StylesNoDefault", uno::Any(false)); + + // Grab-bag handling + comphelper::SequenceAsHashMap aProperties; + + // Add the saved w:themeFontLang setting + aProperties["ThemeFontLangProps"] <<= m_pImpl->GetSettingsTable()->GetThemeFontLangProperties(); + + // Add the saved compat settings + aProperties["CompatSettings"] <<= m_pImpl->GetSettingsTable()->GetCompatSettings(); + + // Add the saved DocumentProtection settings + aProperties["DocumentProtection"] <<= m_pImpl->GetSettingsTable()->GetDocumentProtectionSettings(); + + // Add the saved w:doNotHyphenateCaps setting + aProperties["NoHyphenateCaps"] <<= m_pImpl->GetSettingsTable()->GetNoHyphenateCaps(); + + uno::Reference<beans::XPropertySet> xDocProps(m_pImpl->GetTextDocument(), uno::UNO_QUERY); + if (xDocProps.is()) + { + comphelper::SequenceAsHashMap aGrabBag(xDocProps->getPropertyValue("InteropGrabBag")); + aGrabBag.update(aProperties); + xDocProps->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag.getAsConstPropertyValueList())); + } + + // tdf#138782: for docs created in MS Word 2010 and older (compatibilityMode <= 14) + m_pImpl->SetDocumentSettingsProperty( + "AddFrameOffsets", + uno::Any(14 >= m_pImpl->GetSettingsTable()->GetWordCompatibilityMode())); + } + catch( const uno::Exception& ) {} + +#ifdef DBG_UTIL + TagLogger::getInstance().endDocument(); +#endif +} + +void DomainMapper::lcl_attribute(Id nName, Value & val) +{ + if (m_pImpl->hasTableManager() && m_pImpl->getTableManager().attribute(nName, val)) + return; + + static const int nSingleLineSpacing = 240; + sal_Int32 nIntValue = val.getInt(); + OUString sStringValue = val.getString(); + + SectionPropertyMap * pSectionContext = m_pImpl->GetSectionContext(); + switch( nName ) + { + case NS_ooxml::LN_CT_Lvl_start: + break; + case NS_ooxml::LN_CT_Lvl_numFmt: + break; + case NS_ooxml::LN_CT_Lvl_isLgl: + break; + case NS_ooxml::LN_CT_Lvl_legacy: + break; + case NS_ooxml::LN_CT_AbstractNum_nsid: + break; + case NS_ooxml::LN_CT_AbstractNum_tmpl: + break; + case NS_ooxml::LN_CT_Border_sz: + break; + case NS_ooxml::LN_CT_Border_val: + break; + case NS_ooxml::LN_CT_Border_space: + break; + case NS_ooxml::LN_CT_Border_shadow: + break; + case NS_ooxml::LN_CT_Border_frame: + break; + case NS_ooxml::LN_headerr: + break; + case NS_ooxml::LN_footerr: + break; + case NS_ooxml::LN_endnote: + break; + case NS_ooxml::LN_CT_Bookmark_name: + m_pImpl->SetBookmarkName( sStringValue ); + break; + case NS_ooxml::LN_CT_MarkupRangeBookmark_id: + // add a bookmark range -- this remembers a bookmark starting here + // or, if the bookmark was already started or, if the bookmark was + // already started before, writes out the bookmark + m_pImpl->StartOrEndBookmark( sStringValue ); + break; + case NS_ooxml::LN_CT_MarkupRange_displacedByCustomXml: + break; + case NS_ooxml::LN_NUMBERING: + break; + case NS_ooxml::LN_FONTTABLE: + break; + case NS_ooxml::LN_STYLESHEET: + break; + + case NS_ooxml::LN_CT_Sym_char: + m_pImpl->SetSymbolChar(nIntValue); + break; + case NS_ooxml::LN_CT_Sym_font: + m_pImpl->SetSymbolFont(sStringValue); + break; + case NS_ooxml::LN_CT_Underline_val: + if (m_pImpl->GetTopContext()) + handleUnderlineType(nIntValue, m_pImpl->GetTopContext()); + break; + case NS_ooxml::LN_CT_Color_val: + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_COLOR, uno::Any( nIntValue ) ); + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "val", msfilter::util::ConvertColorOU(Color(ColorTransparency,nIntValue))); + break; + case NS_ooxml::LN_CT_Underline_color: + if (m_pImpl->GetTopContext()) + { + m_pImpl->GetTopContext()->Insert(PROP_CHAR_UNDERLINE_HAS_COLOR, uno::Any( true ) ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_UNDERLINE_COLOR, uno::Any( nIntValue ) ); + } + break; + + case NS_ooxml::LN_CT_TabStop_val: + if (sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_TabJc_clear) + { + m_pImpl->m_aCurrentTabStop.bDeleted = true; + } + else + { + m_pImpl->m_aCurrentTabStop.bDeleted = false; + m_pImpl->m_aCurrentTabStop.Alignment = getTabAlignFromValue(nIntValue); + } + break; + case NS_ooxml::LN_CT_TabStop_leader: + m_pImpl->m_aCurrentTabStop.FillChar = getFillCharFromValue(nIntValue); + break; + case NS_ooxml::LN_CT_TabStop_pos: + m_pImpl->m_aCurrentTabStop.Position = ConversionHelper::convertTwipToMM100(nIntValue); + break; + + case NS_ooxml::LN_CT_Fonts_ascii: + if (m_pImpl->GetTopContext()) + { + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME, uno::Any( sStringValue )); + } + break; + case NS_ooxml::LN_CT_Fonts_asciiTheme: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "asciiTheme", ThemeTable::getStringForTheme(nIntValue)); + if (m_pImpl->GetTopContext()) + { + // note: overwrite Fonts_ascii with Fonts_asciiTheme *even if* + // theme font is empty - this is apparently what Word 2013 does + uno::Any aPropValue( m_pImpl->GetThemeTable()->getFontNameForTheme( nIntValue ) ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME, aPropValue ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_FONT_NAME_ASCII, aPropValue, true, CHAR_GRAB_BAG ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_NAME_ASCII, uno::Any( ThemeTable::getStringForTheme(nIntValue) ), true, CHAR_GRAB_BAG); + } + break; + case NS_ooxml::LN_CT_Fonts_hAnsi: + break;//unsupported + case NS_ooxml::LN_CT_Fonts_hAnsiTheme: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "hAnsiTheme", ThemeTable::getStringForTheme(nIntValue)); + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_NAME_H_ANSI, uno::Any( ThemeTable::getStringForTheme(nIntValue) ), true, CHAR_GRAB_BAG); + break; + case NS_ooxml::LN_CT_Fonts_eastAsia: + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME_ASIAN, uno::Any( sStringValue )); + break; + case NS_ooxml::LN_CT_Fonts_eastAsiaTheme: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "eastAsiaTheme", ThemeTable::getStringForTheme(nIntValue)); + if (m_pImpl->GetTopContext()) + { + uno::Any aPropValue( m_pImpl->GetThemeTable()->getFontNameForTheme( nIntValue ) ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME_ASIAN, aPropValue ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_FONT_NAME_EAST_ASIA, aPropValue, true, CHAR_GRAB_BAG ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_NAME_EAST_ASIA, uno::Any( ThemeTable::getStringForTheme(nIntValue) ), true, CHAR_GRAB_BAG); + } + break; + case NS_ooxml::LN_CT_Fonts_cs: + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME_COMPLEX, uno::Any( sStringValue )); + break; + case NS_ooxml::LN_CT_Fonts_cstheme: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "cstheme", ThemeTable::getStringForTheme(nIntValue)); + if (m_pImpl->GetTopContext()) + { + uno::Any aPropValue( m_pImpl->GetThemeTable()->getFontNameForTheme( nIntValue ) ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_FONT_NAME_COMPLEX, aPropValue ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_FONT_NAME_CS, aPropValue, true, CHAR_GRAB_BAG ); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_NAME_CS, uno::Any( ThemeTable::getStringForTheme(nIntValue) ), true, CHAR_GRAB_BAG); + } + break; + case NS_ooxml::LN_CT_Spacing_before: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "before", OUString::number(nIntValue)); + if (m_pImpl->GetTopContext()) + // Don't overwrite NS_ooxml::LN_CT_Spacing_beforeAutospacing. + m_pImpl->GetTopContext()->Insert( + PROP_PARA_TOP_MARGIN, + uno::Any(static_cast<sal_Int32>(convertTwipToMm100(nIntValue))), false); + break; + case NS_ooxml::LN_CT_Spacing_beforeLines: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "beforeLines", OUString::number(nIntValue)); + // We would need to make sure that this doesn't overwrite any + // NS_ooxml::LN_CT_Spacing_before in parent styles before style + // sheet support can be enabled. + if (m_pImpl->GetTopContext() && !IsStyleSheetImport()) + m_pImpl->GetTopContext()->Insert(PROP_PARA_TOP_MARGIN, uno::Any(ConversionHelper::convertTwipToMM100(nIntValue * nSingleLineSpacing / 100)), false); + break; + case NS_ooxml::LN_CT_Spacing_after: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "after", OUString::number(nIntValue)); + if (m_pImpl->GetTopContext()) + { + // Don't overwrite NS_ooxml::LN_CT_Spacing_afterAutospacing. + m_pImpl->GetTopContext()->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any( ConversionHelper::convertTwipToMM100( nIntValue ) ), false); + + uno::Any aContextualSpacingFromStyle = m_pImpl->GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN); + if (aContextualSpacingFromStyle.hasValue()) + // Setting "after" spacing means Writer doesn't inherit + // contextual spacing anymore from style, but Word does. + m_pImpl->GetTopContext()->Insert(PROP_PARA_CONTEXT_MARGIN, aContextualSpacingFromStyle); + } + break; + case NS_ooxml::LN_CT_Spacing_afterLines: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "afterLines", OUString::number(nIntValue)); + // We would need to make sure that this doesn't overwrite any + // NS_ooxml::LN_CT_Spacing_after in parent styles before style + // sheet support can be enabled. + if (m_pImpl->GetTopContext() && !IsStyleSheetImport()) + m_pImpl->GetTopContext()->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(ConversionHelper::convertTwipToMM100(nIntValue * nSingleLineSpacing / 100)), false); + break; + case NS_ooxml::LN_CT_Spacing_line: //91434 + case NS_ooxml::LN_CT_Spacing_lineRule: //91435 + { + style::LineSpacing aSpacing; + PropertyMapPtr pTopContext = m_pImpl->GetTopContext(); + std::optional<PropertyMap::Property> aLineSpacingVal; + if (pTopContext && (aLineSpacingVal = pTopContext->getProperty(PROP_PARA_LINE_SPACING)) ) + { + aLineSpacingVal->second >>= aSpacing; + } + else + { + //default to single line spacing + aSpacing.Mode = style::LineSpacingMode::FIX; + aSpacing.Height = sal_Int16(ConversionHelper::convertTwipToMM100( nSingleLineSpacing )); + } + if( nName == NS_ooxml::LN_CT_Spacing_line ) + { + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "line", OUString::number(nIntValue)); + //now set the value depending on the Mode + if( aSpacing.Mode == style::LineSpacingMode::PROP ) + aSpacing.Height = sal_Int16(nIntValue * 100 / nSingleLineSpacing ); + else + aSpacing.Height = sal_Int16(ConversionHelper::convertTwipToMM100( 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) + { + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "lineRule", "auto"); + if (aSpacing.Height >= 0) + { + aSpacing.Mode = style::LineSpacingMode::PROP; + //reinterpret the already set value + aSpacing.Height = sal_Int16( aSpacing.Height * 100 / ConversionHelper::convertTwipToMM100( 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) + { + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "lineRule", "atLeast"); + aSpacing.Mode = style::LineSpacingMode::MINIMUM; + } + else // NS_ooxml::LN_Value_doc_ST_LineSpacingRule_exact + { + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "lineRule", "exact"); + aSpacing.Mode = style::LineSpacingMode::FIX; + } + } + if (pTopContext) + pTopContext->Insert(PROP_PARA_LINE_SPACING, uno::Any( aSpacing )); + } + break; + case NS_ooxml::LN_CT_Ind_start: + case NS_ooxml::LN_CT_Ind_left: + if (m_pImpl->GetTopContext()) + { + // Word inherits FirstLineIndent property of the numbering, even if ParaLeftMargin is set, Writer does not. + // So copy it explicitly, if necessary. + sal_Int32 nFirstLineIndent = m_pImpl->getCurrentNumberingProperty("FirstLineIndent"); + sal_Int32 nIndentAt = m_pImpl->getCurrentNumberingProperty("IndentAt"); + + sal_Int32 nParaLeftMargin = ConversionHelper::convertTwipToMM100(nIntValue); + if (nParaLeftMargin != 0 && nIndentAt == nParaLeftMargin) + // Avoid direct left margin when it's the same as from the + // numbering. + break; + + if (nFirstLineIndent != 0) + m_pImpl->GetTopContext()->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + + m_pImpl->GetTopContext()->Insert(PROP_PARA_LEFT_MARGIN, + uno::Any(nParaLeftMargin)); + } + break; + case NS_ooxml::LN_CT_Ind_end: + case NS_ooxml::LN_CT_Ind_right: + if (m_pImpl->GetTopContext()) + { + // Word inherits FirstLineIndent/ParaLeftMargin property of the numbering, even if ParaRightMargin is set, Writer does not. + // So copy it explicitly, if necessary. + sal_Int32 nFirstLineIndent = m_pImpl->getCurrentNumberingProperty("FirstLineIndent"); + sal_Int32 nParaLeftMargin = m_pImpl->getCurrentNumberingProperty("IndentAt"); + + if (nFirstLineIndent != 0) + m_pImpl->GetTopContext()->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + if (nParaLeftMargin != 0) + m_pImpl->GetTopContext()->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + + m_pImpl->GetTopContext()->Insert( + PROP_PARA_RIGHT_MARGIN, uno::Any( ConversionHelper::convertTwipToMM100(nIntValue ) )); + } + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "right", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Ind_hanging: + if (m_pImpl->GetTopContext()) + { + sal_Int32 nValue = ConversionHelper::convertTwipToMM100( nIntValue ); + m_pImpl->GetTopContext()->Insert( + PROP_PARA_FIRST_LINE_INDENT, uno::Any( - nValue )); + + // See above, need to inherit left margin from list style when first is set. + sal_Int32 nParaLeftMargin = m_pImpl->getCurrentNumberingProperty("IndentAt"); + if (nParaLeftMargin != 0) + m_pImpl->GetTopContext()->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + } + break; + case NS_ooxml::LN_CT_Ind_firstLine: + if (m_pImpl->GetTopContext()) + { + sal_Int32 nFirstLineIndent + = m_pImpl->getCurrentNumberingProperty("FirstLineIndent"); + sal_Int32 nParaFirstLineIndent = ConversionHelper::convertTwipToMM100(nIntValue); + if (nParaFirstLineIndent != 0 && nFirstLineIndent == nParaFirstLineIndent) + // Avoid direct first margin when it's the same as from the + // numbering. + break; + m_pImpl->GetTopContext()->Insert(PROP_PARA_FIRST_LINE_INDENT, + uno::Any(nParaFirstLineIndent)); + } + break; + case NS_ooxml::LN_CT_Ind_rightChars: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "rightChars", OUString::number(nIntValue)); + break; + + case NS_ooxml::LN_CT_EastAsianLayout_id: + break; + case NS_ooxml::LN_CT_EastAsianLayout_combine: + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_COMBINE_IS_ON, uno::Any ( nIntValue != 0 )); + break; + case NS_ooxml::LN_CT_EastAsianLayout_combineBrackets: + if (m_pImpl->GetTopContext()) + { + OUString sCombinePrefix = getBracketStringFromEnum(nIntValue); + OUString sCombineSuffix = getBracketStringFromEnum(nIntValue, false); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_COMBINE_PREFIX, uno::Any ( sCombinePrefix )); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_COMBINE_SUFFIX, uno::Any ( sCombineSuffix )); + } + break; + case NS_ooxml::LN_CT_EastAsianLayout_vert: + if (m_pImpl->GetTopContext()) + { + sal_Int16 nRotationAngle = (nIntValue ? 900 : 0); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_ROTATION, uno::Any ( nRotationAngle )); + } + break; + case NS_ooxml::LN_CT_EastAsianLayout_vertCompress: + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(PROP_CHAR_ROTATION_IS_FIT_TO_LINE, uno::Any ( nIntValue != 0 )); + break; + + case NS_ooxml::LN_CT_PageSz_code: + break; + case NS_ooxml::LN_CT_PageSz_h: + { + sal_Int32 nHeight = ConversionHelper::convertTwipToMM100WithoutLimit(nIntValue); + CT_PageSz.h = PaperInfo::sloppyFitPageDimension(nHeight); + } + break; + case NS_ooxml::LN_CT_PageSz_orient: + CT_PageSz.orient = (nIntValue != NS_ooxml::LN_Value_ST_PageOrientation_portrait); + break; + case NS_ooxml::LN_CT_PageSz_w: + { + sal_Int32 nWidth = ConversionHelper::convertTwipToMM100WithoutLimit(nIntValue); + CT_PageSz.w = PaperInfo::sloppyFitPageDimension(nWidth); + } + break; + + case NS_ooxml::LN_CT_PageMar_top: + m_pImpl->SetPageMarginTwip( PAGE_MAR_TOP, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_right: + m_pImpl->SetPageMarginTwip( PAGE_MAR_RIGHT, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_bottom: + m_pImpl->SetPageMarginTwip( PAGE_MAR_BOTTOM, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_left: + m_pImpl->SetPageMarginTwip( PAGE_MAR_LEFT, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_header: + m_pImpl->SetPageMarginTwip( PAGE_MAR_HEADER, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_footer: + m_pImpl->SetPageMarginTwip( PAGE_MAR_FOOTER, nIntValue ); + break; + case NS_ooxml::LN_CT_PageMar_gutter: + m_pImpl->SetPageMarginTwip( PAGE_MAR_GUTTER, nIntValue ); + break; + case NS_ooxml::LN_CT_Language_val: //90314 + case NS_ooxml::LN_CT_Language_eastAsia: //90315 + case NS_ooxml::LN_CT_Language_bidi: //90316 + { + // store decimal symbol associated to the language of the document + if ( m_pImpl->IsDocDefaultsImport() && ( nName == NS_ooxml::LN_CT_Language_val ) ) + { + LanguageTag aLanguageTag( sStringValue ); + LocaleDataWrapper aLocaleWrapper( std::move(aLanguageTag) ); + if ( aLocaleWrapper.getNumDecimalSep() == "," ) + m_pImpl->SetIsDecimalComma(); + + } + if (nName == NS_ooxml::LN_CT_Language_eastAsia) + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "eastAsia", sStringValue); + else if (nName == NS_ooxml::LN_CT_Language_val) + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "val", sStringValue); + else if (nName == NS_ooxml::LN_CT_Language_bidi) + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "bidi", sStringValue); + lang::Locale aLocale; + if (sStringValue.getLength() <= 3 && sStringValue.getLength() >= 1) + { + // Cheesy Google Docs is known to tag language-only even for + // "en" or others that need some region to distinguish language + // variants for spell-checker and hyphenation. Obtain our known + // fallback to clarify and match. The original value/context is + // unknown anyway. + LanguageTag aLanguageTag( sStringValue); + aLanguageTag.makeFallback(); + if (aLanguageTag.getLanguage() == sStringValue) + aLocale = aLanguageTag.getLocale(); + else + { + // Do not fallback for an unknown language, which usually + // results in "en-US", or any other non-matching case. + aLocale = LanguageTag::convertToLocale( sStringValue); + } + } + else + { + aLocale = LanguageTag::convertToLocale( sStringValue); + } + if (m_pImpl->GetTopContext()) + m_pImpl->GetTopContext()->Insert(NS_ooxml::LN_CT_Language_val== nName ? PROP_CHAR_LOCALE : + NS_ooxml::LN_CT_Language_eastAsia == nName ? PROP_CHAR_LOCALE_ASIAN : PROP_CHAR_LOCALE_COMPLEX, + uno::Any( aLocale ) ); + } + break; + // See SwWW8ImplReader::GetParagraphAutoSpace() on why these are 100 and 280 + case NS_ooxml::LN_CT_Spacing_beforeAutospacing: + { + sal_Int32 default_spacing = -1; + if (nIntValue) + { + m_pImpl->SetParaAutoBefore(true); + + default_spacing = 100; + if (!m_pImpl->GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing()) + { + // 49 is just the old value that should be removed, once the + // root cause in SwTabFrm::MakeAll() is fixed. + if (m_pImpl->GetSettingsTable()->GetView() == NS_ooxml::LN_Value_doc_ST_View_web) + default_spacing = 49; + else + default_spacing = 280; + } + // required at export (here mainly for StyleSheets) to determine if the setting has changed from grab_bag + m_pImpl->GetTopContext()->Insert(PROP_PARA_TOP_MARGIN, uno::Any(ConversionHelper::convertTwipToMM100(default_spacing))); + } + m_pImpl->GetTopContext()->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::Any( ConversionHelper::convertTwipToMM100(default_spacing) ),true, PARA_GRAB_BAG ); + } + break; + case NS_ooxml::LN_CT_Spacing_afterAutospacing: + { + sal_Int32 default_spacing = -1; + if (nIntValue) + { + default_spacing = 100; + if (!m_pImpl->GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing()) + { + if (m_pImpl->GetSettingsTable()->GetView() == NS_ooxml::LN_Value_doc_ST_View_web) + default_spacing = 49; + else + default_spacing = 280; + } + m_pImpl->GetTopContext()->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(ConversionHelper::convertTwipToMM100(default_spacing))); + } + m_pImpl->GetTopContext()->Insert( PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, uno::Any( ConversionHelper::convertTwipToMM100(default_spacing) ),true, PARA_GRAB_BAG ); + } + break; + case NS_ooxml::LN_CT_SmartTagRun_uri: + m_pImpl->getSmartTagHandler().setURI(val.getString()); + break; + case NS_ooxml::LN_CT_SmartTagRun_element: + m_pImpl->getSmartTagHandler().setElement(val.getString()); + break; + case NS_ooxml::LN_CT_Br_type: + // Handled in the OOXMLBreakHandler dtor. + break; + case NS_ooxml::LN_CT_Br_clear: + m_pImpl->HandleLineBreakClear(val.getInt()); + break; + case NS_ooxml::LN_CT_Fonts_hint : + /* assigns script type to ambiguous characters, values can be: + NS_ooxml::LN_Value_ST_Hint_default + NS_ooxml::LN_Value_ST_Hint_eastAsia + NS_ooxml::LN_Value_ST_Hint_cs + */ + //TODO: unsupported? + break; + case NS_ooxml::LN_CT_TblBorders_right: + case NS_ooxml::LN_CT_TblBorders_top: + case NS_ooxml::LN_CT_TblBorders_left: + case NS_ooxml::LN_CT_TblBorders_bottom: + //todo: handle cell mar + break; + case NS_ooxml::LN_blip: // contains the binary graphic + case NS_ooxml::LN_shape: + { + //looks a bit like a hack - and it is. The graphic import is split into the inline_inline part and + //afterwards the adding of the binary data. + m_pImpl->GetGraphicImport( IMPORT_AS_DETECTED_INLINE )->attribute(nName, val); + m_pImpl->ImportGraphic( val.getProperties(), IMPORT_AS_DETECTED_INLINE ); + } + break; + case NS_ooxml::LN_Value_math_ST_Jc_centerGroup: + case NS_ooxml::LN_Value_math_ST_Jc_center: + m_pImpl->appendStarMath(val); + m_pImpl->adjustLastPara(sal_Int8(style::ParagraphAdjust::ParagraphAdjust_CENTER)); + break; + case NS_ooxml::LN_Value_math_ST_Jc_left: + m_pImpl->appendStarMath(val); + m_pImpl->adjustLastPara(sal_Int8(style::ParagraphAdjust::ParagraphAdjust_LEFT)); + break; + case NS_ooxml::LN_Value_math_ST_Jc_right: + m_pImpl->appendStarMath(val); + m_pImpl->adjustLastPara(sal_Int8(style::ParagraphAdjust::ParagraphAdjust_RIGHT)); + break; + case NS_ooxml::LN_starmath: + m_pImpl->appendStarMath(val); + break; + case NS_ooxml::LN_CT_FramePr_dropCap: + case NS_ooxml::LN_CT_FramePr_lines: + case NS_ooxml::LN_CT_FramePr_hAnchor: + case NS_ooxml::LN_CT_FramePr_vAnchor: + case NS_ooxml::LN_CT_FramePr_x: + case NS_ooxml::LN_CT_FramePr_xAlign: + case NS_ooxml::LN_CT_FramePr_y: + case NS_ooxml::LN_CT_FramePr_yAlign: + case NS_ooxml::LN_CT_FramePr_hRule: + case NS_ooxml::LN_CT_FramePr_w: + case NS_ooxml::LN_CT_FramePr_h: + case NS_ooxml::LN_CT_FramePr_wrap: + case NS_ooxml::LN_CT_FramePr_hSpace: + case NS_ooxml::LN_CT_FramePr_vSpace: + { + ParagraphProperties* pParaProperties = nullptr; + // handle frame properties at styles + if( m_pImpl->GetTopContextType() == CONTEXT_STYLESHEET ) + pParaProperties = dynamic_cast< ParagraphProperties*>( m_pImpl->GetTopContextOfType( CONTEXT_STYLESHEET ).get() ); + else + pParaProperties = dynamic_cast< ParagraphProperties*>( m_pImpl->GetTopContextOfType( CONTEXT_PARAGRAPH ).get() ); + + if( pParaProperties ) + { + switch( nName ) + { + case NS_ooxml::LN_CT_FramePr_dropCap: + pParaProperties->SetDropCap( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_lines: + pParaProperties->SetLines( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_hAnchor: + switch(nIntValue) + { + case NS_ooxml::LN_Value_doc_ST_HAnchor_text: //relative to column + nIntValue = text::RelOrientation::FRAME; break; + case NS_ooxml::LN_Value_doc_ST_HAnchor_margin: nIntValue = text::RelOrientation::PAGE_PRINT_AREA; break; + case NS_ooxml::LN_Value_doc_ST_HAnchor_page: nIntValue = text::RelOrientation::PAGE_FRAME; break; + default:; + } + pParaProperties->SethAnchor( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_vAnchor: + switch(nIntValue) + { + case NS_ooxml::LN_Value_doc_ST_VAnchor_text: //relative to paragraph + nIntValue = text::RelOrientation::FRAME; break; + case NS_ooxml::LN_Value_doc_ST_VAnchor_margin:nIntValue = text::RelOrientation::PAGE_PRINT_AREA ; break; + case NS_ooxml::LN_Value_doc_ST_VAnchor_page: nIntValue = text::RelOrientation::PAGE_FRAME; break; + default:; + } + pParaProperties->SetvAnchor( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_x: + pParaProperties->Setx( ConversionHelper::convertTwipToMM100(nIntValue )); + pParaProperties->SetxAlign( text::HoriOrientation::NONE ); + break; + case NS_ooxml::LN_CT_FramePr_xAlign: + switch( nIntValue ) + { + case NS_ooxml::LN_Value_doc_ST_XAlign_center : nIntValue = text::HoriOrientation::CENTER; break; + case NS_ooxml::LN_Value_doc_ST_XAlign_right : nIntValue = text::HoriOrientation::RIGHT; break; + case NS_ooxml::LN_Value_doc_ST_XAlign_inside : nIntValue = text::HoriOrientation::INSIDE; break; + case NS_ooxml::LN_Value_doc_ST_XAlign_outside : nIntValue = text::HoriOrientation::OUTSIDE; break; + case NS_ooxml::LN_Value_doc_ST_XAlign_left : nIntValue = text::HoriOrientation::LEFT; break; + default: nIntValue = text::HoriOrientation::NONE; + } + pParaProperties->SetxAlign( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_y: + pParaProperties->Sety( ConversionHelper::convertTwipToMM100(nIntValue )); + pParaProperties->SetyAlign( text::VertOrientation::NONE ); + break; + case NS_ooxml::LN_CT_FramePr_yAlign: + switch( nIntValue ) + { + case NS_ooxml::LN_Value_doc_ST_YAlign_top : + case NS_ooxml::LN_Value_doc_ST_YAlign_inside :nIntValue = text::VertOrientation::TOP; break; + case NS_ooxml::LN_Value_doc_ST_YAlign_center :nIntValue = text::VertOrientation::CENTER;break; + case NS_ooxml::LN_Value_doc_ST_YAlign_bottom : + case NS_ooxml::LN_Value_doc_ST_YAlign_outside :nIntValue = text::VertOrientation::BOTTOM;break; + case NS_ooxml::LN_Value_doc_ST_YAlign_inline : + { + // HACK: This is for bnc#780851, where a table has one cell that has w:framePr, + // which causes that paragraph to be converted to a text frame, and the original + // paragraph object no longer exists, which makes table creation fail and furthermore + // it would be missing in the table layout anyway. So actually no letting that paragraph + // be a text frame "fixes" it. I'm not sure what "inline" is supposed to mean in practice + // anyway, so as long as this doesn't cause trouble elsewhere ... + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if( pContext ) + { + ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pContext.get() ); + if (pParaContext) + pParaContext->SetFrameMode(false); + } + nIntValue = text::VertOrientation::NONE; + break; + } + default: + nIntValue = text::VertOrientation::NONE; + break; + } + pParaProperties->SetyAlign( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_hRule: + switch( nIntValue ) + { + case NS_ooxml::LN_Value_doc_ST_HeightRule_exact: + nIntValue = text::SizeType::FIX; + break; + case NS_ooxml::LN_Value_doc_ST_HeightRule_atLeast: + nIntValue = text::SizeType::MIN; + break; + case NS_ooxml::LN_Value_doc_ST_HeightRule_auto: + //no break; + default:; + nIntValue = text::SizeType::VARIABLE; + } + pParaProperties->SethRule( nIntValue ); + break; + case NS_ooxml::LN_CT_FramePr_wrap: + { + //should be either LN_Value_doc_ST_Wrap_notBeside or LN_Value_doc_ST_Wrap_around or LN_Value_doc_ST_Wrap_auto + OSL_ENSURE( sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_around || + sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_notBeside || + sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_through || + sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_none || + sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_auto, + "wrap not around, not_Beside, through, none or auto?"); + if( sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_through || + sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_auto ) + pParaProperties->SetWrap ( text::WrapTextMode_DYNAMIC ) ; + else if (sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_around) + pParaProperties->SetWrap(text::WrapTextMode_PARALLEL); + else if (sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_doc_ST_Wrap_none) + pParaProperties->SetWrap ( text::WrapTextMode_THROUGH ) ; + else + pParaProperties->SetWrap ( text::WrapTextMode_NONE ) ; + } + break; + case NS_ooxml::LN_CT_FramePr_w: + pParaProperties->Setw(ConversionHelper::convertTwipToMM100(nIntValue)); + break; + case NS_ooxml::LN_CT_FramePr_h: + pParaProperties->Seth(ConversionHelper::convertTwipToMM100(nIntValue)); + break; + case NS_ooxml::LN_CT_FramePr_hSpace: + pParaProperties->SethSpace( ConversionHelper::convertTwipToMM100(nIntValue )); + break; + case NS_ooxml::LN_CT_FramePr_vSpace: + pParaProperties->SetvSpace( ConversionHelper::convertTwipToMM100(nIntValue )); + break; + default:; + } + } + } + break; + case NS_ooxml::LN_CT_TrackChange_author: + m_pImpl->SetCurrentRedlineAuthor( sStringValue ); + break; + case NS_ooxml::LN_CT_TrackChange_date: + m_pImpl->SetCurrentRedlineDate( sStringValue ); + break; + case NS_ooxml::LN_CT_Markup_id: + m_pImpl->SetCurrentRedlineId( nIntValue ); + break; + case NS_ooxml::LN_EG_RangeMarkupElements_commentRangeStart: + m_pImpl->AddAnnotationPosition( true, nIntValue ); + break; + case NS_ooxml::LN_EG_RangeMarkupElements_commentRangeEnd: + m_pImpl->AddAnnotationPosition( false, nIntValue ); + break; + case NS_ooxml::LN_CT_Comment_initials: + m_pImpl->SetCurrentRedlineInitials(sStringValue); + break; + case NS_ooxml::LN_token: + m_pImpl->SetCurrentRedlineToken( nIntValue ); + break; + case NS_ooxml::LN_CT_LineNumber_start: + case NS_ooxml::LN_CT_LineNumber_distance: + case NS_ooxml::LN_CT_LineNumber_countBy: + case NS_ooxml::LN_CT_LineNumber_restart: + { + //line numbering in Writer is a global document setting + //in Word is a section setting + //if line numbering is switched on anywhere in the document it's set at the global settings + LineNumberSettings aSettings = m_pImpl->GetLineNumberSettings(); + switch( nName ) + { + case NS_ooxml::LN_CT_LineNumber_countBy: + aSettings.nInterval = nIntValue; + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if( pSectionContext ) + pSectionContext->SetLnnMod( nIntValue ); + break; + case NS_ooxml::LN_CT_LineNumber_start: + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if( pSectionContext ) + pSectionContext->SetLnnMin( nIntValue ); + break; + case NS_ooxml::LN_CT_LineNumber_distance: + aSettings.nDistance = ConversionHelper::convertTwipToMM100( nIntValue ); + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if( pSectionContext ) + pSectionContext->SetdxaLnn( nIntValue ); + break; + case NS_ooxml::LN_CT_LineNumber_restart: + aSettings.bRestartAtEachPage = nIntValue == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage; + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if( pSectionContext ) + pSectionContext->SetLnc( nIntValue ); + break; + default:; + } + m_pImpl->SetLineNumberSettings( aSettings ); + } + break; + case NS_ooxml::LN_CT_FtnEdnRef_customMarkFollows: + m_pImpl->StartCustomFootnote(m_pImpl->GetTopContext()); + break; + case NS_ooxml::LN_CT_FtnEdnRef_id: + // footnote or endnote reference id - not needed + break; + case NS_ooxml::LN_CT_Color_themeColor: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "themeColor", TDefTableHandler::getThemeColorTypeString(nIntValue)); + break; + case NS_ooxml::LN_CT_Color_themeTint: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "themeTint", OUString::number(nIntValue, 16)); + break; + case NS_ooxml::LN_CT_Color_themeShade: + m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, "themeShade", OUString::number(nIntValue, 16)); + break; + case NS_ooxml::LN_CT_DocGrid_linePitch: + { + //see SwWW8ImplReader::SetDocumentGrid + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + pSectionContext->SetGridLinePitch( ConversionHelper::convertTwipToMM100( nIntValue ) ); + } + } + break; + case NS_ooxml::LN_CT_DocGrid_charSpace: + { + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + pSectionContext->SetDxtCharSpace( nIntValue ); + } + } + break; + case NS_ooxml::LN_CT_DocGrid_type: + { + if (pSectionContext != nullptr) + { + switch( nIntValue ) + { + case NS_ooxml::LN_Value_doc_ST_DocGrid_default: + pSectionContext->SetGridType(text::TextGridMode::NONE); + break; + case NS_ooxml::LN_Value_doc_ST_DocGrid_lines: + pSectionContext->SetGridType(text::TextGridMode::LINES); + break; + case NS_ooxml::LN_Value_doc_ST_DocGrid_linesAndChars: + pSectionContext->SetGridType(text::TextGridMode::LINES_AND_CHARS); + pSectionContext->SetGridSnapToChars( false ); + break; + case NS_ooxml::LN_Value_doc_ST_DocGrid_snapToChars: + pSectionContext->SetGridType(text::TextGridMode::LINES_AND_CHARS); + pSectionContext->SetGridSnapToChars( true ); + break; + default : + OSL_FAIL("unknown SwTextGrid value"); + } + } + } + break; + case NS_ooxml::LN_CT_SdtBlock_sdtContent: + case NS_ooxml::LN_CT_SdtRun_sdtContent: + if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::unknown) + { + // Still not determined content type? and it is even not unsupported? Then it is plain text field + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); + } + if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent) + { + if (m_pImpl->GetSdtStarts().empty() && !m_pImpl->m_pSdtHelper->getSdtTexts().isEmpty()) + { + // A non-inline SDT is already started, first convert that to a field and only + // then map the inline SDT to a content control. + if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText) + { + m_pImpl->m_pSdtHelper->createPlainTextControl(); + } + } + + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText); + m_pImpl->PushSdt(); + break; + } + m_pImpl->SetSdt(true); + break; + case NS_ooxml::LN_CT_SdtBlock_sdtEndContent: + case NS_ooxml::LN_CT_SdtRun_sdtEndContent: + if (nName == NS_ooxml::LN_CT_SdtRun_sdtEndContent) + { + // Inline SDT. + switch (m_pImpl->m_pSdtHelper->getControlType()) + { + case SdtControlType::richText: + case SdtControlType::checkBox: + case SdtControlType::dropDown: + case SdtControlType::picture: + case SdtControlType::datePicker: + m_pImpl->PopSdt(); + break; + default: + break; + } + } + + m_pImpl->SetSdt(false); + + // It's not possible to insert the relevant property to the character context here: + // the previous, already sent character context may be still active, so the property would be lost. + if (m_pImpl->m_pSdtHelper->isOutsideAParagraph()) + m_pImpl->setParaSdtEndDeferred(true); + else + m_pImpl->setSdtEndDeferred(true); + + switch (m_pImpl->m_pSdtHelper->getControlType()) + { + case SdtControlType::dropDown: + m_pImpl->m_pSdtHelper->createDropDownControl(); + break; + case SdtControlType::plainText: + m_pImpl->m_pSdtHelper->createPlainTextControl(); + break; + case SdtControlType::datePicker: + m_pImpl->m_pSdtHelper->createDateContentControl(); + break; + case SdtControlType::unknown: + default:; + } + break; + case NS_ooxml::LN_CT_SdtListItem_displayText: + m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().push_back(sStringValue); + break; + case NS_ooxml::LN_CT_SdtListItem_value: + m_pImpl->m_pSdtHelper->getDropDownItems().push_back(sStringValue); + break; + case NS_ooxml::LN_CT_SdtDate_fullDate: + m_pImpl->m_pSdtHelper->getDate().append(sStringValue); + break; + case NS_ooxml::LN_CT_Background_color: + if (m_pImpl->GetSettingsTable()->GetDisplayBackgroundShape()) + m_pImpl->m_oBackgroundColor = nIntValue; + break; + case NS_ooxml::LN_CT_PageNumber_start: + if (pSectionContext != nullptr && !m_pImpl->IsAltChunk()) + pSectionContext->SetPageNumber(nIntValue); + break; + case NS_ooxml::LN_CT_PageNumber_fmt: + if (pSectionContext) + { + sal_Int16 nNumberType = ConversionHelper::ConvertNumberingType(nIntValue, -1); + if (nNumberType != -1) + pSectionContext->SetPageNumberType(nNumberType); + } + break; + case NS_ooxml::LN_CT_FtnEdn_type: + // This is the "separator" footnote, ignore its linebreaks/text. + if (static_cast<sal_uInt32>(nIntValue) == NS_ooxml::LN_Value_doc_ST_FtnEdn_separator) + m_pImpl->SetSkipFootnoteState( SkipFootnoteSeparator::ON ); + else + m_pImpl->SetSkipFootnoteState( SkipFootnoteSeparator::OFF ); + break; + case NS_ooxml::LN_CT_FtnEdn_id: + { + SkipFootnoteSeparator eSkip = m_pImpl->GetSkipFootnoteState(); + if ( eSkip == SkipFootnoteSeparator::ON ) + m_pImpl->SetSkipFootnoteState( SkipFootnoteSeparator::SKIPPING ); + else if ( eSkip == SkipFootnoteSeparator::SKIPPING ) + m_pImpl->SetSkipFootnoteState( SkipFootnoteSeparator::OFF ); + } + break; + case NS_ooxml::LN_CT_DataBinding_prefixMappings: + m_pImpl->m_pSdtHelper->setDataBindingPrefixMapping(sStringValue); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_DataBinding_prefixMappings", sStringValue); + break; + case NS_ooxml::LN_CT_DataBinding_xpath: + m_pImpl->m_pSdtHelper->setDataBindingXPath(sStringValue); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_DataBinding_xpath", sStringValue); + break; + case NS_ooxml::LN_CT_DataBinding_storeItemID: + m_pImpl->m_pSdtHelper->setDataBindingStoreItemID(sStringValue); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_DataBinding_storeItemID", sStringValue); + break; + case NS_ooxml::LN_CT_SdtPlaceholder_docPart_val: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtPlaceholder_docPart_val", sStringValue); + m_pImpl->m_pSdtHelper->SetPlaceholderDocPart(sStringValue); + break; + case NS_ooxml::LN_CT_SdtColor_val: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtColor_val", sStringValue); + m_pImpl->m_pSdtHelper->SetColor(sStringValue); + break; + case NS_ooxml::LN_CT_SdtText_multiLine: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtText_multiLine", sStringValue); + break; + case NS_ooxml::LN_CT_PTab_leader: + case NS_ooxml::LN_CT_PTab_relativeTo: + break; + case NS_ooxml::LN_CT_PTab_alignment: + m_pImpl->HandlePTab(nIntValue); + break; + case NS_ooxml::LN_CT_Cnf_lastRowLastColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "lastRowLastColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_lastRowFirstColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "lastRowFirstColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_firstRowLastColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "firstRowLastColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_oddHBand: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "oddHBand", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_firstRowFirstColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "firstRowFirstColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_evenVBand: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "evenVBand", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_evenHBand: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "evenHBand", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_lastColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "lastColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_firstColumn: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "firstColumn", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_oddVBand: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "oddVBand", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_lastRow: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "lastRow", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_firstRow: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "firstRow", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Cnf_val: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "val", sStringValue); + break; + case NS_ooxml::LN_CT_DocPartName_val: + { + m_sGlossaryEntryName = sStringValue; + break; + } + case NS_ooxml::LN_CT_DocPartGallery_val: + { + const OUString& sGlossaryEntryGallery = sStringValue; + if(m_pImpl->GetTopContext()) + { + OUString sName = sGlossaryEntryGallery + ":" + m_sGlossaryEntryName; + // Add glossary entry name as a first paragraph in section + m_pImpl->appendTextPortion(sName, m_pImpl->GetTopContext()); + } + break; + } + case NS_ooxml::LN_CT_PermStart_ed: + { + m_pImpl->setPermissionRangeEd(sStringValue); + break; + } + case NS_ooxml::LN_CT_PermStart_edGrp: + { + m_pImpl->setPermissionRangeEdGrp(sStringValue); + break; + } + case NS_ooxml::LN_CT_PermStart_id: + { + m_pImpl->startOrEndPermissionRange(nIntValue); + break; + } + case NS_ooxml::LN_CT_PermEnd_id: + { + m_pImpl->startOrEndPermissionRange(nIntValue); + break; + } + case NS_ooxml::LN_CT_NumFmt_val: + { + try + { + uno::Reference<beans::XPropertySet> xFtnEdnSettings; + if (m_pImpl->IsInFootnoteProperties()) + { + uno::Reference<text::XFootnotesSupplier> xFootnotesSupplier( + m_pImpl->GetTextDocument(), uno::UNO_QUERY); + if (xFootnotesSupplier.is()) + xFtnEdnSettings = xFootnotesSupplier->getFootnoteSettings(); + } + else + { + uno::Reference<text::XEndnotesSupplier> xEndnotesSupplier( + m_pImpl->GetTextDocument(), uno::UNO_QUERY); + if (xEndnotesSupplier.is()) + xFtnEdnSettings = xEndnotesSupplier->getEndnoteSettings(); + } + if (xFtnEdnSettings.is()) + { + sal_Int16 nNumType = ConversionHelper::ConvertNumberingType(nIntValue); + xFtnEdnSettings->setPropertyValue(getPropertyName(PROP_NUMBERING_TYPE), + uno::Any(nNumType)); + } + } + catch (const uno::Exception&) + { + } + } + break; + case NS_ooxml::LN_CT_AltChunk: + { + m_pImpl->HandleAltChunk(sStringValue); + } + break; + case NS_ooxml::LN_AG_Parids_paraId: + if (ParagraphPropertyMap* pParaContext + = dynamic_cast<ParagraphPropertyMap*>(m_pImpl->GetTopContext().get())) + { + pParaContext->SetParaId(sStringValue); + } + break; + default: + SAL_WARN("writerfilter", "DomainMapper::lcl_attribute: unhandled token: " << nName); + } +} + +void DomainMapper::lcl_sprm(Sprm & rSprm) +{ + if (!m_pImpl->hasTableManager() || !m_pImpl->getTableManager().sprm(rSprm)) + sprmWithProps(rSprm, m_pImpl->GetTopContext()); +} + +// In rtl-paragraphs the meaning of left/right are to be exchanged +static bool ExchangeLeftRight(const PropertyMapPtr& rContext, DomainMapper_Impl& rImpl) +{ + bool bExchangeLeftRight = false; + sal_Int32 aAdjust; + uno::Any aPropPara = rImpl.GetAnyProperty(PROP_WRITING_MODE, rContext); + if( (aPropPara >>= aAdjust) && aAdjust == text::WritingMode2::RL_TB ) + bExchangeLeftRight = true; + return bExchangeLeftRight; +} + +void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) +{ + // These SPRM's are not specific to any section, so it's expected that there is no context yet. + switch (rSprm.getId()) + { + case NS_ooxml::LN_background_background: + return; + default: + break; + } + + OSL_ENSURE(rContext, "PropertyMap has to be valid!"); + if(!rContext) + return ; + + sal_uInt32 nSprmId = rSprm.getId(); + //needed for page properties + SectionPropertyMap * pSectionContext = m_pImpl->GetSectionContext(); + Value::Pointer_t pValue = rSprm.getValue(); + sal_Int32 nIntValue = pValue->getInt(); + const OUString sStringValue = pValue->getString(); + + switch(nSprmId) + { + case NS_ooxml::LN_CT_PPrBase_jc: + { + bool bExchangeLeftRight = !IsRTFImport() && ExchangeLeftRight(rContext, *m_pImpl); + handleParaJustification(nIntValue, rContext, bExchangeLeftRight); + break; + } + case NS_ooxml::LN_CT_PPrBase_keepLines: + rContext->Insert(PROP_PARA_SPLIT, uno::Any(nIntValue == 0)); + break; + case NS_ooxml::LN_CT_PPrBase_keepNext: + rContext->Insert(PROP_PARA_KEEP_TOGETHER, uno::Any( nIntValue != 0 ) ); + break; + case NS_ooxml::LN_CT_PPrBase_pageBreakBefore: + rContext->Insert(PROP_BREAK_TYPE, uno::Any(nIntValue ? style::BreakType_PAGE_BEFORE : style::BreakType_NONE), /*bOverwrite=*/bool(nIntValue)); + break; + case NS_ooxml::LN_CT_NumPr_ilvl: + if (nIntValue < 0 || 10 <= nIntValue) + { + SAL_INFO("writerfilter", + "unsupported numbering level " << nIntValue); + break; + } + if( IsStyleSheetImport() ) + { + //style sheets cannot have a numbering rule attached + StyleSheetPropertyMap* pStyleSheetPropertyMap = dynamic_cast< StyleSheetPropertyMap* >( rContext.get() ); + if (pStyleSheetPropertyMap) + pStyleSheetPropertyMap->SetListLevel( static_cast<sal_Int16>(nIntValue) ); + } + // 0-8 are the 9 levels that Microsoft supports. (LO supports 10 levels). + // 9 indicates "no numbering", for which LO has no corresponding concept, + // and so it will be treated as the 10th level. + // finishParagraph() will convert the 9 into "no numbering" for direct formatting. + // (Styles only use this PROP for round-tripping and UI, but cannot trust it for import) + if (!IsStyleSheetImport() || nIntValue != 9) + rContext->Insert(PROP_NUMBERING_LEVEL, uno::Any(static_cast<sal_Int16>(nIntValue))); + break; + case NS_ooxml::LN_CT_NumPr_numId: + { + //convert the ListTable entry to a NumberingRules property and apply it + ListsManager::Pointer pListTable = m_pImpl->GetListTable(); + ListDef::Pointer pList = pListTable->GetList( nIntValue ); + if( IsStyleSheetImport() ) + { + //style sheets cannot have a numbering rule attached + StyleSheetPropertyMap* pStyleSheetPropertyMap = dynamic_cast< StyleSheetPropertyMap* >( rContext.get() ); + if (pStyleSheetPropertyMap) + pStyleSheetPropertyMap->SetListId( nIntValue ); + } + if( pList ) + { + if( !IsStyleSheetImport() ) + { + uno::Any aRules( pList->GetNumberingRules( ) ); + rContext->Insert( PROP_NUMBERING_RULES, aRules ); + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if (pContext) + { + assert(dynamic_cast<ParagraphPropertyMap*>(pContext.get())); + static_cast<ParagraphPropertyMap*>(pContext.get())->SetListId(pList->GetId()); + } + + // Indentation can came from: + // 1) Paragraph style's numbering's indentation: the current non-style numId has priority over it. + // 2) Numbering's indentation: Writer handles that natively, so it should not be set on rContext. + // 3) Paragraph style's indentation: ditto. + // 4) Direct paragraph formatting: that will came later. + // So no situation where keeping indentation at this point would make sense -> erase. + rContext->Erase(PROP_PARA_FIRST_LINE_INDENT); + rContext->Erase(PROP_PARA_LEFT_MARGIN); + rContext->Erase(PROP_PARA_RIGHT_MARGIN); + } + } + else + { + if( !IsStyleSheetImport() ) + { + // eg. disabled numbering using non-existent numId "0" + rContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::Any( OUString() ) ); + // disable inheritance of indentation of parent styles + rContext->Insert( PROP_PARA_LEFT_MARGIN, uno::Any( sal_Int32(0) ), /*bOverwrite=*/false); + rContext->Insert( PROP_PARA_FIRST_LINE_INDENT, + uno::Any( sal_Int32(0) ), /*bOverwrite=*/false); + } + } + } + break; + case NS_ooxml::LN_CT_PPrBase_suppressLineNumbers: + rContext->Insert(PROP_PARA_LINE_NUMBER_COUNT, uno::Any( nIntValue == 0 ) ); + break; + case NS_ooxml::LN_inTbl: + break; + case NS_ooxml::LN_tblDepth: + //not handled via sprm but via text( 0x07 ) + break; + case NS_ooxml::LN_CT_FramePr_w: + break; + case NS_ooxml::LN_CT_FramePr_wrap: + break; + + case NS_ooxml::LN_CT_PrBase_pBdr: //paragraph border + resolveSprmProps(*this, rSprm); + break; + case NS_ooxml::LN_CT_PBdr_top: + case NS_ooxml::LN_CT_PBdr_left: + case NS_ooxml::LN_CT_PBdr_bottom: + case NS_ooxml::LN_CT_PBdr_right: + case NS_ooxml::LN_CT_PBdr_between: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pBorderHandler = std::make_shared<BorderHandler>( true ); + pProperties->resolve(*pBorderHandler); + PropertyIds eBorderId = PropertyIds( 0 ); + PropertyIds eBorderDistId = PropertyIds( 0 ); + switch( nSprmId ) + { + case NS_ooxml::LN_CT_PBdr_top: + eBorderId = PROP_TOP_BORDER; + eBorderDistId = PROP_TOP_BORDER_DISTANCE; + break; + case NS_ooxml::LN_CT_PBdr_left: + eBorderId = PROP_LEFT_BORDER; + eBorderDistId = PROP_LEFT_BORDER_DISTANCE; + break; + case NS_ooxml::LN_CT_PBdr_bottom: + eBorderId = PROP_BOTTOM_BORDER ; + eBorderDistId = PROP_BOTTOM_BORDER_DISTANCE; + break; + case NS_ooxml::LN_CT_PBdr_right: + eBorderId = PROP_RIGHT_BORDER; + eBorderDistId = PROP_RIGHT_BORDER_DISTANCE ; + break; + case NS_ooxml::LN_CT_PBdr_between: + if (m_pImpl->handlePreviousParagraphBorderInBetween()) + { + // If previous paragraph also had border in between property + // then it is possible to emulate this border as top border + // for current paragraph + eBorderId = PROP_TOP_BORDER; + eBorderDistId = PROP_TOP_BORDER_DISTANCE; + } + // Since there are borders in between, each paragraph will have own borders. No more joining + rContext->Insert(PROP_PARA_CONNECT_BORDERS, uno::Any(false)); + break; + default:; + } + if( eBorderId ) + rContext->Insert( eBorderId, uno::Any( pBorderHandler->getBorderLine()) ); + if(eBorderDistId) + rContext->Insert(eBorderDistId, uno::Any( pBorderHandler->getLineDistance())); + if ( nSprmId == NS_ooxml::LN_CT_PBdr_right ) + { + table::ShadowFormat aFormat; + // Word only allows shadows on visible borders + if ( pBorderHandler->getShadow() && pBorderHandler->getBorderLine().LineStyle != table::BorderLineStyle::NONE ) + aFormat = writerfilter::dmapper::PropertyMap::getShadowFromBorder(pBorderHandler->getBorderLine()); + rContext->Insert(PROP_PARA_SHADOW_FORMAT, uno::Any(aFormat)); + } + } + } + break; + case NS_ooxml::LN_CT_PBdr_bar: + break; + case NS_ooxml::LN_CT_PPrBase_suppressAutoHyphens: + rContext->Insert(PROP_PARA_IS_HYPHENATION, uno::Any( nIntValue == 0 )); + break; + case NS_ooxml::LN_CT_FramePr_h: + break; + case NS_ooxml::LN_CT_PrBase_shd: + { + //contains fore color, back color and shadow percentage, results in a brush + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pCellColorHandler = std::make_shared<CellColorHandler>(); + pCellColorHandler->setOutputFormat( CellColorHandler::Paragraph ); + bool bEnableTempGrabBag = !pCellColorHandler->isInteropGrabBagEnabled(); + if( bEnableTempGrabBag ) + pCellColorHandler->enableInteropGrabBag( "TempShdPropsGrabBag" ); + + pProperties->resolve(*pCellColorHandler); + rContext->InsertProps(pCellColorHandler->getProperties().get()); + + rContext->Insert(PROP_CHAR_THEME_FILL, pCellColorHandler->getInteropGrabBag().Value, true, PARA_GRAB_BAG); + if(bEnableTempGrabBag) + pCellColorHandler->disableInteropGrabBag(); + } + } + break; + case NS_ooxml::LN_CT_FramePr_vSpace: + break; // sprmPDyaFromText + case NS_ooxml::LN_CT_FramePr_hSpace: + break; // sprmPDxaFromText + case NS_ooxml::LN_CT_FramePr_anchorLock: + break; + case NS_ooxml::LN_CT_PPrBase_widowControl: + { + uno::Any aVal( uno::Any( sal_Int8(nIntValue ? 2 : 0 ))); + rContext->Insert( PROP_PARA_WIDOWS, aVal ); + rContext->Insert( PROP_PARA_ORPHANS, aVal ); + } + break; // sprmPFWidowControl + case NS_ooxml::LN_CT_PPrBase_overflowPunct: + rContext->Insert(PROP_PARA_IS_HANGING_PUNCTUATION, uno::Any( nIntValue == 0 )); + break; + case NS_ooxml::LN_CT_PPrBase_topLinePunct: + break; + case NS_ooxml::LN_CT_PPrBase_autoSpaceDE: + break; + case NS_ooxml::LN_CT_PPrBase_autoSpaceDN: + break; + case NS_ooxml::LN_CT_PPrBase_textAlignment: + { + sal_Int16 nAlignment = 0; + switch (nIntValue) + { + case NS_ooxml::LN_Value_doc_ST_TextAlignment_top: + nAlignment = 2; + break; + case NS_ooxml::LN_Value_doc_ST_TextAlignment_center: + nAlignment = 3; + break; + case NS_ooxml::LN_Value_doc_ST_TextAlignment_baseline: + nAlignment = 1; + break; + case NS_ooxml::LN_Value_doc_ST_TextAlignment_bottom: + nAlignment = 4; + break; + case NS_ooxml::LN_Value_doc_ST_TextAlignment_auto: + default: + break; + } + rContext->Insert( PROP_PARA_VERT_ALIGNMENT, uno::Any( nAlignment) ); + } + break; + case NS_ooxml::LN_CT_PPrBase_textDirection: + { + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_TextDirection_tbRl: + { + m_pImpl->SetFrameDirection(text::WritingMode2::TB_RL); + break; + } + case NS_ooxml::LN_Value_ST_TextDirection_btLr: + { + m_pImpl->SetFrameDirection(text::WritingMode2::BT_LR); + break; + } + case NS_ooxml::LN_Value_ST_TextDirection_lrTbV: + { + m_pImpl->SetFrameDirection(text::WritingMode2::LR_TB); + break; + } + case NS_ooxml::LN_Value_ST_TextDirection_tbRlV: + { + m_pImpl->SetFrameDirection(text::WritingMode2::TB_RL); + break; + } + case NS_ooxml::LN_Value_ST_TextDirection_lrTb: + case NS_ooxml::LN_Value_ST_TextDirection_tbLrV: + default: + SAL_WARN("writerfilter", "DomainMapper::sprmWithProps: unhandled textDirection"); + } + } + break; + case NS_ooxml::LN_CT_PPrBase_outlineLvl: + { + if (nIntValue < WW_OUTLINE_MIN || nIntValue > WW_OUTLINE_MAX) + break; // invalid value is ignored by MS Word + + if( IsStyleSheetImport() ) + { + StyleSheetPropertyMap* pStyleSheetPropertyMap = dynamic_cast< StyleSheetPropertyMap* >( rContext.get() ); + if (pStyleSheetPropertyMap) + pStyleSheetPropertyMap->SetOutlineLevel(nIntValue); + } + else + { + // convert MS body level (9) to LO body level (0) and equivalent outline levels + sal_Int16 nLvl = nIntValue == WW_OUTLINE_MAX ? 0 : nIntValue + 1; + rContext->Insert(PROP_OUTLINE_LEVEL, uno::Any ( nLvl )); + } + } + break; + case NS_ooxml::LN_CT_PPrBase_bidi: + { + // Four situations to handle: + // 1.) bidi same as previous setting: no adjust change + // 2.) no previous adjust: set appropriate default for this bidi + // 3.) previous adjust and bidi different from previous: swap adjusts + // 4.) previous adjust and no previous bidi: RTL swaps adjust + + const sal_Int16 nWritingMode = nIntValue ? text::WritingMode2::RL_TB : text::WritingMode2::LR_TB; + sal_Int16 nParentBidi = -1; + m_pImpl->GetPropertyFromParaStyleSheet(PROP_WRITING_MODE) >>= nParentBidi; + // Paragraph justification reverses its meaning in an RTL context. + // 1. Only make adjustments if the BiDi changes. + if ( nParentBidi != nWritingMode && !IsRTFImport() ) + { + style::ParagraphAdjust eAdjust = style::ParagraphAdjust(-1); + // 2. no adjust property exists yet + if ( !(m_pImpl->GetAnyProperty(PROP_PARA_ADJUST, rContext) >>= eAdjust) ) + { + // RTL defaults to right adjust + eAdjust = nIntValue ? style::ParagraphAdjust_RIGHT : style::ParagraphAdjust_LEFT; + rContext->Insert(PROP_PARA_ADJUST, uno::Any( eAdjust ), /*bOverwrite=*/false); + } + // 3,4. existing adjust: if RTL, then swap. If LTR, but previous was RTL, also swap. + else if ( nIntValue || nParentBidi == sal_Int16(text::WritingMode2::RL_TB) ) + { + if ( eAdjust == style::ParagraphAdjust_RIGHT ) + rContext->Insert(PROP_PARA_ADJUST, uno::Any( style::ParagraphAdjust_LEFT )); + else if ( eAdjust == style::ParagraphAdjust_LEFT ) + rContext->Insert(PROP_PARA_ADJUST, uno::Any( style::ParagraphAdjust_RIGHT )); + } + } + rContext->Insert(PROP_WRITING_MODE, uno::Any( nWritingMode )); + } + + break; + case NS_ooxml::LN_EG_SectPrContents_bidi: + if (pSectionContext != nullptr) + { + const sal_Int16 writingMode = (nIntValue != 0) ? sal_Int16(text::WritingMode2::RL_TB) : sal_Int16(text::WritingMode2::LR_TB); + pSectionContext->Insert(PROP_WRITING_MODE, uno::Any(writingMode)); + } + break; + case NS_ooxml::LN_EG_SectPrContents_rtlGutter: + if (pSectionContext != nullptr) + { + bool bRtlGutter = nIntValue != 0; + pSectionContext->Insert(PROP_RTL_GUTTER, uno::Any(bRtlGutter)); + } + break; + case NS_ooxml::LN_EG_RPrBase_highlight: + { + // MS Word completely ignores character highlighting in character styles. + if ( IsStyleSheetImport() ) + { + const StyleSheetEntryPtr pCurrStyle = GetStyleSheetTable()->GetCurrentEntry(); + if ( pCurrStyle && pCurrStyle->nStyleTypeCode == STYLE_TYPE_CHAR ) + break; + } + + // OOXML import uses an ID + if( IsOOXMLImport() ) + { + sal_Int32 nColor = 0; + if( getColorFromId(nIntValue, nColor) ) + rContext->Insert(PROP_CHAR_HIGHLIGHT, uno::Any( nColor )); + } + // RTF import uses the actual color value + else if( IsRTFImport() ) + { + rContext->Insert(PROP_CHAR_HIGHLIGHT, uno::Any( nIntValue )); + } + } + break; + case NS_ooxml::LN_EG_RPrBase_em: + rContext->Insert(PROP_CHAR_EMPHASIS, uno::Any ( getEmphasisValue (nIntValue))); + break; + case NS_ooxml::LN_EG_RPrBase_emboss: + case NS_ooxml::LN_EG_RPrBase_b: + case NS_ooxml::LN_EG_RPrBase_bCs: + case NS_ooxml::LN_EG_RPrBase_i: + case NS_ooxml::LN_EG_RPrBase_iCs: + case NS_ooxml::LN_EG_RPrBase_strike: + case NS_ooxml::LN_EG_RPrBase_dstrike: + case NS_ooxml::LN_EG_RPrBase_outline: + case NS_ooxml::LN_EG_RPrBase_shadow: + case NS_ooxml::LN_EG_RPrBase_caps: + case NS_ooxml::LN_EG_RPrBase_smallCaps: + case NS_ooxml::LN_EG_RPrBase_vanish: + case NS_ooxml::LN_EG_RPrBase_webHidden: + { + PropertyIds ePropertyId = PROP_CHAR_WEIGHT; //initialized to prevent warning! + switch( nSprmId ) + { + case NS_ooxml::LN_EG_RPrBase_b: + case NS_ooxml::LN_EG_RPrBase_bCs: + ePropertyId = nSprmId != NS_ooxml::LN_EG_RPrBase_bCs ? PROP_CHAR_WEIGHT : PROP_CHAR_WEIGHT_COMPLEX; + break; + case NS_ooxml::LN_EG_RPrBase_i: + case NS_ooxml::LN_EG_RPrBase_iCs: + ePropertyId = nSprmId == NS_ooxml::LN_EG_RPrBase_i ? PROP_CHAR_POSTURE : PROP_CHAR_POSTURE_COMPLEX; + break; + case NS_ooxml::LN_EG_RPrBase_strike: + case NS_ooxml::LN_EG_RPrBase_dstrike: + ePropertyId = PROP_CHAR_STRIKEOUT; + break; + case NS_ooxml::LN_EG_RPrBase_outline: + ePropertyId = PROP_CHAR_CONTOURED; + break; + case NS_ooxml::LN_EG_RPrBase_shadow: + ePropertyId = PROP_CHAR_SHADOWED; + break; + case NS_ooxml::LN_EG_RPrBase_caps: + case NS_ooxml::LN_EG_RPrBase_smallCaps: + ePropertyId = PROP_CHAR_CASE_MAP; + break; + case NS_ooxml::LN_EG_RPrBase_vanish: + case NS_ooxml::LN_EG_RPrBase_webHidden: + ePropertyId = PROP_CHAR_HIDDEN; + break; + case NS_ooxml::LN_EG_RPrBase_emboss: + ePropertyId = PROP_CHAR_RELIEF; + break; + } + //expected: 0,1,128,129 + if(nIntValue != 128) //inherited from paragraph - ignore + { + if( nIntValue == 129) //inverted style sheet value + { + //get value from style sheet and invert it + sal_Int16 nStyleValue = 0; + uno::Any aStyleVal = m_pImpl->GetPropertyFromParaStyleSheet(ePropertyId); + if( !aStyleVal.hasValue() ) + { + nIntValue = NS_ooxml::LN_EG_RPrBase_smallCaps == nSprmId ? + 4 : 1; + } + else if(aStyleVal.getValueTypeClass() == uno::TypeClass_FLOAT ) + { + double fDoubleValue = 0; + //only in case of awt::FontWeight + aStyleVal >>= fDoubleValue; + nIntValue = fDoubleValue > 100. ? 0 : 1; + } + else if((aStyleVal >>= nStyleValue) || + (nStyleValue = static_cast<sal_Int16>(comphelper::getEnumAsINT32(aStyleVal))) >= 0 ) + { + nIntValue = NS_ooxml::LN_EG_RPrBase_smallCaps == nSprmId ? + nStyleValue ? 0 : 4 : + nStyleValue ? 0 : 1; + } + else + { + OSL_FAIL( "what type was it"); + } + } + + switch( nSprmId ) + { + case NS_ooxml::LN_EG_RPrBase_b: + case NS_ooxml::LN_EG_RPrBase_bCs: + { + uno::Any aBold( uno::Any( nIntValue ? awt::FontWeight::BOLD : awt::FontWeight::NORMAL ) ); + + rContext->Insert(ePropertyId, aBold ); + if( nSprmId != NS_ooxml::LN_EG_RPrBase_bCs ) + rContext->Insert(PROP_CHAR_WEIGHT_ASIAN, aBold ); + + if (nSprmId == NS_ooxml::LN_EG_RPrBase_b) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "b", OUString::number(nIntValue)); + else if (nSprmId == NS_ooxml::LN_EG_RPrBase_bCs) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "bCs", OUString::number(nIntValue)); + } + break; + case NS_ooxml::LN_EG_RPrBase_i: + case NS_ooxml::LN_EG_RPrBase_iCs: + { + uno::Any aPosture( uno::Any( nIntValue ? awt::FontSlant_ITALIC : awt::FontSlant_NONE ) ); + rContext->Insert( ePropertyId, aPosture ); + if (nSprmId != NS_ooxml::LN_EG_RPrBase_iCs) + rContext->Insert(PROP_CHAR_POSTURE_ASIAN, aPosture ); + if (nSprmId == NS_ooxml::LN_EG_RPrBase_i) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "i", OUString::number(nIntValue)); + } + break; + case NS_ooxml::LN_EG_RPrBase_strike: + rContext->Insert(ePropertyId, + uno::Any( nIntValue ? awt::FontStrikeout::SINGLE : awt::FontStrikeout::NONE ) ); + break; + case NS_ooxml::LN_EG_RPrBase_dstrike: + rContext->Insert(ePropertyId, + uno::Any( nIntValue ? awt::FontStrikeout::DOUBLE : awt::FontStrikeout::NONE ) ); + break; + case NS_ooxml::LN_EG_RPrBase_outline: + case NS_ooxml::LN_EG_RPrBase_shadow: + case NS_ooxml::LN_EG_RPrBase_vanish: + case NS_ooxml::LN_EG_RPrBase_webHidden: + rContext->Insert(ePropertyId, uno::Any( nIntValue != 0 )); + break; + case NS_ooxml::LN_EG_RPrBase_smallCaps: + // If smallcaps would be just disabled and another casemap is already inserted, don't do anything. + if (nIntValue || !rContext->isSet(ePropertyId) ) + rContext->Insert(ePropertyId, uno::Any( nIntValue ? style::CaseMap::SMALLCAPS : style::CaseMap::NONE)); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "smallCaps", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_EG_RPrBase_caps: + rContext->Insert(ePropertyId, + uno::Any( nIntValue ? style::CaseMap::UPPERCASE : style::CaseMap::NONE)); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "caps", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_EG_RPrBase_emboss: + rContext->Insert(ePropertyId, + uno::Any( nIntValue ? awt::FontRelief::EMBOSSED : awt::FontRelief::NONE )); + break; + + } + } + } + break; + case NS_ooxml::LN_EG_RPrBase_sz: + case NS_ooxml::LN_EG_RPrBase_szCs: + { + //multiples of half points (12pt == 24) + double fVal = double(nIntValue) / 2.; + uno::Any aVal( fVal ); + if( NS_ooxml::LN_EG_RPrBase_szCs == nSprmId ) + { + rContext->Insert( PROP_CHAR_HEIGHT_COMPLEX, aVal ); + } + else + { + const RubyInfo &aInfo = m_pImpl->GetRubyInfo(); + if (aInfo.nSprmId == NS_ooxml::LN_CT_Ruby_rt && aInfo.nHps > 0 ) + { + fVal = double(aInfo.nHps) / 2.; + aVal <<= fVal; + } + else if (aInfo.nSprmId == NS_ooxml::LN_CT_Ruby_rubyBase && aInfo.nHpsBaseText > 0 ) + { + fVal = double(aInfo.nHpsBaseText) / 2.; + aVal <<= fVal; + } + //Asian get the same value as Western + rContext->Insert( PROP_CHAR_HEIGHT, aVal ); + rContext->Insert( PROP_CHAR_HEIGHT_ASIAN, aVal ); + } + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, (nSprmId == NS_ooxml::LN_EG_RPrBase_sz ? OUString("sz") : OUString("szCs")), OUString::number(nIntValue)); + } + break; + case NS_ooxml::LN_EG_RPrBase_position: + // The spec says 0 is the same as the lack of the value, so don't parse that. + if ( nIntValue ) + { + if (!IsStyleSheetImport() && !IsNumberingImport()) + m_pImpl->deferCharacterProperty( nSprmId, uno::Any( nIntValue )); + else if (!m_pImpl->IsDocDefaultsImport()) + { + // For some undocumented reason, MS Word seems to ignore this in docDefaults + + // DON'T FIXME: Truly calculating this for Character Styles will be tricky, + // because it depends on the final fontsize - regardless of + // where it is set. So at the style level, + // the escapement value would need to be grabbagged. + // At appendText time the final fontsize needs to be determined, and then + // the escapement can be calculated from the grabbag'd half-point value + // and directly applied. Yuck. + // It seems best to just treat charstyle escapement like + // pre-commit e70df84352d3670508a4666c97df44f82c1ce934 + // which just assigned default values and ignored the actual/given escapement. + sal_Int16 nEscapement = nIntValue > 0 ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB; + sal_Int8 nProp = DFLT_ESC_PROP; + rContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( nEscapement ) ); + rContext->Insert(PROP_CHAR_ESCAPEMENT_HEIGHT, uno::Any( nProp ) ); + } + } + break; + case NS_ooxml::LN_EG_RPrBase_spacing: + { + //Kerning half point values + //TODO: there are two kerning values - + // in ww8par6.cxx NS_sprm::LN_CHpsKern is used as boolean AutoKerning + sal_Int16 nResult = static_cast<sal_Int16>(ConversionHelper::convertTwipToMM100(nIntValue)); + if (m_pImpl->IsInComments()) + { + nResult = static_cast<sal_Int16>(nIntValue); + } + rContext->Insert(PROP_CHAR_CHAR_KERNING, uno::Any(nResult)); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "spacing", OUString::number(nIntValue)); + } + break; + case NS_ooxml::LN_EG_RPrBase_kern: // auto kerning is bound to a minimum font size in Word - but not in Writer :-( + rContext->Insert(PROP_CHAR_AUTO_KERNING, uno::Any( nIntValue != 0 ) ); + break; + case NS_ooxml::LN_EG_RPrBase_w: + // ST_TextScale must fall between 1% and 600% according to spec, otherwise resets to 100% according to experience + if ((1 <= nIntValue) && (nIntValue <= 600)) + { + rContext->Insert(PROP_CHAR_SCALE_WIDTH, + uno::Any( sal_Int16(nIntValue) )); + } + else + { + rContext->Insert(PROP_CHAR_SCALE_WIDTH, + uno::Any( sal_Int16(100) )); + } + break; + case NS_ooxml::LN_EG_RPrBase_imprint: + // FontRelief: NONE, EMBOSSED, ENGRAVED + rContext->Insert(PROP_CHAR_RELIEF, + uno::Any( nIntValue ? awt::FontRelief::ENGRAVED : awt::FontRelief::NONE )); + break; + case NS_ooxml::LN_EG_RPrBase_effect: + // The file-format has many character animations. We have only + // one, so we use it always. Suboptimal solution though. + if (nIntValue != NS_ooxml::LN_Value_ST_TextEffect_none) + rContext->Insert(PROP_CHAR_FLASH, uno::Any( true )); + else + rContext->Insert(PROP_CHAR_FLASH, uno::Any( false )); + break; + case NS_ooxml::LN_EG_RPrBase_rtl: + break; + case NS_ooxml::LN_EG_RPrBase_shd: + { + //contains fore color, back color and shadow percentage, results in a brush + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pCellColorHandler = std::make_shared<CellColorHandler>(); + pCellColorHandler->setOutputFormat( CellColorHandler::Character ); + pProperties->resolve(*pCellColorHandler); + rContext->InsertProps(pCellColorHandler->getProperties().get()); + m_pImpl->GetTopContext()->Insert(PROP_CHAR_SHADING_MARKER, uno::Any(true), true, CHAR_GRAB_BAG ); + } + break; + } + case NS_ooxml::LN_EG_SectPrContents_type: + /* break type + 0 - No break + 1 - New Column + 2 - New page + 3 - Even page + 4 - odd page + */ + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + //continuous break only allowed if it is not the only section break + SectionPropertyMap* pLastContext = m_pImpl->GetLastSectionContext(); + if ( nIntValue != NS_ooxml::LN_Value_ST_SectionMark_continuous || pLastContext || m_pImpl->GetParaSectpr() ) + pSectionContext->SetBreakType( nIntValue ); + } + break; + case NS_ooxml::LN_EG_SectPrContents_titlePg: + { + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + pSectionContext->SetTitlePage( nIntValue > 0 );//section has title page + } + break; + case 165: + { + //page height, rounded to default values, default: 0x3dc0 twip + sal_Int32 nHeight = ConversionHelper::convertTwipToMM100( nIntValue ); + rContext->Insert( PROP_HEIGHT, uno::Any( PaperInfo::sloppyFitPageDimension( nHeight ) ) ); + } + break; + case NS_ooxml::LN_EG_SectPrContents_textDirection: + { + /* 0 HoriLR 1 Vert TR 2 Vert TR 3 Vert TT 4 HoriLT + only 0 and 1 can be imported correctly + */ + text::WritingMode nDirection = text::WritingMode_LR_TB; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_TextDirection_lrTb: + case NS_ooxml::LN_Value_ST_TextDirection_lrTbV: + nDirection = text::WritingMode_LR_TB; + break; + case NS_ooxml::LN_Value_ST_TextDirection_tbRl: + case NS_ooxml::LN_Value_ST_TextDirection_btLr: + nDirection = text::WritingMode_TB_RL; + break; + default:; + } + + PropertyMap * pTargetContext = rContext.get(); + + if (pSectionContext) + { + pTargetContext = pSectionContext; + } + + pTargetContext->Insert(PROP_WRITING_MODE, uno::Any( sal_Int16(nDirection) ) ); + } + break; // sprmSTextFlow + // the following are not part of the official documentation + case NS_ooxml::LN_CT_Tabs_tab: + resolveSprmProps(*this, rSprm); + m_pImpl->IncorporateTabStop(m_pImpl->m_aCurrentTabStop); + m_pImpl->m_aCurrentTabStop = DeletableTabStop(); + break; + case NS_ooxml::LN_CT_PPrBase_tabs: + { + // Initialize tab stop vector from style sheet + // fdo#81033: for RTF, a tab stop is inherited from the style if it + // is also applied to the paragraph directly, and cleared if it is + // not applied to the paragraph directly => don't InitTabStopFromStyle + if ( !IsRTFImport() ) + { + uno::Any aValue = m_pImpl->GetPropertyFromParaStyleSheet(PROP_PARA_TAB_STOPS); + uno::Sequence< style::TabStop > aStyleTabStops; + if(aValue >>= aStyleTabStops) + { + m_pImpl->InitTabStopFromStyle( aStyleTabStops ); + } + } + resolveSprmProps(*this, rSprm); + rContext->Insert(PROP_PARA_TAB_STOPS, uno::Any( m_pImpl->GetCurrentTabStopAndClear())); + } + break; + + case NS_ooxml::LN_CT_DocDefaults_pPrDefault: + case NS_ooxml::LN_CT_DocDefaults_rPrDefault: + GetStyleSheetTable()->sprm( rSprm ); + break; + case NS_ooxml::LN_EG_RPrBase_bdr: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pBorderHandler = std::make_shared<BorderHandler>( true ); + pProperties->resolve(*pBorderHandler); + + rContext->Insert( PROP_CHAR_TOP_BORDER, uno::Any( pBorderHandler->getBorderLine())); + rContext->Insert( PROP_CHAR_BOTTOM_BORDER, uno::Any( pBorderHandler->getBorderLine())); + rContext->Insert( PROP_CHAR_LEFT_BORDER, uno::Any( pBorderHandler->getBorderLine())); + rContext->Insert( PROP_CHAR_RIGHT_BORDER, uno::Any( pBorderHandler->getBorderLine())); + + rContext->Insert( PROP_CHAR_TOP_BORDER_DISTANCE, uno::Any( pBorderHandler->getLineDistance())); + rContext->Insert( PROP_CHAR_BOTTOM_BORDER_DISTANCE, uno::Any( pBorderHandler->getLineDistance())); + rContext->Insert( PROP_CHAR_LEFT_BORDER_DISTANCE, uno::Any( pBorderHandler->getLineDistance())); + rContext->Insert( PROP_CHAR_RIGHT_BORDER_DISTANCE, uno::Any( pBorderHandler->getLineDistance())); + + table::ShadowFormat aFormat; + // Word only allows shadows on visible borders + if ( pBorderHandler->getShadow() && pBorderHandler->getBorderLine().LineStyle != table::BorderLineStyle::NONE ) + aFormat = writerfilter::dmapper::PropertyMap::getShadowFromBorder(pBorderHandler->getBorderLine()); + rContext->Insert(PROP_CHAR_SHADOW_FORMAT, uno::Any(aFormat)); + } + } + break; + case NS_ooxml::LN_CT_PPr_sectPr: + case NS_ooxml::LN_EG_RPrBase_color: + case NS_ooxml::LN_EG_RPrBase_rFonts: + case NS_ooxml::LN_EG_RPrBase_eastAsianLayout: + case NS_ooxml::LN_EG_RPrBase_u: + case NS_ooxml::LN_EG_RPrBase_lang: + case NS_ooxml::LN_CT_PPrBase_spacing: + case NS_ooxml::LN_CT_PPrBase_ind: + case NS_ooxml::LN_CT_RPrDefault_rPr: + case NS_ooxml::LN_CT_PPrDefault_pPr: + case NS_ooxml::LN_CT_Style_pPr: + case NS_ooxml::LN_CT_Style_rPr: + case NS_ooxml::LN_CT_PPr_rPr: + case NS_ooxml::LN_CT_PPrBase_numPr: + { + bool bTempGrabBag = !m_pImpl->isInteropGrabBagEnabled(); + if (nSprmId == NS_ooxml::LN_CT_PPr_sectPr) + m_pImpl->SetParaSectpr(true); + else if (nSprmId == NS_ooxml::LN_EG_RPrBase_color && bTempGrabBag) + // if DomainMapper grab bag is not enabled, enable it temporarily + m_pImpl->enableInteropGrabBag("TempColorPropsGrabBag"); + resolveSprmProps(*this, rSprm); + if (nSprmId == NS_ooxml::LN_CT_PPrBase_spacing) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "spacing", m_pImpl->m_aSubInteropGrabBag); + else if (nSprmId == NS_ooxml::LN_EG_RPrBase_rFonts) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "rFonts", m_pImpl->m_aSubInteropGrabBag); + else if (nSprmId == NS_ooxml::LN_EG_RPrBase_lang) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "lang", m_pImpl->m_aSubInteropGrabBag); + else if (nSprmId == NS_ooxml::LN_EG_RPrBase_color) + { + for (const auto& rItem : m_pImpl->m_aSubInteropGrabBag) + { + if (rItem.Name == "val") + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_ORIGINAL_COLOR, rItem.Value, true, CHAR_GRAB_BAG); + else if (rItem.Name == "themeColor") + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_COLOR, rItem.Value, true, CHAR_GRAB_BAG); + else if (rItem.Name == "themeShade") + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_COLOR_SHADE, rItem.Value, true, CHAR_GRAB_BAG); + else if (rItem.Name == "themeTint") + m_pImpl->GetTopContext()->Insert(PROP_CHAR_THEME_COLOR_TINT, rItem.Value, true, CHAR_GRAB_BAG); + } + if (bTempGrabBag) + //disable and clear DomainMapper grab bag if it wasn't enabled before + m_pImpl->disableInteropGrabBag(); + + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "color", m_pImpl->m_aSubInteropGrabBag); + } + else if (nSprmId == NS_ooxml::LN_CT_PPrBase_ind) + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ind", m_pImpl->m_aSubInteropGrabBag); + } + break; + case NS_ooxml::LN_CT_PPrBase_wordWrap: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "wordWrap", ""); + break; + case NS_ooxml::LN_EG_SectPrContents_footnotePr: + case NS_ooxml::LN_EG_SectPrContents_endnotePr: + m_pImpl->SetInFootnoteProperties( NS_ooxml::LN_EG_SectPrContents_footnotePr == nSprmId ); + resolveSprmProps(*this, rSprm); + break; + case NS_ooxml::LN_EG_SectPrContents_lnNumType: + { + resolveSprmProps(*this, rSprm); + LineNumberSettings aSettings = m_pImpl->GetLineNumberSettings(); + m_pImpl->SetLineNumberSettings( aSettings ); + //apply settings at XLineNumberingProperties + try + { + uno::Reference< text::XLineNumberingProperties > xLineNumberingProperties( m_pImpl->GetTextDocument(), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xLineNumberingPropSet = xLineNumberingProperties->getLineNumberingProperties(); + if( aSettings.nInterval == 0 ) + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_IS_ON ), uno::Any(false) ); + else + { + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_IS_ON ), uno::Any(true) ); + if( aSettings.nInterval ) + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_INTERVAL ), uno::Any(static_cast<sal_Int16>(aSettings.nInterval)) ); + if( aSettings.nDistance != -1 ) + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_DISTANCE ), uno::Any(aSettings.nDistance) ); + else + { + // set Auto value (0.5 cm) + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_DISTANCE ), uno::Any(static_cast<sal_Int32>(500)) ); + if( pSectionContext ) + pSectionContext->SetdxaLnn( static_cast<sal_Int32>(283) ); + } + xLineNumberingPropSet->setPropertyValue(getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::Any(aSettings.bRestartAtEachPage) ); + } + } + catch( const uno::Exception& ) + { + } + + } + break; + case NS_ooxml::LN_CT_PPrBase_framePr: + // Avoid frames if we're inside a structured document tag, would just cause outer tables fail to create. + if (!m_pImpl->GetSdt()) + { + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if( pContext ) + { + // If there is a deferred page break applied to this framed paragraph, + // create a dummy paragraph without extra properties, + // so that the anchored frame will be on the correct page (similar to shapes). + if (pContext->isSet(PROP_BREAK_TYPE)) + { + pContext->Erase(PROP_BREAK_TYPE); + + lcl_startParagraphGroup(); + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + lcl_startCharacterGroup(); + sal_uInt8 const sBreak[] = { 0xd }; + lcl_text(sBreak, 1); + lcl_endCharacterGroup(); + lcl_endParagraphGroup(); + } + + ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pContext.get() ); + if (pParaContext) + pParaContext->SetFrameMode(); + + if (!IsInHeaderFooter()) + m_pImpl->m_bIsActualParagraphFramed = true; + } + else + { + //TODO: What about style sheet import of frame properties + } + m_pImpl->NewFrameDirection(); + resolveSprmProps(*this, rSprm); + } + break; + case NS_ooxml::LN_EG_SectPrContents_pgSz: + { + PaperInfo aLetter(PAPER_LETTER); + CT_PageSz.w = aLetter.getWidth(); + CT_PageSz.h = aLetter.getHeight(); + } + CT_PageSz.orient = false; + resolveSprmProps(*this, rSprm); + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + if (!m_pImpl->IsAltChunk()) + { + pSectionContext->Insert(PROP_HEIGHT, uno::Any(CT_PageSz.h)); + } + pSectionContext->Insert( PROP_IS_LANDSCAPE, uno::Any( CT_PageSz.orient )); + if (!m_pImpl->IsAltChunk()) + { + pSectionContext->Insert(PROP_WIDTH, uno::Any(CT_PageSz.w)); + } + } + break; + + case NS_ooxml::LN_EG_SectPrContents_pgMar: + m_pImpl->InitPageMargins(); + resolveSprmProps(*this, rSprm); + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + const PageMar& rPageMar = m_pImpl->GetPageMargins(); + pSectionContext->SetTopMargin( rPageMar.top ); + pSectionContext->SetRightMargin( rPageMar.right ); + pSectionContext->SetBottomMargin( rPageMar.bottom ); + pSectionContext->SetLeftMargin( rPageMar.left ); + pSectionContext->SetHeaderTop( rPageMar.header ); + pSectionContext->SetHeaderBottom( rPageMar.footer ); + pSectionContext->SetGutterMargin(rPageMar.gutter); + } + break; + + case NS_ooxml::LN_EG_SectPrContents_cols: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + + tools::SvRef< SectionColumnHandler > pSectHdl( new SectionColumnHandler ); + pProperties->resolve(*pSectHdl); + if(pSectionContext && !m_pImpl->isInIndexContext()) + { + sal_Int16 nColumnCount = pSectHdl->GetNum() == 1 ? 0 : pSectHdl->GetNum(); + if( pSectHdl->IsEqualWidth() ) + { + pSectionContext->SetEvenlySpaced( true ); + pSectionContext->SetColumnCount( nColumnCount ); + pSectionContext->SetColumnDistance( pSectHdl->GetSpace() ); + pSectionContext->SetSeparatorLine( pSectHdl->IsSeparator() ); + } + else if( !pSectHdl->GetColumns().empty() ) + { + pSectionContext->SetEvenlySpaced( false ); + pSectionContext->SetColumnDistance( pSectHdl->GetSpace() ); + nColumnCount = pSectHdl->GetColumns().size(); + pSectionContext->SetColumnCount( nColumnCount == 1 ? 0 : nColumnCount ); + std::vector<Column_>::const_iterator tmpIter = pSectHdl->GetColumns().begin(); + for (; tmpIter != pSectHdl->GetColumns().end(); ++tmpIter) + { + pSectionContext->AppendColumnWidth( tmpIter->nWidth ); + if ((tmpIter != pSectHdl->GetColumns().end() - 1) || (tmpIter->nSpace > 0)) + pSectionContext->AppendColumnSpacing( tmpIter->nSpace ); + } + pSectionContext->SetSeparatorLine( pSectHdl->IsSeparator() ); + } + else if( nColumnCount ) + { + pSectionContext->SetColumnCount( nColumnCount ); + pSectionContext->SetColumnDistance( pSectHdl->GetSpace() ); + pSectionContext->SetSeparatorLine( pSectHdl->IsSeparator() ); + } + } + + else if ( pSectionContext ) + { + FieldContextPtr pContext = m_pImpl->GetTopFieldContext(); + uno::Reference< beans::XPropertySet > xTOC = pContext->GetTOC(); + if( xTOC.is() ) + { + uno::Reference<text::XTextColumns> xTextColumns; + xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns; + if (xTextColumns.is()) + { + uno::Reference< beans::XPropertySet > xColumnPropSet( xTextColumns, uno::UNO_QUERY_THROW ); + xColumnPropSet->setPropertyValue( getPropertyName( PROP_AUTOMATIC_DISTANCE ), uno::Any( pSectHdl->GetSpace() )); + xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xTextColumns ) ); + } + } + } + } + } + break; + case NS_ooxml::LN_EG_SectPrContents_docGrid: + resolveSprmProps(*this, rSprm); + break; + case NS_ooxml::LN_EG_SectPrContents_pgBorders: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties && pSectionContext ) + { + tools::SvRef< PageBordersHandler > pHandler( new PageBordersHandler ); + pProperties->resolve( *pHandler ); + + // Set the borders to the context and apply them to the styles + pHandler->SetBorders( pSectionContext ); + } + } + break; + + case NS_ooxml::LN_CT_PPrBase_snapToGrid: + if (!IsStyleSheetImport()||!m_pImpl->isInteropGrabBagEnabled()) + { + rContext->Insert( PROP_SNAP_TO_GRID, uno::Any(bool(nIntValue))); + } + else + { + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "snapToGrid", OUString::number(nIntValue)); + } + break; + case NS_ooxml::LN_CT_PPrBase_pStyle: + { + StyleSheetTablePtr pStyleTable = m_pImpl->GetStyleSheetTable(); + const OUString sConvertedStyleName = pStyleTable->ConvertStyleName( sStringValue, true ); + m_pImpl->SetCurrentParaStyleName( sConvertedStyleName ); + if (m_pImpl->GetTopContext() && m_pImpl->GetTopContextType() != CONTEXT_SECTION) + m_pImpl->GetTopContext()->Insert( PROP_PARA_STYLE_NAME, uno::Any( sConvertedStyleName )); + } + break; + case NS_ooxml::LN_EG_RPrBase_rStyle: + { + OUString sConvertedName( m_pImpl->GetStyleSheetTable()->ConvertStyleName( sStringValue, true ) ); + if (m_pImpl->CheckFootnoteStyle() && m_pImpl->GetFootnoteContext()) + m_pImpl->SetHasFootnoteStyle(m_pImpl->GetFootnoteContext()->GetFootnoteStyle() == sConvertedName); + + // First check if the style exists in the document. + StyleSheetEntryPtr pEntry = m_pImpl->GetStyleSheetTable( )->FindStyleSheetByConvertedStyleName( sConvertedName ); + bool bExists = pEntry && ( pEntry->nStyleTypeCode == STYLE_TYPE_CHAR ); + // Add the property if the style exists, but do not add it elements in TOC: + // they will receive later another style references from TOC + if ( bExists && m_pImpl->GetTopContext() && !m_pImpl->IsInTOC()) + m_pImpl->GetTopContext()->Insert( PROP_CHAR_STYLE_NAME, uno::Any( sConvertedName ) ); + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblCellMar: //cell margins + { + resolveSprmProps(*this, rSprm);//contains LN_CT_TblCellMar_top, LN_CT_TblCellMar_left, LN_CT_TblCellMar_bottom, LN_CT_TblCellMar_right + } + break; + case NS_ooxml::LN_CT_TblCellMar_top: + case NS_ooxml::LN_CT_TblCellMar_start: + case NS_ooxml::LN_CT_TblCellMar_left: + case NS_ooxml::LN_CT_TblCellMar_bottom: + case NS_ooxml::LN_CT_TblCellMar_end: + case NS_ooxml::LN_CT_TblCellMar_right: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + MeasureHandlerPtr pMeasureHandler( new MeasureHandler ); + pProperties->resolve(*pMeasureHandler); + sal_Int32 nMeasureValue = pMeasureHandler->getMeasureValue(); + PropertyIds eId = META_PROP_CELL_MAR_TOP; + bool rtl = false; // TODO + switch(nSprmId) + { + case NS_ooxml::LN_CT_TblCellMar_top: + break; + case NS_ooxml::LN_CT_TblCellMar_start: + eId = rtl ? META_PROP_CELL_MAR_RIGHT : META_PROP_CELL_MAR_LEFT; + break; + case NS_ooxml::LN_CT_TblCellMar_left: + eId = META_PROP_CELL_MAR_LEFT; + break; + case NS_ooxml::LN_CT_TblCellMar_bottom: + eId = META_PROP_CELL_MAR_BOTTOM; + break; + case NS_ooxml::LN_CT_TblCellMar_end: + eId = rtl ? META_PROP_CELL_MAR_LEFT : META_PROP_CELL_MAR_RIGHT; + break; + case NS_ooxml::LN_CT_TblCellMar_right: + eId = META_PROP_CELL_MAR_RIGHT; + break; + default:; + } + rContext->Insert( eId, uno::Any(nMeasureValue), false); + } + } + break; + case NS_ooxml::LN_EG_RPrBase_noProof: // no grammar and spell checking, unsupported + break; + case NS_ooxml::LN_anchor_anchor: // at_character drawing + case NS_ooxml::LN_inline_inline: // as_character drawing + { + if ( m_pImpl->IsDiscardHeaderFooter() ) + break; + //tdf112342: Break before images as well, if there are page break + if (m_pImpl->isBreakDeferred(BreakType::PAGE_BREAK)) + { + m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH) + ->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + m_pImpl->clearDeferredBreak(PAGE_BREAK); + } + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + GraphicImportType eGraphicType = + (NS_ooxml::LN_anchor_anchor == + sal::static_int_cast<Id>(nSprmId)) ? + IMPORT_AS_DETECTED_ANCHOR : + IMPORT_AS_DETECTED_INLINE; + GraphicImportPtr pGraphicImport = + m_pImpl->GetGraphicImport(eGraphicType); + pProperties->resolve(*pGraphicImport); + m_pImpl->ImportGraphic(pProperties, eGraphicType); + if( !pGraphicImport->IsGraphic() ) + { + m_pImpl->ResetGraphicImport(); + // todo: It's a shape, now start shape import + } + } + } + break; + case NS_ooxml::LN_EG_RPrBase_vertAlign: + { + sal_Int16 nEscapement = 0; + sal_Int8 nProp = DFLT_ESC_PROP; + if ( sStringValue == "superscript" ) + nEscapement = DFLT_ESC_AUTO_SUPER; + else if ( sStringValue == "subscript" ) + nEscapement = DFLT_ESC_AUTO_SUB; + else + nProp = 100; + + rContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( nEscapement ) ); + rContext->Insert(PROP_CHAR_ESCAPEMENT_HEIGHT, uno::Any( nProp ) ); + } + break; + case NS_ooxml::LN_CT_FtnProps_pos: + //footnotes in word can be at page end or beneath text - writer supports only the first + //endnotes in word can be at section end or document end - writer supports only the latter + // -> so this property can be ignored + break; + case NS_ooxml::LN_CT_FtnProps_numFmt: + case NS_ooxml::LN_CT_EdnProps_numFmt: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + } + break; + case NS_ooxml::LN_EG_FtnEdnNumProps_numStart: + case NS_ooxml::LN_EG_FtnEdnNumProps_numRestart: + { + try + { + uno::Reference< beans::XPropertySet > xFtnEdnSettings; + if( m_pImpl->IsInFootnoteProperties() ) + { + uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( m_pImpl->GetTextDocument(), uno::UNO_QUERY ); + if (xFootnotesSupplier.is()) + xFtnEdnSettings = xFootnotesSupplier->getFootnoteSettings(); + } + else + { + uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( m_pImpl->GetTextDocument(), uno::UNO_QUERY ); + if (xEndnotesSupplier.is()) + xFtnEdnSettings = xEndnotesSupplier->getEndnoteSettings(); + } + if( NS_ooxml::LN_EG_FtnEdnNumProps_numStart == nSprmId && xFtnEdnSettings.is()) + { + xFtnEdnSettings->setPropertyValue( + getPropertyName( PROP_START_AT), + uno::Any( sal_Int16( nIntValue - 1 ))); + } + else if( NS_ooxml::LN_EG_FtnEdnNumProps_numRestart == nSprmId && xFtnEdnSettings.is()) + { + sal_Int16 nFootnoteCounting = 0; + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_RestartNumber_continuous: nFootnoteCounting = text::FootnoteNumbering::PER_DOCUMENT; break; + case NS_ooxml::LN_Value_ST_RestartNumber_eachPage: nFootnoteCounting = text::FootnoteNumbering::PER_PAGE; break; + case NS_ooxml::LN_Value_ST_RestartNumber_eachSect: nFootnoteCounting = text::FootnoteNumbering::PER_CHAPTER; break; + default: break; + } + xFtnEdnSettings->setPropertyValue( + getPropertyName( PROP_FOOTNOTE_COUNTING ), + uno::Any( nFootnoteCounting )); + } + else if (xFtnEdnSettings.is()) + { + sal_Int16 nNumType = ConversionHelper::ConvertNumberingType( nIntValue ); + xFtnEdnSettings->setPropertyValue( + getPropertyName( PROP_NUMBERING_TYPE), + uno::Any( nNumType )); + } + } + catch( const uno::Exception& ) + { + } + } + break; + case NS_ooxml::LN_EG_RangeMarkupElements_moveFromRangeStart: + m_pImpl->SetMoveBookmark(/*bIsFrom=*/true); + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().setMoved( getPropertyName(PROP_TABLE_ROW_DELETE) ); + break; + case NS_ooxml::LN_EG_RangeMarkupElements_moveToRangeStart: + m_pImpl->SetMoveBookmark(/*bIsFrom=*/false); + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().setMoved( getPropertyName(PROP_TABLE_ROW_INSERT) ); + break; + case NS_ooxml::LN_EG_RangeMarkupElements_moveFromRangeEnd: + case NS_ooxml::LN_EG_RangeMarkupElements_moveToRangeEnd: + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().setMoved( OUString() ); + break; + case NS_ooxml::LN_CT_ParaRPr_moveFrom: + case NS_ooxml::LN_CT_ParaRPr_moveTo: + m_pImpl->StartParaMarkerMove( ); + break; + case NS_ooxml::LN_paratrackchange: + m_pImpl->StartParaMarkerChange( ); + [[fallthrough]]; + case NS_ooxml::LN_CT_PPr_pPrChange: + case NS_ooxml::LN_CT_ParaRPr_rPrChange: + case NS_ooxml::LN_trackchange: + case NS_ooxml::LN_EG_RPrContent_rPrChange: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlDelRangeStart: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlDelRangeEnd: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlMoveFromRangeStart: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlMoveFromRangeEnd: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlMoveToRangeStart: + case NS_ooxml::LN_EG_RangeMarkupElements_customXmlMoveToRangeEnd: + { + HandleRedline( rSprm ); + } + break; + case NS_ooxml::LN_endtrackchange: + m_pImpl->RemoveTopRedline(); + break; + case NS_ooxml::LN_CT_RPrChange_rPr: + { + // Push all the current 'Character' properties to the stack, so that we don't store them + // as 'tracked changes' by mistake + m_pImpl->PushProperties(CONTEXT_CHARACTER); + + // Resolve all the properties that are under the 'rPrChange'->'rPr' XML node + resolveSprmProps(*this, rSprm ); + + // Get all the properties that were processed in the 'rPrChange'->'rPr' XML node + uno::Sequence< beans::PropertyValue > currentRedlineRevertProperties = m_pImpl->GetTopContext()->GetPropertyValues(); + + // Pop back out the character properties that were on the run + m_pImpl->PopProperties(CONTEXT_CHARACTER); + + // Store these properties in the current redline object (do it after the PopProperties() above, since + // otherwise it'd be stored in the content dropped there). + m_pImpl->SetCurrentRedlineRevertProperties( currentRedlineRevertProperties ); + } + break; + case NS_ooxml::LN_CT_PPrChange_pPr: + { + // Push all the current 'Paragraph' properties to the stack, so that we don't store them + // as 'tracked changes' by mistake + m_pImpl->PushProperties(CONTEXT_PARAGRAPH); + + // Resolve all the properties that are under the 'pPrChange'->'pPr' XML node + resolveSprmProps(*this, rSprm ); + + // Get all the properties that were processed in the 'pPrChange'->'pPr' XML node + uno::Sequence< beans::PropertyValue > currentRedlineRevertProperties = m_pImpl->GetTopContext()->GetPropertyValues(); + + // Pop back out the character properties that were on the run + m_pImpl->PopProperties(CONTEXT_PARAGRAPH); + + // Store these properties in the current redline object (do it after the PopProperties() above, since + // otherwise it'd be stored in the content dropped there). + m_pImpl->SetCurrentRedlineRevertProperties( currentRedlineRevertProperties ); + } + break; + case NS_ooxml::LN_object: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pOLEHandler = std::make_shared<OLEHandler>(*this); + pProperties->resolve(*pOLEHandler); + if ( pOLEHandler->isOLEObject( ) ) + { + OUString sStreamName = pOLEHandler->copyOLEOStream( m_pImpl->GetTextDocument() ); + if( !sStreamName.isEmpty() ) + { + m_pImpl->appendOLE( sStreamName, pOLEHandler ); + } + } + } + } + break; + case NS_ooxml::LN_EG_HdrFtrReferences_headerReference: // header reference - not needed + case NS_ooxml::LN_EG_HdrFtrReferences_footerReference: // footer reference - not needed + break; + case NS_ooxml::LN_EG_RPrBase_snapToGrid: // "Use document grid settings for inter-paragraph spacing" + break; + case NS_ooxml::LN_CT_PPrBase_contextualSpacing: + rContext->Insert(PROP_PARA_CONTEXT_MARGIN, uno::Any( nIntValue != 0 )); + break; + case NS_ooxml::LN_CT_PPrBase_mirrorIndents: // mirrorIndents + rContext->Insert(PROP_MIRROR_INDENTS, uno::Any( nIntValue != 0 ), true, PARA_GRAB_BAG); + break; + case NS_ooxml::LN_EG_SectPrContents_formProt: //section protection + { + if( pSectionContext ) + pSectionContext->Insert( PROP_IS_PROTECTED, uno::Any( bool(nIntValue) ) ); + } + break; + case NS_ooxml::LN_EG_SectPrContents_vAlign: + { + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if( pSectionContext ) + { + drawing::TextVerticalAdjust nVA = drawing::TextVerticalAdjust_TOP; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_VerticalJc_center: //92367 + nVA = drawing::TextVerticalAdjust_CENTER; + break; + case NS_ooxml::LN_Value_ST_VerticalJc_both: //92368 - justify + nVA = drawing::TextVerticalAdjust_BLOCK; + break; + case NS_ooxml::LN_Value_ST_VerticalJc_bottom: //92369 + nVA = drawing::TextVerticalAdjust_BOTTOM; + break; + default: + break; + } + pSectionContext->Insert( PROP_TEXT_VERTICAL_ADJUST, uno::Any( nVA ), true, PARA_GRAB_BAG ); + } + } + break; + case NS_ooxml::LN_EG_RPrBase_fitText: + break; + case NS_ooxml::LN_ffdata: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + FFDataHandler::Pointer_t pFFDataHandler(new FFDataHandler()); + + pProperties->resolve(*pFFDataHandler); + m_pImpl->SetFieldFFData(pFFDataHandler); + } + } + break; + case NS_ooxml::LN_CT_SdtPr_dropDownList: + case NS_ooxml::LN_CT_SdtPr_comboBox: + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::dropDown); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_SdtDropDownList_listItem: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + + size_t nDropDownDisplayTexts = m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().size(); + size_t nDropDownItems = m_pImpl->m_pSdtHelper->getDropDownItems().size(); + + if (pProperties) + pProperties->resolve(*this); + + if (m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().size() != nDropDownDisplayTexts + 1) + { + // w:displayText="..." is optional, add empty value if it was not provided. + m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().push_back(OUString()); + } + if (m_pImpl->m_pSdtHelper->getDropDownItems().size() != nDropDownItems + 1) + { + // w:value="..." is optional, add empty value if it was not provided. + m_pImpl->m_pSdtHelper->getDropDownItems().push_back(OUString()); + } + } + break; + case NS_ooxml::LN_CT_SdtPr_placeholder: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + break; + case NS_ooxml::LN_CT_SdtPr_date: + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker); + resolveSprmProps(*this, rSprm); + m_pImpl->m_pSdtHelper->setDateFieldStartRange(GetCurrentTextRange()->getEnd()); + } + break; + case NS_ooxml::LN_CT_SdtDate_dateFormat: + { + m_pImpl->m_pSdtHelper->getDateFormat().append(sStringValue); + } + break; + case NS_ooxml::LN_CT_SdtDate_storeMappedDataAs: + { + } + break; + case NS_ooxml::LN_CT_SdtDate_calendar: + { + } + break; + case NS_ooxml::LN_CT_SdtDate_lid: + { + m_pImpl->m_pSdtHelper->getLocale().append(sStringValue); + } + break; + case NS_ooxml::LN_CT_SdtPr_text: + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); + enableInteropGrabBag("ooxml:CT_SdtPr_text"); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + m_pImpl->m_pSdtHelper->appendToInteropGrabBag(getInteropGrabBag()); + m_pImpl->disableInteropGrabBag(); + } + break; + case NS_ooxml::LN_CT_SdtPr_showingPlcHdr: + { + m_pImpl->m_pSdtHelper->SetShowingPlcHdr(); + } + break; + case NS_ooxml::LN_CT_SdtPr_dataBinding: + case NS_ooxml::LN_CT_SdtPr_equation: + case NS_ooxml::LN_CT_SdtPr_checkbox: + case NS_ooxml::LN_CT_SdtPr_docPartObj: + case NS_ooxml::LN_CT_SdtPr_docPartList: + case NS_ooxml::LN_CT_SdtPr_picture: + case NS_ooxml::LN_CT_SdtPr_citation: + case NS_ooxml::LN_CT_SdtPr_group: + case NS_ooxml::LN_CT_SdtPr_id: + case NS_ooxml::LN_CT_SdtPr_alias: + case NS_ooxml::LN_CT_SdtPlaceholder_docPart: + case NS_ooxml::LN_CT_SdtPr_color: + { + if (!m_pImpl->GetSdtStarts().empty()) + { + if (nSprmId == NS_ooxml::LN_CT_SdtPr_color) + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + break; + } + + if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox) + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + break; + } + else if (nSprmId == NS_ooxml::LN_CT_SdtPr_picture) + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::picture); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + break; + } + else if (nSprmId == NS_ooxml::LN_CT_SdtPr_date) + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + break; + } + } + + // this is an unsupported SDT property, create a grab bag for it + OUString sName; + switch (nSprmId) + { + case NS_ooxml::LN_CT_SdtPr_dataBinding: sName = "ooxml:CT_SdtPr_dataBinding"; break; + case NS_ooxml::LN_CT_SdtPr_equation: sName = "ooxml:CT_SdtPr_equation"; break; + case NS_ooxml::LN_CT_SdtPr_checkbox: sName = "ooxml:CT_SdtPr_checkbox"; break; + case NS_ooxml::LN_CT_SdtPr_docPartObj: sName = "ooxml:CT_SdtPr_docPartObj"; break; + case NS_ooxml::LN_CT_SdtPr_docPartList: sName = "ooxml:CT_SdtPr_docPartList"; break; + case NS_ooxml::LN_CT_SdtPr_picture: sName = "ooxml:CT_SdtPr_picture"; break; + case NS_ooxml::LN_CT_SdtPr_citation: sName = "ooxml:CT_SdtPr_citation"; break; + case NS_ooxml::LN_CT_SdtPr_group: sName = "ooxml:CT_SdtPr_group"; break; + case NS_ooxml::LN_CT_SdtPr_id: sName = "ooxml:CT_SdtPr_id"; break; + case NS_ooxml::LN_CT_SdtPr_alias: sName = "ooxml:CT_SdtPr_alias"; break; + case NS_ooxml::LN_CT_SdtPlaceholder_docPart: sName = "ooxml:CT_SdtPlaceholder_docPart"; break; + case NS_ooxml::LN_CT_SdtPr_color: sName = "ooxml:CT_SdtPr_color"; break; + default: assert(false); + }; + if ( + nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox || + nSprmId == NS_ooxml::LN_CT_SdtPr_docPartObj || + nSprmId == NS_ooxml::LN_CT_SdtPr_docPartList || + nSprmId == NS_ooxml::LN_CT_SdtPr_picture || + nSprmId == NS_ooxml::LN_CT_SdtPr_citation) + { + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::unsupported); + } + enableInteropGrabBag(sName); + + // process subitems + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + + if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias) + { + beans::PropertyValue aValue; + aValue.Name = sName; + aValue.Value <<= sStringValue; + m_pImpl->m_pSdtHelper->appendToInteropGrabBag(aValue); + } + else + m_pImpl->m_pSdtHelper->appendToInteropGrabBag(getInteropGrabBag()); + m_pImpl->m_pSdtHelper->setOutsideAParagraph(m_pImpl->IsOutsideAParagraph()); + m_pImpl->disableInteropGrabBag(); + } + break; + case NS_ooxml::LN_CT_SdtCheckbox_checked: + if (!m_pImpl->GetSdtStarts().empty()) + { + // nIntValue is not just 0 or 1, because we're in the w14 namespace's ST_OnOff. + if (nIntValue == NS_ooxml::LN_ST_OnOff_true || nIntValue == NS_ooxml::LN_ST_OnOff_1) + { + m_pImpl->m_pSdtHelper->SetChecked(); + } + } + else + { + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtCheckbox_checked", + TextEffectsHandler::getOnOffString(nIntValue)); + } + break; + case NS_ooxml::LN_CT_SdtCheckbox_checkedState: + if (!m_pImpl->GetSdtStarts().empty()) + { + m_pImpl->m_pSdtHelper->SetCheckedState(OUString(sal_Unicode(sStringValue.toInt32(16)))); + } + else + { + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtCheckbox_checkedState", + sStringValue); + } + break; + case NS_ooxml::LN_CT_SdtCheckbox_uncheckedState: + if (!m_pImpl->GetSdtStarts().empty()) + { + m_pImpl->m_pSdtHelper->SetUncheckedState( + OUString(sal_Unicode(sStringValue.toInt32(16)))); + } + else + { + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, + "ooxml:CT_SdtCheckbox_uncheckedState", sStringValue); + } + break; + case NS_ooxml::LN_CT_SdtDocPart_docPartGallery: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtDocPart_docPartGallery", sStringValue); + break; + case NS_ooxml::LN_CT_SdtDocPart_docPartCategory: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtDocPart_docPartCategory", sStringValue); + break; + case NS_ooxml::LN_CT_SdtDocPart_docPartUnique: + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "ooxml:CT_SdtDocPart_docPartUnique", sStringValue); + break; + case NS_ooxml::LN_EG_SectPrContents_pgNumType: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve(*this); + } + } + break; + case NS_ooxml::LN_tblStart: + { + if (m_pImpl->hasTableManager()) + { + bool bTableStartsAtCellStart = m_pImpl->m_nTableDepth > 0 && m_pImpl->m_nTableCellDepth > m_pImpl->m_nLastTableCellParagraphDepth + 1; + m_pImpl->getTableManager().setTableStartsAtCellStart(bTableStartsAtCellStart); + } + /* + * Hack for Importing Section Properties + * LO is not able to import section properties if first element in the + * section is a table. So in case first element is a table add a dummy para + * and remove it again when lcl_endSectionGroup is called + */ + if(m_pImpl->m_nTableDepth == 0 && m_pImpl->GetIsFirstParagraphInSection() + && !m_pImpl->GetIsDummyParaAddedForTableInSection() && !m_pImpl->GetIsTextFrameInserted() + && !IsInHeaderFooter()) + { + m_pImpl->AddDummyParaForTableInSection(); + } + + // if first paragraph style in table has break-before-page, transfer that setting to the table itself. + if( m_pImpl->m_nTableDepth == 0 ) + { + const uno::Any aBreakType(style::BreakType_PAGE_BEFORE); + const PropertyMapPtr pParagraphProps = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if( pParagraphProps && pParagraphProps->isSet(PROP_PARA_STYLE_NAME) ) + { + StyleSheetEntryPtr pStyle; + OUString sStyleName; + pParagraphProps->getProperty(PROP_PARA_STYLE_NAME)->second >>= sStyleName; + if( !sStyleName.isEmpty() && GetStyleSheetTable() ) + pStyle = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( sStyleName ); + + if( pStyle && pStyle->pProperties + && pStyle->pProperties->isSet(PROP_BREAK_TYPE) + && pStyle->pProperties->getProperty(PROP_BREAK_TYPE)->second == aBreakType ) + { + pParagraphProps->Insert(PROP_BREAK_TYPE, aBreakType); + } + } + } + + m_pImpl->m_nTableDepth++; + } + break; + case NS_ooxml::LN_tblEnd: + m_pImpl->m_nTableDepth--; + break; + case NS_ooxml::LN_tcStart: + m_pImpl->m_nTableCellDepth++; + break; + case NS_ooxml::LN_tcEnd: + m_pImpl->m_nTableCellDepth--; + m_pImpl->m_nLastTableCellParagraphDepth = 0; + break; + case NS_ooxml::LN_glow_glow: + case NS_ooxml::LN_shadow_shadow: + case NS_ooxml::LN_reflection_reflection: + case NS_ooxml::LN_textOutline_textOutline: + case NS_ooxml::LN_textFill_textFill: + case NS_ooxml::LN_scene3d_scene3d: + case NS_ooxml::LN_props3d_props3d: + case NS_ooxml::LN_ligatures_ligatures: + case NS_ooxml::LN_numForm_numForm: + case NS_ooxml::LN_numSpacing_numSpacing: + case NS_ooxml::LN_stylisticSets_stylisticSets: + case NS_ooxml::LN_cntxtAlts_cntxtAlts: + { + tools::SvRef<TextEffectsHandler> pTextEffectsHandlerPtr( new TextEffectsHandler(nSprmId) ); + std::optional<PropertyIds> aPropertyId = pTextEffectsHandlerPtr->getGrabBagPropertyId(); + if(aPropertyId) + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve(*pTextEffectsHandlerPtr); + + beans::PropertyValue aGrabBag = pTextEffectsHandlerPtr->getInteropGrabBag(); + rContext->Insert(*aPropertyId, uno::Any(aGrabBag), true, CHAR_GRAB_BAG); + + sal_Int16 nTransparency = TextEffectsHandler::GetTextFillSolidFillAlpha(aGrabBag); + if (nTransparency != 0) + { + rContext->Insert(PROP_CHAR_TRANSPARENCE, uno::Any(nTransparency)); + } + } + else if (nSprmId == NS_ooxml::LN_cntxtAlts_cntxtAlts) + { + pTextEffectsHandlerPtr->lcl_sprm(rSprm); + beans::PropertyValue aGrabBag = pTextEffectsHandlerPtr->getInteropGrabBag(); + rContext->Insert(*aPropertyId, uno::Any(aGrabBag), true, CHAR_GRAB_BAG); + } + } + } + break; + case NS_ooxml::LN_CT_SdtPr_rPr: + { + // Make sure properties from a previous SDT are not merged with the current ones. + m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblLook: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + m_pImpl->getTableManager().finishTableLook(); + } + } + break; + case NS_ooxml::LN_CT_TrPrBase_cnfStyle: + { + m_pImpl->enableInteropGrabBag("cnfStyle"); + resolveSprmProps(*this, rSprm); + + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_ROW_CNF_STYLE, uno::Any(comphelper::containerToSequence(m_pImpl->m_aInteropGrabBag)), true, ROW_GRAB_BAG); + m_pImpl->getTableManager().insertRowProps(pPropMap); + + m_pImpl->disableInteropGrabBag(); + } + break; + case NS_ooxml::LN_CT_TcPrBase_cnfStyle: + { + m_pImpl->enableInteropGrabBag("cnfStyle"); + resolveSprmProps(*this, rSprm); + + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_CELL_CNF_STYLE, uno::Any(comphelper::containerToSequence(m_pImpl->m_aInteropGrabBag)), true, CELL_GRAB_BAG); + m_pImpl->getTableManager().cellProps(pPropMap); + + m_pImpl->disableInteropGrabBag(); + } + break; + case NS_ooxml::LN_CT_PPrBase_cnfStyle: + { + m_pImpl->enableInteropGrabBag("cnfStyle"); + resolveSprmProps(*this, rSprm); + rContext->Insert(PROP_PARA_CNF_STYLE, uno::Any(comphelper::containerToSequence(m_pImpl->m_aInteropGrabBag)), true, PARA_GRAB_BAG); + m_pImpl->disableInteropGrabBag(); + } + break; + case NS_ooxml::LN_EG_RunInnerContent_sym: + { + resolveSprmProps(*this, rSprm); + SymbolData aSymbolData = m_pImpl->GetSymbolData(); + uno::Any aVal( aSymbolData.sFont ); + auto xFootnote = rContext->GetFootnote(); + if (!xFootnote.is() && m_pImpl->IsInCustomFootnote()) + xFootnote = m_pImpl->GetFootnoteContext()->GetFootnote(); + if (xFootnote.is()) + { + // DOCX can have different labels for the footnote reference and the footnote area. + // This skips the one from the footnote area and just uses the reference one. + if (!m_pImpl->IsInFootOrEndnote()) + { + auto xAnchorRange = xFootnote->getAnchor(); + auto xAnchorCursor(xAnchorRange->getText()->createTextCursorByRange(xAnchorRange)); + + // append a dummy character, so the following properties will be set as + // as SwpHints::SwTextAttr instead of the SwAttrSet of the paragraph, + // which would be removed by SwXText::Impl::finishOrAppendParagraph + xAnchorCursor->collapseToEnd(); + uno::Reference<text::XTextRange> xHackRange(xAnchorCursor, uno::UNO_QUERY); + xHackRange->setString("x"); + + uno::Reference<beans::XPropertySet> xAnchorProps(xAnchorRange, uno::UNO_QUERY); + xAnchorProps->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal); + xAnchorProps->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal); + xAnchorProps->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal); + xAnchorProps->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::Any(awt::CharSet::SYMBOL)); + + // remove the dummy char + xHackRange->setString(""); + + OUString sLabel = xFootnote->getLabel() + OUStringChar(aSymbolData.cSymbol); + xFootnote->setLabel(sLabel); + } + } + else //it's a _real_ symbol + { + rContext->Insert(PROP_CHAR_FONT_NAME, aVal); + rContext->Insert(PROP_CHAR_FONT_NAME_ASIAN, aVal); + rContext->Insert(PROP_CHAR_FONT_NAME_COMPLEX, aVal); + rContext->Insert(PROP_CHAR_FONT_CHAR_SET, uno::Any(awt::CharSet::SYMBOL)); + utext( reinterpret_cast < const sal_uInt8 * >( &(aSymbolData.cSymbol) ), 1 ); + } + } + break; + case NS_ooxml::LN_EG_RunInnerContent_ruby: + { + RubyInfo aInfo ; + m_pImpl->SetRubyInfo(aInfo); + } + break; + case NS_ooxml::LN_CT_RubyPr: + case NS_ooxml::LN_CT_Ruby_rt: + case NS_ooxml::LN_CT_Ruby_rubyBase: + { + m_pImpl->SetRubySprmId(nSprmId); + if (nSprmId == NS_ooxml::LN_CT_RubyPr) + { + resolveSprmProps(*this, rSprm); + } + } + break; + case NS_ooxml::LN_EG_RubyContent_r: + { + const RubyInfo & aInfo = m_pImpl->GetRubyInfo(); + if (aInfo.nSprmId == NS_ooxml::LN_CT_Ruby_rubyBase) + { + rContext->Insert(PROP_RUBY_TEXT, uno::Any(aInfo.sRubyText)); + rContext->Insert(PROP_RUBY_STYLE, uno::Any(aInfo.sRubyStyle)); + rContext->Insert(PROP_RUBY_ADJUST, uno::Any(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign)))); + if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical ) + rContext->Insert(PROP_RUBY_POSITION, uno::Any(css::text::RubyPosition::INTER_CHARACTER)); + + m_pImpl->SetRubySprmId(0); + } + } + break; + case NS_ooxml::LN_CT_RubyPr_rubyAlign: + case NS_ooxml::LN_CT_RubyPr_hps: + case NS_ooxml::LN_CT_RubyPr_hpsBaseText: + { + RubyInfo aInfo = m_pImpl->GetRubyInfo(); + switch(nSprmId) + { + case NS_ooxml::LN_CT_RubyPr_rubyAlign: + aInfo.nRubyAlign = nIntValue; + break; + case NS_ooxml::LN_CT_RubyPr_hps: + aInfo.nHps= nIntValue; + break; + case NS_ooxml::LN_CT_RubyPr_hpsBaseText: + aInfo.nHpsBaseText = nIntValue; + break; + } + m_pImpl->SetRubyInfo(aInfo); + } + break; + case NS_ooxml::LN_CT_SmartTagRun_smartTagPr: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties && m_pImpl->GetTopContextType() == CONTEXT_PARAGRAPH) + pProperties->resolve(m_pImpl->getSmartTagHandler()); + } + break; + case NS_ooxml::LN_CT_DocPartPr_name: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_DocPartPr_category: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_DocPartCategory_gallery: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("unhandled"); + TagLogger::getInstance().attribute("id", nSprmId); + TagLogger::getInstance().attribute("name", rSprm.getName()); + TagLogger::getInstance().endElement(); +#endif + } + } +} + +void DomainMapper::processDeferredCharacterProperties( const std::map< sal_Int32, uno::Any >& deferredCharacterProperties ) +{ + assert( m_pImpl->GetTopContextType() == CONTEXT_CHARACTER ); + PropertyMapPtr rContext = m_pImpl->GetTopContext(); + for( const auto& rProp : deferredCharacterProperties ) + { + sal_Int32 Id = rProp.first; + sal_Int32 nIntValue = 0; + OUString sStringValue; + rProp.second >>= nIntValue; + rProp.second >>= sStringValue; + switch( Id ) + { + case NS_ooxml::LN_EG_RPrBase_position: + { + double nEscapement = 0; + sal_Int8 nProp = 0; + if ( nIntValue ) + { + nProp = 100; + double fFontSize = 0; + m_pImpl->GetAnyProperty(PROP_CHAR_HEIGHT, rContext) >>= fFontSize; + if ( fFontSize ) + // nIntValue is in half-points, fontsize is in points, escapement is a percentage. + nEscapement = round( nIntValue/2.0 / fFontSize * 100 ); + else + nEscapement = nIntValue > 0 ? DFLT_ESC_SUPER : DFLT_ESC_SUB; + } + if ( nEscapement > MAX_ESC_POS ) + nEscapement = MAX_ESC_POS; + else if ( nEscapement < -MAX_ESC_POS ) + nEscapement = -MAX_ESC_POS; + + rContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( sal_Int16(nEscapement) ) ); + rContext->Insert(PROP_CHAR_ESCAPEMENT_HEIGHT, uno::Any( nProp ) ); + } + break; + default: + SAL_WARN( "writerfilter", "Unhandled property in processDeferredCharacterProperty()" ); + break; + } + } +} + +void DomainMapper::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) +{ + ref->resolve(*this); +} + +void DomainMapper::data(const sal_uInt8* /*buf*/, size_t /*len*/) +{ +} + +void DomainMapper::lcl_startSectionGroup() +{ + if (!m_pImpl->isInIndexContext() && !m_pImpl->isInBibliographyContext()) + { + m_pImpl->PushProperties(CONTEXT_SECTION); + } + m_pImpl->SetIsFirstParagraphInSection(true); + m_pImpl->SetIsFirstParagraphInSectionAfterRedline(true); +} + +void DomainMapper::lcl_endSectionGroup() +{ + if (m_pImpl->isInIndexContext() || m_pImpl->isInBibliographyContext()) + return; + + m_pImpl->CheckUnregisteredFrameConversion(); + m_pImpl->ExecuteFrameConversion(); + // When pasting, it's fine to not have any paragraph inside the document at all. + if (m_pImpl->GetIsFirstParagraphInSection() && m_pImpl->IsNewDoc()) + { + // This section has no paragraph at all (e.g. they are all actually in a frame). + // If this section has a page break, there would be nothing to apply to the page + // style, so force a dummy paragraph. + lcl_startParagraphGroup(); + lcl_startCharacterGroup(); + sal_uInt8 const sBreak[] = { 0xd }; + lcl_text(sBreak, 1); + lcl_endCharacterGroup(); + lcl_endParagraphGroup(); + } + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_SECTION); + SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + OSL_ENSURE(pSectionContext, "SectionContext unavailable!"); + if(pSectionContext) + { + pSectionContext->CloseSectionGroup( *m_pImpl ); + // Remove the dummy paragraph if added for + // handling the section properties if section starts with a table + if (m_pImpl->GetIsDummyParaAddedForTableInSection()) + m_pImpl->RemoveDummyParaForTableInSection(); + } + m_pImpl->SetIsTextFrameInserted( false ); + m_pImpl->PopProperties(CONTEXT_SECTION); +} + +void DomainMapper::lcl_startParagraphGroup() +{ + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().startParagraphGroup(); + /* + * Add new para properties only if paragraph is not split + * or the top context is not of paragraph properties + * Set mbIsSplitPara to false as it has been handled + */ + if (!mbIsSplitPara) + m_pImpl->PushProperties(CONTEXT_PARAGRAPH); + mbIsSplitPara = false; + if (m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH) != m_pImpl->GetTopContext()) + m_pImpl->PushProperties(CONTEXT_PARAGRAPH); + + if (m_pImpl->GetTopContext()) + { + if (!m_pImpl->IsInShape()) + { + const OUString& sDefaultParaStyle = m_pImpl->GetDefaultParaStyleName(); + m_pImpl->GetTopContext()->Insert( PROP_PARA_STYLE_NAME, uno::Any( sDefaultParaStyle ) ); + m_pImpl->SetCurrentParaStyleName( sDefaultParaStyle ); + + if (m_pImpl->isBreakDeferred(PAGE_BREAK)) + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + else if (m_pImpl->isBreakDeferred(COLUMN_BREAK)) + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_COLUMN_BEFORE)); + mbWasShapeInPara = false; + } + + if (m_pImpl->isParaSdtEndDeferred()) + m_pImpl->GetTopContext()->Insert(PROP_PARA_SDT_END_BEFORE, uno::Any(true), true, PARA_GRAB_BAG); + } + m_pImpl->SetIsFirstRun(true); + m_pImpl->SetIsOutsideAParagraph(false); + if (!m_pImpl->IsInShape()) + m_pImpl->clearDeferredBreaks(); + m_pImpl->setParaSdtEndDeferred(false); +} + +void DomainMapper::lcl_endParagraphGroup() +{ + if (m_pImpl->isBreakDeferred(LINE_BREAK)) + { + if (m_pImpl->GetIsLastParagraphInSection()) + m_pImpl->clearDeferredBreak(LINE_BREAK); + + while (m_pImpl->isBreakDeferred(LINE_BREAK)) + { + m_pImpl->clearDeferredBreak(LINE_BREAK); + m_pImpl->appendTextPortion("\n", m_pImpl->GetTopContext()); + } + } + + m_pImpl->PopProperties(CONTEXT_PARAGRAPH); + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().endParagraphGroup(); + //frame conversion has to be executed after table conversion + m_pImpl->ExecuteFrameConversion(); + m_pImpl->SetIsOutsideAParagraph(true); +} + +void DomainMapper::markLastParagraphInSection( ) +{ + m_pImpl->SetIsLastParagraphInSection( true ); +} + +void DomainMapper::markLastSectionGroup( ) +{ + m_pImpl->SetIsLastSectionGroup( true ); +} + +void DomainMapper::lcl_startShape(uno::Reference<drawing::XShape> const& xShape) +{ + assert(xShape.is()); + + m_pImpl->AttachTextBoxContentToShape(xShape); + if (m_pImpl->GetTopContext()) + { + // If there is a deferred page break, handle it now, so that the + // started shape will be on the correct page. + if (m_pImpl->isBreakDeferred(PAGE_BREAK)) + { + m_pImpl->clearDeferredBreak(PAGE_BREAK); + lcl_startCharacterGroup(); + sal_uInt8 const sBreak[] = { 0xd }; + lcl_text(sBreak, 1); + lcl_endCharacterGroup(); + lcl_endParagraphGroup(); + lcl_startParagraphGroup(); + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + } + m_pImpl->PushShapeContext( xShape ); + lcl_startParagraphGroup(); + } + else + { + // No context? Then this image should not appear directly inside the + // document, just save it for later usage. + m_pImpl->PushPendingShape(xShape); + } + + m_pImpl->SetIsFirstParagraphInShape(true); + mbWasShapeInPara = true; +} + +void DomainMapper::lcl_endShape( ) +{ + if (!m_pImpl->GetTopContext()) + return; + + // End the current table, if there are any. Otherwise the unavoidable + // empty paragraph at the end of the shape text will cause problems: if + // the shape text ends with a table, the extra paragraph will be + // handled as an additional row of the ending table. + if (m_pImpl->hasTableManager()) + m_pImpl->getTableManager().endTable(); + + lcl_endParagraphGroup(); + m_pImpl->PopShapeContext( ); + // A shape is always inside a paragraph (anchored or inline). + m_pImpl->SetIsOutsideAParagraph(false); +} + +void DomainMapper::lcl_startTextBoxContent() +{ + m_pImpl->PushTextBoxContent(); +} + +void DomainMapper::lcl_endTextBoxContent() +{ + m_pImpl->PopTextBoxContent(); +} + +void DomainMapper::PushStyleSheetProperties( const PropertyMapPtr& pStyleProperties, bool bAffectTableMngr ) +{ + m_pImpl->PushStyleProperties( pStyleProperties ); + if ( bAffectTableMngr ) + m_pImpl->getTableManager( ).SetStyleProperties( pStyleProperties ); +} + +void DomainMapper::PopStyleSheetProperties( bool bAffectTableMngr ) +{ + m_pImpl->PopProperties( CONTEXT_STYLESHEET ); + if ( bAffectTableMngr ) + { + PropertyMapPtr emptyPtr; + m_pImpl->getTableManager( ).SetStyleProperties( emptyPtr ); + } +} + +void DomainMapper::PushListProperties( const ::tools::SvRef<PropertyMap>& pListProperties ) +{ + m_pImpl->PushListProperties( pListProperties ); +} + +void DomainMapper::PopListProperties() +{ + m_pImpl->PopProperties( CONTEXT_LIST ); +} + +void DomainMapper::lcl_startCharacterGroup() +{ + m_pImpl->PushProperties(CONTEXT_CHARACTER); + if (m_pImpl->isSdtEndDeferred()) + { + // Fields have an empty character group before the real one, so don't + // call setSdtEndDeferred(false) here, that will happen only in lcl_utext(). + m_pImpl->GetTopContext()->Insert(PROP_SDT_END_BEFORE, uno::Any(true), true, CHAR_GRAB_BAG); + } +} + +void DomainMapper::lcl_endCharacterGroup() +{ + if (m_pImpl->CheckFootnoteStyle()) + { + m_pImpl->SetCheckFootnoteStyle(m_pImpl->IsInCustomFootnote()); + m_pImpl->SetHasFootnoteStyle(false); + } + m_pImpl->PopProperties(CONTEXT_CHARACTER); +} + +void DomainMapper::lcl_text(const sal_uInt8 * data_, size_t len) +{ + //TODO: Determine the right text encoding (FIB?) + OUString sText( reinterpret_cast<const char*>(data_), len, RTL_TEXTENCODING_MS_1252 ); +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("text"); + TagLogger::getInstance().chars(sText); + TagLogger::getInstance().endElement(); +#endif + + try + { + if(len == 1) + { + switch(*data_) + { + case 0x02: return; //footnote character + case 0x08: // Lock field if in field context + if (m_pImpl->IsOpenField()) + m_pImpl->SetFieldLocked(); + return; + case 0x0c: //page break + // page breaks aren't supported in footnotes and endnotes + if (!m_pImpl->IsInFootOrEndnote()) + { + m_pImpl->deferBreak(PAGE_BREAK); + m_pImpl->SetIsDummyParaAddedForTableInSectionPage(false); + } + return; + case 0x0e: //column break + m_pImpl->deferBreak(COLUMN_BREAK); + return; + case 0x0a: //line break + if (m_pImpl->GetIsLastParagraphInSection()) + { + m_pImpl->deferBreak(LINE_BREAK); + return; + } + break; + case 0x07: + m_pImpl->getTableManager().text(data_, len); + return; + case 0x0d: + { + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if (pContext && m_pImpl->isBreakDeferred(COLUMN_BREAK)) + { + pContext->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_COLUMN_BEFORE)); + m_pImpl->clearDeferredBreak(COLUMN_BREAK); + } + finishParagraph(); + return; + } + case cFieldStart: + m_pImpl->PushFieldContext(); + return; + case cFieldSep: + // delimiter not necessarily available + // appears only if field contains further content + m_pImpl->CloseFieldCommand(); + return; + case cFieldEnd: + m_pImpl->PopFieldContext(); + return; + default: + break; + } + } + + // GetTopContext() is changed by inserted breaks, but we want to keep the current context + PropertyMapPtr pContext = m_pImpl->GetTopContext(); + + while (m_pImpl->isBreakDeferred(LINE_BREAK)) + { + m_pImpl->clearDeferredBreak(LINE_BREAK); + m_pImpl->appendTextPortion("\n", pContext); + } + + if (!m_pImpl->GetFootnoteContext()) + { + if (m_pImpl->isBreakDeferred(PAGE_BREAK)) + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + else if (m_pImpl->isBreakDeferred(COLUMN_BREAK)) + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_COLUMN_BEFORE)); + m_pImpl->clearDeferredBreaks(); + } + + if (pContext && pContext->GetFootnote().is() && m_pImpl->IsInCustomFootnote()) + { + pContext->GetFootnote()->setLabel(sText); + m_pImpl->EndCustomFootnote(); + //otherwise ignore sText + } + else if (m_pImpl->IsOpenFieldCommand() && !m_pImpl->IsForceGenericFields()) + { + m_pImpl->AppendFieldCommand(sText); + } + else if( m_pImpl->IsOpenField() && m_pImpl->IsFieldResultAsString()) + /*depending on the success of the field insert operation this result will be + set at the field or directly inserted into the text*/ + m_pImpl->AppendFieldResult(sText); + else + { + if (pContext == nullptr) + pContext = new PropertyMap(); + + if (sText == "\n") + { + m_pImpl->HandleLineBreak(pContext); + } + else + { + m_pImpl->appendTextPortion(sText, pContext); + } + } + } + catch( const uno::RuntimeException& ) + { + TOOLS_WARN_EXCEPTION("writerfilter", ""); + } +} + +void DomainMapper::lcl_positionOffset(const OUString& rText, bool bVertical) +{ + if (bVertical) + m_pImpl->m_aPositionOffsets.second = rText; + else + m_pImpl->m_aPositionOffsets.first = rText; +} + +awt::Point DomainMapper::getPositionOffset() +{ + awt::Point aRet; + aRet.X = oox::drawingml::convertEmuToHmm(m_pImpl->m_aPositionOffsets.first.toInt32()); + aRet.Y = oox::drawingml::convertEmuToHmm(m_pImpl->m_aPositionOffsets.second.toInt32()); + return aRet; +} + +void DomainMapper::lcl_align(const OUString& rText, bool bVertical) +{ + if (bVertical) + m_pImpl->m_aAligns.second = rText; + else + m_pImpl->m_aAligns.first = rText; +} + +void DomainMapper::lcl_positivePercentage(const OUString& rText) +{ + m_pImpl->m_aPositivePercentages.push(rText); +} + +void DomainMapper::lcl_checkId(const sal_Int32 nId) +{ + if (m_pImpl->IsInFootnote()) + { + m_pImpl->m_aFootnoteIds.push_back(nId); + // keep only the first real footnote + if (m_pImpl->GetFootnoteCount() == -1 && m_pImpl->m_aFootnoteIds.size() == 2) + m_pImpl->m_aFootnoteIds.pop_front(); + } + else + { + m_pImpl->m_aEndnoteIds.push_back(nId); + // keep only the first real endnote + if (m_pImpl->GetEndnoteCount() == -1 && m_pImpl->m_aEndnoteIds.size() == 2) + m_pImpl->m_aEndnoteIds.pop_front(); + } +} + +void DomainMapper::lcl_utext(const sal_uInt8 * data_, size_t len) +{ + // All these fixed values are defined as static const sal_Unicode codepoints in the fast parser, + // like uFtnEdnRef = 0x2, uFtnEdnSep = 0x3, ... and have a len of 1, if they aren't valid unicode. + + OUString sText(reinterpret_cast<const sal_Unicode *>(data_), len); + const RubyInfo & aInfo = m_pImpl->GetRubyInfo(); + if (aInfo.nSprmId == NS_ooxml::LN_CT_Ruby_rt) + { + PropertyMapPtr pContext = m_pImpl->GetTopContext(); + PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pContext->GetPropertyValues()); + OUString sStyle = getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false); + m_pImpl->SetRubyText(sText,sStyle); + return; + } + + if (len == 1) + { + // preload all footnotes in separated footnotes + if (sText[0] == 0x5) + { + if (m_pImpl->IsInFootnote()) + { + if (m_pImpl->GetFootnoteCount() > -1) + { + m_pImpl->PopFootOrEndnote(); + m_pImpl->PushFootOrEndnote(/*bIsFootnote=*/true); + } + m_pImpl->IncrementFootnoteCount(); + } + else + { + if (m_pImpl->GetEndnoteCount() > -1) + { + m_pImpl->PopFootOrEndnote(); + m_pImpl->PushFootOrEndnote(/*bIsFootnote=*/false); + } + m_pImpl->IncrementEndnoteCount(); + } + } + + // If the footnote contains a Footnote Reference Mark, it can't be a custom footnote + // ****** + // This code block is wrong, as it should also be in m_pImpl->IsInFootOrEndnote(). + // The main problem is that + // + // assert(len != 1 || sText[0] != 0x2) + // + // is triggered by the unit test SwLayoutWriter::testForcepoint75, so all these pseudo + // value handling is broken. + // But this is just a symptom, as I guess it's possible to generate broken DOCX documents, + // which might be problematic, triggering *funny* code paths left and right. + // ****** + if (sText[0] == 0x2) + { + m_pImpl->EndCustomFootnote(); + return; + } + + if (m_pImpl->IsInCustomFootnote()) + { + if (sText[0] != 0xd && sText[0] != 0x3) + { + // DOCX can have different labels for the footnote reference and the footnote area. + // This skips the one from the footnote area and just uses the reference one. + if (!m_pImpl->IsInFootOrEndnote()) + { + if (PropertyMapPtr pFootnoteContext = m_pImpl->GetFootnoteContext()) + { + auto xFootnote = pFootnoteContext->GetFootnote(); + xFootnote->setLabel(xFootnote->getLabel() + sText); + } + } + return; + } + else + m_pImpl->SetHasFootnoteStyle(true); + } + } + + if (m_pImpl->isSdtEndDeferred()) + { + // In case we have a field context, then save the property there, so + // SDT's ending right before a field start are handled as well. + PropertyMapPtr pContext = m_pImpl->GetTopContext(); + if (m_pImpl->IsOpenField()) + pContext = m_pImpl->GetTopFieldContext()->getProperties(); + pContext->Insert(PROP_SDT_END_BEFORE, uno::Any(true), true, CHAR_GRAB_BAG); + m_pImpl->setSdtEndDeferred(false); + } + + bool bNewLine = len == 1 && (sText[0] == 0x0d || sText[0] == 0x07); + if (m_pImpl->GetSdtStarts().empty() && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::dropDown) + { + // Block, cell or row SDT. + if (bNewLine) + // Dropdown control has single-line texts, so in case of newline, create the control. + m_pImpl->m_pSdtHelper->createDropDownControl(); + else + { + m_pImpl->m_pSdtHelper->getSdtTexts().append(sText); + return; + } + } + else if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::datePicker) + { + if (IsInHeaderFooter() && m_pImpl->IsDiscardHeaderFooter()) + { + m_pImpl->m_pSdtHelper->getDateFormat().truncate(); + m_pImpl->m_pSdtHelper->getLocale().truncate(); + return; + } + } + else if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText) + { + m_pImpl->m_pSdtHelper->getSdtTexts().append(sText); + if (bNewLine) + { + m_pImpl->m_pSdtHelper->createPlainTextControl(); + finishParagraph(); + } + return; + } + else if (!m_pImpl->m_pSdtHelper->isInteropGrabBagEmpty()) + { + // there are unsupported SDT properties in the document + // save them in the paragraph interop grab bag + if (m_pImpl->IsDiscardHeaderFooter()) + { + // Unless we're supposed to ignore this header/footer. + m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + return; + } + if((m_pImpl->m_pSdtHelper->containedInInteropGrabBag("ooxml:CT_SdtPr_checkbox") || + m_pImpl->m_pSdtHelper->containedInInteropGrabBag("ooxml:CT_SdtPr_text") || + m_pImpl->m_pSdtHelper->containedInInteropGrabBag("ooxml:CT_SdtPr_dataBinding") || + m_pImpl->m_pSdtHelper->containedInInteropGrabBag("ooxml:CT_SdtPr_citation") || + (m_pImpl->m_pSdtHelper->containedInInteropGrabBag("ooxml:CT_SdtPr_id") && + m_pImpl->m_pSdtHelper->getInteropGrabBagSize() == 1)) && !m_pImpl->m_pSdtHelper->isOutsideAParagraph()) + { + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_CHARACTER); + + if (m_pImpl->IsOpenField()) + // We have a field, insert the SDT properties to the field's grab-bag, so they won't be lost. + pContext = m_pImpl->GetTopFieldContext()->getProperties(); + + uno::Sequence<beans::PropertyValue> aGrabBag = m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + if (m_pImpl->GetSdtStarts().empty() || m_pImpl->m_pSdtHelper->getControlType() != SdtControlType::dropDown) + { + pContext->Insert(PROP_SDTPR, uno::Any(aGrabBag), true, CHAR_GRAB_BAG); + } + } + else + { + uno::Sequence<beans::PropertyValue> aGrabBag = m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + if (m_pImpl->GetSdtStarts().empty() + || (m_pImpl->m_pSdtHelper->getControlType() != SdtControlType::dropDown && m_pImpl->m_pSdtHelper->getControlType() != SdtControlType::richText)) + { + m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH)->Insert(PROP_SDTPR, + uno::Any(aGrabBag), true, PARA_GRAB_BAG); + } + } + } + else if (len == 1 && sText[0] == 0x03) + { + // This is the uFtnEdnSep, remember that the document has a separator. + m_pImpl->m_bHasFtnSep = true; + return; + } + else if (len == 1 && sText[0] == '\r') + { + // Clear "last" one linebreak at end of section + if (m_pImpl->GetIsLastParagraphInSection() && m_pImpl->isBreakDeferred(LINE_BREAK)) + m_pImpl->clearDeferredBreak(LINE_BREAK); + // And emit all other linebreaks + while (m_pImpl->isBreakDeferred(LINE_BREAK)) + { + m_pImpl->clearDeferredBreak(LINE_BREAK); + m_pImpl->appendTextPortion("\n", m_pImpl->GetTopContext()); + } + } + else if (len == 1 && sText[0] == '\t' ) + { + if ( m_pImpl->m_bCheckFirstFootnoteTab && m_pImpl->IsInFootOrEndnote() ) + { + // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent + m_pImpl->m_bCheckFirstFootnoteTab = false; + sal_Int32 nFirstLineIndent = 0; + m_pImpl->GetAnyProperty(PROP_PARA_FIRST_LINE_INDENT, m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH)) >>= nFirstLineIndent; + if ( nFirstLineIndent < 0 ) + m_pImpl->m_bIgnoreNextTab = true; + } + + if ( m_pImpl->m_bIgnoreNextTab ) + { + m_pImpl->m_bIgnoreNextTab = false; + return; + } + } + if (!m_pImpl->hasTableManager()) + return; + + SkipFootnoteSeparator eSkip = m_pImpl->GetSkipFootnoteState(); + if ( eSkip == SkipFootnoteSeparator::ON || eSkip == SkipFootnoteSeparator::SKIPPING ) + { + m_pImpl->SetSkipFootnoteState( SkipFootnoteSeparator::SKIPPING ); + return; + } + + try + { + while (m_pImpl->isBreakDeferred(LINE_BREAK)) + { + m_pImpl->clearDeferredBreak(LINE_BREAK); + m_pImpl->appendTextPortion("\n", m_pImpl->GetTopContext()); + } + + m_pImpl->getTableManager().utext(data_, len); + + if (bNewLine) + { + const bool bSingleParagraph = m_pImpl->GetIsFirstParagraphInSection() && m_pImpl->GetIsLastParagraphInSection(); + const bool bSingleParagraphAfterRedline = m_pImpl->GetIsFirstParagraphInSection(/*bAfterRedline=*/true) && + m_pImpl->GetIsLastParagraphInSection(); + PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); + if (!m_pImpl->GetFootnoteContext()) + { + if (m_pImpl->isBreakDeferred(PAGE_BREAK)) + { + if (m_pImpl->GetSettingsTable()->GetSplitPgBreakAndParaMark()) + { + if ( m_pImpl->GetIsFirstParagraphInSection() || !m_pImpl->IsFirstRun() ) + { + m_pImpl->m_bIsSplitPara = true; + finishParagraph(); + lcl_startParagraphGroup(); + } + + + pContext->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + m_pImpl->clearDeferredBreaks(); + } + } + else if (m_pImpl->isBreakDeferred(COLUMN_BREAK)) + { + if ( m_pImpl->GetIsFirstParagraphInSection() || !m_pImpl->IsFirstRun() ) + { + mbIsSplitPara = true; + finishParagraph(); + lcl_startParagraphGroup(); + } + + pContext->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_COLUMN_BEFORE)); + m_pImpl->clearDeferredBreaks(); + } + } + + // If the paragraph contains only the section properties and it has + // no runs, we should not create a paragraph for it in Writer, unless that would remove the whole section. + // Also do not remove here column breaks: they are treated in a different way and place. + bool bIsColumnBreak = false; + if (pContext->isSet(PROP_BREAK_TYPE)) + { + const uno::Any aBreakType = pContext->getProperty(PROP_BREAK_TYPE)->second; + bIsColumnBreak = + aBreakType == style::BreakType_COLUMN_BEFORE || + aBreakType == style::BreakType_COLUMN_AFTER || + aBreakType == style::BreakType_COLUMN_BOTH; + } + + bool bRemove = (!m_pImpl->GetParaChanged() && m_pImpl->GetRemoveThisPara()) || + (!m_pImpl->GetParaChanged() && m_pImpl->GetParaSectpr() + && !bSingleParagraphAfterRedline + && !bIsColumnBreak + && !m_pImpl->GetParaHadField() + && (!m_pImpl->GetIsDummyParaAddedForTableInSectionPage()) + && !m_pImpl->GetIsPreviousParagraphFramed() + && !m_pImpl->HasTopAnchoredObjects() + && !m_pImpl->IsParaWithInlineObject()); + + const bool bNoNumbering = bRemove || (!m_pImpl->GetParaChanged() && m_pImpl->GetParaSectpr() && bSingleParagraph); + PropertyMapPtr xContext = bNoNumbering ? m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH) : PropertyMapPtr(); + if (xContext) + { + // tdf#97417 delete numbering of the paragraph + // it will be deleted anyway, and the numbering would be copied + // to the next paragraph in sw SplitNode and then be applied to + // every following paragraph + xContext->Erase(PROP_NUMBERING_RULES); + static_cast<ParagraphPropertyMap*>(xContext.get())->SetListId(-1);; + xContext->Erase(PROP_NUMBERING_LEVEL); + } + finishParagraph(bRemove, bNoNumbering); + if (bRemove) + m_pImpl->RemoveLastParagraph(); + m_pImpl->SetParaSectpr(false); + } + else + { + // GetTopContext() is changed by inserted breaks, but we want to keep the current context + PropertyMapPtr pContext = m_pImpl->GetTopContext(); + if (!m_pImpl->GetFootnoteContext()) + { + if (m_pImpl->isBreakDeferred(PAGE_BREAK)) + { + /* If PAGEBREAK appears in first paragraph of the section or + * after first run of any paragraph then need to split paragraph + * to handle it properly. + */ + if (m_pImpl->GetIsFirstParagraphInSection() || !m_pImpl->IsFirstRun()) + { + m_pImpl->m_bIsSplitPara = true; + finishParagraph(); + lcl_startParagraphGroup(); + } + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_PAGE_BEFORE)); + } + else if (m_pImpl->isBreakDeferred(COLUMN_BREAK)) + { + if (m_pImpl->GetIsFirstParagraphInSection() || !m_pImpl->IsFirstRun() || mbWasShapeInPara) + { + mbWasShapeInPara = false; + mbIsSplitPara = true; + finishParagraph(); + lcl_startParagraphGroup(); + } + m_pImpl->GetTopContext()->Insert(PROP_BREAK_TYPE, uno::Any(style::BreakType_COLUMN_BEFORE)); + } + m_pImpl->clearDeferredBreaks(); + } + + if (pContext && pContext->GetFootnote().is()) + { + pContext->GetFootnote()->setLabel( sText ); + // tdf#141548 don't lose footnote/endnote text of the run with uFtnEdnRef + // (i.e. when footnoteRef/endnoteRef is followed by some text in the same run) + m_pImpl->appendTextPortion( sText, pContext ); + } + else if (m_pImpl->IsOpenFieldCommand() && !m_pImpl->IsForceGenericFields()) + { + m_pImpl->AppendFieldCommand(sText); + } + else if( m_pImpl->IsOpenField() && m_pImpl->IsFieldResultAsString()) + /*depending on the success of the field insert operation this result will be + set at the field or directly inserted into the text*/ + m_pImpl->AppendFieldResult(sText); + else + { + if (pContext == nullptr) + pContext = new PropertyMap(); + + m_pImpl->appendTextPortion( sText, pContext ); + } + + } + m_pImpl->SetIsFirstRun(false); + } + catch( const uno::RuntimeException& ) + { + } +} + +void DomainMapper::lcl_props(writerfilter::Reference<Properties>::Pointer_t ref) +{ + ref->resolve(*this); +} + +void DomainMapper::lcl_table(Id name, writerfilter::Reference<Table>::Pointer_t ref) +{ + m_pImpl->SetAnyTableImport(true); + switch(name) + { + case NS_ooxml::LN_FONTTABLE: + + // create a font table object that listens to the attributes + // each entry call inserts a new font entry + ref->resolve( *m_pImpl->GetFontTable() ); + break; + case NS_ooxml::LN_STYLESHEET: + //same as above to import style sheets + m_pImpl->SetStyleSheetImport( true ); + ref->resolve( *m_pImpl->GetStyleSheetTable() ); + m_pImpl->GetStyleSheetTable()->ApplyStyleSheets(m_pImpl->GetFontTable()); + m_pImpl->SetStyleSheetImport( false ); + break; + case NS_ooxml::LN_NUMBERING: + { + m_pImpl->SetNumberingImport(true); + //the same for list tables + ref->resolve( *m_pImpl->GetListTable() ); + m_pImpl->GetListTable( )->CreateNumberingRules( ); + m_pImpl->SetNumberingImport(false); + } + break; + case NS_ooxml::LN_THEMETABLE: + m_pImpl->GetThemeTable()->setThemeFontLangProperties( + m_pImpl->GetSettingsTable()->GetThemeFontLangProperties() ); + ref->resolve ( *m_pImpl->GetThemeTable() ); + break; + case NS_ooxml::LN_settings_settings: + ref->resolve ( *m_pImpl->GetSettingsTable() ); + m_pImpl->ApplySettingsTable(); + break; + default: + OSL_FAIL( "which table is to be filled here?"); + } + m_pImpl->SetAnyTableImport(false); +} + +void DomainMapper::lcl_substream(Id rName, ::writerfilter::Reference<Stream>::Pointer_t ref) +{ + m_pImpl->substream(rName, ref); +} + +void DomainMapper::lcl_startGlossaryEntry() +{ + uno::Reference< text::XTextRange > xTextRange = GetCurrentTextRange(); + m_pImpl->setGlossaryEntryStart(xTextRange); +} + +void DomainMapper::lcl_endGlossaryEntry() +{ + m_pImpl->appendGlossaryEntry(); +} + +void DomainMapper::handleUnderlineType(const Id nId, const ::tools::SvRef<PropertyMap>& rContext) +{ + sal_Int16 nUnderline = awt::FontUnderline::NONE; + + switch (nId) + { + case NS_ooxml::LN_Value_ST_Underline_none: + nUnderline = awt::FontUnderline::NONE; + break; + case NS_ooxml::LN_Value_ST_Underline_words: + rContext->Insert(PROP_CHAR_WORD_MODE, uno::Any(true)); + [[fallthrough]]; + case NS_ooxml::LN_Value_ST_Underline_single: + nUnderline = awt::FontUnderline::SINGLE; + break; + case NS_ooxml::LN_Value_ST_Underline_double: + nUnderline = awt::FontUnderline::DOUBLE; + break; + case NS_ooxml::LN_Value_ST_Underline_dotted: + nUnderline = awt::FontUnderline::DOTTED; + break; + case NS_ooxml::LN_Value_ST_Underline_dash: + nUnderline = awt::FontUnderline::DASH; + break; + case NS_ooxml::LN_Value_ST_Underline_dotDash: + nUnderline = awt::FontUnderline::DASHDOT; + break; + case NS_ooxml::LN_Value_ST_Underline_dotDotDash: + nUnderline = awt::FontUnderline::DASHDOTDOT; + break; + case NS_ooxml::LN_Value_ST_Underline_thick: + nUnderline = awt::FontUnderline::BOLD; + break; + case NS_ooxml::LN_Value_ST_Underline_wave: + nUnderline = awt::FontUnderline::WAVE; + break; + case NS_ooxml::LN_Value_ST_Underline_dottedHeavy: + nUnderline = awt::FontUnderline::BOLDDOTTED; + break; + case NS_ooxml::LN_Value_ST_Underline_dashedHeavy: + nUnderline = awt::FontUnderline::BOLDDASH; + break; + case NS_ooxml::LN_Value_ST_Underline_dashLong: + nUnderline = awt::FontUnderline::LONGDASH; + break; + case NS_ooxml::LN_Value_ST_Underline_dashLongHeavy: + nUnderline = awt::FontUnderline::BOLDLONGDASH; + break; + case NS_ooxml::LN_Value_ST_Underline_dashDotHeavy: + nUnderline = awt::FontUnderline::BOLDDASHDOT; + break; + case NS_ooxml::LN_Value_ST_Underline_dashDotDotHeavy: + nUnderline = awt::FontUnderline::BOLDDASHDOTDOT; + break; + case NS_ooxml::LN_Value_ST_Underline_wavyHeavy: + nUnderline = awt::FontUnderline::BOLDWAVE; + break; + case NS_ooxml::LN_Value_ST_Underline_wavyDouble: + nUnderline = awt::FontUnderline::DOUBLEWAVE; + break; + } + rContext->Insert(PROP_CHAR_UNDERLINE, uno::Any(nUnderline)); +} + +void DomainMapper::handleParaJustification(const sal_Int32 nIntValue, const ::tools::SvRef<PropertyMap>& rContext, const bool bExchangeLeftRight) +{ + style::ParagraphAdjust nAdjust = style::ParagraphAdjust_LEFT; + style::ParagraphAdjust nLastLineAdjust = style::ParagraphAdjust_LEFT; + OUString aStringValue = "left"; + switch(nIntValue) + { + case NS_ooxml::LN_Value_ST_Jc_center: + nAdjust = style::ParagraphAdjust_CENTER; + aStringValue = "center"; + break; + case NS_ooxml::LN_Value_ST_Jc_right: + case NS_ooxml::LN_Value_ST_Jc_end: + nAdjust = bExchangeLeftRight ? style::ParagraphAdjust_LEFT : style::ParagraphAdjust_RIGHT; + aStringValue = "right"; + break; + case NS_ooxml::LN_Value_ST_Jc_distribute: + nLastLineAdjust = style::ParagraphAdjust_BLOCK; + [[fallthrough]]; + case NS_ooxml::LN_Value_ST_Jc_both: + nAdjust = style::ParagraphAdjust_BLOCK; + aStringValue = "both"; + break; + case NS_ooxml::LN_Value_ST_Jc_left: + case NS_ooxml::LN_Value_ST_Jc_start: + default: + nAdjust = bExchangeLeftRight ? style::ParagraphAdjust_RIGHT : style::ParagraphAdjust_LEFT; + break; + } + rContext->Insert( PROP_PARA_ADJUST, uno::Any( nAdjust ) ); + rContext->Insert( PROP_PARA_LAST_LINE_ADJUST, uno::Any( nLastLineAdjust ) ); + m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, "jc", aStringValue); +} + +bool DomainMapper::getColorFromId(const Id nId, sal_Int32 &nColor) +{ + nColor = 0; + if ((nId < NS_ooxml::LN_Value_ST_HighlightColor_black) || (nId > NS_ooxml::LN_Value_ST_HighlightColor_none)) + return false; + + switch (nId) + { + case NS_ooxml::LN_Value_ST_HighlightColor_black: nColor=0x000000; break; + case NS_ooxml::LN_Value_ST_HighlightColor_blue: nColor=0x0000ff; break; + case NS_ooxml::LN_Value_ST_HighlightColor_cyan: nColor=0x00ffff; break; + case NS_ooxml::LN_Value_ST_HighlightColor_green: nColor=0x00ff00; break; + case NS_ooxml::LN_Value_ST_HighlightColor_magenta: nColor=0xff00ff; break; + case NS_ooxml::LN_Value_ST_HighlightColor_red: nColor=0xff0000; break; + case NS_ooxml::LN_Value_ST_HighlightColor_yellow: nColor=0xffff00; break; + case NS_ooxml::LN_Value_ST_HighlightColor_white: nColor=0xffffff; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkBlue: nColor=0x000080; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkCyan: nColor=0x008080; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkGreen: nColor=0x008000; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkMagenta: nColor=0x800080; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkRed: nColor=0x800000; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkYellow: nColor=0x808000; break; + case NS_ooxml::LN_Value_ST_HighlightColor_darkGray: nColor=0x808080; break; + case NS_ooxml::LN_Value_ST_HighlightColor_lightGray: nColor=0xC0C0C0; break; + case NS_ooxml::LN_Value_ST_HighlightColor_none: nColor=0xFFFFFFFF; break; //COL_AUTO + default: + return false; + } + return true; +} + +sal_Int16 DomainMapper::getEmphasisValue(const sal_Int32 nIntValue) +{ + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_Em_dot: + return text::FontEmphasis::DOT_ABOVE; + case NS_ooxml::LN_Value_ST_Em_comma: + return text::FontEmphasis::ACCENT_ABOVE; + case NS_ooxml::LN_Value_ST_Em_circle: + return text::FontEmphasis::CIRCLE_ABOVE; + case NS_ooxml::LN_Value_ST_Em_underDot: + return text::FontEmphasis::DOT_BELOW; + default: + return text::FontEmphasis::NONE; + } +} + +OUString DomainMapper::getBracketStringFromEnum(const sal_Int32 nIntValue, const bool bIsPrefix) +{ + switch(nIntValue) + { + case NS_ooxml::LN_Value_ST_CombineBrackets_round: + if (bIsPrefix) + return "("; + return ")"; + + case NS_ooxml::LN_Value_ST_CombineBrackets_square: + if (bIsPrefix) + return "["; + return "]"; + + case NS_ooxml::LN_Value_ST_CombineBrackets_angle: + if (bIsPrefix) + return "<"; + return ">"; + + case NS_ooxml::LN_Value_ST_CombineBrackets_curly: + if (bIsPrefix) + return "{"; + return "}"; + + case NS_ooxml::LN_Value_ST_CombineBrackets_none: + default: + return OUString(); + } +} + +style::TabAlign DomainMapper::getTabAlignFromValue(const sal_Int32 nIntValue) +{ + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_TabJc_start: + case NS_ooxml::LN_Value_ST_TabJc_left: + case NS_ooxml::LN_Value_ST_TabJc_bar: // bar not supported + case NS_ooxml::LN_Value_ST_TabJc_num: // num not supported + return style::TabAlign_LEFT; + case NS_ooxml::LN_Value_ST_TabJc_center: + return style::TabAlign_CENTER; + case NS_ooxml::LN_Value_ST_TabJc_end: + case NS_ooxml::LN_Value_ST_TabJc_right: + return style::TabAlign_RIGHT; + case NS_ooxml::LN_Value_ST_TabJc_decimal: + return style::TabAlign_DECIMAL; + } + return style::TabAlign_LEFT; +} + +sal_Unicode DomainMapper::getFillCharFromValue(const sal_Int32 nIntValue) +{ + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_TabTlc_dot: + return u'.'; + case NS_ooxml::LN_Value_ST_TabTlc_hyphen: + return u'-'; + case NS_ooxml::LN_Value_ST_TabTlc_underscore: + case NS_ooxml::LN_Value_ST_TabTlc_heavy: // FIXME ??? + return u'_'; + case NS_ooxml::LN_Value_ST_TabTlc_middleDot: // middleDot + return u'\x00b7'; + case NS_ooxml::LN_Value_ST_TabTlc_none: + default: + return u' '; // blank space + } +} + +bool DomainMapper::IsOOXMLImport() const +{ + return m_pImpl->IsOOXMLImport(); +} + +bool DomainMapper::IsRTFImport() const +{ + return m_pImpl->IsRTFImport(); +} + +uno::Reference < lang::XMultiServiceFactory > const & DomainMapper::GetTextFactory() const +{ + return m_pImpl->GetTextFactory(); +} + +uno::Reference< text::XTextRange > DomainMapper::GetCurrentTextRange() +{ + if (m_pImpl->HasTopText()) + return m_pImpl->GetTopTextAppend()->getEnd(); + return m_pImpl->m_xInsertTextRange; +} + +OUString DomainMapper::getOrCreateCharStyle( PropertyValueVector_t& rCharProperties, bool bAlwaysCreate ) +{ + StyleSheetTablePtr pStyleSheets = m_pImpl->GetStyleSheetTable(); + return pStyleSheets->getOrCreateCharStyle( rCharProperties, bAlwaysCreate ); +} + +StyleSheetTablePtr const & DomainMapper::GetStyleSheetTable( ) +{ + return m_pImpl->GetStyleSheetTable( ); +} + +SettingsTablePtr const & DomainMapper::GetSettingsTable() +{ + return m_pImpl->GetSettingsTable(); +} + +GraphicZOrderHelper* DomainMapper::graphicZOrderHelper() +{ + if (zOrderHelper == nullptr) + zOrderHelper.reset( new GraphicZOrderHelper ); + return zOrderHelper.get(); +} + +GraphicNamingHelper& DomainMapper::GetGraphicNamingHelper() +{ + if (m_pGraphicNamingHelper == nullptr) + m_pGraphicNamingHelper.reset(new GraphicNamingHelper()); + return *m_pGraphicNamingHelper; +} + +uno::Reference<drawing::XShape> DomainMapper::PopPendingShape() +{ + return m_pImpl->PopPendingShape(); +} + +bool DomainMapper::IsInHeaderFooter() const +{ + return m_pImpl->IsInHeaderFooter(); +} + +bool DomainMapper::IsInShape() const { return m_pImpl->IsInShape(); } + +bool DomainMapper::IsInTable() const +{ + return m_pImpl->hasTableManager() && m_pImpl->getTableManager().isInCell(); +} + +OUString DomainMapper::GetListStyleName(sal_Int32 nListId) const +{ + return m_pImpl->GetListStyleName( nListId ); +} + +void DomainMapper::ValidateListLevel(const OUString& sStyleIdentifierD) +{ + m_pImpl->ValidateListLevel(sStyleIdentifierD); +} + +void DomainMapper::SetDocDefaultsImport(bool bSet) +{ + m_pImpl->SetDocDefaultsImport(bSet); +} + +bool DomainMapper::IsStyleSheetImport() const +{ + return m_pImpl->IsStyleSheetImport(); +} + +bool DomainMapper::IsNumberingImport() const +{ + return m_pImpl->IsNumberingImport(); +} + +void DomainMapper::enableInteropGrabBag(const OUString& aName) +{ + m_pImpl->m_aInteropGrabBagName = aName; +} + +beans::PropertyValue DomainMapper::getInteropGrabBag() +{ + beans::PropertyValue aRet; + aRet.Name = m_pImpl->m_aInteropGrabBagName; + aRet.Value <<= comphelper::containerToSequence(m_pImpl->m_aInteropGrabBag); + + m_pImpl->m_aInteropGrabBag.clear(); + m_pImpl->m_aInteropGrabBagName.clear(); + return aRet; +} + +void DomainMapper::HandleRedline( Sprm& rSprm ) +{ + sal_uInt32 nSprmId = rSprm.getId(); + + m_pImpl->AddNewRedline( nSprmId ); + + if (nSprmId == NS_ooxml::LN_CT_PPr_pPrChange) + { + m_pImpl->SetCurrentRedlineToken(XML_ParagraphFormat); + } + else if (nSprmId == NS_ooxml::LN_CT_TrPr_ins) + { + m_pImpl->SetCurrentRedlineToken(XML_tableRowInsert); + } + else if (nSprmId == NS_ooxml::LN_CT_TrPr_del) + { + m_pImpl->SetCurrentRedlineToken(XML_tableRowDelete); + } + else if (nSprmId == NS_ooxml::LN_CT_TcPrBase_cellIns) + { + m_pImpl->SetCurrentRedlineToken(XML_tableCellInsert); + } + else if (nSprmId == NS_ooxml::LN_CT_TcPrBase_cellDel) + { + m_pImpl->SetCurrentRedlineToken(XML_tableCellDelete); + } + + resolveSprmProps(*this, rSprm ); + // now the properties author, date and id should be available + sal_Int32 nToken = m_pImpl->GetCurrentRedlineToken(); + switch( nToken & 0xffff ) + { + case XML_mod: + case XML_ins: + case XML_del: + case XML_moveTo: + case XML_moveFrom: + case XML_ParagraphFormat: + case XML_tableRowInsert: + case XML_tableRowDelete: + case XML_tableCellInsert: + case XML_tableCellDelete: + break; + default: OSL_FAIL( "redline token other than mod, ins, del, moveTo, moveFrom or table row" ); break; + } + m_pImpl->EndParaMarkerChange( ); + m_pImpl->SetCurrentRedlineIsRead(); +} + +void DomainMapper::finishParagraph(const bool bRemove, const bool bNoNumbering) +{ + if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::datePicker) + m_pImpl->m_pSdtHelper->createDateContentControl(); + m_pImpl->finishParagraph(m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH), bRemove, bNoNumbering); +} + +void DomainMapper::commentProps(const OUString& sId, const CommentProperties& rProps) +{ + m_pImpl->commentProps(sId, rProps); +} + +css::uno::Reference<css::container::XNameContainer> const & DomainMapper::GetCharacterStyles() +{ + return m_pImpl->GetCharacterStyles(); +} + +OUString DomainMapper::GetUnusedCharacterStyleName() +{ + return m_pImpl->GetUnusedCharacterStyleName(); +} + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapper.hxx b/writerfilter/source/dmapper/DomainMapper.hxx new file mode 100644 index 000000000..4096b2b5d --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapper.hxx @@ -0,0 +1,195 @@ +/* -*- 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 . + */ +#pragma once + +#include <dmapper/DomainMapperFactory.hxx> +#include "LoggedResources.hxx" +#include "PropertyMap.hxx" +#include "SettingsTable.hxx" +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/style/TabAlign.hpp> + +#include <map> +#include <vector> +#include <memory> + +namespace com::sun::star{ + namespace beans{ + struct PropertyValue; + } + namespace io{ + class XInputStream; + } + namespace uno{ + class XComponentContext; + } + namespace lang{ + class XMultiServiceFactory; + } + namespace text{ + class XTextRange; + } +} + +namespace utl +{ +class MediaDescriptor; +} + +typedef std::vector<css::beans::PropertyValue> PropertyValueVector_t; + +namespace writerfilter::dmapper +{ + +class PropertyMap; +class DomainMapper_Impl; +class ListsManager; +class StyleSheetTable; +class GraphicZOrderHelper; +class GraphicNamingHelper; + +typedef tools::SvRef<StyleSheetTable> StyleSheetTablePtr; + +class DomainMapper : public LoggedProperties, public LoggedTable, + public BinaryObj, public LoggedStream +{ + std::unique_ptr<DomainMapper_Impl> m_pImpl; + +public: + DomainMapper(const css::uno::Reference<css::uno::XComponentContext>& xContext, + css::uno::Reference<css::io::XInputStream> const& xInputStream, + css::uno::Reference<css::lang::XComponent> const& xModel, + bool bRepairStorage, + SourceDocumentType eDocumentType, + utl::MediaDescriptor const & rMediaDesc); + virtual ~DomainMapper() override; + + virtual void setDocumentReference(writerfilter::ooxml::OOXMLDocument* pDocument) override; + + // Stream + virtual void markLastParagraphInSection() override; + virtual void markLastSectionGroup() override; + + // BinaryObj + virtual void data(const sal_uInt8* buf, size_t len) override; + + void sprmWithProps( Sprm& sprm, const PropertyMapPtr& pContext ); + + void PushStyleSheetProperties( const PropertyMapPtr& pStyleProperties, bool bAffectTableMngr = false ); + void PopStyleSheetProperties( bool bAffectTableMngr = false ); + + void PushListProperties( const ::tools::SvRef<PropertyMap>& pListProperties ); + void PopListProperties(); + OUString GetListStyleName(sal_Int32 nListId) const; + void ValidateListLevel(const OUString& sStyleIdentifierD); + + bool IsOOXMLImport() const; + bool IsRTFImport() const; + css::uno::Reference<css::lang::XMultiServiceFactory> const & GetTextFactory() const; + css::uno::Reference<css::text::XTextRange> GetCurrentTextRange(); + + OUString getOrCreateCharStyle( PropertyValueVector_t& rCharProperties, bool bAlwaysCreate ); + StyleSheetTablePtr const & GetStyleSheetTable( ); + SettingsTablePtr const & GetSettingsTable(); + GraphicZOrderHelper* graphicZOrderHelper(); + GraphicNamingHelper& GetGraphicNamingHelper(); + + /// Return the first from the pending (not inserted to the document) shapes, if there are any. + css::uno::Reference<css::drawing::XShape> PopPendingShape(); + + bool IsInHeaderFooter() const; + bool IsInTable() const; + void SetDocDefaultsImport(bool bSet); + bool IsStyleSheetImport() const; + bool IsNumberingImport() const; + bool IsInShape() const; + + void hasControls( const bool bSet ) { mbHasControls = bSet; } + + /** + @see DomainMapper_Impl::processDeferredCharacterProperties() + */ + void processDeferredCharacterProperties(const std::map<sal_Int32, css::uno::Any>& rDeferredCharacterProperties); + + /// Enable storing of seen tokens in a named grab bag. + void enableInteropGrabBag(const OUString& aName); + /// Get the stored tokens and clear the internal storage. + css::beans::PropertyValue getInteropGrabBag(); + + void HandleRedline( Sprm& rSprm ); + + virtual void commentProps(const OUString& sId, const CommentProperties& rProps) override; + + css::uno::Reference<css::container::XNameContainer> const & GetCharacterStyles(); + OUString GetUnusedCharacterStyleName(); + +private: + // Stream + virtual void lcl_startSectionGroup() override; + virtual void lcl_endSectionGroup() override; + virtual void lcl_startParagraphGroup() override; + virtual void lcl_endParagraphGroup() override; + virtual void lcl_startCharacterGroup() override; + virtual void lcl_endCharacterGroup() override; + virtual void lcl_startShape(css::uno::Reference<css::drawing::XShape> const& xShape) override; + virtual void lcl_endShape( ) override; + virtual void lcl_startTextBoxContent() override; + virtual void lcl_endTextBoxContent() override; + virtual void lcl_text(const sal_uInt8 * data, size_t len) override; + virtual void lcl_utext(const sal_uInt8 * data, size_t len) override; + virtual void lcl_positionOffset(const OUString& rText, bool bVertical) override; + virtual css::awt::Point getPositionOffset() override; + virtual void lcl_align(const OUString& rText, bool bVertical) override; + virtual void lcl_positivePercentage(const OUString& rText) override; + virtual void lcl_props(writerfilter::Reference<Properties>::Pointer_t ref) override; + virtual void lcl_table(Id name, + writerfilter::Reference<Table>::Pointer_t ref) override; + virtual void lcl_substream(Id name, + ::writerfilter::Reference<Stream>::Pointer_t ref) override; + virtual void lcl_startGlossaryEntry() override; + virtual void lcl_endGlossaryEntry() override; + virtual void lcl_checkId(const sal_Int32 nId) override; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + + void finishParagraph(const bool bRemove = false, const bool bNoNumbering = false); + + static void handleUnderlineType(const Id nId, const ::tools::SvRef<PropertyMap>& rContext); + void handleParaJustification(const sal_Int32 nIntValue, const ::tools::SvRef<PropertyMap>& rContext, const bool bExchangeLeftRight); + static bool getColorFromId(const Id, sal_Int32 &nColor); + static sal_Int16 getEmphasisValue(const sal_Int32 nIntValue); + static OUString getBracketStringFromEnum(const sal_Int32 nIntValue, const bool bIsPrefix = true); + static css::style::TabAlign getTabAlignFromValue(const sal_Int32 nIntValue); + static sal_Unicode getFillCharFromValue(const sal_Int32 nIntValue); + bool mbIsSplitPara; + bool mbHasControls; + bool mbWasShapeInPara; + std::unique_ptr< GraphicZOrderHelper > zOrderHelper; + std::unique_ptr<GraphicNamingHelper> m_pGraphicNamingHelper; + OUString m_sGlossaryEntryName; +}; + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx new file mode 100644 index 000000000..bf170d9cc --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx @@ -0,0 +1,1700 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include "DomainMapperTableHandler.hxx" +#include "DomainMapper_Impl.hxx" +#include "StyleSheetTable.hxx" +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/table/TableBorderDistances.hpp> +#include <com/sun/star/table/TableBorder.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRangeCompare.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include "TablePositionHandler.hxx" +#include "TagLogger.hxx" +#include "util.hxx" +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <boost/lexical_cast.hpp> + +#ifdef DBG_UTIL +#include "PropertyMapHelper.hxx" +#include <rtl/ustring.hxx> +#endif + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; +using namespace ::std; + +#define DEF_BORDER_DIST 190 //0,19cm +#define CNF_FIRST_ROW 0x800 +#define CNF_LAST_ROW 0x400 +#define CNF_FIRST_COLUMN 0x200 +#define CNF_LAST_COLUMN 0x100 +#define CNF_ODD_VBAND 0x080 +#define CNF_EVEN_VBAND 0x040 +#define CNF_ODD_HBAND 0x020 +#define CNF_EVEN_HBAND 0x010 +#define CNF_FIRST_ROW_LAST_COLUMN 0x008 +#define CNF_FIRST_ROW_FIRST_COLUMN 0x004 +#define CNF_LAST_ROW_LAST_COLUMN 0x002 +#define CNF_LAST_ROW_FIRST_COLUMN 0x001 +#define CNF_ALL 0xFFF + +// total number of table columns +#define MAXTABLECELLS 63 + +DomainMapperTableHandler::DomainMapperTableHandler( + css::uno::Reference<css::text::XTextAppendAndConvert> const& xText, + DomainMapper_Impl& rDMapper_Impl) + : m_xText(xText), + m_rDMapper_Impl( rDMapper_Impl ), + m_bHadFootOrEndnote(false) +{ +} + +DomainMapperTableHandler::~DomainMapperTableHandler() +{ +} + +void DomainMapperTableHandler::startTable(const TablePropertyMapPtr& pProps) +{ + m_aTableProperties = pProps; + m_aTableRanges.clear(); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablehandler.table"); + + if (pProps) + pProps->dumpXml(); +#endif +} + +static void lcl_mergeBorder( PropertyIds nId, const PropertyMapPtr& pOrig, const PropertyMapPtr& pDest ) +{ + std::optional<PropertyMap::Property> pOrigVal = pOrig->getProperty(nId); + + if ( pOrigVal ) + { + pDest->Insert( nId, pOrigVal->second, false ); + } +} + +static void lcl_computeCellBorders( const PropertyMapPtr& pTableBorders, const PropertyMapPtr& pCellProps, + sal_uInt32 nCell, sal_uInt32 nFirstCell, sal_uInt32 nLastCell, sal_Int32 nRow, bool bIsEndRow, bool bMergedVertically ) +{ + const bool bIsStartCol = nCell == nFirstCell; + const bool bIsEndCol = nCell == nLastCell; + std::optional<PropertyMap::Property> pVerticalVal = pCellProps->getProperty(META_PROP_VERTICAL_BORDER); + std::optional<PropertyMap::Property> pHorizontalVal = pCellProps->getProperty(META_PROP_HORIZONTAL_BORDER); + + // Handle the vertical and horizontal borders + uno::Any aVertProp; + if ( !pVerticalVal) + { + pVerticalVal = pTableBorders->getProperty(META_PROP_VERTICAL_BORDER); + if ( pVerticalVal ) + aVertProp = pVerticalVal->second; + } + else + { + aVertProp = pVerticalVal->second; + pCellProps->Erase( pVerticalVal->first ); + } + + uno::Any aHorizProp; + if ( !pHorizontalVal ) + { + pHorizontalVal = pTableBorders->getProperty(META_PROP_HORIZONTAL_BORDER); + if ( pHorizontalVal ) + aHorizProp = pHorizontalVal->second; + } + else + { + aHorizProp = pHorizontalVal->second; + pCellProps->Erase( pHorizontalVal->first ); + } + + if ( bIsStartCol ) + lcl_mergeBorder( PROP_LEFT_BORDER, pTableBorders, pCellProps ); + + if ( bIsEndCol ) + lcl_mergeBorder( PROP_RIGHT_BORDER, pTableBorders, pCellProps ); + + // <w:insideV> counts if there are multiple cells in this row. + if ( pVerticalVal ) + { + if ( !bIsEndCol && nCell >= nFirstCell ) + pCellProps->Insert( PROP_RIGHT_BORDER, aVertProp, false ); + if ( !bIsStartCol && nCell <= nLastCell ) + pCellProps->Insert( PROP_LEFT_BORDER, aVertProp, false ); + } + + if ( nRow == 0 ) + { + lcl_mergeBorder( PROP_TOP_BORDER, pTableBorders, pCellProps ); + if ( pHorizontalVal && !bMergedVertically ) + pCellProps->Insert( PROP_BOTTOM_BORDER, aHorizProp, false ); + } + + if ( bMergedVertically ) + lcl_mergeBorder( PROP_BOTTOM_BORDER, pTableBorders, pCellProps ); + + if ( bIsEndRow ) + { + lcl_mergeBorder( PROP_BOTTOM_BORDER, pTableBorders, pCellProps ); + if ( pHorizontalVal ) + pCellProps->Insert( PROP_TOP_BORDER, aHorizProp, false ); + } + + if ( nRow > 0 && !bIsEndRow ) + { + if ( pHorizontalVal ) + { + pCellProps->Insert( PROP_TOP_BORDER, aHorizProp, false ); + pCellProps->Insert( PROP_BOTTOM_BORDER, aHorizProp, false ); + } + } +} + +#ifdef DBG_UTIL + +static void lcl_debug_BorderLine(table::BorderLine const & rLine) +{ + TagLogger::getInstance().startElement("BorderLine"); + TagLogger::getInstance().attribute("Color", rLine.Color); + TagLogger::getInstance().attribute("InnerLineWidth", rLine.InnerLineWidth); + TagLogger::getInstance().attribute("OuterLineWidth", rLine.OuterLineWidth); + TagLogger::getInstance().attribute("LineDistance", rLine.LineDistance); + TagLogger::getInstance().endElement(); +} + +static void lcl_debug_TableBorder(table::TableBorder const & rBorder) +{ + TagLogger::getInstance().startElement("TableBorder"); + lcl_debug_BorderLine(rBorder.TopLine); + TagLogger::getInstance().attribute("IsTopLineValid", sal_uInt32(rBorder.IsTopLineValid)); + lcl_debug_BorderLine(rBorder.BottomLine); + TagLogger::getInstance().attribute("IsBottomLineValid", sal_uInt32(rBorder.IsBottomLineValid)); + lcl_debug_BorderLine(rBorder.LeftLine); + TagLogger::getInstance().attribute("IsLeftLineValid", sal_uInt32(rBorder.IsLeftLineValid)); + lcl_debug_BorderLine(rBorder.RightLine); + TagLogger::getInstance().attribute("IsRightLineValid", sal_uInt32(rBorder.IsRightLineValid)); + lcl_debug_BorderLine(rBorder.VerticalLine); + TagLogger::getInstance().attribute("IsVerticalLineValid", sal_uInt32(rBorder.IsVerticalLineValid)); + lcl_debug_BorderLine(rBorder.HorizontalLine); + TagLogger::getInstance().attribute("IsHorizontalLineValid", sal_uInt32(rBorder.IsHorizontalLineValid)); + TagLogger::getInstance().attribute("Distance", rBorder.Distance); + TagLogger::getInstance().attribute("IsDistanceValid", sal_uInt32(rBorder.IsDistanceValid)); + TagLogger::getInstance().endElement(); +} +#endif + +struct TableInfo +{ + sal_Int32 nLeftBorderDistance; + sal_Int32 nRightBorderDistance; + sal_Int32 nTopBorderDistance; + sal_Int32 nBottomBorderDistance; + sal_Int32 nTblLook; + sal_Int32 nNestLevel; + PropertyMapPtr pTableDefaults; + PropertyMapPtr pTableBorders; + TableStyleSheetEntry* pTableStyle; + css::beans::PropertyValues aTableProperties; + std::vector< PropertyIds > aTablePropertyIds; + + TableInfo() + : nLeftBorderDistance(DEF_BORDER_DIST) + , nRightBorderDistance(DEF_BORDER_DIST) + , nTopBorderDistance(0) + , nBottomBorderDistance(0) + , nTblLook(0x4a0) + , nNestLevel(0) + , pTableDefaults(new PropertyMap) + , pTableBorders(new PropertyMap) + , pTableStyle(nullptr) + { + } + +}; + +namespace +{ + +bool lcl_extractTableBorderProperty(const PropertyMapPtr& pTableProperties, const PropertyIds nId, TableInfo const & rInfo, table::BorderLine2& rLine) +{ + if (!pTableProperties) + return false; + + const std::optional<PropertyMap::Property> aTblBorder = pTableProperties->getProperty(nId); + if( aTblBorder ) + { + OSL_VERIFY(aTblBorder->second >>= rLine); + + rInfo.pTableBorders->Insert( nId, uno::Any( rLine ) ); + rInfo.pTableDefaults->Erase( nId ); + + return true; + } + + return false; +} + +void lcl_extractHoriOrient(std::vector<beans::PropertyValue>& rFrameProperties, sal_Int32& nHoriOrient) +{ + // Shifts the frame left by the given value. + for (const beans::PropertyValue & rFrameProperty : rFrameProperties) + { + if (rFrameProperty.Name == "HoriOrient") + { + sal_Int32 nValue = rFrameProperty.Value.get<sal_Int32>(); + if (nValue != text::HoriOrientation::NONE) + nHoriOrient = nValue; + return; + } + } +} + +void lcl_DecrementHoriOrientPosition(std::vector<beans::PropertyValue>& rFrameProperties, sal_Int32 nAmount) +{ + // Shifts the frame left by the given value. + for (beans::PropertyValue & rPropertyValue : rFrameProperties) + { + if (rPropertyValue.Name == "HoriOrientPosition") + { + sal_Int32 nValue = rPropertyValue.Value.get<sal_Int32>(); + nValue -= nAmount; + rPropertyValue.Value <<= nValue; + return; + } + } +} + +void lcl_adjustBorderDistance(TableInfo& rInfo, const table::BorderLine2& rLeftBorder, + const table::BorderLine2& rRightBorder) +{ + // MS Word appears to do these things to adjust the cell horizontal area: + // + // bll = left borderline width + // blr = right borderline width + // cea = cell's edit area rectangle + // cea_w = cea width + // cml = cell's left margin (padding) defined in cell settings + // cmr = cell's right margin (padding) defined in cell settings + // cw = cell width (distance between middles of left borderline and right borderline) + // pad_l = actual cea left padding = (its left pos relative to middle of bll) + // pad_r = actual cea right padding = abs (its right pos relative to middle of blr) + // + // pad_l = max(bll/2, cml) -> cea does not overlap left borderline + // cea_w = cw-max(pad_l+blr/2, cml+cmr) -> cea does not overlap right borderline + // pad_r = max(pad_l+blr/2, cml+cmr) - pad_l + // + // It means that e.g. for border widths of 6 pt (~2.12 mm), left margin 0 mm, and right margin + // 2 mm, actual left and right margins will (unexpectedly) coincide with inner edges of cell's + // borderlines - the right margin won't create spacing between right of edit rectangle and the + // inner edge of right borderline. + + const sal_Int32 nActualL + = std::max<sal_Int32>(rLeftBorder.LineWidth / 2, rInfo.nLeftBorderDistance); + const sal_Int32 nActualR + = std::max<sal_Int32>(nActualL + rRightBorder.LineWidth / 2, + rInfo.nLeftBorderDistance + rInfo.nRightBorderDistance) + - nActualL; + rInfo.nLeftBorderDistance = nActualL; + rInfo.nRightBorderDistance = nActualR; +} + +} + +TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo, + std::vector<beans::PropertyValue>& rFrameProperties, + bool bConvertToFloatingInFootnote) +{ + // will receive the table style if any + TableStyleSheetEntry* pTableStyle = nullptr; + + if( m_aTableProperties ) + { + //create properties from the table attributes + //...pPropMap->Insert( PROP_LEFT_MARGIN, uno::makeAny( m_nLeftMargin - m_nGapHalf )); + //pPropMap->Insert( PROP_HORI_ORIENT, uno::makeAny( text::HoriOrientation::RIGHT )); + sal_Int32 nGapHalf = 0; + sal_Int32 nLeftMargin = 0; + + comphelper::SequenceAsHashMap aGrabBag; + + if (nullptr != m_rDMapper_Impl.getTableManager().getCurrentTableRealPosition()) + { + TablePositionHandler *pTablePositions = m_rDMapper_Impl.getTableManager().getCurrentTableRealPosition(); + + uno::Sequence< beans::PropertyValue > aGrabBagTS{ + comphelper::makePropertyValue("bottomFromText", pTablePositions->getBottomFromText()), + comphelper::makePropertyValue("horzAnchor", pTablePositions->getHorzAnchor()), + comphelper::makePropertyValue("leftFromText", pTablePositions->getLeftFromText()), + comphelper::makePropertyValue("rightFromText", pTablePositions->getRightFromText()), + comphelper::makePropertyValue("tblpX", pTablePositions->getX()), + comphelper::makePropertyValue("tblpXSpec", pTablePositions->getXSpec()), + comphelper::makePropertyValue("tblpY", pTablePositions->getY()), + comphelper::makePropertyValue("tblpYSpec", pTablePositions->getYSpec()), + comphelper::makePropertyValue("topFromText", pTablePositions->getTopFromText()), + comphelper::makePropertyValue("vertAnchor", pTablePositions->getVertAnchor()) + }; + + aGrabBag["TablePosition"] <<= aGrabBagTS; + } + else if (bConvertToFloatingInFootnote) + { + // define empty "TablePosition" to avoid export temporary floating + aGrabBag["TablePosition"] = uno::Any(); + } + + std::optional<PropertyMap::Property> aTableStyleVal = m_aTableProperties->getProperty(META_PROP_TABLE_STYLE_NAME); + if(aTableStyleVal) + { + // Apply table style properties recursively + OUString sTableStyleName; + aTableStyleVal->second >>= sTableStyleName; + StyleSheetTablePtr pStyleSheetTable = m_rDMapper_Impl.GetStyleSheetTable(); + const StyleSheetEntryPtr pStyleSheet = pStyleSheetTable->FindStyleSheetByISTD( sTableStyleName ); + pTableStyle = dynamic_cast<TableStyleSheetEntry*>( pStyleSheet.get( ) ); + m_aTableProperties->Erase( aTableStyleVal->first ); + + aGrabBag["TableStyleName"] <<= sTableStyleName; + + if( pStyleSheet ) + { + // First get the style properties, then the table ones + PropertyMapPtr pTableProps( m_aTableProperties.get() ); + TablePropertyMapPtr pEmptyProps( new TablePropertyMap ); + + m_aTableProperties = pEmptyProps; + + PropertyMapPtr pMergedProperties = pStyleSheet->GetMergedInheritedProperties(pStyleSheetTable); + + table::BorderLine2 aBorderLine; + TableInfo rStyleInfo; + if (lcl_extractTableBorderProperty(pMergedProperties, PROP_TOP_BORDER, rStyleInfo, aBorderLine)) + { + aGrabBag["TableStyleTopBorder"] <<= aBorderLine; + } + if (lcl_extractTableBorderProperty(pMergedProperties, PROP_BOTTOM_BORDER, rStyleInfo, aBorderLine)) + { + aGrabBag["TableStyleBottomBorder"] <<= aBorderLine; + } + if (lcl_extractTableBorderProperty(pMergedProperties, PROP_LEFT_BORDER, rStyleInfo, aBorderLine)) + { + aGrabBag["TableStyleLeftBorder"] <<= aBorderLine; + } + if (lcl_extractTableBorderProperty(pMergedProperties, PROP_RIGHT_BORDER, rStyleInfo, aBorderLine)) + { + aGrabBag["TableStyleRightBorder"] <<= aBorderLine; + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("mergedProps"); + if (pMergedProperties) + pMergedProperties->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + + m_aTableProperties->InsertProps(pMergedProperties); + m_aTableProperties->InsertProps(pTableProps); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("TableProperties"); + m_aTableProperties->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + if (pTableStyle) + { + // apply tblHeader setting of the table style + PropertyMapPtr pHeaderStyleProps = pTableStyle->GetProperties(CNF_FIRST_ROW); + if ( pHeaderStyleProps->getProperty(PROP_HEADER_ROW_COUNT) ) + m_aTableProperties->Insert(PROP_HEADER_ROW_COUNT, uno::Any( sal_Int32(1)), false); + } + } + } + + // This is the one preserving just all the table look attributes. + std::optional<PropertyMap::Property> oTableLook = m_aTableProperties->getProperty(META_PROP_TABLE_LOOK); + if (oTableLook) + { + aGrabBag["TableStyleLook"] = oTableLook->second; + m_aTableProperties->Erase(oTableLook->first); + } + + // This is just the "val" attribute's numeric value. + const std::optional<PropertyMap::Property> aTblLook = m_aTableProperties->getProperty(PROP_TBL_LOOK); + if(aTblLook) + { + aTblLook->second >>= rInfo.nTblLook; + m_aTableProperties->Erase( aTblLook->first ); + } + + // apply cell margin settings of the table style + const std::optional<PropertyMap::Property> oLeftMargin = m_aTableProperties->getProperty(META_PROP_CELL_MAR_LEFT); + if (oLeftMargin) + { + oLeftMargin->second >>= rInfo.nLeftBorderDistance; + m_aTableProperties->Erase(oLeftMargin->first); + } + const std::optional<PropertyMap::Property> oRightMargin = m_aTableProperties->getProperty(META_PROP_CELL_MAR_RIGHT); + if (oRightMargin) + { + oRightMargin->second >>= rInfo.nRightBorderDistance; + m_aTableProperties->Erase(oRightMargin->first); + } + const std::optional<PropertyMap::Property> oTopMargin = m_aTableProperties->getProperty(META_PROP_CELL_MAR_TOP); + if (oTopMargin) + { + oTopMargin->second >>= rInfo.nTopBorderDistance; + m_aTableProperties->Erase(oTopMargin->first); + } + const std::optional<PropertyMap::Property> oBottomMargin = m_aTableProperties->getProperty(META_PROP_CELL_MAR_BOTTOM); + if (oBottomMargin) + { + oBottomMargin->second >>= rInfo.nBottomBorderDistance; + m_aTableProperties->Erase(oBottomMargin->first); + } + + // Set the table default attributes for the cells + rInfo.pTableDefaults->InsertProps(m_aTableProperties.get()); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("TableDefaults"); + rInfo.pTableDefaults->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + + if (!aGrabBag.empty()) + { + m_aTableProperties->Insert( PROP_TABLE_INTEROP_GRAB_BAG, uno::Any( aGrabBag.getAsConstPropertyValueList() ) ); + } + + m_aTableProperties->getValue( TablePropertyMap::GAP_HALF, nGapHalf ); + + std::optional<PropertyMap::Property> oLeftMarginFromStyle = m_aTableProperties->getProperty(PROP_LEFT_MARGIN); + if (oLeftMarginFromStyle) + { + oLeftMarginFromStyle->second >>= nLeftMargin; + // don't need to erase, we will push back the adjusted value + // of this (or the direct formatting, if that exists) later + } + m_aTableProperties->getValue( TablePropertyMap::LEFT_MARGIN, nLeftMargin ); + + m_aTableProperties->getValue( TablePropertyMap::CELL_MAR_LEFT, + rInfo.nLeftBorderDistance ); + m_aTableProperties->getValue( TablePropertyMap::CELL_MAR_RIGHT, + rInfo.nRightBorderDistance ); + m_aTableProperties->getValue( TablePropertyMap::CELL_MAR_TOP, + rInfo.nTopBorderDistance ); + m_aTableProperties->getValue( TablePropertyMap::CELL_MAR_BOTTOM, + rInfo.nBottomBorderDistance ); + + table::TableBorderDistances aDistances; + aDistances.IsTopDistanceValid = + aDistances.IsBottomDistanceValid = + aDistances.IsLeftDistanceValid = + aDistances.IsRightDistanceValid = true; + aDistances.TopDistance = static_cast<sal_Int16>( rInfo.nTopBorderDistance ); + aDistances.BottomDistance = static_cast<sal_Int16>( rInfo.nBottomBorderDistance ); + aDistances.LeftDistance = static_cast<sal_Int16>( rInfo.nLeftBorderDistance ); + aDistances.RightDistance = static_cast<sal_Int16>( rInfo.nRightBorderDistance ); + + m_aTableProperties->Insert( PROP_TABLE_BORDER_DISTANCES, uno::Any( aDistances ) ); + + if (!rFrameProperties.empty()) + lcl_DecrementHoriOrientPosition(rFrameProperties, rInfo.nLeftBorderDistance); + + // Set table above/bottom spacing to 0. + m_aTableProperties->Insert( PROP_TOP_MARGIN, uno::Any( sal_Int32( 0 ) ) ); + m_aTableProperties->Insert( PROP_BOTTOM_MARGIN, uno::Any( sal_Int32( 0 ) ) ); + + //table border settings + table::TableBorder aTableBorder; + table::BorderLine2 aBorderLine, aLeftBorder, aRightBorder; + + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), PROP_TOP_BORDER, rInfo, aBorderLine)) + { + aTableBorder.TopLine = aBorderLine; + aTableBorder.IsTopLineValid = true; + } + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), PROP_BOTTOM_BORDER, rInfo, aBorderLine)) + { + aTableBorder.BottomLine = aBorderLine; + aTableBorder.IsBottomLineValid = true; + } + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), PROP_LEFT_BORDER, rInfo, aLeftBorder)) + { + aTableBorder.LeftLine = aLeftBorder; + aTableBorder.IsLeftLineValid = true; + } + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), PROP_RIGHT_BORDER, rInfo, + aRightBorder)) + { + aTableBorder.RightLine = aRightBorder; + aTableBorder.IsRightLineValid = true; + } + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), META_PROP_HORIZONTAL_BORDER, rInfo, aBorderLine)) + { + aTableBorder.HorizontalLine = aBorderLine; + aTableBorder.IsHorizontalLineValid = true; + } + if (lcl_extractTableBorderProperty(m_aTableProperties.get(), META_PROP_VERTICAL_BORDER, rInfo, aBorderLine)) + { + aTableBorder.VerticalLine = aBorderLine; + aTableBorder.IsVerticalLineValid = true; + } + + aTableBorder.Distance = 0; + aTableBorder.IsDistanceValid = false; + + m_aTableProperties->Insert( PROP_TABLE_BORDER, uno::Any( aTableBorder ) ); + +#ifdef DBG_UTIL + lcl_debug_TableBorder(aTableBorder); +#endif + + // Table position in Office is computed in 2 different ways : + // - top level tables: the goal is to have in-cell text starting at table indent pos (tblInd), + // so table's position depends on table's cells margin + // - nested tables: the goal is to have left-most border starting at table_indent pos + + // Only top level table position depends on border width of Column A. + if ( !m_aCellProperties.empty() && !m_aCellProperties[0].empty() ) + { + // aLeftBorder already contains tblBorder; overwrite if cell is different. + std::optional<PropertyMap::Property> aCellBorder + = m_aCellProperties[0][0]->getProperty(PROP_LEFT_BORDER); + if ( aCellBorder ) + aCellBorder->second >>= aLeftBorder; + aCellBorder = m_aCellProperties[0][0]->getProperty(PROP_RIGHT_BORDER); + if (aCellBorder) + aCellBorder->second >>= aRightBorder; + } + if (rInfo.nNestLevel == 1 && aLeftBorder.LineWidth && !rFrameProperties.empty()) + { + lcl_DecrementHoriOrientPosition(rFrameProperties, aLeftBorder.LineWidth * 0.5); + } + lcl_adjustBorderDistance(rInfo, aLeftBorder, aRightBorder); + + // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables; + // the default behavior when DOCX doesn't define "compatibilityMode" option is to add the cell spacing + + // Undefined should not be possible any more for DOCX, but it is for RTF. + // In any case, continue to treat undefined as version 12 during import. + sal_Int32 nMode = m_rDMapper_Impl.GetSettingsTable()->GetWordCompatibilityMode(); + + if (((nMode < 0) || (0 < nMode && nMode <= 14)) && rInfo.nNestLevel == 1) + { + const sal_Int32 nAdjustedMargin = nLeftMargin - nGapHalf - rInfo.nLeftBorderDistance; + m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::Any( nAdjustedMargin ) ); + } + else + { + // Writer starts a table in the middle of the border. + // Word starts a table at the left edge of the border, + // so emulate that by adding the half the width. (also see docxattributeoutput) + if ( rInfo.nNestLevel > 1 && nLeftMargin < 0 ) + nLeftMargin = 0; + const sal_Int32 nAdjustedMargin = nLeftMargin - nGapHalf + (aLeftBorder.LineWidth / 2); + m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::Any( nAdjustedMargin ) ); + } + + sal_Int32 nTableWidth = 0; + sal_Int32 nTableWidthType = text::SizeType::FIX; + m_aTableProperties->getValue( TablePropertyMap::TABLE_WIDTH, nTableWidth ); + m_aTableProperties->getValue( TablePropertyMap::TABLE_WIDTH_TYPE, nTableWidthType ); + if( nTableWidthType == text::SizeType::FIX ) + { + if( nTableWidth > 0 ) + m_aTableProperties->Insert( PROP_WIDTH, uno::Any( nTableWidth )); + else + { + // tdf#109524: If there is no width for the table, make it simply 100% by default. + // TODO: use cell contents to evaluate width (according to ECMA-376-1:2016 17.18.87) + nTableWidth = 100; + nTableWidthType = text::SizeType::VARIABLE; + } + } + if (nTableWidthType != text::SizeType::FIX) + { + m_aTableProperties->Insert( PROP_RELATIVE_WIDTH, uno::Any( sal_Int16( nTableWidth ) ) ); + m_aTableProperties->Insert( PROP_IS_WIDTH_RELATIVE, uno::Any( true ) ); + } + + sal_Int32 nHoriOrient = text::HoriOrientation::LEFT_AND_WIDTH; + // Fetch Horizontal Orientation in rFrameProperties if not set in m_aTableProperties + if ( !m_aTableProperties->getValue( TablePropertyMap::HORI_ORIENT, nHoriOrient ) ) + lcl_extractHoriOrient( rFrameProperties, nHoriOrient ); + m_aTableProperties->Insert( PROP_HORI_ORIENT, uno::Any( sal_Int16(nHoriOrient) ) ); + //fill default value - if not available + m_aTableProperties->Insert( PROP_HEADER_ROW_COUNT, uno::Any( sal_Int32(0)), false); + + // if table is only a single row, and row is set as don't split, set the same value for the whole table. + if( m_aRowProperties.size() == 1 && m_aRowProperties[0] ) + { + std::optional<PropertyMap::Property> oSplitAllowed = m_aRowProperties[0]->getProperty(PROP_IS_SPLIT_ALLOWED); + if( oSplitAllowed ) + { + bool bRowCanSplit = true; + oSplitAllowed->second >>= bRowCanSplit; + if( !bRowCanSplit ) + m_aTableProperties->Insert( PROP_SPLIT, uno::Any(bRowCanSplit) ); + } + } + + rInfo.aTableProperties = m_aTableProperties->GetPropertyValues(); + rInfo.aTablePropertyIds = m_aTableProperties->GetPropertyIds(); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("debug.tableprops"); + m_aTableProperties->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + + } + + return pTableStyle; +} + +CellPropertyValuesSeq_t DomainMapperTableHandler::endTableGetCellProperties(TableInfo & rInfo, std::vector<HorizontallyMergedCell>& rMerges) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("getCellProperties"); +#endif + + CellPropertyValuesSeq_t aCellProperties( m_aCellProperties.size() ); + + if ( m_aCellProperties.empty() ) + { + #ifdef DBG_UTIL + TagLogger::getInstance().endElement(); + #endif + return aCellProperties; + } + // std::vector< std::vector<PropertyMapPtr> > m_aCellProperties + PropertyMapVector2::const_iterator aRowOfCellsIterator = m_aCellProperties.begin(); + PropertyMapVector2::const_iterator aRowOfCellsIteratorEnd = m_aCellProperties.end(); + PropertyMapVector2::const_iterator aLastRowIterator = m_aCellProperties.end() - 1; + sal_Int32 nRow = 0; + + css::uno::Sequence<css::beans::PropertyValues>* pCellProperties = aCellProperties.getArray(); + PropertyMapVector1::const_iterator aRowIter = m_aRowProperties.begin(); + while( aRowOfCellsIterator != aRowOfCellsIteratorEnd ) + { + //aRowOfCellsIterator points to a vector of PropertyMapPtr + PropertyMapVector1::const_iterator aCellIterator = aRowOfCellsIterator->begin(); + PropertyMapVector1::const_iterator aCellIteratorEnd = aRowOfCellsIterator->end(); + + sal_Int32 nRowStyleMask = 0; + + if (aRowOfCellsIterator==m_aCellProperties.begin()) + { + if(rInfo.nTblLook&0x20) + nRowStyleMask |= CNF_FIRST_ROW; // first row style used + } + else if (aRowOfCellsIterator==aLastRowIterator) + { + if(rInfo.nTblLook&0x40) + nRowStyleMask |= CNF_LAST_ROW; // last row style used + } + else if (*aRowIter && (*aRowIter)->isSet(PROP_TBL_HEADER)) + nRowStyleMask |= CNF_FIRST_ROW; // table header implies first row + if(!nRowStyleMask) // if no row style used yet + { + // banding used only if not first and or last row style used + if(!(rInfo.nTblLook&0x200)) + { // hbanding used + int n = nRow + 1; + if(rInfo.nTblLook&0x20) + n++; + if(n & 1) + nRowStyleMask = CNF_ODD_HBAND; + else + nRowStyleMask = CNF_EVEN_HBAND; + } + } + + // Note that this is intentionally called "cell" and not "column". + // Don't make the mistake that all cell x's will be in the same column. + // Merged cells (grid span) in a row will affect the actual column. (fake cells were added to handle gridBefore/After) + sal_Int32 nCell = 0; + pCellProperties[nRow].realloc( aRowOfCellsIterator->size() ); + beans::PropertyValues* pSingleCellProperties = pCellProperties[nRow].getArray(); + + while( aCellIterator != aCellIteratorEnd ) + { + PropertyMapPtr pAllCellProps( new PropertyMap ); + + PropertyMapVector1::const_iterator aLastCellIterator = aRowOfCellsIterator->end() - 1; + bool bIsEndCol = aCellIterator == aLastCellIterator; + bool bIsEndRow = aRowOfCellsIterator == aLastRowIterator; + + //aCellIterator points to a PropertyMapPtr; + if( *aCellIterator ) + { + // remove directly applied insideV/H borders since they are meaningless without a context (tdf#82177) + (*aCellIterator)->Erase(META_PROP_VERTICAL_BORDER); + (*aCellIterator)->Erase(META_PROP_HORIZONTAL_BORDER); + + pAllCellProps->InsertProps(rInfo.pTableDefaults); + + sal_Int32 nCellStyleMask = 0; + if (aCellIterator==aRowOfCellsIterator->begin()) + { + if(rInfo.nTblLook&0x80) + nCellStyleMask = CNF_FIRST_COLUMN; // first col style used + } + else if (bIsEndCol) + { + if(rInfo.nTblLook&0x100) + nCellStyleMask = CNF_LAST_COLUMN; // last col style used + } + if(!nCellStyleMask) // if no cell style is used yet + { + if(!(rInfo.nTblLook&0x400)) + { // vbanding used + int n = nCell + 1; + if(rInfo.nTblLook&0x80) + n++; + if(n & 1) + nCellStyleMask = CNF_ODD_VBAND; + else + nCellStyleMask = CNF_EVEN_VBAND; + } + } + sal_Int32 nCnfStyleMask = nCellStyleMask + nRowStyleMask; + if(nCnfStyleMask == CNF_FIRST_COLUMN + CNF_FIRST_ROW) + nCnfStyleMask |= CNF_FIRST_ROW_FIRST_COLUMN; + else if(nCnfStyleMask == CNF_FIRST_COLUMN + CNF_LAST_ROW) + nCnfStyleMask |= CNF_LAST_ROW_FIRST_COLUMN; + else if(nCnfStyleMask == CNF_LAST_COLUMN + CNF_FIRST_ROW) + nCnfStyleMask |= CNF_FIRST_ROW_LAST_COLUMN; + else if(nCnfStyleMask == CNF_LAST_COLUMN + CNF_LAST_ROW) + nCnfStyleMask |= CNF_LAST_ROW_LAST_COLUMN; + + if ( rInfo.pTableStyle ) + { + PropertyMapPtr pStyleProps = rInfo.pTableStyle->GetProperties( nCnfStyleMask ); + + // Check if we need to clean up some empty border definitions to match what Word does. + static const PropertyIds pBorders[] = + { + PROP_TOP_BORDER, PROP_LEFT_BORDER, PROP_BOTTOM_BORDER, PROP_RIGHT_BORDER + }; + for (const PropertyIds& rBorder : pBorders) + { + std::optional<PropertyMap::Property> oStyleCellBorder = pStyleProps->getProperty(rBorder); + std::optional<PropertyMap::Property> oDirectCellBorder = (*aCellIterator)->getProperty(rBorder); + if (oStyleCellBorder && oDirectCellBorder) + { + // We have a cell border from the table style and as direct formatting as well. + table::BorderLine2 aStyleCellBorder = oStyleCellBorder->second.get<table::BorderLine2>(); + table::BorderLine2 aDirectCellBorder = oDirectCellBorder->second.get<table::BorderLine2>(); + if (aStyleCellBorder.LineStyle != table::BorderLineStyle::NONE && aDirectCellBorder.LineStyle == table::BorderLineStyle::NONE) + { + // The style one would be visible, but then cleared away as direct formatting. + // Delete both, so that table formatting can become visible. + pStyleProps->Erase(rBorder); + (*aCellIterator)->Erase(rBorder); + } + else + { + std::optional<PropertyMap::Property> oTableBorder = rInfo.pTableBorders->getProperty(rBorder); + if (oTableBorder) + { + table::BorderLine2 aTableBorder = oTableBorder->second.get<table::BorderLine2>(); + // Both style and direct formatting says that the cell has no border. + bool bNoCellBorder = aStyleCellBorder.LineStyle == table::BorderLineStyle::NONE && aDirectCellBorder.LineStyle == table::BorderLineStyle::NONE; + if (aTableBorder.LineStyle != table::BorderLineStyle::NONE && bNoCellBorder) + { + // But at a table-level, there is a border, then again delete both cell properties. + pStyleProps->Erase(rBorder); + (*aCellIterator)->Erase(rBorder); + } + } + } + } + } + + pAllCellProps->InsertProps( pStyleProps ); + } + + // Remove properties from style/row that aren't allowed in cells + pAllCellProps->Erase( PROP_HEADER_ROW_COUNT ); + pAllCellProps->Erase( PROP_TBL_HEADER ); + + // Then add the cell properties + pAllCellProps->InsertProps(*aCellIterator); + std::swap(*(*aCellIterator), *pAllCellProps ); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("cell"); + TagLogger::getInstance().attribute("cell", nCell); + TagLogger::getInstance().attribute("row", nRow); +#endif + + // Do not apply horizontal and vertical borders to a one cell table. + if (m_aCellProperties.size() <= 1 && aRowOfCellsIterator->size() <= 1) + { + rInfo.pTableBorders->Erase(META_PROP_HORIZONTAL_BORDER); + rInfo.pTableBorders->Erase(META_PROP_VERTICAL_BORDER); + } + // Do not apply vertical borders to a one column table. + else if (m_aCellProperties.size() > 1 && aRowOfCellsIterator->size() <= 1) + { + bool isOneCol = true; + for (size_t i = nRow; i < m_aCellProperties.size(); i++) + { + if (m_aCellProperties[i].size() > 1) + { + isOneCol = false; + break; + } + } + if (isOneCol) + rInfo.pTableBorders->Erase(META_PROP_VERTICAL_BORDER); + } + // Do not apply horizontal borders to a one row table. + else if (m_aCellProperties.size() == 1 && aRowOfCellsIterator->size() > 1) + { + rInfo.pTableBorders->Erase(META_PROP_HORIZONTAL_BORDER); + } + + // tdf#129452 Checking if current cell is vertically merged with all the other cells below to the bottom. + // This must be done in order to apply the bottom border of the table to the first cell in a vertical merge. + std::optional<PropertyMap::Property> oProp = m_aCellProperties[nRow][nCell]->getProperty(PROP_VERTICAL_MERGE); + bool bMergedVertically = oProp && oProp->second.get<bool>(); // starting cell + if ( bMergedVertically ) + { + const sal_uInt32 nColumn = m_rDMapper_Impl.getTableManager().findColumn(nRow, nCell); + sal_Int32 nLastMergedRow = 0; + for (size_t i = nRow + 1; bMergedVertically && i < m_aCellProperties.size(); i++) + { + const sal_uInt32 nColumnCell = m_rDMapper_Impl.getTableManager().findColumnCell(i, nColumn); + if ( m_aCellProperties[i].size() > sal::static_int_cast<std::size_t>(nColumnCell) ) + { + oProp = m_aCellProperties[i][nColumnCell]->getProperty(PROP_VERTICAL_MERGE); + bMergedVertically = oProp && !oProp->second.get<bool>(); //continuing cell + if ( bMergedVertically ) + nLastMergedRow = i; + } + else + bMergedVertically = false; + } + + // Only consider the bottom border setting from the last merged cell. + // Note: in MSO, left/right apply per-unmerged-row. Can't do that in LO, so just using the top cell's borders should be fine. + if ( nRow < nLastMergedRow ) + { + (*aCellIterator)->Erase(PROP_BOTTOM_BORDER); + const sal_uInt32 nColumnCell = m_rDMapper_Impl.getTableManager().findColumnCell(nLastMergedRow, nColumn); + lcl_mergeBorder( PROP_BOTTOM_BORDER, m_aCellProperties[nLastMergedRow][nColumnCell], *aCellIterator ); + } + } + + const sal_uInt32 nFirstCell = m_rDMapper_Impl.getTableManager().getGridBefore(nRow); + const sal_uInt32 nLastCell = m_aCellProperties[nRow].size() - m_rDMapper_Impl.getTableManager().getGridAfter(nRow) - 1; + lcl_computeCellBorders( rInfo.pTableBorders, *aCellIterator, nCell, nFirstCell, nLastCell, nRow, bIsEndRow, bMergedVertically ); + + //now set the default left+right border distance TODO: there's an sprm containing the default distance! + aCellIterator->get()->Insert( PROP_LEFT_BORDER_DISTANCE, + uno::Any(rInfo.nLeftBorderDistance ), false); + aCellIterator->get()->Insert( PROP_RIGHT_BORDER_DISTANCE, + uno::Any(rInfo.nRightBorderDistance ), false); + aCellIterator->get()->Insert( PROP_TOP_BORDER_DISTANCE, + uno::Any(rInfo.nTopBorderDistance ), false); + aCellIterator->get()->Insert( PROP_BOTTOM_BORDER_DISTANCE, + uno::Any(rInfo.nBottomBorderDistance ), false); + + // Horizontal merge is not a UNO property, extract that info here to rMerges, and then remove it from the map. + const std::optional<PropertyMap::Property> aHorizontalMergeVal = (*aCellIterator)->getProperty(PROP_HORIZONTAL_MERGE); + if (aHorizontalMergeVal) + { + if (aHorizontalMergeVal->second.get<bool>()) + { + // first cell in a merge + HorizontallyMergedCell aMerge(nRow, nCell); + rMerges.push_back(aMerge); + } + else if (!rMerges.empty()) + { + // resuming an earlier merge + HorizontallyMergedCell& rMerge = rMerges.back(); + rMerge.m_nLastRow = nRow; + rMerge.m_nLastCol = nCell; + } + (*aCellIterator)->Erase(PROP_HORIZONTAL_MERGE); + } + pSingleCellProperties[nCell] = (*aCellIterator)->GetPropertyValues(); +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + } + ++nCell; + ++aCellIterator; + } + ++nRow; + ++aRowOfCellsIterator; + ++aRowIter; + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + + return aCellProperties; +} + +/// Do all cells in this row have a CellHideMark property? +static bool lcl_hideMarks(PropertyMapVector1& rCellProperties) +{ + for (const PropertyMapPtr & p : rCellProperties) + { + // if anything is vertically merged, the row must not be set to fixed + // as Writer's layout doesn't handle that well + if (!p->isSet(PROP_CELL_HIDE_MARK) || p->isSet(PROP_VERTICAL_MERGE)) + return false; + } + return true; +} + +/// Are all cells in this row empty? +static bool lcl_emptyRow(std::vector<RowSequence_t>& rTableRanges, sal_Int32 nRow) +{ + if (nRow >= static_cast<sal_Int32>(rTableRanges.size())) + { + SAL_WARN("writerfilter.dmapper", "m_aCellProperties not in sync with rTableRanges?"); + return false; + } + + const RowSequence_t rRowSeq = rTableRanges[nRow]; + if (!rRowSeq.hasElements()) + { + SAL_WARN("writerfilter.dmapper", "m_aCellProperties not in sync with rTableRanges?"); + return false; + } + + if (!rRowSeq[0][0].is()) + { + // This can happen when we can't import the table, e.g. we're inside a + // comment. + SAL_WARN("writerfilter.dmapper", "rRowSeq[0][0] is an empty reference"); + return false; + } + + uno::Reference<text::XTextRangeCompare> xTextRangeCompare(rRowSeq[0][0]->getText(), uno::UNO_QUERY); + try + { + // See SwXText::Impl::ConvertCell(), we need to compare the start of + // the start and the end of the end. However for our text ranges, only + // the starts are set, so compareRegionStarts() does what we need. + bool bRangesAreNotEqual = std::any_of(rRowSeq.begin(), rRowSeq.end(), + [&xTextRangeCompare](const CellSequence_t& rCellSeq) { + return xTextRangeCompare->compareRegionStarts(rCellSeq[0], rCellSeq[1]) != 0; }); + if (bRangesAreNotEqual) + return false; + } + catch (const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "compareRegionStarts() failed"); + return false; + } + return true; +} + +css::uno::Sequence<css::beans::PropertyValues> DomainMapperTableHandler::endTableGetRowProperties() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("getRowProperties"); +#endif + + css::uno::Sequence<css::beans::PropertyValues> aRowProperties( m_aRowProperties.size() ); + auto aRowPropertiesRange = asNonConstRange(aRowProperties); + sal_Int32 nRow = 0; + for( const auto& rRow : m_aRowProperties ) + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("rowProps.row"); +#endif + if (rRow) + { + //set default to 'break across pages" + rRow->Insert( PROP_IS_SPLIT_ALLOWED, uno::Any(true ), false ); + // tblHeader is only our property, remove before the property map hits UNO + rRow->Erase(PROP_TBL_HEADER); + + if (lcl_hideMarks(m_aCellProperties[nRow]) && lcl_emptyRow(m_aTableRanges, nRow)) + { + // We have CellHideMark on all cells, and also all cells are empty: + // Force the row height to be exactly as specified, and not just as the minimum suggestion. + rRow->Insert(PROP_SIZE_TYPE, uno::Any(text::SizeType::FIX)); + } + + aRowPropertiesRange[nRow] = rRow->GetPropertyValues(); +#ifdef DBG_UTIL + rRow->dumpXml(); + lcl_DumpPropertyValues(aRowProperties[nRow]); +#endif + } + ++nRow; +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + + return aRowProperties; +} + +// table style has got bigger precedence than docDefault style, +// but lower precedence than the paragraph styles and direct paragraph formatting +void DomainMapperTableHandler::ApplyParagraphPropertiesFromTableStyle(TableParagraph rParaProp, std::vector< PropertyIds > aAllTableParaProperties, const css::beans::PropertyValues rCellProperties) +{ + for( auto const& eId : aAllTableParaProperties ) + { + // apply paragraph and character properties of the table style on table paragraphs + // if there is no direct paragraph formatting + bool bIsParaLevel = rParaProp.m_pPropertyMap->isSet(eId); + if ( !bIsParaLevel || isCharacterProperty(eId) ) + { + if ( (eId == PROP_PARA_LEFT_MARGIN || eId == PROP_PARA_FIRST_LINE_INDENT) && + rParaProp.m_pPropertyMap->isSet(PROP_NUMBERING_RULES) ) + { + // indentation of direct numbering has bigger precedence, than table style + continue; + } + + OUString sPropertyName = getPropertyName(eId); + + auto pCellProp = std::find_if(rCellProperties.begin(), rCellProperties.end(), + [&](const beans::PropertyValue& rProp) { return rProp.Name == sPropertyName; }); + // this cell applies the table style property + if (pCellProp != rCellProperties.end()) + { + bool bDocDefault; + // handle paragraph background color defined in CellColorHandler + if (eId == PROP_FILL_COLOR) + { + // table style defines paragraph background color, use the correct property name + auto pFillStyleProp = std::find_if(rCellProperties.begin(), rCellProperties.end(), + [](const beans::PropertyValue& rProp) { return rProp.Name == "FillStyle"; }); + if ( pFillStyleProp != rCellProperties.end() && + pFillStyleProp->Value == uno::Any(drawing::FillStyle_SOLID) ) + { + sPropertyName = "ParaBackColor"; + } + else + { + // FillStyle_NONE, skip table style usage for paragraph background color + continue; + } + } + OUString sParaStyleName; + rParaProp.m_rPropertySet->getPropertyValue("ParaStyleName") >>= sParaStyleName; + StyleSheetEntryPtr pEntry = m_rDMapper_Impl.GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sParaStyleName); + uno::Any aParaStyle = m_rDMapper_Impl.GetPropertyFromStyleSheet(eId, pEntry, true, true, &bDocDefault); + // A very strange compatibility rule says that the DEFAULT style's specified fontsize of 11 or 12 + // or a specified left justify will always be overridden by the table-style. + // Normally this rule is applied, so always do this unless a compatSetting indicates otherwise. + bool bCompatOverride = false; + if ( (eId == PROP_CHAR_HEIGHT || eId == PROP_PARA_ADJUST) && sParaStyleName == m_rDMapper_Impl.GetDefaultParaStyleName() ) + { + if ( eId == PROP_CHAR_HEIGHT ) + bCompatOverride = aParaStyle == uno::Any(double(11)) || aParaStyle == uno::Any(double(12)); + else if ( eId == PROP_PARA_ADJUST ) + { + style::ParagraphAdjust eAdjust(style::ParagraphAdjust_CENTER); + aParaStyle >>= eAdjust; + bCompatOverride = eAdjust == style::ParagraphAdjust_LEFT; + } + + // The wording is confusing here. Normally, the paragraph style DOES override the table-style. + // But for these two special situations, do not override the table-style. So the default is false. + // If false, then "CompatOverride" the normal behaviour, and apply the table-style's value. + bCompatOverride &= !m_rDMapper_Impl.GetSettingsTable()->GetCompatSettingValue(u"overrideTableStyleFontSizeAndJustification"); + } + + // use table style when no paragraph style setting or a docDefault value is applied instead of it + if ( aParaStyle == uno::Any() || bDocDefault || bCompatOverride ) try + { + // check property state of paragraph + uno::Reference<text::XParagraphCursor> xParagraph( + rParaProp.m_rEndParagraph->getText()->createTextCursorByRange(rParaProp.m_rEndParagraph), uno::UNO_QUERY_THROW ); + // select paragraph + xParagraph->gotoStartOfParagraph( true ); + uno::Reference< beans::XPropertyState > xParaProperties( xParagraph, uno::UNO_QUERY_THROW ); + if ( xParaProperties->getPropertyState(sPropertyName) == css::beans::PropertyState_DEFAULT_VALUE ) + { + // don't overwrite empty paragraph with table style, if it has a direct paragraph formatting + if ( bIsParaLevel && xParagraph->getString().getLength() == 0 ) + continue; + + if ( eId != PROP_FILL_COLOR ) + { + // apply style setting when the paragraph doesn't modify it + rParaProp.m_rPropertySet->setPropertyValue( sPropertyName, pCellProp->Value ); + } + else + { + // we need this for complete import of table-style based paragraph background color + rParaProp.m_rPropertySet->setPropertyValue( "FillColor", pCellProp->Value ); + rParaProp.m_rPropertySet->setPropertyValue( "FillStyle", uno::Any(drawing::FillStyle_SOLID) ); + } + } + else + { + // apply style setting only on text portions without direct modification of it + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xParagraph, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while ( xRunEnum->hasMoreElements() ) + { + uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + uno::Reference< beans::XPropertyState > xRunProperties( xRun, uno::UNO_QUERY_THROW ); + if ( xRunProperties->getPropertyState(sPropertyName) == css::beans::PropertyState_DEFAULT_VALUE ) + { + uno::Reference< beans::XPropertySet > xRunPropertySet( xRun, uno::UNO_QUERY_THROW ); + xRunPropertySet->setPropertyValue( sPropertyName, pCellProp->Value ); + } + } + } + } + catch ( const uno::Exception & ) + { + TOOLS_INFO_EXCEPTION("writerfilter.dmapper", "Exception during table style correction"); + } + } + } + } +} + +// convert formula range identifier ABOVE, BELOW, LEFT and RIGHT +static void lcl_convertFormulaRanges(const uno::Reference<text::XTextTable> & xTable) +{ + uno::Reference<table::XCellRange> xCellRange(xTable, uno::UNO_QUERY_THROW); + uno::Reference<container::XIndexAccess> xTableRows(xTable->getRows(), uno::UNO_QUERY_THROW); + sal_Int32 nRows = xTableRows->getCount(); + for (sal_Int32 nRow = 0; nRow < nRows; ++nRow) + { + for (sal_Int16 nCol = 0; nCol < MAXTABLECELLS; ++nCol) + { + try + { + uno::Reference<beans::XPropertySet> xCellProperties(xCellRange->getCellByPosition(nCol, nRow), uno::UNO_QUERY_THROW); + uno::Sequence<beans::PropertyValue> aCellGrabBag; + xCellProperties->getPropertyValue("CellInteropGrabBag") >>= aCellGrabBag; + OUString sFormula; + bool bReplace = false; + for (const auto& rProp : std::as_const(aCellGrabBag)) + { + if ( rProp.Name == "CellFormulaConverted" ) + { + rProp.Value >>= sFormula; + struct RangeDirection + { + OUString m_sName; + sal_Int16 m_nCol; + sal_Int16 m_nRow; + }; + static const RangeDirection pDirections[] = + { + { OUString(" LEFT "), -1, 0}, + { OUString(" RIGHT "), 1, 0}, + { OUString(" ABOVE "), 0, -1}, + { OUString(" BELOW "), 0, 1 } + }; + for (const RangeDirection& rRange : pDirections) + { + if ( sFormula.indexOf(rRange.m_sName) > -1 ) + { + // range starts at the first cell above/below/left/right, but ends at the + // table border or at the first non-value cell after a value cell + bool bFoundFirst = false; + OUString sNextCell; + OUString sLastCell; + OUString sLastValueCell; + // walk through the cells of the range + try + { + sal_Int32 nCell = 0; + while (++nCell) + { + uno::Reference<beans::XPropertySet> xCell( + xCellRange->getCellByPosition(nCol + nCell * rRange.m_nCol, nRow + nCell * rRange.m_nRow), + uno::UNO_QUERY_THROW); + // empty cell or cell with text content is end of the range + uno::Reference<text::XText> xText(xCell, uno::UNO_QUERY_THROW); + sLastCell = xCell->getPropertyValue("CellName").get<OUString>(); + if (sNextCell.isEmpty()) + sNextCell = sLastCell; + try + { + // accept numbers with comma and percent + OUString sCellText = xText->getString().replace(',', '.'); + if (sCellText.endsWith("%")) + sCellText = sCellText.copy(0, sCellText.getLength()-1); + boost::lexical_cast<double>(sCellText); + } + catch( boost::bad_lexical_cast const& ) + { + if ( !bFoundFirst ) + { + // still search value cells + continue; + } + else + { + // end of range + break; + } + } + sLastValueCell = sLastCell; + bFoundFirst = true; + } + } + catch ( const lang::IndexOutOfBoundsException & ) + { + } + + if ( !sNextCell.isEmpty() ) + { + OUString sRange = "<" + sNextCell + ":" + + ( sLastValueCell.isEmpty() ? sLastCell : sLastValueCell ) + ">"; + sFormula = sFormula.replaceAll(rRange.m_sName, sRange); + bReplace = true; + } + } + } + + // update formula field + if (bReplace) + { + uno::Reference<text::XText> xCell(xCellRange->getCellByPosition(nCol, nRow), uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCell, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while ( xRunEnum->hasMoreElements() ) + { + uno::Reference<text::XTextRange> xRun(xRunEnum->nextElement(), uno::UNO_QUERY); + uno::Reference< beans::XPropertySet > xRunProperties( xRun, uno::UNO_QUERY_THROW ); + if ( xRunProperties->getPropertyValue("TextPortionType") == uno::Any(OUString("TextField")) ) + { + uno::Reference<text::XTextField> const xField(xRunProperties->getPropertyValue("TextField").get<uno::Reference<text::XTextField>>()); + uno::Reference< beans::XPropertySet > xFieldProperties( xField, uno::UNO_QUERY_THROW ); + // cell can contain multiple text fields, but only one is handled now (~formula cell) + if ( rProp.Value != xFieldProperties->getPropertyValue("Content") ) + continue; + xFieldProperties->setPropertyValue("Content", uno::Any(sFormula)); + // update grab bag + auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aCellGrabBag); + beans::PropertyValue aValue; + aValue.Name = "CellFormulaConverted"; + aValue.Value <<= sFormula; + aGrabBag.push_back(aValue); + xCellProperties->setPropertyValue("CellInteropGrabBag", uno::Any(comphelper::containerToSequence(aGrabBag))); + } + } + } + } + } + } + catch ( const lang::IndexOutOfBoundsException & ) + { + // jump to next table row + break; + } + } + } +} + +void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTableStartsAtCellStart) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablehandler.endTable"); +#endif + + // If we want to make this table a floating one. + std::vector<beans::PropertyValue> aFrameProperties = comphelper::sequenceToContainer<std::vector<beans::PropertyValue> > + (m_rDMapper_Impl.getTableManager().getCurrentTablePosition()); + TableInfo aTableInfo; + aTableInfo.nNestLevel = nestedTableLevel; + + // non-floating tables need floating in footnotes and endnotes, because + // Writer core cannot handle (i.e. save in ODT, copy, edit etc.) them otherwise + bool bConvertToFloating = aFrameProperties.empty() && + nestedTableLevel <= 1 && + m_rDMapper_Impl.IsInFootOrEndnote(); + bool bFloating = !aFrameProperties.empty() || bConvertToFloating; + + aTableInfo.pTableStyle = endTableGetTableStyle(aTableInfo, aFrameProperties, bConvertToFloating); + // expands to uno::Sequence< Sequence< beans::PropertyValues > > + + std::vector<HorizontallyMergedCell> aMerges; + CellPropertyValuesSeq_t aCellProperties = endTableGetCellProperties(aTableInfo, aMerges); + + css::uno::Sequence<css::beans::PropertyValues> aRowProperties = endTableGetRowProperties(); + +#ifdef DBG_UTIL + lcl_DumpPropertyValueSeq(aRowProperties); +#endif + + if (!m_aTableRanges.empty()) + { + uno::Reference<text::XTextRange> xStart; + uno::Reference<text::XTextRange> xEnd; + + // fill empty frame properties to create an invisible frame around the table: + // hide frame borders and zero inner and outer frame margins + if (bConvertToFloating) + DomainMapper_Impl::fillEmptyFrameProperties(aFrameProperties, true); + + // OOXML table style may contain paragraph properties, apply these on cell paragraphs + if ( m_aTableRanges[0].hasElements() && m_aTableRanges[0][0].hasElements() ) + { + // collect all paragraph properties used in table styles + PropertyMapPtr pAllTableProps( new PropertyMap ); + pAllTableProps->InsertProps(aTableInfo.pTableDefaults); + if ( aTableInfo.pTableStyle ) + pAllTableProps->InsertProps(aTableInfo.pTableStyle->GetProperties( CNF_ALL )); + for (const auto& eId : pAllTableProps->GetPropertyIds()) + { + if ( !isParagraphProperty(eId) && !isCharacterProperty(eId) ) + pAllTableProps->Erase(eId); + } + std::vector< PropertyIds > aAllTableParaProperties = pAllTableProps->GetPropertyIds(); + + if ( !aAllTableParaProperties.empty() ) + { + TableParagraphVectorPtr pTableParagraphs = m_rDMapper_Impl.getTableManager().getCurrentParagraphs(); + for (size_t nRow = 0; nRow < m_aTableRanges.size(); ++nRow) + { + // Note that this is "cell" since you must not treat it as "column". + for (size_t nCell = 0; nCell < m_aTableRanges[nRow].size(); ++nCell) + { + auto rStartPara = m_aTableRanges[nRow][nCell][0]; + if (!rStartPara.is()) + continue; + auto rEndPara = m_aTableRanges[nRow][nCell][1]; + uno::Reference<text::XTextRangeCompare> xTextRangeCompare(rStartPara->getText(), uno::UNO_QUERY); + bool bApply = false; + // search paragraphs of the cell + std::vector<TableParagraph>::iterator aIt = pTableParagraphs->begin(); + while ( aIt != pTableParagraphs->end() ) try + { + if (!bApply && xTextRangeCompare->compareRegionStarts(rStartPara, aIt->m_rStartParagraph) == 0) + bApply = true; + if (bApply) + { + bool bEndOfApply = (xTextRangeCompare->compareRegionEnds(rEndPara, aIt->m_rEndParagraph) == 0); + // tdf#153891 handle missing cell properties (exception in style handling?) + if ( nCell < sal::static_int_cast<std::size_t>(aCellProperties[nRow].getLength()) ) + ApplyParagraphPropertiesFromTableStyle(*aIt, aAllTableParaProperties, aCellProperties[nRow][nCell]); + // erase processed paragraph from list of pending paragraphs + aIt = pTableParagraphs->erase(aIt); + if (bEndOfApply) + break; + } + else + ++aIt; + } + catch( const lang::IllegalArgumentException & ) + { + // skip compareRegion with nested tables + ++aIt; + } + } + } + } + } + + // Additional checks: if we can do this. + if (bFloating && m_aTableRanges[0].hasElements() && m_aTableRanges[0][0].hasElements()) + { + xStart = m_aTableRanges[0][0][0]; + uno::Sequence< uno::Sequence< uno::Reference<text::XTextRange> > >& rLastRow = m_aTableRanges[m_aTableRanges.size() - 1]; + if (rLastRow.hasElements()) + { + const uno::Sequence< uno::Reference<text::XTextRange> >& rLastCell = rLastRow[rLastRow.getLength() - 1]; + xEnd = rLastCell[1]; + } + } + uno::Reference<text::XTextTable> xTable; + try + { + if (m_xText.is()) + { + xTable = m_xText->convertToTable(comphelper::containerToSequence(m_aTableRanges), aCellProperties, aRowProperties, aTableInfo.aTableProperties); + + if (xTable.is()) + { + if (!aMerges.empty()) + { + static const std::vector<std::u16string_view> aBorderNames + = { u"TopBorder", u"LeftBorder", u"BottomBorder", u"RightBorder" }; + + // Perform horizontal merges in reverse order, so the fact that merging changes the position of cells won't cause a problem for us. + for (std::vector<HorizontallyMergedCell>::reverse_iterator it = aMerges.rbegin(); it != aMerges.rend(); ++it) + { + uno::Reference<table::XCellRange> xCellRange(xTable, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xFirstCell( + xCellRange->getCellByPosition(it->m_nFirstCol, it->m_nFirstRow), + uno::UNO_QUERY_THROW); + OUString aFirst + = xFirstCell->getPropertyValue("CellName").get<OUString>(); + // tdf#105852: Only try to merge if m_nLastCol is set (i.e. there were some merge continuation cells) + if (it->m_nLastCol != -1) + { + // Save border properties of the first cell + // before merge. + table::BorderLine2 aBorderValues[4]; + for (size_t i = 0; i < aBorderNames.size(); ++i) + xFirstCell->getPropertyValue(OUString(aBorderNames[i])) + >>= aBorderValues[i]; + + uno::Reference<beans::XPropertySet> xLastCell( + xCellRange->getCellByPosition(it->m_nLastCol, it->m_nLastRow), + uno::UNO_QUERY_THROW); + OUString aLast + = xLastCell->getPropertyValue("CellName").get<OUString>(); + + uno::Reference<text::XTextTableCursor> xCursor = xTable->createCursorByCellName(aFirst); + xCursor->gotoCellByName(aLast, true); + + xCursor->mergeRange(); + + // Handle conflicting properties: mergeRange() + // takes the last cell, Word takes the first + // cell. + for (size_t i = 0; i < aBorderNames.size(); ++i) + { + if (aBorderValues[i].LineStyle != table::BorderLineStyle::NONE) + xFirstCell->setPropertyValue( + OUString(aBorderNames[i]), uno::Any(aBorderValues[i])); + } + } + } + } + + // convert special range IDs ABOVE, BELOW, LEFT and RIGHT + lcl_convertFormulaRanges(xTable); + } + } + } + catch ( const lang::IllegalArgumentException & ) + { + TOOLS_INFO_EXCEPTION("writerfilter.dmapper", "Conversion to table error"); +#ifdef DBG_UTIL + TagLogger::getInstance().chars(std::string("failed to import table!")); +#endif + } + catch ( const uno::Exception & ) + { + TOOLS_INFO_EXCEPTION("writerfilter.dmapper", "Exception during table creation"); + } + + // If we have a table with a start and an end position, we should make it a floating one. + // Unless the table had a foot or endnote, as Writer doesn't support those in TextFrames. + if (xTable.is() && xStart.is() && xEnd.is() && !m_bHadFootOrEndnote) + { + uno::Reference<beans::XPropertySet> xTableProperties(xTable, uno::UNO_QUERY); + bool bIsRelative = false; + xTableProperties->getPropertyValue("IsWidthRelative") >>= bIsRelative; + if (!bIsRelative) + { + beans::PropertyValue aValue; + aValue.Name = "Width"; + aValue.Value = xTableProperties->getPropertyValue("Width"); + aFrameProperties.push_back(aValue); + } + else + { + beans::PropertyValue aValue; + aValue.Name = "FrameWidthPercent"; + aValue.Value = xTableProperties->getPropertyValue("RelativeWidth"); + aFrameProperties.push_back(aValue); + + // Applying the relative width to the frame, needs to have the table width to be 100% of the frame width + xTableProperties->setPropertyValue("RelativeWidth", uno::Any(sal_Int16(100))); + } + + // A non-zero left margin would move the table out of the frame, move the frame itself instead. + xTableProperties->setPropertyValue("LeftMargin", uno::Any(sal_Int32(0))); + + if (nestedTableLevel >= 2) + { + // Floating tables inside a table always stay inside the cell. + aFrameProperties.push_back( + comphelper::makePropertyValue("IsFollowingTextFlow", true)); + } + + // In case the document ends with a table, we're called after + // SectionPropertyMap::CloseSectionGroup(), so we'll have no idea + // about the text area width, nor can fix this by delaying the text + // frame conversion: just do it here. + // Also, when the anchor is within a table, then do it here as well, + // as xStart/xEnd would not point to the start/end at conversion + // time anyway. + // Next exception: it's pointless to delay the conversion if the + // table is not in the body text. + sal_Int32 nTableWidth = 0; + m_aTableProperties->getValue(TablePropertyMap::TABLE_WIDTH, nTableWidth); + sal_Int32 nTableWidthType = text::SizeType::FIX; + m_aTableProperties->getValue(TablePropertyMap::TABLE_WIDTH_TYPE, nTableWidthType); + if (m_rDMapper_Impl.GetSectionContext() && nestedTableLevel <= 1 && !m_rDMapper_Impl.IsInHeaderFooter()) + { + m_rDMapper_Impl.m_aPendingFloatingTables.emplace_back(xStart, xEnd, + comphelper::containerToSequence(aFrameProperties), + nTableWidth, nTableWidthType, bConvertToFloating); + } + else + { + // m_xText points to the body text, get the current xText from m_rDMapper_Impl, in case e.g. we would be in a header. + uno::Reference<text::XTextAppendAndConvert> xTextAppendAndConvert(m_rDMapper_Impl.GetTopTextAppend(), uno::UNO_QUERY); + // Only execute the conversion if the table is not anchored at + // the start of an outer table cell, that's not yet + // implemented. + if (xTextAppendAndConvert.is() && !bTableStartsAtCellStart) + xTextAppendAndConvert->convertToTextFrame(xStart, xEnd, comphelper::containerToSequence(aFrameProperties)); + } + } + + // We're right after a table conversion. + m_rDMapper_Impl.m_bConvertedTable = true; + } + + m_aTableProperties.clear(); + m_aCellProperties.clear(); + m_aRowProperties.clear(); + m_bHadFootOrEndnote = false; + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); + TagLogger::getInstance().endElement(); +#endif +} + +void DomainMapperTableHandler::startRow(const TablePropertyMapPtr& pProps) +{ + m_aRowProperties.push_back( pProps.get() ); + m_aCellProperties.emplace_back( ); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("table.row"); + if (pProps != nullptr) + pProps->dumpXml(); +#endif + + m_aRowRanges.clear(); +} + +void DomainMapperTableHandler::endRow() +{ + m_aTableRanges.push_back(comphelper::containerToSequence(m_aRowRanges)); +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void DomainMapperTableHandler::startCell(const css::uno::Reference< css::text::XTextRange > & start, + const TablePropertyMapPtr& pProps ) +{ + sal_uInt32 nRow = m_aRowProperties.size(); + if ( pProps ) + m_aCellProperties[nRow - 1].push_back( pProps.get() ); + else + { + // Adding an empty cell properties map to be able to get + // the table defaults properties + TablePropertyMapPtr pEmptyProps( new TablePropertyMap( ) ); + m_aCellProperties[nRow - 1].push_back( pEmptyProps.get() ); + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("table.cell"); + TagLogger::getInstance().startElement("table.cell.start"); + TagLogger::getInstance().chars(XTextRangeToString(start)); + TagLogger::getInstance().endElement(); + if (pProps) + pProps->printProperties(); +#endif + + //add a new 'row' of properties + m_aCellRange.clear(); + uno::Reference<text::XTextRange> xStart; + if (start) + xStart = start->getStart(); + m_aCellRange.push_back(xStart); +} + +void DomainMapperTableHandler::endCell(const css::uno::Reference< css::text::XTextRange > & end) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("table.cell.end"); + TagLogger::getInstance().chars(XTextRangeToString(end)); + TagLogger::getInstance().endElement(); + TagLogger::getInstance().endElement(); +#endif + + uno::Reference<text::XTextRange> xEnd; + if (end) + xEnd = end->getEnd(); + m_aCellRange.push_back(xEnd); + m_aRowRanges.push_back(comphelper::containerToSequence(m_aCellRange)); +} + +void DomainMapperTableHandler::setHadFootOrEndnote(bool bHadFootOrEndnote) +{ + m_bHadFootOrEndnote = bHadFootOrEndnote; +} + +DomainMapper_Impl& DomainMapperTableHandler::getDomainMapperImpl() +{ + return m_rDMapper_Impl; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.hxx b/writerfilter/source/dmapper/DomainMapperTableHandler.hxx new file mode 100644 index 000000000..4e396b6b2 --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapperTableHandler.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ +#pragma once + +#include "PropertyMap.hxx" +#include <vector> + +#include <com/sun/star/text/XTextAppendAndConvert.hpp> + +namespace writerfilter::dmapper { + +typedef css::uno::Sequence< css::uno::Reference< css::text::XTextRange > > CellSequence_t; +typedef css::uno::Sequence<CellSequence_t> RowSequence_t; + +typedef css::uno::Sequence< css::uno::Sequence<css::beans::PropertyValues> > CellPropertyValuesSeq_t; + +typedef std::vector<PropertyMapPtr> PropertyMapVector1; +typedef std::vector<PropertyMapVector1> PropertyMapVector2; + +class DomainMapper_Impl; +class TableStyleSheetEntry; +struct TableInfo; + +/// A horizontally merged cell is in fact a range of cells till its merge is performed. +struct HorizontallyMergedCell +{ + sal_Int32 m_nFirstRow; + sal_Int32 m_nFirstCol; + sal_Int32 m_nLastRow; + sal_Int32 m_nLastCol; + HorizontallyMergedCell(sal_Int32 nFirstRow, sal_Int32 nFirstCol) + : m_nFirstRow(nFirstRow) + , m_nFirstCol(nFirstCol) + , m_nLastRow(nFirstRow) + , m_nLastCol(-1) + { + } +}; + +/// Class to handle events generated by TableManager::resolveCurrentTable(). +class DomainMapperTableHandler final : public virtual SvRefBase +{ + css::uno::Reference<css::text::XTextAppendAndConvert> m_xText; + DomainMapper_Impl& m_rDMapper_Impl; + std::vector< css::uno::Reference<css::text::XTextRange> > m_aCellRange; + std::vector<CellSequence_t> m_aRowRanges; + std::vector<RowSequence_t> m_aTableRanges; + + // properties + PropertyMapVector2 m_aCellProperties; + PropertyMapVector1 m_aRowProperties; + TablePropertyMapPtr m_aTableProperties; + + /// Did we have a foot or endnote in this table? + bool m_bHadFootOrEndnote; + + TableStyleSheetEntry * endTableGetTableStyle(TableInfo & rInfo, + std::vector<css::beans::PropertyValue>& rFrameProperties, + bool bConvertToFloating); + CellPropertyValuesSeq_t endTableGetCellProperties(TableInfo & rInfo, std::vector<HorizontallyMergedCell>& rMerges); + css::uno::Sequence<css::beans::PropertyValues> endTableGetRowProperties(); + +public: + typedef tools::SvRef<DomainMapperTableHandler> Pointer_t; + + DomainMapperTableHandler(css::uno::Reference<css::text::XTextAppendAndConvert> const& xText, + DomainMapper_Impl& rDMapper_Impl); + ~DomainMapperTableHandler() override; + + /** + Handle start of table. + + @param pProps properties of the table + */ + void startTable(const TablePropertyMapPtr& pProps); + + void ApplyParagraphPropertiesFromTableStyle(TableParagraph rParaProp, std::vector< PropertyIds > aAllTableProperties, const css::beans::PropertyValues rCellProperties); + + /// Handle end of table. + void endTable(unsigned int nestedTableLevel, bool bTableStartsAtCellStart); + /** + Handle start of row. + + @param pProps properties of the row + */ + void startRow(const TablePropertyMapPtr& pProps); + /// Handle end of row. + void endRow(); + /** + Handle start of cell. + + @param rT start handle of the cell + @param pProps properties of the cell + */ + void startCell(const css::uno::Reference< css::text::XTextRange > & start, const TablePropertyMapPtr& pProps); + /** + Handle end of cell. + + @param rT end handle of cell + */ + void endCell(const css::uno::Reference< css::text::XTextRange > & end); + + void setHadFootOrEndnote(bool bHadFootOrEndnote); + + DomainMapper_Impl& getDomainMapperImpl(); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapperTableManager.cxx b/writerfilter/source/dmapper/DomainMapperTableManager.cxx new file mode 100644 index 000000000..004f34971 --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapperTableManager.cxx @@ -0,0 +1,859 @@ +/* -*- 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 <optional> +#include "DomainMapperTableManager.hxx" +#include "ConversionHelper.hxx" +#include "MeasureHandler.hxx" +#include "TagLogger.hxx" +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TableColumnSeparator.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <o3tl/numeric.hxx> +#include <o3tl/safeint.hxx> +#include <ooxml/resourceids.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <numeric> +#include "TrackChangesHandler.hxx" +#include <oox/token/tokens.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; +using namespace ::std; + +DomainMapperTableManager::DomainMapperTableManager() : + m_nRow(0), + m_nGridSpan(1), + m_nHeaderRepeat(0), + m_nTableWidth(0), + m_bIsInShape(false), + m_bPushCurrentWidth(false), + m_bTableSizeTypeInserted(false), + m_nLayoutType(0), + m_pTablePropsHandler(new TablePropertiesHandler()) +{ + m_pTablePropsHandler->SetTableManager( this ); +} + + +DomainMapperTableManager::~DomainMapperTableManager() +{ +} + +bool DomainMapperTableManager::attribute(Id nName, Value const & rValue) +{ + bool bRet = true; + + switch (nName) + { + case NS_ooxml::LN_CT_TblLook_val: + { + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_TBL_LOOK, uno::Any(sal_Int32(rValue.getInt()))); + insertTableProps(pPropMap); + m_aTableLook["val"] <<= static_cast<sal_Int32>(rValue.getInt()); + } + break; + case NS_ooxml::LN_CT_TblLook_noVBand: + m_aTableLook["noVBand"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + case NS_ooxml::LN_CT_TblLook_noHBand: + m_aTableLook["noHBand"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + case NS_ooxml::LN_CT_TblLook_lastColumn: + m_aTableLook["lastColumn"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + case NS_ooxml::LN_CT_TblLook_lastRow: + m_aTableLook["lastRow"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + case NS_ooxml::LN_CT_TblLook_firstColumn: + m_aTableLook["firstColumn"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + case NS_ooxml::LN_CT_TblLook_firstRow: + m_aTableLook["firstRow"] <<= static_cast<sal_Int32>(rValue.getInt()); + break; + default: + bRet = false; + } + + return bRet; +} + +void DomainMapperTableManager::finishTableLook() +{ + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(META_PROP_TABLE_LOOK, uno::Any(m_aTableLook.getAsConstPropertyValueList())); + m_aTableLook.clear(); + insertTableProps(pPropMap); +} + +bool DomainMapperTableManager::sprm(Sprm & rSprm) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.sprm"); + string sSprm = rSprm.toString(); + TagLogger::getInstance().chars(sSprm); + TagLogger::getInstance().endElement(); +#endif + bool bRet = TableManager::sprm(rSprm); + if( !bRet ) + { + bRet = m_pTablePropsHandler->sprm( rSprm ); + } + + if ( !bRet ) + { + bRet = true; + sal_uInt32 nSprmId = rSprm.getId(); + Value::Pointer_t pValue = rSprm.getValue(); + sal_Int32 nIntValue = (pValue ? pValue->getInt() : 0); + switch ( nSprmId ) + { + case NS_ooxml::LN_CT_TblPrBase_tblW: + case NS_ooxml::LN_CT_TblPrBase_tblInd: + { + //contains unit and value + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + MeasureHandlerPtr pMeasureHandler( new MeasureHandler ); + pProperties->resolve(*pMeasureHandler); + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + if (nSprmId == sal_uInt32(NS_ooxml::LN_CT_TblPrBase_tblInd)) + { + pPropMap->setValue( TablePropertyMap::LEFT_MARGIN, pMeasureHandler->getMeasureValue() ); + } + else + { + m_nTableWidth = pMeasureHandler->getMeasureValue(); + if( m_nTableWidth ) + { + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH_TYPE, text::SizeType::FIX ); + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH, m_nTableWidth ); + m_bTableSizeTypeInserted = true; + } + else if( sal::static_int_cast<Id>(pMeasureHandler->getUnit()) == NS_ooxml::LN_Value_ST_TblWidth_pct ) + { + sal_Int32 nPercent = pMeasureHandler->getValue() / 50; + if(nPercent > 100) + nPercent = 100; + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH_TYPE, text::SizeType::VARIABLE ); + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH, nPercent ); + m_bTableSizeTypeInserted = true; + } + else if( sal::static_int_cast<Id>(pMeasureHandler->getUnit()) == NS_ooxml::LN_Value_ST_TblWidth_auto ) + { + /* + This attribute specifies the width type of table. This is used as part of the table layout + algorithm specified by the tblLayout element.(See 17.4.64 and 17.4.65 of the ISO/IEC 29500-1:2011.) + If this value is 'auto', the table layout has to use the preferred widths on the table items to generate + the final sizing of the table, but then must use the contents of each cell to determine final column widths. + (See 17.18.87 of the ISO/IEC 29500-1:2011.) + */ + IntVectorPtr pCellWidths = getCurrentCellWidths(); + // Check whether all cells have fixed widths in the given row of table. + bool bFixed = std::find(pCellWidths->begin(), pCellWidths->end(), -1) == pCellWidths->end(); + if (!bFixed) + { + // Set the width type of table with 'Auto' and set the width value to 0 (as per grid values) + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH_TYPE, text::SizeType::VARIABLE ); + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH, 0 ); + m_bTableSizeTypeInserted = true; + } + else if (getTableDepth() > 1) + { + // tdf#131819 limiting the fix for nested tables temporarily + // TODO revert the fix for tdf#104876 and reopen it + m_bTableSizeTypeInserted = true; + } + } + } +#ifdef DBG_UTIL + pPropMap->dumpXml(); +#endif + insertTableProps(pPropMap); + } + } + break; + case NS_ooxml::LN_CT_TrPrBase_tblHeader: + // if nIntValue == 1 then the row is a repeated header line + // to prevent later rows from increasing the repeating m_nHeaderRepeat is set to NULL when repeating stops + if( nIntValue > 0 && m_nHeaderRepeat == static_cast<int>(m_nRow) ) + { + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + + // FIXME: DOCX tables with more than 10 repeating header lines imported + // without repeating header lines to mimic an MSO workaround for its usability bug. + // Explanation: it's very hard to set and modify repeating header rows in Word, + // often resulting tables with a special workaround: setting all table rows as + // repeating header, because exceeding the pages by "unlimited" header rows turns off the + // table headers automatically in MSO. 10-row limit is a reasonable temporary limit + // to handle DOCX tables with "unlimited" repeating header, till the same "turn off + // exceeding header" feature is ready (see tdf#88496). +#define HEADER_ROW_LIMIT_FOR_MSO_WORKAROUND 10 + if ( m_nHeaderRepeat == HEADER_ROW_LIMIT_FOR_MSO_WORKAROUND ) + { + m_nHeaderRepeat = -1; + pPropMap->Insert( PROP_HEADER_ROW_COUNT, uno::Any(sal_Int32(0))); + } + else + { + ++m_nHeaderRepeat; + pPropMap->Insert( PROP_HEADER_ROW_COUNT, uno::Any( m_nHeaderRepeat )); + } + insertTableProps(pPropMap); + } + else + { + if ( nIntValue == 0 && m_nRow == 0 ) + { + // explicit tblHeader=0 in the first row must overwrite table style + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( PROP_HEADER_ROW_COUNT, uno::Any(sal_Int32(0))); + insertTableProps(pPropMap); + } + m_nHeaderRepeat = -1; + } + if (nIntValue) + { + // Store the info that this is a header, we'll need that when we apply table styles. + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( PROP_TBL_HEADER, uno::Any(nIntValue)); + insertRowProps(pPropMap); + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblStyle: //table style name + { + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( META_PROP_TABLE_STYLE_NAME, uno::Any( pValue->getString() )); + insertTableProps(pPropMap); + } + break; + case NS_ooxml::LN_CT_TblGridBase_gridCol: + { + if (nIntValue == -1) + getCurrentGrid()->clear(); + else + getCurrentGrid()->push_back( nIntValue ); + } + break; + case NS_ooxml::LN_CT_TcPrBase_vMerge : //vertical merge + { + // values can be: LN_Value_ST_Merge_restart, LN_Value_ST_Merge_continue, in reality the second one is a 0 + TablePropertyMapPtr pMergeProps( new TablePropertyMap ); + pMergeProps->Insert( PROP_VERTICAL_MERGE, uno::Any( sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_Merge_restart ) ); + cellProps( pMergeProps); + } + break; + case NS_ooxml::LN_CT_TcPrBase_hMerge: + { + // values can be: LN_Value_ST_Merge_restart, LN_Value_ST_Merge_continue, in reality the second one is a 0 + TablePropertyMapPtr pMergeProps(new TablePropertyMap()); + pMergeProps->Insert(PROP_HORIZONTAL_MERGE, uno::Any( sal::static_int_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_Merge_restart )); + cellProps(pMergeProps); + } + break; + case NS_ooxml::LN_CT_TcPrBase_gridSpan: //number of grid positions spanned by this cell + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.GridSpan"); + TagLogger::getInstance().attribute("gridSpan", nIntValue); + TagLogger::getInstance().endElement(); +#endif + m_nGridSpan = nIntValue; + } + break; + case NS_ooxml::LN_CT_TcPrBase_textDirection: + { + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + bool bInsertCellProps = true; + switch ( nIntValue ) + { + case NS_ooxml::LN_Value_ST_TextDirection_tbRl: + // Binary filter takes BiDirection into account ( but I have no idea about that here ) + // or even what it is. But... here's where to handle it if it becomes an issue + pPropMap->Insert( PROP_FRM_DIRECTION, uno::Any( text::WritingMode2::TB_RL )); + SAL_INFO( "writerfilter", "Have inserted textDirection " << nIntValue ); + break; + case NS_ooxml::LN_Value_ST_TextDirection_btLr: + pPropMap->Insert( PROP_FRM_DIRECTION, uno::Any( text::WritingMode2::BT_LR )); + break; + case NS_ooxml::LN_Value_ST_TextDirection_lrTbV: + pPropMap->Insert( PROP_FRM_DIRECTION, uno::Any( text::WritingMode2::LR_TB )); + break; + case NS_ooxml::LN_Value_ST_TextDirection_tbRlV: + pPropMap->Insert( PROP_FRM_DIRECTION, uno::Any( text::WritingMode2::TB_RL )); + break; + case NS_ooxml::LN_Value_ST_TextDirection_lrTb: + case NS_ooxml::LN_Value_ST_TextDirection_tbLrV: + default: + // Ignore - we can't handle these + bInsertCellProps = false; + break; + } + if ( bInsertCellProps ) + cellProps( pPropMap ); + break; + } + case NS_ooxml::LN_CT_TcPrBase_tcW: + { + // Contains unit and value, but unit is not interesting for + // us, later we'll just distribute these values in a + // 0..10000 scale. + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + MeasureHandlerPtr pMeasureHandler(new MeasureHandler()); + pProperties->resolve(*pMeasureHandler); + if (sal::static_int_cast<Id>(pMeasureHandler->getUnit()) == NS_ooxml::LN_Value_ST_TblWidth_auto) + getCurrentCellWidths()->push_back(sal_Int32(-1)); + else + // store the original value to limit rounding mistakes, if it's there in a recognized measure (twip) + getCurrentCellWidths()->push_back(pMeasureHandler->getMeasureValue() ? pMeasureHandler->getValue() : sal_Int32(0)); + if (getTableDepthDifference()) + m_bPushCurrentWidth = true; + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblpPr: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + // Ignore <w:tblpPr> in shape text, those tables should be always non-floating ones. + if (!m_bIsInShape && pProperties) + { + TablePositionHandlerPtr pHandler = m_aTmpPosition.back(); + if ( !pHandler ) + { + m_aTmpPosition.pop_back(); + pHandler = new TablePositionHandler; + m_aTmpPosition.push_back( pHandler ); + } + pProperties->resolve(*m_aTmpPosition.back()); + } + } + break; + case NS_ooxml::LN_CT_TrPrBase_gridBefore: + setCurrentGridBefore( nIntValue ); + break; + case NS_ooxml::LN_CT_TrPrBase_gridAfter: + setCurrentGridAfter( nIntValue ); + break; + case NS_ooxml::LN_CT_TblPrBase_tblCaption: + // To-Do: Not yet preserved + break; + case NS_ooxml::LN_CT_TblPrBase_tblDescription: + // To-Do: Not yet preserved + break; + case NS_ooxml::LN_CT_TrPrBase_tblCellSpacing: + // To-Do: Not yet preserved + break; + case NS_ooxml::LN_CT_TblPrBase_tblCellSpacing: + // To-Do: Not yet preserved + break; + case NS_ooxml::LN_CT_TblPrBase_bidiVisual: + { + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_WRITING_MODE, uno::Any(sal_Int16(nIntValue ? text::WritingMode2::RL_TB : text::WritingMode2::LR_TB))); + insertTableProps(pPropMap); + break; + } + default: + bRet = false; + +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } + return bRet; +} + +DomainMapperTableManager::IntVectorPtr const & DomainMapperTableManager::getCurrentGrid( ) +{ + if (m_aTableGrid.empty()) + throw std::out_of_range("no current grid"); + return m_aTableGrid.back( ); +} + +DomainMapperTableManager::IntVectorPtr const & DomainMapperTableManager::getCurrentCellWidths( ) +{ + return m_aCellWidths.back( ); +} + +uno::Sequence<beans::PropertyValue> DomainMapperTableManager::getCurrentTablePosition( ) +{ + if ( !m_aTablePositions.empty( ) && m_aTablePositions.back() ) + return m_aTablePositions.back( )->getTablePosition(); + else + return uno::Sequence< beans::PropertyValue >(); +} + +TablePositionHandler* DomainMapperTableManager::getCurrentTableRealPosition() +{ + if ( !m_aTablePositions.empty( ) && m_aTablePositions.back() ) + return m_aTablePositions.back().get(); + else + return nullptr; +} + +const TableParagraphVectorPtr & DomainMapperTableManager::getCurrentParagraphs( ) +{ + return m_aParagraphsToEndTable.top( ); +} + +void DomainMapperTableManager::setIsInShape(bool bIsInShape) +{ + m_bIsInShape = bIsInShape; +} + +void DomainMapperTableManager::startLevel( ) +{ + TableManager::startLevel( ); + + // If requested, pop the value that was pushed too early. + std::optional<sal_Int32> oCurrentWidth; + if (m_bPushCurrentWidth && !m_aCellWidths.empty() && !m_aCellWidths.back()->empty()) + { + oCurrentWidth = m_aCellWidths.back()->back(); + m_aCellWidths.back()->pop_back(); + } + std::optional<TableParagraph> oParagraph; + if (getTableDepthDifference() > 0 && !m_aParagraphsToEndTable.empty() && !m_aParagraphsToEndTable.top()->empty()) + { + oParagraph = m_aParagraphsToEndTable.top()->back(); + m_aParagraphsToEndTable.top()->pop_back(); + } + + IntVectorPtr pNewGrid = std::make_shared<vector<sal_Int32>>(); + IntVectorPtr pNewCellWidths = std::make_shared<vector<sal_Int32>>(); + TablePositionHandlerPtr pNewPositionHandler; + m_aTableGrid.push_back( pNewGrid ); + m_aCellWidths.push_back( pNewCellWidths ); + m_aTablePositions.push_back( pNewPositionHandler ); + // empty name will be replaced by the table style name, if it exists + m_aTableStyleNames.push_back( OUString() ); + m_aMoved.push_back( OUString() ); + + TablePositionHandlerPtr pTmpPosition; + TablePropertyMapPtr pTmpProperties( new TablePropertyMap( ) ); + m_aTmpPosition.push_back( pTmpPosition ); + m_aTmpTableProperties.push_back( pTmpProperties ); + m_nCell.push_back( 0 ); + m_nTableWidth = 0; + m_nLayoutType = 0; + TableParagraphVectorPtr pNewParagraphs = std::make_shared<vector<TableParagraph>>(); + m_aParagraphsToEndTable.push( pNewParagraphs ); + + // And push it back to the right level. + if (oCurrentWidth) + m_aCellWidths.back()->push_back(*oCurrentWidth); + if (oParagraph) + m_aParagraphsToEndTable.top()->push_back(*oParagraph); +} + +void DomainMapperTableManager::endLevel( ) +{ + if (m_aTableGrid.empty()) + { + SAL_WARN("writerfilter.dmapper", "Table stack is empty"); + return; + } + + m_aTableGrid.pop_back( ); + + // Do the same trick as in startLevel(): pop the value that was pushed too early. + std::optional<sal_Int32> oCurrentWidth; + if (m_bPushCurrentWidth && !m_aCellWidths.empty() && !m_aCellWidths.back()->empty()) + oCurrentWidth = m_aCellWidths.back()->back(); + m_aCellWidths.pop_back( ); + // And push it back to the right level. + if (oCurrentWidth && !m_aCellWidths.empty() && !m_aCellWidths.back()->empty()) + m_aCellWidths.back()->push_back(*oCurrentWidth); + + m_nCell.pop_back( ); + m_nTableWidth = 0; + m_nLayoutType = 0; + + m_aTmpPosition.pop_back( ); + m_aTmpTableProperties.pop_back( ); + + TableManager::endLevel( ); +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("dmappertablemanager.endLevel"); + PropertyMapPtr pProps = getTableProps().get(); + if (pProps) + getTableProps()->dumpXml(); + + TagLogger::getInstance().endElement(); +#endif + + // Pop back the table position after endLevel as it's used + // in the endTable method called in endLevel. + m_aTablePositions.pop_back(); + m_aTableStyleNames.pop_back(); + m_aMoved.pop_back( ); + + std::optional<TableParagraph> oParagraph; + if (getTableDepthDifference() < 0 && !m_aParagraphsToEndTable.top()->empty()) + oParagraph = m_aParagraphsToEndTable.top()->back(); + m_aParagraphsToEndTable.pop(); + if (oParagraph && m_aParagraphsToEndTable.size()) + m_aParagraphsToEndTable.top()->push_back(*oParagraph); +} + +void DomainMapperTableManager::endOfCellAction() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element("endOFCellAction"); +#endif + + if ( !isInTable() ) + throw std::out_of_range("cell without a table"); + if ( m_nGridSpan > 1 ) + setCurrentGridSpan( m_nGridSpan ); + m_nGridSpan = 1; + ++m_nCell.back( ); +} + +bool DomainMapperTableManager::shouldInsertRow(IntVectorPtr pCellWidths, IntVectorPtr pTableGrid, size_t nGrids, bool& rIsIncompleteGrid) +{ + if (pCellWidths->empty()) + return false; + if (m_nLayoutType == NS_ooxml::LN_Value_doc_ST_TblLayout_fixed) + return true; + if (pCellWidths->size() == nGrids) + return true; + rIsIncompleteGrid = true; + return nGrids > pTableGrid->size(); +} + +void DomainMapperTableManager::endOfRowAction() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("endOfRowAction"); +#endif + + // Compare the table position and style with the previous ones. We may need to split + // into two tables if those are different. We surely don't want to do anything + // if we don't have any row yet. + if (m_aTmpPosition.empty()) + throw std::out_of_range("row without a position"); + TablePositionHandlerPtr pTmpPosition = m_aTmpPosition.back(); + TablePropertyMapPtr pTablePropMap = m_aTmpTableProperties.back( ); + TablePositionHandlerPtr pCurrentPosition = m_aTablePositions.back(); + bool bSamePosition = ( pTmpPosition == pCurrentPosition ) || + ( pTmpPosition && pCurrentPosition && *pTmpPosition == *pCurrentPosition ); + bool bIsSetTableStyle = pTablePropMap->isSet(META_PROP_TABLE_STYLE_NAME); + OUString sTableStyleName; + bool bSameTableStyle = ( !bIsSetTableStyle && m_aTableStyleNames.back().isEmpty()) || + ( bIsSetTableStyle && + (pTablePropMap->getProperty(META_PROP_TABLE_STYLE_NAME)->second >>= sTableStyleName) && + sTableStyleName == m_aTableStyleNames.back() ); + if ( (!bSamePosition || !bSameTableStyle) && m_nRow > 0 ) + { + // Save the grid infos to have them survive the end/start level + IntVectorPtr pTmpTableGrid = m_aTableGrid.back(); + IntVectorPtr pTmpCellWidths = m_aCellWidths.back(); + sal_uInt32 nTmpCell = m_nCell.back(); + TableParagraphVectorPtr pTableParagraphs = getCurrentParagraphs(); + + // endLevel and startLevel are taking care of the non finished row + // to carry it over to the next table + setKeepUnfinishedRow( true ); + endLevel(); + setKeepUnfinishedRow( false ); + startLevel(); + + m_aTableGrid.pop_back(); + m_aCellWidths.pop_back(); + m_nCell.pop_back(); + m_aTableGrid.push_back(pTmpTableGrid); + m_aCellWidths.push_back(pTmpCellWidths); + m_nCell.push_back(nTmpCell); + m_aParagraphsToEndTable.pop( ); + m_aParagraphsToEndTable.push( pTableParagraphs ); + } + // save table style in the first row for comparison + if ( m_nRow == 0 && pTablePropMap->isSet(META_PROP_TABLE_STYLE_NAME) ) + { + pTablePropMap->getProperty(META_PROP_TABLE_STYLE_NAME)->second >>= sTableStyleName; + m_aTableStyleNames.pop_back(); + m_aTableStyleNames.push_back( sTableStyleName ); + } + + // Push the tmp position now that we compared it + m_aTablePositions.pop_back(); + m_aTablePositions.push_back( pTmpPosition ); + m_aTmpPosition.back().clear( ); + + + IntVectorPtr pTableGrid = getCurrentGrid( ); + IntVectorPtr pCellWidths = getCurrentCellWidths( ); + if(!m_nTableWidth && !pTableGrid->empty()) + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tableWidth"); +#endif + + for( const auto& rCell : *pTableGrid ) + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("col"); + TagLogger::getInstance().attribute("width", rCell); + TagLogger::getInstance().endElement(); +#endif + + m_nTableWidth = o3tl::saturating_add(m_nTableWidth, rCell); + } + if (m_nTableWidth) + // convert sum of grid twip values to 1/100 mm with rounding up to avoid table width loss + m_nTableWidth = static_cast<sal_Int32>(ceil(ConversionHelper::convertTwipToMM100Double(m_nTableWidth))); + + if (m_nTableWidth > 0 && !m_bTableSizeTypeInserted) + { + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->setValue( TablePropertyMap::TABLE_WIDTH, m_nTableWidth ); + insertTableProps(pPropMap); + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + } + + std::vector<sal_uInt32> rCurrentSpans = getCurrentGridSpans(); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("gridSpans"); + { + for (const auto& rGridSpan : rCurrentSpans) + { + TagLogger::getInstance().startElement("gridSpan"); + TagLogger::getInstance().attribute("span", rGridSpan); + TagLogger::getInstance().endElement(); + } + } + TagLogger::getInstance().endElement(); +#endif + + //calculate number of used grids - it has to match the size of m_aTableGrid + size_t nGrids = std::accumulate(rCurrentSpans.begin(), rCurrentSpans.end(), sal::static_int_cast<size_t>(0)); + + // sj: the grid is having no units... it is containing only relative values. + // a table with a grid of "1:2:1" looks identical as if the table is having + // a grid of "20:40:20" and it doesn't have to do something with the tableWidth + // -> so we have get the sum of each grid entry for the fullWidthRelative: + int nFullWidthRelative = 0; + for (int i : (*pTableGrid)) + nFullWidthRelative = o3tl::saturating_add(nFullWidthRelative, i); + + bool bIsIncompleteGrid = false; + if( pTableGrid->size() == nGrids && m_nCell.back( ) > 0 ) + { + /* + * If table width property set earlier is smaller than the current table width, + * then replace the TABLE_WIDTH property, set earlier. + */ + sal_Int32 nTableWidth(0); + sal_Int32 nTableWidthType(text::SizeType::VARIABLE); + pTablePropMap->getValue(TablePropertyMap::TABLE_WIDTH, nTableWidth); + pTablePropMap->getValue(TablePropertyMap::TABLE_WIDTH_TYPE, nTableWidthType); + if ((nTableWidthType == text::SizeType::FIX) && (nTableWidth < m_nTableWidth)) + { + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH, m_nTableWidth); + } + if (nTableWidthType == text::SizeType::VARIABLE ) + { + if(nTableWidth > 100 || nTableWidth <= 0) + { + if(getTableDepth() > 1 && !m_bTableSizeTypeInserted) + { + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH, sal_Int32(100)); + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH_TYPE, text::SizeType::VARIABLE); + } + else + { + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH, m_nTableWidth); + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH_TYPE, text::SizeType::FIX); + } + } + } + uno::Sequence< text::TableColumnSeparator > aSeparators( getCurrentGridBefore() + m_nCell.back() - 1 ); + text::TableColumnSeparator* pSeparators = aSeparators.getArray(); + double nLastRelPos = 0.0; + sal_uInt32 nBorderGridIndex = 0; + + size_t nWidthsBound = getCurrentGridBefore() + m_nCell.back() - 1; + if (nWidthsBound) + { + ::std::vector< sal_uInt32 >::const_iterator aSpansIter = rCurrentSpans.begin(); + for( size_t nBorder = 0; nBorder < nWidthsBound; ++nBorder ) + { + double nRelPos, fGridWidth = 0.; + for ( sal_Int32 nGridCount = *aSpansIter; nGridCount > 0; --nGridCount ) + fGridWidth += (*pTableGrid)[nBorderGridIndex++]; + + if (fGridWidth == 0.) + { + // allow nFullWidthRelative here, with a sane 0.0 result + nRelPos = 0.; + } + else + { + if (nFullWidthRelative == 0) + throw o3tl::divide_by_zero(); + + nRelPos = (fGridWidth * 10000) / nFullWidthRelative; + } + + pSeparators[nBorder].Position = rtl::math::round(nRelPos + nLastRelPos); + pSeparators[nBorder].IsVisible = true; + nLastRelPos = nLastRelPos + nRelPos; + ++aSpansIter; + } + } + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( PROP_TABLE_COLUMN_SEPARATORS, uno::Any( aSeparators ) ); + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("rowProperties"); + pPropMap->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + + // set row insertion/deletion at tracked drag & drop of tables + OUString aMoved = getMoved(); + if ( !aMoved.isEmpty() ) + { + auto pTrackChangesHandler = std::make_shared<TrackChangesHandler>( + aMoved == getPropertyName( PROP_TABLE_ROW_DELETE ) + ? oox::XML_tableRowDelete + : oox::XML_tableRowInsert ); + uno::Sequence<beans::PropertyValue> aTableRedlineProperties = pTrackChangesHandler->getRedlineProperties(); + pPropMap->Insert( PROP_TABLE_REDLINE_PARAMS , uno::Any( aTableRedlineProperties )); + } + + insertRowProps(pPropMap); + } + else if (shouldInsertRow(pCellWidths, pTableGrid, nGrids, bIsIncompleteGrid)) + { + // If we're here, then the number of cells does not equal to the amount + // defined by the grid, even after taking care of + // gridSpan/gridBefore/gridAfter. Handle this by ignoring the grid and + // providing the separators based on the provided cell widths, as long + // as we have a fixed layout; + // On the other hand even if the layout is not fixed, but the cell widths + // provided equal the total number of cells, and there are no after/before cells + // then use the cell widths to calculate the column separators. + // Also handle autofit tables with incomplete grids, when rows can have + // different widths and last cells can be wider, than their values. + uno::Sequence< text::TableColumnSeparator > aSeparators(pCellWidths->size() - 1); + text::TableColumnSeparator* pSeparators = aSeparators.getArray(); + sal_Int16 nSum = 0; + sal_uInt32 nPos = 0; + + if (bIsIncompleteGrid) + nFullWidthRelative = 0; + + // Avoid divide by zero (if there's no grid, position using cell widths). + if( nFullWidthRelative == 0 ) + for (size_t i = 0; i < pCellWidths->size(); ++i) + nFullWidthRelative += (*pCellWidths)[i]; + + if (bIsIncompleteGrid) + { + /* + * If table width property set earlier is smaller than the current table row width, + * then replace the TABLE_WIDTH property, set earlier. + */ + sal_Int32 nFullWidth = static_cast<sal_Int32>(ceil(ConversionHelper::convertTwipToMM100Double(nFullWidthRelative))); + sal_Int32 nTableWidth(0); + sal_Int32 nTableWidthType(text::SizeType::VARIABLE); + pTablePropMap->getValue(TablePropertyMap::TABLE_WIDTH, nTableWidth); + pTablePropMap->getValue(TablePropertyMap::TABLE_WIDTH_TYPE, nTableWidthType); + if (nTableWidth < nFullWidth) + { + pTablePropMap->setValue(TablePropertyMap::TABLE_WIDTH, nFullWidth); + } + } + + size_t nWidthsBound = pCellWidths->size() - 1; + if (nWidthsBound) + { + // At incomplete table grids, last cell width can be smaller, than its final width. + // Correct it based on the last but one column width and their span values. + if ( bIsIncompleteGrid && rCurrentSpans.size()-1 == nWidthsBound ) + { + auto aSpansIter = std::next(rCurrentSpans.begin(), nWidthsBound - 1); + sal_Int32 nFixLastCellWidth = (*pCellWidths)[nWidthsBound-1] / *aSpansIter * *std::next(aSpansIter); + if (nFixLastCellWidth > (*pCellWidths)[nWidthsBound]) + nFullWidthRelative += nFixLastCellWidth - (*pCellWidths)[nWidthsBound]; + } + + // tdf#131203 handle missing w:tblGrid + if (nFullWidthRelative > 0) + { + for (size_t i = 0; i < nWidthsBound; ++i) + { + nSum += (*pCellWidths)[i]; + pSeparators[nPos].Position = (nSum * 10000) / nFullWidthRelative; // Relative position + pSeparators[nPos].IsVisible = true; + nPos++; + } + } + } + + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( PROP_TABLE_COLUMN_SEPARATORS, uno::Any( aSeparators ) ); +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("rowProperties"); + pPropMap->dumpXml(); + TagLogger::getInstance().endElement(); +#endif + insertRowProps(pPropMap); + } + + // Now that potentially opened table is closed, save the table properties + TableManager::insertTableProps(pTablePropMap); + + m_aTmpTableProperties.pop_back(); + TablePropertyMapPtr pEmptyTableProps( new TablePropertyMap() ); + m_aTmpTableProperties.push_back( pEmptyTableProps ); + + ++m_nRow; + m_nCell.back( ) = 0; + getCurrentGrid()->clear(); + pCellWidths->clear(); + + m_bTableSizeTypeInserted = false; + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void DomainMapperTableManager::clearData() +{ + m_nRow = m_nHeaderRepeat = m_nTableWidth = m_nLayoutType = 0; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapperTableManager.hxx b/writerfilter/source/dmapper/DomainMapperTableManager.hxx new file mode 100644 index 000000000..a2e492936 --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapperTableManager.hxx @@ -0,0 +1,172 @@ +/* -*- 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 . + */ +#pragma once + +#include "TablePropertiesHandler.hxx" +#include "TablePositionHandler.hxx" + +#include "TableManager.hxx" +#include "PropertyMap.hxx" +#include <vector> +#include <memory> +#include <comphelper/sequenceashashmap.hxx> + +namespace writerfilter::dmapper { + +class DomainMapper; + +class DomainMapperTableManager : public TableManager +{ + typedef std::shared_ptr< std::vector<sal_Int32> > IntVectorPtr; + + sal_uInt32 m_nRow; + ::std::vector< sal_uInt32 > m_nCell; + sal_uInt32 m_nGridSpan; + sal_Int32 m_nHeaderRepeat; //counter of repeated headers - if == -1 then the repeating stops + sal_Int32 m_nTableWidth; //might be set directly or has to be calculated from the column positions + /// Are we in a shape (text append stack is not empty) or in the body document? + bool m_bIsInShape; + std::vector< OUString > m_aTableStyleNames; + /// Moved table (in moveRangeFromStart...moveRangeFromEnd or moveRangeToStart...moveRangeToEnd) + std::vector< OUString > m_aMoved; + /// Grab-bag of table look attributes for preserving. + comphelper::SequenceAsHashMap m_aTableLook; + std::vector< TablePositionHandlerPtr > m_aTablePositions; + std::vector< TablePositionHandlerPtr > m_aTmpPosition; ///< Temporarily stores the position to compare it later + std::vector< TablePropertyMapPtr > m_aTmpTableProperties; ///< Temporarily stores the table properties until end of row + + ::std::vector< IntVectorPtr > m_aTableGrid; + /// If this is true, then we pushed a width before the next level started, and that should be carried over when starting the next level. + bool m_bPushCurrentWidth; + /// Individual table cell width values, used only in case the number of cells doesn't match the table grid. + ::std::vector< IntVectorPtr > m_aCellWidths; + /// Remember if table width was already set, when we lack a w:tblW, it should be set manually at the end. + bool m_bTableSizeTypeInserted; + /// Table layout algorithm, IOW if we should consider fixed column width or not. + sal_uInt32 m_nLayoutType; + /// Collected table paragraphs for table style handling + std::stack< TableParagraphVectorPtr > m_aParagraphsToEndTable; + + std::unique_ptr<TablePropertiesHandler> m_pTablePropsHandler; + PropertyMapPtr m_pStyleProps; + + bool shouldInsertRow(IntVectorPtr pCellWidths, IntVectorPtr pTableGrid, size_t nGrids, bool& rIsIncompleteGrid); + + virtual void clearData() override; + +public: + + DomainMapperTableManager(); + virtual ~DomainMapperTableManager() override; + + // use this method to avoid adding the properties for the table + // but in the provided properties map. + void SetStyleProperties( PropertyMapPtr pProperties ) { m_pStyleProps = pProperties; }; + + virtual bool sprm(Sprm & rSprm) override; + bool attribute(Id nName, Value const & val); + + virtual void startLevel( ) override; + virtual void endLevel( ) override; + + virtual void endOfCellAction() override; + virtual void endOfRowAction() override; + + IntVectorPtr const & getCurrentGrid( ); + IntVectorPtr const & getCurrentCellWidths( ); + const TableParagraphVectorPtr & getCurrentParagraphs( ); + + /// Turn the attributes collected so far in m_aTableLook into a property and clear the container. + void finishTableLook(); + css::uno::Sequence<css::beans::PropertyValue> getCurrentTablePosition(); + TablePositionHandler* getCurrentTableRealPosition(); + + virtual void cellProps(const TablePropertyMapPtr& pProps) override + { + if ( m_pStyleProps ) + m_pStyleProps->InsertProps(pProps.get()); + else + TableManager::cellProps( pProps ); + }; + + virtual void insertRowProps(const TablePropertyMapPtr& pProps) override + { + if ( m_pStyleProps ) + m_pStyleProps->InsertProps(pProps.get()); + else + TableManager::insertRowProps( pProps ); + }; + + virtual void insertTableProps(const TablePropertyMapPtr& pProps) override + { + if ( m_pStyleProps ) + m_pStyleProps->InsertProps(pProps.get()); + else + m_aTmpTableProperties.back()->InsertProps(pProps.get()); + }; + + void SetLayoutType(sal_uInt32 nLayoutType) + { + m_nLayoutType = nLayoutType; + } + + using TableManager::isInCell; + + void setIsInShape(bool bIsInShape); + + // moveFromRangeStart and moveToRangeStart are there + // in the first paragraph in the first cell of the + // table moved by drag & drop with track changes, but + // moveFromRangeEnd and moveToRangeEnd follow the + // table element w:tbl in the same level (not in paragraph). + // (Special indexing is related to the load of the tables: + // first-level tables handled by two levels during the + // import, to support table join etc. In the first cell, + // setMoved() writes the first level from these two levels + // i.e. second startLevel() hasn't been called, yet.) + // TODO: check drag & drop of only a part of the tables. + void setMoved(OUString sMoved) + { + if ( m_aMoved.empty() ) + return; + + if ( !sMoved.isEmpty() ) + m_aMoved[m_aMoved.size() - 1] = sMoved; + else if ( m_aMoved.size() >= 2 ) + // next table rows weren't moved + m_aMoved[m_aMoved.size() - 2] = ""; + else + m_aMoved[m_aMoved.size() - 1] = ""; + } + + OUString getMoved() const + { + if ( m_aMoved.size() >= 2 && !m_aMoved[m_aMoved.size() - 2].isEmpty() ) + return m_aMoved[m_aMoved.size() - 2]; + else if ( !m_aMoved.empty() ) + return m_aMoved[m_aMoved.size() -1 ]; + + return OUString(); + } + +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx new file mode 100644 index 000000000..ae0a8a27d --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -0,0 +1,8792 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <ooxml/resourceids.hxx> +#include "DomainMapper_Impl.hxx" +#include "ConversionHelper.hxx" +#include "SdtHelper.hxx" +#include "DomainMapperTableHandler.hxx" +#include "TagLogger.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/document/PrinterIndependentLayout.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/i18n/NumberFormatMapper.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/style/LineNumberPosition.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/text/ChapterFormat.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/text/SetVariableType.hpp> +#include <com/sun/star/text/XDocumentIndex.hpp> +#include <com/sun/star/text/XDocumentIndexesSupplier.hpp> +#include <com/sun/star/text/XFootnote.hpp> +#include <com/sun/star/text/XEndnotesSupplier.hpp> +#include <com/sun/star/text/XFootnotesSupplier.hpp> +#include <com/sun/star/text/XLineNumberingProperties.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/text/PageNumberType.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/ReferenceFieldPart.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/ReferenceFieldSource.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XDependentTextField.hpp> +#include <com/sun/star/text/XParagraphCursor.hpp> +#include <com/sun/star/text/XRedline.hpp> +#include <com/sun/star/text/XTextFieldsSupplier.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/RubyPosition.hpp> +#include <com/sun/star/text/XTextRangeCompare.hpp> +#include <com/sun/star/style/DropCapFormat.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormatter.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/text/ControlCharacter.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/document/XImporter.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <comphelper/indexedpropertyvalues.hxx> +#include <editeng/flditem.hxx> +#include <editeng/unotext.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/temporary.hxx> +#include <oox/mathml/import.hxx> +#include <xmloff/odffields.hxx> +#include <rtl/uri.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> +#include <comphelper/string.hxx> + +#include <dmapper/GraphicZOrderHelper.hxx> + +#include <oox/token/tokens.hxx> + +#include <cmath> +#include <optional> +#include <map> +#include <tuple> +#include <unordered_map> +#include <regex> +#include <algorithm> + +#include <officecfg/Office/Common.hxx> +#include <filter/msfilter/util.hxx> +#include <filter/msfilter/ww8fields.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/propertysequence.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/mediadescriptor.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/drawing/FillStyle.hpp> + +#include <unicode/errorcode.h> +#include <unicode/regex.h> + +using namespace ::com::sun::star; +using namespace oox; +namespace writerfilter::dmapper{ + +//line numbering for header/footer +static void lcl_linenumberingHeaderFooter( const uno::Reference<container::XNameContainer>& xStyles, const OUString& rname, DomainMapper_Impl* dmapper ) +{ + const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname ); + if (!pEntry) + return; + const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->pProperties.get(); + if ( !pStyleSheetProperties ) + return; + sal_Int32 nListId = pStyleSheetProperties->GetListId(); + if( xStyles.is() ) + { + if( xStyles->hasByName( rname ) ) + { + uno::Reference< style::XStyle > xStyle; + xStyles->getByName( rname ) >>= xStyle; + if( !xStyle.is() ) + return; + uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY ); + xPropertySet->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::Any( nListId >= 0 ) ); + } + } +} + +// Populate Dropdown Field properties from FFData structure +static void lcl_handleDropdownField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler ) +{ + if ( !rxFieldProps.is() ) + return; + + if ( !pFFDataHandler->getName().isEmpty() ) + rxFieldProps->setPropertyValue( "Name", uno::Any( pFFDataHandler->getName() ) ); + + const FFDataHandler::DropDownEntries_t& rEntries = pFFDataHandler->getDropDownEntries(); + uno::Sequence< OUString > sItems( rEntries.size() ); + ::std::copy( rEntries.begin(), rEntries.end(), sItems.getArray()); + if ( sItems.hasElements() ) + rxFieldProps->setPropertyValue( "Items", uno::Any( sItems ) ); + + sal_Int32 nResult = pFFDataHandler->getDropDownResult().toInt32(); + if (nResult > 0 && o3tl::make_unsigned(nResult) < sItems.size()) + rxFieldProps->setPropertyValue( "SelectedItem", uno::Any( std::as_const(sItems)[ nResult ] ) ); + if ( !pFFDataHandler->getHelpText().isEmpty() ) + rxFieldProps->setPropertyValue( "Help", uno::Any( pFFDataHandler->getHelpText() ) ); +} + +static void lcl_handleTextField( const uno::Reference< beans::XPropertySet >& rxFieldProps, const FFDataHandler::Pointer_t& pFFDataHandler ) +{ + if ( rxFieldProps.is() && pFFDataHandler ) + { + rxFieldProps->setPropertyValue + (getPropertyName(PROP_HINT), + uno::Any(pFFDataHandler->getStatusText())); + rxFieldProps->setPropertyValue + (getPropertyName(PROP_HELP), + uno::Any(pFFDataHandler->getHelpText())); + rxFieldProps->setPropertyValue + (getPropertyName(PROP_CONTENT), + uno::Any(pFFDataHandler->getTextDefault())); + } +} + +/** + Very similar to DomainMapper_Impl::GetPropertyFromStyleSheet + It is focused on paragraph properties search in current & parent stylesheet entries. + But it will not take into account properties with listid: these are "list paragraph styles" and + not used in some cases. +*/ +static uno::Any lcl_GetPropertyFromParaStyleSheetNoNum(PropertyIds eId, StyleSheetEntryPtr pEntry, const StyleSheetTablePtr& rStyleSheet) +{ + while (pEntry) + { + if (pEntry->pProperties) + { + std::optional<PropertyMap::Property> aProperty = + pEntry->pProperties->getProperty(eId); + if (aProperty) + { + if (pEntry->pProperties->GetListId()) + // It is a paragraph style with list. Paragraph list styles are not taken into account + return uno::Any(); + else + return aProperty->second; + } + } + //search until the property is set or no parent is available + StyleSheetEntryPtr pNewEntry; + if (!pEntry->sBaseStyleIdentifier.isEmpty()) + pNewEntry = rStyleSheet->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + + SAL_WARN_IF(pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?"); + + if (pEntry == pNewEntry) //fdo#49587 + break; + + pEntry = pNewEntry; + } + return uno::Any(); +} + + +namespace { + +struct FieldConversion +{ + const char* cFieldServiceName; + FieldId eFieldId; +}; + +} + +typedef std::unordered_map<OUString, FieldConversion> FieldConversionMap_t; + +/// Gives access to the parent field context of the topmost one, if there is any. +static FieldContextPtr GetParentFieldContext(const std::deque<FieldContextPtr>& rFieldStack) +{ + if (rFieldStack.size() < 2) + { + return nullptr; + } + + return rFieldStack[rFieldStack.size() - 2]; +} + +/// Decides if the pInner field inside pOuter is allowed in Writer core, depending on their type. +static bool IsFieldNestingAllowed(const FieldContextPtr& pOuter, const FieldContextPtr& pInner) +{ + std::optional<FieldId> oOuterFieldId = pOuter->GetFieldId(); + OUString aCommand = pOuter->GetCommand(); + + // Ignore leading space before the field name, but don't accept IFF when we check for IF. + if (!aCommand.isEmpty() && aCommand[0] == ' ') + { + aCommand = aCommand.subView(1); + } + + if (!oOuterFieldId && aCommand.startsWith("IF ")) + { + // This will be FIELD_IF once the command is closed. + oOuterFieldId = FIELD_IF; + } + + if (!oOuterFieldId) + { + return true; + } + + if (!pInner->GetFieldId()) + { + return true; + } + + switch (*oOuterFieldId) + { + case FIELD_IF: + { + switch (*pInner->GetFieldId()) + { + case FIELD_DOCVARIABLE: + case FIELD_FORMULA: + case FIELD_IF: + case FIELD_MERGEFIELD: + { + return false; + } + default: + break; + } + break; + } + default: + break; + } + + return true; +} + +uno::Any FloatingTableInfo::getPropertyValue(std::u16string_view propertyName) +{ + for( beans::PropertyValue const & propVal : std::as_const(m_aFrameProperties) ) + if( propVal.Name == propertyName ) + return propVal.Value ; + return uno::Any() ; +} + +DomainMapper_Impl::DomainMapper_Impl( + DomainMapper& rDMapper, + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<lang::XComponent> const& xModel, + SourceDocumentType eDocumentType, + utl::MediaDescriptor const & rMediaDesc) : + m_eDocumentType( eDocumentType ), + m_rDMapper( rDMapper ), + m_pOOXMLDocument(nullptr), + m_xTextDocument( xModel, uno::UNO_QUERY ), + m_xTextFactory( xModel, uno::UNO_QUERY ), + m_xComponentContext( xContext ), + m_bForceGenericFields(!utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get()), + m_bIsDecimalComma( false ), + m_bSetUserFieldContent( false ), + m_bSetCitation( false ), + m_bSetDateValue( false ), + m_bIsFirstSection( true ), + m_bIsColumnBreakDeferred( false ), + m_bIsPageBreakDeferred( false ), + m_nLineBreaksDeferred( 0 ), + m_bSdtEndDeferred(false), + m_bParaSdtEndDeferred(false), + m_bStartTOC(false), + m_bStartTOCHeaderFooter(false), + m_bStartedTOC(false), + m_bStartIndex(false), + m_bStartBibliography(false), + m_nStartGenericField(0), + m_bTextInserted(false), + m_sCurrentPermId(0), + m_bFrameDirectionSet(false), + m_bInDocDefaultsImport(false), + m_bInStyleSheetImport( false ), + m_bInNumberingImport(false), + m_bInAnyTableImport( false ), + m_eInHeaderFooterImport( HeaderFooterImportState::none ), + m_bDiscardHeaderFooter( false ), + m_bInFootOrEndnote(false), + m_bInFootnote(false), + m_bHasFootnoteStyle(false), + m_bCheckFootnoteStyle(false), + m_eSkipFootnoteState(SkipFootnoteSeparator::OFF), + m_nFootnotes(-1), + m_nEndnotes(-1), + m_nFirstFootnoteIndex(-1), + m_nFirstEndnoteIndex(-1), + m_bLineNumberingSet( false ), + m_bIsInFootnoteProperties( false ), + m_bIsParaMarkerChange( false ), + m_bIsParaMarkerMove( false ), + m_bRedlineImageInPreviousRun( false ), + m_bParaChanged( false ), + m_bIsFirstParaInSection( true ), + m_bIsFirstParaInSectionAfterRedline( true ), + m_bDummyParaAddedForTableInSection( false ), + m_bDummyParaAddedForTableInSectionPage( false ), + m_bTextFrameInserted(false), + m_bIsPreviousParagraphFramed( false ), + m_bIsLastParaInSection( false ), + m_bIsLastSectionGroup( false ), + m_bIsInComments( false ), + m_bParaSectpr( false ), + m_bUsingEnhancedFields( false ), + m_bSdt(false), + m_bIsFirstRun(false), + m_bIsOutsideAParagraph(true), + m_nAnnotationId( -1 ), + m_aSmartTagHandler(m_xComponentContext, m_xTextDocument), + m_xInsertTextRange(rMediaDesc.getUnpackedValueOrDefault("TextInsertModeRange", uno::Reference<text::XTextRange>())), + m_xAltChunkStartingRange(rMediaDesc.getUnpackedValueOrDefault("AltChunkStartingRange", uno::Reference<text::XTextRange>())), + m_bIsInTextBox(false), + m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)), + m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault("AltChunkMode", false)), + m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)), + m_nTableDepth(0), + m_nTableCellDepth(0), + m_nLastTableCellParagraphDepth(0), + m_bHasFtn(false), + m_bHasFtnSep(false), + m_bCheckFirstFootnoteTab(false), + m_bIgnoreNextTab(false), + m_bIsSplitPara(false), + m_bIsActualParagraphFramed( false ), + m_bParaHadField(false), + m_bSaveParaHadField(false), + m_bParaAutoBefore(false), + m_bFirstParagraphInCell(true), + m_bSaveFirstParagraphInCell(false), + m_bParaWithInlineObject(false), + m_bSaxError(false) +{ + m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString()); + if (m_aBaseUrl.isEmpty()) { + m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_URL, OUString()); + } + + appendTableManager( ); + GetBodyText(); + if (!m_bIsNewDoc && !m_xBodyText) + { + throw uno::Exception("failed to find body text of the insert position", nullptr); + } + + uno::Reference< text::XTextAppend > xBodyTextAppend( m_xBodyText, uno::UNO_QUERY ); + m_aTextAppendStack.push(TextAppendContext(xBodyTextAppend, + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : m_xBodyText->createTextCursorByRange(m_xInsertTextRange))); + + //todo: does it makes sense to set the body text as static text interface? + uno::Reference< text::XTextAppendAndConvert > xBodyTextAppendAndConvert( m_xBodyText, uno::UNO_QUERY ); + m_pTableHandler = new DomainMapperTableHandler(xBodyTextAppendAndConvert, *this); + getTableManager( ).setHandler(m_pTableHandler); + + getTableManager( ).startLevel(); + m_bUsingEnhancedFields = !utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get(); + + m_pSdtHelper = new SdtHelper(*this, m_xComponentContext); + + m_aRedlines.push(std::vector<RedlineParamsPtr>()); + + if (m_bIsAltChunk) + { + m_bIsFirstSection = false; + } +} + + +DomainMapper_Impl::~DomainMapper_Impl() +{ + ChainTextFrames(); + // Don't remove last paragraph when pasting, sw expects that empty paragraph. + if (m_bIsNewDoc) + RemoveLastParagraph(); + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } +} + +writerfilter::ooxml::OOXMLDocument* DomainMapper_Impl::getDocumentReference() const +{ + return m_pOOXMLDocument; +} + +uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetPageStyles() +{ + if(!m_xPageStyles1.is()) + { + uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY ); + if (xSupplier.is()) + xSupplier->getStyleFamilies()->getByName("PageStyles") >>= m_xPageStyles1; + } + return m_xPageStyles1; +} + +OUString DomainMapper_Impl::GetUnusedPageStyleName() +{ + static const char DEFAULT_STYLE[] = "Converted"; + if (!m_xNextUnusedPageStyleNo) + { + const uno::Sequence< OUString > aPageStyleNames = GetPageStyles()->getElementNames(); + sal_Int32 nMaxIndex = 0; + // find the highest number x in each style with the name "DEFAULT_STYLE+x" and + // return an incremented name + + for ( const auto& rStyleName : aPageStyleNames ) + { + if ( rStyleName.startsWith( DEFAULT_STYLE ) ) + { + sal_Int32 nIndex = o3tl::toInt32(rStyleName.subView( strlen( DEFAULT_STYLE ) )); + if ( nIndex > nMaxIndex ) + nMaxIndex = nIndex; + } + } + m_xNextUnusedPageStyleNo = nMaxIndex + 1; + } + + OUString sPageStyleName = DEFAULT_STYLE + OUString::number( *m_xNextUnusedPageStyleNo ); + *m_xNextUnusedPageStyleNo = *m_xNextUnusedPageStyleNo + 1; + return sPageStyleName; +} + +uno::Reference< container::XNameContainer > const & DomainMapper_Impl::GetCharacterStyles() +{ + if(!m_xCharacterStyles.is()) + { + uno::Reference< style::XStyleFamiliesSupplier > xSupplier( m_xTextDocument, uno::UNO_QUERY ); + if (xSupplier.is()) + xSupplier->getStyleFamilies()->getByName("CharacterStyles") >>= m_xCharacterStyles; + } + return m_xCharacterStyles; +} + +OUString DomainMapper_Impl::GetUnusedCharacterStyleName() +{ + static const char cListLabel[] = "ListLabel "; + if (!m_xNextUnusedCharacterStyleNo) + { + //search for all character styles with the name sListLabel + <index> + const uno::Sequence< OUString > aCharacterStyleNames = GetCharacterStyles()->getElementNames(); + sal_Int32 nMaxIndex = 0; + for ( const auto& rStyleName : aCharacterStyleNames ) + { + OUString sSuffix; + if ( rStyleName.startsWith( cListLabel, &sSuffix ) ) + { + sal_Int32 nSuffix = sSuffix.toInt32(); + if( nSuffix > 0 && nSuffix > nMaxIndex ) + nMaxIndex = nSuffix; + } + } + m_xNextUnusedCharacterStyleNo = nMaxIndex + 1; + } + + OUString sPageStyleName = cListLabel + OUString::number( *m_xNextUnusedCharacterStyleNo ); + *m_xNextUnusedCharacterStyleNo = *m_xNextUnusedCharacterStyleNo + 1; + return sPageStyleName; +} + +uno::Reference< text::XText > const & DomainMapper_Impl::GetBodyText() +{ + if(!m_xBodyText.is()) + { + if (m_xInsertTextRange.is()) + m_xBodyText = m_xInsertTextRange->getText(); + else if (m_xTextDocument.is()) + m_xBodyText = m_xTextDocument->getText(); + } + return m_xBodyText; +} + + +uno::Reference< beans::XPropertySet > const & DomainMapper_Impl::GetDocumentSettings() +{ + if( !m_xDocumentSettings.is() && m_xTextFactory.is()) + { + m_xDocumentSettings.set( m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY ); + } + return m_xDocumentSettings; +} + + +void DomainMapper_Impl::SetDocumentSettingsProperty( const OUString& rPropName, const uno::Any& rValue ) +{ + uno::Reference< beans::XPropertySet > xSettings = GetDocumentSettings(); + if( xSettings.is() ) + { + try + { + xSettings->setPropertyValue( rPropName, rValue ); + } + catch( const uno::Exception& ) + { + } + } +} +void DomainMapper_Impl::RemoveDummyParaForTableInSection() +{ + SetIsDummyParaAddedForTableInSection(false); + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION); + SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + if (!pSectionContext) + return; + + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + return; + + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(pSectionContext->GetStartingRange()); + + // Remove the extra NumPicBullets from the document, + // which get attached to the first paragraph in the + // document + ListsManager::Pointer pListTable = GetListTable(); + pListTable->DisposeNumPicBullets(); + + uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY); + if (xEnumerationAccess.is() && m_aTextAppendStack.size() == 1 ) + { + uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration(); + uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY); + xParagraph->dispose(); + } +} +void DomainMapper_Impl::AddDummyParaForTableInSection() +{ + // Shapes and textboxes can't have sections. + if (IsInShape() || m_bIsInTextBox) + return; + + if (!m_aTextAppendStack.empty()) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + xTextAppend->finishParagraph( uno::Sequence< beans::PropertyValue >() ); + SetIsDummyParaAddedForTableInSection(true); + } + } +} + + static OUString lcl_FindLastBookmark(const uno::Reference<text::XTextCursor>& xCursor) + { + OUString sName; + if (!xCursor.is()) + return sName; + + // Select 1 previous element + xCursor->goLeft(1, true); + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xCursor, uno::UNO_QUERY); + if (!xParaEnumAccess.is()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through selection paragraphs + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + if (!xParaEnum->hasMoreElements()) + { + xCursor->goRight(1, true); + return sName; + } + + // Iterate through first para portions + uno::Reference<container::XEnumerationAccess> xRunEnumAccess(xParaEnum->nextElement(), + uno::UNO_QUERY_THROW); + uno::Reference<container::XEnumeration> xRunEnum = xRunEnumAccess->createEnumeration(); + while (xRunEnum->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xProps(xRunEnum->nextElement(), uno::UNO_QUERY_THROW); + uno::Any aType(xProps->getPropertyValue("TextPortionType")); + OUString sType; + aType >>= sType; + if (sType == "Bookmark") + { + uno::Reference<container::XNamed> xBookmark(xProps->getPropertyValue("Bookmark"), + uno::UNO_QUERY_THROW); + sName = xBookmark->getName(); + // Do not stop the scan here. Maybe there are 2 bookmarks? + } + } + + xCursor->goRight(1, true); + return sName; + } + +void DomainMapper_Impl::RemoveLastParagraph( ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + return; + try + { + uno::Reference< text::XTextCursor > xCursor; + if (m_bIsNewDoc) + { + xCursor = xTextAppend->createTextCursor(); + xCursor->gotoEnd(false); + } + else + xCursor = m_aTextAppendStack.top().xCursor; + uno::Reference<container::XEnumerationAccess> xEnumerationAccess(xCursor, uno::UNO_QUERY); + // Keep the character properties of the last but one paragraph, even if + // it's empty. This works for headers/footers, and maybe in other cases + // as well, but surely not in textboxes. + // fdo#58327: also do this at the end of the document: when pasting, + // a table before the cursor position would be deleted + // (but only for paste/insert, not load; otherwise it can happen that + // flys anchored at the disposed paragraph are deleted (fdo47036.rtf)) + bool const bEndOfDocument(m_aTextAppendStack.size() == 1); + + // Try to find and remember last bookmark in document: it potentially + // can be deleted by xCursor->setString() but not by xParagraph->dispose() + OUString sLastBookmarkName; + if (bEndOfDocument) + sLastBookmarkName = lcl_FindLastBookmark(xCursor); + + if ((IsInHeaderFooter() || (bEndOfDocument && !m_bIsNewDoc)) + && xEnumerationAccess.is()) + { + uno::Reference<container::XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration(); + uno::Reference<lang::XComponent> xParagraph(xEnumeration->nextElement(), uno::UNO_QUERY); + xParagraph->dispose(); + } + else if (xCursor.is()) + { + xCursor->goLeft( 1, true ); + // If this is a text on a shape, possibly the text has the trailing + // newline removed already. + if (xCursor->getString() == SAL_NEWLINE_STRING || + // tdf#105444 comments need an exception, if SAL_NEWLINE_STRING defined as "\r\n" + (sizeof(SAL_NEWLINE_STRING)-1 == 2 && xCursor->getString() == "\n")) + { + uno::Reference<beans::XPropertySet> xDocProps(GetTextDocument(), uno::UNO_QUERY); + static const OUStringLiteral aRecordChanges(u"RecordChanges"); + uno::Any aPreviousValue(xDocProps->getPropertyValue(aRecordChanges)); + + // disable redlining for this operation, otherwise we might + // end up with an unwanted recorded deletion + xDocProps->setPropertyValue(aRecordChanges, uno::Any(false)); + + // delete + xCursor->setString(OUString()); + + // While removing paragraphs that contain section properties, reset list + // related attributes to prevent them leaking into the following section's lists + if (GetParaSectpr()) + { + uno::Reference<beans::XPropertySet> XCursorProps(xCursor, uno::UNO_QUERY); + XCursorProps->setPropertyValue("ResetParagraphListAttributes", uno::Any()); + } + + // call to xCursor->setString possibly did remove final bookmark + // from previous paragraph. We need to restore it, if there was any. + if (sLastBookmarkName.getLength()) + { + OUString sBookmarkNameAfterRemoval = lcl_FindLastBookmark(xCursor); + if (sBookmarkNameAfterRemoval.isEmpty()) + { + // Yes, it was removed. Restore + uno::Reference<text::XTextContent> xBookmark( + m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), + uno::UNO_QUERY_THROW); + + uno::Reference<container::XNamed> xBkmNamed(xBookmark, + uno::UNO_QUERY_THROW); + xBkmNamed->setName(sLastBookmarkName); + xTextAppend->insertTextContent( + uno::Reference<text::XTextRange>(xCursor, uno::UNO_QUERY_THROW), + xBookmark, !xCursor->isCollapsed()); + } + } + // restore redline options again + xDocProps->setPropertyValue(aRecordChanges, aPreviousValue); + } + } + } + catch( const uno::Exception& ) + { + } +} + + +void DomainMapper_Impl::SetIsLastSectionGroup( bool bIsLast ) +{ + m_bIsLastSectionGroup = bIsLast; +} + +void DomainMapper_Impl::SetIsLastParagraphInSection( bool bIsLast ) +{ + m_bIsLastParaInSection = bIsLast; +} + + +void DomainMapper_Impl::SetIsFirstParagraphInSection( bool bIsFirst ) +{ + m_bIsFirstParaInSection = bIsFirst; +} + +void DomainMapper_Impl::SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline ) +{ + m_bIsFirstParaInSectionAfterRedline = bIsFirstAfterRedline; +} + +bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const +{ + // Anchored objects may include multiple paragraphs, + // and none of them should be considered the first para in section. + return ( bAfterRedline ? m_bIsFirstParaInSectionAfterRedline : m_bIsFirstParaInSection ) + && !IsInShape() + && !m_bIsInComments + && !IsInFootOrEndnote(); +} + +void DomainMapper_Impl::SetIsFirstParagraphInShape(bool bIsFirst) +{ + m_bIsFirstParaInShape = bIsFirst; +} + +void DomainMapper_Impl::SetIsDummyParaAddedForTableInSection( bool bIsAdded ) +{ + m_bDummyParaAddedForTableInSection = bIsAdded; + m_bDummyParaAddedForTableInSectionPage = bIsAdded; +} + +void DomainMapper_Impl::SetIsDummyParaAddedForTableInSectionPage( bool bIsAdded ) +{ + m_bDummyParaAddedForTableInSectionPage = bIsAdded; +} + + +void DomainMapper_Impl::SetIsTextFrameInserted( bool bIsInserted ) +{ + m_bTextFrameInserted = bIsInserted; +} + + +void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr) +{ + m_bParaSectpr = bParaSectpr; +} + + +void DomainMapper_Impl::SetSdt(bool bSdt) +{ + m_bSdt = bSdt; + + if (m_bSdt && !m_aTextAppendStack.empty()) + { + m_xSdtEntryStart = GetTopTextAppend()->getEnd(); + } + else + { + m_xSdtEntryStart.clear(); + } +} + +void DomainMapper_Impl::PushSdt() +{ + if (m_aTextAppendStack.empty()) + { + return; + } + + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + uno::Reference<text::XTextCursor> xCursor + = xTextAppend->getText()->createTextCursorByRange(xTextAppend->getEnd()); + // Offset so the cursor is not adjusted as we import the SDT's content. + bool bStart = !xCursor->goLeft(1, /*bExpand=*/false); + m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()}); +} + +const std::stack<BookmarkInsertPosition>& DomainMapper_Impl::GetSdtStarts() const +{ + return m_xSdtStarts; +} + +void DomainMapper_Impl::PopSdt() +{ + if (m_xSdtStarts.empty()) + { + return; + } + + BookmarkInsertPosition aPosition = m_xSdtStarts.top(); + m_xSdtStarts.pop(); + uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange; + uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd(); + uno::Reference<text::XText> xText = xEnd->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursorByRange(xStart); + if (!xCursor) + { + SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position"); + return; + } + + if (aPosition.m_bIsStartOfText) + { + // Go to the start of the end's paragraph. This helps in case + // DomainMapper_Impl::AddDummyParaForTableInSection() would make our range multi-paragraph, + // while the intention is to keep start/end inside the same paragraph for run SDTs. + uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY); + if (xParagraphCursor.is()) + { + xCursor->gotoRange(xEnd, /*bExpand=*/false); + xParagraphCursor->gotoStartOfParagraph(/*bExpand=*/false); + } + } + else + { + // Undo the goLeft() in DomainMapper_Impl::PushSdt(); + xCursor->goRight(1, /*bExpand=*/false); + } + xCursor->gotoRange(xEnd, /*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + m_xTextFactory->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + if (m_pSdtHelper->GetShowingPlcHdr()) + { + xContentControlProps->setPropertyValue("ShowingPlaceHolder", + uno::Any(m_pSdtHelper->GetShowingPlcHdr())); + } + + if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty()) + { + xContentControlProps->setPropertyValue("PlaceholderDocPart", + uno::Any(m_pSdtHelper->GetPlaceholderDocPart())); + } + + if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingPrefixMappings", + uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping())); + } + if (!m_pSdtHelper->GetDataBindingXPath().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingXpath", + uno::Any(m_pSdtHelper->GetDataBindingXPath())); + } + if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty()) + { + xContentControlProps->setPropertyValue("DataBindingStoreItemID", + uno::Any(m_pSdtHelper->GetDataBindingStoreItemID())); + } + + if (!m_pSdtHelper->GetColor().isEmpty()) + { + xContentControlProps->setPropertyValue("Color", + uno::Any(m_pSdtHelper->GetColor())); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::checkBox) + { + xContentControlProps->setPropertyValue("Checkbox", uno::Any(true)); + + xContentControlProps->setPropertyValue("Checked", uno::Any(m_pSdtHelper->GetChecked())); + + xContentControlProps->setPropertyValue("CheckedState", + uno::Any(m_pSdtHelper->GetCheckedState())); + + xContentControlProps->setPropertyValue("UncheckedState", + uno::Any(m_pSdtHelper->GetUncheckedState())); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::dropDown) + { + std::vector<OUString>& rDisplayTexts = m_pSdtHelper->getDropDownDisplayTexts(); + std::vector<OUString>& rValues = m_pSdtHelper->getDropDownItems(); + if (rDisplayTexts.size() == rValues.size()) + { + uno::Sequence<beans::PropertyValues> aItems(rValues.size()); + beans::PropertyValues* pItems = aItems.getArray(); + for (size_t i = 0; i < rValues.size(); ++i) + { + uno::Sequence<beans::PropertyValue> aItem = { + comphelper::makePropertyValue("DisplayText", rDisplayTexts[i]), + comphelper::makePropertyValue("Value", rValues[i]), + }; + pItems[i] = aItem; + } + xContentControlProps->setPropertyValue("ListItems", uno::Any(aItems)); + } + } + + if (m_pSdtHelper->getControlType() == SdtControlType::picture) + { + xContentControlProps->setPropertyValue("Picture", uno::Any(true)); + } + + if (m_pSdtHelper->getControlType() == SdtControlType::datePicker) + { + xContentControlProps->setPropertyValue("Date", uno::Any(true)); + OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear(); + xContentControlProps->setPropertyValue("DateFormat", + uno::Any(aDateFormat.replaceAll("'", "\""))); + xContentControlProps->setPropertyValue("DateLanguage", + uno::Any(m_pSdtHelper->getLocale().makeStringAndClear())); + xContentControlProps->setPropertyValue("CurrentDate", + uno::Any(m_pSdtHelper->getDate().makeStringAndClear())); + } + + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + + m_pSdtHelper->clear(); +} + +void DomainMapper_Impl::PushProperties(ContextType eId) +{ + PropertyMapPtr pInsert(eId == CONTEXT_SECTION ? + (new SectionPropertyMap( m_bIsFirstSection )) : + eId == CONTEXT_PARAGRAPH ? new ParagraphPropertyMap : new PropertyMap); + if(eId == CONTEXT_SECTION) + { + if( m_bIsFirstSection ) + m_bIsFirstSection = false; + // beginning with the second section group a section has to be inserted + // into the document + SectionPropertyMap* pSectionContext_ = dynamic_cast< SectionPropertyMap* >( pInsert.get() ); + if (!m_aTextAppendStack.empty()) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is() && pSectionContext_) + pSectionContext_->SetStart( xTextAppend->getEnd() ); + } + } + if(eId == CONTEXT_PARAGRAPH && m_bIsSplitPara) + { + m_aPropertyStacks[eId].push( GetTopContextOfType(eId)); + m_bIsSplitPara = false; + } + else + { + m_aPropertyStacks[eId].push( pInsert ); + } + m_aContextStack.push(eId); + + m_pTopContext = m_aPropertyStacks[eId].top(); +} + + +void DomainMapper_Impl::PushStyleProperties( const PropertyMapPtr& pStyleProperties ) +{ + m_aPropertyStacks[CONTEXT_STYLESHEET].push( pStyleProperties ); + m_aContextStack.push(CONTEXT_STYLESHEET); + + m_pTopContext = m_aPropertyStacks[CONTEXT_STYLESHEET].top(); +} + + +void DomainMapper_Impl::PushListProperties(const PropertyMapPtr& pListProperties) +{ + m_aPropertyStacks[CONTEXT_LIST].push( pListProperties ); + m_aContextStack.push(CONTEXT_LIST); + m_pTopContext = m_aPropertyStacks[CONTEXT_LIST].top(); +} + + +void DomainMapper_Impl::PopProperties(ContextType eId) +{ + OSL_ENSURE(!m_aPropertyStacks[eId].empty(), "section stack already empty"); + if ( m_aPropertyStacks[eId].empty() ) + return; + + if ( eId == CONTEXT_SECTION ) + { + if (m_aPropertyStacks[eId].size() == 1) // tdf#112202 only top level !!! + { + m_pLastSectionContext = m_aPropertyStacks[eId].top(); + } + } + else if (eId == CONTEXT_CHARACTER) + { + m_pLastCharacterContext = m_aPropertyStacks[eId].top(); + // Sadly an assert about deferredCharacterProperties being empty is not possible + // here, because appendTextPortion() may not be called for every character section. + deferredCharacterProperties.clear(); + } + + if (!IsInFootOrEndnote() && IsInCustomFootnote() && !m_aPropertyStacks[eId].empty()) + { + PropertyMapPtr pRet = m_aPropertyStacks[eId].top(); + if (pRet->GetFootnote().is() && m_pFootnoteContext.is()) + EndCustomFootnote(); + } + + m_aPropertyStacks[eId].pop(); + m_aContextStack.pop(); + if(!m_aContextStack.empty() && !m_aPropertyStacks[m_aContextStack.top()].empty()) + + m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top(); + else + { + // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section context end"); + m_pTopContext.clear(); + } +} + + +PropertyMapPtr DomainMapper_Impl::GetTopContextOfType(ContextType eId) +{ + PropertyMapPtr pRet; + if(!m_aPropertyStacks[eId].empty()) + pRet = m_aPropertyStacks[eId].top(); + return pRet; +} + +bool DomainMapper_Impl::HasTopText() const +{ + return !m_aTextAppendStack.empty(); +} + +uno::Reference< text::XTextAppend > const & DomainMapper_Impl::GetTopTextAppend() +{ + OSL_ENSURE(!m_aTextAppendStack.empty(), "text append stack is empty" ); + return m_aTextAppendStack.top().xTextAppend; +} + +FieldContextPtr const & DomainMapper_Impl::GetTopFieldContext() +{ + SAL_WARN_IF(m_aFieldStack.empty(), "writerfilter.dmapper", "Field stack is empty"); + return m_aFieldStack.back(); +} + +bool DomainMapper_Impl::HasTopAnchoredObjects() const +{ + return !m_aTextAppendStack.empty() && !m_aTextAppendStack.top().m_aAnchoredObjects.empty(); +} + +void DomainMapper_Impl::InitTabStopFromStyle( const uno::Sequence< style::TabStop >& rInitTabStops ) +{ + OSL_ENSURE(m_aCurrentTabStops.empty(), "tab stops already initialized"); + for( const auto& rTabStop : rInitTabStops) + { + m_aCurrentTabStops.emplace_back(rTabStop); + } +} + +void DomainMapper_Impl::IncorporateTabStop( const DeletableTabStop & rTabStop ) +{ + sal_Int32 nConverted = rTabStop.Position; + auto aIt = std::find_if(m_aCurrentTabStops.begin(), m_aCurrentTabStops.end(), + [&nConverted](const DeletableTabStop& rCurrentTabStop) { return rCurrentTabStop.Position == nConverted; }); + if( aIt != m_aCurrentTabStops.end() ) + { + if( rTabStop.bDeleted ) + m_aCurrentTabStops.erase( aIt ); + else + *aIt = rTabStop; + } + else + m_aCurrentTabStops.push_back( rTabStop ); +} + + +uno::Sequence< style::TabStop > DomainMapper_Impl::GetCurrentTabStopAndClear() +{ + std::vector<style::TabStop> aRet; + for (const DeletableTabStop& rStop : m_aCurrentTabStops) + { + if (!rStop.bDeleted) + aRet.push_back(rStop); + } + m_aCurrentTabStops.clear(); + return comphelper::containerToSequence(aRet); +} + +OUString DomainMapper_Impl::GetCurrentParaStyleName() +{ + OUString sName; + // use saved currParaStyleName as a fallback, in case no particular para style name applied. + // tdf#134784 except in the case of first paragraph of shapes to avoid bad fallback. + // TODO fix this "highly inaccurate" m_sCurrentParaStyleName + if ( !IsInShape() ) + sName = m_sCurrentParaStyleName; + + PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH); + if ( pParaContext && pParaContext->isSet(PROP_PARA_STYLE_NAME) ) + pParaContext->getProperty(PROP_PARA_STYLE_NAME)->second >>= sName; + + // In rare situations the name might still be blank, so use the default style, + // despite documentation that states, "If this attribute is not specified for any style, + // then no properties shall be applied to objects of the specified type." + // Word, however, assigns "Normal" style even in these situations. + if ( !m_bInStyleSheetImport && sName.isEmpty() ) + sName = GetDefaultParaStyleName(); + + return sName; +} + +OUString DomainMapper_Impl::GetDefaultParaStyleName() +{ + // After import the default style won't change and is frequently requested: cache the LO style name. + // TODO assert !InStyleSheetImport? This function really only makes sense once import is finished anyway. + if ( m_sDefaultParaStyleName.isEmpty() ) + { + const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindDefaultParaStyle(); + if ( pEntry && !pEntry->sConvertedStyleName.isEmpty() ) + { + if ( !m_bInStyleSheetImport ) + m_sDefaultParaStyleName = pEntry->sConvertedStyleName; + return pEntry->sConvertedStyleName; + } + else + return "Standard"; + } + return m_sDefaultParaStyleName; +} + +uno::Any DomainMapper_Impl::GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* pIsDocDefault) +{ + while(pEntry) + { + if(pEntry->pProperties) + { + std::optional<PropertyMap::Property> aProperty = + pEntry->pProperties->getProperty(eId); + if( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = pEntry->pProperties->isDocDefault(eId); + + return aProperty->second; + } + } + //search until the property is set or no parent is available + StyleSheetEntryPtr pNewEntry; + if ( !pEntry->sBaseStyleIdentifier.isEmpty() ) + pNewEntry = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + + SAL_WARN_IF( pEntry == pNewEntry, "writerfilter.dmapper", "circular loop in style hierarchy?"); + + if (pEntry == pNewEntry) //fdo#49587 + break; + + pEntry = pNewEntry; + } + // not found in style, try the document's DocDefault properties + if ( bDocDefaults && bPara ) + { + const PropertyMapPtr& pDefaultParaProps = GetStyleSheetTable()->GetDefaultParaProps(); + if ( pDefaultParaProps ) + { + std::optional<PropertyMap::Property> aProperty = pDefaultParaProps->getProperty(eId); + if ( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = true; + + return aProperty->second; + } + } + } + if ( bDocDefaults && isCharacterProperty(eId) ) + { + const PropertyMapPtr& pDefaultCharProps = GetStyleSheetTable()->GetDefaultCharProps(); + if ( pDefaultCharProps ) + { + std::optional<PropertyMap::Property> aProperty = pDefaultCharProps->getProperty(eId); + if ( aProperty ) + { + if (pIsDocDefault) + *pIsDocDefault = true; + + return aProperty->second; + } + } + } + + if (pIsDocDefault) + *pIsDocDefault = false; + + return uno::Any(); +} + +uno::Any DomainMapper_Impl::GetPropertyFromParaStyleSheet(PropertyIds eId) +{ + StyleSheetEntryPtr pEntry; + if ( m_bInStyleSheetImport ) + pEntry = GetStyleSheetTable()->GetCurrentEntry(); + else + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(GetCurrentParaStyleName()); + return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true); +} + +uno::Any DomainMapper_Impl::GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext) +{ + if ( m_bInStyleSheetImport || eId == PROP_CHAR_STYLE_NAME || !isCharacterProperty(eId) ) + return uno::Any(); + + StyleSheetEntryPtr pEntry; + OUString sCharStyleName; + if ( GetAnyProperty(PROP_CHAR_STYLE_NAME, rContext) >>= sCharStyleName ) + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(sCharStyleName); + return GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/false, /*bPara=*/false); +} + +uno::Any DomainMapper_Impl::GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext) +{ + // first look in directly applied attributes + if ( rContext ) + { + std::optional<PropertyMap::Property> aProperty = rContext->getProperty(eId); + if ( aProperty ) + return aProperty->second; + } + + // then look whether it was directly applied as a paragraph property + PropertyMapPtr pParaContext = GetTopContextOfType(CONTEXT_PARAGRAPH); + if (pParaContext && rContext != pParaContext) + { + std::optional<PropertyMap::Property> aProperty = pParaContext->getProperty(eId); + if (aProperty) + return aProperty->second; + } + + // then look whether it was inherited from a directly applied character style + if ( eId != PROP_CHAR_STYLE_NAME && isCharacterProperty(eId) ) + { + uno::Any aRet = GetPropertyFromCharStyleSheet(eId, rContext); + if ( aRet.hasValue() ) + return aRet; + } + + // then look in current paragraph style, and docDefaults + return GetPropertyFromParaStyleSheet(eId); +} + +OUString DomainMapper_Impl::GetListStyleName(sal_Int32 nListId) +{ + auto const pList(GetListTable()->GetList( nListId )); + return pList ? pList->GetStyleName() : OUString(); +} + +ListsManager::Pointer const & DomainMapper_Impl::GetListTable() +{ + if(!m_pListTable) + m_pListTable = + new ListsManager( m_rDMapper, m_xTextFactory ); + return m_pListTable; +} + + +void DomainMapper_Impl::deferBreak( BreakType deferredBreakType) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + m_nLineBreaksDeferred++; + break; + case COLUMN_BREAK: + m_bIsColumnBreakDeferred = true; + break; + case PAGE_BREAK: + // See SwWW8ImplReader::HandlePageBreakChar(), page break should be + // ignored inside tables. + if (m_nTableDepth > 0) + return; + + m_bIsPageBreakDeferred = true; + break; + default: + return; + } +} + +bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType ) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + return m_nLineBreaksDeferred > 0; + case COLUMN_BREAK: + return m_bIsColumnBreakDeferred; + case PAGE_BREAK: + return m_bIsPageBreakDeferred; + default: + return false; + } +} + +void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType) +{ + switch (deferredBreakType) + { + case LINE_BREAK: + assert(m_nLineBreaksDeferred > 0); + m_nLineBreaksDeferred--; + break; + case COLUMN_BREAK: + m_bIsColumnBreakDeferred = false; + break; + case PAGE_BREAK: + m_bIsPageBreakDeferred = false; + break; + default: + break; + } +} + +void DomainMapper_Impl::clearDeferredBreaks() +{ + m_nLineBreaksDeferred = 0; + m_bIsColumnBreakDeferred = false; + m_bIsPageBreakDeferred = false; +} + +void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred) +{ + m_bSdtEndDeferred = bSdtEndDeferred; +} + +bool DomainMapper_Impl::isSdtEndDeferred() const +{ + return m_bSdtEndDeferred; +} + +void DomainMapper_Impl::setParaSdtEndDeferred(bool bParaSdtEndDeferred) +{ + m_bParaSdtEndDeferred = bParaSdtEndDeferred; +} + +bool DomainMapper_Impl::isParaSdtEndDeferred() const +{ + return m_bParaSdtEndDeferred; +} + +static void lcl_MoveBorderPropertiesToFrame(std::vector<beans::PropertyValue>& rFrameProperties, + uno::Reference<text::XTextRange> const& xStartTextRange, + uno::Reference<text::XTextRange> const& xEndTextRange ) +{ + try + { + if (!xStartTextRange.is()) //rhbz#1077780 + return; + uno::Reference<text::XTextCursor> xRangeCursor = xStartTextRange->getText()->createTextCursorByRange( xStartTextRange ); + xRangeCursor->gotoRange( xEndTextRange, true ); + + uno::Reference<beans::XPropertySet> xTextRangeProperties(xRangeCursor, uno::UNO_QUERY); + if(!xTextRangeProperties.is()) + return ; + + static PropertyIds const aBorderProperties[] = + { + PROP_LEFT_BORDER, + PROP_RIGHT_BORDER, + PROP_TOP_BORDER, + PROP_BOTTOM_BORDER, + PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_BORDER_DISTANCE, + PROP_BOTTOM_BORDER_DISTANCE + }; + + for( size_t nProperty = 0; nProperty < SAL_N_ELEMENTS( aBorderProperties ); ++nProperty) + { + OUString sPropertyName = getPropertyName(aBorderProperties[nProperty]); + beans::PropertyValue aValue; + aValue.Name = sPropertyName; + aValue.Value = xTextRangeProperties->getPropertyValue(sPropertyName); + rFrameProperties.push_back(aValue); + if( nProperty < 4 ) + xTextRangeProperties->setPropertyValue( sPropertyName, uno::Any(table::BorderLine2())); + } + } + catch( const uno::Exception& ) + { + } +} + + +static void lcl_AddRangeAndStyle( + ParagraphPropertiesPtr const & pToBeSavedProperties, + uno::Reference< text::XTextAppend > const& xTextAppend, + const PropertyMapPtr& pPropertyMap, + TextAppendContext const & rAppendContext) +{ + uno::Reference<text::XParagraphCursor> xParaCursor( + xTextAppend->createTextCursorByRange( rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()), uno::UNO_QUERY_THROW ); + pToBeSavedProperties->SetEndingRange(xParaCursor->getStart()); + xParaCursor->gotoStartOfParagraph( false ); + + pToBeSavedProperties->SetStartingRange(xParaCursor->getStart()); + if(pPropertyMap) + { + std::optional<PropertyMap::Property> aParaStyle = pPropertyMap->getProperty(PROP_PARA_STYLE_NAME); + if( aParaStyle ) + { + OUString sName; + aParaStyle->second >>= sName; + pToBeSavedProperties->SetParaStyleName(sName); + } + } +} + + +//define some default frame width - 0cm ATM: this allow the frame to be wrapped around the text +constexpr sal_Int32 DEFAULT_FRAME_MIN_WIDTH = 0; +constexpr sal_Int32 DEFAULT_FRAME_MIN_HEIGHT = 0; +constexpr sal_Int32 DEFAULT_VALUE = 0; + +void DomainMapper_Impl::CheckUnregisteredFrameConversion( ) +{ + if (m_aTextAppendStack.empty()) + return; + TextAppendContext& rAppendContext = m_aTextAppendStack.top(); + // n#779642: ignore fly frame inside table as it could lead to messy situations + if (!rAppendContext.pLastParagraphProperties) + return; + if (!rAppendContext.pLastParagraphProperties->IsFrameMode()) + return; + if (!hasTableManager()) + return; + if (getTableManager().isInTable()) + return; + try + { + StyleSheetEntryPtr pParaStyle = + GetStyleSheetTable()->FindStyleSheetByConvertedStyleName(rAppendContext.pLastParagraphProperties->GetParaStyleName()); + + std::vector<beans::PropertyValue> aFrameProperties; + + if ( pParaStyle ) + { + const StyleSheetPropertyMap* pStyleProperties = pParaStyle->pProperties.get(); + if (!pStyleProperties) + return; + sal_Int32 nWidth = + rAppendContext.pLastParagraphProperties->Getw() > 0 ? + rAppendContext.pLastParagraphProperties->Getw() : + pStyleProperties->Getw(); + bool bAutoWidth = nWidth < 1; + if( bAutoWidth ) + nWidth = DEFAULT_FRAME_MIN_WIDTH; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), + rAppendContext.pLastParagraphProperties->Geth() > 0 ? + rAppendContext.pLastParagraphProperties->Geth() : + pStyleProperties->Geth() > 0 ? pStyleProperties->Geth() : DEFAULT_FRAME_MIN_HEIGHT)); + + sal_Int16 nhRule = sal_Int16( + rAppendContext.pLastParagraphProperties->GethRule() >= 0 ? + rAppendContext.pLastParagraphProperties->GethRule() : + pStyleProperties->GethRule()); + if ( nhRule < 0 ) + { + if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 || + pStyleProperties->GethRule() >= 0 ) + { + // [MS-OE376] Word uses a default value of "atLeast" for + // this attribute when the value of the h attribute is not 0. + nhRule = text::SizeType::MIN; + } + else + { + nhRule = text::SizeType::VARIABLE; + } + } + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX)); + + if (const std::optional<sal_Int16> nDirection = PopFrameDirection()) + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_FRM_DIRECTION), *nDirection)); + } + + sal_Int16 nHoriOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetxAlign() : + pStyleProperties->GetxAlign() >= 0 ? pStyleProperties->GetxAlign() : text::HoriOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient)); + + //set a non negative default value + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), + rAppendContext.pLastParagraphProperties->IsxValid() ? + rAppendContext.pLastParagraphProperties->Getx() : + pStyleProperties->IsxValid() ? pStyleProperties->Getx() : DEFAULT_VALUE)); + + //Default the anchor in case FramePr_hAnchor is missing ECMA 17.3.1.11 + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GethAnchor() : + pStyleProperties->GethAnchor() >=0 ? pStyleProperties->GethAnchor() : text::RelOrientation::FRAME ))); + + sal_Int16 nVertOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetyAlign() : + pStyleProperties->GetyAlign() >= 0 ? pStyleProperties->GetyAlign() : text::VertOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient)); + + //set a non negative default value + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), + rAppendContext.pLastParagraphProperties->IsyValid() ? + rAppendContext.pLastParagraphProperties->Gety() : + pStyleProperties->IsyValid() ? pStyleProperties->Gety() : DEFAULT_VALUE)); + + //Default the anchor in case FramePr_vAnchor is missing ECMA 17.3.1.11 + if (rAppendContext.pLastParagraphProperties->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE && + pStyleProperties->GetWrap() == text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE) + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvAnchor() : + pStyleProperties->GetvAnchor() >= 0 ? pStyleProperties->GetvAnchor() : text::RelOrientation::FRAME))); + } + else + { + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_RELATION), sal_Int16( + rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvAnchor() : + pStyleProperties->GetvAnchor() >= 0 ? pStyleProperties->GetvAnchor() : text::RelOrientation::PAGE_PRINT_AREA))); + } + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SURROUND), + rAppendContext.pLastParagraphProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE + ? rAppendContext.pLastParagraphProperties->GetWrap() + : pStyleProperties->GetWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE + ? pStyleProperties->GetWrap() + : text::WrapTextMode_NONE )); + + /** FDO#73546 : distL & distR should be unsigned integers <Ecma 20.4.3.6> + Swapped the array elements 11,12 & 13,14 since 11 & 12 are + LEFT & RIGHT margins and 13,14 are TOP and BOTTOM margins respectively. + */ + sal_Int32 nRightDist; + sal_Int32 nLeftDist = nRightDist = + rAppendContext.pLastParagraphProperties->GethSpace() >= 0 ? + rAppendContext.pLastParagraphProperties->GethSpace() : + pStyleProperties->GethSpace() >= 0 ? pStyleProperties->GethSpace() : 0; + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nLeftDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nRightDist)); + + sal_Int32 nBottomDist; + sal_Int32 nTopDist = nBottomDist = + rAppendContext.pLastParagraphProperties->GetvSpace() >= 0 ? + rAppendContext.pLastParagraphProperties->GetvSpace() : + pStyleProperties->GetvSpace() >= 0 ? pStyleProperties->GetvSpace() : 0; + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nTopDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nBottomDist)); + // If there is no fill, the Word default is 100% transparency. + // Otherwise CellColorHandler has priority, and this setting + // will be ignored. + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BACK_COLOR_TRANSPARENCY), sal_Int32(100))); + + uno::Sequence<beans::PropertyValue> aGrabBag( comphelper::InitPropertySequence({ + { "ParaFrameProperties", uno::Any(rAppendContext.pLastParagraphProperties->IsFrameMode()) } + })); + aFrameProperties.push_back(comphelper::makePropertyValue("FrameInteropGrabBag", aGrabBag)); + + lcl_MoveBorderPropertiesToFrame(aFrameProperties, + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange()); + } + else + { + sal_Int32 nWidth = rAppendContext.pLastParagraphProperties->Getw(); + bool bAutoWidth = nWidth < 1; + if( bAutoWidth ) + nWidth = DEFAULT_FRAME_MIN_WIDTH; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH), nWidth)); + + sal_Int16 nhRule = sal_Int16(rAppendContext.pLastParagraphProperties->GethRule()); + if ( nhRule < 0 ) + { + if ( rAppendContext.pLastParagraphProperties->Geth() >= 0 ) + { + // [MS-OE376] Word uses a default value of atLeast for + // this attribute when the value of the h attribute is not 0. + nhRule = text::SizeType::MIN; + } + else + { + nhRule = text::SizeType::VARIABLE; + } + } + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), nhRule)); + + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), bAutoWidth ? text::SizeType::MIN : text::SizeType::FIX)); + + sal_Int16 nHoriOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetxAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetxAlign() : + text::HoriOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), nHoriOrient)); + + sal_Int16 nVertOrient = sal_Int16( + rAppendContext.pLastParagraphProperties->GetyAlign() >= 0 ? + rAppendContext.pLastParagraphProperties->GetyAlign() : + text::VertOrientation::NONE ); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), nVertOrient)); + + sal_Int32 nVertDist = rAppendContext.pLastParagraphProperties->GethSpace(); + if( nVertDist < 0 ) + nVertDist = 0; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LEFT_MARGIN), nVertOrient == text::VertOrientation::TOP ? 0 : nVertDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_RIGHT_MARGIN), nVertOrient == text::VertOrientation::BOTTOM ? 0 : nVertDist)); + + sal_Int32 nHoriDist = rAppendContext.pLastParagraphProperties->GetvSpace(); + if( nHoriDist < 0 ) + nHoriDist = 0; + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_TOP_MARGIN), nHoriOrient == text::HoriOrientation::LEFT ? 0 : nHoriDist)); + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_BOTTOM_MARGIN), nHoriOrient == text::HoriOrientation::RIGHT ? 0 : nHoriDist)); + + if( rAppendContext.pLastParagraphProperties->Geth() > 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEIGHT), rAppendContext.pLastParagraphProperties->Geth())); + + if( rAppendContext.pLastParagraphProperties->IsxValid() ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Getx())); + + if( rAppendContext.pLastParagraphProperties->GethAnchor() >= 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue("HoriOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GethAnchor()))); + + if( rAppendContext.pLastParagraphProperties->IsyValid() ) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT_POSITION), rAppendContext.pLastParagraphProperties->Gety())); + + if( rAppendContext.pLastParagraphProperties->GetvAnchor() >= 0 ) + aFrameProperties.push_back(comphelper::makePropertyValue("VertOrientRelation", sal_Int16(rAppendContext.pLastParagraphProperties->GetvAnchor()))); + + if( rAppendContext.pLastParagraphProperties->GetWrap() >= text::WrapTextMode_NONE ) + aFrameProperties.push_back(comphelper::makePropertyValue("Surround", rAppendContext.pLastParagraphProperties->GetWrap())); + + lcl_MoveBorderPropertiesToFrame(aFrameProperties, + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange()); + } + + //frame conversion has to be executed after table conversion + RegisterFrameConversion( + rAppendContext.pLastParagraphProperties->GetStartingRange(), + rAppendContext.pLastParagraphProperties->GetEndingRange(), + std::move(aFrameProperties) ); + } + catch( const uno::Exception& ) + { + } +} + +/// Check if the style or its parent has a list id, recursively. +static sal_Int32 lcl_getListId(const StyleSheetEntryPtr& rEntry, const StyleSheetTablePtr& rStyleTable, bool & rNumberingFromBaseStyle) +{ + const StyleSheetPropertyMap* pEntryProperties = rEntry->pProperties.get(); + if (!pEntryProperties) + return -1; + + sal_Int32 nListId = pEntryProperties->GetListId(); + // The style itself has a list id. + if (nListId >= 0) + return nListId; + + // The style has no parent. + if (rEntry->sBaseStyleIdentifier.isEmpty()) + return -1; + + const StyleSheetEntryPtr pParent = rStyleTable->FindStyleSheetByISTD(rEntry->sBaseStyleIdentifier); + // No such parent style or loop in the style hierarchy. + if (!pParent || pParent == rEntry) + return -1; + + rNumberingFromBaseStyle = true; + + return lcl_getListId(pParent, rStyleTable, rNumberingFromBaseStyle); +} + +/// Return the paragraph's list level (from styles, unless pParacontext is provided). +/// -1 indicates the level is not set anywhere. [In that case, with a numId, use 0 (level 1)] +/// 9 indicates that numbering should be at body level (aka disabled) - rarely used by MSWord. +/// 0-8 are the nine valid numbering levels. +sal_Int16 DomainMapper_Impl::GetListLevel(const StyleSheetEntryPtr& pEntry, + const PropertyMapPtr& pParaContext) +{ + sal_Int16 nListLevel = -1; + if (pParaContext) + { + // Deliberately ignore inherited PROP_NUMBERING_LEVEL. Only trust StyleSheetEntry for that. + std::optional<PropertyMap::Property> aLvl = pParaContext->getProperty(PROP_NUMBERING_LEVEL); + if (aLvl) + aLvl->second >>= nListLevel; + + if (nListLevel != -1) + return nListLevel; + } + + if (!pEntry) + return -1; + + const StyleSheetPropertyMap* pEntryProperties = pEntry->pProperties.get(); + if (!pEntryProperties) + return -1; + + nListLevel = pEntryProperties->GetListLevel(); + // The style itself has a list level. + if (nListLevel >= 0) + return nListLevel; + + // The style has no parent. + if (pEntry->sBaseStyleIdentifier.isEmpty()) + return -1; + + const StyleSheetEntryPtr pParent = GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + // No such parent style or loop in the style hierarchy. + if (!pParent || pParent == pEntry) + return -1; + + return GetListLevel(pParent); +} + +void DomainMapper_Impl::ValidateListLevel(const OUString& sStyleIdentifierD) +{ + StyleSheetEntryPtr pMyStyle = GetStyleSheetTable()->FindStyleSheetByISTD(sStyleIdentifierD); + if (!pMyStyle) + return; + + sal_Int8 nListLevel = GetListLevel(pMyStyle); + if (nListLevel < 0 || nListLevel >= WW_OUTLINE_MAX) + return; + + bool bDummy = false; + sal_Int16 nListId = lcl_getListId(pMyStyle, GetStyleSheetTable(), bDummy); + if (nListId < 1) + return; + + auto const pList(GetListTable()->GetList(nListId)); + if (!pList) + return; + + auto pLevel = pList->GetLevel(nListLevel); + if (!pLevel && pList->GetAbstractDefinition()) + pLevel = pList->GetAbstractDefinition()->GetLevel(nListLevel); + if (!pLevel) + return; + + if (!pLevel->GetParaStyle()) + { + // First come, first served, and it hasn't been claimed yet, so claim it now. + pLevel->SetParaStyle(pMyStyle); + } + else if (pLevel->GetParaStyle() != pMyStyle) + { + // This level is already used by another style, so prevent numbering via this style + // by setting to body level (9). + pMyStyle->pProperties->SetListLevel(WW_OUTLINE_MAX); + // WARNING: PROP_NUMBERING_LEVEL is now out of sync with GetListLevel() + } +} + +void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove, const bool bNoNumbering ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (!m_aFieldStack.empty()) + { + FieldContextPtr pFieldContext = m_aFieldStack.back(); + if (pFieldContext && !pFieldContext->IsCommandCompleted()) + { + std::vector<OUString> aCommandParts = pFieldContext->GetCommandParts(); + if (!aCommandParts.empty() && aCommandParts[0] == "IF") + { + // Conditional text field conditions don't support linebreaks in Writer. + return; + } + } + + if (pFieldContext && pFieldContext->IsCommandCompleted()) + { + if (pFieldContext->GetFieldId() == FIELD_IF) + { + // Conditional text fields can't contain newlines, finish the paragraph later. + FieldParagraph aFinish{pPropertyMap, bRemove}; + pFieldContext->GetParagraphsToFinish().push_back(aFinish); + return; + } + } + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("finishParagraph"); +#endif + + m_nLastTableCellParagraphDepth = m_nTableCellDepth; + ParagraphPropertyMap* pParaContext = dynamic_cast< ParagraphPropertyMap* >( pPropertyMap.get() ); + if (m_aTextAppendStack.empty()) + return; + TextAppendContext& rAppendContext = m_aTextAppendStack.top(); + uno::Reference< text::XTextAppend > xTextAppend(rAppendContext.xTextAppend); +#ifdef DBG_UTIL + TagLogger::getInstance().attribute("isTextAppend", sal_uInt32(xTextAppend.is())); +#endif + + const StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetCurrentParaStyleName() ); + OSL_ENSURE( pEntry, "no style sheet found" ); + const StyleSheetPropertyMap* pStyleSheetProperties = pEntry ? pEntry->pProperties.get() : nullptr; + sal_Int32 nListId = pParaContext ? pParaContext->GetListId() : -1; + bool isNumberingViaStyle(false); + bool isNumberingViaRule = nListId > -1; + if ( !bRemove && pStyleSheetProperties && pParaContext ) + { + bool bNumberingFromBaseStyle = false; + if (!isNumberingViaRule) + nListId = lcl_getListId(pEntry, GetStyleSheetTable(), bNumberingFromBaseStyle); + + //apply numbering level/style to paragraph if it was set at the style, but only if the paragraph itself + //does not specify the numbering + sal_Int16 nListLevel = GetListLevel(pEntry, pParaContext); + // Undefined listLevel with a valid numId is treated as a first level numbering. + if (nListLevel == -1 && nListId > (IsOOXMLImport() ? 0 : -1)) + nListLevel = 0; + + if (!bNoNumbering && nListLevel >= 0 && nListLevel < 9) + pParaContext->Insert( PROP_NUMBERING_LEVEL, uno::Any(nListLevel), false ); + + auto const pList(GetListTable()->GetList(nListId)); + if (pList && !pParaContext->isSet(PROP_NUMBERING_STYLE_NAME)) + { + // ListLevel 9 means Body Level/no numbering. + if (bNoNumbering || nListLevel == 9) + { + pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(OUString()), true); + pParaContext->Erase(PROP_NUMBERING_LEVEL); + } + else if ( !isNumberingViaRule ) + { + isNumberingViaStyle = true; + // Since LO7.0/tdf#131321 fixed the loss of numbering in styles, this OUGHT to be obsolete, + // but now other new/critical LO7.0 code expects it, and perhaps some corner cases still need it as well. + pParaContext->Insert(PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true); + } + else + { + // we have direct numbering, as well as paragraph-style numbering. + // Apply the style if it uses the same list as the direct numbering, + // otherwise the directly-applied-to-paragraph status will be lost, + // and the priority of the numbering-style-indents will be lowered. tdf#133000 + bool bDummy; + if (nListId == lcl_getListId(pEntry, GetStyleSheetTable(), bDummy)) + pParaContext->Insert( PROP_NUMBERING_STYLE_NAME, uno::Any(pList->GetStyleName()), true ); + } + } + + if ( isNumberingViaStyle ) + { + // When numbering is defined by the paragraph style, then the para-style indents have priority. + // But since import has just copied para-style's PROP_NUMBERING_STYLE_NAME directly onto the paragraph, + // the numbering indents now have the priority. + // So now import must also copy the para-style indents directly onto the paragraph to compensate. + std::optional<PropertyMap::Property> oProperty; + const StyleSheetEntryPtr pParent = (!pEntry->sBaseStyleIdentifier.isEmpty()) ? GetStyleSheetTable()->FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier) : nullptr; + const StyleSheetPropertyMap* pParentProperties = pParent ? pParent->pProperties.get() : nullptr; + if (!pEntry->sBaseStyleIdentifier.isEmpty()) + { + oProperty = pStyleSheetProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT); + if ( oProperty + // If the numbering comes from a base style, indent of the base style has also priority. + || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_FIRST_LINE_INDENT))) ) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, oProperty->second, /*bOverwrite=*/false); + } + oProperty = pStyleSheetProperties->getProperty(PROP_PARA_LEFT_MARGIN); + if ( oProperty + || (bNumberingFromBaseStyle && pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_LEFT_MARGIN))) ) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, oProperty->second, /*bOverwrite=*/false); + + // We're inheriting properties from a numbering style. Make sure a possible right margin is inherited from the base style. + sal_Int32 nParaRightMargin; + if ( pParentProperties && (oProperty = pParentProperties->getProperty(PROP_PARA_RIGHT_MARGIN)) && (nParaRightMargin = oProperty->second.get<sal_Int32>()) != 0 ) + { + // If we're setting the right margin, we should set the first / left margin as well from the numbering style. + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent"); + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt"); + if (nFirstLineIndent != 0) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + if (nParaLeftMargin != 0) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + + // Override right margin value with value from current style, if any + if (pStyleSheetProperties && pStyleSheetProperties->isSet(PROP_PARA_RIGHT_MARGIN)) + nParaRightMargin = pStyleSheetProperties->getProperty(PROP_PARA_RIGHT_MARGIN)->second.get<sal_Int32>(); + + pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, uno::Any(nParaRightMargin), /*bOverwrite=*/false); + } + } + // Paragraph style based right paragraph indentation affects not paragraph style based lists in DOCX. + // Apply it as direct formatting, also left and first line indentation of numbering to keep them. + else if (isNumberingViaRule) + { + uno::Any aRightMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN); + if ( aRightMargin != uno::Any() ) + { + pParaContext->Insert(PROP_PARA_RIGHT_MARGIN, aRightMargin, /*bOverwrite=*/false); + + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, nListLevel, "FirstLineIndent"); + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, nListLevel, "IndentAt"); + if (nFirstLineIndent != 0) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(nFirstLineIndent), /*bOverwrite=*/false); + if (nParaLeftMargin != 0) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin), /*bOverwrite=*/false); + } + } + + if (nListId == 0 && !pList) + { + // listid = 0 and no list definition is used in DOCX to stop numbering + // defined somewhere in parent styles + // And here we should explicitly set left margin and first-line margin. + // They can be taken from referred style, but not from styles with listid! + uno::Any aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_FIRST_LINE_INDENT, pEntry, m_pStyleSheetTable); + if (aProp.hasValue()) + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, aProp, false); + else + pParaContext->Insert(PROP_PARA_FIRST_LINE_INDENT, uno::Any(sal_uInt32(0)), false); + + aProp = lcl_GetPropertyFromParaStyleSheetNoNum(PROP_PARA_LEFT_MARGIN, pEntry, m_pStyleSheetTable); + if (aProp.hasValue()) + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, aProp, false); + else + pParaContext->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(sal_uInt32(0)), false); + } + } + + // apply AutoSpacing: it has priority over all other margin settings + // (note that numbering with autoSpacing is handled separately later on) + const bool bAllowAdjustments = !GetSettingsTable()->GetDoNotUseHTMLParagraphAutoSpacing(); + sal_Int32 nBeforeAutospacing = -1; + bool bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING); + const bool bNoTopmargin = pParaContext && !pParaContext->isSet(PROP_PARA_TOP_MARGIN); + // apply INHERITED autospacing only if top margin is not set + if ( bIsAutoSet || bNoTopmargin ) + { + GetAnyProperty(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, pPropertyMap) >>= nBeforeAutospacing; + // tdf#137655 only w:beforeAutospacing=0 was specified, but not PARA_TOP_MARGIN + // (see default_spacing = -1 in processing of LN_CT_Spacing_beforeAutospacing) + if ( bNoTopmargin && nBeforeAutospacing == ConversionHelper::convertTwipToMM100(-1) ) + { + sal_Int32 nStyleAuto = -1; + GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING) >>= nStyleAuto; + if (nStyleAuto > 0) + nBeforeAutospacing = 0; + } + } + if ( nBeforeAutospacing > -1 && pParaContext ) + { + if (bAllowAdjustments) + { + if ( GetIsFirstParagraphInShape() || + (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) || + (m_bFirstParagraphInCell && m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth) ) + { + // export requires grabbag to match top_margin, so keep them in sync + if (nBeforeAutospacing && bIsAutoSet) + pParaContext->Insert( PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING, uno::Any( sal_Int32(0) ),true, PARA_GRAB_BAG ); + nBeforeAutospacing = 0; + } + } + pParaContext->Insert(PROP_PARA_TOP_MARGIN, uno::Any(nBeforeAutospacing)); + } + + sal_Int32 nAfterAutospacing = -1; + bIsAutoSet = pParaContext && pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING); + const bool bNoBottomMargin = pParaContext && !pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN); + bool bAppliedBottomAutospacing = false; + if (bIsAutoSet || bNoBottomMargin) + { + GetAnyProperty(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING, pPropertyMap) >>= nAfterAutospacing; + if (bNoBottomMargin && nAfterAutospacing == ConversionHelper::convertTwipToMM100(-1)) + { + sal_Int32 nStyleAuto = -1; + GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING) >>= nStyleAuto; + if (nStyleAuto > 0) + nAfterAutospacing = 0; + } + } + if ( nAfterAutospacing > -1 && pParaContext ) + { + pParaContext->Insert(PROP_PARA_BOTTOM_MARGIN, uno::Any(nAfterAutospacing)); + bAppliedBottomAutospacing = bAllowAdjustments; + } + + // tell TableManager to reset the bottom margin if it determines that this is the cell's last paragraph. + if ( hasTableManager() && getTableManager().isInCell() ) + getTableManager().setCellLastParaAfterAutospacing(bAppliedBottomAutospacing); + + if (xTextAppend.is() && pParaContext && hasTableManager() && !getTableManager().isIgnore()) + { + try + { + /*the following combinations of previous and current frame settings can occur: + (1) - no old frame and no current frame -> no special action + (2) - no old frame and current DropCap -> save DropCap for later use, don't call finishParagraph + remove character properties of the DropCap? + (3) - no old frame and current Frame -> save Frame for later use + (4) - old DropCap and no current frame -> add DropCap to the properties of the finished paragraph, delete previous setting + (5) - old DropCap and current frame -> add DropCap to the properties of the finished paragraph, save current frame settings + (6) - old Frame and new DropCap -> add old Frame, save DropCap for later use + (7) - old Frame and new same Frame -> continue + (8) - old Frame and new different Frame -> add old Frame, save new Frame for later use + (9) - old Frame and no current frame -> add old Frame, delete previous settings + + old _and_ new DropCap must not occur + */ + + bool bIsDropCap = + pParaContext->IsFrameMode() && + sal::static_int_cast<Id>(pParaContext->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none; + + style::DropCapFormat aDrop; + ParagraphPropertiesPtr pToBeSavedProperties; + bool bKeepLastParagraphProperties = false; + if( bIsDropCap ) + { + uno::Reference<text::XParagraphCursor> xParaCursor( + xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW); + //select paragraph + xParaCursor->gotoStartOfParagraph( true ); + uno::Reference< beans::XPropertyState > xParaProperties( xParaCursor, uno::UNO_QUERY_THROW ); + xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT)); + xParaProperties->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT)); + //handles (2) and part of (6) + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + sal_Int32 nCount = xParaCursor->getString().getLength(); + pToBeSavedProperties->SetDropCapLength(nCount > 0 && nCount < 255 ? static_cast<sal_Int8>(nCount) : 1); + } + if( rAppendContext.pLastParagraphProperties ) + { + if( sal::static_int_cast<Id>(rAppendContext.pLastParagraphProperties->GetDropCap()) != NS_ooxml::LN_Value_doc_ST_DropCap_none) + { + //handles (4) and part of (5) + //create a DropCap property, add it to the property sequence of finishParagraph + sal_Int32 nLines = rAppendContext.pLastParagraphProperties->GetLines(); + aDrop.Lines = nLines > 0 && nLines < SAL_MAX_INT8 ? static_cast<sal_Int8>(nLines) : 2; + aDrop.Count = rAppendContext.pLastParagraphProperties->GetDropCapLength(); + sal_Int32 nHSpace = rAppendContext.pLastParagraphProperties->GethSpace(); + aDrop.Distance = nHSpace > 0 && nHSpace < SAL_MAX_INT16 ? static_cast<sal_Int16>(nHSpace) : 0; + //completes (5) + if( pParaContext->IsFrameMode() ) + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + } + else if(*rAppendContext.pLastParagraphProperties == *pParaContext ) + { + //handles (7) + rAppendContext.pLastParagraphProperties->SetEndingRange(rAppendContext.xInsertPosition.is() ? rAppendContext.xInsertPosition : xTextAppend->getEnd()); + bKeepLastParagraphProperties = true; + } + else + { + //handles (8)(9) and completes (6) + CheckUnregisteredFrameConversion( ); + + // If different frame properties are set on this paragraph, keep them. + if ( !bIsDropCap && pParaContext->IsFrameMode() ) + { + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext); + } + } + } + else + { + // (1) doesn't need handling + + if( !bIsDropCap && pParaContext->IsFrameMode() ) + { + pToBeSavedProperties = new ParagraphProperties(*pParaContext); + lcl_AddRangeAndStyle(pToBeSavedProperties, xTextAppend, pPropertyMap, rAppendContext); + } + } + std::vector<beans::PropertyValue> aProperties; + if (pPropertyMap) + { + aProperties = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pPropertyMap->GetPropertyValues()); + } + if (pPropertyMap) + { + // tdf#64222 filter out the "paragraph marker" formatting and + // set it as a separate paragraph property, not a empty hint at + // end of paragraph + std::vector<beans::NamedValue> charProperties; + for (auto it = aProperties.begin(); it != aProperties.end(); ) + { + // this condition isn't ideal but as it happens all + // RES_CHRATR_* have names that start with "Char" + if (it->Name.startsWith("Char")) + { + charProperties.emplace_back(it->Name, it->Value); + // as testN793262 demonstrates, font size in rPr must + // affect the paragraph size => also insert empty hint! +// it = aProperties.erase(it); + } + ++it; + } + if (!charProperties.empty()) + { + aProperties.push_back(beans::PropertyValue("ListAutoFormat", + 0, uno::Any(comphelper::containerToSequence(charProperties)), beans::PropertyState_DIRECT_VALUE)); + } + } + if( !bIsDropCap ) + { + if( aDrop.Lines > 1 ) + { + beans::PropertyValue aValue; + aValue.Name = getPropertyName(PROP_DROP_CAP_FORMAT); + aValue.Value <<= aDrop; + aProperties.push_back(aValue); + } + uno::Reference< text::XTextRange > xTextRange; + if (rAppendContext.xInsertPosition.is()) + { + xTextRange = xTextAppend->finishParagraphInsert( comphelper::containerToSequence(aProperties), rAppendContext.xInsertPosition ); + rAppendContext.xCursor->gotoNextParagraph(false); + if (rAppendContext.pLastParagraphProperties) + rAppendContext.pLastParagraphProperties->SetEndingRange(xTextRange->getEnd()); + } + else + { + uno::Reference<text::XTextCursor> xCursor; + if (m_bParaHadField && !m_bIsInComments && !xTOCMarkerCursor.is()) + { + // Workaround to make sure char props of the field are not lost. + // Not relevant for editeng-based comments. + // Not relevant for fields inside a TOC field. + xCursor = xTextAppend->getText()->createTextCursor(); + if (xCursor.is()) + xCursor->gotoEnd(false); + PropertyMapPtr pEmpty(new PropertyMap()); + appendTextPortion("X", pEmpty); + } + + // Check if top / bottom margin has to be updated, now that we know the numbering status of both the previous and + // the current text node. + auto itNumberingRules = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "NumberingRules"; + }); + + assert( isNumberingViaRule == (itNumberingRules != aProperties.end()) ); + isNumberingViaRule = (itNumberingRules != aProperties.end()); + if (m_xPreviousParagraph.is() && (isNumberingViaRule || isNumberingViaStyle)) + { + // This textnode has numbering. Look up the numbering style name of the current and previous paragraph. + OUString aCurrentNumberingName; + OUString aPreviousNumberingName; + if (isNumberingViaRule) + { + assert(itNumberingRules != aProperties.end() && "by definition itNumberingRules is valid if isNumberingViaRule is true"); + uno::Reference<container::XNamed> xCurrentNumberingRules(itNumberingRules->Value, uno::UNO_QUERY); + if (xCurrentNumberingRules.is()) + aCurrentNumberingName = xCurrentNumberingRules->getName(); + if (m_xPreviousParagraph.is()) + { + uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if (xPreviousNumberingRules.is()) + aPreviousNumberingName = xPreviousNumberingRules->getName(); + } + } + else if ( m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName") && + // don't update before tables + (m_nTableDepth == 0 || !m_bFirstParagraphInCell)) + { + aCurrentNumberingName = GetListStyleName(nListId); + m_xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName; + } + + if (!aPreviousNumberingName.isEmpty() && aCurrentNumberingName == aPreviousNumberingName) + { + uno::Sequence<beans::PropertyValue> aPrevPropertiesSeq; + m_xPreviousParagraph->getPropertyValue("ParaInteropGrabBag") >>= aPrevPropertiesSeq; + const auto & rPrevProperties = aPrevPropertiesSeq; + bool bParaAutoBefore = m_bParaAutoBefore || std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaTopMarginBeforeAutoSpacing"; + }); + // if style based spacing was set to auto in the previous paragraph, style of the actual paragraph must be the same + if (bParaAutoBefore && !m_bParaAutoBefore && m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ParaStyleName")) + { + auto itParaStyle = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaStyleName"; + }); + bParaAutoBefore = itParaStyle != aProperties.end() && + m_xPreviousParagraph->getPropertyValue("ParaStyleName") == itParaStyle->Value; + } + // There was a previous textnode and it had the same numbering. + if (bParaAutoBefore) + { + // This before spacing is set to auto, set before space to 0. + auto itParaTopMargin = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaTopMargin"; + }); + if (itParaTopMargin != aProperties.end()) + itParaTopMargin->Value <<= static_cast<sal_Int32>(0); + else + aProperties.push_back(comphelper::makePropertyValue("ParaTopMargin", static_cast<sal_Int32>(0))); + } + + bool bPrevParaAutoAfter = std::any_of(rPrevProperties.begin(), rPrevProperties.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "ParaBottomMarginAfterAutoSpacing"; + }); + if (bPrevParaAutoAfter) + { + // Previous after spacing is set to auto, set previous after space to 0. + m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0))); + } + } + } + + // apply redlines for inline images + if (IsParaWithInlineObject()) + { + for (const auto& rAnchored : rAppendContext.m_aAnchoredObjects) + { + // process only inline objects with redlining + if (!rAnchored.m_xRedlineForInline) + continue; + + // select the inline image and set its redline + auto xAnchorRange = rAnchored.m_xAnchoredObject->getAnchor(); + uno::Reference< text::XTextCursor > xCursorOnImage = + xAnchorRange->getText()->createTextCursorByRange(xAnchorRange); + xCursorOnImage->goRight(1, true); + CreateRedline( xCursorOnImage, rAnchored.m_xRedlineForInline ); + } + } + + xTextRange = xTextAppend->finishParagraph( comphelper::containerToSequence(aProperties) ); + m_xPreviousParagraph.set(xTextRange, uno::UNO_QUERY); + + if (m_xPreviousParagraph.is() && // null for SvxUnoTextBase + (isNumberingViaStyle || isNumberingViaRule)) + { + assert(pParaContext); + if (ListDef::Pointer const& pList = m_pListTable->GetList(nListId)) + { // styles could refer to non-existing lists... + AbstractListDef::Pointer const& pAbsList = + pList->GetAbstractDefinition(); + if (pAbsList && + // SvxUnoTextRange doesn't have ListId + m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("ListId")) + { + OUString paraId; + m_xPreviousParagraph->getPropertyValue("ListId") >>= paraId; + if (!paraId.isEmpty()) // must be on some list? + { + OUString const listId = pAbsList->MapListId(paraId); + if (listId != paraId) + { + m_xPreviousParagraph->setPropertyValue("ListId", uno::Any(listId)); + } + } + } + + sal_Int16 nCurrentLevel = GetListLevel(pEntry, pPropertyMap); + if (nCurrentLevel == -1) + nCurrentLevel = 0; + + const ListLevel::Pointer pListLevel = pList->GetLevel(nCurrentLevel); + if (pListLevel) + { + sal_Int16 nOverrideLevel = pListLevel->GetStartOverride(); + if (nOverrideLevel != -1 && m_aListOverrideApplied.find(nListId) == m_aListOverrideApplied.end()) + { + // Apply override: we have override instruction for this level + // And this was not done for this list before: we can do this only once on first occurrence + // of list with override + // TODO: Not tested variant with different levels override in different lists. + // Probably m_aListOverrideApplied as a set of overridden listids is not sufficient + // and we need to register level overrides separately. + m_xPreviousParagraph->setPropertyValue("ParaIsNumberingRestart", uno::Any(true)); + m_xPreviousParagraph->setPropertyValue("NumberingStartValue", uno::Any(nOverrideLevel)); + m_aListOverrideApplied.insert(nListId); + } + } + } + } + + if (!rAppendContext.m_aAnchoredObjects.empty() && !IsInHeaderFooter()) + { + // Remember what objects are anchored to this paragraph. + // That list is only used for Word compat purposes, and + // it is only relevant for body text. + AnchoredObjectsInfo aInfo; + aInfo.m_xParagraph = xTextRange; + aInfo.m_aAnchoredObjects = rAppendContext.m_aAnchoredObjects; + m_aAnchoredObjectAnchors.push_back(aInfo); + rAppendContext.m_aAnchoredObjects.clear(); + } + + // We're no longer right after a table conversion. + m_bConvertedTable = false; + + if (xCursor.is()) + { + xCursor->goLeft(1, true); + xCursor->setString(OUString()); + } + } + getTableManager( ).handle(xTextRange); + m_aSmartTagHandler.handle(xTextRange); + + if (xTextRange.is()) + { + // Get the end of paragraph character inserted + uno::Reference< text::XTextCursor > xCur = xTextRange->getText( )->createTextCursor( ); + if (rAppendContext.xInsertPosition.is()) + xCur->gotoRange( rAppendContext.xInsertPosition, false ); + else + xCur->gotoEnd( false ); + + // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode + sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode(); + if ( m_nTableDepth > 0 && nMode > 0 && nMode <= 14 ) + { + // skip new line + xCur->goLeft(1, false); + while ( xCur->goLeft(1, true) ) + { + OUString sChar = xCur->getString(); + if ( sChar == " " || sChar == "\t" || sChar == OUStringChar(u'\x00A0') ) + xCur->setString(""); + else + break; + } + + if (rAppendContext.xInsertPosition.is()) + xCur->gotoRange(rAppendContext.xInsertPosition, false); + else + xCur->gotoEnd(false); + } + + xCur->goLeft( 1 , true ); + // Extend the redline ranges for empty paragraphs + if ( !m_bParaChanged && m_previousRedline ) + CreateRedline( xCur, m_previousRedline ); + CheckParaMarkerRedline( xCur ); + } + + css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY); + + // table style precedence and not hidden shapes anchored to hidden empty table paragraphs + if (xParaProps && (m_nTableDepth > 0 || !m_aAnchoredObjectAnchors.empty()) ) + { + // table style has got bigger precedence than docDefault style + // collect these pending paragraph properties to process in endTable() + uno::Reference<text::XTextCursor> xCur = xTextRange->getText( )->createTextCursor( ); + xCur->gotoEnd(false); + xCur->goLeft(1, false); + uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur); + uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW); + xParaCursor->gotoStartOfParagraph(false); + if (m_nTableDepth > 0) + { + TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps}; + getTableManager().getCurrentParagraphs()->push_back(aPending); + } + + // hidden empty paragraph with a not hidden shape, set as not hidden + std::optional<PropertyMap::Property> pHidden; + if ( !m_aAnchoredObjectAnchors.empty() && (pHidden = pParaContext->getProperty(PROP_CHAR_HIDDEN)) ) + { + bool bIsHidden = {}; // -Werror=maybe-uninitialized + pHidden->second >>= bIsHidden; + if (bIsHidden) + { + bIsHidden = false; + pHidden = GetTopContext()->getProperty(PROP_CHAR_HIDDEN); + if (pHidden) + pHidden->second >>= bIsHidden; + if (!bIsHidden) + { + uno::Reference<text::XTextCursor> xCur3 = xTextRange->getText()->createTextCursorByRange(xParaCursor); + xCur3->goRight(1, true); + if (xCur3->getString() == SAL_NEWLINE_STRING) + { + uno::Reference< beans::XPropertySet > xProp( xCur3, uno::UNO_QUERY ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HIDDEN), uno::Any(false)); + } + } + } + } + } + + // tdf#118521 set paragraph top or bottom margin based on the paragraph style + // if we already set the other margin with direct formatting + if (xParaProps) + { + const bool bTopSet = pParaContext->isSet(PROP_PARA_TOP_MARGIN); + const bool bBottomSet = pParaContext->isSet(PROP_PARA_BOTTOM_MARGIN); + const bool bContextSet = pParaContext->isSet(PROP_PARA_CONTEXT_MARGIN); + if ( bTopSet != bBottomSet || bBottomSet != bContextSet ) + { + + if ( !bTopSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_TOP_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaTopMargin", aMargin); + } + if ( !bBottomSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_BOTTOM_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaBottomMargin", aMargin); + } + if ( !bContextSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_CONTEXT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaContextMargin", aMargin); + } + } + } + + // Left, Right, and Hanging settings are also grouped. Ensure that all or none are set. + if (xParaProps) + { + const bool bLeftSet = pParaContext->isSet(PROP_PARA_LEFT_MARGIN); + const bool bRightSet = pParaContext->isSet(PROP_PARA_RIGHT_MARGIN); + const bool bFirstSet = pParaContext->isSet(PROP_PARA_FIRST_LINE_INDENT); + if (bLeftSet != bRightSet || bRightSet != bFirstSet) + { + if ( !bLeftSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_LEFT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaLeftMargin", aMargin); + else if (isNumberingViaStyle) + { + const sal_Int32 nParaLeftMargin = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "IndentAt"); + if (nParaLeftMargin != 0) + xParaProps->setPropertyValue("ParaLeftMargin", uno::Any(nParaLeftMargin)); + } + } + if ( !bRightSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_RIGHT_MARGIN); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaRightMargin", aMargin); + } + if ( !bFirstSet ) + { + uno::Any aMargin = GetPropertyFromParaStyleSheet(PROP_PARA_FIRST_LINE_INDENT); + if ( aMargin != uno::Any() ) + xParaProps->setPropertyValue("ParaFirstLineIndent", aMargin); + else if (isNumberingViaStyle) + { + const sal_Int32 nFirstLineIndent = getNumberingProperty(nListId, GetListLevel(pEntry, pPropertyMap), "FirstLineIndent"); + if (nFirstLineIndent != 0) + xParaProps->setPropertyValue("ParaFirstLineIndent", uno::Any(nFirstLineIndent)); + } + } + } + } + + // fix table paragraph properties + if ( xTextRange.is() && xParaProps && m_nTableDepth > 0 ) + { + // tdf#128959 table paragraphs haven't got window and orphan controls + uno::Any aAny(static_cast<sal_Int8>(0)); + xParaProps->setPropertyValue("ParaOrphans", aAny); + xParaProps->setPropertyValue("ParaWidows", aAny); + } + } + if( !bKeepLastParagraphProperties ) + rAppendContext.pLastParagraphProperties = pToBeSavedProperties; + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::finishParagraph" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "finishParagraph()" ); + } + + } + + bool bIgnoreFrameState = IsInHeaderFooter(); + if( (!bIgnoreFrameState && pParaContext && pParaContext->IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) ) + SetIsPreviousParagraphFramed(true); + else + SetIsPreviousParagraphFramed(false); + + m_bRemoveThisParagraph = false; + if( !IsInHeaderFooter() && !IsInShape() && (!pParaContext || !pParaContext->IsFrameMode()) ) + { // If the paragraph is in a frame, shape or header/footer, it's not a paragraph of the section itself. + SetIsFirstParagraphInSection(false); + // don't count an empty deleted paragraph as first paragraph in section to avoid of + // the deletion of the next empty paragraph later, resulting loss of the associated page break + if (!m_previousRedline || m_bParaChanged) + { + SetIsFirstParagraphInSectionAfterRedline(false); + SetIsLastParagraphInSection(false); + } + } + m_previousRedline.clear(); + m_bParaChanged = false; + + if (m_bIsInComments && pParaContext) + { + if (const OUString sParaId = pParaContext->GetParaId(); !sParaId.isEmpty()) + { + if (const auto& item = m_aCommentProps.find(sParaId); item != m_aCommentProps.end()) + { + m_bAnnotationResolved = item->second.bDone; + } + } + } + + if (m_bIsFirstParaInShape) + m_bIsFirstParaInShape = false; + + if (pParaContext) + { + // Reset the frame properties for the next paragraph + pParaContext->ResetFrameProperties(); + } + + SetIsOutsideAParagraph(true); + m_bParaHadField = false; + + // don't overwrite m_bFirstParagraphInCell in table separator nodes + // and in text boxes anchored to the first paragraph of table cells + if (m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth && !IsInShape()) + m_bFirstParagraphInCell = false; + + m_bParaAutoBefore = false; + m_bParaWithInlineObject = false; + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + +} + +void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap ) +{ + if (m_bDiscardHeaderFooter) + return; + + if (m_aTextAppendStack.empty()) + return; + // Before placing call to processDeferredCharacterProperties(), TopContextType should be CONTEXT_CHARACTER + // processDeferredCharacterProperties() invokes only if character inserted + if( pPropertyMap == m_pTopContext && !deferredCharacterProperties.empty() && (GetTopContextType() == CONTEXT_CHARACTER) ) + processDeferredCharacterProperties(); + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is() || !hasTableManager() || getTableManager().isIgnore()) + return; + + try + { + // If we are in comments, then disable CharGrabBag, comment text doesn't support that. + uno::Sequence< beans::PropertyValue > aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!m_bIsInComments); + + if (m_bStartTOC || m_bStartIndex || m_bStartBibliography) + for( auto& rValue : asNonConstRange(aValues) ) + { + if (rValue.Name == "CharHidden") + rValue.Value <<= false; + } + + // remove workaround for change tracked images, if they are part of a redline, + // i.e. if the next run is a tracked change with the same type, author and date, + // as in the change tracking of the image. + if ( m_bRedlineImageInPreviousRun ) + { + auto pCurrentRedline = m_aRedlines.top().size() > 0 + ? m_aRedlines.top().back() + : GetTopContextOfType(CONTEXT_CHARACTER) && + GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0 + ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back() + : nullptr; + if ( m_previousRedline && pCurrentRedline && + // same redline + (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) && + m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor && + m_previousRedline->m_sDate == pCurrentRedline->m_sDate ) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); + assert(xCursor.is()); + xCursor->gotoEnd(false); + xCursor->goLeft(2, true); + if ( xCursor->getString() == u"" ) + { + xCursor->goRight(1, true); + xCursor->setString(""); + xCursor->gotoEnd(false); + xCursor->goLeft(1, true); + xCursor->setString(""); + } + } + + m_bRedlineImageInPreviousRun = false; + } + + uno::Reference< text::XTextRange > xTextRange; + if (m_aTextAppendStack.top().xInsertPosition.is()) + { + xTextRange = xTextAppend->insertTextPortion(rString, aValues, m_aTextAppendStack.top().xInsertPosition); + m_aTextAppendStack.top().xCursor->gotoRange(xTextRange->getEnd(), true); + } + else + { + if (m_bStartTOC || m_bStartIndex || m_bStartBibliography || m_nStartGenericField != 0) + { + if (IsInHeaderFooter() && !m_bStartTOCHeaderFooter) + { + xTextRange = xTextAppend->appendTextPortion(rString, aValues); + } + else + { + m_bStartedTOC = true; + uno::Reference< text::XTextCursor > xTOCTextCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); + assert(xTOCTextCursor.is()); + xTOCTextCursor->gotoEnd(false); + if (m_nStartGenericField != 0) + { + xTOCTextCursor->goLeft(1, false); + } + xTextRange = xTextAppend->insertTextPortion(rString, aValues, xTOCTextCursor); + SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed"); + if (!xTextRange.is()) + throw uno::Exception("insertTextPortion failed", nullptr); + m_bTextInserted = true; + xTOCTextCursor->gotoRange(xTextRange->getEnd(), true); + if (m_nStartGenericField == 0) + { + m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor)); + } + } + } + else + { +#if !defined(MACOSX) // TODO: check layout differences and support all platforms, if needed + sal_Int32 nPos = 0; + OUString sFontName; + OUString sDoubleSpace(" "); + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER); + // tdf#123703 workaround for longer space sequences of the old or compatible RTF documents + if (GetSettingsTable()->GetLongerSpaceSequence() && !IsOpenFieldCommand() && (nPos = rString.indexOf(sDoubleSpace)) != -1 && + // monospaced fonts have no longer space sequences, regardless of \fprq2 (not monospaced) font setting + // fix for the base monospaced font Courier + (!pContext || !pContext->isSet(PROP_CHAR_FONT_NAME) || + ((pContext->getProperty(PROP_CHAR_FONT_NAME)->second >>= sFontName) && sFontName.indexOf("Courier") == -1))) + { + // an RTF space character is longer by an extra six-em-space in an old-style RTF space sequence, + // insert them to keep RTF document layout formatted by consecutive spaces + const sal_Unicode aExtraSpace[5] = { 0x2006, 0x20, 0x2006, 0x20, 0 }; + const sal_Unicode aExtraSpace2[4] = { 0x20, 0x2006, 0x20, 0 }; + xTextRange = xTextAppend->appendTextPortion(rString.replaceAll(sDoubleSpace, aExtraSpace, nPos) + .replaceAll(sDoubleSpace, aExtraSpace2, nPos), aValues); + } + else +#endif + xTextRange = xTextAppend->appendTextPortion(rString, aValues); + } + } + + // reset moveFrom/moveTo data of non-terminating runs of the paragraph + if ( m_pParaMarkerRedlineMove ) + { + m_pParaMarkerRedlineMove.clear(); + } + CheckRedline( xTextRange ); + m_bParaChanged = true; + + //getTableManager( ).handle(xTextRange); + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "DomainMapper_Impl::appendTextPortion" ); + } +} + +void DomainMapper_Impl::appendTextContent( + const uno::Reference< text::XTextContent >& xContent, + const uno::Sequence< beans::PropertyValue >& xPropertyValues + ) +{ + SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text append stack"); + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY ); + OSL_ENSURE( xTextAppendAndConvert.is(), "trying to append a text content without XTextAppendAndConvert" ); + if (!xTextAppendAndConvert.is() || !hasTableManager() || getTableManager().isIgnore()) + return; + + try + { + if (m_aTextAppendStack.top().xInsertPosition.is()) + xTextAppendAndConvert->insertTextContentWithProperties( xContent, xPropertyValues, m_aTextAppendStack.top().xInsertPosition ); + else + xTextAppendAndConvert->appendTextContent( xContent, xPropertyValues ); + } + catch(const lang::IllegalArgumentException&) + { + } + catch(const uno::Exception&) + { + } +} + +void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOLEHandler ) +{ + try + { + uno::Reference< text::XTextContent > xOLE( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xOLEProperties(xOLE, uno::UNO_QUERY_THROW); + + OUString aCLSID = pOLEHandler->getCLSID(); + if (aCLSID.isEmpty()) + xOLEProperties->setPropertyValue(getPropertyName( PROP_STREAM_NAME ), + uno::Any( rStreamName )); + else + xOLEProperties->setPropertyValue("CLSID", uno::Any(aCLSID)); + + OUString aDrawAspect = pOLEHandler->GetDrawAspect(); + if(!aDrawAspect.isEmpty()) + xOLEProperties->setPropertyValue("DrawAspect", uno::Any(aDrawAspect)); + + awt::Size aSize = pOLEHandler->getSize(); + if( !aSize.Width ) + aSize.Width = 1000; + if( !aSize.Height ) + aSize.Height = 1000; + xOLEProperties->setPropertyValue(getPropertyName( PROP_WIDTH ), + uno::Any(aSize.Width)); + xOLEProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ), + uno::Any(aSize.Height)); + + OUString aVisAreaWidth = pOLEHandler->GetVisAreaWidth(); + if(!aVisAreaWidth.isEmpty()) + xOLEProperties->setPropertyValue("VisibleAreaWidth", uno::Any(aVisAreaWidth)); + + OUString aVisAreaHeight = pOLEHandler->GetVisAreaHeight(); + if(!aVisAreaHeight.isEmpty()) + xOLEProperties->setPropertyValue("VisibleAreaHeight", uno::Any(aVisAreaHeight)); + + uno::Reference< graphic::XGraphic > xGraphic = pOLEHandler->getReplacement(); + xOLEProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC ), + uno::Any(xGraphic)); + uno::Reference<beans::XPropertySet> xReplacementProperties(pOLEHandler->getShape(), uno::UNO_QUERY); + if (xReplacementProperties.is()) + { + table::BorderLine2 aBorderProps; + xReplacementProperties->getPropertyValue("LineColor") >>= aBorderProps.Color; + xReplacementProperties->getPropertyValue("LineWidth") >>= aBorderProps.LineWidth; + xReplacementProperties->getPropertyValue("LineStyle") >>= aBorderProps.LineStyle; + + if (aBorderProps.LineStyle) // Set line props only if LineStyle is set + { + xOLEProperties->setPropertyValue("RightBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("TopBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("LeftBorder", uno::Any(aBorderProps)); + xOLEProperties->setPropertyValue("BottomBorder", uno::Any(aBorderProps)); + } + OUString pProperties[] = { + "AnchorType", + "Surround", + "SurroundContour", + "HoriOrient", + "HoriOrientPosition", + "VertOrient", + "VertOrientPosition", + "VertOrientRelation", + "HoriOrientRelation", + "LeftMargin", + "RightMargin", + "TopMargin", + "BottomMargin" + }; + for (const OUString& s : pProperties) + { + const uno::Any aVal = xReplacementProperties->getPropertyValue(s); + xOLEProperties->setPropertyValue(s, aVal); + } + + if (xReplacementProperties->getPropertyValue("FillStyle").get<css::drawing::FillStyle>() + != css::drawing::FillStyle::FillStyle_NONE) // Apply fill props if style is set + { + xOLEProperties->setPropertyValue( + "FillStyle", xReplacementProperties->getPropertyValue("FillStyle")); + xOLEProperties->setPropertyValue( + "FillColor", xReplacementProperties->getPropertyValue("FillColor")); + xOLEProperties->setPropertyValue( + "FillColor2", xReplacementProperties->getPropertyValue("FillColor2")); + } + } + else + // mimic the treatment of graphics here... it seems anchoring as character + // gives a better ( visually ) result + xOLEProperties->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ), uno::Any( text::TextContentAnchorType_AS_CHARACTER ) ); + // remove ( if valid ) associated shape ( used for graphic replacement ) + SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack"); + if (!m_aAnchoredStack.empty()) + m_aAnchoredStack.top( ).bToRemove = true; + RemoveLastParagraph(); + SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack"); + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + + appendTextContent( xOLE, uno::Sequence< beans::PropertyValue >() ); + + if (!aCLSID.isEmpty()) + pOLEHandler->importStream(m_xComponentContext, GetTextDocument(), xOLE); + + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of OLE object" ); + } + +} + +void DomainMapper_Impl::appendStarMath( const Value& val ) +{ + uno::Reference< embed::XEmbeddedObject > formula; + val.getAny() >>= formula; + if( !formula.is() ) + return; + + try + { + uno::Reference< text::XTextContent > xStarMath( m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xStarMathProperties(xStarMath, uno::UNO_QUERY_THROW); + + xStarMathProperties->setPropertyValue(getPropertyName( PROP_EMBEDDED_OBJECT ), + val.getAny()); + // tdf#66405: set zero margins for embedded object + xStarMathProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ), + uno::Any(sal_Int32(0))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ), + uno::Any(sal_Int32(0))); + + uno::Reference< uno::XInterface > xInterface( formula->getComponent(), uno::UNO_QUERY ); + // set zero margins for object's component + uno::Reference< beans::XPropertySet > xComponentProperties( xInterface, uno::UNO_QUERY_THROW ); + xComponentProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ), + uno::Any(sal_Int32(0))); + xComponentProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ), + uno::Any(sal_Int32(0))); + Size size( 1000, 1000 ); + if( oox::FormulaImportBase* formulaimport = dynamic_cast< oox::FormulaImportBase* >( xInterface.get())) + size = formulaimport->getFormulaSize(); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_WIDTH ), + uno::Any( sal_Int32(size.Width()))); + xStarMathProperties->setPropertyValue(getPropertyName( PROP_HEIGHT ), + uno::Any( sal_Int32(size.Height()))); + xStarMathProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), + uno::Any(text::TextContentAnchorType_AS_CHARACTER)); + // mimic the treatment of graphics here... it seems anchoring as character + // gives a better ( visually ) result + appendTextContent(xStarMath, uno::Sequence<beans::PropertyValue>()); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in creation of StarMath object" ); + } +} + +void DomainMapper_Impl::adjustLastPara(sal_Int8 nAlign) +{ + PropertyMapPtr pLastPara = GetTopContextOfType(dmapper::CONTEXT_PARAGRAPH); + pLastPara->Insert(PROP_PARA_ADJUST, uno::Any(nAlign), true); +} + +uno::Reference< beans::XPropertySet > DomainMapper_Impl::appendTextSectionAfter( + uno::Reference< text::XTextRange > const & xBefore ) +{ + uno::Reference< beans::XPropertySet > xRet; + if (m_aTextAppendStack.empty()) + return xRet; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XParagraphCursor > xCursor( + xTextAppend->createTextCursorByRange( xBefore ), uno::UNO_QUERY_THROW); + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + if (m_aTextAppendStack.top().xInsertPosition.is()) + xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true ); + else + xCursor->gotoEnd( true ); + //the paragraph after this new section is already inserted + xCursor->goLeft(1, true); + css::uno::Reference<css::text::XTextRange> xTextRange(xCursor, css::uno::UNO_QUERY_THROW); + + if (css::uno::Reference<css::text::XDocumentIndexesSupplier> xIndexSupplier{ + GetTextDocument(), css::uno::UNO_QUERY }) + { + css::uno::Reference<css::text::XTextRangeCompare> xCompare( + xTextAppend, css::uno::UNO_QUERY); + const auto xIndexAccess = xIndexSupplier->getDocumentIndexes(); + for (sal_Int32 i = xIndexAccess->getCount(); i > 0; --i) + { + if (css::uno::Reference<css::text::XDocumentIndex> xIndex{ + xIndexAccess->getByIndex(i - 1), css::uno::UNO_QUERY }) + { + const auto xIndexTextRange = xIndex->getAnchor(); + if (xCompare->compareRegionStarts(xTextRange, xIndexTextRange) == 0 + && xCompare->compareRegionEnds(xTextRange, xIndexTextRange) == 0) + { + // The boundaries coincide with an index: trying to attach a section + // to the range will insert the section inside the index. goRight will + // extend the range outside of the index, so that created section will + // be around it. Alternatively we could return index section itself + // instead : xRet.set(xIndex, uno::UNO_QUERY) - to set its properties, + // like columns/fill. + xCursor->goRight(1, true); + break; + } + } + } + } + + uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY_THROW ); + xSection->attach( xTextRange ); + xRet.set(xSection, uno::UNO_QUERY ); + } + catch(const uno::Exception&) + { + } + + } + + return xRet; +} + +void DomainMapper_Impl::appendGlossaryEntry() +{ + appendTextSectionAfter(m_xGlossaryEntryStart); +} + +void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar) +{ + if (bSetAnchorToChar) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER)); + + uno::Any aEmptyBorder{table::BorderLine2()}; + static const std::vector<PropertyIds> aBorderIds + = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER }; + for (size_t i = 0; i < aBorderIds.size(); ++i) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder)); + + static const std::vector<PropertyIds> aMarginIds + = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE, + PROP_LEFT_MARGIN, PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_MARGIN, PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_MARGIN, PROP_TOP_BORDER_DISTANCE }; + for (size_t i = 0; i < aMarginIds.size(); ++i) + rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0))); +} + +void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom) +{ + while (!m_aHeaderFooterTextAppendStack.empty()) + { + auto aFooterHeader = m_aHeaderFooterTextAppendStack.top(); + if ((aFooterHeader.second && !bDynamicHeightTop) || (!aFooterHeader.second && !bDynamicHeightBottom)) + { + uno::Reference< text::XTextAppend > xTextAppend = aFooterHeader.first.xTextAppend; + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor(); + uno::Reference< text::XTextRange > xRangeStart, xRangeEnd; + + xRangeStart = xCursor->getStart(); + xCursor->gotoEnd(false); + xRangeEnd = xCursor->getStart(); + + std::vector<beans::PropertyValue> aFrameProperties + { + comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH), + comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT), + comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false), + comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN), + comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN), + // tdf#143384 If the header/footer started with a table, convertToTextFrame could not + // convert the table, because it used createTextCursor() -which ignore tables- + // to set the conversion range. + // This dummy property is set to make convertToTextFrame to use another CreateTextCursor + // method that can be parameterized to not ignore tables. + comphelper::makePropertyValue(getPropertyName(PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF), true) + }; + + fillEmptyFrameProperties(aFrameProperties, false); + + // If it is a footer, then orient the frame to the bottom + if (!aFooterHeader.second) + aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM)); + + uno::Reference<text::XTextAppendAndConvert> xBodyText( + xRangeStart->getText(), uno::UNO_QUERY); + xBodyText->convertToTextFrame(xTextAppend, xRangeEnd, + comphelper::containerToSequence(aFrameProperties)); + } + m_aHeaderFooterTextAppendStack.pop(); + } +} + +void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType) +{ + m_bSaveParaHadField = m_bParaHadField; + m_aHeaderFooterStack.push(HeaderFooterContext(m_bTextInserted, m_nTableDepth)); + m_bTextInserted = false; + m_nTableDepth = 0; + + const PropertyIds ePropIsOn = bHeader? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON; + const PropertyIds ePropShared = bHeader? PROP_HEADER_IS_SHARED: PROP_FOOTER_IS_SHARED; + const PropertyIds ePropTextLeft = bHeader? PROP_HEADER_TEXT_LEFT: PROP_FOOTER_TEXT_LEFT; + const PropertyIds ePropText = bHeader? PROP_HEADER_TEXT: PROP_FOOTER_TEXT; + + m_bDiscardHeaderFooter = true; + m_eInHeaderFooterImport + = bHeader ? HeaderFooterImportState::header : HeaderFooterImportState::footer; + + //get the section context + PropertyMapPtr pContext = DomainMapper_Impl::GetTopContextOfType(CONTEXT_SECTION); + //ask for the header/footer name of the given type + SectionPropertyMap* pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + if(!pSectionContext) + return; + + // clear the "Link To Previous" flag so that the header/footer + // content is not copied from the previous section + pSectionContext->ClearHeaderFooterLinkToPrevious(bHeader, eType); + + if (!m_bIsNewDoc) + { + return; // TODO sw cannot Undo insert header/footer without crashing + } + + uno::Reference< beans::XPropertySet > xPageStyle = + pSectionContext->GetPageStyle( + *this, + eType == SectionPropertyMap::PAGE_FIRST ); + if (!xPageStyle.is()) + return; + try + { + bool bLeft = eType == SectionPropertyMap::PAGE_LEFT; + bool bFirst = eType == SectionPropertyMap::PAGE_FIRST; + if (!bLeft || GetSettingsTable()->GetEvenAndOddHeaders()) + { + //switch on header/footer use + xPageStyle->setPropertyValue( + getPropertyName(ePropIsOn), + uno::Any(true)); + + // If the 'Different Even & Odd Pages' flag is turned on - do not ignore it + // Even if the 'Even' header/footer is blank - the flag should be imported (so it would look in LO like in Word) + if (!bFirst && GetSettingsTable()->GetEvenAndOddHeaders()) + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false)); + + //set the interface + uno::Reference< text::XText > xText; + xPageStyle->getPropertyValue(getPropertyName(bLeft? ePropTextLeft: ePropText)) >>= xText; + + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc + ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart()))); + m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc + ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart())), + bHeader)); + } + // If we have *hidden* header footer + else + { + bool bIsShared = false; + // Turn on the headers + xPageStyle->setPropertyValue(getPropertyName(ePropIsOn), uno::Any(true)); + // Store the state of the previous state of shared prop + xPageStyle->getPropertyValue(getPropertyName(ePropShared)) >>= bIsShared; + // Turn on the shared prop in order to save the headers/footers in time + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(false)); + // Add the content of the headers footers to the doc + uno::Reference<text::XText> xText; + xPageStyle->getPropertyValue(getPropertyName(bLeft ? ePropTextLeft : ePropText)) + >>= xText; + + m_aTextAppendStack.push( + TextAppendContext(uno::Reference<text::XTextAppend>(xText, uno::UNO_QUERY_THROW), + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() + : xText->createTextCursorByRange(xText->getStart()))); + // Restore the original state of the shared prop after we stored the necessary values. + xPageStyle->setPropertyValue(getPropertyName(ePropShared), uno::Any(bIsShared)); + } + m_bDiscardHeaderFooter = false; // set only on success! + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +void DomainMapper_Impl::PushPageHeader(SectionPropertyMap::PageType eType) +{ + PushPageHeaderFooter(/* bHeader = */ true, eType); +} + +void DomainMapper_Impl::PushPageFooter(SectionPropertyMap::PageType eType) +{ + PushPageHeaderFooter(/* bHeader = */ false, eType); +} + +void DomainMapper_Impl::PopPageHeaderFooter() +{ + //header and footer always have an empty paragraph at the end + //this has to be removed + RemoveLastParagraph( ); + + if (!m_aTextAppendStack.empty()) + { + if (!m_bDiscardHeaderFooter) + { + m_aTextAppendStack.pop(); + } + m_bDiscardHeaderFooter = false; + } + m_eInHeaderFooterImport = HeaderFooterImportState::none; + + if (!m_aHeaderFooterStack.empty()) + { + m_bTextInserted = m_aHeaderFooterStack.top().getTextInserted(); + m_nTableDepth = m_aHeaderFooterStack.top().getTableDepth(); + m_aHeaderFooterStack.pop(); + } + + m_bParaHadField = m_bSaveParaHadField; +} + +void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote ) +{ + SAL_WARN_IF(m_bInFootOrEndnote, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote"); + m_bInFootOrEndnote = true; + m_bInFootnote = bIsFootnote; + m_bCheckFirstFootnoteTab = true; + m_bSaveFirstParagraphInCell = m_bFirstParagraphInCell; + try + { + // Redlines outside the footnote should not affect footnote content + m_aRedlines.push(std::vector< RedlineParamsPtr >()); + + // IMHO character styles from footnote labels should be ignored in the edit view of Writer. + // This adds a hack on top of the following hack to save the style name in the context. + PropertyMapPtr pTopContext = GetTopContext(); + OUString sFootnoteCharStyleName; + std::optional< PropertyMap::Property > aProp = pTopContext->getProperty(PROP_CHAR_STYLE_NAME); + if (aProp) + aProp->second >>= sFootnoteCharStyleName; + + // Remove style reference, if any. This reference did appear here as a side effect of tdf#43017 + // Seems it is not required by LO, but causes side effects during editing. So remove it + // for footnotes/endnotes to restore original LO behavior here. + pTopContext->Erase(PROP_CHAR_STYLE_NAME); + + uno::Reference< text::XText > xFootnoteText; + if (GetTextFactory().is()) + xFootnoteText.set( GetTextFactory()->createInstance( + bIsFootnote ? + OUString( "com.sun.star.text.Footnote" ) : OUString( "com.sun.star.text.Endnote" )), + uno::UNO_QUERY_THROW ); + uno::Reference< text::XFootnote > xFootnote( xFootnoteText, uno::UNO_QUERY_THROW ); + pTopContext->SetFootnote(xFootnote, sFootnoteCharStyleName); + uno::Sequence< beans::PropertyValue > aFontProperties; + if (GetTopContextOfType(CONTEXT_CHARACTER)) + aFontProperties = GetTopContextOfType(CONTEXT_CHARACTER)->GetPropertyValues(); + appendTextContent( uno::Reference< text::XTextContent >( xFootnoteText, uno::UNO_QUERY_THROW ), aFontProperties ); + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xFootnoteText, uno::UNO_QUERY_THROW ), + xFootnoteText->createTextCursorByRange(xFootnoteText->getStart()))); + + // Redlines for the footnote anchor in the main text content + std::vector< RedlineParamsPtr > aFootnoteRedline = std::move(m_aRedlines.top()); + m_aRedlines.pop(); + CheckRedline( xFootnote->getAnchor( ) ); + m_aRedlines.push( aFootnoteRedline ); + + // Try scanning for custom footnote labels + if (!sFootnoteCharStyleName.isEmpty()) + StartCustomFootnote(pTopContext); + else + EndCustomFootnote(); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "PushFootOrEndnote"); + } +} + +void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xRange, + const RedlineParamsPtr& pRedline) +{ + if ( !pRedline ) + return; + + bool bRedlineMoved = false; + try + { + OUString sType; + switch ( pRedline->m_nToken & 0xffff ) + { + case XML_mod: + sType = getPropertyName( PROP_FORMAT ); + break; + case XML_moveTo: + bRedlineMoved = true; + m_pParaMarkerRedlineMove = pRedline.get(); + [[fallthrough]]; + case XML_ins: + sType = getPropertyName( PROP_INSERT ); + break; + case XML_moveFrom: + bRedlineMoved = true; + m_pParaMarkerRedlineMove = pRedline.get(); + [[fallthrough]]; + case XML_del: + sType = getPropertyName( PROP_DELETE ); + break; + case XML_ParagraphFormat: + sType = getPropertyName( PROP_PARAGRAPH_FORMAT ); + break; + default: + throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0); + } + beans::PropertyValues aRedlineProperties( 4 ); + beans::PropertyValue * pRedlineProperties = aRedlineProperties.getArray( ); + pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_AUTHOR ); + pRedlineProperties[0].Value <<= pRedline->m_sAuthor; + pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_DATE_TIME ); + util::DateTime aDateTime = ConversionHelper::ConvertDateStringToDateTime( pRedline->m_sDate ); + // tdf#146171 import not specified w:date (or specified as zero date "0-00-00") + // as Epoch time to avoid of losing change tracking data during ODF roundtrip + if ( aDateTime.Year == 0 && aDateTime.Month == 0 && aDateTime.Day == 0 ) + { + aDateTime.Year = 1970; + aDateTime.Month = 1; + aDateTime.Day = 1; + } + pRedlineProperties[1].Value <<= aDateTime; + pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES ); + pRedlineProperties[2].Value <<= pRedline->m_aRevertProperties; + pRedlineProperties[3].Name = "RedlineMoved"; + pRedlineProperties[3].Value <<= bRedlineMoved; + + if (!m_bIsActualParagraphFramed) + { + uno::Reference < text::XRedline > xRedline( xRange, uno::UNO_QUERY_THROW ); + xRedline->makeRedline( sType, aRedlineProperties ); + } + // store frame and (possible floating) table redline data for restoring them after frame conversion + enum StoredRedlines eType; + if (m_bIsActualParagraphFramed || m_nTableDepth > 0) + eType = StoredRedlines::FRAME; + else if (IsInFootOrEndnote()) + eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; + else + eType = StoredRedlines::NONE; + + if (eType != StoredRedlines::NONE) + { + m_aStoredRedlines[eType].push_back( uno::Any(xRange) ); + m_aStoredRedlines[eType].push_back( uno::Any(sType) ); + m_aStoredRedlines[eType].push_back( uno::Any(aRedlineProperties) ); + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "in makeRedline" ); + } +} + +void DomainMapper_Impl::CheckParaMarkerRedline( uno::Reference< text::XTextRange > const& xRange ) +{ + if ( m_pParaMarkerRedline ) + { + CreateRedline( xRange, m_pParaMarkerRedline ); + if ( m_pParaMarkerRedline ) + { + m_pParaMarkerRedline.clear(); + m_currentRedline.clear(); + } + } + else if ( m_pParaMarkerRedlineMove && m_bIsParaMarkerMove ) + { + // terminating moveFrom/moveTo redline removes also the paragraph mark + CreateRedline( xRange, m_pParaMarkerRedlineMove ); + } + if ( m_pParaMarkerRedlineMove ) + { + m_pParaMarkerRedlineMove.clear(); + EndParaMarkerMove(); + } +} + +void DomainMapper_Impl::CheckRedline( uno::Reference< text::XTextRange > const& xRange ) +{ + // Writer core "officially" does not like overlapping redlines, and its UNO interface is stupid enough + // to not prevent that. However, in practice in fact everything appears to work fine (except for the debug warnings + // about redline table corruption, which may possibly be harmless in reality). So leave this as it is, since this + // is a better representation of how the changes happened. If this will ever become a problem, overlapping redlines + // will need to be merged into one, just like doing the changes in the UI does, which will lose some information + // (and so if that happens, it may be better to fix Writer). + // Create the redlines here from lowest (formats) to highest (inserts/removals) priority, since the last one is + // what Writer presents graphically, so this will show deletes as deleted text and not as just formatted text being there. + bool bUsedRange = m_aRedlines.top().size() > 0 || (GetTopContextOfType(CONTEXT_CHARACTER) && + GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0); + + // only export ParagraphFormat, when there is no other redline in the same text portion to avoid missing redline compression, + // but always export the first ParagraphFormat redline in a paragraph to keep the paragraph style change data for rejection + if( (!bUsedRange || !m_bParaChanged) && GetTopContextOfType(CONTEXT_PARAGRAPH) ) + { + std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_PARAGRAPH)->Redlines(); + for( const auto& rRedline : avRedLines ) + CreateRedline( xRange, rRedline ); + } + if( GetTopContextOfType(CONTEXT_CHARACTER) ) + { + std::vector<RedlineParamsPtr>& avRedLines = GetTopContextOfType(CONTEXT_CHARACTER)->Redlines(); + for( const auto& rRedline : avRedLines ) + CreateRedline( xRange, rRedline ); + } + for (const auto& rRedline : m_aRedlines.top() ) + CreateRedline( xRange, rRedline ); +} + +void DomainMapper_Impl::StartParaMarkerChange( ) +{ + m_bIsParaMarkerChange = true; +} + +void DomainMapper_Impl::EndParaMarkerChange( ) +{ + m_bIsParaMarkerChange = false; + m_previousRedline = m_currentRedline; + m_currentRedline.clear(); +} + +void DomainMapper_Impl::StartParaMarkerMove( ) +{ + m_bIsParaMarkerMove = true; +} + +void DomainMapper_Impl::EndParaMarkerMove( ) +{ + m_bIsParaMarkerMove = false; +} + +void DomainMapper_Impl::StartCustomFootnote(const PropertyMapPtr pContext) +{ + if (pContext == m_pFootnoteContext) + return; + + assert(pContext->GetFootnote().is()); + m_bHasFootnoteStyle = true; + m_bCheckFootnoteStyle = !pContext->GetFootnoteStyle().isEmpty(); + m_pFootnoteContext = pContext; +} + +void DomainMapper_Impl::EndCustomFootnote() +{ + m_bHasFootnoteStyle = false; + m_bCheckFootnoteStyle = false; +} + +void DomainMapper_Impl::PushAnnotation() +{ + try + { + m_bIsInComments = true; + if (!GetTextFactory().is()) + return; + m_xAnnotationField.set( GetTextFactory()->createInstance( "com.sun.star.text.TextField.Annotation" ), + uno::UNO_QUERY_THROW ); + uno::Reference< text::XText > xAnnotationText; + m_xAnnotationField->getPropertyValue("TextRange") >>= xAnnotationText; + m_aTextAppendStack.push(TextAppendContext(uno::Reference< text::XTextAppend >( xAnnotationText, uno::UNO_QUERY_THROW ), + m_bIsNewDoc ? uno::Reference<text::XTextCursor>() : xAnnotationText->createTextCursorByRange(xAnnotationText->getStart()))); + } + catch( const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +static void lcl_CopyRedlines( + uno::Reference< text::XText > const& xSrc, + std::deque<css::uno::Any>& rRedlines, + std::vector<sal_Int32>& redPos, + std::vector<sal_Int32>& redLen, + sal_Int32& redIdx) +{ + redIdx = -1; + for( size_t i = 0; i < rRedlines.size(); i+=3) + { + uno::Reference< text::XTextRange > xRange; + rRedlines[i] >>= xRange; + + // is this a redline of the temporary footnote? + uno::Reference<text::XTextCursor> xRangeCursor; + try + { + xRangeCursor = xSrc->createTextCursorByRange( xRange ); + } + catch( const uno::Exception& ) + { + } + if (xRangeCursor.is()) + { + redIdx = i; + sal_Int32 nLen = xRange->getString().getLength(); + redLen.push_back(nLen); + xRangeCursor->gotoRange(xSrc->getStart(), true); + redPos.push_back(xRangeCursor->getString().getLength() - nLen); + } + else + { + // we have already found all redlines of the footnote, + // skip checking the redlines of the other footnotes + if (redIdx > -1) + break; + // failed createTextCursorByRange(), for example, table inside the frame + redLen.push_back(-1); + redPos.push_back(-1); + } + } +} + +static void lcl_PasteRedlines( + uno::Reference< text::XText > const& xDest, + std::deque<css::uno::Any>& rRedlines, + std::vector<sal_Int32>& redPos, + std::vector<sal_Int32>& redLen, + sal_Int32 redIdx) +{ + // create redlines in the copied footnote + for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx); i+=3) + { + OUString sType; + beans::PropertyValues aRedlineProperties( 3 ); + // skip failed createTextCursorByRange() + if (redPos[i/3] == -1) + continue; + rRedlines[i+1] >>= sType; + rRedlines[i+2] >>= aRedlineProperties; + uno::Reference< text::XTextCursor > xCrsr = xDest->getText()->createTextCursor(); + xCrsr->goRight(redPos[i/3], false); + xCrsr->goRight(redLen[i/3], true); + uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW ); + try { + xRedline->makeRedline( sType, aRedlineProperties ); + } + catch(const uno::Exception&) + { + // ignore (footnotes of tracked deletions) + } + } +} + +bool DomainMapper_Impl::CopyTemporaryNotes( + uno::Reference< text::XFootnote > xNoteSrc, + uno::Reference< text::XFootnote > xNoteDest ) +{ + if (!m_bSaxError && xNoteSrc != xNoteDest) + { + uno::Reference< text::XText > xSrc( xNoteSrc, uno::UNO_QUERY_THROW ); + uno::Reference< text::XText > xDest( xNoteDest, uno::UNO_QUERY_THROW ); + uno::Reference< text::XTextCopy > xTxt, xTxt2; + xTxt.set( xSrc, uno::UNO_QUERY_THROW ); + xTxt2.set( xDest, uno::UNO_QUERY_THROW ); + xTxt2->copyText( xTxt ); + + // copy its redlines + std::vector<sal_Int32> redPos, redLen; + sal_Int32 redIdx; + enum StoredRedlines eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; + lcl_CopyRedlines(xSrc, m_aStoredRedlines[eType], redPos, redLen, redIdx); + lcl_PasteRedlines(xDest, m_aStoredRedlines[eType], redPos, redLen, redIdx); + + // remove processed redlines + for( size_t i = 0; redIdx > -1 && i <= sal::static_int_cast<size_t>(redIdx) + 2; i++) + m_aStoredRedlines[eType].pop_front(); + + return true; + } + + return false; +} + +void DomainMapper_Impl::RemoveTemporaryFootOrEndnotes() +{ + uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XFootnote > xNote; + if (GetFootnoteCount() > 0) + { + auto xFootnotes = xFootnotesSupplier->getFootnotes(); + if ( m_nFirstFootnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xFootnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xFootnotes->getByIndex(m_nFirstFootnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } + for (sal_Int32 i = GetFootnoteCount(); i > 0; --i) + { + xFootnotes->getByIndex(i) >>= xNote; + xNote->getAnchor()->setString(""); + } + } + if (GetEndnoteCount() > 0) + { + auto xEndnotes = xEndnotesSupplier->getEndnotes(); + if ( m_nFirstEndnoteIndex > 0 ) + { + uno::Reference< text::XFootnote > xFirstNote; + xEndnotes->getByIndex(0) >>= xFirstNote; + uno::Reference< text::XText > xText( xFirstNote, uno::UNO_QUERY_THROW ); + xText->setString(""); + xEndnotes->getByIndex(m_nFirstEndnoteIndex) >>= xNote; + CopyTemporaryNotes(xNote, xFirstNote); + } + for (sal_Int32 i = GetEndnoteCount(); i > 0; --i) + { + xEndnotes->getByIndex(i) >>= xNote; + xNote->getAnchor()->setString(""); + } + } +} + +static void lcl_convertToNoteIndices(std::deque<sal_Int32>& rNoteIds, sal_Int32& rFirstNoteIndex) +{ + // rNoteIds contains XML footnote identifiers in the loaded order of the footnotes + // (the same order as in footnotes.xml), i.e. it maps temporary footnote positions to the + // identifiers. For example: Ids[0] = 100; Ids[1] = -1, Ids[2] = 5. + // To copy the footnotes in their final place, create an array, which map the (normalized) + // footnote identifiers to the temporary footnote positions. Using the previous example, + // Pos[0] = 1; Pos[1] = 2; Pos[2] = 0 (where [0], [1], [2] are the normalized + // -1, 5 and 100 identifiers). + std::deque<sal_Int32> aSortedIds = rNoteIds; + std::sort(aSortedIds.begin(), aSortedIds.end()); + std::map<sal_Int32, size_t> aMapIds; + // normalize footnote identifiers to 0, 1, 2 ... + for (size_t i = 0; i < aSortedIds.size(); ++i) + aMapIds[aSortedIds[i]] = i; + // reusing rNoteIds, create the Pos array to map normalized identifiers to the loaded positions + std::deque<sal_Int32> aOrigNoteIds = rNoteIds; + for (size_t i = 0; i < rNoteIds.size(); ++i) + rNoteIds[aMapIds[aOrigNoteIds[i]]] = i; + rFirstNoteIndex = rNoteIds.front(); + rNoteIds.pop_front(); +} + +void DomainMapper_Impl::PopFootOrEndnote() +{ + // content of the footnotes were inserted after the first footnote in temporary footnotes, + // restore the content of the actual footnote by copying its content from the first + // (remaining) temporary footnote and remove the temporary footnote. + uno::Reference< text::XFootnotesSupplier> xFootnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< text::XEndnotesSupplier> xEndnotesSupplier( GetTextDocument(), uno::UNO_QUERY ); + bool bCopied = false; + if ( IsInFootOrEndnote() && ( ( IsInFootnote() && GetFootnoteCount() > -1 && xFootnotesSupplier.is() ) || + ( !IsInFootnote() && GetEndnoteCount() > -1 && xEndnotesSupplier.is() ) ) ) + { + uno::Reference< text::XFootnote > xNoteFirst, xNoteLast; + auto xFootnotes = xFootnotesSupplier->getFootnotes(); + auto xEndnotes = xEndnotesSupplier->getEndnotes(); + if ( ( ( IsInFootnote() && xFootnotes->getCount() > 1 && + ( xFootnotes->getByIndex(xFootnotes->getCount()-1) >>= xNoteLast ) ) || + ( !IsInFootnote() && xEndnotes->getCount() > 1 && + ( xEndnotes->getByIndex(xEndnotes->getCount()-1) >>= xNoteLast ) ) + ) && xNoteLast->getLabel().isEmpty() ) + { + // copy content of the next temporary footnote + try + { + if ( IsInFootnote() && !m_aFootnoteIds.empty() ) + { + if ( m_nFirstFootnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aFootnoteIds, m_nFirstFootnoteIndex); + if (m_aFootnoteIds.empty()) // lcl_convertToNoteIndices pops m_aFootnoteIds + m_bSaxError = true; + else + { + xFootnotes->getByIndex(m_aFootnoteIds.front()) >>= xNoteFirst; + m_aFootnoteIds.pop_front(); + } + } + else if ( !IsInFootnote() && !m_aEndnoteIds.empty() ) + { + if ( m_nFirstEndnoteIndex == -1 ) + lcl_convertToNoteIndices(m_aEndnoteIds, m_nFirstEndnoteIndex); + if (m_aEndnoteIds.empty()) // lcl_convertToNoteIndices pops m_aEndnoteIds + m_bSaxError = true; + else + { + xEndnotes->getByIndex(m_aEndnoteIds.front()) >>= xNoteFirst; + m_aEndnoteIds.pop_front(); + } + } + else + m_bSaxError = true; + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert footnote/endnote"); + m_bSaxError = true; + } + + bCopied = CopyTemporaryNotes(xNoteFirst, xNoteLast); + } + } + + if (!IsRTFImport() && !bCopied) + RemoveLastParagraph(); + + // In case the foot or endnote did not contain a tab. + m_bIgnoreNextTab = false; + + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + + if (m_aRedlines.size() == 1) + { + SAL_WARN("writerfilter.dmapper", "PopFootOrEndnote() is called without PushFootOrEndnote()?"); + return; + } + m_aRedlines.pop(); + m_eSkipFootnoteState = SkipFootnoteSeparator::OFF; + m_bInFootOrEndnote = false; + m_pFootnoteContext = nullptr; + m_bFirstParagraphInCell = m_bSaveFirstParagraphInCell; +} + +void DomainMapper_Impl::PopAnnotation() +{ + RemoveLastParagraph(); + + m_bIsInComments = false; + m_aTextAppendStack.pop(); + + try + { + if (m_bAnnotationResolved) + m_xAnnotationField->setPropertyValue("Resolved", css::uno::Any(true)); + + // See if the annotation will be a single position or a range. + if (m_nAnnotationId == -1 || !m_aAnnotationPositions[m_nAnnotationId].m_xStart.is() || !m_aAnnotationPositions[m_nAnnotationId].m_xEnd.is()) + { + uno::Sequence< beans::PropertyValue > aEmptyProperties; + uno::Reference< text::XTextContent > xContent( m_xAnnotationField, uno::UNO_QUERY_THROW ); + appendTextContent( xContent, aEmptyProperties ); + CheckRedline( xContent->getAnchor( ) ); + } + else + { + AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[m_nAnnotationId]; + // Create a range that points to the annotation start/end. + uno::Reference<text::XText> const xText = aAnnotationPosition.m_xStart->getText(); + uno::Reference<text::XTextCursor> const xCursor = xText->createTextCursorByRange(aAnnotationPosition.m_xStart); + + bool bMarker = false; + uno::Reference<text::XTextRangeCompare> xTextRangeCompare(xText, uno::UNO_QUERY); + if (xTextRangeCompare->compareRegionStarts(aAnnotationPosition.m_xStart, aAnnotationPosition.m_xEnd) == 0) + { + // Insert a marker so that comment around an anchored image is not collapsed during + // insertion. + xText->insertString(xCursor, "x", false); + bMarker = true; + } + + xCursor->gotoRange(aAnnotationPosition.m_xEnd, true); + uno::Reference<text::XTextRange> const xTextRange(xCursor, uno::UNO_QUERY_THROW); + + // Attach the annotation to the range. + uno::Reference<text::XTextAppend> const xTextAppend = m_aTextAppendStack.top().xTextAppend; + xTextAppend->insertTextContent(xTextRange, uno::Reference<text::XTextContent>(m_xAnnotationField, uno::UNO_QUERY_THROW), !xCursor->isCollapsed()); + + if (bMarker) + { + // Remove the marker. + xCursor->goLeft(1, true); + xCursor->setString(OUString()); + } + } + m_aAnnotationPositions.erase( m_nAnnotationId ); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Cannot insert annotation field"); + } + + m_xAnnotationField.clear(); + m_nAnnotationId = -1; + m_bAnnotationResolved = false; +} + +void DomainMapper_Impl::PushPendingShape( const uno::Reference< drawing::XShape > & xShape ) +{ + m_aPendingShapes.push_back(xShape); +} + +uno::Reference<drawing::XShape> DomainMapper_Impl::PopPendingShape() +{ + uno::Reference<drawing::XShape> xRet; + if (!m_aPendingShapes.empty()) + { + xRet = m_aPendingShapes.front(); + m_aPendingShapes.pop_front(); + } + return xRet; +} + +void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape > & xShape ) +{ + // Append these early, so the context and the table manager stack will be + // in sync, even if the text append stack is empty. + appendTableManager(); + appendTableHandler(); + getTableManager().startLevel(); + + if (m_aTextAppendStack.empty()) + return; + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + + try + { + uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW ); + if (xSInfo->supportsService("com.sun.star.drawing.GroupShape")) + { + // Textboxes in shapes do not support styles, so check saved style information and apply properties directly to the child shapes. + const uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY); + const sal_uInt32 nShapeCount = xShapes.is() ? xShapes->getCount() : 0; + for ( sal_uInt32 i = 0; i < nShapeCount; ++i ) + { + try + { + uno::Reference<text::XTextRange> xFrame(xShapes->getByIndex(i), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xFramePropertySet; + if (xFrame) + xFramePropertySet.set(xFrame, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShapes->getByIndex(i), uno::UNO_QUERY_THROW); + + comphelper::SequenceAsHashMap aGrabBag( xShapePropertySet->getPropertyValue("CharInteropGrabBag") ); + + // only VML import has checked for style. Don't apply default parastyle properties to other imported shapes + // - except for fontsize - to maintain compatibility with previous versions of LibreOffice. + const bool bOnlyApplyCharHeight = !aGrabBag["mso-pStyle"].hasValue(); + + OUString sStyleName; + aGrabBag["mso-pStyle"] >>= sStyleName; + StyleSheetEntryPtr pEntry = GetStyleSheetTable()->FindStyleSheetByISTD( sStyleName ); + if ( !pEntry ) + { + // Use default style even in ambiguous cases (where multiple styles were defined) since MOST styles inherit + // MOST of their properties from the default style. In the ambiguous case, we have to accept some kind of compromise + // and the default paragraph style ought to be the safest one... (compared to DocDefaults or program defaults) + pEntry = GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( GetDefaultParaStyleName() ); + } + if ( pEntry ) + { + // The Ids here come from oox/source/vml/vmltextbox.cxx. + // It probably could safely expand to all Ids that shapes support. + const PropertyIds eIds[] = { + PROP_CHAR_HEIGHT, + PROP_CHAR_FONT_NAME, + PROP_CHAR_WEIGHT, + PROP_CHAR_CHAR_KERNING, + PROP_CHAR_COLOR, + PROP_PARA_ADJUST + }; + const uno::Reference<beans::XPropertyState> xShapePropertyState(xShapePropertySet, uno::UNO_QUERY_THROW); + for ( const auto& eId : eIds ) + { + try + { + if ( bOnlyApplyCharHeight && eId != PROP_CHAR_HEIGHT ) + continue; + + const OUString sPropName = getPropertyName(eId); + if ( beans::PropertyState_DEFAULT_VALUE == xShapePropertyState->getPropertyState(sPropName) ) + { + const uno::Any aProp = GetPropertyFromStyleSheet(eId, pEntry, /*bDocDefaults=*/true, /*bPara=*/true); + if (aProp.hasValue()) + { + if (xFrame) + xFramePropertySet->setPropertyValue(sPropName, aProp); + else + xShapePropertySet->setPropertyValue(sPropName, aProp); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext() text stylesheet property exception" ); + } + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "PushShapeContext()" ); + } + } + + // A GroupShape doesn't implement text::XTextRange, but appending + // an empty reference to the stacks still makes sense, because this + // way bToRemove can be set, and we won't end up with duplicated + // shapes for OLE objects. + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>())); + uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY); + m_aAnchoredStack.push(AnchoredContext(xTxtContent)); + } + else if (xSInfo->supportsService("com.sun.star.drawing.OLE2Shape")) + { + // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw. + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>())); + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY); + m_aAnchoredStack.push(AnchoredContext(xTextContent)); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + + m_xEmbedded.set(m_xTextFactory->createInstance("com.sun.star.text.TextEmbeddedObject"), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT))); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER)); + // So that the original bitmap-only shape will be replaced by the embedded object. + m_aAnchoredStack.top().bToRemove = true; + m_aTextAppendStack.pop(); + appendTextContent(m_xEmbedded, uno::Sequence<beans::PropertyValue>()); + } + else + { + uno::Reference<text::XTextRange> xShapeTextRange(xShape, uno::UNO_QUERY_THROW); + // Add the shape to the text append stack + uno::Reference<text::XTextAppend> xShapeTextAppend(xShape, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextCursor> xTextCursor; + if (!m_bIsNewDoc) + { + xTextCursor = xShapeTextRange->getText()->createTextCursorByRange( + xShapeTextRange->getStart()); + } + TextAppendContext aContext(xShapeTextAppend, xTextCursor); + m_aTextAppendStack.push(aContext); + + // Add the shape to the anchored objects stack + uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW ); + m_aAnchoredStack.push( AnchoredContext(xTxtContent) ); + + uno::Reference< beans::XPropertySet > xProps( xShape, uno::UNO_QUERY_THROW ); +#ifdef DBG_UTIL + TagLogger::getInstance().unoPropertySet(xProps); +#endif + text::TextContentAnchorType nAnchorType(text::TextContentAnchorType_AT_PARAGRAPH); + xProps->getPropertyValue(getPropertyName( PROP_ANCHOR_TYPE )) >>= nAnchorType; + bool checkZOrderStatus = false; + if (xSInfo->supportsService("com.sun.star.text.TextFrame")) + { + SetIsTextFrameInserted(true); + // Extract the special "btLr text frame" mode, requested by oox, if needed. + // Extract vml ZOrder from FrameInteropGrabBag + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aGrabBag; + xShapePropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag; + + for (const auto& rProp : std::as_const(aGrabBag)) + { + if (rProp.Name == "VML-Z-ORDER") + { + GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper(); + sal_Int32 zOrder(0); + rProp.Value >>= zOrder; + xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder))); + pZOrderHelper->addItem(xShapePropertySet, zOrder); + xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) ); + checkZOrderStatus = true; + } + else if ( rProp.Name == "TxbxHasLink" ) + { + //Chaining of textboxes will happen in ~DomainMapper_Impl + //i.e when all the textboxes are read and all its attributes + //have been set ( basically the Name/LinkedDisplayName ) + //which is set in Graphic Import. + m_vTextFramesForChaining.push_back(xShape); + } + } + + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextRange> xTextRange(xTextAppend->createTextCursorByRange(xTextAppend->getEnd()), uno::UNO_QUERY_THROW); + xTextAppend->insertTextContent(xTextRange, xTextContent, false); + + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + // we need to re-set this value to xTextContent, then only values are preserved. + xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aGrabBag)); + } + else if (nAnchorType == text::TextContentAnchorType_AS_CHARACTER) + { + // Fix spacing for as-character objects. If the paragraph has CT_Spacing_after set, + // it needs to be set on the object too, as that's what object placement code uses. + PropertyMapPtr paragraphContext = GetTopContextOfType( CONTEXT_PARAGRAPH ); + std::optional<PropertyMap::Property> aPropMargin = paragraphContext->getProperty(PROP_PARA_BOTTOM_MARGIN); + if(aPropMargin) + xProps->setPropertyValue( getPropertyName( PROP_BOTTOM_MARGIN ), aPropMargin->second ); + } + else + { + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aGrabBag; + xShapePropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + for (const auto& rProp : std::as_const(aGrabBag)) + { + if (rProp.Name == "VML-Z-ORDER") + { + GraphicZOrderHelper* pZOrderHelper = m_rDMapper.graphicZOrderHelper(); + sal_Int32 zOrder(0); + rProp.Value >>= zOrder; + xShapePropertySet->setPropertyValue( "ZOrder", uno::Any(pZOrderHelper->findZOrder(zOrder))); + pZOrderHelper->addItem(xShapePropertySet, zOrder); + xShapePropertySet->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any( zOrder >= 0 ) ); + checkZOrderStatus = true; + } + else if ( rProp.Name == "TxbxHasLink" ) + { + //Chaining of textboxes will happen in ~DomainMapper_Impl + //i.e when all the textboxes are read and all its attributes + //have been set ( basically the Name/LinkedDisplayName ) + //which is set in Graphic Import. + m_vTextFramesForChaining.push_back(xShape); + } + } + + if(IsSdtEndBefore()) + { + uno::Reference< beans::XPropertySetInfo > xPropSetInfo; + if(xShapePropertySet.is()) + { + xPropSetInfo = xShapePropertySet->getPropertySetInfo(); + if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("InteropGrabBag")) + { + uno::Sequence<beans::PropertyValue> aShapeGrabBag( comphelper::InitPropertySequence({ + { "SdtEndBefore", uno::Any(true) } + })); + xShapePropertySet->setPropertyValue("InteropGrabBag",uno::Any(aShapeGrabBag)); + } + } + } + } + if (!IsInHeaderFooter() && !checkZOrderStatus) + xProps->setPropertyValue( + getPropertyName( PROP_OPAQUE ), + uno::Any( true ) ); + } + m_bParaChanged = true; + getTableManager().setIsInShape(true); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "Exception when adding shape"); + } +} +/* + * Updating chart height and width after reading the actual values from wp:extent +*/ +void DomainMapper_Impl::UpdateEmbeddedShapeProps(const uno::Reference< drawing::XShape > & xShape) +{ + if (!xShape.is()) + return; + + uno::Reference<beans::XPropertySet> xEmbeddedProperties(m_xEmbedded, uno::UNO_QUERY_THROW); + awt::Size aSize = xShape->getSize( ); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_WIDTH), uno::Any(sal_Int32(aSize.Width))); + xEmbeddedProperties->setPropertyValue(getPropertyName(PROP_HEIGHT), uno::Any(sal_Int32(aSize.Height))); +} + + +void DomainMapper_Impl::PopShapeContext() +{ + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } + if ( m_aAnchoredStack.empty() ) + return; + + // For OLE object replacement shape, the text append context was already removed + // or the OLE object couldn't be inserted. + if ( !m_aAnchoredStack.top().bToRemove ) + { + RemoveLastParagraph(); + if (!m_aTextAppendStack.empty()) + m_aTextAppendStack.pop(); + } + + uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent; + try + { + appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() ); + } + catch ( const uno::RuntimeException& ) + { + // this is normal: the shape is already attached + } + + const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW ); + // Remove the shape if required (most likely replacement shape for OLE object) + // or anchored to a discarded header or footer + if ( m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter ) + { + try + { + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(m_xTextDocument, uno::UNO_QUERY_THROW); + uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage(); + if ( xDrawPage.is() ) + xDrawPage->remove( xShape ); + } + catch( const uno::Exception& ) + { + } + } + + // Relative width calculations deferred until section's margins are defined. + // Being cautious: only deferring undefined/minimum-width shapes in order to avoid causing potential regressions + css::awt::Size aShapeSize; + try + { + aShapeSize = xShape->getSize(); + } + catch (const css::uno::RuntimeException& e) + { + // May happen e.g. when text frame has no frame format + // See sw/qa/extras/ooxmlimport/data/n779627.docx + SAL_WARN("writerfilter.dmapper", "getSize failed. " << e.Message); + } + if( aShapeSize.Width <= 2 ) + { + const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY ); + SectionPropertyMap* pSectionContext = GetSectionContext(); + if ( pSectionContext && (!hasTableManager() || !getTableManager().isInTable()) && + xShapePropertySet->getPropertySetInfo()->hasPropertyByName(getPropertyName(PROP_RELATIVE_WIDTH)) ) + { + pSectionContext->addRelativeWidthShape(xShape); + } + } + + m_aAnchoredStack.pop(); +} + +bool DomainMapper_Impl::IsSdtEndBefore() +{ + bool bIsSdtEndBefore = false; + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_CHARACTER); + if(pContext) + { + const uno::Sequence< beans::PropertyValue > currentCharProps = pContext->GetPropertyValues(); + for (const auto& rCurrentCharProp : currentCharProps) + { + if (rCurrentCharProp.Name == "CharInteropGrabBag") + { + uno::Sequence<beans::PropertyValue> aCharGrabBag; + rCurrentCharProp.Value >>= aCharGrabBag; + for (const auto& rProp : std::as_const(aCharGrabBag)) + { + if(rProp.Name == "SdtEndBefore") + { + rProp.Value >>= bIsSdtEndBefore; + } + } + } + } + } + return bIsSdtEndBefore; +} + +bool DomainMapper_Impl::IsDiscardHeaderFooter() const +{ + return m_bDiscardHeaderFooter; +} + +// called from TableManager::closeCell() +void DomainMapper_Impl::ClearPreviousParagraph() +{ + // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering + if ((m_nTableDepth == (m_nTableCellDepth + 1)) + && m_xPreviousParagraph.is() + && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing()) + { + uno::Reference<container::XNamed> xPreviousNumberingRules(m_xPreviousParagraph->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if ( !xPreviousNumberingRules.is() || xPreviousNumberingRules->getName().isEmpty() ) + m_xPreviousParagraph->setPropertyValue("ParaBottomMargin", uno::Any(static_cast<sal_Int32>(0))); + } + + m_xPreviousParagraph.clear(); + + // next table paragraph will be first paragraph in a cell + m_bFirstParagraphInCell = true; +} + +void DomainMapper_Impl::HandleAltChunk(const OUString& rStreamName) +{ + try + { + // Create the import filter. + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory( + comphelper::getProcessServiceFactory()); + uno::Reference<uno::XInterface> xDocxFilter + = xMultiServiceFactory->createInstance("com.sun.star.comp.Writer.WriterFilter"); + + // Set the target document. + uno::Reference<document::XImporter> xImporter(xDocxFilter, uno::UNO_QUERY); + xImporter->setTargetDocument(m_xTextDocument); + + // Set the import parameters. + uno::Reference<embed::XHierarchicalStorageAccess> xStorageAccess(m_xDocumentStorage, + uno::UNO_QUERY); + if (!xStorageAccess.is()) + { + return; + } + // Turn the ZIP stream into a seekable one, as the importer only works with such streams. + uno::Reference<io::XStream> xStream = xStorageAccess->openStreamElementByHierarchicalName( + rStreamName, embed::ElementModes::READ); + std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(xStream, true); + SvMemoryStream aMemory; + aMemory.WriteStream(*pStream); + uno::Reference<io::XStream> xInputStream = new utl::OStreamWrapper(aMemory); + // Not handling AltChunk during paste for now. + uno::Reference<text::XTextRange> xInsertTextRange = GetCurrentXText()->getEnd(); + uno::Reference<text::XTextRange> xSectionStartingRange; + SectionPropertyMap* pSectionContext = GetSectionContext(); + if (pSectionContext) + { + xSectionStartingRange = pSectionContext->GetStartingRange(); + } + uno::Sequence<beans::PropertyValue> aDescriptor(comphelper::InitPropertySequence({ + { "InputStream", uno::Any(xInputStream) }, + { "InsertMode", uno::Any(true) }, + { "TextInsertModeRange", uno::Any(xInsertTextRange) }, + { "AltChunkMode", uno::Any(true) }, + { "AltChunkStartingRange", uno::Any(xSectionStartingRange) }, + })); + + // Do the actual import. + uno::Reference<document::XFilter> xFilter(xDocxFilter, uno::UNO_QUERY); + xFilter->filter(aDescriptor); + } + catch (const uno::Exception& rException) + { + SAL_WARN("writerfilter", "DomainMapper_Impl::HandleAltChunk: failed to handle alt chunk: " + << rException.Message); + } +} + +void DomainMapper_Impl::HandlePTab(sal_Int32 nAlignment) +{ + // We only handle the case when the line already has content, so the left-aligned ptab is + // equivalent to a line break. + if (nAlignment != NS_ooxml::LN_Value_ST_PTabAlignment_left) + { + return; + } + + if (m_aTextAppendStack.empty()) + { + return; + } + + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + { + return; + } + + uno::Reference<css::text::XTextRange> xInsertPosition + = m_aTextAppendStack.top().xInsertPosition; + if (!xInsertPosition.is()) + { + xInsertPosition = xTextAppend->getEnd(); + } + uno::Reference<text::XTextCursor> xCursor + = xTextAppend->createTextCursorByRange(xInsertPosition); + + // Assume that we just inserted a tab character. + xCursor->goLeft(1, true); + if (xCursor->getString() != "\t") + { + return; + } + + // Assume that there is some content before the tab character. + uno::Reference<text::XParagraphCursor> xParagraphCursor(xCursor, uno::UNO_QUERY); + if (!xParagraphCursor.is()) + { + return; + } + + xCursor->collapseToStart(); + xParagraphCursor->gotoStartOfParagraph(true); + if (xCursor->isCollapsed()) + { + return; + } + + // Then select the tab again and replace with a line break. + xCursor->collapseToEnd(); + xCursor->goRight(1, true); + xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::LINE_BREAK, true); +} + +void DomainMapper_Impl::HandleLineBreakClear(sal_Int32 nClear) +{ + switch (nClear) + { + case NS_ooxml::LN_Value_ST_BrClear_left: + // SwLineBreakClear::LEFT + m_oLineBreakClear = 1; + break; + case NS_ooxml::LN_Value_ST_BrClear_right: + // SwLineBreakClear::RIGHT + m_oLineBreakClear = 2; + break; + case NS_ooxml::LN_Value_ST_BrClear_all: + // SwLineBreakClear::ALL + m_oLineBreakClear = 3; + break; + } +} + +void DomainMapper_Impl::HandleLineBreak(const PropertyMapPtr& pPropertyMap) +{ + if (!m_oLineBreakClear.has_value()) + { + appendTextPortion("\n", pPropertyMap); + return; + } + + if (GetTextFactory().is()) + { + uno::Reference<text::XTextContent> xLineBreak( + GetTextFactory()->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY); + xLineBreakProps->setPropertyValue("Clear", uno::Any(*m_oLineBreakClear)); + appendTextContent(xLineBreak, pPropertyMap->GetPropertyValues()); + } + m_oLineBreakClear.reset(); +} + +static sal_Int16 lcl_ParseNumberingType( std::u16string_view rCommand ) +{ + sal_Int16 nRet = style::NumberingType::PAGE_DESCRIPTOR; + + // The command looks like: " PAGE \* Arabic " + // tdf#132185: but may as well be "PAGE \* Arabic" + OUString sNumber; + constexpr OUStringLiteral rSeparator(u"\\* "); + if (size_t nStartIndex = rCommand.find(rSeparator); nStartIndex != std::u16string_view::npos) + { + sal_Int32 nStartIndex2 = nStartIndex + rSeparator.getLength(); + sNumber = o3tl::getToken(rCommand, 0, ' ', nStartIndex2); + } + + if( !sNumber.isEmpty() ) + { + //todo: might make sense to hash this list, too + struct NumberingPairs + { + const char* cWordName; + sal_Int16 nType; + }; + static const NumberingPairs aNumberingPairs[] = + { + {"Arabic", style::NumberingType::ARABIC} + ,{"ROMAN", style::NumberingType::ROMAN_UPPER} + ,{"roman", style::NumberingType::ROMAN_LOWER} + ,{"ALPHABETIC", style::NumberingType::CHARS_UPPER_LETTER} + ,{"alphabetic", style::NumberingType::CHARS_LOWER_LETTER} + ,{"CircleNum", style::NumberingType::CIRCLE_NUMBER} + ,{"ThaiArabic", style::NumberingType::CHARS_THAI} + ,{"ThaiCardText", style::NumberingType::CHARS_THAI} + ,{"ThaiLetter", style::NumberingType::CHARS_THAI} +// ,{"SBCHAR", style::NumberingType::} +// ,{"DBCHAR", style::NumberingType::} +// ,{"DBNUM1", style::NumberingType::} +// ,{"DBNUM2", style::NumberingType::} +// ,{"DBNUM3", style::NumberingType::} +// ,{"DBNUM4", style::NumberingType::} + ,{"Aiueo", style::NumberingType::AIU_FULLWIDTH_JA} + ,{"Iroha", style::NumberingType::IROHA_FULLWIDTH_JA} +// ,{"ZODIAC1", style::NumberingType::} +// ,{"ZODIAC2", style::NumberingType::} +// ,{"ZODIAC3", style::NumberingType::} +// ,{"CHINESENUM1", style::NumberingType::} +// ,{"CHINESENUM2", style::NumberingType::} +// ,{"CHINESENUM3", style::NumberingType::} + ,{"ArabicAlpha", style::NumberingType::CHARS_ARABIC} + ,{"ArabicAbjad", style::NumberingType::FULLWIDTH_ARABIC} + ,{"Ganada", style::NumberingType::HANGUL_JAMO_KO} + ,{"Chosung", style::NumberingType::HANGUL_SYLLABLE_KO} + ,{"KoreanCounting", style::NumberingType::NUMBER_HANGUL_KO} + ,{"KoreanLegal", style::NumberingType::NUMBER_LEGAL_KO} + ,{"KoreanDigital", style::NumberingType::NUMBER_DIGITAL_KO} + ,{"KoreanDigital2", style::NumberingType::NUMBER_DIGITAL2_KO} +/* possible values: +style::NumberingType:: + + CHARS_UPPER_LETTER_N + CHARS_LOWER_LETTER_N + TRANSLITERATION + NATIVE_NUMBERING + CIRCLE_NUMBER + NUMBER_LOWER_ZH + NUMBER_UPPER_ZH + NUMBER_UPPER_ZH_TW + TIAN_GAN_ZH + DI_ZI_ZH + NUMBER_TRADITIONAL_JA + AIU_HALFWIDTH_JA + IROHA_HALFWIDTH_JA + NUMBER_UPPER_KO + NUMBER_HANGUL_KO + HANGUL_JAMO_KO + HANGUL_SYLLABLE_KO + HANGUL_CIRCLED_JAMO_KO + HANGUL_CIRCLED_SYLLABLE_KO + CHARS_HEBREW + CHARS_NEPALI + CHARS_KHMER + CHARS_LAO + CHARS_TIBETAN + CHARS_CYRILLIC_UPPER_LETTER_BG + CHARS_CYRILLIC_LOWER_LETTER_BG + CHARS_CYRILLIC_UPPER_LETTER_N_BG + CHARS_CYRILLIC_LOWER_LETTER_N_BG + CHARS_CYRILLIC_UPPER_LETTER_RU + CHARS_CYRILLIC_LOWER_LETTER_RU + CHARS_CYRILLIC_UPPER_LETTER_N_RU + CHARS_CYRILLIC_LOWER_LETTER_N_RU + CHARS_CYRILLIC_UPPER_LETTER_SR + CHARS_CYRILLIC_LOWER_LETTER_SR + CHARS_CYRILLIC_UPPER_LETTER_N_SR + CHARS_CYRILLIC_LOWER_LETTER_N_SR*/ + + }; + for(const NumberingPairs& rNumberingPair : aNumberingPairs) + { + if( /*sCommand*/sNumber.equalsAscii(rNumberingPair.cWordName )) + { + nRet = rNumberingPair.nType; + break; + } + } + + } + return nRet; +} + + +static OUString lcl_ParseFormat( const OUString& rCommand ) +{ + // The command looks like: " DATE \@"dd MMMM yyyy" or "09/02/2014" + OUString command; + sal_Int32 delimPos = rCommand.indexOf("\\@"); + if (delimPos != -1) + { + // Remove whitespace permitted by standard between \@ and " + sal_Int32 wsChars = rCommand.indexOf('\"') - delimPos - 2; + command = rCommand.replaceAt(delimPos+2, wsChars, u""); + return OUString(msfilter::util::findQuotedText(command, "\\@\"", '\"')); + } + + return OUString(); +} +/*------------------------------------------------------------------------- +extract a parameter (with or without quotes) between the command and the following backslash + -----------------------------------------------------------------------*/ +static OUString lcl_ExtractToken(OUString const& rCommand, + sal_Int32 & rIndex, bool & rHaveToken, bool & rIsSwitch) +{ + rHaveToken = false; + rIsSwitch = false; + + OUStringBuffer token; + bool bQuoted(false); + for (; rIndex < rCommand.getLength(); ++rIndex) + { + sal_Unicode const currentChar(rCommand[rIndex]); + switch (currentChar) + { + case '\\': + { + if (rIndex == rCommand.getLength() - 1) + { + SAL_INFO("writerfilter.dmapper", "field: trailing escape"); + ++rIndex; + return OUString(); + } + sal_Unicode const nextChar(rCommand[rIndex+1]); + if (bQuoted || '\\' == nextChar) + { + ++rIndex; // read 2 chars + token.append(nextChar); + } + else // field switch (case insensitive) + { + rHaveToken = true; + if (token.isEmpty()) + { + rIsSwitch = true; + rIndex += 2; // read 2 chars + return rCommand.copy(rIndex - 2, 2).toAsciiUpperCase(); + } + else + { // leave rIndex, read it again next time + return token.makeStringAndClear(); + } + } + } + break; + case '\"': + if (bQuoted || !token.isEmpty()) + { + rHaveToken = true; + if (bQuoted) + { + ++rIndex; + } + return token.makeStringAndClear(); + } + else + { + bQuoted = true; + } + break; + case ' ': + if (bQuoted) + { + token.append(' '); + } + else + { + if (!token.isEmpty()) + { + rHaveToken = true; + ++rIndex; + return token.makeStringAndClear(); + } + } + break; + case '=': + if (token.isEmpty()) + { + rHaveToken = true; + ++rIndex; + return "FORMULA"; + } + else + token.append('='); + break; + default: + token.append(currentChar); + break; + } + } + assert(rIndex == rCommand.getLength()); + if (bQuoted) + { + // MS Word allows this, so just emit a debug message + SAL_INFO("writerfilter.dmapper", + "field argument with unterminated quote"); + } + rHaveToken = !token.isEmpty(); + return token.makeStringAndClear(); +} + +std::tuple<OUString, std::vector<OUString>, std::vector<OUString> > splitFieldCommand(const OUString& rCommand) +{ + OUString sType; + std::vector<OUString> arguments; + std::vector<OUString> switches; + sal_Int32 nStartIndex(0); + // tdf#54584: Field may be prepended by a backslash + // This is not an escapement, but already escaped literal "\" + // MS Word allows this, so just skip it + if ((rCommand.getLength() >= nStartIndex + 2) && + (rCommand[nStartIndex] == L'\\') && + (rCommand[nStartIndex + 1] != L'\\') && + (rCommand[nStartIndex + 1] != L' ')) + { + ++nStartIndex; + } + + do + { + bool bHaveToken; + bool bIsSwitch; + OUString const token = + lcl_ExtractToken(rCommand, nStartIndex, bHaveToken, bIsSwitch); + assert(nStartIndex <= rCommand.getLength()); + if (bHaveToken) + { + if (sType.isEmpty()) + { + sType = token.toAsciiUpperCase(); + } + else if (bIsSwitch || !switches.empty()) + { + switches.push_back(token); + } + else + { + arguments.push_back(token); + } + } + } while (nStartIndex < rCommand.getLength()); + + return std::make_tuple(sType, arguments, switches); +} + +static OUString lcl_ExtractVariableAndHint( std::u16string_view rCommand, OUString& rHint ) +{ + // the first word after "ASK " is the variable + // the text after the variable and before a '\' is the hint + // if no hint is set the variable is used as hint + // the quotes of the hint have to be removed + size_t nIndex = rCommand.find( ' ', 2); //find last space after 'ASK' + if (nIndex == std::u16string_view::npos) + return OUString(); + while (nIndex < rCommand.size() && rCommand[nIndex] == ' ') + ++nIndex; + std::u16string_view sShortCommand( rCommand.substr( nIndex ) ); //cut off the " ASK " + + sShortCommand = o3tl::getToken(sShortCommand, 0, '\\'); + sal_Int32 nIndex2 = 0; + std::u16string_view sRet = o3tl::getToken(sShortCommand, 0, ' ', nIndex2); + if( nIndex2 > 0) + rHint = sShortCommand.substr( nIndex2 ); + if( rHint.isEmpty() ) + rHint = sRet; + return OUString(sRet); +} + + +static bool lcl_FindInCommand( + const OUString& rCommand, + sal_Unicode cSwitch, + OUString& rValue ) +{ + bool bRet = false; + OUString sSearch = "\\" + OUStringChar( cSwitch ); + sal_Int32 nIndex = rCommand.indexOf( sSearch ); + if( nIndex >= 0 ) + { + bRet = true; + //find next '\' or end of string + sal_Int32 nEndIndex = rCommand.indexOf( '\\', nIndex + 1); + if( nEndIndex < 0 ) + nEndIndex = rCommand.getLength() ; + if( nEndIndex - nIndex > 3 ) + rValue = rCommand.copy( nIndex + 3, nEndIndex - nIndex - 3); + } + return bRet; +} + +static OUString lcl_trim(std::u16string_view sValue) +{ + // it seems, all kind of quotation marks are allowed around index type identifiers + // TODO apply this on bookmarks, too, if needed + return OUString(o3tl::trim(sValue)).replaceAll("\"","").replaceAll(u"“", "").replaceAll(u"”", ""); +} + +/*------------------------------------------------------------------------- + extract the number format from the command and apply the resulting number + format to the XPropertySet + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::SetNumberFormat( const OUString& rCommand, + uno::Reference< beans::XPropertySet > const& xPropertySet, + bool const bDetectFormat) +{ + OUString sFormatString = lcl_ParseFormat( rCommand ); + // find \h - hijri/luna calendar todo: what about saka/era calendar? + bool bHijri = 0 < rCommand.indexOf("\\h "); + lang::Locale aUSLocale; + aUSLocale.Language = "en"; + aUSLocale.Country = "US"; + + lang::Locale aCurrentLocale; + GetAnyProperty(PROP_CHAR_LOCALE, GetTopContext()) >>= aCurrentLocale; + + if (sFormatString.isEmpty()) + { + // No format specified. MS Word uses different formats depending on w:lang, + // "M/d/yyyy h:mm:ss AM/PM" for en-US, and "dd/MM/yyyy hh:mm:ss AM/PM" for en-GB. + // ALSO SEE: ww8par5's GetWordDefaultDateStringAsUS. + sal_Int32 nPos = rCommand.indexOf(" \\"); + OUString sCommand = nPos == -1 ? rCommand.trim() + : OUString(o3tl::trim(rCommand.subView(0, nPos))); + if (sCommand == "CREATEDATE" || sCommand == "PRINTDATE" || sCommand == "SAVEDATE") + { + try + { + css::uno::Reference<css::i18n::XNumberFormatCode> const& xNumberFormatCode = + i18n::NumberFormatMapper::create(m_xComponentContext); + sFormatString = xNumberFormatCode->getFormatCode( + css::i18n::NumberFormatIndex::DATE_SYSTEM_SHORT, aCurrentLocale).Code; + nPos = sFormatString.indexOf("YYYY"); + if (nPos == -1) + sFormatString = sFormatString.replaceFirst("YY", "YYYY"); + if (aCurrentLocale == aUSLocale) + sFormatString += " h:mm:ss AM/PM"; + else + sFormatString += " hh:mm:ss AM/PM"; + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } + } + } + OUString sFormat = ConversionHelper::ConvertMSFormatStringToSO( sFormatString, aCurrentLocale, bHijri); + //get the number formatter and convert the string to a format value + try + { + sal_Int32 nKey = 0; + uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW ); + if( bDetectFormat ) + { + uno::Reference< util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW); + xFormatter->attachNumberFormatsSupplier( xNumberSupplier ); + nKey = xFormatter->detectNumberFormat( 0, rCommand ); + } + else + { + nKey = xNumberSupplier->getNumberFormats()->addNewConverted( sFormat, aUSLocale, aCurrentLocale ); + } + xPropertySet->setPropertyValue( + getPropertyName(PROP_NUMBER_FORMAT), + uno::Any( nKey )); + } + catch(const uno::Exception&) + { + } +} + +static uno::Any lcl_getGrabBagValue( const uno::Sequence<beans::PropertyValue>& grabBag, OUString const & name ) +{ + auto pProp = std::find_if(grabBag.begin(), grabBag.end(), + [&name](const beans::PropertyValue& rProp) { return rProp.Name == name; }); + if (pProp != grabBag.end()) + return pProp->Value; + return uno::Any(); +} + +//Link the text frames. +void DomainMapper_Impl::ChainTextFrames() +{ + //can't link textboxes if there are not even two of them... + if( 2 > m_vTextFramesForChaining.size() ) + return ; + + struct TextFramesForChaining { + css::uno::Reference< css::drawing::XShape > xShape; + sal_Int32 nId; + sal_Int32 nSeq; + OUString s_mso_next_textbox; + OUString shapeName; + TextFramesForChaining() : nId(0), nSeq(0) {} + } ; + typedef std::map <OUString, TextFramesForChaining> ChainMap; + + try + { + ChainMap aTextFramesForChainingHelper; + ::std::vector<TextFramesForChaining> chainingWPS; + OUString sChainNextName("ChainNextName"); + + //learn about ALL of the textboxes and their chaining values first - because frames are processed in no specific order. + for( const auto& rTextFrame : m_vTextFramesForChaining ) + { + uno::Reference<text::XTextContent> xTextContent(rTextFrame, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo; + if( xPropertySet.is() ) + xPropertySetInfo = xPropertySet->getPropertySetInfo(); + uno::Sequence<beans::PropertyValue> aGrabBag; + uno::Reference<lang::XServiceInfo> xServiceInfo(xPropertySet, uno::UNO_QUERY); + + TextFramesForChaining aChainStruct; + OUString sShapeName; + OUString sLinkChainName; + + //The chaining name and the shape name CAN be different in .docx. + //MUST use LinkDisplayName/ChainName as the shape name for establishing links. + if ( xServiceInfo->supportsService("com.sun.star.text.TextFrame") ) + { + xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= aGrabBag; + xPropertySet->getPropertyValue("LinkDisplayName") >>= sShapeName; + } + else + { + xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + xPropertySet->getPropertyValue("ChainName") >>= sShapeName; + } + + lcl_getGrabBagValue( aGrabBag, "Txbx-Id") >>= aChainStruct.nId; + lcl_getGrabBagValue( aGrabBag, "Txbx-Seq") >>= aChainStruct.nSeq; + lcl_getGrabBagValue( aGrabBag, "LinkChainName") >>= sLinkChainName; + lcl_getGrabBagValue( aGrabBag, "mso-next-textbox") >>= aChainStruct.s_mso_next_textbox; + + //Sometimes the shape names have not been imported. If not, we may have a fallback name. + //Set name later, only if required for linking. + aChainStruct.shapeName = sShapeName; + + if (!sLinkChainName.isEmpty()) + { + aChainStruct.xShape = rTextFrame; + aTextFramesForChainingHelper[sLinkChainName] = aChainStruct; + } + if (aChainStruct.s_mso_next_textbox.isEmpty()) + { // no VML chaining => try to chain DrawingML via IDs + aChainStruct.xShape = rTextFrame; + if (!sLinkChainName.isEmpty()) + { // for member of group shapes, TestTdf73499 + aChainStruct.shapeName = sLinkChainName; + } + chainingWPS.emplace_back(aChainStruct); + } + } + + //if mso-next-textbox tags are provided, create those vml-style links first. Afterwards we will make dml-style id/seq links. + for (auto& msoItem : aTextFramesForChainingHelper) + { + //if no mso-next-textbox, we are done. + //if it points to itself, we are done. + if( !msoItem.second.s_mso_next_textbox.isEmpty() + && msoItem.second.s_mso_next_textbox != msoItem.first ) + { + ChainMap::iterator nextFinder=aTextFramesForChainingHelper.find(msoItem.second.s_mso_next_textbox); + if( nextFinder != aTextFramesForChainingHelper.end() ) + { + //if the frames have no name yet, then set them. LinkDisplayName / ChainName are read-only. + if (msoItem.second.shapeName.isEmpty()) + { + uno::Reference< container::XNamed > xNamed( msoItem.second.xShape, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + xNamed->setName( msoItem.first ); + msoItem.second.shapeName = msoItem.first; + } + } + if (nextFinder->second.shapeName.isEmpty()) + { + uno::Reference< container::XNamed > xNamed( nextFinder->second.xShape, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + xNamed->setName( nextFinder->first ); + nextFinder->second.shapeName = msoItem.first; + } + } + + uno::Reference<text::XTextContent> xTextContent(msoItem.second.xShape, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + + //The reverse chaining happens automatically, so only one direction needs to be set + xPropertySet->setPropertyValue(sChainNextName, uno::Any(nextFinder->second.shapeName)); + + //the last item in an mso-next-textbox chain is indistinguishable from id/seq items. Now that it is handled, remove it. + if( nextFinder->second.s_mso_next_textbox.isEmpty() ) + aTextFramesForChainingHelper.erase(nextFinder->first); + } + } + } + + //TODO: Perhaps allow reverse sequences when mso-layout-flow-alt = "bottom-to-top" + const sal_Int32 nDirection = 1; + + //Finally - go through and attach the chains based on matching ID and incremented sequence number (dml-style). + for (const auto& rOuter : chainingWPS) + { + for (const auto& rInner : chainingWPS) + { + if (rInner.nId == rOuter.nId) + { + if (rInner.nSeq == (rOuter.nSeq + nDirection)) + { + uno::Reference<text::XTextContent> const xTextContent(rOuter.xShape, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + + //The reverse chaining happens automatically, so only one direction needs to be set + xPropertySet->setPropertyValue(sChainNextName, uno::Any(rInner.shapeName)); + break ; //there cannot be more than one next frame + } + } + } + } + m_vTextFramesForChaining.clear(); //clear the vector + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper"); + } +} + +void DomainMapper_Impl::PushTextBoxContent() +{ + if (m_bIsInTextBox) + return; + + // tdf#154481: check for TOC creation with empty field stack, + // and close TOC, unless pages will lost. FIXME. + if (IsInTOC() && m_aFieldStack.size() == 0) + { + m_bStartTOC = false; + SAL_WARN("writerfilter.dmapper", + "broken TOC creation in textbox, but field stack is empty, so closing TOC!"); + } + + try + { + uno::Reference<text::XTextFrame> xTBoxFrame( + m_xTextFactory->createInstance("com.sun.star.text.TextFrame"), uno::UNO_QUERY_THROW); + uno::Reference<container::XNamed>(xTBoxFrame, uno::UNO_QUERY_THROW) + ->setName("textbox" + OUString::number(m_xPendingTextBoxFrames.size() + 1)); + uno::Reference<text::XTextAppendAndConvert>(m_aTextAppendStack.top().xTextAppend, + uno::UNO_QUERY_THROW) + ->appendTextContent(xTBoxFrame, beans::PropertyValues()); + m_xPendingTextBoxFrames.push(xTBoxFrame); + + m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xTBoxFrame, uno::UNO_QUERY_THROW), {})); + m_bIsInTextBox = true; + + appendTableManager(); + appendTableHandler(); + getTableManager().startLevel(); + } + catch (uno::Exception& e) + { + SAL_WARN("writerfilter.dmapper", "Exception during creating textbox (" + e.Message + ")!"); + } +} + +void DomainMapper_Impl::PopTextBoxContent() +{ + if (!m_bIsInTextBox || m_xPendingTextBoxFrames.empty()) + return; + + if (uno::Reference<text::XTextFrame>(m_aTextAppendStack.top().xTextAppend, uno::UNO_QUERY).is()) + { + if (hasTableManager()) + { + getTableManager().endLevel(); + popTableManager(); + } + RemoveLastParagraph(); + + m_aTextAppendStack.pop(); + m_bIsInTextBox = false; + } +} + +void DomainMapper_Impl::AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape) +{ + // Without textbox or shape pointless to continue + if (m_xPendingTextBoxFrames.empty() || !xShape) + return; + + uno::Reference< drawing::XShapes >xGroup(xShape, uno::UNO_QUERY); + uno::Reference< beans::XPropertySet >xProps(xShape, uno::UNO_QUERY); + + // If this is a group go inside + if (xGroup) + for (sal_Int32 i = 0; i < xGroup->getCount(); ++i) + AttachTextBoxContentToShape( + uno::Reference<drawing::XShape>(xGroup->getByIndex(i), uno::UNO_QUERY)); + + // if this shape has to be a textbox, attach the frame + if (!xProps->getPropertyValue("TextBox").get<bool>()) + return; + + // if this is a textbox there must be a waiting frame + auto xTextBox = m_xPendingTextBoxFrames.front(); + if (!xTextBox) + return; + + // Pop the pending frames + m_xPendingTextBoxFrames.pop(); + + // Attach the textbox to the shape + try + { + xProps->setPropertyValue("TextBoxContent", uno::Any(xTextBox)); + } + catch (...) + { + SAL_WARN("writerfilter.dmapper", "Exception while trying to attach textboxes!"); + return; + } + + // If attaching is successful, then do the linking + try + { + // Get the name of the textbox + OUString sTextBoxName; + uno::Reference<container::XNamed> xName(xTextBox, uno::UNO_QUERY); + if (xName && !xName->getName().isEmpty()) + sTextBoxName = xName->getName(); + + // Try to get the grabbag + uno::Sequence<beans::PropertyValue> aOldGrabBagSeq; + if (xProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag")) + xProps->getPropertyValue("InteropGrabBag") >>= aOldGrabBagSeq; + + // If the grabbag successfully get... + if (!aOldGrabBagSeq.hasElements()) + return; + + // Check for the existing linking information + bool bSuccess = false; + beans::PropertyValues aNewGrabBagSeq; + const auto& aHasLink = lcl_getGrabBagValue(aOldGrabBagSeq, "TxbxHasLink"); + + // If there must be a link, do it + if (aHasLink.hasValue() && aHasLink.get<bool>()) + { + auto aLinkProp = comphelper::makePropertyValue("LinkChainName", sTextBoxName); + for (sal_uInt32 i = 0; i < aOldGrabBagSeq.size(); ++i) + { + aNewGrabBagSeq.realloc(i + 1); + // If this is the link name replace it + if (!aOldGrabBagSeq[i].Name.isEmpty() && !aLinkProp.Name.isEmpty() + && (aOldGrabBagSeq[i].Name == aLinkProp.Name)) + { + aNewGrabBagSeq.getArray()[i] = aLinkProp; + bSuccess = true; + } + // else copy + else + aNewGrabBagSeq.getArray()[i] = aOldGrabBagSeq[i]; + } + + // If there was no replacement, append the linking data + if (!bSuccess) + { + aNewGrabBagSeq.realloc(aNewGrabBagSeq.size() + 1); + aNewGrabBagSeq.getArray()[aNewGrabBagSeq.size() - 1] = aLinkProp; + bSuccess = true; + } + } + + // If the linking changed the grabbag, apply the modifications + if (aNewGrabBagSeq.hasElements() && bSuccess) + { + xProps->setPropertyValue("InteropGrabBag", uno::Any(aNewGrabBagSeq)); + m_vTextFramesForChaining.push_back(xShape); + } + } + catch (...) + { + SAL_WARN("writerfilter.dmapper", "Exception while trying to link textboxes!"); + } +} + +uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName) +{ + // query master, create if not available + uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters(); + uno::Reference< beans::XPropertySet > xMaster; + OUString sFieldMasterService( OUString::createFromAscii(pFieldMasterService) ); + OUStringBuffer aFieldMasterName; + OUString sDatabaseDataSourceName = GetSettingsTable()->GetCurrentDatabaseDataSource(); + bool bIsMergeField = sFieldMasterService.endsWith("Database"); + aFieldMasterName.appendAscii( pFieldMasterService ); + aFieldMasterName.append('.'); + if ( bIsMergeField && !sDatabaseDataSourceName.isEmpty() ) + { + aFieldMasterName.append(sDatabaseDataSourceName); + aFieldMasterName.append('.'); + } + aFieldMasterName.append(rFieldMasterName); + OUString sFieldMasterName = aFieldMasterName.makeStringAndClear(); + if(xFieldMasterAccess->hasByName(sFieldMasterName)) + { + //get the master + xMaster.set(xFieldMasterAccess->getByName(sFieldMasterName), uno::UNO_QUERY_THROW); + } + else if( m_xTextFactory.is() ) + { + //create the master + xMaster.set( m_xTextFactory->createInstance(sFieldMasterService), uno::UNO_QUERY_THROW); + if ( !bIsMergeField || sDatabaseDataSourceName.isEmpty() ) + { + //set the master's name + xMaster->setPropertyValue( + getPropertyName(PROP_NAME), + uno::Any(rFieldMasterName)); + } else { + // set database data, based on the "databasename.tablename" of sDatabaseDataSourceName + xMaster->setPropertyValue( + getPropertyName(PROP_DATABASE_NAME), + uno::Any(sDatabaseDataSourceName.copy(0, sDatabaseDataSourceName.indexOf('.')))); + xMaster->setPropertyValue( + getPropertyName(PROP_COMMAND_TYPE), + uno::Any(sal_Int32(0))); + xMaster->setPropertyValue( + getPropertyName(PROP_DATATABLE_NAME), + uno::Any(sDatabaseDataSourceName.copy(sDatabaseDataSourceName.indexOf('.') + 1))); + xMaster->setPropertyValue( + getPropertyName(PROP_DATACOLUMN_NAME), + uno::Any(rFieldMasterName)); + } + } + return xMaster; +} + +void DomainMapper_Impl::PushFieldContext() +{ + m_bParaHadField = true; + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("pushFieldContext"); +#endif + + uno::Reference<text::XTextCursor> xCrsr; + if (!m_aTextAppendStack.empty()) + { + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + xCrsr = xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd()); + } + + uno::Reference< text::XTextRange > xStart; + if (xCrsr.is()) + xStart = xCrsr->getStart(); + m_aFieldStack.push_back(new FieldContext(xStart)); +} +/*------------------------------------------------------------------------- +//the current field context waits for the completion of the command + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsOpenFieldCommand() const +{ + return !m_aFieldStack.empty() && !m_aFieldStack.back()->IsCommandCompleted(); +} +/*------------------------------------------------------------------------- +//the current field context waits for the completion of the command + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsOpenField() const +{ + return !m_aFieldStack.empty(); +} + +// Mark top field context as containing a fixed field +void DomainMapper_Impl::SetFieldLocked() +{ + if (IsOpenField()) + m_aFieldStack.back()->SetFieldLocked(); +} + +HeaderFooterContext::HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth) + : m_bTextInserted(bTextInserted) + , m_nTableDepth(nTableDepth) +{ +} + +bool HeaderFooterContext::getTextInserted() const +{ + return m_bTextInserted; +} + +sal_Int32 HeaderFooterContext::getTableDepth() const { return m_nTableDepth; } + +FieldContext::FieldContext(uno::Reference< text::XTextRange > const& xStart) + : m_bFieldCommandCompleted(false) + , m_xStartRange( xStart ) + , m_bFieldLocked( false ) +{ + m_pProperties = new PropertyMap(); +} + + +FieldContext::~FieldContext() +{ +} + +void FieldContext::SetTextField(uno::Reference<text::XTextField> const& xTextField) +{ +#ifndef NDEBUG + if (xTextField.is()) + { + uno::Reference<lang::XServiceInfo> const xServiceInfo(xTextField, uno::UNO_QUERY); + assert(xServiceInfo.is()); + // those must be set by SetFormField() + assert(!xServiceInfo->supportsService("com.sun.star.text.Fieldmark") + && !xServiceInfo->supportsService("com.sun.star.text.FormFieldmark")); + } +#endif + m_xTextField = xTextField; +} + +void FieldContext::CacheVariableValue(const uno::Any& rAny) +{ + rAny >>= m_sVariableValue; +} + +void FieldContext::AppendCommand(std::u16string_view rPart) +{ + m_sCommand += rPart; +} + +::std::vector<OUString> FieldContext::GetCommandParts() const +{ + ::std::vector<OUString> aResult; + sal_Int32 nIndex = 0; + bool bInString = false; + OUString sPart; + while (nIndex != -1) + { + OUString sToken = GetCommand().getToken(0, ' ', nIndex); + bool bInStringNext = bInString; + + if (sToken.isEmpty()) + continue; + + if (sToken[0] == '"') + { + bInStringNext = true; + sToken = sToken.copy(1); + } + if (sToken.endsWith("\"")) + { + bInStringNext = false; + sToken = sToken.copy(0, sToken.getLength() - 1); + } + + if (bInString) + { + sPart += " " + sToken; + if (!bInStringNext) + { + aResult.push_back(sPart); + } + } + else + { + if (bInStringNext) + { + sPart = sToken; + } + else + { + aResult.push_back(sToken); + } + } + + bInString = bInStringNext; + } + + return aResult; +} + +/*------------------------------------------------------------------------- +//collect the pieces of the command + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::AppendFieldCommand(OUString const & rPartOfCommand) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("appendFieldCommand"); + TagLogger::getInstance().chars(rPartOfCommand); + TagLogger::getInstance().endElement(); +#endif + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + pContext->AppendCommand( rPartOfCommand ); + } +} + + +typedef std::multimap < sal_Int32, OUString > TOCStyleMap; + + +static ww::eField GetWW8FieldId(OUString const& rType) +{ + std::unordered_map<OUString, ww::eField> mapID + { + {"ADDRESSBLOCK", ww::eADDRESSBLOCK}, + {"ADVANCE", ww::eADVANCE}, + {"ASK", ww::eASK}, + {"AUTONUM", ww::eAUTONUM}, + {"AUTONUMLGL", ww::eAUTONUMLGL}, + {"AUTONUMOUT", ww::eAUTONUMOUT}, + {"AUTOTEXT", ww::eAUTOTEXT}, + {"AUTOTEXTLIST", ww::eAUTOTEXTLIST}, + {"AUTHOR", ww::eAUTHOR}, + {"BARCODE", ww::eBARCODE}, + {"BIDIOUTLINE", ww::eBIDIOUTLINE}, + {"DATE", ww::eDATE}, + {"COMMENTS", ww::eCOMMENTS}, + {"COMPARE", ww::eCOMPARE}, + {"CONTROL", ww::eCONTROL}, + {"CREATEDATE", ww::eCREATEDATE}, + {"DATABASE", ww::eDATABASE}, + {"DDEAUTOREF", ww::eDDEAUTOREF}, + {"DDEREF", ww::eDDEREF}, + {"DOCPROPERTY", ww::eDOCPROPERTY}, + {"DOCVARIABLE", ww::eDOCVARIABLE}, + {"EDITTIME", ww::eEDITTIME}, + {"EMBED", ww::eEMBED}, + {"EQ", ww::eEQ}, + {"FILLIN", ww::eFILLIN}, + {"FILENAME", ww::eFILENAME}, + {"FILESIZE", ww::eFILESIZE}, + {"FOOTREF", ww::eFOOTREF}, +// {"FORMULA", ww::}, + {"FORMCHECKBOX", ww::eFORMCHECKBOX}, + {"FORMDROPDOWN", ww::eFORMDROPDOWN}, + {"FORMTEXT", ww::eFORMTEXT}, + {"GLOSSREF", ww::eGLOSSREF}, + {"GOTOBUTTON", ww::eGOTOBUTTON}, + {"GREETINGLINE", ww::eGREETINGLINE}, + {"HTMLCONTROL", ww::eHTMLCONTROL}, + {"HYPERLINK", ww::eHYPERLINK}, + {"IF", ww::eIF}, + {"INFO", ww::eINFO}, + {"INCLUDEPICTURE", ww::eINCLUDEPICTURE}, + {"INCLUDETEXT", ww::eINCLUDETEXT}, + {"INCLUDETIFF", ww::eINCLUDETIFF}, + {"KEYWORDS", ww::eKEYWORDS}, + {"LASTSAVEDBY", ww::eLASTSAVEDBY}, + {"LINK", ww::eLINK}, + {"LISTNUM", ww::eLISTNUM}, + {"MACRO", ww::eMACRO}, + {"MACROBUTTON", ww::eMACROBUTTON}, + {"MERGEDATA", ww::eMERGEDATA}, + {"MERGEFIELD", ww::eMERGEFIELD}, + {"MERGEINC", ww::eMERGEINC}, + {"MERGEREC", ww::eMERGEREC}, + {"MERGESEQ", ww::eMERGESEQ}, + {"NEXT", ww::eNEXT}, + {"NEXTIF", ww::eNEXTIF}, + {"NOTEREF", ww::eNOTEREF}, + {"PAGE", ww::ePAGE}, + {"PAGEREF", ww::ePAGEREF}, + {"PLUGIN", ww::ePLUGIN}, + {"PRINT", ww::ePRINT}, + {"PRINTDATE", ww::ePRINTDATE}, + {"PRIVATE", ww::ePRIVATE}, + {"QUOTE", ww::eQUOTE}, + {"RD", ww::eRD}, + {"REF", ww::eREF}, + {"REVNUM", ww::eREVNUM}, + {"SAVEDATE", ww::eSAVEDATE}, + {"SECTION", ww::eSECTION}, + {"SECTIONPAGES", ww::eSECTIONPAGES}, + {"SEQ", ww::eSEQ}, + {"SET", ww::eSET}, + {"SKIPIF", ww::eSKIPIF}, + {"STYLEREF", ww::eSTYLEREF}, + {"SUBSCRIBER", ww::eSUBSCRIBER}, + {"SUBJECT", ww::eSUBJECT}, + {"SYMBOL", ww::eSYMBOL}, + {"TA", ww::eTA}, + {"TEMPLATE", ww::eTEMPLATE}, + {"TIME", ww::eTIME}, + {"TITLE", ww::eTITLE}, + {"TOA", ww::eTOA}, + {"USERINITIALS", ww::eUSERINITIALS}, + {"USERADDRESS", ww::eUSERADDRESS}, + {"USERNAME", ww::eUSERNAME}, + + {"TOC", ww::eTOC}, + {"TC", ww::eTC}, + {"NUMCHARS", ww::eNUMCHARS}, + {"NUMWORDS", ww::eNUMWORDS}, + {"NUMPAGES", ww::eNUMPAGES}, + {"INDEX", ww::eINDEX}, + {"XE", ww::eXE}, + {"BIBLIOGRAPHY", ww::eBIBLIOGRAPHY}, + {"CITATION", ww::eCITATION}, + }; + auto const it = mapID.find(rType); + return (it == mapID.end()) ? ww::eNONE : it->second; +} + +static const FieldConversionMap_t & lcl_GetFieldConversion() +{ + static const FieldConversionMap_t aFieldConversionMap + { +// {"ADDRESSBLOCK", {"", FIELD_ADDRESSBLOCK }}, +// {"ADVANCE", {"", FIELD_ADVANCE }}, + {"ASK", {"SetExpression", FIELD_ASK }}, + {"AUTONUM", {"SetExpression", FIELD_AUTONUM }}, + {"AUTONUMLGL", {"SetExpression", FIELD_AUTONUMLGL }}, + {"AUTONUMOUT", {"SetExpression", FIELD_AUTONUMOUT }}, + {"AUTHOR", {"DocInfo.CreateAuthor", FIELD_AUTHOR }}, + {"DATE", {"DateTime", FIELD_DATE }}, + {"COMMENTS", {"DocInfo.Description", FIELD_COMMENTS }}, + {"CREATEDATE", {"DocInfo.CreateDateTime", FIELD_CREATEDATE }}, + {"DOCPROPERTY", {"", FIELD_DOCPROPERTY }}, + {"DOCVARIABLE", {"User", FIELD_DOCVARIABLE }}, + {"EDITTIME", {"DocInfo.EditTime", FIELD_EDITTIME }}, + {"EQ", {"", FIELD_EQ }}, + {"FILLIN", {"Input", FIELD_FILLIN }}, + {"FILENAME", {"FileName", FIELD_FILENAME }}, +// {"FILESIZE", {"", FIELD_FILESIZE }}, + {"FORMULA", {"TableFormula", FIELD_FORMULA }}, + {"FORMCHECKBOX", {"", FIELD_FORMCHECKBOX }}, + {"FORMDROPDOWN", {"DropDown", FIELD_FORMDROPDOWN }}, + {"FORMTEXT", {"Input", FIELD_FORMTEXT }}, + {"GOTOBUTTON", {"", FIELD_GOTOBUTTON }}, + {"HYPERLINK", {"", FIELD_HYPERLINK }}, + {"IF", {"ConditionalText", FIELD_IF }}, +// {"INFO", {"", FIELD_INFO }}, + {"INCLUDEPICTURE", {"", FIELD_INCLUDEPICTURE}}, + {"KEYWORDS", {"DocInfo.KeyWords", FIELD_KEYWORDS }}, + {"LASTSAVEDBY", {"DocInfo.ChangeAuthor", FIELD_LASTSAVEDBY }}, + {"MACROBUTTON", {"Macro", FIELD_MACROBUTTON }}, + {"MERGEFIELD", {"Database", FIELD_MERGEFIELD }}, + {"MERGEREC", {"DatabaseNumberOfSet", FIELD_MERGEREC }}, +// {"MERGESEQ", {"", FIELD_MERGESEQ }}, + {"NEXT", {"DatabaseNextSet", FIELD_NEXT }}, + {"NEXTIF", {"DatabaseNextSet", FIELD_NEXTIF }}, + {"PAGE", {"PageNumber", FIELD_PAGE }}, + {"PAGEREF", {"GetReference", FIELD_PAGEREF }}, + {"PRINTDATE", {"DocInfo.PrintDateTime", FIELD_PRINTDATE }}, + {"REF", {"GetReference", FIELD_REF }}, + {"REVNUM", {"DocInfo.Revision", FIELD_REVNUM }}, + {"SAVEDATE", {"DocInfo.ChangeDateTime", FIELD_SAVEDATE }}, +// {"SECTION", {"", FIELD_SECTION }}, +// {"SECTIONPAGES", {"", FIELD_SECTIONPAGES }}, + {"SEQ", {"SetExpression", FIELD_SEQ }}, + {"SET", {"SetExpression", FIELD_SET }}, +// {"SKIPIF", {"", FIELD_SKIPIF }}, +// {"STYLEREF", {"", FIELD_STYLEREF }}, + {"SUBJECT", {"DocInfo.Subject", FIELD_SUBJECT }}, + {"SYMBOL", {"", FIELD_SYMBOL }}, + {"TEMPLATE", {"TemplateName", FIELD_TEMPLATE }}, + {"TIME", {"DateTime", FIELD_TIME }}, + {"TITLE", {"DocInfo.Title", FIELD_TITLE }}, + {"USERINITIALS", {"Author", FIELD_USERINITIALS }}, +// {"USERADDRESS", {"", FIELD_USERADDRESS }}, + {"USERNAME", {"Author", FIELD_USERNAME }}, + + + {"TOC", {"com.sun.star.text.ContentIndex", FIELD_TOC }}, + {"TC", {"com.sun.star.text.ContentIndexMark", FIELD_TC }}, + {"NUMCHARS", {"CharacterCount", FIELD_NUMCHARS }}, + {"NUMWORDS", {"WordCount", FIELD_NUMWORDS }}, + {"NUMPAGES", {"PageCount", FIELD_NUMPAGES }}, + {"INDEX", {"com.sun.star.text.DocumentIndex", FIELD_INDEX }}, + {"XE", {"com.sun.star.text.DocumentIndexMark", FIELD_XE }}, + {"BIBLIOGRAPHY",{"com.sun.star.text.Bibliography", FIELD_BIBLIOGRAPHY }}, + {"CITATION", {"com.sun.star.text.TextField.Bibliography",FIELD_CITATION }}, + }; + + return aFieldConversionMap; +} + +static const FieldConversionMap_t & lcl_GetEnhancedFieldConversion() +{ + static const FieldConversionMap_t aEnhancedFieldConversionMap = + { + {"FORMCHECKBOX", {"FormFieldmark", FIELD_FORMCHECKBOX}}, + {"FORMDROPDOWN", {"FormFieldmark", FIELD_FORMDROPDOWN}}, + {"FORMTEXT", {"Fieldmark", FIELD_FORMTEXT}}, + }; + + return aEnhancedFieldConversionMap; +} + +void DomainMapper_Impl::handleFieldSet + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > const & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + OUString sVariable, sHint; + + sVariable = lcl_ExtractVariableAndHint(pContext->GetCommand(), sHint); + + // remove surrounding "" if exists + if(sHint.getLength() >= 2) + { + std::u16string_view sTmp = o3tl::trim(sHint); + if (o3tl::starts_with(sTmp, u"\"") && o3tl::ends_with(sTmp, u"\"")) + { + sHint = sTmp.substr(1, sTmp.size() - 2); + } + } + + // determine field master name + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", sVariable); + + // a set field is a string + xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + uno::Any aAnyHint(sHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_HINT), aAnyHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), aAnyHint); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // Mimic MS Word behavior (hide the SET) + xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false)); +} + +void DomainMapper_Impl::handleFieldAsk + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + //doesn the command contain a variable name? + OUString sVariable, sHint; + + sVariable = lcl_ExtractVariableAndHint( pContext->GetCommand(), + sHint ); + if(!sVariable.isEmpty()) + { + // determine field master name + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", sVariable ); + // An ASK field is always a string of characters + xMaster->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + // set input flag at the field + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_INPUT), uno::Any( true )); + // set the prompt + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), + uno::Any( sHint )); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + // The ASK has no field value to display + xFieldProperties->setPropertyValue(getPropertyName(PROP_IS_VISIBLE), uno::Any(false)); + } + else + { + //don't insert the field + //todo: maybe import a 'normal' input field here? + xFieldInterface = nullptr; + } +} + +/** + * Converts a Microsoft Word field formula into LibreOffice syntax + * @param input The Microsoft Word field formula, with no leading '=' sign + * @return An equivalent LibreOffice field formula + */ +OUString DomainMapper_Impl::convertFieldFormula(const OUString& input) { + + if (!m_pSettingsTable) + { + return input; + } + + OUString listSeparator = m_pSettingsTable->GetListSeparator(); + + /* Replace logical condition functions with LO equivalent operators */ + OUString changed = input.replaceAll(" <> ", " NEQ "); + changed = changed.replaceAll(" <= ", " LEQ "); + changed = changed.replaceAll(" >= ", " GEQ "); + changed = changed.replaceAll(" = " , " EQ "); + changed = changed.replaceAll(" < " , " L "); + changed = changed.replaceAll(" > " , " G "); + + changed = changed.replaceAll("<>", " NEQ "); + changed = changed.replaceAll("<=", " LEQ "); + changed = changed.replaceAll(">=", " GEQ "); + changed = changed.replaceAll("=" , " EQ "); + changed = changed.replaceAll("<" , " L "); + changed = changed.replaceAll(">" , " G "); + + /* Replace function calls with infix keywords for AND(), OR(), and ROUND(). Nothing needs to be + * done for NOT(). This simple regex will work properly with most common cases. However, it may + * not work correctly when the arguments are nested subcalls to other functions, like + * ROUND(MIN(1,2),MAX(3,4)). See TDF#134765. */ + icu::ErrorCode status; + icu::UnicodeString usInput(changed.getStr()); + const uint32_t rMatcherFlags = UREGEX_CASE_INSENSITIVE; + OUString regex = "\\b(AND|OR|ROUND)\\s*\\(\\s*([^" + listSeparator + "]+)\\s*" + listSeparator + "\\s*([^)]+)\\s*\\)"; + icu::UnicodeString usRegex(regex.getStr()); + icu::RegexMatcher rmatch1(usRegex, usInput, rMatcherFlags, status); + usInput = rmatch1.replaceAll(icu::UnicodeString("(($2) $1 ($3))"), status); + + /* Assumes any remaining list separators separate arguments to functions that accept lists + * (SUM, MIN, MAX, MEAN, etc.) */ + usInput.findAndReplace(icu::UnicodeString(listSeparator.getStr()), "|"); + + /* Surround single cell references with angle brackets. + * If there is ever added a function name that ends with a digit, this regex will need to be revisited. */ + icu::RegexMatcher rmatch2("\\b([A-Z]{1,3}[0-9]+)\\b(?![(])", usInput, rMatcherFlags, status); + usInput = rmatch2.replaceAll(icu::UnicodeString("<$1>"), status); + + /* Cell references must be upper case + * TODO: convert reference to other tables, e.g. SUM(Table1 A1:B2), where "Table1" is a bookmark of the table, + * TODO: also column range A:A */ + icu::RegexMatcher rmatch3("(<[a-z]{1,3}[0-9]+>|\\b(above|below|left|right)\\b)", usInput, rMatcherFlags, status); + icu::UnicodeString replacedCellRefs; + while (rmatch3.find(status) && status.isSuccess()) { + rmatch3.appendReplacement(replacedCellRefs, rmatch3.group(status).toUpper(), status); + } + rmatch3.appendTail(replacedCellRefs); + + /* Fix up cell ranges */ + icu::RegexMatcher rmatch4("<([A-Z]{1,3}[0-9]+)>:<([A-Z]{1,3}[0-9]+)>", replacedCellRefs, rMatcherFlags, status); + usInput = rmatch4.replaceAll(icu::UnicodeString("<$1:$2>"), status); + + /* Fix up user defined names */ + icu::RegexMatcher rmatch5("\\bDEFINED\\s*\\(<([A-Z]+[0-9]+)>\\)", usInput, rMatcherFlags, status); + usInput = rmatch5.replaceAll(icu::UnicodeString("DEFINED($1)"), status); + + /* Prepare replace of ABOVE/BELOW/LEFT/RIGHT by adding spaces around them */ + icu::RegexMatcher rmatch6("\\b(ABOVE|BELOW|LEFT|RIGHT)\\b", usInput, rMatcherFlags, status); + usInput = rmatch6.replaceAll(icu::UnicodeString(" $1 "), status); + + /* DOCX allows to set decimal symbol independently from the locale of the document, so if + * needed, convert decimal comma to get working formula in a document language (locale), + * which doesn't use decimal comma */ + if ( m_pSettingsTable->GetDecimalSymbol() == "," && !m_bIsDecimalComma ) + { + icu::RegexMatcher rmatch7("\\b([0-9]+),([0-9]+([eE][-]?[0-9]+)?)\\b", usInput, rMatcherFlags, status); + usInput = rmatch7.replaceAll(icu::UnicodeString("$1.$2"), status); + } + + return OUString(usInput.getTerminatedBuffer()); +} + +void DomainMapper_Impl::handleFieldFormula + (const FieldContextPtr& pContext, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + OUString command = pContext->GetCommand().trim(); + + // Remove number formatting from \# to end of command + // TODO: handle custom number formatting + sal_Int32 delimPos = command.indexOf("\\#"); + if (delimPos != -1) + { + command = command.replaceAt(delimPos, command.getLength() - delimPos, u"").trim(); + } + + // command must contains = and at least another char + if (command.getLength() < 2) + return; + + // we don't copy the = symbol from the command + OUString formula = convertFieldFormula(command.copy(1)); + + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(formula)); + xFieldProperties->setPropertyValue(getPropertyName(PROP_NUMBER_FORMAT), uno::Any(sal_Int32(0))); + xFieldProperties->setPropertyValue("IsShowFormula", uno::Any(false)); + + // grab-bag the original and converted formula + if (hasTableManager()) + { + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_CELL_FORMULA, uno::Any(command.copy(1)), true, CELL_GRAB_BAG); + pPropMap->Insert(PROP_CELL_FORMULA_CONVERTED, uno::Any(formula), true, CELL_GRAB_BAG); + getTableManager().cellProps(pPropMap); + } +} + +void DomainMapper_Impl::handleRubyEQField( const FieldContextPtr& pContext) +{ + const OUString & rCommand(pContext->GetCommand()); + sal_Int32 nIndex = 0, nEnd = 0; + RubyInfo aInfo ; + nIndex = rCommand.indexOf("\\* jc" ); + if (nIndex != -1) + { + nIndex += 5; + sal_uInt32 nJc = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex)); + const sal_Int32 aRubyAlignValues[] = + { + NS_ooxml::LN_Value_ST_RubyAlign_center, + NS_ooxml::LN_Value_ST_RubyAlign_distributeLetter, + NS_ooxml::LN_Value_ST_RubyAlign_distributeSpace, + NS_ooxml::LN_Value_ST_RubyAlign_left, + NS_ooxml::LN_Value_ST_RubyAlign_right, + NS_ooxml::LN_Value_ST_RubyAlign_rightVertical, + }; + aInfo.nRubyAlign = aRubyAlignValues[(nJc<SAL_N_ELEMENTS(aRubyAlignValues))?nJc:0]; + } + + // we don't parse or use the font field in rCommand + + nIndex = rCommand.indexOf("\\* hps" ); + if (nIndex != -1) + { + nIndex += 6; + aInfo.nHps = o3tl::toInt32(o3tl::getToken(rCommand, 0, ' ',nIndex)); + } + + nIndex = rCommand.indexOf("\\o"); + if (nIndex == -1) + return; + nIndex = rCommand.indexOf('(', nIndex); + if (nIndex == -1) + return; + nEnd = rCommand.lastIndexOf(')'); + if (nEnd == -1) + return; + if (nEnd <= nIndex) + return; + + std::u16string_view sRubyParts = rCommand.subView(nIndex+1,nEnd-nIndex-1); + nIndex = 0; + std::u16string_view sPart1 = o3tl::getToken(sRubyParts, 0, ',', nIndex); + std::u16string_view sPart2 = o3tl::getToken(sRubyParts, 0, ',', nIndex); + size_t nIndex2 = 0; + size_t nEnd2 = 0; + if ((nIndex2 = sPart1.find('(')) != std::u16string_view::npos && (nEnd2 = sPart1.rfind(')')) != std::u16string_view::npos && nEnd2 > nIndex2) + { + aInfo.sRubyText = sPart1.substr(nIndex2+1,nEnd2-nIndex2-1); + } + + PropertyMapPtr pRubyContext(new PropertyMap()); + pRubyContext->InsertProps(GetTopContext()); + if (aInfo.nHps > 0) + { + double fVal = double(aInfo.nHps) / 2.; + uno::Any aVal( fVal ); + + pRubyContext->Insert(PROP_CHAR_HEIGHT, aVal); + pRubyContext->Insert(PROP_CHAR_HEIGHT_ASIAN, aVal); + } + PropertyValueVector_t aProps = comphelper::sequenceToContainer< PropertyValueVector_t >(pRubyContext->GetPropertyValues()); + aInfo.sRubyStyle = m_rDMapper.getOrCreateCharStyle(aProps, /*bAlwaysCreate=*/false); + PropertyMapPtr pCharContext(new PropertyMap()); + if (m_pLastCharacterContext) + pCharContext->InsertProps(m_pLastCharacterContext); + pCharContext->InsertProps(pContext->getProperties()); + pCharContext->Insert(PROP_RUBY_TEXT, uno::Any( aInfo.sRubyText ) ); + pCharContext->Insert(PROP_RUBY_ADJUST, uno::Any(static_cast<sal_Int16>(ConversionHelper::convertRubyAlign(aInfo.nRubyAlign)))); + if ( aInfo.nRubyAlign == NS_ooxml::LN_Value_ST_RubyAlign_rightVertical ) + pCharContext->Insert(PROP_RUBY_POSITION, uno::Any(css::text::RubyPosition::INTER_CHARACTER)); + pCharContext->Insert(PROP_RUBY_STYLE, uno::Any(aInfo.sRubyStyle)); + appendTextPortion(OUString(sPart2), pCharContext); + +} + +void DomainMapper_Impl::handleAutoNum + (const FieldContextPtr& pContext, + uno::Reference< uno::XInterface > const & xFieldInterface, + uno::Reference< beans::XPropertySet > const& xFieldProperties) +{ + //create a sequence field master "AutoNr" + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster + ("com.sun.star.text.FieldMaster.SetExpression", + "AutoNr"); + + xMaster->setPropertyValue( getPropertyName(PROP_SUB_TYPE), + uno::Any(text::SetVariableType::SEQUENCE)); + + //apply the numbering type + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField + ( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); +} + +void DomainMapper_Impl::handleAuthor + (std::u16string_view, + uno::Reference< beans::XPropertySet > const& xFieldProperties, + FieldId eFieldId ) +{ + if (eFieldId == FIELD_USERNAME) + xFieldProperties->setPropertyValue + ( getPropertyName(PROP_FULL_NAME), uno::Any( true )); + + // Always set as FIXED b/c MS Word only updates these fields via user intervention (F9) + // AUTHOR of course never changes and USERNAME is easily mis-used as an original author field. + // Additionally, this was forced as fixed if any special case-formatting was provided. + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), + uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } +} + + void DomainMapper_Impl::handleDocProperty + (const FieldContextPtr& pContext, + OUString const& rFirstParam, + uno::Reference< uno::XInterface > & xFieldInterface) +{ + //some docproperties should be imported as document statistic fields, some as DocInfo fields + //others should be user fields + if (rFirstParam.isEmpty()) + return; + + constexpr sal_uInt8 SET_ARABIC = 0x01; + constexpr sal_uInt8 SET_DATE = 0x04; + struct DocPropertyMap + { + const char* pDocPropertyName; + const char* pServiceName; + sal_uInt8 nFlags; + }; + static const DocPropertyMap aDocProperties[] = + { + {"CreateTime", "DocInfo.CreateDateTime", SET_DATE}, + {"Characters", "CharacterCount", SET_ARABIC}, + {"Comments", "DocInfo.Description", 0}, + {"Keywords", "DocInfo.KeyWords", 0}, + {"LastPrinted", "DocInfo.PrintDateTime", 0}, + {"LastSavedBy", "DocInfo.ChangeAuthor", 0}, + {"LastSavedTime", "DocInfo.ChangeDateTime", SET_DATE}, + {"Paragraphs", "ParagraphCount", SET_ARABIC}, + {"RevisionNumber", "DocInfo.Revision", 0}, + {"Subject", "DocInfo.Subject", 0}, + {"Template", "TemplateName", 0}, + {"Title", "DocInfo.Title", 0}, + {"TotalEditingTime", "DocInfo.EditTime", 0}, + {"Words", "WordCount", SET_ARABIC} + + //other available DocProperties: + //Bytes, Category, CharactersWithSpaces, Company + //HyperlinkBase, + //Lines, Manager, NameofApplication, ODMADocId, Pages, + //Security, + }; + uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(m_xTextDocument, uno::UNO_QUERY); + uno::Reference<document::XDocumentProperties> xDocumentProperties = xDocumentPropertiesSupplier->getDocumentProperties(); + uno::Reference<beans::XPropertySet> xUserDefinedProps(xDocumentProperties->getUserDefinedProperties(), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xUserDefinedProps->getPropertySetInfo(); + //search for a field mapping + OUString sFieldServiceName; + size_t nMap = 0; + if (!xPropertySetInfo->hasPropertyByName(rFirstParam)) + { + for( ; nMap < SAL_N_ELEMENTS(aDocProperties); ++nMap ) + { + if (rFirstParam.equalsAscii(aDocProperties[nMap].pDocPropertyName)) + { + sFieldServiceName = OUString::createFromAscii(aDocProperties[nMap].pServiceName); + break; + } + } + } + else + pContext->CacheVariableValue(xUserDefinedProps->getPropertyValue(rFirstParam)); + + OUString sServiceName("com.sun.star.text.TextField."); + bool bIsCustomField = false; + if(sFieldServiceName.isEmpty()) + { + //create a custom property field + sServiceName += "DocInfo.Custom"; + bIsCustomField = true; + } + else + { + sServiceName += sFieldServiceName; + } + if (m_xTextFactory.is()) + xFieldInterface = m_xTextFactory->createInstance(sServiceName); + uno::Reference<beans::XPropertySet> xFieldProperties( xFieldInterface, uno::UNO_QUERY_THROW); + if( bIsCustomField ) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NAME), uno::Any(rFirstParam)); + pContext->SetCustomField( xFieldProperties ); + } + else + { + if(0 != (aDocProperties[nMap].nFlags & SET_ARABIC)) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( style::NumberingType::ARABIC )); + else if(0 != (aDocProperties[nMap].nFlags & SET_DATE)) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_DATE), + uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + } +} + +static uno::Sequence< beans::PropertyValues > lcl_createTOXLevelHyperlinks( bool bHyperlinks, const OUString& sChapterNoSeparator, + const uno::Sequence< beans::PropertyValues >& aLevel ) +{ + //create a copy of the level and add two new entries - hyperlink start and end + bool bChapterNoSeparator = !sChapterNoSeparator.isEmpty(); + sal_Int32 nAdd = (bHyperlinks && bChapterNoSeparator) ? 4 : 2; + uno::Sequence< beans::PropertyValues > aNewLevel( aLevel.getLength() + nAdd); + beans::PropertyValues* pNewLevel = aNewLevel.getArray(); + if( bHyperlinks ) + { + beans::PropertyValues aHyperlink{ comphelper::makePropertyValue( + getPropertyName( PROP_TOKEN_TYPE ), getPropertyName( PROP_TOKEN_HYPERLINK_START )) }; + pNewLevel[0] = aHyperlink; + aHyperlink = { comphelper::makePropertyValue( + getPropertyName(PROP_TOKEN_TYPE), getPropertyName( PROP_TOKEN_HYPERLINK_END )) }; + pNewLevel[aNewLevel.getLength() -1] = aHyperlink; + } + if( bChapterNoSeparator ) + { + beans::PropertyValues aChapterNo{ + comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ), + getPropertyName( PROP_TOKEN_CHAPTER_INFO )), + comphelper::makePropertyValue(getPropertyName( PROP_CHAPTER_FORMAT ), + //todo: is ChapterFormat::Number correct? + sal_Int16(text::ChapterFormat::NUMBER)) + }; + pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 4 : 2) ] = aChapterNo; + + beans::PropertyValues aChapterSeparator{ + comphelper::makePropertyValue(getPropertyName( PROP_TOKEN_TYPE ), + getPropertyName( PROP_TOKEN_TEXT )), + comphelper::makePropertyValue(getPropertyName( PROP_TEXT ), sChapterNoSeparator) + }; + pNewLevel[aNewLevel.getLength() - (bHyperlinks ? 3 : 1)] = aChapterSeparator; + } + //copy the 'old' entries except the last (page no) + std::copy(aLevel.begin(), std::prev(aLevel.end()), std::next(pNewLevel)); + //copy page no entry (last or last but one depending on bHyperlinks + sal_Int32 nPageNo = aNewLevel.getLength() - (bHyperlinks ? 2 : 3); + pNewLevel[nPageNo] = aLevel[aLevel.getLength() - 1]; + + return aNewLevel; +} + +/// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame +OUString DomainMapper_Impl::extractTocTitle() +{ + if (!m_xSdtEntryStart.is()) + return OUString(); + + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(!xTextAppend.is()) + return OUString(); + + // try-catch was added in the same way as inside appendTextSectionAfter() + try + { + uno::Reference<text::XParagraphCursor> xCursor(xTextAppend->createTextCursorByRange(m_xSdtEntryStart), uno::UNO_QUERY_THROW); + if (!xCursor.is()) + return OUString(); + + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + if (m_aTextAppendStack.top().xInsertPosition.is()) + xCursor->gotoRange( m_aTextAppendStack.top().xInsertPosition, true ); + else + xCursor->gotoEnd( true ); + + // the paragraph after this new section might have been already inserted + OUString sResult = xCursor->getString(); + if (sResult.endsWith(SAL_NEWLINE_STRING)) + sResult = sResult.copy(0, sResult.getLength() - SAL_N_ELEMENTS(SAL_NEWLINE_STRING) + 1); + + return sResult; + } + catch(const uno::Exception&) + { + } + + return OUString(); +} + +css::uno::Reference<css::beans::XPropertySet> +DomainMapper_Impl::StartIndexSectionChecked(const OUString& sServiceName) +{ + if (m_bParaChanged) + { + finishParagraph(GetTopContextOfType(CONTEXT_PARAGRAPH), false); // resets m_bParaChanged + PopProperties(CONTEXT_PARAGRAPH); + PushProperties(CONTEXT_PARAGRAPH); + SetIsFirstRun(true); + // The first paragraph of the index that is continuation of just finished one needs to be + // removed when finished (unless more content will arrive, which will set m_bParaChanged) + m_bRemoveThisParagraph = true; + } + const auto& xTextAppend = GetTopTextAppend(); + const auto xTextRange = xTextAppend->getEnd(); + const auto xRet = createSectionForRange(xTextRange, xTextRange, sServiceName, false); + if (!m_aTextAppendStack.top().xInsertPosition) + { + try + { + m_bStartedTOC = true; + uno::Reference<text::XTextCursor> xTOCTextCursor + = xTextRange->getText()->createTextCursor(); + assert(xTOCTextCursor.is()); + xTOCTextCursor->gotoEnd(false); + m_aTextAppendStack.push(TextAppendContext(xTextAppend, xTOCTextCursor)); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", + "DomainMapper_Impl::StartIndexSectionChecked:"); + } + } + return xRet; +} + +void DomainMapper_Impl::handleToc + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + OUString sValue; + if (IsInHeaderFooter()) + m_bStartTOCHeaderFooter = true; + bool bTableOfFigures = false; + bool bHyperlinks = false; + bool bFromOutline = false; + bool bFromEntries = false; + bool bHideTabLeaderPageNumbers = false ; + bool bIsTabEntry = false ; + bool bNewLine = false ; + bool bParagraphOutlineLevel = false; + + sal_Int16 nMaxLevel = 10; + OUString sTemplate; + OUString sChapterNoSeparator; + OUString sFigureSequence; + uno::Reference< beans::XPropertySet > xTOC; + OUString aBookmarkName; + +// \a Builds a table of figures but does not include the captions's label and number + if( lcl_FindInCommand( pContext->GetCommand(), 'a', sValue )) + { //make it a table of figures + bTableOfFigures = true; + } +// \b Uses a bookmark to specify area of document from which to build table of contents + if( lcl_FindInCommand( pContext->GetCommand(), 'b', sValue )) + { + aBookmarkName = sValue.trim().replaceAll("\"",""); + } + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) +// \c Builds a table of figures of the given label + { + //todo: sValue contains the label's name + bTableOfFigures = true; + sFigureSequence = sValue.trim(); + sFigureSequence = sFigureSequence.replaceAll("\"", "").replaceAll("'",""); + } +// \d Defines the separator between sequence and page numbers + if( lcl_FindInCommand( pContext->GetCommand(), 'd', sValue )) + { + //todo: insert the chapter number into each level and insert the separator additionally + sChapterNoSeparator = sValue; + } +// \f Builds a table of contents using TC entries instead of outline levels + if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue )) + { + //todo: sValue can contain a TOC entry identifier - use unclear + bFromEntries = true; + } +// \h Hyperlinks the entries and page numbers within the table of contents + if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue )) + { + //todo: make all entries to hyperlinks + bHyperlinks = true; + } +// \l Defines the TC entries field level used to build a table of contents +// if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue )) +// { + //todo: entries can only be included completely +// } +// \n Builds a table of contents or a range of entries, such as 1-9 in a table of contents without page numbers +// if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) +// { + //todo: what does the description mean? +// } +// \o Builds a table of contents by using outline levels instead of TC entries + if( lcl_FindInCommand( pContext->GetCommand(), 'o', sValue )) + { + bFromOutline = true; + if (sValue.isEmpty()) + nMaxLevel = WW_OUTLINE_MAX; + else + { + sal_Int32 nIndex = 0; + o3tl::getToken(sValue, 0, '-', nIndex ); + nMaxLevel = static_cast<sal_Int16>(nIndex != -1 ? o3tl::toInt32(sValue.subView(nIndex)) : 0); + } + } +// \p Defines the separator between the table entry and its page number +// \s Builds a table of contents by using a sequence type +// \t Builds a table of contents by using style names other than the standard outline styles + if( lcl_FindInCommand( pContext->GetCommand(), 't', sValue )) + { + OUString sToken = sValue.getToken(1, '"'); + sTemplate = sToken.isEmpty() ? sValue : sToken; + } +// \u Builds a table of contents by using the applied paragraph outline level + if( lcl_FindInCommand( pContext->GetCommand(), 'u', sValue )) + { + bFromOutline = true; + bParagraphOutlineLevel = true; + //todo: what doesn 'the applied paragraph outline level' refer to? + } +// \w Preserve tab characters within table entries + if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue )) + { + bIsTabEntry = true ; + } +// \x Preserve newline characters within table entries + if( lcl_FindInCommand( pContext->GetCommand(), 'x', sValue )) + { + bNewLine = true ; + } +// \z Hides page numbers within the table of contents when shown in Web Layout View + if( lcl_FindInCommand( pContext->GetCommand(), 'z', sValue )) + { + bHideTabLeaderPageNumbers = true ; + } + + //if there's no option then it should be created from outline + if( !bFromOutline && !bFromEntries && sTemplate.isEmpty() ) + bFromOutline = true; + + const OUString aTocTitle = extractTocTitle(); + + if (m_xTextFactory.is() && ! m_aTextAppendStack.empty()) + { + const auto& xTextAppend = GetTopTextAppend(); + if (aTocTitle.isEmpty() || bTableOfFigures) + { + // reset marker of the TOC title + m_xSdtEntryStart.clear(); + + // Create section before setting m_bStartTOC: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + xTOC = StartIndexSectionChecked(bTableOfFigures ? "com.sun.star.text.IllustrationsIndex" + : sTOCServiceName); + + const auto xTextCursor = xTextAppend->getText()->createTextCursor(); + if (xTextCursor) + xTextCursor->gotoEnd(false); + xTOCMarkerCursor = xTextCursor; + } + else + { + // create TOC section + css::uno::Reference<css::text::XTextRange> xTextRangeEndOfTocHeader = GetTopTextAppend()->getEnd(); + xTOC = createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, sTOCServiceName, false); + + // init [xTOCMarkerCursor] + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + xTOCMarkerCursor = xCrsr; + + // create header of the TOC with the TOC title inside + createSectionForRange(m_xSdtEntryStart, xTextRangeEndOfTocHeader, "com.sun.star.text.IndexHeaderSection", true); + } + } + + m_bStartTOC = true; + + if (xTOC.is()) + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(aTocTitle)); + + if (!aBookmarkName.isEmpty()) + xTOC->setPropertyValue(getPropertyName(PROP_TOC_BOOKMARK), uno::Any(aBookmarkName)); + if( !bTableOfFigures && xTOC.is() ) + { + xTOC->setPropertyValue( getPropertyName( PROP_LEVEL ), uno::Any( nMaxLevel ) ); + xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_OUTLINE ), uno::Any( bFromOutline )); + xTOC->setPropertyValue( getPropertyName( PROP_CREATE_FROM_MARKS ), uno::Any( bFromEntries )); + xTOC->setPropertyValue( getPropertyName( PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS ), uno::Any( bHideTabLeaderPageNumbers )); + xTOC->setPropertyValue( getPropertyName( PROP_TAB_IN_TOC ), uno::Any( bIsTabEntry )); + xTOC->setPropertyValue( getPropertyName( PROP_TOC_NEW_LINE ), uno::Any( bNewLine )); + xTOC->setPropertyValue( getPropertyName( PROP_TOC_PARAGRAPH_OUTLINE_LEVEL ), uno::Any( bParagraphOutlineLevel )); + if( !sTemplate.isEmpty() ) + { + //the string contains comma separated the names and related levels + //like: "Heading 1,1,Heading 2,2" + TOCStyleMap aMap; + sal_Int32 nLevel; + sal_Int32 nPosition = 0; + while( nPosition >= 0) + { + OUString sStyleName = sTemplate.getToken( 0, ',', nPosition ); + //empty tokens should be skipped + while( sStyleName.isEmpty() && nPosition > 0 ) + sStyleName = sTemplate.getToken( 0, ',', nPosition ); + nLevel = o3tl::toInt32(o3tl::getToken(sTemplate, 0, ',', nPosition )); + if( !nLevel ) + nLevel = 1; + if( !sStyleName.isEmpty() ) + aMap.emplace(nLevel, sStyleName); + } + uno::Reference< container::XIndexReplace> xParaStyles; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_PARAGRAPH_STYLES)) >>= xParaStyles; + for( nLevel = 1; nLevel < 10; ++nLevel) + { + sal_Int32 nLevelCount = aMap.count( nLevel ); + if( nLevelCount ) + { + TOCStyleMap::iterator aTOCStyleIter = aMap.find( nLevel ); + + uno::Sequence< OUString> aStyles( nLevelCount ); + for ( auto& rStyle : asNonConstRange(aStyles) ) + { + rStyle = (aTOCStyleIter++)->second; + } + xParaStyles->replaceByIndex(nLevel - 1, uno::Any(aStyles)); + } + } + xTOC->setPropertyValue(getPropertyName(PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES), uno::Any( true )); + + } + if(bHyperlinks || !sChapterNoSeparator.isEmpty()) + { + uno::Reference< container::XIndexReplace> xLevelFormats; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats; + sal_Int32 nLevelCount = xLevelFormats->getCount(); + //start with level 1, 0 is the header level + for( sal_Int32 nLevel = 1; nLevel < nLevelCount; ++nLevel) + { + uno::Sequence< beans::PropertyValues > aLevel; + xLevelFormats->getByIndex( nLevel ) >>= aLevel; + + uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks( + bHyperlinks, sChapterNoSeparator, + aLevel ); + xLevelFormats->replaceByIndex( nLevel, uno::Any( aNewLevel ) ); + } + } + } + else if (bTableOfFigures && xTOC.is()) + { + if (!sFigureSequence.isEmpty()) + xTOC->setPropertyValue(getPropertyName(PROP_LABEL_CATEGORY), + uno::Any(sFigureSequence)); + + if ( bHyperlinks ) + { + uno::Reference< container::XIndexReplace> xLevelFormats; + xTOC->getPropertyValue(getPropertyName(PROP_LEVEL_FORMAT)) >>= xLevelFormats; + uno::Sequence< beans::PropertyValues > aLevel; + xLevelFormats->getByIndex( 1 ) >>= aLevel; + + uno::Sequence< beans::PropertyValues > aNewLevel = lcl_createTOXLevelHyperlinks( + bHyperlinks, sChapterNoSeparator, + aLevel ); + xLevelFormats->replaceByIndex( 1, uno::Any( aNewLevel ) ); + } + } + pContext->SetTOC( xTOC ); + m_bParaHadField = false; +} + +uno::Reference<beans::XPropertySet> DomainMapper_Impl::createSectionForRange( + uno::Reference< css::text::XTextRange > xStart, + uno::Reference< css::text::XTextRange > xEnd, + const OUString & sObjectType, + bool stepLeft) +{ + if (!xStart.is()) + return uno::Reference<beans::XPropertySet>(); + if (!xEnd.is()) + return uno::Reference<beans::XPropertySet>(); + + uno::Reference< beans::XPropertySet > xRet; + if (m_aTextAppendStack.empty()) + return xRet; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XParagraphCursor > xCursor( + xTextAppend->createTextCursorByRange( xStart ), uno::UNO_QUERY_THROW); + //the cursor has been moved to the end of the paragraph because of the appendTextPortion() calls + xCursor->gotoStartOfParagraph( false ); + xCursor->gotoRange( xEnd, true ); + //the paragraph after this new section is already inserted + if (stepLeft) + xCursor->goLeft(1, true); + uno::Reference< text::XTextContent > xSection( m_xTextFactory->createInstance(sObjectType), uno::UNO_QUERY_THROW ); + xSection->attach( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW) ); + xRet.set(xSection, uno::UNO_QUERY ); + } + catch(const uno::Exception&) + { + } + } + + return xRet; +} + +void DomainMapper_Impl::handleBibliography + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + if (m_aTextAppendStack.empty()) + { + // tdf#130214: a workaround to avoid crash on import errors + SAL_WARN("writerfilter.dmapper", "no text append stack"); + return; + } + // Create section before setting m_bStartTOC and m_bStartBibliography: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + const auto xTOC = StartIndexSectionChecked(sTOCServiceName); + m_bStartTOC = true; + m_bStartBibliography = true; + + if (xTOC.is()) + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString())); + + pContext->SetTOC( xTOC ); + m_bParaHadField = false; + + uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); + appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); +} + +void DomainMapper_Impl::handleIndex + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName) +{ + // only UserIndex can handle user index defined by \f + // e.g. INDEX \f "user-index-id" + OUString sUserIndex; + if ( lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) ) + sUserIndex = lcl_trim(sUserIndex); + + // Create section before setting m_bStartTOC and m_bStartIndex: finishing paragraph + // inside StartIndexSectionChecked could do the wrong thing otherwise + const auto xTOC = StartIndexSectionChecked( sUserIndex.isEmpty() + ? sTOCServiceName + : "com.sun.star.text.UserIndex"); + + m_bStartTOC = true; + m_bStartIndex = true; + OUString sValue; + if (xTOC.is()) + { + xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString())); + + if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + xTOC->setPropertyValue("IsCommaSeparated", uno::Any(true)); + } + if( lcl_FindInCommand( pContext->GetCommand(), 'h', sValue )) + { + xTOC->setPropertyValue("UseAlphabeticalSeparators", uno::Any(true)); + } + if( !sUserIndex.isEmpty() ) + { + xTOC->setPropertyValue("UserIndexName", uno::Any(sUserIndex)); + } + } + pContext->SetTOC( xTOC ); + m_bParaHadField = false; + + uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); + appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); + + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) + { + sValue = sValue.replaceAll("\"", ""); + uno::Reference<text::XTextColumns> xTextColumns; + xTOC->getPropertyValue(getPropertyName( PROP_TEXT_COLUMNS )) >>= xTextColumns; + if (xTextColumns.is()) + { + xTextColumns->setColumnCount( sValue.toInt32() ); + xTOC->setPropertyValue( getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xTextColumns ) ); + } + } +} + +static auto InsertFieldmark(std::stack<TextAppendContext> & rTextAppendStack, + uno::Reference<text::XFormField> const& xFormField, + uno::Reference<text::XTextRange> const& xStartRange, + std::optional<FieldId> const oFieldId) -> void +{ + uno::Reference<text::XTextContent> const xTextContent(xFormField, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextAppend> const& xTextAppend(rTextAppendStack.top().xTextAppend); + uno::Reference<text::XTextCursor> const xCursor = + xTextAppend->createTextCursorByRange(xStartRange); + if (rTextAppendStack.top().xInsertPosition.is()) + { + uno::Reference<text::XTextRangeCompare> const xCompare( + rTextAppendStack.top().xTextAppend, + uno::UNO_QUERY); + if (xCompare->compareRegionStarts(xStartRange, rTextAppendStack.top().xInsertPosition) < 0) + { + SAL_WARN("writerfilter.dmapper", "invalid field mark positions"); + assert(false); + } + xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, true); + } + else + { + xCursor->gotoEnd(true); + } + xTextAppend->insertTextContent(xCursor, xTextContent, true); + if (oFieldId + && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN)) + { + return; // only a single CH_TXT_ATR_FORMELEMENT! + } + // problem: the fieldmark must be inserted in CloseFieldCommand(), because + // attach() takes 2 positions, not 3! + // FAIL: AppendTextNode() ignores the content index! + // plan B: insert a spurious paragraph break now and join + // it in PopFieldContext()! + xCursor->gotoRange(xTextContent->getAnchor()->getEnd(), false); + xCursor->goLeft(1, false); // skip CH_TXT_ATR_FIELDEND + xTextAppend->insertControlCharacter(xCursor, text::ControlCharacter::PARAGRAPH_BREAK, false); + xCursor->goLeft(1, false); // back to previous paragraph + rTextAppendStack.push(TextAppendContext(xTextAppend, xCursor)); +} + +static auto PopFieldmark(std::stack<TextAppendContext> & rTextAppendStack, + uno::Reference<text::XTextCursor> const& xCursor, + std::optional<FieldId> const oFieldId) -> void +{ + if (oFieldId + && (oFieldId == FIELD_FORMCHECKBOX || oFieldId == FIELD_FORMDROPDOWN)) + { + return; // only a single CH_TXT_ATR_FORMELEMENT! + } + xCursor->gotoRange(rTextAppendStack.top().xInsertPosition, false); + xCursor->goRight(1, true); + xCursor->setString(OUString()); // undo SplitNode from CloseFieldCommand() + // note: paragraph properties will be overwritten + // by finishParagraph() anyway so ignore here + rTextAppendStack.pop(); +} + +void DomainMapper_Impl::CloseFieldCommand() +{ + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("closeFieldCommand"); +#endif + + FieldContextPtr pContext; + if(!m_aFieldStack.empty()) + pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( !pContext ) + return; + + m_bSetUserFieldContent = false; + m_bSetCitation = false; + m_bSetDateValue = false; + const FieldConversionMap_t& aFieldConversionMap = lcl_GetFieldConversion(); + + try + { + uno::Reference< uno::XInterface > xFieldInterface; + + const auto& [sType, vArguments, vSwitches]{ splitFieldCommand(pContext->GetCommand()) }; + (void)vSwitches; + OUString const sFirstParam(vArguments.empty() ? OUString() : vArguments.front()); + + // apply font size to the form control + if (!m_aTextAppendStack.empty() && m_pLastCharacterContext && ( m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT) || m_pLastCharacterContext->isSet(PROP_CHAR_FONT_NAME ))) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + uno::Reference< text::XTextCursor > xCrsr = xTextAppend->getText()->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY ); + if (m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT)) + { + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT)->second); + if (m_pLastCharacterContext->isSet(PROP_CHAR_HEIGHT_COMPLEX)) + xProp->setPropertyValue(getPropertyName(PROP_CHAR_HEIGHT_COMPLEX), m_pLastCharacterContext->getProperty(PROP_CHAR_HEIGHT_COMPLEX)->second); + } + if (m_pLastCharacterContext->isSet(PROP_CHAR_FONT_NAME)) + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), m_pLastCharacterContext->getProperty(PROP_CHAR_FONT_NAME)->second); + } + } + } + + FieldConversionMap_t::const_iterator const aIt = aFieldConversionMap.find(sType); + if (aIt != aFieldConversionMap.end() + && (!m_bForceGenericFields + // these need to convert ffData to properties... + || (aIt->second.eFieldId == FIELD_FORMCHECKBOX) + || (aIt->second.eFieldId == FIELD_FORMDROPDOWN) + || (aIt->second.eFieldId == FIELD_FORMTEXT))) + { + pContext->SetFieldId(aIt->second.eFieldId); + bool bCreateEnhancedField = false; + uno::Reference< beans::XPropertySet > xFieldProperties; + bool bCreateField = true; + switch (aIt->second.eFieldId) + { + case FIELD_HYPERLINK: + case FIELD_DOCPROPERTY: + case FIELD_TOC: + case FIELD_INDEX: + case FIELD_XE: + case FIELD_BIBLIOGRAPHY: + case FIELD_CITATION: + case FIELD_TC: + case FIELD_EQ: + case FIELD_INCLUDEPICTURE: + case FIELD_SYMBOL: + case FIELD_GOTOBUTTON: + bCreateField = false; + break; + case FIELD_FORMCHECKBOX : + case FIELD_FORMTEXT : + case FIELD_FORMDROPDOWN : + { + // If we use 'enhanced' fields then FIELD_FORMCHECKBOX, + // FIELD_FORMTEXT & FIELD_FORMDROPDOWN are treated specially + if ( m_bUsingEnhancedFields ) + { + bCreateField = false; + bCreateEnhancedField = true; + } + // for non enhanced fields checkboxes are displayed + // as an awt control not a field + else if ( aIt->second.eFieldId == FIELD_FORMCHECKBOX ) + bCreateField = false; + break; + } + default: + { + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back())) + { + // Parent field can't host this child field: don't create a child field + // in this case. + bCreateField = false; + } + } + break; + } + } + if (m_bStartTOC && (aIt->second.eFieldId == FIELD_PAGEREF) ) + { + bCreateField = false; + } + + if( bCreateField || bCreateEnhancedField ) + { + //add the service prefix + OUString sServiceName("com.sun.star.text."); + if ( bCreateEnhancedField ) + { + const FieldConversionMap_t& aEnhancedFieldConversionMap = lcl_GetEnhancedFieldConversion(); + FieldConversionMap_t::const_iterator aEnhancedIt = + aEnhancedFieldConversionMap.find(sType); + if ( aEnhancedIt != aEnhancedFieldConversionMap.end()) + sServiceName += OUString::createFromAscii(aEnhancedIt->second.cFieldServiceName ); + } + else + { + sServiceName += "TextField." + OUString::createFromAscii(aIt->second.cFieldServiceName ); + } + +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("fieldService"); + TagLogger::getInstance().chars(sServiceName); + TagLogger::getInstance().endElement(); +#endif + + if (m_xTextFactory.is()) + { + xFieldInterface = m_xTextFactory->createInstance(sServiceName); + xFieldProperties.set( xFieldInterface, uno::UNO_QUERY_THROW); + } + } + switch( aIt->second.eFieldId ) + { + case FIELD_ADDRESSBLOCK: break; + case FIELD_ADVANCE : break; + case FIELD_ASK : + handleFieldAsk(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_AUTONUM : + case FIELD_AUTONUMLGL : + case FIELD_AUTONUMOUT : + handleAutoNum(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_AUTHOR : + case FIELD_USERNAME : + case FIELD_USERINITIALS : + handleAuthor(sFirstParam, + xFieldProperties, + aIt->second.eFieldId); + break; + case FIELD_DATE: + if (xFieldProperties.is()) + { + // Get field fixed property from the context handler + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( true )); + m_bSetDateValue = true; + } + else + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( false )); + + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_DATE), + uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_COMMENTS : + { + // OUString sParam = lcl_ExtractParameter(pContext->GetCommand(), sizeof(" COMMENTS") ); + // A parameter with COMMENTS shouldn't set fixed + // ( or at least the binary filter doesn't ) + // If we set fixed then we won't export a field cmd. + // Additionally the para in COMMENTS is more like an + // instruction to set the document property comments + // with the param ( e.g. each COMMENT with a param will + // overwrite the Comments document property + // #TODO implement the above too + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( false )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + break; + case FIELD_CREATEDATE : + case FIELD_PRINTDATE: + case FIELD_SAVEDATE: + { + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), uno::Any( true )); + } + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_DATE ), uno::Any( true )); + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_DOCPROPERTY : + handleDocProperty(pContext, sFirstParam, + xFieldInterface); + break; + case FIELD_DOCVARIABLE : + { + if (bCreateField) + { + //create a user field and type + uno::Reference<beans::XPropertySet> xMaster = FindOrCreateFieldMaster( + "com.sun.star.text.FieldMaster.User", sFirstParam); + uno::Reference<text::XDependentTextField> xDependentField( + xFieldInterface, uno::UNO_QUERY_THROW); + xDependentField->attachTextFieldMaster(xMaster); + m_bSetUserFieldContent = true; + } + } + break; + case FIELD_EDITTIME : + //it's a numbering type, no number format! SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + break; + case FIELD_EQ: + { + OUString aCommand = pContext->GetCommand().trim(); + + msfilter::util::EquationResult aResult(msfilter::util::ParseCombinedChars(aCommand)); + if (!aResult.sType.isEmpty() && m_xTextFactory.is()) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField." + aResult.sType); + xFieldProperties = + uno::Reference< beans::XPropertySet >( xFieldInterface, + uno::UNO_QUERY_THROW); + xFieldProperties->setPropertyValue(getPropertyName(PROP_CONTENT), uno::Any(aResult.sResult)); + } + else + { + //merge Read_SubF_Ruby into filter/.../util.cxx and reuse that ? + sal_Int32 nSpaceIndex = aCommand.indexOf(' '); + if(nSpaceIndex > 0) + aCommand = o3tl::trim(aCommand.subView(nSpaceIndex)); + if (aCommand.startsWith("\\s")) + { + aCommand = aCommand.copy(2); + if (aCommand.startsWith("\\do")) + { + aCommand = aCommand.copy(3); + sal_Int32 nStartIndex = aCommand.indexOf('('); + sal_Int32 nEndIndex = aCommand.indexOf(')'); + if (nStartIndex > 0 && nEndIndex > 0) + { + // nDown is the requested "lower by" value in points. + sal_Int32 nDown = o3tl::toInt32(aCommand.subView(0, nStartIndex)); + OUString aContent = aCommand.copy(nStartIndex + 1, nEndIndex - nStartIndex - 1); + PropertyMapPtr pCharContext = GetTopContext(); + // dHeight is the font size of the current style. + double dHeight = 0; + if ((GetPropertyFromParaStyleSheet(PROP_CHAR_HEIGHT) >>= dHeight) && dHeight != 0) + // Character escapement should be given in negative percents for subscripts. + pCharContext->Insert(PROP_CHAR_ESCAPEMENT, uno::Any( sal_Int16(- 100 * nDown / dHeight) ) ); + appendTextPortion(aContent, pCharContext); + } + } + } + else if (aCommand.startsWith("\\* jc")) + { + handleRubyEQField(pContext); + } + } + } + break; + case FIELD_FILLIN : + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), uno::Any( pContext->GetCommand().getToken(1, '\"'))); + break; + case FIELD_FILENAME: + { + sal_Int32 nNumberingTypeIndex = pContext->GetCommand().indexOf("\\p"); + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_FILE_FORMAT), + uno::Any( nNumberingTypeIndex > 0 ? text::FilenameDisplayFormat::FULL : text::FilenameDisplayFormat::NAME_AND_EXT )); + } + break; + case FIELD_FILESIZE : break; + case FIELD_FORMULA : + if (bCreateField) + { + handleFieldFormula(pContext, xFieldProperties); + } + break; + case FIELD_FORMCHECKBOX : + case FIELD_FORMDROPDOWN : + case FIELD_FORMTEXT : + { + if (bCreateEnhancedField) + { + FFDataHandler::Pointer_t + pFFDataHandler(pContext->getFFDataHandler()); + FormControlHelper::Pointer_t + pFormControlHelper(new FormControlHelper + (m_bUsingEnhancedFields ? aIt->second.eFieldId : FIELD_FORMCHECKBOX, + + m_xTextDocument, pFFDataHandler)); + pContext->setFormControlHelper(pFormControlHelper); + uno::Reference< text::XFormField > xFormField( xFieldInterface, uno::UNO_QUERY ); + uno::Reference< container::XNamed > xNamed( xFormField, uno::UNO_QUERY ); + if ( xNamed.is() ) + { + if ( pFFDataHandler && !pFFDataHandler->getName().isEmpty() ) + xNamed->setName( pFFDataHandler->getName() ); + pContext->SetFormField( xFormField ); + } + InsertFieldmark(m_aTextAppendStack, + xFormField, pContext->GetStartRange(), + pContext->GetFieldId()); + } + else + { + if ( aIt->second.eFieldId == FIELD_FORMDROPDOWN ) + lcl_handleDropdownField( xFieldProperties, pContext->getFFDataHandler() ); + else + lcl_handleTextField( xFieldProperties, pContext->getFFDataHandler() ); + } + } + break; + case FIELD_GOTOBUTTON : break; + case FIELD_HYPERLINK: + { + ::std::vector<OUString> aParts = pContext->GetCommandParts(); + + // Syntax is either: + // HYPERLINK "" \l "link" + // or + // HYPERLINK \l "link" + // Make sure "HYPERLINK" doesn't end up as part of link in the second case. + if (!aParts.empty() && aParts[0] == "HYPERLINK") + aParts.erase(aParts.begin()); + + ::std::vector<OUString>::const_iterator aItEnd = aParts.end(); + ::std::vector<OUString>::const_iterator aPartIt = aParts.begin(); + + OUString sURL; + OUString sTarget; + + while (aPartIt != aItEnd) + { + if ( *aPartIt == "\\l" ) + { + ++aPartIt; + + if (aPartIt == aItEnd) + break; + + sURL += "#" + *aPartIt; + } + else if (*aPartIt == "\\m" || *aPartIt == "\\n" || *aPartIt == "\\h") + { + } + else if ( *aPartIt == "\\o" || *aPartIt == "\\t" ) + { + ++aPartIt; + + if (aPartIt == aItEnd) + break; + + sTarget = *aPartIt; + } + else + { + sURL = *aPartIt; + } + + ++aPartIt; + } + + if (!sURL.isEmpty()) + { + if (sURL.startsWith("file:///")) + { + // file:///absolute\\path\\to\\file => invalid file URI (Writer cannot open) + // convert all double backslashes to slashes: + sURL = sURL.replaceAll("\\\\", "/"); + + // file:///absolute\path\to\file => invalid file URI (Writer cannot open) + // convert all backslashes to slashes: + sURL = sURL.replace('\\', '/'); + } + // Try to make absolute any relative URLs, except + // for relative same-document URLs that only contain + // a fragment part: + else if (!sURL.startsWith("#")) { + try { + sURL = rtl::Uri::convertRelToAbs( + m_aBaseUrl, sURL); + } catch (rtl::MalformedUriException & e) { + SAL_WARN( + "writerfilter.dmapper", + "MalformedUriException " + << e.getMessage()); + } + } + pContext->SetHyperlinkURL(sURL); + } + + if (!sTarget.isEmpty()) + pContext->SetHyperlinkTarget(sTarget); + } + break; + case FIELD_IF : break; + case FIELD_INFO : break; + case FIELD_INCLUDEPICTURE: break; + case FIELD_KEYWORDS : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_LASTSAVEDBY : + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), uno::Any(true)); + break; + case FIELD_MACROBUTTON: + { + if (xFieldProperties.is()) + { + sal_Int32 nIndex = sizeof(" MACROBUTTON "); + OUString sCommand = pContext->GetCommand(); + + //extract macro name + if (sCommand.getLength() >= nIndex) + { + OUString sMacro = sCommand.getToken(0, ' ', nIndex); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_MACRO_NAME), uno::Any( sMacro )); + } + + //extract quick help text + if (sCommand.getLength() > nIndex + 1) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_HINT), + uno::Any( sCommand.copy( nIndex ))); + } + } + } + break; + case FIELD_MERGEFIELD : + { + //todo: create a database field and fieldmaster pointing to a column, only + //create a user field and type + uno::Reference< beans::XPropertySet > xMaster = + FindOrCreateFieldMaster("com.sun.star.text.FieldMaster.Database", sFirstParam); + +// xFieldProperties->setPropertyValue( +// "FieldCode", +// uno::makeAny( pContext->GetCommand().copy( nIndex + 1 ))); + uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + } + break; + case FIELD_MERGEREC : break; + case FIELD_MERGESEQ : break; + case FIELD_NEXT : break; + case FIELD_NEXTIF : break; + case FIELD_PAGE : + if (xFieldProperties.is()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_SUB_TYPE), + uno::Any( text::PageNumberType_CURRENT )); + } + + break; + case FIELD_PAGEREF: + case FIELD_REF: + if (xFieldProperties.is() && !m_bStartTOC) + { + bool bPageRef = aIt->second.eFieldId == FIELD_PAGEREF; + + // Do we need a GetReference (default) or a GetExpression field? + uno::Reference< text::XTextFieldsSupplier > xFieldsSupplier( GetTextDocument(), uno::UNO_QUERY ); + uno::Reference< container::XNameAccess > xFieldMasterAccess = xFieldsSupplier->getTextFieldMasters(); + + if (!xFieldMasterAccess->hasByName( + "com.sun.star.text.FieldMaster.SetExpression." + + sFirstParam)) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_REFERENCE_FIELD_SOURCE), + uno::Any( sal_Int16(text::ReferenceFieldSource::BOOKMARK)) ); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_SOURCE_NAME), + uno::Any(sFirstParam) ); + sal_Int16 nFieldPart = (bPageRef ? text::ReferenceFieldPart::PAGE : text::ReferenceFieldPart::TEXT); + OUString sValue; + if( lcl_FindInCommand( pContext->GetCommand(), 'p', sValue )) + { + //above-below + nFieldPart = text::ReferenceFieldPart::UP_DOWN; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + //number + nFieldPart = text::ReferenceFieldPart::NUMBER; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) + { + //number-no-context + nFieldPart = text::ReferenceFieldPart::NUMBER_NO_CONTEXT; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'w', sValue )) + { + //number-full-context + nFieldPart = text::ReferenceFieldPart::NUMBER_FULL_CONTEXT; + } + xFieldProperties->setPropertyValue( + getPropertyName( PROP_REFERENCE_FIELD_PART ), uno::Any( nFieldPart )); + } + else if( m_xTextFactory.is() ) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.TextField.GetExpression"); + xFieldProperties.set(xFieldInterface, uno::UNO_QUERY); + xFieldProperties->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any(sFirstParam)); + xFieldProperties->setPropertyValue(getPropertyName(PROP_SUB_TYPE), uno::Any(text::SetVariableType::STRING)); + } + } + break; + case FIELD_REVNUM : break; + case FIELD_SECTION : break; + case FIELD_SECTIONPAGES : break; + case FIELD_SEQ : + { + // command looks like: " SEQ Table \* ARABIC " + OUString sCmd(pContext->GetCommand()); + // find the sequence name, e.g. "SEQ" + std::u16string_view sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\'); + sSeqName = o3tl::trim(sSeqName); + + // create a sequence field master using the sequence name + uno::Reference< beans::XPropertySet > xMaster = FindOrCreateFieldMaster( + "com.sun.star.text.FieldMaster.SetExpression", + OUString(sSeqName)); + + xMaster->setPropertyValue( + getPropertyName(PROP_SUB_TYPE), + uno::Any(text::SetVariableType::SEQUENCE)); + + // apply the numbering type + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + + // attach the master to the field + uno::Reference< text::XDependentTextField > xDependentField( xFieldInterface, uno::UNO_QUERY_THROW ); + xDependentField->attachTextFieldMaster( xMaster ); + + OUString sFormula = OUString::Concat(sSeqName) + "+1"; + OUString sValue; + if( lcl_FindInCommand( pContext->GetCommand(), 'c', sValue )) + { + sFormula = sSeqName; + } + else if( lcl_FindInCommand( pContext->GetCommand(), 'r', sValue )) + { + sFormula = sValue; + } + // TODO \s isn't handled, but the spec isn't easy to understand without + // an example for this one. + xFieldProperties->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any(sFormula)); + + // Take care of the numeric formatting definition, default is Arabic + sal_Int16 nNumberingType = lcl_ParseNumberingType(pContext->GetCommand()); + if (nNumberingType == style::NumberingType::PAGE_DESCRIPTOR) + nNumberingType = style::NumberingType::ARABIC; + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any(nNumberingType)); + } + break; + case FIELD_SET : + handleFieldSet(pContext, xFieldInterface, xFieldProperties); + break; + case FIELD_SKIPIF : break; + case FIELD_STYLEREF : break; + case FIELD_SUBJECT : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_SYMBOL: + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + OUString sSymbol( sal_Unicode( sFirstParam.startsWithIgnoreAsciiCase("0x") ? o3tl::toUInt32(sFirstParam.subView(2),16) : sFirstParam.toUInt32() ) ); + OUString sFont; + bool bHasFont = lcl_FindInCommand( pContext->GetCommand(), 'f', sFont); + if ( bHasFont ) + { + sFont = sFont.trim(); + if (sFont.startsWith("\"")) + sFont = sFont.copy(1); + if (sFont.endsWith("\"")) + sFont = sFont.copy(0,sFont.getLength()-1); + } + + + + if (xTextAppend.is()) + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + xText->insertString(xCrsr, sSymbol, true); + uno::Reference< beans::XPropertySet > xProp( xCrsr, uno::UNO_QUERY ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_CHAR_SET), uno::Any(awt::CharSet::SYMBOL)); + if(bHasFont) + { + uno::Any aVal( sFont ); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME), aVal); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_ASIAN), aVal); + xProp->setPropertyValue(getPropertyName(PROP_CHAR_FONT_NAME_COMPLEX), aVal); + + } + } + } + } + break; + case FIELD_TEMPLATE: break; + case FIELD_TIME : + { + if (pContext->IsFieldLocked()) + { + xFieldProperties->setPropertyValue( + getPropertyName(PROP_IS_FIXED), + uno::Any( true )); + m_bSetDateValue = true; + } + SetNumberFormat( pContext->GetCommand(), xFieldProperties ); + } + break; + case FIELD_TITLE : + { + if (!sFirstParam.isEmpty()) + { + xFieldProperties->setPropertyValue( + getPropertyName( PROP_IS_FIXED ), uno::Any( true )); + //PROP_CURRENT_PRESENTATION is set later anyway + } + } + break; + case FIELD_USERADDRESS : //todo: user address collects street, city ... + break; + case FIELD_INDEX: + handleIndex(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_BIBLIOGRAPHY: + handleBibliography(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_TOC: + handleToc(pContext, + OUString::createFromAscii(aIt->second.cFieldServiceName)); + break; + case FIELD_XE: + { + if( !m_xTextFactory.is() ) + break; + + // only UserIndexMark can handle user index types defined by \f + // e.g. XE "text" \f "user-index-id" + OUString sUserIndex; + OUString sFieldServiceName = + lcl_FindInCommand( pContext->GetCommand(), 'f', sUserIndex ) + ? "com.sun.star.text.UserIndexMark" + : OUString::createFromAscii(aIt->second.cFieldServiceName); + uno::Reference< beans::XPropertySet > xTC( + m_xTextFactory->createInstance(sFieldServiceName), + uno::UNO_QUERY_THROW); + + if (!sFirstParam.isEmpty()) + { + xTC->setPropertyValue(sUserIndex.isEmpty() + ? OUString("PrimaryKey") + : OUString("AlternativeText"), + uno::Any(sFirstParam)); + } + + sUserIndex = lcl_trim(sUserIndex); + if (!sUserIndex.isEmpty()) + { + xTC->setPropertyValue("UserIndexName", + uno::Any(sUserIndex)); + } + uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY ); + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (xTextAppend.is()) + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + if (xCrsr.is()) + { + xCrsr->gotoEnd(false); + xText->insertTextContent(uno::Reference< text::XTextRange >( xCrsr, uno::UNO_QUERY_THROW ), xToInsert, false); + } + } + } + break; + case FIELD_CITATION: + { + if( !m_xTextFactory.is() ) + break; + + xFieldInterface = m_xTextFactory->createInstance( + OUString::createFromAscii(aIt->second.cFieldServiceName)); + uno::Reference< beans::XPropertySet > xTC(xFieldInterface, + uno::UNO_QUERY_THROW); + OUString sCmd(pContext->GetCommand());//sCmd is the entire instrText including the index e.g. CITATION Kra06 \l 1033 + if( !sCmd.isEmpty()){ + uno::Sequence<beans::PropertyValue> aValues( comphelper::InitPropertySequence({ + { "Identifier", uno::Any(sCmd) } + })); + xTC->setPropertyValue("Fields", uno::Any(aValues)); + } + uno::Reference< text::XTextContent > xToInsert( xTC, uno::UNO_QUERY ); + + uno::Sequence<beans::PropertyValue> aValues + = m_aFieldStack.back()->getProperties()->GetPropertyValues(); + appendTextContent(xToInsert, aValues); + m_bSetCitation = true; + } + break; + + case FIELD_TC : + { + if( !m_xTextFactory.is() ) + break; + + uno::Reference< beans::XPropertySet > xTC( + m_xTextFactory->createInstance( + OUString::createFromAscii(aIt->second.cFieldServiceName)), + uno::UNO_QUERY_THROW); + if (!sFirstParam.isEmpty()) + { + xTC->setPropertyValue(getPropertyName(PROP_ALTERNATIVE_TEXT), + uno::Any(sFirstParam)); + } + OUString sValue; + // \f TC entry in doc with multiple tables +// if( lcl_FindInCommand( pContext->GetCommand(), 'f', sValue )) +// { + // todo: unsupported +// } + if( lcl_FindInCommand( pContext->GetCommand(), 'l', sValue )) + // \l Outline Level + { + sal_Int32 nLevel = sValue.toInt32(); + if( !sValue.isEmpty() && nLevel >= 0 && nLevel <= 10 ) + xTC->setPropertyValue(getPropertyName(PROP_LEVEL), uno::Any( static_cast<sal_Int16>(nLevel) )); + } +// if( lcl_FindInCommand( pContext->GetCommand(), 'n', sValue )) +// \n Suppress page numbers +// { + //todo: unsupported feature +// } + pContext->SetTC( xTC ); + } + break; + case FIELD_NUMCHARS: + case FIELD_NUMWORDS: + case FIELD_NUMPAGES: + if (xFieldProperties.is()) + xFieldProperties->setPropertyValue( + getPropertyName(PROP_NUMBERING_TYPE), + uno::Any( lcl_ParseNumberingType(pContext->GetCommand()) )); + break; + } + + if (!bCreateEnhancedField) + { + pContext->SetTextField( uno::Reference<text::XTextField>(xFieldInterface, uno::UNO_QUERY) ); + } + } + else + { + /* Unsupported fields will be handled here for docx file. + * To handle unsupported fields used fieldmark API. + */ + OUString aCode( pContext->GetCommand().trim() ); + // Don't waste resources on wrapping shapes inside a fieldmark. + if (sType != "SHAPE" && m_xTextFactory.is() && !m_aTextAppendStack.empty()) + { + xFieldInterface = m_xTextFactory->createInstance("com.sun.star.text.Fieldmark"); + + uno::Reference<text::XFormField> const xFormField(xFieldInterface, uno::UNO_QUERY); + InsertFieldmark(m_aTextAppendStack, xFormField, pContext->GetStartRange(), + pContext->GetFieldId()); + xFormField->setFieldType(ODF_UNHANDLED); + ++m_nStartGenericField; + pContext->SetFormField( xFormField ); + uno::Reference<container::XNameContainer> const xNameCont(xFormField->getParameters()); + // note: setting the code to empty string is *required* in + // m_bForceGenericFields mode, or the export will write + // the ODF_UNHANDLED string! + assert(!m_bForceGenericFields || aCode.isEmpty()); + xNameCont->insertByName(ODF_CODE_PARAM, uno::Any(aCode)); + ww::eField const id(GetWW8FieldId(sType)); + if (id != ww::eNONE) + { // tdf#129247 tdf#134264 set WW8 id for WW8 export + xNameCont->insertByName(ODF_ID_PARAM, uno::Any(OUString::number(id))); + } + } + else + m_bParaHadField = false; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter.dmapper", "Exception in CloseFieldCommand()" ); + } + pContext->SetCommandCompleted(); +} +/*------------------------------------------------------------------------- +//the _current_ fields require a string type result while TOCs accept richt results + -----------------------------------------------------------------------*/ +bool DomainMapper_Impl::IsFieldResultAsString() +{ + bool bRet = false; + OSL_ENSURE( !m_aFieldStack.empty(), "field stack empty?"); + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + bRet = pContext->GetTextField().is() + || pContext->GetFieldId() == FIELD_FORMDROPDOWN + || pContext->GetFieldId() == FIELD_FILLIN; + } + + if (!bRet) + { + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, m_aFieldStack.back())) + { + // If nesting is not allowed, then the result can only be a string. + bRet = true; + } + } + } + return bRet; +} + +void DomainMapper_Impl::AppendFieldResult(std::u16string_view rString) +{ + assert(!m_aFieldStack.empty()); + FieldContextPtr pContext = m_aFieldStack.back(); + SAL_WARN_IF(!pContext, "writerfilter.dmapper", "no field context"); + if (!pContext) + return; + + FieldContextPtr pOuter = GetParentFieldContext(m_aFieldStack); + if (pOuter) + { + if (!IsFieldNestingAllowed(pOuter, pContext)) + { + if (pOuter->IsCommandCompleted()) + { + // Child can't host the field result, forward to parent's result. + pOuter->AppendResult(rString); + } + return; + } + } + + pContext->AppendResult(rString); +} + +// Calculates css::DateTime based on ddddd.sssss since 1899-12-30 +static util::DateTime lcl_dateTimeFromSerial(const double& dSerial) +{ + DateTime d(Date(30, 12, 1899)); + d.AddTime(dSerial); + return d.GetUNODateTime(); +} + +void DomainMapper_Impl::SetFieldResult(OUString const& rResult) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("setFieldResult"); + TagLogger::getInstance().chars(rResult); +#endif + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + + if (m_aFieldStack.size() > 1) + { + // This is a nested field. See if the parent supports nesting on the Writer side. + FieldContextPtr pParentContext = m_aFieldStack[m_aFieldStack.size() - 2]; + if (pParentContext) + { + std::vector<OUString> aParentParts = pParentContext->GetCommandParts(); + // Conditional text fields don't support nesting in Writer. + if (!aParentParts.empty() && aParentParts[0] == "IF") + { + return; + } + } + } + + if( !pContext ) + return; + + uno::Reference<text::XTextField> xTextField = pContext->GetTextField(); + try + { + OSL_ENSURE( xTextField.is() + //||m_xTOC.is() ||m_xTC.is() + //||m_sHyperlinkURL.getLength() + , "DomainMapper_Impl::SetFieldResult: field not created" ); + if(xTextField.is()) + { + try + { + if( m_bSetUserFieldContent ) + { + // user field content has to be set at the field master + uno::Reference< text::XDependentTextField > xDependentField( xTextField, uno::UNO_QUERY_THROW ); + xDependentField->getTextFieldMaster()->setPropertyValue( + getPropertyName(PROP_CONTENT), + uno::Any( rResult )); + } + else if ( m_bSetCitation ) + { + + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + // In case of SetExpression, the field result contains the content of the variable. + uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY); + + bool bIsSetbiblio = xServiceInfo->supportsService("com.sun.star.text.TextField.Bibliography"); + if( bIsSetbiblio ) + { + uno::Any aProperty = xFieldProperties->getPropertyValue("Fields"); + uno::Sequence<beans::PropertyValue> aValues ; + aProperty >>= aValues; + beans::PropertyValue propertyVal; + sal_Int32 nTitleFoundIndex = -1; + for (sal_Int32 i = 0; i < aValues.getLength(); ++i) + { + propertyVal = aValues[i]; + if (propertyVal.Name == "Title") + { + nTitleFoundIndex = i; + break; + } + } + if (nTitleFoundIndex != -1) + { + OUString titleStr; + uno::Any aValue(propertyVal.Value); + aValue >>= titleStr; + titleStr += rResult; + propertyVal.Value <<= titleStr; + aValues.getArray()[nTitleFoundIndex] = propertyVal; + } + else + { + aValues.realloc(aValues.getLength() + 1); + propertyVal.Name = "Title"; + propertyVal.Value <<= rResult; + aValues.getArray()[aValues.getLength() - 1] = propertyVal; + } + xFieldProperties->setPropertyValue("Fields", + uno::Any(aValues)); + } + } + else if ( m_bSetDateValue ) + { + uno::Reference< util::XNumberFormatsSupplier > xNumberSupplier( m_xTextDocument, uno::UNO_QUERY_THROW ); + + uno::Reference<util::XNumberFormatter> xFormatter(util::NumberFormatter::create(m_xComponentContext), uno::UNO_QUERY_THROW); + xFormatter->attachNumberFormatsSupplier( xNumberSupplier ); + sal_Int32 nKey = 0; + + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + + xFieldProperties->getPropertyValue( "NumberFormat" ) >>= nKey; + xFieldProperties->setPropertyValue( + "DateTimeValue", + uno::Any( lcl_dateTimeFromSerial( xFormatter->convertStringToNumber( nKey, rResult ) ) ) ); + } + else + { + uno::Reference< beans::XPropertySet > xFieldProperties( xTextField, uno::UNO_QUERY_THROW); + // In case of SetExpression, and Input fields the field result contains the content of the variable. + uno::Reference<lang::XServiceInfo> xServiceInfo(xTextField, uno::UNO_QUERY); + // there are fields with a content property, which aren't working correctly with + // a generalized try catch of the content, property, so just restrict content + // handling to these explicit services. + const bool bHasContent = xServiceInfo->supportsService("com.sun.star.text.TextField.SetExpression") || + xServiceInfo->supportsService("com.sun.star.text.TextField.Input"); + // If we already have content set, then use the current presentation + OUString sValue; + if (bHasContent) + { + // this will throw for field types without Content + uno::Any aValue(xFieldProperties->getPropertyValue( + getPropertyName(PROP_CONTENT))); + aValue >>= sValue; + } + xFieldProperties->setPropertyValue( + getPropertyName(bHasContent && sValue.isEmpty()? PROP_CONTENT : PROP_CURRENT_PRESENTATION), + uno::Any( rResult )); + + // LO always automatically updates a DocInfo field from the File-Properties-Custom Prop + // while MS Word requires the user to manually refresh the field (with F9). + // In other words, Word lets the field to be out of sync with the controlling variable. + // Marking as FIXEDFLD solves the automatic replacement problem, but of course prevents + // Writer from making any changes, even on an F9 refresh. + OUString sVariable = pContext->GetVariableValue(); + if (rResult.getLength() != sVariable.getLength()) + { + sal_Int32 nLen = sVariable.indexOf('\x0'); + if (nLen >= 0) + sVariable = sVariable.copy(0, nLen); + } + bool bCustomFixedField = rResult != sVariable && + xServiceInfo->supportsService("com.sun.star.text.TextField.DocInfo.Custom"); + + if (bCustomFixedField || xServiceInfo->supportsService( + "com.sun.star.text.TextField.DocInfo.CreateDateTime")) + { + // Creation time is const, don't try to update it. + xFieldProperties->setPropertyValue("IsFixed", uno::Any(true)); + } + } + } + catch( const beans::UnknownPropertyException& ) + { + //some fields don't have a CurrentPresentation (DateTime) + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("writerfilter.dmapper", "DomainMapper_Impl::SetFieldResult"); + } +} + +void DomainMapper_Impl::SetFieldFFData(const FFDataHandler::Pointer_t& pFFDataHandler) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("setFieldFFData"); +#endif + + if (!m_aFieldStack.empty()) + { + FieldContextPtr pContext = m_aFieldStack.back(); + if (pContext) + { + pContext->setFFDataHandler(pFFDataHandler); + } + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void DomainMapper_Impl::PopFieldContext() +{ + if(m_bDiscardHeaderFooter) + return; +#ifdef DBG_UTIL + TagLogger::getInstance().element("popFieldContext"); +#endif + + if (m_aFieldStack.empty()) + return; + + FieldContextPtr pContext = m_aFieldStack.back(); + OSL_ENSURE( pContext, "no field context available"); + if( pContext ) + { + if( !pContext->IsCommandCompleted() ) + CloseFieldCommand(); + + if (!pContext->GetResult().isEmpty()) + { + uno::Reference< beans::XPropertySet > xFieldProperties = pContext->GetCustomField(); + if(xFieldProperties.is()) + SetNumberFormat( pContext->GetResult(), xFieldProperties, true ); + SetFieldResult( pContext->GetResult() ); + } + + //insert the field, TC or TOC + uno::Reference< text::XTextAppend > xTextAppend; + if (!m_aTextAppendStack.empty()) + xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XTextContent > xToInsert( pContext->GetTOC(), uno::UNO_QUERY ); + if( xToInsert.is() ) + { + if (m_bStartedTOC || m_bStartIndex || m_bStartBibliography) + { + // inside SDT, last empty paragraph is also part of index + if (!m_bParaChanged && !m_xSdtEntryStart) + { + // End of index is the first item on a new paragraph - this paragraph + // should not be part of index + auto xCursor + = xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd()); + xCursor->goLeft(1, true); + // delete + xCursor->setString(OUString()); + // But a new paragraph should be started after the index instead + if (m_bIsNewDoc) // this check - see testTdf129402 + { // where finishParagraph inserts between 2 EndNode + xTextAppend->finishParagraph(css::beans::PropertyValues()); + } + else + { + xTextAppend->finishParagraphInsert(css::beans::PropertyValues(), + m_aTextAppendStack.top().xInsertPosition); + } + } + m_bStartedTOC = false; + m_aTextAppendStack.pop(); + m_bTextInserted = false; + m_bParaChanged = true; // the paragraph must stay anyway + } + m_bStartTOC = false; + m_bStartIndex = false; + m_bStartBibliography = false; + if (IsInHeaderFooter() && m_bStartTOCHeaderFooter) + m_bStartTOCHeaderFooter = false; + } + else + { + xToInsert.set(pContext->GetTC(), uno::UNO_QUERY); + if( !xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography ) + xToInsert = pContext->GetTextField(); + if( xToInsert.is() && !m_bStartTOC && !m_bStartIndex && !m_bStartBibliography) + { + PropertyMap aMap; + // Character properties of the field show up here the + // last (always empty) run. Inherit character + // properties from there. + // Also merge in the properties from the field context, + // e.g. SdtEndBefore. + if (m_pLastCharacterContext) + aMap.InsertProps(m_pLastCharacterContext); + aMap.InsertProps(m_aFieldStack.back()->getProperties()); + appendTextContent(xToInsert, aMap.GetPropertyValues()); + CheckRedline( xToInsert->getAnchor( ) ); + } + else + { + uno::Reference< text::XTextCursor > xCrsr = xTextAppend->createTextCursorByRange(pContext->GetStartRange()); + FormControlHelper::Pointer_t pFormControlHelper(pContext->getFormControlHelper()); + if (pFormControlHelper) + { + // xCrsr may be empty e.g. when pContext->GetStartRange() is outside of + // xTextAppend, like when a field started in a parent paragraph is being + // closed inside an anchored text box. It could be possible to throw an + // exception here, and abort import, but Word tolerates such invalid + // input, so it makes sense to do the same (tdf#152200) + if (xCrsr.is()) + { + uno::Reference< text::XFormField > xFormField(pContext->GetFormField()); + if (pFormControlHelper->hasFFDataHandler()) + { + xToInsert.set(xFormField, uno::UNO_QUERY); + if (xFormField.is() && xToInsert.is()) + { + PopFieldmark(m_aTextAppendStack, xCrsr, + pContext->GetFieldId()); + pFormControlHelper->processField(xFormField); + } + else + { + pFormControlHelper->insertControl(xCrsr); + } + } + else + { + PopFieldmark(m_aTextAppendStack, xCrsr, + pContext->GetFieldId()); + uno::Reference<lang::XComponent>(xFormField, uno::UNO_QUERY_THROW)->dispose(); // presumably invalid? + } + } + } + else if (!pContext->GetHyperlinkURL().isEmpty() && xCrsr.is()) + { + if (m_aTextAppendStack.top().xInsertPosition.is()) + { + xCrsr->gotoRange(m_aTextAppendStack.top().xInsertPosition, true); + } + else + { + xCrsr->gotoEnd(true); + } + + // Draw components (like comments) need hyperlinks set differently + SvxUnoTextRangeBase* pDrawText = dynamic_cast<SvxUnoTextRangeBase*>(xCrsr.get()); + if ( pDrawText ) + pDrawText->attachField( std::make_unique<SvxURLField>(pContext->GetHyperlinkURL(), xCrsr->getString(), SvxURLFormat::AppDefault) ); + else + { + uno::Reference< beans::XPropertySet > xCrsrProperties( xCrsr, uno::UNO_QUERY_THROW ); + xCrsrProperties->setPropertyValue(getPropertyName(PROP_HYPER_LINK_U_R_L), uno:: + Any(pContext->GetHyperlinkURL())); + + if (!pContext->GetHyperlinkTarget().isEmpty()) + xCrsrProperties->setPropertyValue("HyperLinkTarget", uno::Any(pContext->GetHyperlinkTarget())); + + if (m_bStartTOC) { + OUString sDisplayName("Index Link"); + xCrsrProperties->setPropertyValue("VisitedCharStyleName",uno::Any(sDisplayName)); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName",uno::Any(sDisplayName)); + } + else + { + uno::Any aAny = xCrsrProperties->getPropertyValue("CharStyleName"); + OUString charStyle; + if (css::uno::fromAny(aAny, &charStyle)) + { + if (charStyle.isEmpty()) + { + xCrsrProperties->setPropertyValue("VisitedCharStyleName", uno::Any(OUString("Default Style"))); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", uno::Any(OUString("Default Style"))); + } + else if (charStyle.equalsIgnoreAsciiCase("Internet Link")) + { + xCrsrProperties->setPropertyValue("CharStyleName", uno::Any(OUString("Default Style"))); + } + else + { + xCrsrProperties->setPropertyValue("VisitedCharStyleName", aAny); + xCrsrProperties->setPropertyValue("UnvisitedCharStyleName", aAny); + } + } + } + } + } + else if (m_nStartGenericField != 0) + { + --m_nStartGenericField; + PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId()); + if(m_bTextInserted) + { + m_bTextInserted = false; + } + } + } + } + } + catch(const lang::IllegalArgumentException&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "PopFieldContext()" ); + } + } + + //TOCs have to include all the imported content + } + + std::vector<FieldParagraph> aParagraphsToFinish; + if (pContext) + { + aParagraphsToFinish = pContext->GetParagraphsToFinish(); + } + + //remove the field context + m_aFieldStack.pop_back(); + + // Finish the paragraph(s) now that the field is closed. + for (const auto& rFinish : aParagraphsToFinish) + { + finishParagraph(rFinish.m_pPropertyMap, rFinish.m_bRemove); + } +} + + +void DomainMapper_Impl::SetBookmarkName( const OUString& rBookmarkName ) +{ + BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( m_sCurrentBkmkId ); + if( aBookmarkIter != m_aBookmarkMap.end() ) + { + // fields are internal bookmarks: consume redundant "normal" bookmark + if ( IsOpenField() ) + { + FFDataHandler::Pointer_t pFFDataHandler(GetTopFieldContext()->getFFDataHandler()); + if (pFFDataHandler && pFFDataHandler->getName() == rBookmarkName) + { + // HACK: At the END marker, StartOrEndBookmark will START + // a bookmark which will eventually be abandoned, not created. + m_aBookmarkMap.erase(aBookmarkIter); + return; + } + } + + aBookmarkIter->second.m_sBookmarkName = m_sCurrentBkmkPrefix + rBookmarkName; + m_sCurrentBkmkPrefix.clear(); + } + else + { + m_sCurrentBkmkName = rBookmarkName; + m_sCurrentBkmkPrefix.clear(); + } +} + +// This method was used as-is for DomainMapper_Impl::startOrEndPermissionRange() implementation. +void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) +{ + /* + * Add the dummy paragraph to handle section properties + * iff the first element in the section is a table. If the dummy para is not added yet, then add it; + * So bookmark is not attached to the wrong paragraph. + */ + if(hasTableManager() && getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() &&!GetIsTextFrameInserted()) + { + AddDummyParaForTableInSection(); + } + + bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection(); + if (m_aTextAppendStack.empty()) + return; + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + BookmarkMap_t::iterator aBookmarkIter = m_aBookmarkMap.find( rId ); + //is the bookmark name already registered? + try + { + if( aBookmarkIter != m_aBookmarkMap.end() ) + { + if (m_xTextFactory.is()) + { + uno::Reference< text::XTextContent > xBookmark( m_xTextFactory->createInstance( "com.sun.star.text.Bookmark" ), uno::UNO_QUERY_THROW ); + uno::Reference< text::XTextCursor > xCursor; + uno::Reference< text::XText > xText = aBookmarkIter->second.m_xTextRange->getText(); + if( aBookmarkIter->second.m_bIsStartOfText && !bIsAfterDummyPara) + { + xCursor = xText->createTextCursorByRange( xText->getStart() ); + } + else + { + xCursor = xText->createTextCursorByRange( aBookmarkIter->second.m_xTextRange ); + xCursor->goRight( 1, false ); + } + + xCursor->gotoRange( xTextAppend->getEnd(), true ); + // A Paragraph was recently finished, and a new Paragraph has not been started as yet + // then move the bookmark-End to the earlier paragraph + if (IsOutsideAParagraph()) + { + // keep bookmark range + uno::Reference< text::XTextRange > xStart = xCursor->getStart(); + xCursor->goLeft( 1, false ); + xCursor->gotoRange(xStart, true ); + } + uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW ); + SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark"); + //todo: make sure the name is not used already! + xBkmNamed->setName( aBookmarkIter->second.m_sBookmarkName ); + xTextAppend->insertTextContent( uno::Reference< text::XTextRange >( xCursor, uno::UNO_QUERY_THROW), xBookmark, !xCursor->isCollapsed() ); + } + m_aBookmarkMap.erase( aBookmarkIter ); + m_sCurrentBkmkId.clear(); + } + else + { + //otherwise insert a text range as marker + bool bIsStart = true; + uno::Reference< text::XTextRange > xCurrent; + if (xTextAppend.is()) + { + uno::Reference<text::XTextCursor> const xCursor = + xTextAppend->createTextCursorByRange( + m_aTextAppendStack.top().xInsertPosition.is() + ? m_aTextAppendStack.top().xInsertPosition + : xTextAppend->getEnd() ); + + if (!xCursor) + return; + + if (!bIsAfterDummyPara) + bIsStart = !xCursor->goLeft(1, false); + xCurrent = xCursor->getStart(); + } + m_sCurrentBkmkId = rId; + m_aBookmarkMap.emplace( rId, BookmarkInsertPosition( bIsStart, m_sCurrentBkmkName, xCurrent ) ); + m_sCurrentBkmkName.clear(); + } + } + catch( const uno::Exception& ) + { + //TODO: What happens to bookmarks where start and end are at different XText objects? + } +} + +void DomainMapper_Impl::SetMoveBookmark( bool bIsFrom ) +{ + static constexpr OUStringLiteral MoveFrom_Bookmark_NamePrefix = u"__RefMoveFrom__"; + static constexpr OUStringLiteral MoveTo_Bookmark_NamePrefix = u"__RefMoveTo__"; + if ( bIsFrom ) + m_sCurrentBkmkPrefix = MoveFrom_Bookmark_NamePrefix; + else + m_sCurrentBkmkPrefix = MoveTo_Bookmark_NamePrefix; +} + +void DomainMapper_Impl::setPermissionRangeEd(const OUString& user) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_Ed = user; + else + m_sCurrentPermEd = user; +} + +void DomainMapper_Impl::setPermissionRangeEdGrp(const OUString& group) +{ + PermMap_t::iterator aPremIter = m_aPermMap.find(m_sCurrentPermId); + if (aPremIter != m_aPermMap.end()) + aPremIter->second.m_EdGrp = group; + else + m_sCurrentPermEdGrp = group; +} + +// This method is based on implementation from DomainMapper_Impl::StartOrEndBookmark() +void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId) +{ + /* + * Add the dummy paragraph to handle section properties + * if the first element in the section is a table. If the dummy para is not added yet, then add it; + * So permission is not attached to the wrong paragraph. + */ + if (getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted()) + { + AddDummyParaForTableInSection(); + } + + if (m_aTextAppendStack.empty()) + return; + + const bool bIsAfterDummyPara = GetIsDummyParaAddedForTableInSection() && GetIsFirstParagraphInSection(); + + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + PermMap_t::iterator aPermIter = m_aPermMap.find(permissinId); + + //is the bookmark name already registered? + try + { + if (aPermIter == m_aPermMap.end()) + { + //otherwise insert a text range as marker + bool bIsStart = true; + uno::Reference< text::XTextRange > xCurrent; + if (xTextAppend.is()) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd()); + + if (!bIsAfterDummyPara) + bIsStart = !xCursor->goLeft(1, false); + xCurrent = xCursor->getStart(); + } + + // register the start of the new permission + m_sCurrentPermId = permissinId; + m_aPermMap.emplace(permissinId, PermInsertPosition(bIsStart, permissinId, m_sCurrentPermEd, m_sCurrentPermEdGrp, xCurrent)); + + // clean up + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + else + { + if (m_xTextFactory.is()) + { + uno::Reference< text::XTextCursor > xCursor; + uno::Reference< text::XText > xText = aPermIter->second.m_xTextRange->getText(); + if (aPermIter->second.m_bIsStartOfText && !bIsAfterDummyPara) + { + xCursor = xText->createTextCursorByRange(xText->getStart()); + } + else + { + xCursor = xText->createTextCursorByRange(aPermIter->second.m_xTextRange); + xCursor->goRight(1, false); + } + + xCursor->gotoRange(xTextAppend->getEnd(), true); + // A Paragraph was recently finished, and a new Paragraph has not been started as yet + // then move the bookmark-End to the earlier paragraph + if (IsOutsideAParagraph()) + { + xCursor->goLeft(1, false); + } + + // create a new bookmark using specific bookmark name pattern for permissions + uno::Reference< text::XTextContent > xPerm(m_xTextFactory->createInstance("com.sun.star.text.Bookmark"), uno::UNO_QUERY_THROW); + uno::Reference< container::XNamed > xPermNamed(xPerm, uno::UNO_QUERY_THROW); + xPermNamed->setName(aPermIter->second.createBookmarkName()); + + // add new bookmark + const bool bAbsorb = !xCursor->isCollapsed(); + uno::Reference< text::XTextRange > xCurrent(xCursor, uno::UNO_QUERY_THROW); + xTextAppend->insertTextContent(xCurrent, xPerm, bAbsorb); + } + + // remove processed permission + m_aPermMap.erase(aPermIter); + + // clean up + m_sCurrentPermId = 0; + m_sCurrentPermEd.clear(); + m_sCurrentPermEdGrp.clear(); + } + } + catch (const uno::Exception&) + { + //TODO: What happens to bookmarks where start and end are at different XText objects? + } +} + +void DomainMapper_Impl::AddAnnotationPosition( + const bool bStart, + const sal_Int32 nAnnotationId) +{ + if (m_aTextAppendStack.empty()) + return; + + // Create a cursor, pointing to the current position. + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + uno::Reference<text::XTextRange> xCurrent; + if (xTextAppend.is()) + { + uno::Reference<text::XTextCursor> xCursor; + if (m_bIsNewDoc) + xCursor = xTextAppend->createTextCursorByRange(xTextAppend->getEnd()); + else + xCursor = m_aTextAppendStack.top().xCursor; + if (xCursor.is()) + xCurrent = xCursor->getStart(); + } + + // And save it, to be used by PopAnnotation() later. + AnnotationPosition& aAnnotationPosition = m_aAnnotationPositions[ nAnnotationId ]; + if (bStart) + { + aAnnotationPosition.m_xStart = xCurrent; + } + else + { + aAnnotationPosition.m_xEnd = xCurrent; + } + m_aAnnotationPositions[ nAnnotationId ] = aAnnotationPosition; +} + +GraphicImportPtr const & DomainMapper_Impl::GetGraphicImport(GraphicImportType eGraphicImportType) +{ + if(!m_pGraphicImport) + m_pGraphicImport = new GraphicImport( m_xComponentContext, m_xTextFactory, m_rDMapper, eGraphicImportType, m_aPositionOffsets, m_aAligns, m_aPositivePercentages ); + return m_pGraphicImport; +} +/*------------------------------------------------------------------------- + reset graphic import if the last import resulted in a shape, not a graphic + -----------------------------------------------------------------------*/ +void DomainMapper_Impl::ResetGraphicImport() +{ + m_pGraphicImport.clear(); +} + + +void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference< Properties >::Pointer_t& ref, GraphicImportType eGraphicImportType) +{ + GetGraphicImport(eGraphicImportType); + if( eGraphicImportType != IMPORT_AS_DETECTED_INLINE && eGraphicImportType != IMPORT_AS_DETECTED_ANCHOR ) + { + //create the graphic + ref->resolve( *m_pGraphicImport ); + } + + //insert it into the document at the current cursor position + + uno::Reference<text::XTextContent> xTextContent + (m_pGraphicImport->GetGraphicObject()); + + // In case the SDT starts with the text portion of the graphic, then set the SDT properties here. + bool bHasGrabBag = false; + uno::Reference<beans::XPropertySet> xPropertySet(xTextContent, uno::UNO_QUERY); + if (xPropertySet.is()) + { + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + bHasGrabBag = xPropertySetInfo->hasPropertyByName("FrameInteropGrabBag"); + // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one. + if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph()) + { + comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue("FrameInteropGrabBag")); + aFrameGrabBag["SdtPr"] <<= m_pSdtHelper->getInteropGrabBagAndClear(); + xPropertySet->setPropertyValue("FrameInteropGrabBag", uno::Any(aFrameGrabBag.getAsConstPropertyValueList())); + } + } + + /* Set "SdtEndBefore" property on Drawing. + * It is required in a case when Drawing appears immediately after first run i.e. + * there is no text/space/tab in between two runs. + * In this case "SdtEndBefore" property needs to be set on Drawing. + */ + if(IsSdtEndBefore()) + { + if(xPropertySet.is() && bHasGrabBag) + { + uno::Sequence<beans::PropertyValue> aFrameGrabBag( comphelper::InitPropertySequence({ + { "SdtEndBefore", uno::Any(true) } + })); + xPropertySet->setPropertyValue("FrameInteropGrabBag",uno::Any(aFrameGrabBag)); + } + } + + + // Update the shape properties if it is embedded object. + if(m_xEmbedded.is()){ + if (m_pGraphicImport->GetXShapeObject()) + m_pGraphicImport->GetXShapeObject()->setPosition( + m_pGraphicImport->GetGraphicObjectPosition()); + + uno::Reference<drawing::XShape> xShape = m_pGraphicImport->GetXShapeObject(); + UpdateEmbeddedShapeProps(xShape); + if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR) + { + uno::Reference<beans::XPropertySet> xEmbeddedProps(m_xEmbedded, uno::UNO_QUERY); + xEmbeddedProps->setPropertyValue("AnchorType", uno::Any(text::TextContentAnchorType_AT_CHARACTER)); + xEmbeddedProps->setPropertyValue("IsFollowingTextFlow", uno::Any(m_pGraphicImport->GetLayoutInCell())); + uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY); + xEmbeddedProps->setPropertyValue("HoriOrient", xShapeProps->getPropertyValue("HoriOrient")); + xEmbeddedProps->setPropertyValue("HoriOrientPosition", xShapeProps->getPropertyValue("HoriOrientPosition")); + xEmbeddedProps->setPropertyValue("HoriOrientRelation", xShapeProps->getPropertyValue("HoriOrientRelation")); + xEmbeddedProps->setPropertyValue("VertOrient", xShapeProps->getPropertyValue("VertOrient")); + xEmbeddedProps->setPropertyValue("VertOrientPosition", xShapeProps->getPropertyValue("VertOrientPosition")); + xEmbeddedProps->setPropertyValue("VertOrientRelation", xShapeProps->getPropertyValue("VertOrientRelation")); + //tdf123873 fix missing textwrap import + xEmbeddedProps->setPropertyValue("TextWrap", xShapeProps->getPropertyValue("TextWrap")); + + // GraphicZOrderHelper::findZOrder() was called already, so can just copy it over. + xEmbeddedProps->setPropertyValue("ZOrder", xShapeProps->getPropertyValue("ZOrder")); + } + } + //insert it into the document at the current cursor position + OSL_ENSURE( xTextContent.is(), "DomainMapper_Impl::ImportGraphic"); + if( xTextContent.is()) + { + bool bAppend = true; + // workaround for images anchored to characters: add ZWSPs around the anchoring point + if ( eGraphicImportType != IMPORT_AS_DETECTED_INLINE && !m_aRedlines.top().empty() ) + { + uno::Reference< text::XTextAppend > xTextAppend = m_aTextAppendStack.top().xTextAppend; + if(xTextAppend.is()) + { + try + { + uno::Reference< text::XText > xText = xTextAppend->getText(); + uno::Reference< text::XTextCursor > xCrsr = xText->createTextCursor(); + xCrsr->gotoEnd(false); + PropertyMapPtr pEmpty(new PropertyMap()); + appendTextPortion(u"", pEmpty); + appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() ); + bAppend = false; + xCrsr->gotoEnd(false); + appendTextPortion(u"", pEmpty); + + m_bRedlineImageInPreviousRun = true; + m_previousRedline = m_currentRedline; + } + catch( const uno::Exception& ) + { + } + } + } + + if ( bAppend ) + appendTextContent( xTextContent, uno::Sequence< beans::PropertyValue >() ); + + if (eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR && !m_aTextAppendStack.empty()) + { + // Remember this object is anchored to the current paragraph. + AnchoredObjectInfo aInfo; + aInfo.m_xAnchoredObject = xTextContent; + if (m_pGraphicImport) + { + // We still have the graphic import around, remember the original margin, so later + // SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing() can use it. + aInfo.m_nLeftMargin = m_pGraphicImport->GetLeftMarginOrig(); + } + m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo); + } + else if (eGraphicImportType == IMPORT_AS_DETECTED_INLINE) + { + m_bParaWithInlineObject = true; + + // store inline images with track changes, because the anchor point + // to set redlining is not available yet + if (!m_aTextAppendStack.empty() && !m_aRedlines.top().empty() ) + { + // Remember this object is anchored to the current paragraph. + AnchoredObjectInfo aInfo; + aInfo.m_xAnchoredObject = xTextContent; + aInfo.m_xRedlineForInline = m_aRedlines.top().back(); + m_aTextAppendStack.top().m_aAnchoredObjects.push_back(aInfo); + } + + } + } + + // Clear the reference, so in case the embedded object is inside a + // TextFrame, we won't try to resize it (to match the size of the + // TextFrame) here. + m_xEmbedded.clear(); + m_pGraphicImport.clear(); +} + + +void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn ) +{ + if( !m_bLineNumberingSet ) + { + try + { + uno::Reference< text::XLineNumberingProperties > xLineProperties( m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProperties = xLineProperties->getLineNumberingProperties(); + uno::Any aTrue( uno::Any( true )); + xProperties->setPropertyValue( getPropertyName( PROP_IS_ON ), aTrue); + xProperties->setPropertyValue( getPropertyName( PROP_COUNT_EMPTY_LINES ), aTrue ); + xProperties->setPropertyValue( getPropertyName( PROP_COUNT_LINES_IN_FRAMES ), uno::Any( false ) ); + xProperties->setPropertyValue( getPropertyName( PROP_INTERVAL ), uno::Any( static_cast< sal_Int16 >( nLnnMod ))); + xProperties->setPropertyValue( getPropertyName( PROP_DISTANCE ), uno::Any( ConversionHelper::convertTwipToMM100(ndxaLnn) )); + xProperties->setPropertyValue( getPropertyName( PROP_NUMBER_POSITION ), uno::Any( style::LineNumberPosition::LEFT)); + xProperties->setPropertyValue( getPropertyName( PROP_NUMBERING_TYPE ), uno::Any( style::NumberingType::ARABIC)); + xProperties->setPropertyValue( getPropertyName( PROP_RESTART_AT_EACH_PAGE ), uno::Any( nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newPage )); + } + catch( const uno::Exception& ) + {} + } + m_bLineNumberingSet = true; + uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( GetTextDocument(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xStyles; + xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xStyles; + lcl_linenumberingHeaderFooter( xStyles, "Header", this ); + lcl_linenumberingHeaderFooter( xStyles, "Footer", this ); +} + + +void DomainMapper_Impl::SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue ) +{ + nValue = ConversionHelper::convertTwipToMM100(nValue); + switch(eElement) + { + case PAGE_MAR_TOP : m_aPageMargins.top = nValue; break; + case PAGE_MAR_RIGHT : m_aPageMargins.right = nValue; break; + case PAGE_MAR_BOTTOM : m_aPageMargins.bottom = nValue; break; + case PAGE_MAR_LEFT : m_aPageMargins.left = nValue; break; + case PAGE_MAR_HEADER : m_aPageMargins.header = nValue; break; + case PAGE_MAR_FOOTER : m_aPageMargins.footer = nValue; break; + case PAGE_MAR_GUTTER: + m_aPageMargins.gutter = nValue; + break; + } +} + + +PageMar::PageMar() + : top(ConversionHelper::convertTwipToMM100( sal_Int32(1440))) + // This is strange, the RTF spec says it's 1800, but it's clearly 1440 in Word + // OOXML seems not to specify a default value + , right(ConversionHelper::convertTwipToMM100( sal_Int32(1440))) + , bottom(top) + , left(right) + , header(ConversionHelper::convertTwipToMM100(sal_Int32(720))) + , footer(header) + , gutter(0) +{ +} + + +void DomainMapper_Impl::RegisterFrameConversion( + uno::Reference< text::XTextRange > const& xFrameStartRange, + uno::Reference< text::XTextRange > const& xFrameEndRange, + std::vector<beans::PropertyValue>&& rFrameProperties + ) +{ + OSL_ENSURE( + m_aFrameProperties.empty() && !m_xFrameStartRange.is() && !m_xFrameEndRange.is(), + "frame properties not removed"); + m_aFrameProperties = std::move(rFrameProperties); + m_xFrameStartRange = xFrameStartRange; + m_xFrameEndRange = xFrameEndRange; +} + + +void DomainMapper_Impl::ExecuteFrameConversion() +{ + if( m_xFrameStartRange.is() && m_xFrameEndRange.is() && !m_bDiscardHeaderFooter ) + { + std::vector<sal_Int32> redPos, redLen; + try + { + uno::Reference< text::XTextAppendAndConvert > xTextAppendAndConvert( GetTopTextAppend(), uno::UNO_QUERY_THROW ); + // convert redline ranges to cursor movement and character length + sal_Int32 redIdx; + lcl_CopyRedlines(GetTopTextAppend(), m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx); + + const uno::Reference< text::XTextContent >& xTextContent = xTextAppendAndConvert->convertToTextFrame( + m_xFrameStartRange, + m_xFrameEndRange, + comphelper::containerToSequence(m_aFrameProperties) ); + + uno::Reference< text::XText > xDest( xTextContent, uno::UNO_QUERY_THROW ); + lcl_PasteRedlines(xDest, m_aStoredRedlines[StoredRedlines::FRAME], redPos, redLen, redIdx); + } + catch( const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION( "writerfilter.dmapper", "Exception caught when converting to frame"); + } + + m_bIsActualParagraphFramed = false; + + if (redPos.size() == m_aStoredRedlines[StoredRedlines::FRAME].size()/3) + { + for( sal_Int32 i = m_aStoredRedlines[StoredRedlines::FRAME].size() - 1; i >= 0; --i) + { + // keep redlines of floating tables to process them in CloseSectionGroup() + if ( redPos[i/3] != -1 ) + { + m_aStoredRedlines[StoredRedlines::FRAME].erase(m_aStoredRedlines[StoredRedlines::FRAME].begin() + i); + } + } + } + else + m_aStoredRedlines[StoredRedlines::FRAME].clear(); + } + m_xFrameStartRange = nullptr; + m_xFrameEndRange = nullptr; + m_aFrameProperties.clear(); +} + +void DomainMapper_Impl::AddNewRedline( sal_uInt32 sprmId ) +{ + RedlineParamsPtr pNew( new RedlineParams ); + pNew->m_nToken = XML_mod; + if ( !m_bIsParaMarkerChange ) + { + // <w:rPrChange> applies to the whole <w:r>, <w:pPrChange> applies to the whole <w:p>, + // so keep those two in CONTEXT_CHARACTERS and CONTEXT_PARAGRAPH, which will take + // care of their scope (i.e. when they should be used and discarded). + // Let's keep the rest the same way they used to be handled (explicitly dropped + // from a global stack by endtrackchange), but quite possibly they should not be handled + // that way either (I don't know). + if( sprmId == NS_ooxml::LN_EG_RPrContent_rPrChange ) + GetTopContextOfType( CONTEXT_CHARACTER )->Redlines().push_back( pNew ); + else if( sprmId == NS_ooxml::LN_CT_PPr_pPrChange ) + GetTopContextOfType( CONTEXT_PARAGRAPH )->Redlines().push_back( pNew ); + else if( sprmId != NS_ooxml::LN_CT_ParaRPr_rPrChange ) + m_aRedlines.top().push_back( pNew ); + } + else + { + m_pParaMarkerRedline = pNew; + } + // Newly read data will go into this redline. + m_currentRedline = pNew; +} + +void DomainMapper_Impl::SetCurrentRedlineIsRead() +{ + m_currentRedline.clear(); +} + +sal_Int32 DomainMapper_Impl::GetCurrentRedlineToken( ) const +{ + assert(m_currentRedline); + return m_currentRedline->m_nToken; +} + +void DomainMapper_Impl::SetCurrentRedlineAuthor( const OUString& sAuthor ) +{ + if (!m_xAnnotationField.is()) + { + if (m_currentRedline) + m_currentRedline->m_sAuthor = sAuthor; + else + SAL_INFO("writerfilter.dmapper", "numberingChange not implemented"); + } + else + m_xAnnotationField->setPropertyValue("Author", uno::Any(sAuthor)); +} + +void DomainMapper_Impl::SetCurrentRedlineInitials( const OUString& sInitials ) +{ + if (m_xAnnotationField.is()) + m_xAnnotationField->setPropertyValue("Initials", uno::Any(sInitials)); +} + +void DomainMapper_Impl::SetCurrentRedlineDate( const OUString& sDate ) +{ + if (!m_xAnnotationField.is()) + { + if (m_currentRedline) + m_currentRedline->m_sDate = sDate; + else + SAL_INFO("writerfilter.dmapper", "numberingChange not implemented"); + } + else + m_xAnnotationField->setPropertyValue("DateTimeValue", uno::Any(ConversionHelper::ConvertDateStringToDateTime(sDate))); +} + +void DomainMapper_Impl::SetCurrentRedlineId( sal_Int32 sId ) +{ + if (m_xAnnotationField.is()) + { + m_nAnnotationId = sId; + } + else + { + // This should be an assert, but somebody had the smart idea to reuse this function also for comments and whatnot, + // and in some cases the id is actually not handled, which may be in fact a bug. + if( !m_currentRedline) + SAL_INFO("writerfilter.dmapper", "no current redline"); + } +} + +void DomainMapper_Impl::SetCurrentRedlineToken( sal_Int32 nToken ) +{ + assert(m_currentRedline); + m_currentRedline->m_nToken = nToken; +} + +void DomainMapper_Impl::SetCurrentRedlineRevertProperties( const uno::Sequence<beans::PropertyValue>& aProperties ) +{ + assert(m_currentRedline); + m_currentRedline->m_aRevertProperties = aProperties; +} + + +// This removes only the last redline stored here, those stored in contexts are automatically removed when +// the context is destroyed. +void DomainMapper_Impl::RemoveTopRedline( ) +{ + if (m_aRedlines.top().empty()) + { + if (GetFootnoteCount() > -1 || GetEndnoteCount() > -1) + return; + SAL_WARN("writerfilter.dmapper", "RemoveTopRedline called with empty stack"); + throw uno::Exception("RemoveTopRedline failed", nullptr); + } + m_aRedlines.top().pop_back( ); + m_currentRedline.clear(); +} + +void DomainMapper_Impl::ApplySettingsTable() +{ + if (!(m_pSettingsTable && m_xTextFactory.is())) + return; + + try + { + uno::Reference< beans::XPropertySet > xTextDefaults(m_xTextFactory->createInstance("com.sun.star.text.Defaults"), uno::UNO_QUERY_THROW ); + sal_Int32 nDefTab = m_pSettingsTable->GetDefaultTabStop(); + xTextDefaults->setPropertyValue( getPropertyName( PROP_TAB_STOP_DISTANCE ), uno::Any(nDefTab) ); + if (m_pSettingsTable->GetLinkStyles()) + { + // If linked styles are enabled, set paragraph defaults from Word's default template + xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_BOTTOM_MARGIN), uno::Any(ConversionHelper::convertTwipToMM100(200))); + style::LineSpacing aSpacing; + aSpacing.Mode = style::LineSpacingMode::PROP; + aSpacing.Height = sal_Int16(115); + xTextDefaults->setPropertyValue(getPropertyName(PROP_PARA_LINE_SPACING), uno::Any(aSpacing)); + } + + if (m_pSettingsTable->GetZoomFactor() || m_pSettingsTable->GetView()) + { + std::vector<beans::PropertyValue> aViewProps; + if (m_pSettingsTable->GetZoomFactor()) + { + aViewProps.emplace_back("ZoomFactor", -1, uno::Any(m_pSettingsTable->GetZoomFactor()), beans::PropertyState_DIRECT_VALUE); + aViewProps.emplace_back("VisibleBottom", -1, uno::Any(sal_Int32(0)), beans::PropertyState_DIRECT_VALUE); + aViewProps.emplace_back("ZoomType", -1, + uno::Any(m_pSettingsTable->GetZoomType()), + beans::PropertyState_DIRECT_VALUE); + } + rtl::Reference< comphelper::IndexedPropertyValuesContainer > xBox = new comphelper::IndexedPropertyValuesContainer(); + xBox->insertByIndex(sal_Int32(0), uno::Any(comphelper::containerToSequence(aViewProps))); + uno::Reference<document::XViewDataSupplier> xViewDataSupplier(m_xTextDocument, uno::UNO_QUERY); + xViewDataSupplier->setViewData(xBox); + } + + uno::Reference< beans::XPropertySet > xSettings(m_xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY); + + if (m_pSettingsTable->GetDoNotExpandShiftReturn()) + xSettings->setPropertyValue( "DoNotJustifyLinesWithManualBreak", uno::Any(true) ); + if (m_pSettingsTable->GetUsePrinterMetrics()) + xSettings->setPropertyValue("PrinterIndependentLayout", uno::Any(document::PrinterIndependentLayout::DISABLED)); + if( m_pSettingsTable->GetEmbedTrueTypeFonts()) + xSettings->setPropertyValue( getPropertyName( PROP_EMBED_FONTS ), uno::Any(true) ); + if( m_pSettingsTable->GetEmbedSystemFonts()) + xSettings->setPropertyValue( getPropertyName( PROP_EMBED_SYSTEM_FONTS ), uno::Any(true) ); + xSettings->setPropertyValue("AddParaTableSpacing", uno::Any(m_pSettingsTable->GetDoNotUseHTMLParagraphAutoSpacing())); + if (m_pSettingsTable->GetNoLeading()) + { + xSettings->setPropertyValue("AddExternalLeading", uno::Any(!m_pSettingsTable->GetNoLeading())); + } + if( m_pSettingsTable->GetProtectForm() ) + xSettings->setPropertyValue("ProtectForm", uno::Any( true )); + if( m_pSettingsTable->GetReadOnly() ) + xSettings->setPropertyValue("LoadReadonly", uno::Any( true )); + if (m_pSettingsTable->GetGutterAtTop()) + { + xSettings->setPropertyValue("GutterAtTop", uno::Any(true)); + } + uno::Sequence<beans::PropertyValue> aWriteProtection + = m_pSettingsTable->GetWriteProtectionSettings(); + if (aWriteProtection.hasElements()) + xSettings->setPropertyValue("ModifyPasswordInfo", uno::Any(aWriteProtection)); + } + catch(const uno::Exception&) + { + } +} + +SectionPropertyMap * DomainMapper_Impl::GetSectionContext() +{ + SectionPropertyMap* pSectionContext = nullptr; + //the section context is not available before the first call of startSectionGroup() + if( !IsAnyTableImport() ) + { + PropertyMapPtr pContext = GetTopContextOfType(CONTEXT_SECTION); + pSectionContext = dynamic_cast< SectionPropertyMap* >( pContext.get() ); + } + + return pSectionContext; +} + +void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any& value) +{ + deferredCharacterProperties[ id ] = value; +} + +void DomainMapper_Impl::processDeferredCharacterProperties() +{ + // Actually process in DomainMapper, so that it's the same source file like normal processing. + if( !deferredCharacterProperties.empty()) + { + m_rDMapper.processDeferredCharacterProperties( deferredCharacterProperties ); + deferredCharacterProperties.clear(); + } +} + +sal_Int32 DomainMapper_Impl::getNumberingProperty(const sal_Int32 nListId, sal_Int32 nNumberingLevel, const OUString& aProp) +{ + sal_Int32 nRet = 0; + if ( nListId < 0 ) + return nRet; + + try + { + if (nNumberingLevel < 0) // It seems it's valid to omit numbering level, and in that case it means zero. + nNumberingLevel = 0; + + auto const pList(GetListTable()->GetList(nListId)); + assert(pList); + const OUString aListName = pList->GetStyleName(); + const uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(GetTextDocument(), uno::UNO_QUERY_THROW); + const uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xNumberingStyles; + xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles; + const uno::Reference<beans::XPropertySet> xStyle(xNumberingStyles->getByName(aListName), uno::UNO_QUERY); + const uno::Reference<container::XIndexAccess> xNumberingRules(xStyle->getPropertyValue("NumberingRules"), uno::UNO_QUERY); + if (xNumberingRules.is()) + { + uno::Sequence<beans::PropertyValue> aProps; + xNumberingRules->getByIndex(nNumberingLevel) >>= aProps; + auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps), + [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; }); + if (pProp != std::cend(aProps)) + pProp->Value >>= nRet; + } + } + catch( const uno::Exception& ) + { + // This can happen when the doc contains some hand-crafted invalid list level. + } + + return nRet; +} + +sal_Int32 DomainMapper_Impl::getCurrentNumberingProperty(const OUString& aProp) +{ + sal_Int32 nRet = 0; + + std::optional<PropertyMap::Property> pProp = m_pTopContext->getProperty(PROP_NUMBERING_RULES); + uno::Reference<container::XIndexAccess> xNumberingRules; + if (pProp) + xNumberingRules.set(pProp->second, uno::UNO_QUERY); + pProp = m_pTopContext->getProperty(PROP_NUMBERING_LEVEL); + // Default numbering level is the first one. + sal_Int32 nNumberingLevel = 0; + if (pProp) + pProp->second >>= nNumberingLevel; + if (xNumberingRules.is()) + { + uno::Sequence<beans::PropertyValue> aProps; + xNumberingRules->getByIndex(nNumberingLevel) >>= aProps; + auto pPropVal = std::find_if(std::cbegin(aProps), std::cend(aProps), + [&aProp](const beans::PropertyValue& rProp) { return rProp.Name == aProp; }); + if (pPropVal != std::cend(aProps)) + pPropVal->Value >>= nRet; + } + + return nRet; +} + + +void DomainMapper_Impl::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +void DomainMapper_Impl::disableInteropGrabBag() +{ + m_aInteropGrabBagName.clear(); + m_aInteropGrabBag.clear(); + m_aSubInteropGrabBag.clear(); +} + +bool DomainMapper_Impl::isInteropGrabBagEnabled() const +{ + return !(m_aInteropGrabBagName.isEmpty()); +} + +void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= aValue; + rInteropGrabBag.push_back(aProperty); +} + +void DomainMapper_Impl::appendGrabBag(std::vector<beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<beans::PropertyValue>& rValue) +{ + if (m_aInteropGrabBagName.isEmpty()) + return; + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= comphelper::containerToSequence(rValue); + rValue.clear(); + rInteropGrabBag.push_back(aProperty); +} + +void DomainMapper_Impl::substream(Id rName, + ::writerfilter::Reference<Stream>::Pointer_t const& ref) +{ +#ifndef NDEBUG + size_t contextSize(m_aContextStack.size()); + size_t propSize[NUMBER_OF_CONTEXTS]; + for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) { + propSize[i] = m_aPropertyStacks[i].size(); + } +#endif + + // Save "has footnote" state, which is specific to a section in the body + // text, so state from substreams is not relevant. + bool bHasFtn = m_bHasFtn; + + //finalize any waiting frames before starting alternate streams + CheckUnregisteredFrameConversion(); + ExecuteFrameConversion(); + + appendTableManager(); + // Appending a TableManager resets its TableHandler, so we need to append + // that as well, or tables won't be imported properly in headers/footers. + appendTableHandler(); + getTableManager().startLevel(); + + //import of page header/footer + //Ensure that only one header/footer per section is pushed + + switch( rName ) + { + case NS_ooxml::LN_headerl: + PushPageHeader(SectionPropertyMap::PAGE_LEFT); + break; + case NS_ooxml::LN_headerr: + PushPageHeader(SectionPropertyMap::PAGE_RIGHT); + break; + case NS_ooxml::LN_headerf: + PushPageHeader(SectionPropertyMap::PAGE_FIRST); + break; + case NS_ooxml::LN_footerl: + PushPageFooter(SectionPropertyMap::PAGE_LEFT); + break; + case NS_ooxml::LN_footerr: + PushPageFooter(SectionPropertyMap::PAGE_RIGHT); + break; + case NS_ooxml::LN_footerf: + PushPageFooter(SectionPropertyMap::PAGE_FIRST); + break; + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + PushFootOrEndnote( NS_ooxml::LN_footnote == rName ); + break; + case NS_ooxml::LN_annotation : + PushAnnotation(); + break; + } + + try + { + ref->resolve(m_rDMapper); + } + catch (xml::sax::SAXException const&) + { + m_bSaxError = true; + throw; + } + + switch( rName ) + { + case NS_ooxml::LN_headerl: + case NS_ooxml::LN_headerr: + case NS_ooxml::LN_headerf: + case NS_ooxml::LN_footerl: + case NS_ooxml::LN_footerr: + case NS_ooxml::LN_footerf: + PopPageHeaderFooter(); + break; + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + PopFootOrEndnote(); + break; + case NS_ooxml::LN_annotation : + PopAnnotation(); + break; + } + + getTableManager().endLevel(); + popTableManager(); + m_bHasFtn = bHasFtn; + + switch(rName) + { + case NS_ooxml::LN_footnote: + case NS_ooxml::LN_endnote: + m_pTableHandler->setHadFootOrEndnote(true); + m_bHasFtn = true; + break; + } + + // check that stacks are the same as before substream + assert(m_aContextStack.size() == contextSize); + for (int i = 0; i < NUMBER_OF_CONTEXTS; ++i) { + assert(m_aPropertyStacks[i].size() == propSize[i]); + } +} + +void DomainMapper_Impl::commentProps(const OUString& sId, const CommentProperties& rProps) +{ + m_aCommentProps[sId] = rProps; +} + + +bool DomainMapper_Impl::handlePreviousParagraphBorderInBetween() const +{ + if (!m_xPreviousParagraph.is()) + return false; + + // Connected borders ("ParaIsConnectBorder") are always on by default + // and never changed by DomainMapper. Except one case when border in + // between is used. So this is not the best, but easiest way to check + // is previous paragraph has border in between. + bool bConnectBorders = true; + m_xPreviousParagraph->getPropertyValue(getPropertyName(PROP_PARA_CONNECT_BORDERS)) >>= bConnectBorders; + + if (bConnectBorders) + return false; + + // Previous paragraph has border in between. Current one also has (since this + // method is called). So current paragraph will get border above, but + // also need to ensure, that no unexpected bottom border are remaining in previous + // paragraph: since ParaIsConnectBorder=false it will be displayed in unexpected way. + m_xPreviousParagraph->setPropertyValue(getPropertyName(PROP_BOTTOM_BORDER), uno::Any(table::BorderLine2())); + + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx new file mode 100644 index 000000000..4a8db689c --- /dev/null +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -0,0 +1,1235 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/text/XParagraphCursor.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextCursor.hpp> +#include <com/sun/star/text/XTextAppend.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/style/TabStop.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <queue> +#include <stack> +#include <string_view> +#include <o3tl/sorted_vector.hxx> +#include <unordered_map> +#include <vector> +#include <optional> + +#include <dmapper/CommentProperties.hxx> + +#include "DomainMapper.hxx" +#include "DomainMapperTableManager.hxx" +#include "DomainMapperTableHandler.hxx" +#include "PropertyMap.hxx" +#include "FontTable.hxx" +#include "NumberingManager.hxx" +#include "StyleSheetTable.hxx" +#include "SettingsTable.hxx" +#include "ThemeTable.hxx" +#include "GraphicImport.hxx" +#include "OLEHandler.hxx" +#include "FFDataHandler.hxx" +#include "SmartTagHandler.hxx" +#include "FormControlHelper.hxx" +#include <map> + +namespace com::sun::star{ + namespace awt{ + struct Size; + } + namespace lang{ + class XMultiServiceFactory; + struct Locale; + } + namespace text + { + class XTextField; + class XTextFrame; + class XFormField; + } + namespace beans{ class XPropertySet;} +} + +namespace writerfilter::ooxml { + class OOXMLDocument; +} + +namespace writerfilter::dmapper { + +class SdtHelper; + +struct PageMar +{ + sal_Int32 top; + sal_Int32 right; + sal_Int32 bottom; + sal_Int32 left; + sal_Int32 header; + sal_Int32 footer; + sal_Int32 gutter; + public: + PageMar(); +}; +enum PageMarElement +{ + PAGE_MAR_TOP, + PAGE_MAR_RIGHT, + PAGE_MAR_BOTTOM, + PAGE_MAR_LEFT, + PAGE_MAR_HEADER, + PAGE_MAR_FOOTER, + PAGE_MAR_GUTTER +}; + +/// property stack element +enum ContextType +{ + CONTEXT_SECTION, + CONTEXT_PARAGRAPH, + CONTEXT_CHARACTER, + CONTEXT_STYLESHEET, + CONTEXT_LIST +}; +enum { NUMBER_OF_CONTEXTS = CONTEXT_LIST + 1 }; + +enum BreakType +{ + PAGE_BREAK, + COLUMN_BREAK, + LINE_BREAK +}; + +/** + * Two special footnotes are a separator line, and a continuation line. + * In MSOffice, these can contain text as well, but LO doesn't implement this + * rarely used feature, so the separator text needs to be skipped. (tdf#123262) + * Three-way logic is needed because there is no guaranteed on-off event. + * OFF == not in footnote separator + * ON == in footnote separator + * SKIPPING == ON status has been recognized. + */ +enum class SkipFootnoteSeparator +{ + OFF, + ON, + SKIPPING +}; + +// type of stored redlines +enum StoredRedlines +{ + FRAME = 0, + FOOTNOTE, + ENDNOTE, + NONE +}; + +/** + * Storage for state that is relevant outside a header/footer, but not inside it. + * + * In case some state of DomainMapper_Impl should be reset before handling the + * header/footer and should be restored once handling of header/footer is done, + * then you can use this class to do so. + */ +class HeaderFooterContext +{ + bool m_bTextInserted; + sal_Int32 m_nTableDepth; + +public: + explicit HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth); + bool getTextInserted() const; + sal_Int32 getTableDepth() const; +}; + +/// Information about a paragraph to be finished after a field end. +struct FieldParagraph +{ + PropertyMapPtr m_pPropertyMap; + bool m_bRemove = false; +}; + +/// field stack element +class FieldContext : public virtual SvRefBase +{ + bool m_bFieldCommandCompleted; + css::uno::Reference<css::text::XTextRange> m_xStartRange; + + OUString m_sCommand; + OUString m_sResult; + OUString m_sVariableValue; + std::optional<FieldId> m_eFieldId; + bool m_bFieldLocked; + + css::uno::Reference<css::text::XTextField> m_xTextField; + css::uno::Reference<css::text::XFormField> m_xFormField; + css::uno::Reference<css::beans::XPropertySet> m_xTOC; + css::uno::Reference<css::beans::XPropertySet> m_xTC; // TOX entry + css::uno::Reference<css::beans::XPropertySet> m_xCustomField; + + OUString m_sHyperlinkURL; + /// A frame for the hyperlink when one exists. + OUString m_sHyperlinkTarget; + + FFDataHandler::Pointer_t m_pFFDataHandler; + FormControlHelper::Pointer_t m_pFormControlHelper; + /// (Character) properties of the field itself. + PropertyMapPtr m_pProperties; + + std::vector<FieldParagraph> m_aParagraphsToFinish; + +public: + explicit FieldContext(css::uno::Reference<css::text::XTextRange> const& xStart); + ~FieldContext() override; + + const css::uno::Reference<css::text::XTextRange>& GetStartRange() const { return m_xStartRange; } + + void AppendCommand(std::u16string_view rPart); + const OUString& GetCommand() const {return m_sCommand; } + + void SetFieldId(FieldId eFieldId ) { m_eFieldId = eFieldId; } + std::optional<FieldId> const & GetFieldId() const { return m_eFieldId; } + + void AppendResult(std::u16string_view rResult) { m_sResult += rResult; } + const OUString& GetResult() const { return m_sResult; } + + void CacheVariableValue(const css::uno::Any& rAny); + const OUString& GetVariableValue() { return m_sVariableValue; } + + void SetCommandCompleted() { m_bFieldCommandCompleted = true; } + bool IsCommandCompleted() const { return m_bFieldCommandCompleted; } + + void SetFieldLocked() { m_bFieldLocked = true; } + bool IsFieldLocked() const { return m_bFieldLocked; } + + const css::uno::Reference<css::beans::XPropertySet>& GetCustomField() const { return m_xCustomField; } + void SetCustomField(css::uno::Reference<css::beans::XPropertySet> const& xCustomField) { m_xCustomField = xCustomField; } + const css::uno::Reference<css::text::XTextField>& GetTextField() const { return m_xTextField;} + void SetTextField(css::uno::Reference<css::text::XTextField> const& xTextField); + const css::uno::Reference<css::text::XFormField>& GetFormField() const { return m_xFormField;} + void SetFormField(css::uno::Reference<css::text::XFormField> const& xFormField) { m_xFormField = xFormField;} + + void SetTOC(css::uno::Reference<css::beans::XPropertySet> const& xTOC) { m_xTOC = xTOC; } + const css::uno::Reference<css::beans::XPropertySet>& GetTOC() const { return m_xTOC; } + + void SetTC(css::uno::Reference<css::beans::XPropertySet> const& xTC) { m_xTC = xTC; } + const css::uno::Reference<css::beans::XPropertySet>& GetTC() const { return m_xTC; } + + void SetHyperlinkURL( const OUString& rURL ) { m_sHyperlinkURL = rURL; } + const OUString& GetHyperlinkURL() const { return m_sHyperlinkURL; } + void SetHyperlinkTarget(const OUString& rTarget) { m_sHyperlinkTarget = rTarget; } + const OUString& GetHyperlinkTarget() const { return m_sHyperlinkTarget; } + + void setFFDataHandler(FFDataHandler::Pointer_t pFFDataHandler) { m_pFFDataHandler = pFFDataHandler; } + const FFDataHandler::Pointer_t& getFFDataHandler() const { return m_pFFDataHandler; } + + void setFormControlHelper(FormControlHelper::Pointer_t pFormControlHelper) { m_pFormControlHelper = pFormControlHelper; } + const FormControlHelper::Pointer_t& getFormControlHelper() const { return m_pFormControlHelper; } + const PropertyMapPtr& getProperties() const { return m_pProperties; } + + ::std::vector<OUString> GetCommandParts() const; + + std::vector<FieldParagraph>& GetParagraphsToFinish() { return m_aParagraphsToFinish; } +}; + +struct TextAppendContext +{ + css::uno::Reference<css::text::XTextAppend> xTextAppend; + css::uno::Reference<css::text::XTextRange> xInsertPosition; + css::uno::Reference<css::text::XParagraphCursor> xCursor; + ParagraphPropertiesPtr pLastParagraphProperties; + + /** + * Objects anchored to the current paragraph, may affect the paragraph + * spacing. + */ + std::vector<AnchoredObjectInfo> m_aAnchoredObjects; + + inline TextAppendContext(const css::uno::Reference<css::text::XTextAppend>& xAppend, const css::uno::Reference<css::text::XTextCursor>& xCur); +}; + +struct AnchoredContext +{ + css::uno::Reference<css::text::XTextContent> xTextContent; + bool bToRemove; + + explicit AnchoredContext(const css::uno::Reference<css::text::XTextContent>& xContent) + : xTextContent(xContent), bToRemove(false) + { + } +}; + +typedef tools::SvRef<FieldContext> FieldContextPtr; + +/*------------------------------------------------------------------------- + extended tab stop struct + -----------------------------------------------------------------------*/ +struct DeletableTabStop : public css::style::TabStop +{ + bool bDeleted; + explicit DeletableTabStop() + : bDeleted(false) + { + FillChar = ' '; // same default as SvxXMLTabStopContext_Impl + } + DeletableTabStop(const css::style::TabStop& rTabStop) + : TabStop(rTabStop), + bDeleted(false) + { + } +}; +/// helper to remember bookmark start position +struct BookmarkInsertPosition +{ + bool m_bIsStartOfText; + OUString m_sBookmarkName; + css::uno::Reference<css::text::XTextRange> m_xTextRange; + BookmarkInsertPosition(bool bIsStartOfText, const OUString& rName, css::uno::Reference<css::text::XTextRange> const& xTextRange): + m_bIsStartOfText( bIsStartOfText ), + m_sBookmarkName( rName ), + m_xTextRange( xTextRange ) + {} +}; + +struct PermInsertPosition +{ + bool m_bIsStartOfText; + sal_Int32 m_Id; + OUString m_Ed; + OUString m_EdGrp; + + css::uno::Reference<css::text::XTextRange> m_xTextRange; + + PermInsertPosition(bool bIsStartOfText, sal_Int32 id, const OUString& ed, const OUString& edGrp, css::uno::Reference<css::text::XTextRange> const& xTextRange) + : m_bIsStartOfText(bIsStartOfText) + , m_Id(id) + , m_Ed(ed) + , m_EdGrp(edGrp) + , m_xTextRange(xTextRange) + {} + + OUString createBookmarkName() const + { + OUString bookmarkName; + + assert((!m_Ed.isEmpty()) || (!m_EdGrp.isEmpty())); + + if (m_Ed.isEmpty()) + { + bookmarkName += "permission-for-group:" + + OUString::number(m_Id) + + ":" + + m_EdGrp; + } + else + { + bookmarkName += "permission-for-user:" + + OUString::number(m_Id) + + ":" + + m_Ed; + } + + //todo: make sure the name is not used already! + return bookmarkName; + } +}; + +/// Stores the start/end positions of an annotation before its insertion. +struct AnnotationPosition +{ + css::uno::Reference<css::text::XTextRange> m_xStart; + css::uno::Reference<css::text::XTextRange> m_xEnd; +}; + +struct RubyInfo +{ + OUString sRubyText; + OUString sRubyStyle; + sal_uInt32 nSprmId; + sal_uInt32 nRubyAlign; + sal_uInt32 nHps; + sal_uInt32 nHpsBaseText; + + RubyInfo(): + nSprmId(0), + nRubyAlign(0), + nHps(0), + nHpsBaseText(0) + { + } +}; + +struct LineNumberSettings +{ + sal_Int32 nDistance; + sal_Int32 nInterval; + bool bRestartAtEachPage; + LineNumberSettings() : + nDistance(-1) + ,nInterval(0) + ,bRestartAtEachPage(true) + {} + +}; + +/// Contains information about a table that will be potentially converted to a floating one at the section end. +struct FloatingTableInfo +{ + css::uno::Reference<css::text::XTextRange> m_xStart; + css::uno::Reference<css::text::XTextRange> m_xEnd; + css::uno::Sequence<css::beans::PropertyValue> m_aFrameProperties; + sal_Int32 m_nTableWidth; + sal_Int32 m_nTableWidthType; + /// Break type of the section that contains this table. + sal_Int32 m_nBreakType = -1; + /// Tables in footnotes and endnotes are always floating + bool m_bConvertToFloatingInFootnote = false; + + FloatingTableInfo(css::uno::Reference<css::text::XTextRange> const& xStart, + css::uno::Reference<css::text::XTextRange> const& xEnd, + const css::uno::Sequence<css::beans::PropertyValue>& aFrameProperties, + sal_Int32 nTableWidth, sal_Int32 nTableWidthType, bool bConvertToFloatingInFootnote) + : m_xStart(xStart), + m_xEnd(xEnd), + m_aFrameProperties(aFrameProperties), + m_nTableWidth(nTableWidth), + m_nTableWidthType(nTableWidthType), + m_bConvertToFloatingInFootnote(bConvertToFloatingInFootnote) + { + } + css::uno::Any getPropertyValue(std::u16string_view propertyName); +}; + +/// Stores original/in-file-format info about a single anchored object. +struct AnchoredObjectInfo +{ + css::uno::Reference<css::text::XTextContent> m_xAnchoredObject; + sal_Int32 m_nLeftMargin = 0; + RedlineParamsPtr m_xRedlineForInline; +}; + +/// Stores info about objects anchored to a given paragraph. +struct AnchoredObjectsInfo +{ + css::uno::Reference<css::text::XTextRange> m_xParagraph; + std::vector<AnchoredObjectInfo> m_aAnchoredObjects; +}; + +struct SymbolData +{ + sal_Unicode cSymbol; + OUString sFont; + SymbolData(): + cSymbol(), + sFont() + { } +}; + +class DomainMapper; +class DomainMapper_Impl final +{ +public: + typedef std::map < OUString, BookmarkInsertPosition > BookmarkMap_t; + typedef std::map < sal_Int32, PermInsertPosition > PermMap_t; + +private: + SourceDocumentType m_eDocumentType; + DomainMapper& m_rDMapper; + writerfilter::ooxml::OOXMLDocument* m_pOOXMLDocument; + OUString m_aBaseUrl; + css::uno::Reference<css::text::XTextDocument> m_xTextDocument; + css::uno::Reference<css::beans::XPropertySet> m_xDocumentSettings; + css::uno::Reference<css::lang::XMultiServiceFactory> m_xTextFactory; + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + css::uno::Reference<css::container::XNameContainer> m_xPageStyles1; + // cache next available number, expensive to repeatedly compute + std::optional<int> m_xNextUnusedPageStyleNo; + css::uno::Reference<css::container::XNameContainer> m_xCharacterStyles; + // cache next available number, expensive to repeatedly compute + std::optional<int> m_xNextUnusedCharacterStyleNo; + css::uno::Reference<css::text::XText> m_xBodyText; + css::uno::Reference<css::text::XTextContent> m_xEmbedded; + + std::stack<TextAppendContext> m_aTextAppendStack; + std::stack<AnchoredContext> m_aAnchoredStack; + std::stack<HeaderFooterContext> m_aHeaderFooterStack; + std::stack<std::pair<TextAppendContext, bool>> m_aHeaderFooterTextAppendStack; + std::deque<FieldContextPtr> m_aFieldStack; + bool m_bForceGenericFields; + /// Type of decimal symbol associated to the document language in Writer locale definition + bool m_bIsDecimalComma; + bool m_bSetUserFieldContent; + bool m_bSetCitation; + bool m_bSetDateValue; + bool m_bIsFirstSection; + bool m_bIsColumnBreakDeferred; + bool m_bIsPageBreakDeferred; + sal_Int32 m_nLineBreaksDeferred; + /// If we want to set "sdt end" on the next character context. + bool m_bSdtEndDeferred; + /// If we want to set "paragraph sdt end" on the next paragraph context. + bool m_bParaSdtEndDeferred; + bool m_bStartTOC; + bool m_bStartTOCHeaderFooter; + /// If we got any text that is the pre-rendered result of the TOC field. + bool m_bStartedTOC; + bool m_bStartIndex; + bool m_bStartBibliography; + unsigned int m_nStartGenericField; + bool m_bTextInserted; + LineNumberSettings m_aLineNumberSettings; + + BookmarkMap_t m_aBookmarkMap; + OUString m_sCurrentBkmkId; + OUString m_sCurrentBkmkName; + OUString m_sCurrentBkmkPrefix; + + PermMap_t m_aPermMap; + sal_Int32 m_sCurrentPermId; + OUString m_sCurrentPermEd; + OUString m_sCurrentPermEdGrp; + + PageMar m_aPageMargins; + SymbolData m_aSymbolData; + + // TableManagers are stacked: one for each stream to avoid any confusion + std::stack< tools::SvRef< DomainMapperTableManager > > m_aTableManagers; + tools::SvRef<DomainMapperTableHandler> m_pTableHandler; + // List of document lists overrides. They are applied only once on first occurrence in document + o3tl::sorted_vector<sal_Int32> m_aListOverrideApplied; + + //each context needs a stack of currently used attributes + std::stack<PropertyMapPtr> m_aPropertyStacks[NUMBER_OF_CONTEXTS]; + std::stack<ContextType> m_aContextStack; + std::queue<std::optional<sal_Int16>> m_aFrameDirectionQueue; + bool m_bFrameDirectionSet; + FontTablePtr m_pFontTable; + ListsManager::Pointer m_pListTable; + std::deque< css::uno::Reference<css::drawing::XShape> > m_aPendingShapes; + StyleSheetTablePtr m_pStyleSheetTable; + ThemeTablePtr m_pThemeTable; + SettingsTablePtr m_pSettingsTable; + GraphicImportPtr m_pGraphicImport; + + + PropertyMapPtr m_pTopContext; + PropertyMapPtr m_pLastSectionContext; + PropertyMapPtr m_pLastCharacterContext; + + ::std::vector<DeletableTabStop> m_aCurrentTabStops; + OUString m_sCurrentParaStyleName; //highly inaccurate. Overwritten by "overlapping" paragraphs like comments, flys. + OUString m_sDefaultParaStyleName; //caches the ConvertedStyleName of the default paragraph style + bool m_bInDocDefaultsImport; + bool m_bInStyleSheetImport; //in import of fonts, styles, lists or lfos + bool m_bInNumberingImport; //in import of numbering (i.e. numbering.xml) + bool m_bInAnyTableImport; //in import of fonts, styles, lists or lfos + enum class HeaderFooterImportState + { + none, + header, + footer, + } m_eInHeaderFooterImport; + bool m_bDiscardHeaderFooter; + bool m_bInFootOrEndnote; + bool m_bInFootnote; + PropertyMapPtr m_pFootnoteContext; + bool m_bHasFootnoteStyle; + bool m_bCheckFootnoteStyle; + /// Skip paragraphs from the <w:separator/> footnote + SkipFootnoteSeparator m_eSkipFootnoteState; + /// preload footnotes and endnotes + sal_Int32 m_nFootnotes; // footnote count + sal_Int32 m_nEndnotes; // endnote count + // these are the real first notes, use their content in the first notes + sal_Int32 m_nFirstFootnoteIndex; + sal_Int32 m_nFirstEndnoteIndex; + + bool m_bLineNumberingSet; + bool m_bIsInFootnoteProperties; + + RubyInfo m_aRubyInfo; + //registered frame properties + std::vector<css::beans::PropertyValue> m_aFrameProperties; + css::uno::Reference<css::text::XTextRange> m_xFrameStartRange; + css::uno::Reference<css::text::XTextRange> m_xFrameEndRange; + + // Redline stack + std::stack< std::vector< RedlineParamsPtr > > m_aRedlines; + // The redline currently read, may be also stored by a context instead of m_aRedlines. + RedlineParamsPtr m_currentRedline; + RedlineParamsPtr m_previousRedline; + RedlineParamsPtr m_pParaMarkerRedline; + bool m_bIsParaMarkerChange; + bool m_bIsParaMarkerMove; + // redline data of the terminating run, if it's a moveFrom deletion or a moveTo insertion + RedlineParamsPtr m_pParaMarkerRedlineMove; + // This is for removing workaround (double ZWSPs around the anchoring point) for track + // changed images anchored *to* character, if it's followed by a redline text run immediately. + // (In that case, the image is part of a tracked text range, no need for the dummy + // text ZWSPs to keep the change tracking of the image in Writer.) + bool m_bRedlineImageInPreviousRun; + + /// If the current paragraph has any runs. + bool m_bParaChanged; + bool m_bIsFirstParaInSection; + bool m_bIsFirstParaInSectionAfterRedline; + bool m_bIsFirstParaInShape = false; + bool m_bDummyParaAddedForTableInSection; + bool m_bDummyParaAddedForTableInSectionPage; + bool m_bTextFrameInserted; + bool m_bIsPreviousParagraphFramed; + bool m_bIsLastParaInSection; + bool m_bIsLastSectionGroup; + bool m_bIsInComments; + /// If the current paragraph contains section property definitions. + bool m_bParaSectpr; + bool m_bUsingEnhancedFields; + /// If the current paragraph is inside a structured document element. + bool m_bSdt; + bool m_bIsFirstRun; + bool m_bIsOutsideAParagraph; + /// This is a continuation of already finished paragraph - e.g., first in an index section + bool m_bRemoveThisParagraph = false; + + css::uno::Reference< css::text::XTextCursor > xTOCMarkerCursor; + + //annotation import + css::uno::Reference< css::beans::XPropertySet > m_xAnnotationField; + sal_Int32 m_nAnnotationId; + bool m_bAnnotationResolved = false; + std::unordered_map< sal_Int32, AnnotationPosition > m_aAnnotationPositions; + + void SetNumberFormat(const OUString& rCommand, css::uno::Reference<css::beans::XPropertySet> const& xPropertySet, bool bDetectFormat = false); + /// @throws css::uno::Exception + css::uno::Reference<css::beans::XPropertySet> FindOrCreateFieldMaster(const char* pFieldMasterService, const OUString& rFieldMasterName); + css::uno::Reference<css::beans::XPropertySet> const & GetDocumentSettings(); + + std::map<sal_Int32, css::uno::Any> deferredCharacterProperties; + SmartTagHandler m_aSmartTagHandler; + + css::uno::Reference<css::text::XTextRange> m_xGlossaryEntryStart; + css::uno::Reference<css::text::XTextRange> m_xSdtEntryStart; + std::stack<BookmarkInsertPosition> m_xSdtStarts; + + std::queue< css::uno::Reference< css::text::XTextFrame > > m_xPendingTextBoxFrames; + +public: + css::uno::Reference<css::text::XTextRange> m_xInsertTextRange; + css::uno::Reference<css::text::XTextRange> m_xAltChunkStartingRange; + std::deque<sal_Int32> m_aFootnoteIds; + std::deque<sal_Int32> m_aEndnoteIds; + + bool m_bIsInTextBox; +private: + bool m_bIsNewDoc; + bool m_bIsAltChunk = false; + bool m_bIsReadGlossaries; + std::optional<sal_Int16> m_oLineBreakClear; + +public: + DomainMapper_Impl( + DomainMapper& rDMapper, + css::uno::Reference < css::uno::XComponentContext > const& xContext, + css::uno::Reference< css::lang::XComponent > const& xModel, + SourceDocumentType eDocumentType, + utl::MediaDescriptor const & rMediaDesc); + ~DomainMapper_Impl(); + + void setDocumentReference(writerfilter::ooxml::OOXMLDocument* pDocument) { if (!m_pOOXMLDocument) m_pOOXMLDocument = pDocument; }; + writerfilter::ooxml::OOXMLDocument* getDocumentReference() const; + + SectionPropertyMap* GetLastSectionContext( ) + { + return dynamic_cast< SectionPropertyMap* >( m_pLastSectionContext.get( ) ); + } + + css::uno::Reference<css::container::XNameContainer> const & GetPageStyles(); + OUString GetUnusedPageStyleName(); + css::uno::Reference<css::container::XNameContainer> const & GetCharacterStyles(); + OUString GetUnusedCharacterStyleName(); + css::uno::Reference<css::text::XText> const & GetBodyText(); + const css::uno::Reference<css::lang::XMultiServiceFactory>& GetTextFactory() const + { + return m_xTextFactory; + } + const css::uno::Reference<css::text::XTextDocument>& GetTextDocument() const + { + return m_xTextDocument; + } + void SetDocumentSettingsProperty( const OUString& rPropName, const css::uno::Any& rValue ); + + void CreateRedline(css::uno::Reference<css::text::XTextRange> const& xRange, const RedlineParamsPtr& pRedline); + + void CheckParaMarkerRedline(css::uno::Reference<css::text::XTextRange> const& xRange); + + void CheckRedline(css::uno::Reference<css::text::XTextRange> const& xRange); + + void StartParaMarkerChange( ); + void EndParaMarkerChange( ); + void StartParaMarkerMove( ); + void EndParaMarkerMove( ); + void ChainTextFrames(); + + void PushTextBoxContent(); + void PopTextBoxContent(); + void AttachTextBoxContentToShape(css::uno::Reference<css::drawing::XShape> xShape); + + void RemoveDummyParaForTableInSection(); + void AddDummyParaForTableInSection(); + void RemoveLastParagraph( ); + void SetIsDecimalComma() { m_bIsDecimalComma = true; }; + void SetIsLastParagraphInSection( bool bIsLast ); + bool GetIsLastParagraphInSection() const { return m_bIsLastParaInSection;} + void SetRubySprmId( sal_uInt32 nSprmId) { m_aRubyInfo.nSprmId = nSprmId ; } + void SetRubyText( OUString const &sText, OUString const &sStyle) { + m_aRubyInfo.sRubyText = sText; + m_aRubyInfo.sRubyStyle = sStyle; + } + const RubyInfo & GetRubyInfo() const { return m_aRubyInfo;} + void SetRubyInfo(const RubyInfo & rInfo) { m_aRubyInfo = rInfo;} + + void SetIsLastSectionGroup( bool bIsLast ); + bool GetIsLastSectionGroup() const { return m_bIsLastSectionGroup;} + void SetIsFirstParagraphInSection( bool bIsFirst ); + void SetIsFirstParagraphInSectionAfterRedline( bool bIsFirstAfterRedline ); + bool GetIsFirstParagraphInSection( bool bAfterRedline = false ) const; + void SetIsFirstParagraphInShape(bool bIsFirst); + bool GetIsFirstParagraphInShape() const { return m_bIsFirstParaInShape; } + void SetIsDummyParaAddedForTableInSection( bool bIsAdded ); + bool GetIsDummyParaAddedForTableInSection() const { return m_bDummyParaAddedForTableInSection;} + void SetIsDummyParaAddedForTableInSectionPage(bool bIsAdded); + bool GetIsDummyParaAddedForTableInSectionPage() const { return m_bDummyParaAddedForTableInSectionPage; } + + /// Track if a textframe has been inserted into this section + void SetIsTextFrameInserted( bool bIsInserted ); + bool GetIsTextFrameInserted() const { return m_bTextFrameInserted;} + + void SetIsPreviousParagraphFramed( bool bIsFramed ) { m_bIsPreviousParagraphFramed = bIsFramed; } + bool GetIsPreviousParagraphFramed() const { return m_bIsPreviousParagraphFramed; } + void SetParaSectpr(bool bParaSectpr); + bool GetParaSectpr() const { return m_bParaSectpr;} + + void SetSymbolChar( sal_Int32 nSymbol) { m_aSymbolData.cSymbol = sal_Unicode(nSymbol); } + void SetSymbolFont( OUString const &rName ) { m_aSymbolData.sFont = rName; } + const SymbolData & GetSymbolData() const { return m_aSymbolData;} + + /// Setter method for m_bSdt. + void SetSdt(bool bSdt); + + void PushSdt(); + void PopSdt(); + /// Gives access to the currently open run/inline SDTs. + const std::stack<BookmarkInsertPosition>& GetSdtStarts() const; + + /// Getter method for m_bSdt. + bool GetSdt() const { return m_bSdt;} + bool GetParaChanged() const { return m_bParaChanged;} + bool GetParaHadField() const { return m_bParaHadField; } + bool GetRemoveThisPara() const { return m_bRemoveThisParagraph; } + + void deferBreak( BreakType deferredBreakType ); + bool isBreakDeferred( BreakType deferredBreakType ); + void clearDeferredBreaks(); + void clearDeferredBreak(BreakType deferredBreakType); + + void setSdtEndDeferred(bool bSdtEndDeferred); + bool isSdtEndDeferred() const; + void setParaSdtEndDeferred(bool bParaSdtEndDeferred); + bool isParaSdtEndDeferred() const; + + void finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove = false, const bool bNoNumbering = false); + void appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap ); + void appendTextContent(const css::uno::Reference<css::text::XTextContent>&, const css::uno::Sequence<css::beans::PropertyValue>&); + void appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOleHandler ); + void appendStarMath( const Value& v); + void adjustLastPara(sal_Int8 nAlign); + css::uno::Reference<css::beans::XPropertySet> appendTextSectionAfter(css::uno::Reference<css::text::XTextRange> const & xBefore); + + /// AutoText import: each entry is placed in the separate section + void appendGlossaryEntry(); + /// Remember where entry was started + void setGlossaryEntryStart( css::uno::Reference<css::text::XTextRange> const & xStart ) + { + m_xGlossaryEntryStart = xStart; + } + + // push the new properties onto the stack and make it the 'current' property map + void PushProperties(ContextType eId); + void PushStyleProperties(const PropertyMapPtr& pStyleProperties); + void PushListProperties(const PropertyMapPtr& pListProperties); + void PopProperties(ContextType eId); + + ContextType GetTopContextType() const { return m_aContextStack.top(); } + const PropertyMapPtr& GetTopContext() const + { + return m_pTopContext; + } + PropertyMapPtr GetTopContextOfType(ContextType eId); + + bool HasTopText() const; + css::uno::Reference<css::text::XTextAppend> const & GetTopTextAppend(); + FieldContextPtr const & GetTopFieldContext(); + + bool HasTopAnchoredObjects() const; + + FontTablePtr const & GetFontTable() + { + if(!m_pFontTable) + m_pFontTable = new FontTable(); + return m_pFontTable; + } + StyleSheetTablePtr const & GetStyleSheetTable() + { + if(!m_pStyleSheetTable) + m_pStyleSheetTable = new StyleSheetTable( m_rDMapper, m_xTextDocument, m_bIsNewDoc ); + return m_pStyleSheetTable; + } + OUString GetListStyleName(sal_Int32 nListId); + ListsManager::Pointer const & GetListTable(); + ThemeTablePtr const & GetThemeTable() + { + if(!m_pThemeTable) + m_pThemeTable = new ThemeTable; + return m_pThemeTable; + } + + SettingsTablePtr const & GetSettingsTable() + { + if( !m_pSettingsTable ) + m_pSettingsTable = new SettingsTable(m_rDMapper); + return m_pSettingsTable; + } + + GraphicImportPtr const & GetGraphicImport( GraphicImportType eGraphicImportType ); + void ResetGraphicImport(); + // this method deletes the current m_pGraphicImport after import + void ImportGraphic(const writerfilter::Reference< Properties>::Pointer_t&, GraphicImportType eGraphicImportType ); + + void InitTabStopFromStyle(const css::uno::Sequence<css::style::TabStop>& rInitTabStops); + void IncorporateTabStop( const DeletableTabStop &aTabStop ); + css::uno::Sequence<css::style::TabStop> GetCurrentTabStopAndClear(); + + void SetCurrentParaStyleName(const OUString& sStringValue) {m_sCurrentParaStyleName = sStringValue;} + OUString GetCurrentParaStyleName(); + OUString GetDefaultParaStyleName(); + + // specified style - including inherited properties. Indicate whether paragraph defaults should be checked. + css::uno::Any GetPropertyFromStyleSheet(PropertyIds eId, StyleSheetEntryPtr pEntry, const bool bDocDefaults, const bool bPara, bool* bIsDocDefault = nullptr); + // current paragraph style - including inherited properties + css::uno::Any GetPropertyFromParaStyleSheet(PropertyIds eId); + // context's character style - including inherited properties + css::uno::Any GetPropertyFromCharStyleSheet(PropertyIds eId, const PropertyMapPtr& rContext); + // get property first from the given context, or secondly via inheritance from styles/docDefaults + css::uno::Any GetAnyProperty(PropertyIds eId, const PropertyMapPtr& rContext); + void SetDocDefaultsImport( bool bSet ) { m_bInDocDefaultsImport = bSet;} + bool IsDocDefaultsImport()const { return m_bInDocDefaultsImport;} + void SetStyleSheetImport( bool bSet ) { m_bInStyleSheetImport = bSet;} + bool IsStyleSheetImport()const { return m_bInStyleSheetImport;} + void SetNumberingImport( bool bSet ) { m_bInNumberingImport = bSet;} + bool IsNumberingImport() const { return m_bInNumberingImport;} + void SetAnyTableImport( bool bSet ) { m_bInAnyTableImport = bSet;} + bool IsAnyTableImport()const { return m_bInAnyTableImport;} + bool IsInShape()const { return m_aAnchoredStack.size() > 0;} + + void PushShapeContext(const css::uno::Reference<css::drawing::XShape>& xShape); + void PopShapeContext(); + void UpdateEmbeddedShapeProps(const css::uno::Reference<css::drawing::XShape>& xShape); + /// Add a pending shape: it's currently inserted into the document, but it should be removed before the import finishes. + void PushPendingShape(const css::uno::Reference<css::drawing::XShape>& xShape); + /// Get the first pending shape, if there are any. + css::uno::Reference<css::drawing::XShape> PopPendingShape(); + + void PushPageHeader(SectionPropertyMap::PageType eType); + void PushPageFooter(SectionPropertyMap::PageType eType); + + void PopPageHeaderFooter(); + bool IsInHeaderFooter() const { return m_eInHeaderFooterImport != HeaderFooterImportState::none; } + void ConvertHeaderFooterToTextFrame(bool, bool); + static void fillEmptyFrameProperties(std::vector<css::beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar); + + bool IsInTOC() const { return m_bStartTOC; } + + void PushFootOrEndnote( bool bIsFootnote ); + void PopFootOrEndnote(); + bool IsInFootOrEndnote() const { return m_bInFootOrEndnote; } + bool IsInFootnote() const { return m_bInFootnote; } + + void StartCustomFootnote(const PropertyMapPtr pContext); + void EndCustomFootnote(); + bool IsInCustomFootnote() const { return m_bHasFootnoteStyle; } + bool CheckFootnoteStyle() const { return m_bCheckFootnoteStyle; } + void SetHasFootnoteStyle(bool bVal) { m_bHasFootnoteStyle = bVal; } + void SetCheckFootnoteStyle(bool bVal) { m_bCheckFootnoteStyle = bVal; } + + const PropertyMapPtr& GetFootnoteContext() const { return m_pFootnoteContext; } + + SkipFootnoteSeparator GetSkipFootnoteState() const { return m_eSkipFootnoteState; } + void SetSkipFootnoteState(SkipFootnoteSeparator eId) { m_eSkipFootnoteState = eId; } + sal_Int32 GetFootnoteCount() const { return m_nFootnotes; } + void IncrementFootnoteCount() { ++m_nFootnotes; } + sal_Int32 GetEndnoteCount() const { return m_nEndnotes; } + void IncrementEndnoteCount() { ++m_nEndnotes; } + bool CopyTemporaryNotes( + css::uno::Reference< css::text::XFootnote > xNoteSrc, + css::uno::Reference< css::text::XFootnote > xNoteDest ); + void RemoveTemporaryFootOrEndnotes(); + + void PushAnnotation(); + void PopAnnotation(); + + /// A field context starts with a cFieldStart. + void PushFieldContext(); + //the current field context waits for the completion of the command + bool IsOpenFieldCommand() const; + bool IsOpenField() const; + //mark field in current context as locked (fixed) + void SetFieldLocked(); + //collect the pieces of the command + void AppendFieldCommand(OUString const & rPartOfCommand); + void handleRubyEQField( const FieldContextPtr& pContext); + void handleFieldSet + (const FieldContextPtr& pContext, + css::uno::Reference< css::uno::XInterface > const & xFieldInterface, + css::uno::Reference< css::beans::XPropertySet > const& xFieldProperties); + void handleFieldAsk + (const FieldContextPtr& pContext, + css::uno::Reference< css::uno::XInterface > & xFieldInterface, + css::uno::Reference< css::beans::XPropertySet > const& xFieldProperties); + OUString convertFieldFormula(const OUString& input); + void handleFieldFormula + (const FieldContextPtr& pContext, + css::uno::Reference< css::beans::XPropertySet > const& xFieldProperties); + void handleAutoNum + (const FieldContextPtr& pContext, + css::uno::Reference< css::uno::XInterface > const & xFieldInterface, + css::uno::Reference< css::beans::XPropertySet > const& xFieldProperties); + static void handleAuthor + (std::u16string_view rFirstParam, + css::uno::Reference< css::beans::XPropertySet > const& xFieldProperties, + FieldId eFieldId); + void handleDocProperty + (const FieldContextPtr& pContext, + OUString const& rFirstParam, + css::uno::Reference< css::uno::XInterface > & xFieldInterface); + void handleToc + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName); + void handleIndex + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName); + + void handleBibliography + (const FieldContextPtr& pContext, + const OUString & sTOCServiceName); + /// The field command has to be closed (cFieldSep appeared). + void CloseFieldCommand(); + //the _current_ fields require a string type result while TOCs accept richt results + bool IsFieldResultAsString(); + void AppendFieldResult(std::u16string_view rResult); + //apply the result text to the related field + void SetFieldResult(OUString const& rResult); + // set FFData of top field context + void SetFieldFFData( const FFDataHandler::Pointer_t& pFFDataHandler ); + /// The end of field is reached (cFieldEnd appeared) - the command might still be open. + void PopFieldContext(); + + /// Returns title of the TOC placed in paragraph(s) before TOC field inside STD-frame + OUString extractTocTitle(); + css::uno::Reference<css::beans::XPropertySet> createSectionForRange(css::uno::Reference< css::text::XTextRange > xStart, css::uno::Reference< css::text::XTextRange > xEnd, const OUString & sObjectType, bool stepLeft); + + void SetBookmarkName( const OUString& rBookmarkName ); + void StartOrEndBookmark( const OUString& rId ); + + void SetMoveBookmark( bool IsFrom ); + + void setPermissionRangeEd(const OUString& user); + void setPermissionRangeEdGrp(const OUString& group); + void startOrEndPermissionRange(sal_Int32 permissinId); + + void AddAnnotationPosition( + const bool bStart, + const sal_Int32 nAnnotationId ); + + bool hasTableManager() const + { + return !m_aTableManagers.empty(); + } + + DomainMapperTableManager& getTableManager() + { + return *m_aTableManagers.top(); + } + + void appendTableManager( ) + { + tools::SvRef<DomainMapperTableManager> pMngr(new DomainMapperTableManager()); + m_aTableManagers.push( pMngr ); + } + + void appendTableHandler( ) + { + if (m_pTableHandler) + m_aTableManagers.top()->setHandler(m_pTableHandler); + } + + void popTableManager( ) + { + if (hasTableManager()) + m_aTableManagers.pop(); + } + + void SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sal_Int32 ndxaLnn ); + bool IsLineNumberingSet() const {return m_bLineNumberingSet;} + + DeletableTabStop m_aCurrentTabStop; + + /// If we're right after the end of a table. + bool m_bConvertedTable = false; + + bool IsOOXMLImport() const { return m_eDocumentType == SourceDocumentType::OOXML; } + + bool IsRTFImport() const { return m_eDocumentType == SourceDocumentType::RTF; } + + void InitPageMargins() { m_aPageMargins = PageMar(); } + void SetPageMarginTwip( PageMarElement eElement, sal_Int32 nValue ); + const PageMar& GetPageMargins() const {return m_aPageMargins;} + + const LineNumberSettings& GetLineNumberSettings() const { return m_aLineNumberSettings;} + void SetLineNumberSettings(const LineNumberSettings& rSet) { m_aLineNumberSettings = rSet;} + + void SetInFootnoteProperties(bool bSet) { m_bIsInFootnoteProperties = bSet;} + bool IsInFootnoteProperties() const { return m_bIsInFootnoteProperties;} + + bool IsInComments() const { return m_bIsInComments; }; + + void CheckUnregisteredFrameConversion( ); + + void RegisterFrameConversion(css::uno::Reference<css::text::XTextRange> const& xFrameStartRange, + css::uno::Reference<css::text::XTextRange> const& xFrameEndRange, + std::vector<css::beans::PropertyValue>&& aFrameProperties); + void ExecuteFrameConversion(); + + void AddNewRedline( sal_uInt32 sprmId ); + + sal_Int32 GetCurrentRedlineToken( ) const; + void SetCurrentRedlineAuthor( const OUString& sAuthor ); + void SetCurrentRedlineDate( const OUString& sDate ); + void SetCurrentRedlineId( sal_Int32 nId ); + void SetCurrentRedlineToken( sal_Int32 nToken ); + void SetCurrentRedlineRevertProperties( const css::uno::Sequence<css::beans::PropertyValue>& aProperties ); + void SetCurrentRedlineIsRead(); + void RemoveTopRedline( ); + void SetCurrentRedlineInitials( const OUString& sInitials ); + bool IsFirstRun() const { return m_bIsFirstRun;} + void SetIsFirstRun(bool bval) { m_bIsFirstRun = bval;} + bool IsOutsideAParagraph() const { return m_bIsOutsideAParagraph;} + void SetIsOutsideAParagraph(bool bval) { m_bIsOutsideAParagraph = bval;} + + void ApplySettingsTable(); + + css::uno::Reference<css::text::XTextAppend> GetCurrentXText() { + return m_aTextAppendStack.empty() ? nullptr : m_aTextAppendStack.top().xTextAppend; + } + + void NewFrameDirection() { + m_aFrameDirectionQueue.push(std::nullopt); + m_bFrameDirectionSet = false; + } + void SetFrameDirection(sal_Int16 nDirection) { + if (!m_bFrameDirectionSet && !m_aFrameDirectionQueue.empty()) { + m_aFrameDirectionQueue.back() = nDirection; + m_bFrameDirectionSet = true; + } + } + std::optional<sal_Int16> PopFrameDirection() { + if (m_aFrameDirectionQueue.empty()) + return {}; + const std::optional<sal_Int16> nDirection = m_aFrameDirectionQueue.front(); + m_aFrameDirectionQueue.pop(); + return nDirection; + } + + SectionPropertyMap * GetSectionContext(); + + sal_Int16 GetListLevel(const StyleSheetEntryPtr& pEntry, const PropertyMapPtr& pParaContext = nullptr); + void ValidateListLevel(const OUString& sStyleIdentifierD); + + /** + Used for attributes/sprms which cannot be evaluated immediately (e.g. they depend + on another one that comes in the same CONTEXT_CHARACTER). The property will be processed + again in DomainMapper::processDeferredCharacterProperties(). + */ + void deferCharacterProperty(sal_Int32 id, const css::uno::Any& value); + /** + Processes properties deferred using deferCharacterProperty(). To be called whenever the top + CONTEXT_CHARACTER is going to be used (e.g. by appendText()). + */ + void processDeferredCharacterProperties(); + + sal_Int32 getNumberingProperty(const sal_Int32 nListId, sal_Int32 nListLevel, const OUString& aProp); + /// Get a property of the current numbering style's current level. + sal_Int32 getCurrentNumberingProperty(const OUString& aProp); + + /// If we're importing into a new document, or just pasting to an existing one. + bool IsNewDoc() const { return m_bIsNewDoc;} + + bool IsAltChunk() const { return m_bIsAltChunk;} + + /// If we're importing autotext. + bool IsReadGlossaries() const { return m_bIsReadGlossaries;} + + tools::SvRef<SdtHelper> m_pSdtHelper; + + /// Document background color, applied to every page style. + std::optional<sal_Int32> m_oBackgroundColor; + + /** + * This contains the raw table depth. m_nTableDepth > 0 is the same as + * getTableManager().isInTable(), unless we're in the first paragraph of a + * table, or first paragraph after a table, as the table manager is only + * updated once we ended the paragraph (and know if the para has the + * inTbl SPRM or not). + */ + sal_Int32 m_nTableDepth; + /// Raw table cell depth. + sal_Int32 m_nTableCellDepth; + /// Table cell depth of the last finished paragraph. + sal_Int32 m_nLastTableCellParagraphDepth; + + /// If the current section has footnotes. + bool m_bHasFtn; + /// If the current section has a footnote separator. + bool m_bHasFtnSep; + + /// If the next tab should be ignored, used for footnotes. + bool m_bCheckFirstFootnoteTab; + bool m_bIgnoreNextTab; + /// Pending floating tables: they may be converted to text frames at the section end. + std::vector<FloatingTableInfo> m_aPendingFloatingTables; + + /// Paragraphs with anchored objects in the current section. + std::vector<AnchoredObjectsInfo> m_aAnchoredObjectAnchors; + + /// Append a property to a sub-grabbag if necessary (e.g. 'lineRule', 'auto') + void appendGrabBag(std::vector<css::beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, const OUString& aValue); + void appendGrabBag(std::vector<css::beans::PropertyValue>& rInteropGrabBag, const OUString& aKey, std::vector<css::beans::PropertyValue>& rValue); + + /// Enable, disable and check status of grabbags + void enableInteropGrabBag(const OUString& aName); + void disableInteropGrabBag(); + bool isInteropGrabBagEnabled() const; + + /// Name of m_aInteropGrabBag. + OUString m_aInteropGrabBagName; + + /// A toplevel dmapper grabbag, like 'pPr'. + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + + /// A sub-grabbag of m_aInteropGrabBag, like 'spacing'. + std::vector<css::beans::PropertyValue> m_aSubInteropGrabBag; + + /// ST_PositionOffset values we received + std::pair<OUString, OUString> m_aPositionOffsets; + /// ST_AlignH/V values we received + std::pair<OUString, OUString> m_aAligns; + /// ST_PositivePercentage values we received + std::queue<OUString> m_aPositivePercentages; + bool isInIndexContext() const { return m_bStartIndex;} + bool isInBibliographyContext() const { return m_bStartBibliography;} + SmartTagHandler& getSmartTagHandler() { return m_aSmartTagHandler; } + + void substream(Id rName, ::writerfilter::Reference<Stream>::Pointer_t const& ref); + + /// If the document needs to split paragraph. + bool m_bIsSplitPara; + + /// Check if "SdtEndBefore" property is set + bool IsSdtEndBefore(); + + bool IsDiscardHeaderFooter() const; + + bool IsForceGenericFields() const { return m_bForceGenericFields; } + + void SetParaAutoBefore(bool bParaAutoBefore) { m_bParaAutoBefore = bParaAutoBefore; } + + /// Forget about the previous paragraph, as it's not inside the same + /// start/end node. + void ClearPreviousParagraph(); + + /// Check if previous paragraph has borders in between and do the border magic to it if so + bool handlePreviousParagraphBorderInBetween() const; + + /// Handle redline text portions in a frame, footnotes and redlines: + /// store their data, and create them after frame creation or footnote/endnote copying + bool m_bIsActualParagraphFramed; + std::deque<css::uno::Any> m_aStoredRedlines[StoredRedlines::NONE]; + + bool IsParaWithInlineObject() const { return m_bParaWithInlineObject; } + + css::uno::Reference< css::embed::XStorage > m_xDocumentStorage; + + /// Handles <w:altChunk>. + void HandleAltChunk(const OUString& rStreamName); + + /// Handles <w:ptab>. + void HandlePTab(sal_Int32 nAlignment); + + /// Handles <w:br w:clear="...">. + void HandleLineBreakClear(sal_Int32 nClear); + + /// Handles <w:br>. + void HandleLineBreak(const PropertyMapPtr& pPropertyMap); + + void commentProps(const OUString& sId, const CommentProperties& rProps); + +private: + void PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType); + // Start a new index section; if needed, finish current paragraph + css::uno::Reference<css::beans::XPropertySet> StartIndexSectionChecked(const OUString& sServiceName); + std::vector<css::uno::Reference< css::drawing::XShape > > m_vTextFramesForChaining ; + /// Current paragraph had at least one field in it. + bool m_bParaHadField; + bool m_bSaveParaHadField; + css::uno::Reference<css::beans::XPropertySet> m_xPreviousParagraph; + /// Current paragraph has automatic before spacing. + bool m_bParaAutoBefore; + /// Current paragraph in a table is first paragraph of a cell + bool m_bFirstParagraphInCell; + bool m_bSaveFirstParagraphInCell; + /// Current paragraph had at least one inline object in it. + bool m_bParaWithInlineObject; + /// SAXException was seen so document will be abandoned + bool m_bSaxError; + + std::unordered_map<OUString, CommentProperties> m_aCommentProps; +}; + +TextAppendContext::TextAppendContext(const css::uno::Reference<css::text::XTextAppend>& xAppend, const css::uno::Reference<css::text::XTextCursor>& xCur) + : xTextAppend(xAppend) +{ + xCursor.set(xCur, css::uno::UNO_QUERY); + xInsertPosition = xCursor; +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FFDataHandler.cxx b/writerfilter/source/dmapper/FFDataHandler.cxx new file mode 100644 index 000000000..507327cf8 --- /dev/null +++ b/writerfilter/source/dmapper/FFDataHandler.cxx @@ -0,0 +1,190 @@ +/* -*- 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 "FFDataHandler.hxx" +#include "TagLogger.hxx" + +#include <ooxml/resourceids.hxx> + +namespace writerfilter::dmapper { + +/************************ + * class: FFDataHandler * + ************************/ + +FFDataHandler::FFDataHandler() : +LoggedProperties("FFDataHandler"), +m_nCheckboxHeight(0), +m_bCheckboxAutoHeight(false), +m_nCheckboxChecked(-1), +m_nCheckboxDefault(-1), +m_nTextMaxLength(0) +{ +} + + +FFDataHandler::~FFDataHandler() +{ +} + + +bool FFDataHandler::getCheckboxChecked() const +{ + if (m_nCheckboxChecked != -1) + return m_nCheckboxChecked; + else if (m_nCheckboxDefault != -1) + return m_nCheckboxDefault; + else + return false; +} + + +void FFDataHandler::lcl_sprm(Sprm & r_Sprm) +{ + switch(r_Sprm.getId()) + { + case NS_ooxml::LN_CT_FFData_name: + { + m_sName = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFData_helpText: + { + resolveSprm(r_Sprm); + } + break; + case NS_ooxml::LN_CT_FFData_statusText: + { + resolveSprm(r_Sprm); + } + break; + case NS_ooxml::LN_CT_FFData_entryMacro: + { + m_sEntryMacro = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFData_exitMacro: + { + m_sExitMacro = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFCheckBox_size: + { + m_nCheckboxHeight = r_Sprm.getValue()->getInt(); + } + break; + case NS_ooxml::LN_CT_FFCheckBox_sizeAuto: + { + m_bCheckboxAutoHeight = r_Sprm.getValue()->getInt(); + } + break; + case NS_ooxml::LN_CT_FFCheckBox_checked: + { + m_nCheckboxChecked = r_Sprm.getValue()->getInt(); + } + break; + case NS_ooxml::LN_CT_FFCheckBox_default: + { + m_nCheckboxDefault = r_Sprm.getValue()->getInt(); + } + break; + case NS_ooxml::LN_CT_FFData_checkBox: + { + resolveSprm(r_Sprm); + } + break; + case NS_ooxml::LN_CT_FFDDList_result: + { + m_sDropDownResult = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFDDList_listEntry: + { + m_DropDownEntries.push_back(r_Sprm.getValue()->getString()); + } + break; + case NS_ooxml::LN_CT_FFData_ddList: + { + resolveSprm(r_Sprm); + } + break; + case NS_ooxml::LN_CT_FFTextInput_type: + { + m_sTextType = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFTextInput_default: + { + m_sTextDefault = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFTextInput_maxLength: + { + m_nTextMaxLength = r_Sprm.getValue()->getInt(); + } + break; + case NS_ooxml::LN_CT_FFTextInput_format: + { + m_sTextFormat = r_Sprm.getValue()->getString(); + } + break; + case NS_ooxml::LN_CT_FFData_textInput: + { + resolveSprm(r_Sprm); + } + break; + default: +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } +} + +void FFDataHandler::resolveSprm(Sprm & r_Sprm) +{ + writerfilter::Reference<Properties>::Pointer_t pProperties = r_Sprm.getProps(); + if( pProperties) + pProperties->resolve(*this); +} + +void FFDataHandler::lcl_attribute(Id name, Value & val) +{ + switch (name) + { + case NS_ooxml::LN_CT_FFHelpText_val: + { + m_sHelpText = val.getString(); + } + break; + case NS_ooxml::LN_CT_FFStatusText_val: + { + m_sStatusText = val.getString(); + } + break; + default: +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FFDataHandler.hxx b/writerfilter/source/dmapper/FFDataHandler.hxx new file mode 100644 index 000000000..f8a88f5c9 --- /dev/null +++ b/writerfilter/source/dmapper/FFDataHandler.hxx @@ -0,0 +1,99 @@ +/* -*- 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 . + */ +#pragma once +#include "LoggedResources.hxx" +#include <rtl/ustring.hxx> +#include <vector> +namespace writerfilter::dmapper +{ +class FFDataHandler : public LoggedProperties +{ +public: + // typedefs + typedef ::tools::SvRef<FFDataHandler> Pointer_t; + typedef ::std::vector<OUString> DropDownEntries_t; + + // constructor + FFDataHandler(); + // destructor + virtual ~FFDataHandler() override; + + // member: name + const OUString& getName() const { return m_sName; } + + // member: helpText + const OUString& getHelpText() const { return m_sHelpText; } + + // member: statusText + const OUString& getStatusText() const { return m_sStatusText; } + + const OUString& getEntryMacro() const { return m_sEntryMacro; } + const OUString& getExitMacro() const { return m_sExitMacro; } + + // member: checkboxHeight + sal_uInt32 getCheckboxHeight() const { return m_nCheckboxHeight; } + + // member: checkboxAutoHeight + bool getCheckboxAutoHeight() const { return m_bCheckboxAutoHeight; } + + // member: checkboxChecked or checkboxDefault (if the previous is not set) + bool getCheckboxChecked() const; + + // member: dropDownResult + const OUString& getDropDownResult() const { return m_sDropDownResult; } + + // member: dropDownEntries + const DropDownEntries_t& getDropDownEntries() const { return m_DropDownEntries; } + + // member: textDefault + const OUString& getTextDefault() const { return m_sTextDefault; } + + const OUString& getTextType() const { return m_sTextType; } + const OUString& getTextFormat() const { return m_sTextFormat; } + sal_uInt16 getTextMaxLength() const { return m_nTextMaxLength; } + + // sprm + void resolveSprm(Sprm& r_sprm); + +private: + OUString m_sName; + OUString m_sHelpText; + OUString m_sStatusText; + OUString m_sEntryMacro; + OUString m_sExitMacro; + sal_uInt32 m_nCheckboxHeight; + bool m_bCheckboxAutoHeight; + int m_nCheckboxChecked; + int m_nCheckboxDefault; + OUString m_sDropDownResult; + DropDownEntries_t m_DropDownEntries; + OUString m_sTextDefault; + OUString m_sTextType; + OUString m_sTextFormat; + sal_uInt16 m_nTextMaxLength; + + // sprm + void lcl_sprm(Sprm& r_sprm) override; + + // attribute + void lcl_attribute(Id name, Value& val) override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FieldTypes.hxx b/writerfilter/source/dmapper/FieldTypes.hxx new file mode 100644 index 000000000..a907a7af6 --- /dev/null +++ b/writerfilter/source/dmapper/FieldTypes.hxx @@ -0,0 +1,305 @@ +/* -*- 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 . + */ +#pragma once + +namespace writerfilter::dmapper { + +enum FieldId +{ + /* ADDRESSBLOCK \d \* MERGEFORMAT -> Addressblock completely unsupported*/ + FIELD_ADDRESSBLOCK + /* ADVANCE \d downvalue \l leftvalue \r rightvalue \u upvalue \x xvalue \y yvalue -> unsupported*/ + ,FIELD_ADVANCE + /* ASK bookmarkname "hint" \d defaultanswer \o \* MERGEFORMAT -> + the hint is not always quoted, inputfield with default answer, prompt before merge (\o) + */ + ,FIELD_ASK + /* AUTONUM \* Numberingswitch -> + mapped to sequence field "AutoNr" + */ + ,FIELD_AUTONUM + /* AUTONUMLGL \* Numberingswitch -> + mapped to sequence field "AutoNr" + */ + ,FIELD_AUTONUMLGL + /* AUTONUMOUT \* Numberingswitch -> + mapped to sequence field "AutoNr" + */ + ,FIELD_AUTONUMOUT + /* AUTHOR NewAuthor \* defaultswitch \* MERGEFORMAT -> + mapped to sequence field "AutoNr" + */ + ,FIELD_AUTHOR + /* COMMENTS "comment" \* MERGEFORMAT -> + Docinfo-Comments + */ + ,FIELD_COMMENTS + /* CREATEDATE \h \* MERGEFORMAT -> + docinfo-created-date + */ + ,FIELD_CREATEDATE + /* DATE \@ "number format" \s \* MERGEFORMAT -> + ww8filterimprovement: multiple languages now supported + */ + ,FIELD_DATE + /* DOCPROPERTY propertyname \* MERGEFORMAT -> + ww8filterimprovement: some fields imported as functionally equivalent fields if possible, + the others imported as UserField + */ + ,FIELD_DOCPROPERTY + /* DOCVARIABLE Name \* MERGEFORMAT -> + ww8filterimprovement: now imported as user fields + */ + ,FIELD_DOCVARIABLE + /* EDITTIME \# "displayformat" \* Numberingswitch \* MERGEFORMAT -> + DocInfo-Modified-Date + ww8filterimprovement: multiple languages now supported + */ + ,FIELD_EDITTIME + ,FIELD_EQ + /* FILLIN "text to fill in" \d defaultanswer \o \* MERGEFORMAT -> + Function-InputField + */ + ,FIELD_FILLIN + /* FILENAME \p \* * MERGEFORMAT -> + file name (\p with path) + */ + ,FIELD_FILENAME + /* FILESIZE \* NumberingType \* MERGEFORMAT -> + not imported in old ww8 filter, see lcl_ParseNumberingType + todo find alternative field + */ + ,FIELD_FILESIZE + /* =formula \# "number format" + todo find alternative field + */ + ,FIELD_FORMULA + /* FORMCHECKBOX */ + ,FIELD_FORMCHECKBOX + /* FORMDROPDOWN */ + ,FIELD_FORMDROPDOWN + /* FORMTEXT */ + ,FIELD_FORMTEXT + /* GOTOBUTTON text \* MERGEFORMAT -> + not imported in old ww8 filter + todo find alternative field + */ + ,FIELD_GOTOBUTTON + /* HYPERLINK "link" \* MERGEFORMAT -> + not imported in old ww8 filter + ww8filterimprovement: now imported as hyperlink + */ + ,FIELD_HYPERLINK + /* IF condition "then text" "else text" -> + not imported in old ww8 filter + ww8filterimprovement: now imported + todo: condition, if text, else text still missing + */ + ,FIELD_IF + /* INFO NameOfInfo \* MERGEFORMAT -> old + todo: filter imports wrong? + */ + ,FIELD_INFO + /* INCLUDEPICTURE path \* MERGEFORMAT-> + old filter imports an embedded picture + */ + ,FIELD_INCLUDEPICTURE + /* KEYWORDS keyword \* defaultswitch \* Numberingswitch \* MERGEFORMAT -> + DocInfo Keywords + */ + ,FIELD_KEYWORDS + /* LASTSAVEDBY \* MERGEFORMAT -> + DocInfo-Modified-Author + */ + ,FIELD_LASTSAVEDBY + /* MACROBUTTON MacroName quick help text -> + Macro field + */ + ,FIELD_MACROBUTTON + /* MERGEFIELD ColumName \b prefix \f suffix \* MERGEFORMAT -> + ww8filterimprovement: column-only API now supported + */ + ,FIELD_MERGEFIELD + /* MERGEREC \* MERGEFORMAT -> + RecordNumber field, maybe without db name + todo: currently unchecked + */ + ,FIELD_MERGEREC + /* MERGESEQ \* MERGEFORMAT -> + not imported in old ww8 filter + ww8filterimprovement: now imported + todo: currently unchecked + */ + ,FIELD_MERGESEQ + /* NEXT text -> + Next record + todo: currently unchecked + */ + ,FIELD_NEXT + /* NEXTIF condition + todo: condition not imported + */ + ,FIELD_NEXTIF + /* PAGE \* Numberingswitch \* MERGEFORMAT -> + see lcl_ParseNumberingType + */ + ,FIELD_PAGE + ,FIELD_PAGEREF + ,FIELD_PRINTDATE + /* REF targetbkm \f \* MERGEFORMAT -> + imports a ShowVariable (bookmarkname)? + \h hyperlink to paragraph + \p relative to para above/below + \f reference number + \d separator number separator + \n paragraph number + \r paragraph number in relative context + \t suppress non delimiters + \w paragraph number in full context + \* Upper/Lower... + */ + ,FIELD_REF + /* REVNUM \* Numberingswitch \* MERGEFORMAT -> + DocInfo-revision number + */ + ,FIELD_REVNUM + /* SAVEDATE \@ "NumberFormat"\* MERGEFORMAT -> + DocInfo-modified-date + */ + ,FIELD_SAVEDATE + /* SECTION \* NumberFormat \* MERGEFORMAT -> + not imported in old ww8 filter see lcl_ParseNumberingType + todo: find alternative + */ + ,FIELD_SECTION + /* SECTIONPAGES \* NumberFormat \* MERGEFORMAT -> + not imported in old ww8 filter see lcl_ParseNumberingType + todo: find alternative + */ + ,FIELD_SECTIONPAGES + /* SEQ sequencename \h \c \n \r \s \* MERGEFORMAT -> + number range name:sequencename value:sequencename+1 + todo: only partially implemented, switches unsupported + */ + ,FIELD_SEQ + /* SET bookmarkname newtext \* MERGEFORMAT -> + SetVariable bookmarkname = newtext + todo: not implemented yet + */ + ,FIELD_SET + /* SKIPIF condition \* MERGEFORMAT -> + ?? + todo: not implemented yet + */ + ,FIELD_SKIPIF + /* STYLEREF stylename \* MERGEFORMAT -> + not imported in old ww8 filter + todo: add an equivalent field type + */ + ,FIELD_STYLEREF + /* SUBJECT subject \* Defaultswitch \* MERGEFORMAT -> + DocInfo - subject + */ + ,FIELD_SUBJECT + /* SYMBOL symbolnumber \* MERGEFORMAT -> + inserts a special char (symbolnumber) + todo: find alternative + */ + ,FIELD_SYMBOL + /* TEMPLATE \* Defaultswitch \* MERGEFORMAT + TemplateName field + */ + ,FIELD_TEMPLATE + /* TIME \@ "number format" \* MERGEFORMAT + ww8filterimprovement: multiple languages now supported + */ + ,FIELD_TIME + /* TITLE \* Defaultswitch \* MERGEFORMAT -> + DocInfo-title + */ + ,FIELD_TITLE + /* USERINITIALS newinitials \* MERGEFORMAT -> + ExtendedUser field (SHORTCUT) + */ + ,FIELD_USERINITIALS + /* USERADDRESS \* MERGEFORMAT -> + not imported in old ww8 filter + todo: find alternative + */ + ,FIELD_USERADDRESS + /* USERNAME newusername \* MERGEFORMAT -> + not imported in old ww8 filter + todo: import as extended user field(s) + */ + ,FIELD_USERNAME + /* + TOC options: + \a Builds a table of figures but does not include the captions's label and number + \b Uses a bookmark to specify area of document from which to build table of contents + \c Builds a table of figures of the given label + \d Defines the separator between sequence and page numbers + \f Builds a table of contents using TC entries instead of outline levels + \h Hyperlinks the entries and page numbers within the table of contents + \l Defines the TC entries field level used to build a table of contents + \n Builds a table of contents or a range of entries, such as 1-9, in a table of contents without page numbers + \o Builds a table of contents by using outline levels instead of TC entries + \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 + \u Builds a table of contents by using the applied paragraph outline level + \w Preserve tab characters within table entries + \x Preserve newline characters within table entries + \z Hides page numbers within the table of contents when shown in Web Layout View + */ + ,FIELD_TOC + /* + TOC entry: text + \f TC entry in doc with multiple tables + \l Outline Level + \n Suppress page numbers + example: TOC "EntryText \f \l 2 \n + */ + ,FIELD_TC + /* document statistic - number of characters + */ + ,FIELD_NUMCHARS + /* document statistic - number of words + */ + ,FIELD_NUMWORDS + /* document statistic - number of pages + */ + ,FIELD_NUMPAGES + /* Document alphabetical index + */ + ,FIELD_INDEX + /* Document alphabetical index marks + */ + ,FIELD_XE + /** + * Bibliography + */ + ,FIELD_BIBLIOGRAPHY + /* Citation + */ + ,FIELD_CITATION +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FontTable.cxx b/writerfilter/source/dmapper/FontTable.cxx new file mode 100644 index 000000000..470deff83 --- /dev/null +++ b/writerfilter/source/dmapper/FontTable.cxx @@ -0,0 +1,299 @@ +/* -*- 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 "FontTable.hxx" +#include <o3tl/deleter.hxx> +#include <ooxml/resourceids.hxx> +#include <vector> +#include <sal/log.hxx> +#include <rtl/tencinfo.h> +#include <vcl/embeddedfontshelper.hxx> +#include <unotools/fontdefs.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper +{ + +struct FontTable_Impl +{ + std::unique_ptr<EmbeddedFontsHelper, o3tl::default_delete<EmbeddedFontsHelper>> xEmbeddedFontHelper; + std::vector< FontEntry::Pointer_t > aFontEntries; + FontEntry::Pointer_t pCurrentEntry; + FontTable_Impl() {} +}; + +FontTable::FontTable() +: LoggedProperties("FontTable") +, LoggedTable("FontTable") +, LoggedStream("FontTable") +, m_pImpl( new FontTable_Impl ) +{ +} + +FontTable::~FontTable() +{ +} + +void FontTable::lcl_attribute(Id Name, Value & val) +{ + SAL_WARN_IF( !m_pImpl->pCurrentEntry, "writerfilter.dmapper", "current entry has to be set here" ); + if(!m_pImpl->pCurrentEntry) + return ; + int nIntValue = val.getInt(); + OUString sValue = val.getString(); + switch(Name) + { + case NS_ooxml::LN_CT_Pitch_val: + if (static_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_Pitch_fixed) + ; + else if (static_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_Pitch_variable) + ; + else if (static_cast<Id>(nIntValue) == NS_ooxml::LN_Value_ST_Pitch_default) + ; + else + SAL_WARN("writerfilter.dmapper", "FontTable::lcl_attribute: unhandled NS_ooxml::CT_Pitch_val: " << nIntValue); + break; + case NS_ooxml::LN_CT_Font_name: + m_pImpl->pCurrentEntry->sFontName = sValue; + break; + case NS_ooxml::LN_CT_Charset_val: + // w:characterSet has higher priority, set only if that one is not set + if( m_pImpl->pCurrentEntry->nTextEncoding == RTL_TEXTENCODING_DONTKNOW ) + { + m_pImpl->pCurrentEntry->nTextEncoding = rtl_getTextEncodingFromWindowsCharset( nIntValue ); + if( IsStarSymbol( m_pImpl->pCurrentEntry->sFontName )) + m_pImpl->pCurrentEntry->nTextEncoding = RTL_TEXTENCODING_SYMBOL; + } + break; + case NS_ooxml::LN_CT_Charset_characterSet: + { + OString tmp; + sValue.convertToString( &tmp, RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS ); + m_pImpl->pCurrentEntry->nTextEncoding = rtl_getTextEncodingFromMimeCharset( tmp.getStr() ); + // Older LO versions used to write incorrect character set for OpenSymbol, fix. + if( IsStarSymbol( m_pImpl->pCurrentEntry->sFontName )) + m_pImpl->pCurrentEntry->nTextEncoding = RTL_TEXTENCODING_SYMBOL; + break; + } + default: ; + } +} + +void FontTable::lcl_sprm(Sprm& rSprm) +{ + SAL_WARN_IF( !m_pImpl->pCurrentEntry, "writerfilter.dmapper", "current entry has to be set here" ); + if(!m_pImpl->pCurrentEntry) + return ; + sal_uInt32 nSprmId = rSprm.getId(); + + switch(nSprmId) + { + case NS_ooxml::LN_CT_Font_charset: + case NS_ooxml::LN_CT_Font_pitch: + resolveSprm( rSprm ); + break; + case NS_ooxml::LN_CT_Font_embedRegular: + case NS_ooxml::LN_CT_Font_embedBold: + case NS_ooxml::LN_CT_Font_embedItalic: + case NS_ooxml::LN_CT_Font_embedBoldItalic: + { + writerfilter::Reference< Properties >::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + EmbeddedFontHandler handler(*this, m_pImpl->pCurrentEntry->sFontName, + nSprmId == NS_ooxml::LN_CT_Font_embedRegular ? "" + : nSprmId == NS_ooxml::LN_CT_Font_embedBold ? "b" + : nSprmId == NS_ooxml::LN_CT_Font_embedItalic ? "i" + : /*NS_ooxml::LN_CT_Font_embedBoldItalic*/ "bi" ); + pProperties->resolve( handler ); + } + break; + } + case NS_ooxml::LN_CT_Font_altName: + break; + case NS_ooxml::LN_CT_Font_panose1: + break; + case NS_ooxml::LN_CT_Font_family: + break; + case NS_ooxml::LN_CT_Font_sig: + break; + case NS_ooxml::LN_CT_Font_notTrueType: + break; + default: + SAL_WARN("writerfilter.dmapper", "FontTable::lcl_sprm: unhandled token: " << nSprmId); + break; + } +} + +void FontTable::resolveSprm(Sprm & r_Sprm) +{ + writerfilter::Reference<Properties>::Pointer_t pProperties = r_Sprm.getProps(); + if( pProperties ) + pProperties->resolve(*this); +} + +void FontTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) +{ + //create a new font entry + SAL_WARN_IF( m_pImpl->pCurrentEntry, "writerfilter.dmapper", "current entry has to be NULL here" ); + m_pImpl->pCurrentEntry = new FontEntry; + ref->resolve(*this); + //append it to the table + m_pImpl->aFontEntries.push_back( m_pImpl->pCurrentEntry ); + m_pImpl->pCurrentEntry.clear(); +} + +void FontTable::lcl_startSectionGroup() +{ +} + +void FontTable::lcl_endSectionGroup() +{ +} + +void FontTable::lcl_startParagraphGroup() +{ +} + +void FontTable::lcl_endParagraphGroup() +{ +} + +void FontTable::lcl_startCharacterGroup() +{ +} + +void FontTable::lcl_endCharacterGroup() +{ +} + +void FontTable::lcl_text(const sal_uInt8*, size_t ) +{ +} + +void FontTable::lcl_utext(const sal_uInt8* , size_t) +{ +} + +void FontTable::lcl_props(writerfilter::Reference<Properties>::Pointer_t) +{ +} + +void FontTable::lcl_table(Id, writerfilter::Reference<Table>::Pointer_t) +{ +} + +void FontTable::lcl_substream(Id, ::writerfilter::Reference<Stream>::Pointer_t) +{ +} + +void FontTable::lcl_startShape(uno::Reference<drawing::XShape> const&) +{ +} + +void FontTable::lcl_endShape( ) +{ +} + +FontEntry::Pointer_t FontTable::getFontEntry(sal_uInt32 nIndex) +{ + return (m_pImpl->aFontEntries.size() > nIndex) + ? m_pImpl->aFontEntries[nIndex] + : FontEntry::Pointer_t(); +} + +sal_uInt32 FontTable::size() +{ + return m_pImpl->aFontEntries.size(); +} + +void FontTable::addEmbeddedFont(const css::uno::Reference<css::io::XInputStream>& stream, + const OUString& fontName, const char* extra, + std::vector<unsigned char> const & key) +{ + if (!m_pImpl->xEmbeddedFontHelper) + m_pImpl->xEmbeddedFontHelper.reset(new EmbeddedFontsHelper); + m_pImpl->xEmbeddedFontHelper->addEmbeddedFont(stream, fontName, extra, key); +} + +EmbeddedFontHandler::EmbeddedFontHandler(FontTable& rFontTable, const OUString& _fontName, const char* _style ) +: LoggedProperties("EmbeddedFontHandler") +, fontTable( rFontTable ) +, fontName( _fontName ) +, style( _style ) +{ +} + +EmbeddedFontHandler::~EmbeddedFontHandler() +{ + if( !inputStream.is()) + return; + std::vector< unsigned char > key( 32 ); + if( !fontKey.isEmpty()) + { // key for unobfuscating + // 1 3 5 7 10 2 5 7 20 2 5 7 9 1 3 5 + // {62E79491-959F-41E9-B76B-6B32631DEA5C} + static const int pos[ 16 ] = { 35, 33, 31, 29, 27, 25, 22, 20, 17, 15, 12, 10, 7, 5, 3, 1 }; + for( int i = 0; + i < 16; + ++i ) + { + int v1 = fontKey[ pos[ i ]]; + int v2 = fontKey[ pos[ i ] + 1 ]; + assert(( v1 >= '0' && v1 <= '9' ) || ( v1 >= 'A' && v1 <= 'F' )); + assert(( v2 >= '0' && v2 <= '9' ) || ( v2 >= 'A' && v2 <= 'F' )); + int val = ( v1 - ( v1 <= '9' ? '0' : 'A' - 10 )) * 16 + v2 - ( v2 <= '9' ? '0' : 'A' - 10 ); + key[ i ] = val; + key[ i + 16 ] = val; + } + } + fontTable.addEmbeddedFont( inputStream, fontName, style, key ); + inputStream->closeInput(); +} + +void EmbeddedFontHandler::lcl_attribute( Id name, Value& val ) +{ + OUString sValue = val.getString(); + switch( name ) + { + case NS_ooxml::LN_CT_FontRel_fontKey: + fontKey = sValue; + break; + case NS_ooxml::LN_CT_Rel_id: + break; + case NS_ooxml::LN_CT_FontRel_subsetted: + break; // TODO? Let's just ignore this for now and hope + // it doesn't break anything. + case NS_ooxml::LN_inputstream: // the actual font data as stream + val.getAny() >>= inputStream; + break; + default: + break; + } +} + +void EmbeddedFontHandler::lcl_sprm( Sprm& ) +{ +} + + +}//namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FontTable.hxx b/writerfilter/source/dmapper/FontTable.hxx new file mode 100644 index 000000000..5f32776a2 --- /dev/null +++ b/writerfilter/source/dmapper/FontTable.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> +#include <vector> +#include "LoggedResources.hxx" +#include <com/sun/star/io/XInputStream.hpp> + +namespace writerfilter::dmapper +{ + +struct FontTable_Impl; +struct FontEntry : public virtual SvRefBase +{ + typedef tools::SvRef<FontEntry> Pointer_t; + + OUString sFontName; + sal_Int32 nTextEncoding; + FontEntry() : + nTextEncoding( RTL_TEXTENCODING_DONTKNOW ) + {} +}; + +class FontTable : public LoggedProperties, public LoggedTable + /*,public BinaryObj*/, public LoggedStream +{ + std::unique_ptr<FontTable_Impl> m_pImpl; + + public: + FontTable(); + virtual ~FontTable() override; + + sal_uInt32 size(); + FontEntry::Pointer_t getFontEntry(sal_uInt32 nIndex); + + void addEmbeddedFont(const css::uno::Reference<css::io::XInputStream>& stream, + const OUString& fontName, const char* extra, + std::vector<unsigned char> const & key); + + private: + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + void resolveSprm(Sprm & r_sprm); + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + + // Stream + virtual void lcl_startSectionGroup() override; + virtual void lcl_endSectionGroup() override; + virtual void lcl_startParagraphGroup() override; + virtual void lcl_endParagraphGroup() override; + virtual void lcl_startCharacterGroup() override; + virtual void lcl_endCharacterGroup() override; + virtual void lcl_text(const sal_uInt8 * data, size_t len) override; + virtual void lcl_utext(const sal_uInt8 * data, size_t len) override; + virtual void lcl_props(writerfilter::Reference<Properties>::Pointer_t ref) override; + virtual void lcl_table(Id name, + writerfilter::Reference<Table>::Pointer_t ref) override; + virtual void lcl_substream(Id name, + ::writerfilter::Reference<Stream>::Pointer_t ref) override; + virtual void lcl_startShape(css::uno::Reference<css::drawing::XShape> const& xShape) override; + virtual void lcl_endShape( ) override; + virtual void lcl_startTextBoxContent() override {}; + virtual void lcl_endTextBoxContent() override {}; +}; +typedef tools::SvRef< FontTable > FontTablePtr; + +class EmbeddedFontHandler : public LoggedProperties +{ +public: + EmbeddedFontHandler(FontTable& rFontTable, const OUString& fontName, const char* style); + virtual ~EmbeddedFontHandler() override; +private: + virtual void lcl_attribute( Id name, Value& val ) override; + virtual void lcl_sprm( Sprm& rSprm ) override; + FontTable& fontTable; + OUString fontName; + const char* const style; + OUString fontKey; + css::uno::Reference<css::io::XInputStream> inputStream; +}; + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FormControlHelper.cxx b/writerfilter/source/dmapper/FormControlHelper.cxx new file mode 100644 index 000000000..e00c4bebd --- /dev/null +++ b/writerfilter/source/dmapper/FormControlHelper.cxx @@ -0,0 +1,378 @@ +/* -*- 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 <math.h> + +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/uno/Any.hxx> + +#include <o3tl/safeint.hxx> + +#include "FormControlHelper.hxx" +#include <xmloff/odffields.hxx> +#include <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + +struct FormControlHelper::FormControlHelper_Impl : public virtual SvRefBase +{ + FieldId m_eFieldId; + awt::Size aSize; + uno::Reference<drawing::XDrawPage> rDrawPage; + uno::Reference<form::XForm> rForm; + uno::Reference<form::XFormComponent> rFormComponent; + uno::Reference<lang::XMultiServiceFactory> rServiceFactory; + uno::Reference<text::XTextDocument> rTextDocument; + + uno::Reference<drawing::XDrawPage> const & getDrawPage(); + uno::Reference<lang::XMultiServiceFactory> const & getServiceFactory(); + uno::Reference<form::XForm> const & getForm(); + uno::Reference<container::XIndexContainer> getFormComps(); +}; + +uno::Reference<drawing::XDrawPage> const & FormControlHelper::FormControlHelper_Impl::getDrawPage() +{ + if (! rDrawPage.is()) + { + uno::Reference<drawing::XDrawPageSupplier> + xDrawPageSupplier(rTextDocument, uno::UNO_QUERY); + if (xDrawPageSupplier.is()) + rDrawPage = xDrawPageSupplier->getDrawPage(); + } + + return rDrawPage; +} + +uno::Reference<lang::XMultiServiceFactory> const & FormControlHelper::FormControlHelper_Impl::getServiceFactory() +{ + if (! rServiceFactory.is()) + rServiceFactory.set(rTextDocument, uno::UNO_QUERY); + + return rServiceFactory; +} + +uno::Reference<form::XForm> const & FormControlHelper::FormControlHelper_Impl::getForm() +{ + if (! rForm.is()) + { + uno::Reference<form::XFormsSupplier> xFormsSupplier(getDrawPage(), uno::UNO_QUERY); + + if (xFormsSupplier.is()) + { + uno::Reference<container::XNameContainer> xFormsNamedContainer(xFormsSupplier->getForms()); + static constexpr OUStringLiteral sDOCXForm = u"DOCX-Standard"; + + OUString sFormName(sDOCXForm); + sal_uInt16 nUnique = 0; + + while (xFormsNamedContainer->hasByName(sFormName)) + { + ++nUnique; + sFormName = sDOCXForm + OUString::number(nUnique); + } + + uno::Reference<uno::XInterface> xForm(getServiceFactory()->createInstance("com.sun.star.form.component.Form")); + if (xForm.is()) + { + uno::Reference<beans::XPropertySet> + xFormProperties(xForm, uno::UNO_QUERY); + uno::Any aAny(sFormName); + xFormProperties->setPropertyValue("Name", aAny); + } + + rForm.set(xForm, uno::UNO_QUERY); + + uno::Reference<container::XIndexContainer> xForms(xFormsNamedContainer, uno::UNO_QUERY); + uno::Any aAny(xForm); + xForms->insertByIndex(xForms->getCount(), aAny); + } + } + + return rForm; +} + +uno::Reference<container::XIndexContainer> FormControlHelper::FormControlHelper_Impl::getFormComps() +{ + uno::Reference<container::XIndexContainer> xIndexContainer(getForm(), uno::UNO_QUERY); + + return xIndexContainer; +} + +FormControlHelper::FormControlHelper(FieldId eFieldId, + uno::Reference<text::XTextDocument> const& xTextDocument, + FFDataHandler::Pointer_t const & pFFData) + : m_pFFData(pFFData), m_pImpl(new FormControlHelper_Impl) +{ + m_pImpl->m_eFieldId = eFieldId; + m_pImpl->rTextDocument = xTextDocument; +} + +FormControlHelper::~FormControlHelper() +{ +} + +bool FormControlHelper::createCheckbox(uno::Reference<text::XTextRange> const& xTextRange, + const OUString & rControlName) +{ + if ( !m_pFFData ) + return false; + uno::Reference<lang::XMultiServiceFactory> + xServiceFactory(m_pImpl->getServiceFactory()); + + if (! xServiceFactory.is()) + return false; + + uno::Reference<uno::XInterface> xInterface = xServiceFactory->createInstance("com.sun.star.form.component.CheckBox"); + + if (!xInterface.is()) + return false; + + m_pImpl->rFormComponent.set(xInterface, uno::UNO_QUERY); + if (!m_pImpl->rFormComponent.is()) + return false; + + uno::Reference<beans::XPropertySet> xPropSet(xInterface, uno::UNO_QUERY); + + sal_uInt32 nCheckBoxHeight = 16 * m_pFFData->getCheckboxHeight(); + + if (m_pFFData->getCheckboxAutoHeight()) + { + uno::Reference<beans::XPropertySet> xTextRangeProps(xTextRange, uno::UNO_QUERY); + + try + { + float fCheckBoxHeight = 0.0; + xTextRangeProps->getPropertyValue("CharHeight") >>= fCheckBoxHeight; + nCheckBoxHeight = static_cast<sal_uInt32>(floor(fCheckBoxHeight * 35.3)); + } + catch (beans::UnknownPropertyException &) + { + } + } + + m_pImpl->aSize.Width = nCheckBoxHeight; + m_pImpl->aSize.Height = m_pImpl->aSize.Width; + + if (!m_pFFData->getStatusText().isEmpty()) + { + xPropSet->setPropertyValue("HelpText", uno::Any(m_pFFData->getStatusText())); + } + + xPropSet->setPropertyValue("DefaultState", uno::Any(m_pFFData->getCheckboxChecked())); + + if (!m_pFFData->getHelpText().isEmpty()) + { + xPropSet->setPropertyValue("HelpF1Text", uno::Any(m_pFFData->getHelpText())); + } + + xPropSet->setPropertyValue("Name", uno::Any(rControlName)); + + return true; +} + +void FormControlHelper::processField(uno::Reference<text::XFormField> const& xFormField) +{ + // Set field type first before adding parameters. + if (m_pImpl->m_eFieldId == FIELD_FORMTEXT ) + { + xFormField->setFieldType(ODF_FORMTEXT); + } + else if (m_pImpl->m_eFieldId == FIELD_FORMCHECKBOX ) + { + xFormField->setFieldType(ODF_FORMCHECKBOX); + } + else if (m_pImpl->m_eFieldId == FIELD_FORMDROPDOWN ) + { + xFormField->setFieldType(ODF_FORMDROPDOWN); + } + + uno::Reference<container::XNameContainer> xNameCont = xFormField->getParameters(); + uno::Reference<container::XNamed> xNamed( xFormField, uno::UNO_QUERY ); + if ( !(m_pFFData && xNamed.is() && xNameCont.is()) ) + return; + + OUString sTmp = m_pFFData->getEntryMacro(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "EntryMacro", uno::Any(sTmp) ); + sTmp = m_pFFData->getExitMacro(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "ExitMacro", uno::Any(sTmp) ); + + sTmp = m_pFFData->getHelpText(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "Help", uno::Any(sTmp) ); + + sTmp = m_pFFData->getStatusText(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "Hint", uno::Any(sTmp) ); + + if (m_pImpl->m_eFieldId == FIELD_FORMTEXT ) + { + sTmp = m_pFFData->getName(); + try + { + if ( !sTmp.isEmpty() ) + xNamed->setName( sTmp ); + } + catch ( uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("writerfilter", "Set Formfield name failed"); + } + + sTmp = m_pFFData->getTextType(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "Type", uno::Any(sTmp) ); + + const sal_uInt16 nMaxLength = m_pFFData->getTextMaxLength(); + if ( nMaxLength ) + { + xNameCont->insertByName( "MaxLength", uno::Any(nMaxLength) ); + } + + sTmp = m_pFFData->getTextDefault(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "Content", uno::Any(sTmp) ); + + sTmp = m_pFFData->getTextFormat(); + if ( !sTmp.isEmpty() ) + xNameCont->insertByName( "Format", uno::Any(sTmp) ); + } + else if (m_pImpl->m_eFieldId == FIELD_FORMCHECKBOX ) + { + uno::Reference<beans::XPropertySet> xPropSet(xFormField, uno::UNO_QUERY); + uno::Any aAny; + aAny <<= m_pFFData->getCheckboxChecked(); + if ( xPropSet.is() ) + xPropSet->setPropertyValue("Checked", aAny); + } + else if (m_pImpl->m_eFieldId == FIELD_FORMDROPDOWN ) + { + const FFDataHandler::DropDownEntries_t& rEntries = m_pFFData->getDropDownEntries(); + if (!rEntries.empty()) + { + if ( xNameCont->hasByName(ODF_FORMDROPDOWN_LISTENTRY) ) + xNameCont->replaceByName(ODF_FORMDROPDOWN_LISTENTRY, uno::Any(comphelper::containerToSequence(rEntries))); + else + xNameCont->insertByName(ODF_FORMDROPDOWN_LISTENTRY, uno::Any(comphelper::containerToSequence(rEntries))); + + sal_Int32 nResult = m_pFFData->getDropDownResult().toInt32(); + // 0 is valid, but also how toInt32 reports parse error, but it's a sensible default... + if (0 <= nResult && o3tl::make_unsigned(nResult) < rEntries.size()) + { + if ( xNameCont->hasByName(ODF_FORMDROPDOWN_RESULT) ) + xNameCont->replaceByName(ODF_FORMDROPDOWN_RESULT, uno::Any( nResult ) ); + else + xNameCont->insertByName(ODF_FORMDROPDOWN_RESULT, uno::Any( nResult ) ); + } + } + } +} + +void FormControlHelper::insertControl(uno::Reference<text::XTextRange> const& xTextRange) +{ + bool bCreated = false; + if ( !m_pFFData ) + return; + uno::Reference<container::XNameContainer> xFormCompsByName(m_pImpl->getForm(), uno::UNO_QUERY); + uno::Reference<container::XIndexContainer> xFormComps(m_pImpl->getFormComps()); + if (! xFormComps.is()) + return; + + sal_Int32 nControl = 0; + bool bDone = false; + OUString sControlName; + + do + { + OUString sTmp = "Control" + OUString::number(nControl); + + nControl++; + if (! xFormCompsByName->hasByName(sTmp)) + { + sControlName = sTmp; + bDone = true; + } + } + while (! bDone); + + switch (m_pImpl->m_eFieldId) + { + case FIELD_FORMCHECKBOX: + bCreated = createCheckbox(xTextRange, sControlName); + break; + default: + break; + } + + if (!bCreated) + return; + + uno::Any aAny(m_pImpl->rFormComponent); + xFormComps->insertByIndex(xFormComps->getCount(), aAny); + + if (! m_pImpl->getServiceFactory().is()) + return; + + uno::Reference<uno::XInterface> xInterface = m_pImpl->getServiceFactory()->createInstance("com.sun.star.drawing.ControlShape"); + + if (! xInterface.is()) + return; + + uno::Reference<drawing::XShape> xShape(xInterface, uno::UNO_QUERY); + + if (! xShape.is()) + return; + + xShape->setSize(m_pImpl->aSize); + + uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY); + + sal_uInt16 nTmp = sal_uInt16(text::TextContentAnchorType_AS_CHARACTER); + xShapeProps->setPropertyValue("AnchorType", uno::Any(sal_uInt16(nTmp))); + + nTmp = text::VertOrientation::CENTER; + xShapeProps->setPropertyValue("VertOrient", uno::Any(sal_uInt16(nTmp))); + + xShapeProps->setPropertyValue("TextRange", uno::Any(xTextRange)); + + uno::Reference<drawing::XControlShape> xControlShape(xShape, uno::UNO_QUERY); + uno::Reference<awt::XControlModel> xControlModel(m_pImpl->rFormComponent, uno::UNO_QUERY); + xControlShape->setControl(xControlModel); + + m_pImpl->getDrawPage()->add(xShape); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/FormControlHelper.hxx b/writerfilter/source/dmapper/FormControlHelper.hxx new file mode 100644 index 000000000..b1f262024 --- /dev/null +++ b/writerfilter/source/dmapper/FormControlHelper.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ +#pragma once + +#include "FFDataHandler.hxx" +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XFormField.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include "FieldTypes.hxx" + +namespace writerfilter::dmapper +{ +class FormControlHelper : public virtual SvRefBase +{ +public: + typedef tools::SvRef<FormControlHelper> Pointer_t; + FormControlHelper(FieldId eFieldId, + css::uno::Reference<css::text::XTextDocument> const& rTextDocument, + FFDataHandler::Pointer_t const& pFFData); + ~FormControlHelper() override; + + void insertControl(css::uno::Reference<css::text::XTextRange> const& xTextRange); + void processField(css::uno::Reference<css::text::XFormField> const& xFormField); + bool hasFFDataHandler() const { return (m_pFFData != nullptr); } + +private: + FFDataHandler::Pointer_t m_pFFData; + struct FormControlHelper_Impl; + tools::SvRef<FormControlHelper_Impl> m_pImpl; + + bool createCheckbox(css::uno::Reference<css::text::XTextRange> const& xTextRange, + const OUString& rControlName); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/GraphicHelpers.cxx b/writerfilter/source/dmapper/GraphicHelpers.cxx new file mode 100644 index 000000000..d4fc4b9a8 --- /dev/null +++ b/writerfilter/source/dmapper/GraphicHelpers.cxx @@ -0,0 +1,348 @@ +/* -*- 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 "GraphicHelpers.hxx" +#include "TagLogger.hxx" +#include <dmapper/GraphicZOrderHelper.hxx> +#include "PropertyIds.hxx" + +#include <ooxml/resourceids.hxx> + +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> + +#include <oox/drawingml/drawingmltypes.hxx> +#include <sal/log.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <tools/diagnose_ex.h> + +#include <iostream> + +namespace writerfilter::dmapper { + +using namespace com::sun::star; + +PositionHandler::PositionHandler( std::pair<OUString, OUString>& rPositionOffsets, std::pair<OUString, OUString>& rAligns ) : +LoggedProperties("PositionHandler"), +m_nOrient(text::VertOrientation::NONE), +m_nRelation(text::RelOrientation::FRAME), +m_nPosition(0), +m_rPositionOffsets(rPositionOffsets), +m_rAligns(rAligns) +{ +} + +PositionHandler::~PositionHandler( ) +{ +} + +void PositionHandler::lcl_attribute( Id aName, Value& rVal ) +{ + sal_Int32 nIntValue = rVal.getInt( ); + switch ( aName ) + { + case NS_ooxml::LN_CT_PosV_relativeFrom: + { + switch ( nIntValue ) + { + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_margin: + m_nRelation = text::RelOrientation::PAGE_PRINT_AREA; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_page: + m_nRelation = text::RelOrientation::PAGE_FRAME; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_topMargin: + m_nRelation = text::RelOrientation::PAGE_PRINT_AREA_TOP; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_bottomMargin: + m_nRelation = text::RelOrientation::PAGE_PRINT_AREA_BOTTOM; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_paragraph: + m_nRelation = text::RelOrientation::FRAME; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromV_line: + m_nRelation = text::RelOrientation::TEXT_LINE; + break; + + // TODO There are some other unhandled values + default: + SAL_WARN("writerfilter", "unhandled case (" << nIntValue << ") in NS_ooxml::LN_CT_PosV_relativeFrom"); + } + } + break; + + case NS_ooxml::LN_CT_PosH_relativeFrom: + { + switch ( nIntValue ) + { + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_margin: + m_nRelation = text::RelOrientation::PAGE_PRINT_AREA; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_page: + m_nRelation = text::RelOrientation::PAGE_FRAME; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_insideMargin: + m_nRelation = text::RelOrientation::PAGE_FRAME; + m_bPageToggle = true; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_column: + m_nRelation = text::RelOrientation::FRAME; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_character: + m_nRelation = text::RelOrientation::CHAR; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_leftMargin: + m_nRelation = text::RelOrientation::PAGE_LEFT; + break; + + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_RelFromH_rightMargin: + m_nRelation = text::RelOrientation::PAGE_RIGHT; + break; + + // TODO There are some other unhandled values + default: + SAL_WARN("writerfilter", "unhandled case (" << nIntValue << ") in NS_ooxml::LN_CT_PosH_relativeFrom"); + } + } + break; + default: +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } +} + +void PositionHandler::lcl_sprm(Sprm& rSprm) +{ + sal_uInt32 nSprmId = rSprm.getId(); + + switch (nSprmId) + { + case NS_ooxml::LN_CT_PosH_posOffset: + m_nPosition = oox::drawingml::convertEmuToHmm(m_rPositionOffsets.first.toInt32()); + m_rPositionOffsets.first.clear(); + break; + case NS_ooxml::LN_CT_PosV_posOffset: + m_nPosition = oox::drawingml::convertEmuToHmm(m_rPositionOffsets.second.toInt32()); + m_rPositionOffsets.second.clear(); + break; + case NS_ooxml::LN_CT_PosH_align: + { + OUString& rAlign = m_rAligns.first; + if (rAlign == "left") + m_nOrient = text::HoriOrientation::LEFT; + else if (rAlign == "right") + m_nOrient = text::HoriOrientation::RIGHT; + else if (rAlign == "center") + m_nOrient = text::HoriOrientation::CENTER; + else if (rAlign == "inside") + m_nOrient = text::HoriOrientation::INSIDE; + else if (rAlign == "outside") + m_nOrient = text::HoriOrientation::OUTSIDE; + rAlign.clear(); + break; + } + case NS_ooxml::LN_CT_PosV_align: + { + OUString& rAlign = m_rAligns.second; + if (rAlign == "top") + m_nOrient = text::VertOrientation::TOP; + else if (rAlign == "bottom") + m_nOrient = text::VertOrientation::BOTTOM; + else if (rAlign == "center") + m_nOrient = text::VertOrientation::CENTER; + else if (rAlign == "inside" && m_nRelation == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + m_nOrient = text::VertOrientation::TOP; + else if (rAlign == "outside" && m_nRelation == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + m_nOrient = text::VertOrientation::BOTTOM; + rAlign.clear(); + break; + } + } +} + +sal_Int16 PositionHandler::orientation() const +{ + if( m_nRelation == text::RelOrientation::TEXT_LINE ) + { // It appears that to 'line of text' alignment is backwards to other alignments, + // 'top' meaning putting on top of the line instead of having top at the line. + if( m_nOrient == text::VertOrientation::TOP ) + return text::VertOrientation::BOTTOM; + else if( m_nOrient == text::VertOrientation::BOTTOM ) + return text::VertOrientation::TOP; + } + return m_nOrient; +} + +WrapHandler::WrapHandler( ) : +LoggedProperties("WrapHandler"), + m_nType( 0 ), + m_nSide( 0 ) +{ +} + +WrapHandler::~WrapHandler( ) +{ +} + +void WrapHandler::lcl_attribute( Id aName, Value& rVal ) +{ + switch ( aName ) + { + case NS_ooxml::LN_CT_Wrap_type: + m_nType = sal_Int32( rVal.getInt( ) ); + break; + case NS_ooxml::LN_CT_Wrap_side: + m_nSide = sal_Int32( rVal.getInt( ) ); + break; + default:; + } +} + +void WrapHandler::lcl_sprm( Sprm& ) +{ +} + +text::WrapTextMode WrapHandler::getWrapMode( ) const +{ + // The wrap values do not map directly to our wrap mode, + // e.g. none in .docx actually means through in LO. + text::WrapTextMode nMode = text::WrapTextMode_THROUGH; + + switch ( m_nType ) + { + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapType_square: + // through and tight are somewhat complicated, approximate + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapType_tight: + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapType_through: + { + switch ( m_nSide ) + { + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapSide_left: + nMode = text::WrapTextMode_LEFT; + break; + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapSide_right: + nMode = text::WrapTextMode_RIGHT; + break; + default: + nMode = text::WrapTextMode_PARALLEL; + } + } + break; + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapType_topAndBottom: + nMode = text::WrapTextMode_NONE; + break; + case NS_ooxml::LN_Value_vml_wordprocessingDrawing_ST_WrapType_none: + default: + nMode = text::WrapTextMode_THROUGH; + } + + return nMode; +} + + +void GraphicZOrderHelper::addItem(uno::Reference<beans::XPropertySet> const& props, sal_Int32 const relativeHeight) +{ + items[ relativeHeight ] = props; +} + +// The relativeHeight value in .docx is an arbitrary number, where only the relative ordering matters. +// But in Writer, the z-order is index in 0..(numitems-1) range, so whenever a new item needs to be +// added in the proper z-order, it is necessary to find the proper index. +sal_Int32 GraphicZOrderHelper::findZOrder( sal_Int32 relativeHeight, bool bOldStyle ) +{ + // std::map is iterated sorted by key + auto it = std::find_if(items.cbegin(), items.cend(), + [relativeHeight, bOldStyle](const Items::value_type& rItem) { + // Old-style ordering differs in what should happen when there is already an item with the same z-order: + // we belong under it in case of new-style, but we belong above it in case of old-style. + return bOldStyle ? (rItem.first > relativeHeight) : (rItem.first >= relativeHeight); + } + ); + sal_Int32 itemZOrderOffset(0); // before the item + if( it == items.end()) // we're topmost + { + if( items.empty()) + return 0; + --it; + itemZOrderOffset = 1; // after the topmost + + // Check if this shape has a textbox. If so, the textbox will have its own ZOrder, so + // suggest a larger offset. + bool bTextBox = false; + uno::Reference<beans::XPropertySet> xShape = it->second; + uno::Reference<beans::XPropertySetInfo> xInfo = xShape->getPropertySetInfo(); + if (xInfo->hasPropertyByName("TextBox")) + { + xShape->getPropertyValue("TextBox") >>= bTextBox; + } + if (bTextBox) + { + ++itemZOrderOffset; + } + } + // SwXFrame::getPropertyValue throws uno::RuntimeException + // when its GetFrameFormat() returns nullptr + try { + sal_Int32 itemZOrder(0); + if( it->second->getPropertyValue(getPropertyName( PROP_Z_ORDER )) >>= itemZOrder ) + return itemZOrder + itemZOrderOffset; + } + catch (const uno::RuntimeException&) { + TOOLS_WARN_EXCEPTION("writerfilter", "Exception when getting item z-order"); + } + SAL_WARN( "writerfilter", "findZOrder() didn't find item z-order" ); + return 0; // this should not(?) happen +} + +GraphicNamingHelper::GraphicNamingHelper() + : m_nCounter(0) +{ +} + +OUString GraphicNamingHelper::NameGraphic(const OUString& rTemplate) +{ + OUString aRet = rTemplate; + + if (aRet.isEmpty()) + { + // Empty template: then auto-generate a unique name. + OUString aPrefix(SvxResId(STR_ObjNameSingulGRAF)); + aRet += aPrefix + OUString::number(++m_nCounter); + } + + return aRet; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/GraphicHelpers.hxx b/writerfilter/source/dmapper/GraphicHelpers.hxx new file mode 100644 index 000000000..4d9c46394 --- /dev/null +++ b/writerfilter/source/dmapper/GraphicHelpers.hxx @@ -0,0 +1,79 @@ +/* -*- 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 . + */ + +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/text/WrapTextMode.hpp> + +#include <utility> + +namespace writerfilter::dmapper +{ +class PositionHandler : public LoggedProperties +{ +public: + PositionHandler(std::pair<OUString, OUString>& rPositionOffsets, + std::pair<OUString, OUString>& rAligns); + virtual ~PositionHandler() override; + sal_Int16 orientation() const; + sal_Int16 relation() const { return m_nRelation; } + sal_Int32 position() const { return m_nPosition; } + bool GetPageToggle() const { return m_bPageToggle; } + +private: + virtual void lcl_attribute(Id aName, Value& rVal) override; + virtual void lcl_sprm(Sprm& rSprm) override; + sal_Int16 m_nOrient; + sal_Int16 m_nRelation; + sal_Int32 m_nPosition; + std::pair<OUString, OUString>& m_rPositionOffsets; + std::pair<OUString, OUString>& m_rAligns; + bool m_bPageToggle = false; +}; + +class WrapHandler : public LoggedProperties +{ +public: + WrapHandler(); + virtual ~WrapHandler() override; + + css::text::WrapTextMode getWrapMode() const; + +private: + virtual void lcl_attribute(Id aName, Value& rVal) override; + virtual void lcl_sprm(Sprm& rSprm) override; + + sal_Int32 m_nType; + sal_Int32 m_nSide; +}; + +/// Keeps track of the next available unique automatic name. +class GraphicNamingHelper +{ + int m_nCounter; + +public: + GraphicNamingHelper(); + /// Name a graphic based on rTemplate. + OUString NameGraphic(const OUString& rTemplate); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/GraphicImport.cxx b/writerfilter/source/dmapper/GraphicImport.cxx new file mode 100644 index 000000000..88a6be9ac --- /dev/null +++ b/writerfilter/source/dmapper/GraphicImport.cxx @@ -0,0 +1,2029 @@ +/* -*- 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 <string.h> + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/drawing/ColorMode.hpp> +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> + +#include <svx/svditer.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdtrans.hxx> +#include <svx/unoapi.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/string.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/sequence.hxx> + +#include <oox/drawingml/drawingmltypes.hxx> + +#include "DomainMapper.hxx" +#include <dmapper/GraphicZOrderHelper.hxx> +#include <ooxml/resourceids.hxx> + +#include "ConversionHelper.hxx" +#include "GraphicHelpers.hxx" +#include "GraphicImport.hxx" +#include "PropertyMap.hxx" +#include "TagLogger.hxx" +#include "WrapPolygonHandler.hxx" +#include "util.hxx" + +#include <comphelper/propertysequence.hxx> +#include <algorithm> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <o3tl/unit_conversion.hxx> +#include <oox/export/drawingml.hxx> + +using namespace css; + +namespace +{ +bool isTopGroupObj(const uno::Reference<drawing::XShape>& xShape) +{ + SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape); + if (!pObject) + return false; + + if (pObject->getParentSdrObjectFromSdrObject()) + return false; + + return pObject->IsGroupObject(); +} +} + +namespace writerfilter::dmapper +{ + +namespace { + +class XInputStreamHelper : public cppu::WeakImplHelper<io::XInputStream> +{ + const sal_uInt8* m_pBuffer; + const sal_Int32 m_nLength; + sal_Int32 m_nPosition; +public: + XInputStreamHelper(const sal_uInt8* buf, size_t len); + + virtual ::sal_Int32 SAL_CALL readBytes( uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nBytesToRead ) override; + virtual ::sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( ::sal_Int32 nBytesToSkip ) override; + virtual ::sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; +}; + +} + +XInputStreamHelper::XInputStreamHelper(const sal_uInt8* buf, size_t len) : + m_pBuffer( buf ), + m_nLength( len ), + m_nPosition( 0 ) +{ +} + +sal_Int32 XInputStreamHelper::readBytes( uno::Sequence<sal_Int8>& aData, sal_Int32 nBytesToRead ) +{ + return readSomeBytes( aData, nBytesToRead ); +} + +sal_Int32 XInputStreamHelper::readSomeBytes( uno::Sequence<sal_Int8>& aData, sal_Int32 nMaxBytesToRead ) +{ + sal_Int32 nRet = 0; + if( nMaxBytesToRead > 0 ) + { + if( nMaxBytesToRead > m_nLength - m_nPosition ) + nRet = m_nLength - m_nPosition; + else + nRet = nMaxBytesToRead; + aData.realloc( nRet ); + sal_Int8* pData = aData.getArray(); + if( nRet ) + { + memcpy( pData, m_pBuffer + m_nPosition, nRet ); + m_nPosition += nRet; + } + } + return nRet; +} + + +void XInputStreamHelper::skipBytes( sal_Int32 nBytesToSkip ) +{ + if( nBytesToSkip < 0 || m_nPosition + nBytesToSkip > m_nLength) + throw io::BufferSizeExceededException(); + m_nPosition += nBytesToSkip; +} + + +sal_Int32 XInputStreamHelper::available( ) +{ + return m_nLength - m_nPosition; +} + + +void XInputStreamHelper::closeInput( ) +{ +} + +namespace { + +struct GraphicBorderLine +{ + sal_Int32 nLineWidth; + bool bHasShadow; + + GraphicBorderLine() : + nLineWidth(0) + ,bHasShadow(false) + {} + + bool isEmpty() const + { + return nLineWidth == 0 && !bHasShadow; + } + +}; + +} + +class GraphicImport_Impl +{ +private: + sal_Int32 nXSize; + bool bXSizeValid; + sal_Int32 nYSize; + bool bYSizeValid; + +public: + GraphicImportType eGraphicImportType; + DomainMapper& rDomainMapper; + + sal_Int32 nLeftPosition; + sal_Int32 nTopPosition; + + bool bUseSimplePos; + sal_Int32 zOrder; + + sal_Int16 nHoriOrient; + sal_Int16 nHoriRelation; + bool bPageToggle = false; + sal_Int16 nVertOrient; + sal_Int16 nVertRelation; + text::WrapTextMode nWrap; + bool bLayoutInCell; + bool bCompatForcedLayoutInCell; + bool bAllowOverlap = true; + bool bOpaque; + bool bBehindDoc; + bool bContour; + bool bContourOutside; + WrapPolygon::Pointer_t mpWrapPolygon; + + sal_Int32 nLeftMargin; + sal_Int32 nLeftMarginOrig = 0; + sal_Int32 nRightMargin; + sal_Int32 nTopMargin; + sal_Int32 nBottomMargin; + + bool bShadow; + sal_Int32 nShadowXDistance; + sal_Int32 nShadowYDistance; + sal_Int32 nShadowColor; + sal_Int32 nShadowTransparence; + + sal_Int32 nContrast; + sal_Int32 nBrightness; + + static constexpr sal_Int32 nFillColor = 0xffffffff; + + drawing::ColorMode eColorMode; + + GraphicBorderLine aBorders[4]; + + bool bIsGraphic; + + bool bSizeProtected; + bool bPositionProtected; + bool bHidden; + + sal_Int32 nShapeOptionType; + + OUString sName; + OUString sAlternativeText; + OUString title; + OUString sHyperlinkURL; + std::pair<OUString, OUString>& m_rPositionOffsets; + std::pair<OUString, OUString>& m_rAligns; + std::queue<OUString>& m_rPositivePercentages; + OUString sAnchorId; + comphelper::SequenceAsHashMap m_aInteropGrabBag; + std::optional<sal_Int32> m_oEffectExtentLeft; + std::optional<sal_Int32> m_oEffectExtentTop; + std::optional<sal_Int32> m_oEffectExtentRight; + std::optional<sal_Int32> m_oEffectExtentBottom; + + GraphicImport_Impl(GraphicImportType eImportType, DomainMapper& rDMapper, std::pair<OUString, OUString>& rPositionOffsets, std::pair<OUString, OUString>& rAligns, std::queue<OUString>& rPositivePercentages) : + nXSize(0) + ,bXSizeValid(false) + ,nYSize(0) + ,bYSizeValid(false) + ,eGraphicImportType( eImportType ) + ,rDomainMapper( rDMapper ) + ,nLeftPosition(0) + ,nTopPosition(0) + ,bUseSimplePos(false) + ,zOrder(-1) + ,nHoriOrient( text::HoriOrientation::NONE ) + ,nHoriRelation( text::RelOrientation::FRAME ) + ,nVertOrient( text::VertOrientation::NONE ) + ,nVertRelation( text::RelOrientation::FRAME ) + ,nWrap(text::WrapTextMode_NONE) + ,bLayoutInCell(true) + ,bCompatForcedLayoutInCell(false) + ,bOpaque( !rDMapper.IsInHeaderFooter() ) + ,bBehindDoc(false) + ,bContour(false) + ,bContourOutside(true) + ,nLeftMargin(319) + ,nRightMargin(319) + ,nTopMargin(0) + ,nBottomMargin(0) + ,bShadow(false) + ,nShadowXDistance(0) + ,nShadowYDistance(0) + ,nShadowColor(0) + ,nShadowTransparence(0) + ,nContrast(0) + ,nBrightness(0) + ,eColorMode( drawing::ColorMode_STANDARD ) + ,bIsGraphic(false) + ,bSizeProtected(false) + ,bPositionProtected(false) + ,bHidden(false) + ,nShapeOptionType(0) + ,m_rPositionOffsets(rPositionOffsets) + ,m_rAligns(rAligns) + ,m_rPositivePercentages(rPositivePercentages) + { + if (eGraphicImportType == GraphicImportType::IMPORT_AS_DETECTED_INLINE + && !rDMapper.IsInShape()) + { + zOrder = 0; + } + } + + void setXSize(sal_Int32 _nXSize) + { + nXSize = _nXSize; + bXSizeValid = true; + } + + sal_uInt32 getXSize() const + { + return nXSize; + } + + bool isXSizeValid() const + { + return bXSizeValid; + } + + void setYSize(sal_Int32 _nYSize) + { + nYSize = _nYSize; + bYSizeValid = true; + } + + sal_uInt32 getYSize() const + { + return nYSize; + } + + bool isYSizeValid() const + { + return bYSizeValid; + } + + void applyMargins(const uno::Reference< beans::XPropertySet >& xGraphicObjectProperties) const + { + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_LEFT_MARGIN ), uno::Any(nLeftMargin)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_RIGHT_MARGIN ), uno::Any(nRightMargin)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_TOP_MARGIN ), uno::Any(nTopMargin)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_BOTTOM_MARGIN ), uno::Any(nBottomMargin)); + } + + void applyPosition(const uno::Reference< beans::XPropertySet >& xGraphicObjectProperties) const + { + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_HORI_ORIENT ), + uno::Any(nHoriOrient)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_VERT_ORIENT ), + uno::Any(nVertOrient)); + } + + void applyRelativePosition(const uno::Reference< beans::XPropertySet >& xGraphicObjectProperties, bool bRelativeOnly = false) const + { + if (!bRelativeOnly) + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_HORI_ORIENT_POSITION), + uno::Any(nLeftPosition)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_HORI_ORIENT_RELATION ), + uno::Any(nHoriRelation)); + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_PAGE_TOGGLE), + uno::Any(bPageToggle)); + if (!bRelativeOnly) + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_VERT_ORIENT_POSITION), + uno::Any(nTopPosition)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_VERT_ORIENT_RELATION ), + uno::Any(nVertRelation)); + } + + void applyZOrder(uno::Reference<beans::XPropertySet> const & xGraphicObjectProperties) const + { + if (zOrder >= 0) + { + // tdf#120760 Send objects with behinddoc=true to the back. + sal_Int32 nZOrder = zOrder; + if (bBehindDoc && rDomainMapper.IsInHeaderFooter()) + nZOrder -= SAL_MAX_INT32; + GraphicZOrderHelper* pZOrderHelper = rDomainMapper.graphicZOrderHelper(); + bool bOldStyle = eGraphicImportType == GraphicImportType::IMPORT_AS_DETECTED_INLINE; + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_Z_ORDER), + uno::Any(pZOrderHelper->findZOrder(nZOrder, bOldStyle))); + pZOrderHelper->addItem(xGraphicObjectProperties, nZOrder); + } + } + + void applyName(uno::Reference<beans::XPropertySet> const & xGraphicObjectProperties) const + { + try + { + if (!sName.isEmpty()) + { + uno::Reference<container::XNamed> const xNamed(xGraphicObjectProperties, uno::UNO_QUERY_THROW); + xNamed->setName(sName); + } + // else: name is automatically generated by SwDoc::MakeFlySection_() + + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_DESCRIPTION ), + uno::Any( sAlternativeText )); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_TITLE ), + uno::Any( title )); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter", "failed"); + } + } + + void applyHyperlink(uno::Reference<beans::XPropertySet> const & xShapeProps, bool bIsShape) + { + // Graphic objects have a different hyperlink prop than shapes + auto aHyperlinkProp = bIsShape ? PROP_HYPERLINK : PROP_HYPER_LINK_U_R_L; + if (!sHyperlinkURL.isEmpty()) + { + xShapeProps->setPropertyValue( + getPropertyName(aHyperlinkProp), uno::Any(sHyperlinkURL)); + } + } + + /// Getter for m_aInteropGrabBag, but also merges in the values from other members if they are set. + comphelper::SequenceAsHashMap const & getInteropGrabBag() + { + comphelper::SequenceAsHashMap aEffectExtent; + if (m_oEffectExtentLeft) + aEffectExtent["l"] <<= *m_oEffectExtentLeft; + if (m_oEffectExtentTop) + aEffectExtent["t"] <<= *m_oEffectExtentTop; + if (m_oEffectExtentRight) + aEffectExtent["r"] <<= *m_oEffectExtentRight; + if (m_oEffectExtentBottom) + aEffectExtent["b"] <<= *m_oEffectExtentBottom; + if (!aEffectExtent.empty()) + m_aInteropGrabBag["CT_EffectExtent"] <<= aEffectExtent.getAsConstPropertyValueList(); + return m_aInteropGrabBag; + } +}; + +GraphicImport::GraphicImport(uno::Reference<uno::XComponentContext> const& xComponentContext, + uno::Reference<lang::XMultiServiceFactory> const& xTextFactory, + DomainMapper& rDMapper, + GraphicImportType eImportType, + std::pair<OUString, OUString>& rPositionOffsets, + std::pair<OUString, OUString>& rAligns, + std::queue<OUString>& rPositivePercentages) +: LoggedProperties("GraphicImport") +, LoggedTable("GraphicImport") +, LoggedStream("GraphicImport") +, m_pImpl(new GraphicImport_Impl(eImportType, rDMapper, rPositionOffsets, rAligns, rPositivePercentages)) +, m_xComponentContext(xComponentContext) +, m_xTextFactory(xTextFactory) +{ +} + +GraphicImport::~GraphicImport() +{ +} + +com::sun::star::awt::Point GraphicImport::GetGraphicObjectPosition() const +{ + return (com::sun::star::awt::Point(m_pImpl->nLeftPosition, m_pImpl->nTopPosition)); +} + +bool GraphicImport::GetLayoutInCell() const +{ + return m_pImpl->bLayoutInCell; +} + +void GraphicImport::handleWrapTextValue(sal_uInt32 nVal) +{ + switch (nVal) + { + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_WrapText_bothSides: // 90920; + m_pImpl->nWrap = text::WrapTextMode_PARALLEL; + break; + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_WrapText_left: // 90921; + m_pImpl->nWrap = text::WrapTextMode_LEFT; + break; + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_WrapText_right: // 90922; + m_pImpl->nWrap = text::WrapTextMode_RIGHT; + break; + case NS_ooxml::LN_Value_wordprocessingDrawing_ST_WrapText_largest: // 90923; + m_pImpl->nWrap = text::WrapTextMode_DYNAMIC; + break; + default:; + } +} + +void GraphicImport::putPropertyToFrameGrabBag( const OUString& sPropertyName, const uno::Any& aPropertyValue ) +{ + beans::PropertyValue aProperty; + aProperty.Name = sPropertyName; + aProperty.Value = aPropertyValue; + + if (!m_xShape.is()) + return; + + uno::Reference< beans::XPropertySet > xSet(m_xShape, uno::UNO_QUERY_THROW); + + uno::Reference< beans::XPropertySetInfo > xSetInfo(xSet->getPropertySetInfo()); + if (!xSetInfo.is()) + return; + + OUString aGrabBagPropName; + uno::Reference<lang::XServiceInfo> xServiceInfo(m_xShape, uno::UNO_QUERY_THROW); + if (xServiceInfo->supportsService("com.sun.star.text.TextFrame")) + aGrabBagPropName = "FrameInteropGrabBag"; + else + aGrabBagPropName = "InteropGrabBag"; + + if (xSetInfo->hasPropertyByName(aGrabBagPropName)) + { + //Add pProperty to the end of the Sequence for aGrabBagPropName + uno::Sequence<beans::PropertyValue> aTmp; + xSet->getPropertyValue(aGrabBagPropName) >>= aTmp; + std::vector<beans::PropertyValue> aGrabBag(comphelper::sequenceToContainer<std::vector<beans::PropertyValue> >(aTmp)); + aGrabBag.push_back(aProperty); + + xSet->setPropertyValue(aGrabBagPropName, uno::Any(comphelper::containerToSequence(aGrabBag))); + } +} + +static bool lcl_bHasGroupSlantedChild(const SdrObject* pObj) +{ + // Returns true, if a child object differs more than 0.02deg from horizontal or vertical. + // Because lines sometimes are imported as customshapes, a horizontal or vertical line + // might not have exactly 0, 90, 180, or 270 degree as rotate angle. + if (!pObj) + return false; + if (!pObj->IsGroupObject()) + return false; + SdrObjList* pSubList = pObj->GetSubList(); + if (!pSubList) + return false; + SdrObjListIter aIterator(pSubList, SdrIterMode::DeepNoGroups); + while (aIterator.IsMore()) + { + const SdrObject* pSubObj = aIterator.Next(); + const Degree100 nRotateAngle = NormAngle36000(pSubObj->GetRotateAngle()); + const sal_uInt16 nRot = nRotateAngle.get(); + if ((3 < nRot && nRot < 8997) || (9003 < nRot && nRot < 17997) + || (18003 < nRot && nRot < 26997) || (27003 < nRot && nRot < 35997)) + return true; + } + return false; +} + +void GraphicImport::lcl_correctWord2007EffectExtent(const sal_Int32 nMSOAngle) +{ + // Word versions older than 14 do not swap width and height (see lcl_doMSOWidthHeightSwap) + // and therefore generate different effectExtent. We correct them here. + sal_Int16 nAngleDeg = (nMSOAngle / 60000) % 180; + if (nAngleDeg < 45 || nAngleDeg >= 135) + return; + + sal_Int32 nDiff = o3tl::convert( + (double(m_pImpl->getXSize()) - double(m_pImpl->getYSize())) / 2.0, + o3tl::Length::mm100, o3tl::Length::emu); + if (m_pImpl->m_oEffectExtentLeft) + *m_pImpl->m_oEffectExtentLeft += nDiff; + if (m_pImpl->m_oEffectExtentRight) + *m_pImpl->m_oEffectExtentRight += nDiff; + if (m_pImpl->m_oEffectExtentTop) + *m_pImpl->m_oEffectExtentTop -= nDiff; + if (m_pImpl->m_oEffectExtentBottom) + *m_pImpl->m_oEffectExtentBottom -= nDiff; +} + +static void lcl_doMSOWidthHeightSwap(awt::Point& rLeftTop, awt::Size& rSize, + const sal_Int32 nMSOAngle) +{ + if (nMSOAngle == 0) + return; + // convert nMSOAngle to degree in [0°,180°[ + sal_Int16 nAngleDeg = (nMSOAngle / 60000) % 180; + if (nAngleDeg >= 45 && nAngleDeg < 135) + { + // keep center of rectangle given in rLeftTop and rSize + sal_Int32 aTemp = rSize.Width - rSize.Height; + rLeftTop.X += aTemp / 2; + rLeftTop.Y -= aTemp / 2; + std::swap(rSize.Width, rSize.Height); + } + return; +} + +void GraphicImport::lcl_expandRectangleByEffectExtent(awt::Point& rLeftTop, awt::Size& rSize) +{ + sal_Int32 nEffectExtent = (m_pImpl->m_oEffectExtentLeft) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentLeft) + : 0; + rLeftTop.X -= nEffectExtent; + rSize.Width += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentRight) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentRight) + : 0; + rSize.Width += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentTop) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentTop) + : 0; + rLeftTop.Y -= nEffectExtent; + rSize.Height += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentBottom) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentBottom) + : 0; + rSize.Height += nEffectExtent; +} + +void GraphicImport::lcl_attribute(Id nName, Value& rValue) +{ + sal_Int32 nIntValue = rValue.getInt(); + switch( nName ) + { + case NS_ooxml::LN_CT_Hyperlink_URL://90682; + m_pImpl->sHyperlinkURL = rValue.getString(); + break; + case NS_ooxml::LN_blip: //the binary graphic data in a shape + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rValue.getProperties(); + if( pProperties ) + { + pProperties->resolve(*this); + } + } + break; + case NS_ooxml::LN_payload : + { + writerfilter::Reference<BinaryObj>::Pointer_t pPictureData = rValue.getBinary(); + if( pPictureData ) + pPictureData->resolve(*this); + } + break; + + //border properties + case NS_ooxml::LN_CT_Border_sz: + m_pImpl->aBorders[BORDER_TOP].nLineWidth = nIntValue; + break; + case NS_ooxml::LN_CT_Border_val: + //graphic borders don't support different line types + break; + case NS_ooxml::LN_CT_Border_space: + break; + case NS_ooxml::LN_CT_Border_shadow: + m_pImpl->aBorders[BORDER_TOP].bHasShadow = nIntValue != 0; + break; + case NS_ooxml::LN_CT_Border_frame: + break; + case NS_ooxml::LN_CT_PositiveSize2D_cx: + case NS_ooxml::LN_CT_PositiveSize2D_cy: + { + sal_Int32 nDim = oox::drawingml::convertEmuToHmm(nIntValue); + // drawingML equivalent of oox::vml::ShapeType::getAbsRectangle(): + // make sure a shape isn't hidden implicitly just because it has + // zero height or width. + if (nDim == 0) + nDim = 1; + + if( nName == NS_ooxml::LN_CT_PositiveSize2D_cx ) + m_pImpl->setXSize(nDim); + else + m_pImpl->setYSize(nDim); + } + break; + case NS_ooxml::LN_CT_EffectExtent_l: + m_pImpl->m_oEffectExtentLeft = nIntValue; + break; + case NS_ooxml::LN_CT_EffectExtent_t: + m_pImpl->m_oEffectExtentTop = nIntValue; + break; + case NS_ooxml::LN_CT_EffectExtent_r: + m_pImpl->m_oEffectExtentRight = nIntValue; + break; + case NS_ooxml::LN_CT_EffectExtent_b: + m_pImpl->m_oEffectExtentBottom = nIntValue; + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_id:// 90650; + //id of the object - ignored + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_name:// 90651; + //name of the object + m_pImpl->sName = rValue.getString(); + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_descr:// 90652; + //alternative text + m_pImpl->sAlternativeText = rValue.getString(); + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_title: + //alternative text + m_pImpl->title = rValue.getString(); + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_hidden: + m_pImpl->bHidden = (nIntValue == 1); + break; + case NS_ooxml::LN_CT_GraphicalObjectFrameLocking_noChangeAspect://90644; + //disallow aspect ratio change - ignored + break; + case NS_ooxml::LN_CT_GraphicalObjectFrameLocking_noMove:// 90645; + m_pImpl->bPositionProtected = true; + break; + case NS_ooxml::LN_CT_GraphicalObjectFrameLocking_noResize: // 90646; + m_pImpl->bSizeProtected = true; + break; + case NS_ooxml::LN_CT_Anchor_distT: // 90983; + case NS_ooxml::LN_CT_Anchor_distB: // 90984; + case NS_ooxml::LN_CT_Anchor_distL: // 90985; + case NS_ooxml::LN_CT_Anchor_distR: // 90986; + { + m_pImpl->nShapeOptionType = nName; + ProcessShapeOptions(rValue); + } + break; + case NS_ooxml::LN_CT_Anchor_simplePos_attr: // 90987; + m_pImpl->bUseSimplePos = nIntValue > 0; + break; + case NS_ooxml::LN_CT_Anchor_relativeHeight: // 90988; + m_pImpl->zOrder = nIntValue; + break; + case NS_ooxml::LN_CT_Anchor_behindDoc: // 90989; - in background + if (nIntValue > 0) + { + m_pImpl->bOpaque = false; + m_pImpl->bBehindDoc = true; + } + break; + case NS_ooxml::LN_CT_Anchor_locked: // 90990; - ignored + break; + case NS_ooxml::LN_CT_Anchor_layoutInCell: // 90991; - ignored + // Starting in MSO 2013, anchors are ALWAYS considered to be laid out in table cell. + m_pImpl->bCompatForcedLayoutInCell = !nIntValue + && m_pImpl->rDomainMapper.GetSettingsTable()->GetWordCompatibilityMode() > 14 + && m_pImpl->rDomainMapper.IsInTable(); + m_pImpl->bLayoutInCell = m_pImpl->bCompatForcedLayoutInCell || nIntValue; + break; + case NS_ooxml::LN_CT_Anchor_hidden: // 90992; - ignored + break; + case NS_ooxml::LN_CT_Anchor_allowOverlap: + m_pImpl->bAllowOverlap = nIntValue != 0; + break; + case NS_ooxml::LN_CT_Anchor_wp14_anchorId: + case NS_ooxml::LN_CT_Inline_wp14_anchorId: + { + OUStringBuffer aBuffer = OUString::number(nIntValue, 16); + OUStringBuffer aString; + comphelper::string::padToLength(aString, 8 - aBuffer.getLength(), '0'); + aString.append(aBuffer.getStr()); + m_pImpl->sAnchorId = aString.makeStringAndClear().toAsciiUpperCase(); + } + break; + case NS_ooxml::LN_CT_Point2D_x: // 90405; + m_pImpl->nLeftPosition = ConversionHelper::convertTwipToMM100(nIntValue); + m_pImpl->nHoriRelation = text::RelOrientation::PAGE_FRAME; + m_pImpl->nHoriOrient = text::HoriOrientation::NONE; + break; + case NS_ooxml::LN_CT_Point2D_y: // 90406; + m_pImpl->nTopPosition = ConversionHelper::convertTwipToMM100(nIntValue); + m_pImpl->nVertRelation = text::RelOrientation::PAGE_FRAME; + m_pImpl->nVertOrient = text::VertOrientation::NONE; + break; + case NS_ooxml::LN_CT_WrapTight_wrapText: // 90934; + m_pImpl->bContour = true; + m_pImpl->bContourOutside = true; + + handleWrapTextValue(rValue.getInt()); + + break; + case NS_ooxml::LN_CT_WrapThrough_wrapText: + m_pImpl->bContour = true; + m_pImpl->bContourOutside = false; + + handleWrapTextValue(rValue.getInt()); + + break; + case NS_ooxml::LN_CT_WrapSquare_wrapText: //90928; + handleWrapTextValue(rValue.getInt()); + break; + case NS_ooxml::LN_shape: + { + uno::Reference< drawing::XShape> xShape; + rValue.getAny( ) >>= xShape; + if ( xShape.is( ) ) + { + // Is it a graphic image + bool bUseShape = true; + try + { + uno::Reference< beans::XPropertySet > xShapeProps + ( xShape, uno::UNO_QUERY_THROW ); + + uno::Reference<graphic::XGraphic> xGraphic; + xShapeProps->getPropertyValue("Graphic") >>= xGraphic; + + sal_Int32 nRotation = 0; + xShapeProps->getPropertyValue("RotateAngle") >>= nRotation; + + css::beans::PropertyValues aGrabBag; + xShapeProps->getPropertyValue("InteropGrabBag") >>= aGrabBag; + // if the shape contains effects in the grab bag, we should not transform it + // in a XTextContent so those effects can be preserved + bool bContainsEffects = std::any_of(std::cbegin(aGrabBag), std::cend(aGrabBag), [](const auto& rProp) { + return rProp.Name == "EffectProperties" + || rProp.Name == "3DEffectProperties" + || rProp.Name == "ArtisticEffectProperties"; + }); + + xShapeProps->getPropertyValue("Shadow") >>= m_pImpl->bShadow; + if (m_pImpl->bShadow) + { + xShapeProps->getPropertyValue("ShadowXDistance") >>= m_pImpl->nShadowXDistance; + xShapeProps->getPropertyValue("ShadowYDistance") >>= m_pImpl->nShadowYDistance; + xShapeProps->getPropertyValue("ShadowColor") >>= m_pImpl->nShadowColor; + xShapeProps->getPropertyValue("ShadowTransparence") >>= m_pImpl->nShadowTransparence; + } + + xShapeProps->getPropertyValue("GraphicColorMode") >>= m_pImpl->eColorMode; + xShapeProps->getPropertyValue("AdjustLuminance") >>= m_pImpl->nBrightness; + xShapeProps->getPropertyValue("AdjustContrast") >>= m_pImpl->nContrast; + + // fdo#70457: transform XShape into a SwXTextGraphicObject only if there's no rotation + if ( nRotation == 0 && !bContainsEffects ) + m_xGraphicObject = createGraphicObject( xGraphic, xShapeProps ); + + bUseShape = !m_xGraphicObject.is( ); + + if ( !bUseShape ) + { + // Define the object size + uno::Reference< beans::XPropertySet > xGraphProps( m_xGraphicObject, + uno::UNO_QUERY ); + awt::Size aSize = xShape->getSize( ); + xGraphProps->setPropertyValue("Height", + uno::Any( aSize.Height ) ); + xGraphProps->setPropertyValue("Width", + uno::Any( aSize.Width ) ); + + text::GraphicCrop aGraphicCrop( 0, 0, 0, 0 ); + uno::Reference< beans::XPropertySet > xSourceGraphProps( xShape, uno::UNO_QUERY ); + uno::Any aAny = xSourceGraphProps->getPropertyValue("GraphicCrop"); + if(aAny >>= aGraphicCrop) { + xGraphProps->setPropertyValue("GraphicCrop", + uno::Any( aGraphicCrop ) ); + } + + // We need to drop the shape here somehow + uno::Reference< lang::XComponent > xShapeComponent( xShape, uno::UNO_QUERY ); + xShapeComponent->dispose( ); + } + } + catch( const beans::UnknownPropertyException & ) + { + // It isn't a graphic image + } + + if ( bUseShape ) + m_xShape = xShape; + + if ( m_xShape.is( ) ) + { + uno::Reference< beans::XPropertySet > xShapeProps + (m_xShape, uno::UNO_QUERY_THROW); + + + xShapeProps->setPropertyValue + (getPropertyName(PROP_ANCHOR_TYPE), + uno::Any + (text::TextContentAnchorType_AS_CHARACTER)); + + // In Word, if a shape is anchored inline, that + // excludes being in the background. + xShapeProps->setPropertyValue("Opaque", uno::Any(true)); + + uno::Reference<lang::XServiceInfo> xServiceInfo(m_xShape, uno::UNO_QUERY_THROW); + + // TextFrames can't be rotated. But for anything else, + // make sure that setting size doesn't affect rotation, + // that would not match Word's definition of rotation. + bool bKeepRotation = false; + if (!xServiceInfo->supportsService("com.sun.star.text.TextFrame")) + { + bKeepRotation = true; + xShapeProps->setPropertyValue + (getPropertyName(PROP_TEXT_RANGE), + uno::Any + (m_pImpl->rDomainMapper.GetCurrentTextRange())); + } + + awt::Size aSize(m_xShape->getSize()); + + // One purpose of the next part is, to set the logic rectangle of the SdrObject + // to nXSize and nYSize from import. That doesn't work for groups or lines, + // because they do not have a logic rectangle and m_xShape->getSize and + // m_xShape->setSize would work on the snap rectangle. In case a shape is + // rotated, non-uniform scaling the snap rectangle will introduce shearing on + // the shape. In case group or line is rotated, nXSize and nYSize contain the + // unrotated size from oox. The rotation is already incorporated into group + // children and line points. We must not scale them to unrotated size. Exclude + // those shapes here. + + // Get MSO rotation angle. GetRotateAngle from SdrObject is not suitable + // here, because it returns the rotate angle of the first child for groups + // and slope angle for lines, even if line or group had not been rotated. + // Import in oox has put the rotation from oox file into InteropGrabBag. + comphelper::SequenceAsHashMap aInteropGrabBag(xShapeProps->getPropertyValue("InteropGrabBag")); + sal_Int32 nOOXAngle(0); + aInteropGrabBag.getValue("mso-rotation-angle") >>= nOOXAngle; // 1/60000 deg + // tdf#143455: A diagram is imported as group, but has no valid object list + // and contour wrap is different to Word. As workaround diagrams are excluded + // here in various places. + const SdrObject* pDiagramCandidate(SdrObject::getSdrObjectFromXShape(m_xShape)); + const bool bIsDiagram(nullptr != pDiagramCandidate && pDiagramCandidate->isDiagram()); + // tdf#143476: A lockedCanvas (Word2007) is imported as group, but has not + // got size and position. Values from m_Impl has to be used. + bool bIsLockedCanvas(false); + aInteropGrabBag.getValue("LockedCanvas") >>= bIsLockedCanvas; + const bool bIsGroupOrLine = (xServiceInfo->supportsService("com.sun.star.drawing.GroupShape") + && !bIsDiagram && !bIsLockedCanvas) + || xServiceInfo->supportsService("com.sun.star.drawing.LineShape"); + SdrObject* pShape = SdrObject::getSdrObjectFromXShape(m_xShape); + if ((bIsGroupOrLine && !lcl_bHasGroupSlantedChild(pShape) && nOOXAngle == 0) + || !bIsGroupOrLine) + { + if (m_pImpl->isXSizeValid()) + aSize.Width = m_pImpl->getXSize(); + if (m_pImpl->isYSizeValid()) + aSize.Height = m_pImpl->getYSize(); + } + + Degree100 nRotation; + if (bKeepRotation) + { + // Use internal API, getPropertyValue("RotateAngle") + // would use GetObjectRotation(), which is not what + // we want. + if (pShape) + nRotation = pShape->GetRotateAngle(); + } + m_xShape->setSize(aSize); + if (bKeepRotation) + { + xShapeProps->setPropertyValue("RotateAngle", uno::Any(nRotation.get())); + } + + m_pImpl->bIsGraphic = true; + + if (!m_pImpl->sAnchorId.isEmpty()) + { + putPropertyToFrameGrabBag("AnchorId", uno::Any(m_pImpl->sAnchorId)); + } + + // Calculate mso unrotated rectangle and its center, needed below + awt::Size aImportSize(m_xShape->getSize()); // here only fallback + if (m_pImpl->isXSizeValid()) + aImportSize.Width = m_pImpl->getXSize(); // Hmm + if (m_pImpl->isYSizeValid()) + aImportSize.Height = m_pImpl->getYSize(); // Hmm + const awt::Point aImportPosition(GetGraphicObjectPosition()); // Hmm + double fCentrumX = aImportPosition.X + aImportSize.Width / 2.0; + double fCentrumY = aImportPosition.Y + aImportSize.Height / 2.0; + + // In case of group and lines, transformations are incorporated in the child + // shapes or points respectively in LO. MSO has rotation as separate property. + // The position refers to the unrotated rectangle of MSO. We need to adapt it + // to the left-top of the transformed shape. + awt::Size aLOSize(m_xShape->getSize()); // LO snap rectangle size in Hmm + if (bIsGroupOrLine && !(m_pImpl->mpWrapPolygon)) + { + // Set LO position. MSO rotation is done on shape center. + if(pShape && pShape->IsGroupObject()) + { + tools::Rectangle aSnapRect = pShape->GetSnapRect(); // Twips + m_pImpl->nLeftPosition = ConversionHelper::convertTwipToMM100(aSnapRect.Left()); + m_pImpl->nTopPosition = ConversionHelper::convertTwipToMM100(aSnapRect.Top()); + aLOSize.Width = ConversionHelper::convertTwipToMM100(aSnapRect.getWidth()); + aLOSize.Height = ConversionHelper::convertTwipToMM100(aSnapRect.getHeight()); + } + else + { + m_pImpl->nLeftPosition = fCentrumX - aLOSize.Width / 2.0; + m_pImpl->nTopPosition = fCentrumY - aLOSize.Height / 2.0; + } + m_xShape->setPosition(GetGraphicObjectPosition()); + } + // ToDo: Rotated shapes with position type "Alignment" (UI of Word) have + // wrong position. Word aligns the unrotated logic rectangle, LO the rotated + // snap rectangle. + + // Margin correction + + // tdf#143475: Word 2007 (vers 12) calculates effectExtent for rotated images + // based on the unrotated image without width-height-swap. We correct this to + // those values, which would be calculated if width-height-swap was used. + if (m_pImpl->rDomainMapper.GetSettingsTable()->GetWordCompatibilityMode() < 14 + && xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape") + && nOOXAngle != 0) + { + lcl_correctWord2007EffectExtent(nOOXAngle); + } + + if (m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_INLINE) + { + if (nOOXAngle == 0) + { + // EffectExtent contains all needed additional space, including fat + // stroke and shadow. Simple add it to the margins. + sal_Int32 nEffectExtent = (m_pImpl->m_oEffectExtentLeft) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentLeft) + : 0; + m_pImpl->nLeftMargin += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentRight) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentRight) : 0; + m_pImpl->nRightMargin += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentTop) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentTop) : 0; + m_pImpl->nTopMargin += nEffectExtent; + nEffectExtent = (m_pImpl->m_oEffectExtentBottom) + ? oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentBottom) : 0; + m_pImpl->nBottomMargin += nEffectExtent; + } + else + { + // As of June 2021 LibreOffice uses an area, which is large enough to + // contain the rotated snap rectangle. MSO uses a smaller area, so + // that the rotated snap rectangle covers text. + awt::Point aMSOBaseLeftTop = aImportPosition; + awt::Size aMSOBaseSize = aImportSize; + lcl_doMSOWidthHeightSwap(aMSOBaseLeftTop, aMSOBaseSize, nOOXAngle); + lcl_expandRectangleByEffectExtent(aMSOBaseLeftTop, aMSOBaseSize); + + // Get LO SnapRect from SdrObject if possible + awt::Rectangle aLOSnapRect; + // For case we have no SdrObject, initialize with values from m_pImpl + aLOSnapRect.X = m_pImpl->nLeftPosition; + aLOSnapRect.Y = m_pImpl->nTopPosition; + aLOSnapRect.Width = aLOSize.Width; + aLOSnapRect.Height = aLOSize.Height; + if (pShape) + { + tools::Rectangle aSnapRect = pShape->GetSnapRect(); // Twip + aLOSnapRect.X = ConversionHelper::convertTwipToMM100(aSnapRect.Left()); + aLOSnapRect.Y = ConversionHelper::convertTwipToMM100(aSnapRect.Top()); + aLOSnapRect.Width = ConversionHelper::convertTwipToMM100(aSnapRect.getWidth()); + aLOSnapRect.Height = ConversionHelper::convertTwipToMM100(aSnapRect.getHeight()); + } + + m_pImpl->nLeftMargin += aLOSnapRect.X - aMSOBaseLeftTop.X; + m_pImpl->nRightMargin += aMSOBaseLeftTop.X + aMSOBaseSize.Width + - (aLOSnapRect.X + aLOSnapRect.Width); + m_pImpl->nTopMargin += aLOSnapRect.Y - aMSOBaseLeftTop.Y; + m_pImpl->nBottomMargin += aMSOBaseLeftTop.Y + aMSOBaseSize.Height + - (aLOSnapRect.Y + aLOSnapRect.Height); + // tdf#141880 LibreOffice cannot handle negative vertical margins. + // Those cases are caught below at common place. + } + } // end IMPORT_AS_DETECTED_INLINE + else if ((m_pImpl->nWrap == text::WrapTextMode_PARALLEL + || m_pImpl->nWrap == text::WrapTextMode_DYNAMIC + || m_pImpl->nWrap == text::WrapTextMode_LEFT + || m_pImpl->nWrap == text::WrapTextMode_RIGHT + || m_pImpl->nWrap == text::WrapTextMode_NONE) + && !(m_pImpl->mpWrapPolygon) && !bIsDiagram) + { + // For wrap "Square" an area is defined around which the text wraps. MSO + // describes the area by a base rectangle and effectExtent. LO uses the + // shape bounding box and margins. We adapt the margins to get the same + // area as MSO. + awt::Point aMSOBaseLeftTop = aImportPosition; + awt::Size aMSOBaseSize = aImportSize; + lcl_doMSOWidthHeightSwap(aMSOBaseLeftTop, aMSOBaseSize, nOOXAngle); + lcl_expandRectangleByEffectExtent(aMSOBaseLeftTop, aMSOBaseSize); + + // Get LO bound rectangle from SdrObject if possible + awt::Rectangle aLOBoundRect; + // For case we have no SdrObject, initialize with values from m_pImpl + aLOBoundRect.X = m_pImpl->nLeftPosition; + aLOBoundRect.Y = m_pImpl->nTopPosition; + aLOBoundRect.Width = aLOSize.Width; + aLOBoundRect.Height = aLOSize.Height; + if (pShape) + { + tools::Rectangle aBoundRect = pShape->GetCurrentBoundRect(); // Twip + aLOBoundRect.X = ConversionHelper::convertTwipToMM100(aBoundRect.Left()); + aLOBoundRect.Y = ConversionHelper::convertTwipToMM100(aBoundRect.Top()); + aLOBoundRect.Width = ConversionHelper::convertTwipToMM100(aBoundRect.getWidth()); + aLOBoundRect.Height = ConversionHelper::convertTwipToMM100(aBoundRect.getHeight()); + } + + m_pImpl->nLeftMargin += aLOBoundRect.X - aMSOBaseLeftTop.X; + m_pImpl->nRightMargin += aMSOBaseLeftTop.X + aMSOBaseSize.Width + - (aLOBoundRect.X + aLOBoundRect.Width); + m_pImpl->nTopMargin += aLOBoundRect.Y - aMSOBaseLeftTop.Y; + m_pImpl->nBottomMargin += aMSOBaseLeftTop.Y + aMSOBaseSize.Height + - (aLOBoundRect.Y + aLOBoundRect.Height); + } + else if (m_pImpl->mpWrapPolygon && !bIsDiagram) + { + // Word uses a wrap polygon, LibreOffice has no explicit wrap polygon + // but creates the wrap contour based on the shape geometry, without + // stroke width and shadow, but with rotation and flip. The concepts + // are not compatible. We approximate Word's rendering by setting + // wrap margins. + + // Build a range from the wrap polygon from Word. + const drawing::PointSequenceSequence aWrapPolygon + = m_pImpl->mpWrapPolygon->getPointSequenceSequence(); + basegfx::B2DPolyPolygon aB2DWrapPolyPolygon + = basegfx::utils::UnoPointSequenceSequenceToB2DPolyPolygon( + aWrapPolygon); + // Wrap polygon values are relative to 0..21600|0..21600. + // Scale to shape size (in Hmm). + basegfx::B2DHomMatrix aMatrix = basegfx::utils::createScaleB2DHomMatrix( + aImportSize.Width / 21600.0, aImportSize.Height / 21600.0); + aB2DWrapPolyPolygon.transform(aMatrix); + + // Shape geometry will be rotated, rotate wrap polygon too. + if (nOOXAngle != 0) + { + aMatrix = basegfx::utils::createRotateAroundPoint( + aImportSize.Width / 2.0, aImportSize.Height / 2.0, + basegfx::deg2rad<60000>(nOOXAngle)); + aB2DWrapPolyPolygon.transform(aMatrix); + } + basegfx::B2DRange aB2DWrapRange = aB2DWrapPolyPolygon.getB2DRange(); + + // Build a range from shape geometry + basegfx::B2DRange aShapeRange; + if (pShape) + { + basegfx::B2DPolyPolygon aShapePolygon = pShape->TakeXorPoly(); // Twips + aMatrix = basegfx::utils::createScaleB2DHomMatrix( + o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100), + o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100)); + aShapePolygon.transform(aMatrix); + // Wrap polygon treats left/top of shape as origin, shift shape polygon accordingly + aMatrix = basegfx::utils::createTranslateB2DHomMatrix( + -aImportPosition.X, -aImportPosition.Y); + aShapePolygon.transform(aMatrix); + aShapeRange = aShapePolygon.getB2DRange(); + } + else // can this happen? + { + aShapeRange + = basegfx::B2DRange(0, 0, aImportSize.Width, aImportSize.Height); + if (nOOXAngle != 0) + { + aMatrix = basegfx::utils::createRotateB2DHomMatrix( + basegfx::deg2rad<60000>(nOOXAngle)); + aShapeRange.transform(aMatrix); + } + } + + // Add difference between shape and wrap range to margin and remember + // difference in Twips for export. + comphelper::SequenceAsHashMap aAnchorDistDiff; + + const double fTopDiff = aShapeRange.getMinY() - aB2DWrapRange.getMinY(); + m_pImpl->nTopMargin += basegfx::fround(fTopDiff); + aAnchorDistDiff["distTDiff"] <<= basegfx::fround( + o3tl::convert(fTopDiff, o3tl::Length::mm100, o3tl::Length::twip)); + + const double fBottomDiff = aB2DWrapRange.getMaxY() - aShapeRange.getMaxY(); + m_pImpl->nBottomMargin += basegfx::fround(fBottomDiff); + aAnchorDistDiff["distBDiff"] <<= basegfx::fround( + o3tl::convert(fBottomDiff, o3tl::Length::mm100, o3tl::Length::twip)); + + const double fLeftDiff = aShapeRange.getMinX() - aB2DWrapRange.getMinX(); + m_pImpl->nLeftMargin += basegfx::fround(fLeftDiff); + aAnchorDistDiff["distLDiff"] <<= basegfx::fround( + o3tl::convert(fLeftDiff, o3tl::Length::mm100, o3tl::Length::twip)); + + const double fRightDiff = aB2DWrapRange.getMaxX() - aShapeRange.getMaxX(); + m_pImpl->nRightMargin += basegfx::fround(fRightDiff); + aAnchorDistDiff["distRDiff"] <<= basegfx::fround( + o3tl::convert(fRightDiff, o3tl::Length::mm100, o3tl::Length::twip)); + + m_pImpl->m_aInteropGrabBag["AnchorDistDiff"] + <<= aAnchorDistDiff.getAsConstPropertyValueList(); + + // FixMe: tdf#141880. LibreOffice cannot handle negative horizontal margin in contour wrap + if (m_pImpl->nLeftMargin < 0) + m_pImpl->nLeftMargin = 0; + if (m_pImpl->nRightMargin < 0) + m_pImpl->nRightMargin = 0; + } + else if (!bIsDiagram) // text::WrapTextMode_THROUGH + { + // Word writes and evaluates the effectExtent in case of position + // type 'Alignment' (UI). We move these values to margin to approximate + // Word's rendering. + if (m_pImpl->m_oEffectExtentLeft) + { + m_pImpl->nLeftMargin + += oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentLeft); + } + if (m_pImpl->m_oEffectExtentTop) + { + m_pImpl->nTopMargin + += oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentTop); + } + if (m_pImpl->m_oEffectExtentRight) + { + m_pImpl->nRightMargin + += oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentRight); + } + if (m_pImpl->m_oEffectExtentBottom) + { + m_pImpl->nBottomMargin + += oox::drawingml::convertEmuToHmm(*m_pImpl->m_oEffectExtentBottom); + } + } + + // FixMe: tdf#141880 LibreOffice cannot handle negative vertical margins + // although they are allowed in ODF. + if (m_pImpl->nTopMargin < 0) + m_pImpl->nTopMargin = 0; + if (m_pImpl->nBottomMargin < 0) + m_pImpl->nBottomMargin = 0; + } + + if (bUseShape && m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR) + { + // If we are here, this is a drawingML shape. For those, only dmapper (and not oox) knows the anchoring infos (just like for Writer pictures). + // But they aren't Writer pictures, either (which are already handled above). + uno::Reference< beans::XPropertySet > xShapeProps(m_xShape, uno::UNO_QUERY_THROW); + + if (m_pImpl->nWrap == text::WrapTextMode_THROUGH && m_pImpl->nHoriRelation == text::RelOrientation::FRAME) + { + // text::RelOrientation::FRAME is OOXML's "column", which behaves as if + // layout-in-cell would be always off. + m_pImpl->bLayoutInCell = false; + } + + // Anchored: Word only supports at-char in that case. + text::TextContentAnchorType eAnchorType = text::TextContentAnchorType_AT_CHARACTER; + + if (m_pImpl->bHidden) + { + xShapeProps->setPropertyValue("Visible", uno::Any(false)); + xShapeProps->setPropertyValue("Printable", uno::Any(false)); + } + + // Avoid setting AnchorType for TextBoxes till SwTextBoxHelper::syncProperty() doesn't handle transition. + bool bTextBox = false; + xShapeProps->getPropertyValue("TextBox") >>= bTextBox; + + // The positioning change caused by LayoutInCell doesn't sync well + // in the text / frame duo. So the compatibility fix only correctly + // positions the frame and not the text currently. + // tdf#135943: Instead of half-fixing and making a complete mess, + // just avoid until layout's repositioning is sync'd to the text frame. + if (m_pImpl->bLayoutInCell && bTextBox) + m_pImpl->bLayoutInCell = !m_pImpl->bCompatForcedLayoutInCell; + + xShapeProps->setPropertyValue("AnchorType", uno::Any(eAnchorType)); + + if (m_pImpl->nVertRelation == text::RelOrientation::TEXT_LINE) + { + // Word's "line" is "below the bottom of the line", our TEXT_LINE is + // "towards top, from the bottom of the line", so invert the vertical + // position. + awt::Point aPoint = xShape->getPosition(); + aPoint.Y *= -1; + xShape->setPosition(aPoint); + } + + if (m_pImpl->bLayoutInCell && bTextBox && m_pImpl->rDomainMapper.IsInTable() + && m_pImpl->nHoriRelation == text::RelOrientation::PAGE_FRAME) + m_pImpl->nHoriRelation = text::RelOrientation::FRAME; + if(m_pImpl->rDomainMapper.IsInTable()) + xShapeProps->setPropertyValue(getPropertyName(PROP_FOLLOW_TEXT_FLOW), + uno::Any(m_pImpl->bLayoutInCell)); + //only the position orientation is handled in applyPosition() + m_pImpl->applyPosition(xShapeProps); + + uno::Reference<lang::XServiceInfo> xServiceInfo(m_xShape, uno::UNO_QUERY_THROW); + if (xServiceInfo->supportsService("com.sun.star.drawing.GroupShape") || + xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape")) + { + // You would expect that position and rotation are + // independent, but they are not. Till we are not + // there yet to handle all scaling, translation and + // rotation with a single transformation matrix, + // make sure there is no graphic rotation set when we set + // the position. + sal_Int32 nRotation = 0; + if (xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape")) + { + xShapeProps->getPropertyValue("RotateAngle") >>= nRotation; + } + if (nRotation) + xShapeProps->setPropertyValue("RotateAngle", uno::Any(sal_Int32(0))); + + // Position of the groupshape should be set after children have been added. + // Long-term we should get rid of positioning group + // shapes, though. Do it for top-level ones with + // absolute page position as a start. + // fdo#80555: also set position for graphic shapes here + if (!isTopGroupObj(m_xShape) + || m_pImpl->nHoriRelation != text::RelOrientation::PAGE_FRAME + || m_pImpl->nVertRelation != text::RelOrientation::PAGE_FRAME) + m_xShape->setPosition( + awt::Point(m_pImpl->nLeftPosition, m_pImpl->nTopPosition)); + + if (nRotation) + xShapeProps->setPropertyValue("RotateAngle", uno::Any(nRotation)); + } + + + m_pImpl->applyRelativePosition(xShapeProps, /*bRelativeOnly=*/true); + + xShapeProps->setPropertyValue("SurroundContour", uno::Any(m_pImpl->bContour)); + xShapeProps->setPropertyValue("ContourOutside", uno::Any(m_pImpl->bContourOutside)); + m_pImpl->applyMargins(xShapeProps); + xShapeProps->setPropertyValue("Opaque", uno::Any(m_pImpl->bOpaque)); + xShapeProps->setPropertyValue("Surround", uno::Any(static_cast<sal_Int32>(m_pImpl->nWrap))); + m_pImpl->applyZOrder(xShapeProps); + m_pImpl->applyName(xShapeProps); + m_pImpl->applyHyperlink(xShapeProps, bUseShape); + xShapeProps->setPropertyValue("AllowOverlap", + uno::Any(m_pImpl->bAllowOverlap)); + + // Get the grab-bag set by oox, merge with our one and then put it back. + comphelper::SequenceAsHashMap aInteropGrabBag(xShapeProps->getPropertyValue("InteropGrabBag")); + aInteropGrabBag.update(m_pImpl->getInteropGrabBag()); + xShapeProps->setPropertyValue("InteropGrabBag", uno::Any(aInteropGrabBag.getAsConstPropertyValueList())); + } + else if (bUseShape && m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_INLINE) + { + uno::Reference< beans::XPropertySet > xShapeProps(m_xShape, uno::UNO_QUERY_THROW); + m_pImpl->applyMargins(xShapeProps); + m_pImpl->applyZOrder(xShapeProps); + comphelper::SequenceAsHashMap aInteropGrabBag(xShapeProps->getPropertyValue("InteropGrabBag")); + aInteropGrabBag.update(m_pImpl->getInteropGrabBag()); + xShapeProps->setPropertyValue("InteropGrabBag", uno::Any(aInteropGrabBag.getAsConstPropertyValueList())); + } + } + } + break; + case NS_ooxml::LN_CT_Inline_distT: + m_pImpl->nTopMargin = 0; + break; + case NS_ooxml::LN_CT_Inline_distB: + m_pImpl->nBottomMargin = 0; + break; + case NS_ooxml::LN_CT_Inline_distL: + m_pImpl->nLeftMargin = 0; + break; + case NS_ooxml::LN_CT_Inline_distR: + m_pImpl->nRightMargin = 0; + break; + case NS_ooxml::LN_CT_GraphicalObjectData_uri: + rValue.getString(); + //TODO: does it need to be handled? + break; + case NS_ooxml::LN_CT_SizeRelH_relativeFrom: + { + switch (nIntValue) + { + case NS_ooxml::LN_ST_SizeRelFromH_margin: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeWidthRelation", uno::Any(text::RelOrientation::FRAME)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromH_leftMargin: + case NS_ooxml::LN_ST_SizeRelFromH_outsideMargin: + if (m_xShape.is()) + { + // Here we handle the relative size of the width of some shape. + // The size of the shape's width is going to be relative to the size of the left margin. + // E.g.: (left margin = 8 && relative size = 150%) -> width of some shape = 12. + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeWidthRelation", uno::Any(text::RelOrientation::PAGE_LEFT)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromH_rightMargin: + case NS_ooxml::LN_ST_SizeRelFromH_insideMargin: + if (m_xShape.is()) + { + // Same as the left margin above. + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeWidthRelation", uno::Any(text::RelOrientation::PAGE_RIGHT)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromH_page: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeWidthRelation", uno::Any(text::RelOrientation::PAGE_FRAME)); + } + break; + default: + SAL_WARN("writerfilter", "GraphicImport::lcl_attribute: unhandled NS_ooxml::LN_CT_SizeRelH_relativeFrom value: " << nIntValue); + break; + } + } + break; + case NS_ooxml::LN_CT_SizeRelV_relativeFrom: + { + switch (nIntValue) + { + case NS_ooxml::LN_ST_SizeRelFromV_margin: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeHeightRelation", uno::Any(text::RelOrientation::FRAME)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromV_page: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeHeightRelation", uno::Any(text::RelOrientation::PAGE_FRAME)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromV_topMargin: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeHeightRelation", uno::Any(text::RelOrientation::PAGE_PRINT_AREA)); + } + break; + case NS_ooxml::LN_ST_SizeRelFromV_bottomMargin: + if (m_xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RelativeHeightRelation", uno::Any(text::RelOrientation::PAGE_PRINT_AREA_BOTTOM)); + } + break; + default: + SAL_WARN("writerfilter", "GraphicImport::lcl_attribute: unhandled NS_ooxml::LN_CT_SizeRelV_relativeFrom value: " << nIntValue); + break; + } + } + break; + default: +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } +} + +uno::Reference<text::XTextContent> GraphicImport::GetGraphicObject() +{ + uno::Reference<text::XTextContent> xResult; + + if (m_xGraphicObject.is()) + xResult = m_xGraphicObject; + else if (m_xShape.is()) + { + xResult.set(m_xShape, uno::UNO_QUERY_THROW); + } + + return xResult; +} + + +void GraphicImport::ProcessShapeOptions(Value const & rValue) +{ + sal_Int32 nIntValue = rValue.getInt(); + switch( m_pImpl->nShapeOptionType ) + { + case NS_ooxml::LN_CT_Anchor_distL: + m_pImpl->nLeftMargin = nIntValue / 360; + m_pImpl->nLeftMarginOrig = m_pImpl->nLeftMargin; + break; + case NS_ooxml::LN_CT_Anchor_distT: + //todo: changes have to be applied depending on the orientation, see SwWW8ImplReader::AdjustULWrapForWordMargins() + m_pImpl->nTopMargin = nIntValue / 360; + break; + case NS_ooxml::LN_CT_Anchor_distR: + //todo: changes have to be applied depending on the orientation, see SwWW8ImplReader::AdjustLRWrapForWordMargins() + m_pImpl->nRightMargin = nIntValue / 360; + break; + case NS_ooxml::LN_CT_Anchor_distB: + //todo: changes have to be applied depending on the orientation, see SwWW8ImplReader::AdjustULWrapForWordMargins() + m_pImpl->nBottomMargin = nIntValue / 360; + break; + default: + OSL_FAIL( "shape option unsupported?"); + } +} + + +void GraphicImport::lcl_sprm(Sprm& rSprm) +{ + sal_uInt32 nSprmId = rSprm.getId(); + + switch(nSprmId) + { + case NS_ooxml::LN_CT_Inline_extent: // 90911; + case NS_ooxml::LN_CT_Inline_effectExtent: // 90912; + case NS_ooxml::LN_CT_Inline_docPr: // 90913; + case NS_ooxml::LN_CT_Inline_cNvGraphicFramePr: // 90914; + case NS_ooxml::LN_CT_NonVisualGraphicFrameProperties_graphicFrameLocks:// 90657 + case NS_ooxml::LN_CT_Inline_a_graphic:// 90915 + case NS_ooxml::LN_CT_Anchor_simplePos_elem: // 90975; + case NS_ooxml::LN_CT_Anchor_extent: // 90978; + case NS_ooxml::LN_CT_Anchor_effectExtent: // 90979; + case NS_ooxml::LN_EG_WrapType_wrapSquare: // 90945; + case NS_ooxml::LN_EG_WrapType_wrapTight: // 90946; + case NS_ooxml::LN_EG_WrapType_wrapThrough: + case NS_ooxml::LN_CT_Anchor_docPr: // 90980; + case NS_ooxml::LN_CT_Anchor_cNvGraphicFramePr: // 90981; + case NS_ooxml::LN_CT_Anchor_a_graphic: // 90982; + case NS_ooxml::LN_CT_WrapPath_start: // 90924; + case NS_ooxml::LN_CT_WrapPath_lineTo: // 90925; + case NS_ooxml::LN_graphic_graphic: + case NS_ooxml::LN_pic_pic: + case NS_ooxml::LN_dgm_relIds: + case NS_ooxml::LN_lc_lockedCanvas: + case NS_ooxml::LN_c_chart: + case NS_ooxml::LN_wps_wsp: + case NS_ooxml::LN_wpg_wgp: + case NS_ooxml::LN_sizeRelH_sizeRelH: + case NS_ooxml::LN_sizeRelV_sizeRelV: + case NS_ooxml::LN_hlinkClick_hlinkClick: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve(*this); + } + + // We'll map these to PARALLEL, save the original wrap type. + if (nSprmId == NS_ooxml::LN_EG_WrapType_wrapTight) + m_pImpl->m_aInteropGrabBag["EG_WrapType"] <<= OUString("wrapTight"); + else if (nSprmId == NS_ooxml::LN_EG_WrapType_wrapThrough) + m_pImpl->m_aInteropGrabBag["EG_WrapType"] <<= OUString("wrapThrough"); + + switch (nSprmId) + { + case NS_ooxml::LN_EG_WrapType_wrapSquare: + case NS_ooxml::LN_EG_WrapType_wrapThrough: + case NS_ooxml::LN_EG_WrapType_wrapTight: + { + // tdf#137850: Word >= 2013 seems to ignore bBehindDoc except for wrapNone, but older versions honour it. + if (m_pImpl->bBehindDoc && m_pImpl->rDomainMapper.GetSettingsTable()->GetWordCompatibilityMode() > 14) + m_pImpl->bOpaque = true; + } + break; + } + + } + break; + case NS_ooxml::LN_CT_WrapTight_wrapPolygon: + case NS_ooxml::LN_CT_WrapThrough_wrapPolygon: + { + WrapPolygonHandler aHandler; + + resolveSprmProps(aHandler, rSprm); + + m_pImpl->mpWrapPolygon = aHandler.getPolygon(); + + // Save the wrap path in case we can't handle it natively: drawinglayer shapes, TextFrames. + m_pImpl->m_aInteropGrabBag["CT_WrapPath"] <<= m_pImpl->mpWrapPolygon->getPointSequenceSequence(); + } + break; + case NS_ooxml::LN_CT_Anchor_positionH: // 90976; + { + // Use a special handler for the positioning + auto pHandler = std::make_shared<PositionHandler>( m_pImpl->m_rPositionOffsets, m_pImpl->m_rAligns ); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve( *pHandler ); + if( !m_pImpl->bUseSimplePos ) + { + m_pImpl->nHoriRelation = pHandler->relation(); + m_pImpl->bPageToggle = pHandler->GetPageToggle(); + m_pImpl->nHoriOrient = pHandler->orientation(); + m_pImpl->nLeftPosition = pHandler->position(); + + // Left adjustments: if horizontally aligned to left of margin, then remove the + // left wrapping. + if (m_pImpl->nHoriOrient == text::HoriOrientation::LEFT) + { + if (m_pImpl->nHoriRelation == text::RelOrientation::PAGE_PRINT_AREA) + { + m_pImpl->nLeftMargin = 0; + } + } + } + } + } + break; + case NS_ooxml::LN_CT_Anchor_positionV: // 90977; + { + // Use a special handler for the positioning + auto pHandler = std::make_shared<PositionHandler>( m_pImpl->m_rPositionOffsets, m_pImpl->m_rAligns); + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve( *pHandler ); + if( !m_pImpl->bUseSimplePos ) + { + m_pImpl->nVertRelation = pHandler->relation(); + m_pImpl->nVertOrient = pHandler->orientation(); + m_pImpl->nTopPosition = pHandler->position(); + } + } + } + break; + case NS_ooxml::LN_CT_SizeRelH_pctWidth: + case NS_ooxml::LN_CT_SizeRelV_pctHeight: + if (m_pImpl->m_rPositivePercentages.empty()) + break; + + if (m_xShape.is()) + { + sal_Int16 nPositivePercentage = rtl::math::round(m_pImpl->m_rPositivePercentages.front().toDouble() / oox::drawingml::PER_PERCENT); + + if (nPositivePercentage) + { + uno::Reference<beans::XPropertySet> xPropertySet(m_xShape, uno::UNO_QUERY); + OUString aProperty = nSprmId == NS_ooxml::LN_CT_SizeRelH_pctWidth ? OUString("RelativeWidth") : OUString("RelativeHeight"); + + sal_Int32 nTextPreRotateAngle = 0; + uno::Any aAny; + if (xPropertySet->getPropertySetInfo()->hasPropertyByName( + "CustomShapeGeometry")) + { + aAny = xPropertySet->getPropertyValue("CustomShapeGeometry"); + } + comphelper::SequenceAsHashMap aCustomShapeGeometry(aAny); + auto it = aCustomShapeGeometry.find("TextPreRotateAngle"); + if (it != aCustomShapeGeometry.end()) + { + nTextPreRotateAngle = it->second.get<sal_Int32>(); + } + if (nTextPreRotateAngle == 0) + { + xPropertySet->setPropertyValue(aProperty, + uno::Any(nPositivePercentage)); + } + } + } + + // Make sure the token is consumed even if xShape is an empty + // reference. + m_pImpl->m_rPositivePercentages.pop(); + break; + case NS_ooxml::LN_EG_WrapType_wrapNone: // 90944; - doesn't contain attributes + //depending on the behindDoc attribute text wraps through behind or in front of the object + m_pImpl->nWrap = text::WrapTextMode_THROUGH; + + // Wrap though means the margins defined earlier should not be + // respected. + m_pImpl->nLeftMargin = 0; + m_pImpl->nTopMargin = 0; + m_pImpl->nRightMargin = 0; + m_pImpl->nBottomMargin = 0; + break; + case NS_ooxml::LN_EG_WrapType_wrapTopAndBottom: // 90948; + // tdf#137850: Word >= 2013 seems to ignore bBehindDoc except for wrapNone, but older versions honour it. + if (m_pImpl->bBehindDoc && m_pImpl->rDomainMapper.GetSettingsTable()->GetWordCompatibilityMode() > 14) + m_pImpl->bOpaque = true; + m_pImpl->nWrap = text::WrapTextMode_NONE; + break; + case NS_ooxml::LN_CT_GraphicalObject_graphicData:// 90660; + { + m_pImpl->bIsGraphic = true; + + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_NonVisualDrawingProps_a_hlinkClick: // 90689; + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + pProperties->resolve( *this ); + } + break; + default: + SAL_WARN("writerfilter", "GraphicImport::lcl_sprm: unhandled token: " << nSprmId); + break; + } +} + +void GraphicImport::lcl_entry(writerfilter::Reference<Properties>::Pointer_t /*ref*/) +{ +} + +uno::Reference<text::XTextContent> GraphicImport::createGraphicObject(uno::Reference<graphic::XGraphic> const & rxGraphic, + uno::Reference<beans::XPropertySet> const & xShapeProps) +{ + uno::Reference<text::XTextContent> xGraphicObject; + try + { + if (rxGraphic.is()) + { + uno::Reference< beans::XPropertySet > xGraphicObjectProperties( + m_xTextFactory->createInstance("com.sun.star.text.TextGraphicObject"), + uno::UNO_QUERY_THROW); + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_GRAPHIC), uno::Any(rxGraphic)); + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), + uno::Any( m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR ? + text::TextContentAnchorType_AT_CHARACTER : + text::TextContentAnchorType_AS_CHARACTER )); + xGraphicObject.set( xGraphicObjectProperties, uno::UNO_QUERY_THROW ); + + //shapes have only one border + table::BorderLine2 aBorderLine; + GraphicBorderLine& rBorderLine = m_pImpl->aBorders[0]; + if (rBorderLine.isEmpty() && xShapeProps.is() && xShapeProps->getPropertyValue("LineStyle").get<drawing::LineStyle>() != drawing::LineStyle_NONE) + { + // In case we got no border tokens and we have the + // original shape, then use its line properties as the + // border. + aBorderLine.Color = xShapeProps->getPropertyValue("LineColor").get<sal_Int32>(); + aBorderLine.LineWidth = xShapeProps->getPropertyValue("LineWidth").get<sal_Int32>(); + } + else + { + aBorderLine.Color = 0; + aBorderLine.InnerLineWidth = 0; + aBorderLine.OuterLineWidth = static_cast<sal_Int16>(rBorderLine.nLineWidth); + aBorderLine.LineDistance = 0; + } + PropertyIds const aBorderProps[] = + { + PROP_LEFT_BORDER, + PROP_RIGHT_BORDER, + PROP_TOP_BORDER, + PROP_BOTTOM_BORDER + }; + + for(PropertyIds const & rBorderProp : aBorderProps) + xGraphicObjectProperties->setPropertyValue(getPropertyName(rBorderProp), uno::Any(aBorderLine)); + + // setting graphic object shadow properties + if (m_pImpl->bShadow) + { + // Shadow width is approximated by average of X and Y + table::ShadowFormat aShadow; + sal_uInt32 nShadowColor = m_pImpl->nShadowColor & 0x00FFFFFF; // The shadow color we get is RGB only. + sal_Int32 nShadowWidth = (abs(m_pImpl->nShadowXDistance) + + abs(m_pImpl->nShadowYDistance)) / 2; + + aShadow.ShadowWidth = nShadowWidth; + sal_uInt8 nShadowTransparence = float(m_pImpl->nShadowTransparence) * 2.55; + nShadowColor |= (nShadowTransparence << 24); // Add transparence to the color. + aShadow.Color = nShadowColor; + // Distances -ve for top and right, +ve for bottom and left + if (m_pImpl->nShadowXDistance > 0) + { + if (m_pImpl->nShadowYDistance > 0) + aShadow.Location = table::ShadowLocation_BOTTOM_RIGHT; + else + aShadow.Location = table::ShadowLocation_TOP_RIGHT; + } + else + { + if (m_pImpl->nShadowYDistance > 0) + aShadow.Location = table::ShadowLocation_BOTTOM_LEFT; + else + aShadow.Location = table::ShadowLocation_TOP_LEFT; + } + + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_SHADOW_FORMAT), uno::Any(aShadow)); + } + + // setting properties for all types + if( m_pImpl->bPositionProtected ) + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_POSITION_PROTECTED ), + uno::Any(true)); + if( m_pImpl->bSizeProtected ) + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_SIZE_PROTECTED ), + uno::Any(true)); + + sal_Int32 nWidth = - m_pImpl->nLeftPosition; + if (m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR) + { + //adjust margins + if( (m_pImpl->nHoriOrient == text::HoriOrientation::LEFT && + (m_pImpl->nHoriRelation == text::RelOrientation::PAGE_PRINT_AREA || + m_pImpl->nHoriRelation == text::RelOrientation::FRAME) ) || + (m_pImpl->nHoriOrient == text::HoriOrientation::INSIDE && + m_pImpl->nHoriRelation == text::RelOrientation::PAGE_PRINT_AREA )) + m_pImpl->nLeftMargin = 0; + if((m_pImpl->nHoriOrient == text::HoriOrientation::RIGHT && + (m_pImpl->nHoriRelation == text::RelOrientation::PAGE_PRINT_AREA || + m_pImpl->nHoriRelation == text::RelOrientation::FRAME) ) || + (m_pImpl->nHoriOrient == text::HoriOrientation::INSIDE && + m_pImpl->nHoriRelation == text::RelOrientation::PAGE_PRINT_AREA )) + m_pImpl->nRightMargin = 0; + // adjust top/bottom margins + if( m_pImpl->nVertOrient == text::VertOrientation::TOP && + ( m_pImpl->nVertRelation == text::RelOrientation::PAGE_PRINT_AREA || + m_pImpl->nVertRelation == text::RelOrientation::PAGE_FRAME)) + m_pImpl->nTopMargin = 0; + if( m_pImpl->nVertOrient == text::VertOrientation::BOTTOM && + ( m_pImpl->nVertRelation == text::RelOrientation::PAGE_PRINT_AREA || + m_pImpl->nVertRelation == text::RelOrientation::PAGE_FRAME)) + m_pImpl->nBottomMargin = 0; + if( m_pImpl->nVertOrient == text::VertOrientation::BOTTOM && + m_pImpl->nVertRelation == text::RelOrientation::PAGE_PRINT_AREA ) + m_pImpl->nBottomMargin = 0; + //adjust alignment + if( m_pImpl->nHoriOrient == text::HoriOrientation::INSIDE && + m_pImpl->nHoriRelation == text::RelOrientation::PAGE_FRAME ) + { + // convert 'left to page' to 'from left -<width> to page text area' + m_pImpl->nHoriOrient = text::HoriOrientation::NONE; + m_pImpl->nHoriRelation = text::RelOrientation::PAGE_PRINT_AREA; + m_pImpl->nLeftPosition = - nWidth; + } + else if( m_pImpl->nHoriOrient == text::HoriOrientation::OUTSIDE && + m_pImpl->nHoriRelation == text::RelOrientation::PAGE_FRAME ) + { + // convert 'right to page' to 'from left 0 to right page border' + m_pImpl->nHoriOrient = text::HoriOrientation::NONE; + m_pImpl->nHoriRelation = text::RelOrientation::PAGE_RIGHT; + m_pImpl->nLeftPosition = 0; + } + + m_pImpl->applyPosition(xGraphicObjectProperties); + m_pImpl->applyRelativePosition(xGraphicObjectProperties); + if( !m_pImpl->bOpaque ) + { + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_OPAQUE ), uno::Any(m_pImpl->bOpaque)); + } + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_SURROUND ), + uno::Any(static_cast<sal_Int32>(m_pImpl->nWrap))); + if( m_pImpl->rDomainMapper.IsInTable()) + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_FOLLOW_TEXT_FLOW ), + uno::Any(m_pImpl->bLayoutInCell)); + + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_SURROUND_CONTOUR ), + uno::Any(m_pImpl->bContour)); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_CONTOUR_OUTSIDE ), + uno::Any(m_pImpl->bContourOutside)); + m_pImpl->applyMargins(xGraphicObjectProperties); + } + + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_ADJUST_CONTRAST ), + uno::Any(static_cast<sal_Int16>(m_pImpl->nContrast))); + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_ADJUST_LUMINANCE ), + uno::Any(static_cast<sal_Int16>(m_pImpl->nBrightness))); + if(m_pImpl->eColorMode != drawing::ColorMode_STANDARD) + { + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_GRAPHIC_COLOR_MODE ), + uno::Any(m_pImpl->eColorMode)); + } + + xGraphicObjectProperties->setPropertyValue(getPropertyName( PROP_BACK_COLOR ), + uno::Any( GraphicImport_Impl::nFillColor )); + m_pImpl->applyZOrder(xGraphicObjectProperties); + + //there seems to be no way to detect the original size via _real_ API + uno::Reference< beans::XPropertySet > xGraphicProperties(rxGraphic, uno::UNO_QUERY_THROW); + + if (m_pImpl->mpWrapPolygon) + { + uno::Any aContourPolyPolygon; + awt::Size aGraphicSize; + WrapPolygon::Pointer_t pCorrected; + xGraphicProperties->getPropertyValue(getPropertyName(PROP_SIZE100th_M_M)) >>= aGraphicSize; + if (aGraphicSize.Width && aGraphicSize.Height) + { + pCorrected = m_pImpl->mpWrapPolygon->correctWordWrapPolygon(aGraphicSize); + } + else + { + xGraphicProperties->getPropertyValue(getPropertyName(PROP_SIZE_PIXEL)) >>= aGraphicSize; + if (aGraphicSize.Width && aGraphicSize.Height) + { + pCorrected = m_pImpl->mpWrapPolygon->correctWordWrapPolygonPixel(aGraphicSize); + } + } + + text::GraphicCrop aGraphicCrop; + xShapeProps->getPropertyValue("GraphicCrop") >>= aGraphicCrop; + if (aGraphicCrop.Top != 0 || aGraphicCrop.Bottom != 0 || aGraphicCrop.Left != 0 + || aGraphicCrop.Right != 0) + { + // Word's wrap polygon deals with a canvas which has the size of the already + // cropped graphic, correct our polygon to have the same render result. + pCorrected = pCorrected->correctCrop(aGraphicSize, aGraphicCrop); + } + + if (pCorrected) + { + aContourPolyPolygon <<= pCorrected->getPointSequenceSequence(); + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_CONTOUR_POLY_POLYGON), + aContourPolyPolygon); + // We should bring it to front, even if wp:anchor's behindDoc="1", + // because otherwise paragraph background (if set) overlaps the graphic + // TODO: if paragraph's background becomes bottommost, then remove this hack + xGraphicObjectProperties->setPropertyValue("Opaque", uno::Any(true)); + } + } + + + if(m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_INLINE || m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_ANCHOR) + { + if( m_pImpl->getXSize() && m_pImpl->getYSize() ) + xGraphicObjectProperties->setPropertyValue(getPropertyName(PROP_SIZE), + uno::Any( awt::Size( m_pImpl->getXSize(), m_pImpl->getYSize() ))); + m_pImpl->applyMargins(xGraphicObjectProperties); + m_pImpl->applyName(xGraphicObjectProperties); + m_pImpl->applyHyperlink(xGraphicObjectProperties, false); + } + + // Handle horizontal flip. + bool bMirrored = false; + xShapeProps->getPropertyValue("IsMirrored") >>= bMirrored; + if (bMirrored) + { + xGraphicObjectProperties->setPropertyValue("HoriMirroredOnEvenPages", + uno::Any(true)); + xGraphicObjectProperties->setPropertyValue("HoriMirroredOnOddPages", + uno::Any(true)); + } + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter", ""); + } + return xGraphicObject; +} + + +void GraphicImport::data(const sal_uInt8* buf, size_t len) +{ + uno::Reference< io::XInputStream > xIStream = new XInputStreamHelper( buf, len ); + beans::PropertyValues aMediaProperties{ comphelper::makePropertyValue( + getPropertyName(PROP_INPUT_STREAM), xIStream) }; + + uno::Reference<beans::XPropertySet> xPropertySet; + uno::Reference<graphic::XGraphicProvider> xGraphicProvider(graphic::GraphicProvider::create(m_xComponentContext)); + uno::Reference<graphic::XGraphic> xGraphic = xGraphicProvider->queryGraphic(aMediaProperties); + m_xGraphicObject = createGraphicObject(xGraphic, xPropertySet); +} + + +void GraphicImport::lcl_startSectionGroup() +{ +} + + +void GraphicImport::lcl_endSectionGroup() +{ +} + + +void GraphicImport::lcl_startParagraphGroup() +{ +} + + +void GraphicImport::lcl_endParagraphGroup() +{ +} + + +void GraphicImport::lcl_startCharacterGroup() +{ +} + + +void GraphicImport::lcl_endCharacterGroup() +{ +} + + +void GraphicImport::lcl_text(const sal_uInt8 * /*_data*/, size_t /*len*/) +{ +} + + +void GraphicImport::lcl_utext(const sal_uInt8 * /*_data*/, size_t /*len*/) +{ +} + + +void GraphicImport::lcl_props(writerfilter::Reference<Properties>::Pointer_t /*ref*/) +{ +} + + +void GraphicImport::lcl_table(Id /*name*/, writerfilter::Reference<Table>::Pointer_t /*ref*/) +{ +} + + +void GraphicImport::lcl_substream(Id /*name*/, ::writerfilter::Reference<Stream>::Pointer_t /*ref*/) +{ +} + +void GraphicImport::lcl_startShape(uno::Reference<drawing::XShape> const&) +{ +} + +void GraphicImport::lcl_endShape( ) +{ +} + +bool GraphicImport::IsGraphic() const +{ + return m_pImpl->bIsGraphic; +} + +sal_Int32 GraphicImport::GetLeftMarginOrig() const +{ + return m_pImpl->nLeftMarginOrig; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/GraphicImport.hxx b/writerfilter/source/dmapper/GraphicImport.hxx new file mode 100644 index 000000000..93be2df3f --- /dev/null +++ b/writerfilter/source/dmapper/GraphicImport.hxx @@ -0,0 +1,140 @@ +/* -*- 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 . + */ +#pragma once + +#include <queue> +#include <memory> + +#include "LoggedResources.hxx" + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> + +namespace com::sun::star { + namespace uno + { + class XComponentContext; + } + namespace lang + { + class XMultiServiceFactory; + } + namespace text + { + class XTextContent; + } + namespace drawing + { + class XShape; + } + namespace beans + { + struct PropertyValue; + } +} + +namespace writerfilter::dmapper +{ +class GraphicImport_Impl; +class DomainMapper; + +enum GraphicImportType +{ + IMPORT_AS_DETECTED_INLINE, + IMPORT_AS_DETECTED_ANCHOR +}; + +class GraphicImport : public LoggedProperties, public LoggedTable + ,public BinaryObj, public LoggedStream +{ + std::unique_ptr<GraphicImport_Impl> m_pImpl; + + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + css::uno::Reference<css::lang::XMultiServiceFactory> m_xTextFactory; + + css::uno::Reference<css::text::XTextContent> m_xGraphicObject; + + css::uno::Reference<css::drawing::XShape> m_xShape; + void ProcessShapeOptions(Value const & val); + + css::uno::Reference<css::text::XTextContent> + createGraphicObject(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic, + css::uno::Reference<css::beans::XPropertySet> const & xShapeProps); + + void putPropertyToFrameGrabBag( const OUString& sPropertyName, const css::uno::Any& aPropertyValue ); + +public: + explicit GraphicImport( css::uno::Reference<css::uno::XComponentContext> const& xComponentContext, + css::uno::Reference<css::lang::XMultiServiceFactory> const& xTextFactory, + DomainMapper& rDomainMapper, + GraphicImportType eGraphicImportType, + std::pair<OUString, OUString>& rPositionOffsets, + std::pair<OUString, OUString>& rAligns, + std::queue<OUString>& rPositivePercentages); + virtual ~GraphicImport() override; + + // BinaryObj + virtual void data(const sal_uInt8* buffer, size_t len) override; + + css::uno::Reference<css::text::XTextContent> GetGraphicObject(); + const css::uno::Reference<css::drawing::XShape>& GetXShapeObject() const { return m_xShape;} + bool IsGraphic() const; + sal_Int32 GetLeftMarginOrig() const; + + com::sun::star::awt::Point GetGraphicObjectPosition() const; + + bool GetLayoutInCell() const; + + private: + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + + // Stream + virtual void lcl_startSectionGroup() override; + virtual void lcl_endSectionGroup() override; + virtual void lcl_startParagraphGroup() override; + virtual void lcl_endParagraphGroup() override; + virtual void lcl_startCharacterGroup() override; + virtual void lcl_endCharacterGroup() override; + virtual void lcl_text(const sal_uInt8 * data, size_t len) override; + virtual void lcl_utext(const sal_uInt8 * data, size_t len) override; + virtual void lcl_props(writerfilter::Reference<Properties>::Pointer_t ref) override; + virtual void lcl_table(Id name, + writerfilter::Reference<Table>::Pointer_t ref) override; + virtual void lcl_substream(Id name, writerfilter::Reference<Stream>::Pointer_t ref) override; + virtual void lcl_startShape(css::uno::Reference<css::drawing::XShape> const& xShape) override; + virtual void lcl_startTextBoxContent() override {}; + virtual void lcl_endTextBoxContent() override {}; + virtual void lcl_endShape() override; + + void handleWrapTextValue(sal_uInt32 nVal); + void lcl_expandRectangleByEffectExtent(css::awt::Point& rLeftTop, css::awt::Size& rSize); + void lcl_correctWord2007EffectExtent(const sal_Int32 nMSOAngle); +}; + +typedef tools::SvRef<GraphicImport> GraphicImportPtr; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/LatentStyleHandler.cxx b/writerfilter/source/dmapper/LatentStyleHandler.cxx new file mode 100644 index 000000000..bc381d21f --- /dev/null +++ b/writerfilter/source/dmapper/LatentStyleHandler.cxx @@ -0,0 +1,71 @@ +/* -*- 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/. + */ +#include "LatentStyleHandler.hxx" +#include "TagLogger.hxx" +#include <ooxml/resourceids.hxx> + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; + +LatentStyleHandler::LatentStyleHandler() + : LoggedProperties("LatentStyleHandler") +{ +} + +LatentStyleHandler::~LatentStyleHandler() = default; + +void LatentStyleHandler::lcl_attribute(Id nId, Value& rVal) +{ + beans::PropertyValue aValue; + bool bFound = true; + switch (nId) + { + case NS_ooxml::LN_CT_LsdException_name: + aValue.Name = "name"; + break; + case NS_ooxml::LN_CT_LsdException_locked: + aValue.Name = "locked"; + break; + case NS_ooxml::LN_CT_LsdException_uiPriority: + aValue.Name = "uiPriority"; + break; + case NS_ooxml::LN_CT_LsdException_semiHidden: + aValue.Name = "semiHidden"; + break; + case NS_ooxml::LN_CT_LsdException_unhideWhenUsed: + aValue.Name = "unhideWhenUsed"; + break; + case NS_ooxml::LN_CT_LsdException_qFormat: + aValue.Name = "qFormat"; + break; + default: + bFound = false; +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } + if (bFound) + { + aValue.Value <<= rVal.getString(); + m_aAttributes.push_back(aValue); + } +} + +void LatentStyleHandler::lcl_sprm(Sprm& /*rSprm*/) {} + +const std::vector<beans::PropertyValue>& LatentStyleHandler::getAttributes() const +{ + return m_aAttributes; +} + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/LatentStyleHandler.hxx b/writerfilter/source/dmapper/LatentStyleHandler.hxx new file mode 100644 index 000000000..52a4d9e7c --- /dev/null +++ b/writerfilter/source/dmapper/LatentStyleHandler.hxx @@ -0,0 +1,35 @@ +/* -*- 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/. + */ +#pragma once + +#include "LoggedResources.hxx" +#include <vector> +#include <com/sun/star/beans/PropertyValue.hpp> + +namespace writerfilter::dmapper +{ +/// Handler for a latent style (w:lsdException element) +class LatentStyleHandler : public LoggedProperties +{ + std::vector<css::beans::PropertyValue> m_aAttributes; + + // Properties + void lcl_attribute(Id nId, Value& rVal) override; + void lcl_sprm(Sprm& sprm) override; + +public: + LatentStyleHandler(); + ~LatentStyleHandler() override; + + const std::vector<css::beans::PropertyValue>& getAttributes() const; +}; + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/LoggedResources.cxx b/writerfilter/source/dmapper/LoggedResources.cxx new file mode 100644 index 000000000..4eeb5db10 --- /dev/null +++ b/writerfilter/source/dmapper/LoggedResources.cxx @@ -0,0 +1,400 @@ +/* -*- 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 "LoggedResources.hxx" +#include "TagLogger.hxx" +#include <ooxml/QNameToString.hxx> + +using namespace ::com::sun::star; + +namespace writerfilter +{ +#ifdef DBG_UTIL + +LoggedResourcesHelper::LoggedResourcesHelper(const std::string& sPrefix) + : msPrefix(sPrefix) +{ +} + +LoggedResourcesHelper::~LoggedResourcesHelper() {} + +void LoggedResourcesHelper::startElement(const std::string& sElement) +{ + TagLogger::getInstance().startElement(msPrefix + "." + sElement); +} + +void LoggedResourcesHelper::endElement() { TagLogger::getInstance().endElement(); } + +void LoggedResourcesHelper::chars(std::u16string_view rChars) +{ + TagLogger::getInstance().chars(rChars); +} + +void LoggedResourcesHelper::chars(const std::string& rChars) +{ + TagLogger::getInstance().chars(rChars); +} + +void LoggedResourcesHelper::attribute(const std::string& rName, const std::string& rValue) +{ + TagLogger::getInstance().attribute(rName, rValue); +} + +void LoggedResourcesHelper::attribute(const std::string& rName, sal_uInt32 nValue) +{ + TagLogger::getInstance().attribute(rName, nValue); +} + +#endif + +LoggedStream::LoggedStream( +#ifdef DBG_UTIL + const std::string& sPrefix) + : mHelper(sPrefix) +#else + const std::string&) +#endif +{ +} + +LoggedStream::~LoggedStream() {} + +void LoggedStream::startSectionGroup() +{ +#ifdef DBG_UTIL + mHelper.startElement("section"); +#endif + + lcl_startSectionGroup(); +} + +void LoggedStream::endSectionGroup() +{ + lcl_endSectionGroup(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::startParagraphGroup() +{ +#ifdef DBG_UTIL + mHelper.startElement("paragraph"); +#endif + + lcl_startParagraphGroup(); +} + +void LoggedStream::endParagraphGroup() +{ + lcl_endParagraphGroup(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::startCharacterGroup() +{ +#ifdef DBG_UTIL + mHelper.startElement("charactergroup"); +#endif + + lcl_startCharacterGroup(); +} + +void LoggedStream::endCharacterGroup() +{ + lcl_endCharacterGroup(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::startShape(uno::Reference<drawing::XShape> const& xShape) +{ +#ifdef DBG_UTIL + mHelper.startElement("shape"); +#endif + + lcl_startShape(xShape); +} + +void LoggedStream::endShape() +{ + lcl_endShape(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::startTextBoxContent() { lcl_startTextBoxContent(); } + +void LoggedStream::endTextBoxContent() { lcl_endTextBoxContent(); } + +void LoggedStream::text(const sal_uInt8* data, size_t len) +{ +#ifdef DBG_UTIL + mHelper.startElement("text"); + + OUString sText(reinterpret_cast<const char*>(data), len, RTL_TEXTENCODING_MS_1252); + + mHelper.startElement("data"); + LoggedResourcesHelper::chars(sText); + LoggedResourcesHelper::endElement(); +#endif + + lcl_text(data, len); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::utext(const sal_uInt8* data, size_t len) +{ +#ifdef DBG_UTIL + mHelper.startElement("utext"); + mHelper.startElement("data"); + + OUString sText(reinterpret_cast<const sal_Unicode*>(data), len); + + LoggedResourcesHelper::chars(sText); + + LoggedResourcesHelper::endElement(); +#endif + + lcl_utext(data, len); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::positionOffset(const OUString& rText, bool bVertical) +{ +#ifdef DBG_UTIL + mHelper.startElement("positionOffset"); + LoggedResourcesHelper::attribute("vertical", static_cast<int>(bVertical)); + LoggedResourcesHelper::chars(rText); +#endif + + lcl_positionOffset(rText, bVertical); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::align(const OUString& rText, bool bVertical) +{ +#ifdef DBG_UTIL + mHelper.startElement("align"); + LoggedResourcesHelper::attribute("vertical", static_cast<int>(bVertical)); + LoggedResourcesHelper::chars(rText); +#endif + + lcl_align(rText, bVertical); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::positivePercentage(const OUString& rText) +{ +#ifdef DBG_UTIL + mHelper.startElement("positivePercentage"); + LoggedResourcesHelper::chars(rText); +#endif + + lcl_positivePercentage(rText); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::props(writerfilter::Reference<Properties>::Pointer_t ref) +{ +#ifdef DBG_UTIL + mHelper.startElement("props"); +#endif + + lcl_props(ref); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::table(Id name, writerfilter::Reference<Table>::Pointer_t ref) +{ +#ifdef DBG_UTIL + mHelper.startElement("table"); + LoggedResourcesHelper::attribute("name", QNameToString(name)); +#endif + + lcl_table(name, ref); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::substream(Id name, writerfilter::Reference<Stream>::Pointer_t ref) +{ +#ifdef DBG_UTIL + mHelper.startElement("substream"); + LoggedResourcesHelper::attribute("name", QNameToString(name)); +#endif + + lcl_substream(name, ref); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::info(const std::string& _info) +{ +#ifdef DBG_UTIL + mHelper.startElement("info"); + LoggedResourcesHelper::attribute("text", _info); +#else + (void)_info; +#endif + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::startGlossaryEntry() +{ +#ifdef DBG_UTIL + mHelper.startElement("startGlossaryEntry"); +#endif + + lcl_startGlossaryEntry(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::endGlossaryEntry() +{ +#ifdef DBG_UTIL + mHelper.startElement("endGlossaryEntry"); +#endif + + lcl_endGlossaryEntry(); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +void LoggedStream::checkId(const sal_Int32 nId) +{ +#ifdef DBG_UTIL + mHelper.startElement("checkId"); + LoggedResourcesHelper::chars(OUString::number(nId)); +#endif + + lcl_checkId(nId); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +LoggedProperties::LoggedProperties( +#ifdef DBG_UTIL + const std::string& sPrefix) + : mHelper(sPrefix) +#else + const std::string&) +#endif +{ +} + +LoggedProperties::~LoggedProperties() {} + +void LoggedProperties::attribute(Id name, Value& val) +{ +#ifdef DBG_UTIL + mHelper.startElement("attribute"); + LoggedResourcesHelper::attribute("name", QNameToString(name)); + LoggedResourcesHelper::attribute("value", val.toString()); + LoggedResourcesHelper::endElement(); +#endif + + lcl_attribute(name, val); +} + +void LoggedProperties::sprm(Sprm& rSprm) +{ +#ifdef DBG_UTIL + mHelper.startElement("sprm"); + LoggedResourcesHelper::attribute("name", QNameToString(rSprm.getId())); + LoggedResourcesHelper::chars(rSprm.toString()); +#endif + + lcl_sprm(rSprm); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} + +LoggedTable::LoggedTable( +#ifdef DBG_UTIL + const std::string& sPrefix) + : mHelper(sPrefix) +#else + const std::string&) +#endif +{ +} + +LoggedTable::~LoggedTable() {} + +void LoggedTable::entry(int pos, writerfilter::Reference<Properties>::Pointer_t ref) +{ +#ifdef DBG_UTIL + mHelper.startElement("entry"); + LoggedResourcesHelper::attribute("pos", pos); +#else + (void)pos; +#endif + + lcl_entry(ref); + +#ifdef DBG_UTIL + LoggedResourcesHelper::endElement(); +#endif +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/LoggedResources.hxx b/writerfilter/source/dmapper/LoggedResources.hxx new file mode 100644 index 000000000..76efbe5c4 --- /dev/null +++ b/writerfilter/source/dmapper/LoggedResources.hxx @@ -0,0 +1,144 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <dmapper/resourcemodel.hxx> + +namespace writerfilter +{ +#ifdef DBG_UTIL +class LoggedResourcesHelper final +{ +public: + explicit LoggedResourcesHelper(const std::string& sPrefix); + ~LoggedResourcesHelper(); + + void startElement(const std::string& sElement); + static void endElement(); + static void chars(std::u16string_view rChars); + static void chars(const std::string& rChars); + static void attribute(const std::string& rName, const std::string& rValue); + static void attribute(const std::string& rName, sal_uInt32 nValue); + +private: + std::string msPrefix; +}; +#endif + +class LoggedStream : public Stream +{ +public: + explicit LoggedStream(const std::string& sPrefix); + virtual ~LoggedStream() override; + + void startSectionGroup() override; + void endSectionGroup() override; + void startParagraphGroup() override; + void endParagraphGroup() override; + void startCharacterGroup() override; + void endCharacterGroup() override; + void startShape(css::uno::Reference<css::drawing::XShape> const& xShape) override; + void endShape() override; + void startTextBoxContent() override; + void endTextBoxContent() override; + void text(const sal_uInt8* data, size_t len) override; + void utext(const sal_uInt8* data, size_t len) override; + void positionOffset(const OUString& rText, bool bVertical) override; + void align(const OUString& rText, bool bVertical) override; + void positivePercentage(const OUString& rText) override; + void props(writerfilter::Reference<Properties>::Pointer_t ref) override; + void table(Id name, writerfilter::Reference<Table>::Pointer_t ref) override; + void substream(Id name, writerfilter::Reference<Stream>::Pointer_t ref) override; + void info(const std::string& info) override; + void startGlossaryEntry() override; + void endGlossaryEntry() override; + void checkId(const sal_Int32 nId) override; + + virtual void setDocumentReference(writerfilter::ooxml::OOXMLDocument* /*pDocument*/) override{}; + +protected: + virtual void lcl_startSectionGroup() = 0; + virtual void lcl_endSectionGroup() = 0; + virtual void lcl_startParagraphGroup() = 0; + virtual void lcl_endParagraphGroup() = 0; + virtual void lcl_startCharacterGroup() = 0; + virtual void lcl_endCharacterGroup() = 0; + virtual void lcl_startShape(css::uno::Reference<css::drawing::XShape> const& xShape) = 0; + virtual void lcl_endShape() = 0; + virtual void lcl_startTextBoxContent() = 0; + virtual void lcl_endTextBoxContent() = 0; + virtual void lcl_text(const sal_uInt8* data, size_t len) = 0; + virtual void lcl_utext(const sal_uInt8* data, size_t len) = 0; + virtual void lcl_positionOffset(const OUString& /*rText*/, bool /*bVertical*/) {} + virtual css::awt::Point getPositionOffset() override { return css::awt::Point(); } + virtual void lcl_align(const OUString& /*rText*/, bool /*bVertical*/) {} + virtual void lcl_positivePercentage(const OUString& /*rText*/) {} + virtual void lcl_props(writerfilter::Reference<Properties>::Pointer_t ref) = 0; + virtual void lcl_table(Id name, writerfilter::Reference<Table>::Pointer_t ref) = 0; + virtual void lcl_substream(Id name, writerfilter::Reference<Stream>::Pointer_t ref) = 0; + virtual void lcl_startGlossaryEntry() {} + virtual void lcl_endGlossaryEntry() {} + virtual void lcl_checkId(const sal_Int32) {} + +#ifdef DBG_UTIL + LoggedResourcesHelper mHelper; +#endif +}; + +class LoggedProperties : public Properties +{ +public: + explicit LoggedProperties(const std::string& sPrefix); + virtual ~LoggedProperties() override; + + void attribute(Id name, Value& val) override; + void sprm(Sprm& sprm) override; + +protected: + virtual void lcl_attribute(Id name, Value& val) = 0; + virtual void lcl_sprm(Sprm& sprm) = 0; + +#ifdef DBG_UTIL + LoggedResourcesHelper mHelper; +#endif +}; + +class LoggedTable : public Table +{ +public: + explicit LoggedTable(const std::string& sPrefix); + virtual ~LoggedTable() override; + + void entry(int pos, writerfilter::Reference<Properties>::Pointer_t ref) override; + +protected: + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) = 0; + +#ifdef DBG_UTIL + LoggedResourcesHelper mHelper; +#endif +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/MeasureHandler.cxx b/writerfilter/source/dmapper/MeasureHandler.cxx new file mode 100644 index 000000000..5eea9a5c6 --- /dev/null +++ b/writerfilter/source/dmapper/MeasureHandler.cxx @@ -0,0 +1,136 @@ +/* -*- 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 "MeasureHandler.hxx" +#include "ConversionHelper.hxx" +#include <ooxml/resourceids.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/text/SizeType.hpp> +#include <comphelper/sequence.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + + +MeasureHandler::MeasureHandler() : +LoggedProperties("MeasureHandler"), +m_nMeasureValue( 0 ), +m_nUnit( -1 ), +m_nRowHeightSizeType( text::SizeType::MIN ) +{ +} + + +MeasureHandler::~MeasureHandler() +{ +} + + +void MeasureHandler::lcl_attribute(Id rName, Value & rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch( rName ) + { + case NS_ooxml::LN_CT_TblWidth_type: + { + //can be: NS_ooxml::LN_Value_ST_TblWidth_nil, NS_ooxml::LN_Value_ST_TblWidth_pct, + // NS_ooxml::LN_Value_ST_TblWidth_dxa, NS_ooxml::LN_Value_ST_TblWidth_auto; + m_nUnit = nIntValue; + + if (!m_aInteropGrabBagName.isEmpty()) + { + beans::PropertyValue aValue; + aValue.Name = "type"; + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_TblWidth_nil: aValue.Value <<= OUString("nil"); break; + case NS_ooxml::LN_Value_ST_TblWidth_pct: aValue.Value <<= OUString("pct"); break; + case NS_ooxml::LN_Value_ST_TblWidth_dxa: aValue.Value <<= OUString("dxa"); break; + case NS_ooxml::LN_Value_ST_TblWidth_auto: aValue.Value <<= OUString("auto"); break; + } + m_aInteropGrabBag.push_back(aValue); + } + } + break; + case NS_ooxml::LN_CT_Height_hRule: + { + OUString sHeightType = rVal.getString(); + if ( sHeightType == "exact" ) + m_nRowHeightSizeType = text::SizeType::FIX; + } + break; + case NS_ooxml::LN_CT_TblWidth_w: + m_nMeasureValue = nIntValue; + if (!m_aInteropGrabBagName.isEmpty()) + { + beans::PropertyValue aValue; + aValue.Name = "w"; + aValue.Value <<= nIntValue; + m_aInteropGrabBag.push_back(aValue); + } + break; + case NS_ooxml::LN_CT_Height_val: // a string value + { + m_nUnit = NS_ooxml::LN_Value_ST_TblWidth_dxa; + OUString sHeight = rVal.getString(); + m_nMeasureValue = sHeight.toInt32(); + } + break; + default: + OSL_FAIL( "unknown attribute"); + } +} + + +void MeasureHandler::lcl_sprm(Sprm &) {} + + +sal_Int32 MeasureHandler::getMeasureValue() const +{ + sal_Int32 nRet = 0; + if( m_nMeasureValue != 0 && m_nUnit >= 0 ) + { + // TODO m_nUnit 3 - twip, other values unknown :-( + if( m_nUnit == 3 || sal::static_int_cast<Id>(m_nUnit) == NS_ooxml::LN_Value_ST_TblWidth_dxa) + { + nRet = ConversionHelper::convertTwipToMM100( m_nMeasureValue ); + } + //todo: handle additional width types: + //NS_ooxml::LN_Value_ST_TblWidth_nil, NS_ooxml::LN_Value_ST_TblWidth_pct, + //NS_ooxml::LN_Value_ST_TblWidth_dxa, NS_ooxml::LN_Value_ST_TblWidth_auto; + } + return nRet; +} + +void MeasureHandler::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +beans::PropertyValue MeasureHandler::getInteropGrabBag() +{ + beans::PropertyValue aRet; + aRet.Name = m_aInteropGrabBagName; + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + return aRet; +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/MeasureHandler.hxx b/writerfilter/source/dmapper/MeasureHandler.hxx new file mode 100644 index 000000000..8a6f9639e --- /dev/null +++ b/writerfilter/source/dmapper/MeasureHandler.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include <vector> +#include <com/sun/star/beans/PropertyValue.hpp> + +namespace writerfilter::dmapper +{ +/** Handler for sprms that contain a measure and a unit + - Left indent of tables + - Preferred width of tables + */ +class MeasureHandler : public LoggedProperties +{ + sal_Int32 m_nMeasureValue; + sal_Int32 m_nUnit; + sal_Int16 m_nRowHeightSizeType; //table row height type + + OUString m_aInteropGrabBagName; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + + // Properties + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + +public: + MeasureHandler(); + virtual ~MeasureHandler() override; + + sal_Int32 getMeasureValue() const; + + sal_Int32 getValue() const { return m_nMeasureValue; } + sal_Int32 getUnit() const { return m_nUnit; } + + sal_Int16 GetRowHeightSizeType() const { return m_nRowHeightSizeType; } + void enableInteropGrabBag(const OUString& aName); + css::beans::PropertyValue getInteropGrabBag(); +}; +typedef tools::SvRef<MeasureHandler> MeasureHandlerPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ModelEventListener.cxx b/writerfilter/source/dmapper/ModelEventListener.cxx new file mode 100644 index 000000000..8e20f7ad9 --- /dev/null +++ b/writerfilter/source/dmapper/ModelEventListener.cxx @@ -0,0 +1,117 @@ +/* -*- 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 "ModelEventListener.hxx" +#include "PropertyIds.hxx" +#include <rtl/ustring.hxx> +#include <com/sun/star/document/XEventBroadcaster.hpp> +#include <com/sun/star/text/XTextFieldsSupplier.hpp> +#include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/ReferenceFieldPart.hpp> +#include <com/sun/star/text/ReferenceFieldSource.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/view/XFormLayerAccess.hpp> +#include <tools/diagnose_ex.h> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + + +ModelEventListener::ModelEventListener(bool bIndexes, bool bControls) + : m_bIndexes(bIndexes), + m_bControls(bControls) +{ +} + + +ModelEventListener::~ModelEventListener() +{ +} + + +void ModelEventListener::notifyEvent( const document::EventObject& rEvent ) +{ + if ( rEvent.EventName == "OnFocus" && m_bIndexes) + { + try + { + //remove listener + uno::Reference<document::XEventBroadcaster>(rEvent.Source, uno::UNO_QUERY_THROW )->removeEventListener( + uno::Reference<document::XEventListener>(this)); + + // If we have PAGEREF fields, update fields as well. + uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(rEvent.Source, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xEnumeration = xTextFieldsSupplier->getTextFields()->createEnumeration(); + sal_Int32 nIndex = 0; + while(xEnumeration->hasMoreElements()) + { + try + { + uno::Reference<beans::XPropertySet> xPropertySet(xEnumeration->nextElement(), uno::UNO_QUERY); + sal_Int16 nSource = 0; + xPropertySet->getPropertyValue(getPropertyName(PROP_REFERENCE_FIELD_SOURCE)) >>= nSource; + sal_Int16 nPart = 0; + xPropertySet->getPropertyValue(getPropertyName(PROP_REFERENCE_FIELD_PART)) >>= nPart; + if (nSource == text::ReferenceFieldSource::BOOKMARK && nPart == text::ReferenceFieldPart::PAGE) + ++nIndex; + } + catch( const beans::UnknownPropertyException& ) + { + // doesn't even have such a property? ignore + } + } + if (nIndex) + { + uno::Reference<util::XRefreshable> xRefreshable(xTextFieldsSupplier->getTextFields(), uno::UNO_QUERY); + xRefreshable->refresh(); + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "exception while updating indexes"); + } + } + + if ( rEvent.EventName == "OnFocus" && m_bControls) + { + + // Form design mode is enabled by default in Writer, not in Word. + uno::Reference<frame::XModel> xModel(rEvent.Source, uno::UNO_QUERY); + uno::Reference<view::XFormLayerAccess> xFormLayerAccess(xModel->getCurrentController(), uno::UNO_QUERY); + xFormLayerAccess->setFormDesignMode(false); + } +} + + +void ModelEventListener::disposing( const lang::EventObject& rEvent ) +{ + try + { + uno::Reference<document::XEventBroadcaster>(rEvent.Source, uno::UNO_QUERY_THROW )->removeEventListener( + uno::Reference<document::XEventListener>(this)); + } + catch( const uno::Exception& ) + { + } +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ModelEventListener.hxx b/writerfilter/source/dmapper/ModelEventListener.hxx new file mode 100644 index 000000000..47cd94b80 --- /dev/null +++ b/writerfilter/source/dmapper/ModelEventListener.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/document/XEventListener.hpp> +#include <cppuhelper/implbase.hxx> + +namespace writerfilter::dmapper +{ +class ModelEventListener : public cppu::WeakImplHelper<css::document::XEventListener> +{ + bool m_bIndexes; + bool m_bControls; + +public: + ModelEventListener(bool bIndexes, bool bControls); + virtual ~ModelEventListener() override; + + virtual void SAL_CALL notifyEvent(const css::document::EventObject& Event) override; + virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override; +}; +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/NumberingManager.cxx b/writerfilter/source/dmapper/NumberingManager.cxx new file mode 100644 index 000000000..cb97f59ff --- /dev/null +++ b/writerfilter/source/dmapper/NumberingManager.cxx @@ -0,0 +1,1210 @@ +/* -*- 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 <sal/config.h> + +#include "ConversionHelper.hxx" +#include "NumberingManager.hxx" +#include "StyleSheetTable.hxx" +#include "PropertyIds.hxx" + +#include <ooxml/resourceids.hxx> + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/PositionAndSpaceMode.hpp> +#include <com/sun/star/text/XChapterNumberingSupplier.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/awt/XBitmap.hpp> + +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <tools/UnitConversion.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/string.hxx> +#include <regex> + +using namespace com::sun::star; + +namespace writerfilter::dmapper { + +//--------------------------------------------------- Utility functions +template <typename T> +static beans::PropertyValue lcl_makePropVal(PropertyIds nNameID, T const & aValue) +{ + return {getPropertyName(nNameID), 0, uno::Any(aValue), beans::PropertyState_DIRECT_VALUE}; +} + +static sal_Int32 lcl_findProperty( const uno::Sequence< beans::PropertyValue >& aProps, std::u16string_view sName ) +{ + sal_Int32 i = 0; + sal_Int32 nLen = aProps.getLength( ); + sal_Int32 nPos = -1; + + while ( nPos == -1 && i < nLen ) + { + if ( aProps[i].Name == sName ) + nPos = i; + else + i++; + } + + return nPos; +} + +static void lcl_mergeProperties( const uno::Sequence< beans::PropertyValue >& aSrc, + uno::Sequence< beans::PropertyValue >& aDst ) +{ + for ( const auto& rProp : aSrc ) + { + // Look for the same property in aDst + sal_Int32 nPos = lcl_findProperty( aDst, rProp.Name ); + if ( nPos >= 0 ) + { + // Replace the property value by the one in aSrc + aDst.getArray()[nPos] = rProp; + } + else + { + // Simply add the new value + aDst.realloc( aDst.getLength( ) + 1 ); + aDst.getArray()[ aDst.getLength( ) - 1 ] = rProp; + } + } +} + +//-------------------------------------------- ListLevel implementation +void ListLevel::SetValue( Id nId, sal_Int32 nValue ) +{ + switch( nId ) + { + case NS_ooxml::LN_CT_Lvl_start: + m_nIStartAt = nValue; + break; + case NS_ooxml::LN_CT_NumLvl_startOverride: + m_nStartOverride = nValue; + break; + case NS_ooxml::LN_CT_NumFmt_val: + m_nNFC = nValue; + break; + case NS_ooxml::LN_CT_Lvl_isLgl: + break; + case NS_ooxml::LN_CT_Lvl_legacy: + break; + case NS_ooxml::LN_CT_Lvl_suff: + m_nXChFollow = nValue; + break; + case NS_ooxml::LN_CT_TabStop_pos: + if (nValue < 0) + { + SAL_INFO("writerfilter", + "unsupported list tab stop position " << nValue); + } + else + m_nTabstop = nValue; + break; + default: + OSL_FAIL( "this line should never be reached"); + } + m_bHasValues = true; +} + +void ListLevel::SetCustomNumberFormat(const OUString& rValue) { m_aCustomNumberFormat = rValue; } + +sal_Int16 ListLevel::GetNumberingType(sal_Int16 nDefault) const +{ + return ConversionHelper::ConvertNumberingType(m_nNFC, nDefault); +} + +bool ListLevel::HasValues() const +{ + return m_bHasValues; +} + +void ListLevel::SetParaStyle( const tools::SvRef< StyleSheetEntry >& pStyle ) +{ + if (!pStyle) + return; + m_pParaStyle = pStyle; +} + +uno::Sequence<beans::PropertyValue> ListLevel::GetProperties(bool bDefaults) +{ + uno::Sequence<beans::PropertyValue> aLevelProps = GetLevelProperties(bDefaults); + if (m_pParaStyle) + AddParaProperties( &aLevelProps ); + return aLevelProps; +} + +static bool IgnoreForCharStyle(std::u16string_view aStr, const bool bIsSymbol) +{ + //Names found in PropertyIds.cxx, Lines 56-396 + return (aStr==u"Adjust" || aStr==u"IndentAt" || aStr==u"FirstLineIndent" + || aStr==u"FirstLineOffset" || aStr==u"LeftMargin" + // We need font names when they are different for the bullet and for the text. + // But leave symbols alone, we only want to keep the font style for letters and numbers. + || (bIsSymbol && aStr==u"CharFontName") + ); +} +uno::Sequence< beans::PropertyValue > ListLevel::GetCharStyleProperties( ) +{ + PropertyValueVector_t rProperties; + + const uno::Sequence< beans::PropertyValue > vPropVals = PropertyMap::GetPropertyValues(); + const bool bIsSymbol(GetBulletChar().getLength() <= 1); + for( const auto& rPropNal : vPropVals ) + if (! IgnoreForCharStyle(rPropNal.Name, bIsSymbol)) + rProperties.emplace_back(rPropNal.Name, 0, rPropNal.Value, beans::PropertyState_DIRECT_VALUE); + + return comphelper::containerToSequence(rProperties); +} + +uno::Sequence<beans::PropertyValue> ListLevel::GetLevelProperties(bool bDefaults) +{ + std::vector<beans::PropertyValue> aNumberingProperties; + + if (m_nIStartAt >= 0) + aNumberingProperties.push_back(lcl_makePropVal<sal_Int16>(PROP_START_WITH, m_nIStartAt) ); + else if (bDefaults) + aNumberingProperties.push_back(lcl_makePropVal<sal_Int16>(PROP_START_WITH, 0)); + + sal_Int16 nNumberFormat = -1; + if (m_nNFC == NS_ooxml::LN_Value_ST_NumberFormat_custom) + { + nNumberFormat = ConversionHelper::ConvertCustomNumberFormat(m_aCustomNumberFormat); + } + else + { + nNumberFormat = ConversionHelper::ConvertNumberingType(m_nNFC); + } + if( m_nNFC >= 0) + { + if (m_xGraphicBitmap.is()) + nNumberFormat = style::NumberingType::BITMAP; + aNumberingProperties.push_back(lcl_makePropVal(PROP_NUMBERING_TYPE, nNumberFormat)); + } + + // todo: this is not the bullet char + if( nNumberFormat == style::NumberingType::CHAR_SPECIAL ) + { + if (!GetBulletChar().isEmpty()) + { + aNumberingProperties.push_back(lcl_makePropVal(PROP_BULLET_CHAR, m_sBulletChar->copy(0, 1))); + } + else + { + // If w:lvlText's value is null - set bullet char to zero. + aNumberingProperties.push_back(lcl_makePropVal<sal_Unicode>(PROP_BULLET_CHAR, 0)); + } + } + if (m_xGraphicBitmap.is()) + { + aNumberingProperties.push_back(lcl_makePropVal(PROP_GRAPHIC_BITMAP, m_xGraphicBitmap)); + aNumberingProperties.push_back(lcl_makePropVal(PROP_GRAPHIC_SIZE, m_aGraphicSize)); + } + + if (m_nTabstop.has_value()) + aNumberingProperties.push_back(lcl_makePropVal(PROP_LISTTAB_STOP_POSITION, *m_nTabstop)); + else if (bDefaults) + aNumberingProperties.push_back(lcl_makePropVal<sal_Int16>(PROP_LISTTAB_STOP_POSITION, 0)); + + //TODO: handling of nFLegal? + //TODO: nFNoRestart lower levels do not restart when higher levels are incremented, like: + //1. + //1.1 + //2.2 + //2.3 + //3.4 + +// TODO: sRGBXchNums; array of inherited numbers + +// nXChFollow; following character 0 - tab, 1 - space, 2 - nothing + if (bDefaults || m_nXChFollow != SvxNumberFormat::LISTTAB) + aNumberingProperties.push_back(lcl_makePropVal(PROP_LEVEL_FOLLOW, m_nXChFollow)); + + PropertyIds const aReadIds[] = + { + PROP_ADJUST, PROP_INDENT_AT, PROP_FIRST_LINE_INDENT, + PROP_FIRST_LINE_OFFSET, PROP_LEFT_MARGIN + }; + for(PropertyIds const & rReadId : aReadIds) { + std::optional<PropertyMap::Property> aProp = getProperty(rReadId); + if (aProp) + aNumberingProperties.emplace_back( getPropertyName(aProp->first), 0, aProp->second, beans::PropertyState_DIRECT_VALUE ); + else if (rReadId == PROP_FIRST_LINE_INDENT && bDefaults) + // Writer default is -360 twips, Word default seems to be 0. + aNumberingProperties.emplace_back("FirstLineIndent", 0, uno::Any(static_cast<sal_Int32>(0)), beans::PropertyState_DIRECT_VALUE); + else if (rReadId == PROP_INDENT_AT && bDefaults) + // Writer default is 720 twips, Word default seems to be 0. + aNumberingProperties.emplace_back("IndentAt", 0, + uno::Any(static_cast<sal_Int32>(0)), + beans::PropertyState_DIRECT_VALUE); + } + + std::optional<PropertyMap::Property> aPropFont = getProperty(PROP_CHAR_FONT_NAME); + if (aPropFont) + aNumberingProperties.emplace_back( getPropertyName(PROP_BULLET_FONT_NAME), 0, aPropFont->second, beans::PropertyState_DIRECT_VALUE ); + + return comphelper::containerToSequence(aNumberingProperties); +} + +// Add the properties only if they do not already exist in the sequence. +void ListLevel::AddParaProperties( uno::Sequence< beans::PropertyValue >* props ) +{ + uno::Sequence< beans::PropertyValue >& aProps = *props; + + OUString sFirstLineIndent = getPropertyName( + PROP_FIRST_LINE_INDENT ); + OUString sIndentAt = getPropertyName( + PROP_INDENT_AT ); + + bool hasFirstLineIndent = lcl_findProperty( aProps, sFirstLineIndent ); + bool hasIndentAt = lcl_findProperty( aProps, sIndentAt ); + + if( hasFirstLineIndent && hasIndentAt ) + return; // has them all, nothing to add + + const uno::Sequence< beans::PropertyValue > aParaProps = m_pParaStyle->pProperties->GetPropertyValues( ); + + // ParaFirstLineIndent -> FirstLineIndent + // ParaLeftMargin -> IndentAt + + OUString sParaIndent = getPropertyName( + PROP_PARA_FIRST_LINE_INDENT ); + OUString sParaLeftMargin = getPropertyName( + PROP_PARA_LEFT_MARGIN ); + + for ( const auto& rParaProp : aParaProps ) + { + if ( !hasFirstLineIndent && rParaProp.Name == sParaIndent ) + { + aProps.realloc( aProps.getLength() + 1 ); + auto pProps = aProps.getArray(); + pProps[aProps.getLength( ) - 1] = rParaProp; + pProps[aProps.getLength( ) - 1].Name = sFirstLineIndent; + } + else if ( !hasIndentAt && rParaProp.Name == sParaLeftMargin ) + { + aProps.realloc( aProps.getLength() + 1 ); + auto pProps = aProps.getArray(); + pProps[aProps.getLength( ) - 1] = rParaProp; + pProps[aProps.getLength( ) - 1].Name = sIndentAt; + } + + } +} + +NumPicBullet::NumPicBullet() + : m_nId(0) +{ +} + +NumPicBullet::~NumPicBullet() +{ +} + +void NumPicBullet::SetId(sal_Int32 nId) +{ + m_nId = nId; +} + +void NumPicBullet::SetShape(uno::Reference<drawing::XShape> const& xShape) +{ + m_xShape = xShape; +} + + +//--------------------------------------- AbstractListDef implementation + +AbstractListDef::AbstractListDef( ) : + m_nId( -1 ) +{ +} + +AbstractListDef::~AbstractListDef( ) +{ +} + +void AbstractListDef::SetValue( sal_uInt32 nSprmId ) +{ + switch( nSprmId ) + { + case NS_ooxml::LN_CT_AbstractNum_tmpl: + break; + default: + OSL_FAIL( "this line should never be reached"); + } +} + +ListLevel::Pointer AbstractListDef::GetLevel( sal_uInt16 nLvl ) +{ + ListLevel::Pointer pLevel; + if ( m_aLevels.size( ) > nLvl ) + pLevel = m_aLevels[ nLvl ]; + return pLevel; +} + +void AbstractListDef::AddLevel( sal_uInt16 nLvl ) +{ + if ( nLvl >= m_aLevels.size() ) + m_aLevels.resize( nLvl+1 ); + + if (!m_aLevels[nLvl]) + { + m_aLevels[nLvl] = new ListLevel; + } + + m_pCurrentLevel = m_aLevels[nLvl]; +} + +uno::Sequence<uno::Sequence<beans::PropertyValue>> AbstractListDef::GetPropertyValues(bool bDefaults) +{ + uno::Sequence< uno::Sequence< beans::PropertyValue > > result( sal_Int32( m_aLevels.size( ) ) ); + uno::Sequence< beans::PropertyValue >* aResult = result.getArray( ); + + int nLevels = m_aLevels.size( ); + for ( int i = 0; i < nLevels; i++ ) + { + if (m_aLevels[i]) + aResult[i] = m_aLevels[i]->GetProperties(bDefaults); + } + + return result; +} + +const OUString& AbstractListDef::MapListId(OUString const& rId) +{ + if (!m_oListId) + { + m_oListId = rId; + } + return *m_oListId; +} + +//---------------------------------------------- ListDef implementation + +ListDef::ListDef( ) +{ +} + +ListDef::~ListDef( ) +{ +} + +const OUString & ListDef::GetStyleName(sal_Int32 const nId, + uno::Reference<container::XNameContainer> const& xStyles) +{ + if (xStyles.is()) + { + OUString sStyleName = "WWNum" + OUString::number( nId ); + + while (xStyles->hasByName(sStyleName)) // unique + { + sStyleName += "a"; + } + + m_StyleName = sStyleName; + } + else + { +// fails in rtftok test assert(!m_StyleName.isEmpty()); // must be inited first + } + + return m_StyleName; +} + +uno::Sequence<uno::Sequence<beans::PropertyValue>> ListDef::GetMergedPropertyValues() +{ + if (!m_pAbstractDef) + return uno::Sequence< uno::Sequence< beans::PropertyValue > >(); + + // [1] Call the same method on the abstract list + uno::Sequence<uno::Sequence<beans::PropertyValue>> aAbstract + = m_pAbstractDef->GetPropertyValues(/*bDefaults=*/true); + auto aAbstractRange = asNonConstRange(aAbstract); + + // [2] Call the upper class method + uno::Sequence<uno::Sequence<beans::PropertyValue>> aThis + = AbstractListDef::GetPropertyValues(/*bDefaults=*/false); + + // Merge the results of [2] in [1] + sal_Int32 nThisCount = aThis.getLength( ); + sal_Int32 nAbstractCount = aAbstract.getLength( ); + for ( sal_Int32 i = 0; i < nThisCount && i < nAbstractCount; i++ ) + { + uno::Sequence< beans::PropertyValue > level = aThis[i]; + if (level.hasElements() && GetLevel(i)->HasValues()) + { + // If the element contains something, merge it, but ignore stub overrides. + lcl_mergeProperties( level, aAbstractRange[i] ); + } + } + + return aAbstract; +} + +static uno::Reference< container::XNameContainer > lcl_getUnoNumberingStyles( + uno::Reference<lang::XMultiServiceFactory> const& xFactory) +{ + uno::Reference< container::XNameContainer > xStyles; + + try + { + uno::Reference< style::XStyleFamiliesSupplier > xFamilies( xFactory, uno::UNO_QUERY_THROW ); + uno::Any oFamily = xFamilies->getStyleFamilies( )->getByName("NumberingStyles"); + + oFamily >>= xStyles; + } + catch ( const uno::Exception & ) + { + } + + return xStyles; +} + +/// Rank the list in terms of suitability for becoming the Outline numbering rule in LO. +sal_uInt16 ListDef::GetChapterNumberingWeight() const +{ + sal_Int16 nWeight = 0; + const sal_Int8 nAbstLevels = m_pAbstractDef ? m_pAbstractDef->Size() : 0; + for (sal_Int8 nLevel = 0; nLevel < nAbstLevels; ++nLevel) + { + const ListLevel::Pointer pAbsLevel = m_pAbstractDef->GetLevel(nLevel); + if (!pAbsLevel) + continue; + const StyleSheetEntryPtr pParaStyle = pAbsLevel->GetParaStyle(); + if (!pParaStyle) + continue; + const StyleSheetPropertyMap& rProps = *pParaStyle->pProperties; + // In LO, the level's paraStyle outlineLevel always matches this listLevel. + // An undefined listLevel is treated as the first level. + sal_Int8 nListLevel = std::clamp<sal_Int8>(rProps.GetListLevel(), 0, 9); + if (nListLevel != nLevel || rProps.GetOutlineLevel() != nLevel) + return 0; + else if (pAbsLevel->GetNumberingType(style::NumberingType::NUMBER_NONE) + != style::NumberingType::NUMBER_NONE) + { + // Arbitrarily chosen weighting factors - trying to round-trip LO choices if possible. + // LibreOffice always saves Outline rule (usually containing heading styles) as numId 1. + sal_uInt16 nWeightingFactor = GetId() == 1 ? 8 : 1; + if (pParaStyle->sStyleIdentifierD.startsWith("Heading") ) + ++nWeightingFactor; + nWeight += nWeightingFactor; + } + } + return nWeight; +} + +void ListDef::CreateNumberingRules( DomainMapper& rDMapper, + uno::Reference<lang::XMultiServiceFactory> const& xFactory, sal_Int16 nOutline) +{ + // Get the UNO Numbering styles + uno::Reference< container::XNameContainer > xStyles = lcl_getUnoNumberingStyles( xFactory ); + + // Do the whole thing + if( !(!m_xNumRules.is() && xFactory.is() && xStyles.is( )) ) + return; + + try + { + // Create the numbering style + uno::Reference< beans::XPropertySet > xStyle ( + xFactory->createInstance("com.sun.star.style.NumberingStyle"), + uno::UNO_QUERY_THROW ); + + if (GetId() == nOutline) + m_StyleName = "Outline"; //SwNumRule.GetOutlineRuleName() + else + xStyles->insertByName(GetStyleName(GetId(), xStyles), css::uno::Any(xStyle)); + + uno::Any oStyle = xStyles->getByName(GetStyleName()); + xStyle.set( oStyle, uno::UNO_QUERY_THROW ); + + // Get the default OOo Numbering style rules + uno::Any aRules = xStyle->getPropertyValue( getPropertyName( PROP_NUMBERING_RULES ) ); + aRules >>= m_xNumRules; + + uno::Sequence<uno::Sequence<beans::PropertyValue>> aProps = GetMergedPropertyValues(); + + sal_Int32 nAbstLevels = m_pAbstractDef ? m_pAbstractDef->Size() : 0; + sal_Int32 nLevel = 0; + while ( nLevel < nAbstLevels ) + { + ListLevel::Pointer pAbsLevel = m_pAbstractDef->GetLevel( nLevel ); + ListLevel::Pointer pLevel = GetLevel( nLevel ); + + // Get the merged level properties + auto aLvlProps = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aProps[nLevel]); + + // Get the char style + auto aAbsCharStyleProps = pAbsLevel + ? pAbsLevel->GetCharStyleProperties() + : uno::Sequence<beans::PropertyValue>(); + if ( pLevel ) + { + uno::Sequence< beans::PropertyValue >& rAbsCharStyleProps = aAbsCharStyleProps; + uno::Sequence< beans::PropertyValue > aCharStyleProps = + pLevel->GetCharStyleProperties( ); + uno::Sequence< beans::PropertyValue >& rCharStyleProps = aCharStyleProps; + lcl_mergeProperties( rAbsCharStyleProps, rCharStyleProps ); + } + + // Change the sequence into a vector + auto aStyleProps + = comphelper::sequenceToContainer<PropertyValueVector_t>(aAbsCharStyleProps); + + //create (or find) a character style containing the character + // attributes of the symbol and apply it to the numbering level + OUString sStyle = rDMapper.getOrCreateCharStyle(aStyleProps, /*bAlwaysCreate=*/true); + aLvlProps.push_back( + comphelper::makePropertyValue(getPropertyName(PROP_CHAR_STYLE_NAME), sStyle)); + + OUString sText = pAbsLevel + ? pAbsLevel->GetBulletChar() + : OUString(); + // Inherit <w:lvlText> from the abstract level in case the override would be empty. + if (pLevel && pLevel->HasBulletChar()) + sText = pLevel->GetBulletChar( ); + + aLvlProps.push_back(comphelper::makePropertyValue(getPropertyName(PROP_LIST_FORMAT), sText)); + + aLvlProps.push_back(comphelper::makePropertyValue(getPropertyName(PROP_POSITION_AND_SPACE_MODE), sal_Int16(text::PositionAndSpaceMode::LABEL_ALIGNMENT))); + + // Replace the numbering rules for the level + m_xNumRules->replaceByIndex(nLevel, uno::Any(comphelper::containerToSequence(aLvlProps))); + + // Handle the outline level here + if (GetId() == nOutline && pAbsLevel && pAbsLevel->GetParaStyle()) + { + uno::Reference< text::XChapterNumberingSupplier > xOutlines ( + xFactory, uno::UNO_QUERY_THROW ); + uno::Reference< container::XIndexReplace > xOutlineRules = + xOutlines->getChapterNumberingRules( ); + + StyleSheetEntryPtr pParaStyle = pAbsLevel->GetParaStyle( ); + pParaStyle->bAssignedAsChapterNumbering = true; + aLvlProps.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HEADING_STYLE_NAME), pParaStyle->sConvertedStyleName)); + + xOutlineRules->replaceByIndex(nLevel, uno::Any(comphelper::containerToSequence(aLvlProps))); + } + + nLevel++; + } + + // Create the numbering style for these rules + OUString sNumRulesName = getPropertyName( PROP_NUMBERING_RULES ); + xStyle->setPropertyValue( sNumRulesName, uno::Any( m_xNumRules ) ); + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "" ); + assert( !"Incorrect argument to UNO call" ); + } + catch( const uno::RuntimeException& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "" ); + assert( !"Incorrect argument to UNO call" ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "" ); + } + +} + +//------------------------------------- NumberingManager implementation + + +ListsManager::ListsManager(DomainMapper& rDMapper, + const uno::Reference<lang::XMultiServiceFactory> & xFactory) + : LoggedProperties("ListsManager") + , LoggedTable("ListsManager") + , m_rDMapper(rDMapper) + , m_xFactory(xFactory) +{ +} + +ListsManager::~ListsManager( ) +{ + DisposeNumPicBullets(); +} + +void ListsManager::DisposeNumPicBullets( ) +{ + uno::Reference<drawing::XShape> xShape; + for (const auto& rNumPicBullet : m_aNumPicBullets) + { + xShape = rNumPicBullet->GetShape(); + if (xShape.is()) + { + uno::Reference<lang::XComponent> xShapeComponent(xShape, uno::UNO_QUERY); + xShapeComponent->dispose(); + } + } +} + +void ListsManager::lcl_attribute( Id nName, Value& rVal ) +{ + ListLevel::Pointer pCurrentLvl; + + if (nName != NS_ooxml::LN_CT_NumPicBullet_numPicBulletId) + { + OSL_ENSURE( m_pCurrentDefinition, "current entry has to be set here"); + if(!m_pCurrentDefinition) + return ; + pCurrentLvl = m_pCurrentDefinition->GetCurrentLevel( ); + } + else + { + SAL_WARN_IF(!m_pCurrentNumPicBullet, "writerfilter", "current entry has to be set here"); + if (!m_pCurrentNumPicBullet) + return; + } + int nIntValue = rVal.getInt(); + + + switch(nName) + { + case NS_ooxml::LN_CT_LevelText_val: + { + if(pCurrentLvl) + { + //if the BulletChar is a soft-hyphen (0xad) + //replace it with a hard-hyphen (0x2d) + //-> this fixes missing hyphen export in PDF etc. + // see tdf#101626 + std::string sLevelText = rVal.getString().replace(0xad, 0x2d).toUtf8().getStr(); + + // DOCX level-text contains levels definition in format "%1.%2.%3" + // we need to convert it to LO internal representation: "%1%.%2%.%3%" + static const std::regex aTokenRegex("(%\\d)"); + sLevelText = std::regex_replace(sLevelText, aTokenRegex, "$1%"); + pCurrentLvl->SetBulletChar( OUString::fromUtf8(sLevelText) ); + } + } + break; + case NS_ooxml::LN_CT_Lvl_start: + case NS_ooxml::LN_CT_Lvl_numFmt: + case NS_ooxml::LN_CT_NumFmt_format: + case NS_ooxml::LN_CT_NumFmt_val: + case NS_ooxml::LN_CT_Lvl_isLgl: + case NS_ooxml::LN_CT_Lvl_legacy: + if ( pCurrentLvl ) + { + if (nName == NS_ooxml::LN_CT_NumFmt_format) + { + pCurrentLvl->SetCustomNumberFormat(rVal.getString()); + } + else + { + pCurrentLvl->SetValue(nName, sal_Int32(nIntValue)); + } + } + break; + case NS_ooxml::LN_CT_Num_numId: + m_pCurrentDefinition->SetId( rVal.getString().toInt32( ) ); + break; + case NS_ooxml::LN_CT_AbstractNum_nsid: + m_pCurrentDefinition->SetId( nIntValue ); + break; + case NS_ooxml::LN_CT_AbstractNum_tmpl: + AbstractListDef::SetValue( nName ); + break; + case NS_ooxml::LN_CT_NumLvl_ilvl: + //add a new level to the level vector and make it the current one + m_pCurrentDefinition->AddLevel(rVal.getString().toUInt32()); + break; + case NS_ooxml::LN_CT_Lvl_ilvl: + m_pCurrentDefinition->AddLevel(rVal.getString().toUInt32()); + break; + case NS_ooxml::LN_CT_AbstractNum_abstractNumId: + { + // This one corresponds to the AbstractNum Id definition + // The reference to the abstract num is in the sprm method + sal_Int32 nVal = rVal.getString().toInt32(); + m_pCurrentDefinition->SetId( nVal ); + } + break; + case NS_ooxml::LN_CT_Ind_start: + case NS_ooxml::LN_CT_Ind_left: + if ( pCurrentLvl ) + pCurrentLvl->Insert( + PROP_INDENT_AT, uno::Any( ConversionHelper::convertTwipToMM100( nIntValue ) )); + break; + case NS_ooxml::LN_CT_Ind_hanging: + if ( pCurrentLvl ) + pCurrentLvl->Insert( + PROP_FIRST_LINE_INDENT, uno::Any( - ConversionHelper::convertTwipToMM100( nIntValue ) )); + break; + case NS_ooxml::LN_CT_Ind_firstLine: + if ( pCurrentLvl ) + pCurrentLvl->Insert( + PROP_FIRST_LINE_INDENT, uno::Any( ConversionHelper::convertTwipToMM100( nIntValue ) )); + break; + case NS_ooxml::LN_CT_Lvl_tplc: //template code - unsupported + case NS_ooxml::LN_CT_Lvl_tentative: //marks level as unused in the document - unsupported + break; + case NS_ooxml::LN_CT_TabStop_pos: + { + //no paragraph attributes in ListTable char style sheets + if ( pCurrentLvl ) + pCurrentLvl->SetValue( nName, + ConversionHelper::convertTwipToMM100( nIntValue ) ); + } + break; + case NS_ooxml::LN_CT_TabStop_val: + { + // TODO Do something of that + } + break; + case NS_ooxml::LN_CT_NumPicBullet_numPicBulletId: + m_pCurrentNumPicBullet->SetId(rVal.getString().toInt32()); + break; + default: + SAL_WARN("writerfilter", "ListsManager::lcl_attribute: unhandled token: " << nName); + } +} + +void ListsManager::lcl_sprm( Sprm& rSprm ) +{ + //fill the attributes of the style sheet + sal_uInt32 nSprmId = rSprm.getId(); + if( !(m_pCurrentDefinition || + nSprmId == NS_ooxml::LN_CT_Numbering_abstractNum || + nSprmId == NS_ooxml::LN_CT_Numbering_num || + (nSprmId == NS_ooxml::LN_CT_NumPicBullet_pict && m_pCurrentNumPicBullet) || + nSprmId == NS_ooxml::LN_CT_Numbering_numPicBullet)) + return; + + static bool bIsStartVisited = false; + sal_Int32 nIntValue = rSprm.getValue()->getInt(); + switch( nSprmId ) + { + case NS_ooxml::LN_CT_Numbering_abstractNum: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + { + //create a new Abstract list entry + OSL_ENSURE( !m_pCurrentDefinition, "current entry has to be NULL here"); + m_pCurrentDefinition = new AbstractListDef; + pProperties->resolve( *this ); + //append it to the table + m_aAbstractLists.push_back( m_pCurrentDefinition ); + m_pCurrentDefinition = AbstractListDef::Pointer(); + } + } + break; + case NS_ooxml::LN_CT_Numbering_num: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + { + // Create a new list entry + OSL_ENSURE( !m_pCurrentDefinition, "current entry has to be NULL here"); + ListDef::Pointer listDef( new ListDef ); + m_pCurrentDefinition = listDef.get(); + pProperties->resolve( *this ); + //append it to the table + m_aLists.push_back( listDef ); + + m_pCurrentDefinition = AbstractListDef::Pointer(); + } + } + break; + case NS_ooxml::LN_CT_Numbering_numPicBullet: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + NumPicBullet::Pointer numPicBullet(new NumPicBullet()); + m_pCurrentNumPicBullet = numPicBullet; + pProperties->resolve(*this); + m_aNumPicBullets.push_back(numPicBullet); + m_pCurrentNumPicBullet = NumPicBullet::Pointer(); + } + } + break; + case NS_ooxml::LN_CT_NumPicBullet_pict: + { + uno::Reference<drawing::XShape> xShape = m_rDMapper.PopPendingShape(); + + m_pCurrentNumPicBullet->SetShape(xShape); + } + break; + case NS_ooxml::LN_CT_Lvl_lvlPicBulletId: + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + uno::Reference<drawing::XShape> xShape; + for (const auto& rNumPicBullet : m_aNumPicBullets) + { + if (rNumPicBullet->GetId() == nIntValue) + { + xShape = rNumPicBullet->GetShape(); + break; + } + } + if (xShape.is()) + { + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + try + { + uno::Any aAny = xPropertySet->getPropertyValue("Graphic"); + if (aAny.has<uno::Reference<graphic::XGraphic>>() && pCurrentLevel) + { + auto xGraphic = aAny.get<uno::Reference<graphic::XGraphic>>(); + if (xGraphic.is()) + { + uno::Reference<awt::XBitmap> xBitmap(xGraphic, uno::UNO_QUERY); + pCurrentLevel->SetGraphicBitmap(xBitmap); + } + } + } + catch (const beans::UnknownPropertyException&) + {} + + // Respect only the aspect ratio of the picture, not its size. + awt::Size aPrefSize = xShape->getSize(); + if ( aPrefSize.Height * aPrefSize.Width != 0 ) + { + // See SwDefBulletConfig::InitFont(), default height is 14. + const int nFontHeight = 14; + // Point -> mm100. + const int nHeight = nFontHeight * 35; + int nWidth = (nHeight * aPrefSize.Width) / aPrefSize.Height; + + awt::Size aSize( o3tl::toTwips(nWidth, o3tl::Length::mm100), o3tl::toTwips(nHeight, o3tl::Length::mm100) ); + pCurrentLevel->SetGraphicSize( aSize ); + } + else + { + awt::Size aSize( o3tl::toTwips(aPrefSize.Width, o3tl::Length::mm100), o3tl::toTwips(aPrefSize.Height, o3tl::Length::mm100) ); + pCurrentLevel->SetGraphicSize( aSize ); + } + } + } + break; + case NS_ooxml::LN_CT_Num_abstractNumId: + { + sal_Int32 nAbstractNumId = rSprm.getValue()->getInt(); + ListDef* pListDef = dynamic_cast< ListDef* >( m_pCurrentDefinition.get( ) ); + if ( pListDef != nullptr ) + { + // The current def should be a ListDef + pListDef->SetAbstractDefinition( + GetAbstractList( nAbstractNumId ) ); + } + } + break; + case NS_ooxml::LN_CT_AbstractNum_multiLevelType: + break; + case NS_ooxml::LN_CT_AbstractNum_tmpl: + AbstractListDef::SetValue( nSprmId ); + break; + case NS_ooxml::LN_CT_AbstractNum_lvl: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_Lvl_start: + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + pCurrentLevel->SetValue( nSprmId, nIntValue ); + bIsStartVisited = true; + break; + case NS_ooxml::LN_CT_Lvl_numFmt: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + } + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + if( !bIsStartVisited ) + { + pCurrentLevel->SetValue( NS_ooxml::LN_CT_Lvl_start, 0 ); + bIsStartVisited = true; + } + } + } + break; + case NS_ooxml::LN_CT_Lvl_isLgl: + case NS_ooxml::LN_CT_Lvl_legacy: + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + pCurrentLevel->SetValue(nSprmId, nIntValue); + } + break; + case NS_ooxml::LN_CT_Lvl_suff: + { + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + SvxNumberFormat::LabelFollowedBy value = SvxNumberFormat::LISTTAB; + if( rSprm.getValue()->getString() == "tab" ) + value = SvxNumberFormat::LISTTAB; + else if( rSprm.getValue()->getString() == "space" ) + value = SvxNumberFormat::SPACE; + else if( rSprm.getValue()->getString() == "nothing" ) + value = SvxNumberFormat::NOTHING; + else + SAL_WARN( "writerfilter", "Unknown ST_LevelSuffix value " + << rSprm.getValue()->getString()); + pCurrentLevel->SetValue( nSprmId, value ); + } + } + break; + case NS_ooxml::LN_CT_Lvl_lvlText: + case NS_ooxml::LN_CT_Lvl_rPr : //contains LN_EG_RPrBase_rFonts + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_NumLvl_lvl: + { + // overwrite level + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_Lvl_lvlJc: + { + sal_Int16 nValue = text::HoriOrientation::NONE; + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_Jc_left: + case NS_ooxml::LN_Value_ST_Jc_start: + nValue = text::HoriOrientation::LEFT; + break; + case NS_ooxml::LN_Value_ST_Jc_center: + nValue = text::HoriOrientation::CENTER; + break; + case NS_ooxml::LN_Value_ST_Jc_right: + case NS_ooxml::LN_Value_ST_Jc_end: + nValue = text::HoriOrientation::RIGHT; + break; + } + + if (nValue != text::HoriOrientation::NONE) + { + if (ListLevel::Pointer pLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + pLevel->Insert( + PROP_ADJUST, uno::Any( nValue ) ); + } + } + } + break; + case NS_ooxml::LN_CT_Lvl_pPr: + case NS_ooxml::LN_CT_PPrBase_ind: + { + //todo: how to handle paragraph properties within numbering levels (except LeftIndent and FirstLineIndent)? + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_PPrBase_tabs: + case NS_ooxml::LN_CT_Tabs_tab: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if(pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_Lvl_pStyle: + { + OUString sStyleName = rSprm.getValue( )->getString( ); + if (ListLevel::Pointer pLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + StyleSheetTablePtr pStylesTable = m_rDMapper.GetStyleSheetTable( ); + const StyleSheetEntryPtr pStyle = pStylesTable->FindStyleSheetByISTD( sStyleName ); + pLevel->SetParaStyle( pStyle ); + } + } + break; + case NS_ooxml::LN_CT_Num_lvlOverride: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_NumLvl_startOverride: + { + if(m_pCurrentDefinition) + { + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + pCurrentLevel->SetValue(NS_ooxml::LN_CT_NumLvl_startOverride, nIntValue); + } + } + } + break; + case NS_ooxml::LN_CT_AbstractNum_numStyleLink: + { + OUString sStyleName = rSprm.getValue( )->getString( ); + m_pCurrentDefinition->SetNumStyleLink(sStyleName); + } + break; + case NS_ooxml::LN_CT_AbstractNum_styleLink: + { + OUString sStyleName = rSprm.getValue()->getString(); + m_pCurrentDefinition->SetStyleLink(sStyleName); + } + break; + case NS_ooxml::LN_EG_RPrBase_rFonts: //contains font properties + case NS_ooxml::LN_EG_RPrBase_color: + case NS_ooxml::LN_EG_RPrBase_u: + case NS_ooxml::LN_EG_RPrBase_sz: + case NS_ooxml::LN_EG_RPrBase_lang: + case NS_ooxml::LN_EG_RPrBase_eastAsianLayout: + //no break! + default: + if (ListLevel::Pointer pCurrentLevel = m_pCurrentDefinition->GetCurrentLevel()) + { + m_rDMapper.PushListProperties(pCurrentLevel.get()); + m_rDMapper.sprm( rSprm ); + m_rDMapper.PopListProperties(); + } + } +} + +void ListsManager::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref ) +{ + if( m_rDMapper.IsOOXMLImport() || m_rDMapper.IsRTFImport() ) + { + ref->resolve(*this); + } + else + { + // Create AbstractListDef's + OSL_ENSURE( !m_pCurrentDefinition, "current entry has to be NULL here"); + m_pCurrentDefinition = new AbstractListDef( ); + ref->resolve(*this); + //append it to the table + m_aAbstractLists.push_back( m_pCurrentDefinition ); + m_pCurrentDefinition = AbstractListDef::Pointer(); + } +} + +AbstractListDef::Pointer ListsManager::GetAbstractList( sal_Int32 nId ) +{ + for (const auto& listDef : m_aAbstractLists) + { + if (listDef->GetId( ) == nId) + { + if (listDef->GetNumStyleLink().getLength() > 0) + { + // If the abstract num has a style linked, check the linked style's number id. + StyleSheetTablePtr pStylesTable = m_rDMapper.GetStyleSheetTable( ); + + const StyleSheetEntryPtr pStyleSheetEntry = + pStylesTable->FindStyleSheetByISTD(listDef->GetNumStyleLink() ); + + const StyleSheetPropertyMap* pStyleSheetProperties = + pStyleSheetEntry ? pStyleSheetEntry->pProperties.get() : nullptr; + + if( pStyleSheetProperties && pStyleSheetProperties->GetListId() >= 0 ) + { + ListDef::Pointer pList = GetList( pStyleSheetProperties->GetListId() ); + if ( pList!=nullptr ) + return pList->GetAbstractDefinition(); + } + + // In stylesheet we did not found anything useful. Try to find base abstractnum having this stylelink + for (const auto & baseListDef : m_aAbstractLists) + { + if (baseListDef->GetStyleLink() == listDef->GetNumStyleLink()) + { + return baseListDef; + } + } + } + + // Standalone abstract list + return listDef; + } + } + + return nullptr; +} + +ListDef::Pointer ListsManager::GetList( sal_Int32 nId ) +{ + ListDef::Pointer pList; + if (nId == -1) + return pList; + + int nLen = m_aLists.size( ); + int i = 0; + while ( !pList && i < nLen ) + { + if ( m_aLists[i]->GetId( ) == nId ) + pList = m_aLists[i]; + i++; + } + + // nId 0 is only valid for abstractNum, not numId (which has an abstract definition) + assert(!pList || nId || !pList->GetAbstractDefinition() || m_rDMapper.IsRTFImport()); + + return pList; +} + +void ListsManager::CreateNumberingRules( ) +{ + // Try to determine which numId would best work as LO's Chapter Numbering Outline rule. + // (The best fix for many import bugs is just to prevent ANY assignment as chapter numbering.) + sal_Int16 nChosenAsChapterNumberingId = -1; + sal_uInt16 nHighestWeight = 5; // arbitrarily chosen minimum threshold + for (const auto& rList : m_aLists) + { + sal_uInt16 nWeight = rList->GetChapterNumberingWeight(); + if (nWeight > nHighestWeight) + { + nHighestWeight = nWeight; + nChosenAsChapterNumberingId = rList->GetId(); + //Optimization: if the weight cannot be beaten anymore, then quit early + if (nHighestWeight > 17) + break; + } + } + + // Loop over the definitions + for ( const auto& rList : m_aLists ) + { + rList->CreateNumberingRules(m_rDMapper, m_xFactory, nChosenAsChapterNumberingId); + } + m_rDMapper.GetStyleSheetTable()->ReApplyInheritedOutlineLevelFromChapterNumbering(); + m_rDMapper.GetStyleSheetTable()->ApplyNumberingStyleNameToParaStyles(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/NumberingManager.hxx b/writerfilter/source/dmapper/NumberingManager.hxx new file mode 100644 index 000000000..2c1e0af3c --- /dev/null +++ b/writerfilter/source/dmapper/NumberingManager.hxx @@ -0,0 +1,250 @@ +/* -*- 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 . + */ + +#pragma once + +#include "PropertyMap.hxx" + +#include "DomainMapper.hxx" +#include "LoggedResources.hxx" +#include "StyleSheetTable.hxx" + +#include <editeng/numitem.hxx> + +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/awt/XBitmap.hpp> + +namespace writerfilter::dmapper { + +class DomainMapper; +class StyleSheetEntry; + + +/** Class representing the numbering level properties. + */ +class ListLevel : public PropertyMap +{ + sal_Int32 m_nIStartAt; //LN_CT_Lvl_start + sal_Int32 m_nStartOverride; + sal_Int32 m_nNFC; //LN_CT_Lvl_numFmt + /// LN_CT_NumFmt_format, in case m_nNFC is custom. + OUString m_aCustomNumberFormat; + sal_Int16 m_nXChFollow; //LN_IXCHFOLLOW + std::optional<OUString> m_sBulletChar; + css::awt::Size m_aGraphicSize; + css::uno::Reference<css::awt::XBitmap> m_xGraphicBitmap; + std::optional<sal_Int32> m_nTabstop; + tools::SvRef< StyleSheetEntry > m_pParaStyle; + bool m_bHasValues = false; + +public: + + typedef tools::SvRef< ListLevel > Pointer; + + ListLevel() : + m_nIStartAt(-1) + ,m_nStartOverride(-1) + ,m_nNFC(-1) + ,m_nXChFollow(SvxNumberFormat::LISTTAB) + {} + + // Setters for the import + void SetValue( Id nId, sal_Int32 nValue ); + void SetCustomNumberFormat(const OUString& rValue); + void SetBulletChar( const OUString& sValue ) { m_sBulletChar = sValue; }; + void SetGraphicSize( const css::awt::Size& aValue ) { m_aGraphicSize = aValue; }; + + void SetGraphicBitmap(css::uno::Reference<css::awt::XBitmap> const& xGraphicBitmap) + { m_xGraphicBitmap = xGraphicBitmap; } + void SetParaStyle( const tools::SvRef< StyleSheetEntry >& pStyle ); + + // Getters + sal_Int16 GetNumberingType(sal_Int16 nDefault) const; + bool HasBulletChar() const { return m_sBulletChar.has_value(); }; + OUString GetBulletChar( ) const { return m_sBulletChar.has_value()? *m_sBulletChar : OUString(); }; + const tools::SvRef< StyleSheetEntry >& GetParaStyle( ) const { return m_pParaStyle; }; + sal_Int32 GetStartOverride() const { return m_nStartOverride; }; + /// Determines if SetValue() was called at least once. + bool HasValues() const; + + // UNO mapping functions + css::uno::Sequence<css::beans::PropertyValue> GetProperties(bool bDefaults); + + css::uno::Sequence<css::beans::PropertyValue> GetCharStyleProperties(); +private: + + css::uno::Sequence<css::beans::PropertyValue> GetLevelProperties(bool bDefaults); + + void AddParaProperties(css::uno::Sequence<css::beans::PropertyValue>* pProps); +}; + +/// Represents a numbering picture bullet: an id and a graphic. +class NumPicBullet final : public virtual SvRefBase +{ +public: + typedef tools::SvRef<NumPicBullet> Pointer; + NumPicBullet(); + ~NumPicBullet() override; + + void SetId(sal_Int32 nId); + sal_Int32 GetId() const { return m_nId;} + void SetShape(css::uno::Reference<css::drawing::XShape> const& xShape); + const css::uno::Reference<css::drawing::XShape>& GetShape() const { return m_xShape; } +private: + sal_Int32 m_nId; + css::uno::Reference<css::drawing::XShape> m_xShape; +}; + +class AbstractListDef : public virtual SvRefBase +{ +private: + // The ID member reflects either the abstractNumId or the numId + // depending on the use of the class + sal_Int32 m_nId; + + // Properties of each level. This can also reflect the overridden + // levels of a numbering. + ::std::vector< ListLevel::Pointer > m_aLevels; + + // Only used during the numbering import + ListLevel::Pointer m_pCurrentLevel; + + // The style name linked to. + OUString m_sNumStyleLink; + + // This abstract numbering is a base definition for this style + OUString m_sStyleLink; + + /// list id to use for all derived numbering definitions + std::optional<OUString> m_oListId; + +public: + typedef tools::SvRef< AbstractListDef > Pointer; + + AbstractListDef( ); + virtual ~AbstractListDef( ) override; + + // Setters using during the import + void SetId( sal_Int32 nId ) { m_nId = nId; }; + static void SetValue( sal_uInt32 nSprmId ); + + // Accessors + sal_Int32 GetId( ) const { return m_nId; }; + + sal_Int16 Size( ) { return sal_Int16( m_aLevels.size( ) ); }; + ListLevel::Pointer GetLevel( sal_uInt16 nLvl ); + void AddLevel( sal_uInt16 nLvl ); + + const ListLevel::Pointer& GetCurrentLevel( ) const { return m_pCurrentLevel; }; + + css::uno::Sequence< css::uno::Sequence<css::beans::PropertyValue> > GetPropertyValues(bool bDefaults); + + void SetNumStyleLink(const OUString& sValue) { m_sNumStyleLink = sValue; }; + const OUString& GetNumStyleLink() const { return m_sNumStyleLink; }; + + void SetStyleLink(const OUString& sValue) { m_sStyleLink = sValue; }; + const OUString& GetStyleLink() const { return m_sStyleLink; }; + + const OUString& MapListId(OUString const& rId); +}; + +class ListDef : public AbstractListDef +{ +private: + // Pointer to the abstract numbering + AbstractListDef::Pointer m_pAbstractDef; + + // Cache for the UNO numbering rules + css::uno::Reference< css::container::XIndexReplace > m_xNumRules; + + /// mapped list style name + OUString m_StyleName; + +public: + typedef tools::SvRef< ListDef > Pointer; + + ListDef( ); + virtual ~ListDef( ) override; + + // Accessors + void SetAbstractDefinition( AbstractListDef::Pointer pAbstract ) { m_pAbstractDef = pAbstract; }; + const AbstractListDef::Pointer& GetAbstractDefinition( ) const { return m_pAbstractDef; }; + + // Mapping functions + const OUString & GetStyleName() const { return m_StyleName; }; + const OUString & GetStyleName(sal_Int32 nId, css::uno::Reference<css::container::XNameContainer> const& xStyles); + + css::uno::Sequence< css::uno::Sequence<css::beans::PropertyValue> > GetMergedPropertyValues(); + + sal_uInt16 GetChapterNumberingWeight() const; + void CreateNumberingRules(DomainMapper& rDMapper, css::uno::Reference<css::lang::XMultiServiceFactory> const& xFactory, sal_Int16 nOutline); + + const css::uno::Reference<css::container::XIndexReplace>& GetNumberingRules() const { return m_xNumRules; } + +}; + +/** This class provides access to the defined numbering styles. + */ +class ListsManager : + public LoggedProperties, + public LoggedTable +{ +private: + + DomainMapper& m_rDMapper; + css::uno::Reference<css::lang::XMultiServiceFactory> m_xFactory; + + // The numbering entries + std::vector< NumPicBullet::Pointer > m_aNumPicBullets; + std::vector< AbstractListDef::Pointer > m_aAbstractLists; + std::vector< ListDef::Pointer > m_aLists; + + + // These members are used for import only + AbstractListDef::Pointer m_pCurrentDefinition; + NumPicBullet::Pointer m_pCurrentNumPicBullet; + + AbstractListDef::Pointer GetAbstractList( sal_Int32 nId ); + + // Properties + virtual void lcl_attribute( Id nName, Value & rVal ) override; + virtual void lcl_sprm(Sprm & sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + +public: + + ListsManager(DomainMapper& rDMapper, const css::uno::Reference<css::lang::XMultiServiceFactory>& xFactory); + virtual ~ListsManager() override; + + typedef tools::SvRef< ListsManager > Pointer; + + ListDef::Pointer GetList( sal_Int32 nId ); + + // Mapping methods + void CreateNumberingRules( ); + + // Dispose the NumPicBullets + void DisposeNumPicBullets( ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/OLEHandler.cxx b/writerfilter/source/dmapper/OLEHandler.cxx new file mode 100644 index 000000000..8a495e32f --- /dev/null +++ b/writerfilter/source/dmapper/OLEHandler.cxx @@ -0,0 +1,331 @@ +/* -*- 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 "OLEHandler.hxx" +#include "DomainMapper.hxx" +#include "GraphicHelpers.hxx" + +#include <oox/ole/oleobjecthelper.hxx> +#include <ooxml/resourceids.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <unotools/mediadescriptor.hxx> +#include <officecfg/Office/Common.hxx> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/document/XEmbeddedObjectResolver.hpp> +#include <com/sun/star/document/XEmbeddedObjectSupplier.hpp> +#include <com/sun/star/document/XFilter.hpp> +#include <com/sun/star/document/XImporter.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + + +OLEHandler::OLEHandler(DomainMapper& rDomainMapper) : +LoggedProperties("OLEHandler"), + m_nWrapMode(text::WrapTextMode_THROUGHT), + m_rDomainMapper(rDomainMapper) +{ +} + + +OLEHandler::~OLEHandler() +{ +} + + +void OLEHandler::lcl_attribute(Id rName, Value & rVal) +{ + OUString sStringValue = rVal.getString(); + switch( rName ) + { + case NS_ooxml::LN_CT_OLEObject_Type: + break; + case NS_ooxml::LN_CT_OLEObject_ProgID: + m_sProgId = sStringValue; + break; + case NS_ooxml::LN_CT_OLEObject_ShapeID: + break; + case NS_ooxml::LN_CT_OLEObject_DrawAspect: + m_sDrawAspect = sStringValue; + break; + case NS_ooxml::LN_CT_OLEObject_ObjectID: + break; + case NS_ooxml::LN_CT_OLEObject_r_id: + break; + case NS_ooxml::LN_inputstream: + rVal.getAny() >>= m_xInputStream; + break; + case NS_ooxml::LN_CT_Object_dxaOrig: + m_sVisAreaWidth = sStringValue; + break; + case NS_ooxml::LN_CT_Object_dyaOrig: + m_sVisAreaHeight = sStringValue; + break; + case NS_ooxml::LN_shape: + { + uno::Reference< drawing::XShape > xTempShape; + rVal.getAny() >>= xTempShape; + + // Control shape is handled on a different code path + uno::Reference< lang::XServiceInfo > xSInfo( xTempShape, uno::UNO_QUERY_THROW ); + if(xSInfo->supportsService("com.sun.star.drawing.ControlShape")) + { + m_rDomainMapper.hasControls(true); + break; + } + + if( xTempShape.is() ) + { + m_xShape.set( xTempShape ); + + // No need to set the wrapping here as it's either set in oox or will be set later + + // Shapes in the header or footer should be in the background, since the default is WrapTextMode_THROUGH. + if (m_rDomainMapper.IsInHeaderFooter()) + { + try + { + uno::Reference<beans::XPropertySet> xShapeProps(m_xShape, uno::UNO_QUERY); + xShapeProps->setPropertyValue("Opaque", uno::Any(false)); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter", "Exception in OLE Handler"); + } + } + } + } + break; + default: + OSL_FAIL( "unknown attribute"); + } +} + +css::awt::Size OLEHandler::getSize() const +{ + if (!m_xShape) + return css::awt::Size(); + return m_xShape->getSize(); +} + +css::uno::Reference<css::graphic::XGraphic> OLEHandler::getReplacement() const +{ + if (!m_xShape) + return nullptr; + uno::Reference<beans::XPropertySet> xShapeProps(m_xShape, uno::UNO_QUERY); + css::uno::Reference<css::graphic::XGraphic> xReplacement; + xShapeProps->getPropertyValue(getPropertyName(PROP_BITMAP)) >>= xReplacement; + return xReplacement; +} + +void OLEHandler::lcl_sprm(Sprm & rSprm) +{ + sal_uInt32 nSprmId = rSprm.getId(); + switch( nSprmId ) + { + case NS_ooxml::LN_OLEObject_OLEObject: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + pProperties->resolve(*this); + } + } + break; + case NS_ooxml::LN_wrap_wrap: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if ( pProperties ) + { + tools::SvRef<WrapHandler> pHandler( new WrapHandler ); + pProperties->resolve( *pHandler ); + + m_nWrapMode = pHandler->getWrapMode( ); + + try + { + uno::Reference< beans::XPropertySet > xShapeProps( m_xShape, uno::UNO_QUERY_THROW ); + + xShapeProps->setPropertyValue( + getPropertyName( PROP_SURROUND ), + uno::Any( static_cast<sal_Int32>(m_nWrapMode) ) ); + + // Through shapes in the header or footer(that spill into the body) should be in the background. + // It is just assumed that all shapes will spill into the body. + if( m_rDomainMapper.IsInHeaderFooter() ) + xShapeProps->setPropertyValue("Opaque", uno::Any(m_nWrapMode != text::WrapTextMode_THROUGH)); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("writerfilter", "Exception in OLE Handler"); + } + } + } + break; + default: + { + OSL_FAIL( "unknown attribute"); + } + } +} + +void OLEHandler::importStream(const uno::Reference<uno::XComponentContext>& xComponentContext, const uno::Reference<text::XTextDocument>& xTextDocument, const uno::Reference<text::XTextContent>& xOLE) +{ + OUString aFilterService; + if (m_sProgId == "Word.Document.12") + aFilterService = "com.sun.star.comp.Writer.WriterFilter"; + else if (m_sProgId == "Excel.Sheet.12") + aFilterService = "com.sun.star.comp.oox.xls.ExcelFilter"; + else if (m_sProgId == "Equation.3") + aFilterService = "com.sun.star.comp.Math.MathTypeFilter"; + else + SAL_WARN("writerfilter", "OLEHandler::importStream: unhandled m_sProgId: " << m_sProgId); + + if (!m_xInputStream.is() || aFilterService.isEmpty()) + return; + + // Create the filter service. + uno::Reference<uno::XInterface> xInterface = xComponentContext->getServiceManager()->createInstanceWithContext(aFilterService, xComponentContext); + + // Set target document. + uno::Reference<document::XImporter> xImporter(xInterface, uno::UNO_QUERY); + uno::Reference<document::XEmbeddedObjectSupplier> xSupplier(xOLE, uno::UNO_QUERY); + uno::Reference<lang::XComponent> xEmbeddedObject = xSupplier->getEmbeddedObject(); + if (!xEmbeddedObject.is()) + return; + xImporter->setTargetDocument( xEmbeddedObject ); + + // Import the input stream. + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["InputStream"] <<= m_xInputStream; + uno::Reference<document::XFilter> xFilter(xInterface, uno::UNO_QUERY); + xFilter->filter(aMediaDescriptor.getAsConstPropertyValueList()); + + // Now that the data is imported, update the (typically) changed stream name. + uno::Reference<beans::XPropertySet> xPropertySet(xOLE, uno::UNO_QUERY); + ::oox::ole::SaveInteropProperties(xTextDocument, + xPropertySet->getPropertyValue("StreamName").get<OUString>(), &m_aURL, + m_sProgId); +} + +OUString OLEHandler::getCLSID() const +{ + OUString aRet; + + // See officecfg/registry/data/org/openoffice/Office/Embedding.xcu. + if (m_sProgId == "Word.Document.12") + { + if (officecfg::Office::Common::Filter::Microsoft::Import::WinWordToWriter::get()) + aRet = "8BC6B165-B1B2-4EDD-aa47-dae2ee689dd6"; + } + else if (m_sProgId == "Excel.Sheet.12") + { + if (officecfg::Office::Common::Filter::Microsoft::Import::ExcelToCalc::get()) + aRet = "47BBB4CB-CE4C-4E80-A591-42D9AE74950F"; + } + else if (m_sProgId == "Equation.3") + { + if (officecfg::Office::Common::Filter::Microsoft::Import::MathTypeToMath::get()) + aRet = "078B7ABA-54FC-457F-8551-6147E776A997"; + } + else + SAL_WARN("writerfilter", "OLEHandler::getCLSID: unhandled m_sProgId: " << m_sProgId); + + return aRet; +} + +OUString const & OLEHandler::GetDrawAspect() const +{ + return m_sDrawAspect; +} + +OUString const & OLEHandler::GetVisAreaWidth() const +{ + return m_sVisAreaWidth; +} + +OUString const & OLEHandler::GetVisAreaHeight() const +{ + return m_sVisAreaHeight; +} + +OUString OLEHandler::copyOLEOStream( + uno::Reference<text::XTextDocument> const& xTextDocument) +{ + OUString sRet; + if( !m_xInputStream.is( ) ) + return sRet; + try + { + uno::Reference < lang::XMultiServiceFactory > xFactory(xTextDocument, uno::UNO_QUERY_THROW); + uno::Reference< document::XEmbeddedObjectResolver > xEmbeddedResolver( + xFactory->createInstance("com.sun.star.document.ImportEmbeddedObjectResolver"), uno::UNO_QUERY_THROW ); + //hack to work with the ImportEmbeddedObjectResolver + static sal_Int32 nObjectCount = 100; + uno::Reference< container::XNameAccess > xNA( xEmbeddedResolver, uno::UNO_QUERY_THROW ); + OUString aURL = "Obj" + OUString::number( nObjectCount++ ); + uno::Reference < io::XOutputStream > xOLEStream; + if( (xNA->getByName( aURL ) >>= xOLEStream) && xOLEStream.is() ) + { + const sal_Int32 nReadRequest = 0x1000; + uno::Sequence< sal_Int8 > aData; + + while( true ) + { + sal_Int32 nRead = m_xInputStream->readBytes( aData, nReadRequest ); + xOLEStream->writeBytes( aData ); + if( nRead < nReadRequest ) + { + xOLEStream->closeOutput(); + break; + } + } + + ::oox::ole::SaveInteropProperties(xTextDocument, aURL, nullptr, m_sProgId); + + OUString aPersistName( xEmbeddedResolver->resolveEmbeddedObjectURL( aURL ) ); + sRet = aPersistName.copy( strlen("vnd.sun.star.EmbeddedObject:") ); + + } + uno::Reference< lang::XComponent > xComp( xEmbeddedResolver, uno::UNO_QUERY_THROW ); + xComp->dispose(); + m_aURL = aURL; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "OLEHandler::createOLEObject"); + } + return sRet; +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/OLEHandler.hxx b/writerfilter/source/dmapper/OLEHandler.hxx new file mode 100644 index 000000000..67fed0128 --- /dev/null +++ b/writerfilter/source/dmapper/OLEHandler.hxx @@ -0,0 +1,94 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> + +namespace com::sun::star{ + namespace graphic{ + class XGraphic; + } + namespace io{ + class XInputStream; + } + namespace text{ + class XTextContent; + class XTextDocument; + } + namespace uno { + class XComponentContext; + } +} +namespace writerfilter::dmapper +{ +class DomainMapper; +/** Handler for OLE objects + */ +class OLEHandler : public LoggedProperties +{ + OUString m_sProgId; + OUString m_sDrawAspect; + OUString m_sVisAreaWidth; + OUString m_sVisAreaHeight; + /// The stream URL right after the import of the raw data. + OUString m_aURL; + + css::text::WrapTextMode m_nWrapMode; + + css::uno::Reference<css::drawing::XShape> m_xShape; + + css::uno::Reference<css::io::XInputStream> m_xInputStream; + DomainMapper& m_rDomainMapper; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + +public: + explicit OLEHandler(DomainMapper& rDomainMapper); + virtual ~OLEHandler() override; + + const css::uno::Reference<css::drawing::XShape>& getShape() const { return m_xShape; }; + + bool isOLEObject() const { return m_xInputStream.is(); } + + /// In case of a valid CLSID, import the native data to the previously created empty OLE object. + void importStream(const css::uno::Reference<css::uno::XComponentContext>& xComponentContext, + const css::uno::Reference<css::text::XTextDocument>& xTextDocument, + const css::uno::Reference<css::text::XTextContent>& xOLE); + + /// Get the CLSID of the OLE object, in case we can find one based on m_sProgId. + OUString getCLSID() const; + + OUString const & GetDrawAspect() const; + OUString const & GetVisAreaWidth() const; + OUString const & GetVisAreaHeight() const; + + OUString copyOLEOStream(css::uno::Reference<css::text::XTextDocument> const& xTextDocument); + + css::awt::Size getSize() const; + css::uno::Reference<css::graphic::XGraphic> getReplacement() const; + +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PageBordersHandler.cxx b/writerfilter/source/dmapper/PageBordersHandler.cxx new file mode 100644 index 000000000..89548eb35 --- /dev/null +++ b/writerfilter/source/dmapper/PageBordersHandler.cxx @@ -0,0 +1,145 @@ +/* -*- 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 "BorderHandler.hxx" +#include "PageBordersHandler.hxx" + +#include <ooxml/resourceids.hxx> + +namespace writerfilter::dmapper { + +PgBorder::PgBorder( ) : + m_nDistance( 0 ), + m_ePos( BORDER_RIGHT ), + m_bShadow(false) +{ +} + +PageBordersHandler::PageBordersHandler( ) : +LoggedProperties("PageBordersHandler"), +m_eBorderApply(SectionPropertyMap::BorderApply::ToAllInSection), +m_eOffsetFrom(SectionPropertyMap::BorderOffsetFrom::Text) +{ +} + +PageBordersHandler::~PageBordersHandler( ) +{ +} + +void PageBordersHandler::lcl_attribute( Id eName, Value& rVal ) +{ + int nIntValue = rVal.getInt( ); + switch ( eName ) + { + case NS_ooxml::LN_CT_PageBorders_display: + { + switch ( nIntValue ) + { + default: + case NS_ooxml::LN_Value_doc_ST_PageBorderDisplay_allPages: + m_eBorderApply = SectionPropertyMap::BorderApply::ToAllInSection; + break; + case NS_ooxml::LN_Value_doc_ST_PageBorderDisplay_firstPage: + m_eBorderApply = SectionPropertyMap::BorderApply::ToFirstPageInSection; + break; + case NS_ooxml::LN_Value_doc_ST_PageBorderDisplay_notFirstPage: + m_eBorderApply = SectionPropertyMap::BorderApply::ToAllButFirstInSection; + break; + } + } + break; + case NS_ooxml::LN_CT_PageBorders_offsetFrom: + { + switch ( nIntValue ) + { + default: + case NS_ooxml::LN_Value_doc_ST_PageBorderOffset_page: + m_eOffsetFrom = SectionPropertyMap::BorderOffsetFrom::Edge; + break; + case NS_ooxml::LN_Value_doc_ST_PageBorderOffset_text: + m_eOffsetFrom = SectionPropertyMap::BorderOffsetFrom::Text; + break; + } + } + break; + default:; + } +} + +void PageBordersHandler::lcl_sprm( Sprm& rSprm ) +{ + switch ( rSprm.getId( ) ) + { + case NS_ooxml::LN_CT_PageBorders_top: + case NS_ooxml::LN_CT_PageBorders_left: + case NS_ooxml::LN_CT_PageBorders_bottom: + case NS_ooxml::LN_CT_PageBorders_right: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pBorderHandler = std::make_shared<BorderHandler>( true ); + pProperties->resolve(*pBorderHandler); + BorderPosition ePos = BorderPosition( 0 ); + switch( rSprm.getId( ) ) + { + case NS_ooxml::LN_CT_PageBorders_top: + ePos = BORDER_TOP; + break; + case NS_ooxml::LN_CT_PageBorders_left: + ePos = BORDER_LEFT; + break; + case NS_ooxml::LN_CT_PageBorders_bottom: + ePos = BORDER_BOTTOM; + break; + case NS_ooxml::LN_CT_PageBorders_right: + ePos = BORDER_RIGHT; + break; + default:; + } + + PgBorder aPgBorder; + aPgBorder.m_rLine = pBorderHandler->getBorderLine( ); + aPgBorder.m_nDistance = pBorderHandler->getLineDistance( ); + aPgBorder.m_ePos = ePos; + aPgBorder.m_bShadow = pBorderHandler->getShadow(); + if (pBorderHandler->getLineType() != NS_ooxml::LN_Value_ST_Border_none) + { + m_aBorders.push_back( aPgBorder ); + } + } + } + break; + default:; + } +} + +void PageBordersHandler::SetBorders( SectionPropertyMap* pSectContext ) +{ + for (const PgBorder& rBorder : m_aBorders) + { + pSectContext->SetBorder( rBorder.m_ePos, rBorder.m_nDistance, rBorder.m_rLine, rBorder.m_bShadow ); + } + pSectContext->SetBorderApply(m_eBorderApply); + pSectContext->SetBorderOffsetFrom(m_eOffsetFrom); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PageBordersHandler.hxx b/writerfilter/source/dmapper/PageBordersHandler.hxx new file mode 100644 index 000000000..537d34b81 --- /dev/null +++ b/writerfilter/source/dmapper/PageBordersHandler.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ +#pragma once + +#include "PropertyMap.hxx" + +#include "LoggedResources.hxx" + +#include <com/sun/star/table/BorderLine2.hpp> + +#include <vector> + +namespace writerfilter::dmapper +{ +class PgBorder +{ +public: + css::table::BorderLine2 m_rLine; + sal_Int32 m_nDistance; + BorderPosition m_ePos; + bool m_bShadow; + + PgBorder(); +}; + +class PageBordersHandler : public LoggedProperties +{ +private: + // See implementation of SectionPropertyMap::ApplyBorderToPageStyles + SectionPropertyMap::BorderApply m_eBorderApply; + SectionPropertyMap::BorderOffsetFrom m_eOffsetFrom; + std::vector<PgBorder> m_aBorders; + + // Properties + virtual void lcl_attribute(Id eName, Value& rVal) override; + virtual void lcl_sprm(Sprm& rSprm) override; + +public: + PageBordersHandler(); + virtual ~PageBordersHandler() override; + + void SetBorders(SectionPropertyMap* pSectContext); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyIds.cxx b/writerfilter/source/dmapper/PropertyIds.cxx new file mode 100644 index 000000000..658a73040 --- /dev/null +++ b/writerfilter/source/dmapper/PropertyIds.cxx @@ -0,0 +1,386 @@ +/* -*- 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 <rtl/ustring.hxx> +#include "PropertyIds.hxx" + +namespace writerfilter::dmapper{ + +OUString getPropertyName( PropertyIds eId ) +{ + OUString sName; + switch(eId) { + case PROP_CHAR_WEIGHT: sName = "CharWeight"; break; + case PROP_CHAR_POSTURE: sName = "CharPosture"; break; + case PROP_CHAR_STRIKEOUT: sName = "CharStrikeout"; break; + case PROP_CHAR_CONTOURED: sName = "CharContoured"; break; + case PROP_CHAR_SHADOWED: sName = "CharShadowed"; break; + case PROP_CHAR_CASE_MAP: sName = "CharCaseMap"; break; + case PROP_CHAR_COLOR: sName = "CharColor"; break; + case PROP_CHAR_RELIEF: sName = "CharRelief"; break; + case PROP_CHAR_UNDERLINE: sName = "CharUnderline"; break; + case PROP_CHAR_UNDERLINE_COLOR: sName = "CharUnderlineColor"; break; + case PROP_CHAR_UNDERLINE_HAS_COLOR: sName = "CharUnderlineHasColor"; break; + case PROP_CHAR_WORD_MODE: sName = "CharWordMode"; break; + case PROP_CHAR_ESCAPEMENT : sName = "CharEscapement"; break; + case PROP_CHAR_ESCAPEMENT_HEIGHT: sName = "CharEscapementHeight"; break; + case PROP_CHAR_HEIGHT: sName = "CharHeight"; break; + case PROP_CHAR_HEIGHT_COMPLEX: sName = "CharHeightComplex"; break; + case PROP_CHAR_LOCALE: sName = "CharLocale"; break; + case PROP_CHAR_LOCALE_ASIAN: sName = "CharLocaleAsian"; break; + case PROP_CHAR_LOCALE_COMPLEX: sName = "CharLocaleComplex"; break; + case PROP_CHAR_WEIGHT_COMPLEX : sName = "CharWeightComplex"; break; + case PROP_CHAR_POSTURE_COMPLEX: sName = "CharPostureComplex"; break; + case PROP_CHAR_CHAR_KERNING: sName = "CharKerning"; break; + case PROP_CHAR_AUTO_KERNING: sName = "CharAutoKerning"; break; + case PROP_CHAR_SCALE_WIDTH: sName = "CharScaleWidth"; break; + case PROP_CHAR_STYLE_NAME: sName = "CharStyleName"; break; + case PROP_CHAR_FONT_NAME: sName = "CharFontName"; break; + case PROP_CHAR_FONT_CHAR_SET: sName = "CharFontCharSet"; break; + case PROP_CHAR_FONT_NAME_ASIAN : sName = "CharFontNameAsian"; break; + case PROP_CHAR_HEIGHT_ASIAN : sName = "CharHeightAsian"; break; + case PROP_CHAR_FONT_NAME_COMPLEX : sName = "CharFontNameComplex"; break; + case PROP_CHAR_HIDDEN : sName = "CharHidden"; break; + case PROP_CHAR_WEIGHT_ASIAN : sName = "CharWeightAsian"; break; + case PROP_CHAR_POSTURE_ASIAN : sName = "CharPostureAsian"; break; + case PROP_CHAR_BACK_COLOR: sName = "CharBackColor"; break; + case PROP_CHAR_EMPHASIS: sName = "CharEmphasis"; break; + case PROP_CHAR_COMBINE_IS_ON: sName = "CharCombineIsOn"; break; + case PROP_CHAR_COMBINE_PREFIX: sName = "CharCombinePrefix"; break; + case PROP_CHAR_COMBINE_SUFFIX: sName = "CharCombineSuffix"; break; + case PROP_CHAR_ROTATION: sName = "CharRotation"; break; + case PROP_CHAR_ROTATION_IS_FIT_TO_LINE: sName = "CharRotationIsFitToLine"; break; + case PROP_CHAR_FLASH: sName = "CharFlash"; break; + case PROP_CHAR_LEFT_BORDER: sName = "CharLeftBorder";break; + case PROP_CHAR_RIGHT_BORDER: sName = "CharRightBorder";break; + case PROP_CHAR_TOP_BORDER: sName = "CharTopBorder";break; + case PROP_CHAR_BOTTOM_BORDER: sName = "CharBottomBorder";break; + case PROP_CHAR_LEFT_BORDER_DISTANCE: sName = "CharLeftBorderDistance"; break; + case PROP_CHAR_RIGHT_BORDER_DISTANCE: sName = "CharRightBorderDistance"; break; + case PROP_CHAR_TOP_BORDER_DISTANCE: sName = "CharTopBorderDistance";break; + case PROP_CHAR_BOTTOM_BORDER_DISTANCE: sName = "CharBottomBorderDistance"; break; + case PROP_CHAR_SHADOW_FORMAT: sName = "CharShadowFormat"; break; + case PROP_CHAR_HIGHLIGHT: sName = "CharHighlight"; break; + case PROP_PARA_STYLE_NAME: sName = "ParaStyleName"; break; + case PROP_PARA_ADJUST: sName = "ParaAdjust"; break; + case PROP_PARA_VERT_ALIGNMENT: sName = "ParaVertAlignment"; break; + case PROP_PARA_LAST_LINE_ADJUST: sName = "ParaLastLineAdjust"; break; + case PROP_PARA_RIGHT_MARGIN : sName = "ParaRightMargin"; break; + case PROP_PARA_LEFT_MARGIN : sName = "ParaLeftMargin"; break; + case PROP_PARA_FIRST_LINE_INDENT: sName = "ParaFirstLineIndent"; break; + case PROP_PARA_KEEP_TOGETHER: sName = "ParaKeepTogether"; break; + case PROP_PARA_TOP_MARGIN: sName = "ParaTopMargin"; break; + case PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING: sName = "ParaTopMarginBeforeAutoSpacing"; break; + case PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING: sName = "ParaBottomMarginAfterAutoSpacing"; break; + case PROP_PARA_CONTEXT_MARGIN: sName = "ParaContextMargin"; break; + case PROP_PARA_BOTTOM_MARGIN: sName = "ParaBottomMargin"; break; + case PROP_PARA_IS_HYPHENATION: sName = "ParaIsHyphenation"; break; + case PROP_PARA_HYPHENATION_NO_CAPS: sName = "ParaHyphenationNoCaps"; break; + case PROP_PARA_HYPHENATION_ZONE: sName = "ParaHyphenationZone"; break; + case PROP_PARA_LINE_NUMBER_COUNT: sName = "ParaLineNumberCount"; break; + case PROP_PARA_IS_HANGING_PUNCTUATION: sName = "ParaIsHangingPunctuation"; break; + case PROP_PARA_LINE_SPACING: sName = "ParaLineSpacing"; break; + case PROP_PARA_TAB_STOPS: sName = "ParaTabStops"; break; + case PROP_PARA_WIDOWS: sName = "ParaWidows"; break; + case PROP_PARA_ORPHANS: sName = "ParaOrphans"; break; + case PROP_PARA_LINE_NUMBER_START_VALUE: sName = "ParaLineNumberStartValue"; break; + case PROP_NUMBERING_LEVEL: sName = "NumberingLevel"; break; + case PROP_NUMBERING_RULES: sName = "NumberingRules"; break; + case PROP_NUMBERING_TYPE: sName = "NumberingType"; break; + case PROP_START_WITH: sName = "StartWith"; break; + case PROP_ADJUST: sName = "Adjust"; break; + case PROP_PARENT_NUMBERING: sName = "ParentNumbering"; break; + case PROP_RIGHT_MARGIN : sName = "RightMargin"; break; + case PROP_LEFT_MARGIN : sName = "LeftMargin"; break; + case PROP_TOP_MARGIN : sName = "TopMargin"; break; + case PROP_BOTTOM_MARGIN : sName = "BottomMargin"; break; + case PROP_FIRST_LINE_OFFSET: sName = "FirstLineOffset"; break; + case PROP_LEFT_BORDER : sName = "LeftBorder";break; + case PROP_RIGHT_BORDER : sName = "RightBorder";break; + case PROP_TOP_BORDER : sName = "TopBorder";break; + case PROP_BOTTOM_BORDER : sName = "BottomBorder";break; + case PROP_TABLE_BORDER : sName = "TableBorder";break; + case PROP_TABLE_ROW_DELETE : sName = "TableRowDelete"; break; + case PROP_TABLE_ROW_INSERT : sName = "TableRowInsert"; break; + case PROP_TABLE_CELL_DELETE : sName = "TableCellDelete"; break; + case PROP_TABLE_CELL_INSERT : sName = "TableCellInsert"; break; + case PROP_LEFT_BORDER_DISTANCE : sName = "LeftBorderDistance"; break; + case PROP_RIGHT_BORDER_DISTANCE : sName = "RightBorderDistance"; break; + case PROP_TOP_BORDER_DISTANCE : sName = "TopBorderDistance";break; + case PROP_BOTTOM_BORDER_DISTANCE: sName = "BottomBorderDistance"; break; + case PROP_CURRENT_PRESENTATION : sName = "CurrentPresentation"; break; + case PROP_IS_FIXED : sName = "IsFixed"; break; + case PROP_SUB_TYPE : sName = "SubType"; break; + case PROP_FILE_FORMAT : sName = "FileFormat"; break; + case PROP_HYPER_LINK_U_R_L : sName = "HyperLinkURL"; break; + case PROP_HYPERLINK : sName = "Hyperlink"; break; + case PROP_NUMBER_FORMAT : sName = "NumberFormat"; break; + case PROP_NAME : sName = "Name"; break; + case PROP_IS_INPUT : sName = "IsInput"; break; + case PROP_HINT : sName = "Hint"; break; + case PROP_FULL_NAME : sName = "FullName"; break; + case PROP_DESCRIPTION : sName = "Description"; break; + case PROP_MACRO_NAME : sName = "MacroName"; break; + case PROP_TITLE : sName = "Title"; break; + case PROP_CONTENT : sName = "Content"; break; + case PROP_INPUT_STREAM : sName = "InputStream"; break; + case PROP_GRAPHIC : sName = "Graphic"; break; + case PROP_ANCHOR_TYPE : sName = "AnchorType"; break; + case PROP_SIZE : sName = "Size"; break; + case PROP_HORI_ORIENT : sName = "HoriOrient"; break; + case PROP_HORI_ORIENT_POSITION : sName = "HoriOrientPosition"; break; + case PROP_HORI_ORIENT_RELATION : sName = "HoriOrientRelation"; break; + case PROP_VERT_ORIENT : sName = "VertOrient"; break; + case PROP_VERT_ORIENT_POSITION : sName = "VertOrientPosition"; break; + case PROP_VERT_ORIENT_RELATION : sName = "VertOrientRelation"; break; + case PROP_SIZE100th_M_M : sName = "Size100thMM"; break; + case PROP_SIZE_PIXEL : sName = "SizePixel"; break; + case PROP_SURROUND : sName = "Surround"; break; + case PROP_SURROUND_CONTOUR : sName = "SurroundContour"; break; + case PROP_ADJUST_CONTRAST : sName = "AdjustContrast"; break; + case PROP_ADJUST_LUMINANCE : sName = "AdjustLuminance"; break; + case PROP_GRAPHIC_COLOR_MODE : sName = "GraphicColorMode"; break; + case PROP_CONTOUR_OUTSIDE : sName = "ContourOutside"; break; + case PROP_CONTOUR_POLY_POLYGON : sName = "ContourPolyPolygon"; break; + case PROP_PAGE_TOGGLE : sName = "PageToggle"; break; + case PROP_BACK_COLOR : sName = "BackColor"; break; + case PROP_BACK_COLOR_TRANSPARENCY: sName = "BackColorTransparency"; break; + case PROP_ALTERNATIVE_TEXT : sName = "AlternativeText"; break; + case PROP_HEADER_TEXT_LEFT : sName = "HeaderTextLeft"; break; + case PROP_HEADER_TEXT : sName = "HeaderText"; break; + case PROP_HEADER_IS_SHARED : sName = "HeaderIsShared"; break; + case PROP_HEADER_IS_ON : sName = "HeaderIsOn"; break; + case PROP_FOOTER_TEXT_LEFT : sName = "FooterTextLeft"; break; + case PROP_FOOTER_TEXT : sName = "FooterText"; break; + case PROP_FOOTER_IS_SHARED : sName = "FooterIsShared"; break; + case PROP_FOOTER_IS_ON : sName = "FooterIsOn"; break; + case PROP_FOOTNOTE_COUNTING : sName = "FootnoteCounting"; break; + case PROP_FOOTNOTE_LINE_ADJUST : sName = "FootnoteLineAdjust"; break; + case PROP_WIDTH : sName = "Width"; break; + case PROP_HEIGHT : sName = "Height"; break; + case PROP_TEXT_COLUMNS : sName = "TextColumns"; break; + case PROP_AUTOMATIC_DISTANCE : sName = "AutomaticDistance"; break; + case PROP_IS_LANDSCAPE : sName = "IsLandscape"; break; + case PROP_FIRST_PAGE : sName = "First Page"; break; + case PROP_PAGE_DESC_NAME : sName = "PageDescName"; break; + case PROP_PAGE_NUMBER_OFFSET: sName = "PageNumberOffset"; break; + case PROP_BREAK_TYPE : sName = "BreakType"; break; + case PROP_FOOTER_IS_DYNAMIC_HEIGHT: sName = "FooterIsDynamicHeight"; break; + case PROP_FOOTER_DYNAMIC_SPACING: sName = "FooterDynamicSpacing"; break; + case PROP_FOOTER_HEIGHT : sName = "FooterHeight"; break; + case PROP_FOOTER_BODY_DISTANCE : sName = "FooterBodyDistance"; break; + case PROP_HEADER_IS_DYNAMIC_HEIGHT: sName = "HeaderIsDynamicHeight"; break; + case PROP_HEADER_DYNAMIC_SPACING: sName = "HeaderDynamicSpacing"; break; + case PROP_HEADER_HEIGHT : sName = "HeaderHeight"; break; + case PROP_HEADER_BODY_DISTANCE : sName = "HeaderBodyDistance"; break; + case PROP_WRITING_MODE : sName = "WritingMode"; break; + case PROP_GRID_MODE : sName = "GridMode"; break; + case PROP_GRID_DISPLAY : sName = "GridDisplay"; break; + case PROP_GRID_PRINT : sName = "GridPrint"; break; + case PROP_GRID_LINES : sName = "GridLines"; break; + case PROP_GRID_BASE_HEIGHT : sName = "GridBaseHeight"; break; + case PROP_GRID_BASE_WIDTH : sName = "GridBaseWidth"; break; + case PROP_GRID_RUBY_HEIGHT : sName = "GridRubyHeight"; break; + case PROP_GRID_STANDARD_MODE : sName = "StandardPageMode"; break; + case PROP_IS_ON : sName = "IsOn"; break; + case PROP_RESTART_AT_EACH_PAGE : sName = "RestartAtEachPage"; break; + case PROP_COUNT_EMPTY_LINES : sName = "CountEmptyLines"; break; + case PROP_COUNT_LINES_IN_FRAMES : sName = "CountLinesInFrames"; break; + case PROP_INTERVAL : sName = "Interval"; break; + case PROP_DISTANCE : sName = "Distance"; break; + case PROP_NUMBER_POSITION : sName = "NumberPosition"; break; + case PROP_LEVEL : sName = "Level"; break; + case PROP_LEVEL_FOLLOW : sName = "LabelFollowedBy"; break; + case PROP_LEVEL_PARAGRAPH_STYLES : sName = "LevelParagraphStyles"; break; + case PROP_LEVEL_FORMAT : sName = "LevelFormat"; break; + case PROP_LIST_FORMAT : sName = "ListFormat"; break; + case PROP_TOKEN_TYPE : sName = "TokenType"; break; + case PROP_TOKEN_HYPERLINK_START : sName = "TokenHyperlinkStart"; break; + case PROP_TOKEN_HYPERLINK_END : sName = "TokenHyperlinkEnd"; break; + case PROP_TOKEN_CHAPTER_INFO : sName = "TokenChapterInfo"; break; + case PROP_CHAPTER_FORMAT : sName = "ChapterFormat"; break; + case PROP_TOKEN_TEXT : sName = "TokenText"; break; + case PROP_TEXT : sName = "Text"; break; + case PROP_CREATE_FROM_OUTLINE : sName = "CreateFromOutline"; break; + case PROP_CREATE_FROM_MARKS : sName = "CreateFromMarks"; break; + case PROP_STANDARD : sName = "Standard"; break; + case PROP_SPLIT : sName = "Split"; break; + case PROP_IS_SPLIT_ALLOWED : sName = "IsSplitAllowed"; break; + case META_PROP_VERTICAL_BORDER : sName = "VerticalBorder"; break; + case META_PROP_HORIZONTAL_BORDER : sName = "HorizontalBorder"; break; + case PROP_HEADER_ROW_COUNT : sName = "HeaderRowCount"; break; + case PROP_SIZE_TYPE : sName = "SizeType"; break; + case PROP_TABLE_COLUMN_SEPARATORS: sName = "TableColumnSeparators"; break; + case META_PROP_TABLE_STYLE_NAME : sName = "TableStyleName"; break; + case PROP_TABLE_REDLINE_PARAMS : sName = "TableRedlineParams"; break; + case PROP_REDLINE_AUTHOR : sName = "RedlineAuthor"; break; + case PROP_REDLINE_DATE_TIME : sName = "RedlineDateTime"; break; + case PROP_REDLINE_TYPE : sName = "RedlineType"; break; + case PROP_REDLINE_REVERT_PROPERTIES: sName = "RedlineRevertProperties"; break; + case PROP_IS_PROTECTED : sName = "IsProtected"; break; + case PROP_SIZE_PROTECTED : sName = "SizeProtected"; break; + case PROP_POSITION_PROTECTED : sName = "PositionProtected"; break; + case PROP_OPAQUE : sName = "Opaque"; break; + case PROP_VERTICAL_MERGE : sName = "VerticalMerge"; break; + case PROP_BULLET_CHAR : sName = "BulletChar"; break; + case PROP_BULLET_FONT_NAME : sName = "BulletFontName"; break; + case PROP_TABS_RELATIVE_TO_INDENT: sName = "TabsRelativeToIndent"; break; + case PROP_PREFIX : sName = "Prefix"; break; + case PROP_SUFFIX : sName = "Suffix"; break; + case PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES: sName = "CreateFromLevelParagraphStyles"; break; + case PROP_DROP_CAP_FORMAT : sName = "DropCapFormat"; break; + case PROP_REFERENCE_FIELD_PART : sName = "ReferenceFieldPart"; break; + case PROP_SOURCE_NAME: sName = "SourceName"; break; + case PROP_REFERENCE_FIELD_SOURCE : sName = "ReferenceFieldSource"; break; + case PROP_WIDTH_TYPE : sName = "WidthType"; break; + case PROP_TBL_LOOK : sName = "TblLook"; break; + case PROP_TEXT_RANGE: sName = "TextRange"; break; + case PROP_TEXT_VERTICAL_ADJUST : sName = "TextVerticalAdjust"; break; + case PROP_SERVICE_CHAR_STYLE : sName = "com.sun.star.style.CharacterStyle"; break; + case PROP_SERVICE_PARA_STYLE : sName = "com.sun.star.style.ParagraphStyle"; break; + case PROP_CHARACTER_STYLES : sName = "CharacterStyles"; break; + case PROP_PARAGRAPH_STYLES : sName = "ParagraphStyles"; break; + case PROP_TABLE_BORDER_DISTANCES: sName = "TableBorderDistances"; break; + case META_PROP_CELL_MAR_TOP : sName = "MetaPropCellMarTop"; break; + case META_PROP_CELL_MAR_BOTTOM : sName = "MetaPropCellMarBottom"; break; + case META_PROP_CELL_MAR_LEFT : sName = "MetaPropCellMarLeft"; break; + case META_PROP_CELL_MAR_RIGHT : sName = "MetaPropCellMarRight"; break; + case PROP_START_AT : sName = "StartAt"; break; + case PROP_CHAR_PROP_HEIGHT : sName = "CharPropHeight"; break; + case PROP_CHAR_PROP_HEIGHT_ASIAN : sName = "CharPropHeightAsian"; break; + case PROP_CHAR_PROP_HEIGHT_COMPLEX: sName = "CharPropHeightComplex"; break; + case PROP_FORMAT : sName = "Format"; break; + case PROP_INSERT : sName = "Insert"; break; + case PROP_DELETE : sName = "Delete"; break; + case PROP_PARAGRAPH_FORMAT : sName = "ParagraphFormat"; break; + case PROP_STREAM_NAME: sName = "StreamName"; break; + case PROP_BITMAP : sName = "Bitmap"; break; + case PROP_IS_DATE : sName = "IsDate"; break; + case PROP_TAB_STOP_DISTANCE : sName = "TabStopDistance"; break; + case PROP_INDENT_AT : sName = "IndentAt"; break; + case PROP_FIRST_LINE_INDENT : sName = "FirstLineIndent"; break; + case PROP_NUMBERING_STYLE_NAME : sName = "NumberingStyleName"; break; + case PROP_OUTLINE_LEVEL : sName = "OutlineLevel"; break; + case PROP_LISTTAB_STOP_POSITION : sName = "ListtabStopPosition"; break; + case PROP_POSITION_AND_SPACE_MODE : sName = "PositionAndSpaceMode"; break; + case PROP_PARA_SPLIT: sName = "ParaSplit"; break; + case PROP_HELP: sName = "Help"; break; + case PROP_HEADING_STYLE_NAME: sName = "HeadingStyleName"; break; + case PROP_FRM_DIRECTION: sName = "FRMDirection"; break; + case PROP_EMBEDDED_OBJECT : sName = "EmbeddedObject"; break; + case PROP_IS_VISIBLE: sName = "IsVisible"; break; + case PROP_PAGE_STYLE_LAYOUT: sName = "PageStyleLayout"; break; + case PROP_Z_ORDER: sName = "ZOrder"; break; + case PROP_EMBED_FONTS: sName = "EmbedFonts"; break; + case PROP_EMBED_SYSTEM_FONTS: sName = "EmbedSystemFonts"; break; + case PROP_SHADOW_FORMAT: sName = "ShadowFormat"; break; + case PROP_RELATIVE_WIDTH: sName = "RelativeWidth"; break; + case PROP_IS_WIDTH_RELATIVE: sName = "IsWidthRelative"; break; + case PROP_GRAPHIC_BITMAP: sName = "GraphicBitmap"; break; + case PROP_GRAPHIC_SIZE: sName = "GraphicSize"; break; + case PROP_CHAR_SHADING_VALUE: sName = "CharShadingValue"; break; + case PROP_CHAR_SHADING_MARKER: sName = "CharShadingMarker"; break; + case PROP_LABEL_CATEGORY: sName = "LabelCategory"; break; + case PROP_MIRROR_INDENTS : sName = "MirrorIndents"; break; + case PROP_SURROUND_TEXT_WRAP_SMALL: sName = "SurroundTextWrapSmall"; break; + case PROP_PARA_SHADOW_FORMAT: sName = "ParaShadowFormat"; break; + case PROP_FOOTNOTE_LINE_RELATIVE_WIDTH: sName = "FootnoteLineRelativeWidth"; break; + case PROP_TBL_HEADER: sName = "TblHeader"; break; + case PROP_CHAR_THEME_NAME_ASCII : sName = "CharThemeNameAscii"; break; + case PROP_CHAR_THEME_NAME_CS : sName = "CharThemeNameCs"; break; + case PROP_CHAR_THEME_NAME_H_ANSI : sName = "CharThemeNameHAnsi"; break; + case PROP_CHAR_THEME_NAME_EAST_ASIA : sName = "CharThemeNameEastAsia"; break; + case PROP_CHAR_THEME_FONT_NAME_ASCII : sName = "CharThemeFontNameAscii"; break; + case PROP_CHAR_THEME_FONT_NAME_CS : sName = "CharThemeFontNameCs"; break; + case PROP_CHAR_THEME_FONT_NAME_EAST_ASIA: sName = "CharThemeFontNameEastAsia"; break; + case PROP_CHAR_THEME_COLOR : sName = "CharThemeColor"; break; + case PROP_CHAR_THEME_ORIGINAL_COLOR : sName = "CharThemeOriginalColor"; break; + case PROP_CHAR_THEME_COLOR_SHADE : sName = "CharThemeColorShade"; break; + case PROP_CHAR_THEME_FILL : sName = "CharThemeFill"; break; + case PROP_HORIZONTAL_MERGE: sName = "HorizontalMerge"; break; + case PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS : sName = "HideTabLeaderAndPageNumber" ; break ; + case PROP_TAB_IN_TOC : sName = "TabInTOC"; break ; + case PROP_TOC_BOOKMARK: sName = "TOCBookmark"; break; + case PROP_TOC_NEW_LINE: sName = "TOCNewLine"; break; + case PROP_TOC_PARAGRAPH_OUTLINE_LEVEL : sName = "TOCParagraphOutlineLevel"; break; + case PROP_CHAR_THEME_COLOR_TINT : sName = "CharThemeColorTint"; break; + case PROP_CHAR_GLOW_TEXT_EFFECT : sName = "CharGlowTextEffect"; break; + case PROP_CHAR_SHADOW_TEXT_EFFECT : sName = "CharShadowTextEffect"; break; + case PROP_CHAR_REFLECTION_TEXT_EFFECT : sName = "CharReflectionTextEffect"; break; + case PROP_CHAR_TEXTOUTLINE_TEXT_EFFECT : sName = "CharTextOutlineTextEffect"; break; + case PROP_CHAR_TEXTFILL_TEXT_EFFECT : sName = "CharTextFillTextEffect"; break; + case PROP_CHAR_SCENE3D_TEXT_EFFECT : sName = "CharScene3DTextEffect"; break; + case PROP_CHAR_PROPS3D_TEXT_EFFECT : sName = "CharProps3DTextEffect"; break; + case PROP_CHAR_LIGATURES_TEXT_EFFECT : sName = "CharLigaturesTextEffect"; break; + case PROP_CHAR_NUMFORM_TEXT_EFFECT : sName = "CharNumFormTextEffect"; break; + case PROP_CHAR_NUMSPACING_TEXT_EFFECT : sName = "CharNumSpacingTextEffect"; break; + case PROP_CHAR_STYLISTICSETS_TEXT_EFFECT : sName = "CharStylisticSetsTextEffect"; break; + case PROP_CHAR_CNTXTALTS_TEXT_EFFECT : sName = "CharCntxtAltsTextEffect"; break; + case PROP_SDTPR : sName = "SdtPr"; break; + case PROP_CELL_INTEROP_GRAB_BAG : sName = "CellInteropGrabBag"; break; + case PROP_TABLE_INTEROP_GRAB_BAG : sName = "TableInteropGrabBag"; break; + case PROP_APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING : sName = "ApplyParagraphMarkFormatToNumbering"; break; + case PROP_SDT_END_BEFORE: sName = "SdtEndBefore"; break; + case PROP_PARA_SDT_END_BEFORE: sName = "ParaSdtEndBefore"; break; + case META_PROP_TABLE_LOOK: sName = "TableStyleLook"; break; + case PROP_PARA_CNF_STYLE: sName = "ParaCnfStyle"; break; + case PROP_CELL_CNF_STYLE: sName = "CellCnfStyle"; break; + case PROP_ROW_CNF_STYLE: sName = "RowCnfStyle"; break; + case PROP_CELL_HIDE_MARK: sName = "CellHideMark"; break; + case PROP_FOLLOW_TEXT_FLOW: sName = "IsFollowingTextFlow"; break; + case PROP_FILL_STYLE: sName = "FillStyle"; break; + case PROP_FILL_COLOR: sName = "FillColor"; break; + case PROP_SNAP_TO_GRID: sName = "SnapToGrid"; break; + case PROP_GRID_SNAP_TO_CHARS: sName = "GridSnapToChars"; break; + case PROP_RUBY_STYLE: sName = "RubyCharStyleName"; break; + case PROP_RUBY_TEXT: sName = "RubyText"; break; + case PROP_RUBY_ADJUST: sName = "RubyAdjust"; break; + case PROP_RUBY_POSITION: sName = "RubyPosition"; break; + case PROP_DATABASE_NAME: sName = "DataBaseName"; break; + case PROP_COMMAND_TYPE: sName = "DataCommandType"; break; + case PROP_DATATABLE_NAME: sName = "DataTableName"; break; + case PROP_DATACOLUMN_NAME: sName = "DataColumnName"; break; + case PROP_CHAR_TRANSPARENCE: sName = "CharTransparence"; break; + case PROP_CELL_FORMULA: sName = "CellFormula"; break; + case PROP_CELL_FORMULA_CONVERTED: sName = "CellFormulaConverted"; break; + case PROP_GUTTER_MARGIN: + sName = "GutterMargin"; + break; + case PROP_RTL_GUTTER: + sName = "RtlGutter"; + break; + case PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF: sName = "CursorNotIgnoreTables"; break; + case PROP_PARA_CONNECT_BORDERS: sName= "ParaIsConnectBorder"; break; + } + assert(sName.getLength()>0); + return sName; +} + +bool isCharacterProperty( const PropertyIds eId ) +{ + return eId > PROP_CHARACTER_STYLES && eId < PROP_CHARACTER_END; +} + +bool isParagraphProperty( const PropertyIds eId ) +{ + return (eId >= PROP_PARA_ADJUST && eId <= PROP_PARA_WIDOWS) || eId == PROP_FILL_COLOR; +} + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyIds.hxx b/writerfilter/source/dmapper/PropertyIds.hxx new file mode 100644 index 000000000..20e4a1cc0 --- /dev/null +++ b/writerfilter/source/dmapper/PropertyIds.hxx @@ -0,0 +1,377 @@ +/* -*- 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 . + */ +#pragma once + +#include <rtl/ustring.hxx> + +namespace writerfilter::dmapper{ +// Ensure that Character Properties are placed between PROP_CHARACTER_STYLES and PROP_CHARACTER_END +enum PropertyIds + { + PROP_ID_START = 1 + ,META_PROP_CELL_MAR_BOTTOM = PROP_ID_START + ,META_PROP_CELL_MAR_LEFT + ,META_PROP_CELL_MAR_RIGHT + ,META_PROP_CELL_MAR_TOP + ,META_PROP_HORIZONTAL_BORDER + ,META_PROP_TABLE_STYLE_NAME + ,META_PROP_VERTICAL_BORDER + ,PROP_ADJUST + ,PROP_ADJUST_CONTRAST + ,PROP_ADJUST_LUMINANCE + ,PROP_ALTERNATIVE_TEXT + ,PROP_ANCHOR_TYPE + ,PROP_AUTOMATIC_DISTANCE + ,PROP_BACK_COLOR + ,PROP_BACK_COLOR_TRANSPARENCY + ,PROP_BITMAP + ,PROP_BOTTOM_BORDER + ,PROP_BOTTOM_BORDER_DISTANCE + ,PROP_BOTTOM_MARGIN + ,PROP_BREAK_TYPE + ,PROP_BULLET_CHAR + ,PROP_BULLET_FONT_NAME + ,PROP_CHAPTER_FORMAT + ,PROP_CHARACTER_STYLES + ,PROP_CHAR_AUTO_KERNING + ,PROP_CHAR_BACK_COLOR + ,PROP_CHAR_CASE_MAP + ,PROP_CHAR_CHAR_KERNING + ,PROP_CHAR_COLOR + ,PROP_CHAR_COMBINE_IS_ON + ,PROP_CHAR_COMBINE_PREFIX + ,PROP_CHAR_COMBINE_SUFFIX + ,PROP_CHAR_CONTOURED + ,PROP_CHAR_LEFT_BORDER + ,PROP_CHAR_RIGHT_BORDER + ,PROP_CHAR_TOP_BORDER + ,PROP_CHAR_BOTTOM_BORDER + ,PROP_CHAR_LEFT_BORDER_DISTANCE + ,PROP_CHAR_RIGHT_BORDER_DISTANCE + ,PROP_CHAR_TOP_BORDER_DISTANCE + ,PROP_CHAR_BOTTOM_BORDER_DISTANCE + ,PROP_CHAR_EMPHASIS + ,PROP_CHAR_ESCAPEMENT + ,PROP_CHAR_ESCAPEMENT_HEIGHT + ,PROP_CHAR_FLASH + ,PROP_CHAR_FONT_CHAR_SET + ,PROP_CHAR_FONT_NAME + ,PROP_CHAR_FONT_NAME_ASIAN + ,PROP_CHAR_FONT_NAME_COMPLEX + ,PROP_CHAR_HEIGHT + ,PROP_CHAR_HEIGHT_ASIAN + ,PROP_CHAR_HEIGHT_COMPLEX + ,PROP_CHAR_HIDDEN + ,PROP_CHAR_HIGHLIGHT + ,PROP_CHAR_LOCALE + ,PROP_CHAR_LOCALE_ASIAN + ,PROP_CHAR_LOCALE_COMPLEX + ,PROP_CHAR_POSTURE + ,PROP_CHAR_POSTURE_ASIAN + ,PROP_CHAR_POSTURE_COMPLEX + ,PROP_CHAR_PROP_HEIGHT + ,PROP_CHAR_PROP_HEIGHT_ASIAN + ,PROP_CHAR_PROP_HEIGHT_COMPLEX + ,PROP_CHAR_RELIEF + ,PROP_CHAR_ROTATION + ,PROP_CHAR_ROTATION_IS_FIT_TO_LINE + ,PROP_CHAR_SCALE_WIDTH + ,PROP_CHAR_SHADOW_FORMAT + ,PROP_CHAR_SHADING_MARKER + ,PROP_CHAR_SHADING_VALUE + ,PROP_CHAR_SHADOWED + ,PROP_CHAR_STRIKEOUT + ,PROP_CHAR_STYLE_NAME + ,PROP_CHAR_TEXTOUTLINE_TEXT_EFFECT + ,PROP_CHAR_TEXTFILL_TEXT_EFFECT + ,PROP_CHAR_THEME_NAME_ASCII + ,PROP_CHAR_THEME_NAME_CS + ,PROP_CHAR_THEME_NAME_H_ANSI + ,PROP_CHAR_THEME_NAME_EAST_ASIA + ,PROP_CHAR_THEME_FONT_NAME_ASCII + ,PROP_CHAR_THEME_FONT_NAME_CS + ,PROP_CHAR_THEME_FONT_NAME_EAST_ASIA + ,PROP_CHAR_THEME_COLOR + ,PROP_CHAR_THEME_ORIGINAL_COLOR + ,PROP_CHAR_THEME_COLOR_SHADE + ,PROP_CHAR_THEME_FILL + ,PROP_CHAR_THEME_COLOR_TINT + ,PROP_CHAR_UNDERLINE + ,PROP_CHAR_UNDERLINE_COLOR + ,PROP_CHAR_UNDERLINE_HAS_COLOR + ,PROP_CHAR_WEIGHT + ,PROP_CHAR_WEIGHT_ASIAN + ,PROP_CHAR_WEIGHT_COMPLEX + ,PROP_CHAR_WORD_MODE + ,PROP_CHAR_GLOW_TEXT_EFFECT + ,PROP_CHAR_SHADOW_TEXT_EFFECT + ,PROP_CHAR_REFLECTION_TEXT_EFFECT + ,PROP_CHAR_SCENE3D_TEXT_EFFECT + ,PROP_CHAR_PROPS3D_TEXT_EFFECT + ,PROP_CHAR_LIGATURES_TEXT_EFFECT + ,PROP_CHAR_NUMFORM_TEXT_EFFECT + ,PROP_CHAR_NUMSPACING_TEXT_EFFECT + ,PROP_CHAR_STYLISTICSETS_TEXT_EFFECT + ,PROP_CHAR_CNTXTALTS_TEXT_EFFECT + ,PROP_CHARACTER_END + ,PROP_CONTENT = PROP_CHARACTER_END + ,PROP_CONTOUR_OUTSIDE + ,PROP_CONTOUR_POLY_POLYGON + ,PROP_COUNT_EMPTY_LINES + ,PROP_COUNT_LINES_IN_FRAMES + ,PROP_CREATE_FROM_LEVEL_PARAGRAPH_STYLES + ,PROP_CREATE_FROM_MARKS + ,PROP_CREATE_FROM_OUTLINE + ,PROP_CURRENT_PRESENTATION + ,PROP_DELETE + ,PROP_DESCRIPTION + ,PROP_DISTANCE + ,PROP_DROP_CAP_FORMAT + ,PROP_FILE_FORMAT + ,PROP_FIRST_LINE_INDENT + ,PROP_FIRST_LINE_OFFSET + ,PROP_FIRST_PAGE + ,PROP_FOOTER_BODY_DISTANCE + ,PROP_FOOTER_DYNAMIC_SPACING + ,PROP_FOOTER_HEIGHT + ,PROP_FOOTER_IS_DYNAMIC_HEIGHT + ,PROP_FOOTER_IS_ON + ,PROP_FOOTER_IS_SHARED + ,PROP_FOOTER_TEXT + ,PROP_FOOTER_TEXT_LEFT + ,PROP_FOOTNOTE_COUNTING + ,PROP_FOOTNOTE_LINE_ADJUST + ,PROP_FORMAT + ,PROP_FULL_NAME + ,PROP_GRAPHIC + ,PROP_GRAPHIC_COLOR_MODE + ,PROP_GRID_BASE_HEIGHT + ,PROP_GRID_BASE_WIDTH + ,PROP_GRID_DISPLAY + ,PROP_GRID_LINES + ,PROP_GRID_MODE + ,PROP_GRID_PRINT + ,PROP_GRID_RUBY_HEIGHT + ,PROP_HEADER_BODY_DISTANCE + ,PROP_HEADER_DYNAMIC_SPACING + ,PROP_HEADER_HEIGHT + ,PROP_HEADER_IS_DYNAMIC_HEIGHT + ,PROP_HEADER_IS_ON + ,PROP_HEADER_IS_SHARED + ,PROP_HEADER_ROW_COUNT + ,PROP_HEADER_TEXT + ,PROP_HEADER_TEXT_LEFT + ,PROP_HEADING_STYLE_NAME + ,PROP_HEIGHT + ,PROP_HELP + ,PROP_HINT + ,PROP_HORI_ORIENT + ,PROP_HORI_ORIENT_POSITION + ,PROP_HORI_ORIENT_RELATION + ,PROP_HYPER_LINK_U_R_L + ,PROP_HYPERLINK + ,PROP_INDENT_AT + ,PROP_INPUT_STREAM + ,PROP_INSERT + ,PROP_INTERVAL + ,PROP_IS_DATE + ,PROP_IS_FIXED + ,PROP_IS_INPUT + ,PROP_IS_LANDSCAPE + ,PROP_IS_ON + ,PROP_IS_SPLIT_ALLOWED + ,PROP_IS_VISIBLE + ,PROP_LABEL_CATEGORY + ,PROP_LEFT_BORDER + ,PROP_LEFT_BORDER_DISTANCE + ,PROP_LEFT_MARGIN + ,PROP_LEVEL + ,PROP_LEVEL_FOLLOW + ,PROP_LEVEL_FORMAT + ,PROP_LEVEL_PARAGRAPH_STYLES + ,PROP_LISTTAB_STOP_POSITION + ,PROP_LIST_FORMAT + ,PROP_MACRO_NAME + ,PROP_NAME + ,PROP_NUMBERING_LEVEL + ,PROP_NUMBERING_RULES + ,PROP_NUMBERING_STYLE_NAME + ,PROP_NUMBERING_TYPE + ,PROP_NUMBER_FORMAT + ,PROP_NUMBER_POSITION + ,PROP_OPAQUE + ,PROP_OUTLINE_LEVEL + ,PROP_PAGE_DESC_NAME + ,PROP_PAGE_NUMBER_OFFSET + ,PROP_PAGE_TOGGLE + ,PROP_PARAGRAPH_FORMAT + ,PROP_PARAGRAPH_STYLES + ,PROP_PARA_ADJUST + ,PROP_PARA_BOTTOM_MARGIN + ,PROP_PARA_FIRST_LINE_INDENT + ,PROP_PARA_IS_HANGING_PUNCTUATION + ,PROP_PARA_IS_HYPHENATION + ,PROP_PARA_HYPHENATION_NO_CAPS + ,PROP_PARA_HYPHENATION_ZONE + ,PROP_PARA_KEEP_TOGETHER + ,PROP_PARA_LAST_LINE_ADJUST + ,PROP_PARA_LEFT_MARGIN + ,PROP_PARA_LINE_NUMBER_COUNT + ,PROP_PARA_LINE_NUMBER_START_VALUE + ,PROP_PARA_LINE_SPACING + ,PROP_PARA_ORPHANS + ,PROP_PARA_RIGHT_MARGIN + ,PROP_PARA_SPLIT + ,PROP_PARA_STYLE_NAME + ,PROP_PARA_TAB_STOPS + ,PROP_PARA_TOP_MARGIN + ,PROP_PARA_VERT_ALIGNMENT + ,PROP_PARA_WIDOWS + ,PROP_PARENT_NUMBERING + ,PROP_POSITION_AND_SPACE_MODE + ,PROP_POSITION_PROTECTED + ,PROP_IS_PROTECTED + ,PROP_PREFIX + ,PROP_REDLINE_AUTHOR + ,PROP_REDLINE_DATE_TIME + ,PROP_REDLINE_TYPE + ,PROP_REDLINE_REVERT_PROPERTIES + ,PROP_REFERENCE_FIELD_PART + ,PROP_REFERENCE_FIELD_SOURCE + ,PROP_RESTART_AT_EACH_PAGE + ,PROP_RIGHT_BORDER + ,PROP_RIGHT_BORDER_DISTANCE + ,PROP_RIGHT_MARGIN + ,PROP_SERVICE_CHAR_STYLE + ,PROP_SERVICE_PARA_STYLE + ,PROP_SIZE + ,PROP_SIZE100th_M_M + ,PROP_SIZE_PIXEL + ,PROP_SIZE_PROTECTED + ,PROP_SIZE_TYPE + ,PROP_SOURCE_NAME + ,PROP_SPLIT + ,PROP_STANDARD + ,PROP_START_AT + ,PROP_START_WITH + ,PROP_STREAM_NAME + ,PROP_SUB_TYPE + ,PROP_SUFFIX + ,PROP_SURROUND + ,PROP_SURROUND_CONTOUR + ,PROP_TABLE_BORDER + ,PROP_TABLE_BORDER_DISTANCES + ,PROP_TABLE_COLUMN_SEPARATORS + ,PROP_TABLE_REDLINE_PARAMS + ,PROP_TABLE_ROW_DELETE + ,PROP_TABLE_ROW_INSERT + ,PROP_TABLE_CELL_DELETE + ,PROP_TABLE_CELL_INSERT + ,PROP_TABS_RELATIVE_TO_INDENT + ,PROP_TAB_STOP_DISTANCE + ,PROP_TEXT + ,PROP_TEXT_COLUMNS + ,PROP_TEXT_RANGE + ,PROP_TEXT_VERTICAL_ADJUST + ,PROP_TITLE + ,PROP_TOKEN_CHAPTER_INFO + ,PROP_TOKEN_HYPERLINK_END + ,PROP_TOKEN_HYPERLINK_START + ,PROP_TOKEN_TEXT + ,PROP_TOKEN_TYPE + ,PROP_TOP_BORDER + ,PROP_TOP_BORDER_DISTANCE + ,PROP_TOP_MARGIN + ,PROP_VERTICAL_MERGE + ,PROP_GRID_STANDARD_MODE + ,PROP_VERT_ORIENT + ,PROP_VERT_ORIENT_POSITION + ,PROP_VERT_ORIENT_RELATION + ,PROP_WIDTH + ,PROP_WIDTH_TYPE + ,PROP_TBL_LOOK + ,PROP_WRITING_MODE + ,PROP_FRM_DIRECTION + ,PROP_EMBEDDED_OBJECT + ,PROP_PARA_CONTEXT_MARGIN + ,PROP_PAGE_STYLE_LAYOUT + ,PROP_Z_ORDER + ,PROP_EMBED_FONTS + ,PROP_EMBED_SYSTEM_FONTS + ,PROP_SHADOW_FORMAT + ,PROP_RELATIVE_WIDTH + ,PROP_IS_WIDTH_RELATIVE + ,PROP_GRAPHIC_BITMAP + ,PROP_GRAPHIC_SIZE + ,PROP_MIRROR_INDENTS + ,PROP_SURROUND_TEXT_WRAP_SMALL + ,PROP_PARA_SHADOW_FORMAT + ,PROP_FOOTNOTE_LINE_RELATIVE_WIDTH + ,PROP_PARA_TOP_MARGIN_BEFORE_AUTO_SPACING + ,PROP_PARA_BOTTOM_MARGIN_AFTER_AUTO_SPACING + ,PROP_TBL_HEADER + ,PROP_HORIZONTAL_MERGE + ,PROP_HIDE_TAB_LEADER_AND_PAGE_NUMBERS + ,PROP_TAB_IN_TOC + ,PROP_TOC_BOOKMARK + ,PROP_TOC_NEW_LINE + ,PROP_TOC_PARAGRAPH_OUTLINE_LEVEL + ,PROP_SDTPR + ,PROP_CELL_INTEROP_GRAB_BAG + ,PROP_TABLE_INTEROP_GRAB_BAG + ,PROP_APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING + ,PROP_SDT_END_BEFORE + ,PROP_PARA_SDT_END_BEFORE + ,META_PROP_TABLE_LOOK + ,PROP_PARA_CNF_STYLE + ,PROP_CELL_CNF_STYLE + ,PROP_ROW_CNF_STYLE + ,PROP_CELL_HIDE_MARK + ,PROP_FOLLOW_TEXT_FLOW + ,PROP_FILL_STYLE + ,PROP_FILL_COLOR + ,PROP_SNAP_TO_GRID + ,PROP_GRID_SNAP_TO_CHARS + ,PROP_RUBY_STYLE + ,PROP_RUBY_TEXT + ,PROP_RUBY_ADJUST + ,PROP_RUBY_POSITION + ,PROP_DATABASE_NAME + ,PROP_COMMAND_TYPE + ,PROP_DATATABLE_NAME + ,PROP_DATACOLUMN_NAME + ,PROP_CHAR_TRANSPARENCE + ,PROP_CELL_FORMULA + ,PROP_CELL_FORMULA_CONVERTED + ,PROP_GUTTER_MARGIN + ,PROP_RTL_GUTTER + ,PROP_CURSOR_NOT_IGNORE_TABLES_IN_HF + ,PROP_PARA_CONNECT_BORDERS + }; + +//Returns the UNO string equivalent to eId. +OUString getPropertyName(PropertyIds eId); + +bool isCharacterProperty(const PropertyIds eId); + +bool isParagraphProperty(const PropertyIds eId); + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx new file mode 100644 index 000000000..1ae28759d --- /dev/null +++ b/writerfilter/source/dmapper/PropertyMap.cxx @@ -0,0 +1,2219 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include "PropertyMap.hxx" +#include "TagLogger.hxx" +#include <ooxml/resourceids.hxx> +#include "DomainMapper_Impl.hxx" +#include "ConversionHelper.hxx" +#include <editeng/boxitem.hxx> +#include <i18nutil/paper.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/PageStyleLayout.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/HorizontalAdjust.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XRedline.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/XTextTablesSupplier.hpp> +#include <com/sun/star/text/TextGridMode.hpp> +#include <com/sun/star/text/XTextCopy.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <tools/diagnose_ex.h> +#include "PropertyMapHelper.hxx" +#include <o3tl/sorted_vector.hxx> +#include <o3tl/unit_conversion.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper { + +uno::Sequence< beans::PropertyValue > PropertyMap::GetPropertyValues( bool bCharGrabBag ) +{ + using comphelper::makePropertyValue; + + if ( !m_aValues.empty() || m_vMap.empty() ) + return comphelper::containerToSequence( m_aValues ); + + size_t nCharGrabBag = 0; + size_t nParaGrabBag = 0; + size_t nCellGrabBag = 0; + size_t nRowGrabBag = 0; + + const PropValue* pParaStyleProp = nullptr; + const PropValue* pCharStyleProp = nullptr; + const PropValue* pNumRuleProp = nullptr; + + m_aValues.reserve( m_vMap.size() ); + for ( const auto& rPropPair : m_vMap ) + { + if ( rPropPair.second.getGrabBagType() == CHAR_GRAB_BAG ) + nCharGrabBag++; + else if ( rPropPair.second.getGrabBagType() == PARA_GRAB_BAG ) + nParaGrabBag++; + else if ( rPropPair.second.getGrabBagType() == CELL_GRAB_BAG ) + nCellGrabBag++; + else if ( rPropPair.first == PROP_CELL_INTEROP_GRAB_BAG ) + { + uno::Sequence< beans::PropertyValue > aSeq; + rPropPair.second.getValue() >>= aSeq; + nCellGrabBag += aSeq.getLength(); + } + else if ( rPropPair.second.getGrabBagType() == ROW_GRAB_BAG ) + nRowGrabBag++; + + if ( rPropPair.first == PROP_PARA_STYLE_NAME ) pParaStyleProp = &rPropPair.second; + if ( rPropPair.first == PROP_CHAR_STYLE_NAME ) pCharStyleProp = &rPropPair.second; + if ( rPropPair.first == PROP_NUMBERING_RULES ) pNumRuleProp = &rPropPair.second; + } + + // Style names have to be the first elements within the property sequence + // otherwise they will overwrite 'hard' attributes + if ( pParaStyleProp != nullptr ) + m_aValues.push_back( makePropertyValue( getPropertyName( PROP_PARA_STYLE_NAME ), pParaStyleProp->getValue() ) ); + if ( pCharStyleProp != nullptr ) + m_aValues.push_back( makePropertyValue( getPropertyName( PROP_CHAR_STYLE_NAME ), pCharStyleProp->getValue() ) ); + if ( pNumRuleProp != nullptr ) + m_aValues.push_back( makePropertyValue(getPropertyName( PROP_NUMBERING_RULES ), pNumRuleProp->getValue() ) ); + + // If there are any grab bag properties, we need one slot for them. + uno::Sequence< beans::PropertyValue > aCharGrabBagValues( nCharGrabBag ); + uno::Sequence< beans::PropertyValue > aParaGrabBagValues( nParaGrabBag ); + uno::Sequence< beans::PropertyValue > aCellGrabBagValues( nCellGrabBag ); + uno::Sequence< beans::PropertyValue > aRowGrabBagValues ( nRowGrabBag ); + beans::PropertyValue* pCharGrabBagValues = aCharGrabBagValues.getArray(); + beans::PropertyValue* pParaGrabBagValues = aParaGrabBagValues.getArray(); + beans::PropertyValue* pCellGrabBagValues = aCellGrabBagValues.getArray(); + beans::PropertyValue* pRowGrabBagValues = aRowGrabBagValues.getArray(); + // Record index for the next property to be added in each grab bag. + sal_Int32 nRowGrabBagValue = 0; + sal_Int32 nCellGrabBagValue = 0; + sal_Int32 nParaGrabBagValue = 0; + sal_Int32 nCharGrabBagValue = 0; + + for ( const auto& rPropPair : m_vMap ) + { + if ( rPropPair.first != PROP_PARA_STYLE_NAME && + rPropPair.first != PROP_CHAR_STYLE_NAME && + rPropPair.first != PROP_NUMBERING_RULES ) + { + if ( rPropPair.second.getGrabBagType() == CHAR_GRAB_BAG ) + { + if ( bCharGrabBag ) + { + pCharGrabBagValues[nCharGrabBagValue].Name = getPropertyName( rPropPair.first ); + pCharGrabBagValues[nCharGrabBagValue].Value = rPropPair.second.getValue(); + ++nCharGrabBagValue; + } + } + else if ( rPropPair.second.getGrabBagType() == PARA_GRAB_BAG ) + { + pParaGrabBagValues[nParaGrabBagValue].Name = getPropertyName( rPropPair.first ); + pParaGrabBagValues[nParaGrabBagValue].Value = rPropPair.second.getValue(); + ++nParaGrabBagValue; + } + else if ( rPropPair.second.getGrabBagType() == CELL_GRAB_BAG ) + { + pCellGrabBagValues[nCellGrabBagValue].Name = getPropertyName( rPropPair.first ); + pCellGrabBagValues[nCellGrabBagValue].Value = rPropPair.second.getValue(); + ++nCellGrabBagValue; + } + else if ( rPropPair.second.getGrabBagType() == ROW_GRAB_BAG ) + { + pRowGrabBagValues[nRowGrabBagValue].Name = getPropertyName( rPropPair.first ); + pRowGrabBagValues[nRowGrabBagValue].Value = rPropPair.second.getValue(); + ++nRowGrabBagValue; + } + else if ( rPropPair.first == PROP_CELL_INTEROP_GRAB_BAG ) + { + uno::Sequence< beans::PropertyValue > aSeq; + rPropPair.second.getValue() >>= aSeq; + std::copy(std::cbegin(aSeq), std::cend(aSeq), pCellGrabBagValues + nCellGrabBagValue); + nCellGrabBagValue += aSeq.getLength(); + } + else + { + m_aValues.push_back( makePropertyValue( getPropertyName( rPropPair.first ), rPropPair.second.getValue() ) ); + } + } + } + + if ( nCharGrabBag && bCharGrabBag ) + m_aValues.push_back( makePropertyValue( "CharInteropGrabBag", uno::Any( aCharGrabBagValues ) ) ); + + if ( nParaGrabBag ) + m_aValues.push_back( makePropertyValue( "ParaInteropGrabBag", uno::Any( aParaGrabBagValues ) ) ); + + if ( nCellGrabBag ) + m_aValues.push_back( makePropertyValue( "CellInteropGrabBag", uno::Any( aCellGrabBagValues ) ) ); + + if ( nRowGrabBag ) + m_aValues.push_back( makePropertyValue( "RowInteropGrabBag", uno::Any( aRowGrabBagValues ) ) ); + + return comphelper::containerToSequence( m_aValues ); +} + +std::vector< PropertyIds > PropertyMap::GetPropertyIds() +{ + std::vector< PropertyIds > aRet; + for ( const auto& rPropPair : m_vMap ) + aRet.push_back( rPropPair.first ); + return aRet; +} + +#ifdef DBG_UTIL +static void lcl_AnyToTag( const uno::Any& rAny ) +{ + try { + sal_Int32 aInt = 0; + if ( rAny >>= aInt ) + { + TagLogger::getInstance().attribute( "value", rAny ); + } + else + { + TagLogger::getInstance().attribute( "unsignedValue", 0 ); + } + + sal_uInt32 auInt = 0; + rAny >>= auInt; + TagLogger::getInstance().attribute( "unsignedValue", auInt ); + + float aFloat = 0.0f; + if ( rAny >>= aFloat ) + { + TagLogger::getInstance().attribute( "floatValue", rAny ); + } + else + { + TagLogger::getInstance().attribute( "unsignedValue", 0 ); + } + + OUString aStr; + rAny >>= aStr; + TagLogger::getInstance().attribute( "stringValue", aStr ); + } + catch ( ... ) + { + } +} +#endif + +void PropertyMap::Insert( PropertyIds eId, const uno::Any& rAny, bool bOverwrite, GrabBagType i_GrabBagType, bool bDocDefault ) +{ +#ifdef DBG_UTIL + const OUString& rInsert = getPropertyName(eId); + + TagLogger::getInstance().startElement("propertyMap.insert"); + TagLogger::getInstance().attribute("name", rInsert); + lcl_AnyToTag(rAny); + TagLogger::getInstance().endElement(); +#endif + + if ( !bOverwrite ) + m_vMap.insert(std::make_pair(eId, PropValue(rAny, i_GrabBagType, bDocDefault))); + else + m_vMap[eId] = PropValue(rAny, i_GrabBagType); + + Invalidate(); +} + +void PropertyMap::Erase( PropertyIds eId ) +{ + // Safe call to erase, it throws no exceptions, even if eId is not in m_vMap + m_vMap.erase(eId); + + Invalidate(); +} + +std::optional< PropertyMap::Property > PropertyMap::getProperty( PropertyIds eId ) const +{ + std::map< PropertyIds, PropValue >::const_iterator aIter = m_vMap.find( eId ); + if ( aIter == m_vMap.end() ) + return std::optional<Property>(); + else + return std::make_pair( eId, aIter->second.getValue() ); +} + +bool PropertyMap::isSet( PropertyIds eId) const +{ + return m_vMap.find( eId ) != m_vMap.end(); +} + +bool PropertyMap::isDocDefault( PropertyIds eId ) const +{ + std::map< PropertyIds, PropValue >::const_iterator aIter = m_vMap.find( eId ); + if ( aIter == m_vMap.end() ) + return false; + else + return aIter->second.getIsDocDefault(); +} + +#ifdef DBG_UTIL +void PropertyMap::dumpXml() const +{ + TagLogger::getInstance().startElement( "PropertyMap" ); + + for ( const auto& rPropPair : m_vMap ) + { + TagLogger::getInstance().startElement( "property" ); + + TagLogger::getInstance().attribute( "name", getPropertyName( rPropPair.first ) ); + + switch ( rPropPair.first ) + { + case PROP_TABLE_COLUMN_SEPARATORS: + lcl_DumpTableColumnSeparators( rPropPair.second.getValue() ); + break; + default: + { + try + { + sal_Int32 aInt = 0; + rPropPair.second.getValue() >>= aInt; + TagLogger::getInstance().attribute( "value", aInt ); + + sal_uInt32 auInt = 0; + rPropPair.second.getValue() >>= auInt; + TagLogger::getInstance().attribute( "unsignedValue", auInt ); + + float aFloat = 0.0; + rPropPair.second.getValue() >>= aFloat; + TagLogger::getInstance().attribute( "floatValue", aFloat ); + + rPropPair.second.getValue() >>= auInt; + TagLogger::getInstance().attribute( "stringValue", std::u16string_view() ); + } + catch ( ... ) + { + } + } + break; + } + + TagLogger::getInstance().endElement(); + } + + TagLogger::getInstance().endElement(); +} +#endif + +void PropertyMap::InsertProps( const PropertyMapPtr& rMap, const bool bOverwrite ) +{ + if ( !rMap ) + return; + + for ( const auto& rPropPair : rMap->m_vMap ) + { + if ( bOverwrite || !m_vMap.count(rPropPair.first) ) + { + if ( !bOverwrite && !rPropPair.second.getIsDocDefault() ) + m_vMap.insert(std::make_pair(rPropPair.first, PropValue(rPropPair.second.getValue(), rPropPair.second.getGrabBagType(), true))); + else + m_vMap[rPropPair.first] = rPropPair.second; + } + } + + insertTableProperties( rMap.get(), bOverwrite ); + + Invalidate(); +} + +void PropertyMap::insertTableProperties( const PropertyMap*, const bool ) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element( "PropertyMap.insertTableProperties" ); +#endif +} + +void PropertyMap::printProperties() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement( "properties" ); + + for ( const auto& rPropPair : m_vMap ) + { + SAL_INFO( "writerfilter", getPropertyName( rPropPair.first ) ); + + table::BorderLine2 aLine; + sal_Int32 nColor; + if ( rPropPair.second.getValue() >>= aLine ) + { + TagLogger::getInstance().startElement( "borderline" ); + TagLogger::getInstance().attribute( "color", aLine.Color ); + TagLogger::getInstance().attribute( "inner", aLine.InnerLineWidth ); + TagLogger::getInstance().attribute( "outer", aLine.OuterLineWidth ); + TagLogger::getInstance().endElement(); + } + else if ( rPropPair.second.getValue() >>= nColor ) + { + TagLogger::getInstance().startElement( "color" ); + TagLogger::getInstance().attribute( "number", nColor ); + TagLogger::getInstance().endElement(); + } + } + + TagLogger::getInstance().endElement(); +#else + (void) this; // avoid loplugin:staticmethods +#endif +} + +SectionPropertyMap::SectionPropertyMap( bool bIsFirstSection ) + : m_bIsFirstSection( bIsFirstSection ) + , m_eBorderApply( BorderApply::ToAllInSection ) + , m_eBorderOffsetFrom( BorderOffsetFrom::Text ) + , m_bTitlePage( false ) + , m_nColumnCount( 0 ) + , m_nColumnDistance( 1249 ) + , m_bSeparatorLineIsOn( false ) + , m_bEvenlySpaced( false ) + , m_nPageNumber( -1 ) + , m_nPageNumberType( -1 ) + , m_nBreakType( -1 ) + , m_nLeftMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nRightMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nGutterMargin(0) + , m_nTopMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nBottomMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nHeaderTop( o3tl::convert(0.5, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nHeaderBottom( o3tl::convert(0.5, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nGridType( 0 ) + , m_nGridLinePitch( 1 ) + , m_nDxtCharSpace( 0 ) + , m_bGridSnapToChars( true ) + , m_nLnnMod( 0 ) + , m_nLnc(NS_ooxml::LN_Value_ST_LineNumberRestart_newPage) + , m_ndxaLnn( 0 ) + , m_nLnnMin( 0 ) + , m_bDynamicHeightTop( true ) + , m_bDynamicHeightBottom( true ) + , m_bDefaultHeaderLinkToPrevious( true ) + , m_bEvenPageHeaderLinkToPrevious( true ) + , m_bFirstPageHeaderLinkToPrevious( true ) + , m_bDefaultFooterLinkToPrevious( true ) + , m_bEvenPageFooterLinkToPrevious( true ) + , m_bFirstPageFooterLinkToPrevious( true ) +{ +#ifdef DBG_UTIL + static sal_Int32 nNumber = 0; + m_nDebugSectionNumber = nNumber++; +#endif + + for ( sal_Int32 nBorder = 0; nBorder < 4; ++nBorder ) + { + m_nBorderDistances[nBorder] = -1; + m_bBorderShadows[nBorder] = false; + } + // todo: set defaults in ApplyPropertiesToPageStyles + // initialize defaults + PaperInfo aLetter( PAPER_LETTER ); + // page height, 1/100mm + Insert( PROP_HEIGHT, uno::Any( static_cast<sal_Int32>(aLetter.getHeight()) ) ); + // page width, 1/100mm + Insert( PROP_WIDTH, uno::Any( static_cast<sal_Int32>(aLetter.getWidth()) ) ); + // page left margin, 1/100 mm + Insert( PROP_LEFT_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page right margin, 1/100 mm + Insert( PROP_RIGHT_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page top margin, 1/100 mm + Insert( PROP_TOP_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page bottom margin, 1/100 mm + Insert( PROP_BOTTOM_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page style layout + Insert( PROP_PAGE_STYLE_LAYOUT, uno::Any( style::PageStyleLayout_ALL ) ); + uno::Any aFalse( uno::Any( false ) ); + Insert( PROP_GRID_DISPLAY, aFalse ); + Insert( PROP_GRID_PRINT, aFalse ); + Insert( PROP_GRID_MODE, uno::Any( text::TextGridMode::NONE ) ); + + if ( m_bIsFirstSection ) + { + m_sFirstPageStyleName = getPropertyName( PROP_FIRST_PAGE ); + m_sFollowPageStyleName = getPropertyName( PROP_STANDARD ); + } +} + +uno::Reference< beans::XPropertySet > SectionPropertyMap::GetPageStyle( DomainMapper_Impl& rDM_Impl, + bool bFirst ) +{ + const uno::Reference< container::XNameContainer >& xPageStyles = rDM_Impl.GetPageStyles(); + const uno::Reference < lang::XMultiServiceFactory >& xTextFactory = rDM_Impl.GetTextFactory(); + uno::Reference< beans::XPropertySet > xRet; + try + { + if ( bFirst ) + { + if ( m_sFirstPageStyleName.isEmpty() && xPageStyles.is() ) + { + assert( !rDM_Impl.IsInFootOrEndnote() && "Don't create useless page styles" ); + m_sFirstPageStyleName = rDM_Impl.GetUnusedPageStyleName(); + m_aFirstPageStyle.set( xTextFactory->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + + // Call insertByName() before GetPageStyle(), otherwise the + // first and the follow page style will have the same name, and + // insertByName() will fail. + if ( xPageStyles.is() ) + xPageStyles->insertByName( m_sFirstPageStyleName, uno::Any( m_aFirstPageStyle ) ); + + // Ensure that m_aFollowPageStyle has been created + GetPageStyle( rDM_Impl, false ); + // Chain m_aFollowPageStyle to be after m_aFirstPageStyle + m_aFirstPageStyle->setPropertyValue( "FollowStyle", + uno::Any( m_sFollowPageStyleName ) ); + } + else if ( !m_aFirstPageStyle.is() && xPageStyles.is() ) + { + xPageStyles->getByName( m_sFirstPageStyleName ) >>= m_aFirstPageStyle; + } + xRet = m_aFirstPageStyle; + } + else + { + if ( m_sFollowPageStyleName.isEmpty() && xPageStyles.is() ) + { + assert( !rDM_Impl.IsInFootOrEndnote() && "Don't create useless page styles" ); + m_sFollowPageStyleName = rDM_Impl.GetUnusedPageStyleName(); + m_aFollowPageStyle.set( xTextFactory->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + xPageStyles->insertByName( m_sFollowPageStyleName, uno::Any( m_aFollowPageStyle ) ); + } + else if ( !m_aFollowPageStyle.is() && xPageStyles.is() ) + { + xPageStyles->getByName( m_sFollowPageStyleName ) >>= m_aFollowPageStyle; + } + xRet = m_aFollowPageStyle; + } + + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION( "writerfilter" ); + } + + return xRet; +} + +void SectionPropertyMap::SetBorder( BorderPosition ePos, sal_Int32 nLineDistance, const table::BorderLine2& rBorderLine, bool bShadow ) +{ + m_oBorderLines[ePos] = rBorderLine; + m_nBorderDistances[ePos] = nLineDistance; + m_bBorderShadows[ePos] = bShadow; +} + +void SectionPropertyMap::ApplyBorderToPageStyles( DomainMapper_Impl& rDM_Impl, + BorderApply eBorderApply, BorderOffsetFrom eOffsetFrom ) +{ + /* + page border applies to: + nIntValue & 0x07 -> + 0 all pages in this section + 1 first page in this section + 2 all pages in this section but first + 3 whole document (all sections) + nIntValue & 0x18 -> page border depth 0 - in front 1- in back + nIntValue & 0xe0 -> + page border offset from: + 0 offset from text + 1 offset from edge of page + */ + uno::Reference< beans::XPropertySet > xFirst; + uno::Reference< beans::XPropertySet > xSecond; + // todo: negative spacing (from ww8par6.cxx) + switch ( eBorderApply ) + { + case BorderApply::ToAllInSection: // all styles + if ( !m_sFollowPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, false ); + if ( !m_sFirstPageStyleName.isEmpty() ) + xSecond = GetPageStyle( rDM_Impl, true ); + break; + case BorderApply::ToFirstPageInSection: // first page + if ( !m_sFirstPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, true ); + break; + case BorderApply::ToAllButFirstInSection: // left and right + if ( !m_sFollowPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, false ); + break; + default: + return; + } + + // has to be sorted like enum BorderPosition: l-r-t-b + const PropertyIds aBorderIds[4] = + { + PROP_LEFT_BORDER, + PROP_RIGHT_BORDER, + PROP_TOP_BORDER, + PROP_BOTTOM_BORDER + }; + + const PropertyIds aBorderDistanceIds[4] = + { + PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_BORDER_DISTANCE, + PROP_BOTTOM_BORDER_DISTANCE + }; + + const PropertyIds aMarginIds[4] = + { + PROP_LEFT_MARGIN, + PROP_RIGHT_MARGIN, + PROP_TOP_MARGIN, + PROP_BOTTOM_MARGIN + }; + + for ( sal_Int32 nBorder = 0; nBorder < 4; ++nBorder ) + { + if ( m_oBorderLines[nBorder] ) + { + const OUString sBorderName = getPropertyName( aBorderIds[nBorder] ); + if ( xFirst.is() ) + xFirst->setPropertyValue( sBorderName, uno::Any( *m_oBorderLines[nBorder] ) ); + if ( xSecond.is() ) + xSecond->setPropertyValue( sBorderName, uno::Any( *m_oBorderLines[nBorder] ) ); + } + if ( m_nBorderDistances[nBorder] >= 0 ) + { + sal_uInt32 nLineWidth = 0; + if ( m_oBorderLines[nBorder] ) + nLineWidth = m_oBorderLines[nBorder]->LineWidth; + if ( xFirst.is() ) + SetBorderDistance( xFirst, aMarginIds[nBorder], aBorderDistanceIds[nBorder], + m_nBorderDistances[nBorder], eOffsetFrom, nLineWidth, rDM_Impl ); + if ( xSecond.is() ) + SetBorderDistance( xSecond, aMarginIds[nBorder], aBorderDistanceIds[nBorder], + m_nBorderDistances[nBorder], eOffsetFrom, nLineWidth, rDM_Impl ); + } + } + + if ( m_bBorderShadows[BORDER_RIGHT] ) + { + table::ShadowFormat aFormat = getShadowFromBorder( *m_oBorderLines[BORDER_RIGHT] ); + if ( xFirst.is() ) + xFirst->setPropertyValue( getPropertyName( PROP_SHADOW_FORMAT ), uno::Any( aFormat ) ); + if ( xSecond.is() ) + xSecond->setPropertyValue( getPropertyName( PROP_SHADOW_FORMAT ), uno::Any( aFormat ) ); + } +} + +table::ShadowFormat PropertyMap::getShadowFromBorder( const table::BorderLine2& rBorder ) +{ + // In Word UI, shadow is a boolean property, in OOXML, it's a boolean + // property of each 4 border type, finally in Writer the border is a + // property of the page style, with shadow location, distance and + // color. See SwWW8ImplReader::SetShadow(). + table::ShadowFormat aFormat; + aFormat.Color = sal_Int32(COL_BLACK); + aFormat.Location = table::ShadowLocation_BOTTOM_RIGHT; + aFormat.ShadowWidth = rBorder.LineWidth; + return aFormat; +} + +void SectionPropertyMap::SetBorderDistance( const uno::Reference< beans::XPropertySet >& xStyle, + PropertyIds eMarginId, + PropertyIds eDistId, + sal_Int32 nDistance, + BorderOffsetFrom eOffsetFrom, + sal_uInt32 nLineWidth, + DomainMapper_Impl& rDM_Impl ) +{ + if (!xStyle.is()) + return; + const OUString sMarginName = getPropertyName( eMarginId ); + const OUString sBorderDistanceName = getPropertyName( eDistId ); + uno::Any aMargin = xStyle->getPropertyValue( sMarginName ); + sal_Int32 nMargin = 0; + aMargin >>= nMargin; + editeng::BorderDistanceFromWord(eOffsetFrom == BorderOffsetFrom::Edge, nMargin, nDistance, + nLineWidth); + + if (eOffsetFrom == BorderOffsetFrom::Edge) + { + uno::Any aGutterMargin = xStyle->getPropertyValue( "GutterMargin" ); + sal_Int32 nGutterMargin = 0; + aGutterMargin >>= nGutterMargin; + + if (eMarginId == PROP_LEFT_MARGIN && !rDM_Impl.GetSettingsTable()->GetGutterAtTop()) + { + nMargin -= nGutterMargin; + nDistance += nGutterMargin; + } + + if (eMarginId == PROP_TOP_MARGIN && rDM_Impl.GetSettingsTable()->GetGutterAtTop()) + { + nMargin -= nGutterMargin; + nDistance += nGutterMargin; + } + } + + // Change the margins with the border distance + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sMarginName, sBorderDistanceName }; + uno::Sequence<uno::Any> aValues { uno::Any( nMargin ), uno::Any( nDistance ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); +} + +void SectionPropertyMap::DontBalanceTextColumns() +{ + try + { + if ( m_xColumnContainer.is() ) + m_xColumnContainer->setPropertyValue( "DontBalanceTextColumns", uno::Any( true ) ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::DontBalanceTextColumns" ); + } +} + +void SectionPropertyMap::ApplySectionProperties( const uno::Reference< beans::XPropertySet >& xSection, DomainMapper_Impl& /*rDM_Impl*/ ) +{ + try + { + if ( xSection.is() ) + { + std::optional< PropertyMap::Property > pProp = getProperty( PROP_WRITING_MODE ); + if ( pProp ) + xSection->setPropertyValue( "WritingMode", pProp->second ); + } + } + catch ( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "Exception in SectionPropertyMap::ApplySectionProperties"); + } +} + +void SectionPropertyMap::ApplyProtectionProperties( uno::Reference< beans::XPropertySet >& xSection, DomainMapper_Impl& rDM_Impl ) +{ + try + { + // Word implements section protection differently than LO. + // PROP_IS_PROTECTED only applies if global setting GetProtectForm is enabled. + bool bIsProtected = rDM_Impl.GetSettingsTable()->GetProtectForm(); + if ( bIsProtected ) + { + // If form protection is enabled then section protection is enabled, unless explicitly disabled + if ( isSet(PROP_IS_PROTECTED) ) + getProperty(PROP_IS_PROTECTED)->second >>= bIsProtected; + if ( !xSection.is() ) + xSection = rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + xSection->setPropertyValue( getPropertyName(PROP_IS_PROTECTED), uno::Any(bIsProtected) ); + } + } + catch ( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "ApplyProtectionProperties failed setting PROP_IS_PROTECTED"); + } +} + +uno::Reference< text::XTextColumns > SectionPropertyMap::ApplyColumnProperties( const uno::Reference< beans::XPropertySet >& xColumnContainer, + DomainMapper_Impl& rDM_Impl ) +{ + uno::Reference< text::XTextColumns > xColumns; + assert( m_nColumnCount > 1 && "ApplyColumnProperties called without any columns" ); + try + { + const OUString sTextColumns = getPropertyName( PROP_TEXT_COLUMNS ); + if ( xColumnContainer.is() ) + xColumnContainer->getPropertyValue( sTextColumns ) >>= xColumns; + uno::Reference< beans::XPropertySet > xColumnPropSet( xColumns, uno::UNO_QUERY_THROW ); + if ( !m_bEvenlySpaced && + ( sal_Int32(m_aColWidth.size()) == m_nColumnCount ) && + ( (sal_Int32(m_aColDistance.size()) == m_nColumnCount - 1) || (sal_Int32(m_aColDistance.size()) == m_nColumnCount) ) ) + { + // the column width in word is an absolute value, in OOo it's relative + // the distances are both absolute + sal_Int32 nColSum = 0; + for ( sal_Int32 nCol = 0; nCol < m_nColumnCount; ++nCol ) + { + nColSum += m_aColWidth[nCol]; + if ( nCol ) + nColSum += m_aColDistance[nCol - 1]; + } + + sal_Int32 nRefValue = xColumns->getReferenceValue(); + double fRel = nColSum ? double( nRefValue ) / double( nColSum ) : 0.0; + uno::Sequence< text::TextColumn > aColumns( m_nColumnCount ); + text::TextColumn* pColumn = aColumns.getArray(); + + nColSum = 0; + for ( sal_Int32 nCol = 0; nCol < m_nColumnCount; ++nCol ) + { + const double fLeft = nCol ? m_aColDistance[nCol - 1] / 2 : 0; + pColumn[nCol].LeftMargin = fLeft; + const double fRight = (nCol == m_nColumnCount - 1) ? 0 : m_aColDistance[nCol] / 2; + pColumn[nCol].RightMargin = fRight; + const double fWidth = m_aColWidth[nCol]; + pColumn[nCol].Width = (fWidth + fLeft + fRight) * fRel; + nColSum += pColumn[nCol].Width; + } + if ( nColSum != nRefValue ) + pColumn[m_nColumnCount - 1].Width += (nRefValue - nColSum); + assert( pColumn[m_nColumnCount - 1].Width >= 0 ); + + xColumns->setColumns( aColumns ); + } + else + { + xColumns->setColumnCount( m_nColumnCount ); + xColumnPropSet->setPropertyValue( getPropertyName( PROP_AUTOMATIC_DISTANCE ), uno::Any( m_nColumnDistance ) ); + } + + if ( m_bSeparatorLineIsOn ) + { + xColumnPropSet->setPropertyValue( "SeparatorLineIsOn", uno::Any( true ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineVerticalAlignment", uno::Any( style::VerticalAlignment_TOP ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineRelativeHeight", uno::Any( static_cast<sal_Int8>(100) ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineColor", uno::Any( static_cast<sal_Int32>(COL_BLACK) ) ); + // 1 twip -> 2 mm100. + xColumnPropSet->setPropertyValue( "SeparatorLineWidth", uno::Any( static_cast<sal_Int32>(2) ) ); + } + xColumnContainer->setPropertyValue( sTextColumns, uno::Any( xColumns ) ); + // Set the columns to be unbalanced if that compatibility option is set or this is the last section. + m_xColumnContainer = xColumnContainer; + if ( rDM_Impl.GetSettingsTable()->GetNoColumnBalance() || rDM_Impl.GetIsLastSectionGroup() ) + DontBalanceTextColumns(); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyColumnProperties" ); + } + return xColumns; +} + +bool SectionPropertyMap::HasHeader( bool bFirstPage ) const +{ + bool bRet = false; + if ( (bFirstPage && m_aFirstPageStyle.is()) || (!bFirstPage && m_aFollowPageStyle.is()) ) + { + if ( bFirstPage ) + m_aFirstPageStyle->getPropertyValue( + getPropertyName( PROP_HEADER_IS_ON ) ) >>= bRet; + else + m_aFollowPageStyle->getPropertyValue( + getPropertyName( PROP_HEADER_IS_ON ) ) >>= bRet; + } + return bRet; +} + +bool SectionPropertyMap::HasFooter( bool bFirstPage ) const +{ + bool bRet = false; + if ( (bFirstPage && m_aFirstPageStyle.is()) || (!bFirstPage && m_aFollowPageStyle.is()) ) + { + if ( bFirstPage ) + m_aFirstPageStyle->getPropertyValue( getPropertyName( PROP_FOOTER_IS_ON ) ) >>= bRet; + else + m_aFollowPageStyle->getPropertyValue( getPropertyName( PROP_FOOTER_IS_ON ) ) >>= bRet; + } + return bRet; +} + +#define MIN_HEAD_FOOT_HEIGHT 100 // minimum header/footer height + +void SectionPropertyMap::CopyHeaderFooterTextProperty( const uno::Reference< beans::XPropertySet >& xPrevStyle, + const uno::Reference< beans::XPropertySet >& xStyle, + PropertyIds ePropId ) +{ + try { + OUString sName = getPropertyName( ePropId ); + + SAL_INFO( "writerfilter", "Copying " << sName ); + uno::Reference< text::XTextCopy > xTxt; + if ( xStyle.is() ) + xTxt.set( xStyle->getPropertyValue( sName ), uno::UNO_QUERY_THROW ); + + uno::Reference< text::XTextCopy > xPrevTxt; + if ( xPrevStyle.is() ) + xPrevTxt.set( xPrevStyle->getPropertyValue( sName ), uno::UNO_QUERY_THROW ); + + xTxt->copyText( xPrevTxt ); + } + catch ( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION( "writerfilter", "An exception occurred in SectionPropertyMap::CopyHeaderFooterTextProperty( )" ); + } +} + +// Copy headers and footers from the previous page style. +void SectionPropertyMap::CopyHeaderFooter( const DomainMapper_Impl& rDM_Impl, + const uno::Reference< beans::XPropertySet >& xPrevStyle, + const uno::Reference< beans::XPropertySet >& xStyle, + bool bOmitRightHeader, + bool bOmitLeftHeader, + bool bOmitRightFooter, + bool bOmitLeftFooter ) +{ + if (!rDM_Impl.IsNewDoc()) + { // see also DomainMapper_Impl::PushPageHeaderFooter() + return; // tdf#139737 SwUndoInserts cannot deal with new header/footer + } + bool bHasPrevHeader = false; + bool bHeaderIsShared = true; + OUString sHeaderIsOn = getPropertyName( PROP_HEADER_IS_ON ); + OUString sHeaderIsShared = getPropertyName( PROP_HEADER_IS_SHARED ); + if ( xPrevStyle.is() ) + { + xPrevStyle->getPropertyValue( sHeaderIsOn ) >>= bHasPrevHeader; + xPrevStyle->getPropertyValue( sHeaderIsShared ) >>= bHeaderIsShared; + } + + if ( bHasPrevHeader ) + { + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sHeaderIsOn, sHeaderIsShared }; + uno::Sequence<uno::Any> aValues { uno::Any( true ), uno::Any( bHeaderIsShared ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); + if ( !bOmitRightHeader ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_HEADER_TEXT ); + } + if ( !bHeaderIsShared && !bOmitLeftHeader ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_HEADER_TEXT_LEFT ); + } + } + + bool bHasPrevFooter = false; + bool bFooterIsShared = true; + OUString sFooterIsOn = getPropertyName( PROP_FOOTER_IS_ON ); + OUString sFooterIsShared = getPropertyName( PROP_FOOTER_IS_SHARED ); + if ( xPrevStyle.is() ) + { + xPrevStyle->getPropertyValue( sFooterIsOn ) >>= bHasPrevFooter; + xPrevStyle->getPropertyValue( sFooterIsShared ) >>= bFooterIsShared; + } + + if ( !bHasPrevFooter ) + return; + + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sFooterIsOn, sFooterIsShared }; + uno::Sequence<uno::Any> aValues { uno::Any( true ), uno::Any( bFooterIsShared ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); + if ( !bOmitRightFooter ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_FOOTER_TEXT ); + } + if ( !bFooterIsShared && !bOmitLeftFooter ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_FOOTER_TEXT_LEFT ); + } +} + +// Copy header and footer content from the previous docx section as needed. +// +// Any headers and footers which were not defined in this docx section +// should be "linked" with the corresponding header or footer from the +// previous section. LO does not support linking of header/footer content +// across page styles so we just copy the content from the previous section. +void SectionPropertyMap::CopyLastHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ) +{ + SAL_INFO( "writerfilter", "START>>> SectionPropertyMap::CopyLastHeaderFooter()" ); + SectionPropertyMap* pLastContext = rDM_Impl.GetLastSectionContext(); + if ( pLastContext ) + { + const bool bUseEvenPages = rDM_Impl.GetSettingsTable()->GetEvenAndOddHeaders(); + uno::Reference< beans::XPropertySet > xPrevStyle = pLastContext->GetPageStyle( rDM_Impl, + bFirstPage ); + uno::Reference< beans::XPropertySet > xStyle = GetPageStyle( rDM_Impl, + bFirstPage ); + + if ( bFirstPage ) + { + CopyHeaderFooter(rDM_Impl, xPrevStyle, xStyle, + !m_bFirstPageHeaderLinkToPrevious, true, + !m_bFirstPageFooterLinkToPrevious, true ); + } + else + { + CopyHeaderFooter(rDM_Impl, xPrevStyle, xStyle, + !m_bDefaultHeaderLinkToPrevious, + !(m_bEvenPageHeaderLinkToPrevious && bUseEvenPages), + !m_bDefaultFooterLinkToPrevious, + !(m_bEvenPageFooterLinkToPrevious && bUseEvenPages)); + } + } + SAL_INFO( "writerfilter", "END>>> SectionPropertyMap::CopyLastHeaderFooter()" ); +} + +void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage ) +{ + bool bCopyFirstToFollow = bFirstPage && m_bTitlePage && m_aFollowPageStyle.is(); + + sal_Int32 nTopMargin = m_nTopMargin; + sal_Int32 nHeaderHeight = m_nHeaderTop; + if ( HasHeader( bFirstPage ) ) + { + nTopMargin = m_nHeaderTop; + nHeaderHeight = m_nTopMargin - m_nHeaderTop; + + // minimum header height 1mm + if ( nHeaderHeight < MIN_HEAD_FOOT_HEIGHT ) + nHeaderHeight = MIN_HEAD_FOOT_HEIGHT; + } + + Insert(PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(m_bDynamicHeightTop)); + Insert(PROP_HEADER_DYNAMIC_SPACING, uno::Any(m_bDynamicHeightTop)); + Insert(PROP_HEADER_BODY_DISTANCE, uno::Any(nHeaderHeight - MIN_HEAD_FOOT_HEIGHT)); + Insert(PROP_HEADER_HEIGHT, uno::Any(nHeaderHeight)); + // looks like PROP_HEADER_HEIGHT = height of the header + space between the header, and the body + + if ( m_bDynamicHeightTop ) //fixed height header -> see WW8Par6.hxx + { + if (bCopyFirstToFollow && HasHeader(/*bFirstPage=*/true)) + { + m_aFollowPageStyle->setPropertyValue("HeaderDynamicSpacing", + getProperty(PROP_HEADER_DYNAMIC_SPACING)->second); + m_aFollowPageStyle->setPropertyValue("HeaderHeight", + getProperty(PROP_HEADER_HEIGHT)->second); + } + } + + sal_Int32 nBottomMargin = m_nBottomMargin; + sal_Int32 nFooterHeight = m_nHeaderBottom; + if ( HasFooter( bFirstPage ) ) + { + nBottomMargin = m_nHeaderBottom; + nFooterHeight = m_nBottomMargin - m_nHeaderBottom; + + // minimum footer height 1mm + if ( nFooterHeight < MIN_HEAD_FOOT_HEIGHT ) + nFooterHeight = MIN_HEAD_FOOT_HEIGHT; + } + + Insert(PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::Any(m_bDynamicHeightBottom)); + Insert(PROP_FOOTER_DYNAMIC_SPACING, uno::Any(m_bDynamicHeightBottom)); + Insert(PROP_FOOTER_BODY_DISTANCE, uno::Any(nFooterHeight - MIN_HEAD_FOOT_HEIGHT)); + Insert(PROP_FOOTER_HEIGHT, uno::Any(nFooterHeight)); + if (m_bDynamicHeightBottom) //fixed height footer -> see WW8Par6.hxx + { + if (bCopyFirstToFollow && HasFooter(/*bFirstPage=*/true)) + { + m_aFollowPageStyle->setPropertyValue("FooterDynamicSpacing", + getProperty(PROP_FOOTER_DYNAMIC_SPACING)->second); + m_aFollowPageStyle->setPropertyValue("FooterHeight", + getProperty(PROP_FOOTER_HEIGHT)->second); + } + } + + //now set the top/bottom margin for the follow page style + Insert( PROP_TOP_MARGIN, uno::Any( std::max<sal_Int32>(nTopMargin, 0) ) ); + Insert( PROP_BOTTOM_MARGIN, uno::Any( std::max<sal_Int32>(nBottomMargin, 0) ) ); +} + +static uno::Reference< beans::XPropertySet > lcl_GetRangeProperties( bool bIsFirstSection, + DomainMapper_Impl& rDM_Impl, + const uno::Reference< text::XTextRange >& xStartingRange ) +{ + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( bIsFirstSection && rDM_Impl.GetBodyText().is() ) + { + uno::Reference< container::XEnumerationAccess > xEnumAccess( rDM_Impl.GetBodyText(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration > xEnum = xEnumAccess->createEnumeration(); + xRangeProperties.set( xEnum->nextElement(), uno::UNO_QUERY_THROW ); + if ( rDM_Impl.GetIsDummyParaAddedForTableInSection() && xEnum->hasMoreElements() ) + xRangeProperties.set( xEnum->nextElement(), uno::UNO_QUERY_THROW ); + } + else if ( xStartingRange.is() ) + xRangeProperties.set( xStartingRange, uno::UNO_QUERY_THROW ); + return xRangeProperties; +} + +void SectionPropertyMap::HandleMarginsHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ) +{ + Insert( PROP_LEFT_MARGIN, uno::Any( m_nLeftMargin ) ); + Insert( PROP_RIGHT_MARGIN, uno::Any( m_nRightMargin ) ); + Insert(PROP_GUTTER_MARGIN, uno::Any(m_nGutterMargin)); + + if ( rDM_Impl.m_oBackgroundColor ) + Insert( PROP_BACK_COLOR, uno::Any( *rDM_Impl.m_oBackgroundColor ) ); + + // Check for missing footnote separator only in case there is at least + // one footnote. + if (rDM_Impl.m_bHasFtn && !rDM_Impl.m_bHasFtnSep) + { + // Set footnote line width to zero, document has no footnote separator. + Insert(PROP_FOOTNOTE_LINE_RELATIVE_WIDTH, uno::Any(sal_Int32(0))); + } + if ( rDM_Impl.m_bHasFtnSep ) + { + //If default paragraph style is RTL, footnote separator should be right aligned + //and for RTL locales, LTR default paragraph style should present a left aligned footnote separator + try + { + uno::Reference<style::XStyleFamiliesSupplier> xStylesSupplier(rDM_Impl.GetTextDocument(), uno::UNO_QUERY); + if ( xStylesSupplier.is() ) + { + uno::Reference<container::XNameAccess> xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xParagraphStyles; + if ( xStyleFamilies.is() ) + xStyleFamilies->getByName("ParagraphStyles") >>= xParagraphStyles; + uno::Reference<beans::XPropertySet> xStandard; + if ( xParagraphStyles.is() ) + xParagraphStyles->getByName("Standard") >>= xStandard; + if ( xStandard.is() ) + { + sal_Int16 aWritingMode(0); + xStandard->getPropertyValue( getPropertyName(PROP_WRITING_MODE) ) >>= aWritingMode; + if( aWritingMode == text::WritingMode2::RL_TB ) + Insert( PROP_FOOTNOTE_LINE_ADJUST, uno::Any( sal_Int16(text::HorizontalAdjust_RIGHT) ), false ); + else + Insert( PROP_FOOTNOTE_LINE_ADJUST, uno::Any( sal_Int16(text::HorizontalAdjust_LEFT) ), false ); + } + } + } + catch ( const uno::Exception& ) {} + } + + /*** if headers/footers are available then the top/bottom margins of the + header/footer are copied to the top/bottom margin of the page + */ + CopyLastHeaderFooter( bFirstPage, rDM_Impl ); + PrepareHeaderFooterProperties( bFirstPage ); + + // tdf#119952: If top/bottom margin was negative during docx import, + // then the header/footer and the body could be on top of each other + // writer is unable to display both of them in the same position, but can be simulated + // by moving the header/footer text into a flyframe anchored to the header/footer, + // leaving an empty dummy header/footer. + rDM_Impl.ConvertHeaderFooterToTextFrame(m_bDynamicHeightTop, m_bDynamicHeightBottom); +} + +bool SectionPropertyMap::FloatingTableConversion( const DomainMapper_Impl& rDM_Impl, FloatingTableInfo& rInfo ) +{ + // always convert non-floating tables to floating ones in footnotes and endnotes + if ( rInfo.m_bConvertToFloatingInFootnote ) + return true; + // This is OOXML version of the code deciding if the table needs to be + // in a floating frame. + // For ww8 code, see SwWW8ImplReader::FloatingTableConversion in + // sw/source/filter/ww8/ww8par.cxx + // The two should do the same, so if you make changes here, please check + // that the other is in sync. + + // Note that this is just a list of heuristics till sw core can have a + // table that is floating and can span over multiple pages at the same + // time. + + // If there is an explicit section break right after a table, then there + // will be no wrapping anyway. + if (rDM_Impl.m_bConvertedTable && !rDM_Impl.GetIsLastSectionGroup() && rInfo.m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextPage) + return false; + + sal_Int32 nVertOrientPosition = rInfo.getPropertyValue(u"VertOrientPosition").get<sal_Int32>(); + sal_Int16 nHoriOrientRelation = rInfo.getPropertyValue( u"HoriOrientRelation" ).get<sal_Int16>(); + if (nVertOrientPosition < 0 && nHoriOrientRelation != text::RelOrientation::PAGE_FRAME) + { + // Negative vertical position: then need a floating table, as normal tables can't have + // negative top margins. + return true; + } + + sal_Int32 nPageWidth = GetPageWidth(); + sal_Int32 nTextAreaWidth = nPageWidth - GetLeftMargin() - GetRightMargin(); + // Count the layout width of the table. + sal_Int32 nTableWidth = rInfo.m_nTableWidth; + if (rInfo.m_nTableWidthType == text::SizeType::VARIABLE) + { + nTableWidth *= nTextAreaWidth / 100.0; + } + sal_Int32 nLeftMargin = 0; + if ( rInfo.getPropertyValue( u"LeftMargin" ) >>= nLeftMargin ) + nTableWidth += nLeftMargin; + sal_Int32 nRightMargin = 0; + if ( rInfo.getPropertyValue( u"RightMargin" ) >>= nRightMargin ) + nTableWidth += nRightMargin; + + sal_Int16 nVertOrientRelation = rInfo.getPropertyValue( u"VertOrientRelation" ).get<sal_Int16>(); + if ( nHoriOrientRelation == text::RelOrientation::PAGE_FRAME && nVertOrientRelation == text::RelOrientation::PAGE_FRAME ) + { + sal_Int16 nHoriOrient = rInfo.getPropertyValue( u"HoriOrient" ).get<sal_Int16>(); + sal_Int16 nVertOrient = rInfo.getPropertyValue( u"VertOrient" ).get<sal_Int16>(); + if ( nHoriOrient == text::HoriOrientation::NONE && nVertOrient == text::VertOrientation::NONE ) + { + // Anchor position is relative to the page horizontally and vertically as well and is an absolute position. + // The more close we are to the left edge, the less likely there will be any wrapping. + // The more close we are to the bottom, the more likely the table will span over to the next page + // So if we're in the bottom left quarter, don't do any conversion. + sal_Int32 nHoriOrientPosition = rInfo.getPropertyValue( u"HoriOrientPosition" ).get<sal_Int32>(); + sal_Int32 nPageHeight = getProperty( PROP_HEIGHT )->second.get<sal_Int32>(); + if ( nHoriOrientPosition < (nPageWidth / 2) && nVertOrientPosition >( nPageHeight / 2 ) ) + return false; + } + } + + // It seems Word has a limit here, so that in case the table width is quite + // close to the text area width, then it won't perform a wrapping, even in + // case the content (e.g. an empty paragraph) would fit. The magic constant + // here represents this limit. + const sal_Int32 nMagicNumber = 469; + + // If the table's width is smaller than the text area width, text might + // be next to the table and so it should behave as a floating table. + if ( (nTableWidth + nMagicNumber) < nTextAreaWidth ) + return true; + + // If the position is relative to the edge of the page, then we need to check the whole + // page width to see whether text can fit next to the table. + if ( nHoriOrientRelation == text::RelOrientation::PAGE_FRAME ) + { + // If the table is wide enough so that no text fits next to it, then don't create a fly + // for the table: no wrapping will be performed anyway, but multi-page + // tables will be broken. + if ((nTableWidth + nMagicNumber) < (nPageWidth - std::min(GetLeftMargin(), GetRightMargin()))) + return true; + } + + // If there are columns, always create the fly, otherwise the columns would + // restrict geometry of the table. + if ( ColumnCount() > 1 ) + return true; + + return false; +} + +void SectionPropertyMap::InheritOrFinalizePageStyles( DomainMapper_Impl& rDM_Impl ) +{ + // if no new styles have been created for this section, inherit from the previous section, + // otherwise apply this section's settings to the new style. + // Ensure that FollowPage is inherited first - otherwise GetPageStyle may auto-create a follow when checking FirstPage. + SectionPropertyMap* pLastContext = rDM_Impl.GetLastSectionContext(); + //tdf124637 TODO: identify and skip special sections (like footnotes/endnotes) + if ( pLastContext && m_sFollowPageStyleName.isEmpty() ) + m_sFollowPageStyleName = pLastContext->GetPageStyleName(); + else + { + HandleMarginsHeaderFooter( /*bFirst=*/false, rDM_Impl ); + GetPageStyle( rDM_Impl, /*bFirst=*/false ); + if ( rDM_Impl.IsNewDoc() && m_aFollowPageStyle.is() ) + ApplyProperties_( m_aFollowPageStyle ); + } + + // FirstPageStyle may only be inherited if it will not be used or re-linked to a different follow + if ( !m_bTitlePage && pLastContext && m_sFirstPageStyleName.isEmpty() ) + m_sFirstPageStyleName = pLastContext->GetPageStyleName( /*bFirst=*/true ); + else + { + HandleMarginsHeaderFooter( /*bFirst=*/true, rDM_Impl ); + GetPageStyle( rDM_Impl, /*bFirst=*/true ); + if ( rDM_Impl.IsNewDoc() && m_aFirstPageStyle.is() ) + ApplyProperties_( m_aFirstPageStyle ); + + // Chain m_aFollowPageStyle to be after m_aFirstPageStyle + m_aFirstPageStyle->setPropertyValue( "FollowStyle", uno::Any( m_sFollowPageStyleName ) ); + } +} + +void SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing(DomainMapper_Impl& rDM_Impl) +{ + // Ignore Word 2010 and older. + if (rDM_Impl.GetSettingsTable()->GetWordCompatibilityMode() < 15) + return; + + sal_Int32 nPageWidth = GetPageWidth(); + sal_Int32 nTextAreaWidth = nPageWidth - GetLeftMargin() - GetRightMargin(); + + std::vector<AnchoredObjectsInfo>& rAnchoredObjectAnchors = rDM_Impl.m_aAnchoredObjectAnchors; + for (const auto& rAnchor : rAnchoredObjectAnchors) + { + // Ignore this paragraph when there are not enough shapes to trigger the Word bug we + // emulate. + if (rAnchor.m_aAnchoredObjects.size() < 4) + continue; + + // Ignore this paragraph if none of the objects are wrapped in the background. + sal_Int32 nOpaqueCount = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + // Ignore inline objects stored only for redlining. + if (rAnchored.m_xRedlineForInline) + continue; + + uno::Reference<beans::XPropertySet> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + { + continue; + } + + bool bOpaque = true; + xShape->getPropertyValue("Opaque") >>= bOpaque; + if (!bOpaque) + { + ++nOpaqueCount; + } + } + if (nOpaqueCount < 1) + { + continue; + } + + // Analyze the anchored objects of this paragraph, now that we know the + // page width. + sal_Int32 nShapesWidth = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + uno::Reference<drawing::XShape> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + continue; + + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + if (!xPropertySet.is()) + continue; + + // Ignore objects with no wrapping. + text::WrapTextMode eWrap = text::WrapTextMode_THROUGH; + xPropertySet->getPropertyValue("Surround") >>= eWrap; + if (eWrap == text::WrapTextMode_THROUGH) + continue; + + // Use the original left margin, in case GraphicImport::lcl_sprm() reduced the doc model + // one to 0. + sal_Int32 nLeftMargin = rAnchored.m_nLeftMargin; + sal_Int32 nRightMargin = 0; + xPropertySet->getPropertyValue("RightMargin") >>= nRightMargin; + nShapesWidth += xShape->getSize().Width + nLeftMargin + nRightMargin; + } + + // Ignore cases when we have enough horizontal space for the shapes. + if (nTextAreaWidth > nShapesWidth) + continue; + + sal_Int32 nHeight = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + uno::Reference<drawing::XShape> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + continue; + + nHeight += xShape->getSize().Height; + } + + uno::Reference<beans::XPropertySet> xParagraph(rAnchor.m_xParagraph, uno::UNO_QUERY); + if (xParagraph.is()) + { + sal_Int32 nTopMargin = 0; + xParagraph->getPropertyValue("ParaTopMargin") >>= nTopMargin; + // Increase top spacing of the paragraph to match Word layout + // behavior. + nTopMargin = std::max(nTopMargin, nHeight); + xParagraph->setPropertyValue("ParaTopMargin", uno::Any(nTopMargin)); + } + } + rAnchoredObjectAnchors.clear(); +} + +void SectionPropertyMap::CloseSectionGroup( DomainMapper_Impl& rDM_Impl ) +{ + SectionPropertyMap* pPrevSection = rDM_Impl.GetLastSectionContext(); + + // The default section type is nextPage. + if ( m_nBreakType == -1 ) + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextColumn ) + { + // Word 2013+ seems to treat a section column break as a page break all the time. + // It always acts like a page break if there are no columns, or a different number of columns. + // Also, if this is the first section, the break type is basically irrelevant - works best as nextPage. + if ( rDM_Impl.GetSettingsTable()->GetWordCompatibilityMode() > 14 + || !pPrevSection + || m_nColumnCount < 2 + || m_nColumnCount != pPrevSection->ColumnCount() + ) + { + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + } + } + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_continuous ) + { + // if page orientation differs from previous section, it can't be treated as continuous + if ( pPrevSection ) + { + bool bIsLandscape = false; + std::optional< PropertyMap::Property > pProp = getProperty( PROP_IS_LANDSCAPE ); + if ( pProp ) + pProp->second >>= bIsLandscape; + + bool bPrevIsLandscape = false; + pProp = pPrevSection->getProperty( PROP_IS_LANDSCAPE ); + if ( pProp ) + pProp->second >>= bPrevIsLandscape; + + if ( bIsLandscape != bPrevIsLandscape ) + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + } + } + + // Text area width is known at the end of a section: decide if tables should be converted or not. + std::vector<FloatingTableInfo>& rPendingFloatingTables = rDM_Impl.m_aPendingFloatingTables; + for ( FloatingTableInfo & rInfo : rPendingFloatingTables ) + { + rInfo.m_nBreakType = m_nBreakType; + if ( FloatingTableConversion( rDM_Impl, rInfo ) ) + { + uno::Reference<text::XTextAppendAndConvert> xBodyText( + rInfo.m_bConvertToFloatingInFootnote + ? rInfo.m_xStart->getText() + : rDM_Impl.GetBodyText(), uno::UNO_QUERY ); + std::deque<css::uno::Any> aFramedRedlines = rDM_Impl.m_aStoredRedlines[StoredRedlines::FRAME]; + try + { + // convert redline ranges to cursor movement and character length + std::vector<sal_Int32> redPos, redLen; + std::vector<OUString> redCell; + std::vector<OUString> redTable; + for( size_t i = 0; i < aFramedRedlines.size(); i+=3) + { + uno::Reference<text::XText> xCell; + uno::Reference< text::XTextRange > xRange; + aFramedRedlines[i] >>= xRange; + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( xRange.is() ) + { + OUString sTableName; + OUString sCellName; + xRangeProperties.set( xRange, uno::UNO_QUERY_THROW ); + if (xRangeProperties->getPropertySetInfo()->hasPropertyByName("TextTable")) + { + uno::Any aTable = xRangeProperties->getPropertyValue("TextTable"); + if ( aTable != uno::Any() ) + { + uno::Reference<text::XTextTable> xTable; + aTable >>= xTable; + uno::Reference<beans::XPropertySet> xTableProperties(xTable, uno::UNO_QUERY); + xTableProperties->getPropertyValue("TableName") >>= sTableName; + } + if (xRangeProperties->getPropertySetInfo()->hasPropertyByName("Cell")) + { + uno::Any aCell = xRangeProperties->getPropertyValue("Cell"); + if ( aCell != uno::Any() ) + { + aCell >>= xCell; + uno::Reference<beans::XPropertySet> xCellProperties(xCell, uno::UNO_QUERY); + xCellProperties->getPropertyValue("CellName") >>= sCellName; + } + } + } + redTable.push_back(sTableName); + redCell.push_back(sCellName); + bool bOk = false; + if (!sTableName.isEmpty() && !sCellName.isEmpty()) + { + uno::Reference<text::XTextCursor> xRangeCursor = xCell->createTextCursorByRange( xRange ); + if ( xRangeCursor.is() ) + { + bOk = true; + sal_Int32 nLen = xRange->getString().getLength(); + redLen.push_back(nLen); + xRangeCursor->gotoStart(true); + redPos.push_back(xRangeCursor->getString().getLength() - nLen); + } + } + if (!bOk) + { + // missing cell or failed createTextCursorByRange() + redLen.push_back(-1); + redPos.push_back(-1); + } + } + } + + const uno::Reference< text::XTextContent >& xTextContent = + xBodyText->convertToTextFrame(rInfo.m_xStart, rInfo.m_xEnd, + rInfo.m_aFrameProperties); + + // paragraph of the anchoring point of the floating table needs zero top and bottom + // margins, if the table was a not floating table in the footnote, otherwise + // docDefault margins could result bigger vertical spaces around the table + if ( rInfo.m_bConvertToFloatingInFootnote && xTextContent.is() ) + { + uno::Reference<beans::XPropertySet> xParagraph( + xTextContent->getAnchor(), uno::UNO_QUERY); + if ( xParagraph.is() ) + { + xParagraph->setPropertyValue("ParaTopMargin", + uno::Any(static_cast<sal_Int32>(0))); + xParagraph->setPropertyValue("ParaBottomMargin", + uno::Any(static_cast<sal_Int32>(0))); + } + } + + uno::Reference<text::XTextTablesSupplier> xTextDocument(rDM_Impl.GetTextDocument(), uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTables = xTextDocument->getTextTables(); + for( size_t i = 0; i < aFramedRedlines.size(); i+=3) + { + OUString sType; + beans::PropertyValues aRedlineProperties( 3 ); + // skip failed createTextCursorByRange() + if (redPos[i/3] == -1) + continue; + aFramedRedlines[i+1] >>= sType; + aFramedRedlines[i+2] >>= aRedlineProperties; + uno::Reference<text::XTextTable> xTable(xTables->getByName(redTable[i/3]), uno::UNO_QUERY); + uno::Reference<text::XText> xCell(xTable->getCellByName(redCell[i/3]), uno::UNO_QUERY); + uno::Reference<text::XTextCursor> xCrsr = xCell->createTextCursor(); + xCrsr->goRight(redPos[i/3], false); + xCrsr->goRight(redLen[i/3], true); + uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW ); + try + { + xRedline->makeRedline( sType, aRedlineProperties ); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "makeRedline() failed"); + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "convertToTextFrame() failed"); + } + + aFramedRedlines.clear(); + } + } + rPendingFloatingTables.clear(); + + try + { + HandleIncreasedAnchoredObjectSpacing(rDM_Impl); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "HandleIncreasedAnchoredObjectSpacing() failed"); + } + + if ( m_nLnnMod ) + { + bool bFirst = rDM_Impl.IsLineNumberingSet(); + rDM_Impl.SetLineNumbering( m_nLnnMod, m_nLnc, m_ndxaLnn ); + if ( m_nLnnMin > 0 || (bFirst && m_nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newSection) ) + { + //set the starting value at the beginning of the section + try + { + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( m_xStartingRange.is() ) + { + xRangeProperties.set( m_xStartingRange, uno::UNO_QUERY_THROW ); + } + else + { + //set the start value at the beginning of the document + xRangeProperties.set( rDM_Impl.GetTextDocument()->getText()->getStart(), uno::UNO_QUERY_THROW ); + } + // Writer is 1-based, Word is 0-based. + xRangeProperties->setPropertyValue( + getPropertyName(PROP_PARA_LINE_NUMBER_START_VALUE), + uno::Any(m_nLnnMin + 1)); + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper", "Exception in SectionPropertyMap::CloseSectionGroup"); + } + } + } + + if (m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_continuous + && !rDM_Impl.IsInComments()) + { + //todo: insert a section or access the already inserted section + uno::Reference< beans::XPropertySet > xSection = + rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + { + if ( m_nColumnCount > 1 ) + ApplyColumnProperties( xSection, rDM_Impl ); + + ApplyProtectionProperties( xSection, rDM_Impl ); + } + + try + { + InheritOrFinalizePageStyles( rDM_Impl ); + ApplySectionProperties( xSection, rDM_Impl ); //depends on InheritOrFinalizePageStyles + OUString aName = m_bTitlePage ? m_sFirstPageStyleName : m_sFollowPageStyleName; + uno::Reference< beans::XPropertySet > xRangeProperties( lcl_GetRangeProperties( m_bIsFirstSection, rDM_Impl, m_xStartingRange ) ); + if ( m_bIsFirstSection && !aName.isEmpty() && xRangeProperties.is() ) + { + xRangeProperties->setPropertyValue( getPropertyName( PROP_PAGE_DESC_NAME ), uno::Any( aName ) ); + } + else if ((!m_bFirstPageHeaderLinkToPrevious || + !m_bFirstPageFooterLinkToPrevious || + !m_bDefaultHeaderLinkToPrevious || + !m_bDefaultFooterLinkToPrevious || + !m_bEvenPageHeaderLinkToPrevious || + !m_bEvenPageFooterLinkToPrevious) + && rDM_Impl.GetCurrentXText()) + { // find a node in the section that has a page break and change + // it to apply the page style; see "nightmare scenario" in + // wwSectionManager::InsertSegments() + auto xTextAppend = rDM_Impl.GetCurrentXText(); + uno::Reference<container::XEnumerationAccess> const xCursor( + xTextAppend->createTextCursorByRange( + uno::Reference<text::XTextContent>(xSection, uno::UNO_QUERY_THROW)->getAnchor()), + uno::UNO_QUERY_THROW); + uno::Reference<container::XEnumeration> const xEnum( + xCursor->createEnumeration()); + bool isFound = false; + while (xEnum->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xElem; + xEnum->nextElement() >>= xElem; + if (xElem->getPropertySetInfo()->hasPropertyByName("BreakType")) + { + style::BreakType bt; + if ((xElem->getPropertyValue("BreakType") >>= bt) + && bt == style::BreakType_PAGE_BEFORE) + { + // tdf#112201: do *not* use m_sFirstPageStyleName here! + xElem->setPropertyValue(getPropertyName(PROP_PAGE_DESC_NAME), + uno::Any(m_sFollowPageStyleName)); + isFound = true; + break; + } + } + } + uno::Reference<text::XParagraphCursor> const xPCursor(xCursor, + uno::UNO_QUERY_THROW); + float fCharHeight = 0; + if (!isFound) + { // HACK: try the last paragraph of the previous section + xPCursor->gotoPreviousParagraph(false); + uno::Reference<beans::XPropertySet> const xPSCursor(xCursor, uno::UNO_QUERY_THROW); + style::BreakType bt; + if ((xPSCursor->getPropertyValue("BreakType") >>= bt) + && bt == style::BreakType_PAGE_BEFORE) + { + xPSCursor->setPropertyValue(getPropertyName(PROP_PAGE_DESC_NAME), + uno::Any(m_sFollowPageStyleName)); + isFound = true; + } + else + { + xPSCursor->getPropertyValue("CharHeight") >>= fCharHeight; + } + } + if (!isFound && fCharHeight <= 1.0) + { + // If still not found, see if the last paragraph is ~invisible, and work with + // the last-in-practice paragraph. + xPCursor->gotoPreviousParagraph(false); + uno::Reference<beans::XPropertySet> xPropertySet(xCursor, uno::UNO_QUERY_THROW); + OUString aPageDescName; + if ((xPropertySet->getPropertyValue("PageDescName") >>= aPageDescName) + && !aPageDescName.isEmpty()) + { + uno::Reference<beans::XPropertySet> xPageStyle( + rDM_Impl.GetPageStyles()->getByName(aPageDescName), uno::UNO_QUERY); + xPageStyle->setPropertyValue("FollowStyle", + uno::Any(m_sFollowPageStyleName)); + } + } + } + } + catch ( const uno::Exception& ) + { + SAL_WARN( "writerfilter", "failed to set PageDescName!" ); + } + } + // If the section is of type "New column" (0x01), then simply insert a column break. + // But only if there actually are columns on the page, otherwise a column break + // seems to be handled like a page break by MSO. + else if (m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextColumn + && m_nColumnCount > 1 && !rDM_Impl.IsInComments()) + { + try + { + InheritOrFinalizePageStyles( rDM_Impl ); + /*TODO tdf#135343: Just inserting a column break sounds like the right idea, but the implementation is wrong. + * Somehow, the previous column section needs to be extended to cover this new text. + * Currently, it is completely broken, producing a no-column section that starts on a new page. + */ + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( m_xStartingRange.is() ) + { + xRangeProperties.set( m_xStartingRange, uno::UNO_QUERY_THROW ); + } + else + { + //set the start value at the beginning of the document + xRangeProperties.set( rDM_Impl.GetTextDocument()->getText()->getStart(), uno::UNO_QUERY_THROW ); + } + xRangeProperties->setPropertyValue( getPropertyName( PROP_BREAK_TYPE ), uno::Any( style::BreakType_COLUMN_BEFORE ) ); + } + catch ( const uno::Exception& ) {} + } + else if (!rDM_Impl.IsInComments()) + { + uno::Reference< beans::XPropertySet > xSection; + ApplyProtectionProperties( xSection, rDM_Impl ); + + //get the properties and create appropriate page styles + uno::Reference< beans::XPropertySet > xFollowPageStyle; + //This part certainly is not needed for footnotes, so don't create unused page styles. + if ( !rDM_Impl.IsInFootOrEndnote() ) + { + xFollowPageStyle.set( GetPageStyle( rDM_Impl, false ) ); + + HandleMarginsHeaderFooter(/*bFirstPage=*/false, rDM_Impl ); + } + + if ( rDM_Impl.GetSettingsTable()->GetMirrorMarginSettings() ) + { + Insert( PROP_PAGE_STYLE_LAYOUT, uno::Any( style::PageStyleLayout_MIRRORED ) ); + } + uno::Reference< text::XTextColumns > xColumns; + if ( m_nColumnCount > 1 ) + { + // prefer setting column properties into a section, not a page style if at all possible. + if ( !xSection.is() ) + xSection = rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + ApplyColumnProperties( xSection, rDM_Impl ); + else if ( xFollowPageStyle.is() ) + xColumns = ApplyColumnProperties( xFollowPageStyle, rDM_Impl ); + } + + // these BreakTypes are effectively page-breaks: don't evenly distribute text in columns before a page break; + if ( pPrevSection && pPrevSection->ColumnCount() ) + pPrevSection->DontBalanceTextColumns(); + + //prepare text grid properties + sal_Int32 nHeight = 1; + std::optional< PropertyMap::Property > pProp = getProperty( PROP_HEIGHT ); + if ( pProp ) + pProp->second >>= nHeight; + + sal_Int32 nWidth = 1; + pProp = getProperty( PROP_WIDTH ); + if ( pProp ) + pProp->second >>= nWidth; + + sal_Int16 nWritingMode = text::WritingMode2::LR_TB; + pProp = getProperty( PROP_WRITING_MODE ); + if ( pProp ) + pProp->second >>= nWritingMode; + + sal_Int32 nTextAreaHeight = nWritingMode == text::WritingMode2::LR_TB ? + nHeight - m_nTopMargin - m_nBottomMargin : + nWidth - m_nLeftMargin - m_nRightMargin; + + sal_Int32 nGridLinePitch = m_nGridLinePitch; + //sep.dyaLinePitch + if ( nGridLinePitch < 1 || nGridLinePitch > 31680 ) + { + SAL_WARN( "writerfilter", "sep.dyaLinePitch outside legal range: " << nGridLinePitch ); + nGridLinePitch = 1; + } + + const sal_Int32 nGridLines = nTextAreaHeight / nGridLinePitch; + sal_Int16 nGridType = m_nGridType; + if ( nGridLines >= 0 && nGridLines <= SAL_MAX_INT16 ) + Insert( PROP_GRID_LINES, uno::Any( sal_Int16(nGridLines) ) ); + else + nGridType = text::TextGridMode::NONE; + + // PROP_GRID_MODE + if ( nGridType == text::TextGridMode::LINES_AND_CHARS ) + { + if (!m_nDxtCharSpace) + nGridType = text::TextGridMode::LINES; + else + Insert( PROP_GRID_SNAP_TO_CHARS, uno::Any( m_bGridSnapToChars ) ); + } + + Insert( PROP_GRID_MODE, uno::Any( nGridType ) ); + + sal_Int32 nCharWidth = 423; //240 twip/ 12 pt + const StyleSheetEntryPtr pEntry = rDM_Impl.GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( u"Standard" ); + if ( pEntry ) + { + std::optional< PropertyMap::Property > pPropHeight = pEntry->pProperties->getProperty( PROP_CHAR_HEIGHT_ASIAN ); + if ( pPropHeight ) + { + double fHeight = 0; + if ( pPropHeight->second >>= fHeight ) + nCharWidth = ConversionHelper::convertTwipToMM100( static_cast<tools::Long>(fHeight * 20.0 + 0.5) ); + } + } + + //dxtCharSpace + if ( m_nDxtCharSpace ) + { + sal_Int32 nCharSpace = m_nDxtCharSpace; + //main lives in top 20 bits, and is signed. + sal_Int32 nMain = (nCharSpace & 0xFFFFF000); + nMain /= 0x1000; + nCharWidth += ConversionHelper::convertTwipToMM100( nMain * 20 ); + + sal_Int32 nFraction = (nCharSpace & 0x00000FFF); + nFraction = (nFraction * 20) / 0xFFF; + nCharWidth += ConversionHelper::convertTwipToMM100( nFraction ); + } + + if ( m_nPageNumberType >= 0 ) + Insert( PROP_NUMBERING_TYPE, uno::Any( m_nPageNumberType ) ); + + // #i119558#, force to set document as standard page mode, + // refer to ww8 import process function "SwWW8ImplReader::SetDocumentGrid" + try + { + uno::Reference< beans::XPropertySet > xDocProperties; + xDocProperties.set( rDM_Impl.GetTextDocument(), uno::UNO_QUERY_THROW ); + Insert(PROP_GRID_STANDARD_MODE, uno::Any(true)); + xDocProperties->setPropertyValue("DefaultPageMode", uno::Any(false)); + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper", "Exception in SectionPropertyMap::CloseSectionGroup"); + } + + Insert( PROP_GRID_BASE_HEIGHT, uno::Any( nGridLinePitch ) ); + Insert( PROP_GRID_BASE_WIDTH, uno::Any( nCharWidth ) ); + Insert( PROP_GRID_RUBY_HEIGHT, uno::Any( sal_Int32( 0 ) ) ); + + if ( rDM_Impl.IsNewDoc() && xFollowPageStyle.is() ) + ApplyProperties_( xFollowPageStyle ); + + //todo: creating a "First Page" style depends on HasTitlePage and _fFacingPage_ + if ( m_bTitlePage ) + { + CopyLastHeaderFooter( true, rDM_Impl ); + PrepareHeaderFooterProperties( true ); + uno::Reference< beans::XPropertySet > xFirstPageStyle = GetPageStyle( + rDM_Impl, true ); + if ( rDM_Impl.IsNewDoc() ) + ApplyProperties_( xFirstPageStyle ); + + if ( xColumns.is() ) + xFirstPageStyle->setPropertyValue( + getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xColumns ) ); + } + + ApplyBorderToPageStyles( rDM_Impl, m_eBorderApply, m_eBorderOffsetFrom ); + + try + { + //now apply this break at the first paragraph of this section + uno::Reference< beans::XPropertySet > xRangeProperties( lcl_GetRangeProperties( m_bIsFirstSection, rDM_Impl, m_xStartingRange ) ); + + // Handle page breaks with odd/even page numbering. We need to use an extra page style for setting the page style + // to left/right, because if we set it to the normal style, we'd set it to "First Page"/"Default Style", which would + // break them (all default pages would be only left or right). + if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_evenPage || m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_oddPage ) + { + OUString* pageStyle = m_bTitlePage ? &m_sFirstPageStyleName : &m_sFollowPageStyleName; + OUString evenOddStyleName = rDM_Impl.GetUnusedPageStyleName(); + uno::Reference< beans::XPropertySet > evenOddStyle( + rDM_Impl.GetTextFactory()->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + // Unfortunately using setParent() does not work for page styles, so make a deep copy of the page style. + uno::Reference< beans::XPropertySet > pageProperties( m_bTitlePage ? m_aFirstPageStyle : m_aFollowPageStyle ); + uno::Reference< beans::XPropertySetInfo > pagePropertiesInfo( pageProperties->getPropertySetInfo() ); + const uno::Sequence< beans::Property > propertyList( pagePropertiesInfo->getProperties() ); + // Ignore write-only properties. + static const o3tl::sorted_vector<OUString> aDenylist + = { "FooterBackGraphicURL", "BackGraphicURL", "HeaderBackGraphicURL" }; + for ( const auto& rProperty : propertyList ) + { + if ( (rProperty.Attributes & beans::PropertyAttribute::READONLY) == 0 ) + { + if (aDenylist.find(rProperty.Name) == aDenylist.end()) + evenOddStyle->setPropertyValue( + rProperty.Name, + pageProperties->getPropertyValue(rProperty.Name)); + } + } + evenOddStyle->setPropertyValue( "FollowStyle", uno::Any( *pageStyle ) ); + rDM_Impl.GetPageStyles()->insertByName( evenOddStyleName, uno::Any( evenOddStyle ) ); + evenOddStyle->setPropertyValue( "HeaderIsOn", uno::Any( false ) ); + evenOddStyle->setPropertyValue( "FooterIsOn", uno::Any( false ) ); + CopyHeaderFooter(rDM_Impl, pageProperties, evenOddStyle); + *pageStyle = evenOddStyleName; // And use it instead of the original one (which is set as follow of this one). + if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_evenPage ) + evenOddStyle->setPropertyValue( getPropertyName( PROP_PAGE_STYLE_LAYOUT ), uno::Any( style::PageStyleLayout_LEFT ) ); + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_oddPage ) + evenOddStyle->setPropertyValue( getPropertyName( PROP_PAGE_STYLE_LAYOUT ), uno::Any( style::PageStyleLayout_RIGHT ) ); + } + + if (rDM_Impl.m_xAltChunkStartingRange.is()) + { + xRangeProperties.set(rDM_Impl.m_xAltChunkStartingRange, uno::UNO_QUERY); + } + if (xRangeProperties.is() && (rDM_Impl.IsNewDoc() || rDM_Impl.IsAltChunk())) + { + // Avoid setting page style in case of autotext: so inserting the autotext at the + // end of the document does not introduce an unwanted page break. + // Also avoid setting the page style at the very beginning if it still is the default page style. + const OUString sPageStyle = m_bTitlePage ? m_sFirstPageStyleName : m_sFollowPageStyleName; + if (!rDM_Impl.IsReadGlossaries() + && !rDM_Impl.IsInFootOrEndnote() + && !(m_bIsFirstSection && sPageStyle == getPropertyName( PROP_STANDARD ) && m_nPageNumber < 0) + ) + { + xRangeProperties->setPropertyValue( + getPropertyName( PROP_PAGE_DESC_NAME ), + uno::Any(sPageStyle) ); + } + + if (0 <= m_nPageNumber) + { + sal_Int16 nPageNumber = static_cast< sal_Int16 >(m_nPageNumber); + xRangeProperties->setPropertyValue(getPropertyName(PROP_PAGE_NUMBER_OFFSET), + uno::Any(nPageNumber)); + } + } + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::CloseSectionGroup" ); + } + } + + // Now that the margins are known, resize relative width shapes because some shapes in LO do not support percentage-sizes + sal_Int32 nParagraphWidth = GetPageWidth() - m_nLeftMargin - m_nRightMargin; + if ( m_nColumnCount > 1 ) + { + // skip custom-width columns since we don't know what column the shape is in. + if ( !m_aColWidth.empty() ) + nParagraphWidth = 0; + else + nParagraphWidth = (nParagraphWidth - (m_nColumnDistance * (m_nColumnCount - 1))) / m_nColumnCount; + } + if ( nParagraphWidth > 0 ) + { + const OUString sPropRelativeWidth = getPropertyName(PROP_RELATIVE_WIDTH); + for ( const auto& xShape : m_xRelativeWidthShapes ) + { + const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY ); + if ( xShapePropertySet->getPropertySetInfo()->hasPropertyByName(sPropRelativeWidth) ) + { + sal_uInt16 nPercent = 0; + try + { + xShapePropertySet->getPropertyValue(sPropRelativeWidth) >>= nPercent; + } + 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", "Getting relative width failed. " << e.Message); + } + if ( nPercent ) + { + const sal_Int32 nWidth = nParagraphWidth * nPercent / 100; + xShape->setSize( awt::Size( nWidth, xShape->getSize().Height ) ); + } + } + } + } + m_xRelativeWidthShapes.clear(); + + rDM_Impl.SetIsLastSectionGroup( false ); + rDM_Impl.SetIsFirstParagraphInSection( true ); + + if ( !rDM_Impl.IsInFootOrEndnote() && !rDM_Impl.IsInComments() ) + { + rDM_Impl.m_bHasFtn = false; + rDM_Impl.m_bHasFtnSep = false; + } +} + +// Clear the flag that says we should take the header/footer content from +// the previous section. This should be called when we encounter a header +// or footer definition for this section. +void SectionPropertyMap::ClearHeaderFooterLinkToPrevious( bool bHeader, PageType eType ) +{ + if ( bHeader ) + { + switch ( eType ) + { + case PAGE_FIRST: m_bFirstPageHeaderLinkToPrevious = false; break; + case PAGE_LEFT: m_bEvenPageHeaderLinkToPrevious = false; break; + case PAGE_RIGHT: m_bDefaultHeaderLinkToPrevious = false; break; + // no default case as all enumeration values have been covered + } + } + else + { + switch ( eType ) + { + case PAGE_FIRST: m_bFirstPageFooterLinkToPrevious = false; break; + case PAGE_LEFT: m_bEvenPageFooterLinkToPrevious = false; break; + case PAGE_RIGHT: m_bDefaultFooterLinkToPrevious = false; break; + } + } +} + +namespace { + +class NamedPropertyValue +{ +private: + OUString m_aName; + +public: + explicit NamedPropertyValue( const OUString& i_aStr ) + : m_aName( i_aStr ) + { + } + + bool operator() ( beans::PropertyValue const & aVal ) + { + return aVal.Name == m_aName; + } +}; + +} + +void SectionPropertyMap::ApplyProperties_( const uno::Reference< beans::XPropertySet >& xStyle ) +{ + uno::Reference< beans::XMultiPropertySet > const xMultiSet( xStyle, uno::UNO_QUERY ); + + std::vector< OUString > vNames; + std::vector< uno::Any > vValues; + { + // Convert GetPropertyValues() value into something useful + const uno::Sequence< beans::PropertyValue > vPropVals = GetPropertyValues(); + + //Temporarily store the items that are in grab bags + uno::Sequence< beans::PropertyValue > vCharVals; + uno::Sequence< beans::PropertyValue > vParaVals; + const beans::PropertyValue* pCharGrabBag = std::find_if( vPropVals.begin(), vPropVals.end(), NamedPropertyValue( "CharInteropGrabBag" ) ); + if ( pCharGrabBag != vPropVals.end() ) + (pCharGrabBag->Value) >>= vCharVals; + const beans::PropertyValue* pParaGrabBag = std::find_if( vPropVals.begin(), vPropVals.end(), NamedPropertyValue( "ParaInteropGrabBag" ) ); + if ( pParaGrabBag != vPropVals.end() ) + (pParaGrabBag->Value) >>= vParaVals; + + for ( const beans::PropertyValue* pIter = vPropVals.begin(); pIter != vPropVals.end(); ++pIter ) + { + if ( pIter != pCharGrabBag && pIter != pParaGrabBag + && pIter->Name != "IsProtected" //section-only property + ) + { + vNames.push_back( pIter->Name ); + vValues.push_back( pIter->Value ); + } + } + for ( const beans::PropertyValue & v : std::as_const(vCharVals) ) + { + vNames.push_back( v.Name ); + vValues.push_back( v.Value ); + } + for ( const beans::PropertyValue & v : std::as_const(vParaVals) ) + { + vNames.push_back( v.Name ); + vValues.push_back( v.Value ); + } + } + if ( xMultiSet.is() ) + { + try + { + xMultiSet->setPropertyValues( comphelper::containerToSequence( vNames ), comphelper::containerToSequence( vValues ) ); + return; + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyProperties_" ); + } + } + for ( size_t i = 0; i < vNames.size(); ++i ) + { + try + { + if ( xStyle.is() ) + xStyle->setPropertyValue( vNames[i], vValues[i] ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyProperties_" ); + } + } +} + +sal_Int32 SectionPropertyMap::GetPageWidth() const +{ + return getProperty( PROP_WIDTH )->second.get<sal_Int32>(); +} + +StyleSheetPropertyMap::StyleSheetPropertyMap() + : mnListLevel( -1 ) + , mnOutlineLevel( -1 ) +{ +} + +ParagraphProperties::ParagraphProperties() + : m_bFrameMode( false ) + , m_nDropCap( NS_ooxml::LN_Value_doc_ST_DropCap_none ) + , m_nLines( 0 ) + , m_w( -1 ) + , m_h( -1 ) + , m_nWrap( text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE ) + , m_hAnchor( -1 ) + , m_vAnchor( -1 ) + , m_x( -1 ) + , m_bxValid( false ) + , m_y( -1 ) + , m_byValid( false ) + , m_hSpace( -1 ) + , m_vSpace( -1 ) + , m_hRule( -1 ) + , m_xAlign( -1 ) + , m_yAlign( -1 ) + , m_nDropCapLength( 0 ) +{ +} + +bool ParagraphProperties::operator==( const ParagraphProperties& rCompare ) +{ + return ( m_bFrameMode == rCompare.m_bFrameMode && + m_nDropCap == rCompare.m_nDropCap && + m_nLines == rCompare.m_nLines && + m_w == rCompare.m_w && + m_h == rCompare.m_h && + m_nWrap == rCompare.m_nWrap && + m_hAnchor == rCompare.m_hAnchor && + m_vAnchor == rCompare.m_vAnchor && + m_x == rCompare.m_x && + m_bxValid == rCompare.m_bxValid && + m_y == rCompare.m_y && + m_byValid == rCompare.m_byValid && + m_hSpace == rCompare.m_hSpace && + m_vSpace == rCompare.m_vSpace && + m_hRule == rCompare.m_hRule && + m_xAlign == rCompare.m_xAlign && + m_yAlign == rCompare.m_yAlign ); +} + +void ParagraphProperties::ResetFrameProperties() +{ + m_bFrameMode = false; + m_nDropCap = NS_ooxml::LN_Value_doc_ST_DropCap_none; + m_nLines = 0; + m_w = -1; + m_h = -1; + m_nWrap = text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE; + m_hAnchor = -1; + m_vAnchor = -1; + m_x = -1; + m_bxValid = false; + m_y = -1; + m_byValid = false; + m_hSpace = -1; + m_vSpace = -1; + m_hRule = -1; + m_xAlign = -1; + m_yAlign = -1; + m_nDropCapLength = 0; +} + +bool TablePropertyMap::getValue( TablePropertyMapTarget eWhich, sal_Int32& nFill ) +{ + if ( eWhich < TablePropertyMapTarget_MAX ) + { + if ( m_aValidValues[eWhich].bValid ) + nFill = m_aValidValues[eWhich].nValue; + return m_aValidValues[eWhich].bValid; + } + else + { + OSL_FAIL( "invalid TablePropertyMapTarget" ); + return false; + } +} + +void TablePropertyMap::setValue( TablePropertyMapTarget eWhich, sal_Int32 nSet ) +{ + if ( eWhich < TablePropertyMapTarget_MAX ) + { + m_aValidValues[eWhich].bValid = true; + m_aValidValues[eWhich].nValue = nSet; + } + else + OSL_FAIL( "invalid TablePropertyMapTarget" ); +} + +void TablePropertyMap::insertTableProperties( const PropertyMap* pMap, const bool bOverwrite ) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement( "TablePropertyMap.insertTableProperties" ); + pMap->dumpXml(); +#endif + + const TablePropertyMap* pSource = dynamic_cast< const TablePropertyMap* >(pMap); + if ( pSource ) + { + for ( sal_Int32 eTarget = TablePropertyMapTarget_START; + eTarget < TablePropertyMapTarget_MAX; ++eTarget ) + { + if ( pSource->m_aValidValues[eTarget].bValid && (bOverwrite || !m_aValidValues[eTarget].bValid) ) + { + m_aValidValues[eTarget].bValid = true; + m_aValidValues[eTarget].nValue = pSource->m_aValidValues[eTarget].nValue; + } + } + } + +#ifdef DBG_UTIL + dumpXml(); + TagLogger::getInstance().endElement(); +#endif +} + +} // namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyMap.hxx b/writerfilter/source/dmapper/PropertyMap.hxx new file mode 100644 index 000000000..988c99b02 --- /dev/null +++ b/writerfilter/source/dmapper/PropertyMap.hxx @@ -0,0 +1,614 @@ +/* -*- 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 . + */ +#pragma once + +#include <rtl/ustring.hxx> +#include <tools/ref.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/drawing/XShape.hpp> +#include "PropertyIds.hxx" +#include <memory> +#include <optional> +#include <map> +#include <vector> +#include <set> + +namespace com::sun::star { + namespace beans { + struct PropertyValue; + } + namespace container { + class XNameContainer; + } + namespace lang { + class XMultiServiceFactory; + } + namespace text { + class XTextRange; + class XTextColumns; + class XFootnote; + } + namespace table { + struct BorderLine2; + struct ShadowFormat; + } +} + +namespace writerfilter::dmapper { + +class DomainMapper_Impl; +struct FloatingTableInfo; +struct AnchoredObjectInfo; + +enum BorderPosition +{ + BORDER_LEFT, + BORDER_RIGHT, + BORDER_TOP, + BORDER_BOTTOM +}; + +enum GrabBagType +{ + NO_GRAB_BAG, + ROW_GRAB_BAG, + CELL_GRAB_BAG, + PARA_GRAB_BAG, + CHAR_GRAB_BAG +}; + +struct RedlineParams : public virtual SvRefBase +{ + OUString m_sAuthor; + OUString m_sDate; + sal_Int32 m_nToken; + + // This can hold properties of runs that had formatted 'track changes' properties + css::uno::Sequence< css::beans::PropertyValue > m_aRevertProperties; +}; + +typedef tools::SvRef< RedlineParams > RedlineParamsPtr; + +class PropValue +{ +private: + css::uno::Any m_aValue; + GrabBagType m_GrabBagType; + bool m_bIsDocDefault; + +public: + PropValue( const css::uno::Any& rValue, GrabBagType i_GrabBagType, bool bDocDefault ) + : m_aValue( rValue ) + , m_GrabBagType( i_GrabBagType ) + , m_bIsDocDefault( bDocDefault ) + { + } + + PropValue( const css::uno::Any& rValue, GrabBagType i_GrabBagType ) + : m_aValue( rValue ) + , m_GrabBagType( i_GrabBagType ) + , m_bIsDocDefault( false ) + { + } + + PropValue() + : m_aValue() + , m_GrabBagType( NO_GRAB_BAG ) + , m_bIsDocDefault( false ) + { + } + + const css::uno::Any& getValue() const { return m_aValue; } + + GrabBagType getGrabBagType() const { return m_GrabBagType; } + + bool getIsDocDefault() const { return m_bIsDocDefault; } +}; + +class PropertyMap; +typedef tools::SvRef< PropertyMap > PropertyMapPtr; + +class PropertyMap : public virtual SvRefBase +{ +private: + // Cache the property values for the GetPropertyValues() call(s). + std::vector< css::beans::PropertyValue > m_aValues; + + // marks context as footnote context - ::text( ) events contain either the footnote character or can be ignored + // depending on sprmCSymbol + css::uno::Reference< css::text::XFootnote > m_xFootnote; + OUString m_sFootnoteCharStyleName; + std::map< PropertyIds, PropValue > m_vMap; + std::vector< RedlineParamsPtr > m_aRedlines; + +public: + typedef std::pair< PropertyIds, css::uno::Any > Property; + + PropertyMap() {} + + // Sequence: Grab Bags: The CHAR_GRAB_BAG has Name "CharInteropGrabBag" and the PARA_GRAB_BAG has Name "ParaInteropGrabBag" + // the contained properties are their Value. + css::uno::Sequence< css::beans::PropertyValue > GetPropertyValues( bool bCharGrabBag = true ); + + std::vector< PropertyIds > GetPropertyIds(); + + // Add property, optionally overwriting existing attributes + void Insert( PropertyIds eId, const css::uno::Any& rAny, bool bOverwrite = true, GrabBagType i_GrabBagType = NO_GRAB_BAG, bool bDocDefault = false ); + + // Remove a named property from *this, does nothing if the property id has not been set + void Erase( PropertyIds eId); + + // Imports properties from pMap (bOverwrite==false means m_bIsDocDefault=true setting) + void InsertProps( const PropertyMapPtr& rMap, const bool bOverwrite = true ); + + // Returns a copy of the property if it exists, .first is its PropertyIds and .second is its Value (type css::uno::Any) + std::optional< Property > getProperty( PropertyIds eId ) const; + + // Has the property named been set (via Insert)? + bool isSet( PropertyIds eId ) const; + bool isDocDefault( PropertyIds eId ) const; + + const css::uno::Reference< css::text::XFootnote >& GetFootnote() const { return m_xFootnote; } + const OUString& GetFootnoteStyle() const { return m_sFootnoteCharStyleName; } + + void SetFootnote(const css::uno::Reference< css::text::XFootnote >& xFootnote, const OUString& sStyleName) + { + m_xFootnote = xFootnote; + m_sFootnoteCharStyleName = sStyleName; + } + + virtual void insertTableProperties( const PropertyMap*, const bool bOverwrite = true ); + + const std::vector< RedlineParamsPtr >& Redlines() const { return m_aRedlines; } + + std::vector< RedlineParamsPtr >& Redlines() { return m_aRedlines; } + + void printProperties(); + +#ifdef DBG_UTIL + void dumpXml() const; +#endif + + static css::table::ShadowFormat getShadowFromBorder( const css::table::BorderLine2& rBorder ); + +protected: + void Invalidate() + { + if ( m_aValues.size() ) + m_aValues.clear(); + } +}; + +class SectionPropertyMap : public PropertyMap +{ +public: + enum class BorderApply + { + ToAllInSection = 0, + ToFirstPageInSection = 1, + ToAllButFirstInSection = 2 + }; + enum class BorderOffsetFrom + { + Text = 0, + Edge = 1, + }; +private: +#ifdef DBG_UTIL + sal_Int32 m_nDebugSectionNumber; +#endif + + // 'temporarily' the section page settings are imported as page styles + // empty strings mark page settings as not yet imported + + bool m_bIsFirstSection; + css::uno::Reference< css::text::XTextRange > m_xStartingRange; + + OUString m_sFirstPageStyleName; + OUString m_sFollowPageStyleName; + css::uno::Reference< css::beans::XPropertySet > m_aFirstPageStyle; + css::uno::Reference< css::beans::XPropertySet > m_aFollowPageStyle; + + std::optional< css::table::BorderLine2 > m_oBorderLines[4]; + sal_Int32 m_nBorderDistances[4]; + BorderApply m_eBorderApply; + BorderOffsetFrom m_eBorderOffsetFrom; + bool m_bBorderShadows[4]; + + bool m_bTitlePage; + sal_Int16 m_nColumnCount; + sal_Int32 m_nColumnDistance; + css::uno::Reference< css::beans::XPropertySet > m_xColumnContainer; + std::vector< sal_Int32 > m_aColWidth; + std::vector< sal_Int32 > m_aColDistance; + + bool m_bSeparatorLineIsOn; + bool m_bEvenlySpaced; + + sal_Int32 m_nPageNumber; + // Page number type is a value from css::style::NumberingType. + sal_Int16 m_nPageNumberType; + sal_Int32 m_nBreakType; + + sal_Int32 m_nLeftMargin; + sal_Int32 m_nRightMargin; + sal_Int32 m_nGutterMargin; + sal_Int32 m_nTopMargin; + sal_Int32 m_nBottomMargin; + sal_Int32 m_nHeaderTop; + sal_Int32 m_nHeaderBottom; + + sal_Int32 m_nGridType; + sal_Int32 m_nGridLinePitch; + sal_Int32 m_nDxtCharSpace; + bool m_bGridSnapToChars; + + // line numbering + sal_Int32 m_nLnnMod; + sal_uInt32 m_nLnc; + sal_Int32 m_ndxaLnn; + sal_Int32 m_nLnnMin; + + bool m_bDynamicHeightTop; + bool m_bDynamicHeightBottom; + + std::vector<css::uno::Reference<css::drawing::XShape>> m_xRelativeWidthShapes; + + // The "Link To Previous" flag indicates whether the header/footer + // content should be taken from the previous section + bool m_bDefaultHeaderLinkToPrevious; + bool m_bEvenPageHeaderLinkToPrevious; + bool m_bFirstPageHeaderLinkToPrevious; + bool m_bDefaultFooterLinkToPrevious; + bool m_bEvenPageFooterLinkToPrevious; + bool m_bFirstPageFooterLinkToPrevious; + + void ApplyProperties_( const css::uno::Reference< css::beans::XPropertySet >& xStyle ); + + void DontBalanceTextColumns(); + + /// Apply section-specific properties: only valid to use after PageStyle has been determined by InheritOrFinalizePageStyles + void ApplySectionProperties( const css::uno::Reference< css::beans::XPropertySet >& xSection, DomainMapper_Impl& rDM_Impl ); + + /// Check if document is protected. If so, ensure a section exists, and apply its protected value. + void ApplyProtectionProperties( css::uno::Reference< css::beans::XPropertySet >& xSection, DomainMapper_Impl& rDM_Impl ); + + css::uno::Reference< css::text::XTextColumns > ApplyColumnProperties( const css::uno::Reference< css::beans::XPropertySet >& xFollowPageStyle, + DomainMapper_Impl& rDM_Impl); + + void CopyLastHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ); + + static void CopyHeaderFooter( const DomainMapper_Impl& rDM_Impl, + const css::uno::Reference< css::beans::XPropertySet >& xPrevStyle, + const css::uno::Reference< css::beans::XPropertySet >& xStyle, + bool bOmitRightHeader = false, bool bOmitLeftHeader = false, + bool bOmitRightFooter = false, bool bOmitLeftFooter = false ); + + static void CopyHeaderFooterTextProperty( const css::uno::Reference< css::beans::XPropertySet >& xPrevStyle, + const css::uno::Reference< css::beans::XPropertySet >& xStyle, + PropertyIds ePropId ); + + void PrepareHeaderFooterProperties( bool bFirstPage ); + + bool HasHeader( bool bFirstPage ) const; + bool HasFooter( bool bFirstPage ) const; + + static void SetBorderDistance( const css::uno::Reference< css::beans::XPropertySet >& xStyle, + PropertyIds eMarginId, + PropertyIds eDistId, + sal_Int32 nDistance, + BorderOffsetFrom eOffsetFrom, + sal_uInt32 nLineWidth, + DomainMapper_Impl& rDM_Impl ); + + // Determines if conversion of a given floating table is wanted or not. + bool FloatingTableConversion( const DomainMapper_Impl& rDM_Impl, FloatingTableInfo& rInfo ); + + /// Increases paragraph spacing according to Word 2013+ needs if necessary. + void HandleIncreasedAnchoredObjectSpacing(DomainMapper_Impl& rDM_Impl); + +public: + enum PageType + { + PAGE_FIRST, + PAGE_LEFT, + PAGE_RIGHT + }; + + explicit SectionPropertyMap( bool bIsFirstSection ); + + bool IsFirstSection() const { return m_bIsFirstSection; } + + void SetStart( const css::uno::Reference< css::text::XTextRange >& xRange ) { m_xStartingRange = xRange; } + + const css::uno::Reference< css::text::XTextRange >& GetStartingRange() const { return m_xStartingRange; } + + css::uno::Reference< css::beans::XPropertySet > GetPageStyle( DomainMapper_Impl& rDM_Impl, bool bFirst ); + + const OUString& GetPageStyleName( bool bFirstPage = false ) + { + return bFirstPage ? m_sFirstPageStyleName : m_sFollowPageStyleName; + } + + // @throws css::beans::UnknownPropertyException + // @throws css::beans::PropertyVetoException + // @throws css::lang::IllegalArgumentException + // @throws css::lang::WrappedTargetException + // @throws css::uno::RuntimeException + void InheritOrFinalizePageStyles( DomainMapper_Impl& rDM_Impl ); + + void SetBorder( BorderPosition ePos, sal_Int32 nLineDistance, const css::table::BorderLine2& rBorderLine, bool bShadow ); + void SetBorderApply( BorderApply nSet ) { m_eBorderApply = nSet; } + void SetBorderOffsetFrom( BorderOffsetFrom nSet ) { m_eBorderOffsetFrom = nSet; } + + void SetColumnCount( sal_Int16 nCount ) { m_nColumnCount = nCount; } + sal_Int16 ColumnCount() const { return m_nColumnCount; } + + void SetColumnDistance( sal_Int32 nDist ) { m_nColumnDistance = nDist; } + void AppendColumnWidth( sal_Int32 nWidth ) { m_aColWidth.push_back( nWidth ); } + void AppendColumnSpacing( sal_Int32 nDist ) { m_aColDistance.push_back( nDist ); } + + void SetTitlePage( bool bSet ) { m_bTitlePage = bSet; } + void SetSeparatorLine( bool bSet ) { m_bSeparatorLineIsOn = bSet; } + void SetEvenlySpaced( bool bSet ) { m_bEvenlySpaced = bSet; } + void SetPageNumber( sal_Int32 nSet ) { m_nPageNumber = nSet; } + void SetPageNumberType( sal_Int32 nSet ) { m_nPageNumberType = nSet; } + void SetBreakType( sal_Int32 nSet ) { m_nBreakType = nSet; } + // GetBreakType returns -1 if the breakType has not yet been identified for the section + sal_Int32 GetBreakType() const { return m_nBreakType; } + + void SetLeftMargin( sal_Int32 nSet ) { m_nLeftMargin = nSet; } + sal_Int32 GetLeftMargin() const { return m_nLeftMargin; } + void SetRightMargin( sal_Int32 nSet ) { m_nRightMargin = nSet; } + sal_Int32 GetRightMargin() const { return m_nRightMargin; } + void SetTopMargin(sal_Int32 nSet) { m_bDynamicHeightTop = nSet >= 0; m_nTopMargin = std::abs(nSet); } + void SetBottomMargin( sal_Int32 nSet ) { m_bDynamicHeightBottom = nSet >= 0; m_nBottomMargin = std::abs(nSet); } + void SetHeaderTop( sal_Int32 nSet ) { m_nHeaderTop = nSet; } + void SetHeaderBottom( sal_Int32 nSet ) { m_nHeaderBottom = nSet; } + void SetGutterMargin( sal_Int32 nGutterMargin ) { m_nGutterMargin = nGutterMargin; } + sal_Int32 GetPageWidth() const; + + void SetGridType( sal_Int32 nSet ) { m_nGridType = nSet; } + void SetGridLinePitch( sal_Int32 nSet ) { m_nGridLinePitch = nSet; } + void SetGridSnapToChars( bool bSet ) { m_bGridSnapToChars = bSet; } + void SetDxtCharSpace( sal_Int32 nSet ) { m_nDxtCharSpace = nSet; } + + void SetLnnMod( sal_Int32 nValue ) { m_nLnnMod = nValue; } + void SetLnc( sal_Int32 nValue ) { m_nLnc = nValue; } + void SetdxaLnn( sal_Int32 nValue ) { m_ndxaLnn = nValue; } + void SetLnnMin( sal_Int32 nValue ) { m_nLnnMin = nValue; } + + void addRelativeWidthShape( css::uno::Reference<css::drawing::XShape> xShape ) { m_xRelativeWidthShapes.push_back( xShape ); } + + // determine which style gets the borders + void ApplyBorderToPageStyles( DomainMapper_Impl &rDM_Impl, + BorderApply eBorderApply, BorderOffsetFrom eOffsetFrom ); + + void CloseSectionGroup( DomainMapper_Impl& rDM_Impl ); + // Handling of margins, header and footer for any kind of sections breaks. + void HandleMarginsHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ); + void ClearHeaderFooterLinkToPrevious( bool bHeader, PageType eType ); +}; + +class ParagraphProperties : public virtual SvRefBase +{ +private: + bool m_bFrameMode; + sal_Int32 m_nDropCap; // drop, margin ST_DropCap + sal_Int32 m_nLines; // number of lines of the drop cap + sal_Int32 m_w; // width + sal_Int32 m_h; // height + css::text::WrapTextMode m_nWrap; // from ST_Wrap around, auto, none, notBeside, through, tight + sal_Int32 m_hAnchor; // page, from ST_HAnchor margin, page, text + sal_Int32 m_vAnchor; // around from ST_VAnchor margin, page, text + sal_Int32 m_x; // x-position + bool m_bxValid; + sal_Int32 m_y; // y-position + bool m_byValid; + sal_Int32 m_hSpace; // frame padding h + sal_Int32 m_vSpace; // frame padding v + sal_Int32 m_hRule; // from ST_HeightRule exact, atLeast, auto + sal_Int32 m_xAlign; // from ST_XAlign center, inside, left, outside, right + sal_Int32 m_yAlign; // from ST_YAlign bottom, center, inline, inside, outside, top + sal_Int8 m_nDropCapLength; // number of characters + OUString m_sParaStyleName; + OUString m_sParaId; // [MS-DOCX] sect. 2.2.4 "p and tr Extensions" + + css::uno::Reference< css::text::XTextRange > m_xStartingRange; // start of a frame + css::uno::Reference< css::text::XTextRange > m_xEndingRange; // end of the frame + sal_Int32 m_nListId = -1; + +public: + ParagraphProperties(); + + ParagraphProperties(ParagraphProperties const &) = default; + ParagraphProperties(ParagraphProperties &&) = default; + ParagraphProperties & operator =(ParagraphProperties const &) = default; + ParagraphProperties & operator =(ParagraphProperties &&) = default; + + // Does not compare the starting/ending range, m_sParaStyleName and m_nDropCapLength + bool operator==( const ParagraphProperties& ); + + sal_Int32 GetListId() const { return m_nListId; } + void SetListId( sal_Int32 nId ) { m_nListId = nId; } + + bool IsFrameMode() const { return m_bFrameMode; } + void SetFrameMode( bool set = true ) { m_bFrameMode = set; } + + sal_Int32 GetDropCap() const { return m_nDropCap; } + void SetDropCap( sal_Int32 nSet ) { m_nDropCap = nSet; } + + sal_Int32 GetLines() const { return m_nLines; } + void SetLines( sal_Int32 nSet ) { m_nLines = nSet; } + + sal_Int32 Getw() const { return m_w; } + void Setw( sal_Int32 nSet ) { m_w = nSet; } + + sal_Int32 Geth() const { return m_h; } + void Seth( sal_Int32 nSet ) { m_h = nSet; } + + css::text::WrapTextMode GetWrap() const { return m_nWrap; } + void SetWrap( css::text::WrapTextMode nSet ) { m_nWrap = nSet; } + + sal_Int32 GethAnchor() const { return m_hAnchor; } + void SethAnchor( sal_Int32 nSet ) { m_hAnchor = nSet; } + + sal_Int32 GetvAnchor() const { return m_vAnchor; } + void SetvAnchor( sal_Int32 nSet ) { m_vAnchor = nSet; } + + sal_Int32 Getx() const { return m_x; } + void Setx( sal_Int32 nSet ) { m_x = nSet; m_bxValid = true; } + bool IsxValid() const { return m_bxValid; } + + sal_Int32 Gety() const { return m_y; } + void Sety( sal_Int32 nSet ) { m_y = nSet; m_byValid = true; } + bool IsyValid() const { return m_byValid; } + + void SethSpace( sal_Int32 nSet ) { m_hSpace = nSet; } + sal_Int32 GethSpace() const { return m_hSpace; } + + sal_Int32 GetvSpace() const { return m_vSpace; } + void SetvSpace( sal_Int32 nSet ) { m_vSpace = nSet; } + + sal_Int32 GethRule() const { return m_hRule; } + void SethRule( sal_Int32 nSet ) { m_hRule = nSet; } + + sal_Int32 GetxAlign() const { return m_xAlign; } + void SetxAlign( sal_Int32 nSet ) { m_xAlign = nSet; } + + sal_Int32 GetyAlign() const { return m_yAlign; } + void SetyAlign( sal_Int32 nSet ) { m_yAlign = nSet; } + + sal_Int8 GetDropCapLength() const { return m_nDropCapLength; } + void SetDropCapLength( sal_Int8 nSet ) { m_nDropCapLength = nSet; } + + const css::uno::Reference< css::text::XTextRange >& GetStartingRange() const { return m_xStartingRange; } + void SetStartingRange( const css::uno::Reference< css::text::XTextRange >& xSet ) { m_xStartingRange = xSet; } + + const css::uno::Reference< css::text::XTextRange >& GetEndingRange() const { return m_xEndingRange; } + void SetEndingRange( const css::uno::Reference< css::text::XTextRange >& xSet ) { m_xEndingRange = xSet; } + + const OUString& GetParaStyleName() const { return m_sParaStyleName; } + void SetParaStyleName( const OUString& rSet ) { m_sParaStyleName = rSet; } + + const OUString& GetParaId() const { return m_sParaId; } + void SetParaId(const OUString& rSet) { m_sParaId = rSet; } + + void ResetFrameProperties(); +}; + +typedef tools::SvRef< ParagraphProperties > ParagraphPropertiesPtr; + +/*------------------------------------------------------------------------- + property map of a stylesheet + -----------------------------------------------------------------------*/ + +#define WW_OUTLINE_MAX sal_Int16( 9 ) +#define WW_OUTLINE_MIN sal_Int16( 0 ) + +class StyleSheetPropertyMap + : public PropertyMap + , public ParagraphProperties +{ +private: + sal_Int16 mnListLevel; + sal_Int16 mnOutlineLevel; + +public: + explicit StyleSheetPropertyMap(); + + sal_Int16 GetListLevel() const { return mnListLevel; } + void SetListLevel( sal_Int16 nLevel ) { mnListLevel = nLevel; } + + sal_Int16 GetOutlineLevel() const { return mnOutlineLevel; } + void SetOutlineLevel(sal_Int16 nLevel) { if (nLevel <= WW_OUTLINE_MAX) mnOutlineLevel = nLevel; } +}; + +class ParagraphPropertyMap + : public PropertyMap + , public ParagraphProperties +{ +public: + explicit ParagraphPropertyMap() {} +}; + +class TablePropertyMap + : public PropertyMap +{ +public: + enum TablePropertyMapTarget + { + TablePropertyMapTarget_START, + CELL_MAR_LEFT = TablePropertyMapTarget_START, + CELL_MAR_RIGHT, + CELL_MAR_TOP, + CELL_MAR_BOTTOM, + TABLE_WIDTH, + TABLE_WIDTH_TYPE, + GAP_HALF, + LEFT_MARGIN, + HORI_ORIENT, + TablePropertyMapTarget_MAX + }; + +private: + struct ValidValue + { + sal_Int32 nValue; + bool bValid; + + ValidValue() + : nValue( 0 ) + , bValid( false ) + { + } + }; + + ValidValue m_aValidValues[TablePropertyMapTarget_MAX]; + +public: + explicit TablePropertyMap() {} + + bool getValue( TablePropertyMapTarget eWhich, sal_Int32& nFill ); + void setValue( TablePropertyMapTarget eWhich, sal_Int32 nSet ); + + virtual void insertTableProperties( const PropertyMap*, const bool bOverwrite = true ) override; +}; + +typedef tools::SvRef< TablePropertyMap > TablePropertyMapPtr; + +/// Information about a paragraph to be finished after a table end. +struct TableParagraph +{ + css::uno::Reference<css::text::XTextRange> m_rStartParagraph; + css::uno::Reference<css::text::XTextRange> m_rEndParagraph; + PropertyMapPtr m_pPropertyMap; + css::uno::Reference<css::beans::XPropertySet> m_rPropertySet; +}; + +typedef std::shared_ptr< std::vector<TableParagraph> > TableParagraphVectorPtr; + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyMapHelper.cxx b/writerfilter/source/dmapper/PropertyMapHelper.cxx new file mode 100644 index 000000000..a944dc147 --- /dev/null +++ b/writerfilter/source/dmapper/PropertyMapHelper.cxx @@ -0,0 +1,98 @@ +/* -*- 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/text/TableColumnSeparator.hpp> +#include "TagLogger.hxx" +#include "PropertyMapHelper.hxx" + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; + +void lcl_DumpTableColumnSeparators(const uno::Any& rTableColumnSeparators) +{ +#ifdef DBG_UTIL + uno::Sequence<text::TableColumnSeparator> aSeq; + rTableColumnSeparators >>= aSeq; + + TagLogger::getInstance().startElement("property.TableColumnSeparators"); + + sal_uInt32 nLength = aSeq.getLength(); + for (sal_uInt32 n = 0; n < nLength; ++n) + { + TagLogger::getInstance().startElement("separator"); + + TagLogger::getInstance().attribute("position", aSeq[n].Position); + TagLogger::getInstance().attribute("visible", sal_uInt32(aSeq[n].IsVisible)); + + TagLogger::getInstance().endElement(); + } + + TagLogger::getInstance().endElement(); +#else + (void)rTableColumnSeparators; +#endif // DBG_UTIL +} + +#ifdef DBG_UTIL +void lcl_DumpPropertyValues(beans::PropertyValues const& rValues) +{ + TagLogger::getInstance().startElement("propertyValues"); + + for (beans::PropertyValue const& propVal : rValues) + { + TagLogger::getInstance().startElement("propertyValue"); + + TagLogger::getInstance().attribute("name", propVal.Name); + + try + { + sal_Int32 aInt = 0; + propVal.Value >>= aInt; + TagLogger::getInstance().attribute("value", aInt); + } + catch (...) + { + } + + if (propVal.Name == "TableColumnSeparators") + { + lcl_DumpTableColumnSeparators(propVal.Value); + } + + TagLogger::getInstance().endElement(); + } + TagLogger::getInstance().endElement(); +} + +void lcl_DumpPropertyValueSeq(css::uno::Sequence<css::beans::PropertyValues> const& rPropValSeq) +{ + TagLogger::getInstance().startElement("PropertyValueSeq"); + + for (auto const& propVal : rPropValSeq) + { + lcl_DumpPropertyValues(propVal); + } + + TagLogger::getInstance().endElement(); +} +#endif // DBG_UTIL +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/PropertyMapHelper.hxx b/writerfilter/source/dmapper/PropertyMapHelper.hxx new file mode 100644 index 000000000..472e1bcfa --- /dev/null +++ b/writerfilter/source/dmapper/PropertyMapHelper.hxx @@ -0,0 +1,34 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/PropertyValues.hpp> + +namespace writerfilter::dmapper +{ +void lcl_DumpTableColumnSeparators(const css::uno::Any& rTableColumnSeparators); +#ifdef DBG_UTIL +void lcl_DumpPropertyValues(css::beans::PropertyValues const& rValues); + +void lcl_DumpPropertyValueSeq(css::uno::Sequence<css::beans::PropertyValues> const& rPropValSeq); +#endif // DBG_UTIL +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx new file mode 100644 index 000000000..31c83d5a3 --- /dev/null +++ b/writerfilter/source/dmapper/SdtHelper.cxx @@ -0,0 +1,513 @@ +/* -*- 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/. + */ + +#include "SdtHelper.hxx" +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <editeng/unoprnms.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <comphelper/string.hxx> +#include <comphelper/sequence.hxx> +#include <xmloff/odffields.hxx> +#include <com/sun/star/text/XTextField.hpp> +#include "DomainMapper_Impl.hxx" +#include "StyleSheetTable.hxx" +#include <officecfg/Office/Writer.hxx> +#include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/text/XTextFieldsSupplier.hpp> +#include <com/sun/star/document/XOOXMLDocumentPropertiesImporter.hpp> +#include <ooxml/OOXMLDocument.hxx> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; +using namespace ::css::xml::xpath; +using namespace ::comphelper; + +/// w:sdt's w:dropDownList doesn't have width, so guess the size based on the longest string. +static awt::Size lcl_getOptimalWidth(const StyleSheetTablePtr& pStyleSheet, + OUString const& rDefault, std::vector<OUString>& rItems) +{ + OUString aLongest = rDefault; + sal_Int32 nHeight = 0; + for (const OUString& rItem : rItems) + if (rItem.getLength() > aLongest.getLength()) + aLongest = rItem; + + MapMode aMap(MapUnit::Map100thMM); + OutputDevice* pOut = Application::GetDefaultDevice(); + pOut->Push(vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE); + + PropertyMapPtr pDefaultCharProps = pStyleSheet->GetDefaultCharProps(); + vcl::Font aFont(pOut->GetFont()); + std::optional<PropertyMap::Property> aFontName + = pDefaultCharProps->getProperty(PROP_CHAR_FONT_NAME); + if (aFontName) + aFont.SetFamilyName(aFontName->second.get<OUString>()); + std::optional<PropertyMap::Property> aHeight = pDefaultCharProps->getProperty(PROP_CHAR_HEIGHT); + if (aHeight) + { + nHeight = aHeight->second.get<double>() * 35; // points -> mm100 + aFont.SetFontSize(Size(0, nHeight)); + } + pOut->SetFont(aFont); + pOut->SetMapMode(aMap); + sal_Int32 nWidth = pOut->GetTextWidth(aLongest); + + pOut->Pop(); + + // Border: see PDFWriterImpl::drawFieldBorder(), border size is font height / 4, + // so additional width / height needed is height / 2. + sal_Int32 nBorder = nHeight / 2; + + // Width: space for the text + the square having the dropdown arrow. + return { nWidth + nBorder + nHeight, nHeight + nBorder }; +} + +SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl, + css::uno::Reference<css::uno::XComponentContext> const& xContext) + : m_rDM_Impl(rDM_Impl) + , m_xComponentContext(xContext) + , m_aControlType(SdtControlType::unknown) + , m_bHasElements(false) + , m_bOutsideAParagraph(false) + , m_bPropertiesXMLsLoaded(false) +{ +} + +SdtHelper::~SdtHelper() = default; + +void SdtHelper::loadPropertiesXMLs() +{ + // Initialize properties xml storage (m_xPropertiesXMLs) + uno::Reference<uno::XInterface> xTemp + = m_xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.OOXMLDocumentPropertiesImporter", m_xComponentContext); + uno::Reference<document::XOOXMLDocumentPropertiesImporter> xImporter(xTemp, uno::UNO_QUERY); + if (!xImporter.is()) + return; + + uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder( + xml::dom::DocumentBuilder::create(m_xComponentContext)); + if (!xDomBuilder.is()) + return; + + // Load core properties + try + { + auto xCorePropsStream = xImporter->getCorePropertiesStream(m_rDM_Impl.m_xDocumentStorage); + m_xPropertiesXMLs.insert( + { OUString("{6C3C8BC8-F283-45AE-878A-BAB7291924A1}"), // hardcoded id for core props + xDomBuilder->parse(xCorePropsStream) }); + } + catch (const uno::Exception&) + { + SAL_WARN("writerfilter", + "SdtHelper::loadPropertiesXMLs: failed loading core properties XML"); + } + + // Load extended properties + try + { + auto xExtPropsStream + = xImporter->getExtendedPropertiesStream(m_rDM_Impl.m_xDocumentStorage); + m_xPropertiesXMLs.insert( + { OUString("{6668398D-A668-4E3E-A5EB-62B293D839F1}"), // hardcoded id for extended props + xDomBuilder->parse(xExtPropsStream) }); + } + catch (const uno::Exception&) + { + SAL_WARN("writerfilter", + "SdtHelper::loadPropertiesXMLs: failed loading extended properties XML"); + } + + // TODO: some other property items? + + // Add custom XMLs + uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmls + = m_rDM_Impl.getDocumentReference()->getCustomXmlDomList(); + uno::Sequence<uno::Reference<xml::dom::XDocument>> aCustomXmlProps + = m_rDM_Impl.getDocumentReference()->getCustomXmlDomPropsList(); + if (aCustomXmls.getLength()) + { + uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext); + xXpathAPI->registerNS("ds", + "http://schemas.openxmlformats.org/officeDocument/2006/customXml"); + sal_Int32 nItem = 0; + // Hereby we assume that items from getCustomXmlDomList() and getCustomXmlDomPropsList() + // are matching each other: + // item1.xml -> itemProps1.xml, item2.xml -> itemProps2.xml + // This does works practically, but is it true in general? + for (const auto& xDoc : aCustomXmls) + { + // Retrieve storeid from properties xml + OUString aStoreId; + uno::Reference<XXPathObject> xResult + = xXpathAPI->eval(aCustomXmlProps[nItem], "string(/ds:datastoreItem/@ds:itemID)"); + + if (xResult.is() && xResult->getString().getLength()) + { + aStoreId = xResult->getString(); + } + else + { + SAL_WARN("writerfilter", + "SdtHelper::loadPropertiesXMLs: can't fetch storeid for custom doc!"); + } + + m_xPropertiesXMLs.insert({ aStoreId, xDoc }); + nItem++; + } + } + + m_bPropertiesXMLsLoaded = true; +} + +static void lcl_registerNamespaces(std::u16string_view sNamespaceString, + const uno::Reference<XXPathAPI>& xXPathAPI) +{ + // Split namespaces and register it in XPathAPI + auto aNamespaces = string::split(sNamespaceString, ' '); + for (const auto& sNamespace : aNamespaces) + { + // Here we have just one namespace in format "xmlns:ns0='http://someurl'" + auto aNamespace = string::split(sNamespace, '='); + if (aNamespace.size() < 2) + { + SAL_WARN("writerfilter", + "SdtHelper::getValueFromDataBinding: invalid namespace: " << sNamespace); + continue; + } + + auto aNamespaceId = string::split(aNamespace[0], ':'); + if (aNamespaceId.size() < 2) + { + SAL_WARN("writerfilter", + "SdtHelper::getValueFromDataBinding: invalid namespace: " << aNamespace[0]); + continue; + } + + OUString sNamespaceURL = aNamespace[1]; + sNamespaceURL = string::strip(sNamespaceURL, ' '); + sNamespaceURL = string::strip(sNamespaceURL, '\''); + + xXPathAPI->registerNS(aNamespaceId[1], sNamespaceURL); + } +} + +std::optional<OUString> SdtHelper::getValueFromDataBinding() +{ + // No xpath - nothing to do + if (m_sDataBindingXPath.isEmpty()) + return {}; + + // Load properties XMLs + if (!m_bPropertiesXMLsLoaded) + loadPropertiesXMLs(); + + uno::Reference<XXPathAPI> xXpathAPI = XPathAPI::create(m_xComponentContext); + + lcl_registerNamespaces(m_sDataBindingPrefixMapping, xXpathAPI); + + // Find storage by store id and eval xpath there + const auto& aSourceIt = m_xPropertiesXMLs.find(m_sDataBindingStoreItemID); + if (aSourceIt != m_xPropertiesXMLs.end()) + { + uno::Reference<XXPathObject> xResult + = xXpathAPI->eval(aSourceIt->second, m_sDataBindingXPath); + + if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength() + && xResult->getString().getLength()) + { + return xResult->getString(); + } + } + + // Nothing found? Try to iterate storages and eval xpath + for (const auto& aSource : m_xPropertiesXMLs) + { + uno::Reference<XXPathObject> xResult = xXpathAPI->eval(aSource.second, m_sDataBindingXPath); + + if (xResult.is() && xResult->getNodeList() && xResult->getNodeList()->getLength() + && xResult->getString().getLength()) + { + return xResult->getString(); + } + } + + // No data + return {}; +} + +void SdtHelper::createDropDownControl() +{ + assert(getControlType() == SdtControlType::dropDown); + + const bool bDropDown + = officecfg::Office::Writer::Filter::Import::DOCX::ImportComboBoxAsDropDown::get(); + const OUString aDefaultText = m_aSdtTexts.makeStringAndClear(); + + if (bDropDown) + { + // create field + uno::Reference<css::text::XTextField> xControlModel( + m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.DropDown"), + uno::UNO_QUERY); + + const auto it = std::find_if( + m_aDropDownItems.begin(), m_aDropDownItems.end(), + [aDefaultText](const OUString& item) -> bool { return !item.compareTo(aDefaultText); }); + + if (m_aDropDownItems.end() == it) + { + m_aDropDownItems.push_back(aDefaultText); + } + + // set properties + uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); + xPropertySet->setPropertyValue("SelectedItem", uno::Any(aDefaultText)); + xPropertySet->setPropertyValue("Items", + uno::Any(comphelper::containerToSequence(m_aDropDownItems))); + + // add it into document + m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>()); + + m_bHasElements = true; + } + else + { + // create control + uno::Reference<awt::XControlModel> xControlModel( + m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.form.component.ComboBox"), + uno::UNO_QUERY); + + // set properties + uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); + xPropertySet->setPropertyValue("DefaultText", uno::Any(aDefaultText)); + xPropertySet->setPropertyValue("Dropdown", uno::Any(true)); + xPropertySet->setPropertyValue("StringItemList", + uno::Any(comphelper::containerToSequence(m_aDropDownItems))); + + // add it into document + createControlShape( + lcl_getOptimalWidth(m_rDM_Impl.GetStyleSheetTable(), aDefaultText, m_aDropDownItems), + xControlModel, uno::Sequence<beans::PropertyValue>()); + } + + // clean up + clear(); +} + +void SdtHelper::createPlainTextControl() +{ + assert(getControlType() == SdtControlType::plainText); + + OUString aDefaultText = m_aSdtTexts.makeStringAndClear(); + + // create field + uno::Reference<css::text::XTextField> xControlModel( + m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.TextField.Input"), + uno::UNO_QUERY); + + // set properties + uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); + + std::optional<OUString> oData = getValueFromDataBinding(); + if (oData.has_value()) + aDefaultText = *oData; + + xPropertySet->setPropertyValue("Content", uno::Any(aDefaultText)); + + // add it into document + m_rDM_Impl.appendTextContent(xControlModel, uno::Sequence<beans::PropertyValue>()); + + // Store all unused sdt parameters from grabbag + xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, + uno::Any(getInteropGrabBagAndClear())); + + // clean up + clear(); +} + +void SdtHelper::createDateContentControl() +{ + if (!m_xDateFieldStartRange.is()) + return; + + uno::Reference<text::XTextCursor> xCrsr; + if (m_rDM_Impl.HasTopText()) + { + uno::Reference<text::XTextAppend> xTextAppend = m_rDM_Impl.GetTopTextAppend(); + if (xTextAppend.is()) + { + xCrsr = xTextAppend->createTextCursorByRange(xTextAppend); + } + } + if (!xCrsr.is()) + return; + + try + { + xCrsr->gotoRange(m_xDateFieldStartRange, false); + bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable()) + || (m_rDM_Impl.m_nTableDepth > 0); + if (bIsInTable) + xCrsr->goRight(1, false); + xCrsr->gotoEnd(true); + } + catch (uno::Exception&) + { + OSL_ENSURE(false, "Cannot get the right text range for date field"); + return; + } + + uno::Reference<uno::XInterface> xFieldInterface + = m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.text.Fieldmark"); + uno::Reference<text::XFormField> xFormField(xFieldInterface, uno::UNO_QUERY); + uno::Reference<text::XTextContent> xToInsert(xFormField, uno::UNO_QUERY); + if (!(xFormField.is() && xToInsert.is())) + return; + + xToInsert->attach(uno::Reference<text::XTextRange>(xCrsr, uno::UNO_QUERY_THROW)); + xFormField->setFieldType(ODF_FORMDATE); + uno::Reference<container::XNameContainer> xNameCont = xFormField->getParameters(); + if (xNameCont.is()) + { + OUString sDateFormat = m_sDateFormat.makeStringAndClear(); + + // Replace quotation mark used for marking static strings in date format + sDateFormat = sDateFormat.replaceAll("'", "\""); + xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT, uno::Any(sDateFormat)); + xNameCont->insertByName(ODF_FORMDATE_DATEFORMAT_LANGUAGE, + uno::Any(m_sLocale.makeStringAndClear())); + } + OUString sFullDate = m_sDate.makeStringAndClear(); + + std::optional<OUString> oData = getValueFromDataBinding(); + if (oData.has_value()) + sFullDate = *oData; + + if (!sFullDate.isEmpty()) + { + sal_Int32 nTimeSep = sFullDate.indexOf("T"); + if (nTimeSep != -1) + sFullDate = sFullDate.copy(0, nTimeSep); + xNameCont->insertByName(ODF_FORMDATE_CURRENTDATE, uno::Any(sFullDate)); + } + + uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(m_rDM_Impl.GetTextDocument(), + uno::UNO_QUERY); + uno::Reference<util::XRefreshable> xRefreshable(xTextFieldsSupplier->getTextFields(), + uno::UNO_QUERY); + xRefreshable->refresh(); + + // Store all unused sdt parameters from grabbag + xNameCont->insertByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, + uno::Any(getInteropGrabBagAndClear())); + + clear(); +} + +void SdtHelper::createControlShape(awt::Size aSize, + uno::Reference<awt::XControlModel> const& xControlModel, + const uno::Sequence<beans::PropertyValue>& rGrabBag) +{ + uno::Reference<drawing::XControlShape> xControlShape( + m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.drawing.ControlShape"), + uno::UNO_QUERY); + xControlShape->setSize(aSize); + xControlShape->setControl(xControlModel); + + uno::Reference<beans::XPropertySet> xPropertySet(xControlShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("VertOrient", uno::Any(text::VertOrientation::CENTER)); + + if (rGrabBag.hasElements()) + xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, uno::Any(rGrabBag)); + + uno::Reference<text::XTextContent> xTextContent(xControlShape, uno::UNO_QUERY); + m_rDM_Impl.appendTextContent(xTextContent, uno::Sequence<beans::PropertyValue>()); + m_bHasElements = true; +} + +void SdtHelper::appendToInteropGrabBag(const beans::PropertyValue& rValue) +{ + m_aGrabBag.push_back(rValue); +} + +uno::Sequence<beans::PropertyValue> SdtHelper::getInteropGrabBagAndClear() +{ + uno::Sequence<beans::PropertyValue> aRet = comphelper::containerToSequence(m_aGrabBag); + m_aGrabBag.clear(); + return aRet; +} + +bool SdtHelper::isInteropGrabBagEmpty() const { return m_aGrabBag.empty(); } + +sal_Int32 SdtHelper::getInteropGrabBagSize() const { return m_aGrabBag.size(); } + +bool SdtHelper::containedInInteropGrabBag(const OUString& rValueName) +{ + return std::any_of( + m_aGrabBag.begin(), m_aGrabBag.end(), + [&rValueName](const beans::PropertyValue& i) { return i.Name == rValueName; }); +} + +void SdtHelper::SetShowingPlcHdr() { m_bShowingPlcHdr = true; } + +bool SdtHelper::GetShowingPlcHdr() const { return m_bShowingPlcHdr; } + +void SdtHelper::SetChecked() { m_bChecked = true; } + +bool SdtHelper::GetChecked() const { return m_bChecked; } + +void SdtHelper::SetCheckedState(const OUString& rCheckedState) { m_aCheckedState = rCheckedState; } + +OUString SdtHelper::GetCheckedState() const { return m_aCheckedState; } + +void SdtHelper::SetUncheckedState(const OUString& rUncheckedState) +{ + m_aUncheckedState = rUncheckedState; +} + +OUString SdtHelper::GetUncheckedState() const { return m_aUncheckedState; } + +void SdtHelper::clear() +{ + m_aDropDownItems.clear(); + m_aDropDownDisplayTexts.clear(); + setControlType(SdtControlType::unknown); + m_sDataBindingPrefixMapping.clear(); + m_sDataBindingXPath.clear(); + m_sDataBindingStoreItemID.clear(); + m_aGrabBag.clear(); + m_bShowingPlcHdr = false; + m_bChecked = false; + m_aCheckedState.clear(); + m_aUncheckedState.clear(); +} + +void SdtHelper::SetPlaceholderDocPart(const OUString& rPlaceholderDocPart) +{ + m_aPlaceholderDocPart = rPlaceholderDocPart; +} + +OUString SdtHelper::GetPlaceholderDocPart() const { return m_aPlaceholderDocPart; } + +void SdtHelper::SetColor(const OUString& rColor) { m_aColor = rColor; } + +OUString SdtHelper::GetColor() const { return m_aColor; } + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SdtHelper.hxx b/writerfilter/source/dmapper/SdtHelper.hxx new file mode 100644 index 000000000..11bdfdcd1 --- /dev/null +++ b/writerfilter/source/dmapper/SdtHelper.hxx @@ -0,0 +1,208 @@ +/* -*- 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/. + */ + +#pragma once + +#include <vector> +#include <optional> +#include <unordered_map> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xml/dom/XDocument.hpp> + +#include <rtl/ustrbuf.hxx> +#include <tools/ref.hxx> + +namespace com::sun::star +{ +namespace uno +{ +class XComponentContext; +} +namespace awt +{ +struct Size; +class XControlModel; +} +} + +namespace writerfilter::dmapper +{ +class DomainMapper_Impl; + +enum class SdtControlType +{ + datePicker, + dropDown, + plainText, + richText, + checkBox, + picture, + unsupported, // Sdt block is defined, but we still do not support such type of field + unknown +}; + +/** + * Helper to create form controls from w:sdt tokens. + * + * w:sdt tokens can't be imported as form fields, as w:sdt supports + * e.g. date picking as well. + */ +class SdtHelper final : public virtual SvRefBase +{ + DomainMapper_Impl& m_rDM_Impl; + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + + /// Items of the drop-down control: <w:listItem w:value="...">. + std::vector<OUString> m_aDropDownItems; + /// Display texts of a drop-down control: <w:listItem w:displayText="...">. + std::vector<OUString> m_aDropDownDisplayTexts; + /// Type of sdt control + SdtControlType m_aControlType; + /// Pieces of the default text -- currently used only by the dropdown control. + OUStringBuffer m_aSdtTexts; + /// Date ISO string contained in the w:date element, used by the date control. + OUStringBuffer m_sDate; + /// Date format string as it comes from the ooxml document. + OUStringBuffer m_sDateFormat; + + /// <w:dataBinding w:prefixMappings=""> + OUString m_sDataBindingPrefixMapping; + /// <w:dataBinding w:xpath=""> + OUString m_sDataBindingXPath; + /// <w:dataBinding w:storeItemID=""> + OUString m_sDataBindingStoreItemID; + + /// Start range of the date field + css::uno::Reference<css::text::XTextRange> m_xDateFieldStartRange; + /// Locale string as it comes from the ooxml document. + OUStringBuffer m_sLocale; + /// Grab bag to store unsupported SDTs, aiming to save them back on export. + std::vector<css::beans::PropertyValue> m_aGrabBag; + + bool m_bHasElements; + /// The last stored SDT element is outside paragraphs. + bool m_bOutsideAParagraph; + + /// Storage for all properties documents as xml::dom::XDocument for later querying xpath for data + std::unordered_map<OUString, css::uno::Reference<css::xml::dom::XDocument>> m_xPropertiesXMLs; + + /// Check if m_xPropertiesXMLs is initialized and loaded (need extra flag to distinguish + /// empty sequence from not yet initialized) + bool m_bPropertiesXMLsLoaded; + + /// Current contents are placeholder text. + bool m_bShowingPlcHdr = false; + + /// If this is a checkbox, is the checkbox checked? + bool m_bChecked = false; + + /// If this is a checkbox, the value of a checked checkbox. + OUString m_aCheckedState; + + /// If this is a checkbox, the value of an unchecked checkbox. + OUString m_aUncheckedState; + + /// Create and append the drawing::XControlShape, containing the various models. + void createControlShape(css::awt::Size aSize, + css::uno::Reference<css::awt::XControlModel> const& xControlModel, + const css::uno::Sequence<css::beans::PropertyValue>& rGrabBag); + + std::optional<OUString> getValueFromDataBinding(); + + void loadPropertiesXMLs(); + + /// <w:placeholder>'s <w:docPart w:val="...">. + OUString m_aPlaceholderDocPart; + + /// <w:sdtPr>'s <w15:color w:val="...">. + OUString m_aColor; + +public: + explicit SdtHelper(DomainMapper_Impl& rDM_Impl, + css::uno::Reference<css::uno::XComponentContext> const& xContext); + ~SdtHelper() override; + + std::vector<OUString>& getDropDownItems() { return m_aDropDownItems; } + std::vector<OUString>& getDropDownDisplayTexts() { return m_aDropDownDisplayTexts; } + OUStringBuffer& getSdtTexts() { return m_aSdtTexts; } + + OUStringBuffer& getDate() { return m_sDate; } + + OUStringBuffer& getDateFormat() { return m_sDateFormat; } + + void setDataBindingPrefixMapping(const OUString& sValue) + { + m_sDataBindingPrefixMapping = sValue; + } + OUString GetDataBindingPrefixMapping() const { return m_sDataBindingPrefixMapping; } + + void setDataBindingXPath(const OUString& sValue) { m_sDataBindingXPath = sValue; } + OUString GetDataBindingXPath() const { return m_sDataBindingXPath; } + + void setDataBindingStoreItemID(const OUString& sValue) { m_sDataBindingStoreItemID = sValue; } + OUString GetDataBindingStoreItemID() const { return m_sDataBindingStoreItemID; } + + void setDateFieldStartRange(const css::uno::Reference<css::text::XTextRange>& xStartRange) + { + m_xDateFieldStartRange = xStartRange; + } + + OUStringBuffer& getLocale() { return m_sLocale; } + /// If createControlShape() was ever called. + bool hasElements() const { return m_bHasElements; } + + void setOutsideAParagraph(bool bOutsideAParagraph) + { + m_bOutsideAParagraph = bOutsideAParagraph; + } + + bool isOutsideAParagraph() const { return m_bOutsideAParagraph; } + + SdtControlType getControlType() { return m_aControlType; } + void setControlType(SdtControlType aType) { m_aControlType = aType; } + + /// Create drop-down control from w:sdt's w:dropDownList. + void createDropDownControl(); + /// Create date control from w:sdt's w:date. + void createDateContentControl(); + + void createPlainTextControl(); + + void appendToInteropGrabBag(const css::beans::PropertyValue& rValue); + css::uno::Sequence<css::beans::PropertyValue> getInteropGrabBagAndClear(); + bool isInteropGrabBagEmpty() const; + bool containedInInteropGrabBag(const OUString& rValueName); + sal_Int32 getInteropGrabBagSize() const; + + void SetShowingPlcHdr(); + bool GetShowingPlcHdr() const; + + void SetChecked(); + bool GetChecked() const; + void SetCheckedState(const OUString& rCheckedState); + OUString GetCheckedState() const; + void SetUncheckedState(const OUString& rUncheckedState); + OUString GetUncheckedState() const; + + /// Clear all collected attributes for further reuse + void clear(); + + void SetPlaceholderDocPart(const OUString& rPlaceholderDocPart); + OUString GetPlaceholderDocPart() const; + + void SetColor(const OUString& rColor); + OUString GetColor() const; +}; + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SectionColumnHandler.cxx b/writerfilter/source/dmapper/SectionColumnHandler.cxx new file mode 100644 index 000000000..9fed9c34a --- /dev/null +++ b/writerfilter/source/dmapper/SectionColumnHandler.cxx @@ -0,0 +1,90 @@ +/* -*- 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 "SectionColumnHandler.hxx" +#include "ConversionHelper.hxx" +#include <ooxml/resourceids.hxx> +#include <osl/diagnose.h> + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; + +SectionColumnHandler::SectionColumnHandler() + : LoggedProperties("SectionColumnHandler") + , m_bEqualWidth(false) + , m_nSpace(1270) // 720 twips + , m_nNum(0) + , m_bSep(false) +{ + m_aTempColumn.nWidth = m_aTempColumn.nSpace = 0; +} + +SectionColumnHandler::~SectionColumnHandler() {} + +void SectionColumnHandler::lcl_attribute(Id rName, Value& rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch (rName) + { + case NS_ooxml::LN_CT_Columns_equalWidth: + m_bEqualWidth = (nIntValue != 0); + break; + case NS_ooxml::LN_CT_Columns_space: + m_nSpace = ConversionHelper::convertTwipToMM100(nIntValue); + break; + case NS_ooxml::LN_CT_Columns_num: + m_nNum = nIntValue; + break; + case NS_ooxml::LN_CT_Columns_sep: + m_bSep = (nIntValue != 0); + break; + + case NS_ooxml::LN_CT_Column_w: + m_aTempColumn.nWidth = ConversionHelper::convertTwipToMM100(nIntValue); + break; + case NS_ooxml::LN_CT_Column_space: + m_aTempColumn.nSpace = ConversionHelper::convertTwipToMM100(nIntValue); + break; + default: + OSL_FAIL("SectionColumnHandler: unknown attribute"); + } +} + +void SectionColumnHandler::lcl_sprm(Sprm& rSprm) +{ + switch (rSprm.getId()) + { + case NS_ooxml::LN_CT_Columns_col: + { + m_aTempColumn.nWidth = m_aTempColumn.nSpace = 0; + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + m_aCols.push_back(m_aTempColumn); + } + } + break; + default: + OSL_FAIL("SectionColumnHandler: unknown sprm"); + } +} +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SectionColumnHandler.hxx b/writerfilter/source/dmapper/SectionColumnHandler.hxx new file mode 100644 index 000000000..cbef67460 --- /dev/null +++ b/writerfilter/source/dmapper/SectionColumnHandler.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include <vector> + +namespace writerfilter::dmapper +{ +struct Column_ +{ + sal_Int32 nWidth; + sal_Int32 nSpace; +}; + + +class SectionColumnHandler : public LoggedProperties +{ + bool m_bEqualWidth; + sal_Int32 m_nSpace; + sal_Int32 m_nNum; + bool m_bSep; + std::vector<Column_> m_aCols; + + Column_ m_aTempColumn; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + +public: + SectionColumnHandler(); + virtual ~SectionColumnHandler() override; + + bool IsEqualWidth() const { return m_bEqualWidth; } + sal_Int32 GetSpace() const { return m_nSpace; } + sal_Int32 GetNum() const { return m_nNum; } + bool IsSeparator() const { return m_bSep; } + + const std::vector<Column_>& GetColumns() const { return m_aCols;} + +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SettingsTable.cxx b/writerfilter/source/dmapper/SettingsTable.cxx new file mode 100644 index 000000000..45dc67b9f --- /dev/null +++ b/writerfilter/source/dmapper/SettingsTable.cxx @@ -0,0 +1,692 @@ +/* -*- 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 "SettingsTable.hxx" +#include "DocumentProtection.hxx" +#include "TagLogger.hxx" +#include "WriteProtection.hxx" + +#include <vector> + +#include <rtl/ustring.hxx> +#include <sfx2/zoomitem.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include "ConversionHelper.hxx" +#include "DomainMapper.hxx" +#include "util.hxx" + +using namespace com::sun::star; + +namespace writerfilter { +namespace +{ +/// Maps OOXML <w:zoom w:val="..."> to SvxZoomType. +sal_Int16 lcl_GetZoomType(Id nType) +{ + switch (nType) + { + case NS_ooxml::LN_Value_doc_ST_Zoom_fullPage: + return sal_Int16(SvxZoomType::WHOLEPAGE); + case NS_ooxml::LN_Value_doc_ST_Zoom_bestFit: + return sal_Int16(SvxZoomType::PAGEWIDTH); + case NS_ooxml::LN_Value_doc_ST_Zoom_textFit: + return sal_Int16(SvxZoomType::OPTIMAL); + } + + return sal_Int16(SvxZoomType::PERCENT); +} +} + +namespace dmapper +{ + +struct SettingsTable_Impl +{ + int m_nDefaultTabStop; + + bool m_bRecordChanges; + bool m_bShowInsDelChanges; + bool m_bShowFormattingChanges; + bool m_bShowMarkupChanges; + bool m_bLinkStyles; + sal_Int16 m_nZoomFactor; + sal_Int16 m_nZoomType = 0; + sal_Int32 m_nWordCompatibilityMode; + Id m_nView; + bool m_bEvenAndOddHeaders; + bool m_bUsePrinterMetrics; + bool embedTrueTypeFonts; + bool embedSystemFonts; + bool m_bDoNotUseHTMLParagraphAutoSpacing; + bool m_bNoColumnBalance; + bool m_bAutoHyphenation; + bool m_bNoHyphenateCaps; + sal_Int16 m_nHyphenationZone; + bool m_bWidowControl; + bool m_bLongerSpaceSequence; + bool m_bSplitPgBreakAndParaMark; + bool m_bMirrorMargin; + bool m_bDoNotExpandShiftReturn; + bool m_bDisplayBackgroundShape; + bool m_bNoLeading = false; + OUString m_sDecimalSymbol; + OUString m_sListSeparator; + + uno::Sequence<beans::PropertyValue> m_pThemeFontLangProps; + + std::vector<beans::PropertyValue> m_aCompatSettings; + uno::Sequence<beans::PropertyValue> m_pCurrentCompatSetting; + OUString m_sCurrentDatabaseDataSource; + + std::shared_ptr<DocumentProtection> m_pDocumentProtection; + std::shared_ptr<WriteProtection> m_pWriteProtection; + bool m_bGutterAtTop = false; + + SettingsTable_Impl() : + m_nDefaultTabStop( 720 ) //default is 1/2 in + , m_bRecordChanges(false) + , m_bShowInsDelChanges(true) + , m_bShowFormattingChanges(false) + , m_bShowMarkupChanges(true) + , m_bLinkStyles(false) + , m_nZoomFactor(0) + , m_nWordCompatibilityMode(-1) + , m_nView(0) + , m_bEvenAndOddHeaders(false) + , m_bUsePrinterMetrics(false) + , embedTrueTypeFonts(false) + , embedSystemFonts(false) + , m_bDoNotUseHTMLParagraphAutoSpacing(false) + , m_bNoColumnBalance(false) + , m_bAutoHyphenation(false) + , m_bNoHyphenateCaps(false) + , m_nHyphenationZone(0) + , m_bWidowControl(false) + , m_bLongerSpaceSequence(false) + , m_bSplitPgBreakAndParaMark(false) + , m_bMirrorMargin(false) + , m_bDoNotExpandShiftReturn(false) + , m_bDisplayBackgroundShape(false) + , m_sDecimalSymbol(".") + , m_sListSeparator(",") + , m_pThemeFontLangProps(3) + , m_pCurrentCompatSetting(3) + {} + +}; + +SettingsTable::SettingsTable(const DomainMapper& rDomainMapper) +: LoggedProperties("SettingsTable") +, LoggedTable("SettingsTable") +, m_pImpl( new SettingsTable_Impl ) +{ + if (rDomainMapper.IsRTFImport()) + { + // HTML paragraph auto-spacing is opt-in for RTF, opt-out for OOXML. + m_pImpl->m_bDoNotUseHTMLParagraphAutoSpacing = true; + // Longer space sequence is opt-in for RTF, and not in OOXML. + m_pImpl->m_bLongerSpaceSequence = true; + } + m_pImpl->m_pDocumentProtection = std::make_shared<DocumentProtection>(); + m_pImpl->m_pWriteProtection = std::make_shared<WriteProtection>(); +} + +SettingsTable::~SettingsTable() +{ +} + +void SettingsTable::lcl_attribute(Id nName, Value & val) +{ + int nIntValue = val.getInt(); + OUString sStringValue = val.getString(); + + switch(nName) + { + case NS_ooxml::LN_CT_Zoom_percent: + m_pImpl->m_nZoomFactor = nIntValue; + break; + case NS_ooxml::LN_CT_Zoom_val: + m_pImpl->m_nZoomType = lcl_GetZoomType(nIntValue); + break; + case NS_ooxml::LN_CT_Language_val: + m_pImpl->m_pThemeFontLangProps.getArray()[0] + = comphelper::makePropertyValue("val", sStringValue); + break; + case NS_ooxml::LN_CT_Language_eastAsia: + m_pImpl->m_pThemeFontLangProps.getArray()[1] + = comphelper::makePropertyValue("eastAsia", sStringValue); + break; + case NS_ooxml::LN_CT_Language_bidi: + m_pImpl->m_pThemeFontLangProps.getArray()[2] + = comphelper::makePropertyValue("bidi", sStringValue); + break; + case NS_ooxml::LN_CT_View_val: + m_pImpl->m_nView = nIntValue; + break; + case NS_ooxml::LN_CT_CompatSetting_name: + m_pImpl->m_pCurrentCompatSetting.getArray()[0] + = comphelper::makePropertyValue("name", sStringValue); + break; + case NS_ooxml::LN_CT_CompatSetting_uri: + m_pImpl->m_pCurrentCompatSetting.getArray()[1] + = comphelper::makePropertyValue("uri", sStringValue); + break; + case NS_ooxml::LN_CT_CompatSetting_val: + m_pImpl->m_pCurrentCompatSetting.getArray()[2] + = comphelper::makePropertyValue("val", sStringValue); + break; + case NS_ooxml::LN_CT_TrackChangesView_insDel: + m_pImpl->m_bShowInsDelChanges = (nIntValue != 0); + break; + case NS_ooxml::LN_CT_TrackChangesView_formatting: + m_pImpl->m_bShowFormattingChanges = (nIntValue != 0); + break; + case NS_ooxml::LN_CT_TrackChangesView_markup: + m_pImpl->m_bShowMarkupChanges = (nIntValue != 0); + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } +} + +void SettingsTable::lcl_sprm(Sprm& rSprm) +{ + sal_uInt32 nSprmId = rSprm.getId(); + + Value::Pointer_t pValue = rSprm.getValue(); + sal_Int32 nIntValue = pValue->getInt(); + + switch(nSprmId) + { + case NS_ooxml::LN_CT_Settings_zoom: // 92469; + case NS_ooxml::LN_CT_Settings_proofState: // 92489; + case NS_ooxml::LN_CT_Settings_attachedTemplate: // 92491; + case NS_ooxml::LN_CT_Settings_hdrShapeDefaults: // 92544; + case NS_ooxml::LN_CT_Settings_footnotePr: // 92545; + case NS_ooxml::LN_CT_Settings_endnotePr: // 92546; + case NS_ooxml::LN_CT_Settings_compat: // 92547; + case NS_ooxml::LN_CT_Settings_themeFontLang: // 92552; + case NS_ooxml::LN_CT_Settings_shapeDefaults: // 92560; + case NS_ooxml::LN_CT_Settings_view: + //PropertySetValues - need to be resolved + { + resolveSprmProps(*this, rSprm); + } + break; + case NS_ooxml::LN_CT_Settings_stylePaneFormatFilter: // 92493; + break; + case NS_ooxml::LN_CT_Settings_defaultTabStop: // 92505; + m_pImpl->m_nDefaultTabStop = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_linkStyles: // 92663; + m_pImpl->m_bLinkStyles = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_evenAndOddHeaders: + m_pImpl->m_bEvenAndOddHeaders = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_noPunctuationKerning: // 92526; + break; + case NS_ooxml::LN_CT_Settings_characterSpacingControl: // 92527; + // doNotCompress, compressPunctuation, compressPunctuationAndJapaneseKana + break; + case NS_ooxml::LN_CT_Settings_doNotIncludeSubdocsInStats: // 92554; // Do Not Include Content in Text Boxes, Footnotes, and Endnotes in Document Statistics) + break; + case NS_ooxml::LN_CT_Settings_decimalSymbol: // 92562; + m_pImpl->m_sDecimalSymbol = pValue->getString(); + break; + case NS_ooxml::LN_CT_Settings_listSeparator: // 92563; + m_pImpl->m_sListSeparator = pValue->getString(); + break; + case NS_ooxml::LN_CT_Settings_rsids: // 92549; revision save Ids - probably not necessary + break; + case NS_ooxml::LN_CT_Settings_hyphenationZone: // 92508; + m_pImpl->m_nHyphenationZone = nIntValue; + break; + case NS_ooxml::LN_CT_Compat_useFELayout: // 92422; + // useFELayout (Do Not Bypass East Asian/Complex Script Layout Code - support of old versions of Word - ignored) + break; + case NS_ooxml::LN_CT_Settings_trackRevisions: + { + m_pImpl->m_bRecordChanges = bool(rSprm.getValue( )->getInt( ) ); + } + break; + case NS_ooxml::LN_CT_Settings_revisionView: + resolveSprmProps(*this, rSprm); + break; + case NS_ooxml::LN_CT_Settings_documentProtection: + resolveSprmProps(*(m_pImpl->m_pDocumentProtection), rSprm); + break; + case NS_ooxml::LN_CT_Settings_writeProtection: + resolveSprmProps(*(m_pImpl->m_pWriteProtection), rSprm); + break; + case NS_ooxml::LN_CT_Compat_usePrinterMetrics: + m_pImpl->m_bUsePrinterMetrics = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_embedTrueTypeFonts: + m_pImpl->embedTrueTypeFonts = nIntValue != 0; + break; + case NS_ooxml::LN_CT_Settings_embedSystemFonts: + m_pImpl->embedSystemFonts = nIntValue != 0; + break; + case NS_ooxml::LN_CT_Compat_doNotUseHTMLParagraphAutoSpacing: + m_pImpl->m_bDoNotUseHTMLParagraphAutoSpacing = nIntValue; + break; + case NS_ooxml::LN_CT_Compat_splitPgBreakAndParaMark: + m_pImpl->m_bSplitPgBreakAndParaMark = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_mirrorMargins: + m_pImpl->m_bMirrorMargin = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_mailMerge: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_MailMerge_query: + { + // try to get the "database.table" name from the query saved previously + OUString sVal = pValue->getString(); + if ( sVal.endsWith("$") && sVal.indexOf(".dbo.") > 0 ) + { + sal_Int32 nSpace = sVal.lastIndexOf(' '); + sal_Int32 nDbo = sVal.lastIndexOf(".dbo."); + if ( nSpace > 0 && nSpace < nDbo - 1 ) + { + m_pImpl->m_sCurrentDatabaseDataSource = OUString::Concat(sVal.subView(nSpace + 1, nDbo - nSpace - 1)) + + sVal.subView(nDbo + 4, sVal.getLength() - nDbo - 5); + } + } + } + break; + case NS_ooxml::LN_CT_Compat_compatSetting: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + pProperties->resolve(*this); + + beans::PropertyValue aValue; + aValue.Name = "compatSetting"; + aValue.Value <<= m_pImpl->m_pCurrentCompatSetting; + m_pImpl->m_aCompatSettings.push_back(aValue); + } + } + break; + case NS_ooxml::LN_CT_Compat_noColumnBalance: + m_pImpl->m_bNoColumnBalance = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_autoHyphenation: + m_pImpl->m_bAutoHyphenation = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_doNotHyphenateCaps: + m_pImpl->m_bNoHyphenateCaps = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_widowControl: + m_pImpl->m_bWidowControl = nIntValue; + break; + case NS_ooxml::LN_CT_Settings_longerSpaceSequence: + m_pImpl->m_bLongerSpaceSequence = nIntValue; + break; + case NS_ooxml::LN_CT_Compat_doNotExpandShiftReturn: + m_pImpl->m_bDoNotExpandShiftReturn = true; + break; + case NS_ooxml::LN_CT_Settings_displayBackgroundShape: + m_pImpl->m_bDisplayBackgroundShape = nIntValue; + break; + case NS_ooxml::LN_CT_Compat_noLeading: + m_pImpl->m_bNoLeading = nIntValue != 0; + break; + case NS_ooxml::LN_CT_Settings_gutterAtTop: + m_pImpl->m_bGutterAtTop = nIntValue != 0; + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } +} + +void SettingsTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) +{ + ref->resolve(*this); +} + +//returns default TabStop in 1/100th mm +int SettingsTable::GetDefaultTabStop() const +{ + return ConversionHelper::convertTwipToMM100( m_pImpl->m_nDefaultTabStop ); +} + +bool SettingsTable::GetLinkStyles() const +{ + return m_pImpl->m_bLinkStyles; +} + +sal_Int16 SettingsTable::GetZoomFactor() const +{ + return m_pImpl->m_nZoomFactor; +} + +sal_Int16 SettingsTable::GetZoomType() const { return m_pImpl->m_nZoomType; } + +Id SettingsTable::GetView() const +{ + return m_pImpl->m_nView; +} + +bool SettingsTable::GetUsePrinterMetrics() const +{ + return m_pImpl->m_bUsePrinterMetrics; +} + +bool SettingsTable::GetEvenAndOddHeaders() const +{ + return m_pImpl->m_bEvenAndOddHeaders; +} + +bool SettingsTable::GetEmbedTrueTypeFonts() const +{ + return m_pImpl->embedTrueTypeFonts; +} + +bool SettingsTable::GetEmbedSystemFonts() const +{ + return m_pImpl->embedSystemFonts; +} + +bool SettingsTable::GetDoNotUseHTMLParagraphAutoSpacing() const +{ + return m_pImpl->m_bDoNotUseHTMLParagraphAutoSpacing; +} + +bool SettingsTable::GetNoColumnBalance() const +{ + return m_pImpl->m_bNoColumnBalance; +} + +bool SettingsTable::GetSplitPgBreakAndParaMark() const +{ + return m_pImpl->m_bSplitPgBreakAndParaMark; +} + +bool SettingsTable::GetMirrorMarginSettings() const +{ + return m_pImpl->m_bMirrorMargin; +} + +bool SettingsTable::GetDisplayBackgroundShape() const +{ + return m_pImpl->m_bDisplayBackgroundShape; +} + +bool SettingsTable::GetDoNotExpandShiftReturn() const +{ + return m_pImpl->m_bDoNotExpandShiftReturn; +} + +bool SettingsTable::GetProtectForm() const +{ + return m_pImpl->m_pDocumentProtection->getProtectForm() + && m_pImpl->m_pDocumentProtection->getEnforcement(); +} + +bool SettingsTable::GetReadOnly() const +{ + return m_pImpl->m_pWriteProtection->getRecommended() + || (m_pImpl->m_pDocumentProtection->getReadOnly() + && m_pImpl->m_pDocumentProtection->getEnforcement()); +} + +bool SettingsTable::GetNoHyphenateCaps() const +{ + return m_pImpl->m_bNoHyphenateCaps; +} + +sal_Int16 SettingsTable::GetHyphenationZone() const +{ + return m_pImpl->m_nHyphenationZone; +} + +const OUString & SettingsTable::GetDecimalSymbol() const +{ + return m_pImpl->m_sDecimalSymbol; +} + +const OUString & SettingsTable::GetListSeparator() const +{ + return m_pImpl->m_sListSeparator; +} + + +uno::Sequence<beans::PropertyValue> const & SettingsTable::GetThemeFontLangProperties() const +{ + return m_pImpl->m_pThemeFontLangProps; +} + +uno::Sequence<beans::PropertyValue> SettingsTable::GetCompatSettings() const +{ + if ( GetWordCompatibilityMode() == -1 ) + { + // the default value for an undefined compatibilityMode is 12 (Word 2007) + uno::Sequence<beans::PropertyValue> aCompatSetting( comphelper::InitPropertySequence({ + { "name", uno::Any(OUString("compatibilityMode")) }, + { "uri", uno::Any(OUString("http://schemas.microsoft.com/office/word")) }, + { "val", uno::Any(OUString("12")) } //12: Use word processing features specified in ECMA-376. This is the default. + })); + + beans::PropertyValue aValue; + aValue.Name = "compatSetting"; + aValue.Value <<= aCompatSetting; + + m_pImpl->m_aCompatSettings.push_back(aValue); + } + + return comphelper::containerToSequence(m_pImpl->m_aCompatSettings); +} + +uno::Sequence<beans::PropertyValue> SettingsTable::GetDocumentProtectionSettings() const +{ + return m_pImpl->m_pDocumentProtection->toSequence(); +} + +uno::Sequence<beans::PropertyValue> SettingsTable::GetWriteProtectionSettings() const +{ + return m_pImpl->m_pWriteProtection->toSequence(); +} + +const OUString & SettingsTable::GetCurrentDatabaseDataSource() const +{ + return m_pImpl->m_sCurrentDatabaseDataSource; +} + +static bool lcl_isDefault(const uno::Reference<beans::XPropertyState>& xPropertyState, const OUString& rPropertyName) +{ + return xPropertyState->getPropertyState(rPropertyName) == beans::PropertyState_DEFAULT_VALUE; +} + +void SettingsTable::ApplyProperties(uno::Reference<text::XTextDocument> const& xDoc) +{ + uno::Reference< beans::XPropertySet> xDocProps( xDoc, uno::UNO_QUERY ); + uno::Reference<lang::XMultiServiceFactory> xTextFactory(xDoc, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xDocumentSettings(xTextFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY_THROW); + + xDocumentSettings->setPropertyValue("TableRowKeep", uno::Any(true)); + + if (GetWordCompatibilityMode() <= 14) + { + xDocumentSettings->setPropertyValue("MsWordCompMinLineHeightByFly", uno::Any(true)); + xDocumentSettings->setPropertyValue("TabOverMargin", uno::Any(true)); + } + + // Show changes value + if (xDocProps.is()) + { + bool bHideChanges = !m_pImpl->m_bShowInsDelChanges || !m_pImpl->m_bShowMarkupChanges; + xDocProps->setPropertyValue("ShowChanges", uno::Any( !bHideChanges || m_pImpl->m_bShowFormattingChanges ) ); + } + + // Record changes value + if (xDocProps.is()) + { + xDocProps->setPropertyValue("RecordChanges", uno::Any( m_pImpl->m_bRecordChanges ) ); + // Password protected Record changes + if (m_pImpl->m_bRecordChanges && m_pImpl->m_pDocumentProtection->getRedlineProtection() + && m_pImpl->m_pDocumentProtection->getEnforcement()) + { + // use dummy protection key to forbid disabling of Record changes without a notice + // (extending the recent GrabBag support) TODO support password verification... + css::uno::Sequence<sal_Int8> aDummyKey { 1 }; + xDocProps->setPropertyValue("RedlineProtectionKey", uno::Any( aDummyKey )); + } + } + + // Auto hyphenation: turns on hyphenation by default, <w:suppressAutoHyphens/> may still disable it at a paragraph level. + // Situation is similar for RTF_WIDOWCTRL, which turns on widow / orphan control by default. + if (!(m_pImpl->m_bAutoHyphenation || m_pImpl->m_bNoHyphenateCaps || m_pImpl->m_bWidowControl)) + return; + + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xDoc, uno::UNO_QUERY); + if (!xStyleFamiliesSupplier.is()) + return; + + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xParagraphStyles = xStyleFamilies->getByName("ParagraphStyles").get< uno::Reference<container::XNameContainer> >(); + uno::Reference<style::XStyle> xDefault = xParagraphStyles->getByName("Standard").get< uno::Reference<style::XStyle> >(); + uno::Reference<beans::XPropertyState> xPropertyState(xDefault, uno::UNO_QUERY); + if (m_pImpl->m_bAutoHyphenation && lcl_isDefault(xPropertyState, "ParaIsHyphenation")) + { + uno::Reference<beans::XPropertySet> xPropertySet(xDefault, uno::UNO_QUERY); + xPropertySet->setPropertyValue("ParaIsHyphenation", uno::Any(true)); + } + if (m_pImpl->m_bNoHyphenateCaps) + { + uno::Reference<beans::XPropertySet> xPropertySet(xDefault, uno::UNO_QUERY); + xPropertySet->setPropertyValue("ParaHyphenationNoCaps", uno::Any(true)); + } + if (m_pImpl->m_nHyphenationZone) + { + uno::Reference<beans::XPropertySet> xPropertySet(xDefault, uno::UNO_QUERY); + xPropertySet->setPropertyValue("ParaHyphenationZone", uno::Any(GetHyphenationZone())); + } + if (m_pImpl->m_bWidowControl && lcl_isDefault(xPropertyState, "ParaWidows") && lcl_isDefault(xPropertyState, "ParaOrphans")) + { + uno::Reference<beans::XPropertySet> xPropertySet(xDefault, uno::UNO_QUERY); + uno::Any aAny(static_cast<sal_Int8>(2)); + xPropertySet->setPropertyValue("ParaWidows", aAny); + xPropertySet->setPropertyValue("ParaOrphans", aAny); + } +} + +bool SettingsTable::GetCompatSettingValue( std::u16string_view sCompatName ) const +{ + bool bRet = false; + for (const auto& rProp : m_pImpl->m_aCompatSettings) + { + if (rProp.Name == "compatSetting") //always true + { + css::uno::Sequence<css::beans::PropertyValue> aCurrentCompatSettings; + rProp.Value >>= aCurrentCompatSettings; + + OUString sName; + aCurrentCompatSettings[0].Value >>= sName; + if ( sName != sCompatName ) + continue; + + OUString sUri; + aCurrentCompatSettings[1].Value >>= sUri; + if ( sUri != "http://schemas.microsoft.com/office/word" ) + continue; + + OUString sVal; + aCurrentCompatSettings[2].Value >>= sVal; + // if repeated, what happens? Last one wins + bRet = sVal.toBoolean(); + } + } + + return bRet; +} + +//Keep this function in-sync with the one in sw/.../docxattributeoutput.cxx +sal_Int32 SettingsTable::GetWordCompatibilityMode() const +{ + if ( m_pImpl->m_nWordCompatibilityMode != -1 ) + return m_pImpl->m_nWordCompatibilityMode; + + for (const auto& rProp : m_pImpl->m_aCompatSettings) + { + if (rProp.Name == "compatSetting") //always true + { + css::uno::Sequence<css::beans::PropertyValue> aCurrentCompatSettings; + rProp.Value >>= aCurrentCompatSettings; + + OUString sName; + aCurrentCompatSettings[0].Value >>= sName; + if ( sName != "compatibilityMode" ) + continue; + + OUString sUri; + aCurrentCompatSettings[1].Value >>= sUri; + if ( sUri != "http://schemas.microsoft.com/office/word" ) + continue; + + OUString sVal; + aCurrentCompatSettings[2].Value >>= sVal; + const sal_Int32 nValidMode = sVal.toInt32(); + // if repeated, highest mode wins in MS Word. 11 is the first valid mode. + if ( nValidMode > 10 && nValidMode > m_pImpl->m_nWordCompatibilityMode ) + m_pImpl->m_nWordCompatibilityMode = nValidMode; + } + } + + return m_pImpl->m_nWordCompatibilityMode; +} + +bool SettingsTable::GetLongerSpaceSequence() const +{ + return m_pImpl->m_bLongerSpaceSequence; +} + +bool SettingsTable::GetNoLeading() const +{ + return m_pImpl->m_bNoLeading; +} + +bool SettingsTable::GetGutterAtTop() const { return m_pImpl->m_bGutterAtTop; } + +}//namespace dmapper +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SettingsTable.hxx b/writerfilter/source/dmapper/SettingsTable.hxx new file mode 100644 index 000000000..a0af31bed --- /dev/null +++ b/writerfilter/source/dmapper/SettingsTable.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/text/XTextDocument.hpp> +#include <memory> + +namespace com::sun::star::lang +{ +class XMultiServiceFactory; +struct Locale; +} + +namespace writerfilter::dmapper +{ +class DomainMapper; + +struct SettingsTable_Impl; + +class SettingsTable : public LoggedProperties, public LoggedTable +{ + std::unique_ptr<SettingsTable_Impl> m_pImpl; + +public: + SettingsTable(const DomainMapper& rDomainMapper); + virtual ~SettingsTable() override; + + //returns default TabStop in 1/100th mm + int GetDefaultTabStop() const; + + /// Automatically update styles from document template? + bool GetLinkStyles() const; + + /// What's the zoom factor set in percents? + sal_Int16 GetZoomFactor() const; + + /// Gets the type of the zoom. + sal_Int16 GetZoomType() const; + + /// What's the requested view? E.g. "web". + Id GetView() const; + + bool GetEvenAndOddHeaders() const; + + bool GetUsePrinterMetrics() const; + + bool GetEmbedTrueTypeFonts() const; + bool GetEmbedSystemFonts() const; + + bool GetDoNotUseHTMLParagraphAutoSpacing() const; + bool GetSplitPgBreakAndParaMark() const; + bool GetMirrorMarginSettings() const; + bool GetDisplayBackgroundShape() const; + bool GetDoNotExpandShiftReturn() const; + bool GetNoColumnBalance() const; + bool GetProtectForm() const; + bool GetReadOnly() const; + bool GetLongerSpaceSequence() const; + bool GetNoLeading() const; + bool GetNoHyphenateCaps() const; + sal_Int16 GetHyphenationZone() const; + + const OUString& GetDecimalSymbol() const; + const OUString& GetListSeparator() const; + + css::uno::Sequence<css::beans::PropertyValue> const& GetThemeFontLangProperties() const; + + css::uno::Sequence<css::beans::PropertyValue> GetCompatSettings() const; + + css::uno::Sequence<css::beans::PropertyValue> GetDocumentProtectionSettings() const; + + css::uno::Sequence<css::beans::PropertyValue> GetWriteProtectionSettings() const; + + void ApplyProperties(css::uno::Reference<css::text::XTextDocument> const& xDoc); + + bool GetCompatSettingValue(std::u16string_view sCompatName) const; + sal_Int32 GetWordCompatibilityMode() const; + + const OUString& GetCurrentDatabaseDataSource() const; + bool GetGutterAtTop() const; + +private: + // Properties + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; +}; +typedef tools::SvRef<SettingsTable> SettingsTablePtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SmartTagHandler.cxx b/writerfilter/source/dmapper/SmartTagHandler.cxx new file mode 100644 index 000000000..c92fa7c44 --- /dev/null +++ b/writerfilter/source/dmapper/SmartTagHandler.cxx @@ -0,0 +1,127 @@ +/* -*- 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/. + */ + +#include "SmartTagHandler.hxx" + +#include <com/sun/star/rdf/Literal.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextRange.hpp> + +#include <ooxml/resourceids.hxx> + +#include <sal/log.hxx> + +namespace +{ +OUString lcl_getTypePath(OUString& rType) +{ + OUString aRet; + if (rType.startsWith("urn:bails")) + { + rType = "urn:bails"; + aRet = "tscp/bails.rdf"; + } + return aRet; +} +} + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; + +SmartTagHandler::SmartTagHandler(uno::Reference<uno::XComponentContext> xComponentContext, + const uno::Reference<text::XTextDocument>& xTextDocument) + : LoggedProperties("SmartTagHandler") + , m_xComponentContext(std::move(xComponentContext)) + , m_xDocumentMetadataAccess(xTextDocument, uno::UNO_QUERY) +{ +} + +SmartTagHandler::~SmartTagHandler() = default; + +void SmartTagHandler::lcl_attribute(Id nId, Value& rValue) +{ + switch (nId) + { + case NS_ooxml::LN_CT_Attr_name: + m_aAttributes.emplace_back(rValue.getString(), OUString()); + break; + case NS_ooxml::LN_CT_Attr_val: + if (!m_aAttributes.empty()) + m_aAttributes.back().second = rValue.getString(); + break; + default: + SAL_WARN("writerfilter", "SmartTagHandler::lcl_attribute: unhandled attribute " + << nId << " (string value: '" << rValue.getString() + << "')"); + break; + } +} + +void SmartTagHandler::lcl_sprm(Sprm& rSprm) +{ + switch (rSprm.getId()) + { + case NS_ooxml::LN_CT_SmartTagPr_attr: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(*this); + break; + } + } +} + +void SmartTagHandler::setURI(const OUString& rURI) { m_aURI = rURI; } + +void SmartTagHandler::setElement(const OUString& rElement) { m_aElement = rElement; } + +void SmartTagHandler::handle(const uno::Reference<text::XTextRange>& xParagraph) +{ + if (m_aURI.isEmpty() || m_aElement.isEmpty() || m_aAttributes.empty()) + return; + + uno::Reference<rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY); + + for (const std::pair<OUString, OUString>& rAttribute : m_aAttributes) + { + OUString aTypeNS = rAttribute.first; + OUString aMetadataFilePath = lcl_getTypePath(aTypeNS); + if (aMetadataFilePath.isEmpty()) + continue; + + uno::Reference<rdf::XURI> xType = rdf::URI::create(m_xComponentContext, aTypeNS); + uno::Sequence<uno::Reference<rdf::XURI>> aGraphNames + = m_xDocumentMetadataAccess->getMetadataGraphsWithType(xType); + uno::Reference<rdf::XURI> xGraphName; + if (aGraphNames.hasElements()) + xGraphName = aGraphNames[0]; + else + { + uno::Sequence<uno::Reference<rdf::XURI>> xTypes = { xType }; + xGraphName = m_xDocumentMetadataAccess->addMetadataFile(aMetadataFilePath, xTypes); + } + uno::Reference<rdf::XNamedGraph> xGraph + = m_xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(m_xComponentContext, rAttribute.first); + uno::Reference<rdf::XLiteral> xValue + = rdf::Literal::create(m_xComponentContext, rAttribute.second); + xGraph->addStatement(xSubject, xKey, xValue); + } + + m_aURI.clear(); + m_aElement.clear(); + m_aAttributes.clear(); +} + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/SmartTagHandler.hxx b/writerfilter/source/dmapper/SmartTagHandler.hxx new file mode 100644 index 000000000..7999b9dcc --- /dev/null +++ b/writerfilter/source/dmapper/SmartTagHandler.hxx @@ -0,0 +1,60 @@ +/* -*- 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/. + */ +#pragma once + +#include <vector> + +#include "LoggedResources.hxx" + +namespace com::sun::star +{ +namespace rdf +{ +class XDocumentMetadataAccess; +} +namespace text +{ +class XTextDocument; +class XTextRange; +} +namespace uno +{ +class XComponentContext; +} +} + +namespace writerfilter::dmapper +{ +/// Handler for smart tags, i.e. <w:smartTag> and below. +class SmartTagHandler : public LoggedProperties +{ + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + css::uno::Reference<css::rdf::XDocumentMetadataAccess> m_xDocumentMetadataAccess; + OUString m_aURI; + OUString m_aElement; + std::vector<std::pair<OUString, OUString>> m_aAttributes; + +public: + SmartTagHandler(css::uno::Reference<css::uno::XComponentContext> xComponentContext, + const css::uno::Reference<css::text::XTextDocument>& xTextDocument); + ~SmartTagHandler() override; + + void lcl_attribute(Id nId, Value& rValue) override; + void lcl_sprm(Sprm& rSprm) override; + + void setURI(const OUString& rURI); + void setElement(const OUString& rElement); + + /// Set m_aAttributes as RDF statements on xParagraph. + void handle(const css::uno::Reference<css::text::XTextRange>& xParagraph); +}; + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/StyleSheetTable.cxx b/writerfilter/source/dmapper/StyleSheetTable.cxx new file mode 100644 index 000000000..3a6d540b7 --- /dev/null +++ b/writerfilter/source/dmapper/StyleSheetTable.cxx @@ -0,0 +1,1707 @@ +/* -*- 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 "StyleSheetTable.hxx" +#include "util.hxx" +#include "ConversionHelper.hxx" +#include "TblStylePrHandler.hxx" +#include "TagLogger.hxx" +#include "BorderHandler.hxx" +#include "LatentStyleHandler.hxx" +#include <ooxml/resourceids.hxx> +#include <vector> +#include <iterator> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <map> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/string.hxx> +#include <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/sorted_vector.hxx> + +using namespace ::com::sun::star; + +namespace writerfilter::dmapper +{ + +StyleSheetEntry::StyleSheetEntry() : + bIsDefaultStyle(false) + ,bAssignedAsChapterNumbering(false) + ,bInvalidHeight(false) + ,bHasUPE(false) + ,nStyleTypeCode(STYLE_TYPE_UNKNOWN) + ,pProperties(new StyleSheetPropertyMap) + ,bAutoRedefine(false) +{ +} + +StyleSheetEntry::~StyleSheetEntry() +{ +} + +TableStyleSheetEntry::TableStyleSheetEntry( StyleSheetEntry const & rEntry ) +{ + bIsDefaultStyle = rEntry.bIsDefaultStyle; + bInvalidHeight = rEntry.bInvalidHeight; + bHasUPE = rEntry.bHasUPE; + nStyleTypeCode = STYLE_TYPE_TABLE; + sBaseStyleIdentifier = rEntry.sBaseStyleIdentifier; + sNextStyleIdentifier = rEntry.sNextStyleIdentifier; + sLinkStyleIdentifier = rEntry.sLinkStyleIdentifier; + sStyleName = rEntry.sStyleName; + sStyleIdentifierD = rEntry.sStyleIdentifierD; +} + +TableStyleSheetEntry::~TableStyleSheetEntry( ) +{ +} + +void TableStyleSheetEntry::AddTblStylePr( TblStyleType nType, const PropertyMapPtr& pProps ) +{ + static const int nTypesProps = 4; + static const TblStyleType pTypesToFix[nTypesProps] = + { + TBL_STYLE_FIRSTROW, + TBL_STYLE_LASTROW, + TBL_STYLE_FIRSTCOL, + TBL_STYLE_LASTCOL + }; + + static const PropertyIds pPropsToCheck[nTypesProps] = + { + PROP_BOTTOM_BORDER, + PROP_TOP_BORDER, + PROP_RIGHT_BORDER, + PROP_LEFT_BORDER + }; + + for (int i=0; i < nTypesProps; ++i ) + { + if ( nType == pTypesToFix[i] ) + { + PropertyIds nChecked = pPropsToCheck[i]; + std::optional<PropertyMap::Property> pChecked = pProps->getProperty(nChecked); + + PropertyIds nInsideProp = ( i < 2 ) ? META_PROP_HORIZONTAL_BORDER : META_PROP_VERTICAL_BORDER; + std::optional<PropertyMap::Property> pInside = pProps->getProperty(nInsideProp); + + if ( pChecked && pInside ) + { + // In this case, remove the inside border + pProps->Erase( nInsideProp ); + } + + break; + } + } + + // Append the tblStylePr + m_aStyles[nType] = pProps; +} + +PropertyMapPtr TableStyleSheetEntry::GetProperties( sal_Int32 nMask ) +{ + PropertyMapPtr pProps( new PropertyMap ); + + // And finally get the mask ones + pProps->InsertProps(GetLocalPropertiesFromMask(nMask)); + + return pProps; +} + +beans::PropertyValues StyleSheetEntry::GetInteropGrabBagSeq() const +{ + return comphelper::containerToSequence(m_aInteropGrabBag); +} + +beans::PropertyValue StyleSheetEntry::GetInteropGrabBag() +{ + beans::PropertyValue aRet; + aRet.Name = sStyleIdentifierD; + + beans::PropertyValues aSeq = GetInteropGrabBagSeq(); + aRet.Value <<= aSeq; + return aRet; +} + +void StyleSheetEntry::AppendInteropGrabBag(const beans::PropertyValue& rValue) +{ + m_aInteropGrabBag.push_back(rValue); +} + +PropertyMapPtr StyleSheetEntry::GetMergedInheritedProperties(const StyleSheetTablePtr& pStyleSheetTable) +{ + PropertyMapPtr pRet; + if ( pStyleSheetTable && !sBaseStyleIdentifier.isEmpty() && sBaseStyleIdentifier != sStyleIdentifierD ) + { + const StyleSheetEntryPtr pParentStyleSheet = pStyleSheetTable->FindStyleSheetByISTD(sBaseStyleIdentifier); + if ( pParentStyleSheet ) + pRet = pParentStyleSheet->GetMergedInheritedProperties(pStyleSheetTable); + } + + if ( !pRet ) + pRet = new PropertyMap; + + pRet->InsertProps(pProperties.get()); + + return pRet; +} + +static void lcl_mergeProps( const PropertyMapPtr& pToFill, const PropertyMapPtr& pToAdd, TblStyleType nStyleId ) +{ + static const PropertyIds pPropsToCheck[] = + { + PROP_BOTTOM_BORDER, + PROP_TOP_BORDER, + PROP_RIGHT_BORDER, + PROP_LEFT_BORDER, + }; + + bool pRemoveInside[] = + { + ( nStyleId == TBL_STYLE_FIRSTROW ), + ( nStyleId == TBL_STYLE_LASTROW ), + ( nStyleId == TBL_STYLE_LASTCOL ), + ( nStyleId == TBL_STYLE_FIRSTCOL ) + }; + + for ( unsigned i = 0 ; i != SAL_N_ELEMENTS(pPropsToCheck); i++ ) + { + PropertyIds nId = pPropsToCheck[i]; + std::optional<PropertyMap::Property> pProp = pToAdd->getProperty(nId); + + if ( pProp ) + { + if ( pRemoveInside[i] ) + { + // Remove the insideH and insideV depending on the cell pos + PropertyIds nInsideProp = ( i < 2 ) ? META_PROP_HORIZONTAL_BORDER : META_PROP_VERTICAL_BORDER; + pToFill->Erase(nInsideProp); + } + } + } + + pToFill->InsertProps(pToAdd); +} + +PropertyMapPtr TableStyleSheetEntry::GetLocalPropertiesFromMask( sal_Int32 nMask ) +{ + // Order from right to left + struct TblStyleTypeAndMask { + sal_Int32 mask; + TblStyleType type; + }; + + static const TblStyleTypeAndMask aOrderedStyleTable[] = + { + { 0x010, TBL_STYLE_BAND2HORZ }, + { 0x020, TBL_STYLE_BAND1HORZ }, + { 0x040, TBL_STYLE_BAND2VERT }, + { 0x080, TBL_STYLE_BAND1VERT }, + { 0x100, TBL_STYLE_LASTCOL }, + { 0x200, TBL_STYLE_FIRSTCOL }, + { 0x400, TBL_STYLE_LASTROW }, + { 0x800, TBL_STYLE_FIRSTROW }, + { 0x001, TBL_STYLE_SWCELL }, + { 0x002, TBL_STYLE_SECELL }, + { 0x004, TBL_STYLE_NWCELL }, + { 0x008, TBL_STYLE_NECELL } + }; + + // Get the properties applying according to the mask + PropertyMapPtr pProps( new PropertyMap( ) ); + for (const TblStyleTypeAndMask & i : aOrderedStyleTable) + { + TblStylePrs::iterator pIt = m_aStyles.find( i.type ); + if ( ( nMask & i.mask ) && ( pIt != m_aStyles.end( ) ) ) + lcl_mergeProps( pProps, pIt->second, i.type ); + } + return pProps; +} + +namespace { + +struct ListCharStylePropertyMap_t +{ + OUString sCharStyleName; + PropertyValueVector_t aPropertyValues; + + ListCharStylePropertyMap_t(const OUString& rCharStyleName, PropertyValueVector_t&& rPropertyValues): + sCharStyleName( rCharStyleName ), + aPropertyValues( std::move(rPropertyValues) ) + {} +}; + +} + +struct StyleSheetTable_Impl +{ + DomainMapper& m_rDMapper; + uno::Reference< text::XTextDocument> m_xTextDocument; + uno::Reference< beans::XPropertySet> m_xTextDefaults; + std::vector< StyleSheetEntryPtr > m_aStyleSheetEntries; + std::map< OUString, StyleSheetEntryPtr > m_aStyleSheetEntriesMap; + StyleSheetEntryPtr m_pCurrentEntry; + PropertyMapPtr m_pDefaultParaProps, m_pDefaultCharProps; + OUString m_sDefaultParaStyleName; //WW8 name + std::vector< ListCharStylePropertyMap_t > m_aListCharStylePropertyVector; + bool m_bHasImportedDefaultParaProps; + bool m_bIsNewDoc; + + StyleSheetTable_Impl(DomainMapper& rDMapper, uno::Reference< text::XTextDocument> const& xTextDocument, bool bIsNewDoc); + + OUString HasListCharStyle( const PropertyValueVector_t& rCharProperties ); + + /// Appends the given key-value pair to the list of latent style properties of the current entry. + void AppendLatentStyleProperty(const OUString& aName, Value const & rValue); + /// Sets all properties of xStyle back to default. + static void SetPropertiesToDefault(const uno::Reference<style::XStyle>& xStyle); +}; + + +StyleSheetTable_Impl::StyleSheetTable_Impl(DomainMapper& rDMapper, + uno::Reference< text::XTextDocument> const& xTextDocument, + bool const bIsNewDoc) + : m_rDMapper( rDMapper ), + m_xTextDocument( xTextDocument ), + m_pDefaultParaProps(new PropertyMap), + m_pDefaultCharProps(new PropertyMap), + m_sDefaultParaStyleName("Normal"), + m_bHasImportedDefaultParaProps(false), + m_bIsNewDoc(bIsNewDoc) +{ + //set font height default to 10pt + uno::Any aVal( 10.0 ); + m_pDefaultCharProps->Insert( PROP_CHAR_HEIGHT, aVal ); + m_pDefaultCharProps->Insert( PROP_CHAR_HEIGHT_ASIAN, aVal ); + m_pDefaultCharProps->Insert( PROP_CHAR_HEIGHT_COMPLEX, aVal ); + + // See SwDoc::RemoveAllFormatLanguageDependencies(), internal filters + // disable kerning by default, do the same here. + m_pDefaultCharProps->Insert(PROP_CHAR_AUTO_KERNING, uno::Any(false)); +} + + +OUString StyleSheetTable_Impl::HasListCharStyle( const PropertyValueVector_t& rPropValues ) +{ + for( const auto& rListVector : m_aListCharStylePropertyVector ) + { + const auto& rPropertyValues = rListVector.aPropertyValues; + //if size is identical + if( rPropertyValues.size() == rPropValues.size() ) + { + bool bBreak = false; + //then search for all contained properties + for( const auto& rPropVal1 : rPropValues) + { + //find the property + auto aListIter = std::find_if(rPropertyValues.begin(), rPropertyValues.end(), + [&rPropVal1](const css::beans::PropertyValue& rPropVal2) { return rPropVal2.Name == rPropVal1.Name; }); + //set break flag if property hasn't been found + bBreak = (aListIter == rPropertyValues.end()) || (aListIter->Value != rPropVal1.Value); + if( bBreak ) + break; + } + if( !bBreak ) + return rListVector.sCharStyleName; + } + } + return OUString(); +} + +void StyleSheetTable_Impl::AppendLatentStyleProperty(const OUString& aName, Value const & rValue) +{ + beans::PropertyValue aValue; + aValue.Name = aName; + aValue.Value <<= rValue.getString(); + m_pCurrentEntry->aLatentStyles.push_back(aValue); +} + +void StyleSheetTable_Impl::SetPropertiesToDefault(const uno::Reference<style::XStyle>& xStyle) +{ + // See if the existing style has any non-default properties. If so, reset them back to default. + uno::Reference<beans::XPropertySet> xPropertySet(xStyle, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + const uno::Sequence<beans::Property> aProperties = xPropertySetInfo->getProperties(); + std::vector<OUString> aPropertyNames; + aPropertyNames.reserve(aProperties.getLength()); + std::transform(aProperties.begin(), aProperties.end(), std::back_inserter(aPropertyNames), + [](const beans::Property& rProp) { return rProp.Name; }); + + uno::Reference<beans::XPropertyState> xPropertyState(xStyle, uno::UNO_QUERY); + uno::Sequence<beans::PropertyState> aStates = xPropertyState->getPropertyStates(comphelper::containerToSequence(aPropertyNames)); + for (sal_Int32 i = 0; i < aStates.getLength(); ++i) + { + if (aStates[i] == beans::PropertyState_DIRECT_VALUE) + { + try + { + xPropertyState->setPropertyToDefault(aPropertyNames[i]); + } + catch(const uno::Exception&) + { + TOOLS_INFO_EXCEPTION("writerfilter", "setPropertyToDefault(" << aPropertyNames[i] << ") failed"); + } + } + } +} + +StyleSheetTable::StyleSheetTable(DomainMapper& rDMapper, + uno::Reference< text::XTextDocument> const& xTextDocument, + bool const bIsNewDoc) +: LoggedProperties("StyleSheetTable") +, LoggedTable("StyleSheetTable") +, m_pImpl( new StyleSheetTable_Impl(rDMapper, xTextDocument, bIsNewDoc) ) +{ +} + + +StyleSheetTable::~StyleSheetTable() +{ +} + +void StyleSheetTable::SetDefaultParaProps(PropertyIds eId, const css::uno::Any& rAny) +{ + m_pImpl->m_pDefaultParaProps->Insert(eId, rAny, /*bOverwrite=*/false, NO_GRAB_BAG, /*bDocDefault=*/true); +} + +PropertyMapPtr const & StyleSheetTable::GetDefaultParaProps() const +{ + return m_pImpl->m_pDefaultParaProps; +} + +PropertyMapPtr const & StyleSheetTable::GetDefaultCharProps() const +{ + return m_pImpl->m_pDefaultCharProps; +} + +void StyleSheetTable::lcl_attribute(Id Name, Value & val) +{ + OSL_ENSURE( m_pImpl->m_pCurrentEntry, "current entry has to be set here"); + if(!m_pImpl->m_pCurrentEntry) + return ; + int nIntValue = val.getInt(); + OUString sValue = val.getString(); + + // The default type is paragraph, and it needs to be processed first, + // because the NS_ooxml::LN_CT_Style_type handling may set m_pImpl->m_pCurrentEntry + // to point to a different object. + if( m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_UNKNOWN ) + { + if( Name != NS_ooxml::LN_CT_Style_type ) + m_pImpl->m_pCurrentEntry->nStyleTypeCode = STYLE_TYPE_PARA; + } + switch(Name) + { + case NS_ooxml::LN_CT_Style_type: + { + SAL_WARN_IF( m_pImpl->m_pCurrentEntry->nStyleTypeCode != STYLE_TYPE_UNKNOWN, + "writerfilter", "Style type needs to be processed first" ); + StyleType nType(STYLE_TYPE_UNKNOWN); + switch (nIntValue) + { + case NS_ooxml::LN_Value_ST_StyleType_paragraph: + nType = STYLE_TYPE_PARA; + break; + case NS_ooxml::LN_Value_ST_StyleType_character: + nType = STYLE_TYPE_CHAR; + break; + case NS_ooxml::LN_Value_ST_StyleType_table: + nType = STYLE_TYPE_TABLE; + break; + case NS_ooxml::LN_Value_ST_StyleType_numbering: + nType = STYLE_TYPE_LIST; + break; + default: + SAL_WARN("writerfilter", "unknown LN_CT_Style_type " << nType); + [[fallthrough]]; + case 0: // explicit unknown set by tokenizer + break; + + } + if ( nType == STYLE_TYPE_TABLE ) + { + StyleSheetEntryPtr pEntry = m_pImpl->m_pCurrentEntry; + tools::SvRef<TableStyleSheetEntry> pTableEntry( new TableStyleSheetEntry( *pEntry ) ); + m_pImpl->m_pCurrentEntry = pTableEntry.get(); + } + else + m_pImpl->m_pCurrentEntry->nStyleTypeCode = nType; + } + break; + case NS_ooxml::LN_CT_Style_default: + m_pImpl->m_pCurrentEntry->bIsDefaultStyle = (nIntValue != 0); + + if (m_pImpl->m_pCurrentEntry->nStyleTypeCode != STYLE_TYPE_UNKNOWN) + { + // "If this attribute is specified by multiple styles, then the last instance shall be used." + if (m_pImpl->m_pCurrentEntry->bIsDefaultStyle + && m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_PARA + && !m_pImpl->m_pCurrentEntry->sStyleIdentifierD.isEmpty()) + { + m_pImpl->m_sDefaultParaStyleName = m_pImpl->m_pCurrentEntry->sStyleIdentifierD; + } + + beans::PropertyValue aValue; + aValue.Name = "default"; + aValue.Value <<= m_pImpl->m_pCurrentEntry->bIsDefaultStyle; + m_pImpl->m_pCurrentEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_Style_customStyle: + if (m_pImpl->m_pCurrentEntry->nStyleTypeCode != STYLE_TYPE_UNKNOWN) + { + beans::PropertyValue aValue; + aValue.Name = "customStyle"; + aValue.Value <<= (nIntValue != 0); + m_pImpl->m_pCurrentEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_Style_styleId: + m_pImpl->m_pCurrentEntry->sStyleIdentifierD = sValue; + if(m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + TableStyleSheetEntry* pTableEntry = static_cast<TableStyleSheetEntry *>(m_pImpl->m_pCurrentEntry.get()); + beans::PropertyValue aValue; + aValue.Name = "styleId"; + aValue.Value <<= sValue; + pTableEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_TblWidth_w: + break; + case NS_ooxml::LN_CT_TblWidth_type: + break; + case NS_ooxml::LN_CT_LatentStyles_defQFormat: + m_pImpl->AppendLatentStyleProperty("defQFormat", val); + break; + case NS_ooxml::LN_CT_LatentStyles_defUnhideWhenUsed: + m_pImpl->AppendLatentStyleProperty("defUnhideWhenUsed", val); + break; + case NS_ooxml::LN_CT_LatentStyles_defSemiHidden: + m_pImpl->AppendLatentStyleProperty("defSemiHidden", val); + break; + case NS_ooxml::LN_CT_LatentStyles_count: + m_pImpl->AppendLatentStyleProperty("count", val); + break; + case NS_ooxml::LN_CT_LatentStyles_defUIPriority: + m_pImpl->AppendLatentStyleProperty("defUIPriority", val); + break; + case NS_ooxml::LN_CT_LatentStyles_defLockedState: + m_pImpl->AppendLatentStyleProperty("defLockedState", val); + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + break; + } +} + + +void StyleSheetTable::lcl_sprm(Sprm & rSprm) +{ + sal_uInt32 nSprmId = rSprm.getId(); + Value::Pointer_t pValue = rSprm.getValue(); + sal_Int32 nIntValue = pValue ? pValue->getInt() : 0; + OUString sStringValue = pValue ? pValue->getString() : OUString(); + + switch(nSprmId) + { + case NS_ooxml::LN_CT_Style_name: + //this is only a UI name! + m_pImpl->m_pCurrentEntry->sStyleName = sStringValue; + if(m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + TableStyleSheetEntry* pTableEntry = static_cast<TableStyleSheetEntry *>(m_pImpl->m_pCurrentEntry.get()); + beans::PropertyValue aValue; + aValue.Name = "name"; + aValue.Value <<= sStringValue; + pTableEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_Style_basedOn: + m_pImpl->m_pCurrentEntry->sBaseStyleIdentifier = sStringValue; + if(m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + TableStyleSheetEntry* pTableEntry = static_cast<TableStyleSheetEntry *>(m_pImpl->m_pCurrentEntry.get()); + beans::PropertyValue aValue; + aValue.Name = "basedOn"; + aValue.Value <<= sStringValue; + pTableEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_Style_link: + m_pImpl->m_pCurrentEntry->sLinkStyleIdentifier = sStringValue; + break; + case NS_ooxml::LN_CT_Style_next: + m_pImpl->m_pCurrentEntry->sNextStyleIdentifier = sStringValue; + break; + case NS_ooxml::LN_CT_Style_aliases: + case NS_ooxml::LN_CT_Style_hidden: + case NS_ooxml::LN_CT_Style_personal: + case NS_ooxml::LN_CT_Style_personalCompose: + case NS_ooxml::LN_CT_Style_personalReply: + break; + case NS_ooxml::LN_CT_Style_autoRedefine: + m_pImpl->m_pCurrentEntry->bAutoRedefine = nIntValue; + break; + case NS_ooxml::LN_CT_Style_tcPr: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties && m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + auto pTblStylePrHandler = std::make_shared<TblStylePrHandler>(m_pImpl->m_rDMapper); + pProperties->resolve(*pTblStylePrHandler); + StyleSheetEntry* pEntry = m_pImpl->m_pCurrentEntry.get(); + TableStyleSheetEntry& rTableEntry = dynamic_cast<TableStyleSheetEntry&>(*pEntry); + rTableEntry.AppendInteropGrabBag(pTblStylePrHandler->getInteropGrabBag("tcPr")); + + // This is a <w:tcPr> directly under <w:style>, so it affects the whole table. + rTableEntry.pProperties->InsertProps(pTblStylePrHandler->getProperties()); + } + } + break; + case NS_ooxml::LN_CT_Style_trPr: + break; + case NS_ooxml::LN_CT_Style_rsid: + case NS_ooxml::LN_CT_Style_qFormat: + case NS_ooxml::LN_CT_Style_semiHidden: + case NS_ooxml::LN_CT_Style_unhideWhenUsed: + case NS_ooxml::LN_CT_Style_uiPriority: + case NS_ooxml::LN_CT_Style_locked: + if (m_pImpl->m_pCurrentEntry->nStyleTypeCode != STYLE_TYPE_UNKNOWN) + { + StyleSheetEntryPtr pEntry = m_pImpl->m_pCurrentEntry; + beans::PropertyValue aValue; + switch (nSprmId) + { + case NS_ooxml::LN_CT_Style_rsid: + { + // We want the rsid as a hex string, but always with the length of 8. + OUStringBuffer aBuf = OUString::number(nIntValue, 16); + OUStringBuffer aStr; + comphelper::string::padToLength(aStr, 8 - aBuf.getLength(), '0'); + aStr.append(aBuf.getStr()); + + aValue.Name = "rsid"; + aValue.Value <<= aStr.makeStringAndClear(); + } + break; + case NS_ooxml::LN_CT_Style_qFormat: + aValue.Name = "qFormat"; + break; + case NS_ooxml::LN_CT_Style_semiHidden: + aValue.Name = "semiHidden"; + break; + case NS_ooxml::LN_CT_Style_unhideWhenUsed: + aValue.Name = "unhideWhenUsed"; + break; + case NS_ooxml::LN_CT_Style_uiPriority: + { + aValue.Name = "uiPriority"; + aValue.Value <<= OUString::number(nIntValue); + } + break; + case NS_ooxml::LN_CT_Style_locked: + aValue.Name = "locked"; + break; + } + pEntry->AppendInteropGrabBag(aValue); + } + break; + case NS_ooxml::LN_CT_Style_tblPr: //contains table properties + case NS_ooxml::LN_CT_Style_tblStylePr: //contains to table properties + case NS_ooxml::LN_CT_TblPrBase_tblInd: //table properties - at least width value and type + case NS_ooxml::LN_EG_RPrBase_rFonts: //table fonts + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pTblStylePrHandler = std::make_shared<TblStylePrHandler>( m_pImpl->m_rDMapper ); + pProperties->resolve( *pTblStylePrHandler ); + + // Add the properties to the table style + TblStyleType nType = pTblStylePrHandler->getType( ); + PropertyMapPtr pProps = pTblStylePrHandler->getProperties( ); + StyleSheetEntry * pEntry = m_pImpl->m_pCurrentEntry.get(); + + TableStyleSheetEntry * pTableEntry = dynamic_cast<TableStyleSheetEntry*>( pEntry ); + if (nType == TBL_STYLE_UNKNOWN) + { + pEntry->pProperties->InsertProps(pProps); + } + else + { + if (pTableEntry != nullptr) + pTableEntry->AddTblStylePr( nType, pProps ); + } + + if (nSprmId == NS_ooxml::LN_CT_Style_tblPr) + { + if (pTableEntry != nullptr) + pTableEntry->AppendInteropGrabBag(pTblStylePrHandler->getInteropGrabBag("tblPr")); + } + else if (nSprmId == NS_ooxml::LN_CT_Style_tblStylePr) + { + pTblStylePrHandler->appendInteropGrabBag("type", pTblStylePrHandler->getTypeString()); + if (pTableEntry != nullptr) + pTableEntry->AppendInteropGrabBag(pTblStylePrHandler->getInteropGrabBag("tblStylePr")); + } + } + break; + } + case NS_ooxml::LN_CT_PPrDefault_pPr: + case NS_ooxml::LN_CT_DocDefaults_pPrDefault: + if (nSprmId == NS_ooxml::LN_CT_DocDefaults_pPrDefault) + m_pImpl->m_rDMapper.SetDocDefaultsImport(true); + + m_pImpl->m_rDMapper.PushStyleSheetProperties( m_pImpl->m_pDefaultParaProps ); + resolveSprmProps( m_pImpl->m_rDMapper, rSprm ); + if ( nSprmId == NS_ooxml::LN_CT_DocDefaults_pPrDefault && m_pImpl->m_pDefaultParaProps && + !m_pImpl->m_pDefaultParaProps->isSet( PROP_PARA_TOP_MARGIN ) ) + { + SetDefaultParaProps( PROP_PARA_TOP_MARGIN, uno::Any( sal_Int32(0) ) ); + } + m_pImpl->m_rDMapper.PopStyleSheetProperties(); + applyDefaults( true ); + m_pImpl->m_bHasImportedDefaultParaProps = true; + if (nSprmId == NS_ooxml::LN_CT_DocDefaults_pPrDefault) + m_pImpl->m_rDMapper.SetDocDefaultsImport(false); + break; + case NS_ooxml::LN_CT_RPrDefault_rPr: + case NS_ooxml::LN_CT_DocDefaults_rPrDefault: + if (nSprmId == NS_ooxml::LN_CT_DocDefaults_rPrDefault) + m_pImpl->m_rDMapper.SetDocDefaultsImport(true); + + m_pImpl->m_rDMapper.PushStyleSheetProperties( m_pImpl->m_pDefaultCharProps ); + resolveSprmProps( m_pImpl->m_rDMapper, rSprm ); + m_pImpl->m_rDMapper.PopStyleSheetProperties(); + applyDefaults( false ); + if (nSprmId == NS_ooxml::LN_CT_DocDefaults_rPrDefault) + m_pImpl->m_rDMapper.SetDocDefaultsImport(false); + break; + case NS_ooxml::LN_CT_TblPrBase_jc: //table alignment - row properties! + m_pImpl->m_pCurrentEntry->pProperties->Insert( PROP_HORI_ORIENT, + uno::Any( ConversionHelper::convertTableJustification( nIntValue ))); + break; + case NS_ooxml::LN_CT_TrPrBase_jc: //table alignment - row properties! + break; + case NS_ooxml::LN_CT_TblPrBase_tblBorders: //table borders, might be defined in table style + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pBorderHandler = std::make_shared<BorderHandler>(m_pImpl->m_rDMapper.IsOOXMLImport()); + pProperties->resolve(*pBorderHandler); + m_pImpl->m_pCurrentEntry->pProperties->InsertProps( + pBorderHandler->getProperties()); + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblStyleRowBandSize: + case NS_ooxml::LN_CT_TblPrBase_tblStyleColBandSize: + break; + case NS_ooxml::LN_CT_TblPrBase_tblCellMar: + //no cell margins in styles + break; + case NS_ooxml::LN_CT_LatentStyles_lsdException: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + tools::SvRef<LatentStyleHandler> pLatentStyleHandler(new LatentStyleHandler()); + pProperties->resolve(*pLatentStyleHandler); + beans::PropertyValue aValue; + aValue.Name = "lsdException"; + aValue.Value <<= comphelper::containerToSequence(pLatentStyleHandler->getAttributes()); + m_pImpl->m_pCurrentEntry->aLsdExceptions.push_back(aValue); + } + } + break; + case NS_ooxml::LN_CT_Style_pPr: + // no break + case NS_ooxml::LN_CT_Style_rPr: + // no break + default: + { + if (!m_pImpl->m_pCurrentEntry) + break; + + tools::SvRef<TablePropertiesHandler> pTblHandler(new TablePropertiesHandler()); + pTblHandler->SetProperties( m_pImpl->m_pCurrentEntry->pProperties.get() ); + if ( !pTblHandler->sprm( rSprm ) ) + { + m_pImpl->m_rDMapper.PushStyleSheetProperties( m_pImpl->m_pCurrentEntry->pProperties.get() ); + + PropertyMapPtr pProps(new PropertyMap()); + if (m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + if (nSprmId == NS_ooxml::LN_CT_Style_pPr) + m_pImpl->m_rDMapper.enableInteropGrabBag("pPr"); + else if (nSprmId == NS_ooxml::LN_CT_Style_rPr) + m_pImpl->m_rDMapper.enableInteropGrabBag("rPr"); + } + m_pImpl->m_rDMapper.sprmWithProps( rSprm, pProps ); + if (m_pImpl->m_pCurrentEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + if (nSprmId == NS_ooxml::LN_CT_Style_pPr || nSprmId == NS_ooxml::LN_CT_Style_rPr) + { + TableStyleSheetEntry* pTableEntry = static_cast<TableStyleSheetEntry *>(m_pImpl->m_pCurrentEntry.get()); + pTableEntry->AppendInteropGrabBag(m_pImpl->m_rDMapper.getInteropGrabBag()); + } + } + + m_pImpl->m_pCurrentEntry->pProperties->InsertProps(pProps); + + m_pImpl->m_rDMapper.PopStyleSheetProperties( ); + } + } + break; +} +} + + +void StyleSheetTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) +{ + //create a new style entry + OSL_ENSURE( !m_pImpl->m_pCurrentEntry, "current entry has to be NULL here"); + StyleSheetEntryPtr pNewEntry( new StyleSheetEntry ); + m_pImpl->m_pCurrentEntry = pNewEntry; + m_pImpl->m_rDMapper.PushStyleSheetProperties( m_pImpl->m_pCurrentEntry->pProperties.get() ); + ref->resolve(*this); + //append it to the table + m_pImpl->m_rDMapper.PopStyleSheetProperties(); + if( !m_pImpl->m_rDMapper.IsOOXMLImport() || !m_pImpl->m_pCurrentEntry->sStyleName.isEmpty()) + { + m_pImpl->m_pCurrentEntry->sConvertedStyleName = ConvertStyleName( m_pImpl->m_pCurrentEntry->sStyleName ); + m_pImpl->m_aStyleSheetEntries.push_back( m_pImpl->m_pCurrentEntry ); + m_pImpl->m_aStyleSheetEntriesMap.emplace( m_pImpl->m_pCurrentEntry->sStyleIdentifierD, m_pImpl->m_pCurrentEntry ); + } + else + { + //TODO: this entry contains the default settings - they have to be added to the settings + } + + if (!m_pImpl->m_pCurrentEntry->aLatentStyles.empty()) + { + // We have latent styles for this entry, then process them. + std::vector<beans::PropertyValue>& rLatentStyles = m_pImpl->m_pCurrentEntry->aLatentStyles; + + if (!m_pImpl->m_pCurrentEntry->aLsdExceptions.empty()) + { + std::vector<beans::PropertyValue>& rLsdExceptions = m_pImpl->m_pCurrentEntry->aLsdExceptions; + beans::PropertyValue aValue; + aValue.Name = "lsdExceptions"; + aValue.Value <<= comphelper::containerToSequence(rLsdExceptions); + rLatentStyles.push_back(aValue); + } + + uno::Sequence<beans::PropertyValue> aLatentStyles( comphelper::containerToSequence(rLatentStyles) ); + + // We can put all latent style info directly to the document interop + // grab bag, as we can be sure that only a single style entry has + // latent style info. + uno::Reference<beans::XPropertySet> xPropertySet(m_pImpl->m_xTextDocument, uno::UNO_QUERY); + auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(xPropertySet->getPropertyValue("InteropGrabBag").get< uno::Sequence<beans::PropertyValue> >()); + beans::PropertyValue aValue; + aValue.Name = "latentStyles"; + aValue.Value <<= aLatentStyles; + aGrabBag.push_back(aValue); + xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(comphelper::containerToSequence(aGrabBag))); + } + + StyleSheetEntryPtr pEmptyEntry; + m_pImpl->m_pCurrentEntry = pEmptyEntry; +} +/*------------------------------------------------------------------------- + sorting helper + -----------------------------------------------------------------------*/ +namespace { + +class PropValVector +{ + std::vector<beans::PropertyValue> m_aValues; +public: + PropValVector(){} + + void Insert(const beans::PropertyValue& rVal); + uno::Sequence< uno::Any > getValues(); + uno::Sequence< OUString > getNames(); + const std::vector<beans::PropertyValue>& getProperties() const { return m_aValues; }; +}; + +} + +void PropValVector::Insert(const beans::PropertyValue& rVal) +{ + auto aIt = std::find_if(m_aValues.begin(), m_aValues.end(), + [&rVal](beans::PropertyValue& rPropVal) { return rPropVal.Name > rVal.Name; }); + if (aIt != m_aValues.end()) + { + m_aValues.insert( aIt, rVal ); + return; + } + m_aValues.push_back(rVal); +} + +uno::Sequence< uno::Any > PropValVector::getValues() +{ + std::vector<uno::Any> aRet; + std::transform(m_aValues.begin(), m_aValues.end(), std::back_inserter(aRet), [](const beans::PropertyValue& rValue) { return rValue.Value; }); + return comphelper::containerToSequence(aRet); +} + +uno::Sequence< OUString > PropValVector::getNames() +{ + std::vector<OUString> aRet; + std::transform(m_aValues.begin(), m_aValues.end(), std::back_inserter(aRet), [](const beans::PropertyValue& rValue) { return rValue.Name; }); + return comphelper::containerToSequence(aRet); +} + +void StyleSheetTable::ApplyNumberingStyleNameToParaStyles() +{ + try + { + uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< lang::XMultiServiceFactory > xDocFactory( m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xParaStyles; + xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xParaStyles; + + if ( !xParaStyles.is() ) + return; + + for ( const auto& pEntry : m_pImpl->m_aStyleSheetEntries ) + { + StyleSheetPropertyMap* pStyleSheetProperties = nullptr; + if ( pEntry->nStyleTypeCode == STYLE_TYPE_PARA && (pStyleSheetProperties = pEntry->pProperties.get()) ) + { + // ListId 0 means turn off numbering - to cancel inheritance - so make sure that can be set. + if (pStyleSheetProperties->GetListId() > -1) + { + uno::Reference< style::XStyle > xStyle; + xParaStyles->getByName( ConvertStyleName(pEntry->sStyleName) ) >>= xStyle; + + if ( !xStyle.is() ) + break; + + uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY_THROW ); + const OUString sNumberingStyleName = m_pImpl->m_rDMapper.GetListStyleName( pStyleSheetProperties->GetListId() ); + if ( !sNumberingStyleName.isEmpty() || !pStyleSheetProperties->GetListId() ) + xPropertySet->setPropertyValue( getPropertyName(PROP_NUMBERING_STYLE_NAME), uno::Any(sNumberingStyleName) ); + + // Word 2010+ (not Word 2003, and Word 2007 is completely broken) + // does something rather strange. It does not allow two paragraph styles + // to share the same listLevel on a numbering rule. + // Consider this style to just be body level if already used previously. + m_pImpl->m_rDMapper.ValidateListLevel(pEntry->sStyleIdentifierD); + } + } + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "Failed applying numbering style name to Paragraph styles"); + } +} + +/* Counteract the destructive tendencies of LibreOffice's Chapter Numbering + * + * Any assignment to Chapter Numbering will erase the numbering-like properties of inherited styles. + * So go through the list of styles and any that inherit from a Chapter Numbering style + * should have the Outline Level reapplied. + */ +void StyleSheetTable::ReApplyInheritedOutlineLevelFromChapterNumbering() +{ + try + { + uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier(m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW); + uno::Reference< lang::XMultiServiceFactory > xDocFactory(m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW); + uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xParaStyles; + xStyleFamilies->getByName(getPropertyName(PROP_PARAGRAPH_STYLES)) >>= xParaStyles; + + if (!xParaStyles.is()) + return; + + for (const auto& pEntry : m_pImpl->m_aStyleSheetEntries) + { + if (pEntry->nStyleTypeCode != STYLE_TYPE_PARA || pEntry->sBaseStyleIdentifier.isEmpty()) + continue; + + sal_Int16 nOutlineLevel = pEntry->pProperties->GetOutlineLevel(); + if (nOutlineLevel != -1) + continue; + + StyleSheetEntryPtr pParent = FindStyleSheetByISTD(pEntry->sBaseStyleIdentifier); + if (!pParent || !pParent->bAssignedAsChapterNumbering) + continue; + + nOutlineLevel = pParent->pProperties->GetOutlineLevel(); + assert(nOutlineLevel >= WW_OUTLINE_MIN && nOutlineLevel < WW_OUTLINE_MAX); + + // convert MS level to LO equivalent outline level + ++nOutlineLevel; + + uno::Reference< style::XStyle > xStyle; + xParaStyles->getByName(pEntry->sConvertedStyleName) >>= xStyle; + if ( !xStyle.is() ) + break; + + uno::Reference<beans::XPropertySet> xPropertySet( xStyle, uno::UNO_QUERY_THROW ); + xPropertySet->setPropertyValue(getPropertyName(PROP_OUTLINE_LEVEL), uno::Any(nOutlineLevel)); + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "Failed applying outlineLevel to Paragraph styles"); + } +} + +void StyleSheetTable::ApplyStyleSheets( const FontTablePtr& rFontTable ) +{ + try + { + uno::Reference< style::XStyleFamiliesSupplier > xStylesSupplier( m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< lang::XMultiServiceFactory > xDocFactory( m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameContainer> xCharStyles; + uno::Reference<container::XNameContainer> xParaStyles; + uno::Reference<container::XNameContainer> xNumberingStyles; + + xStyleFamilies->getByName(getPropertyName( PROP_CHARACTER_STYLES )) >>= xCharStyles; + xStyleFamilies->getByName(getPropertyName( PROP_PARAGRAPH_STYLES )) >>= xParaStyles; + xStyleFamilies->getByName("NumberingStyles") >>= xNumberingStyles; + if(xCharStyles.is() && xParaStyles.is()) + { + std::vector< ::std::pair<OUString, uno::Reference<style::XStyle>> > aMissingParent; + std::vector< ::std::pair<OUString, uno::Reference<style::XStyle>> > aMissingFollow; + std::vector<std::pair<OUString, uno::Reference<style::XStyle>>> aMissingLink; + std::vector<beans::PropertyValue> aTableStylesVec; + for( auto& pEntry : m_pImpl->m_aStyleSheetEntries ) + { + if( pEntry->nStyleTypeCode == STYLE_TYPE_UNKNOWN && !pEntry->sStyleName.isEmpty() ) + pEntry->nStyleTypeCode = STYLE_TYPE_PARA; // unspecified style types are considered paragraph styles + + if( pEntry->nStyleTypeCode == STYLE_TYPE_CHAR || pEntry->nStyleTypeCode == STYLE_TYPE_PARA || pEntry->nStyleTypeCode == STYLE_TYPE_LIST ) + { + bool bParaStyle = pEntry->nStyleTypeCode == STYLE_TYPE_PARA; + bool bCharStyle = pEntry->nStyleTypeCode == STYLE_TYPE_CHAR; + bool bListStyle = pEntry->nStyleTypeCode == STYLE_TYPE_LIST; + bool bInsert = false; + uno::Reference< container::XNameContainer > xStyles = bParaStyle ? xParaStyles : (bListStyle ? xNumberingStyles : xCharStyles); + uno::Reference< style::XStyle > xStyle; + const OUString sConvertedStyleName = ConvertStyleName( pEntry->sStyleName ); + + if(xStyles->hasByName( sConvertedStyleName )) + { + // When pasting, don't update existing styles. + if (!m_pImpl->m_bIsNewDoc) + { + continue; + } + xStyles->getByName( sConvertedStyleName ) >>= xStyle; + + { + StyleSheetTable_Impl::SetPropertiesToDefault(xStyle); + + // resolve import conflicts with built-in styles (only if defaults have been defined) + if ( m_pImpl->m_bHasImportedDefaultParaProps + && pEntry->sBaseStyleIdentifier.isEmpty() //imported style has no inheritance + && !xStyle->getParentStyle().isEmpty() ) //built-in style has a default inheritance + { + xStyle->setParentStyle( "" ); + } + } + } + else + { + bInsert = true; + xStyle.set(xDocFactory->createInstance( + bParaStyle ? + getPropertyName( PROP_SERVICE_PARA_STYLE ) : + (bListStyle ? OUString("com.sun.star.style.NumberingStyle") : getPropertyName( PROP_SERVICE_CHAR_STYLE ))), + uno::UNO_QUERY_THROW); + + // Numbering styles have to be inserted early, as e.g. the NumberingRules property is only available after insertion. + if (bListStyle) + { + xStyles->insertByName( sConvertedStyleName, uno::Any( xStyle ) ); + xStyle.set(xStyles->getByName(sConvertedStyleName), uno::UNO_QUERY_THROW); + + StyleSheetPropertyMap* pPropertyMap = pEntry->pProperties.get(); + if (pPropertyMap && pPropertyMap->GetListId() == -1) + { + // No properties? Word default is 'none', Writer one is 'arabic', handle this. + uno::Reference<beans::XPropertySet> xPropertySet(xStyle, uno::UNO_QUERY_THROW); + uno::Reference<container::XIndexReplace> xNumberingRules; + xPropertySet->getPropertyValue("NumberingRules") >>= xNumberingRules; + uno::Reference<container::XIndexAccess> xIndexAccess(xNumberingRules, uno::UNO_QUERY_THROW); + for (sal_Int32 i = 0; i < xIndexAccess->getCount(); ++i) + { + uno::Sequence< beans::PropertyValue > aLvlProps{ + comphelper::makePropertyValue( + "NumberingType", style::NumberingType::NUMBER_NONE) + }; + xNumberingRules->replaceByIndex(i, uno::Any(aLvlProps)); + xPropertySet->setPropertyValue("NumberingRules", uno::Any(xNumberingRules)); + } + } + } + } + if( !pEntry->sBaseStyleIdentifier.isEmpty() ) + { + try + { + //TODO: Handle cases where a paragraph <> character style relation is needed + StyleSheetEntryPtr pParent = FindStyleSheetByISTD( pEntry->sBaseStyleIdentifier ); + // Writer core doesn't support numbering styles having a parent style, it seems + if (pParent && !bListStyle) + { + const OUString sParentStyleName = ConvertStyleName( pParent->sStyleName ); + if ( !sParentStyleName.isEmpty() && !xStyles->hasByName( sParentStyleName ) ) + aMissingParent.emplace_back( sParentStyleName, xStyle ); + else + xStyle->setParentStyle( sParentStyleName ); + } + } + catch( const uno::RuntimeException& ) + { + OSL_FAIL( "Styles parent could not be set"); + } + } + else if( bParaStyle ) + { + // Paragraph styles that don't inherit from some parent need to apply the DocDefaults + pEntry->pProperties->InsertProps( m_pImpl->m_pDefaultParaProps, /*bOverwrite=*/false ); + + //now it's time to set the default parameters - for paragraph styles + //Fonts: Western first entry in font table + //CJK: second entry + //CTL: third entry, if it exists + + sal_uInt32 nFontCount = rFontTable->size(); + if( !m_pImpl->m_rDMapper.IsOOXMLImport() && nFontCount > 2 ) + { + uno::Any aTwoHundredFortyTwip(12.); + + // font size to 240 twip (12 pts) for all if not set + pEntry->pProperties->Insert(PROP_CHAR_HEIGHT, aTwoHundredFortyTwip, false); + + // western font not already set -> apply first font + const FontEntry::Pointer_t pWesternFontEntry(rFontTable->getFontEntry( 0 )); + OUString sWesternFontName = pWesternFontEntry->sFontName; + pEntry->pProperties->Insert(PROP_CHAR_FONT_NAME, uno::Any( sWesternFontName ), false); + + // CJK ... apply second font + const FontEntry::Pointer_t pCJKFontEntry(rFontTable->getFontEntry( 2 )); + pEntry->pProperties->Insert(PROP_CHAR_FONT_NAME_ASIAN, uno::Any( pCJKFontEntry->sFontName ), false); + pEntry->pProperties->Insert(PROP_CHAR_HEIGHT_ASIAN, aTwoHundredFortyTwip, false); + + // CTL ... apply third font, if available + if( nFontCount > 3 ) + { + const FontEntry::Pointer_t pCTLFontEntry(rFontTable->getFontEntry( 3 )); + pEntry->pProperties->Insert(PROP_CHAR_FONT_NAME_COMPLEX, uno::Any( pCTLFontEntry->sFontName ), false); + pEntry->pProperties->Insert(PROP_CHAR_HEIGHT_COMPLEX, aTwoHundredFortyTwip, false); + } + } + } + + auto aPropValues = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(pEntry->pProperties->GetPropertyValues()); + + if (bParaStyle || bCharStyle) + { + // delay adding LinkStyle property: all styles need to be created first + if (!pEntry->sLinkStyleIdentifier.isEmpty()) + { + StyleSheetEntryPtr pLinkStyle + = FindStyleSheetByISTD(pEntry->sLinkStyleIdentifier); + if (pLinkStyle && !pLinkStyle->sStyleName.isEmpty()) + aMissingLink.emplace_back(ConvertStyleName(pLinkStyle->sStyleName), + xStyle); + } + } + + if( bParaStyle ) + { + // delay adding FollowStyle property: all styles need to be created first + if ( !pEntry->sNextStyleIdentifier.isEmpty() ) + { + StyleSheetEntryPtr pFollowStyle = FindStyleSheetByISTD( pEntry->sNextStyleIdentifier ); + if ( pFollowStyle && !pFollowStyle->sStyleName.isEmpty() ) + aMissingFollow.emplace_back( ConvertStyleName( pFollowStyle->sStyleName ), xStyle ); + } + + // Set the outline levels + StyleSheetPropertyMap* pStyleSheetProperties = pEntry ? pEntry->pProperties.get() : nullptr; + + if ( pStyleSheetProperties ) + { + sal_Int16 nLvl = pStyleSheetProperties->GetOutlineLevel(); + // convert MS body Level (9) to LO body level (0) and equivalent outline levels + if (nLvl != -1) + { + if (nLvl == WW_OUTLINE_MAX) + nLvl = 0; + else + ++nLvl; + + beans::PropertyValue aLvlVal(getPropertyName(PROP_OUTLINE_LEVEL), 0, + uno::Any(nLvl), + beans::PropertyState_DIRECT_VALUE); + aPropValues.push_back(aLvlVal); + } + } + + uno::Reference< beans::XPropertyState >xState( xStyle, uno::UNO_QUERY_THROW ); + if( sConvertedStyleName == "Contents Heading" || + sConvertedStyleName == "User Index Heading" || + sConvertedStyleName == "Index Heading" ) + { + // remove Left/RightMargin values from TOX heading styles + //left margin is set to NULL by default + xState->setPropertyToDefault(getPropertyName( PROP_PARA_LEFT_MARGIN )); + } + else if ( sConvertedStyleName == "Text body" ) + xState->setPropertyToDefault(getPropertyName( PROP_PARA_BOTTOM_MARGIN )); + else if ( sConvertedStyleName == "Heading 1" || + sConvertedStyleName == "Heading 2" || + sConvertedStyleName == "Heading 3" || + sConvertedStyleName == "Heading 4" || + sConvertedStyleName == "Heading 5" || + sConvertedStyleName == "Heading 6" || + sConvertedStyleName == "Heading 7" || + sConvertedStyleName == "Heading 8" || + sConvertedStyleName == "Heading 9" ) + { + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_WEIGHT )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_WEIGHT_ASIAN )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_WEIGHT_COMPLEX )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_POSTURE )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_POSTURE_ASIAN )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_POSTURE_COMPLEX )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_PROP_HEIGHT )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_PROP_HEIGHT_ASIAN )); + xState->setPropertyToDefault(getPropertyName( PROP_CHAR_PROP_HEIGHT_COMPLEX)); + + } + } + + if ( !aPropValues.empty() ) + { + PropValVector aSortedPropVals; + for (const beans::PropertyValue& rValue : aPropValues) + { + // Don't add the style name properties + bool bIsParaStyleName = rValue.Name == "ParaStyleName"; + bool bIsCharStyleName = rValue.Name == "CharStyleName"; + if ( !bIsParaStyleName && !bIsCharStyleName ) + { + aSortedPropVals.Insert(rValue); + } + } + + try + { + uno::Reference< beans::XMultiPropertySet > xMultiPropertySet( xStyle, uno::UNO_QUERY_THROW); + try + { + xMultiPropertySet->setPropertyValues( aSortedPropVals.getNames(), aSortedPropVals.getValues() ); + } + catch ( const uno::Exception& ) + { + uno::Reference<beans::XPropertySet> xPropertySet(xStyle, uno::UNO_QUERY_THROW); + for ( const beans::PropertyValue& rValue : aSortedPropVals.getProperties() ) + { + try + { + xPropertySet->setPropertyValue( rValue.Name, rValue.Value ); + } + catch ( const uno::Exception& ) + { + SAL_WARN( "writerfilter", "StyleSheetTable::ApplyStyleSheets could not set property " << rValue.Name ); + } + } + } + // Duplicate MSWord's single footnote reference into Footnote Characters and Footnote anchor + if( pEntry->sStyleName.equalsIgnoreAsciiCase("footnote reference") + || pEntry->sStyleName.equalsIgnoreAsciiCase("endnote reference") ) + { + uno::Reference< style::XStyle > xCopyStyle; + if( pEntry->sStyleName.equalsIgnoreAsciiCase("footnote reference") ) + xStyles->getByName( "Footnote anchor" ) >>= xCopyStyle; + else + xStyles->getByName( "Endnote anchor" ) >>= xCopyStyle; + + xMultiPropertySet.set( xCopyStyle, uno::UNO_QUERY_THROW); + xMultiPropertySet->setPropertyValues( aSortedPropVals.getNames(), aSortedPropVals.getValues() ); + } + } + catch( const lang::WrappedTargetException& rWrapped) + { +#ifdef DBG_UTIL + OUString aMessage("StyleSheetTable::ApplyStyleSheets: Some style properties could not be set"); + beans::UnknownPropertyException aUnknownPropertyException; + + if (rWrapped.TargetException >>= aUnknownPropertyException) + aMessage += ": " + aUnknownPropertyException.Message; + + SAL_WARN("writerfilter", aMessage); +#else + (void) rWrapped; +#endif + } + catch( const uno::Exception& ) + { + OSL_FAIL( "Some style properties could not be set"); + } + } + // Numbering style got inserted earlier. + if(bInsert && !bListStyle) + { + const OUString sParentStyle = xStyle->getParentStyle(); + if( !sParentStyle.isEmpty() && !xStyles->hasByName( sParentStyle ) ) + aMissingParent.emplace_back( sParentStyle, xStyle ); + + xStyles->insertByName( sConvertedStyleName, uno::Any( xStyle) ); + } + + beans::PropertyValues aGrabBag = pEntry->GetInteropGrabBagSeq(); + uno::Reference<beans::XPropertySet> xPropertySet(xStyle, uno::UNO_QUERY); + if (aGrabBag.hasElements()) + { + xPropertySet->setPropertyValue("StyleInteropGrabBag", uno::Any(aGrabBag)); + } + + // Only paragraph styles support automatic updates. + if (pEntry->bAutoRedefine && bParaStyle) + xPropertySet->setPropertyValue("IsAutoUpdate", uno::Any(true)); + } + else if(pEntry->nStyleTypeCode == STYLE_TYPE_TABLE) + { + // If this is a table style, save its contents as-is for roundtrip purposes. + TableStyleSheetEntry* pTableEntry = static_cast<TableStyleSheetEntry *>(pEntry.get()); + aTableStylesVec.push_back(pTableEntry->GetInteropGrabBag()); + + // if DocDefaults exist, MS Word includes these in the table style definition. + pEntry->pProperties->InsertProps( m_pImpl->m_pDefaultCharProps, /*bOverwrite=*/false ); + pEntry->pProperties->InsertProps( m_pImpl->m_pDefaultParaProps, /*bOverwrite=*/false ); + } + } + + // Update the styles that were created before their parents or next-styles + for( auto const & iter : aMissingParent ) + { + iter.second->setParentStyle( iter.first ); + } + + for( auto const & iter : aMissingFollow ) + { + try + { + uno::Reference<beans::XPropertySet> xPropertySet(iter.second, uno::UNO_QUERY); + xPropertySet->setPropertyValue( "FollowStyle", uno::Any(iter.first) ); + } + catch( uno::Exception & ) {} + } + + // Update the styles that were created before their linked styles. + for (auto const& rLinked : aMissingLink) + { + try + { + uno::Reference<beans::XPropertySet> xPropertySet(rLinked.second, + uno::UNO_QUERY); + xPropertySet->setPropertyValue("LinkStyle", uno::Any(rLinked.first)); + } + catch (uno::Exception&) + { + TOOLS_WARN_EXCEPTION( + "writerfilter", + "StyleSheetTable::ApplyStyleSheets: failed to set LinkStyle"); + } + } + + if (!aTableStylesVec.empty()) + { + // If we had any table styles, add a new document-level InteropGrabBag entry for them. + uno::Reference<beans::XPropertySet> xPropertySet(m_pImpl->m_xTextDocument, uno::UNO_QUERY); + uno::Any aAny = xPropertySet->getPropertyValue("InteropGrabBag"); + auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aAny.get< uno::Sequence<beans::PropertyValue> >()); + beans::PropertyValue aValue; + aValue.Name = "tableStyles"; + aValue.Value <<= comphelper::containerToSequence(aTableStylesVec); + aGrabBag.push_back(aValue); + xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(comphelper::containerToSequence(aGrabBag))); + } + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "Styles could not be imported completely"); + } +} + + +StyleSheetEntryPtr StyleSheetTable::FindStyleSheetByISTD(const OUString& sIndex) +{ + auto findIt = m_pImpl->m_aStyleSheetEntriesMap.find(sIndex); + if (findIt != m_pImpl->m_aStyleSheetEntriesMap.end()) + return findIt->second; + return StyleSheetEntryPtr(); +} + + +StyleSheetEntryPtr StyleSheetTable::FindStyleSheetByConvertedStyleName(std::u16string_view sIndex) +{ + StyleSheetEntryPtr pRet; + for(const StyleSheetEntryPtr & rpEntry : m_pImpl->m_aStyleSheetEntries) + { + if( rpEntry->sConvertedStyleName == sIndex) + { + pRet = rpEntry; + break; + } + } + return pRet; +} + + +StyleSheetEntryPtr StyleSheetTable::FindDefaultParaStyle() +{ + return FindStyleSheetByISTD( m_pImpl->m_sDefaultParaStyleName ); +} + +const StyleSheetEntryPtr & StyleSheetTable::GetCurrentEntry() const +{ + return m_pImpl->m_pCurrentEntry; +} + +OUString StyleSheetTable::ConvertStyleName( const OUString& rWWName, bool bExtendedSearch) +{ + OUString sRet( rWWName ); + if( bExtendedSearch ) + { + //search for the rWWName in the IdentifierD of the existing styles and convert the sStyleName member + auto findIt = m_pImpl->m_aStyleSheetEntriesMap.find(rWWName); + if (findIt != m_pImpl->m_aStyleSheetEntriesMap.end()) + sRet = findIt->second->sStyleName; + } + + // create a map only once + static const std::map< OUString, OUString> StyleNameMap { + { "Normal", "Standard" }, + { "heading 1", "Heading 1" }, + { "heading 2", "Heading 2" }, + { "heading 3", "Heading 3" }, + { "heading 4", "Heading 4" }, + { "heading 5", "Heading 5" }, + { "heading 6", "Heading 6" }, + { "heading 7", "Heading 7" }, + { "heading 8", "Heading 8" }, + { "heading 9", "Heading 9" }, + { "Heading 1", "Heading 1" }, + { "Heading 2", "Heading 2" }, + { "Heading 3", "Heading 3" }, + { "Heading 4", "Heading 4" }, + { "Heading 5", "Heading 5" }, + { "Heading 6", "Heading 6" }, + { "Heading 7", "Heading 7" }, + { "Heading 8", "Heading 8" }, + { "Heading 9", "Heading 9" }, + { "Index 1", "Index 1" }, + { "Index 2", "Index 2" }, + { "Index 3", "Index 3" }, +// { "Index 4", "" }, +// { "Index 5", "" }, +// { "Index 6", "" }, +// { "Index 7", "" }, +// { "Index 8", "" }, +// { "Index 9", "" }, + { "TOC 1", "Contents 1" }, + { "TOC 2", "Contents 2" }, + { "TOC 3", "Contents 3" }, + { "TOC 4", "Contents 4" }, + { "TOC 5", "Contents 5" }, + { "TOC 6", "Contents 6" }, + { "TOC 7", "Contents 7" }, + { "TOC 8", "Contents 8" }, + { "TOC 9", "Contents 9" }, + { "TOC Heading", "Contents Heading" }, + { "TOCHeading", "Contents Heading" }, + { "toc 1", "Contents 1" }, + { "toc 2", "Contents 2" }, + { "toc 3", "Contents 3" }, + { "toc 4", "Contents 4" }, + { "toc 5", "Contents 5" }, + { "toc 6", "Contents 6" }, + { "toc 7", "Contents 7" }, + { "toc 8", "Contents 8" }, + { "toc 9", "Contents 9" }, + { "TOC1", "Contents 1" }, + { "TOC2", "Contents 2" }, + { "TOC3", "Contents 3" }, + { "TOC4", "Contents 4" }, + { "TOC5", "Contents 5" }, + { "TOC6", "Contents 6" }, + { "TOC7", "Contents 7" }, + { "TOC8", "Contents 8" }, + { "TOC9", "Contents 9" }, +// { "Normal Indent", "" }, + { "footnote text", "Footnote" }, + { "Footnote Text", "Footnote" }, +// { "Annotation Text", "" }, + { "Header", "Header" }, + { "header", "Header" }, + { "Footer", "Footer" }, + { "footer", "Footer" }, + { "Index Heading", "Index Heading" }, +// { "Caption", "" }, +// { "Table of Figures", "" }, + { "Envelope Address", "Addressee" }, + { "Envelope Return", "Sender" }, + { "footnote reference", "Footnote Symbol" }, + { "Footnote Reference", "Footnote Symbol" }, +// { "Annotation Reference", "" }, + { "Line Number", "Line numbering" }, + { "Page Number", "Page Number" }, + { "endnote reference", "Endnote Symbol" }, + { "Endnote Reference", "Endnote Symbol" }, + { "endnote text", "Endnote" }, + { "Endnote Text", "Endnote" }, +// { "Table of Authorities", "" }, +// { "Macro Text", "" }, +// { "TOA Heading", "" }, + { "List", "List" }, +// { "List 2", "" }, +// { "List 3", "" }, +// { "List 4", "" }, +// { "List 5", "" }, +// { "List Bullet", "" }, +// { "List Bullet 2", "" }, +// { "List Bullet 3", "" }, +// { "List Bullet 4", "" }, +// { "List Bullet 5", "" }, +// { "List Number", "" }, +// { "List Number 2", "" }, +// { "List Number 3", "" }, +// { "List Number 4", "" }, +// { "List Number 5", "" }, + { "Title", "Title" }, +// { "Closing", "" }, + { "Signature", "Signature" }, +// { "Default Paragraph Font", "" }, + { "DefaultParagraphFont", "Default Paragraph Font" }, + { "Body Text", "Text body" }, + { "BodyText", "Text body" }, + { "BodyTextIndentItalic", "Text body indent italic" }, + { "Body Text Indent", "Text body indent" }, + { "BodyTextIndent", "Text body indent" }, + { "BodyTextIndent2", "Text body indent2" }, +// { "List Continue", "" }, +// { "List Continue 2", "" }, +// { "List Continue 3", "" }, +// { "List Continue 4", "" }, +// { "List Continue 5", "" }, +// { "Message Header", "" }, + { "Subtitle", "Subtitle" }, +// { "Salutation", "" }, +// { "Date", "" }, + { "Body Text First Indent", "Body Text Indent" }, +// { "Body Text First Indent 2", "" }, +// { "Note Heading", "" }, +// { "Body Text 2", "" }, +// { "Body Text 3", "" }, +// { "Body Text Indent 2", "" }, +// { "Body Text Indent 3", "" }, +// { "Block Text", "" }, + { "Hyperlink", "Internet link" }, + { "FollowedHyperlink", "Visited Internet Link" }, + { "Emphasis", "Emphasis" }, +// { "Document Map", "" }, +// { "Plain Text", "" }, + { "NoList", "No List" }, + { "AbstractHeading", "Abstract Heading" }, + { "AbstractBody", "Abstract Body" }, + { "PageNumber", "page number" }, + { "TableNormal", "Normal Table" }, + { "DocumentMap", "Document Map" }, + }; + + // find style-name using map + if (const auto aIt = StyleNameMap.find(sRet); aIt != StyleNameMap.end()) + { + sRet = aIt->second; + } + else + { + // Style names which should not be used without a " (user)" suffix + static const o3tl::sorted_vector<OUString> ReservedStyleNames = [] { + o3tl::sorted_vector<OUString> set; + for (const auto& pair : StyleNameMap) + set.insert(pair.second); + return set; + }(); + // SwStyleNameMapper doc says: If the UI style name equals a + // programmatic name, then it must append " (user)" to the end. + if (ReservedStyleNames.find(sRet) != ReservedStyleNames.end()) + sRet += " (user)"; + } + + return sRet; +} + +void StyleSheetTable::applyDefaults(bool bParaProperties) +{ + try{ + + if (!m_pImpl->m_bIsNewDoc) + { + // tdf#72942: do not corrupts original styles in master document + // during inserting of text from second document + return; + } + + if(!m_pImpl->m_xTextDefaults.is()) + { + m_pImpl->m_xTextDefaults.set( + m_pImpl->m_rDMapper.GetTextFactory()->createInstance("com.sun.star.text.Defaults"), + uno::UNO_QUERY_THROW ); + } + + // WARNING: these defaults only take effect IF there is a DocDefaults style section. Normally there is, but not always. + if( bParaProperties && m_pImpl->m_pDefaultParaProps) + { + // tdf#87533 LO will have different defaults here, depending on the locale. Import with documented defaults + SetDefaultParaProps(PROP_WRITING_MODE, uno::Any(sal_Int16(text::WritingMode_LR_TB))); + SetDefaultParaProps(PROP_PARA_ADJUST, uno::Any(sal_Int16(style::ParagraphAdjust_LEFT))); + + // Widow/Orphan -> set both to two if not already set + uno::Any aTwo(sal_Int8(2)); + SetDefaultParaProps(PROP_PARA_WIDOWS, aTwo); + SetDefaultParaProps(PROP_PARA_ORPHANS, aTwo); + + uno::Reference<style::XStyleFamiliesSupplier> xStylesSupplier(m_pImpl->m_xTextDocument, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xParagraphStyles; + xStyleFamilies->getByName("ParagraphStyles") >>= xParagraphStyles; + uno::Reference<beans::XPropertySet> xDefault; + // This is the built-in default style that every style inherits from + xParagraphStyles->getByName("Paragraph style") >>= xDefault; + + const uno::Sequence< beans::PropertyValue > aPropValues = m_pImpl->m_pDefaultParaProps->GetPropertyValues(); + for( const auto& rPropValue : aPropValues ) + { + try + { + xDefault->setPropertyValue(rPropValue.Name, rPropValue.Value); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "setPropertyValue"); + } + } + } + if( !bParaProperties && m_pImpl->m_pDefaultCharProps ) + { + // tdf#108350: Earlier in DomainMapper for DOCX, Calibri/11pt was set to match MSWord 2007+, + // but that is valid only if DocDefaults_rPrDefault is omitted. + // Now that DocDefaults_rPrDefault is known, the defaults should be reset to Times New Roman/10pt. + if ( m_pImpl->m_rDMapper.IsOOXMLImport() ) + m_pImpl->m_xTextDefaults->setPropertyValue( getPropertyName(PROP_CHAR_FONT_NAME), css::uno::Any(OUString("Times New Roman")) ); + + const uno::Sequence< beans::PropertyValue > aPropValues = m_pImpl->m_pDefaultCharProps->GetPropertyValues(); + for( const auto& rPropValue : aPropValues ) + { + try + { + m_pImpl->m_xTextDefaults->setPropertyValue( rPropValue.Name, rPropValue.Value ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "exception"); + } + } + } + } + catch( const uno::Exception& ) + { + } +} + + +OUString StyleSheetTable::getOrCreateCharStyle( PropertyValueVector_t& rCharProperties, bool bAlwaysCreate ) +{ + //find out if any of the styles already has the required properties then return its name + OUString sListLabel = m_pImpl->HasListCharStyle(rCharProperties); + // Don't try to reuse an existing character style if requested. + if( !sListLabel.isEmpty() && !bAlwaysCreate) + return sListLabel; + + //create a new one otherwise + const uno::Reference< container::XNameContainer >& xCharStyles = m_pImpl->m_rDMapper.GetCharacterStyles(); + sListLabel = m_pImpl->m_rDMapper.GetUnusedCharacterStyleName(); + uno::Reference< lang::XMultiServiceFactory > xDocFactory( m_pImpl->m_xTextDocument, uno::UNO_QUERY_THROW ); + try + { + uno::Reference< style::XStyle > xStyle( xDocFactory->createInstance( + getPropertyName( PROP_SERVICE_CHAR_STYLE )), uno::UNO_QUERY_THROW); + uno::Reference< beans::XPropertySet > xStyleProps(xStyle, uno::UNO_QUERY_THROW ); + for( const auto& rCharProp : rCharProperties) + { + try + { + xStyleProps->setPropertyValue( rCharProp.Name, rCharProp.Value ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "StyleSheetTable::getOrCreateCharStyle - Style::setPropertyValue"); + } + } + xCharStyles->insertByName( sListLabel, uno::Any( xStyle) ); + m_pImpl->m_aListCharStylePropertyVector.emplace_back( sListLabel, std::vector(rCharProperties) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "StyleSheetTable::getOrCreateCharStyle"); + } + + return sListLabel; +} + +}//namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/StyleSheetTable.hxx b/writerfilter/source/dmapper/StyleSheetTable.hxx new file mode 100644 index 000000000..5dcf84b78 --- /dev/null +++ b/writerfilter/source/dmapper/StyleSheetTable.hxx @@ -0,0 +1,150 @@ +/* -*- 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 . + */ +#pragma once + +#include <memory> +#include "TblStylePrHandler.hxx" + +#include "DomainMapper.hxx" +#include <com/sun/star/beans/PropertyValues.hpp> +#include "PropertyMap.hxx" +#include "FontTable.hxx" +#include "LoggedResources.hxx" + +namespace com::sun::star::text { class XTextDocument; } + + +namespace writerfilter::dmapper +{ + + +enum StyleType +{ + STYLE_TYPE_UNKNOWN, + STYLE_TYPE_PARA, + STYLE_TYPE_CHAR, + STYLE_TYPE_TABLE, + STYLE_TYPE_LIST +}; +class StyleSheetTable; +typedef tools::SvRef<StyleSheetTable> StyleSheetTablePtr; + +struct StyleSheetTable_Impl; +class StyleSheetEntry : public virtual SvRefBase +{ + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; +public: + OUString sStyleIdentifierD; // WW8 name + bool bIsDefaultStyle; + bool bAssignedAsChapterNumbering; + bool bInvalidHeight; + bool bHasUPE; //universal property expansion + StyleType nStyleTypeCode; //sgc + OUString sBaseStyleIdentifier; + OUString sNextStyleIdentifier; + OUString sLinkStyleIdentifier; + OUString sStyleName; + const tools::SvRef<StyleSheetPropertyMap> pProperties; + OUString sConvertedStyleName; + std::vector<css::beans::PropertyValue> aLatentStyles; ///< Attributes of latentStyles + std::vector<css::beans::PropertyValue> aLsdExceptions; ///< List of lsdException attribute lists + bool bAutoRedefine; ///< Writer calls this auto-update. + + void AppendInteropGrabBag(const css::beans::PropertyValue& rValue); + css::beans::PropertyValue GetInteropGrabBag(); ///< Used for table styles, has a name. + css::beans::PropertyValues GetInteropGrabBagSeq() const; ///< Used for existing styles, just a list of properties. + + // Get all properties, merged with the all of the parent's properties + PropertyMapPtr GetMergedInheritedProperties(const StyleSheetTablePtr& pStyleSheetTable); + + StyleSheetEntry(); + virtual ~StyleSheetEntry() override; +}; + +typedef tools::SvRef<StyleSheetEntry> StyleSheetEntryPtr; + +class DomainMapper; +class StyleSheetTable : + public LoggedProperties, + public LoggedTable +{ + std::unique_ptr<StyleSheetTable_Impl> m_pImpl; + +public: + StyleSheetTable(DomainMapper& rDMapper, css::uno::Reference<css::text::XTextDocument> const& xTextDocument, bool bIsNewDoc); + virtual ~StyleSheetTable() override; + + void ReApplyInheritedOutlineLevelFromChapterNumbering(); + void ApplyNumberingStyleNameToParaStyles(); + void ApplyStyleSheets( const FontTablePtr& rFontTable ); + StyleSheetEntryPtr FindStyleSheetByISTD(const OUString& sIndex); + StyleSheetEntryPtr FindStyleSheetByConvertedStyleName(std::u16string_view rIndex); + StyleSheetEntryPtr FindDefaultParaStyle(); + + OUString ConvertStyleName( const OUString& rWWName, bool bExtendedSearch = false ); + + OUString getOrCreateCharStyle( PropertyValueVector_t& rCharProperties, bool bAlwaysCreate ); + + void SetDefaultParaProps(PropertyIds eId, const css::uno::Any& rAny); + PropertyMapPtr const & GetDefaultParaProps() const; + /// Returns the default character properties. + PropertyMapPtr const & GetDefaultCharProps() const; + + const StyleSheetEntryPtr & GetCurrentEntry() const; + +private: + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + + void applyDefaults(bool bParaProperties); +}; + + +class TableStyleSheetEntry : + public StyleSheetEntry +{ +public: + // Adds a new tblStylePr to the table style entry. This method + // fixes some possible properties conflicts, like borders ones. + void AddTblStylePr( TblStyleType nType, const PropertyMapPtr& pProps ); + + // Gets all the properties + // + corresponding to the mask, + // + from the parent styles + + // @param mask mask describing which properties to return + PropertyMapPtr GetProperties( sal_Int32 nMask); + + TableStyleSheetEntry( StyleSheetEntry const & aEntry ); + virtual ~TableStyleSheetEntry( ) override; + +private: + typedef std::map<TblStyleType, PropertyMapPtr> TblStylePrs; + TblStylePrs m_aStyles; + PropertyMapPtr GetLocalPropertiesFromMask( sal_Int32 nMask ); +}; + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TDefTableHandler.cxx b/writerfilter/source/dmapper/TDefTableHandler.cxx new file mode 100644 index 000000000..c47ac7db8 --- /dev/null +++ b/writerfilter/source/dmapper/TDefTableHandler.cxx @@ -0,0 +1,458 @@ +/* -*- 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 "TDefTableHandler.hxx" +#include "PropertyMap.hxx" +#include "ConversionHelper.hxx" +#include <ooxml/resourceids.hxx> +#include <filter/msfilter/util.hxx> +#include <tools/color.hxx> +#include <com/sun/star/table/BorderLine2.hpp> +#include <comphelper/sequence.hxx> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; + + +TDefTableHandler::TDefTableHandler() : +LoggedProperties("TDefTableHandler"), +m_nLineWidth(0), +m_nLineType(0), +m_nLineColor(0) +{ +} + + +TDefTableHandler::~TDefTableHandler() +{ +} + +OUString TDefTableHandler::getBorderTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_Value_ST_Border_nil: return "nil"; + case NS_ooxml::LN_Value_ST_Border_none: return "none"; + case NS_ooxml::LN_Value_ST_Border_single: return "single"; + case NS_ooxml::LN_Value_ST_Border_thick: return "thick"; + case NS_ooxml::LN_Value_ST_Border_double: return "double"; + case NS_ooxml::LN_Value_ST_Border_dotted: return "dotted"; + case NS_ooxml::LN_Value_ST_Border_dashed: return "dashed"; + case NS_ooxml::LN_Value_ST_Border_dotDash: return "dotDash"; + case NS_ooxml::LN_Value_ST_Border_dotDotDash: return "dotDotDash"; + case NS_ooxml::LN_Value_ST_Border_triple: return "triple"; + case NS_ooxml::LN_Value_ST_Border_thinThickSmallGap: return "thinThickSmallGap"; + case NS_ooxml::LN_Value_ST_Border_thickThinSmallGap: return "thickThinSmallGap"; + case NS_ooxml::LN_Value_ST_Border_thinThickThinSmallGap: return "thinThickThinSmallGap"; + case NS_ooxml::LN_Value_ST_Border_thinThickMediumGap: return "thinThickMediumGap"; + case NS_ooxml::LN_Value_ST_Border_thickThinMediumGap: return "thickThinMediumGap"; + case NS_ooxml::LN_Value_ST_Border_thinThickThinMediumGap: return "thinThickThinMediumGap"; + case NS_ooxml::LN_Value_ST_Border_thinThickLargeGap: return "thinThickLargeGap"; + case NS_ooxml::LN_Value_ST_Border_thickThinLargeGap: return "thickThinLargeGap"; + case NS_ooxml::LN_Value_ST_Border_thinThickThinLargeGap: return "thinThickThinLargeGap"; + case NS_ooxml::LN_Value_ST_Border_wave: return "wave"; + case NS_ooxml::LN_Value_ST_Border_doubleWave: return "doubleWave"; + case NS_ooxml::LN_Value_ST_Border_dashSmallGap: return "dashSmallGap"; + case NS_ooxml::LN_Value_ST_Border_dashDotStroked: return "dashDotStroked"; + case NS_ooxml::LN_Value_ST_Border_threeDEmboss: return "threeDEmboss"; + case NS_ooxml::LN_Value_ST_Border_threeDEngrave: return "threeDEngrave"; + case NS_ooxml::LN_Value_ST_Border_outset: return "outset"; + case NS_ooxml::LN_Value_ST_Border_inset: return "inset"; + case NS_ooxml::LN_Value_ST_Border_apples: return "apples"; + case NS_ooxml::LN_Value_ST_Border_archedScallops: return "archedScallops"; + case NS_ooxml::LN_Value_ST_Border_babyPacifier: return "babyPacifier"; + case NS_ooxml::LN_Value_ST_Border_babyRattle: return "babyRattle"; + case NS_ooxml::LN_Value_ST_Border_balloons3Colors: return "balloons3Colors"; + case NS_ooxml::LN_Value_ST_Border_balloonsHotAir: return "balloonsHotAir"; + case NS_ooxml::LN_Value_ST_Border_basicBlackDashes: return "basicBlackDashes"; + case NS_ooxml::LN_Value_ST_Border_basicBlackDots: return "basicBlackDots"; + case NS_ooxml::LN_Value_ST_Border_basicBlackSquares: return "basicBlackSquares"; + case NS_ooxml::LN_Value_ST_Border_basicThinLines: return "basicThinLines"; + case NS_ooxml::LN_Value_ST_Border_basicWhiteDashes: return "basicWhiteDashes"; + case NS_ooxml::LN_Value_ST_Border_basicWhiteDots: return "basicWhiteDots"; + case NS_ooxml::LN_Value_ST_Border_basicWhiteSquares: return "basicWhiteSquares"; + case NS_ooxml::LN_Value_ST_Border_basicWideInline: return "basicWideInline"; + case NS_ooxml::LN_Value_ST_Border_basicWideMidline: return "basicWideMidline"; + case NS_ooxml::LN_Value_ST_Border_basicWideOutline: return "basicWideOutline"; + case NS_ooxml::LN_Value_ST_Border_bats: return "bats"; + case NS_ooxml::LN_Value_ST_Border_birds: return "birds"; + case NS_ooxml::LN_Value_ST_Border_birdsFlight: return "birdsFlight"; + case NS_ooxml::LN_Value_ST_Border_cabins: return "cabins"; + case NS_ooxml::LN_Value_ST_Border_cakeSlice: return "cakeSlice"; + case NS_ooxml::LN_Value_ST_Border_candyCorn: return "candyCorn"; + case NS_ooxml::LN_Value_ST_Border_celticKnotwork: return "celticKnotwork"; + case NS_ooxml::LN_Value_ST_Border_certificateBanner: return "certificateBanner"; + case NS_ooxml::LN_Value_ST_Border_chainLink: return "chainLink"; + case NS_ooxml::LN_Value_ST_Border_champagneBottle: return "champagneBottle"; + case NS_ooxml::LN_Value_ST_Border_checkedBarBlack: return "checkedBarBlack"; + case NS_ooxml::LN_Value_ST_Border_checkedBarColor: return "checkedBarColor"; + case NS_ooxml::LN_Value_ST_Border_checkered: return "checkered"; + case NS_ooxml::LN_Value_ST_Border_christmasTree: return "christmasTree"; + case NS_ooxml::LN_Value_ST_Border_circlesLines: return "circlesLines"; + case NS_ooxml::LN_Value_ST_Border_circlesRectangles: return "circlesRectangles"; + case NS_ooxml::LN_Value_ST_Border_classicalWave: return "classicalWave"; + case NS_ooxml::LN_Value_ST_Border_clocks: return "clocks"; + case NS_ooxml::LN_Value_ST_Border_compass: return "compass"; + case NS_ooxml::LN_Value_ST_Border_confetti: return "confetti"; + case NS_ooxml::LN_Value_ST_Border_confettiGrays: return "confettiGrays"; + case NS_ooxml::LN_Value_ST_Border_confettiOutline: return "confettiOutline"; + case NS_ooxml::LN_Value_ST_Border_confettiStreamers: return "confettiStreamers"; + case NS_ooxml::LN_Value_ST_Border_confettiWhite: return "confettiWhite"; + case NS_ooxml::LN_Value_ST_Border_cornerTriangles: return "cornerTriangles"; + case NS_ooxml::LN_Value_ST_Border_couponCutoutDashes: return "couponCutoutDashes"; + case NS_ooxml::LN_Value_ST_Border_couponCutoutDots: return "couponCutoutDots"; + case NS_ooxml::LN_Value_ST_Border_crazyMaze: return "crazyMaze"; + case NS_ooxml::LN_Value_ST_Border_creaturesButterfly: return "creaturesButterfly"; + case NS_ooxml::LN_Value_ST_Border_creaturesFish: return "creaturesFish"; + case NS_ooxml::LN_Value_ST_Border_creaturesInsects: return "creaturesInsects"; + case NS_ooxml::LN_Value_ST_Border_creaturesLadyBug: return "creaturesLadyBug"; + case NS_ooxml::LN_Value_ST_Border_crossStitch: return "crossStitch"; + case NS_ooxml::LN_Value_ST_Border_cup: return "cup"; + case NS_ooxml::LN_Value_ST_Border_decoArch: return "decoArch"; + case NS_ooxml::LN_Value_ST_Border_decoArchColor: return "decoArchColor"; + case NS_ooxml::LN_Value_ST_Border_decoBlocks: return "decoBlocks"; + case NS_ooxml::LN_Value_ST_Border_diamondsGray: return "diamondsGray"; + case NS_ooxml::LN_Value_ST_Border_doubleD: return "doubleD"; + case NS_ooxml::LN_Value_ST_Border_doubleDiamonds: return "doubleDiamonds"; + case NS_ooxml::LN_Value_ST_Border_earth1: return "earth1"; + case NS_ooxml::LN_Value_ST_Border_earth2: return "earth2"; + case NS_ooxml::LN_Value_ST_Border_eclipsingSquares1: return "eclipsingSquares1"; + case NS_ooxml::LN_Value_ST_Border_eclipsingSquares2: return "eclipsingSquares2"; + case NS_ooxml::LN_Value_ST_Border_eggsBlack: return "eggsBlack"; + case NS_ooxml::LN_Value_ST_Border_fans: return "fans"; + case NS_ooxml::LN_Value_ST_Border_film: return "film"; + case NS_ooxml::LN_Value_ST_Border_firecrackers: return "firecrackers"; + case NS_ooxml::LN_Value_ST_Border_flowersBlockPrint: return "flowersBlockPrint"; + case NS_ooxml::LN_Value_ST_Border_flowersDaisies: return "flowersDaisies"; + case NS_ooxml::LN_Value_ST_Border_flowersModern1: return "flowersModern1"; + case NS_ooxml::LN_Value_ST_Border_flowersModern2: return "flowersModern2"; + case NS_ooxml::LN_Value_ST_Border_flowersPansy: return "flowersPansy"; + case NS_ooxml::LN_Value_ST_Border_flowersRedRose: return "flowersRedRose"; + case NS_ooxml::LN_Value_ST_Border_flowersRoses: return "flowersRoses"; + case NS_ooxml::LN_Value_ST_Border_flowersTeacup: return "flowersTeacup"; + case NS_ooxml::LN_Value_ST_Border_flowersTiny: return "flowersTiny"; + case NS_ooxml::LN_Value_ST_Border_gems: return "gems"; + case NS_ooxml::LN_Value_ST_Border_gingerbreadMan: return "gingerbreadMan"; + case NS_ooxml::LN_Value_ST_Border_gradient: return "gradient"; + case NS_ooxml::LN_Value_ST_Border_handmade1: return "handmade1"; + case NS_ooxml::LN_Value_ST_Border_handmade2: return "handmade2"; + case NS_ooxml::LN_Value_ST_Border_heartBalloon: return "heartBalloon"; + case NS_ooxml::LN_Value_ST_Border_heartGray: return "heartGray"; + case NS_ooxml::LN_Value_ST_Border_hearts: return "hearts"; + case NS_ooxml::LN_Value_ST_Border_heebieJeebies: return "heebieJeebies"; + case NS_ooxml::LN_Value_ST_Border_holly: return "holly"; + case NS_ooxml::LN_Value_ST_Border_houseFunky: return "houseFunky"; + case NS_ooxml::LN_Value_ST_Border_hypnotic: return "hypnotic"; + case NS_ooxml::LN_Value_ST_Border_iceCreamCones: return "iceCreamCones"; + case NS_ooxml::LN_Value_ST_Border_lightBulb: return "lightBulb"; + case NS_ooxml::LN_Value_ST_Border_lightning1: return "lightning1"; + case NS_ooxml::LN_Value_ST_Border_lightning2: return "lightning2"; + case NS_ooxml::LN_Value_ST_Border_mapPins: return "mapPins"; + case NS_ooxml::LN_Value_ST_Border_mapleLeaf: return "mapleLeaf"; + case NS_ooxml::LN_Value_ST_Border_mapleMuffins: return "mapleMuffins"; + case NS_ooxml::LN_Value_ST_Border_marquee: return "marquee"; + case NS_ooxml::LN_Value_ST_Border_marqueeToothed: return "marqueeToothed"; + case NS_ooxml::LN_Value_ST_Border_moons: return "moons"; + case NS_ooxml::LN_Value_ST_Border_mosaic: return "mosaic"; + case NS_ooxml::LN_Value_ST_Border_musicNotes: return "musicNotes"; + case NS_ooxml::LN_Value_ST_Border_northwest: return "northwest"; + case NS_ooxml::LN_Value_ST_Border_ovals: return "ovals"; + case NS_ooxml::LN_Value_ST_Border_packages: return "packages"; + case NS_ooxml::LN_Value_ST_Border_palmsBlack: return "palmsBlack"; + case NS_ooxml::LN_Value_ST_Border_palmsColor: return "palmsColor"; + case NS_ooxml::LN_Value_ST_Border_paperClips: return "paperClips"; + case NS_ooxml::LN_Value_ST_Border_papyrus: return "papyrus"; + case NS_ooxml::LN_Value_ST_Border_partyFavor: return "partyFavor"; + case NS_ooxml::LN_Value_ST_Border_partyGlass: return "partyGlass"; + case NS_ooxml::LN_Value_ST_Border_pencils: return "pencils"; + case NS_ooxml::LN_Value_ST_Border_people: return "people"; + case NS_ooxml::LN_Value_ST_Border_peopleWaving: return "peopleWaving"; + case NS_ooxml::LN_Value_ST_Border_peopleHats: return "peopleHats"; + case NS_ooxml::LN_Value_ST_Border_poinsettias: return "poinsettias"; + case NS_ooxml::LN_Value_ST_Border_postageStamp: return "postageStamp"; + case NS_ooxml::LN_Value_ST_Border_pumpkin1: return "pumpkin1"; + case NS_ooxml::LN_Value_ST_Border_pushPinNote2: return "pushPinNote2"; + case NS_ooxml::LN_Value_ST_Border_pushPinNote1: return "pushPinNote1"; + case NS_ooxml::LN_Value_ST_Border_pyramids: return "pyramids"; + case NS_ooxml::LN_Value_ST_Border_pyramidsAbove: return "pyramidsAbove"; + case NS_ooxml::LN_Value_ST_Border_quadrants: return "quadrants"; + case NS_ooxml::LN_Value_ST_Border_rings: return "rings"; + case NS_ooxml::LN_Value_ST_Border_safari: return "safari"; + case NS_ooxml::LN_Value_ST_Border_sawtooth: return "sawtooth"; + case NS_ooxml::LN_Value_ST_Border_sawtoothGray: return "sawtoothGray"; + case NS_ooxml::LN_Value_ST_Border_scaredCat: return "scaredCat"; + case NS_ooxml::LN_Value_ST_Border_seattle: return "seattle"; + case NS_ooxml::LN_Value_ST_Border_shadowedSquares: return "shadowedSquares"; + case NS_ooxml::LN_Value_ST_Border_sharksTeeth: return "sharksTeeth"; + case NS_ooxml::LN_Value_ST_Border_shorebirdTracks: return "shorebirdTracks"; + case NS_ooxml::LN_Value_ST_Border_skyrocket: return "skyrocket"; + case NS_ooxml::LN_Value_ST_Border_snowflakeFancy: return "snowflakeFancy"; + case NS_ooxml::LN_Value_ST_Border_snowflakes: return "snowflakes"; + case NS_ooxml::LN_Value_ST_Border_sombrero: return "sombrero"; + case NS_ooxml::LN_Value_ST_Border_southwest: return "southwest"; + case NS_ooxml::LN_Value_ST_Border_stars: return "stars"; + case NS_ooxml::LN_Value_ST_Border_starsTop: return "starsTop"; + case NS_ooxml::LN_Value_ST_Border_stars3d: return "stars3d"; + case NS_ooxml::LN_Value_ST_Border_starsBlack: return "starsBlack"; + case NS_ooxml::LN_Value_ST_Border_starsShadowed: return "starsShadowed"; + case NS_ooxml::LN_Value_ST_Border_sun: return "sun"; + case NS_ooxml::LN_Value_ST_Border_swirligig: return "swirligig"; + case NS_ooxml::LN_Value_ST_Border_tornPaper: return "tornPaper"; + case NS_ooxml::LN_Value_ST_Border_tornPaperBlack: return "tornPaperBlack"; + case NS_ooxml::LN_Value_ST_Border_trees: return "trees"; + case NS_ooxml::LN_Value_ST_Border_triangleParty: return "triangleParty"; + case NS_ooxml::LN_Value_ST_Border_triangles: return "triangles"; + case NS_ooxml::LN_Value_ST_Border_tribal1: return "tribal1"; + case NS_ooxml::LN_Value_ST_Border_tribal2: return "tribal2"; + case NS_ooxml::LN_Value_ST_Border_tribal3: return "tribal3"; + case NS_ooxml::LN_Value_ST_Border_tribal4: return "tribal4"; + case NS_ooxml::LN_Value_ST_Border_tribal5: return "tribal5"; + case NS_ooxml::LN_Value_ST_Border_tribal6: return "tribal6"; + case NS_ooxml::LN_Value_ST_Border_twistedLines1: return "twistedLines1"; + case NS_ooxml::LN_Value_ST_Border_twistedLines2: return "twistedLines2"; + case NS_ooxml::LN_Value_ST_Border_vine: return "vine"; + case NS_ooxml::LN_Value_ST_Border_waveline: return "waveline"; + case NS_ooxml::LN_Value_ST_Border_weavingAngles: return "weavingAngles"; + case NS_ooxml::LN_Value_ST_Border_weavingBraid: return "weavingBraid"; + case NS_ooxml::LN_Value_ST_Border_weavingRibbon: return "weavingRibbon"; + case NS_ooxml::LN_Value_ST_Border_weavingStrips: return "weavingStrips"; + case NS_ooxml::LN_Value_ST_Border_whiteFlowers: return "whiteFlowers"; + case NS_ooxml::LN_Value_ST_Border_woodwork: return "woodwork"; + case NS_ooxml::LN_Value_ST_Border_xIllusions: return "xIllusions"; + case NS_ooxml::LN_Value_ST_Border_zanyTriangles: return "zanyTriangles"; + case NS_ooxml::LN_Value_ST_Border_zigZag: return "zigZag"; + case NS_ooxml::LN_Value_ST_Border_zigZagStitch: return "zigZagStitch"; + default: break; + } + return OUString(); +} + +OUString TDefTableHandler::getThemeColorTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_Value_St_ThemeColor_dark1: return "dark1"; + case NS_ooxml::LN_Value_St_ThemeColor_light1: return "light1"; + case NS_ooxml::LN_Value_St_ThemeColor_dark2: return "dark2"; + case NS_ooxml::LN_Value_St_ThemeColor_light2: return "light2"; + case NS_ooxml::LN_Value_St_ThemeColor_accent1: return "accent1"; + case NS_ooxml::LN_Value_St_ThemeColor_accent2: return "accent2"; + case NS_ooxml::LN_Value_St_ThemeColor_accent3: return "accent3"; + case NS_ooxml::LN_Value_St_ThemeColor_accent4: return "accent4"; + case NS_ooxml::LN_Value_St_ThemeColor_accent5: return "accent5"; + case NS_ooxml::LN_Value_St_ThemeColor_accent6: return "accent6"; + case NS_ooxml::LN_Value_St_ThemeColor_hyperlink: return "hyperlink"; + case NS_ooxml::LN_Value_St_ThemeColor_followedHyperlink: return "followedHyperlink"; + case NS_ooxml::LN_Value_St_ThemeColor_none: return "none"; + case NS_ooxml::LN_Value_St_ThemeColor_background1: return "background1"; + case NS_ooxml::LN_Value_St_ThemeColor_text1: return "text1"; + case NS_ooxml::LN_Value_St_ThemeColor_background2: return "background2"; + case NS_ooxml::LN_Value_St_ThemeColor_text2: return "text2"; + default: break; + } + return OUString(); +} + +void TDefTableHandler::lcl_attribute(Id rName, Value & rVal) +{ + sal_Int32 nIntValue = rVal.getInt(); + switch( rName ) + { + case NS_ooxml::LN_CT_Border_sz: + // width of a single line in 1/8 pt, max of 32 pt -> twip * 5 / 2. + m_nLineWidth = nIntValue * 5 / 2; + appendGrabBag("sz", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_val: + m_nLineType = nIntValue; + appendGrabBag("val", TDefTableHandler::getBorderTypeString(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_color: + appendGrabBag("color", msfilter::util::ConvertColorOU(Color(ColorTransparency,nIntValue))); + m_nLineColor = nIntValue; + break; + case NS_ooxml::LN_CT_Border_space: + appendGrabBag("space", OUString::number(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_shadow: + //if 1 then line has shadow - unsupported + case NS_ooxml::LN_CT_Border_frame: + // ignored + break; + case NS_ooxml::LN_CT_Border_themeColor: + appendGrabBag("themeColor", TDefTableHandler::getThemeColorTypeString(nIntValue)); + break; + case NS_ooxml::LN_CT_Border_themeTint: + case NS_ooxml::LN_CT_Border_themeShade: + // ignored + break; + default: + OSL_FAIL("unknown attribute"); + } +} + + +void TDefTableHandler::localResolve(Id rName, const writerfilter::Reference<Properties>::Pointer_t& pProperties) +{ + if( !pProperties ) + return; + + m_nLineWidth = m_nLineType = m_nLineColor = 0; + std::vector<beans::PropertyValue> aSavedGrabBag; + if (!m_aInteropGrabBagName.isEmpty()) + { + aSavedGrabBag = m_aInteropGrabBag; + m_aInteropGrabBag.clear(); + } + pProperties->resolve( *this ); + table::BorderLine2 aBorderLine; + ConversionHelper::MakeBorderLine(m_nLineWidth, m_nLineType, m_nLineColor, aBorderLine, /*bIsOOXML=*/true); + const bool rtl = false; // TODO + switch( rName ) + { + case NS_ooxml::LN_CT_TcBorders_top: + m_aTopBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("top")); + break; + case NS_ooxml::LN_CT_TcBorders_start: + if( rtl ) + m_aRightBorderLines.push_back(aBorderLine); + else + m_aLeftBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("start")); + break; + case NS_ooxml::LN_CT_TcBorders_left: + m_aLeftBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("left")); + break; + case NS_ooxml::LN_CT_TcBorders_bottom: + m_aBottomBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("bottom")); + break; + case NS_ooxml::LN_CT_TcBorders_end: + if( rtl ) + m_aLeftBorderLines.push_back(aBorderLine); + else + m_aRightBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("end")); + break; + case NS_ooxml::LN_CT_TcBorders_right: + m_aRightBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("right")); + break; + case NS_ooxml::LN_CT_TcBorders_insideH: + m_aInsideHBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("insideH")); + break; + case NS_ooxml::LN_CT_TcBorders_insideV: + m_aInsideVBorderLines.push_back(aBorderLine); + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("insideV")); + break; + case NS_ooxml::LN_CT_TcBorders_tl2br: + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("tl2br")); + break; + case NS_ooxml::LN_CT_TcBorders_tr2bl: + if (!m_aInteropGrabBagName.isEmpty()) + aSavedGrabBag.push_back(getInteropGrabBag("tr2bl")); + break; + default:; + } + if (!m_aInteropGrabBagName.isEmpty()) + m_aInteropGrabBag = aSavedGrabBag; +} + + +void TDefTableHandler::lcl_sprm(Sprm & rSprm) +{ + switch( rSprm.getId() ) + { + case NS_ooxml::LN_CT_TcBorders_top: + case NS_ooxml::LN_CT_TcBorders_left: + case NS_ooxml::LN_CT_TcBorders_start: + case NS_ooxml::LN_CT_TcBorders_bottom: + case NS_ooxml::LN_CT_TcBorders_right: + case NS_ooxml::LN_CT_TcBorders_end: + case NS_ooxml::LN_CT_TcBorders_insideH: + case NS_ooxml::LN_CT_TcBorders_insideV: + case NS_ooxml::LN_CT_TcBorders_tl2br: + case NS_ooxml::LN_CT_TcBorders_tr2bl: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + localResolve( rSprm.getId(), pProperties ); + } + break; + default:; + } +} + +void TDefTableHandler::fillCellProperties( const ::tools::SvRef< TablePropertyMap >& pCellProperties ) const +{ + if( !m_aTopBorderLines.empty() ) + pCellProperties->Insert( PROP_TOP_BORDER, uno::Any( m_aTopBorderLines[0] ) ); + if( !m_aLeftBorderLines.empty() ) + pCellProperties->Insert( PROP_LEFT_BORDER, uno::Any( m_aLeftBorderLines[0] ) ); + if( !m_aBottomBorderLines.empty() ) + pCellProperties->Insert( PROP_BOTTOM_BORDER, uno::Any( m_aBottomBorderLines[0] ) ); + if( !m_aRightBorderLines.empty() ) + pCellProperties->Insert( PROP_RIGHT_BORDER, uno::Any( m_aRightBorderLines[0] ) ); + if( !m_aInsideHBorderLines.empty() ) + pCellProperties->Insert( META_PROP_HORIZONTAL_BORDER, uno::Any( m_aInsideHBorderLines[0] ) ); + if( !m_aInsideVBorderLines.empty() ) + pCellProperties->Insert( META_PROP_VERTICAL_BORDER, uno::Any( m_aInsideVBorderLines[0] ) ); +} + + +void TDefTableHandler::enableInteropGrabBag(const OUString& aName) +{ + m_aInteropGrabBagName = aName; +} + +beans::PropertyValue TDefTableHandler::getInteropGrabBag(const OUString& aName) +{ + beans::PropertyValue aRet; + if (aName.isEmpty()) + aRet.Name = m_aInteropGrabBagName; + else + aRet.Name = aName; + + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + m_aInteropGrabBag.clear(); + return aRet; +} + +void TDefTableHandler::appendGrabBag(const OUString& aKey, const OUString& aValue) +{ + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= aValue; + m_aInteropGrabBag.push_back(aProperty); +} + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TDefTableHandler.hxx b/writerfilter/source/dmapper/TDefTableHandler.hxx new file mode 100644 index 000000000..17e6f2ed4 --- /dev/null +++ b/writerfilter/source/dmapper/TDefTableHandler.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ +#pragma once + +#include "LoggedResources.hxx" +#include <vector> +namespace com::sun::star{ + namespace table { + struct BorderLine2; + } + namespace beans { + struct PropertyValue; + } +} + +namespace writerfilter::dmapper +{ +class PropertyMap; +class TablePropertyMap; +class TDefTableHandler : public LoggedProperties +{ + std::vector<css::table::BorderLine2> m_aLeftBorderLines; + std::vector<css::table::BorderLine2> m_aRightBorderLines; + std::vector<css::table::BorderLine2> m_aTopBorderLines; + std::vector<css::table::BorderLine2> m_aBottomBorderLines; + std::vector<css::table::BorderLine2> m_aInsideHBorderLines; + std::vector<css::table::BorderLine2> m_aInsideVBorderLines; + + //values of the current border + sal_Int32 m_nLineWidth; + sal_Int32 m_nLineType; + sal_Int32 m_nLineColor; + + OUString m_aInteropGrabBagName; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + void appendGrabBag(const OUString& aKey, const OUString& aValue); + + void localResolve(Id Name, const writerfilter::Reference<Properties>::Pointer_t& pProperties); + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + +public: + TDefTableHandler(); + virtual ~TDefTableHandler() override; + + void fillCellProperties( const ::tools::SvRef< TablePropertyMap >& pCellProperties) const; + void enableInteropGrabBag(const OUString& aName); + css::beans::PropertyValue getInteropGrabBag(const OUString& aName = OUString()); + static OUString getBorderTypeString(sal_Int32 nType); + static OUString getThemeColorTypeString(sal_Int32 nType); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TableData.hxx b/writerfilter/source/dmapper/TableData.hxx new file mode 100644 index 000000000..a863e9d9f --- /dev/null +++ b/writerfilter/source/dmapper/TableData.hxx @@ -0,0 +1,390 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/text/XTextRange.hpp> + +#include "PropertyMap.hxx" + +#include <vector> + +namespace writerfilter::dmapper +{ + +/** + Class containing the data to describe a table cell. + */ +class CellData final : public virtual SvRefBase +{ + /** + Handle to start of cell. + */ + css::uno::Reference<css::text::XTextRange> mStart; + + /** + Handle to end of cell. + */ + css::uno::Reference<css::text::XTextRange> mEnd; + + /** + Pointer to properties of cell. + */ + TablePropertyMapPtr mpProps; + + bool mbOpen; + + sal_uInt32 m_nGridSpan; ///< number of grid columns in the parent table's table grid which this cell defines + +public: + typedef tools::SvRef<CellData> Pointer_t; + + CellData(css::uno::Reference<css::text::XTextRange> const & start, TablePropertyMapPtr pProps) + : mStart(start), mEnd(start), mpProps(pProps), mbOpen(true) + , m_nGridSpan(1) + { + } + + /** + Set the end handle of a cell. + + @param end the end handle of the cell + */ + void setEnd(css::uno::Reference<css::text::XTextRange> const & end) { mEnd = end; mbOpen = false; } + + /** + Adds properties to the cell. + + @param pProps the properties to add + */ + void insertProperties(TablePropertyMapPtr pProps) + { + if( mpProps ) + mpProps->InsertProps(pProps.get()); + else + mpProps = pProps; + } + + /** + Return start handle of the cell. + */ + const css::uno::Reference<css::text::XTextRange>& getStart() const { return mStart; } + + /** + Return end handle of the cell. + */ + const css::uno::Reference<css::text::XTextRange>& getEnd() const { return mEnd; } + + /** + Return properties of the cell. + */ + const TablePropertyMapPtr& getProperties() const { return mpProps; } + + bool isOpen() const { return mbOpen; } + + sal_uInt32 getGridSpan() const { return m_nGridSpan; } + void setGridSpan( sal_uInt32 nSpan ) { m_nGridSpan = nSpan; } +}; + +/** + Class to handle data of a table row. + */ +class RowData final : public virtual SvRefBase +{ + typedef ::std::vector<CellData::Pointer_t> Cells; + + /** + the cell data of the row + */ + Cells mCells; + + /** + the properties of the row + */ + mutable TablePropertyMapPtr mpProperties; + + sal_uInt32 m_nGridBefore; ///< number of grid columns in the parent table's table grid which must be skipped before the contents of this table row are added to the parent table + sal_uInt32 m_nGridAfter; ///< number of grid columns in the parent table's table grid which shall be left after the last cell in the table row + +public: + typedef tools::SvRef<RowData> Pointer_t; + + RowData() + : m_nGridBefore(0) + , m_nGridAfter(0) + { + } + + RowData(const RowData& rRowData) + : SvRefBase(), mCells(rRowData.mCells), mpProperties(rRowData.mpProperties) + , m_nGridBefore(rRowData.m_nGridBefore) + , m_nGridAfter(rRowData.m_nGridAfter) + { + } + + /** + Add a cell to the row. + + @param start the start handle of the cell + @param end the end handle of the cell + @param pProps the properties of the cell + @param bAddBefore true: add an empty cell at beginning of the row for gridBefore + */ + void addCell(const css::uno::Reference<css::text::XTextRange>& start, TablePropertyMapPtr pProps, bool bAddBefore = false) + { + CellData::Pointer_t pCellData(new CellData(start, pProps)); + if (bAddBefore) + { + mCells.insert(mCells.begin(), pCellData); + mCells[0]->setEnd(start); + } + else + mCells.push_back(pCellData); + } + + void endCell(const css::uno::Reference<css::text::XTextRange>& end) + { + if (mCells.size() > 0) + mCells.back()->setEnd(end); + } + + bool isCellOpen() const + { + return mCells.size() > 0 && mCells.back()->isOpen(); + } + + /** + Add properties to the row. + + @param pProperties the properties to set + */ + void insertProperties(TablePropertyMapPtr pProperties) + { + if( pProperties ) + { + if( !mpProperties ) + mpProperties = pProperties; + else + mpProperties->InsertProps(pProperties.get()); + } + } + + /** + Add properties to the last cell of the row. + */ + void insertCellProperties(TablePropertyMapPtr pProps) + { + if (!mCells.empty()) + mCells.back()->insertProperties(pProps); + } + + /** + Return number of cells in the row. + */ + unsigned int getCellCount() const + { + return mCells.size(); + } + + /** + Return start handle of a cell in the row. + + @param i index of the cell + */ + const css::uno::Reference<css::text::XTextRange>& getCellStart(unsigned int i) const + { + return mCells[i]->getStart(); + } + + /** + Return end handle of a cell in the row. + + @param i index of the cell + */ + const css::uno::Reference<css::text::XTextRange>& getCellEnd(unsigned int i) const + { + return mCells[i]->getEnd(); + } + + /** + Return the properties of a cell in the row. + + @param i index of the cell + */ + TablePropertyMapPtr const & getCellProperties(unsigned int i) const + { + return mCells[i]->getProperties(); + } + + /** + Return properties of the row. + */ + const TablePropertyMapPtr& getProperties() const + { + return mpProperties; + } + + sal_uInt32 getGridBefore() const { return m_nGridBefore; } + void setGridBefore(sal_uInt32 nSkipGrids) { m_nGridBefore = nSkipGrids; } + sal_uInt32 getGridAfter() const { return m_nGridAfter; } + void setGridAfter(sal_uInt32 nSkipGrids) { m_nGridAfter = nSkipGrids; } + sal_uInt32 getGridSpan(sal_uInt32 i) { return mCells[i]->getGridSpan(); } + std::vector< sal_uInt32 > getGridSpans() + { + std::vector< sal_uInt32 > nRet; + for (auto const& aCell: mCells) + nRet.push_back(aCell->getGridSpan()); + return nRet; + } + void setCurrentGridSpan(sal_uInt32 nSpan, bool bFirstCell = false) + { + if ( mCells.size() ) + { + if ( bFirstCell ) + mCells.front()->setGridSpan(nSpan); + else + mCells.back()->setGridSpan(nSpan); + } + } +}; + +/** + Class that holds the data of a table. + */ +class TableData : public virtual SvRefBase +{ + typedef RowData::Pointer_t RowPointer_t; + typedef ::std::vector<RowPointer_t> Rows; + + /** + the data of the rows of the table + */ + Rows mRows; + + /** + pointer to the data of the current row (while building up the table data). + */ + RowPointer_t mpRow; + + /** + depth of the current table in a hierarchy of tables + */ + unsigned int mnDepth; + + /** + initialize mpRow + */ + void newRow() { mpRow = RowPointer_t(new RowData()); } + +public: + typedef tools::SvRef<TableData> Pointer_t; + + explicit TableData(unsigned int nDepth) : mnDepth(nDepth) { newRow(); } + + /** + End the current row. + + Sets properties of the current row and pushes the row to the + back of the rows currently contained in the table. + + @param pProperties properties of the row to be ended + */ + void endRow(TablePropertyMapPtr pProperties) + { + mpRow->insertProperties(pProperties); + mRows.push_back(mpRow); + newRow(); + } + + /** + Add a cell to the current row. + + @param start start handle of the cell + @param end end handle of the cell + @param pProps properties of the cell + */ + void addCell(const css::uno::Reference<css::text::XTextRange>& start, TablePropertyMapPtr pProps) + { + mpRow->addCell(start, pProps); + } + + /** + End the current cell of the current row. + + @parm end end handle of the cell + */ + void endCell(const css::uno::Reference<css::text::XTextRange>& end) + { + mpRow->endCell(end); + } + + /** + Return if the current cell of the current row is open. + */ + bool isCellOpen() const + { + return mpRow->isCellOpen(); + } + + /** + Insert properties to the current cell of the current row. + + @param pProps the properties to add + */ + void insertCellProperties(TablePropertyMapPtr pProps) + { + mpRow->insertCellProperties(pProps); + } + + /** + Return number of rows in the table. + */ + unsigned int getRowCount() const + { + return mRows.size(); + } + + /** + Return depth of table in surrounding table hierarchy. + */ + unsigned int getDepth() const + { + return mnDepth; + } + + /** + Return row data of a certain row. + + @param i index of the row + */ + RowPointer_t const & getRow(unsigned int i) const + { + return mRows[i]; + } + + const RowPointer_t& getCurrentRow() const + { + return mpRow; + } +}; + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TableManager.cxx b/writerfilter/source/dmapper/TableManager.cxx new file mode 100644 index 000000000..17fc3085d --- /dev/null +++ b/writerfilter/source/dmapper/TableManager.cxx @@ -0,0 +1,609 @@ +/* -*- 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 "TableManager.hxx" +#include <ooxml/resourceids.hxx> +#include "TagLogger.hxx" +#include "DomainMapperTableHandler.hxx" +#include "DomainMapper_Impl.hxx" +#include "util.hxx" + +#include <tools/diagnose_ex.h> + +namespace writerfilter::dmapper +{ +void TableManager::clearData() {} + +void TableManager::openCell(const css::uno::Reference<css::text::XTextRange>& rHandle, + const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.openCell"); + TagLogger::getInstance().chars(XTextRangeToString(rHandle)); + TagLogger::getInstance().endElement(); +#endif + + if (!mTableDataStack.empty()) + { + TableData::Pointer_t pTableData = mTableDataStack.top(); + + pTableData->addCell(rHandle, pProps); + } +} + +bool TableManager::isIgnore() const { return isRowEnd(); } + +sal_uInt32 TableManager::getGridBefore(sal_uInt32 nRow) +{ + assert(isInTable()); + if (nRow >= mTableDataStack.top()->getRowCount()) + return 0; + return mTableDataStack.top()->getRow(nRow)->getGridBefore(); +} + +sal_uInt32 TableManager::getCurrentGridBefore() +{ + return mTableDataStack.top()->getCurrentRow()->getGridBefore(); +} + +void TableManager::setCurrentGridBefore(sal_uInt32 nSkipGrids) +{ + mTableDataStack.top()->getCurrentRow()->setGridBefore(nSkipGrids); +} + +sal_uInt32 TableManager::getGridAfter(sal_uInt32 nRow) +{ + assert(isInTable()); + if (nRow >= mTableDataStack.top()->getRowCount()) + return 0; + return mTableDataStack.top()->getRow(nRow)->getGridAfter(); +} + +void TableManager::setCurrentGridAfter(sal_uInt32 nSkipGrids) +{ + assert(isInTable()); + mTableDataStack.top()->getCurrentRow()->setGridAfter(nSkipGrids); +} + +std::vector<sal_uInt32> TableManager::getCurrentGridSpans() +{ + return mTableDataStack.top()->getCurrentRow()->getGridSpans(); +} + +void TableManager::setCurrentGridSpan(sal_uInt32 nGridSpan, bool bFirstCell) +{ + mTableDataStack.top()->getCurrentRow()->setCurrentGridSpan(nGridSpan, bFirstCell); +} + +sal_uInt32 TableManager::findColumn(const sal_uInt32 nRow, const sal_uInt32 nCell) +{ + if (nRow >= mTableDataStack.top()->getRowCount()) + return SAL_MAX_UINT32; + + RowData::Pointer_t pRow = mTableDataStack.top()->getRow(nRow); + if (!pRow || nCell < pRow->getGridBefore() + || nCell >= pRow->getCellCount() - pRow->getGridAfter()) + { + return SAL_MAX_UINT32; + } + + // The gridSpans provide a one-based index, so add up all the spans of the PREVIOUS columns, + // and that result will provide the first possible zero-based number for the desired column. + sal_uInt32 nColumn = 0; + for (sal_uInt32 n = 0; n < nCell; ++n) + nColumn += pRow->getGridSpan(n); + return nColumn; +} + +sal_uInt32 TableManager::findColumnCell(const sal_uInt32 nRow, const sal_uInt32 nCol) +{ + if (nRow >= mTableDataStack.top()->getRowCount()) + return SAL_MAX_UINT32; + + RowData::Pointer_t pRow = mTableDataStack.top()->getRow(nRow); + if (!pRow || nCol < pRow->getGridBefore()) + return SAL_MAX_UINT32; + + sal_uInt32 nCell = 0; + sal_uInt32 nGrids = 0; + // The gridSpans give us a one-based index, but requested column is zero-based - so keep that in mind. + const sal_uInt32 nMaxCell = pRow->getCellCount() - pRow->getGridAfter() - 1; + for (const auto& rSpan : pRow->getGridSpans()) + { + nGrids += rSpan; + if (nCol < nGrids) + return nCell; + + ++nCell; + if (nCell > nMaxCell) + break; + } + return SAL_MAX_UINT32; // must be in gridAfter or invalid column request +} + +void TableManager::endOfRowAction() {} + +void TableManager::endOfCellAction() {} + +void TableManager::insertTableProps(const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.insertTableProps"); +#endif + + if (getTableProps() && getTableProps() != pProps) + getTableProps()->InsertProps(pProps.get()); + else + mState.setTableProps(pProps); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::insertRowProps(const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.insertRowProps"); +#endif + + if (getRowProps()) + getRowProps()->InsertProps(pProps.get()); + else + mState.setRowProps(pProps); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::cellProps(const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.cellProps"); +#endif + + if (getCellProps()) + getCellProps()->InsertProps(pProps.get()); + else + mState.setCellProps(pProps); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::tableExceptionProps(const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.tableExceptionProps"); +#endif + + if (getTableExceptionProps()) + getTableExceptionProps()->InsertProps(pProps.get()); + else + mState.setTableExceptionProps(pProps); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::utext(const sal_uInt8* data, std::size_t len) +{ + // optimization: cell/row end characters are the last characters in a run + + if (len > 0) + { + sal_Unicode nChar = data[(len - 1) * 2] + (data[(len - 1) * 2 + 1] << 8); + if (nChar == 0x7) + handle0x7(); + } +} + +void TableManager::text(const sal_uInt8* data, std::size_t len) +{ + // optimization: cell/row end characters are the last characters in a run + if (len > 0 && data[len - 1] == 0x7) + handle0x7(); +} + +void TableManager::handle0x7() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.handle0x7"); +#endif + + if (mnTableDepthNew < 1) + mnTableDepthNew = 1; + + if (isInCell()) + endCell(); + else + endRow(); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +bool TableManager::sprm(Sprm& rSprm) +{ + bool bRet = true; + switch (rSprm.getId()) + { + case NS_ooxml::LN_tblDepth: + { + Value::Pointer_t pValue = rSprm.getValue(); + + cellDepth(pValue->getInt()); + } + break; + case NS_ooxml::LN_inTbl: + inCell(); + break; + case NS_ooxml::LN_tblCell: + endCell(); + break; + case NS_ooxml::LN_tblRow: + endRow(); + break; + default: + bRet = false; + } + return bRet; +} + +void TableManager::closeCell(const css::uno::Reference<css::text::XTextRange>& rHandle) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.closeCell"); + TagLogger::getInstance().chars(XTextRangeToString(rHandle)); + TagLogger::getInstance().endElement(); +#endif + + if (!mTableDataStack.empty()) + { + TableData::Pointer_t pTableData = mTableDataStack.top(); + + pTableData->endCell(rHandle); + + if (mpTableDataHandler) + mpTableDataHandler->getDomainMapperImpl().ClearPreviousParagraph(); + } +} + +void TableManager::ensureOpenCell(const TablePropertyMapPtr& pProps) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.ensureOpenCell"); +#endif + + if (!mTableDataStack.empty()) + { + TableData::Pointer_t pTableData = mTableDataStack.top(); + + if (pTableData != nullptr) + { + if (!pTableData->isCellOpen()) + openCell(getHandle(), pProps); + else + pTableData->insertCellProperties(pProps); + } + } +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::endParagraphGroup() +{ + sal_Int32 nTableDepthDifference = mnTableDepthNew - mnTableDepth; + + TablePropertyMapPtr pEmptyProps; + + while (nTableDepthDifference > 0) + { + ensureOpenCell(pEmptyProps); + startLevel(); + + --nTableDepthDifference; + } + while (nTableDepthDifference < 0) + { + endLevel(); + + ++nTableDepthDifference; + } + + mnTableDepth = mnTableDepthNew; + + if (mnTableDepth <= 0) + return; + + if (isRowEnd()) + { + endOfRowAction(); + mTableDataStack.top()->endRow(getRowProps()); + mState.resetRowProps(); + } + + else if (isInCell()) + { + ensureOpenCell(getCellProps()); + + if (mState.isCellEnd()) + { + endOfCellAction(); + closeCell(getHandle()); + } + } + mState.resetCellProps(); +} + +void TableManager::startParagraphGroup() +{ + mState.resetCellSpecifics(); + mnTableDepthNew = 0; +} + +void TableManager::resolveCurrentTable() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.resolveCurrentTable"); +#endif + + if (mpTableDataHandler != nullptr) + { + try + { + TableData::Pointer_t pTableData = mTableDataStack.top(); + + unsigned int nRows = pTableData->getRowCount(); + + mpTableDataHandler->startTable(getTableProps()); + + for (unsigned int nRow = 0; nRow < nRows; ++nRow) + { + RowData::Pointer_t pRowData = pTableData->getRow(nRow); + + unsigned int nCells = pRowData->getCellCount(); + + mpTableDataHandler->startRow(pRowData->getProperties()); + + for (unsigned int nCell = 0; nCell < nCells; ++nCell) + { + mpTableDataHandler->startCell(pRowData->getCellStart(nCell), + pRowData->getCellProperties(nCell)); + + mpTableDataHandler->endCell(pRowData->getCellEnd(nCell)); + } + + mpTableDataHandler->endRow(); + } + + mpTableDataHandler->endTable(mTableDataStack.size() - 1, m_bTableStartsAtCellStart); + } + catch (css::uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("writerfilter", "resolving of current table failed"); + } + } + mState.resetTableProps(); + clearData(); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::endLevel() +{ + if (mpTableDataHandler != nullptr) + resolveCurrentTable(); + + // Store the unfinished row as it will be used for the next table + if (mbKeepUnfinishedRow) + mpUnfinishedRow = mTableDataStack.top()->getCurrentRow(); + mState.endLevel(); + mTableDataStack.pop(); + +#ifdef DBG_UTIL + TableData::Pointer_t pTableData; + + if (!mTableDataStack.empty()) + pTableData = mTableDataStack.top(); + + TagLogger::getInstance().startElement("tablemanager.endLevel"); + TagLogger::getInstance().attribute("level", mTableDataStack.size()); + + if (pTableData != nullptr) + TagLogger::getInstance().attribute("openCell", pTableData->isCellOpen() ? "yes" : "no"); + + TagLogger::getInstance().endElement(); +#endif +} + +void TableManager::startLevel() +{ +#ifdef DBG_UTIL + TableData::Pointer_t pTableData; + + if (!mTableDataStack.empty()) + pTableData = mTableDataStack.top(); + + TagLogger::getInstance().startElement("tablemanager.startLevel"); + TagLogger::getInstance().attribute("level", mTableDataStack.size()); + + if (pTableData != nullptr) + TagLogger::getInstance().attribute("openCell", pTableData->isCellOpen() ? "yes" : "no"); + + TagLogger::getInstance().endElement(); +#endif + + TableData::Pointer_t pTableData2(new TableData(mTableDataStack.size())); + + // If we have an unfinished row stored here, then push it to the new TableData + if (mpUnfinishedRow) + { + for (unsigned int i = 0; i < mpUnfinishedRow->getCellCount(); ++i) + { + pTableData2->addCell(mpUnfinishedRow->getCellStart(i), + mpUnfinishedRow->getCellProperties(i)); + pTableData2->endCell(mpUnfinishedRow->getCellEnd(i)); + pTableData2->getCurrentRow()->setCurrentGridSpan(mpUnfinishedRow->getGridSpan(i)); + } + pTableData2->getCurrentRow()->setGridBefore(mpUnfinishedRow->getGridBefore()); + pTableData2->getCurrentRow()->setGridAfter(mpUnfinishedRow->getGridAfter()); + mpUnfinishedRow.clear(); + } + + mTableDataStack.push(pTableData2); + mState.startLevel(); +} + +bool TableManager::isInTable() +{ + bool bInTable = false; + if (!mTableDataStack.empty()) + bInTable = mTableDataStack.top()->getDepth() > 0; + return bInTable; +} + +void TableManager::handle(const css::uno::Reference<css::text::XTextRange>& rHandle) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.handle"); + TagLogger::getInstance().chars(XTextRangeToString(rHandle)); + TagLogger::getInstance().endElement(); +#endif + + setHandle(rHandle); +} + +void TableManager::setHandler(const tools::SvRef<DomainMapperTableHandler>& pTableDataHandler) +{ + mpTableDataHandler = pTableDataHandler; +} + +void TableManager::endRow() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element("tablemanager.endRow"); +#endif + TableData::Pointer_t pTableData = mTableDataStack.top(); + + // Add borderless w:gridBefore cell(s) to the row + sal_uInt32 nGridBefore = getCurrentGridBefore(); + if (pTableData && nGridBefore > 0 && pTableData->getCurrentRow()->getCellCount() > 0) + { + const css::uno::Reference<css::text::XTextRange>& xRowStart + = pTableData->getCurrentRow()->getCellStart(0); + if (xRowStart.is()) + { + try + { + // valid TextRange for table creation (not a nested table)? + xRowStart->getText()->createTextCursorByRange(xRowStart); + + for (unsigned int i = 0; i < nGridBefore; ++i) + { + css::table::BorderLine2 aBorderLine; + aBorderLine.Color = 0; + aBorderLine.InnerLineWidth = 0; + aBorderLine.OuterLineWidth = 0; + TablePropertyMapPtr pCellProperties(new TablePropertyMap); + pCellProperties->Insert(PROP_TOP_BORDER, css::uno::Any(aBorderLine)); + pCellProperties->Insert(PROP_LEFT_BORDER, css::uno::Any(aBorderLine)); + pCellProperties->Insert(PROP_BOTTOM_BORDER, css::uno::Any(aBorderLine)); + pCellProperties->Insert(PROP_RIGHT_BORDER, css::uno::Any(aBorderLine)); + pTableData->getCurrentRow()->addCell(xRowStart, pCellProperties, + /*bAddBefore=*/true); + } + } + catch (css::uno::Exception const&) + { + // don't add gridBefore cells in not valid TextRange + setCurrentGridBefore(0); + setCurrentGridSpan(getCurrentGridSpans().front() + nGridBefore, + /*bFirstCell=*/true); + } + } + } + + setRowEnd(true); +} + +void TableManager::endCell() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element("tablemanager.endCell"); +#endif + + setCellEnd(true); +} + +void TableManager::inCell() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element("tablemanager.inCell"); +#endif + setInCell(true); + + if (mnTableDepthNew < 1) + mnTableDepthNew = 1; +} + +void TableManager::cellDepth(sal_uInt32 nDepth) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("tablemanager.cellDepth"); + TagLogger::getInstance().attribute("depth", nDepth); + TagLogger::getInstance().endElement(); +#endif + + mnTableDepthNew = nDepth; +} + +void TableManager::setTableStartsAtCellStart(bool bTableStartsAtCellStart) +{ + m_bTableStartsAtCellStart = bTableStartsAtCellStart; +} + +void TableManager::setCellLastParaAfterAutospacing(bool bIsAfterAutospacing) +{ + m_bCellLastParaAfterAutospacing = bIsAfterAutospacing; +} + +TableManager::TableManager() + : mnTableDepthNew(0) + , mnTableDepth(0) + , mbKeepUnfinishedRow(false) + , m_bTableStartsAtCellStart(false) +{ + setRowEnd(false); + setInCell(false); + setCellEnd(false); + m_bCellLastParaAfterAutospacing = false; +} + +TableManager::~TableManager() = default; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TableManager.hxx b/writerfilter/source/dmapper/TableManager.hxx new file mode 100644 index 000000000..e6600d357 --- /dev/null +++ b/writerfilter/source/dmapper/TableManager.hxx @@ -0,0 +1,530 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> +#include <stack> + +#include "PropertyMap.hxx" +#include "TableData.hxx" + +#include <dmapper/resourcemodel.hxx> + +namespace writerfilter::dmapper +{ + +class DomainMapperTableHandler; + +/** + The table manager. + + This class gets forwarded events from the tokenizer. It gathers the + table data and after ending the table generates events for the + table structure. The events have to be handles by a TableDataHandler. + + */ +class TableManager : public virtual SvRefBase +{ + class TableManagerState final + { + /** + properties of the current cell + */ + TablePropertyMapPtr mpCellProps; + + /** + properties of the current row + */ + TablePropertyMapPtr mpRowProps; + + /** + table exception properties of the current row + */ + TablePropertyMapPtr mpTableExceptionProps; + + /** + properties of the current table + */ + std::stack<TablePropertyMapPtr> mTableProps; + + /** + true if at the end of a row + */ + bool mbRowEnd; + + /** + true when in a cell + */ + bool mbInCell; + + /** + true when at the end of a cell + */ + bool mbCellEnd; + + public: + /** + Constructor + */ + TableManagerState() + : mbRowEnd(false), mbInCell(false), mbCellEnd(false) + { + } + + void startLevel() + { + TablePropertyMapPtr pProps; + mTableProps.push(pProps); + } + + void endLevel() + { + mTableProps.pop(); + } + + /** + Reset to initial state at beginning of row. + */ + void resetCellSpecifics() + { + mbRowEnd = false; + mbInCell = false; + mbCellEnd = false; + } + + void resetCellProps() + { + // copy tblPrEx table exception properties, if they exist + if (getTableExceptionProps().is()) + { + mpCellProps = new TablePropertyMap; + mpCellProps->InsertProps(getTableExceptionProps().get()); + } + else + mpCellProps.clear(); + } + + void setCellProps(TablePropertyMapPtr pProps) + { + mpCellProps = pProps; + } + + const TablePropertyMapPtr& getCellProps() const + { + return mpCellProps; + } + + void resetRowProps() + { + // reset also table exception and + // its copy set by the previous resetCellProps() + mpTableExceptionProps.clear(); + resetCellProps(); + mpRowProps.clear(); + } + + void setRowProps(TablePropertyMapPtr pProps) + { + mpRowProps = pProps; + } + + const TablePropertyMapPtr& getRowProps() const + { + return mpRowProps; + } + + void setTableExceptionProps(TablePropertyMapPtr pProps) + { + mpTableExceptionProps = pProps; + // set table exception properties of the first cell + resetCellProps(); + } + + const TablePropertyMapPtr& getTableExceptionProps() const + { + return mpTableExceptionProps; + } + + void resetTableProps() + { + if (mTableProps.size() > 0) + mTableProps.top().clear(); + } + + void setTableProps(TablePropertyMapPtr pProps) + { + if (mTableProps.size() > 0) + mTableProps.top() = pProps; + } + + TablePropertyMapPtr getTableProps() + { + TablePropertyMapPtr pResult; + + if (mTableProps.size() > 0) + pResult = mTableProps.top(); + + return pResult; + } + + void setInCell(bool bInCell) + { + mbInCell = bInCell; + } + + bool isInCell() const + { + return mbInCell; + } + + void setCellEnd(bool bCellEnd) + { + mbCellEnd = bCellEnd; + } + + bool isCellEnd() const + { + return mbCellEnd; + } + + void setRowEnd(bool bRowEnd) + { + mbRowEnd = bRowEnd; + } + + bool isRowEnd() const + { + return mbRowEnd; + } + }; + + /** + handle for the current position in document + */ + css::uno::Reference<css::text::XTextRange> mCurHandle; + + TableManagerState mState; + +protected: + TablePropertyMapPtr const & getCellProps() const + { + return mState.getCellProps(); + } + +public: + TablePropertyMapPtr const & getRowProps() const + { + return mState.getRowProps(); + } + + TablePropertyMapPtr const & getTableExceptionProps() const + { + return mState.getTableExceptionProps(); + } + +protected: + void setInCell(bool bInCell) + { + mState.setInCell(bInCell); + } + + bool isInCell() const + { + return mState.isInCell(); + } + + void setCellEnd(bool bCellEnd) + { + mState.setCellEnd(bCellEnd); + } + + void setRowEnd(bool bRowEnd) + { + mState.setRowEnd(bRowEnd); + } + + bool isRowEnd() const + { + return mState.isRowEnd(); + } + + TablePropertyMapPtr getTableProps() + { + return mState.getTableProps(); + } + + const css::uno::Reference<css::text::XTextRange>& getHandle() const + { + return mCurHandle; + } + + void setHandle(const css::uno::Reference<css::text::XTextRange>& rHandle) + { + mCurHandle = rHandle; + } + +private: + typedef tools::SvRef< css::uno::Reference<css::text::XTextRange> > T_p; + + /** + depth of the current cell + */ + sal_uInt32 mnTableDepthNew; + + /** + depth of the previous cell + */ + sal_uInt32 mnTableDepth; + + /** + stack of table data + + for each level of nested tables there is one frame in the stack + */ + std::stack<TableData::Pointer_t> mTableDataStack; + RowData::Pointer_t mpUnfinishedRow; + bool mbKeepUnfinishedRow; + /// If this is a nested table, does it start at cell start? + bool m_bTableStartsAtCellStart; + + bool m_bCellLastParaAfterAutospacing; + + /** + handler for resolveCurrentTable + */ + tools::SvRef<DomainMapperTableHandler> mpTableDataHandler; + + /** + Set flag which indicates the current handle is in a cell. + */ + void inCell(); + + /** + Set flag which indicate the current handle is at the end of a cell. + */ + void endCell(); + + /** + Set the table depth of the current cell. + + @param nDepth the cell depth + */ + void cellDepth(sal_uInt32 nDepth); + + /** + Set flag indication the current handle is at the end of a row. + */ + void endRow(); + + /** + Resolve the current table to the TableDataHandler. + */ + void resolveCurrentTable(); + + /** + Open a cell at current level. + */ + + void openCell(const css::uno::Reference<css::text::XTextRange>& rHandle, const TablePropertyMapPtr& pProps); + + /** + Close a cell at current level. + */ + void closeCell(const css::uno::Reference<css::text::XTextRange>& rHandle); + + /** + Ensure a cell is open at the current level. + */ + void ensureOpenCell(const TablePropertyMapPtr& pProps); + +protected: + /** + Return the current table difference, i.e. 1 if we are in the first cell of a new table, etc. + */ + sal_Int32 getTableDepthDifference() const { return mnTableDepthNew - mnTableDepth; } + + sal_uInt32 getTableDepth() const { return mnTableDepthNew; } + + /** + Action to be carried out at the end of the last paragraph of a + cell. + */ + virtual void endOfCellAction(); + + /** + Action to be carried out at the end of the "table row" + paragraph. + */ + virtual void endOfRowAction(); + /** let the derived class clear their table related data + */ + virtual void clearData(); + + /** Should we keep the unfinished row in endLevel to initialize the table + data in the following startLevel. + */ + void setKeepUnfinishedRow(bool bKeep) + { + mbKeepUnfinishedRow = bKeep; + } + + +public: + TableManager(); + ~TableManager(); + + /** + Set handler for resolveCurrentTable. + + @param pTableDataHandler the handler + */ + void setHandler(const tools::SvRef<DomainMapperTableHandler>& pTableDataHandler); + + /** + Set the current handle. + + @param rHandle the handle + */ + void handle(const css::uno::Reference<css::text::XTextRange>& rHandle); + + /** + Start a new table level. + + A new context is pushed onto the table data stack, + */ + virtual void startLevel(); + + /** + End a table level. + + The current table is resolved and the context is popped from + the stack. + */ + virtual void endLevel(); + + /** + * Signal that the next paragraph definitely won't be part of any table. + */ + void endTable() + { + setRowEnd(false); + } + + /** + Tells whether a table has been started or not + */ + bool isInTable(); + + /** + Handle the start of a paragraph group. + */ + void startParagraphGroup(); + + /** + Handle the end of a paragraph group. + */ + void endParagraphGroup(); + + /** + Handle an SPRM at current handle. + + @param rSprm the SPRM + */ + virtual bool sprm(Sprm & rSprm); + + /** + Handle occurrence of character 0x7. + */ + void handle0x7(); + + /** + Handle 8 bit text at current handle. + + @param data array of characters + @param len number of characters to handle + */ + void text(const sal_uInt8 * data, size_t len); + + /** + Handle 16 bit text at current handle. + + @param data array of characters + @param len number of characters to handle + */ + void utext(const sal_uInt8 * data, size_t len); + + /** + Handle properties of the current cell. + + @param pProps the properties + */ + virtual void cellProps(const TablePropertyMapPtr& pProps); + + /** + Handle properties of the current row. + + @param pProps the properties + */ + virtual void insertRowProps(const TablePropertyMapPtr& pProps); + + /** + Handle table exception properties of the current row. + + @param pProps the properties + */ + virtual void tableExceptionProps(const TablePropertyMapPtr& pProps); + + /** + Handle properties of the current table. + + @param pProps the properties + */ + virtual void insertTableProps(const TablePropertyMapPtr& pProps); + + /** + Return if table manager has detected paragraph to ignore. + + If this function returns true the current paragraph contains + only control information, e.g. end of row. + */ + bool isIgnore() const; + + sal_uInt32 getGridBefore(sal_uInt32 nRow); + sal_uInt32 getCurrentGridBefore(); + void setCurrentGridBefore( sal_uInt32 nSkipGrids ); + sal_uInt32 getGridAfter(sal_uInt32 nRow); + void setCurrentGridAfter( sal_uInt32 nSkipGrids ); + std::vector<sal_uInt32> getCurrentGridSpans(); + void setCurrentGridSpan( sal_uInt32 nGridSpan, bool bFirstCell = false ); + /// Given a zero-based row/cell, return the zero-based grid it belongs to, or SAL_MAX_UINT16 for invalid. + sal_uInt32 findColumn( const sal_uInt32 nRow, const sal_uInt32 nCell ); + /// Given a zero-based row/col, return the zero-based cell describing that grid, or SAL_MAX_UINT16 for invalid. + sal_uInt32 findColumnCell( const sal_uInt32 nRow, const sal_uInt32 nCol ); + + void setTableStartsAtCellStart(bool bTableStartsAtCellStart); + void setCellLastParaAfterAutospacing(bool bIsAfterAutospacing); + bool isCellLastParaAfterAutospacing() const {return m_bCellLastParaAfterAutospacing;} +}; + +} + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TablePositionHandler.cxx b/writerfilter/source/dmapper/TablePositionHandler.cxx new file mode 100644 index 000000000..5eea4a000 --- /dev/null +++ b/writerfilter/source/dmapper/TablePositionHandler.cxx @@ -0,0 +1,154 @@ +/* -*- 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/. + */ +#include "TablePositionHandler.hxx" +#include "ConversionHelper.hxx" +#include "TagLogger.hxx" +#include <ooxml/resourceids.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <comphelper/sequenceashashmap.hxx> + +namespace writerfilter::dmapper +{ +using namespace ::com::sun::star; + +TablePositionHandler::TablePositionHandler() + : LoggedProperties("TablePositionHandler") +{ +} + +TablePositionHandler::~TablePositionHandler() = default; + +void TablePositionHandler::lcl_attribute(Id nId, Value& rVal) +{ + switch (nId) + { + case NS_ooxml::LN_CT_TblPPr_vertAnchor: + m_aVertAnchor = rVal.getString(); + break; + case NS_ooxml::LN_CT_TblPPr_tblpYSpec: + m_aYSpec = rVal.getString(); + break; + case NS_ooxml::LN_CT_TblPPr_horzAnchor: + m_aHorzAnchor = rVal.getString(); + break; + case NS_ooxml::LN_CT_TblPPr_tblpXSpec: + m_aXSpec = rVal.getString(); + break; + case NS_ooxml::LN_CT_TblPPr_tblpY: + m_nY = rVal.getInt(); + break; + case NS_ooxml::LN_CT_TblPPr_tblpX: + m_nX = rVal.getInt(); + break; + case NS_ooxml::LN_CT_TblPPr_leftFromText: + m_nLeftFromText = rVal.getInt(); + break; + case NS_ooxml::LN_CT_TblPPr_rightFromText: + m_nRightFromText = rVal.getInt(); + break; + case NS_ooxml::LN_CT_TblPPr_topFromText: + m_nTopFromText = rVal.getInt(); + break; + case NS_ooxml::LN_CT_TblPPr_bottomFromText: + m_nBottomFromText = rVal.getInt(); + break; + default: +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + break; + } +} + +void TablePositionHandler::lcl_sprm(Sprm& /*rSprm*/) {} + +uno::Sequence<beans::PropertyValue> TablePositionHandler::getTablePosition() const +{ + comphelper::SequenceAsHashMap aFrameProperties; + + aFrameProperties["LeftBorderDistance"] <<= sal_Int32(0); + aFrameProperties["RightBorderDistance"] <<= sal_Int32(0); + aFrameProperties["TopBorderDistance"] <<= sal_Int32(0); + aFrameProperties["BottomBorderDistance"] <<= sal_Int32(0); + + aFrameProperties["LeftMargin"] <<= ConversionHelper::convertTwipToMM100(m_nLeftFromText); + aFrameProperties["RightMargin"] <<= ConversionHelper::convertTwipToMM100(m_nRightFromText); + aFrameProperties["TopMargin"] <<= ConversionHelper::convertTwipToMM100(m_nTopFromText); + aFrameProperties["BottomMargin"] <<= ConversionHelper::convertTwipToMM100(m_nBottomFromText); + + table::BorderLine2 aEmptyBorder; + aFrameProperties["TopBorder"] <<= aEmptyBorder; + aFrameProperties["BottomBorder"] <<= aEmptyBorder; + aFrameProperties["LeftBorder"] <<= aEmptyBorder; + aFrameProperties["RightBorder"] <<= aEmptyBorder; + + // Horizontal positioning + sal_Int16 nHoriOrient = text::HoriOrientation::NONE; + if (m_aXSpec == "center") + nHoriOrient = text::HoriOrientation::CENTER; + else if (m_aXSpec == "inside") + nHoriOrient = text::HoriOrientation::INSIDE; + else if (m_aXSpec == "left") + nHoriOrient = text::HoriOrientation::LEFT; + else if (m_aXSpec == "outside") + nHoriOrient = text::HoriOrientation::OUTSIDE; + else if (m_aXSpec == "right") + nHoriOrient = text::HoriOrientation::RIGHT; + + sal_Int16 nHoriOrientRelation; + if (m_aHorzAnchor == "margin") + nHoriOrientRelation = text::RelOrientation::PAGE_PRINT_AREA; + else if (m_aHorzAnchor == "page") + nHoriOrientRelation = text::RelOrientation::PAGE_FRAME; + else if (m_aHorzAnchor == "text") + nHoriOrientRelation = text::RelOrientation::FRAME; + + aFrameProperties["HoriOrient"] <<= nHoriOrient; + aFrameProperties["HoriOrientRelation"] <<= nHoriOrientRelation; + aFrameProperties["HoriOrientPosition"] <<= ConversionHelper::convertTwipToMM100(m_nX); + + // Vertical positioning + sal_Int16 nVertOrient = text::VertOrientation::NONE; + if (m_aYSpec == "bottom") + nVertOrient = text::VertOrientation::BOTTOM; + else if (m_aYSpec == "center") + nVertOrient = text::VertOrientation::CENTER; + else if (m_aYSpec == "top") + nVertOrient = text::VertOrientation::TOP; + // TODO There are a few cases we can't map ATM. + + sal_Int16 nVertOrientRelation; + if (m_aVertAnchor == "margin") + nVertOrientRelation = text::RelOrientation::PAGE_PRINT_AREA; + else if (m_aVertAnchor == "page") + nVertOrientRelation = text::RelOrientation::PAGE_FRAME; + else if (m_aVertAnchor == "text") + nVertOrientRelation = text::RelOrientation::FRAME; + + aFrameProperties["VertOrient"] <<= nVertOrient; + aFrameProperties["VertOrientRelation"] <<= nVertOrientRelation; + aFrameProperties["VertOrientPosition"] <<= ConversionHelper::convertTwipToMM100(m_nY); + aFrameProperties["FillTransparence"] <<= sal_Int32(100); + + return aFrameProperties.getAsConstPropertyValueList(); +} + +bool TablePositionHandler::operator==(const TablePositionHandler& rHandler) const +{ + return m_aVertAnchor == rHandler.m_aVertAnchor && m_aYSpec == rHandler.m_aYSpec + && m_aHorzAnchor == rHandler.m_aHorzAnchor && m_aXSpec == rHandler.m_aXSpec + && m_nY == rHandler.m_nY && m_nX == rHandler.m_nX; +} + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TablePositionHandler.hxx b/writerfilter/source/dmapper/TablePositionHandler.hxx new file mode 100644 index 000000000..23d042b69 --- /dev/null +++ b/writerfilter/source/dmapper/TablePositionHandler.hxx @@ -0,0 +1,68 @@ +/* -*- 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/. + */ +#pragma once + +#include "LoggedResources.hxx" + +namespace com::sun::star::beans +{ +struct PropertyValue; +} + +namespace writerfilter::dmapper +{ +/// Handler for floating table positioning +class TablePositionHandler : public LoggedProperties +{ + OUString m_aVertAnchor{ "margin" }; + OUString m_aYSpec; + OUString m_aHorzAnchor{ "text" }; + OUString m_aXSpec; + sal_Int32 m_nY = 0; + sal_Int32 m_nX = 0; + sal_Int32 m_nLeftFromText = 0; + sal_Int32 m_nRightFromText = 0; + sal_Int32 m_nTopFromText = 0; + sal_Int32 m_nBottomFromText = 0; + + // Properties + void lcl_attribute(Id nId, Value& rVal) override; + void lcl_sprm(Sprm& sprm) override; + +public: + sal_Int32 getY() const { return m_nY; } + sal_Int32 getX() const { return m_nX; } + sal_Int32 getLeftFromText() const { return m_nLeftFromText; } + sal_Int32 getRightFromText() const { return m_nRightFromText; } + sal_Int32 getTopFromText() const { return m_nTopFromText; } + sal_Int32 getBottomFromText() const { return m_nBottomFromText; } + + const OUString& getVertAnchor() const { return m_aVertAnchor; } + const OUString& getYSpec() const { return m_aYSpec; } + const OUString& getHorzAnchor() const { return m_aHorzAnchor; } + const OUString& getXSpec() const { return m_aXSpec; } + + TablePositionHandler(); + ~TablePositionHandler() override; + + /** Compute the UNO properties for the frame containing the table based + on the received tokens. + + Note that the properties will need to be adjusted with the table + properties before actually using them. + */ + css::uno::Sequence<css::beans::PropertyValue> getTablePosition() const; + + bool operator==(const TablePositionHandler& rHandler) const; +}; + +using TablePositionHandlerPtr = tools::SvRef<TablePositionHandler>; +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TablePropertiesHandler.cxx b/writerfilter/source/dmapper/TablePropertiesHandler.cxx new file mode 100644 index 000000000..8ea3eae5c --- /dev/null +++ b/writerfilter/source/dmapper/TablePropertiesHandler.cxx @@ -0,0 +1,398 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include "BorderHandler.hxx" +#include "CellColorHandler.hxx" +#include "CellMarginHandler.hxx" +#include "ConversionHelper.hxx" +#include "MeasureHandler.hxx" +#include "TrackChangesHandler.hxx" +#include "TablePropertiesHandler.hxx" +#include "TagLogger.hxx" +#include "TDefTableHandler.hxx" +#include "DomainMapperTableManager.hxx" + +#include <ooxml/resourceids.hxx> + +#include <com/sun/star/text/VertOrientation.hpp> +#include <oox/token/tokens.hxx> + +using namespace com::sun::star; +using namespace oox; + +namespace writerfilter::dmapper { + + TablePropertiesHandler::TablePropertiesHandler() : + m_pCurrentInteropGrabBag(nullptr), + m_pTableManager( nullptr ) + { + } + + bool TablePropertiesHandler::sprm(Sprm & rSprm) + { +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("TablePropertiesHandler.sprm"); + TagLogger::getInstance().attribute("sprm", rSprm.toString()); +#endif + + bool bRet = true; + sal_uInt32 nSprmId = rSprm.getId(); + Value::Pointer_t pValue = rSprm.getValue(); + sal_Int32 nIntValue = (pValue ? pValue->getInt() : 0); + switch( nSprmId ) + { + case NS_ooxml::LN_CT_TrPrBase_jc: + case NS_ooxml::LN_CT_TblPrBase_jc: + { + sal_Int16 nOrient = ConversionHelper::convertTableJustification( nIntValue ); + TablePropertyMapPtr pTableMap( new TablePropertyMap ); + pTableMap->setValue( TablePropertyMap::HORI_ORIENT, nOrient ); + insertTableProps( pTableMap ); + } + break; + case NS_ooxml::LN_CT_TrPrBase_trHeight: + { + //contains unit and value + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { //contains attributes x2902 (LN_unit) and x17e2 (LN_trleft) + MeasureHandlerPtr pMeasureHandler( new MeasureHandler ); + pProperties->resolve(*pMeasureHandler); + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + + pPropMap->Insert( PROP_SIZE_TYPE, uno::Any( pMeasureHandler->GetRowHeightSizeType() ), false); + pPropMap->Insert( PROP_HEIGHT, uno::Any(pMeasureHandler->getMeasureValue() )); + + insertRowProps(pPropMap); + } + } + break; + case NS_ooxml::LN_CT_TrPr_ins: + case NS_ooxml::LN_CT_TrPr_del: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + sal_Int32 nToken = sal_Int32(); + switch( nSprmId ) + { + case NS_ooxml::LN_CT_TrPr_ins: + nToken = XML_tableRowInsert; + break; + case NS_ooxml::LN_CT_TrPr_del: + nToken = XML_tableRowDelete; + break; + } + auto pTrackChangesHandler = std::make_shared<TrackChangesHandler>( nToken ); + pProperties->resolve(*pTrackChangesHandler); + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + + // Add the 'track changes' properties to the 'table row' via UNO. + // This way - in the SW core - when it receives this - it will create a new 'Table Redline' object for that row + uno::Sequence<beans::PropertyValue> aTableRedlineProperties = pTrackChangesHandler->getRedlineProperties(); + pPropMap->Insert( PROP_TABLE_REDLINE_PARAMS , uno::Any( aTableRedlineProperties )); + insertRowProps(pPropMap); + } + } + break; + case NS_ooxml::LN_CT_TcPrBase_cellIns: + case NS_ooxml::LN_CT_TcPrBase_cellDel: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + sal_Int32 nToken; + switch( nSprmId ) + { + case NS_ooxml::LN_CT_TcPrBase_cellIns: + nToken = XML_tableCellInsert; + break; + case NS_ooxml::LN_CT_TcPrBase_cellDel: + nToken = XML_tableCellDelete; + break; + default: + throw lang::IllegalArgumentException("illegal redline token type", nullptr, 0); + break; + } + auto pTrackChangesHandler = std::make_shared<TrackChangesHandler>( nToken ); + pProperties->resolve(*pTrackChangesHandler); + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + + // Add the 'track changes' properties to the 'table row' via UNO. + // This way - in the SW core - when it receives this - it will create a new 'Table Redline' object for that row + uno::Sequence<beans::PropertyValue> aTableRedlineProperties = pTrackChangesHandler->getRedlineProperties(); + pPropMap->Insert( PROP_TABLE_REDLINE_PARAMS , uno::Any( aTableRedlineProperties )); + cellProps(pPropMap); + } + } + break; + case NS_ooxml::LN_CT_TrPrBase_cantSplit: + { + //row can't break across pages if nIntValue == 1 + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->Insert( PROP_IS_SPLIT_ALLOWED, uno::Any( nIntValue != 1 ) ); + insertRowProps(pPropMap); + } + break; + case NS_ooxml::LN_CT_TcPrBase_vAlign: + { + sal_Int16 nVertOrient = text::VertOrientation::NONE; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_VerticalJc_center: nVertOrient = text::VertOrientation::CENTER; break; + case NS_ooxml::LN_Value_ST_VerticalJc_bottom: nVertOrient = text::VertOrientation::BOTTOM; break; + default:; + } + TablePropertyMapPtr pCellPropMap( new TablePropertyMap() ); + pCellPropMap->Insert( PROP_VERT_ORIENT, uno::Any( nVertOrient ) ); + //todo: in ooxml import the value of m_ncell is wrong + cellProps( pCellPropMap ); + if (m_pCurrentInteropGrabBag) + { + OUString aVertOrient; + switch( nIntValue ) + { + case NS_ooxml::LN_Value_ST_VerticalJc_top: aVertOrient = "top"; break; + case NS_ooxml::LN_Value_ST_VerticalJc_center: aVertOrient = "center"; break; + case NS_ooxml::LN_Value_ST_VerticalJc_both: aVertOrient = "both"; break; + case NS_ooxml::LN_Value_ST_VerticalJc_bottom: aVertOrient = "bottom"; break; + } + if (!aVertOrient.isEmpty()) + { + beans::PropertyValue aValue; + aValue.Name = "vAlign"; + aValue.Value <<= aVertOrient; + m_pCurrentInteropGrabBag->push_back(aValue); + } + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblBorders: //table borders, might be defined in table style + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pBorderHandler = std::make_shared<BorderHandler>(true); + if (m_pCurrentInteropGrabBag) + pBorderHandler->enableInteropGrabBag("tblBorders"); + pProperties->resolve(*pBorderHandler); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(pBorderHandler->getInteropGrabBag()); + TablePropertyMapPtr pTablePropMap( new TablePropertyMap ); + pTablePropMap->InsertProps(pBorderHandler->getProperties()); + +#ifdef DBG_UTIL + pTablePropMap->dumpXml(); +#endif + insertTableProps( pTablePropMap ); + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblLayout: + { + DomainMapperTableManager* pManager = dynamic_cast<DomainMapperTableManager*>(m_pTableManager); + if (pManager) + pManager->SetLayoutType(static_cast<sal_uInt32>(nIntValue)); + } + break; + case NS_ooxml::LN_CT_TblPrEx_tblBorders: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties) + { + auto pBorderHandler = std::make_shared<BorderHandler>(true); + pProperties->resolve(*pBorderHandler); + TablePropertyMapPtr pTablePropMap( new TablePropertyMap ); + pTablePropMap->InsertProps(pBorderHandler->getProperties()); + +#ifdef DBG_UTIL + pTablePropMap->dumpXml(); +#endif + tableExceptionProps( pTablePropMap ); + } + } + break; + case NS_ooxml::LN_CT_TcPrBase_tcBorders ://cell borders + //contains CT_TcBorders_left, right, top, bottom + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + //in OOXML there's one set of borders at each cell (if there is any) + tools::SvRef< TDefTableHandler > pTDefTableHandler( new TDefTableHandler()); + if (m_pCurrentInteropGrabBag) + pTDefTableHandler->enableInteropGrabBag("tcBorders"); + pProperties->resolve( *pTDefTableHandler ); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(pTDefTableHandler->getInteropGrabBag()); + TablePropertyMapPtr pCellPropMap( new TablePropertyMap ); + pTDefTableHandler->fillCellProperties( pCellPropMap ); + cellProps( pCellPropMap ); + } + } + break; + case NS_ooxml::LN_CT_TcPrBase_tcMar: + + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + auto pCellMarginHandler = std::make_shared<CellMarginHandler>(); + if (m_pCurrentInteropGrabBag) + pCellMarginHandler->enableInteropGrabBag("tcMar"); + pProperties->resolve(*pCellMarginHandler); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(pCellMarginHandler->getInteropGrabBag()); + TablePropertyMapPtr pCellProperties(new TablePropertyMap); + if (pCellMarginHandler->m_bTopMarginValid) + pCellProperties->Insert(PROP_TOP_BORDER_DISTANCE, uno::Any(pCellMarginHandler->m_nTopMargin)); + if (pCellMarginHandler->m_bLeftMarginValid) + pCellProperties->Insert(PROP_LEFT_BORDER_DISTANCE, uno::Any(pCellMarginHandler->m_nLeftMargin)); + if (pCellMarginHandler->m_bBottomMarginValid) + pCellProperties->Insert(PROP_BOTTOM_BORDER_DISTANCE, uno::Any(pCellMarginHandler->m_nBottomMargin)); + if (pCellMarginHandler->m_bRightMarginValid) + pCellProperties->Insert(PROP_RIGHT_BORDER_DISTANCE, uno::Any(pCellMarginHandler->m_nRightMargin)); + cellProps(pCellProperties); + } + } + break; +/* // tdf#123189 skip to keep MSO interoperability + case NS_ooxml::LN_CT_TblPrBase_shd: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties.get()) + { + std::shared_ptr<CellColorHandler> pCellColorHandler( new CellColorHandler); + pProperties->resolve( *pCellColorHandler ); + TablePropertyMapPtr pTablePropMap( new TablePropertyMap ); + insertTableProps( pCellColorHandler->getProperties() ); + } + } +*/ + break; + case NS_ooxml::LN_CT_TcPrBase_shd: + { + // each color sprm contains as much colors as cells are in a row + //LN_CT_TcPrBase_shd: cell shading contains: LN_CT_Shd_val, LN_CT_Shd_fill, LN_CT_Shd_color + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pCellColorHandler = std::make_shared<CellColorHandler>(); + pCellColorHandler->enableInteropGrabBag("shd"); //enable to store shd unsupported props in grab bag + pProperties->resolve( *pCellColorHandler ); + TablePropertyMapPtr pPropertyMap = pCellColorHandler->getProperties(); + beans::PropertyValue aGrabBag = pCellColorHandler->getInteropGrabBag(); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(aGrabBag); + pPropertyMap->Insert( PROP_CELL_INTEROP_GRAB_BAG, aGrabBag.Value ); + cellProps( pPropertyMap ); + } + } + break; +//OOXML table properties + case NS_ooxml::LN_CT_TblPrBase_tblCellMar: //cell margins + { + //contains LN_CT_TblCellMar_top, LN_CT_TblCellMar_left, LN_CT_TblCellMar_bottom, LN_CT_TblCellMar_right + // LN_CT_TblCellMar_start, LN_CT_TblCellMar_end + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + auto pCellMarginHandler = std::make_shared<CellMarginHandler>(); + if (m_pCurrentInteropGrabBag) + pCellMarginHandler->enableInteropGrabBag("tblCellMar"); + pProperties->resolve( *pCellMarginHandler ); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(pCellMarginHandler->getInteropGrabBag()); + TablePropertyMapPtr pMarginProps( new TablePropertyMap ); + if( pCellMarginHandler->m_bTopMarginValid ) + pMarginProps->setValue( TablePropertyMap::CELL_MAR_TOP, pCellMarginHandler->m_nTopMargin ); + if( pCellMarginHandler->m_bBottomMarginValid ) + pMarginProps->setValue( TablePropertyMap::CELL_MAR_BOTTOM, pCellMarginHandler->m_nBottomMargin ); + if( pCellMarginHandler->m_bLeftMarginValid ) + pMarginProps->setValue( TablePropertyMap::CELL_MAR_LEFT, pCellMarginHandler->m_nLeftMargin ); + if( pCellMarginHandler->m_bRightMarginValid ) + pMarginProps->setValue( TablePropertyMap::CELL_MAR_RIGHT, pCellMarginHandler->m_nRightMargin ); + insertTableProps(pMarginProps); + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblInd: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + { + MeasureHandlerPtr pHandler(new MeasureHandler); + if (m_pCurrentInteropGrabBag) + pHandler->enableInteropGrabBag("tblInd"); + pProperties->resolve( *pHandler ); + if (m_pCurrentInteropGrabBag) + m_pCurrentInteropGrabBag->push_back(pHandler->getInteropGrabBag()); + TablePropertyMapPtr pTblIndMap(new TablePropertyMap); + sal_uInt32 nTblInd = pHandler->getMeasureValue(); + pTblIndMap->setValue( TablePropertyMap::LEFT_MARGIN, nTblInd); + insertTableProps(pTblIndMap); + } + } + break; + case NS_ooxml::LN_CT_TcPrBase_hideMark: + if (nIntValue) + { + TablePropertyMapPtr pPropMap(new TablePropertyMap()); + pPropMap->Insert(PROP_CELL_HIDE_MARK, uno::Any(nIntValue)); + cellProps(pPropMap); + } + break; + default: + // Not handled here, give the next handler a chance. + bRet = false; + // However, these logically belong here, so save the value if necessary. + switch (nSprmId) + { + case NS_ooxml::LN_CT_TblPrBase_tblStyleRowBandSize: + case NS_ooxml::LN_CT_TblPrBase_tblStyleColBandSize: + if (m_pCurrentInteropGrabBag) + { + beans::PropertyValue aValue; + aValue.Name = (nSprmId == NS_ooxml::LN_CT_TblPrBase_tblStyleRowBandSize ? std::u16string_view(u"tblStyleRowBandSize") : std::u16string_view(u"tblStyleColBandSize")); + aValue.Value <<= nIntValue; + m_pCurrentInteropGrabBag->push_back(aValue); + } + break; + } + break; + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif + + return bRet; + } + + void TablePropertiesHandler::SetInteropGrabBag(std::vector<beans::PropertyValue>& rValue) + { + m_pCurrentInteropGrabBag = &rValue; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TablePropertiesHandler.hxx b/writerfilter/source/dmapper/TablePropertiesHandler.hxx new file mode 100644 index 000000000..bd0901356 --- /dev/null +++ b/writerfilter/source/dmapper/TablePropertiesHandler.hxx @@ -0,0 +1,94 @@ +/* -*- 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 . + */ + +#pragma once + +#include "PropertyMap.hxx" + +#include "TableManager.hxx" +#include <dmapper/resourcemodel.hxx> + +#include <vector> + +namespace writerfilter::dmapper { + +class DomainMapper; + +class TablePropertiesHandler final : public virtual SvRefBase +{ +private: + PropertyMapPtr m_pCurrentProperties; + std::vector<css::beans::PropertyValue>* m_pCurrentInteropGrabBag; + TableManager* m_pTableManager; + +public: + TablePropertiesHandler(); + + bool sprm(Sprm & sprm); + + void SetTableManager( TableManager* pTableManager ) + { + m_pTableManager = pTableManager; + }; + + void SetProperties( PropertyMapPtr pProperties ) + { + m_pCurrentProperties = pProperties; + }; + + void SetInteropGrabBag(std::vector<css::beans::PropertyValue>& rValue); + +private: + + void cellProps( TablePropertyMapPtr pProps ) + { + if ( m_pTableManager ) + m_pTableManager->cellProps( pProps ); + else + m_pCurrentProperties->InsertProps(pProps.get()); + }; + + void insertRowProps( TablePropertyMapPtr pProps ) + { + if ( m_pTableManager ) + m_pTableManager->insertRowProps( pProps ); + else + m_pCurrentProperties->InsertProps(pProps.get()); + }; + + void tableExceptionProps( TablePropertyMapPtr pProps ) + { + if ( m_pTableManager ) + m_pTableManager->tableExceptionProps( pProps ); + else + m_pCurrentProperties->InsertProps(pProps.get()); + }; + + void insertTableProps( TablePropertyMapPtr pProps ) + { + if ( m_pTableManager ) + m_pTableManager->insertTableProps( pProps ); + else + m_pCurrentProperties->InsertProps(pProps.get()); + }; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TagLogger.cxx b/writerfilter/source/dmapper/TagLogger.cxx new file mode 100644 index 000000000..607f01aaf --- /dev/null +++ b/writerfilter/source/dmapper/TagLogger.cxx @@ -0,0 +1,230 @@ +/* -*- 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 <string.h> +#include "TagLogger.hxx" +#ifdef DBG_UTIL +#include <unotools/pathoptions.hxx> +#endif + +using namespace css; + +namespace writerfilter +{ + TagLogger::TagLogger() + : pWriter( nullptr ), pName( "DOMAINMAPPER" ) + { + } + + TagLogger::~TagLogger() + { + pWriter = nullptr; + pName = nullptr; + } + +#ifdef DBG_UTIL + void TagLogger::setFileName( const std::string & filename ) + { + if ( pWriter ) + endDocument(); + + std::string fileName; + char * temp = getenv("TAGLOGGERTMP"); + + if (temp != nullptr) + fileName += temp; + else + fileName += SvtPathOptions().GetTempPath().toUtf8().getStr(); + + std::string sPrefix = filename; + size_t nLastSlash = sPrefix.find_last_of('/'); + size_t nLastBackslash = sPrefix.find_last_of('\\'); + size_t nCutPos = nLastSlash; + if (nLastBackslash < nCutPos) + nCutPos = nLastBackslash; + if (nCutPos < sPrefix.size()) + sPrefix = sPrefix.substr(nCutPos + 1); + + fileName += "/"; + fileName += sPrefix; + fileName += "."; + fileName += pName; + fileName += ".xml"; + + pWriter = xmlNewTextWriterFilename( fileName.c_str(), 0 ); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + xmlTextWriterSetIndent( pWriter, 4 ); + } + + void TagLogger::startDocument() + { + if (!pWriter) + return; + (void)xmlTextWriterStartDocument( pWriter, nullptr, nullptr, nullptr ); + (void)xmlTextWriterStartElement( pWriter, BAD_CAST( "root" ) ); + } + + void TagLogger::endDocument() + { + if (!pWriter) + return; + (void)xmlTextWriterEndDocument( pWriter ); + xmlFreeTextWriter( pWriter ); + pWriter = nullptr; + } + +#endif + + TagLogger& TagLogger::getInstance() + { + static TagLogger theTagLogger; + return theTagLogger; + } + +#ifdef DBG_UTIL + void TagLogger::element(const std::string & name) + { + startElement(name); + endElement(); + } + + void TagLogger::unoPropertySet(const uno::Reference<beans::XPropertySet>& rPropSet) + { + uno::Reference<beans::XPropertySetInfo> xPropSetInfo(rPropSet->getPropertySetInfo()); + const uno::Sequence<beans::Property> aProps(xPropSetInfo->getProperties()); + + startElement( "unoPropertySet" ); + + for (beans::Property const & prop : aProps) + { + startElement( "property" ); + OUString sName(prop.Name); + + attribute( "name", sName ); + try + { + attribute( "value", rPropSet->getPropertyValue( sName ) ); + } + catch (const uno::Exception &) + { + startElement( "exception" ); + + chars(std::string("getPropertyValue(\"")); + chars(sName); + chars(std::string("\")")); + + endElement( ); + } + endElement( ); + } + endElement( ); + } + + void TagLogger::startElement(const std::string & name) + { + if (!pWriter) + return; + xmlChar* xmlName = xmlCharStrdup( name.c_str() ); + (void)xmlTextWriterStartElement( pWriter, xmlName ); + xmlFree( xmlName ); + } +#endif + + void TagLogger::attribute(const std::string & name, const std::string & value) + { + if (!pWriter) + return; + xmlChar* xmlName = xmlCharStrdup( name.c_str() ); + xmlChar* xmlValue = xmlCharStrdup( value.c_str() ); + (void)xmlTextWriterWriteAttribute( pWriter, xmlName, xmlValue ); + + xmlFree( xmlValue ); + xmlFree( xmlName ); + } + +#ifdef DBG_UTIL + void TagLogger::attribute(const std::string & name, std::u16string_view value) + { + attribute( name, OUStringToOString( value, RTL_TEXTENCODING_ASCII_US ).getStr() ); + } + + void TagLogger::attribute(const std::string & name, sal_uInt32 value) + { + if (!pWriter) + return; + xmlChar* xmlName = xmlCharStrdup( name.c_str() ); + (void)xmlTextWriterWriteFormatAttribute( pWriter, xmlName, + "%" SAL_PRIuUINT32, value ); + xmlFree( xmlName ); + } + + void TagLogger::attribute(const std::string & name, const uno::Any& aAny) + { + if (!pWriter) + return; + + sal_Int32 nInt = 0; + float nFloat = 0.0; + OUString aStr; + + xmlChar* xmlName = xmlCharStrdup( name.c_str() ); + if ( aAny >>= nInt ) + { + (void)xmlTextWriterWriteFormatAttribute( pWriter, xmlName, + "%" SAL_PRIdINT32, nInt ); + } + else if ( aAny >>= nFloat ) + { + (void)xmlTextWriterWriteFormatAttribute( pWriter, xmlName, + "%f", nFloat ); + } + else if ( aAny >>= aStr ) + { + attribute( name, aStr ); + } + xmlFree( xmlName ); + } + + void TagLogger::chars(const std::string & rChars) + { + if (!pWriter) + return; + xmlChar* xmlChars = xmlCharStrdup( rChars.c_str() ); + (void)xmlTextWriterWriteString( pWriter, xmlChars ); + xmlFree( xmlChars ); + } + + void TagLogger::chars(std::u16string_view rChars) + { + chars(OUStringToOString(rChars, RTL_TEXTENCODING_ASCII_US).getStr()); + } + + void TagLogger::endElement() + { + if (!pWriter) + return; + (void)xmlTextWriterEndElement( pWriter ); + } + +#endif + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TagLogger.hxx b/writerfilter/source/dmapper/TagLogger.hxx new file mode 100644 index 000000000..33da346ee --- /dev/null +++ b/writerfilter/source/dmapper/TagLogger.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <tools/ref.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <string> +#include <string_view> +#include <libxml/xmlwriter.h> + +namespace writerfilter +{ + + class TagLogger + { + private: + xmlTextWriterPtr pWriter; + const char* pName; + + public: + explicit TagLogger(); + ~TagLogger(); + + static TagLogger& getInstance(); + +#ifdef DBG_UTIL + void setFileName(const std::string & filename); + void startDocument(); + void endDocument(); + + void element(const std::string & name); + void unoPropertySet(const css::uno::Reference<css::beans::XPropertySet>& rPropSet); + void startElement(const std::string & name); +#endif + void attribute(const std::string & name, const std::string & value); +#ifdef DBG_UTIL + void attribute(const std::string & name, std::u16string_view value); + void attribute(const std::string & name, sal_uInt32 value); + void attribute(const std::string & name, const css::uno::Any& aAny); + void chars(const std::string & chars); + void chars(std::u16string_view chars); + void endElement(); +#endif + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TblStylePrHandler.cxx b/writerfilter/source/dmapper/TblStylePrHandler.cxx new file mode 100644 index 000000000..13656e169 --- /dev/null +++ b/writerfilter/source/dmapper/TblStylePrHandler.cxx @@ -0,0 +1,259 @@ +/* -*- 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 "TblStylePrHandler.hxx" +#include "TagLogger.hxx" +#include "CellMarginHandler.hxx" +#include "PropertyMap.hxx" +#include "MeasureHandler.hxx" +#include <ooxml/resourceids.hxx> +#include <comphelper/sequence.hxx> + + +using namespace css; + +namespace writerfilter::dmapper { + +TblStylePrHandler::TblStylePrHandler( DomainMapper & rDMapper ) : +LoggedProperties("TblStylePrHandler"), +m_rDMapper( rDMapper ), +m_pTablePropsHandler(new TablePropertiesHandler()), +m_nType( TBL_STYLE_UNKNOWN ), +m_pProperties( new PropertyMap ) +{ +} + +TblStylePrHandler::~TblStylePrHandler( ) +{ +} + +OUString TblStylePrHandler::getTypeString() const +{ + switch (m_nType) + { + case TBL_STYLE_WHOLETABLE: return "wholeTable"; + case TBL_STYLE_FIRSTROW: return "firstRow"; + case TBL_STYLE_LASTROW: return "lastRow"; + case TBL_STYLE_FIRSTCOL: return "firstCol"; + case TBL_STYLE_LASTCOL: return "lastCol"; + case TBL_STYLE_BAND1VERT: return "band1Vert"; + case TBL_STYLE_BAND2VERT: return "band2Vert"; + case TBL_STYLE_BAND1HORZ: return "band1Horz"; + case TBL_STYLE_BAND2HORZ: return "band2Horz"; + case TBL_STYLE_NECELL: return "neCell"; + case TBL_STYLE_NWCELL: return "nwCell"; + case TBL_STYLE_SECELL: return "seCell"; + case TBL_STYLE_SWCELL: return "swCell"; + default: break; + } + return OUString(); +} + +void TblStylePrHandler::lcl_attribute(Id rName, Value & rVal) +{ + + switch ( rName ) + { + case NS_ooxml::LN_CT_TblStyleOverrideType: + { + switch (rVal.getInt()) + { + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_wholeTable: + m_nType = TBL_STYLE_WHOLETABLE; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_firstRow: + m_nType = TBL_STYLE_FIRSTROW; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_lastRow: + m_nType = TBL_STYLE_LASTROW; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_firstCol: + m_nType = TBL_STYLE_FIRSTCOL; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_lastCol: + m_nType = TBL_STYLE_LASTCOL; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_band1Vert: + m_nType = TBL_STYLE_BAND1VERT; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_band2Vert: + m_nType = TBL_STYLE_BAND2VERT; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_band1Horz: + m_nType = TBL_STYLE_BAND1HORZ; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_band2Horz: + m_nType = TBL_STYLE_BAND2HORZ; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_neCell: + m_nType = TBL_STYLE_NECELL; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_nwCell: + m_nType = TBL_STYLE_NWCELL; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_seCell: + m_nType = TBL_STYLE_SECELL; + break; + case NS_ooxml::LN_Value_ST_TblStyleOverrideType_swCell: + m_nType = TBL_STYLE_SWCELL; + break; + } + } + break; + } +} + +void TblStylePrHandler::lcl_sprm(Sprm & rSprm) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("TblStylePrHandler.sprm"); + TagLogger::getInstance().attribute("sprm", rSprm.toString()); +#endif + + switch ( rSprm.getId( ) ) + { + case NS_ooxml::LN_CT_PPrBase: + case NS_ooxml::LN_EG_RPrBase: + case NS_ooxml::LN_CT_TblPrBase: + case NS_ooxml::LN_CT_TrPrBase: + case NS_ooxml::LN_CT_TcPrBase: + { + std::vector<beans::PropertyValue> aSavedGrabBag; + bool bGrabBag = rSprm.getId() == NS_ooxml::LN_CT_PPrBase || + rSprm.getId() == NS_ooxml::LN_EG_RPrBase || + rSprm.getId() == NS_ooxml::LN_CT_TblPrBase || + rSprm.getId() == NS_ooxml::LN_CT_TrPrBase || + rSprm.getId() == NS_ooxml::LN_CT_TcPrBase; + if (bGrabBag) + { + std::swap(aSavedGrabBag, m_aInteropGrabBag); + } + resolveSprmProps( rSprm ); + if (bGrabBag) + { + if (rSprm.getId() == NS_ooxml::LN_CT_PPrBase) + aSavedGrabBag.push_back(getInteropGrabBag("pPr")); + else if (rSprm.getId() == NS_ooxml::LN_EG_RPrBase) + aSavedGrabBag.push_back(getInteropGrabBag("rPr")); + else if (rSprm.getId() == NS_ooxml::LN_CT_TblPrBase) + aSavedGrabBag.push_back(getInteropGrabBag("tblPr")); + else if (rSprm.getId() == NS_ooxml::LN_CT_TrPrBase) + aSavedGrabBag.push_back(getInteropGrabBag("trPr")); + else if (rSprm.getId() == NS_ooxml::LN_CT_TcPrBase) + aSavedGrabBag.push_back(getInteropGrabBag("tcPr")); + std::swap(m_aInteropGrabBag, aSavedGrabBag); + } + } + break; + case NS_ooxml::LN_CT_TrPrBase_tblHeader: + { + m_pProperties->Insert( PROP_HEADER_ROW_COUNT, uno::Any(sal_Int32(1))); + beans::PropertyValue aValue; + aValue.Name = "tblHeader"; + aValue.Value <<= true; + m_aInteropGrabBag.push_back(aValue); + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblInd: + { + //contains unit and value + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + { + MeasureHandlerPtr pMeasureHandler( new MeasureHandler ); + pProperties->resolve(*pMeasureHandler); + TablePropertyMapPtr pPropMap( new TablePropertyMap ); + pPropMap->setValue( TablePropertyMap::LEFT_MARGIN, pMeasureHandler->getMeasureValue() ); + m_pProperties->Insert( PROP_LEFT_MARGIN, uno::Any(pMeasureHandler->getMeasureValue()) ); + } + } + break; + case NS_ooxml::LN_CT_TblPrBase_tblCellMar: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if ( pProperties ) + { + auto pCellMarginHandler = std::make_shared<CellMarginHandler>(); + pCellMarginHandler->enableInteropGrabBag("tblCellMar"); + pProperties->resolve( *pCellMarginHandler ); + m_aInteropGrabBag.push_back(pCellMarginHandler->getInteropGrabBag()); + + if( pCellMarginHandler->m_bTopMarginValid ) + m_pProperties->Insert( META_PROP_CELL_MAR_TOP, uno::Any(pCellMarginHandler->m_nTopMargin) ); + if( pCellMarginHandler->m_bBottomMarginValid ) + m_pProperties->Insert( META_PROP_CELL_MAR_BOTTOM, uno::Any(pCellMarginHandler->m_nBottomMargin) ); + if( pCellMarginHandler->m_bLeftMarginValid ) + m_pProperties->Insert( META_PROP_CELL_MAR_LEFT, uno::Any(pCellMarginHandler->m_nLeftMargin) ); + if( pCellMarginHandler->m_bRightMarginValid ) + m_pProperties->Insert( META_PROP_CELL_MAR_RIGHT, uno::Any(pCellMarginHandler->m_nRightMargin) ); + } + } + break; + default: + // Tables specific properties have to handled here + m_pTablePropsHandler->SetProperties( m_pProperties ); + m_pTablePropsHandler->SetInteropGrabBag(m_aInteropGrabBag); + bool bRet = m_pTablePropsHandler->sprm( rSprm ); + + if ( !bRet ) + { + // The DomainMapper can handle some of the properties + m_rDMapper.PushStyleSheetProperties( m_pProperties, true ); + // Just pass a non-empty string, the array will have a single element anyway. + m_rDMapper.enableInteropGrabBag("TblStylePrHandler"); + m_rDMapper.sprm( rSprm ); + uno::Sequence<beans::PropertyValue> aGrabBag = m_rDMapper.getInteropGrabBag().Value.get< uno::Sequence<beans::PropertyValue> >(); + if (aGrabBag.hasElements()) + m_aInteropGrabBag.push_back(aGrabBag[0]); + m_rDMapper.PopStyleSheetProperties( true ); + } + } + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void TblStylePrHandler::resolveSprmProps(Sprm & rSprm) +{ + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + pProperties->resolve(*this); +} + +void TblStylePrHandler::appendInteropGrabBag(const OUString& aKey, const OUString& aValue) +{ + beans::PropertyValue aProperty; + aProperty.Name = aKey; + aProperty.Value <<= aValue; + m_aInteropGrabBag.push_back(aProperty); +} + +beans::PropertyValue TblStylePrHandler::getInteropGrabBag(const OUString& aName) +{ + beans::PropertyValue aRet; + aRet.Name = aName; + + aRet.Value <<= comphelper::containerToSequence(m_aInteropGrabBag); + return aRet; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TblStylePrHandler.hxx b/writerfilter/source/dmapper/TblStylePrHandler.hxx new file mode 100644 index 000000000..4be7d379a --- /dev/null +++ b/writerfilter/source/dmapper/TblStylePrHandler.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include "TablePropertiesHandler.hxx" + +#include "DomainMapper.hxx" +#include "LoggedResources.hxx" +#include <memory> +#include <vector> + +namespace writerfilter::dmapper { + +class DomainMapper; + +enum TblStyleType +{ + TBL_STYLE_UNKNOWN, + TBL_STYLE_WHOLETABLE, + TBL_STYLE_FIRSTROW, + TBL_STYLE_LASTROW, + TBL_STYLE_FIRSTCOL, + TBL_STYLE_LASTCOL, + TBL_STYLE_BAND1VERT, + TBL_STYLE_BAND2VERT, + TBL_STYLE_BAND1HORZ, + TBL_STYLE_BAND2HORZ, + TBL_STYLE_NECELL, + TBL_STYLE_NWCELL, + TBL_STYLE_SECELL, + TBL_STYLE_SWCELL +}; + +class TblStylePrHandler : public LoggedProperties +{ +private: + DomainMapper & m_rDMapper; + std::unique_ptr<TablePropertiesHandler> m_pTablePropsHandler; + + TblStyleType m_nType; + PropertyMapPtr m_pProperties; + std::vector<css::beans::PropertyValue> m_aInteropGrabBag; + + // Properties + virtual void lcl_attribute(Id Name, Value & val) override; + virtual void lcl_sprm(Sprm & sprm) override; + +public: + explicit TblStylePrHandler( DomainMapper & rDMapper ); + virtual ~TblStylePrHandler( ) override; + + const PropertyMapPtr& getProperties() const { return m_pProperties; }; + TblStyleType getType() const { return m_nType; }; + OUString getTypeString() const; + void appendInteropGrabBag(const OUString& aKey, const OUString& aValue); + css::beans::PropertyValue getInteropGrabBag(const OUString& aName); + +private: + + void resolveSprmProps(Sprm & rSprm); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TextEffectsHandler.cxx b/writerfilter/source/dmapper/TextEffectsHandler.cxx new file mode 100644 index 000000000..cce02393c --- /dev/null +++ b/writerfilter/source/dmapper/TextEffectsHandler.cxx @@ -0,0 +1,804 @@ +/* -*- 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/. + * + */ + +#include <sal/config.h> + +#include <map> + +#include "TextEffectsHandler.hxx" + +#include <rtl/ustrbuf.hxx> +#include <comphelper/string.hxx> +#include <ooxml/resourceids.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <oox/drawingml/drawingmltypes.hxx> + +namespace writerfilter::dmapper +{ + +using namespace com::sun::star; + +namespace +{ + +OUString lclGetNameForElementId(sal_uInt32 aId) +{ + static std::map<sal_uInt32, OUString> aIdMap; + if(aIdMap.empty()) + { + aIdMap[NS_ooxml::LN_EG_ColorChoice_srgbClr] = "srgbClr"; + aIdMap[NS_ooxml::LN_EG_ColorChoice_schemeClr] = "schemeClr"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_tint] = "tint"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_shade] = "shade"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_alpha] = "alpha"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_hueMod] = "hueMod"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_sat] = "sat"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_satOff] = "satOff"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_satMod] = "satMod"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_lum] = "lum"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_lumOff] = "lumOff"; + aIdMap[NS_ooxml::LN_EG_ColorTransform_lumMod] = "lumMod"; + aIdMap[NS_ooxml::LN_EG_FillProperties_noFill] = "noFill"; + aIdMap[NS_ooxml::LN_EG_FillProperties_solidFill] = "solidFill"; + aIdMap[NS_ooxml::LN_EG_FillProperties_gradFill] = "gradFill"; + aIdMap[NS_ooxml::LN_CT_GradientFillProperties_gsLst] = "gsLst"; + aIdMap[NS_ooxml::LN_CT_GradientStopList_gs] = "gs"; + aIdMap[NS_ooxml::LN_CT_GradientStop_pos] = "pos"; + aIdMap[NS_ooxml::LN_EG_ShadeProperties_lin] = "lin"; + aIdMap[NS_ooxml::LN_EG_ShadeProperties_path] = "path"; + aIdMap[NS_ooxml::LN_CT_PathShadeProperties_fillToRect] = "fillToRect"; + aIdMap[NS_ooxml::LN_EG_LineDashProperties_prstDash] = "prstDash"; + aIdMap[NS_ooxml::LN_EG_LineJoinProperties_round] = "round"; + aIdMap[NS_ooxml::LN_EG_LineJoinProperties_bevel] = "bevel"; + aIdMap[NS_ooxml::LN_EG_LineJoinProperties_miter] = "miter"; + aIdMap[NS_ooxml::LN_CT_Scene3D_camera] = "camera"; + aIdMap[NS_ooxml::LN_CT_Scene3D_lightRig] = "lightRig"; + aIdMap[NS_ooxml::LN_CT_LightRig_rot] = "rot"; + aIdMap[NS_ooxml::LN_CT_Props3D_bevelT] = "bevelT"; + aIdMap[NS_ooxml::LN_CT_Props3D_bevelB] = "bevelB"; + aIdMap[NS_ooxml::LN_CT_Props3D_extrusionClr] = "extrusionClr"; + aIdMap[NS_ooxml::LN_CT_Props3D_contourClr] = "contourClr"; + aIdMap[NS_ooxml::LN_CT_StylisticSets_styleSet] = "styleSet"; + aIdMap[NS_ooxml::LN_cntxtAlts_cntxtAlts] = "cntxtAlts"; + } + return aIdMap[aId]; +} + +constexpr OUStringLiteral constAttributesSequenceName = u"attributes"; + +} + +OUString TextEffectsHandler::getSchemeColorValTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_SchemeColorVal_bg1: return "bg1"; + case NS_ooxml::LN_ST_SchemeColorVal_tx1: return "tx1"; + case NS_ooxml::LN_ST_SchemeColorVal_bg2: return "bg2"; + case NS_ooxml::LN_ST_SchemeColorVal_tx2: return "tx2"; + case NS_ooxml::LN_ST_SchemeColorVal_accent1: return "accent1"; + case NS_ooxml::LN_ST_SchemeColorVal_accent2: return "accent2"; + case NS_ooxml::LN_ST_SchemeColorVal_accent3: return "accent3"; + case NS_ooxml::LN_ST_SchemeColorVal_accent4: return "accent4"; + case NS_ooxml::LN_ST_SchemeColorVal_accent5: return "accent5"; + case NS_ooxml::LN_ST_SchemeColorVal_accent6: return "accent6"; + case NS_ooxml::LN_ST_SchemeColorVal_hlink: return "hlink"; + case NS_ooxml::LN_ST_SchemeColorVal_folHlink: return "folHlink"; + case NS_ooxml::LN_ST_SchemeColorVal_dk1: return "dk1"; + case NS_ooxml::LN_ST_SchemeColorVal_lt1: return "lt1"; + case NS_ooxml::LN_ST_SchemeColorVal_dk2: return "dk2"; + case NS_ooxml::LN_ST_SchemeColorVal_lt2: return "lt2"; + case NS_ooxml::LN_ST_SchemeColorVal_phClr: return "phClr"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getRectAlignmentString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_RectAlignment_none: return "none"; + case NS_ooxml::LN_ST_RectAlignment_tl: return "tl"; + case NS_ooxml::LN_ST_RectAlignment_t: return "t"; + case NS_ooxml::LN_ST_RectAlignment_tr: return "tr"; + case NS_ooxml::LN_ST_RectAlignment_l: return "l"; + case NS_ooxml::LN_ST_RectAlignment_ctr: return "ctr"; + case NS_ooxml::LN_ST_RectAlignment_r: return "r"; + case NS_ooxml::LN_ST_RectAlignment_bl: return "bl"; + case NS_ooxml::LN_ST_RectAlignment_b: return "b"; + case NS_ooxml::LN_ST_RectAlignment_br: return "br"; + + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getLineCapString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_LineCap_rnd: return "rnd"; + case NS_ooxml::LN_ST_LineCap_sq: return "sq"; + case NS_ooxml::LN_ST_LineCap_flat: return "flat"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getCompoundLineString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_CompoundLine_sng: return "sng"; + case NS_ooxml::LN_ST_CompoundLine_dbl: return "dbl"; + case NS_ooxml::LN_ST_CompoundLine_thickThin: return "thickThin"; + case NS_ooxml::LN_ST_CompoundLine_thinThick: return "thinThick"; + case NS_ooxml::LN_ST_CompoundLine_tri: return "tri"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getPenAlignmentString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_PenAlignment_ctr: return "ctr"; + case NS_ooxml::LN_ST_PenAlignment_in: return "in"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getOnOffString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_OnOff_true: return "true"; + case NS_ooxml::LN_ST_OnOff_false: return "false"; + case NS_ooxml::LN_ST_OnOff_1: return "1"; + case NS_ooxml::LN_ST_OnOff_0: return "0"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getPathShadeTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_PathShadeType_shape: return "shape"; + case NS_ooxml::LN_ST_PathShadeType_circle: return "circle"; + case NS_ooxml::LN_ST_PathShadeType_rect: return "rect"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getPresetLineDashValString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_PresetLineDashVal_solid: return "solid"; + case NS_ooxml::LN_ST_PresetLineDashVal_dot: return "dot"; + case NS_ooxml::LN_ST_PresetLineDashVal_sysDot: return "sysDot"; + case NS_ooxml::LN_ST_PresetLineDashVal_dash: return "dash"; + case NS_ooxml::LN_ST_PresetLineDashVal_sysDash: return "sysDash"; + case NS_ooxml::LN_ST_PresetLineDashVal_lgDash: return "lgDash"; + case NS_ooxml::LN_ST_PresetLineDashVal_dashDot: return "dashDot"; + case NS_ooxml::LN_ST_PresetLineDashVal_sysDashDot: return "sysDashDot"; + case NS_ooxml::LN_ST_PresetLineDashVal_lgDashDot: return "lgDashDot"; + case NS_ooxml::LN_ST_PresetLineDashVal_lgDashDotDot: return "lgDashDotDot"; + case NS_ooxml::LN_ST_PresetLineDashVal_sysDashDotDot: return "sysDashDotDot"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getPresetCameraTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueTopLeft: return "legacyObliqueTopLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueTop: return "legacyObliqueTop"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueTopRight: return "legacyObliqueTopRight"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueLeft: return "legacyObliqueLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueFront: return "legacyObliqueFront"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueRight: return "legacyObliqueRight"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueBottomLeft: return "legacyObliqueBottomLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueBottom: return "legacyObliqueBottom"; + case NS_ooxml::LN_ST_PresetCameraType_legacyObliqueBottomRight: return "legacyObliqueBottomRight"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveTopLeft: return "legacyPerspectiveTopLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveTop: return "legacyPerspectiveTop"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveTopRight: return "legacyPerspectiveTopRight"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveLeft: return "legacyPerspectiveLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveFront: return "legacyPerspectiveFront"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveRight: return "legacyPerspectiveRight"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveBottomLeft: return "legacyPerspectiveBottomLeft"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveBottom: return "legacyPerspectiveBottom"; + case NS_ooxml::LN_ST_PresetCameraType_legacyPerspectiveBottomRight: return "legacyPerspectiveBottomRight"; + case NS_ooxml::LN_ST_PresetCameraType_orthographicFront: return "orthographicFront"; + case NS_ooxml::LN_ST_PresetCameraType_isometricTopUp: return "isometricTopUp"; + case NS_ooxml::LN_ST_PresetCameraType_isometricTopDown: return "isometricTopDown"; + case NS_ooxml::LN_ST_PresetCameraType_isometricBottomUp: return "isometricBottomUp"; + case NS_ooxml::LN_ST_PresetCameraType_isometricBottomDown: return "isometricBottomDown"; + case NS_ooxml::LN_ST_PresetCameraType_isometricLeftUp: return "isometricLeftUp"; + case NS_ooxml::LN_ST_PresetCameraType_isometricLeftDown: return "isometricLeftDown"; + case NS_ooxml::LN_ST_PresetCameraType_isometricRightUp: return "isometricRightUp"; + case NS_ooxml::LN_ST_PresetCameraType_isometricRightDown: return "isometricRightDown"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis1Left: return "isometricOffAxis1Left"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis1Right: return "isometricOffAxis1Right"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis1Top: return "isometricOffAxis1Top"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis2Left: return "isometricOffAxis2Left"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis2Right: return "isometricOffAxis2Right"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis2Top: return "isometricOffAxis2Top"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis3Left: return "isometricOffAxis3Left"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis3Right: return "isometricOffAxis3Right"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis3Bottom: return "isometricOffAxis3Bottom"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis4Left: return "isometricOffAxis4Left"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis4Right: return "isometricOffAxis4Right"; + case NS_ooxml::LN_ST_PresetCameraType_isometricOffAxis4Bottom: return "isometricOffAxis4Bottom"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueTopLeft: return "obliqueTopLeft"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueTop: return "obliqueTop"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueTopRight: return "obliqueTopRight"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueLeft: return "obliqueLeft"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueRight: return "obliqueRight"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueBottomLeft: return "obliqueBottomLeft"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueBottom: return "obliqueBottom"; + case NS_ooxml::LN_ST_PresetCameraType_obliqueBottomRight: return "obliqueBottomRight"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveFront: return "perspectiveFront"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveLeft: return "perspectiveLeft"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveRight: return "perspectiveRight"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveAbove: return "perspectiveAbove"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveBelow: return "perspectiveBelow"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveAboveLeftFacing: return "perspectiveAboveLeftFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveAboveRightFacing: return "perspectiveAboveRightFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveContrastingLeftFacing: return "perspectiveContrastingLeftFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveContrastingRightFacing: return "perspectiveContrastingRightFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveHeroicLeftFacing: return "perspectiveHeroicLeftFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveHeroicRightFacing: return "perspectiveHeroicRightFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveHeroicExtremeLeftFacing: return "perspectiveHeroicExtremeLeftFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveHeroicExtremeRightFacing: return "perspectiveHeroicExtremeRightFacing"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveRelaxed: return "perspectiveRelaxed"; + case NS_ooxml::LN_ST_PresetCameraType_perspectiveRelaxedModerately: return "perspectiveRelaxedModerately"; + default: break; + } + return OUString(); +} + + +OUString TextEffectsHandler::getLightRigTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_LightRigType_legacyFlat1: return "legacyFlat1"; + case NS_ooxml::LN_ST_LightRigType_legacyFlat2: return "legacyFlat2"; + case NS_ooxml::LN_ST_LightRigType_legacyFlat3: return "legacyFlat3"; + case NS_ooxml::LN_ST_LightRigType_legacyFlat4: return "legacyFlat4"; + case NS_ooxml::LN_ST_LightRigType_legacyNormal1: return "legacyNormal1"; + case NS_ooxml::LN_ST_LightRigType_legacyNormal2: return "legacyNormal2"; + case NS_ooxml::LN_ST_LightRigType_legacyNormal3: return "legacyNormal3"; + case NS_ooxml::LN_ST_LightRigType_legacyNormal4: return "legacyNormal4"; + case NS_ooxml::LN_ST_LightRigType_legacyHarsh1: return "legacyHarsh1"; + case NS_ooxml::LN_ST_LightRigType_legacyHarsh2: return "legacyHarsh2"; + case NS_ooxml::LN_ST_LightRigType_legacyHarsh3: return "legacyHarsh3"; + case NS_ooxml::LN_ST_LightRigType_legacyHarsh4: return "legacyHarsh4"; + case NS_ooxml::LN_ST_LightRigType_threePt: return "threePt"; + case NS_ooxml::LN_ST_LightRigType_balanced: return "balanced"; + case NS_ooxml::LN_ST_LightRigType_soft: return "soft"; + case NS_ooxml::LN_ST_LightRigType_harsh: return "harsh"; + case NS_ooxml::LN_ST_LightRigType_flood: return "flood"; + case NS_ooxml::LN_ST_LightRigType_contrasting: return "contrasting"; + case NS_ooxml::LN_ST_LightRigType_morning: return "morning"; + case NS_ooxml::LN_ST_LightRigType_sunrise: return "sunrise"; + case NS_ooxml::LN_ST_LightRigType_sunset: return "sunset"; + case NS_ooxml::LN_ST_LightRigType_chilly: return "chilly"; + case NS_ooxml::LN_ST_LightRigType_freezing: return "freezing"; + case NS_ooxml::LN_ST_LightRigType_flat: return "flat"; + case NS_ooxml::LN_ST_LightRigType_twoPt: return "twoPt"; + case NS_ooxml::LN_ST_LightRigType_glow: return "glow"; + case NS_ooxml::LN_ST_LightRigType_brightRoom: return "brightRoom"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getLightRigDirectionString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_LightRigDirection_tl: return "tl"; + case NS_ooxml::LN_ST_LightRigDirection_t: return "t"; + case NS_ooxml::LN_ST_LightRigDirection_tr: return "tr"; + case NS_ooxml::LN_ST_LightRigDirection_l: return "l"; + case NS_ooxml::LN_ST_LightRigDirection_r: return "r"; + case NS_ooxml::LN_ST_LightRigDirection_bl: return "bl"; + case NS_ooxml::LN_ST_LightRigDirection_b: return "b"; + case NS_ooxml::LN_ST_LightRigDirection_br: return "br"; + + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getBevelPresetTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_BevelPresetType_relaxedInset: return "relaxedInset"; + case NS_ooxml::LN_ST_BevelPresetType_circle: return "circle"; + case NS_ooxml::LN_ST_BevelPresetType_slope: return "slope"; + case NS_ooxml::LN_ST_BevelPresetType_cross: return "cross"; + case NS_ooxml::LN_ST_BevelPresetType_angle: return "angle"; + case NS_ooxml::LN_ST_BevelPresetType_softRound: return "softRound"; + case NS_ooxml::LN_ST_BevelPresetType_convex: return "convex"; + case NS_ooxml::LN_ST_BevelPresetType_coolSlant: return "coolSlant"; + case NS_ooxml::LN_ST_BevelPresetType_divot: return "divot"; + case NS_ooxml::LN_ST_BevelPresetType_riblet: return "riblet"; + case NS_ooxml::LN_ST_BevelPresetType_hardEdge: return "hardEdge"; + case NS_ooxml::LN_ST_BevelPresetType_artDeco: return "artDeco"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getPresetMaterialTypeString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_PresetMaterialType_legacyMatte: return "legacyMatte"; + case NS_ooxml::LN_ST_PresetMaterialType_legacyPlastic: return "legacyPlastic"; + case NS_ooxml::LN_ST_PresetMaterialType_legacyMetal: return "legacyMetal"; + case NS_ooxml::LN_ST_PresetMaterialType_legacyWireframe: return "legacyWireframe"; + case NS_ooxml::LN_ST_PresetMaterialType_matte: return "matte"; + case NS_ooxml::LN_ST_PresetMaterialType_plastic: return "plastic"; + case NS_ooxml::LN_ST_PresetMaterialType_metal: return "metal"; + case NS_ooxml::LN_ST_PresetMaterialType_warmMatte: return "warmMatte"; + case NS_ooxml::LN_ST_PresetMaterialType_translucentPowder: return "translucentPowder"; + case NS_ooxml::LN_ST_PresetMaterialType_powder: return "powder"; + case NS_ooxml::LN_ST_PresetMaterialType_dkEdge: return "dkEdge"; + case NS_ooxml::LN_ST_PresetMaterialType_softEdge: return "softEdge"; + case NS_ooxml::LN_ST_PresetMaterialType_clear: return "clear"; + case NS_ooxml::LN_ST_PresetMaterialType_flat: return "flat"; + case NS_ooxml::LN_ST_PresetMaterialType_softmetal: return "softmetal"; + case NS_ooxml::LN_ST_PresetMaterialType_none: return "none"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getLigaturesString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_Ligatures_none: return "none"; + case NS_ooxml::LN_ST_Ligatures_standard: return "standard"; + case NS_ooxml::LN_ST_Ligatures_contextual: return "contextual"; + case NS_ooxml::LN_ST_Ligatures_historical: return "historical"; + case NS_ooxml::LN_ST_Ligatures_discretional: return "discretional"; + case NS_ooxml::LN_ST_Ligatures_standardContextual: return "standardContextual"; + case NS_ooxml::LN_ST_Ligatures_standardHistorical: return "standardHistorical"; + case NS_ooxml::LN_ST_Ligatures_contextualHistorical: return "contextualHistorical"; + case NS_ooxml::LN_ST_Ligatures_standardDiscretional: return "standardDiscretional"; + case NS_ooxml::LN_ST_Ligatures_contextualDiscretional: return "contextualDiscretional"; + case NS_ooxml::LN_ST_Ligatures_historicalDiscretional: return "historicalDiscretional"; + case NS_ooxml::LN_ST_Ligatures_standardContextualHistorical: return "standardContextualHistorical"; + case NS_ooxml::LN_ST_Ligatures_standardContextualDiscretional: return "standardContextualDiscretional"; + case NS_ooxml::LN_ST_Ligatures_standardHistoricalDiscretional: return "standardHistoricalDiscretional"; + case NS_ooxml::LN_ST_Ligatures_contextualHistoricalDiscretional: return "contextualHistoricalDiscretional"; + case NS_ooxml::LN_ST_Ligatures_all: return "all"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getNumFormString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_NumForm_default: return "default"; + case NS_ooxml::LN_ST_NumForm_lining: return "lining"; + case NS_ooxml::LN_ST_NumForm_oldStyle: return "oldStyle"; + default: break; + } + return OUString(); +} + +OUString TextEffectsHandler::getNumSpacingString(sal_Int32 nType) +{ + switch (nType) + { + case NS_ooxml::LN_ST_NumSpacing_default: return "default"; + case NS_ooxml::LN_ST_NumSpacing_proportional: return "proportional"; + case NS_ooxml::LN_ST_NumSpacing_tabular: return "tabular"; + default: break; + } + return OUString(); +} + +void TextEffectsHandler::convertElementIdToPropertyId(sal_Int32 aElementId) +{ + switch(aElementId) + { + case NS_ooxml::LN_glow_glow: + maPropertyId = PROP_CHAR_GLOW_TEXT_EFFECT; + maElementName = "glow"; + break; + case NS_ooxml::LN_shadow_shadow: + maPropertyId = PROP_CHAR_SHADOW_TEXT_EFFECT; + maElementName = "shadow"; + break; + case NS_ooxml::LN_reflection_reflection: + maPropertyId = PROP_CHAR_REFLECTION_TEXT_EFFECT; + maElementName = "reflection"; + break; + case NS_ooxml::LN_textOutline_textOutline: + maPropertyId = PROP_CHAR_TEXTOUTLINE_TEXT_EFFECT; + maElementName = "textOutline"; + break; + case NS_ooxml::LN_textFill_textFill: + maPropertyId = PROP_CHAR_TEXTFILL_TEXT_EFFECT; + maElementName = "textFill"; + break; + case NS_ooxml::LN_scene3d_scene3d: + maPropertyId = PROP_CHAR_SCENE3D_TEXT_EFFECT; + maElementName = "scene3d"; + break; + case NS_ooxml::LN_props3d_props3d: + maPropertyId = PROP_CHAR_PROPS3D_TEXT_EFFECT; + maElementName = "props3d"; + break; + case NS_ooxml::LN_ligatures_ligatures: + maPropertyId = PROP_CHAR_LIGATURES_TEXT_EFFECT; + maElementName = "ligatures"; + break; + case NS_ooxml::LN_numForm_numForm: + maPropertyId = PROP_CHAR_NUMFORM_TEXT_EFFECT; + maElementName = "numForm"; + break; + case NS_ooxml::LN_numSpacing_numSpacing: + maPropertyId = PROP_CHAR_NUMSPACING_TEXT_EFFECT; + maElementName = "numSpacing"; + break; + case NS_ooxml::LN_stylisticSets_stylisticSets: + maPropertyId = PROP_CHAR_STYLISTICSETS_TEXT_EFFECT; + maElementName = "stylisticSets"; + break; + case NS_ooxml::LN_cntxtAlts_cntxtAlts: + maPropertyId = PROP_CHAR_CNTXTALTS_TEXT_EFFECT; + maElementName = "cntxtAlts"; + break; + default: + break; + } +} + +TextEffectsHandler::TextEffectsHandler(sal_uInt32 aElementId) : + LoggedProperties("TextEffectsHandler") +{ + convertElementIdToPropertyId(aElementId); + mpGrabBagStack.reset(new oox::GrabBagStack(maElementName)); +} + +TextEffectsHandler::~TextEffectsHandler() +{ +} + + +void TextEffectsHandler::lcl_attribute(Id aName, Value& aValue) +{ + if (mpGrabBagStack->getCurrentName() != constAttributesSequenceName) + mpGrabBagStack->push(constAttributesSequenceName); + + switch(aName) + { + case NS_ooxml::LN_CT_Percentage_val: + case NS_ooxml::LN_CT_PositiveFixedPercentage_val: + case NS_ooxml::LN_CT_PositivePercentage_val: + mpGrabBagStack->addInt32("val", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Glow_rad: + mpGrabBagStack->addInt32("rad", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_SchemeColor_val: + { + OUString aString = getSchemeColorValTypeString(sal_Int32(aValue.getInt())); + mpGrabBagStack->addString("val", aString); + } + break; + case NS_ooxml::LN_CT_SRgbColor_val: + { + OUString aBuffer = OUString::number(aValue.getInt(), 16); + OUStringBuffer aString; + comphelper::string::padToLength(aString, 6 - aBuffer.getLength(), '0'); + aString.append(aBuffer.getStr()); + mpGrabBagStack->addString("val", aString.makeStringAndClear().toAsciiUpperCase()); + } + break; + case NS_ooxml::LN_CT_Shadow_blurRad: + case NS_ooxml::LN_CT_Reflection_blurRad: + mpGrabBagStack->addInt32("blurRad", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Reflection_stA: + mpGrabBagStack->addInt32("stA", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Reflection_stPos: + mpGrabBagStack->addInt32("stPos", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Reflection_endA: + mpGrabBagStack->addInt32("endA", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Reflection_endPos: + mpGrabBagStack->addInt32("endPos", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_dist: + case NS_ooxml::LN_CT_Reflection_dist: + mpGrabBagStack->addInt32("dist", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_dir: + case NS_ooxml::LN_CT_Reflection_dir: + mpGrabBagStack->addInt32("dir", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Reflection_fadeDir: + mpGrabBagStack->addInt32("fadeDir", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_sx: + case NS_ooxml::LN_CT_Reflection_sx: + mpGrabBagStack->addInt32("sx", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_sy: + case NS_ooxml::LN_CT_Reflection_sy: + mpGrabBagStack->addInt32("sy", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_kx: + case NS_ooxml::LN_CT_Reflection_kx: + mpGrabBagStack->addInt32("kx", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_ky: + case NS_ooxml::LN_CT_Reflection_ky: + mpGrabBagStack->addInt32("ky", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Shadow_algn: + case NS_ooxml::LN_CT_Reflection_algn: + { + uno::Any aAny(getRectAlignmentString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("algn", aAny); + } + break; + case NS_ooxml::LN_CT_TextOutlineEffect_w: + mpGrabBagStack->addInt32("w", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_TextOutlineEffect_cap: + { + uno::Any aAny(getLineCapString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("cap", aAny); + } + break; + case NS_ooxml::LN_CT_TextOutlineEffect_cmpd: + { + uno::Any aAny(getCompoundLineString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("cmpd", aAny); + } + break; + case NS_ooxml::LN_CT_TextOutlineEffect_algn: + { + uno::Any aAny(getPenAlignmentString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("algn", aAny); + } + break; + case NS_ooxml::LN_CT_GradientStop_pos: + mpGrabBagStack->addInt32("pos", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_LinearShadeProperties_ang: + mpGrabBagStack->addInt32("ang", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_LinearShadeProperties_scaled: + { + uno::Any aAny(getOnOffString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("scaled", aAny); + } + break; + case NS_ooxml::LN_CT_PathShadeProperties_path: + { + uno::Any aAny(getPathShadeTypeString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("path", aAny); + } + break; + case NS_ooxml::LN_CT_RelativeRect_l: + mpGrabBagStack->addInt32("l", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_RelativeRect_t: + mpGrabBagStack->addInt32("t", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_RelativeRect_r: + mpGrabBagStack->addInt32("r", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_RelativeRect_b: + mpGrabBagStack->addInt32("b", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_PresetLineDashProperties_val: + { + uno::Any aAny(getPresetLineDashValString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("val", aAny); + } + break; + case NS_ooxml::LN_CT_LineJoinMiterProperties_lim: + mpGrabBagStack->addInt32("lim", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Camera_prst: + { + uno::Any aAny(getPresetCameraTypeString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("prst", aAny); + } + break; + case NS_ooxml::LN_CT_LightRig_rig: + { + uno::Any aAny(getLightRigTypeString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("rig", aAny); + } + break; + case NS_ooxml::LN_CT_LightRig_dir: + { + uno::Any aAny(getLightRigDirectionString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("dir", aAny); + } + break; + case NS_ooxml::LN_CT_SphereCoords_lat: + mpGrabBagStack->addInt32("lat", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_SphereCoords_lon: + mpGrabBagStack->addInt32("lon", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_SphereCoords_rev: + mpGrabBagStack->addInt32("rev", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Props3D_extrusionH: + mpGrabBagStack->addInt32("extrusionH", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Props3D_contourW: + mpGrabBagStack->addInt32("contourW", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Props3D_prstMaterial: + { + uno::Any aAny(getPresetMaterialTypeString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("prstMaterial", aAny); + } + break; + case NS_ooxml::LN_CT_Bevel_w: + mpGrabBagStack->addInt32("w", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Bevel_h: + mpGrabBagStack->addInt32("h", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_Bevel_prst: + { + uno::Any aAny(getBevelPresetTypeString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("prst", aAny); + } + break; + case NS_ooxml::LN_CT_Ligatures_val: + { + uno::Any aAny(getLigaturesString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("val", aAny); + } + break; + case NS_ooxml::LN_CT_NumForm_val: + { + uno::Any aAny(getNumFormString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("val", aAny); + } + break; + case NS_ooxml::LN_CT_NumSpacing_val: + { + uno::Any aAny(getNumSpacingString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("val", aAny); + } + break; + case NS_ooxml::LN_CT_StyleSet_id: + mpGrabBagStack->addInt32("id", sal_Int32(aValue.getInt())); + break; + case NS_ooxml::LN_CT_StyleSet_val: + case NS_ooxml::LN_CT_OnOff_val: + { + uno::Any aAny(getOnOffString(sal_Int32(aValue.getInt()))); + mpGrabBagStack->appendElement("val", aAny); + } + break; + default: + break; + } +} + +void TextEffectsHandler::lcl_sprm(Sprm& rSprm) +{ + if (mpGrabBagStack->getCurrentName() == constAttributesSequenceName) + mpGrabBagStack->pop(); + + sal_uInt32 nSprmId = rSprm.getId(); + OUString aElementName = lclGetNameForElementId(nSprmId); + if(aElementName.isEmpty()) + { + // Element is unknown -> leave. + return; + } + + mpGrabBagStack->push(aElementName); + + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( !pProperties ) + return; + + pProperties->resolve( *this ); + + if (mpGrabBagStack->getCurrentName() == constAttributesSequenceName) + mpGrabBagStack->pop(); + + mpGrabBagStack->pop(); +} + +beans::PropertyValue TextEffectsHandler::getInteropGrabBag() +{ + beans::PropertyValue aReturn = mpGrabBagStack->getRootProperty(); + mpGrabBagStack.reset(); + return aReturn; +} + +sal_uInt8 TextEffectsHandler::GetTextFillSolidFillAlpha(const css::beans::PropertyValue& rValue) +{ + if (rValue.Name != "textFill") + { + return 0; + } + + uno::Sequence<beans::PropertyValue> aPropertyValues; + rValue.Value >>= aPropertyValues; + comphelper::SequenceAsHashMap aMap(aPropertyValues); + auto it = aMap.find("solidFill"); + if (it == aMap.end()) + { + return 0; + } + + comphelper::SequenceAsHashMap aSolidFillMap(it->second); + it = aSolidFillMap.find("srgbClr"); + if (it == aSolidFillMap.end()) + { + return 0; + } + + comphelper::SequenceAsHashMap aSrgbClrMap(it->second); + it = aSrgbClrMap.find("alpha"); + if (it == aSrgbClrMap.end()) + { + return 0; + } + + comphelper::SequenceAsHashMap aAlphaMap(it->second); + it = aAlphaMap.find("attributes"); + if (it == aAlphaMap.end()) + { + return 0; + } + + comphelper::SequenceAsHashMap aAttributesMap(it->second); + it = aAttributesMap.find("val"); + if (it == aAttributesMap.end()) + { + return 0; + } + sal_Int32 nVal = 0; + it->second >>= nVal; + return nVal / oox::drawingml::PER_PERCENT; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TextEffectsHandler.hxx b/writerfilter/source/dmapper/TextEffectsHandler.hxx new file mode 100644 index 000000000..30a8435b2 --- /dev/null +++ b/writerfilter/source/dmapper/TextEffectsHandler.hxx @@ -0,0 +1,69 @@ +/* -*- 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/. + * + */ + +#pragma once + +#include "LoggedResources.hxx" + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include "PropertyIds.hxx" + +#include <oox/helper/grabbagstack.hxx> + +#include <memory> +#include <optional> + +namespace writerfilter::dmapper +{ +/// Class to process all text effects like glow, textOutline, ... +class TextEffectsHandler : public LoggedProperties +{ +private: + std::optional<PropertyIds> maPropertyId; + OUString maElementName; + std::unique_ptr<oox::GrabBagStack> mpGrabBagStack; + + void convertElementIdToPropertyId(sal_Int32 aElementId); + +public: + explicit TextEffectsHandler(sal_uInt32 aElementId); + virtual ~TextEffectsHandler() override; + + const std::optional<PropertyIds>& getGrabBagPropertyId() const { return maPropertyId; } + + css::beans::PropertyValue getInteropGrabBag(); + + static OUString getSchemeColorValTypeString(sal_Int32 nType); + static OUString getRectAlignmentString(sal_Int32 nType); + static OUString getLineCapString(sal_Int32 nType); + static OUString getCompoundLineString(sal_Int32 nType); + static OUString getPenAlignmentString(sal_Int32 nType); + static OUString getOnOffString(sal_Int32 nType); + static OUString getPathShadeTypeString(sal_Int32 nType); + static OUString getPresetLineDashValString(sal_Int32 nType); + static OUString getPresetCameraTypeString(sal_Int32 nType); + static OUString getLightRigTypeString(sal_Int32 nType); + static OUString getLightRigDirectionString(sal_Int32 nType); + static OUString getBevelPresetTypeString(sal_Int32 nType); + static OUString getPresetMaterialTypeString(sal_Int32 nType); + static OUString getLigaturesString(sal_Int32 nType); + static OUString getNumFormString(sal_Int32 nType); + static OUString getNumSpacingString(sal_Int32 nType); + + static sal_uInt8 GetTextFillSolidFillAlpha(const css::beans::PropertyValue& rValue); + + // LoggedProperties + virtual void lcl_attribute(Id aName, Value& aValue) override; + virtual void lcl_sprm(Sprm& sprm) override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ThemeTable.cxx b/writerfilter/source/dmapper/ThemeTable.cxx new file mode 100644 index 000000000..4d6ed2b3b --- /dev/null +++ b/writerfilter/source/dmapper/ThemeTable.cxx @@ -0,0 +1,563 @@ +/* -*- 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 "TagLogger.hxx" +#include "ThemeTable.hxx" +#include <i18nlangtag/languagetag.hxx> +#include <ooxml/resourceids.hxx> + +#include <map> + +using namespace com::sun::star; + +namespace writerfilter::dmapper +{ + +struct ThemeTable_Impl +{ + ThemeTable_Impl() : + m_currentThemeFontId(0), + m_supplementalFontId(0) + {} + std::map<sal_uInt32, std::map<sal_uInt32, OUString> > m_themeFontMap; + sal_uInt32 m_currentThemeFontId; + std::map<sal_uInt32, OUString> m_currentFontThemeEntry; + OUString m_supplementalFontName; + sal_uInt32 m_supplementalFontId; + OUString m_themeFontLangEastAsia; + OUString m_themeFontLangBidi; +}; + +ThemeTable::ThemeTable() +: LoggedProperties("ThemeTable") +, LoggedTable("ThemeTable") +, m_pImpl( new ThemeTable_Impl ) +{ + +} + +ThemeTable::~ThemeTable() +{ +} + +void ThemeTable::lcl_attribute(Id Name, Value & val) +{ + OUString sValue = val.getString(); + switch(Name) + { + case NS_ooxml::LN_CT_TextFont_typeface: + if (!sValue.isEmpty()) + m_pImpl->m_currentFontThemeEntry[m_pImpl->m_currentThemeFontId] = sValue; + break; + case NS_ooxml::LN_CT_SupplementalFont_script: + if (!sValue.isEmpty()) + { + if (sValue == m_pImpl->m_themeFontLangBidi) + m_pImpl->m_supplementalFontId = NS_ooxml::LN_CT_FontCollection_cs; + else if (sValue == m_pImpl->m_themeFontLangEastAsia) + m_pImpl->m_supplementalFontId = NS_ooxml::LN_CT_FontCollection_ea; + } + break; + case NS_ooxml::LN_CT_SupplementalFont_typeface: + if (!sValue.isEmpty()) + m_pImpl->m_supplementalFontName = sValue; + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } + if(m_pImpl->m_supplementalFontId && m_pImpl->m_supplementalFontName.getLength() > 0) + { + m_pImpl->m_currentFontThemeEntry[m_pImpl->m_supplementalFontId] = m_pImpl->m_supplementalFontName; + m_pImpl->m_supplementalFontName.clear(); + m_pImpl->m_supplementalFontId = 0; + } +} + +void ThemeTable::lcl_sprm(Sprm& rSprm) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("ThemeTable.sprm"); + TagLogger::getInstance().chars(rSprm.toString()); +#endif + + m_pImpl->m_supplementalFontName.clear(); + m_pImpl->m_supplementalFontId = 0; + + sal_uInt32 nSprmId = rSprm.getId(); + switch(nSprmId) + { + case NS_ooxml::LN_CT_BaseStyles_fontScheme: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_FontScheme_majorFont: + case NS_ooxml::LN_CT_FontScheme_minorFont: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + m_pImpl->m_currentFontThemeEntry = std::map<sal_uInt32, OUString>(); + if( pProperties ) + pProperties->resolve(*this); + m_pImpl->m_themeFontMap[nSprmId] = m_pImpl->m_currentFontThemeEntry; + } + break; + case NS_ooxml::LN_CT_FontCollection_latin: + case NS_ooxml::LN_CT_FontCollection_ea: + case NS_ooxml::LN_CT_FontCollection_cs: + { + m_pImpl->m_currentThemeFontId = nSprmId; + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if( pProperties ) + pProperties->resolve(*this); + } + break; + case NS_ooxml::LN_CT_FontCollection_font: + { + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties ) + pProperties->resolve(*this); + } + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +void ThemeTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement("ThemeTable.entry"); +#endif + + ref->resolve(*this); + +#ifdef DBG_UTIL + TagLogger::getInstance().endElement(); +#endif +} + +OUString ThemeTable::getStringForTheme(const Id id) +{ + switch (id) + { + case NS_ooxml::LN_Value_ST_Theme_majorEastAsia: + return "majorEastAsia"; + case NS_ooxml::LN_Value_ST_Theme_majorBidi: + return "majorBidi"; + case NS_ooxml::LN_Value_ST_Theme_majorAscii: + return "majorAscii"; + case NS_ooxml::LN_Value_ST_Theme_majorHAnsi: + return "majorHAnsi"; + case NS_ooxml::LN_Value_ST_Theme_minorEastAsia: + return "minorEastAsia"; + case NS_ooxml::LN_Value_ST_Theme_minorBidi: + return "minorBidi"; + case NS_ooxml::LN_Value_ST_Theme_minorAscii: + return "minorAscii"; + case NS_ooxml::LN_Value_ST_Theme_minorHAnsi: + return "minorHAnsi"; + } + return OUString(); +} +OUString ThemeTable::getFontNameForTheme(const Id id) const +{ + std::map<sal_uInt32, OUString> tmpThemeFontMap; + switch (id) + { + case NS_ooxml::LN_Value_ST_Theme_majorEastAsia: + case NS_ooxml::LN_Value_ST_Theme_majorBidi: + case NS_ooxml::LN_Value_ST_Theme_majorAscii: + case NS_ooxml::LN_Value_ST_Theme_majorHAnsi: + tmpThemeFontMap = m_pImpl->m_themeFontMap[NS_ooxml::LN_CT_FontScheme_majorFont]; + break; + case NS_ooxml::LN_Value_ST_Theme_minorEastAsia: + case NS_ooxml::LN_Value_ST_Theme_minorBidi: + case NS_ooxml::LN_Value_ST_Theme_minorAscii: + case NS_ooxml::LN_Value_ST_Theme_minorHAnsi: + tmpThemeFontMap = m_pImpl->m_themeFontMap[NS_ooxml::LN_CT_FontScheme_minorFont]; + break; + default: + return OUString(); + } + + switch (id) + { + case NS_ooxml::LN_Value_ST_Theme_majorAscii: + case NS_ooxml::LN_Value_ST_Theme_majorHAnsi: + case NS_ooxml::LN_Value_ST_Theme_minorAscii: + case NS_ooxml::LN_Value_ST_Theme_minorHAnsi: + { + std::map<sal_uInt32, OUString>::const_iterator Iter = tmpThemeFontMap.find(NS_ooxml::LN_CT_FontCollection_latin); + if (Iter != tmpThemeFontMap.end()) + return Iter->second; + return OUString(); + } + case NS_ooxml::LN_Value_ST_Theme_majorBidi: + case NS_ooxml::LN_Value_ST_Theme_minorBidi: + { + std::map<sal_uInt32, OUString>::const_iterator Iter = tmpThemeFontMap.find(NS_ooxml::LN_CT_FontCollection_cs); + if (Iter != tmpThemeFontMap.end()) + return Iter->second; + return OUString(); + } + case NS_ooxml::LN_Value_ST_Theme_majorEastAsia: + case NS_ooxml::LN_Value_ST_Theme_minorEastAsia: + { + std::map<sal_uInt32, OUString>::const_iterator Iter = tmpThemeFontMap.find(NS_ooxml::LN_CT_FontCollection_ea); + if (Iter != tmpThemeFontMap.end()) + return Iter->second; + return OUString(); + } + default: + return OUString(); + } +} + +void ThemeTable::setThemeFontLangProperties(const uno::Sequence<beans::PropertyValue>& aPropSeq) +{ + for (const auto& rProp : aPropSeq) + { + OUString sLocaleName; + rProp.Value >>= sLocaleName; + if (rProp.Name == "eastAsia") + m_pImpl->m_themeFontLangEastAsia = fromLocaleToScriptTag(sLocaleName); + if (rProp.Name == "bidi") + m_pImpl->m_themeFontLangBidi = fromLocaleToScriptTag(sLocaleName); + + } +} + +OUString ThemeTable::fromLocaleToScriptTag(const OUString& sLocale) +{ + return fromLCIDToScriptTag(LanguageTag::convertToLanguageType(sLocale)); +} + +OUString ThemeTable::fromLCIDToScriptTag(LanguageType lang) +{ + // conversion list from: + // http://blogs.msdn.com/b/officeinteroperability/archive/2013/04/22/office-open-xml-themes-schemes-and-fonts.aspx + switch (static_cast<sal_uInt16>(lang)) + { + case 0x429 : // lidFarsi + case 0x401 : // lidArabic + case 0x801 : // lidIraq + case 0xc01 : // lidEgyptian + case 0x1001 : // lidLibya + case 0x1401 : // lidAlgerian + case 0x1801 : // lidMorocco + case 0x1c01 : // lidTunisia + case 0x2001 : // lidOman + case 0x2401 : // lidYemen + case 0x2801 : // lidSyria + case 0x2c01 : // lidJordan + case 0x3001 : // lidLebanon + case 0x3401 : // lidKuwait + case 0x3801 : // lidUAE + case 0x3c01 : // lidBahrain + case 0x4001 : // lidQatar + case 0x420 : // lidUrdu + case 0x846 : // lidPunjabiPakistan + case 0x859 : // lidSindhiPakistan + case 0x45f : // lidTamazight + case 0x460 : // lidKashmiri + case 0x463 : // lidPashto + case 0x48c : // lidDari + return "Arab"; + case 0x42b : // lidArmenian + return "Armn"; + case 0x445 : // lidBengali + case 0x845 : // lidBengaliBangladesh + case 0x44d : // lidAssamese + case 0x458 : // lidManipuri + return "Beng"; + case 0x45d : // lidInuktitut + return "Cans"; + case 0x45c : // lidCherokee + return "Cher"; + case 0x419 : // lidRussian + case 0x402 : // lidBulgarian + case 0x281a : // lidSerbianCyrillic + case 0x422 : // lidUkranian + case 0x819 : // lidRussianMoldavia + case 0xc1a : // lidSerbianCyrillicSerbMont + case 0x1c1a : // lidSerbianBosniaHerzegovinaCyrillic + case 0x201a : // lidBosnianBosniaHerzegovinaCyrillic + case 0x301a : // lidSerbianMontenegroCyrillic + case 0x423 : // lidByelorussian + case 0x428 : // lidTajik + case 0x82c : // lidAzeriCyrillic + case 0x42f : // lidMacedonian + case 0x43f : // lidKazakh + case 0x440 : // lidKyrgyz + case 0x843 : // lidUzbekCyrillic + case 0x444 : // lidTatar + case 0x450 : // lidMongolian + case 0x46d : // lidBashkir + case 0x485 : // lidSakha + return "Cyrl"; + case 0x439 : // lidHindi + case 0x44e : // lidMarathi + case 0x44f : // lidSanskrit + case 0x457 : // lidKonkani + case 0x459 : // lidSindhi + case 0x860 : // lidKashmiriIndia + case 0x461 : // lidNepali + case 0x861 : // lidNepaliIndia + return "Deva"; + case 0x45e : // lidAmharic + case 0x473 : // lidTigrignaEthiopic + case 0x873 : // lidTigrignaEritrea + return "Ethi"; + case 0x437 : // lidGeorgian + return "Geor"; + case 0x408 : // lidGreek + return "Grek"; + case 0x447 : // lidGujarati + return "Gujr"; + case 0x446 : // lidPunjabi + return "Guru"; + case 0x412 : // lidKoreanExtWansung + return "Hang"; + case 0x804 : // lidChineseSimp + case 0x1004 : // lidSingapore + return "Hans"; + case 0x404 : // lidChineseTrad + case 0xc04 : // lidHongkong + case 0x1404 : // lidMacau + return "Hant"; + case 0x40d : // lidHebrew + case 0x43d : // lidYiddish + return "Hebr"; + case 0x411 : // lidJapanese + return "Jpan"; + case 0x453 : // lidKhmer + return "Khmr"; + case 0x44b : // lidKannada + return "Knda"; + case 0x454 : // lidLao + return "Laoo"; + case 0x409 : // lidAmerican + case 0xc09 : // lidAustralian + case 0x809 : // lidBritish + case 0x1009 : // lidEnglishCanadian + case 0x403 : // lidCatalan + case 0x406 : // lidDanish + case 0x413 : // lidDutch + case 0x813 : // lidDutchBelgian + case 0x479 : // lidPapiamentu + case 0x40b : // lidFinnish + case 0x40c : // lidFrench + case 0xc0c : // lidFrenchCanadian + case 0x407 : // lidGerman + case 0x807 : // lidSwissGerman + case 0xc07 : // lidAustrianGerman + case 0x1007 : // lidGermanLuxembourg + case 0x1407 : // lidGermanLiechtenstein + case 0x410 : // lidItalian + case 0x414 : // lidNorskBokmal + case 0x814 : // lidNorskNynorsk + case 0x416 : // lidPortBrazil + case 0x816 : // lidPortIberian + case 0x40a : // lidSpanish + case 0x41d : // lidSwedish + case 0x405 : // lidCzech + case 0x40e : // lidHungarian + case 0x415 : // lidPolish + case 0x41f : // lidTurkish + case 0x42d : // lidBasque + case 0x424 : // lidSlovenian + case 0x426 : // lidLatvian + case 0x427 : // lidLithuanian + case 0x418 : // lidRomanian + case 0x818 : // lidRomanianMoldavia + case 0x241a : // lidSerbianLatin + case 0x41a : // lidCroatian, lidCroat + case 0x491 : // lidGaelicScots + case 0x83c : // lidGaelicIrish + case 0x430 : // lidSutu + case 0x431 : // lidTsonga + case 0x432 : // lidTswana + case 0x433 : // lidVenda + case 0x434 : // lidXhosa + case 0x435 : // lidZulu + case 0x436 : // lidAfrikaans + case 0x425 : // lidEstonian + case 0x456 : // lidGalician + case 0x41b : // lidSlovak + case 0x1409 : // lidEnglishNewZealand + case 0x1809 : // lidEnglishIreland + case 0x1c09 : // lidEnglishSouthAfrica + case 0x2009 : // lidEnglishJamaica + case 0x2409 : // lidEnglishCaribbean + case 0x2809 : // lidEnglishBelize + case 0x2c09 : // lidEnglishTrinidad + case 0x3009 : // lidEnglishZimbabwe + case 0x3409 : // lidEnglishPhilippines + case 0x3809 : // lidEnglishIndonesia + case 0x3c09 : // lidEnglishHongKong + case 0x4009 : // lidEnglishIndia + case 0x4409 : // lidEnglishMalaysia + case 0x4809 : // lidEnglishSingapore + case 0x80a : // lidSpanishMexican, lidMexican + case 0xc0a : // lidSpanishModern + case 0x100a : // lidGuatemala + case 0x140a : // lidCostaRica + case 0x180a : // lidPanama + case 0x1c0a : // lidDominicanRepublic + case 0x200a : // lidSpanishSA, lidVenezuela + case 0x240a : // lidColombia + case 0x280a : // lidPeru + case 0x2c0a : // lidArgentina + case 0x300a : // lidEcuador + case 0x340a : // lidChile + case 0x380a : // lidUruguay + case 0x3c0a : // lidParguay + case 0x400a : // lidBolivia + case 0x440a : // lidElSalvador + case 0x480a : // lidHonduras + case 0x4c0a : // lidNicaragua + case 0x500a : // lidPuertoRico + case 0x540a : // lidSpanishUS + case 0x80c : // lidFrenchBelgian + case 0x100c : // lidFrenchSwiss + case 0x140c : // lidFrenchLuxembourg + case 0x180c : // lidFrenchMonaco + case 0x1c0c : // lidFrenchWestIndies + case 0x200c : // lidFrenchReunion + case 0x240c : // lidFrenchCongoDRC, lidFrenchZaire + case 0x280c : // lidFrenchSenegal + case 0x2c0c : // lidFrenchCameroon + case 0x300c : // lidFrenchCotedIvoire + case 0x340c : // lidFrenchMali + case 0x3c0c : // lidFrenchHaiti + case 0x380c : // lidFrenchMorocco + case 0x40f : // lidIcelandic + case 0x810 : // lidItalianSwiss + case 0x417 : // lidRhaetoRomanic, lidRomanic + case 0x81a : // lidSerbianLatinSerbMont, lidCroatSerbo + case 0x101a : // lidBosniaHerzegovina + case 0x141a : // lidBosnianBosniaHerzegovinaLatin + case 0x181a : // lidSerbianBosniaHerzegovinaLatin + case 0x2c1a : // lidSerbianMontenegroLatin + case 0x41c : // lidAlbanian + case 0x81d : // lidSwedishFinland + case 0x421 : // lidBahasa, lidIndonesian + case 0x42c : // lidAzeriLatin + case 0x42e : // lidSorbian + case 0x82e : // lidLowerSorbian + case 0x438 : // lidFaeroese + case 0x43a : // lidMaltese + case 0x43b : // lidSamiLappish + case 0x83b : // lidNorthSamiSwe + case 0xc3b : // lidNorthernSamiFi + case 0x103b : // lidLuleSamiNor + case 0x143b : // lidLuleSamiSwe + case 0x183b : // lidSouthSamiNor + case 0x1c3b : // lidSouthSamiSwe + case 0x203b : // lidSkoltSami + case 0x243b : // lidInariSami + case 0x43e : // lidMalaysian + case 0x83e : // lidMalayBrunei + case 0x441 : // lidSwahili + case 0x442 : // lidTurkmen + case 0x443 : // lidUzbekLatin + case 0x452 : // lidWelsh + case 0x85d : // lidInuktitutLatin + case 0x85f : // lidTamazightLatin + case 0x462 : // lidFrisian + case 0x464 : // lidFilipino + case 0x466 : // lidEdo + case 0x467 : // lidFulfulde + case 0x468 : // lidHausa + case 0x469 : // lidIbibio + case 0x46a : // lidYoruba + case 0x46b : // lidQuechuaBol + case 0x86b : // lidQuechuaEcu + case 0xc6b : // lidQuechuaPe + case 0x46c : // lidSesothoSaLeboa + case 0x46e : // lidLuxembourgish + case 0x46f : // lidGreenlandic + case 0x470 : // lidIgbo + case 0x471 : // lidKanuri + case 0x472 : // lidOromo + case 0x474 : // lidGuarani + case 0x475 : // lidHawaiian + case 0x476 : // lidLatin + case 0x477 : // lidSomali + case 0x47a : // lidMapudungun + case 0x47c : // lidMohawk + case 0x47e : // lidBreton + case 0x481 : // lidMaori + case 0x482 : // lidOccitan + case 0x483 : // lidCorsican + case 0x484 : // lidAlsatian + case 0x486 : // lidKiche + case 0x487 : // lidKinyarwanda + case 0x488 : // lidWolof + return "Latn"; + case 0x44c : // lidMalayalam + return "Mlym"; + case 0x850 : // lidMongolianMongo + return "Mong"; + case 0x455 : // lidBurmese + return "Mymr"; + case 0x448 : // lidOriya + return "Orya"; + case 0x45b : // lidSinhalese + return "Sinh"; + case 0x45a : // lidSyriac + return "Syrc"; + case 0x449 : // lidTamil + return "Taml"; + case 0x44a : // lidTelugu + return "Telu"; + case 0x465 : // lidMaldivian + return "Thaa"; + case 0x41e : // lidThai + return "Thai"; + case 0x451 : // lidTibetan + case 0x851 : // lidBhutanese + return "Tibt"; + case 0x480 : // lidUighur + return "Uigh"; + case 0x42a : // lidVietnamese + return "Viet"; + case 0x478 : // lidYi + return "Yiii"; + default: + return OUString(); + } +} + +} //namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/ThemeTable.hxx b/writerfilter/source/dmapper/ThemeTable.hxx new file mode 100644 index 000000000..164f083c9 --- /dev/null +++ b/writerfilter/source/dmapper/ThemeTable.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> +#include <i18nlangtag/lang.h> +#include <memory> + +namespace writerfilter::dmapper +{ +struct ThemeTable_Impl; + +class ThemeTable : public LoggedProperties, public LoggedTable +{ + std::unique_ptr<ThemeTable_Impl> m_pImpl; + +public: + ThemeTable(); + virtual ~ThemeTable() override; + + OUString getFontNameForTheme(const Id id) const; + static OUString getStringForTheme(const Id id); + void setThemeFontLangProperties(const css::uno::Sequence<css::beans::PropertyValue>& aPropSeq); + +private: + // Properties + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + + // Table + virtual void lcl_entry(writerfilter::Reference<Properties>::Pointer_t ref) override; + + // Helper methods + static OUString fromLocaleToScriptTag(const OUString& sLocale); + static OUString fromLCIDToScriptTag(LanguageType lang); +}; +typedef tools::SvRef<ThemeTable> ThemeTablePtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TrackChangesHandler.cxx b/writerfilter/source/dmapper/TrackChangesHandler.cxx new file mode 100644 index 000000000..212f88261 --- /dev/null +++ b/writerfilter/source/dmapper/TrackChangesHandler.cxx @@ -0,0 +1,95 @@ +/* -*- 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/. + */ +#include "TrackChangesHandler.hxx" +#include "PropertyMap.hxx" +#include "ConversionHelper.hxx" +#include <ooxml/resourceids.hxx> +#include <oox/token/tokens.hxx> +#include <osl/diagnose.h> + +namespace writerfilter::dmapper { + +using namespace ::com::sun::star; +using namespace oox; + + +TrackChangesHandler::TrackChangesHandler( sal_Int32 nToken ) : + LoggedProperties("TrackChangesHandler"), + m_pRedlineParams(new RedlineParams) +{ + m_pRedlineParams->m_nToken = nToken; +} + + +TrackChangesHandler::~TrackChangesHandler() +{ +} + + +void TrackChangesHandler::lcl_attribute(Id rName, Value & rVal) +{ + OUString sStringValue = rVal.getString(); + switch( rName ) + { + case NS_ooxml::LN_CT_TrackChange_author: + { + m_pRedlineParams->m_sAuthor = sStringValue; + } + break; + case NS_ooxml::LN_CT_TrackChange_date: + { + m_pRedlineParams->m_sDate = sStringValue; + } + break; + case NS_ooxml::LN_CT_Markup_id: + break; + default: + OSL_FAIL( "unknown attribute"); + } +} + +uno::Sequence<beans::PropertyValue> TrackChangesHandler::getRedlineProperties() const +{ + uno::Sequence< beans::PropertyValue > aRedlineProperties(3); + beans::PropertyValue* pRedlineProperties = aRedlineProperties.getArray(); + + OUString sType; + switch ( m_pRedlineParams->m_nToken & 0xffff ) + { + case XML_tableRowInsert: + sType = getPropertyName( PROP_TABLE_ROW_INSERT ); + break; + case XML_tableRowDelete: + sType = getPropertyName( PROP_TABLE_ROW_DELETE ); + break; + case XML_tableCellInsert: + sType = getPropertyName( PROP_TABLE_CELL_INSERT ); + break; + case XML_tableCellDelete: + sType = getPropertyName( PROP_TABLE_CELL_DELETE ); + break; + } + + pRedlineProperties[0].Name = getPropertyName( PROP_REDLINE_TYPE ); + pRedlineProperties[0].Value <<= sType; + pRedlineProperties[1].Name = getPropertyName( PROP_REDLINE_AUTHOR ); + pRedlineProperties[1].Value <<= m_pRedlineParams->m_sAuthor; + pRedlineProperties[2].Name = getPropertyName( PROP_REDLINE_DATE_TIME ); + pRedlineProperties[2].Value <<= ConversionHelper::ConvertDateStringToDateTime( m_pRedlineParams->m_sDate ); + //pRedlineProperties[3].Name = getPropertyName( PROP_REDLINE_REVERT_PROPERTIES ); + //pRedlineProperties[3].Value <<= pRedline->m_aRevertProperties; + + return aRedlineProperties; +} + +void TrackChangesHandler::lcl_sprm(Sprm &) {} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/TrackChangesHandler.hxx b/writerfilter/source/dmapper/TrackChangesHandler.hxx new file mode 100644 index 000000000..b3417ccce --- /dev/null +++ b/writerfilter/source/dmapper/TrackChangesHandler.hxx @@ -0,0 +1,40 @@ +/* -*- 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/. + */ +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> +#include <dmapper/PropertyMap.hxx> + +namespace writerfilter::dmapper +{ +/** Handler for sprms that contain 'track changes' attributes + - Author + - Date + - ID + (This class is based on work done in 'MeasureHandler') + */ +class TrackChangesHandler : public LoggedProperties +{ + RedlineParamsPtr m_pRedlineParams; + + // Properties + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + +public: + explicit TrackChangesHandler(sal_Int32 nToken); + virtual ~TrackChangesHandler() override; + + /// Compute the UNO properties for the track changes object based on the received tokens. + css::uno::Sequence<css::beans::PropertyValue> getRedlineProperties() const; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/WrapPolygonHandler.cxx b/writerfilter/source/dmapper/WrapPolygonHandler.cxx new file mode 100644 index 000000000..319fae5c4 --- /dev/null +++ b/writerfilter/source/dmapper/WrapPolygonHandler.cxx @@ -0,0 +1,214 @@ +/* -*- 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/drawing/PointSequence.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <comphelper/sequence.hxx> +#include <tools/UnitConversion.hxx> + +#include <ooxml/resourceids.hxx> + +#include "WrapPolygonHandler.hxx" +#include "util.hxx" + +#include <sal/log.hxx> + +namespace writerfilter { + +using namespace com::sun::star; + +namespace dmapper { + +WrapPolygon::WrapPolygon() +{ +} + +WrapPolygon::~WrapPolygon() +{ +} + +void WrapPolygon::addPoint(const awt::Point & rPoint) +{ + mPoints.push_back(rPoint); +} + +WrapPolygon::Points_t::const_iterator WrapPolygon::begin() const +{ + return mPoints.begin(); +} + +WrapPolygon::Points_t::const_iterator WrapPolygon::end() const +{ + return mPoints.end(); +} + +WrapPolygon::Pointer_t WrapPolygon::move(const awt::Point & rPoint) const +{ + WrapPolygon::Pointer_t pResult(new WrapPolygon); + + Points_t::const_iterator aIt = begin(); + Points_t::const_iterator aItEnd = end(); + + while (aIt != aItEnd) + { + awt::Point aPoint(aIt->X + rPoint.X, aIt->Y + rPoint.Y); + pResult->addPoint(aPoint); + ++aIt; + } + + return pResult; +} + +WrapPolygon::Pointer_t WrapPolygon::scale(const Fraction & rFractionX, const Fraction & rFractionY) const +{ + WrapPolygon::Pointer_t pResult(new WrapPolygon); + + Points_t::const_iterator aIt = begin(); + Points_t::const_iterator aItEnd = end(); + + while (aIt != aItEnd) + { + awt::Point aPoint((Fraction(tools::Long(aIt->X)) * rFractionX).operator long(), (Fraction(tools::Long(aIt->Y)) * rFractionY).operator long()); + pResult->addPoint(aPoint); + ++aIt; + } + + return pResult; +} + +WrapPolygon::Pointer_t WrapPolygon::correctWordWrapPolygon(const awt::Size & rSrcSize) const +{ + WrapPolygon::Pointer_t pResult; + + const tools::Long nWrap100Percent = 21600; + + Fraction aMove(nWrap100Percent, rSrcSize.Width); + aMove = aMove * Fraction(convertTwipToMm100(15), 1); + awt::Point aMovePoint(aMove.operator long(), 0); + pResult = move(aMovePoint); + + Fraction aScaleX = nWrap100Percent / (nWrap100Percent + aMove); + Fraction aScaleY = nWrap100Percent / (nWrap100Percent - aMove); + pResult = pResult->scale(aScaleX, aScaleY); + + Fraction aScaleSrcX(rSrcSize.Width, nWrap100Percent); + Fraction aScaleSrcY(rSrcSize.Height, nWrap100Percent); + pResult = pResult->scale(aScaleSrcX, aScaleSrcY); + + return pResult; +} + +WrapPolygon::Pointer_t WrapPolygon::correctWordWrapPolygonPixel(const awt::Size & rSrcSize) const +{ + WrapPolygon::Pointer_t pResult; + + /* + * https://msdn.microsoft.com/en-us/library/ee342530.aspx + * + * Image wrapping polygons in Microsoft Word use a fixed coordinate space + * that is 21600 units x 21600 units. Coordinate (0,0) is the upper left + * corner of the image and coordinate (21600,21600) is the lower right + * corner of the image. Microsoft Word scales the size of the wrapping + * polygon units to fit the size of the image. The 21600 value is a legacy + * artifact from the drawing layer of early versions of Microsoft Office. + */ + const tools::Long nWrap100Percent = 21600; + + Fraction aScaleX(rSrcSize.Width, nWrap100Percent); + Fraction aScaleY(rSrcSize.Height, nWrap100Percent); + pResult = scale(aScaleX, aScaleY); + + return pResult; +} + +WrapPolygon::Pointer_t WrapPolygon::correctCrop(const awt::Size& rGraphicSize, + const text::GraphicCrop& rGraphicCrop) const +{ + WrapPolygon::Pointer_t pResult; + + Fraction aScaleX(rGraphicSize.Width - rGraphicCrop.Left - rGraphicCrop.Right, + rGraphicSize.Width); + Fraction aScaleY(rGraphicSize.Height - rGraphicCrop.Top - rGraphicCrop.Bottom, + rGraphicSize.Height); + pResult = scale(aScaleX, aScaleY); + + awt::Point aMove(rGraphicCrop.Left, rGraphicCrop.Top); + pResult = pResult->move(aMove); + + return pResult; +} + +drawing::PointSequenceSequence WrapPolygon::getPointSequenceSequence() const +{ + return { comphelper::containerToSequence(mPoints) }; +} + +WrapPolygonHandler::WrapPolygonHandler() + : LoggedProperties("WrapPolygonHandler") + , mpPolygon(new WrapPolygon) + , mnX(0) + , mnY(0) +{ +} + +WrapPolygonHandler::~WrapPolygonHandler() +{ +} + +void WrapPolygonHandler::lcl_attribute(Id Name, Value & val) +{ + sal_Int32 nIntValue = val.getInt(); + + switch(Name) + { + case NS_ooxml::LN_CT_Point2D_x: + mnX = nIntValue; + break; + case NS_ooxml::LN_CT_Point2D_y: + mnY = nIntValue; + break; + default: + SAL_WARN("writerfilter", "WrapPolygonHandler::lcl_attribute: unhandled token: " << Name); + break; + } +} + +void WrapPolygonHandler::lcl_sprm(Sprm & _sprm) +{ + switch (_sprm.getId()) + { + case NS_ooxml::LN_CT_WrapPath_lineTo: + case NS_ooxml::LN_CT_WrapPath_start: + { + resolveSprmProps(*this, _sprm); + + awt::Point aPoint(mnX, mnY); + mpPolygon->addPoint(aPoint); + } + break; + default: + SAL_WARN("writerfilter", "WrapPolygonHandler::lcl_sprm: unhandled token: " << _sprm.getId()); + break; + } +} + + +}} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/WrapPolygonHandler.hxx b/writerfilter/source/dmapper/WrapPolygonHandler.hxx new file mode 100644 index 000000000..8d3e1a3d8 --- /dev/null +++ b/writerfilter/source/dmapper/WrapPolygonHandler.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/drawing/PointSequenceSequence.hpp> +#include "LoggedResources.hxx" +#include <tools/fract.hxx> +#include <vector> + +namespace com::sun::star::text +{ +struct GraphicCrop; +} + +namespace writerfilter::dmapper +{ +/// Handles <wp:wrapPolygon> from DOCX and the pWrapPolygonVertices shape property from RTF. +class WrapPolygon final : public virtual SvRefBase +{ +public: + typedef std::vector<css::awt::Point> Points_t; + typedef ::tools::SvRef<WrapPolygon> Pointer_t; + +private: + Points_t mPoints; + +public: + WrapPolygon(); + ~WrapPolygon() override; + + void addPoint(const css::awt::Point& rPoint); + + Points_t::const_iterator begin() const; + Points_t::const_iterator end() const; + + WrapPolygon::Pointer_t move(const css::awt::Point& rMove) const; + WrapPolygon::Pointer_t scale(const Fraction& rFractionX, const Fraction& rFractionY) const; + WrapPolygon::Pointer_t correctWordWrapPolygon(const css::awt::Size& rSrcSize) const; + WrapPolygon::Pointer_t correctWordWrapPolygonPixel(const css::awt::Size& rSrcSize) const; + WrapPolygon::Pointer_t correctCrop(const css::awt::Size& rGraphicSize, + const css::text::GraphicCrop& rGraphicCrop) const; + css::drawing::PointSequenceSequence getPointSequenceSequence() const; +}; + +class WrapPolygonHandler : public LoggedProperties +{ +public: + WrapPolygonHandler(); + virtual ~WrapPolygonHandler() override; + + const WrapPolygon::Pointer_t& getPolygon() const { return mpPolygon; } + +private: + WrapPolygon::Pointer_t mpPolygon; + + sal_Int32 mnX; + sal_Int32 mnY; + + // Properties + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/WriteProtection.cxx b/writerfilter/source/dmapper/WriteProtection.cxx new file mode 100644 index 000000000..c300ab09e --- /dev/null +++ b/writerfilter/source/dmapper/WriteProtection.cxx @@ -0,0 +1,140 @@ +/* -*- 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 "WriteProtection.hxx" +#include "TagLogger.hxx" + +#include <comphelper/propertyvalue.hxx> +#include <ooxml/resourceids.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper +{ +WriteProtection::WriteProtection() + : LoggedProperties("WriteProtection") + , m_nCryptProviderType(NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES) + , m_CryptSpinCount(0) + , m_bRecommended(false) +{ +} + +WriteProtection::~WriteProtection() {} + +void WriteProtection::lcl_attribute(Id nName, Value& val) +{ + int nIntValue = val.getInt(); + OUString sStringValue = val.getString(); + + switch (nName) + { + case NS_ooxml::LN_AG_Password_cryptProviderType: // 92025 + m_nCryptProviderType = nIntValue; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmClass: // 92026 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgClass_hash) // 92023 + m_sCryptAlgorithmClass = "hash"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmType: // 92027 + if (nIntValue == NS_ooxml::LN_Value_doc_ST_AlgType_typeAny) // 92024 + m_sCryptAlgorithmType = "typeAny"; + break; + case NS_ooxml::LN_AG_Password_cryptAlgorithmSid: // 92028 + { + sal_Int32 nCryptAlgorithmSid = sStringValue.toInt32(); + switch (nCryptAlgorithmSid) + { + case 1: + m_sAlgorithmName = "MD2"; + break; + case 2: + m_sAlgorithmName = "MD4"; + break; + case 3: + m_sAlgorithmName = "MD5"; + break; + case 4: + m_sAlgorithmName = "SHA-1"; + break; + case 5: + m_sAlgorithmName = "MAC"; + break; + case 6: + m_sAlgorithmName = "RIPEMD"; + break; + case 7: + m_sAlgorithmName = "RIPEMD-160"; + break; + case 9: + m_sAlgorithmName = "HMAC"; + break; + case 12: + m_sAlgorithmName = "SHA-256"; + break; + case 13: + m_sAlgorithmName = "SHA-384"; + break; + case 14: + m_sAlgorithmName = "SHA-512"; + break; + default:; // 8, 10, 11, any other value: Undefined. + } + } + break; + case NS_ooxml::LN_AG_Password_cryptSpinCount: // 92029 + m_CryptSpinCount = nIntValue; + break; + case NS_ooxml::LN_AG_Password_hash: // 92035 + m_sHash = sStringValue; + break; + case NS_ooxml::LN_AG_Password_salt: // 92036 + m_sSalt = sStringValue; + break; + case NS_ooxml::LN_CT_WriteProtection_recommended: + m_bRecommended = nIntValue; + break; + default: + { +#ifdef DBG_UTIL + TagLogger::getInstance().element("unhandled"); +#endif + } + } +} + +void WriteProtection::lcl_sprm(Sprm& /*rSprm*/) {} + +uno::Sequence<beans::PropertyValue> WriteProtection::toSequence() const +{ + uno::Sequence<beans::PropertyValue> aResult; + if (!m_sAlgorithmName.isEmpty() && !m_sSalt.isEmpty() && !m_sHash.isEmpty() + && m_sCryptAlgorithmClass == "hash" && m_sCryptAlgorithmType == "typeAny") + { + aResult = { comphelper::makePropertyValue("algorithm-name", m_sAlgorithmName), + comphelper::makePropertyValue("salt", m_sSalt), + comphelper::makePropertyValue("iteration-count", m_CryptSpinCount), + comphelper::makePropertyValue("hash", m_sHash) }; + } + + return aResult; +} + +} //namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/WriteProtection.hxx b/writerfilter/source/dmapper/WriteProtection.hxx new file mode 100644 index 000000000..21b420b4b --- /dev/null +++ b/writerfilter/source/dmapper/WriteProtection.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include "LoggedResources.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> + +namespace writerfilter::dmapper +{ +class WriteProtection : public LoggedProperties +{ +private: + /** Provider type + * + * Possible values: + * "rsaAES" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaAES + * "rsaFull" - NS_ooxml::LN_Value_doc_ST_CryptProv_rsaFull + */ + sal_Int32 m_nCryptProviderType; + OUString m_sCryptAlgorithmClass; + OUString m_sCryptAlgorithmType; + sal_Int32 m_CryptSpinCount; + OUString m_sAlgorithmName; + OUString m_sHash; + OUString m_sSalt; + bool m_bRecommended; + + virtual void lcl_attribute(Id Name, Value& val) override; + virtual void lcl_sprm(Sprm& sprm) override; + +public: + WriteProtection(); + virtual ~WriteProtection() override; + + css::uno::Sequence<css::beans::PropertyValue> toSequence() const; + + bool getRecommended() const { return m_bRecommended; } +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/domainmapperfactory.cxx b/writerfilter/source/dmapper/domainmapperfactory.cxx new file mode 100644 index 000000000..eab017c57 --- /dev/null +++ b/writerfilter/source/dmapper/domainmapperfactory.cxx @@ -0,0 +1,39 @@ +/* -*- 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/. + */ + +#include "DomainMapper.hxx" +#include "TagLogger.hxx" +#include <unotools/mediadescriptor.hxx> + +namespace writerfilter::dmapper +{ +Stream::Pointer_t +DomainMapperFactory::createMapper(css::uno::Reference<css::uno::XComponentContext> const& xContext, + css::uno::Reference<css::io::XInputStream> const& xInputStream, + css::uno::Reference<css::lang::XComponent> const& xModel, + bool bRepairStorage, SourceDocumentType eDocumentType, + utl::MediaDescriptor const& rMediaDesc) +{ +#ifdef DBG_UTIL + OUString sURL + = rMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_URL, OUString()); + ::std::string sURLc = OUStringToOString(sURL, RTL_TEXTENCODING_ASCII_US).getStr(); + + if (getenv("SW_DEBUG_WRITERFILTER")) + TagLogger::getInstance().setFileName(sURLc); + TagLogger::getInstance().startDocument(); +#endif + + return { new DomainMapper(xContext, xInputStream, xModel, bRepairStorage, eDocumentType, + rMediaDesc) }; +} + +} // namespace writerfilter::dmapper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/util.cxx b/writerfilter/source/dmapper/util.cxx new file mode 100644 index 000000000..d5b23a3d4 --- /dev/null +++ b/writerfilter/source/dmapper/util.cxx @@ -0,0 +1,70 @@ +/* -*- 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 <string> +#include "util.hxx" + +namespace writerfilter::dmapper +{ +using namespace com::sun::star; + +std::string XTextRangeToString(uno::Reference<text::XTextRange> const& textRange) +{ + std::string result; + +#ifdef DBG_UTIL + if (textRange) + { + OUString aOUStr; + + try + { + aOUStr = textRange->getString(); + } + catch (const uno::Exception& rException) + { + result += "(exception: "; + result += rException.Message.toUtf8().getStr(); + result += ")"; + } + + OString aOStr(aOUStr.getStr(), aOUStr.getLength(), RTL_TEXTENCODING_ASCII_US); + + result = aOStr.getStr(); + } + else + { + result = "(nil)"; + } +#else + (void)textRange; +#endif + + return result; +} + +void resolveSprmProps(Properties& rHandler, Sprm& rSprm) +{ + writerfilter::Reference<Properties>::Pointer_t pProperties = rSprm.getProps(); + if (pProperties) + pProperties->resolve(rHandler); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/util.hxx b/writerfilter/source/dmapper/util.hxx new file mode 100644 index 000000000..9d172331a --- /dev/null +++ b/writerfilter/source/dmapper/util.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/text/XTextRange.hpp> +#include <string> +#include <dmapper/resourcemodel.hxx> + +namespace writerfilter::dmapper +{ +std::string XTextRangeToString(css::uno::Reference<css::text::XTextRange> const& textRange); +void resolveSprmProps(Properties& rHandler, Sprm& rSprm); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |